Repository navigation
test(driver-sql): pin the NODE_ENV=production auto-migrate guard with a safe drift, beside a dev control - #24967
Conversation
… a safe drift The case staged relax_not_null, which ADR-0113 made needs_confirm, so auto-reconcile never applied it with or without the guard, and it asserted no warning. It now stages a runtime-token column DEFAULT (drop_column_default, category safe), asserts the DEFAULT survives and the production warning is logged, beside a development control showing the same boot removes it. Claude-Session: https://claude.ai/code/session_01XrcLWFkToZjHv2DJDAHhz4 Co-authored-by: Claude <noreply@anthropic.com>
…od-guard-drift-test
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contract reviewServed-tier: Scope: the net diff against the merge base ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对 ·
|
| Item | Reading |
|---|---|
| Shape | Draft PR → main. The body opens Fixes #24903. 1 file, +46/−3, test only. Not governed: check-governed-merges matched 0 of 1 path. |
| Scope | The production-guard case stages a safe drop_column_default drift, and asserts three things under NODE_ENV=production: the DEFAULT survives, the drift is still safe, and the production warning reached the driver's logger. A development control shows the same boot removes the DEFAULT. No product code moves. |
| Seat's own read | The guard is if (autoOn && this.isProductionEnv()) in reconcileAndWarnDrift, and its warning goes through this.logger.warn, which makeDriver mocks. So the case reads the right channel. git merge-tree against today's origin/main is clean. |
| Review | TIER_DEFAULT, in-seat. Record 6299521184 is PASS on this head. |
| Declaration | No changeset, labelled skip-changeset: the file ships in no package. Clause-②: no (test only: …) is at a line start, as on the claim and the PR body. |
| Cross-lane | None in the diff. cli.dev-automigrate-policy's automated.ref in docs/qa/platform-checklist/areas/cli.json is domain:devx's, and the landing comment tells that lane. |
| Gates | Dev at 41b3fdf86: 54 derived, 54 run, all exit 0. driver-sql full suite 3692 passed, core test:repo 82 passed. Ablation: the production condition forced false makes the case red and leaves the control green; restored to HEAD. |
| CI | At this read on 41b3fdf86a: ci-failure GREEN, all 41 check-runs completed, none failed. |
Disposition:
open_questions: both as the dev settled them (record ③):drop_column_defaultoverreplace_unique_index, and an explicitNODE_ENV=developmentcontrol.deviations: all accepted (record ③).out_of_scope_findings:sql-driver-index-drift.test.ts's production case is not vacuous; it meets no filing gate.
Landing: ordinary queue (pr_ready + automerge_enable through the relay). Fixes #24903 then closes the card.
Generated by Claude Code
Fixes #24903
Clause-②: no (test only: no product code, accept set or served shape moves)
What changed
One case in
packages/drivers/driver-sql/src/sql-driver-schema-drift.test.tsis re-staged, and a development control is added beside it. Test only: no product file moves.relax_not_null. ADR-0113 made thatneeds_confirm, andreconcileAndWarnDrift(sql-driver.ts) applies onlyd.category === 'safe'. So the column stayed NOT NULL with or without the production guard, and the case asserted no warning.safedrift: acurrent_userruntime token written as the column DEFAULT by a pre-data: the SQL driver emits thecurrent_userframework token as a literal column DEFAULT, so the database itself writes a non-id into alookup('sys_user')#4560 build, whichdetectManagedDriftclassesdrop_column_default/safe. UnderNODE_ENV=productionwithautoMigrate: 'safe'it asserts:columnInfo().owner.defaultValuestill carriescurrent_user);safe/drop_column_default, so the guard is the only thing that held it back;logger.warnreceived[schema-drift] autoMigrate='safe' is ignored under NODE_ENV=production, matched on that stable prefix. The em-dash tail is left unpinned.NODE_ENV=development. The DEFAULT is removed,detectManagedDrift()answers[], and no production warning is logged. The pair cannot pass vacuously again.NODE_ENVis restored infinallyin both cases. The logger is a per-drivervi.fn()object (the file's ownmakeDriver), so no global spy is left to restore.Why
drop_column_defaultThe
safeopsdetectManagedDriftemits onmain(schema-drift.ts) aredrop_column_default,widen_varchar,replace_unique_indexandcreate_index.widen_varcharis gated onenforcesVarcharLength(postgres / mysql only), so it never fires on this file's SQLite driver.create_index:syncTableIndexescreates a missing declared index additively duringinitObjects, whateverautoMigratesays. The guard would not be the only reason it is absent.replace_unique_indexworks, but it needs a tenant-scoped unique field, an organization column and an index read-back. It is already the stage ofsql-driver-index-drift.test.ts's production case. PR feat(driver-sql,driver-turso,driver-memory,objectql,cli,metadata-protocol): plain organization key part in new unique indexes; the differ reads COALESCE(tenant) as tenant on a certified database (18.x step 7b-1 + 7b-2) #24893 is also rewriting the NULL-safe key part it compares.drop_column_defaultneeds one column and one read-back, and stays in this file'sbiz_unitfixture idiom. The SQLite rebuild path applies it (sql-driver-runtime-token-default.test.tsmeasures that).PR #24893 (draft) edits
schema-drift.ts. Its patch changes nocategory:line, and it does not touch the runtime-token default branch. It does not change which ops aresafe.The guard, as read
reconcileAndWarnDrift:autoOn = this.autoMigrate === 'safe' && this.schemaMode === 'managed', thenif (autoOn && this.isProductionEnv()).isProductionEnv()is(process.env.NODE_ENV ?? '').toLowerCase() === 'production', so the check is case-insensitive and reads nothing else. The warning goes out once per drifting table throughthis.logger.warn, notconsole.warn. The case spies on that logger.Verification (head
41b3fdf86,origin/mainmerged at19e5338cc)pnpm --filter @objectstack/driver-sql exec vitest run --maxWorkers=2 src/sql-driver-schema-drift.test.ts: 15 passed (was 14).node scripts/ablation-replace.mjs --file packages/drivers/driver-sql/src/sql-driver.ts --anchor 'if (autoOn && this.isProductionEnv()) {' --replacement 'if (false /* ABLATED-24903 */) {' -- vitest runon that file.b4393f25d41bbecame98a32996463a.AssertionError: expected 'null' to contain 'current_user'. The control stayed green: 1 failed / 14 passed.b4393f25d41b, andgit diff HEADis empty.SqlDriverfrom../src/index.js, so nodist/rebuild sits between the mutation and the run.pnpm --filter @objectstack/driver-sql typecheckexit 0, run before and after the merge.tsc --listFilesincludes this test file.pnpm --filter @objectstack/driver-sql testexit 0 before and after the merge: 225 files passed and 12 skipped (the live-dialect suites), 3692 tests passed.pnpm --filter @objectstack/core test:repoexit 0: 6 files, 82 tests.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackgives 54 commands. All 54 were re-run on the final head with exit codes captured before any pipe: 54 of 54 exit 0 (26 direct node, 28 pnpm).check:dual-build-cjs-loadsandcheck:lean-entry-closurefirst exited 3 (PREREQUISITE NOT MET). They were re-run afterpnpm build(76/76 tasks) and exited 0.--ranreconciliation: 54 derived, 54 run, 0 NOT-MEASURED (a derived zero).@objectstack/driver-sql'sfiles[](dist,README.md,CHANGELOG.md). The builtdist/has zero hits for the test file name, and the positive control (the warning text) hitsdist/index.js.skip-changesetis declared in the same create.Acceptance notes
qa/,docs/qa/platform-checklist/andcontent/docs/.sql-driver-index-drift.test.ts, "is force-disabled under NODE_ENV=production — the index survives untouched". It stagesreplace_unique_index(safe) beside a dev self-heal sibling, so it is not vacuous. It does not assert the warning. Noted, not built.dev-self-heal-host-parity,runtime/src/dev-auto-migrate,dev-standalone-self-heal) pin a different layer: the host does not passautoMigratein production. They do not pin this driver branch.docs/qa/platform-checklist/areas/cli.jsonnames the warning in a live-boot step and carries this case asknownGaps. Re-adding the case tocli.dev-automigrate-policy'sautomated.refisdomain:devx's, and is not touched here.os-devagent fordomain:engineseat 1, under claim 6297759904 on driver-sql test: "is force-disabled under NODE_ENV=production (warns, does not alter)" stages needs_confirm relax_not_null, which auto-reconcile never applies anyway — it passes with the production guard deleted and asserts no warning #24903.🤖 Generated with Claude Code
Generated by Claude Code