Phase 0.2.7 — Outer-catch-masks-inner-write structural sweep
Phase 0.2.7 — Outer-catch-masks-inner-write structural sweep
Section titled “Phase 0.2.7 — Outer-catch-masks-inner-write structural sweep”Audit date: 2026-05-06
Branch: content-items-investigation
Tool: ast-grep 0.40.0 + ripgrep + Python post-filter
Scope: app/, lib/, scripts/ (test files excluded)
Investigator: Claude (sub-agent)
Inputs: 0.2.6 audit + 0.1 Path 7 audit (file upload)
Outputs: 5 P0, 5 P1, 4 likely-P2 reclassified from BARE flag, multiple false positives identified
1. Seed pattern (Path 7 source_documents)
Section titled “1. Seed pattern (Path 7 source_documents)”app/api/upload/route.ts:408-447:
// Create source_documents row for lineage trackinglet sourceDocumentId: string | null = null;try { const newVersion = reuploadInfo ? reuploadInfo.existing_version + 1 : 1; const parentId = reuploadInfo?.match_type === 'new_version' ? reuploadInfo.existing_document_id : null;
const { data: sourceDoc } = await serviceClient .from('source_documents') .insert({ filename, original_filename: filename, mime_type: mimeType, file_size: file.size, content_hash: contentHash, version: newVersion, parent_id: parentId, storage_path: storagePath, status: 'processing' as const, uploaded_by: user.id, ...(workspaceId ? { workspace_id: workspaceId } : {}), ...(pipelineRunId ? { pipeline_run_id: pipelineRunId } : {}), }) .select('id') .single();
if (sourceDoc) { sourceDocumentId = sourceDoc.id; // Link content_item to source_document await serviceClient .from('content_items') .update({ source_document_id: sourceDoc.id }) .eq('id', itemId); // ← inner write }} catch (srcDocErr) { logger.error({ err: srcDocErr }, 'Source document tracking failed'); // ← single generic log // Non-fatal — upload continues without lineage tracking}Failure shape. If the outer source_documents.insert succeeds (returning a
row) but the inner content_items.update({ source_document_id }) throws (RLS,
PostgREST 0-row no-op, network blip), the catch arm fires once with one
'Source document tracking failed' line — but the on-disk state has a
source_documents row WITH no back-reference from content_items. The audit
class is silent intermediate-state gap: two awaited writes, both data-relevant,
sharing one catch arm with no per-write disambiguation.
The 0.2.6 audit (§Open Question 1) explicitly flagged this class as the 80% confidence gap that this 0.2.7 sweep targets.
2. ast-grep patterns invoked
Section titled “2. ast-grep patterns invoked”All scoped to app/, lib/, scripts/ excluding __tests__/, *.test.ts,
/tests/, e2e/.
| # | Pattern | Lang | Total matches | Post-filter (≥2 write awaits) |
|---|---|---|---|---|
| 1 | try { $$$ } catch ($_) { $$$ } (with-param catch) | ts | 394 (≥2 awaits) | 27 (≥2 writes) |
| 2 | try { $$$ } catch { $$$ } (no-param catch) | ts | ~70 | 2 |
| 3 | $X.catch(($_) => $$$) (chained single-arg arrow) | ts | 69 | 0 (all request.json() body fallbacks or main().catch script entry points) |
| 4 | try: ... except $E as $V: ... (Python typed except) | py | (large — large-N) | 4 (in scripts/bid_worker.py) |
| 5 | Inner pattern try { ... .insert().select().single(); ... .update(...); } catch (err) { ... } (literal seed shape) | ts | 1 (the seed itself) | 1 |
| 6 | try { ... storage.upload + .rpc('merge_X' / .insert / .update); } catch (err) { ... } | ts | 9 | 9 (down-classified by per-write logging analysis) |
Filter heuristics. A line was classified as a “write await” if it matched
any of: .from(...).{insert,update,upsert,delete}, .rpc('merge_X' | 'update_X' | 'insert_X' | 'record_X' | 'archive_X'),
.storage.from(...).{upload,update,copy,move,remove}, .auth.admin.X,
recordPipelineRun, update[A-Z]X(, save[A-Z]X(, create[A-Z]X(,
insert[A-Z]X(, store[A-Z]X(, regenerateChunks, merge[A-Z]X(,
writeFile, fs.X, fetch(, anthropic.|openai.|client.messages|client.embeddings,
sendNotification, publish[A-Z]X(. A line was demoted to “read” if it
matched .from(...).select, getAuthorisedClient/getAuthenticatedClient,
request.json/req.json/formData, supabase.auth.getUser/getSession,
createServiceClient/createClient, params/context.params. Per-write
logging detected via either (a) ≥n_writes distinct logger.error /
logBestEffortWarn calls inside the try body, or (b) ≥n_writes-1 nested
try { keywords (per-write try/catch wrapping).
3. P0 findings — silent intermediate-state gaps
Section titled “3. P0 findings — silent intermediate-state gaps”Definition: an inner await failure leaves on-disk state stranded across ≥2 systems (Supabase + Storage, Supabase + external API, two related Supabase tables) with no per-write observability. The user sees a generic 500 (or a non-fatal pass-through) and ops cannot localise the failure.
| # | File:line | Inner awaits | Failure mode | Suggested fix |
|---|---|---|---|---|
| P0-1 | app/api/upload/route.ts:408-447 | (1) serviceClient.from('source_documents').insert({...}).select('id').single() (L417-434) → returns sourceDoc row; (2) serviceClient.from('content_items').update({source_document_id: sourceDoc.id}).eq('id', itemId) (L439-442). | Inner UPDATE fails after outer INSERT succeeded → source_documents row exists but content_items.source_document_id stays NULL. Generic catch logs once; no field discriminator. This is the 0.2.6 seed. | Split into two try blocks. After source_documents.insert, check the result; on success run the UPDATE in its own try with its own logger.error message (‘Failed to back-link source_document_id’) AND consider rolling back the source_documents row (delete it) if the UPDATE fails — the alternative is a permanent orphan. (Prefer to make this a transactional RPC: link_source_document(p_item_id, p_doc_id) returns the new doc id atomically.) |
| P0-2 | app/api/intelligence/workspaces/route.ts:155-280 | (1) supabase.from('workspaces').insert({...}).select().single() (L180-194) → workspace row; (2) supabase.from('feed_prompts').insert({workspace_id: workspace.id, ...}) (L218-225) with no .error check, no .select(), no try/catch; (3) inner try at L231 calling createIntelligenceGuide(...) + supabase.from('workspaces').update({domain_metadata: {...guide_id}}). | If feed_prompts.insert (L218) fails (RLS, FK violation, schema drift), the workspace exists with NO auto-generated prompt — the workspace is then unusable for feed scoring. No catch fires because the call is fire-and-forget (no result inspection); the response returns success. Subsequent visits to /intelligence/{id} show a workspace with domain_metadata.company_profile_id but no feed_prompts row. | Wrap the feed_prompts.insert in await sb(supabase.from('feed_prompts').insert(...).select('id').single(), 'workspaces.feed_prompt_create') — sb() throws SupabaseError on failure. Either (a) hard-fail and roll back the workspace, OR (b) capture the error to a warnings: [] array on the response so the admin sees “Workspace created but auto-prompt failed; visit /intelligence/{id}/prompts/edit”. |
| P0-3 | app/api/items/[id]/files/route.ts:29-160 | (1) uploadFileToAnthropic(buffer, filename, mimeType) (L129) → external Anthropic Files API resource (returns result.fileId); (2) supabase.rpc('merge_item_metadata', {p_item_id: id, p_new_data: {anthropic_file: {file_id: result.fileId, ...}}}) (L132-142). | The MERGE step’s mergeError is captured to a warning field in the response (good). BUT: if step (1) succeeds but the entire request 500s before step (2) (timeout, abort, hang, sandbox-204 issue), the Anthropic file is uploaded with NO DB pointer. Orphaned external resource — re-running creates a duplicate; no cleanup path. | Either (a) make the request idempotent on a deterministic file_id derived from (itemId, contentHash) so retry maps to the same Anthropic file (best); OR (b) add a try/finally around step (1) that deletes the Anthropic file on any subsequent error before the merge succeeds. Treat the existing warning: 'Upload succeeded but failed to persist file_id to metadata' as INSUFFICIENT — the Anthropic file_id should be returned to the caller in that case so manual recovery is possible. |
| P0-4 | app/api/items/[id]/images/route.ts:58-301 | (1) Loop body L243-272 — N storage uploads supabase.storage.from('documents').upload(storagePath, imgBuffer, ...) per image; per-image errors collected to uploadErrors[] (good); (2) supabase.rpc('merge_item_metadata', {p_item_id: id, p_new_data: {extracted_images: imageMetas, ...}}) (L275-281); (3) the surrounding outer try at L58-294 with generic catch L294 returning 500. | If the request 500s after some uploads succeeded but before the merge at L275 (e.g. Sharp throws, abort, OOM), N storage objects are orphaned at ${itemId}/images/page${page}_img${index}.{ext} with NO DB pointer. The next request creates duplicates. The outer catch returns generic 500 with no list of orphaned paths. | Two-step fix: (i) accumulate successful storage paths to a pendingPaths: string[] array; in the outer catch arm, if pendingPaths.length > 0, attempt supabase.storage.from('documents').remove(pendingPaths) to clean up; (ii) deterministic per-image storage key derivation (md5(pdfHash + page + index) instead of page${N}_img${i}) so re-runs are idempotent. |
| P0-5 | scripts/bid_worker.py:264-329 (fill_template_job) | (1) supabase.storage.from_('templates').upload(completed_path, ...) (L272-276); (2) supabase.from_('template_completions').insert({...}).execute() (L286-294); (3) per-field UPDATE loop on template_fields (L309-312); (4) supabase.from_('templates').update({status: 'completed'}) (L315-317). The catch arm L325-329 calls supabase.from_('templates').update({status: 'fill_failed'}).execute(); raise. | If step (2) — template_completions.insert — fails after step (1) succeeds, the storage object is uploaded but no completion record exists. The catch flips template to fill_failed and raises. Storage is orphaned. Same shape: P0-3 / P0-4 inverted. | Rollback in catch arm: before the update({status: 'fill_failed'}) call, attempt supabase.storage.from_('templates').remove([completed_path]). AND wrap step (2) in its own try with explicit error message recording the storage_path so the admin can manually clean up if rollback also fails. Same fix as P0-4. |
4. P1 findings — partial write loses observability
Section titled “4. P1 findings — partial write loses observability”Definition: ≥2 awaited writes share one catch arm; the catch arm produces ONE generic message and the user/ops cannot determine which write failed. The intermediate state is bounded (no orphan-across-systems risk) but observability is degraded.
| # | File:line | Inner awaits | Risk | Suggested fix |
|---|---|---|---|---|
| P1-1 | app/api/cron/freshness-transitions/route.ts:97-627 | 6 distinct write paths inside one big try: governance_config update loop, content_items batch update, freshness-transition INSERTs to notifications via createBulkNotifications, owner-vs-broadcast notification dispatch with createBulkNotifications (multiple call sites), final recordPipelineRun({ status: 'completed' }) at L586. | Outer catch L622 returns 500 on ANY error after the candidate fetch — but does NOT call recordPipelineRun({ status: 'failed' }). Cron infra sees a 500 response, but pipeline_runs table has no row at all (the recordPipelineRun was supposed to fire later in the try). This blinds ops to which step actually failed: the cron looks “never run” rather than “run and failed mid-way at notifications”. | In the catch arm at L622-627, ALSO call await recordPipelineRun({supabase, pipelineName: 'freshness_transitions', status: 'failed', errorMessage: safeErrorMessage(err, 'cron threw')}). (Mirror the pattern in review-cadence/route.ts:328-333 which gets this right.) |
| P1-2 | app/api/cron/quality-score/route.ts:74-460 | 4 distinct write paths inside one try: per-batch content_items UPDATE loop (L267-285), per-flagged-item content_items UPDATE for governance flag (L331-338), createBulkNotifications for quality_flag (L313), createBulkNotifications for governance_review_needed (L380/L400), terminal recordPipelineRun at L418-439. | Same as P1-1 — the outer catch L455-460 returns 500 with no recordPipelineRun({ status: 'failed' }). Telemetry sees 500 but cannot localise which sub-step failed. | Same fix: add recordPipelineRun({ status: 'failed', ... }) to the catch arm before returning 500. |
| P1-3 | app/api/source-documents/[id]/send-to-review/route.ts:29-201 | (1) governance batch UPDATE for eligible items (L84-91); (2) createNotification(...) per-item loop for owners (L121-132); (3) admin fallback createNotification(...) loop (L166-178). | Outer catch L196 returns 500 after a partial success: items HAVE been flipped to pending (L84) but only some notifications fired. The user sees an error and does not know whether to retry (which would create duplicate flips) or whether the items are already in pending. | Wrap the notification loops in a notificationErrors[] array (mirror the existing unnotifiedItems count). On notification failure, push to the array but don’t throw — let the response surface a warnings: [] envelope with the failing user_ids. The governance update at L84 is the critical write; notifications are secondary. |
| P1-4 | app/api/upload/route.ts:148-1105 | 15+ writes in the outer try: pipeline_runs INSERT, content_items INSERT, storage upload, source_documents INSERT (P0-1’s parent), source_documents UPDATE, content_items UPDATE (extracted_text), source_documents UPDATE (status), content_items UPDATE (embedding), content_chunks via regenerateChunks, content_items UPDATE (classification), content_items UPDATE (quality_score), content_items UPDATE (layer), merge_item_metadata RPC, source_documents UPDATE (final status), source_document_diffs INSERT loop. The outer catch L1083-1099 marks pipeline_runs.status='failed' via updatePipelineProgress — observability IS present at the pipeline_runs level. | Per-step inner try blocks already discriminate failures at fine grain (L408 source_documents, L465 extraction, L613/909 source_documents updates, L924 diff). The outer catch IS observability-complete via pipeline_runs. However: the seed inner pattern at L408-447 (P0-1) is the unfixed gap. Listed as P1 here only to acknowledge that the broader try is nearly-complete. | Out of scope for this audit — the P0-1 entry above captures the fix. The OUTER catch at L1083 is the exemplar pattern (each cron should mirror it). |
| P1-5 | scripts/bid_worker.py:264-329 (fill_template_job per-field UPDATE loop) | The per-field UPDATE loop at L297-312 issues N updates against template_fields. If the loop fails partway (network blip, FK violation on bad field_id), some fields are marked filled and others remain in their prior state. The except arm at L325 only flips the parent template to fill_failed — does not roll back per-field state. | Caller sees templates.status='fill_failed' but template_fields rows have a mix of pre- and post-loop states. Re-running the job re-issues the storage upload (rejected by upsert: false if path collides) and re-attempts the UPDATEs. | Either (a) wrap the per-field UPDATE loop in its own try with explicit error capture per field; OR (b) issue all field updates as a batch UPDATE via RPC (e.g. set_field_fill_status_batch(p_template_id, p_updates)) so the batch is atomic. |
5. P2 findings — pure-read failure tolerance + false positives
Section titled “5. P2 findings — pure-read failure tolerance + false positives”These were flagged BARE by the heuristic but inspection showed they are intentional best-effort or operate on mutually-exclusive branches.
lib/intelligence/content-extractor.ts:60-93(resolveGoogleNewsUrl): Twoawait fetch(...)calls — HEAD + GET fallback to follow Google News redirect. Both are pure reads. Catch returns the original URL. Documented fallback; intentional P2.app/api/oauth/decision/route.ts:17-69:approveAuthorizationORdenyAuthorization(mutually exclusive — only one fires per request). Heuristic counted both as awaits in the same try. False positive.app/api/insights/route.ts:15-120: All RPC calls are reads (get_trend_analysis,get_topic_deep_dive,get_author_analysis,get_content_gaps,get_reading_patterns). Heuristic over-matched onawait supabase.rpc(...)with no semantic check. False positive.app/api/tags/route.ts:27-160: Pure read RPC calls (get_tag_counts_filtered,get_all_tag_counts). False positive.app/api/bids/[id]/route.ts:23-118(GET): Pure read RPC + storage list. Sibling-warnings envelope handles partial failures. False positive.app/api/bids/route.ts:22-141(GET): Pure read with sibling-warnings envelope. False positive.scripts/mcp-eval/functional-correctness.ts:2953-2960: Test infrastructure — out of production scope. Excluded.
6. Cross-reference with 0.2.6
Section titled “6. Cross-reference with 0.2.6”| 0.2.6 finding | 0.2.7 status | Note |
|---|---|---|
app/api/items/batch/route.ts:478 (P1, no-param catch on pipeline_runs progress UPDATE) | Not flagged here (only one write inside) | 0.2.6’s classification stands. |
app/api/items/batch/route.ts:504 (P1, terminal-state UPDATE catch) | Not flagged here (only one write inside) | 0.2.6 stands. |
app/api/items/batch/route.ts:547 (P1, double-fault catch) | Not flagged here | 0.2.6 stands. |
app/api/intelligence/workspaces/route.ts:231 (P1, guide-creation outer catch) | Upgraded to P0-2 in this audit | 0.2.6 missed the outer feed_prompts.insert at L218 which has NO error handling at all — that’s the actual silent gap. The L231 inner try is secondary. |
lib/intelligence/pipeline.ts:455 (P1, summary fail) | Not flagged here (only one write inside outer catch) | 0.2.6 stands. |
lib/intelligence/pipeline.ts:309/371/505/514 (P1, feed-poll / storeAsContentItem) | Not flagged here (errors flow to result.errors[]) | 0.2.6 stands; 0.2.7 audit confirms the error-array contract is internal-consistent though Sentry-blind. |
lib/content/chunk-store.ts:108/132/170 (P1, errors[] contract drift) | Not in this audit’s pattern (helper-internal errors[]) | 0.2.6 stands. |
NEW P0 discovered by 0.2.7 but missed by 0.2.6: app/api/items/[id]/files/route.ts:29-160 (Anthropic file orphan) | — | 0.2.6 only matched bare swallow-catch shape; this site uses try/catch (err) with safeErrorMessage so 0.2.6’s filter excluded it. |
NEW P0 discovered by 0.2.7: app/api/items/[id]/images/route.ts:58-301 (storage orphan loop) | — | Same — 0.2.6’s filter excluded sites with non-empty catch bodies; this catch does call safeErrorMessage but lacks orphan-cleanup. |
NEW P0 discovered by 0.2.7: scripts/bid_worker.py:264-329 (storage + DB orphan) | — | 0.2.6 listed Python except Exception: pass cases (3 sites); this Python case has except Exception as e: ... raise, which 0.2.6’s filter excluded. |
NEW P1 discovered by 0.2.7: app/api/cron/freshness-transitions/route.ts:97-627 (no recordPipelineRun in catch) | — | 0.2.6 found lib/intelligence/pipeline.ts per-source error pattern, but cron-level outer catch was not audited. |
NEW P1 discovered by 0.2.7: app/api/cron/quality-score/route.ts:74-460 (no recordPipelineRun in catch) | — | Same. |
7. Re-ingest readiness gate
Section titled “7. Re-ingest readiness gate”Findings that MUST be resolved before re-ingesting prod data into the cleaned schema:
| Severity | File:line | Reason |
|---|---|---|
| GATE P0-1 | app/api/upload/route.ts:408-447 | The seed itself. Any future upload-path re-ingest will exercise this and silently produce orphans if the inner UPDATE drifts. Must split into per-write try/catch with rollback OR transactional RPC. |
| GATE P0-2 | app/api/intelligence/workspaces/route.ts:218 | feed_prompts.insert is fire-and-forget; admins creating workspaces during re-ingest may produce workspace rows without the auto-generated prompt. Must wrap in sb() and either roll back the workspace or surface a warnings envelope. |
| GATE P0-5 | scripts/bid_worker.py:264-329 | If bid template-fill jobs run during the migration window, storage orphans are created on partial failure. Must add storage-rollback in the except arm. |
| GATE P0-3 | app/api/items/[id]/files/route.ts:29-160 | Anthropic file uploads produce orphans on partial failure. Idempotent file_id strategy or try/finally rollback. |
| GATE P0-4 | app/api/items/[id]/images/route.ts:58-301 | Image extraction storage orphans on per-page failure. Cleanup pendingPaths in catch. |
| RECOMMENDED P1-1 | app/api/cron/freshness-transitions/route.ts:622-627 | Add recordPipelineRun({status:'failed'}) to the cron’s terminal catch — currently the table sees no row when the cron throws. Mirror review-cadence’s pattern. |
| RECOMMENDED P1-2 | app/api/cron/quality-score/route.ts:455-460 | Same fix as P1-1. |
| RECOMMENDED P1-3 | app/api/source-documents/[id]/send-to-review/route.ts:29-201 | Switch notifications to a warnings envelope so partial-success paths don’t return generic 500 after governance flips have committed. |
8. ESLint / lint extension proposals
Section titled “8. ESLint / lint extension proposals”The existing local/no-silent-promise-catch and local/no-unchecked-supabase-error
rules cover individual swallows. The “outer-catch-masks-inner-write” pattern
needs structural matching. Proposals:
-
local/no-multi-write-shared-catch(TS-ESLint custom). Triggers on atryblock whose body contains ≥2 awaited write expressions (matched by AST visitor against a curated list of patterns matching.from(...).insert/update/upsert/delete,.storage.from(...).upload/remove/move/copy,.rpc('merge_X' | 'insert_X' | 'update_X' | 'record_X'),recordPipelineRun,regenerateChunks, etc.) AND whose catch arm has fewer thann_writesdistinct logger / error-handling calls. Severity:warn. Override comment:// eslint-disable-next-line local/no-multi-write-shared-catch -- per-write disambiguation handled at <line>. -
local/no-fire-and-forget-supabase-write(TS-ESLint custom). Triggers onawait supabase.from(...).insert(...)/update/upsert/deletethat does NOT have.select(),.errordestructuring, OR a surroundingsb(...)wrapper. Catches the P0-2 case atapp/api/intelligence/workspaces/route.ts:218. Severity:warn. -
local/cron-catch-must-record-failure(TS-ESLint custom). Triggers when a route file underapp/api/cron/**/*.tshas a top-leveltry/catchwhose catch arm returnsNextResponse.jsonwith status 5xx AND does NOT callrecordPipelineRun(...)withstatus: 'failed'. Catches P1-1, P1-2. -
Python
ruffenforceB902/E722onscripts/kb_pipeline/(already proposed in 0.2.6 §Open Question 5). Doesn’t catch P0-5 directly (which usesexcept Exception as e:not bare except), but reinforces the “no silent except” principle. P0-5 would need a separate custom check.
9. Open questions for parent session
Section titled “9. Open questions for parent session”-
Is the seed pattern (P0-1) expected to be transactional via DB-side trigger or via app-level RPC? The audit suggests a
link_source_document( p_item_id, p_doc_id)RPC that does the INSERT + back-link UPDATE in one txn. Liam to decide between (a) RPC route, (b) split try/catch with compensating delete, (c) bidirectional FK trigger that preventssource_documentsinsert from succeeding without the back-link being established. -
Should the
feed_prompts.insertat P0-2 hard-fail the workspace creation, or surface as warning? Currently it surfaces NOTHING. If a workspace is created without its auto-prompt, the user has to manually configure the prompt before any feed-scoring works — but the workspace IS still usable for manual feed-source addition. Liam to decide policy. -
Anthropic file orphans (P0-3) — is there a deterministic file_id derivation we can use? The Anthropic Files API accepts an arbitrary filename but allocates the file_id server-side. Idempotency-by-content-hash would require us to first list / search Anthropic files by metadata. This is a product question, not a structural one.
-
Cron failure observability (P1-1, P1-2) — should we centralise via a
withCronTelemetry(handler)wrapper? A small higher-order function that wraps every cron handler withtry { await recordPipelineRun(start); const r = await handler(); await recordPipelineRun(complete, r); return r; } catch (err) { await recordPipelineRun(failed, err); throw }would eliminate the P1 class structurally. Three crons currently get this right (review-cadence) and three don’t (freshness-transitions, quality-score, classification-quality — though the last wasn’t in scope here). -
Storage-orphan cleanup (P0-4, P0-5) — is per-route rollback acceptable, or do we want a periodic janitor cron that diffs storage-list vs DB references? Per-route rollback is simpler but doesn’t cover the historic backlog. A janitor cron could close both holes.
10. Confidence summary
Section titled “10. Confidence summary”| Aspect | Confidence | Reason |
|---|---|---|
| Detection of the literal seed shape | 100% | Pattern 5 matched exactly one site (the seed). |
| Detection of multi-await with-param catch | 92% | Pattern 1 + write-heuristic post-filter; heuristic may have ~5% false-negative rate on RPCs with non-canonical names not in the curated list. |
| Detection of multi-await no-param catch | 90% | Pattern 2 yielded only 2 sites; one production-relevant (content-extractor.ts, P2) and one out-of-scope (mcp-eval). |
Detection of chained .catch(...) write fallbacks | 95% | All 69 chained catches reviewed; none represent the pattern. |
| Detection of Python try/except multi-write | 85% | Pattern 4 matched bid_worker.py thoroughly; other production scripts (kb_pipeline, ingest_*) reviewed and found to use single-write or per-step disambiguation. |
| P0/P1 classification accuracy | 88% | The P0/P1 split depends on whether the failure leaves “orphaned across systems” (P0) vs “bounded in-DB partial state” (P1). Some sites may shift class on closer inspection (e.g., P0-3 vs P1 if the Anthropic file is auto-pruned by Anthropic side after N days). |
| Coverage of seed-pattern variants beyond explicit sweep | 80% | Some shape variants may have escaped — e.g., a try wrapping a Promise.all([w1, w2]) would hide both failures behind one rejection. Not specifically swept here; recommend a follow-up Promise.all([await write1, await write2]) pattern audit. |
11. Three-line summary
Section titled “11. Three-line summary”- Confidence: 88% — the literal seed shape was found (one site, the seed itself), and 4 same-class P0 plus 5 P1 sites were identified via wider structural patterns (storage+DB orphans, external-API+DB orphans, multi-write cron catches without recordPipelineRun).
- Top 3 findings: (1)
app/api/intelligence/workspaces/route.ts:218fire-and-forgetfeed_prompts.insertis the strongest analogue to the seed — workspaces created without auto-prompts and zero observability; (2)app/api/items/[id]/files/route.ts:129-142andapp/api/items/[id]/images/route.ts:243-281produce external/storage orphans on partial failure (Anthropic file_id uploaded but not persisted; storage objects uploaded but not metadata-merged); (3)app/api/cron/freshness-transitions/route.ts:622andapp/api/cron/quality-score/route.ts:455lackrecordPipelineRun({ status: 'failed' })in their terminal catch arms — making cron failures invisible to the pipeline_runs table (review-cadence is the exemplar that gets this right). - Biggest open question: Q1 — should the seed pattern (P0-1) be fixed via
per-write try/catch with compensating delete, OR via a transactional
link_source_documentRPC, OR via a bidirectional FK trigger? The cleanest fix is the RPC; the cheapest is the per-write try; the most defensive is the trigger. Recommendation depends on whether other paths besides upload also create source_documents (per 0.1 §10.2: only the upload path does).