one_d4: one writer of the schema, and no scrapers left (#1401, #1419, #1426) - #1449
Conversation
Five fixtures hand-copied DDL, and schema_contract_test compared their CREATE TABLE bodies against the migration files by regex. Both are gone. migration_files applies one_d4/migrations/pg into a schema of its own and refuses a connection that does not resolve there. pg::Client::ExecScript runs a file whole over the simple protocol, since PQexecParams carries one statement. Two fixtures were laxer than production: game_features.request_id and platform are NOT NULL. retention_test now asks information_schema whether the timestamps the sweep binds are naive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ST7XLGWstRE4CfUF84ijKD
TestOneD4SelectorCoversTheServiceTheCppWorkerReportsAs regexed .service_name out of worker_main.cc to show the indexing selectors cover it. It pinned a spelling, and TestOneD4QueriesNameRealInstrumentsAndScopeThem already asserts every indexing selector is service_name=~"one_d4(_worker)?". The coupling is stated in registry.go and worker_main.cc instead. The worker_service_name filegroup and the Go test's data dep go with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ST7XLGWstRE4CfUF84ijKD
one_d4_migrate has applied the migrations before the service starts, so Migration.run() at boot was a second writer of the same DDL. Migration.verify() applies the steps to a scratch schema inside a transaction it always rolls back, and reports what the live schema does not have. Nothing is committed and the deployed tables are never touched — the statements only see the empty copies. Tables, columns, indexes and constraints by name; types are not compared. The H2 test path has no step in front of it and still applies at boot, which SqlDialect.migratedBeforeBoot() names. No tracking table: nothing has pressed for one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ST7XLGWstRE4CfUF84ijKD
|
Review panel on #1449 (four lenses: correctness/control flow; data access/SQL/resource safety; tests/docs/CI; altitude/design). Each agent hunted then tried to refute; survivors re-checked against HEAD Headline: right shape for #1401 / #1419 leftover / #1426 — one deployed writer ( 2 findings in the inline comments on Also outside the diff (fold-in here or follow-up):
Noted, not a hold: Verified clean (not reported):
Non-blocking on the schema-writer change itself. Fold the three doc contradictions so the next author does not reintroduce a boot migrator from the README. |
Two READMEs still said Migration applies at boot. one_d4's also told a local dev that starting the service was enough to bring a database up — it now fails verification instead. Adds the one_d4_migrate run to that recipe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ST7XLGWstRE4CfUF84ijKD
d2c53ee to
bba868a
Compare
|
All three doc findings fixed in The out-of-diff one was the worst of them, and worse than "still describes the second writer": On the O(steps) boot cost: agreed it is not what #1426 bought, and it is not free.
Generated by Claude Code |
|
Recheck on Prior findings: all three closed.
No leftover dual-writer wording in Surviving findings: none. O(steps) verify cost stays noted, not a hold — author’s timing note on the suite is enough. CI: required checks green on this head except |
|
|
Closes #1401, closes #1419, closes #1426.
The C++ Postgres suites run the real migrations (#1401 Group B)
schema_contract_testregex-parsedCREATE TABLE/ALTER TABLEout ofmigrations/pgand compared it to DDL hand-copied into five fixtures. Both halves are gone: the fixtures apply the migration files.migration_files(testonly) readsmanifest.txt, resolves each step the wayMigrationFiles.javadoes, and runspg/+ shared into a schema of its own.pg::Client::ExecScriptruns a file whole over the simple query protocol.Execbinds parameters, which puts it on the extended protocol, and that carries one statement per message however few parameters are passed — so a two-statement.sqlfile is an error there. Running the file whole also means no C++ twin ofSqlStatements.current_schema()before migrating. A fixture whosesearch_pathwent missing would otherwise build its tables in public beside every sibling suite's rows.Two fixtures were laxer than production, and now fail if they stay that way:
game_features.request_idandplatformareNOT NULL, andmotif_occurrences.idis not generated.retention_testkeeps one schema assertion, asked ofinformation_schemarather than of the DDL text: the sweep binds naive UTC literals, and a column migrated toTIMESTAMPTZwould compare through the session timezone and cut a different set of rows — identically to this one under the suite's UTC stamps, so no result can show it.RunsInItsOwnSchemaRatherThanPublicis deleted;ResetToMigratedSchema's check makes it unfailable, and covers all five suites instead of one.Boot verifies the schema instead of writing it (#1426)
one_d4_migrateapplies the migrations before the service starts, soMigration.run()at boot was a second writer of the same DDL.Migration.verify()applies the steps to a scratch schema inside a transaction it always rolls back, then reports what the live schema does not have. Nothing is committed and the deployed tables are never touched — the statements only ever see the empty copies. What to expect comes from the files, not from a list anyone maintains.Existence only: tables, columns, indexes and constraints by name. Types are not compared — both runners read one set of files, and refusing to serve is too blunt an answer to a column somebody widened by hand.
The H2 test path has no deploy step in front of it and still applies at boot.
SqlDialect.migratedBeforeBoot()names that, defaulting to true so a dialect added for a deployed engine cannot become a second writer by omission.Migration.atBoot()holds the choice, so a test can watch what it wrote.Local dev follows:
one_d4/README.mdused to say starting the service was enough to bring a database up. It runsone_d4_migratefirst now.#1426's other question — whether re-run-everything should grow a tracking table — stays answered "no": neither pressure it named (boot time growing with the step count, a step that cannot be written idempotently) exists.
migrations/README.mdkeeps that trigger without the issue.#1426's proposed exit criteria were a handful of clean deploys first. Folding it in here on request, so the first deploy after this is where the verifier proves itself; the failure mode is a crash-loop against a schema
one_d4_migratedid not finish, which is what its message names.The Go scraper (#1401 Group A)
TestOneD4SelectorCoversTheServiceTheCppWorkerReportsAsregexed.service_nameout ofworker_main.cc.TestOneD4QueriesNameRealInstrumentsAndScopeThemalready asserts every indexing selector isservice_name=~"one_d4(_worker)?"; what the scrape added was that this literal matches howworker_main.ccspells it. Deleted, with the coupling stated in both files — theMAX_RUNdisposition.worker_service_nameand the Go test's data dep go with it.What #1419 was still holding open
Its "one copy of the DDL" bullet listed
PostgresSqlDialect, the C++ fixture hand-copies, and the H2 translations. The first went with #1419 itself, the second goes here, and the third is option 1 as the issue chose it —h2/*.sql, test-only. Option 2 (moving the Java schema tests onto Postgres) the issue calls its own decision, and it stays one.one_d4's gate on the migrate one-shot survives with a new reason: not to serialize two writers, but because boot now refuses to serve against a schema that step did not finish.deploy_config_test.go,compose.yamland both READMEs say so.Testing
Local Postgres 16 with
PG_TEST_DB_URLset, so the gated suites ran rather than skipped: 95/95 C++ underdomains/, 66/66 acrossone_d4,prom_proxy,deployandbazel/rules.Mutation checks, each killed:
lease_expires_atrenamed inV014pg_queue_test,pg_game_sink_test,retention_test— and not the two reanalysis suites, which do not read itgame_features.indexed_at→TIMESTAMPTZTheTimestampsTheSweepComparesAreNaive, alone in its suitesearch_pathdropped fromretention_test's conninfoSetUpASchemaNameThatIsNotAPlainIdentifierIsRefusedmanifest.txtEveryMigrationFileIsReachableFromTheManifestpg/and a shared fileEveryManifestStepResolvesForBothEnginesPGRES_EMPTY_QUERYtreated as successRefusesAScriptWithNoStatementsInItatBoot()applies instead of verifyingbootDoesNotCreateTheSchemaOnTheDeploymentDialectverify()commits instead of rolling backwritesNothingnamesTheIndexAMissingStepWouldHaveBuiltH2SqlDialect.migratedBeforeBoot()→ trueI did not run a review panel myself; this session cannot spawn subagents. cursor[bot] ran a four-lens panel against
9732899; its three findings were all authoring-doc drift and are fixed inbba868a, with the two inline threads answered and resolved.mainmoved (#1448) and is merged in at3fedff6.🤖 Generated with Claude Code
https://claude.ai/code/session_01ST7XLGWstRE4CfUF84ijKD