Skip to content

feat(migrations): 0068 guarded drop of build_assets_legacy (#114) - #465

Merged
stdrc merged 2 commits into
mainfrom
rhea/build-assets-0068-drop
Aug 19, 2026
Merged

feat(migrations): 0068 guarded drop of build_assets_legacy (#114)#465
stdrc merged 2 commits into
mainfrom
rhea/build-assets-0068-drop

Conversation

@TennyZhuang

@TennyZhuang TennyZhuang commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

What

Removes build_assets_legacy — the credential-bearing full copy 0062 left behind (renamed the old build_assetsbuild_assets_legacy, copied every row into the new table by id, and deliberately did not drop it). That copy holds r2_key, signature, signing_credential_id, metadata_json and is maintained by nothing.

How — guarded, not a bare drop

An in-migration ⊇ assert runs BEFORE the DROP and aborts the whole migration if it fails, via a CHECK(ok=1) violation (portable; no RAISE/trigger, no TEMP table). It proves every legacy row has a live row with the same id and the same immutable columns — r2_key, file_hash, signature, signing_credential_id (IFNULL-compared). If any legacy row is not preserved, the migration aborts and build_assets_legacy is left intact. No export — the drop is provably lossless. (No-export is deliberate and authorized: an export would copy the credential material to a looser location; artin rejected it as a contradiction — prove-redundant-then-delete, don't pre-copy.)

metadata_json is excluded from the match: it is live-mutable derived data (delta from_version_code, download_count), so comparing it exactly would false-abort — the kind of false red that pushes an on-call to disable the guard.

Design (ruled by @gogo, #114 owner)

  • Strict ⊇ + abort, whitelist mechanism retained but currently empty.
  • Prod --remote COUNT (2026-08-18, read-only): 0 legacy rows unmatched in live (of 286 legacy / 321 live), 0 of them credential-bearing → no whitelist needed today.
  • That count only proved id-match on a snapshot; it does not pre-green the per-column check, and new orphans could appear before this runs — so the strict abort stays unconditionally. A future red is a real signal, not noise.

Verification

  • wrangler d1 execute --local: RED dataset aborts on CHECK constraint failed: ok = 1, build_assets_legacy survives (DROP never runs); GREEN dataset drops it (5 statements OK).
  • worker/test/build_assets_legacy_drop_migration.test.ts (node:sqlite, stop-on-error matching D1) — 5/5: RED missing-row + RED mutation abort & preserve; GREEN ⊇, metadata-divergence, empty-legacy drop.
  • Worker tsc --noEmit clean.

Operational note (must be known before deploying): if the assert ever goes red, the migration aborts and is not recorded in the ledger, so it re-runs and re-fails on every subsequent deploy — halting the entire Hands deploy pipeline, not just this drop — until the offending rows are triaged. That is correct (loud failure, not a silent drop). The diagnostic query in the file header lists offending rows using locator columns only (id, created_at) — never SELECT *, which would print credential columns.

Gates

  • ✅ artin authorization — obtained (2026-08-18, this work's channel): artin authorized dropping this copy directly in the next migration and rejected retention/export. Not to be re-asked.
  • ⬜ Security criteria review (@sentinel) — ⊇ semantics, the metadata_json exclusion, and the diagnostic credential scope. The one substantive design gate outstanding.
  • ⬜ Migration watch-gate — deploy runs through the existing watch-gate (human-present at migration deploy). Merge/deploy execution is @gogo's lane (fix(docs): serve /docs/electron-sdk/ (worker allowlist) #114 owner / prod infra); this PR is the guarded migration + tests only.

🤖 Generated with Claude Code

Rhea Rafferty and others added 2 commits August 18, 2026 12:23
build_assets_legacy is the credential-bearing full copy 0062 left behind
(r2_key/signature/signing_credential_id/metadata_json), maintained by nothing.
0068 removes it behind an in-migration ⊇ assert: it proves every legacy row's
id + immutable columns (r2_key, file_hash, signature, signing_credential_id)
are preserved in live build_assets, then DROPs. The assert fails via a
CHECK(ok=1) violation ordered BEFORE the DROP, so a failing ⊇ aborts the whole
migration and leaves the table intact — no export, no bare drop.

metadata_json is excluded from the match: it is live-mutable derived data
(delta from_version_code, download_count), so comparing it would false-abort.

Design ruled by @gogo (#114 owner): strict ⊇ + abort, whitelist mechanism kept
but currently empty (prod --remote COUNT 2026-08-18: 0 legacy rows unmatched in
live, of 286 legacy / 321 live). The strict abort stays unconditionally — the
count is a snapshot and does not pre-green the per-column check.

Verified:
- wrangler d1 execute --local: RED dataset aborts on "CHECK constraint failed:
  ok = 1", build_assets_legacy survives; GREEN dataset drops it (5 stmts OK).
- worker/test/build_assets_legacy_drop_migration.test.ts (node:sqlite,
  stop-on-error matching D1): 5/5 — RED missing-row + RED mutation abort and
  preserve; GREEN ⊇, metadata-divergence, and empty-legacy drop.

Deploy remains gated on the migration watch-gate + explicit artin authorization
for THIS credential-bearing copy; this PR is the guarded migration only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Rhea Rafferty <hands-rhea@mail.build>
0068 drops build_assets_legacy (the copy 0062 creates), which broke 4 pre-existing
tests. Fixed precisely — preserving the mechanism/guard coverage, removing only the
now-obsolete retention invariant:

- expected_d1_schema.test.ts: the two "stale database" tests modelled a pre-0062 DB
  via chainWithout(/^0062/), which now pulls in 0068 — and 0068 aborts on a chain
  where 0062 never created build_assets_legacy. Skip the 0062/0068 lifecycle pair
  together; the fingerprint drift-detection coverage is unchanged.
- build_asset_direct_upload_migration.test.ts:
  - "does not stop a continuing executor": applied "all except 0062", which pulled in
    the later dependent 0068 → same abort. Apply only migrations BEFORE 0062
    (name >= MIGRATION), matching the test's intent; 0062 preflight-guard coverage
    unchanged.
  - Removed "keeps the original rows recoverable after a successful rebuild": it
    asserted build_assets_legacy is retained as a recovery copy — the invariant 0068
    intentionally removes (artin-authorised; the guarded superset assert proves no
    data loss). Its column/index/FK carry into build_assets is covered by "carries
    every column, index and foreign key across the rebuild".

Verified: full worker suite green except feedback_closure_reason_migration.test.ts,
which fails only because /usr/bin/sqlite3 is absent on this machine (identical on
clean main; passes in CI). tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Rhea Rafferty <hands-rhea@mail.build>
@stdrc
stdrc merged commit 5e24f1a into main Aug 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants