Skip to content
Merged
12 changes: 11 additions & 1 deletion .claude/hooks/session-start.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,15 +64,25 @@ cd "$CLAUDE_PROJECT_DIR"
# merges, which surfaces as fake typecheck/test regressions (2026-07-19 audit).
# Stamp the lockfile hash after a successful install and reinstall whenever the
# lockfile no longer matches the stamp.
LOCK_STAMP="node_modules/.session-start-lock-hash"
# Keep this marker inside node_modules/.cache. npm's postinstall records a
# trusted file inventory of node_modules (scripts/check-installed-lock-parity.mjs
# --write-stamp), and this hook writes its own marker *after* that runs — so a
# marker written directly into node_modules/ leaves the tree one file ahead of
# the stamp and fails check:installed-lock-parity, which is the first real step
# of verify:pr-local. `.cache` is in that check's VOLATILE_DIRECTORIES set, so a
# marker there is ignored by the inventory while still being wiped by npm ci
# along with the rest of node_modules, preserving the staleness semantics below.
LOCK_STAMP="node_modules/.cache/session-start-lock-hash"
lock_hash="$(sha256sum package-lock.json | cut -d' ' -f1)"
if [ ! -d node_modules ]; then
npm ci --no-audit --no-fund
mkdir -p "$(dirname "$LOCK_STAMP")"
echo "$lock_hash" > "$LOCK_STAMP"
echo "[session-start] Dependencies installed"
elif [ ! -f "$LOCK_STAMP" ] || [ "$(cat "$LOCK_STAMP")" != "$lock_hash" ]; then
echo "[session-start] node_modules is stale for the current lockfile, reinstalling"
npm ci --no-audit --no-fund
mkdir -p "$(dirname "$LOCK_STAMP")"
echo "$lock_hash" > "$LOCK_STAMP"
echo "[session-start] Dependencies reinstalled"
else
Expand Down
26 changes: 26 additions & 0 deletions docs/database-drift-detection.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,32 @@ storage bucket rows + storage.objects policies.
carries the _identical_ name-stripped index definition under a legacy name
(the machine-checked version of `search_schema_health()`'s `index_aliases`).

### Known coverage limits

What the check does **not** see — established offline on 2026-08-12 while
answering `/issues` `#248` (the 20260705180000 search-health indexes that were
missing on live despite an applied history):

- **Invalid indexes read as healthy.** `schema_drift_snapshot()`
(`20260706200000`) builds its index rows from `pg_index` via
`pg_get_indexdef` + an md5 `def_hash`, and never reads `indisvalid` /
`indisready`. An index left behind by a failed `CREATE INDEX CONCURRENTLY`
still has a definition, so it compares byte-identical and drift stays green
while the planner refuses to use it. `20260804110240` checks both flags at
apply time, so the guard exists for that one migration but not for the
ongoing probe.
- **Nothing gates on it.** `check:drift` runs only from
`.github/workflows/live-drift.yml` — `workflow_dispatch` plus a weekly Sunday
18:30 UTC cron — and blocks no PR or release. Coverage of the plain
missing-index class is genuine (indexes compare by name on `table` +
`def_hash`, and `supabase/drift-allowlist.json` is empty, so a missing index
fails the run), but nothing forces a run between the drift appearing and
runtime `search_schema_health()` noticing.

Both are decisions rather than defects: adding validity to the snapshot RPC is
a migration, and raising the cadence spends provider budget. Recorded so the
gap is chosen, not assumed away.

### Workflow

- Change `supabase/schema.sql` → run `npm run drift:manifest` (Docker) in the
Expand Down
Loading