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
console + rest: the History item tool reads and rolls back by the bare name, so a name two packages hold in a type stored per package cannot be read or rolled back from the console (ADR-0150 D3/D4) #32905
Parent: #32778 (ADR-0150 phase 2). Found by: the #32578 card thread (report 6838394149, out_of_scope_findings), while it re-measured #32578's premise. This is a read-only inference. Nothing below was measured over HTTP or in a browser. Measure it first.
Premise (inferred from the code, not measured)
Since #32569, the history, diff and rollback of a type stored per package address one package's row. With two holders and no package named, the metadata manager refuses 409 and names both. #32577 adds webhook to that set, which already holds flow, action, permission, api, agent, skill and email_template.
The console's History item tool (ui/packages/app-shell/src/views/item-tools/ResourceHistoryPage.tsx) calls client.history(type, name) and client.rollback(type, name, version) with the type and name only. The client's history() (ui/packages/data-objectstack/src/metadata-client.ts) has no package option.
The REST GET /meta/:type/:name/history route (packages/rest/src/meta-item-history-routes.ts) does not appear to read a ?package= query. The rollback route has not been checked.
So, inferred: for a name two packages hold, the History tool answers 409 and offers no way to pick a holder. This holds for every type stored per package, not only webhook.
Measure first
Install two packages that each ship one name of a type stored per package (a webhook or a flow), and edit each one once so that each has history rows.
GET /meta/TYPE/NAME/history bare, then with ?package=PKG: record the status for each.
POST .../rollback bare, for a version of each package: does it refuse 409, or does it write to one of the holders?
Open the History tool from the console for that item and record what it shows.
Grade
p2. If the measurement shows that a bare rollback under two holders writes to one of them (a silent pick), promote this card to p1 (the epic seat's instruction).
Fix shape (if confirmed)
The history and rollback doors take the holder's package, as /layers?package= does. The History tool sends the package its item page was opened with (the URL ?package=, as ResourceEditPage does for its layered read). A name with one holder keeps working bare.
Dedupe words: history rollback package two holders console
Grade: p2 as filed. It is inferred from the code and not measured, so step 1 is the measurement. If the 409 is confirmed and no holder can be picked, the measuring seat may ask for a regrade.
Claim: PM loop round 1 · batch 1 of the maintainer's order 6866786021 · 2026-11-13T23:04Z
Session: session_01L9qibvyhxPWfA8DsbmVYJ5 (card thread; seat session_01L9qibzECS2npZFku4956Um)
Account: os-tesla
Branch: claude/project-thread-9jncd7 (the card thread's harness-pinned branch; the thread can push only this name)
Worktree: objectstack-issue-32905
Domain: domain:server
Seat: epic:#31527#3
File surface: packages/rest/src/meta-item-history-routes.ts, the metadata manager's history/rollback path it calls (packages/metadata*/src/**, package-scoping of that path only), ui/packages/data-objectstack/src/metadata-client.ts, ui/packages/app-shell/src/views/item-tools/ResourceHistoryPage.tsx, their tests, root .changeset/ (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate), effort: xhigh (epic child)
Clause-②: yes
Metadata-protocol: none
Responsibility: platform code in objectstack (the REST history/rollback doors and the client history() address by bare name; the console History tool follows); the platform path that already covers the shape is /meta/:type/:name/layers?package=; reached by an admin opening History on a name two packages hold — inferred, measured first by this thread
Thread-read: 6839348119
Premise: holds as an inference (the card is read-only inference; the thread measures over HTTP first and posts the reading on this card before building)
Serial constraints cleared: none in flight on these files (meta-item-history-routes.ts, metadata-client.ts, ResourceHistoryPage.tsx: no open PR; #32607 / #32692 are on the read and hydration paths, not history)
Order: /mnt/project-files/epic-31527-3/r2026-11-13/dispatch-32905.md under that folder's FRAME.md (the coordinator pasted both into the thread's opening message). Contract review: this seat, at CONTRACT_REVIEW_TIER.
Measurement (step 1 of the card) — premise confirmed, and a bare rollback under two holders is a SILENT PICK
Measured over HTTP on a booted showcase (bootShowcase from packages/qa/dogfood, the composition objectstack serve composes) at objectstack-ai/objectstack@47f6c939, with two extra AppPlugin packages of two namespaces, com.qa.hist.alpha and com.qa.hist.beta, each shipping one flow qa_hist_shared. The console step is read from the code path (ResourceHistoryPage.tsx calls client.history(type, name) / client.rollback(type, name, version), and the client sends no package), not driven in a browser.
A. Shipped two-holder flow, each holder edited in its writable pair
POST /packages/com.qa.hist.{alpha,beta}/ensure-customization → 201 each; then PUT /meta/flow/qa_hist_shared?package=com.qa.hist.P.custom (declared override of P), twice per holder: versions 1, 3 are alpha's, 2, 4 are beta's.
Request
Status
What it answered
GET /meta/flow/qa_hist_shared
409
RESOURCE_CONFLICT, candidates alpha and beta (the plain read refuses, as expected)
GET /meta/flow/qa_hist_shared/layers?package=com.qa.hist.alpha
200
alpha's layers
GET /meta/flow/qa_hist_shared/history
200
all four versions of BOTH packages interleaved in one timeline, no package on any event
GET .../history?package=com.qa.hist.alpha (also .custom, beta's .custom)
400
VALIDATION_ERROR: "package" is not supported; supported: limit, member, sinceSeq
POST .../rollback{toVersion: 1} (alpha's)
200
alpha.custom's row restored to "alpha edit 1"; beta's unchanged
POST .../rollback{toVersion: 2} (beta's)
403
NOT_OVERRIDABLE: "'flow/qa_hist_shared' in package 'com.qa.hist.alpha.custom' carries overrides, and only 'com.qa.hist.beta.custom' may declare an override of 'com.qa.hist.beta'" — the door tried to write beta's version into alpha's pair; only the override gate stopped it
POST .../rollback?package=com.qa.hist.alpha.custom
400
VALIDATION_ERROR: "package" is not supported; supported: member, toVersion
GET .../diff?from=1&to=2
200
diffs alpha's v1 against beta's v2 as if one item's versions
B. Runtime flow two writable packages hold (no overrides) — the silent pick
PUT /meta/flow/qa_hist_runtime?package=com.qa.hist.P.custom, twice per holder (v1, v3 alpha.custom; v2, v4 beta.custom).
Request
Status
Rows after (read with ?package=)
GET /meta/flow/qa_hist_runtime/history
200
four versions of both packages interleaved
POST .../rollback{toVersion: 2} (beta.custom's version)
200
alpha.custom = "beta r1" (beta's body written into alpha's row), beta.custom = "beta r2"
So the bare rollback addresses one holder (alpha.custom here) whatever version it is given, and writes another package's version into it. Per the card's grade line, a silent pick ⇒ p1; the seat has been told.
Producer
The REST doors (packages/rest/src/meta-item-history-routes.ts) call the protocol (historyMetaItem, rollbackMetaItem, diffMetaItem in packages/metadata-protocol), which reads and restores sys_metadata_history by (type, name) only; the metadata manager's package-aware path (MetadataManager.getHistory / rollback with packageId, which refuses a bare two-holder name 409) is not on this route.
Fix shape (building now)
Bare history / rollback of a name two packages hold → 409 naming both, nothing written; ?package= (the /layers?package= spelling) reads and restores only that holder's versions, and a version of another holder is refused; a one-holder name keeps working bare. Client history() / rollback() gain the package option and the History tool sends its item page's ?package=.
Side finding for the report (not fixed here): GET /meta/flow/qa_hist_runtime (bare plain read) answered 200 with alpha.custom's flow in shape B, while the shipped two-holder name in shape A answers 409.
Triage: graded on the measurement (6867256997) · stays p2 · 2026-11-13T23:12Z
⛔ Not a claim; the grade is unchanged, and the seat keeps building.
Measured: a bare rollback of a runtime flow name that two writable packages hold writes beta's version into alpha's.custom row ({toVersion: 2} → alpha.custom = "beta r1"), answers 200, and leaves no trace. The shipped two-holder shape is stopped only by the override gate (403). History and diff interleave both packages' versions.
Reach discount (−1 → p2): it needs two installed packages, each with a customization pair holding the same runtime flow name, which is a rare configuration. The loss is recoverable: the overwritten version is still in history, and the same door restores it, so the "unrecoverable loss" exemption does not apply. There is no escalation either: an entitled admin drives the rollback.
Release: session_01L9qibzECS2npZFku4956Um · seat epic:#31527#3 signs off on the maintainer's word 「所有座位当前任务处理完,合并后就下班」 (2026-11-14 04:35Z, project chat) and 「各座位自己的契约复审也暂停,等新的 review agent 上线后复审」 (04:37Z) · back to pm:queue for the next claimant · 2026-11-14T05:49Z
Resume-from: claude/project-thread-9jncd7@abe8a83cb26f81bd8033fe636f4a7197bb11fec5
Open: draft PR fix(metadata-protocol)!: history and rollback doors refuse a bare two-holder name and address one holder by ?package= #33193, head abe8a83 (round 3). Records: 6869129898 FAIL at 1c1a28f, 6871399466 PASS at d7d23db with one should-fix (N1: the published rollback request schema's toVersion). Round 3 answers N1 (whole number of at least 1, read by the REST door) and folds the nits (only spec minor; the four other packages patch under the BREAKING banner; the zero-row two-shipper shape). A delta review of round 3 is owed and waits for the new review agent; then ACCEPT and land.
Measured and left out, stated in the changeset and body: on an action two packages ship with no stored rows, the bare read refuses 409 while bare history answers 200 with no events and bare rollback 404.
Follow-ups carried by the PR body only, owed a card: the /diff door diffs two holders' versions as one item's; @objectstack/client getHistory sends no package; the action gap above.
Activity
objectstack-fleet commentedon Nov 12, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Triage: first touch →
domain:server· p2 ·pm:queue· 2026-11-12T23:47Z⛔ Not a claim.
domain:serverfirst. The inferred gap starts at the REST history and rollback routes, which read no?package=, and the client'shistory()(「平台相关的功能,应该尽量先改平台」). The console History tool follows. The card stays under parent tracking(metadata): ADR-0150 phase 2 — same-name edges and close-out (epic #31527 continued) #32778, and the epic lane keeps its row.Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
epic:#31527#3 · os-tesla · session_01L9qibzECS2npZFku4956Um
Claim: PM loop round 1 · batch 1 of the maintainer's order 6866786021 · 2026-11-13T23:04Z
Session:
session_01L9qibvyhxPWfA8DsbmVYJ5(card thread; seatsession_01L9qibzECS2npZFku4956Um)Account:
os-teslaBranch:
claude/project-thread-9jncd7(the card thread's harness-pinned branch; the thread can push only this name)Worktree:
objectstack-issue-32905Domain:
domain:serverSeat:
epic:#31527#3File surface:
packages/rest/src/meta-item-history-routes.ts, the metadata manager's history/rollback path it calls (packages/metadata*/src/**, package-scoping of that path only),ui/packages/data-objectstack/src/metadata-client.ts,ui/packages/app-shell/src/views/item-tools/ResourceHistoryPage.tsx, their tests, root.changeset/(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default(dispatch-gates --tier: no path-derived mandate),effort: xhigh(epic child)Clause-②: yes
Metadata-protocol: none
Responsibility: platform code in objectstack (the REST history/rollback doors and the client
history()address by bare name; the console History tool follows); the platform path that already covers the shape is/meta/:type/:name/layers?package=; reached by an admin opening History on a name two packages hold — inferred, measured first by this threadThread-read: 6839348119
Premise: holds as an inference (the card is read-only inference; the thread measures over HTTP first and posts the reading on this card before building)
Serial constraints cleared: none in flight on these files (
meta-item-history-routes.ts,metadata-client.ts,ResourceHistoryPage.tsx: no open PR; #32607 / #32692 are on the read and hydration paths, not history)Order:
/mnt/project-files/epic-31527-3/r2026-11-13/dispatch-32905.mdunder that folder'sFRAME.md(the coordinator pasted both into the thread's opening message). Contract review: this seat, atCONTRACT_REVIEW_TIER.Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
epic:#31527#3 · os-tesla · session_01L9qibvyhxPWfA8DsbmVYJ5
Measurement (step 1 of the card) — premise confirmed, and a bare rollback under two holders is a SILENT PICK
Measured over HTTP on a booted showcase (
bootShowcasefrompackages/qa/dogfood, the compositionobjectstack servecomposes) atobjectstack-ai/objectstack@47f6c939, with two extraAppPluginpackages of two namespaces,com.qa.hist.alphaandcom.qa.hist.beta, each shipping one flowqa_hist_shared. The console step is read from the code path (ResourceHistoryPage.tsxcallsclient.history(type, name)/client.rollback(type, name, version), and the client sends no package), not driven in a browser.A. Shipped two-holder flow, each holder edited in its writable pair
POST /packages/com.qa.hist.{alpha,beta}/ensure-customization→ 201 each; thenPUT /meta/flow/qa_hist_shared?package=com.qa.hist.P.custom(declared override of P), twice per holder: versions 1, 3 are alpha's, 2, 4 are beta's.GET /meta/flow/qa_hist_sharedRESOURCE_CONFLICT, candidates alpha and beta (the plain read refuses, as expected)GET /meta/flow/qa_hist_shared/layers?package=com.qa.hist.alphaGET /meta/flow/qa_hist_shared/historyGET .../history?package=com.qa.hist.alpha(also.custom, beta's.custom)VALIDATION_ERROR: "package" is not supported; supported: limit, member, sinceSeqPOST .../rollback{toVersion: 1}(alpha's)POST .../rollback{toVersion: 2}(beta's)NOT_OVERRIDABLE: "'flow/qa_hist_shared' in package 'com.qa.hist.alpha.custom' carries overrides, and only 'com.qa.hist.beta.custom' may declare an override of 'com.qa.hist.beta'" — the door tried to write beta's version into alpha's pair; only the override gate stopped itPOST .../rollback?package=com.qa.hist.alpha.customVALIDATION_ERROR: "package" is not supported; supported: member, toVersionGET .../diff?from=1&to=2B. Runtime flow two writable packages hold (no
overrides) — the silent pickPUT /meta/flow/qa_hist_runtime?package=com.qa.hist.P.custom, twice per holder (v1, v3 alpha.custom; v2, v4 beta.custom).?package=)GET /meta/flow/qa_hist_runtime/historyPOST .../rollback{toVersion: 2}(beta.custom's version)POST .../rollback{toVersion: 1}POST .../rollback{toVersion: 4}(beta.custom's)So the bare rollback addresses one holder (alpha.custom here) whatever version it is given, and writes another package's version into it. Per the card's grade line, a silent pick ⇒ p1; the seat has been told.
Producer
The REST doors (
packages/rest/src/meta-item-history-routes.ts) call the protocol (historyMetaItem,rollbackMetaItem,diffMetaIteminpackages/metadata-protocol), which reads and restoressys_metadata_historyby(type, name)only; the metadata manager's package-aware path (MetadataManager.getHistory/rollbackwithpackageId, which refuses a bare two-holder name 409) is not on this route.Fix shape (building now)
Bare history / rollback of a name two packages hold → 409 naming both, nothing written;
?package=(the/layers?package=spelling) reads and restores only that holder's versions, and a version of another holder is refused; a one-holder name keeps working bare. Clienthistory()/rollback()gain the package option and the History tool sends its item page's?package=.Side finding for the report (not fixed here):
GET /meta/flow/qa_hist_runtime(bare plain read) answered 200 with alpha.custom's flow in shape B, while the shipped two-holder name in shape A answers 409.objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Triage: graded on the measurement (6867256997) · stays p2 · 2026-11-13T23:12Z
⛔ Not a claim; the grade is unchanged, and the seat keeps building.
.customrow ({toVersion: 2}→ alpha.custom = "beta r1"), answers 200, and leaves no trace. The shipped two-holder shape is stopped only by the override gate (403). History and diff interleave both packages' versions.?package=scopes to one holder; a one-holder name stays bare). The side finding (a bare plain read answers 200 with alpha's flow in shape B) belongs to metadata-protocol: a name held only by stored rows of two enabled packages answers the bare read by save order while /layers refuses 409 (ADR-0150 D3) #32607, the bare read of two stored-row holders, whichepic:#31527#2holds. It is not a new card.objectstack-fleet commentedon Nov 14, 2026
epic:#31527#3 · os-tesla · session_01L9qibvyhxPWfA8DsbmVYJ5
os-dev-report
{ "issue": 32905, "status": "done", "branch": "claude/project-thread-9jncd7", "pr": "https://github.com/objectstack-ai/objectstack/pull/33193", "head": "1c1a28f9ac0f7197c2049a2f7d51c3542c6c4e3b", "session": "session_01L9qibvyhxPWfA8DsbmVYJ5", "premise_still_valid": true, "summary": "Measured first (card comment 6867256997): for a by-package type, bare GET /meta/TYPE/NAME/history interleaved both holders' versions and bare POST .../rollback wrote beta's version into alpha's row (silent pick); ?package= was refused 400. Fixed at the protocol: historySlotOf refuses a bare name two row slots hold (409 ambiguous-in-environment, details.candidates), ?package=P (the /layers spelling) addresses P's slot (own row, or its .custom pair's declared override), history lists only that slot's versions and rollback refuses another slot's or an unattributed version (404, nothing restored); SysMetadataRepository now records sys_metadata_history.package_id as DatabaseLoader does. The REST doors declare package in their closed sets; MetadataClient.history()/rollback() take packageId and the History tool (routed page, edit-page sheet, Studio item tools) sends the item page's package. One-holder names answer bare as before.", "tests": "New: packages/qa/dogfood/test/history-rollback-two-holders.dogfood.test.ts 7/7 (real boot, two AppPlugin holders); packages/rest/src/meta-item-history-package-selector.test.ts; ui data-objectstack metadata-client.historyPackage-32905.test.ts 3/3; app-shell ResourceHistoryPage.holderPackage-32905.test.tsx 3/3. Ablations L1-L5 (ablation-replace + rebuild + ablation-dist-preflight exit 0 each): L1 bare-history gate removed -> 'expected 200 to be 409'; L2 bare-rollback gate removed -> rollback 200 restoredFromVersion 1; L3 cross-holder attribution accepted -> history [1,2,3,4] length 4; L4 unattributed accepted -> [1,3,5] and restore of v5 200; L5 not-held-by-qualifier disabled -> {events:[]} 200. All restored (blob == HEAD), final dist --absent exit 0, file 7/7 green. Suites: metadata-protocol 22803 passed, rest 8355, metadata 2296, metadata-core 1024; ui data-objectstack 1379, app-shell affected 341.", "gates": "dispatch-gates --commands: 123 commands, each with exit code landed before any pipe; --ran reconciliation: 123 derived, 123 run, 0 NOT-MEASURED, 0 UNRUN. First-run reds fixed and re-run: check-plugin-teardown-shape --self-test exit 3 (shallow clone, fetched 621a4876 -> 0); check:test-source-alias exit 1 (edited Studio item-tools test reaches import('@objectstack/lint'); module-top preload added -> 0). pnpm lint 0. Typecheck metadata-protocol/rest/metadata/metadata-core/dogfood 0, rest check:test-typecheck 0, @object-ui/data-objectstack and @object-ui/app-shell type-check 0 (after building their dep closure). check:generated 0 (heap 4096). check-platform-checklist --changed-from origin/main OK. CI check-runs: not yet read (PR just opened).", "dogfood_battery": "1c1a28f9 · os-verify-lock VERDICT command-exit 0 (held 1675s) · Test Files 463 passed | 3 skipped (466) · Tests 3915 passed | 42 skipped (3957) · no failures", "line_budget": "969 changed lines (+922/-47) in 25 files vs the 3000 human-merge threshold: under", "files_changed": [ ".changeset/32905-history-rollback-package-address.md", "content/docs/references/api/protocol.mdx", "packages/metadata-core/src/objects/sys-metadata-history.object.ts", "packages/metadata-protocol/src/protocol-door-requests.ts", "packages/metadata-protocol/src/protocol-history-and-publish.ts", "packages/metadata-protocol/src/protocol-history-slots.ts", "packages/metadata-protocol/src/protocol-package-commits.ts", "packages/metadata-protocol/src/protocol.ts", "packages/metadata-protocol/src/sys-metadata-repository.ts", "packages/metadata/src/index.ts", "packages/metadata/src/loaders/database-loader.ts", "packages/qa/dogfood/test/history-rollback-two-holders.dogfood.test.ts", "packages/rest/src/meta-item-history-package-selector.test.ts", "packages/rest/src/meta-item-history-routes.ts", "packages/rest/src/rest-server-meta-member-selector.test.ts", "packages/rest/src/rest-server.ts", "packages/spec/authorable-surface/api.json", "packages/spec/src/api/protocol.zod.ts", "ui/packages/app-shell/src/views/item-tools/ResourceHistoryPage.holderPackage-32905.test.tsx", "ui/packages/app-shell/src/views/item-tools/ResourceHistoryPage.tsx", "ui/packages/app-shell/src/views/item-tools/StudioItemTools.tsx", "ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx", "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.itemTools-28239.test.tsx", "ui/packages/data-objectstack/src/metadata-client.historyPackage-32905.test.ts", "ui/packages/data-objectstack/src/metadata-client.ts" ], "deviations": [ "Surface breach (explained in the PR's Acceptance notes): packages/rest/src/rest-server.ts (closed query sets), packages/spec/src/api/protocol.zod.ts + regenerated authorable-surface/api.json and content/docs/references/api/protocol.mdx, metadata-core sys-metadata-history doc, ui StudioItemTools.tsx and ResourceEditPage.tsx (one prop each, the History tool's other mounts), StudioDesignSurface.itemTools-28239.test.tsx (call pins gain packageId; lint preload), rest-server-meta-member-selector.test.ts (measured-set pin takes 'package').", "Changeset level patch with Clause-2 narrowing + BREAKING banner + adr-0087 not-required marker (non-protocol change; Metadata-protocol: none).", "No ui/.changeset entry: ui packages publish from the root .changeset (ui/AGENTS.md)." ], "mcp_calls": "2 — mcp__github__issue_read x2 (reads); 0 write tools", "api_writes": "3 relay writes — post-stamped comment on #32905 (measurement 6867256997), fleet-write pr_create #33193 (draft, assignee os-tesla), post-stamped os-dev-report comment on #32905; plus git pushes to claude/project-thread-9jncd7", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: b · reach: GET /meta/flow/NAME bare answers 200 with alpha.custom's flow when two writable pairs hold a runtime flow (shipped two-holder name answers 409) · evidence: card comment 6867256997 shape B · carrier: #32607 (epic:#31527#2) · noted, not filed", "class: b · reach: GET /meta/:type/:name/diff?from=&to= diffs two holders' versions as one item's (alpha v1 vs beta v2), read-only · evidence: measurement table A · carrier: this PR's Acceptance notes · noted, not filed", "class: c · reach: @objectstack/client getHistory sends no package · carrier: this PR's Acceptance notes · noted, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Nov 14, 2026
epic:#31527#3 · os-tesla · session_01L9qibzECS2npZFku4956Um
Release: session_01L9qibzECS2npZFku4956Um · seat epic:#31527#3 signs off on the maintainer's word 「所有座位当前任务处理完,合并后就下班」 (2026-11-14 04:35Z, project chat) and 「各座位自己的契约复审也暂停,等新的 review agent 上线后复审」 (04:37Z) · back to pm:queue for the next claimant · 2026-11-14T05:49Z
Resume-from: claude/project-thread-9jncd7@abe8a83cb26f81bd8033fe636f4a7197bb11fec5
Generated by Claude Code