Skip to content

fix(cli): os migrate plan and apply name each drop gate's command, never --allow-destructive, for the drops apply refuses - #25029

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-24958-remedies-apply-refuses
Oct 21, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-24958-remedies-apply-refuses

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24958
Clause-②: no

os migrate plan and os migrate apply no longer offer --allow-destructive as the way on for a drop that os migrate apply refuses whatever flag is passed. Three of apply's drop gates refuse the whole --allow-destructive run while such a drop is in it: sys_account.issuer while its retirement pre-flight does not pass, sys_comment.reactions while no verified run of os migrate comment-reactions --apply is recorded, and an organization_id drop while the organization-ownership ceremony has not verified (ADR-0131 D10 item 5). Each such drop is still listed and counted as drift in both commands. Its line names what it waits for, the footer names that gate's commands, and no line offers a re-run with --allow-destructive while one is held. A database with no held drop prints what it printed before, byte for byte, in both commands. The --json documents, the exit codes and every refusal (rule and text) are unchanged.

What changed

  • packages/cli/src/utils/apply-drop-gates.ts (new): apply's drop gates as the doors read them.
    • The two predicates that were inline in apply.ts move here unchanged: isAccountIssuerDrop and isCommentReactionsDrop. isCeremonyOrganizationDrop asks the drop gate's own ceremonyOrganizationColumnDrops. apply asks the first two from here.
    • DropGateReads makes each gate's read at most once per run: probeAccountIdentityCollisions (plugin-auth), readCommentReactionsConversion and readOrganizationOwnershipMarker, the same functions apply's gates call. The listing and the gate share that read, so they cannot answer differently, and a held run reads each gate once, as before. A read that throws is remembered as thrown, so the issuer gate's "could not read" refusal is unchanged.
    • APPLY_DROP_GATES: one row per gate, in apply's order, with its predicate, how it judges its read, the listing line and the footer line. There is no second inventory, dialect or table list.
    • readHeldDrops, describeDriftEntries and describeHeldDrops generalise PR fix(cli): os migrate plan names the ownership ceremony, not --allow-destructive, for the organization_id drops apply refuses #24954's ceremonyHeldDrops, describeWithCeremonyHolds and describeCeremonyHold. A gate that judges no listed entry is never read. The organization_id lines keep PR fix(cli): os migrate plan names the ownership ceremony, not --allow-destructive, for the organization_id drops apply refuses #24954's text byte for byte.
  • packages/cli/src/commands/migrate/plan.ts: the plan asks the module instead of its three local helpers (removed), so the issuer and reactions gates join the ceremony's.
  • packages/cli/src/commands/migrate/apply.ts:
    • The human face reads the held drops before the listing, like the driver's apply limits, and never under --json.
    • The listing names each held drop's gate. While a drop is held, the pre-prompt line is N destructive change(s) will be SKIPPED, and a re-run with --allow-destructive cannot include them yet: followed by one line per gate, and the post-apply line is Skipped N destructive change(s); a re-run with --allow-destructive cannot include them yet (see above).
    • The gates ask the moved predicates and the shared reads. Their refusal rule, refusal text, --json documents and exit codes are untouched.
  • packages/cli/test/migrate-drop-gate-remedies.pin.test.ts (new, unit tier): the enumeration pin, described below.
  • packages/cli/src/commands/migrate/apply.organization-ownership-refusal.test.ts, apply.account-issuer-json-refusal.test.ts: fixture triage. Each fixture spelled the drift entry's finding as description, a key ManagedDriftEntry does not declare, and carried no message, which it requires. The listing now reads message for a held drop, and on these fixtures it threw before the gate. Both org human-face cases went red (the refusal never printed). The issuer human-face control stayed green, exiting 1 from that throw rather than from its refusal. Both fixtures now carry the differ's message, and the issuer control also asserts that its refusal printed.
  • .changeset/24958-migrate-drop-gate-remedies.md: @objectstack/cli patch.

Measured at the public door

Setup:

  • Databases. Copies of a SQLite database that a create-objectstack@17.7.0 app had booted once (43 tables carrying organization_id, sys_comment.reactions present, no ceremony marker, no reactions conversion recorded, no sys_account.issuer). Variants were shaped by hand:
    • held: one comment carrying a stored reaction.
    • reactions: as held, plus a verified ceremony marker row.
    • orgonly: a verified reactions-conversion row.
    • issuer: both rows verified, plus the legacy issuer column, the (provider_id, account_id) unique index dropped, and a second account row sharing the first one's key.
    • issuerclean: as issuer with no second row.
    • verified: both rows verified.
    • fresh18: a fresh 18 database plus one orphan column.
  • How it was run. This repository's CLI, built at 62bc12930 (main) and then at this branch, from a minimal project directory with OS_AUTH_SECRET set so the auth family composes, stdin from /dev/null. Every run used its own fresh copy.

The card's three mechanism assumptions, on main:

  1. Confirmed. On held, os migrate apply --yes exits 0 and lists each organization_id drop as sys_job.organization_id: column exists in the database but not in metadata (orphaned) — "os migrate apply --allow-destructive" to drop it., then prints ⚠ 22 destructive change(s) will be SKIPPED (re-run with --allow-destructive to include them). and, after applying, ⚠ Skipped 22 destructive change(s) (re-run with --allow-destructive to include them). That re-run is refused, exit 1. On held the reactions gate refuses it first. On orgonly the drop gate refuses it: ✗ Refusing this --allow-destructive run: it would drop organization_id from sys_job, sys_job_queue, … Nothing in this run was applied.
  2. Confirmed. On reactions, os migrate plan lists sys_comment.reactions: column exists in the database but not in metadata (orphaned) — "os migrate apply --allow-destructive" to drop it. and closes with Apply with: os migrate apply (add --allow-destructive for drops / tightenings). os migrate apply --allow-destructive --yes exits 1: ✗ Refusing to drop sys_comment.reactions: … Run "os migrate comment-reactions" to preview, then "os migrate comment-reactions --apply"; once it reports the run verified, re-run this command. The gate's predicate moved out of apply.ts unchanged: the same reads, the same answer, the same refusal text (the refusal lines and exit codes below are identical).
  3. It does, so it is in scope. On issuer, the plan lists sys_account.issuer: column exists in the database but not in metadata (orphaned) — "os migrate apply --allow-destructive" to drop it. with the same footer, and apply --allow-destructive --yes exits 1: ✗ Refusing to drop sys_account.issuer: … Resolve them, then re-run "os migrate account-issuer" as the post-check before applying. The control issuerclean passes the pre-flight (✓ sys_account.issuer retirement pre-flight: clean over 1 row(s).) and applies, exit 0, so there the plan's --allow-destructive is right and is kept.

After, on this branch. The held plan's footer:

  ℹ 28 change(s): 6 safe, 0 needs-confirm, 22 destructive
  Apply with: os migrate apply
  ⚠ sys_comment.reactions above is where reactions were stored before they became sys_comment_reaction records, and no verified run of "os migrate comment-reactions --apply" is recorded on this database, so "os migrate apply --allow-destructive" refuses every run until one is (the other 21 destructive change(s) above wait for it too). Run "os migrate comment-reactions" to preview, then "os migrate comment-reactions --apply"; it carries them into records and records the run verified.
  ⚠ 8 organization_id drop(s) above are the organization-ownership ceremony's, and it has not verified on this database, so "os migrate apply --allow-destructive" refuses every run until it has (the other 14 destructive change(s) above wait for it too). Run "os migrate organization-ownership --out PLAN" to write and review its plan, take a backup, then "os migrate organization-ownership --apply --plan-file PLAN --backup-taken"; it drops these columns itself.

os migrate apply --yes on held prints the same two gate lines under ⚠ 22 destructive change(s) will be SKIPPED, and a re-run with --allow-destructive cannot include them yet:, then ⚠ Skipped 22 destructive change(s); a re-run with --allow-destructive cannot include them yet (see above). The held entries read:

sys_comment.reactions: column exists in the database but not in metadata (orphaned) — a drop that waits for "os migrate comment-reactions" to carry any reactions stored in it into sys_comment_reaction records, and no verified run is recorded on this database (see below).
sys_account.issuer: column exists in the database but not in metadata (orphaned) — a drop that waits for its retirement pre-flight, "os migrate account-issuer", which does not pass on this database (see below).

and the issuer footer: ⚠ sys_account.issuer above is the retired account issuer, and its retirement pre-flight finds 1 (provider_id, account_id) key(s) held by more than one row on this database, so "os migrate apply --allow-destructive" refuses every run until it passes (the other 23 destructive change(s) above wait for it too). Run "os migrate account-issuer" for its report, resolve what it lists, then re-run it as the post-check.

Byte for byte, main against this branch, 24 runs. Log timestamps, the duration line and the per-run copy's directory were normalised before comparing. "Refusals and exit" compares every Refusing line plus the exit code.

database command diff lines refusals and exit
held plan · apply --yes · apply --allow-destructive --yes 3 · 24 · 20 identical
reactions the same three 5 · 7 · 3 identical
issuer the same three 5 · 7 · 3 identical
orgonly the same three 0 · 21 · 17 identical
verified the same three 0 · 0 · 0 identical
issuerclean the same three 0 · 0 · 0 identical
fresh18 plan · apply --yes 0 · 0 identical
held · verified plan --json · apply --yes --json 0 · 0 · 0 · 0 identical

Every differing line is a held entry's line, a gate's footer line, or a skip line. orgonly's plan is identical because PR #24954 already handled it, and the generalised code reproduces that text.

The enumeration pin: red on main, then green; one ablation

packages/cli/test/migrate-drop-gate-remedies.pin.test.ts, 33 cases.

  • Discovery. It reads every message: 'refused_…' from apply.ts and requires each to be claimed by a roster row (or by refused_unloadable_host_config, which is not a drop gate). The rows must equal APPLY_DROP_GATES, in order, and each row's entry must be judged by its own gate and no other. A new gate's refusal fails the first case, by name, until it has a row, and its row's cases fail until the module reads it.
  • Per gate, per held state (issuer: colliding keys, and sys_account unreadable; reactions and the ceremony: no row, and an unverified row):
    • plan lists and counts the entry, its line carries no --allow-destructive, and the footer names every command of the gate's remedy and offers --allow-destructive for nothing.
    • apply --yes: the same listing line and footer; no line offers a re-run with --allow-destructive; the safe work still applies.
    • apply --allow-destructive --yes: the same listing line, then the gate's own refusal, exit 1, nothing written, naming the same commands. The gate's read was made once.
  • Controls. A released gate (clean pre-flight, verified row) prints what it always printed in both commands, and its read WAS made. No gated drop: neither command reads anything.
  • --json. The plan document carries the differ's messages and reads no gate.
  • The reads are the gates' real ones over a fake objectql service holding the rows. The boot, the occupancy probe and the one-shot exit are replaced. The subjects resolve through relative src/ imports, not a package's exports, so no dist/ preflight applies.
leg result
apply.ts and plan.ts at 62bc12930 (main), tree only, the new module and pin kept 18 failed, 15 passed. The 16 held cases fail on the listing line (not to contain '--allow-destructive'). The ceremony's two plan cases pass: PR #24954 already handled them. The issuer and reactions plan controls fail only at their final read-count assertion, because main never reads those gates. Restored: blobs 2d4a0663659a and 5d66675a3243 equal HEAD's, git diff HEAD is empty and git status is clean.
branch 33 passed
ablation, scripts/ablation-replace.mjs: the reactions gate's judgement .verified ? null changed to .verified !== undefined /* ABLATION */ ? null (the read made, its verdict ignored) 6 failed (exactly the six reactions held cases), 27 passed. Anchor 1 to 0, blob d93231fab096 to bdb42c39683d. Restored: blob equals HEAD's d93231fab096, and git diff HEAD is empty.

Local verification

All at the final head fc8973ea8d, a clean merge of origin/main (73432299c; no incoming file under packages/cli or the gates' packages), unless a line says otherwise.

  • pnpm --filter @objectstack/cli typecheck: exit 0, test layer included (check:test-typecheck: OK).
  • @objectstack/cli unit tier (vitest run --project unit --maxWorkers=2): Test Files 317 passed (317), Tests 4717 passed (4717), at 8bb7ff69ff, the branch's last own commit before the merge. At fc8973ea8d the pin, its neighbours and the tier partition re-ran green: plan.apply-limit, both gate-refusal tests, migrate-confirmation-exit.pin, exit-signal.pin and vitest-tiers-partition, Tests 225 passed.
  • Integration tier: this diff touches no integration file, spawn entry or boot path, so it is declared to CI. The three files that drive apply's gates against real databases were run anyway because the gates' reads moved: comment-reactions.integration, apply-organization-ownership-gate.integration and apply.account-issuer-retirement.integration, Tests 16 passed.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 68 derived commands, plus the 11 declared WIDE-population families, 79 in all, each exit captured before any pipe.
    • 77 exited 0 on the first pass.
    • check:dual-build-cjs-loads and check:i18n-coverage exited 3 (PREREQUISITE NOT MET: no dist/ for packages this diff does not touch). After pnpm build both exited 0 on the same head.
    • --ran: ✓ dispatch-gates --ran: 68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero — all 68 recorded an exit code and none of them is 3).
  • node scripts/check-platform-checklist.mjs --changed-from origin/main: exit 0 (no anchored file in the change set).
  • pnpm lint (eslint . --no-inline-config, the whole repository): exit 0, no findings.

Acceptance notes

  • Deviation from the claim's file surface: two existing test files. apply.organization-ownership-refusal.test.ts and apply.account-issuer-json-refusal.test.ts are outside the claimed surface. Their fixtures could not represent a real drift entry, and the change made that visible (see "What changed"). They needed a one-key fixture correction, plus one added assertion in the issuer control so it can no longer pass on an unrelated exit 1.
  • Reads added, measured as harmless. A human-face apply (with or without --allow-destructive) and plan now make a gate's read whenever that gate's drop is listed. Before, a run without the flag made none. For the issuer gate that read is the pre-flight's walk of sys_account, which it already made on every --allow-destructive run. With the flag, the listing and the gate share one read, so the count is unchanged. That is pinned, and the existing issuer control's toHaveBeenCalledTimes(1) still holds. --json makes no new read.
  • Unchanged by design (PR fix(cli): os migrate plan names the ownership ceremony, not --allow-destructive, for the organization_id drops apply refuses #24954's shape). While a gate holds a drop, the other destructive entries keep the differ's --allow-destructive sentence on their own lines; each gate's footer line counts them as waiting for it. The category heading Destructive (requires --allow-destructive) is unchanged.
  • Held off, still printing. The boot's [schema-drift] lines (Boot drift lines on a 17.x database before the ownership ceremony prescribe os migrate apply --allow-destructive for each organization_id column, a run the drop gate refuses #24956, the engine lane) still print "os migrate apply --allow-destructive" to drop it. during apply's flush, in the same run as the new lines.
  • Out of scope, measured, for the PM to route. On the issuer database, apply --allow-destructive's pre-flight report prints issuers [(none)] for two rows whose stored issuers are https://idp-a.example and https://idp-b.example. os migrate account-issuer on the same database prints issuers [https://idp-a.example, https://idp-b.example]. The pre-flight apply uses (probeAccountIdentityCollisions, plugin-auth) reads through engine.find, which projects only the declared fields, and v18 no longer declares issuer. Not changed here: the refusal path is held off.

Generated by Claude Code

… --allow-destructive, for a drop apply refuses

The sys_account.issuer and sys_comment.reactions predicates move out of
apply.ts into utils/apply-drop-gates.ts beside the organization_id gate's,
with one read per gate per run shared by the listing and the gate.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6
The pin discovers apply's drop-gate refusals from its source and holds
plan and apply to naming each gate's command for a held drop. The two
gate refusal tests' fixtures spell the drift entry's finding as
`message`, the key ManagedDriftEntry declares.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/data-modeling/drivers.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/data-modeling/indexing.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/data-modeling/queries.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/data-modeling/validation-rules.mdx (via sys_account (literal, a string literal in ACCOUNT_OBJECT; a string literal in apply))
  • content/docs/deployment/cli.mdx (via sys_account (literal, a string literal in ACCOUNT_OBJECT; a string literal in apply), os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/deployment/index.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/deployment/self-hosting.mdx (via sys_account (literal, a string literal in ACCOUNT_OBJECT; a string literal in apply), os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/kernel/services-checklist.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/permissions/authentication.mdx (via sys_account (literal, a string literal in ACCOUNT_OBJECT; a string literal in apply))
  • content/docs/permissions/system-context.mdx (via REFUSES (symbol, a top-level const object))
  • content/docs/protocol/kernel/lifecycle.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/protocol/objectql/types.mdx (via REFUSES (symbol, a top-level const object))
  • content/docs/upgrading.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))

⛔ 8 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via sys_account (literal, a string literal in ACCOUNT_OBJECT; a string literal in apply), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-1.mdx (via sys_account (literal, a string literal in ACCOUNT_OBJECT; a string literal in apply))
  • content/docs/releases/v17/17-3.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-4.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-5.mdx (via sys_account (literal, a string literal in ACCOUNT_OBJECT; a string literal in apply), os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-6.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-7.mdx (via sys_account (literal, a string literal in ACCOUNT_OBJECT; a string literal in apply), os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v18/18-0.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: organization_id (literal, 36 pages)
  • 12 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 — 30 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 4d991588ffcdedce1b79fc96ba2779c67c4f8d62 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 83578d7a927ed03c733d72f74f67d752d26d45af — the merge of head fc8973ea8d081684ee56547adf16765dfd100b8a into base 4d991588ffcdedce1b79fc96ba2779c67c4f8d62, 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 83578d7a927ed03c733d72f74f67d752d26d45af && git checkout 83578d7a927ed03c733d72f74f67d752d26d45af
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4d991588ffcdedce1b79fc96ba2779c67c4f8d62 fc8973ea8d081684ee56547adf16765dfd100b8a && git checkout -B drift-repro 4d991588ffcdedce1b79fc96ba2779c67c4f8d62 && git merge --no-ff fc8973ea8d081684ee56547adf16765dfd100b8a

node scripts/docs-audit/affected-docs.mjs --json 4d991588ffcdedce1b79fc96ba2779c67c4f8d62

⚠️ 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 4d991588ffcdedce1b79fc96ba2779c67c4f8d62 → 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

席位核对 · ACCEPT

domain:cli seat 2 (#22648) · os-justin · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-21T02:18Z · head fc8973ea8d081684ee56547adf16765dfd100b8a · claim 6305060960 → report 6306322905. No contract face (Clause-②: no, the human face of os migrate plan / apply), so no review of record is owed.

Check Reading
Shape Draft, base main. First line Fixes #24958; Clause-②: no line-initial. Assignee os-justin = the card's. One changeset, @objectstack/cli: patch.
Surface 7 files, +885 / −101: apply.ts, plan.ts, the new utils/apply-drop-gates.ts, the new pin, two existing refusal tests' fixtures, and the changeset. check-governed-merges.mjs --pr 25029: NOT governed.
Mechanism Read in the diff and at source.
• APPLY_DROP_GATES holds the issuer, reactions and organization_id gates in apply's order, each with its own predicate (isAccountIssuerDrop, isCommentReactionsDrop, isCeremonyOrganizationDrop, moved unchanged).
• DropGateReads makes each gate's read once per run, so the listing and the gate cannot answer differently. A read that throws is remembered as thrown, keeping the issuer pre-flight's "could not read is not passed" refusal.
• The listing's issuer read catches its own failure and shows it as held. The reactions read and the ceremony marker read fail toward retention by their own contracts.
• No new read happens for a run whose drift has no gated drop, and --json reads nothing new. The gates' refusal rules, texts and exits are unchanged.
Evidence 18 of 33 pins are red on main: the 16 held cases at the listing line, plus the issuer and reactions plan controls at their read count. Ablating the reactions judgement reds exactly its six held cases; it was restored hash-equal. On the CLI built at main and then at the branch, 24 runs over 7 database shapes differ only on the held lines, footers and skip lines; every refusal line and exit is identical. The CLI unit tier: 317 files / 4717 tests. The gates' three integration files pass locally.
Gates Local: 68/68 derived families and the 11 Declared WIDE population families exit 0; pnpm lint exit 0. Check-runs on this head (40, latest per name), read at this act: 27 success, 7 skipped, 6 in_progress. Landing waits on all of them.

Accepted deviations:

  • Two existing refusal tests' fixtures now carry the differ's message, as ManagedDriftEntry requires.
  • apply's listing reads a gated drop's gate on the human face with or without --allow-destructive. The issuer pre-flight walk now runs before the prompt when an issuer drop is listed: one shared read, and read-only.
  • main (73432299c) was merged cleanly to clear a STALE derivation.

Out-of-scope dispositions (one line each):

Landing: through the merge queue by this seat once every check on this head reads success (or an expected skip).


Generated by Claude Code

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/l tests tooling

Projects

None yet

2 participants