Repository navigation
fix(metadata-protocol): the per-item rollback restores the row the read serves, so a .custom pair's override rolls back beside a base-bound row (#29848) - #30268
Conversation
…tom pair's row beside a base-bound row Claude-Session: https://claude.ai/code/session_01Utp6CXdBvpJrhnHyW825Hb Co-authored-by: Claude <noreply@anthropic.com>
…he by-name read serves A .custom pair's declared override beside a row bound to its read-only base was addressed through a package-agnostic binding read, so its rollback restored (and was refused on) the base row. The door now derives the package from the served row (findServedOverlayRow at packagelessReadAddress, the declared override first) and hands it to restoreVersion. Claude-Session: https://claude.ai/code/session_01Utp6CXdBvpJrhnHyW825Hb Co-authored-by: Claude <noreply@anthropic.com>
… row; changeset Claude-Session: https://claude.ai/code/session_01Utp6CXdBvpJrhnHyW825Hb Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ba14058a85cd740e2c3c08497fe54fcbffde61ad && git checkout ba14058a85cd740e2c3c08497fe54fcbffde61ad
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a abd5d69f761a4915b71a13f4c0c06107d11280ce && git checkout -B drift-repro 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a && git merge --no-ff abd5d69f761a4915b71a13f4c0c06107d11280ce
node scripts/docs-audit/affected-docs.mjs --json 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a
|
|
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb 席位核对 · ACCEPT · head
|
| Item | Reading |
|---|---|
| Shape | Draft, base main, relay pr_create. Body: attribution, signature, ## 维护者速读, Fixes #29848; Clause-②: / Metadata-protocol: at line start. Report 6649552076 (status done). |
| Door choice (seat's order) | Package derived from the served row (findServedOverlayRow at packagelessReadAddress, servedOverride: true, the read getMetaItem uses), with the binding read as fallback when nothing is served; no ?package=. Measured: in the two-row state the served row names the pair's row uniquely, so needs_decision did not trigger. restoreVersion receives that package (#29797's parameter). |
| Pins (card) | Red first on 5fa9375e (403 named-base sentence); now the pair row rolls back, the base row is untouched, and both reads serve the restored body. The pair's rollback to an undeclared body is the D2 gate's 403. Once the pair row is deleted, the base rollback is still refused with the close's envelope (status 403, code NOT_OVERRIDABLE). |
| Accepted re-judge | objectql S5 (shadowed: a package-less row plus the pair's declared row). The old pin rolled back the package-less row the read did not serve, which is this card's defect on a package-less row. The rollback now addresses the served row, whose v1 is undeclared, so D2 refuses it and both rows stay as they were. The door's accept set is unchanged, so Clause-②: no stands; the refusal comes from the existing D2 gate on the row the user sees. |
| Gates | Dev: derived 61, 60 green, 1 left to CI (dual-build-cjs-loads); pnpm lint green; metadata-protocol 22357/22357, objectql 10863/10863. CI on abd5d69f running. |
| Declarations | Clause-②: no, citing ADR-0146 D2 and its revert text. Metadata-protocol: none; changeset patch. |
| Docs | Read concepts/metadata-lifecycle.mdx and the /meta rollback mentions (references/api/protocol.mdx, permissions/system-context.mdx): none says which row a by-name rollback addresses, so none is made false. |
| Serial | #30226 is not landed; this PR does not touch its two files (protocol-packaged-base-refusal.ts, protocol-package-bindings.ts). |
| Landing | Not governed (0 of 5 paths), 142 changed lines. Seat lands: pr_ready + automerge_enable. |
Requested by Bill · project thread
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
维护者速读
POST /api/v1/meta/:type/:name/rollback)现在回滚「用户看到的那一行」:定制包(BASE.custom)里声明了覆盖的行,即使旁边还躺着一条关停前写进只读基包的旧行,也能正常回滚,旧行不动。os migrate packageless-overlays跑之前,这一项的历史回滚等于废了。packages/metadata-protocol/src/protocol-package-commits.ts(rollbackMetaItemUnaudited里取包的那一段与restoreVersion的packageId),packages/objectql/src/protocol-packageless-overlay-migration.test.ts(S5 pin 按新寻址重判)。pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.named-base-overlay-write.test.ts -t 29848:三条全绿;定制行回滚成功、基包行原样,删掉定制行后回滚基包行仍是 403NOT_OVERRIDABLE。Fixes #29848
Clause-②: no — the door's accept set does not change (no new parameter, no new value); the package is derived from the row the by-name read serves. ADR-0146 D2: an item of the pair that declares
overrides"replaces the base's item whole for every read that serves the base's name", and its revert ("Restore package original" = delete the overriding item from.custom, so the base's document is served again) is the pair's own row's; neither ever addressed the base row. The 403 refused the pair's own row because the binding read named the base row, a refusal those texts already deny.Metadata-protocol: none
What changes
packages/metadata-protocol/src/protocol-package-commits.ts(rollbackMetaItemUnaudited), two lines of behaviour:findServedOverlayRowatpackagelessReadAddresswithservedOverride: true: the same resolutiongetMetaItemnaming no package uses (ADR-0146 D8 item 2, the pair's declared override first,isServedOverrideRowOfthe one predicate). Before,resolveOverlayPackageBindinganswered the first active row of the name in store order. When no row is served (a row only another package binds under the name), the binding read answers as before.restoreVersionrestores into that row (packageId: rollbackPackageId, the parameter PR fix(metadata-protocol)!: a package commit's plan and revert address the committing package's own row; a deleted row's restore asks the package-less close #29797 added for the commit doors). Before, the restore's active-row read was package-agnostic too, so even with the right binding the parent hash and the write could land on the base row.Every gate after the binding read is unchanged and now reads the served row's package: the package-less close, the named-base close,
restoreWriteFor(the pair's row is a runtime-only write judged by the override write gate, ADR-0146 D2), the registry write-through.protocol.tsgains two docblock lines onrollbackMetaItem. No new parameter, no new error code, no spec change.Tests
packages/metadata-protocol/src/protocol.named-base-overlay-write.test.ts, new top-level describe[#29848]over the file's existing harness:5fa9375ethe rollback to the pair's first version answered403 NOT_OVERRIDABLEwith the named-base sentence ("a rollback of its row bound to the read-only base is closed"), the card's reading. Now: success, the pair row holdsPairFirstwith itsoverridesdeclaration, the base row is untouched, and both the read naming no package and the read naming the base servePairFirst.403 NOT_OVERRIDABLE, not the named-base close's; nothing is written.status403,codeNOT_OVERRIDABLE, the sentence naming the pair); the row is unchanged.packages/objectql/src/protocol-packageless-overlay-migration.test.ts, the shadowed (S5) case re-judged: it pinned "the rollback's row is the first active row of the name in store order", the package-less row, while the read served the pair's declared row beside it. That is this card's defect on a package-less row instead of a base-bound one. The rollback now addresses the served pair row; its version 1 is the package-less row's undeclared body, so the D2 gate refuses it and both rows stay as they were. The save and DELETE halves of the pin are unchanged; the final read now serves the package-less row's current body, since it is no longer rolled back.Ablations
scripts/ablation-replace.mjs(wrap mode), each leg restored with blob == HEAD (35ffb243) andgit status --porcelainempty.rollbackPackageIdback to the binding read)restoreVersionwithoutpackageIdAcceptance notes
shadowed), the per-item rollback used to restore the package-less row; it now restores the pair's row, which is the one every read serves. A rollback of the package-less row itself is not reachable through this door any more; its remedies are unchanged (a DELETE of the pair's row, after which the row ismovableandos migrate packageless-overlaysmoves it).protocol-packaged-base-refusal.ts,protocol-package-bindings.ts); this PR touches neither file. The served-row resolution it reads (findServedOverlayRow,isServedOverrideRowOf) is not in fix(metadata-protocol,app-shell): serve a pair's mapping override, let a pair remove any override it holds, name a working remedy for an excluded type (#30062) #30226's diff.content/docs/concepts/metadata-lifecycle.mdx:171says a write naming no package (a rollback included) onto a packaged item is refused; it speaks of package-less rows, and a rollback of a served pair row already succeeded onmainwhen the pair row was the only one. Left as is.Verification
Head
abd5d69f(base5fa9375e,origin/main771b2db2merged in once, no conflict, no rebase). The merge brought 8 commits (core dispatch loop, objectql lifecycle service, the spec i18n resolver, ui); none touches this diff's files. The affected closure was rebuilt after the merge under the shared lock.pnpm --filter @objectstack/metadata-protocol test(pre-merge head2d469b09): 312 files passed (4 skipped), 22 357 tests passed (26 skipped).typecheck: exit 0 pre- and post-merge (--listFilesincludes the new test file).pnpm --filter @objectstack/objectql test(pre-merge head2d469b09): 548 / 548 files, 10 863 / 10 863 tests.protocol.named-base-overlay-write.test.ts34 / 34; objectqlprotocol-packageless-overlay-migration.test.ts+protocol-publish-rollback.test.ts40 / 40;pnpm --filter @objectstack/spec check:generated: all 14 artifacts up to date.pnpm lint: exit 0 (pre-merge head2d469b09; the merge adds no lintable change of this PR's).5fa9375e: the two-row pin answered the named-base sentence,403 NOT_OVERRIDABLE; the D2-gate pin read that same sentence.Gates: dispatch-gates --commands, each exit code captured before any pipe
61 derived families (head
2d469b09; the list derived atabd5d69fis byte-identical): 60 exit 0, 1 NOT MEASURED.dispatch-gates --ran: exit 0, "61 derived famil(ies) accounted for — 60 run, 1 NOT-MEASURED".pnpm check:dual-build-cjs-loadsexit 3, "Runpnpm buildfirst. This is NOT a pass: nothing was measured" (70 packages outside this diff's closure have nodist/); left to CI.pnpm check:lean-entry-closurefirst exited 3 (nopackages/objectql/dist); afterturbo run build --filter=@objectstack/objectqlit exits 0 ("Admitted set held exactly (15 packages)").node scripts/check-plugin-teardown-shape.mjs --self-testran aftergit fetch origin 621a4876…(the pinned fixture commit): exit 0.abd5d69f:check:type-check-coverage,check:type-check-debt,check:durability-log-level,check:nul-bytes,check:cross-package-test-inputs,check-empty-changeset,check-adr-0087-registration,check-changeset-no-major: all exit 0.Change set: 5 paths, +134 / −8 against
5fa9375e(two source files +33 / −2, two test files +93 / −6, one changeset +8).Human Minimum
packages/specholds no runtime logic. (not touched)pnpm lintpasses.main, one pull request.packages/spectouched: n/a.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01Utp6CXdBvpJrhnHyW825Hb
Generated by Claude Code