Skip to content

fix(ingestion): give the enrichment repair function a caller, a reach, and monitoring - #2548

Open
BigSimmo wants to merge 9 commits into
mainfrom
claude/enrichment-staging
Open

fix(ingestion): give the enrichment repair function a caller, a reach, and monitoring#2548
BigSimmo wants to merge 9 commits into
mainfrom
claude/enrichment-staging

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

#W98GR7 recorded four claims about the enrichment pipeline. Two hold, one holds by a different mechanism than recorded, and the central technical premise is refuted by the code on main. This PR fixes what is real and corrects the record on what is not.

Refuted, and why no ordering change is made

The issue says supabase/functions/indexing-v3-agent "deletes an artifact family before calling OpenAI and re-inserting, one family at a time, never staged-then-swapped", so a provider outage leaves the family permanently empty. On main it does the opposite. In all four writers — upsertMemoryCardsFromSections, upsertSectionIndexUnits, upsertVisualArtifacts, upsertCoreEmbeddingFields — the embeddingBatch await completes before sql.begin is entered, and the delete and re-insert share one Postgres transaction. A provider outage therefore throws before any delete happens, and a failed insert rolls the delete back. tests/indexing-v3-agent.test.ts did not actually pin that ordering — it pinned delete-before-insert inside the transaction, which is a weaker and different claim — so this PR adds the assertion that does. See "Three things review corrected" below.

The Edge Function is not modified here. The ledger row is corrected instead.

One caveat stated rather than buried: this checkout is shallow and the audit commits the issue cites are not reachable, so whether the file was fixed since the audit or the claim was inaccurate from the outset is not established. What is established is what the code does now.

Confirmed, and fixed

The repair function was invoked by nothing. Every repo-wide hit for repair_strict_enrichment_gate_batch is the migration, the schema mirror, generated types, drift bookkeeping, docs, or a schema-text assertion. It now has an operator caller.

And wiring up a caller alone would not have worked — this is the finding that came out of building it. repair_strict_enrichment_gate_batch touches documents.metadata, document_index_quality and ingestion_jobs, and never touches indexing_v3_agent_jobs, which is the table claim_indexing_v3_agent_jobs actually reads. It could not have unstuck a stuck document however often it ran. 20260902120000 adds that reset: completed where the gate passes, back to claimable with a fresh attempt budget where it does not, each repair stamping a counter so a document repaired again and again is visible rather than looping silently.

Terminal states are excluded from claim eligibility forever — by two different mechanisms, not the one recorded. claim_indexing_v3_agent_jobs excludes needs_enrichment_artifacts by name in its status not in (...) filter, while failed is excluded through attempt_count < max_attempts, because agentFailureDecision only writes failed once attempts are spent. A monitor that looked only at the status name would under-report.

No monitoring existed. needs_enrichment_artifacts appeared in no script and no workflow, so a stuck document reported as indexed with an empty artifact family and nothing counted it — silent corruption, not a crash. check:enrichment-health counts the three permanently-excluded states plus gate-failing documents. It reads indexing_v3_agent_jobs itself rather than the indexing_v3_agent_status mirror on documents.metadata: a divergence between recorded state and reality is the whole subject of this issue, so the check reads the table the claim RPC reads.

Design notes

The operator script follows scripts/cleanup-abandoned-reindex-generations.ts, the repo's established shape for a repair RPC that touches live clinical rows: dry run by default, mutating only under --apply plus a confirmation, health-probed first. It is deliberately not wired to a worker loop, a scheduled workflow, or an admin route. cli-utils.ts's confirm() returns false on a non-TTY, so --apply without --yes cannot mutate in a piped or detached context.

check:enrichment-health is provider-backed and is deliberately absent from verify:cheap, verify:pr-local and every CI job, so it cannot fire unattended.

Decision logic lives in src/lib/enrichment-repair.ts so it is unit-testable without a database; the scripts own only the provider I/O.

Three things review corrected, recorded because the first version of this branch shipped all three

The migration was built from the wrong baseline. schema.sql carries repair_strict_enrichment_gate_batch twice: the original 20260625033425 body, and the later 20260712171500 "codify live ahead" body which is the one that wins on replay and is what is actually deployed. They differ by more than whitespace — the original declares v_processing_lock_timeout and preserves a fresh-locked processing row's metadata; the deployed one does neither. Rebuilding from the superseded copy would have shipped that as an undeclared second behaviour change and moved a def_hash that 20260819110500 pins, for reasons unrelated to this issue. The migration is now a true minimal delta on the deployed body, and supabase/schema.sql carries a byte-identical copy — verified programmatically, not by eye.

The reset needed its own lease guard. gate_passed is a structural fact about which artifacts exist, and request_indexing_v3_enrichment re-queues a document without clearing them, so it stays true for the whole of a new run. Without a guard the reset would match a document mid-run and clear status, locked_by and locked_at underneath it — and request_ingestion_reindex_if_agent_idle, which decides "is the agent active?" from exactly those columns, would then approve a concurrent reindex over the same artifact tables. The deployed body has no lease-age guard anywhere, so the new CTE carries its own.

The refutation's premise was not actually pinned by a test. tests/indexing-v3-agent.test.ts asserted delete-before-insert inside the transaction — which would stay green if the embedding call were moved inside sql.begin, precisely the arrangement the issue describes. A new assertion pins the ordering the refutation rests on: embeddingBatch completes before sql.begin is entered, and no second embedding call appears inside the transaction.

Two further review findings applied: the dry run now reruns the function's own candidate predicate instead of counting gate-failing indexed documents (a different set in both directions — a gate-passing document with disagreeing recorded state is a candidate, and a gate-failing one already recorded as pending is not — so a preview reporting "0 failing" while apply repaired 50 was possible), and both the preview and the confirmation prompt now say what --apply actually costs. The script also refuses to apply against any project ref but sjrfecxgysukkwxsowpy.

What --apply actually does, stated plainly

For a gate-failing document the repair queues a pending ingestion_jobs row, and worker/main.ts ignores the incoming stage — so that is a full re-ingestion: download, extract or OCR, chunk, OpenAI embeddings, image captioning. Real provider spend, and a cross-border transfer of clinical document text. It takes the atomic reindex path (isAtomicReindexCandidate is status === "indexed", which every candidate is), so the old generation stays live until the new one commits and no document is unsearchable in between. That belongs in the operator's line of sight, and it is now in both the dry-run output and the confirmation prompt.

Verification

  • npm run typecheck — clean.
  • npm run lint — clean.
  • npm run check:migration-role — passed.
  • npm run check:function-grantsOK — all 36 SECURITY DEFINER public function(s) are revoked from PUBLIC ... and none are re-opened by a grant to PUBLIC/anon.
  • npm run docs:check-scripts1193 npm-run reference(s) resolve to real scripts.
  • npm run docs:check-indexall 64 repository roots/modules/routes and all schema tables are indexed.
  • npm run docs:update — inventory regenerated and committed (285 script files, 286 npm scripts).
  • tests/enrichment-repair.test.ts and tests/indexing-v3-agent.test.tsTest Files 2 passed (2) / Tests 32 passed (32).
  • Full offline unit suite — Test Files 1 failed | 947 passed (948). The single failure is tests/drift-detection.test.ts, the known drift-manifest staleness described below; nothing else is red.
  • npm run format — run, and the result is committed.
  • Reviewed before push by the ingestion-worker-reviewer and clinical-governance-reviewer subagents on this exact diff.

On the honesty of the Edge Function's test coverage, since this PR relies on it for the refutation: tests/indexing-v3-agent.test.ts is two different things. Real unit tests of the pure logic extracted into behavior.tsagentFailureDecision, deferralDecision, completionGateFromRow and siblings — which execute and assert real return values. And static source-text assertions against index.ts that slice the raw file and do string containment and ordering checks; they do not execute it and cannot, because it is Deno-runtime code that Vitest cannot load. There are no Deno tests anywhere under supabase/functions. So the ordering claim rests on reading the source plus a static pin, not on behavioural coverage, and it would be wrong to imply otherwise. The new assertion is the same kind — a stronger static pin, not behavioural proof.

Verification not run: npm run check:enrichment-health and npm run repair:enrichment-gate are the new scripts and both read or write the live clinical database — neither was run. npm run check:supabase-project, check:drift and check:migration-history are provider-backed and were not run. supabase migration up --local could not be run: the Supabase CLI is not installed here, and CI's Migration replay job is skipped on draft pull requests. No statement in 20260902120000 has been executed or parsed anywhere, because there is no Docker daemon and no Postgres in this environment.

tests/drift-detection.test.ts is red on this branch for the same reason as its sibling: supabase/schema.sql changed and supabase/drift-manifest.json cannot be regenerated without Docker. It was not hand-edited to make the check pass.

pr-policy classifies this diff clinicalRisk: true, operationalRisk: true, the latter because package.json gains two script entries. It emits the advisory note that operational-risk changes are bundled with clinical ones; splitting a two-line npm-script registration into its own PR would leave the scripts it registers unreachable, so they are kept together deliberately.

Risk and rollout

  • Risk: medium. The migration recreates one repair function with one added CTE; everything else is byte-identical to the deployed body (20260712171500's), verified programmatically. Applying it also changes the function's def_hash, which supabase/drift-manifest.json currently pins to the old value — regenerating the manifest is what keeps the post-merge live-drift run clean, and is step 2 below. The function is SECURITY INVOKER, granted to service_role only, and is invoked by nothing automatically — so applying the migration changes no behaviour on its own. The behaviour only changes when an operator runs the repair script deliberately.
  • Rollback: revert the commit and apply a follow-up migration restoring the prior function body. The two new scripts are additive and can simply stop being run.
  • Provider or production effects: merging applies 20260902120000 to the live clinical database within seconds — the Supabase GitHub integration has "Deploy to production" enabled with production branch main. Merge only inside a window the owner has approved. The two new scripts make provider calls only when an operator invokes them.
  • RAG impact: no retrieval behaviour change — ingestion repair tooling, monitoring, and a repair-RPC migration; no retrieval, ranking, selection or answer-generation surface is touched.

On whether merging deploys Edge Functions in this repository — the question asked before this work started. Migrations deploy automatically on merge; Edge Functions appear not to, but that is not provable from the repository. No workflow contains supabase functions deploy (all eleven Supabase-mentioning workflows checked); docs/db-maintenance.md and docs/disaster-recovery-runbook.md both give the function deploy as an explicit operator CLI command inside an approved change window; docs/process-hardening.md records a specific past operator-run function deploy by version number; and AGENTS.md documents the migration auto-deploy toggle in detail while saying nothing about functions — which is exactly where that fact would live. The residual ambiguity: the Supabase GitHub integration has a separate function-deploy toggle, and nothing committed proves its state. A dashboard read of that setting would settle it, the same check AGENTS.md records was done for the migration toggle on 2026-08-21. It does not gate this PR, since the Edge Function is not modified — but it must be confirmed before anyone assumes a merged function change is live.

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

The reasoning behind each. Nothing here changes what is cited, how sources are rendered, or how an answer is verified; the diff is ingestion repair tooling. No document workflow is introduced — the repair returns an existing stuck document to the enrichment path it was already meant to take, and no new content is ingested. No Supabase environment value is edited, so check:supabase-project was deliberately not run. Both new scripts are server-only Node entry points using createAdminClient(); neither is reachable from a route or the browser, and errors go through safeErrorLogDetails. Demo mode is untouched. Item 6 is what the change is about: a document with an incomplete artifact family currently reports as fully indexed, and the monitoring makes that conservative rather than silent. On SaMD: no clinical decision-support behaviour changes — a repaired document is re-enriched by the existing pipeline through the existing gates, and nothing bypasses the strict enrichment gate.

Notes

Do not merge this PR. It is a draft and is labelled hold and do-not-merge. Auto-merge is not enabled and must not be.

Approval still needed, and from whom — Josh, the repository owner:

  1. An approved live-database window for 20260902120000. Merging is the apply step; there is no separate one. Merge only inside a window he has approved.
  2. npm run drift:manifest on a machine with Docker, committed, before this leaves draft.
  3. Separate approval before either new script is ever run against production. check:enrichment-health is a read; repair:enrichment-gate --apply writes to live clinical document rows and re-queues enrichment work that will make OpenAI calls.
  4. A dashboard read of the Supabase GitHub integration's Edge Function deploy setting, to close the ambiguity above. Not a blocker for this PR.

The cross-route race in docs/ingestion-state-machine.md R24d — deep-memory.ts deleting the same artifact families unscoped, racing the route that writes them — is a real defect on a different code path, and is very likely the defect this issue's ordering claim was reaching for. It is queued rather than folded in here.

After this lands, npm run issues:reconcile applies the two queued inbox records to the canonical ledger from its own serialized branch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01N8JUuufwbFHy8PsgbztmGN


Generated by Claude Code


Note

High Risk
Changes a live-database repair function (auto-deployed on merge) and adds operator tools that can queue full re-ingestion with OpenAI spend on clinical documents; migration alters ingestion job lease behavior when repair is invoked.

Overview
Addresses #W98GR7 by wiring up the previously unused repair_strict_enrichment_gate_batch path and closing gaps where documents could stay permanently unclaimable by indexing-v3-agent.

Migration 20260902120000 extends the repair RPC with a reset_agent_jobs CTE that updates indexing_v3_agent_jobs (not only metadata/quality/ingestion jobs), adds 45-minute lease guards on ingestion and agent job mutations so repair cannot steal active locks, and surfaces agent_job_reset in the repaired array. supabase/schema.sql is updated to match.

New src/lib/enrichment-repair.ts holds testable candidate selection, health verdicts, and repair output formatting. Operator scripts repair:enrichment-gate (dry-run by default, --apply with confirmation and project ref guard) and provider-backed check:enrichment-health (counts stuck agent states and gate-failing indexed docs) are registered in package.json.

Tests pin repair semantics (tests/enrichment-repair.test.ts) and add a static assertion that embeddingBatch runs before sql.begin in the four indexing-v3-agent upsert paths, documenting that the issue’s “delete-before-OpenAI” premise does not match current code. Ledger/docs snapshots and outstanding-issue inbox records are updated; a separate deep-memory.ts unscoped-delete race is queued, not fixed here.

Reviewed by Cursor Bugbot for commit e807b80. Configure here.

…, and monitoring

#W98GR7 recorded four claims. Two hold, one holds by a different mechanism than
recorded, and the central technical premise is refuted by the code on main.

REFUTED: "deletes an artifact family BEFORE calling OpenAI and re-inserting,
never staged-then-swapped". In all four writers in
supabase/functions/indexing-v3-agent/index.ts — upsertMemoryCardsFromSections,
upsertSectionIndexUnits, upsertVisualArtifacts, upsertCoreEmbeddingFields — the
embeddingBatch await completes before sql.begin is entered, and the delete and
re-insert share one Postgres transaction. A provider outage aborts before any
delete happens, and a failed insert rolls the delete back. So no ordering change
is made here; the ledger row is corrected instead.

The existing test did NOT pin that, and the first version of this change claimed
it did. tests/indexing-v3-agent.test.ts asserted delete-before-insert INSIDE the
transaction, which would stay green if the embedding call moved inside
sql.begin — precisely the arrangement the issue describes. A new assertion pins
the ordering the refutation actually rests on, including that no second
embeddingBatch call appears inside the transaction.

CONFIRMED, and fixed: repair_strict_enrichment_gate_batch is invoked by nothing.
It now has an operator caller — dry run by default, mutating only under --apply
plus a confirmation, health-probed first, following
scripts/cleanup-abandoned-reindex-generations.ts. Deliberately not wired to a
worker loop, a scheduled workflow, or an admin route.

CONFIRMED with the mechanism corrected: both terminal states are excluded from
claim eligibility forever, but by two different routes.
claim_indexing_v3_agent_jobs excludes needs_enrichment_artifacts by name, while
'failed' is excluded through `attempt_count < max_attempts`.

NEW, and the reason wiring up a caller alone would not have worked: the repair
function touches documents.metadata, document_index_quality and ingestion_jobs
and never touches indexing_v3_agent_jobs — the table the claim RPC reads. It
could not have unstuck a stuck document however often it ran. 20260902120000
adds that reset.

Two things about that migration are worth stating, because the first version of
it got both wrong.

Its baseline is the body currently DEPLOYED — the one codified by
20260712171500 and mirrored at schema.sql's later copy — not the original
20260625033425 body that schema.sql still carries earlier in the file as a
superseded copy. Those differ by more than whitespace: the original declares
v_processing_lock_timeout and preserves a fresh-locked processing row's
metadata, the deployed one does neither. Rebuilding from the wrong copy shipped
that as an undeclared second behaviour change and would have moved a def_hash
20260819110500 pins for reasons unrelated to this issue.

And the reset needs its own lease guard, because the deployed body has none.
gate_passed is a structural fact about which artifacts exist, and
request_indexing_v3_enrichment re-queues a document without clearing them, so it
stays true for the whole of a new run. Without the guard the reset would match a
document mid-run and clear status, locked_by and locked_at underneath it, and
request_ingestion_reindex_if_agent_idle — which decides "is the agent active?"
from exactly those columns — would then approve a concurrent reindex over the
same artifact tables.

CONFIRMED, and fixed: no monitoring. check:enrichment-health counts the three
permanently-excluded states and the gate-failing documents. It reads
indexing_v3_agent_jobs itself rather than the metadata mirror: a divergence
between recorded state and reality is the whole subject of this issue. It reads
the live database, so it is confirmation-gated and is in no CI job.

The dry run now reruns the function's own candidate predicate rather than
approximating it. Counting gate-failing indexed documents was a different set in
both directions — a gate-passing document with disagreeing recorded state IS a
candidate, a gate-failing one already recorded as pending is NOT — and a preview
that says "0 failing" while apply repairs 50 is worse than no preview. It also
now says what apply costs: each gate-failing document is queued for a full
re-ingestion with OpenAI embedding and caption calls, and the script refuses to
apply against any project ref but the expected one.

Decision logic lives in src/lib/enrichment-repair.ts so it is unit-testable
without a database; the scripts own only the provider I/O.

The cross-route race in docs/ingestion-state-machine.md R24d — deep-memory.ts
deleting the same artifact families unscoped — is a real defect on a different
code path, and is queued rather than folded in here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8JUuufwbFHy8PsgbztmGN
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 2c2978cf-1fdb-46b7-9ac3-665f394730e3


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@supabase

supabase Bot commented Sep 2, 2026

Copy link
Copy Markdown

Updates to Preview Branch (claude/enrichment-staging) ↗︎

Deployments Status Updated
Database Wed, 02 Sep 2026 20:18:22 UTC
Services Wed, 02 Sep 2026 20:18:22 UTC
APIs Wed, 02 Sep 2026 20:18:22 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Wed, 02 Sep 2026 20:18:26 UTC
Migrations Wed, 02 Sep 2026 20:18:30 UTC
Seeding Wed, 02 Sep 2026 20:18:31 UTC
Edge Functions Wed, 02 Sep 2026 20:18:34 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@BigSimmo
BigSimmo marked this pull request as ready for review September 2, 2026 05:59
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_39f48354-1a82-4972-895a-e318bf438bfc)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 769cd00e1d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

)
.eq("document_status", "indexed")
.order("document_updated_at", { ascending: true, nullsFirst: true })
.limit(500);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Filter the full corpus before limiting the preview

When the 500 oldest indexed documents are healthy but a later document is a repair candidate, this limit removes that candidate before selectStrictGateRepairCandidates runs, while the RPC filters the entire corpus before applying its limit. The confirmation can therefore report zero gate-failing candidates and then queue an unpreviewed full re-ingestion with provider spend and clinical-document transfer. Apply the candidate predicate server-side before limiting; a fixture with 500 healthy rows followed by one failing row proves the mismatch.

Useful? React with 👍 / 👎.

from candidates c
where c.gate_passed
and j.document_id = c.document_id
and j.status in ('pending', 'processing')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exclude live ingestion jobs from repair

During an atomic reindex, the old artifact generation deliberately remains live, so gate_passed can be true while an ingestion job is actively processing. This predicate admits that live job, and completed_open_jobs then marks it completed and clears its lease underneath the running worker, allowing the worker to continue mutating artifacts after it has lost ownership. Preserve fresh processing jobs or require a stale lease; a database test with a gate-passing document and a freshly locked processing job should assert that the repair leaves it unchanged.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T06:04:31.897396Z 769cd00 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Resolves the one conflict, in the generated data/repo-awareness-snapshot.json,
by regenerating it with npm run snapshot:repo-awareness rather than by hand.
check:repo-awareness-snapshot confirms it is in step (204 pages, 575 documents,
2664 reviews).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8JUuufwbFHy8PsgbztmGN
Both found in review on PR #2548.

repair_strict_enrichment_gate_batch cleared ingestion_jobs lease columns with no
lease-age guard on completed_open_jobs or deferred_open_jobs. During an atomic
reindex the old artifact generation stays live, so gate_passed can be true while
a job is actively processing; marking it completed and clearing locked_at/locked_by
let the worker keep mutating artifacts after losing ownership. The new
reset_agent_jobs CTE already carried a 45-minute guard -- the same predicate now
covers all three CTEs that write a lock column. The function had no caller before
this branch, which is why the hazard was latent; adding one is what makes it
reachable, so the fix belongs here.

The operator script's dry run read the 500 oldest indexed documents and filtered
locally, while apply calls the RPC, which filters the whole corpus and only then
limits. A corpus whose oldest page was healthy previewed as zero candidates and
then queued real re-ingestions -- OpenAI spend against live clinical documents --
that the operator never saw. The preview now pages the view and stops once the
limit is met. The predicate deliberately stays in selectStrictGateRepairCandidates
rather than moving into PostgREST filters: neq drops NULLs while the TS predicate
reads NULL as not-completed, and that difference undercounts.

Tests pin both: the paging contract and the single-predicate rule as source
assertions, plus a fixture whose only candidate sits past the old 500-row window.
Verified red against the pre-fix script.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8JUuufwbFHy8PsgbztmGN
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

CI triage

CI failed on this PR. Automated classification of the 3 failed job(s):

  • Unit coverageneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • Migration replaynot baselined: this job did NOT run on the main comparison below (path-scoped skip), so that run says nothing about it either way. Treat the comparison as absent, not green, and inspect the failing step.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

Compared with main CI run #15123 (failure). That run's conclusion is an aggregate and did not exercise Migration replay.

Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger.

…ging

# Conflicts:
#	data/outstanding-issues-snapshot.json
#	data/repo-awareness-snapshot.json
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_cadfb40d-18e6-43d7-821c-e5099224ef81)

BigSimmo commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

Merge conflict resolved; one red check remains and needs a hand off this branch

The conflict is fixed. This branch went mergeable_state: dirty twice while main advanced, both times on generated files — data/repo-awareness-snapshot.json and data/outstanding-issues-snapshot.json. Resolved by regenerating them with npm run snapshot:repo-awareness and npm run snapshot:issues rather than editing by hand, and confirmed in step:

[repo-awareness] in step with data/repo-awareness-snapshot.json (204 pages, 576 documents, 2664 reviews)
[snapshot] in step with data/outstanding-issues-snapshot.json (70 open, 3 pending)

Head 5bb51908 merges cleanly and CI is running again. That recurrence is structural, not incidental — every PR regenerates those files — and is already tracked as #Y090R5.

What will still fail: tests/drift-detection.test.ts, because this PR changes supabase/schema.sql and supabase/drift-manifest.json is stale.

Why I have not fixed it: npm run drift:manifest replays the schema into a supabase/postgres Docker container. This session's container has the docker binary but no daemon (/var/run/docker.sock absent, confirmed), and no local Postgres. schema_sha256 must never be hand-edited — a hand-written hash turns the gate green over a stale snapshot, which is worse than the red.

The fix: take the drift-manifest artifact that the Migration replay job uploads on every run of this branch and commit it as supabase/drift-manifest.json, or run npm run drift:manifest on a machine with Docker. Pushes here use the guard's documented SKIP_DRIFT_GUARD=1 override in the meantime.

Not for merge. Labelled hold and do-not-merge. It carries a migration that reaches the live clinical database within seconds of merge, so it needs an approved database window; and the new repair script and health check read live Supabase, so each needs separate approval before it is ever run against production.


Generated by Claude Code

…ging

# Conflicts:
#	data/outstanding-issues-snapshot.json
#	data/repo-awareness-snapshot.json
#	docs/scripts-index.md
@BigSimmo
BigSimmo enabled auto-merge (squash) September 2, 2026 09:17
…ging

# Conflicts:
#	data/repo-awareness-snapshot.json
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_f8a52a3c-d2e7-470a-9845-0f2824e30307)

@BigSimmo
BigSimmo disabled auto-merge September 2, 2026 10:18
…ging

# Conflicts:
#	data/repo-awareness-snapshot.json
#	docs/scripts-index.md
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_f69d846d-615a-4536-b9d4-19241724f644)

…ging

# Conflicts:
#	data/repo-awareness-snapshot.json
#	docs/scripts-index.md
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_4f088264-b497-4edc-8cea-7418065ff957)

…ging

# Conflicts:
#	data/repo-awareness-snapshot.json
#	docs/scripts-index.md
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_ee79e040-01bf-400b-a944-01263d17a0c4)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants