Repository navigation
fix(metadata-protocol): a package rollback carries every item a revert step refused, and answers success: false - #27957
Conversation
… step's refused items Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
…t step refused, and answers success: false Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
…llback-carries-item-failures
…es refused items Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
…ound, refuses combinators, and is in the engine-double ledger Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 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 42f73054112a33d05b805fb3602fdeb921d3f0d8 && git checkout 42f73054112a33d05b805fb3602fdeb921d3f0d8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ed0e71db94a73acc02512ff2ee0655106d02e585 233b1f0eb9c66199967072c6c18c20453e6a7fb9 && git checkout -B drift-repro ed0e71db94a73acc02512ff2ee0655106d02e585 && git merge --no-ff 233b1f0eb9c66199967072c6c18c20453e6a7fb9
node scripts/docs-audit/affected-docs.mjs --json ed0e71db94a73acc02512ff2ee0655106d02e585
|
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F 维护者速读(席位终稿)
席位意见: ACCEPT。实现与已声明的契约一致,改动小,且只在一个函数内。 席位核对ACCEPT · head
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Requested by Sales · project thread
Fixes #27868
Clause-②: no
Metadata-protocol: none
Skills-docs: none — the answer's declared shape and its
successcontract do not change, and no skill or doc describes the rollback'sfailed[]population维护者速读
rollbackToPackageCommit现在把每一步revertCommit返回的逐项拒绝(例如非规范存储类型的恢复被拒)写进回滚自己的failed[],有任何一项被拒就答success: false。failed[],逐项拒绝被丢弃,管理员回滚一个包时被告知成功,实际上有的条目原样未动,包处于半回滚状态且毫无提示。rollbackToPackageCommit一个方法内部,答复形状不变(仍是{ commitId, error });回滚继续越过被拒条目的行为不变。出问题直接 revert 本 PR 即可。packages/metadata-protocol/src/protocol.ts(rollbackToPackageCommit循环里新增的for (const item of step.failed)),然后packages/metadata-protocol/src/protocol.rollback-carries-item-failures.test.ts。pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.rollback-carries-item-failures.test.ts,预期 3 个用例全部通过。What changes
rollbackToPackageCommit(POST /packages/:id/rollback) runsrevertCommitonce per newerapplycommit.revertCommitreports an item it refuses in its returned per-itemfailed[]and does not throw; the rollback read only thrown errors, so a package left half rolled back answeredsuccess: truewith an emptyfailed[].Now each returned per-item failure becomes one entry in the rollback's own
failed[]:commitId: the commit the item belongs to;error:Item 'TYPE/NAME' was not reverted:followed by the step's ownfailed[].error, which is already client-facing (the P11 withhold inrevertCommithas run on it).successstaysfailed.length === 0, so any refused item makes it false.Shape. The declared answer,
RollbackToPackageCommitResponseSchema(packages/spec/src/api/package-lifecycle.zod.ts), types eachfailed[]entry as{ commitId, error }, with no item fields. So the item is named insideerror, and no spec edit is made. The step's per-itemcode(for exampleSTORED_TYPE_NOT_CANONICAL) has no field in that shape and is not carried. See Acceptance notes.What stays the same.
revertedCommits. A partly reverted commit therefore appears in both lists.{ commitId, error }entry, and the rollback goes on to the next commit.data.Clause-②: no, and why it is not a narrowing. The answer's shape is unchanged. What changes is
successbecomingfalsefor a request that used to readtrue. The published contract already says this is the right answer:RollbackToPackageCommitResponseSchema.successis described as "True exactly when nothing failed", and the siblingRevertPackageCommitResponseSchemaalready answerssuccess: falsefor the same refused item. The change brings the implementation back to its declared contract. It does not narrow an accept set or a published shape: every request accepted before is still accepted and gets the same shape. Hence plainno, with no(narrowing)arm, no!and no BREAKING note.Tests
New
packages/metadata-protocol/src/protocol.rollback-carries-item-failures.test.ts. It runs through the real protocol and the realSysMetadataRepository, with an engine double that also serves the commit ledger, solistCommitsplans the rollback itself. It is the same harness shape asprotocol.revert-stored-type-canonical.test.ts. The package history is: targetc0;c1creates a view;c2editsview/grid, editsfields/a.b(stored under the non-canonical type, whichrevertCommitrefuses withSTORED_TYPE_NOT_CANONICAL) and creates a view.success: false.failedhas exactly one entry with keyscommitIdanderror: commitc2, an error that namesfields/a.band quotes the step's refusal text.revertedCommitsisc1andc2. Both created views are removed,view/gridis back to V1, andfields/a.bstays at V2.{ success: true, revertedCommits: [c1, c2], failed: [] }.c2is listed but cannot be read. The result isfailed: [{ commitId: 'c2', error: "No commit 'c2'." }], andc1is still reverted.The test imports
./protocol.jsfromsrc, not through a packageexportspath, so nodistrebuild sits between the mutation and the run.Ablation. Through
scripts/ablation-replace.mjsonpackages/metadata-protocol/src/protocol.ts, the anchorfor (const item of step.failed) {is replaced byfor (const item of [] as typeof step.failed) {, which drops the carried failures and gives the pre-fix behaviour. The predicted result was case 1 red and both controls green. That is what happened.Ablation output
Package suite, typecheck, lint
The commit after c47a817 changes only the new test's engine double (it holds
limitand refuses unknown combinators) and the engine-double ledger. The three affected test files and the typecheck were run again on that content.Acceptance notes
RollbackToPackageCommitResponseSchema.failed[]has notype,nameorcode, so a client reads the refused item fromerrortext and loses the step's machine code. The schema'sfaileddescription, "Every commit the rollback could not revert.", now also covers a partly reverted commit's refused items. Giving the rollback entry structuredtype/name/codefields would be apackages/specchange (a widening, soClause-②: yes) and is out of this card's surface. carrier: none.check:engine-double-contractledger.scripts/engine-double-contract.pinned.jsongains the new test's engine double, written bynode scripts/check-engine-double-contract.mjs --writeas the gate prescribes. The new double holds the caller'slimit(check:objectql-double-limit) and refuses any combinator other than$or(check:where-matcher).packages/metadata-protocol/src/protocol.ts. All 27 open PRs were read, including all 721 files of the Version Packages PR. The one in-flight branch in the same package,claude/issue-27829-searchfields-credential-refused(no PR yet), does not touchprotocol.ts.git merge-tree --write-tree HEADagainst it exits 0.Verification
Head
233b1f0e, merged withorigin/main(0a370917).The union was run after the final commit, at
233b1f0e:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 69 families. All 69 were run, each exit code captured before any pipe, and every one exited 0. Reconciliation:dispatch-gates --rananswers "69 derived famil(ies) accounted for — 69 run, 0 NOT-MEASURED (a DERIVED zero — all 69 recorded an exit code and none of them is 3)".At
c47a8170the first union had four reds:check:engine-double-contract(ledger not updated),check:objectql-double-limit(the double was limit-blind),check:where-matcher(the double had no combinator refusal), andcheck:dual-build-cjs-loads/check:lean-entry-closure(exit 3, prerequisite not met:distwas missing). The first three were fixed in233b1f0e. The last two were measured after the build and are green.The CI families are cited from this PR's check-runs once they report.
Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F