Repository navigation
fix(runtime)!: package commit revert and rollback act only on a commit of the package their path names - #29090
Conversation
… of the path's package POST /packages/:id/commits/:commitId/revert and POST /packages/:id/rollback never read :id, so a commit of package A reverted through package B's path answered 200 and wrote a revert commit into A's history. Both doors now pass the path's package to the protocol, which refuses a commit of another package with COMMIT_NOT_FOUND (404) before writing, and answer an unknown package id with the sibling doors' 404. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5nWnKw7n4za79sxnfc
…mmit of the path's package Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5nWnKw7n4za79sxnfc
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 33 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 bcc66ff1e5a3822f76a984c1b2203d37efdb7a9e && git checkout bcc66ff1e5a3822f76a984c1b2203d37efdb7a9e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d1f3ac05038cffc28b9f8bcbe2116afe1b5f1a2e a4647aa192a5f76356d05441074559b0a324387e && git checkout -B drift-repro d1f3ac05038cffc28b9f8bcbe2116afe1b5f1a2e && git merge --no-ff a4647aa192a5f76356d05441074559b0a324387e
node scripts/docs-audit/affected-docs.mjs --json d1f3ac05038cffc28b9f8bcbe2116afe1b5f1a2e
|
|
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt 席位核对ACCEPT (landing waits on a same-head contract review PASS) · The maintainer's words for this seat, verbatim: 「接手所有开发,包括跟进之前未完成的开发落地。」
|
|
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt Contract reviewServed-tier: Inputs: card #28987 (body and all 6 comments, the claim 6570512674 ruling and the os-dev-report 6571600286 included), PR #29090 body and file list, ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
|
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt Landing record · PR #29090
Release: ships Generated by Claude Code |
Requested by Leehom · project thread
Fixes #28987
Clause-②: yes (narrowing)
Metadata-protocol: none
Skills-docs: none — no page under
skills/**orcontent/docssays which package a commit revert or rollback acts on;content/docs/api/metadata-api.mdxonly lists the door names.维护者速读
POST /packages/:id/commits/:commitId/revert和POST /packages/:id/rollback现在只处理路径里那个包自己的提交。提交属于别的包 →404 COMMIT_NOT_FOUND("No commit 'X' in package 'B'."),什么都不写;路径里的包不存在(含已卸载) →404 Package 'X' not found,与兄弟门一致。:id。按包回滚的管理员一旦把提交号串错包,就会悄悄回滚另一个包的元数据(创建的被软删、编辑的被还原),回 200,还往那个包的历史里写一条 revert 提交,即使那个包已卸载。packages/metadata-protocol/src/protocol.ts的readRevertableCommit(新增的包比对);packages/runtime/src/domains/packages.ts两个门把packageId交给协议、未知包 404;packages/qa/dogfood/test/package-commit-revert-path-package.dogfood.test.ts。pnpm --filter @objectstack/dogfood exec vitest run test/package-commit-revert-path-package.dogfood.test.ts(需先建闭包),预期 7/7 通过;在 main 上其中 3 例为红。What changes
@objectstack/metadata-protocol.revertCommitandrollbackToPackageCommittake an optionalpackageId.readRevertableCommit, the one commit read both use, refuses a commit whosepackage_idis not that package with the existingCOMMIT_NOT_FOUND(404), wordedNo commit 'ID' in package 'PKG'., before the legacy-organization check and before anything is written. A caller that sends nopackageIdreads any commit, as before. This optional member is a widening of two published method signatures, beside the door narrowing: henceyes (narrowing).@objectstack/runtime. Both doors read:idand hand it to the protocol aspackageId. An unknown:id(packageIsUnknown, the predicate the draft doors andGET /:id/commitsuse) answers404 Package 'ID' not foundwithout calling the protocol. The check sits inside each door's capability probe, so a partial protocol host still answers 501, and the rollback's400 Body { commitId } is requiredstill comes first.manage_metadata, the walled-catalog refusal and the closed query set keep their order ahead of it.requireWritablePackageon both doors. Measured on main: a read-only package carries commits. An environment overlay draft onshowcase_task.defaultbound tocom.example.showcase(a booted code package), published withpublish-drafts, records anapplycommit in the showcase's history, and reverting it through the showcase's own path answers 200 and removes the overlay. Those are the only commits a read-only package can carry (POST /packages/:id/publishalready refuses read-only packages). The siblingPOST /packages/:id/revertanswers a read-only package's stored rows before its own guard, so the guard there never refuses them. A guard on these doors would refuse the undo of an ADR-0131 D6 customization, a narrowing beyond the card. The seat chose not to add it; the overlay revert is pinned as a control.BREAKING (patch, ruling A #25105: a non-protocol narrowing ships
patch):POST /packages/B/commits/COMMIT_OF_A/revertanswered 200, reverted A's items and recorded a revert commit in A's history. TO:404 COMMIT_NOT_FOUND, nothing written.POST /packages/B/rollback{ commitId: COMMIT_OF_A }answered 200 and reverted A's newer commits. TO:404 COMMIT_NOT_FOUND, nothing written.:idacted on the commit. TO:404 Package 'ID' not found.Tests
package-commit-revert-path-package.dogfood.test.ts(7 cases, showcase boot, REST): cross-package revert 404 and A's history and items unchanged; cross-package rollback 404, both histories and A's items unchanged; unknown package 404 on both doors; member withoutmanage_metadata403 on both; controls: own-path revert (200,revertedCount: 1, revert commit recorded, item removed) and own-path rollback (200, the newer commit reverted); a read-only package's overlay commit: 404 through another package's path with the showcase history unchanged, 200 through its own path removing the overlay.packages-commit-door-path-package.test.ts(6 cases): each door hands the protocol{ commitId, packageId }, relays the protocol'sCOMMIT_NOT_FOUNDas 404, and answers an unknown package 404 without calling the protocol.packages/objectql/src/protocol-commit-history.test.ts(+2 cases):revertCommitandrollbackToPackageCommitwith another package's id rejectCOMMIT_NOT_FOUND/404 with the message, with no delete, no restore and no new commit row; the same call with the commit's own package reverts.Red on main, green on head, ablation
ab085a04(dogfood file,dist/built from main):3 failed | 4 passed (7). Cross-package revert: 200{"revertedCount":1,"reverted":[{"type":"object","name":"cmtpa_second","action":"removed"}],"revertCommitId":"cmt_…"}. Cross-package rollback: 200{"revertedCommits":["cmt_…"],"failed":[]}. Unknown package: 200 (revertedCount: 1). The controls passed on main.Saved customization overlay (env-wide, package=com.example.showcase, state=draft)),publish-drafts200outcome: publishedwith acommitId,GET /packages/com.example.showcase/commitslists thatapplycommit, its revert 200reverted: [{ type: view, name: showcase_task.default, action: removed }].a4647aa1: dogfood file7 passed (7);@objectstack/runtimepnpm test:514 passedfiles,8273 passed | 19 skippedtests; objectqlprotocol-commit-history.test.ts32 passed (32).scripts/ablation-replace.mjsonreadRevertableCommit: the condition becomespackageId === '__ablated_pkg_check__' && …, so it never matches;@objectstack/metadata-protocolrebuilt;ablation-dist-preflightfound the marker in 2 built files): dogfood3 failed | 4 passed (7), exactly the cross-package revert, the cross-package rollback and the overlay case at its cross-package step (expected 404, received 200); the unknown-package case (runtime check) and every own-path control stayed green. objectql2 failed | 30 passed (32), the two new mismatch cases only. Restored: blob == HEAD,git diff HEADempty, rebuilt, preflight--absentclean. A first attempt usedif (false && …)as the mutation; the bundler folded it away, the dist preflight answered "marker ABSENT", and that run's reading was discarded.Acceptance notes
Package 'ID' not found);GET /packages/:id/commitsstill serves a trail that outlived its package.revertCommit/rollbackToPackageCommit(no non-REST caller exists in the repo besides the rollback's own innerrevertCommitsteps, which read commits listed under the target's package) keep reading any commit when they send nopackageId.no (narrowing). This diff also adds an optional member to two publishedObjectStackProtocolImplementationmethod requests, which is the widening the order names, so the line isyes (narrowing).packages/metadata-protocol/src/protocol.ts(fix(metadata-protocol)!: refuse a keyed seed whose externalId names no field of its object #29066, fix(objectql)!: the engine refuses evaluating an internal field for every caller but a system context #29003, fix(metadata-protocol)!: a package-less write onto a packaged view, dashboard, report, translation or email template is refused, naming the .custom pair (ADR-0146 D11) #28850, fix(metadata-protocol): the by-id OCC comparison answers only for a row the caller reads #28731) and none touchpackages/runtime/src/domains/packages.ts; no hunk of theirs is inreadRevertableCommit,revertCommitorrollbackToPackageCommit(fix(metadata-protocol)!: a package-less write onto a packaged view, dashboard, report, translation or email template is refused, naming the .custom pair (ADR-0146 D11) #28850's rollback hunks are inrollbackMetaItem).Verification
Every local reading above is on head
a4647aa1.pnpm typecheckfor@objectstack/metadata-protocol,@objectstack/runtime(withcheck:test-typecheck),@objectstack/objectql(withcheck:test-typecheck) and@objectstack/dogfood: exit 0. Derived gate families (node scripts/pm/dispatch-gates.mjs --commands): 67 derived; at the time of writing 63 had run, 61 with exit 0;check-plugin-teardown-shape --self-testanswered exit 3 on the shallow clone and exit 0 (48 cases) aftergit fetch --depth=1of its fixture commit;check:dual-build-cjs-loadsexit 3, NOT MEASURED (its prerequisite, every package'sdist/, is not met in this checkout);check:type-check-debt,check:undeclared-dep-imports,check:watch-hint-literalandcheck:where-matcherwere still running when the maintainer's pause arrived and are NOT MEASURED here.check-adr-0087-registration --base origin/main: exit 0,not-required (no-migration-prescription). Full dogfood battery (owed by the(narrowing)arm): started on this head under the verify lock, still running at the pause; NOT MEASURED here, the reading goes to the seat. The repo-wide lint,test:repoand the full farm are CI's.🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5nWnKw7n4za79sxnfc