Skip to content

feat(cli): the organization-ownership ceremony carries the sys_metadata organization layer into the environment, and a choice deletes only the rows the reviewed plan listed (ADR-0131 D10, C7b S1) - #23452

Merged
objectstack-fleet[bot] merged 19 commits into
mainfrom
claude/issue-15211-c7b-promotions
Oct 14, 2026

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Part of #15211 (stage S1 of C7b; the other stages are this card's S4, #23437, and the split cards #23429 and #23430)
Fixes #23398

Clause-②: yes (widening)

What

os migrate organization-ownership --apply carries the sys_metadata organization layer into the environment (ADR-0131 D10; rulings A on #22011 and #22008; ruling 6163114098, Q1 B and Q6 A; seat answer Q2 A; review rulings in 6165835673).

  • The plan gains metadataConflicts, and --apply gains --metadata-choice ROW_ID. That is the widening, so cli is minor. Nothing is removed. This PR changes only @objectstack/cli.
  • A new step, metadata-promotion, after the authored conversion:
    • Form withdrawals are carried first, under every posture, and fail-closed. Every view of that name the environment serves still open takes an organization's withdrawal, stored or code-served, in every package. It is saved through the metadata door into the view's own package, and any organization's withdrawal wins.

    • Under single, an organization customization whose key no other row holds is promoted in place, with its history. This covers view, dashboard, report, translation and email_template; the key is type, name, package and bundle member.

    • A key held by more than one row is a conflict. Under single it blocks until --metadata-choice ROW_ID names one row. The key then ends with exactly one environment row defining the chosen body:

      • where the key has an environment row, that row takes the body in place, by one UPDATE. Its version is the chosen row's after its lineage is folded, so it names that body's snapshot. Not through the door: the door writes a packaged item's customization into the package's slot, never onto a package-less row of the key (measured; see the dogfood);
      • where it has none, the chosen organization row is promoted in place.

      Any withdrawal another row of the key holds is kept on that body, and also saved through the door (its history, and the package's slot). The row is read back, the choice is recorded in the checkpoint, and only then are the key's organization rows deleted, in one statement, by id. A rerun after a crash anywhere in this accepts the same --metadata-choice and finishes.

    • Under a wall: nothing is promoted unless named, and nothing is ever deleted (ruling 6163114098, Q6 A's text, applied by the seat). A conflict nobody chose for is reported, not blocking, and its rows hold the column.

  • History (Q2 A): event_seq and every (type, name, version) unique environment-wide. The door numbers a save from history in its own scope, so:
    • A promoted row takes its history with it. Its organization's lineage of the item moves above the environment's numbers (wholly above its own range too, because 17.x keeps unique indexes on both counters) and becomes environment history. Every later environment save, the ceremony's or the runtime's, numbers above it, and the row's version still names its own snapshot.
    • A new step, history-renumber, runs just before column-drops, on every apply, even after a checkpoint. It lays out every lineage still tagged to an organization above the environment's: event ranges by one offset each, with their commit ranges; version lineages with the organization's rows.
      • Under single, that history then becomes the environment's too.
      • Under a wall it stays its organization's: private, renumbered before the column can go.
  • A choice deletes only rows the reviewed plan listed. One mechanism for both flags, on S4's ReviewedPlan. The plan file's metadataConflicts and templateConflicts are read into the reviewed plan. Before anything is written, each chosen conflict must hold exactly the rows the file listed for it, or the apply refuses with plan-conflict-mismatch, naming the conflict, the rows added and the rows gone.
  • 17.x databases plan. sys_metadata.bundle_member first shipped in 18.0.0-next.0, and the ceremony boots with schema changes held. So environment-overlay-duplicates groups by bundle_member only where the column exists; on a 17.x table the member reads as NULL, the overlay key's folded part.
  • The anonymous form doors keep their organization read. By the seat's ruling, its deletion moves to S2 (feat(metadata-core,metadata-protocol,runtime): #15211 S2+S5 — the sys_metadata family drops organization_id, and the v18 boot refusal lands with it (C7b, split for seat 1) #23429), which lands with S5's boot refusal, so no release serves an organization-only withdrawal open. rest and metadata-protocol are back to main.

Merge round (review 6173411667 PASS at d02b699d65; head 82bd7e0b80, base: main 222293f60, S3 merged)

  • Merge. main, now carrying S3 (feat(plugin-security,cli,spec)!: retire sys_user_permission_set.permission_set_id; the grant key is unique per organization; the boot grant-name backfill is deleted (C7b S3) #23503), is merged through os-regen-merge.sh.
    • Two conflicts:
      • organization-ownership-plan.ts: TablePlan gains both metadataConflicts and S3's grants, and both types are kept;
      • organization-ownership-apply.test.ts: both sides' new describes are kept.
    • The script's rerun found no generated path to restore.
    • The uniqueness gate stands first in S3's rewritten runColumnDrops. The gate pin and the two column-drop doubles now use S3's { table, column } port shape.
    • The cli dependency closure was rebuilt on the merged tree: S3's plugin-security pin reads its dist.
  • Should-fix (S1). The door's lock verdict is asked once, before the history fold, the promotion and the in-place write, so a refusal leaves nothing written.
    • Pin: ⭐ the lock is asked before the history fold and the promotion: under a wall, a refusal leaves history and rows as they were, with a history table and isolated.
    • Ablation: removing the early check reddens it and the locked-item pin.
  • Nit (S2). The door save after the in-place write sends the row as written, with its stored lock family.
    • Pin: the door save after the in-place write sends the row as written, its stored lock family kept.
    • Ablation: sending body reddens it.
  • history-renumber keeps blocking on a duplicate, by the seat's call. Its refusal names the table, each item holding the duplicated number (by row) and the remedy. Both messages are pinned: the event_seq one verbatim, the version one by its parts.
  • Checks on 82bd7e0b80:
    • cli typecheck exit 0;
    • the ceremony suites (9 files, S3's included): 146/146;
    • narrowed lint of the 8 changed .ts files: 0 errors, 0 warnings;
    • main merges clean again.
    • The integration verdict is CI's Test Core on this head.

Delta round 2 (review 6172666364; head d02b699d65)

The seat's rulings, each fixed and pinned, each ablation red with the restore verified (blob == HEAD):

Item Fix Pin Ablation → red
N1 the fold landed on the organization's own events each fold's offset (event_seq and versions) is based on max(environment, the same organization's other items); excluding the lineage itself keeps a rerun idempotent ⭐ N1: folding one item of an organization never lands on that organization's other events, on a rerun too (the review's smallest case, 17.x's unique indexes, a crash after the first fold's move, then the rerun) base on the environment alone → the N1 pin
N2 checksum the in-place UPDATE restamps checksum with hashSpec ⭐ restamps checksum from the written body restamp removed → that pin
N2 stored _lock keys the row's stored _lock family is kept, whatever the chosen body carries ⭐ keeps the lock family the environment row stores carry removed → that pin
N2 the door's lock check a new port asks the item read's editable (the save door's own verdict) before the write; a locked item blocks the conflict, nothing written or deleted ⭐ a locked item stays locked check removed → that pin
Wall with an environment row (ruled as built) each organization row left is a report line: "Remaining: … blocks column-drops on sys_metadata … Remedy: …" ⭐ the wall pin asserts the line
Uniqueness under a wall (ruled, on condition) history-renumber verifies event_seq and (type, name, version) uniqueness environment-wide on every apply and blocks naming any duplicate; column-drops refuses unless that verification passed in the same apply ⭐ column-drops never runs unless this apply's history-renumber verified uniqueness (two environment events sharing a number) verification removed → that pin
Bundle member in validateMetadataChoices the conflict key carries the member two conflicts of one item that differ only by bundle member are two conflicts, each chosen for member removed → that pin
Title and citation retitled (no rest, metadata-protocol or form doors); the fold fix is 917e0b8cc

Checks on d02b699d65:

  • cli typecheck exit 0. The ceremony suites (organization-ownership*, 8 files): 135/135. Narrowed lint of the 8 changed .ts files: 0 errors, 0 warnings.
  • Main merges clean (git merge-tree), so it is not merged.
  • The integration verdict is CI's Test Core on this head. The local tier was not re-run.
  • The previous head's local runs. On 468b74a09d, CI's Test Core passed (all 6 shards). I stopped the local sweep there for this round: 58 of 65 gates had run, all exit 0.

Verification of the previous round (head 468b74a09d, base: main f9b421716d, S4 merged)

Merge. main, carrying S4, is merged through scripts/pm/os-regen-merge.sh.

The binding (seat option A):

Pin Ablation → red
⭐ --metadata-choice: a row that joined the conflict since the review refuses the apply, naming it; nothing is written; a reviewed row gone since refuses too, and a plan file that lists no such conflict; and the unchanged group resolves metadata leg removed → both refusal pins
⭐ --template-choice: a row that joined the conflict since the review refuses the apply, naming it; nothing is written; and the unchanged group resolves template leg removed → the template pin
reviewedPlanFromFile reads both lists, keyed and sorted

The review round (6165835673) keeps its pins and ablations:

Finding Pin Ablation → red
F1 17.x refused plans a 17.x sys_metadata with no bundle_member column… filter removed → the pin and 7 more plan pins
F2 versions collide ⭐ organization-only history; ⭐ under a wall; ⭐ two organizations with no environment history fold removed → 2 ⭐; single fold removed → the two-organization pin; seq offset not wholly above → the organization-history pin
F3 two environment rows / rerun ⭐ org-over-environment choice (one UPDATE, no organization row cleared); ⭐ the packaged-item pin (below); ⭐ crash, then the same choice; a write that does not land deletes nothing in-place write sent through the door instead → both ⭐; read-back removed → the read-back pin; applied-choice acceptance removed → the crash pin
Wall ruling ⭐ a chosen row defines the environment row, nothing deleted; ⭐ two organizations, the other stays posture branch removed → both

Checks:

  • cli typecheck: exit 0. The ceremony suites (organization-ownership*, 8 files): 129/129. Narrowed lint of the 8 changed .ts files: 0 errors, 0 warnings.
  • The whole cli integration tier: CI's Test Core passed on that head (all 6 shards).
  • dispatch-gates: the local sweep on that head was stopped at 58 of 65, all exit 0, for the next round. The interim head b0093a3337 ran 65/65 at exit 0.
  • Dogfood as an operator, with the real os binary built from this head. One objectstack dev --seed-admin boot of the showcase, then stopped. I made the database a 17.x shape (bundle_member dropped; history keeps 17.x's unique (organization_id, event_seq) and (organization_id, type, name, version) indexes), and planted a conflict on showcase_inquiry.contact's package-less key: the organization's withdrawn copy (with only an organization history, 1–2) beside an open environment copy.
    1. The plan exits 0 and lists the conflict (env_copy, legacy_org_withdrawal).
    2. A row joined the conflict after the plan. --apply --plan-file plan.json --metadata-choice legacy_org_withdrawal exits 1, plan-conflict-mismatch: reviewed env_copy,legacy_org_withdrawal, current with joined_later. The database file is byte-identical, with no ceremony row.
    3. That row removed, the same invocation resolved the conflict. env_copy took the chosen body in place, withdrawal kept; its version is 4, the folded corg2 snapshot. The organization row is deleted, and the package's served copy carries the withdrawal. After the rewrite remedy, the resume ran history-renumber: no history is left tagged to an organization, and no duplicate exists. column-drops blocks on sys_metadata's declared column, which is S2's.
    4. On a reboot, both form doors answer 404, nothing landed, and a runtime save took event_seq and version 5, above everything, with no duplicate.
  • The dogfood found two defects this round, both fixed and pinned:
    • The fold's event_seq offset was smaller than a lineage's own span. 17.x's unique index refused it row by row. Fixed in 917e0b8cc, and its base corrected in this round's N1.
    • The door writes a packaged item's customization into the package's slot, so the conflict's package-less environment row never took the body. The read-back caught it and nothing was deleted. Fixed in 0162d47cd.

Acceptance notes

  • Deviations for the seat to ratify:
    1. Under single, when an organization row is chosen over the key's environment row, the environment row keeps its record and takes the chosen body in place, by one UPDATE, doing to the row what the door does (N2). Its version is the chosen row's after its lineage is folded. The chosen organization row is then deleted with the key's other organization rows; its body survives in the environment row. This is the "replace in place" the ruling offered: it keeps one environment row at every point, and any other order leaves zero or two.
    2. A promoted row's history is re-tagged to the environment (folded) at promotion. Under single, the history left tagged to an organization is folded by the late step. History rows keep their bodies and their order; only organization_id and the numbers change.
    3. Under a wall, a conflict nobody chose for no longer blocks the step: it is reported in the step's notes, and its rows hold the column. Otherwise a wall's conflicts, whose rows are never deleted, would block every rerun.
  • --apply shipped in @objectstack/cli@18.0.0-next.1. This PR adds to it (a plan key, a flag, two steps), hence minor and Clause-②: yes (widening). It refuses no invocation that release could finish. A conflict now blocks the new step until it is chosen, but on such a database 18.0.0-next.1's --apply already stopped at column-drops, which never drops sys_metadata's still-declared column. The changeset says so.
  • The choice binding (seat item 7) is in this head, for both flags; see above. It closes the --template-choice gap S4 left (6167752569). The changeset's example carries --plan-file.
  • No MongoDB path (maintainer ruling 6171293009).
  • Closing-target claim: 6172270710 on os migrate organization-ownership has no step for the inventory's organization-presentational-promotion category: views saved through the v17 /meta door stay unserved at v18, and column-drops blocks on sys_metadata forever #23398 (branch claude/issue-15211-c7b-promotions).
  • Not changed, from the review's non-blocking list:
    • the conflict key does not read state;
    • identical-body pairs are conflicts;
    • the two conflict lists in one plan;
    • the carry skip under single for a key an organization row holds. With the doors' organization read kept, no form is open meanwhile.
  • An observation, not caused by this PR. On a freshly seeded showcase database, rewrite blocks on the --seed-admin grants admin_full_access and organization_admin_no_bypass (6163356782; escalated in 6165835673). The dogfood removed them as the blocker's remedy says.

🤖 Generated with Claude Code

https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH

…customizations and carries form withdrawals; the form doors' organization read is deleted (ADR-0131 D10, C7b S1)

WIP: tests not yet run.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
… the deleted read left (ADR-0131 D10, C7b S1)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…sen rows deleted after the chosen promotion is verified (ruling 6163114098 Q1 B, Q6 A; C7b S1)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
… lineages unique environment-wide before the column goes (seat answer Q2 A, C7b S1)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…the overlay key does (C7b S1)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
… cannot collide (C7b S1)

The door numbers a save from history in its own scope, so the withdrawal
carry, a chosen conflict's carry and the template promotion appended
environment history at numbers history-renumber had just given an
organization (measured on a seeded showcase database: event_seq 3 and
(view, showcase_inquiry.contact, 3) each held twice after the run).

- version-renumber, before the promotion: organization lineages laid
  out one above another, the environment's above them all, each overlay
  row's version with its own lineage.
- sequence-renumber, just before column-drops: each organization's
  event range above the environment's, its commit ranges with it, as
  seat answer Q2 A reads; run on every apply, checkpoint or not.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…t a tracker number (C7b S1)

A runtime string carries no tracker id (check:doc-authoring's prose-id leg).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Oct 14, 2026
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 75 documentable anchor(s).

34 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 222293f60a7fff2779269b20bb4e5ce28a34c935.

⛔ 14 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 13 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 222293f60a7fff2779269b20bb4e5ce28a34c935 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c7c8314c695c4663ca011b588b14800b25dd975e — the merge of head 82bd7e0b803b57b01b8d66b880331d4b89e19e80 into base 222293f60a7fff2779269b20bb4e5ce28a34c935, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c7c8314c695c4663ca011b588b14800b25dd975e && git checkout c7c8314c695c4663ca011b588b14800b25dd975e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 222293f60a7fff2779269b20bb4e5ce28a34c935 82bd7e0b803b57b01b8d66b880331d4b89e19e80 && git checkout -B drift-repro 222293f60a7fff2779269b20bb4e5ce28a34c935 && git merge --no-ff 82bd7e0b803b57b01b8d66b880331d4b89e19e80

node scripts/docs-audit/affected-docs.mjs --json 222293f60a7fff2779269b20bb4e5ce28a34c935

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 222293f60a7fff2779269b20bb4e5ce28a34c935 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b85b0914dbdeb18ede19f46ff4d54d1a438f3f3e
Local-runs: none

The head has not moved since the brief. Read: the net diff origin/main...head (17 files, +1547/−436), the PR body, the card inputs the brief names, the #23398 body and triage 6163659502, and ADR-0131 D10–D12 on origin/main. Check-runs on the head as of 2026-10-14T09:50Z: 25 success, 6 skipped, 1 failure, 7 in progress.

① Derived judgments

F1 · blocking: S1 makes the ceremony refuse on every 17.x database.

F2 · blocking: version lineages are not unique environment-wide after the run (Q2 A). runVersionRenumber shifts only items that already hold two or more lineages when it runs (group.length under 2 is skipped). The door numbers an environment save from environment-scoped history (nextItemVersion, organization_id IS NULL). So any later environment save on an item whose only lineage is an organization's starts at version 1, inside that organization's 1..n. Four paths reach it:

  • (a) The carry onto a code-served view whose key differs from the organization row's. Under a wall that is every carry, because nothing is skipped. Under single it happens across packages, which is the dogfood's own shape: a package-less organization row beside a packaged code view.
  • (b) A two-organization conflict with no environment lineage, chosen and carried. The organizations are laid out 1..n and n+1..m, and the environment save takes 1.
  • (c) The template promotion, where an organization's email_template lineage exists and no environment one does.
  • (d) Runtime saves, until S2 drops the column. Every runtime save of a promoted lone item lands the same way. That item's rollback also cannot see its own snapshots, which stay organization-tagged.

Why the pin and the dogfood missed it:

  • The ⭐ carry pin plants an environment lineage (h_env1, h_env2). Remove those two rows and the pin's own duplicates() reads non-empty.
  • The dogfood planted an environment lineage too.

The PR body says "none of the ceremony's own environment saves … can land on a number an organization holds", and the changeset claims uniqueness. Both are false for versions.

F3 · blocking: a conflict resolved to an organization row beside an environment row is neither fail-closed nor crash-safe (Q1 B). resolveMetadataConflict promotes the chosen row first. When the chosen row is an organization's and an unchosen row is the environment's (the usual single conflict), two environment rows hold one key until the delete. The overlay index is NULL-distinct on organization_id; triage 6163659502 calls this Catch 1.

  • (a) The carry can land on the wrong row. The view carry then saves through the door. The door picks its write head with findOne on (type, name, organization_id NULL, state, package), which the repository's own publishPackageDrafts can promote (and drain) ANOTHER package's draft row — the promote resolves the draft without the ADR-0048 package dimension #8907 note calls a driver-order coin toss.
    • If the save lands on the unchosen row, the withdrawal is written there and deleted with that row. The chosen row keeps the form open.
    • The fail-closed pin covers only the other direction (the environment row chosen), and its fake door always wins.
  • (b) A crash between the promotion and the delete is not recoverable by a rerun.
    • On the rerun no organization row is left in the group, so groupMetadataKeyRows drops the conflict.
    • validateMetadataChoices then refuses the same --metadata-choice ("names no row awaiting a choice").
    • Without that flag, the step reports done over a duplicate environment pair.
    • column-drops then blocks on environment-overlay-duplicates, with no ceremony remedy. The only way out is hand SQL on a table whose API allows get and list only.
    • The rerun is not idempotent, and none of this is pinned.

The promotion (Q6 A, posture).

  • Under single: lone organization keys are promoted in place by an UPDATE conditioned on the owner. Pinned.
  • Under a wall: a lone row is promoted only when it is named. The rest are reported in notes and hold the column. Pinned. walled is posture !== 'single', so an unknown posture fails safe.
  • The one path that does not read the posture: a chosen conflict. Under a wall, choosing one organization's row deletes the other organizations' rows of that key. Q1 B is unqualified, but Q6 A's text says "unchosen rows stay reported and blocking; the ceremony never deletes them". The PR does not name this and does not pin it under a wall: see ③.
  • Should-fix: the conflict key ignores state. Active uniqueness is WHERE state = 'active'. Yet a draft or archived row beside an active row of the same key is listed as a conflict, and the operator's choice deletes it, though nothing collides.

Conflicts (Q1 B).

  • The normal path holds: the choice resolves the conflict, and the unchosen rows are deleted by id only after the promotion is read back. A pin holds the write order.
  • A crash between the two: F3 (b).
  • Rows the plan did not list: a deletion targets the candidates computed at APPLY time, not the plan file's list. A row that joins the key between the plan and the apply (an environment save) is deleted on a choice it was never reviewed under. This is a landing-order item with S4.
  • Unnamed and stricter than the stage plan: an organization row and an environment row with identical bodies are a conflict too. The stage plan said "with a different body".

The form-withdrawal carry. It runs under every posture, before the promotion, through the door, judged by anonymousFormIntakeWithdrawnIn. A door refusal blocks (pinned), and a rerun carries nothing twice (pinned). It is not fail-closed in three places:

  • F3 (a).
  • Under single, a key held by both a withdrawing organization row and an open environment row is skipped (heldByOrganization). It waits for the operator's choice. Meanwhile the doors no longer read the organization layer, so the form is open from the upgrade until that choice. The skip's stated reason ("a door save there would make a conflict of a promotion") holds only for a key the organization holds alone.
  • Unparsable organization bodies (parseBody returns null) are silently not carried. This is minor.

The history renumbering (Q2 A).

  • event_seq is sound. It is unique after a full run. sequence-renumber runs after the last ceremony save and again on every apply (the between-runs pin). It is idempotent on a rerun and after a kill between the commit and history writes (pinned).
    • Minor and unpinned: a kill mid-step, followed by an environment save before the rerun, changes the offset. Commits already moved are not moved again, so their ranges end up off by the difference.
  • Versions: see F2.
    • Each version-renumber run is idempotent, but the step is checkpointed and never re-run, so it cannot catch a lineage created after it.
    • The dogfood's exact self-collision (numbers just given to an organization) is closed and pinned. The same class, through a lone organization lineage, is open.

The deleted organization read on the form doors. No caller outside the tests is left, on main or at the head.

#23398.

Tests.

Cross-stage: landing-order items (S4 is #23437, not landed). None is a FAIL for S1 by itself.

  1. The digest catches a stale plan. S4's digest covers the inventory (stage plan 6162468695). S1 changes the inventory, so S4 refuses a plan file made before S1. That refusal is loud and correct.
  2. The digest does not cover what the apply deletes. --metadata-choice and the conflict lists computed at apply time are outside it, so a reviewed plan does not bind which rows the apply deletes. Whichever lands second should refuse a choice whose conflict's candidates differ from the plan file's metadataConflicts. --template-choice has the same gap, which is S4's own.
  3. If S4 lands first, S1's changeset example needs --plan-file plan.json.
  4. Both edit the same files: organization-ownership.ts and organization-ownership-apply.ts, and its test. Whichever lands second merges main.
  5. S2 must drop its renumbering copy (5326201). The duplicates F2 leaves would make S2's (type, name, version) unique index fail at boot. The reopened-withdrawal window lasts until S5.

② Semver level

The changeset grades @objectstack/cli, @objectstack/rest and @objectstack/metadata-protocol all patch, with Clause-②: no. Against WHICH LEVEL in pr-automation.yml, on the v18 next line, that is wrong in both directions:

  • A widening. The plan payload of os migrate organization-ownership shipped in cli 18.0.0-next.0 (cc305a3). S1 adds two things to it:

    • metadataConflicts, a new key on TablePlan, pinned in the plan integration test;
    • --metadata-choice, a new accepted flag.

    A new key on a published payload takes at least minor. The precedent is PR 3's changeset for the same shape (--template-choice and templateConflicts, with --apply equally unreleased), which is minor with Clause-②: yes (widening). The PR's argument ("steps[].step values do not widen a released contract") holds for the apply report only.

  • A narrowing.

    • ObjectStackProtocolImplementation.getMetaItems, which the package index exports, loses legacyFormOrganizationId from its parameter type. That type shipped in 18.0.0-next.0 (c8b062f), and the PR's own tests now need casts to pass the key.
    • The anonymous doors close more: on a deployment with no organization, one package's withdrawal now closes every package's form.
    • Before the ceremony runs, the doors serve organization-only withdrawals open.
    • The changeset describes these behaviours, but carries no (narrowing) arm, no BREAKING banner and no ADR-0087 marker.
  • What it should carry: Clause-②: yes (narrowing), @objectstack/cli: minor, a BREAKING banner with the FROM → TO line for the removed key, and one ADR-0087 disposition marker.

    • The alternative is a written seat ruling that the protocol-internal key is not a published surface. Even then, yes (widening) and cli minor stand.
    • The changeset also does not mention F1's new plan refusal.

③ Boundary flags

From report 6165486835 (no open questions):

  • Out of scope 1: the --seed-admin grants block rewrite on a seeded showcase database (6163356782). Whether it is carded is NOT MEASURED. Escalated to the seat, because it blocks every --apply dogfood on a seeded showcase.
  • Out of scope 2: S2's renumbering copy is superseded. Answered as landing-order item 5.
  • Acceptance notes:
    • "--plan-file omitted": answered as landing-order item 3.
    • "Not a released contract": wrong for the plan payload (see ②).
    • "Not S1's": agreed. Those items are S2's.

Unnamed deviations (the seat ratifies or rejects each):

  • version-renumber lays out two organizations' lineages even with no environment lineage, beyond Q2 A's "only where an environment lineage … exists". This is needed for uniqueness.
  • Identical-body pairs are conflicts.
  • The carry skip under single covers conflicted keys too.
  • The bundle_member re-key is named in the body, but its F1 consequence is not.

Escalations:

Before the queue:

NOT MEASURED:

Seat adoption (2026-10-14T10:02Z). Adopted after an audit of the reviewer transcript: one Write, to this record only; 63 Bash calls, all reads (git show, git diff, REST GETs); no MCP call, no GitHub write, no worktree, build or test run. The seat re-read F1 in the diff: conflictBy.groupBy names bundle_member unconditionally, and two fixtures gain ALTER TABLE sys_metadata ADD COLUMN bundle_member. The seat rules:

  • F1, F2, F3: fix in this PR, each with a pin that starts from the state the finding names: a 17.x table without bundle_member; an item with only an organization history, then an environment save; a crash between the promotion and the delete, then the same --metadata-choice again.
  • The wall (Q1 B against Q6 A). The ruling of record 6163114098 says, for isolated and group: "unchosen rows stay reported and blocking; the ceremony never deletes them". That clause is specific to a wall, so it governs there. Under a wall a chosen row is promoted and nothing is deleted. Q1 B's delete applies under single. Pin both. This applies the ruling's own text. It is not a new answer, and the director can overturn it.
  • The reopened forms window. This is a question of ordering between technical tasks, so the seat decides it without escalation. S1 keeps the form doors' organization read. Its deletion moves to S2 (feat(metadata-core,metadata-protocol,runtime): #15211 S2+S5 — the sys_metadata family drops organization_id, and the v18 boot refusal lands with it (C7b, split for seat 1) #23429), the stage that drops the column and lands with S5's boot refusal, so no release serves an organization-only withdrawal open. A pointer goes to feat(metadata-core,metadata-protocol,runtime): #15211 S2+S5 — the sys_metadata family drops organization_id, and the v18 boot refusal lands with it (C7b, split for seat 1) #23429.
  • ② follows from that. With the read kept, getMetaItems keeps its parameter, and the narrowing leaves S1. The new metadataConflicts plan key is a widening: minor, Clause-②: yes (widening), the PR 3 precedent.
  • S4, which lands first. S1 lands second, so it carries the merge with S4. After it, an apply's deletions must come from the reviewed plan: --metadata-choice is covered by S4's plan file and digest, the way --template-choice is or is made to be. The changeset example carries --plan-file.
  • Fixes #23398 stays. The seat posts the claim that the closing-card check asks for when S1 is ready to land.

Implemented-by: claude/issue-15211-c7b-promotions
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: FAIL

…versions unique by construction, one environment row per resolved key (C7b S1)

- F1: the overlay-duplicates category groups by bundle_member only where
  the column exists (17.x databases never had it); the fixtures that
  hid this are back in the 17.x shape, with a pin.
- F2: a promoted row takes its history with it (its organization's
  lineage of the item moved above the environment's numbers, then
  re-tagged), so every later environment save numbers above it; the late
  history-renumber step lays out every lineage still tagged to an
  organization before the column goes, and under single folds it.
- F3: a chosen conflict ends with exactly one environment row defining
  the chosen body (the key's environment row takes it through the door,
  in place; with none, the chosen row is promoted in place), read back
  before one DELETE of the key's organization rows; a rerun accepts the
  same --metadata-choice, recorded in the checkpoint before the delete.
- Seat ruling (Q6 A's text): under a wall a choice deletes nothing, and
  an unchosen conflict is reported, not blocking.
- Seat ruling: the form doors keep their organization read; its deletion
  moves to S2. rest and metadata-protocol are back to main.
- The changeset: cli minor, Clause-②: yes (widening).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…own range too (C7b S1)

A 17.x database holds history's per-scope counters under unique indexes
((organization_id, event_seq), (organization_id, type, name, version)),
checked row by row: an offset smaller than the lineage's own span landed a
row on a sibling's number mid-statement (measured on a seeded showcase
database). The test fixtures now carry those indexes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
….0-next.1 shipped (C7b S1)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…b-promotions

# Conflicts:
#	packages/cli/src/commands/migrate/organization-ownership.ts
#	packages/cli/src/utils/organization-ownership-apply.test.ts
… the reviewed plan listed (C7b S1, seat option A)

One mechanism for both flags, on S4's ReviewedPlan: the plan file's
metadataConflicts and templateConflicts are read into the reviewed plan,
and before anything is written each chosen group must equal its
apply-time group, or the apply refuses with plan-conflict-mismatch,
naming the conflict, the rows added and the rows gone.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
… in place, not through the door (C7b S1)

Measured on a seeded showcase database: the metadata door writes an
environment customization of a packaged item into that package's slot,
whatever the target, so a package-less environment row of the key never
took the chosen body (the read-back caught it; nothing was deleted). The
key's environment row now takes the body by one UPDATE, its version the
chosen row's after the fold; a withdrawal the body keeps is still saved
through the door, for its history and the package's slot.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…in place (C7b S1)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 468b74a09d9678b2304c3c0cd4b67bd326def7bb
Local-runs: none

A delta review against the FAIL record 6165835673 (head b85b0914db) and its seat adoption note. The head has not moved since the brief.

What was read:

  • The net diff f9b421716d...head: 9 files, +2011/−31. The base is S4's squash; main has since gained one spec-only commit, 1418de4c7e, which touches none of these files. The baseline was 17 files, +1547/−436; the 8 rest and metadata-protocol files are now back to main.
  • The PR body and the rulings the brief names: 6163114098, 6171293009 and 6171496517.
  • ADR-0131 D10 on origin/main.
  • The 17.x history shape at @objectstack/cli@17.7.0, and the 18.0.0-next.1 command.

No os-dev-report for this head is posted on #15211. The newest one that names S1 is 6165486835, at the baseline head. The seat's option-A answer is not on #15211 either. The dev's commits are weighed instead (7878b47888, 917e0b8ccd, the merge ab797b8c5e, 9058ad10bb, 0162d47cd3, 468b74a09d). Option A is judged as the brief states it.

Check-runs on the head, all finished at 2026-10-14T16:03Z: 61 runs, 47 success, 12 skipped, 2 failure, none running.

① Derived judgments

N1 · blocking: the history fold's event_seq move lands on the organization's own other events, and 17.x's unique (organization_id, event_seq) index refuses it. F2's event_seq fix is not sound.

  • The code. foldLineage moves ONE item's events (organization_id = X AND type AND name) by max(envMax, ownHi) − ownLo + 1, while they still carry X. The re-tag to NULL comes after.
  • Why it collides. The 17.x door numbers event_seq per organization, MAX + 1 ("Per-org monotonic event sequence", 17.7.0 sys-metadata-repository.ts). So X's numbers are contiguous across ALL its items and types, objects and flows included. The moved range starts at max(envMax, ownHi) + 1, which is X's own number whenever ownLo is at or below envMax and X's counter is above max(envMax, ownHi).
  • The smallest case. The environment's history is one event, 1. X holds view A at 1–2 and view B at 3–4, both lone under single. Folding A moves 1 to 3, onto B's (X, 3): the unique constraint fails.
  • No remedy. The statement is atomic. A rerun computes the same offset and fails again, so metadata-promotion throws on every run. The only way out is hand SQL on a get/list-only table.
  • The same path in resolveMetadataConflict. It folds the chosen organization row's lineage the same way, so it fails the same way.
  • The index is real. It is declared at 17.7.0 and is still declared on main (unique: 'global'), and the dev's dogfood kept it physically.
  • Why the pins pass. Every fold pin plants one item per organization, and the three pins with no environment history never shift. The late history-renumber is sound: it moves a whole organization by one offset.
  • Remedy. Base the fold's event_seq offset on max(environment max, X's whole max). Pin it under HISTORY_UNIQUE with an organization that holds a second item's events above the environment's max.

N2 · fix in this round: the in-place UPDATE skips what the door does to that same row (the dev's second dogfood fix, 0162d47cd3).

  • What it does. writeBody sets metadata (and version) only. Its targeting is right, and pinned. It updates the conflict key's own environment row by id, so there is one environment row throughout:
    • for a packaged key, that row is the package slot itself;
    • for a package-less key of a packaged item, it is the package-less row, and a withdrawal is also saved into the slot through the door.
  • checksum is not restamped. 17.x stamps it on every write. The repository serves it as the row's version and etag (servedVersion, etag: record.checksum). For view, which has no ordered maps, storedBodyUnchanged compares it directly. So after the UPDATE, a save of exactly the replaced body can be answered as a no-op while the row holds the chosen body.
    • It stays stale whenever no withdrawal is re-saved onto that row. That is the usual case. It also covers every packaged item, whose withdrawal save lands in the slot.
  • The lock is not respected.
    • The door keeps the _lock* family a row stores, and never sets, changes or removes it (ADR-0010 §3.5, authoredLockFamily). writeBody replaces the row's family with the chosen body's, so a stored lock is dropped when the chosen body carries none.
    • The door's ITEM_LOCKED / NOT_OVERRIDABLE gate is not consulted.
    • A packaged item stays locked only through its artifact's lock.
    • Unpinned. Whether any 17.x presentational row stores _lock* is NOT MEASURED.
  • Remedy.
    • In the same UPDATE, write the body's checksum: hashSpec, and @objectstack/metadata-core is already a cli dependency.
    • Carry the environment row's stored lock family onto the body.
    • Block the conflict where the item's effective lock refuses an overlay.
    • Pin each of these.

F1 · resolved.

  • The fix. planCategory drops bundle_member from the grouping only where the column is absent (FOLDED_KEY_COLUMNS); every other key column is still required.
  • Both fixture ALTERs are gone.
    • The plan seed is now 17.x-shaped.
    • The apply fixture "holds a column whose rows another stage owns" has no bundle_member column, and now plants an object row, which S1 does not own.
  • Pinned: "plans a 17.x sys_metadata with no bundle_member column…". It pins the 18.x half too, and the 17.x seed's metadataConflicts.
  • On the apply side, the read and environmentRowOf skip the absent column. That path is read and was dogfooded, but no unit pin plants a presentational row on a table without the column.

F2 · versions resolved; for event_seq see N1.

  • The construction. A promoted row folds its organization's lineage above the environment's before it leaves. history-renumber, just before column-drops and on every apply, lays out every remaining organization lineage above the environment's. Under single it then re-tags that history to the environment, so every later environment save numbers above all of it.

  • Pinned starting from NO environment history:

    • organization-only history under single, with a carry into another package and runtime saves after the run;
    • the same under a wall;
    • two organizations, one row chosen and carried onto.

    Each pin asserts no duplicate and that the row still names its own snapshot.

  • Under a wall it holds per apply, not by construction. The pin's own name says the carry lands inside the organization's numbers and the late step separates them. Between applies, environment saves and organization saves collide again. This is harmless while a walled row holds the column, because the late step runs before every column-drops. The seat ratifies it or rejects it.

F3 · resolved and pinned.

  • Organization row chosen over the environment row. The environment row takes the body by one UPDATE, and no organization row is cleared. The pin asserts the write log.
  • No environment row. The chosen row is promoted in place, and the others are deleted.
  • Recording. The choice is recorded in the checkpoint before the delete.
  • A crash between the replacement and the delete. The same --metadata-choice finishes the run, and it also does after the delete but before the step's checkpoint (pinned).
  • The read-back is pinned: a write that does not land deletes nothing.
  • The second dogfood fix: see N2.

The wall · pinned both ways.

  • Pinned. Under isolated, nothing is deleted (two ⭐ pins). Under single, Q1 B's delete applies.
  • Not named: under a wall, when the key has an environment row, the chosen organization row is NOT promoted.
    • The environment row takes its body, and the chosen row stays an organization row, its lineage moved to the environment.
    • It holds the column for good, with no ceremony remedy.
    • "A chosen row is promoted" is met by its body only. The changeset says "The other organization rows stay", but the chosen one stays as well.
    • The seat ratifies this or rules on it.

The form doors' organization read: KEPT, byte-identical. No change under packages/rest or packages/metadata-protocol, against either the merge base or origin/main. legacyFormOrganizationId has 15 non-test references on each side.

The binding (option A) · holds.

  • The plan file. reviewedPlanFromFile reads both metadataConflicts and templateConflicts.
  • When the check runs. refuseUnreviewedChoices runs after the digest, posture and database checks and after choice validation. That is before the first ledger write or step.
  • The refusal. A group that differs refuses with plan-conflict-mismatch, naming the file, flag, choice, conflict, the reviewed rows and the current rows. The command passes the reason and the detail through.
  • Which rows are deleted. Deletions come from the group as the step finds it, which equals the group at apply start. No earlier step writes presentational rows, and under single the carry skips every key an organization holds.
  • Pins:
    • --metadata-choice: a row added, a row gone, a group not listed, and an unchanged group.
    • --template-choice: a row added, and an unchanged group.
    • A --template-choice row gone since the review is unpinned; it takes the same code path.
  • Ablations per flag: reported by the dev, NOT MEASURED.
  • Should-fix: validateMetadataChoices keys a conflict WITHOUT the bundle member, while the plan and the binding key it WITH the member.
    • The effect: two conflicts of one item that differ only by member are refused in one run as "both choose for". That is the plan pin's own en-US / fr-FR shape, for example email_template locales.
    • It cannot happen on 17.x, which has no column. It can on an 18.0.0-next database.
    • Unpinned.

The merge with S4 · nothing dropped.

  • Every caller passes a reviewed plan:
    • the command, behind S4's --plan-file gate;
    • served-catalog, which passes reviewedPlan;
    • the apply test helper, whose conflicts come from a plan run;
    • not-declared and one-shot-family, which pass --plan-file;
    • fate-supplement, which passes --plan-file. Its one call without the flag is a supplement-refusal pin that never reaches the apply.
  • Nothing of S4 is lost. Every line the source diff removes is replaced by a superset, and refuseUnreviewedPlan (digest, posture, database) is unchanged.

Fixes #23398 · honest only once N1 is fixed.

② Semver level

  • The level is right. @objectstack/cli: minor with Clause-②: yes (widening) matches WHICH LEVEL. metadataConflicts is a new key on a published plan payload, and --metadata-choice is a new accepted flag.
  • No narrowing is left. rest and metadata-protocol are untouched.
  • No released invocation gains a refusal:
    • 18.0.0-next.1 shipped --apply and --template-choice with no --plan-file. S4, which is unreleased, already refuses those, under its BREAKING note and ADR-0087 D3 entry.
    • plan-conflict-mismatch reaches only invocations that carry --plan-file, which no release accepts.
    • MetadataChoiceRefusal guards the new flag only.
    • The new step's block is a step block, and the released --apply could not complete anyway, at column-drops on the declared column.
    • So no BREAKING banner is owed.
  • The text is right. No sentence calls --apply unreleased. The changeset example and the command example both carry --plan-file.
  • A nit. The plan's printed warning says "--apply blocks on it" under every posture; under a wall it does not.

③ Boundary flags

The dev's deviations, from the PR body:

  1. The environment row takes the body in place. The target is right; N2 is owed.
  2. The fold at promotion, then the late step. Accepted, subject to N1.
  3. A walled conflict that nobody chose for is reported, not blocking. This fits Q6 A, because the rows still hold the column.

Unnamed:

Kept from the baseline's non-blocking list: the conflict key ignores state; identical bodies are a conflict; there are two conflict lists; the single carry skip. The last one is now harmless, because the doors keep their organization read. The seat accepts or rejects each.

Corrections the PR needs:

  • The PR title is stale. It still reads "feat(cli,rest,metadata-protocol): … the anonymous form doors read the environment alone", and the squash would land that on main. Retitle it.
  • A body citation is wrong. The body cites f44694659 for the fold fix. That id is a blob; the commit is 917e0b8ccd.
  • The local runs are unreported. The body's local integration tier and dispatch-gates --ran are "still running", and no result is posted.

Escalated, unchanged since the baseline: whether the --seed-admin grants blocking rewrite are carded is NOT MEASURED.

No MongoDB path is owed (6171293009), and none is asked for.

NOT MEASURED:

  • the dev's ablations;
  • N1 on a real 17.x database (read, not run);
  • whether any presentational row stores _lock*;
  • whether the REST layer answers a stale etag with 304;
  • whether the item read serves a package-less environment row of a packaged item;
  • the seat's option-A answer text.

Before the queue:

  • N1, fixed and pinned;
  • N2;
  • the retitle;
  • the seat's rulings on the walled chosen row and the per-apply uniqueness under a wall.

Seat adoption (2026-10-14T16:04Z). Adopted after an audit of the reviewer transcript: its writes are this record and scratch files under the brief's directory; 73 Bash calls, all reads (git show, git diff, REST GETs); one Monitor polling the check-runs; no MCP call, no GitHub write, no worktree, build or test run. Two notes on the inputs: the os-dev-report for this head (6172251336) was posted while the review ran, and the seat's option-A answer was given in the dev's session, not on the card. Neither changes a finding. The seat rules:

  • N1: fix it in this PR. Each fold's offset is based on the organization's whole-history maximum, for event_seq and for versions. Pin it with two items in one organization, starting from the record's smallest case, plus a rerun.

  • N2: fix it in this PR. The in-place UPDATE does what the door does to that row:

    • it recomputes checksum, so the version, the etag and the view no-op detection read the new body;
    • it keeps the row's stored _lock keys;
    • it applies the door's lock check, so a locked packaged item stays locked.

    Pin each part.

  • Ruling, the wall with an environment row: accepted as built. It is Q6 A's text in 6163114098: "unchosen rows stay reported and blocking; the ceremony never deletes them". The chosen row's body is promoted into the environment row, and the organization row stays. The apply report must name each such row as remaining and blocking column-drops, with the operator's remedy. Pin the report line.

  • Ruling, uniqueness under a wall: accepted as built, on one condition. column-drops never runs in an apply unless that same apply's late sequence-renumber step verified version and event_seq uniqueness environment-wide. Pin the refusal. Between applies the 17.x per-organization index still holds.

  • Should-fix: add the bundle member to validateMetadataChoices' conflict key. 18.0.0-next databases exist since @objectstack/cli@18.0.0-next.1.

  • PR text: retitle it, so it no longer names rest or metadata-protocol or the anonymous form doors, and cite the real fold-fix commit 917e0b8ccd.

Implemented-by: claude/issue-15211-c7b-promotions
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: FAIL

…le history; the in-place write does what the door does; column-drops waits on verified uniqueness (C7b S1)

- N1: each fold moves a lineage above the environment's numbers AND the
  same organization's other events and versions (17.x numbered event_seq
  per organization under a unique index); the base excludes the lineage
  itself, so a rerun moves nothing twice. Pinned from the review's
  smallest case, with a crash and a rerun.
- N2: the in-place UPDATE asks the door's lock verdict first (the item
  read's editable; a locked item stays locked), keeps the row's stored
  _lock family, and restamps checksum with hashSpec.
- The wall with an environment row (ruled as built): each organization
  row left is named in the report as remaining and blocking column-drops,
  with the remedy.
- Uniqueness under a wall (ruled, on condition): history-renumber verifies
  event_seq and version uniqueness environment-wide on every apply, and
  column-drops runs only after that verification in the same apply.
- validateMetadataChoices keys a conflict with its bundle member.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
…ness gate and the remaining-row report (C7b S1)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
@objectstack-fleet objectstack-fleet Bot changed the title feat(cli,rest,metadata-protocol): the ceremony carries the sys_metadata organization layer; the anonymous form doors read the environment alone (ADR-0131 D10, C7b S1) feat(cli): the organization-ownership ceremony carries the sys_metadata organization layer into the environment, and a choice deletes only the rows the reviewed plan listed (ADR-0131 D10, C7b S1) Oct 14, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d02b699d656bf54e05d564c9e2098d2d08b3b428
Local-runs: none

A scoped delta review against the FAIL record 6172666364 (head 468b74a09d) and its seat adoption note. The head has not moved since the brief.

What was read:

  • The delta 468b74a09d..d02b699d65: two commits, f5c3ef4488 and d02b699d65. It touches 5 files, +291/−28: the apply module and its unit test, the command, the served-catalog test and the changeset. main was not merged.
  • The net diff against main: 9 files, +2274/−31, from the merge base f9b421716d. packages/rest and packages/metadata-protocol have no diff.
  • The head no longer merges with main. main (222293f60a) has gained 7 commits since the base. One of them is S3's squash, 6f212166ad (feat(plugin-security,cli,spec)!: retire sys_user_permission_set.permission_set_id; the grant key is unique per organization; the boot grant-name backfill is deleted (C7b S3) #23503, 2026-10-14T16:05Z), which touches 5 of this PR's files.
    • GitHub reports the PR dirty. An in-memory git merge-tree conflicts in organization-ownership-apply.test.ts and organization-ownership-plan.ts.
    • S3 changes ColumnDropPort to drop(columns: ColumnDrop[]) and rewrites runColumnDrops. This delta's gate edits that function, and its gate pin uses the old port shape.
    • The historyVerified guard itself auto-merges intact.
    • The dev's "Main merges clean" predates this, or was checked against a stale main.
  • The PR title and body, and the dev's report 6172990807.
  • To check the door-equivalence claims, read only: the base's SysMetadataRepository.put, the item read's servedLockState, and authoredLockFamily. Also the 17.7.0 history counters, at 4e4e881427.

Check-runs on the head, all finished by 2026-10-14T16:39Z: 47 runs, 39 success, 8 skipped, none failed, none running.

  • Test Core, the cli integration verdict: all 6 shards and the roll-up passed.
  • Type Check: workspace, source gates, consumer gates, the debt ledger and TypeScript Type Check all passed.
  • The PR is still a draft.

① Derived judgments

N1 · resolved and pinned.

  • The base. foldLineage bases both offsets on the greater of two numbers. The first is the environment's: its whole event_seq maximum, or its version lineage of the item. The second is the same organization's maximum over all its other items and types. The offset stays max(base, own hi) − own lo + 1. So the moved range starts above the organization's whole-history maximum, its own lineage included.
  • The exclusion. The base leaves out the lineage being moved, and that only affects the skip test. Once the lineage has moved, it sits above the base, so a rerun moves nothing twice.
  • The record's smallest case, worked through. The environment has event 1. X has view A at 1–2 and view B at 3–4.
    • Folding A: the base is max(1, 4) = 4, so A moves to 5–6, and no (X, n) collides. The re-tag gives (NULL, 5–6).
    • Folding B: the base is 6, so B moves to 7–8.
    • A rerun after a crash just after A's move: A's lowest number, 5, is above the base of 4, so A does not move again.
    • Versions: A's 1–2 move to 5–6 (base 4, X's B), and md_a goes from 2 to 6, which still names a2.
  • Collisions. The new range is above the environment's maximum, above every number X holds and above its own old range, so it lands on none of them. Checked row by row, 17.x's (organization_id, event_seq) and (organization_id, type, name, version) indexes never meet a sibling, before or after the re-tag. Overlap with OTHER organizations is still the late step's, as the baseline judged.
  • Overflow. Each fold raises the top by at most the moved span plus one, so there is no practical overflow. The 17.x column width is NOT MEASURED; the field is Field.number.
  • Pinned: ⭐ N1: folding one item of an organization never lands on that organization's other events, on a rerun too.
    • It runs the smallest case under HISTORY_UNIQUE, with two items in org_default.
    • It crashes at the first re-tag (after A's move), then reruns.
    • It asserts no duplicate, that each row names its own snapshot, and that order is kept.
  • The ablation. Rows are read ORDER BY id, so A folds first. With the environment-only base, A's move lands on (org_default, 3), and the first run fails on the constraint instead of the planted crash. That fits the dev's report that the ablation went red. NOT MEASURED.
  • A gap, not blocking: the versions half of the base is built as ruled, but no pin tells it apart. Versions are per item, so the environment-only base cannot collide, and the ablation goes red on event_seq alone.

N2 · checksum and the stored lock: resolved and pinned. The lock check: applied and pinned, but it does not run first.

  • checksum. In the same UPDATE, writeBody stamps hashSpec(written, type), and written is exactly the body it stores. The door's put stamps hashSpec(body, ref.type) on the body it stores, and the repository's reads compare hashSpec(storedBody, type) with it. Same function, same input. Pinned: ⭐ restamps checksum from the written body.
  • The stored _lock keys. writeBody drops every _lock* key from the chosen body and copies in the target row's stored _lock* keys. That matches the door's family: the four _lock* members of MetadataProtectionFields. Pinned: ⭐ keeps the lock family the environment row stores.
  • The lock check.
    • A new port, MetadataLockPort, asks for the verdict. The command answers it with the item read's editable, which servedLockState derives from the _lock gate's item-lock resolution and the packaged-base refusal: the doors' own verdict.
    • A refusal becomes a blocker, and nothing is deleted.
    • Pinned: ⭐ a locked item stays locked. It asks once, with the conflict's address. The step blocks, the body is unchanged, and nothing is deleted.
  • Should-fix S1: the verdict is asked after history has moved.
    • The order. With an environment row, foldLineage runs before writeBody asks. It moves the chosen organization's lineage, re-tags it to the environment and shifts that row's version. With no environment row, promoteRows re-tags the chosen row itself, and the verdict is asked only afterwards, and only when a withdrawal is carried.
    • The claim it breaks. A refusal is therefore not "nothing written", which the PR body, the changeset ("asks the door's lock verdict first") and the writeBody doc all say.
    • The effect.
      • Under single, the end state is what the late step makes anyway.
      • Under a wall, a named organization's lineage of a locked item becomes environment history, though the environment never takes the body. That organization's kept row then names a snapshot outside its own scope.
    • Why the pin passes. It plants no history table, so foldLineage does nothing there.
    • The fix. Ask the verdict before promoteRows and foldLineage. Pin it with history and the write log.
  • Nit S2: the door save sends body, not written. Whether any 17.x pair of rows differs in lock family is NOT MEASURED.
    • When a withdrawal is carried, door.save(body) follows the UPDATE. body still has the chosen row's _lock* keys, but the target row now stores the environment row's keys.
    • authoredLockFamily refuses a family that matches neither the stored one nor the served one. So where the two rows' families differ, the conflict blocks after the write, and every rerun blocks the same way.
    • Before this delta the row stored the chosen family, so the echo was accepted.
    • The fix: send written, or the body without its _lock* keys.

The wall report line · resolved and pinned.

  • The line. Each organization row of the key other than the keeper gets a note: "Remaining: sys_metadata row … of … blocks column-drops on sys_metadata: under the … posture the ceremony never deletes it. Remedy: check the environment row, then delete this row yourself, or keep it and the column stays; re-run --apply."
  • The chosen row. Where the key has an environment row, the note names the chosen row as such, "whose body the environment row … now holds".
  • Pinned: the wall ⭐ pin asserts the line for md_org, through to the remedy.

The column-drops gate · resolved and pinned.

  • The check. runHistoryRenumber resets run.historyVerified, lays out the history, then counts duplicates of event_seq and of (type, name, version) across all scopes. Any duplicate blocks the step, and the step names each one. None sets the flag. runColumnDrops refuses while the flag is unset.
  • Why it holds per apply.
    • The flag lives on this apply's Run.
    • The step runs on every apply, checkpointed or not.
    • A blocked rerun of a checkpointed step now stops the loop.
  • Pinned: ⭐ column-drops never runs unless this apply's history-renumber verified uniqueness. Two environment events share 7. history-renumber blocks and names "7 (2 rows)", column-drops is not reached, and nothing is dropped.
  • The ablation (verification removed → red) fits the pin. NOT MEASURED.
  • The guard inside runColumnDrops cannot be reached through the loop, because a blocked history-renumber stops it. It is defense in depth, and no pin depends on it. The ruled behavior is pinned through the stop.

The bundle member · resolved and pinned.

  • The key. validateMetadataChoices' key now carries [member], as the step's listing and the plan's key already did.
  • Pinned: an en-US conflict and an fr-FR conflict of one item, with en_org and fr_env chosen. The step finishes with chosen: 2. Under the old key both conflicts fall on one key and are refused as "both choose for".

PR text · resolved.

  • The title no longer names rest, metadata-protocol or the anonymous form doors.
  • The citation. The body cites the fold fix as 917e0b8cc, which resolves to commit 917e0b8ccd and nothing else. The wrong citation f44694659 is gone.

Nothing the baseline judged right is broken:

  • F1: the plan and inventory modules are untouched.
  • F3:
    • There is still one environment row throughout.
    • The read-back now compares the row with the body as written (expected), which is what the UPDATE stores.
    • The read-back before the delete, the checkpoint before the delete, and the crash rerun are unchanged.
  • The binding for both flags: refuseUnreviewedChoices and reviewedPlanFromFile are untouched.
  • The S4 merge:
    • Every caller passes the new required lock port: the command, served-catalog and the test helper. The command-level pins go through the command.
    • refuseUnreviewedPlan is untouched.
  • The kept form read: no diff under rest or metadata-protocol.

② Semver level

  • The level is unchanged and right: @objectstack/cli: minor, with Clause-②: yes (widening).
  • Nothing new is public. The delta adds no flag, plan key or refusal reason.
  • The new blocks reach no release. The history-renumber block and the column-drops refusal are step blocks, and only invocations that carry --plan-file reach them. No release accepts that flag.
  • The new types are internal. cli exports only ., ./console and ./hook-body, and MetadataLockPort and ApplyOptions.lock are not re-exported.
  • No BREAKING note is owed.

③ Boundary flags

The dev's deviations (6172990807):

  1. The base leaves out the lineage being moved. The offset is the same, and reruns stay idempotent. Accepted.
  2. The lock check reads the item read's editable instead of calling the save door. That read is documented to report the doors' verdicts. Accepted, with two caveats:
    • The port treats a missing editable as editable, which fails open.
    • No pin or dogfood exercises the real port this round; the unit pins stub it.
  3. history-renumber itself blocks on a duplicate, not only column-drops. That is stronger than the ruling, and fine. But:
    • Its remedy is hand SQL on history.
    • 17.x's unique index never covered the environment scope (NULL counts as distinct). Its legacy loader computes MAX + 1 outside a transaction, and its own doc says concurrent writes can collide. So concurrent environment saves on 17.x could leave a duplicate that stops the apply here.
    • Whether real 17.x databases hold one is NOT MEASURED. The seat accepts this or rejects it.
  4. No new dogfood. The integration verdict is CI's Test Core.

Not named in the PR:

No MongoDB path is owed (6171293009), and none is asked for.

NOT MEASURED:

  • the dev's ablations;
  • the real lock port on a booted stack;
  • environment-scope duplicates in 17.x history;
  • whether any 17.x row pair differs in _lock* family (S2);
  • the 17.x numeric column width.

Before the queue:

  • Required: merge main, which carries S3. The PR cannot land as it stands. The merged head is a new head, and this record does not cover it. It needs a scoped review of the merge, in particular the column-drops gate on S3's new ColumnDrop port, with S3's retired column now behind the history check, and green CI.
  • S1, recommended in the same round: ask the verdict before the fold and the promotion, and pin it with history. One line of code plus one pin.
  • S2: the nit.
  • The seat's call on deviation 3.

Seat adoption (2026-10-14T16:42Z). Adopted after an audit of the reviewer transcript: its writes are this record and scratch files under the brief's directory; 67 Bash calls, all reads (git show, git diff, git log, an in-memory git merge-tree, REST GETs); monitors polling the check-runs; no MCP call, no GitHub write, no worktree, build or test run. The seat confirmed the conflict: git merge-tree --write-tree origin/main refs/review/pr-23452 exits 1 since S3 landed (#23503, 6f212166ad). The content PASSes at d02b699d65. The seat rules for the merge round:

  • Merge main: a conflict is a reason ruling [Decision] After a PR is open, merge main only on a conflict, a main-caused CI red or a queue removal, and re-check only the overlap; or keep merging main before every push #23390 allows. Resolve against S3's column-drops port and its rewritten runColumnDrops, and keep this round's uniqueness gate in front of it. Port the gate pin to S3's port shape.
  • Should-fix S1, fixed in this round: the lock check runs before the history fold and before the promotion, so a refused write leaves nothing written, as the body and changeset say. Add a pin with a history table under a wall.
  • Nit S2, fixed in this round: the door save after the write sends the written row (written), not the chosen body, so a rerun does not block on differing lock keys.
  • Seat's call, history-renumber blocking on a duplicate: kept. A loud refusal is preferred to a guessed renumbering of history the platform never indexed. The refusal must name the table, the item and the duplicate numbers, and state the remedy. Pin the message. No measurement on real 17.x data is owed before landing.
  • The merged head gets one scoped review of the merge and these fixes, read with the CI of that head.

Implemented-by: claude/issue-15211-c7b-promotions
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: PASS

…b-promotions

# Conflicts:
#	packages/cli/src/utils/organization-ownership-apply.test.ts
#	packages/cli/src/utils/organization-ownership-plan.ts
…solution; the follow-up door save sends the row as written; history duplicates are named (C7b S1)

- The door's lock verdict is asked once, before the history fold, the
  promotion and the in-place write: a refusal leaves nothing written.
  Pinned under a wall with a history table.
- The door save after the in-place write sends the row as written, its
  stored lock family kept, so a rerun meets the same body.
- history-renumber's refusal names the table, each item holding the
  duplicated number and the remedy; both messages pinned.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USccGXR13sWyTeEwe8QFdH
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 82bd7e0b803b57b01b8d66b880331d4b89e19e80
Local-runs: none

A scoped review of the merge round after the PASS record 6173411667 (content at d02b699d65) and its seat adoption note. The head has not moved since the brief.

What was read:

Check-runs on the head, all finished by 2026-10-14T17:21Z: 47 runs, 39 success, 8 skipped, none failed, none running.

  • Test Core, the cli integration verdict: all 6 shards and the roll-up passed.
  • Type Check: workspace, source gates, consumer gates, the debt ledger and TypeScript Type Check all passed.
  • The PR is still a draft, and GitHub now reports it clean against main 222293f60a, which has not moved.

① Derived judgments

The merge · clean, both sides intact.

  • git merge-tree --write-tree origin/main refs/review/pr-23452 exits 0.
  • S3's side. For each shared file, S3's change (f9b421716d..222293f60a) and what the merge brought in (d02b699d65..97408a0761) were compared as sorted line sets. They are identical in the command, the apply module, the plan module and the served-catalog test. In the apply test they differ only by:
    • S3's separate buildOrganizationOwnershipPlan import, folded into S1's import line;
    • S1's two column-drop doubles, rewritten to S3's port.
  • S1's side. The previous net diff and the merge's net diff (222293f60a..97408a0761) are identical line sets in 8 files. The apply test differs only by the same two items.
  • So no hunk of either side is lost. S3's ColumnDrop {table, column} port, its rewritten runColumnDrops (grant duplicates, GRANT_SET_ID_COLUMN with its retired indexes, the not-declared and not-proposed outcomes) and TablePlan.grants are all present. TablePlan carries both metadataConflicts and grants.
  • The fix commit touches no S3 line. It edits historyDuplicates, writeBody, resolveMetadataConflict and S1's pins only.
  • The gate is first. Head's runColumnDrops, minus its first 12 lines (the historyVerified guard), is byte-identical to main's. CEREMONY_STEPS puts history-renumber just before column-drops.
  • The gate pin ⭐ column-drops never runs unless this apply's history-renumber verified uniqueness environment-wide uses S3's port shape: a recording double of drop(columns) returning {table, column, outcome}. Its drops stays empty.
    • It goes red with the verification removed: history-renumber then finishes, the flag is set, and the double records drops. The dev's ablation is NOT MEASURED.
    • It does NOT go red with only the guard inside runColumnDrops removed, because the loop stops at the blocked history-renumber first. The previous record called the guard defense in depth; that is unchanged.
    • It is a unit double, not the command's schema-drift adapter. S3's own organization-ownership-column-drops.test.ts covers the adapter.

The lock check · before every write of the resolution.

  • The order. resolveMetadataConflict now asks lock.verdict once, after the reads (bodies, the key's environment row) and before the try holding promoteRows, foldLineage, writeBody and the door save. writeBody no longer asks.
  • When it asks. When the resolution would write: the key has an environment row whose body differs from the chosen body, or it has none and an organization row was chosen. That covers the fold and the in-place write, and now also a conflict's promotion with no environment row, which before asked only when a withdrawal was carried.
    • When the environment row already defines the body, nothing is folded or written, and only a carried withdrawal reaches the door, which applies its own lock gate.
  • A refusal returns a blocker before any write: "Nothing was written or deleted".
  • The pin ⭐ the lock is asked before the history fold and the promotion: under a wall, a refusal leaves history and rows as they were:
    • isolated, a history table (environment v1 at event 1; org_default v1 and v2 at events 1 and 2), and a conflict with an environment row with md_org chosen.
    • It asserts no SQL write matching sys_metadata (history included), history rows equal, owners unchanged and the environment body unchanged.
    • The ablation holds by reading. Under the old order, foldLineage moves and re-tags h_org1 and h_org2 before the refusal. The loop stops at the blocked step, so history-renumber cannot account for a change instead. The dev's ablation is NOT MEASURED.
    • A gap, not blocking: the pin's title names the promotion, but its conflict has an environment row, and its lone row is not chosen under the wall. The branch with no environment row, which promotes the chosen row, is not pinned under a refusal. One check before both branches covers it by reading, but an ablation that moved the check into the environment branch would stay green.
  • ⭐ a locked item stays locked keeps its single asked entry and now matches "Nothing was written or deleted".
  • Nit, the failure mode. The verdict call moved out of the resolution's try. The command's port throws when no metadata protocol is composed, and getMetaItem throws 503 on a degraded store. Either now aborts --apply with an exception rather than becoming this conflict's blocker. Nothing is written either way. This is unpinned.

The door save sends written · resolved and pinned.

  • door.save now sends expected.
    • With an environment row, that is writeBody's return: the body as written, with the environment row's stored _lock* family.
    • With no environment row and a withdrawal carried, it is the chosen row's own family, which that row still stores after its promotion.
    • Where the environment row already defined the body, it is that stored body.
  • The pin the door save after the in-place write sends the row as written, its stored lock family kept: the environment row stores _lock: 'customize', and the chosen body carries none. The last view save holds the chosen label, _lock: 'customize' and the carried withdrawal. Sending body drops _lock, so the pin goes red by reading.

The duplicate refusal · names everything owed, and is pinned.

  • event_seq: "sys_metadata_history: event_seq is not unique environment-wide — event_seq 7: object deal (row h_e1), object deal (row h_e2). … Remedy: give each of these rows a number no other row holds (or remove the duplicate), then re-run --apply." That is the table, each item holding the number by row, the number and the remedy. It is pinned verbatim. The first 10 numbers are shown, then a count of the rest.
  • Versions: "sys_metadata_history: a version of an item is held more than once — object account version 3 (2 rows). … Remedy: give each of these rows a version no other row of the item holds…". That is the table, the item, the version and the remedy. It is pinned by its start and its remedy.
  • Nits, wording:
    • The version message says "each of these rows" but names no row ids, only a count.
    • Both messages say "the organization_id column is not dropped". Since the merge, the stop also holds S3's sys_user_permission_set.permission_set_id drop and its two index drops, and the message does not say so.
    • The lock refusal's remedy, "choose the row the environment already holds", does not fit when that row was the one chosen and only a carried withdrawal makes the body differ.

The body's claim "a refused write leaves nothing written" is now true for a resolution.

  • Nothing in resolveMetadataConflict writes before the verdict, and a refusal returns before the try.
  • The same step's independent work still lands before a later resolution blocks: the withdrawal carry and the lone promotions. That is not the refused write, and it is idempotent on a rerun.

Nothing the previous record judged right is broken. The fold base (N1), the checksum restamp, the stored _lock carry, the wall report line, the bundle-member key, the binding of both flags, the read-back, the checkpoint and the single delete are line-identical through the merge. The fix commit changes them only by moving the verdict out of writeBody.

② Semver level

  • Unchanged and right: @objectstack/cli: minor, Clause-②: yes (widening). The changeset file did not change this round.
  • Nothing new is public.
    • The new refusals are step blockers on the --plan-file path, which no release accepts.
    • The lock check asked before a conflict's promotion is a stricter block on that same unreleased path.
  • S3's BREAKING note (23430-grant-permission-set-id-retired.md) is a separate changeset, minor with Clause-②: yes (narrowing), and it is not consumed by any prerelease: pre.json lists none, and cli is 18.0.0-next.1.
    • Its "column-drops then drops the column" now also waits on this PR's history check. This changeset's sentence ("column-drops runs only after that same apply's history-renumber has verified…") already states that for every drop, so neither note is false.
    • Nothing this changeset says changes, and no BREAKING note is owed by S1.
  • Nit: the changeset still attaches the lock verdict to "the in-place write" only, and does not say a conflict's promotion with no environment row now asks it too.

③ Boundary flags

The dev's report (6173806262): it names no deviation and no open question.

  • The merge went through os-regen-merge.sh, with two conflicts resolved by keeping both sides. That matches the line sets above.
  • history-renumber keeps blocking, per the seat's call.
  • Its local runs (cli typecheck, 146/146 ceremony suites, narrowed lint, the three ablations) are the dev's. The integration verdict is CI's Test Core.

Carried forward, unchanged, not re-judged:

  • Lone promotions (keys no other row holds) still skip the verdict.
  • The port treats a missing editable as editable.

No MongoDB path is owed (6171293009), and none is asked for.

NOT MEASURED:

  • the dev's three ablations;
  • the real lock port on a booted stack;
  • the gate with S3's real schema-drift adapter.

Before the queue:

  • Required: none. All four of the adoption note's items are done and pinned. The merge loses no hunk of either side, and CI is green on this head.
  • Optional follow-ups, none blocking:
    • a refusal pin for a conflict with no environment row;
    • keep the verdict call's port error a conflict blocker;
    • the wording nits above (row ids in the version message; the message naming only organization_id; the lock remedy; the changeset's lock sentence).

Seat adoption (2026-10-14T17:23Z). Adopted after an audit of the reviewer transcript: its writes are this record and scratch files under the brief's directory; 55 Bash calls, all reads (git show, git diff, an in-memory git merge-tree, REST GETs); one Monitor polling the check-runs; no MCP call, no GitHub write, no worktree, build or test run. The seat re-ran the landing prechecks on 82bd7e0b80:

  • ci-failure.mjs exits 0;
  • check-governed-merges.mjs reports no governed path and 2,384 lines;
  • git merge-tree against origin/main exits 0;
  • the only closing keyword is Fixes #23398, and the closing-target check is green against the claim 6172270710.

The optional findings do not block. None of them writes anything wrong: a failed lock lookup stops with nothing written. They are noted for the epic's follow-up, not built here.

Implemented-by: claude/issue-15211-c7b-promotions
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 14, 2026 17:25
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit 869a381 Oct 14, 2026
48 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-15211-c7b-promotions branch October 14, 2026 17:45
marchtian pushed a commit that referenced this pull request Oct 14, 2026
…issue-23429-metadata-family

One conflict: packages/rest/src/meta-delete-drop-storage-other-rows.test.ts
— main parameterised the seed (seedStoredRow(packageId)); S2 drops the
retired organization_id key from it. Took main's shape without the key.

Auto-merged files both sides touch (main = S1/S3/#23489, kept as merged):
organization-ownership.ts keeps S2's template-lookup column guard (S2's own
hunk); organization-ownership-plan.ts and -inventory.ts are main's (S1)
byte-for-byte; protocol.ts keeps #23489's packagelessReadAddress beside S2's
legacy-row filtering; rest-server.ts keeps S2's deletion of the form doors'
organization read; generated translations and the engine-double ledger are
regenerated after the merge.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXLB6sUdwQZQ9NQS3mj3uZ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants