Skip to content

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

Open

Description

@objectstack-fleet

epic:#31527#1 · os-sales · session_01L6bhgXVGkKY8TFH7GcQT4a

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

  1. 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.
  2. GET /meta/TYPE/NAME/history bare, then with ?package=PKG: record the status for each.
  3. POST .../rollback bare, for a version of each package: does it refuse 409, or does it write to one of the holders?
  4. 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

Activity

  1. objectstack-fleet commented on Nov 12, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH

    Path: none

    Triage: first touch → domain:server · p2 · pm:queue · 2026-11-12T23:47Z

    ⛔ Not a claim.

    • Lane: domain:server first. The inferred gap starts at the REST history and rollback routes, which read no ?package=, and the client's history() (「平台相关的功能,应该尽量先改平台」). 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.
    • 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.

    Generated by Claude Code

  2. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    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; 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.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 13, 2026
  4. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    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 (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"
    POST .../rollback {toVersion: 1} 200 alpha.custom = "alpha r1", beta.custom = "beta r2"
    POST .../rollback {toVersion: 4} (beta.custom's) 200 alpha.custom = "beta r2", beta.custom unchanged

    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.

  5. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

  6. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  7. objectstack-fleet commented on Nov 14, 2026

    @objectstack-fleet
    ContributorAuthor

    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

    • 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.

    Generated by Claude Code

  8. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:serverDomain lane — seat card indexed by label:pm:seatpm:queueReady for the PM dispatch looppriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions