Files
gbrain/docs
Garry TanandClaude Opus 5 e7083aaca2 fix(doctor,migrate): detect and repair a missing pages(source_id, slug) unique index (#550)
put_page upserts with ON CONFLICT (source_id, slug) on both engines. When
the arbiter unique index is missing, every write fails with "no unique or
exclusion constraint matching the ON CONFLICT specification" while reads
stay green — and there was no recovery path: initSchema() is additive-only
and apply-migrations --force-schema re-runs from the current version (a
no-op at head). The root cause is that migration v23 (Postgres) / v21
(PGLite) guarded the ADD CONSTRAINT on pg_constraint.conname — a NAME
match, not a shape match.

Fix:
- src/core/pages-unique-repair.ts: shared column-shape probe over pg_index
  (non-partial, non-expression unique index with key columns exactly
  {source_id, slug}, either order, constraint or bare index — what ON
  CONFLICT inference actually accepts), check + repair helpers, and the
  shape-guarded DDL shared by the migrations and the self-heal. Falls back
  to a different constraint name when pages_source_slug_key is taken by a
  wrong-shaped relation; never auto-deletes duplicate pages.
- src/core/migrate.ts: v23/v21 guards now use the shared column-based DDL;
  runMigrations runs the repair on EVERY pass (same always-run slot as the
  #2038 timeline heal), so a wedged brain heals on the next init/migrate.
- src/commands/doctor.ts: new pages_unique_index check (BRAIN category,
  wired into both buildChecks and doctorReportRemote) that FAILs with the
  literal repair SQL; OK/skip when pages or source_id doesn't exist yet.
- test/pages-unique-repair.test.ts: reproduction, migrate-pass heal
  (fails behaviorally on master), shape-not-name detection, partial-index
  rejection, name-collision fallback, idempotency, fresh-brain skip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 13:11:43 -07:00
..