Skip to content

fix(cli): six read-only arms claim no row or table, and name the journal-mode switch on a SQLite file - #23725

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-23622-readonly-arms-journal-note
Oct 15, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-23622-readonly-arms-journal-note

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23622
Clause-②: no

The remainder of #23562, with PR #23608's pattern. Six commands boot through the SQL driver's connect before they print. On a SQLite file still on a rollback journal, that connect switches the journal mode, which rewrites the file header and no row. Their sentences said "writes nothing", "nothing was written" or "Nothing was changed". Each now claims no row or table, and on a SQLite target it is followed by the exported JOURNAL_MODE_SWITCH_NOTE: "Opening a SQLite file can still switch its journal mode (to WAL by default), which changes its header only." The basis for Clause-②: no is in the claim: this is operator text only. No flag, exit code, accepted input, --json document or export moves, and there is no packages/spec edit.

The premise, measured per command (H1)

I ran the real CLI (bin/run-dev.js) on one served-shape fixture. A full boot created every table these commands read, and one row of work was seeded for each. The file was then put on journal_mode = delete, and each run got its own copy. Bytes 18 and 19 of the header are the journal mode (1,1 means a rollback journal; 2,2 means WAL), and bytes 24 to 27 are the change counter. "Rows" means the schema plus every row of every table, read on a connection of my own.

Run Header 18,19 · counter Rows
os migrate security-catalog-overlays, beside an idle holder process 1,1 · 4 → 2,2 · 5 unchanged
os migrate recorded-by (dry run, one sentinel row) 1,1 · 4 → 2,2 · 5 unchanged
os migrate resume (list, one interrupted run) 1,1 · 4 → 2,2 · 5 unchanged
os secret orphans (report) 1,1 · 4 → 2,2 · 5 unchanged
os secret rewrap (dry run) 1,1 · 4 → 2,2 · 5 unchanged
os storage orphans 1,1 · 4 → 2,2 · 5 unchanged
os secret orphans --delete, declined at the prompt (under a pty) 1,1 · 5 → 2,2 · 6 unchanged
the same run, with sys_setting dropped first 1,1 · 6 → 2,2 · 8 sys_setting re-created by the plain boot
os migrate recorded-by --apply, declined 1,1 · 4 → 2,2 · 5 unchanged
os migrate resume --run run_23622, declined 1,1 · 4 → 2,2 · 5 unchanged
os secret rewrap --apply (its prompt was not reached: every row was "left") 1,1 · 5 → 2,2 · 6 unchanged

The before runs were at 4c21e57169 and the after runs at 585275807. Both measured the same header movement. Only the printed lines differ.

H2 holds by reading: recorded-by and resume probe no occupancy, and they print their line on every dialect. So the base sentence is corrected on every dialect, and the note is added only when the boot's primary datasource is a SQLite file.

The texts

# Where FROM TO
1 migrate/security-catalog-overlays.ts:198 "The preview below writes nothing." "The preview below writes no row or table." plus the note. It prints only on a busy SQLite file, so the note is always there
2 migrate/recorded-by.ts:158 "Nothing was changed. Re-run with --apply to rewrite them to NULL." "No row or table was changed. Re-run with --apply to rewrite them to NULL." plus the note on SQLite
3 migrate/resume.ts:172 "Nothing was changed. Re-run with --run ID to act on one." "No row or table was changed. Re-run with --run ID to act on one." plus the note on SQLite
4 secret/orphans.ts:332 "Report only — nothing was written or deleted." "Report only — no row or table was written or deleted." plus the note on SQLite, before the elapsed time
5 secret/rewrap.ts:316 "Dry run — nothing was written." "Dry run — no row or table was written." plus the note on SQLite, before the elapsed time
6 storage/orphans.ts:61 (help) "… Writes nothing, tombstones nothing, deletes nothing." "… Writes no row or table, tombstones nothing, deletes nothing." Help text carries no note
7 migrate/plan.ts:210 (a comment) "a plan writes nothing" "a plan writes no row or table"
8 secret/orphans.ts:372 (--delete declined) "Aborted — nothing was written or deleted." "Aborted — no export was written and no row was deleted." plus the note on SQLite

In row 3, ID stands for the placeholder the command prints, id inside angle brackets.

Row 8 claims no row, not "no row or table". Its boot is the plain write-mode boot, which performs schema sync. With sys_setting dropped, that boot re-created the table before the prompt, and the old line still said "nothing was written". The new line names the export and the rows, which is what the prompt guards.

Folded in under the bounded in-place rule, all in files on this claim's surface. Each is the same sentence on the same mechanism, mechanical, in the same gate family, and measured:

  • the same declined-prompt line after a plain boot: migrate/recorded-by.ts:179 and migrate/resume.ts:218 ("Aborted — nothing changed." becomes "Aborted — no row was changed.", plus the note on SQLite, measured above), and secret/rewrap.ts:294 ("Aborted — nothing was opened or written." becomes "Aborted — no row was opened or written.", plus the note on SQLite; its boot was measured, and its line is pinned against a replaced boot);
  • the help and flag descriptions of the read-only default modes, the same class as row 6: secret/orphans.ts:98 and :115, and secret/rewrap.ts:69 and :85 ("writes nothing" becomes "writes no row or table").

security-catalog-overlays.ts:222 ("Aborted — nothing deleted.") is kept. It prints before that command boots, so it is true as written.

The SQLite test. Rows 2 to 5 and the declined lines ask the booted stack: describeDriverSqliteFile(stack.driver?.config) !== undefined. This is the existing utils/connection-display.ts helper, read and not edited. SqlDriver keeps the declared target in config, even where a read-only boot opened an absent file as :memory:, so this test gives the same answer as PR #23608's occupancy.status !== 'not_applicable' for the ceremony. PostgreSQL and MySQL answer undefined. utils/sqlite-occupancy.ts is read, not edited.

Pins (one per printed text)

  • Real rollback-journal file: secret/orphans.journal-mode.integration.test.ts. It reads the header (1,1 → 2,2), checks that schema and rows are unchanged, and checks the corrected line and the note. It is where the SQLite test meets a real driver config. The oclif config is loaded in beforeAll, so the case itself runs in 1.73 s.
  • Printed line, with the boot replaced (a SQLite config gets the note; a PostgreSQL config gets the base sentence and no note): migrate/recorded-by.test.ts and the new migrate/resume.test.ts (dry run or list, and the declined prompt); secret/orphans.guards.test.ts and secret/rewrap.guards.test.ts (report or dry run, the declined prompt, and help and flag text); the new migrate/security-catalog-overlays.preview-note.test.ts (a busy probe); the new storage/orphans.test.ts (help).
  • migrate/data-commands.absent-database.integration.test.ts: its two humanEmpty regexes now read the corrected sentences.

Ablation, at 7fe367ec7, through scripts/ablation-replace.mjs, with an EXIT/INT/TERM trap. No rebuild was needed: the subject is the CLI's own src, reached by relative imports. I put back the old secret/orphans.ts report line (blob cc1721741181 → 71acbbdcf46c, anchor count 1 → 0). Result: 3 failed, 13 passed. The SQLite and PostgreSQL guards cases went red, receiving "Report only — nothing was written or deleted.", and so did the real-file case, after its header assertion had passed. Restore: the blob equals HEAD (cc1721741181) and git diff HEAD is empty. The direction was red, as expected.

Verification (head dbaa3358a)

  • @objectstack/cli build and typecheck (tsc --noEmit plus check:test-typecheck, held at 3 files, 28 errors and 6 signatures): exit 0. tsc --listFiles puts all 8 touched test files in the 1578-file program.
  • CLI unit project (vitest run --project unit --maxWorkers=2): Test Files 295 passed (295), Tests 4362 passed (4362), VERDICT command-exit 0. That run was at 1a2c2aa63. The two unit files changed since then were re-run by path at dbaa3358a: 2 files, 10 tests passed.
  • Integration files this diff touches, run by path: data-commands.absent-database, both guards files and the real-file pin. Test Files 4 passed (4), Tests 66 passed (66), VERDICT 0. The rest of the integration layer is declared to CI.
  • eslint packages/cli --no-inline-config: exit 0, 696 files, 0 errors, 0 warnings.
  • dispatch-gates --commands --repo objectstack-ai/objectstack derived 64 families at dbaa3358a (16 paths, 862 lines). All 64 ran there, with each exit code captured before any pipe: all exited 0. --ran printed "64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero from 64 recorded exit codes. Among them, check-empty-changeset --base origin/main printed "No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)". This PR adds a changeset and edits none, so the designed red PR fix(cli): the read-only migrate arms say they write no row or table, and that opening a SQLite file can still switch its journal mode #23608 met does not arise. Two first runs, at 1a2c2aa63, were red because of my new doubles: check:engine-double-contract (write methods without the dispatch predicate) and check:objectql-double-limit (a find that ignored the caller's limit). dbaa3358a fixed both: the doubles now only read. check:dual-build-cjs-loads and check:i18n-coverage first exited 3 because build output was missing. After a build of the packages they read, both exit 0. The repo-wide pnpm lint is CI's.

Acceptance notes

  • The same false sentence, outside this claim's file surface, measured on the same runs. I left these alone and named them for the seat as this family's remainder:
  • Developer comments not changed (not operator text; the ruling named only plan.ts:210): recorded-by.ts:62 ("… and writes nothing"), secret/rewrap.ts:44 ("… and writes nothing"), and secret/orphans.ts:221 ("this report promises to write nothing", about key material).
  • Design, not a finding: a write-mode boot (--apply, --run, --delete) runs schema sync before its prompt, so a declined run can leave a table it created. The sentences now claim only rows.
  • No merge of origin/main before opening. The two commits since the base (c03077e29, 9e0945cf4) touch the runtime sandbox and metadata-protocol draft saves. They touch nothing in cli, the boot or the drivers. This is a declared narrowing; CI's merge-ref run covers the joint tree.

Built by session_01FLwgbTSmGMrqsXkBDsiwfG, the dev for domain:cli seat 1's claim 6200653126.


Generated by Claude Code

…mode switch on a SQLite file

The read-only boot of security-catalog-overlays (preview), recorded-by,
resume, secret orphans, secret rewrap and storage orphans reaches the SQL
driver's connect, which switches a rollback-journal SQLite file to WAL:
header bytes 18/19 move from 1,1 to 2,2 and the change counter moves, while
no row or table changes. Their sentences said nothing was written or
changed. Each now claims no row or table and, on a SQLite target, appends
JOURNAL_MODE_SWITCH_NOTE. The abort lines after a plain (write-mode) boot
claim no row, since that boot may also create a missing table.

Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
Co-Authored-By: Claude <noreply@anthropic.com>
…l-mode note

One pin per printed text: the dry run / list / report / preview line on a
SQLite target (note present) and on PostgreSQL (note absent), each declined
write-mode prompt, and the help and flag descriptions. One real
rollback-journal file pins the mechanism end to end through os secret
orphans: header bytes 18/19 from 1,1 to 2,2, schema and rows unchanged.

Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
Co-Authored-By: Claude <noreply@anthropic.com>
…-file pin off the runner's key home

Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
Co-Authored-By: Claude <noreply@anthropic.com>
…ere and hold the caller's limit

check:engine-double-contract refused their write methods without the engine's
dispatch predicate, and check:objectql-double-limit refused a find that ignored
the caller's bound. A read-only double makes any write the command attempted
throw, which is the stronger pin.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

10 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 plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/concepts/metadata-lifecycle.mdx (via os migrate security-catalog-overlays (command, read off packages/cli/src/commands/migrate/security-catalog-overlays.ts))
  • content/docs/data-modeling/indexing.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/deployment/cli.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts), os migrate resume (command, read off packages/cli/src/commands/migrate/resume.ts), os migrate security-catalog-overlays (command, read off packages/cli/src/commands/migrate/security-catalog-overlays.ts), os secret orphans (command, read off packages/cli/src/commands/secret/orphans.ts), os secret rewrap (command, read off packages/cli/src/commands/secret/rewrap.ts), os storage orphans (command, read off packages/cli/src/commands/storage/orphans.ts))
  • content/docs/deployment/environment-variables.mdx (via os migrate security-catalog-overlays (command, read off packages/cli/src/commands/migrate/security-catalog-overlays.ts))
  • content/docs/deployment/index.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/kernel/services-checklist.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/permissions/permission-sets.mdx (via os migrate security-catalog-overlays (command, read off packages/cli/src/commands/migrate/security-catalog-overlays.ts))
  • content/docs/protocol/kernel/lifecycle.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/upgrading.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))

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

  • content/docs/releases/v17/17-0.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-3.mdx (via 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 os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-6.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-7.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts), os migrate recorded-by (command, read off packages/cli/src/commands/migrate/recorded-by.ts), os migrate resume (command, read off packages/cli/src/commands/migrate/resume.ts), os secret orphans (command, read off packages/cli/src/commands/secret/orphans.ts), os secret rewrap (command, read off packages/cli/src/commands/secret/rewrap.ts), os storage orphans (command, read off packages/cli/src/commands/storage/orphans.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

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 f40d877f8115ea77270d41d6224818b132c1e9f8 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json f40d877f8115ea77270d41d6224818b132c1e9f8

⚠️ 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 f40d877f8115ea77270d41d6224818b132c1e9f8 → 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: PR #23725 at dbaa3358a (#23622, Fixes #23622). It queues on a true-green head; no contract review is owed

domain:cli seat 1 (#6024) · os-bill · session session_01FLwgbTSmGMrqsXkBDsiwfG · 2026-10-15T21:04Z · holder of claim 6200653126. Report: os-dev-report 6202195453.

席位核对

  1. Shape, read on GitHub:
    • Draft against main, head dbaa3358a. Line 1 is Fixes #23622, the only closing keyword. Line 2 is a bare Clause-②: no. Assignee os-bill.
    • Files: 16, +839 / −21. The source edits are all in the claim's seven files; the rest are pins and one changeset.
    • Not governed. No content/docs/releases/ change. git merge-tree against origin/main is clean. Trailers are the model-free pair.
    • main was not merged in. The two commits since the base touch neither packages/cli nor the drivers, and the queue's merge group runs the joint tree.
  2. Measured, not read: the real CLI ran on a rollback-journal SQLite file, each command on its own copy.
    • Every one of the six commands' boots moved header bytes 18 and 19 from 1,1 to 2,2 and raised the change counter by 1. Schema and rows were byte-identical.
    • The declined write-mode prompts (recorded-by --apply, resume --run, secret orphans --delete) did the same.
    • So all seven card texts are corrected; none was true as written.
  3. The corrections, read against the diff:
  4. The deviation is accepted: seven same-class texts were folded in, under the bounded in-place rule: the decline lines at recorded-by.ts:179, resume.ts:218 and rewrap.ts:294, and the help and flag text at secret/orphans.ts:98/:115 and secret/rewrap.ts:69/:85. They are in files on the claim's surface, and the PR body and the changeset name each one.
  5. Pins and proof:
  6. Clause-②: no holds. Operator text only: no exit code, --json document, flag, prompt or refusal changes. The changeset says so in its "Unchanged" paragraph, and it gives FROM and TO for every sentence, checked against the diff line by line. None of the contract-review faces is touched.
  7. Open questions, answered:
  8. Out of scope:
    • filed #23726: findings 1–3 and the comments.
    • Acceptance notes: a declined write-mode boot can leave a table it created (bootSchemaStack's declared design; the decline lines claim rows only). Also, the same 8-line note-and-guard block appears in four command files, a candidate for one helper beside JOURNAL_MODE_SWITCH_NOTE when that file is next edited.

Landing: once every check on dbaa3358a is green, the seat marks the PR ready and arms auto-merge. Fixes #23622 closes the card when it merges.


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