Skip to content

docs(tree): add spec for persisted commit metadata - #28047

Draft
Noah Encke (noencke) wants to merge 5 commits into
microsoft:mainfrom
noencke:docs/persisted-commit-metadata
Draft

docs(tree): add spec for persisted commit metadata#28047
Noah Encke (noencke) wants to merge 5 commits into
microsoft:mainfrom
noencke:docs/persisted-commit-metadata

Conversation

@noencke

Copy link
Copy Markdown
Contributor

Description

Adds a design specification for attaching arbitrary, application-defined metadata to a commit,
persisting it in the document, and querying it later.
The metadata shares the lifetime of the commit it is attached to, so it is dropped once that commit
leaves the collaboration window.

The spec covers the transaction API surface, keying by RevisionTag, the op envelope change,
a new summarizable index, and the behavior required across submit, remote receipt, resubmit,
stashed ops, rollback, eviction, and load.

This is documentation only; no product code is changed.
The design is written to avoid introducing a new persisted format version, and the compatibility
section records the consequences of that choice.

Reviewer Guidance

The review process is outlined on this wiki page.

This is a draft for design feedback rather than an implementation proposal.
Two areas would benefit most from scrutiny:

  • The op envelope approach relies on the Message schemas permitting additional properties.
    Existing clients tolerate this, but it is not currently documented as a guarantee, so the spec
    proposes declaring the field explicitly and annotating the schemas so the behavior is preserved.
  • The summarizable is invisible to clients that do not implement it, which means such a client
    silently drops the metadata when it summarizes. The "Compatibility characteristics" section
    spells this out; please confirm the best-effort guarantee is acceptable.

@github-actions github-actions Bot added area: tools area: dds Issues related to distributed data structures area: repo Repo related work area: website area: dds: tree base: main PRs targeted against main branch labels Aug 21, 2026
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Hi! Thank you for opening this PR. Want me to review it?

Based on the diff (391 lines, 1 files), I've queued these reviewers:

  • Correctness — logic errors, race conditions, lifecycle issues
  • Security — vulnerabilities, secret exposure, injection
  • API Compatibility — breaking changes, release tags, type design
  • Performance — algorithmic regressions, memory leaks
  • Testing — coverage gaps, hollow tests

How this works

  • Adjust the reviewer set by ticking/unticking boxes above. Reviewer toggles alone don't trigger anything.

  • Tick Start review below to dispatch the review fleet.

  • After review finishes, tick Start review again to request another run — it auto-resets after each dispatch.

  • This comment updates as new commits land; your reviewer selections are preserved.

  • Start review

Noah Encke (noencke) and others added 4 commits August 24, 2026 09:52
Store the metadata on the commits in the EditManager summary rather than
separately. This gives the metadata the same lifetime as its commit with no
additional garbage collection, keeps the persisted metadata consistent with the
trunk by construction, and lets the metadata page alongside its commits if
summary history is virtualized later. It also removes the need to encode
revision tag keys, since an encoded commit already carries its revision and
session ID.

Introduce EditManagerFormatVersion v7 and MessageFormatVersion v7 to carry the
field, so writing is gated on minVersionForCollab and a document containing
metadata can only be opened by a client that preserves it.

Add guidance to keep the metadata off GraphCommit: rebaseBranch rebuilds
commits as fresh object literals, so extra properties on the commit object do
not survive rebase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2711c804-43fc-4414-9ab8-1d46d07b5f70
A transaction can run on any branch. An application that renders from a fork
and publishes by merging that fork into main annotates its transactions on the
fork, and those commits reach the wire only when the merge submits them, so
submitCommit must find metadata that a different checkout recorded.

Specify that the index lives on SharedTreeCore and is shared by every checkout
of the tree, that keying by RevisionTag is what keeps lookups valid as a commit
moves between branches, and that entries are removed only on rollback or
eviction rather than on branch disposal.

Add a lifecycle section for commits made on a branch and tests covering the
fork-merge path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2711c804-43fc-4414-9ab8-1d46d07b5f70
Metadata describes the commit a transaction produces, so a transaction that
produces no commit has nothing to describe and its metadata is discarded
without error.

Raising instead would convert a handled result into an exception on the
explicit rollback path, where an application rolls a transaction back after
detecting an invalid edit and returns an error to its own caller. It would also
be unactionable, since whether a body will produce a change is not knowable
before running it, and it would penalize exactly the applications whose
"no change means no checkpoint" rule motivates annotating in the first place.

Specify both paths that reach this, note that callers can detect the case by
comparing the branch head across the call, and cover the no-change and
explicit-rollback cases in the tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2711c804-43fc-4414-9ab8-1d46d07b5f70
Put the metadata directly on GraphCommit as a required property rather than in
a revision-keyed index. Required, not optional: mintCommit and rebaseBranch
rebuild commits from their parts instead of spreading them, so an optional
property would be dropped silently, while a required one turns both into
compile errors that have to be answered.

This removes the index entirely. The metadata is reachable wherever a commit
is, is collected with its commit so eviction needs no participation, cannot
drift out of sync with the commit graph, and crosses branches with the commits
a merge carries.

Call out that the rebuild sites must propagate the property rather than write
undefined to satisfy the compiler, since that would reintroduce the loss the
required property prevents.

Expose reads as persistedMetadata on TreeBranchCommitMetadata, so metadata
arrives with history navigation instead of a separate lookup. There is
deliberately no accessor by revision, which would require the index back.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2711c804-43fc-4414-9ab8-1d46d07b5f70
@github-actions

Copy link
Copy Markdown
Contributor

🔗 No broken links found! ✅

Your attention to detail is admirable.

linkcheck output

$ start-server-and-test "npm run serve -- --host 127.0.0.1 --no-open" http://127.0.0.1:3000 check-links
1: starting server using command "npm run serve -- --host 127.0.0.1 --no-open"
and when url "[ 'http://127.0.0.1:3000' ]" is responding with HTTP status code 200
running tests using command "npm run check-links"


> fluid-framework-website@0.0.0 serve
> docusaurus serve --host 127.0.0.1 --no-open

[SUCCESS] Serving "build" directory at: http://127.0.0.1:3000/

> fluid-framework-website@0.0.0 check-links
> linkcheck http://127.0.0.1:3000 --skip-file skipped-urls.txt

Crawling...

Stats:
  338440 links
    2041 destination URLs
    2297 URLs ignored
       0 warnings
       0 errors


@github-actions

Copy link
Copy Markdown
Contributor

Bundle size comparison

Base commit: 7ab015c49deec84833cdfe1fb5e1606b901f6e81
Head commit: e6c85d1e434e7912d4b689c157d2847197c909ff

Notable changes

No bundles changed by ≥ 500 bytes parsed.

Per-bundle deltas

@fluid-example/bundle-size-tests

  • fluidFrameworkAllAlpha.js: parsed 786030 → 786086 (+56), gzip 216075 → 216129 (+54)
  • azureClient.js: parsed 633307 → 633302 (-5), gzip 169726 → 169799 (+73)
  • odspClient.js: parsed 606510 → 606624 (+114), gzip 163247 → 163367 (+120)
  • aqueduct.js: parsed 537183 → 537196 (+13), gzip 144292 → 144339 (+47)
  • fluidFramework.js: parsed 404588 → 404621 (+33), gzip 114923 → 114960 (+37)
  • sharedTree.js: parsed 393967 → 393993 (+26), gzip 112344 → 112373 (+29)
  • containerRuntime.js: parsed 314010 → 313988 (-22), gzip 86157 → 86156 (-1)
  • sharedString.js: parsed 175203 → 175210 (+7), gzip 49656 → 49663 (+7)
  • experimentalSharedTree.js: parsed 161812 → 161812 (0), gzip 46711 → 46711 (0)
  • matrix.js: parsed 159582 → 159589 (+7), gzip 45896 → 45904 (+8)
  • loader.js: parsed 147287 → 147303 (+16), gzip 40026 → 40036 (+10)
  • odspDriver.js: parsed 111483 → 111559 (+76), gzip 34688 → 34764 (+76)
  • directory.js: parsed 65635 → 65642 (+7), gzip 18481 → 18489 (+8)
  • 578.js: parsed 58686 → 58686 (0), gzip 17657 → 17659 (+2)
  • odspPrefetchSnapshot.js: parsed 45884 → 45865 (-19), gzip 15335 → 15351 (+16)
  • map.js: parsed 45786 → 45793 (+7), gzip 14109 → 14116 (+7)
  • 252.js: parsed 44362 → 44362 (0), gzip 13735 → 13735 (0)
  • summarizerDelayLoadedModule.js: parsed 31287 → 31287 (0), gzip 7929 → 7929 (0)
  • socketModule.js: parsed 26992 → 26962 (-30), gzip 8019 → 8053 (+34)
  • createNewModule.js: parsed 8523 → 8517 (-6), gzip 3550 → 3561 (+11)
  • odspPointInTime.js: parsed 5190 → 5184 (-6), gzip 2026 → 2028 (+2)
  • summaryModule.js: parsed 3888 → 3888 (0), gzip 1874 → 1874 (0)
  • connectionState.js: parsed 909 → 909 (0), gzip 500 → 500 (0)
  • sharedTreeAttributes.js: parsed 845 → 852 (+7), gzip 493 → 503 (+10)
  • debugAssert.js: parsed 429 → 429 (0), gzip 299 → 299 (0)
  • FluidFramework-HashFallback.js: parsed 419 → 419 (0), gzip 313 → 313 (0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: dds: tree area: dds Issues related to distributed data structures area: repo Repo related work area: tools area: website base: main PRs targeted against main branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant