Repository navigation
fix(cli): six read-only arms claim no row or table, and name the journal-mode switch on a SQLite file - #23725
Conversation
…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>
…ected sentences 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>
📓 Docs Drift CheckThis PR changes 1 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
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): Which tree this was computed onThis run read A worktree cut from an older # 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
|
✅ ACCEPT: PR #23725 at
|
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 forClause-②: nois in the claim: this is operator text only. No flag, exit code, accepted input,--jsondocument or export moves, and there is nopackages/specedit.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 onjournal_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.os migrate security-catalog-overlays, beside an idle holder processos migrate recorded-by(dry run, one sentinel row)os migrate resume(list, one interrupted run)os secret orphans(report)os secret rewrap(dry run)os storage orphansos secret orphans --delete, declined at the prompt (under a pty)sys_settingdropped firstsys_settingre-created by the plain bootos migrate recorded-by --apply, declinedos migrate resume --run run_23622, declinedos secret rewrap --apply(its prompt was not reached: every row was "left")The before runs were at
4c21e57169and the after runs at585275807. Both measured the same header movement. Only the printed lines differ.H2 holds by reading:
recorded-byandresumeprobe 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
migrate/security-catalog-overlays.ts:198migrate/recorded-by.ts:158migrate/resume.ts:172secret/orphans.ts:332secret/rewrap.ts:316storage/orphans.ts:61(help)migrate/plan.ts:210(a comment)secret/orphans.ts:372(--deletedeclined)In row 3, ID stands for the placeholder the command prints,
idinside 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_settingdropped, 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:
migrate/recorded-by.ts:179andmigrate/resume.ts:218("Aborted — nothing changed." becomes "Aborted — no row was changed.", plus the note on SQLite, measured above), andsecret/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);secret/orphans.ts:98and:115, andsecret/rewrap.ts:69and: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 existingutils/connection-display.tshelper, read and not edited.SqlDriverkeeps the declared target inconfig, even where a read-only boot opened an absent file as:memory:, so this test gives the same answer as PR #23608'soccupancy.status !== 'not_applicable'for the ceremony. PostgreSQL and MySQL answerundefined.utils/sqlite-occupancy.tsis read, not edited.Pins (one per printed text)
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 inbeforeAll, so the case itself runs in 1.73 s.migrate/recorded-by.test.tsand the newmigrate/resume.test.ts(dry run or list, and the declined prompt);secret/orphans.guards.test.tsandsecret/rewrap.guards.test.ts(report or dry run, the declined prompt, and help and flag text); the newmigrate/security-catalog-overlays.preview-note.test.ts(a busy probe); the newstorage/orphans.test.ts(help).migrate/data-commands.absent-database.integration.test.ts: its twohumanEmptyregexes now read the corrected sentences.Ablation, at
7fe367ec7, throughscripts/ablation-replace.mjs, with an EXIT/INT/TERM trap. No rebuild was needed: the subject is the CLI's ownsrc, reached by relative imports. I put back the oldsecret/orphans.tsreport line (blobcc1721741181→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) andgit diff HEADis empty. The direction was red, as expected.Verification (head
dbaa3358a)@objectstack/clibuild andtypecheck(tsc --noEmitpluscheck:test-typecheck, held at 3 files, 28 errors and 6 signatures): exit 0.tsc --listFilesputs all 8 touched test files in the 1578-file program.vitest run --project unit --maxWorkers=2): Test Files 295 passed (295), Tests 4362 passed (4362), VERDICT command-exit 0. That run was at1a2c2aa63. The two unit files changed since then were re-run by path atdbaa3358a: 2 files, 10 tests passed.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/objectstackderived 64 families atdbaa3358a(16 paths, 862 lines). All 64 ran there, with each exit code captured before any pipe: all exited 0.--ranprinted "64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero from 64 recorded exit codes. Among them,check-empty-changeset --base origin/mainprinted "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, at1a2c2aa63, were red because of my new doubles:check:engine-double-contract(write methods without the dispatch predicate) andcheck:objectql-double-limit(a find that ignored the caller's limit).dbaa3358afixed both: the doubles now only read.check:dual-build-cjs-loadsandcheck:i18n-coveragefirst exited 3 because build output was missing. After a build of the packages they read, both exit 0. The repo-widepnpm lintis CI's.Acceptance notes
os storage orphansstill prints "This pass is READ-ONLY: nothing was written, tombstoned or deleted." That line comes fromservices/service-storage/src/stranded-orphan-inventory.ts:413.os secret rewrap's dry run still prints "Dry run: every attributed row was opened, re-sealed and verified in memory, and nothing was written." That isREWRAP_NOTES.dryRunincli/src/utils/sys-secret-rewrap.ts:504, which is in--jsontoo.os migrate summary-nulls' dry run still prints "Dry run — nothing was written. Re-run with --apply to backfill." That line comes fromobjectql/src/summary-backfill.ts:534. cli: the ownership ceremony's plan arm says "The plan below writes nothing" and "nothing in the database was changed", but its first connect converts a rollback-journal SQLite file to WAL (header bytes and change counter move) #23562's dev measured this boot's switch.plan.ts:210):recorded-by.ts:62("… and writes nothing"),secret/rewrap.ts:44("… and writes nothing"), andsecret/orphans.ts:221("this report promises to write nothing", about key material).--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.origin/mainbefore opening. The two commits since the base (c03077e29,9e0945cf4) touch the runtime sandbox and metadata-protocol draft saves. They touch nothing incli, 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 fordomain:cliseat 1's claim6200653126.Generated by Claude Code