refactor: scheduled_testing_eligible column + derivation bridge (PR A)#88
Merged
Merged
Conversation
…idge (pr a) Decouple billing data (`external_cost_cents`) from the scheduling signal that drives the hourly test scheduler. PR A introduces the new column and a derivation bridge; PR B (separately tracked) will force explicit declarations at INSERT sites and remove the bridge. The May 2026 Haiku token spike (PRs #84/#85/#86/#87) was structurally possible because `external_cost_cents = 0` did double duty as billing data and scheduling signal. A compound-PR pattern (cadence flip + deferred cost-bump) silently turned billing-data lag into a scheduling regression for 73 LLM caps for 7 days. Decoupling these concerns makes that class of failure impossible at the data model. Changes - Schema: add `test_suites.scheduled_testing_eligible BOOLEAN NOT NULL DEFAULT FALSE` to schema.ts. - Migration 0063: backfill `eligible = TRUE` where `cost = 0`. Preserves current dispatch behavior exactly. Post-condition DO block asserts parity between the two filters. - Scheduler: `findOverdueCapabilities`, `countOverdueCapabilities`, and `countPaidSkipped` swap to read the new column. - Diagnostic script `investigate-singapore.ts` swaps the scheduling-pool-proxy reader. - Startup block 0066 `runMigration0066_reconcileEligibilityFromCost`: re-derives eligibility from cost at every boot as the interim derivation bridge. Catches any INSERT site that lands a row without setting eligibility explicitly. PR B removes this block when INSERT sites are forced explicit. PR A intentionally does NOT touch the 12 INSERT call sites. The default FALSE + block 0066's `cost = 0 ⇒ eligible = TRUE` derivation means new free caps land at eligible = TRUE on next boot. Refines DEC-20260503-B without superseding. See DEC-20260511-B. Closing-steps rule walk: - Rule 4 (source-health integrity): the dispatch swap is the named carve-out from the structural follow-up (PR #85 explicit reference). - Rule 7 (bug-fix framework): not invoked. Structural prevention, not a bug fix. The May 2026 leak is contained. - Rules 8 + 9: hand-written Drizzle migration + schema.ts sync in the same commit. Per DEC-20260420-A. - Rule 11 (DEC supersession): does NOT supersede DEC-20260503-B. Refines. No sweep needed. - Rule 12 (audit follow-up tests): new code paths covered — dispatch swap by parity (backfill makes pre/post sets identical), block 0066 by post-condition DO block, observability swap by same parity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
petterlindstrom79
added a commit
that referenced
this pull request
May 11, 2026
…phase 3 harden) (#89) Codify the in-startup-migrations.ts pattern as the official schema-change convention for the api service. Retire the dual-track Drizzle SQL surface that misled PR #88 into a healthcheck failure. Background. PR #88 (2026-05-11) added apps/api/drizzle/0063_*.sql expecting the file to apply at deploy time. Nothing in this codebase applies Drizzle SQL files anywhere in the deploy pipeline — the Dockerfile CMD runs node dist/index.js, which calls runStartupMigrations() (in-TS blocks), which never invokes drizzle-kit migrate. The drizzle.__drizzle_migrations tracking table exists under the `drizzle` schema with 60 historical entries but its last apply was ~2026-04-04; since then, in-TS blocks have been doing the schema work. PR #88's healthcheck failed because in-TS block 0066 referenced the new column before anything created it. Phase 1 (Contain) and Phase 2 (Understand) shipped 2026-05-11. This is Phase 3 (Harden) per DEC-20260511-C. Changes - apps/api/src/lib/startup-migrations.ts — block 0066 renamed to runMigration0066_ensureEligibilityColumnAndReconcile and the SQL now starts with ALTER TABLE ADD COLUMN IF NOT EXISTS before the existing reconciliation UPDATE. Idempotent on existing prod (column already there from Phase 1 manual recovery); functional on fresh DBs. - apps/api/drizzle/ — directory deleted (63 SQL files + meta/ + README.md). - apps/api/drizzle.config.ts — deleted. - apps/api/scripts/check-migration-prefixes.mjs — deleted (vestigial with the dir gone). - apps/api/scripts/verify-migration-rename.ts — deleted (forensic script for an already-resolved 0046 collision). - .github/workflows/ci.yml — check-migration-prefixes step removed. - apps/api/package.json — db:generate/db:migrate/db:push scripts removed, drizzle-kit devDependency removed. - package-lock.json — refreshed. - apps/api/src/lib/schema-validator.ts — fix hint now points at startup-migrations.ts blocks (was: cd apps/api && npx drizzle-kit migrate). - apps/api/src/db/schema.ts — integrity_hash_status external-managed comment reworded (was referencing drizzle-kit generate). - apps/api/src/lib/startup-migrations.test.ts — BLOCKS-list assertion updated to the renamed block. - handoff/_general/from-code/2026-05-11-in-ts-migrations-convention-pr88-phase3.md — session summary + drift inventory + PR B implication note. Verification - Type-check: clean (pre-existing routes/mcp.ts strale-mcp/tools errors unchanged). - vitest: 540 passing / 11 skipped / 1 pre-existing failure (app.classify-error.test.ts, unrelated). - BLOCKS-list canonical test passes with renamed block. DEC-20260420-A (hand-written discipline) and DEC-20260420-B (schema.ts sync) are preserved — we still hand-write, schema.ts edits still ship alongside. Neither is superseded by DEC-20260511-C. Rule 11 does not fire. Closes Notion P1 To-do 35d67c87082c810bbe04e597c38f6d89 (Harden schema-migration deploy pipeline). The canonical Rule 8 in cc-prompts skill is amended in lockstep; the Notion Working rules page header references DEC-20260511-C. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 tasks
petterlindstrom79
added a commit
that referenced
this pull request
May 11, 2026
Two reference-content handoffs from earlier 2026-05-11 sessions. Per Rule G (handoff note hygiene, DEC-20260510-A), notes with substantive "Landed" / "Outcome" / runbook content are promoted; pure session narration is deleted. Promoted: - 2026-05-11-decouple-scheduled-testing-eligible-pr-a.md (PR #88 PR A shipped state — landed-list, decisions locked, follow-ups). - 2026-05-11-haiku-cost-leak-audit-contain-cleanup.md (4-PR incident arc: #84/#85/#86/#87 with outcomes, audits, decisions). Deleted (not in this commit; rm'd locally) the third 2026-05-11 note: - 2026-05-11-pr88-deploy-recovery-and-phase3-halt.md Pure session-progress narration. Phase 3 halt was resolved by PR #89 (merged 2026-05-11); content superseded by DEC-20260511-C + PR #89's handoff note (2026-05-11-in-ts-migrations-convention-pr88-phase3.md). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
test_suites.scheduled_testing_eligible BOOLEAN NOT NULL DEFAULT FALSEand a Drizzle migration that backfillseligible = TRUEwhereexternal_cost_cents = 0(preserves current scheduler behavior exactly).test-scheduler.ts(dispatch + 2 observability) and 1 diagnostic-script reader (investigate-singapore.ts) to filter on the new column.external_cost_centsreturns to being billing-only.0066_reconcileEligibilityFromCostas an interim derivation bridge — re-derives eligibility from cost at every boot so any row inserted by the 12 untouched INSERT call sites lands at the correct value.Why now
The May 2026 Haiku token spike (PRs #84/#85/#86/#87) was structurally possible because
external_cost_cents = 0did double duty as billing data and scheduling signal. PR #46's cadence flip (24h → 1h) plus PR #49's deferred Anthropic-Haiku cost-bump silently turned billing-data lag into a scheduling regression for 73 LLM caps for 7 days. PR #85 shipped a CI assertion as the interim guard. This PR decouples the underlying coupling so the assertion is defense in depth, not load-bearing.Scope split
PR A (this PR):
PR B (separately tracked, Notion To-do):
scheduledTestingEligible: booleanexplicit at all 12 INSERT call sites.LLM-importer + ALWAYS_LLM_CAPABILITY_COSTS + eligible = FALSEinvariant directly.Notion
35d67c87082c81148ee4fc88f1671776moved to In progress with status note.Verification
routes/mcp.tsstrale-mcp/toolserrors unchanged; no new errors.app.classify-error.test.tsis the pre-existing failure noted in the prompt; unrelated to this PR).startup-migrations.test.tsBLOCKS-list assertion (8 → 9 blocks; new block appended in order).external_cost_cents = 0 / > 0scheduling readers insrc/outside the legitimate billing-data UPDATE blocks (0062–0065) and block 0066's intentional derivation.Closing-steps rule walk
After-deploy plan
Both counts must be equal. Then monitor the next hourly dispatch tick — cap set should match the pre-deploy set exactly.
Test plan
\d test_suitesshows new column; bothWHERE eligible = TRUEandWHERE cost = 0counts equal🤖 Generated with Claude Code