You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
metadata-protocol: a commit revert or package-commit rollback can restore a row bound to a read-only base package that the #28905 close now refuses to write #29390
Source: the contract review of PR #29270 (#28905), record 6577747588, should-fix S1.
#28905 closes the save, publish and rollback doors to a write naming a read-only code package on the five overlay types (403 NOT_OVERRIDABLE, with the pair fix). revertCommit and rollbackToPackageCommit restore rows through repo.restoreVersion (source: 'protocol.revertCommit'), outside the save door. So reverting a commit recorded before the close can restore a row bound to the base. The changeset's "Still open" list does not name this path.
Ask: decide and implement one of two. (a) The revert path applies the same close when the restored row would be bound to a read-only base that the pair fix can take, refusing with the same sentence. (b) The path stays open by design, because a commit recorded before the close keeps its revert, as the dogfood control #29090 states. In that case name it in the docs and in the ADR-0087 entry (see the S3 card). Measure first which commits can carry such a row today.
Files: packages/metadata-protocol/src/protocol.ts (the commit doors) and protocol-packaged-base-refusal.ts. Serial with the S6 card (same refusal module).
Claim: PM loop round 1 · card thread · 2026-11-02T14:10Z
Session: session_01ADHkmQJdgarUjPLNgFBY67 (the seat); the card thread is session_01ADHkmTKKCrTrtA7H6oSj28
Account: os-steve (the seat's linked user; the card's assignee)
Branch: claude/project-thread-gvqm55
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#3
Ruling-ref: the maintainer, verbatim, in the project thread: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」
File surface: packages/metadata-protocol/src/protocol.ts (the revertCommit and rollbackToPackageCommit doors only), packages/metadata-protocol/src/protocol-packaged-base-refusal.ts, protocol.packaged-base-refusal.test.ts and the commit-door tests; .changeset/; docs text if option (b) is ruled. The dev measures and reports needs_decision first; nothing is edited before the seat rules. ⛔ Not protocol-packageless-overlay-migration.ts or the cli ceremony (#29392).
Container & model: card thread cmsg_01FdL1XeUyvVNxLreh3F4Qi9MzJJFGjBCFjRXi73LUWGL9, mode:thread, model: opus (--tier names no path mandate: PM's call, default), effort: xhigh
Clause-②: no (narrowing)
Metadata-protocol: none
Responsibility: the card thread measures, reports the (a)/(b) fork, implements the seat ruling and opens the draft PR; the seat rules, reviews, ACCEPTs and lands. The Clause-② value assumes (a); the seat amends it to no if (b) is ruled
Thread-read: none
Premise: holds (site re-read on origin/main7f277a86 by the seat before dispatch)
Serial constraints cleared: #29270 (#28905) merged 838a802b; #29392 runs in parallel on disjoint files; #29397 runs after this card
Resume-from: none — fresh from origin/main
State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim. Order: project file server-seat3/standard-order.md + server-seat3/29390-delta.md.
{
"issue": 29390,
"status": "needs_decision",
"branch": "claude/project-thread-gvqm55",
"pr": null,
"session": "session_01ADHkmTKKCrTrtA7H6oSj28",
"premise_still_valid": true,
"summary": "Premise holds on origin/main 7f277a86. revertCommit's restore limb (and rollbackToPackageCommit through it) writes into the active row of any package via repo.restoreVersion and asks neither the #28576 nor the #28905 close, while rollbackMetaItem asks both on the same binding read. Measured through the REST doors on a booted showcase: with pre-close apply commits on com.example.showcase (produced through the real save and publish-drafts doors with the close dropped on the running instance only), the per-item rollback of the base-bound row answers 403 NOT_OVERRIDABLE, but POST /packages/com.example.showcase/commits/C3/revert answers 200 success:true restored, rewrites the base-bound row (v6 L3 to v7 L2, no overrides) and the read naming no package and ?package=BASE both serve L2; POST /packages/com.example.showcase/rollback replays every pre-close apply commit (C3 reverted a second time, then C2) to L1. Which commits reach it: only apply commits on the base recorded before the close whose item edited an existing row; creation items take the soft-remove limb (delete, open by design); revert commits carry prevVersion null and never take the restore limb; post-close publishes into the base are refused where the fix can be taken. No assertion pins a restore-limb revert of a base-bound row: the #29090 case at 838a802b^ asserted the soft-remove limb only, and today's file states 'keeps its revert' in header prose (lines 34-40). Full readings: /mnt/project-files/server-seat3/29390-readings.md. Nothing edited; waiting for the seat's ruling.",
"tests": "No repository test changed. Measurement: an uncommitted scratch dogfood file over bootShowcase (deleted after the run), run as `bash scripts/pm/os-verify-lock.sh -c 'cd packages/qa/dogfood && pnpm exec vitest run --maxWorkers=2 test/zz-scratch-29390-measure.dogfood.test.ts'`: VERDICT command-exit 0, Tests 1 passed (1), on the closure built by `pnpm --filter '@objectstack/dogfood^...' build` (OS_SKIP_DTS=1, VERDICT command-exit 0). Readings in the summary and the project file; no ablation (no tested behaviour changed).",
"gates": "n/a: no edit and no commit on the branch (git status clean against 7f277a86); dispatch-gates runs after the ruling's implementation.",
"dogfood_battery": "n/a: no change yet. If (a) is ruled, the title carries ! and Clause-2 carries (narrowing), so the full battery runs on the final head.",
"files_changed": [],
"line_budget": "n/a (no diff)",
"deviations": [
"os-dev says a read-only reading builds no closure and reports status reading; the delta asked for a real-door measurement and status needs_decision, so the dogfood closure was built and one scratch boot was run. The scratch test file was created in the worktree, never committed, and deleted.",
"To produce commits recorded before the close on a fresh boot, the running protocol instance's namedBaseOverlayWriteRefusal was shadowed to answer null during phase A only (every row, history row and commit came from the real save and publish-drafts doors), then the shadow was removed and the close checked live (save naming the base answered 403)."
],
"mcp_calls": "3: mcp__github__issue_read (get on #29390, get_comments on #29390 twice). No write tool.",
"api_writes": "1: this os-dev-report comment on #29390 (post-stamped.mjs, issue comment).",
"open_questions": [
{
"question": "The card's fork: does a commit revert or package rollback that would restore a row bound to a read-only base take the #28905 close?",
"options": [
"A: yes. In revertCommit's restore limb, before repo.restoreVersion, ask namedBaseOverlayWriteRefusal on the binding the restore writes (restorePackageId, already read there), with the rollback arm of the same sentence (closedOverlayWriteClauses, no second spelling). The item lands in failed[] with code NOT_OVERRIDABLE and the sentence, nothing written; the revert answers 200 success:false; rollbackToPackageCommit reports it per item and continues. Creation items keep the soft-remove limb (delete, open). Breaks no assertion; one prose sentence of the #29090 header becomes false and is amended. Narrows a write door: ! title, BREAKING banner, adr-0087 marker, full dogfood battery. Effort: about 15 lines in protocol.ts plus a test file (red first, controls, ablation).",
"B: no, open by design. The restore limb keeps rewriting the base-bound row; the per-item rollback (403) and the commit revert (200) of the same row keep disagreeing, and every package rollback replays the pre-close commits. Text only: #28905's unreleased changeset 'Still open' list, the docs, the ADR-0087 entry (the S3 card, spec lane). Effort: text."
],
"recommendation": "A, on the two axes the delta names. Readings: it makes the commit doors agree with the per-item rollback of the very same row (measured 403 vs 200 today) and stops a package rollback from rewriting the base-bound row on every replay, while the deletion that clears such a row (and a pre-close creation commit's revert, which is that deletion) stays open. Controls: A keeps every assertion that exists (the #29090 soft-remove case, #6215's writable-package revert, the pair's own commit revert with no base row) and changes only header prose; B keeps them too but leaves the #29090 header's promise as the only place the open path is stated until the S3 text lands."
},
{
"question": "If A: which binding keys the close?",
"options": [
"A1: the binding the restore writes (resolveOverlayPackageBinding's answer, the key rollbackMetaItem's #28905 close already uses). A pair commit whose plan points at a coexisting pre-close base row (section 5 finding) is then refused naming the pair instead of answering a silent no-op success.",
"A2: A1 and the commit's own package_id equal to that base. Pair commits keep today's silent no-op until the section 5 finding is fixed."
],
"recommendation": "A1: one predicate on one key for both restore doors, and the coexistence case turns from success:true over nothing reverted into a refusal; the addressing defect is fixed on its own card."
},
{
"question": "If A: the refusal channel. The delta says 403 NOT_OVERRIDABLE; revertCommit reports every restore-limb refusal per item.",
"options": [
"Per item: failed[].code NOT_OVERRIDABLE with the sentence, HTTP 200 success:false (the channel VERSION_NOT_FOUND, STORED_TYPE_NOT_CANONICAL and the #25278 refusal family already use).",
"Whole revert: throw 403 before any item is written (a response-shape change for a mixed commit)."
],
"recommendation": "Per item: no new receipt surface, and the commit's other items still revert."
}
],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: POST /api/v1/packages/com.example.showcase.custom/commits/ID/revert on a booted showcase, measured · evidence: with a pre-close row bound to com.example.showcase beside the pair's declared override (the state the #28905 sentence steers an operator into), publishPackageDrafts recorded the pair's first commit as existedBefore true, prevVersion 11 (the base row's version; it created the pair row) and later pair commits as prevVersion 11 again, because the plan reads any package's active row (protocol.ts:19697); their reverts answered 200 success:true reverted restored while writing nothing (lineage counter unmoved) and the pair's edit stayed served, because the restore limb resolves any package's active row too (resolveOverlayPackageBinding, restoreVersion's whereFor with no package). rollbackMetaItem reads the binding the same way (not measured). · dedupe words: revert pair commit no-op, commit plan prevVersion package-agnostic, restoreVersion any package row, success true nothing reverted · carrier: seat to file"
]
}
Why: today the same base-bound row is refused 403 by the per-item rollback and rewritten 200 by a commit revert, and every package rollback replays the pre-close commits onto it. A makes the two restore doors agree.
What stays open: the soft-remove limb, both a pre-close creation commit's revert and a delete, by design.
The key: A1. The binding the restore writes (resolveOverlayPackageBinding's answer, the key rollbackMetaItem already uses), so both restore doors use one predicate on one key. A pair commit whose plan points at a coexisting pre-close base row is then refused, naming the pair, instead of answering success over nothing.
Declarations: narrows a write door. ! title, a BREAKING banner, Clause-②: no (narrowing) (the claim stands, no amendment), Metadata-protocol: none, one adr-0087: marker, and the full dogfood battery on the final head.
Tests: red first through the real commit door, on a pre-close commit produced the way the readings did, with the close shadowed on the instance only:
The edit item's revert is refused per item, and nothing is written.
A package rollback reports the item and continues.
Ablation: drop the close from the restore limb, and the pins go red.
Out of scope, filed by the seat: the readings' section 5 finding (a pair commit's revert beside a pre-close base row answers success while writing nothing, because the commit plan and the restore both read any package's active row). It goes on its own card, serial after this one, because it touches the same commit doors in protocol.ts. ⛔ Do not fix the plan's prevVersion here. With A1 that case refuses instead of succeeding silently, which is enough for this card.
Serial: #29392 runs in parallel on disjoint files; #29397 runs after this card. Merge main before opening.
{
"issue": 29390,
"status": "done",
"branch": "claude/project-thread-gvqm55",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29632",
"head_sha": "a15f252f6ae1bad0978ae63469a9b13f29caf712",
"session": "session_01ADHkmTKKCrTrtA7H6oSj28",
"premise_still_valid": true,
"summary": "Seat ruling 6579511992 implemented: option A, key A1, per-item channel. revertCommit's restore limb asks namedBaseOverlayWriteRefusal before repo.restoreVersion, on the binding the restore writes (resolveOverlayPackageBinding, the key rollbackMetaItem uses), with the rollback arm of the same sentence (closedOverlayWriteClauses, no second spelling). A refused item goes into failed[] with code NOT_OVERRIDABLE and nothing is written; the revert answers 200 success:false; the commit's other items still revert. rollbackToPackageCommit reports each refused item and continues. The soft-remove limb (revert of a creation) stays open. Declared as a narrowing: ! title, BREAKING banner in the changeset, Clause-2 no (narrowing), Metadata-protocol none, one adr-0087 marker (not-required, already-registered packageless-overlay-write-closed). The #29090 dogfood header sentence is amended and the PR body names it. Freeze (seat note): the hunk stays in protocol.ts inside revertCommit's class body; the #25467 E2 section 1 split did not move revertCommit, and the lines cannot move into protocol-packaged-base-refusal.ts without a second spelling (the only calls before restoreVersion are resolveOverlayPackageBinding, also read by the cluster write-through replay and rollbackMetaItem, and SysMetadataRepository.restoreVersion, the registry-less repository layer). PR opened after the lift, on a15f252f (origin/main 0ab4a09e merged); clean merge with today's main (git merge-tree).",
"tests": "Red first through the real commit doors with the close shadowed on the instance only: protocol.named-base-overlay-write.test.ts gains 5 cases (edit revert refused per item naming the pair; package rollback reports per item and continues; CONTROL creation revert removes the row; CONTROL writable package reverts in place (#6215 shape); CONTROL pair's own commit with no base row reverts in place). The dogfood file package-commit-revert-path-package gains one last case on the booted showcase via REST. On a15f252f: unit file Tests 22 passed (22); metadata-protocol Test Files 308 passed, 4 skipped (312), Tests 22251 passed, 26 skipped (22277); objectql Test Files 540 passed (540), Tests 10488 passed (10488); metadata-protocol and dogfood typecheck exit 0; dogfood file Tests 9 passed (9). Ablation (dropping the close from the restore limb, ablation-replace held under an EXIT trap): unit Tests 2 failed | 20 passed (both red-first cases); dogfood leg after rebuild with the dist preflight marker present Tests 1 failed | 7 passed (the #29390 case); restored blob == HEAD, the --absent preflight clean. The ablation ran on ecf913ed; the code under test has not changed since.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands (no paths) on a15f252f: 65 families, all 65 run, every exit 0, after all packages were built (turbo 78/78). --ran reconciliation: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN. pnpm lint exit 0.",
"dogfood_battery": "a15f252f · VERDICT command-exit 0 · Test Files 398 passed | 3 skipped (401) · Tests 3281 passed | 28 skipped (3309). Earlier heads ecf913ed and d776e258 had 4 reds in flow-runas (3) and declarative-endpoint-anonymous-guest (1). They were also red with this PR's src restored to origin/main (control leg) and with #29356's resolve-authz-context reverted (core leg), so this change did not cause them; #29451 fixed them on main.",
"files_changed": [
".changeset/29390-named-base-commit-revert-closed.md",
"packages/metadata-protocol/src/protocol-packaged-base-refusal.ts",
"packages/metadata-protocol/src/protocol.named-base-overlay-write.test.ts",
"packages/metadata-protocol/src/protocol.ts",
"packages/qa/dogfood/test/package-commit-revert-path-package.dogfood.test.ts"
],
"line_budget": "+246 / -12 across 5 files: protocol.ts +23 (9 code, 14 doc and comment), protocol-packaged-base-refusal.ts +8/-5 (doc only), unit test +114/-1, dogfood test +66/-6, changeset +35",
"deviations": [
"Main was merged twice after the first verification round: d776e258, then a15f252f after the protocol.ts freeze lifted. The full round (suites, battery, gates, lint) re-ran on each head. The ablation ran on ecf913ed and was not repeated, because the code under test is unchanged.",
"The PR body ends with the harness attribution lines (robot line and session URL) placed before the repo's standard bare footer, which stays last.",
"On d776e258 the dogfood typecheck ran out of heap at the default 2 GB. It was re-run with --max-old-space-size=4096 and exited 0 (a resource limit, not a type error)."
],
"mcp_calls": "6 reads, no MCP writes: mcp__github__issue_read x4 (#29390 get, get_comments x3) and mcp__github__pull_request_read x1 (get on #29632, read-back), plus mcp__claude-code-remote__subscribe_pr_activity x1 on #29632 (a session subscription, not a GitHub write).",
"api_writes": "3 relay writes: the needs_decision os-dev-report comment 6579484752 on #29390, the pr_create of #29632 (run 41539409148, 12590 bytes sent and stored, identical, no assignees), and this os-dev-report comment. Git pushes to the branch are not counted.",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a · reach: POST /api/v1/packages/BASE.custom/commits/ID/revert, measured on a booted showcase · evidence: publishPackageDrafts' revert plan and the restore limb both read ANY package's active row (resolveOverlayPackageBinding, restoreVersion's whereFor with no package). A pair commit beside a pre-close row bound to the base recorded prevVersion from the base row, and its revert answered success over nothing. With A1 that revert is now refused naming the pair. · dedupe words: revert pair commit no-op, commit plan prevVersion package-agnostic · carrier: seat files it (ruled serial after this card)",
"class: c · reach: packages/runtime/src/domains/packages.ts REST revert door comment · evidence: it says undoing a read-only package's overlay commit stays open. That is still true of the door (no package-level guard) and of the soft-remove limb, but the restore limb now refuses per item. Outside this card's file surface. · dedupe words: packages.ts revert comment stays open read-only · carrier: seat",
"class: b · reach: revertCommit restore limb · evidence: it does not ask the #28576 package-less close. A package-less row is reached there only through the any-package lookup in the first finding · dedupe words: restore limb package-less close · carrier: same card as the first finding"
]
}
Release: session_01ADHkmQJdgarUjPLNgFBY67 · reason: clock-out (maintainer order, quoted below) · next: pm:queue, for the next domain:server seat to follow up the landing
Resume-from: claude/project-thread-gvqm55@85261a810dff62b530193fbe92e7b607158a76d3
If it merges, Fixes #29390 closes this card. Write the landing record (queue refs released, ancestry of the merge commit, a content grep for the restore limb's namedBaseOverlayWriteRefusal call in revertCommit), clear any leftover pm: label, and stop there.
If the queue ejects it, read the queue run. A red that is this PR's goes back to a card thread on the branch above. A head change after 85261a81 needs the seat's diff check before re-queueing.
Landed: merged as 14df37fa, before the seat left, so the hand-off note above is spent. The next seat owes nothing on this card.
A commit revert or package rollback whose restore would land in a row bound to the read-only base is now refused per item, with NOT_OVERRIDABLE and the per-item rollback's sentence naming the .custom pair.
The soft-remove limb and every writable destination stay open.
The seat ACCEPT is on the PR as 6591234666. The narrowing-face contract review PASS is 6590665469. Fixes #29390 closed this card on merge.
Activity
objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Claim: PM loop round 1 · card thread · 2026-11-02T14:10Z
Session:
session_01ADHkmQJdgarUjPLNgFBY67(the seat); the card thread issession_01ADHkmTKKCrTrtA7H6oSj28Account:
os-steve(the seat's linked user; the card's assignee)Branch:
claude/project-thread-gvqm55Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#3Ruling-ref: the maintainer, verbatim, in the project thread: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」
File surface:
packages/metadata-protocol/src/protocol.ts(therevertCommitandrollbackToPackageCommitdoors only),packages/metadata-protocol/src/protocol-packaged-base-refusal.ts,protocol.packaged-base-refusal.test.tsand the commit-door tests;.changeset/; docs text if option (b) is ruled. The dev measures and reports needs_decision first; nothing is edited before the seat rules. ⛔ Notprotocol-packageless-overlay-migration.tsor the cli ceremony (#29392).Container & model: card thread
cmsg_01FdL1XeUyvVNxLreh3F4Qi9MzJJFGjBCFjRXi73LUWGL9,mode:thread,model: opus(--tiernames no path mandate: PM's call, default),effort: xhighClause-②: no (narrowing)
Metadata-protocol: none
Responsibility: the card thread measures, reports the (a)/(b) fork, implements the seat ruling and opens the draft PR; the seat rules, reviews, ACCEPTs and lands. The Clause-② value assumes (a); the seat amends it to
noif (b) is ruledThread-read: none
Premise: holds (site re-read on
origin/main7f277a86by the seat before dispatch)Serial constraints cleared: #29270 (#28905) merged
838a802b; #29392 runs in parallel on disjoint files; #29397 runs after this cardResume-from: none — fresh from
origin/mainState:
pm:queue→pm:dispatched, assigned toos-steve, recorded by this claim. Order: project file server-seat3/standard-order.md + server-seat3/29390-delta.md.objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmTKKCrTrtA7H6oSj28
os-dev-report
{ "issue": 29390, "status": "needs_decision", "branch": "claude/project-thread-gvqm55", "pr": null, "session": "session_01ADHkmTKKCrTrtA7H6oSj28", "premise_still_valid": true, "summary": "Premise holds on origin/main 7f277a86. revertCommit's restore limb (and rollbackToPackageCommit through it) writes into the active row of any package via repo.restoreVersion and asks neither the #28576 nor the #28905 close, while rollbackMetaItem asks both on the same binding read. Measured through the REST doors on a booted showcase: with pre-close apply commits on com.example.showcase (produced through the real save and publish-drafts doors with the close dropped on the running instance only), the per-item rollback of the base-bound row answers 403 NOT_OVERRIDABLE, but POST /packages/com.example.showcase/commits/C3/revert answers 200 success:true restored, rewrites the base-bound row (v6 L3 to v7 L2, no overrides) and the read naming no package and ?package=BASE both serve L2; POST /packages/com.example.showcase/rollback replays every pre-close apply commit (C3 reverted a second time, then C2) to L1. Which commits reach it: only apply commits on the base recorded before the close whose item edited an existing row; creation items take the soft-remove limb (delete, open by design); revert commits carry prevVersion null and never take the restore limb; post-close publishes into the base are refused where the fix can be taken. No assertion pins a restore-limb revert of a base-bound row: the #29090 case at 838a802b^ asserted the soft-remove limb only, and today's file states 'keeps its revert' in header prose (lines 34-40). Full readings: /mnt/project-files/server-seat3/29390-readings.md. Nothing edited; waiting for the seat's ruling.", "tests": "No repository test changed. Measurement: an uncommitted scratch dogfood file over bootShowcase (deleted after the run), run as `bash scripts/pm/os-verify-lock.sh -c 'cd packages/qa/dogfood && pnpm exec vitest run --maxWorkers=2 test/zz-scratch-29390-measure.dogfood.test.ts'`: VERDICT command-exit 0, Tests 1 passed (1), on the closure built by `pnpm --filter '@objectstack/dogfood^...' build` (OS_SKIP_DTS=1, VERDICT command-exit 0). Readings in the summary and the project file; no ablation (no tested behaviour changed).", "gates": "n/a: no edit and no commit on the branch (git status clean against 7f277a86); dispatch-gates runs after the ruling's implementation.", "dogfood_battery": "n/a: no change yet. If (a) is ruled, the title carries ! and Clause-2 carries (narrowing), so the full battery runs on the final head.", "files_changed": [], "line_budget": "n/a (no diff)", "deviations": [ "os-dev says a read-only reading builds no closure and reports status reading; the delta asked for a real-door measurement and status needs_decision, so the dogfood closure was built and one scratch boot was run. The scratch test file was created in the worktree, never committed, and deleted.", "To produce commits recorded before the close on a fresh boot, the running protocol instance's namedBaseOverlayWriteRefusal was shadowed to answer null during phase A only (every row, history row and commit came from the real save and publish-drafts doors), then the shadow was removed and the close checked live (save naming the base answered 403)." ], "mcp_calls": "3: mcp__github__issue_read (get on #29390, get_comments on #29390 twice). No write tool.", "api_writes": "1: this os-dev-report comment on #29390 (post-stamped.mjs, issue comment).", "open_questions": [ { "question": "The card's fork: does a commit revert or package rollback that would restore a row bound to a read-only base take the #28905 close?", "options": [ "A: yes. In revertCommit's restore limb, before repo.restoreVersion, ask namedBaseOverlayWriteRefusal on the binding the restore writes (restorePackageId, already read there), with the rollback arm of the same sentence (closedOverlayWriteClauses, no second spelling). The item lands in failed[] with code NOT_OVERRIDABLE and the sentence, nothing written; the revert answers 200 success:false; rollbackToPackageCommit reports it per item and continues. Creation items keep the soft-remove limb (delete, open). Breaks no assertion; one prose sentence of the #29090 header becomes false and is amended. Narrows a write door: ! title, BREAKING banner, adr-0087 marker, full dogfood battery. Effort: about 15 lines in protocol.ts plus a test file (red first, controls, ablation).", "B: no, open by design. The restore limb keeps rewriting the base-bound row; the per-item rollback (403) and the commit revert (200) of the same row keep disagreeing, and every package rollback replays the pre-close commits. Text only: #28905's unreleased changeset 'Still open' list, the docs, the ADR-0087 entry (the S3 card, spec lane). Effort: text." ], "recommendation": "A, on the two axes the delta names. Readings: it makes the commit doors agree with the per-item rollback of the very same row (measured 403 vs 200 today) and stops a package rollback from rewriting the base-bound row on every replay, while the deletion that clears such a row (and a pre-close creation commit's revert, which is that deletion) stays open. Controls: A keeps every assertion that exists (the #29090 soft-remove case, #6215's writable-package revert, the pair's own commit revert with no base row) and changes only header prose; B keeps them too but leaves the #29090 header's promise as the only place the open path is stated until the S3 text lands." }, { "question": "If A: which binding keys the close?", "options": [ "A1: the binding the restore writes (resolveOverlayPackageBinding's answer, the key rollbackMetaItem's #28905 close already uses). A pair commit whose plan points at a coexisting pre-close base row (section 5 finding) is then refused naming the pair instead of answering a silent no-op success.", "A2: A1 and the commit's own package_id equal to that base. Pair commits keep today's silent no-op until the section 5 finding is fixed." ], "recommendation": "A1: one predicate on one key for both restore doors, and the coexistence case turns from success:true over nothing reverted into a refusal; the addressing defect is fixed on its own card." }, { "question": "If A: the refusal channel. The delta says 403 NOT_OVERRIDABLE; revertCommit reports every restore-limb refusal per item.", "options": [ "Per item: failed[].code NOT_OVERRIDABLE with the sentence, HTTP 200 success:false (the channel VERSION_NOT_FOUND, STORED_TYPE_NOT_CANONICAL and the #25278 refusal family already use).", "Whole revert: throw 403 before any item is written (a response-shape change for a mixed commit)." ], "recommendation": "Per item: no new receipt surface, and the commit's other items still revert." } ], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: POST /api/v1/packages/com.example.showcase.custom/commits/ID/revert on a booted showcase, measured · evidence: with a pre-close row bound to com.example.showcase beside the pair's declared override (the state the #28905 sentence steers an operator into), publishPackageDrafts recorded the pair's first commit as existedBefore true, prevVersion 11 (the base row's version; it created the pair row) and later pair commits as prevVersion 11 again, because the plan reads any package's active row (protocol.ts:19697); their reverts answered 200 success:true reverted restored while writing nothing (lineage counter unmoved) and the pair's edit stayed served, because the restore limb resolves any package's active row too (resolveOverlayPackageBinding, restoreVersion's whereFor with no package). rollbackMetaItem reads the binding the same way (not measured). · dedupe words: revert pair commit no-op, commit plan prevVersion package-agnostic, restoreVersion any package row, success true nothing reverted · carrier: seat to file" ] }Stamped 2026-11-02T14:19Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Seat ruling · #29390 · 2026-11-02T14:21Z
Read: os-dev-report
6579484752and its readings file (a booted showcase, through REST, onorigin/main7f277a86).revertCommit's restore limb, beforerepo.restoreVersion, the metadata-protocol: a save naming a read-only code package itself (?package=<base>) for an overlay type is still accepted and lands an overlay bound to the base #28905 close is asked throughnamedBaseOverlayWriteRefusal, with the rollback arm of the same sentence (closedOverlayWriteClauses). ⛔ There is no second spelling.resolveOverlayPackageBinding's answer, the keyrollbackMetaItemalready uses), so both restore doors use one predicate on one key. A pair commit whose plan points at a coexisting pre-close base row is then refused, naming the pair, instead of answering success over nothing.failed[]getscode: NOT_OVERRIDABLEwith the sentence, HTTP 200 withsuccess: false. This is the channel thatVERSION_NOT_FOUND,STORED_TYPE_NOT_CANONICALand the metadata-protocol: rollbackMetaItem / revertCommit restore a version without the save-door refusal family, so a restore can re-create a refused shape #25278 family already use, so the receipt surface does not change. The commit's other items still revert, androllbackToPackageCommitreports each refused item and continues.Declarations: narrows a write door.
!title, a BREAKING banner,Clause-②: no (narrowing)(the claim stands, no amendment),Metadata-protocol: none, oneadr-0087:marker, and the full dogfood battery on the final head.Tests: red first through the real commit door, on a pre-close commit produced the way the readings did, with the close shadowed on the instance only:
package_id IS NULL#6215); the pair's own commit revert with no base row.Out of scope, filed by the seat: the readings' section 5 finding (a pair commit's revert beside a pre-close base row answers success while writing nothing, because the commit plan and the restore both read any package's active row). It goes on its own card, serial after this one, because it touches the same commit doors in
protocol.ts. ⛔ Do not fix the plan'sprevVersionhere. With A1 that case refuses instead of succeeding silently, which is enough for this card.Serial:
#29392runs in parallel on disjoint files;#29397runs after this card. Merge main before opening.objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmTKKCrTrtA7H6oSj28
os-dev-report
{ "issue": 29390, "status": "done", "branch": "claude/project-thread-gvqm55", "pr": "https://github.com/objectstack-ai/objectstack/pull/29632", "head_sha": "a15f252f6ae1bad0978ae63469a9b13f29caf712", "session": "session_01ADHkmTKKCrTrtA7H6oSj28", "premise_still_valid": true, "summary": "Seat ruling 6579511992 implemented: option A, key A1, per-item channel. revertCommit's restore limb asks namedBaseOverlayWriteRefusal before repo.restoreVersion, on the binding the restore writes (resolveOverlayPackageBinding, the key rollbackMetaItem uses), with the rollback arm of the same sentence (closedOverlayWriteClauses, no second spelling). A refused item goes into failed[] with code NOT_OVERRIDABLE and nothing is written; the revert answers 200 success:false; the commit's other items still revert. rollbackToPackageCommit reports each refused item and continues. The soft-remove limb (revert of a creation) stays open. Declared as a narrowing: ! title, BREAKING banner in the changeset, Clause-2 no (narrowing), Metadata-protocol none, one adr-0087 marker (not-required, already-registered packageless-overlay-write-closed). The #29090 dogfood header sentence is amended and the PR body names it. Freeze (seat note): the hunk stays in protocol.ts inside revertCommit's class body; the #25467 E2 section 1 split did not move revertCommit, and the lines cannot move into protocol-packaged-base-refusal.ts without a second spelling (the only calls before restoreVersion are resolveOverlayPackageBinding, also read by the cluster write-through replay and rollbackMetaItem, and SysMetadataRepository.restoreVersion, the registry-less repository layer). PR opened after the lift, on a15f252f (origin/main 0ab4a09e merged); clean merge with today's main (git merge-tree).", "tests": "Red first through the real commit doors with the close shadowed on the instance only: protocol.named-base-overlay-write.test.ts gains 5 cases (edit revert refused per item naming the pair; package rollback reports per item and continues; CONTROL creation revert removes the row; CONTROL writable package reverts in place (#6215 shape); CONTROL pair's own commit with no base row reverts in place). The dogfood file package-commit-revert-path-package gains one last case on the booted showcase via REST. On a15f252f: unit file Tests 22 passed (22); metadata-protocol Test Files 308 passed, 4 skipped (312), Tests 22251 passed, 26 skipped (22277); objectql Test Files 540 passed (540), Tests 10488 passed (10488); metadata-protocol and dogfood typecheck exit 0; dogfood file Tests 9 passed (9). Ablation (dropping the close from the restore limb, ablation-replace held under an EXIT trap): unit Tests 2 failed | 20 passed (both red-first cases); dogfood leg after rebuild with the dist preflight marker present Tests 1 failed | 7 passed (the #29390 case); restored blob == HEAD, the --absent preflight clean. The ablation ran on ecf913ed; the code under test has not changed since.", "gates": "node scripts/pm/dispatch-gates.mjs --commands (no paths) on a15f252f: 65 families, all 65 run, every exit 0, after all packages were built (turbo 78/78). --ran reconciliation: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN. pnpm lint exit 0.", "dogfood_battery": "a15f252f · VERDICT command-exit 0 · Test Files 398 passed | 3 skipped (401) · Tests 3281 passed | 28 skipped (3309). Earlier heads ecf913ed and d776e258 had 4 reds in flow-runas (3) and declarative-endpoint-anonymous-guest (1). They were also red with this PR's src restored to origin/main (control leg) and with #29356's resolve-authz-context reverted (core leg), so this change did not cause them; #29451 fixed them on main.", "files_changed": [ ".changeset/29390-named-base-commit-revert-closed.md", "packages/metadata-protocol/src/protocol-packaged-base-refusal.ts", "packages/metadata-protocol/src/protocol.named-base-overlay-write.test.ts", "packages/metadata-protocol/src/protocol.ts", "packages/qa/dogfood/test/package-commit-revert-path-package.dogfood.test.ts" ], "line_budget": "+246 / -12 across 5 files: protocol.ts +23 (9 code, 14 doc and comment), protocol-packaged-base-refusal.ts +8/-5 (doc only), unit test +114/-1, dogfood test +66/-6, changeset +35", "deviations": [ "Main was merged twice after the first verification round: d776e258, then a15f252f after the protocol.ts freeze lifted. The full round (suites, battery, gates, lint) re-ran on each head. The ablation ran on ecf913ed and was not repeated, because the code under test is unchanged.", "The PR body ends with the harness attribution lines (robot line and session URL) placed before the repo's standard bare footer, which stays last.", "On d776e258 the dogfood typecheck ran out of heap at the default 2 GB. It was re-run with --max-old-space-size=4096 and exited 0 (a resource limit, not a type error)." ], "mcp_calls": "6 reads, no MCP writes: mcp__github__issue_read x4 (#29390 get, get_comments x3) and mcp__github__pull_request_read x1 (get on #29632, read-back), plus mcp__claude-code-remote__subscribe_pr_activity x1 on #29632 (a session subscription, not a GitHub write).", "api_writes": "3 relay writes: the needs_decision os-dev-report comment 6579484752 on #29390, the pr_create of #29632 (run 41539409148, 12590 bytes sent and stored, identical, no assignees), and this os-dev-report comment. Git pushes to the branch are not counted.", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a · reach: POST /api/v1/packages/BASE.custom/commits/ID/revert, measured on a booted showcase · evidence: publishPackageDrafts' revert plan and the restore limb both read ANY package's active row (resolveOverlayPackageBinding, restoreVersion's whereFor with no package). A pair commit beside a pre-close row bound to the base recorded prevVersion from the base row, and its revert answered success over nothing. With A1 that revert is now refused naming the pair. · dedupe words: revert pair commit no-op, commit plan prevVersion package-agnostic · carrier: seat files it (ruled serial after this card)", "class: c · reach: packages/runtime/src/domains/packages.ts REST revert door comment · evidence: it says undoing a read-only package's overlay commit stays open. That is still true of the door (no package-level guard) and of the soft-remove limb, but the restore limb now refuses per item. Outside this card's file surface. · dedupe words: packages.ts revert comment stays open read-only · carrier: seat", "class: b · reach: revertCommit restore limb · evidence: it does not ask the #28576 package-less close. A package-less row is reached there only through the any-package lookup in the first finding · dedupe words: restore limb package-less close · carrier: same card as the first finding" ] }Stamped 2026-11-02T22:43Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Release:
session_01ADHkmQJdgarUjPLNgFBY67· reason: clock-out (maintainer order, quoted below) · next:pm:queue, for the nextdomain:serverseat to follow up the landingResume-from:
claude/project-thread-gvqm55@85261a810dff62b530193fbe92e7b607158a76d3Hand-off note · #29390 · PR #29632 · 2026-11-02T23:56Z
6591234666) on head85261a81. It is ready, auto-merge is on, and it entered the merge queue at 23:36Z. The narrowing-face contract review PASS is6590665469. Nothing in review or code is open.Fixes #29390closes this card. Write the landing record (queue refs released, ancestry of the merge commit, a content grep for the restore limb'snamedBaseOverlayWriteRefusalcall inrevertCommit), clear any leftoverpm:label, and stop there.85261a81needs the seat's diff check before re-queueing.namedBaseCloseSubject,packagelessCloseSubject) into one fix-can-be-taken predicate #29397, then metadata-protocol: the #28905 overlay-write refusal sentence does not nameos migrate packageless-overlays, the ceremony that now moves a base-bound row into its pair #29479.objectstack-fleet commentedon Nov 2, 2026
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Landing record · #29390 · PR #29632
domain:serverseat 3 ·session_01ADHkmQJdgarUjPLNgFBY67· 2026-11-02T23:59Zgit ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-29632-*'git merge-base --is-ancestor 14df37fa origin/maingit grep -n "namedBaseOverlayWriteRefusal" origin/main -- packages/metadata-protocol/src/protocol.tsrevertCommit)14df37fa, before the seat left, so the hand-off note above is spent. The next seat owes nothing on this card.NOT_OVERRIDABLEand the per-item rollback's sentence naming the.custompair.6591234666. The narrowing-face contract review PASS is6590665469.Fixes #29390closed this card on merge.pm:queue(left by the release) is removed in the same round. No assignee.