Write the migration preflight/postflight skeleton as a shared file
Measured 2026-10-06 by coder-enamel-cc: there is NO enamel migration template. No template/skeleton file under db/, db/README.md mentions neither preflight nor postflight, and of 78 applied migrations plus drafts exactly TWO carry a 'do $preflight$' block (enamel_081, enamel_085) while ZERO applied migrations contain a raise-exception assert of any kind. This FALSIFIES the PM hypothesis that three template defects in two days were one bad template propagating: the defects differ each time because each author reconstructs an unwritten pattern from whichever file they last read. Fix forward, do not retrofit 78 migrations. Write the skeleton as a shared file with the three known defects excluded by construction: (1) privilege checks assert the FORBIDDEN SET via aclexplode over all grantees, never a denylist of named roles; (2) no body-hash pin on a function the same migration CREATE OR REPLACEs - assert required behaviour/end state instead; (3) no plpgsql local named after a catalog column it selects (the tgtype/tgtype collision that aborted the enamel_085 apply with SQLSTATE 42702). Add a fourth from venus the same day: a postflight built with replace() on the old body is a no-op on an already-converted body, so it must assert the old anchor existed AND that expected IS DISTINCT FROM pre - otherwise it passes without the migration having changed anything. Next migration uses the skeleton.
Activity
-
Pushed f5c0509 (db/migration-skeleton.sql new, db/README.md pointer; 242 insertions, pre-push hook green, 353 tests in 14 files). The skeleton sits OUTSIDE db/migrations/ so a filename-sorted replay cannot pick it up, and its first statement is an executable raise rather than a DO-NOT-APPLY banner, because a draft marker in a comment is part of the applied bytes. Four named defects excluded by construction: (1) no plpgsql local named after a catalog column the same block selects, with the reason stated - 42702 raises at RUN time so source review cannot see it, and the camelCase convention is the collision guard; (2) assert required behaviour or end state, never a body-hash pin on a function this file CREATE OR REPLACEs, plus the replace()-no-op case needing both old-anchor-existed and expected IS DISTINCT FROM pre; (3) privilege checks assert the FORBIDDEN SET via aclexplode over all grantees, never a denylist of named roles, with the ALTER DEFAULT PRIVILEGES widening named; (4) encoded literals asserted at apply time and never encoding another party's row. Also carries the structural invariants the pattern was missing: preflight stays inside the transaction so a failed apply is a non-event; the file's own commit means no rehearsal reaches past the preflight and every write-dependent arm is post-apply by construction; isolation contracts enforced not commented, admitting serializable; no SQLSTATE asserted as an invariant; CREATE OR REPLACE resets proconfig; signature change is an overload; pg_locks reads pid-scoped; every arm named, every unexercised arm named as unexercised, every arm forced red; residue declared on the row before any row-creating exercise. COVERAGE: this is a docs/template change under db/ and touches no deployed surface - no app/ file, no DDL, nothing applied. PTD read at 14:38Z: commit status Vercel=pending, target_url tail 4AeUrDrn1UJGPVhamz8fGtZ9wDc1 vs live deploymentId 7btprifTGGRsqx7vRJwtTLFNnGzS, so the deploy had NOT landed at that instant and is NOT verified landed by me. No bump was minted (strict --only), so the live version 0.14.9 is corroboration only and would not have moved either way. NOT exercised: the skeleton has not yet been used to author a migration - enamel_086 is the first file that should start from it, and that is the arm that proves it.