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 per-item rollback of the .custom pair's row is refused 403 naming the base row while a base-bound row of the same name is stored (rollbackMetaItem reads the binding with no package) #29848
Filing gate: ① A class-a out-of-scope finding from the #29414 dev (os-dev-report 6595958400, out-of-scope item 1), accepted by the seat on PR #29797, which fixes the same addressing defect on the commit doors only. Filed by domain:server seat 5. ⛔ Not a claim. Suggested-grade: p2. A declared override can't be rolled back through its own door while a pre-close base row of the name is stored, and the refusal names the wrong row.
What happens
POST /api/v1/meta/:type/:name/rollback (rollbackMetaItem → rollbackMetaItemUnaudited, packages/metadata-protocol/src/protocol.ts) takes no package.
Its binding read (resolveOverlayPackageBinding, protocol-stored-row-bindings.ts) and SysMetadataRepository.restoreVersion's active-row read are both package-agnostic.
Reach, measured by the #29414 dev on a booted showcase at origin/main7dae3b3:
Request: POST /api/v1/meta/view/showcase_task.default/rollback.
Stored rows: one bound to com.example.showcase, written before the close, beside com.example.showcase.custom's declared override.
Answer: 403 NOT_OVERRIDABLE, naming the base row.
Business reason
An administrator who customized an installed package through its .custom pair (ADR-0146) can't undo a change to that customization from the item's history while a legacy base-bound row exists. The refusal points at a row they never meant to touch. Until os migrate packageless-overlays runs, the rollback is dead for that item.
Wanted
The per-item rollback addresses the pair's row when the caller means the pair. Decide, as a door-surface ruling, whether the door takes the package (?package= as on the save door) or derives it from the active served row. Either way, one predicate, no second spelling.
Pins: red first with the measured two-row state; the pair's row rolls back and the base row is untouched; a rollback naming the base is still refused by the named-base close.
Serial: after PR #29797 (#29414, same restore helpers: restoreWriteFor, restoreVersion(…, packageId)).
Reader
A domain:server seat dispatches it after #29797 lands. The door-surface choice is the dispatch order's first decision.
Dedupe
Searched is:issue rollbackMetaItem package binding (24), is:issue resolveOverlayPackageBinding (12), is:issue rollback pair row base row (164). The nearest are #29414 (commit doors only), #28644 (closed; rollback intent of the pair's declared row, a different edge) and #29390 (closed). New.
Read fresh. Measured on a booted showcase: POST /meta/:type/:name/rollback takes no package, and its binding read and active-row read are package-agnostic. So while a pre-close base-bound row of the same name is stored, a .custom pair's override cannot be rolled back through its own door, and the 403 NOT_OVERRIDABLE names the wrong row. p2, as suggested.
Claim: PM loop round 1
Session: session_01Utp6CYVV6fcAQrNTDQRvPb (seat) · dev = project card thread session_01Utp6CXdBvpJrhnHyW825Hb
Account: os-bill
Branch: claude/project-thread-4jj4ed (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-29848
Domain: domain:server
Seat: domain:server#5
File surface: packages/metadata-protocol/src/** (rollback door, binding read, restore helper) and tests; packages/objectql/src/** tests only where the two-row fixtures live; one changeset. ⛔ packages/spec/**, ui/**, skills/**, docs/adr/**, .claude/**, AGENTS.md (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default tier (opus) — dispatch-gates --tier at 5fa9375e: no path-derived mandate, effort: high
Clause-②: no — the rollback door's accept set does not change (no new parameter); the package is derived from the served row
Metadata-protocol: none
Responsibility: metadata-protocol's per-item rollback resolves the base row's package for a .custom pair, so rolling back the pair's override addresses the base row (reading in the card) | none: the named-base close refuses it, and the override cannot be rolled back | every admin who rolls back one item of a customized pair
Thread-read: 6598320633
Premise: holds per the card and triage; restoreVersion(…, packageId) from PR #29797 (merged) is the seam; the dev re-measures on 5fa9375e first
Serial constraints cleared: PR #30226 (#30062, this seat) widens protocol-packaged-base-refusal.ts and protocol-package-bindings.ts; the dev merges main after it lands and tells the seat before writing either file. Seat 3's #30106 (merged) and #30168 are in the same area.
Order: project file server-seat5/dispatch-29848.md, delivered to the card thread. Door choice (seat's call): derive the package from the served row, no new ?package=; ambiguity → needs_decision.
{
"issue": 29848,
"status": "done",
"branch": "claude/project-thread-4jj4ed",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30268",
"session": "session_01Utp6CXdBvpJrhnHyW825Hb",
"premise_still_valid": true,
"summary": "Measured on 5fa9375e: rollbackMetaItemUnaudited read the package with resolveOverlayPackageBinding (first active row of the name in store order) and called restoreVersion with no packageId, so with a base-bound row stored first the .custom pair's declared override was addressed as the base row and refused 403 NOT_OVERRIDABLE by the named-base close. Per the order's door ruling, the package is now derived from the row the by-name read naming no package serves (findServedOverlayRow at packagelessReadAddress, servedOverride true; the binding read only when nothing is served), with no new ?package= parameter, and restoreVersion is handed that package. The served row identifies the pair's row unambiguously in the measured state (one shipper; the declared override is the first candidate), so no needs_decision. One existing objectql pin (S5 shadowed) pinned the old store-order address on a package-less row beside the pair's declared row; re-judged: the rollback now addresses the served pair row and the undeclared version is the D2 gate's refusal.",
"tests": "Red first on 5fa9375e: the two-row pin answered the named-base sentence (403 NOT_OVERRIDABLE). Green: metadata-protocol test 312 files passed (4 skipped), 22357 tests passed (26 skipped) at 2d469b09; typecheck exit 0 (--listFiles includes the new test). objectql test 548/548 files, 10863/10863 at 2d469b09. Post-merge abd5d69f, scoped: named-base-overlay-write 34/34; objectql packageless-overlay-migration + publish-rollback 40/40; spec check:generated up to date. Ablations via scripts/ablation-replace.mjs wrap mode (tests import src, no dist leg): A1 served-row derivation off: 2/34 red; A2 restoreVersion without packageId: 1/34 red; each restored blob == HEAD 35ffb243, git status clean.",
"gates": "dispatch-gates --commands at 2d469b09: 61 derived, 60 exit 0, 1 NOT MEASURED (check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET, 70 unbuilt packages outside the closure; left to CI). check:lean-entry-closure first exit 3, exit 0 after building objectql. --ran exit 0 (61 accounted, 60 run, 1 NOT-MEASURED). List re-derived at abd5d69f byte-identical; ratchet and changeset families re-run there all exit 0. pnpm lint exit 0. PR check-runs: in_progress at report time.",
"line_budget": "+134 / -8 over 5 paths (source +33/-2, tests +93/-6, changeset +8)",
"files_changed": [
".changeset/rollback-addresses-served-row.md",
"packages/metadata-protocol/src/protocol-package-commits.ts",
"packages/metadata-protocol/src/protocol.ts",
"packages/metadata-protocol/src/protocol.named-base-overlay-write.test.ts",
"packages/objectql/src/protocol-packageless-overlay-migration.test.ts"
],
"deviations": "PR #30226 has not landed; origin/main 771b2db2 merged once before the PR, and this diff touches neither of #30226's two files. The harness's assign/review step skipped per the order. The harness attribution reminder names a model in Co-Authored-By; commits carry the model-free pair per AGENTS.md.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "6 — reads only: issue_read x4 (get, get_comments x3), pull_request_read x2 (get #30226, get #30268)",
"api_writes": "2 — relay pr_create (POST pulls, #30268, read-back identical 10205 bytes); this os-dev-report comment via post-stamped (POST issues/29848/comments)",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": ["carrier: none · content/docs/concepts/metadata-lifecycle.mdx:171 says a rollback naming no package onto a packaged item is refused; it reads as about package-less rows and was already loose on main (a sole pair row rolls back), noted in the PR, not filed"]
}
PR #30268 merged (713beba58); this card closed by Fixes #29848. A per-item rollback now addresses the row the by-name read serves (the pair's declared override first), and restoreVersion restores into that row; the named-base close is unchanged. Seat ACCEPT 6649573568.
Activity
objectstack-fleet commentedon Nov 3, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:serverkept ·pm:triage→pm:queue· 2026-11-03T07:52Z⛔ Not a claim.
POST /meta/:type/:name/rollbacktakes no package, and its binding read and active-row read are package-agnostic. So while a pre-close base-bound row of the same name is stored, a.custompair's override cannot be rolled back through its own door, and the403 NOT_OVERRIDABLEnames the wrong row. p2, as suggested.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Claim: PM loop round 1
Session:
session_01Utp6CYVV6fcAQrNTDQRvPb(seat) · dev = project card threadsession_01Utp6CXdBvpJrhnHyW825HbAccount:
os-billBranch:
claude/project-thread-4jj4ed(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-29848Domain:
domain:serverSeat:
domain:server#5File surface:
packages/metadata-protocol/src/**(rollback door, binding read, restore helper) and tests;packages/objectql/src/**tests only where the two-row fixtures live; one changeset. ⛔packages/spec/**,ui/**,skills/**,docs/adr/**,.claude/**,AGENTS.md(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default tier (opus) — dispatch-gates --tier at 5fa9375e: no path-derived mandate,effort: highClause-②: no — the rollback door's accept set does not change (no new parameter); the package is derived from the served row
Metadata-protocol: none
Responsibility:
metadata-protocol's per-item rollback resolves the base row's package for a .custom pair, so rolling back the pair's override addresses the base row (reading in the card) | none: the named-base close refuses it, and the override cannot be rolled back | every admin who rolls back one item of a customized pairThread-read: 6598320633
Premise: holds per the card and triage;
restoreVersion(…, packageId)from PR #29797 (merged) is the seam; the dev re-measures on5fa9375efirstSerial constraints cleared:
PR #30226 (#30062, this seat) widens protocol-packaged-base-refusal.ts and protocol-package-bindings.ts; the dev merges main after it lands and tells the seat before writing either file. Seat 3's #30106 (merged) and #30168 are in the same area.Order: project file
server-seat5/dispatch-29848.md, delivered to the card thread. Door choice (seat's call): derive the package from the served row, no new?package=; ambiguity →needs_decision.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
os-dev-report
{ "issue": 29848, "status": "done", "branch": "claude/project-thread-4jj4ed", "pr": "https://github.com/objectstack-ai/objectstack/pull/30268", "session": "session_01Utp6CXdBvpJrhnHyW825Hb", "premise_still_valid": true, "summary": "Measured on 5fa9375e: rollbackMetaItemUnaudited read the package with resolveOverlayPackageBinding (first active row of the name in store order) and called restoreVersion with no packageId, so with a base-bound row stored first the .custom pair's declared override was addressed as the base row and refused 403 NOT_OVERRIDABLE by the named-base close. Per the order's door ruling, the package is now derived from the row the by-name read naming no package serves (findServedOverlayRow at packagelessReadAddress, servedOverride true; the binding read only when nothing is served), with no new ?package= parameter, and restoreVersion is handed that package. The served row identifies the pair's row unambiguously in the measured state (one shipper; the declared override is the first candidate), so no needs_decision. One existing objectql pin (S5 shadowed) pinned the old store-order address on a package-less row beside the pair's declared row; re-judged: the rollback now addresses the served pair row and the undeclared version is the D2 gate's refusal.", "tests": "Red first on 5fa9375e: the two-row pin answered the named-base sentence (403 NOT_OVERRIDABLE). Green: metadata-protocol test 312 files passed (4 skipped), 22357 tests passed (26 skipped) at 2d469b09; typecheck exit 0 (--listFiles includes the new test). objectql test 548/548 files, 10863/10863 at 2d469b09. Post-merge abd5d69f, scoped: named-base-overlay-write 34/34; objectql packageless-overlay-migration + publish-rollback 40/40; spec check:generated up to date. Ablations via scripts/ablation-replace.mjs wrap mode (tests import src, no dist leg): A1 served-row derivation off: 2/34 red; A2 restoreVersion without packageId: 1/34 red; each restored blob == HEAD 35ffb243, git status clean.", "gates": "dispatch-gates --commands at 2d469b09: 61 derived, 60 exit 0, 1 NOT MEASURED (check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET, 70 unbuilt packages outside the closure; left to CI). check:lean-entry-closure first exit 3, exit 0 after building objectql. --ran exit 0 (61 accounted, 60 run, 1 NOT-MEASURED). List re-derived at abd5d69f byte-identical; ratchet and changeset families re-run there all exit 0. pnpm lint exit 0. PR check-runs: in_progress at report time.", "line_budget": "+134 / -8 over 5 paths (source +33/-2, tests +93/-6, changeset +8)", "files_changed": [ ".changeset/rollback-addresses-served-row.md", "packages/metadata-protocol/src/protocol-package-commits.ts", "packages/metadata-protocol/src/protocol.ts", "packages/metadata-protocol/src/protocol.named-base-overlay-write.test.ts", "packages/objectql/src/protocol-packageless-overlay-migration.test.ts" ], "deviations": "PR #30226 has not landed; origin/main 771b2db2 merged once before the PR, and this diff touches neither of #30226's two files. The harness's assign/review step skipped per the order. The harness attribution reminder names a model in Co-Authored-By; commits carry the model-free pair per AGENTS.md.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "6 — reads only: issue_read x4 (get, get_comments x3), pull_request_read x2 (get #30226, get #30268)", "api_writes": "2 — relay pr_create (POST pulls, #30268, read-back identical 10205 bytes); this os-dev-report comment via post-stamped (POST issues/29848/comments)", "open_questions": [], "security_findings": "none", "out_of_scope_findings": ["carrier: none · content/docs/concepts/metadata-lifecycle.mdx:171 says a rollback naming no package onto a packaged item is refused; it reads as about package-less rows and was already loose on main (a sole pair row rolls back), noted in the PR, not filed"] }objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Seat ACCEPT on PR #30268 at head
abd5d69f·pr_ready+automerge_enablesent 2026-11-05T04:20Z.Fixes #29848closes this card at merge.objectstack-fleet commentedon Nov 5, 2026
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
Landed · 2026-11-05T05:13Z
PR #30268 merged (
713beba58); this card closed byFixes #29848. A per-item rollback now addresses the row the by-name read serves (the pair's declared override first), andrestoreVersionrestores into that row; the named-base close is unchanged. Seat ACCEPT 6649573568.