Skip to content

DR-131: CI suites that mutate the corpus run against a disposable database

DR-131: CI suites that mutate the corpus run against a disposable database

Section titled “DR-131: CI suites that mutate the corpus run against a disposable database”

DR-096 moved the E2E lanes onto a runner-local Supabase stack and scoped itself to those lanes. Nothing ruled on the integration lanes, which kept writing to shared Platform staging. The only record of that split is a parenthetical in id-365 — “integration-nightly still targets staging by design — different lane” — which asserts scope, not a reason. No document states a property of the integration suite that requires a shared database.

S538 measured the cost of that gap. promote-corpus.integration.test.ts calls promoteCorpusExtractions, the authoritative knowledge-admission gate (R2 / DR-025), through an RPC that takes no arguments and claims every eligible row DB-wide. Its teardown tracks only the ids it seeded. Fired nightly by cron against shared staging, it published and embedded 88 mock-tier q_a_pairs over eight runs — attributed to their runs on timestamp, 8 for 8, each run failing and publishing anyway.

Those rows were not confined to the lane that made them. They reached 57% of the q_a_pairs table, polluted q_a_search on every real query (median 10 of 20 results, worst 17 of 20), filled the MCP kb://qa/ resource list 10 of 10, and formed a complete pairwise dedup clique — C(88,2) = 3828 candidates at cosine 1.0000 — that cost the cocoindex nightly ~300s per walk and pushed it past its 90-minute job cap every night. An assertion in two-step-retrieval had already been weakened to survive the accumulation.

A CI suite that mutates the corpus beyond the rows it seeds runs against a disposable database, never a shared one. Both integration lanes (ci.yml’s integration job and integration-nightly.yml) provision a per-run local Supabase stack, using the machinery DR-096 already proved. requireDisposableDatabase() enforces it at the suite boundary by refusing any non-loopback host — the TypeScript analogue of the Python tier’s existing require_disposable_dsn().

  • Scope the promotion RPC. Rejected for this decision, on the narrow ground that the test’s own assertions are already per-seeded-row, so the corpus-wide sweep buys it nothing. Whether the RPC should be scoped remains open — see Consequences.
  • Move only the offending file to its own lane. Rejected: a second vitest config, workflow and env block for the same ~2 minutes, and the env drift that follows. vitest.integration.config.ts has one include.
  • Extend the nightly sweep to delete mock rows. Rejected on measurement: q_a_pairs is outside the sweep’s table set on any key, so it could not have removed one of the 88 rows, and extending it there collides with the ratified S511 D1 amendment that showcase/platform content is never sweep-eligible.
  • ~2 minutes per run (supabase start ~64s + db reset ~28s). No build and no server: the suite imports route handlers in-process, so it needs a database only.
  • The shared integration-staging-integration concurrency group is deleted from both lanes, as integration-staging-e2e was at DR-096. Integration jobs from different PRs stop serialising.
  • environment: Staging is retained on both lanes — it scopes the secrets, not the database.
  • Assertions weakened to tolerate shared-DB accumulation can be tightened. At least one is known (two-step-retrieval.integration.test.ts:364) and is not yet fixed.
  • This does not make the lanes green. 10 of 15 failures were a retired form_types key and are fixed separately; the other 5 are promote/eligibility failures whose cause is not established.
  • Left open, deliberately. Whether promoteCorpusExtractions should accept a scope is unreconciled: a route-file comment (app/api/governance/promotion-candidates/[extractionId]/accept/route.ts:17-23) describes new/self_healing as promoted wholesale, while DR-099 rules that an unscoped global claim is a defect, not a default. A route-file comment is evidence of what exists, never of what is correct. This decision changes the substrate and does not resolve that question.
  • Not validated by execution — no stack was started and no workflow run in S538. The first CI run exercises it.