Skip to content

refactor(rag): extract the rag cluster into src/lib/rag/ (maturity X2) - #994

Merged
BigSimmo merged 6 commits into
mainfrom
claude/maturity-phase-4-rag-extraction
Jul 20, 2026
Merged

refactor(rag): extract the rag cluster into src/lib/rag/ (maturity X2)#994
BigSimmo merged 6 commits into
mainfrom
claude/maturity-phase-4-rag-extraction

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 4 of the maturity backlog (docs/maturity-backlog-workorders.mdX2): the first real domain directory under src/lib, and the seam that unblocks directory-scoped boundary rules for the rest of the 197-file flat lib.

  • Moved the 22-file rag cluster (rag.ts + 21 rag-*.ts) into src/lib/rag/ via git mv (renames preserved).
  • Rewrote every importer: @/lib/rag*@/lib/rag/rag* (51 sites) and the relative ../src/lib/rag*../src/lib/rag/rag* (worker + tests). The ../scripts/rag-offline-contract.mjs import was deliberately left untouched.
  • Updated the maintainability-budget key, two config-path test fixtures (client-bundle boundary + worker-deploy trigger), the two readFileSync contract-test paths, docs/codebase-index.md, and rag path references across 13 maintained docs.

Pure moves + path rewrites — no logic change, so behaviour is identical.

Verification

Full local gate, all green (run as constituent commands rather than the verify:pr-local wrapper):

  • npm run typecheck — pass.
  • npm run test — 3012 pass; the only failure is the pre-existing container-only pdf-extraction-budget flake (a Python-subprocess deadline test with no rag dependency), which fails identically on clean origin/main — not a regression from this refactor.
  • npm run lint — pass.
  • npm run check:maintainability-budgets — pass (src/lib/rag/rag.ts 5143/5238).
  • npm run docs:check-index — pass (new src/lib/rag/ indexed); npm run docs:check-links — pass (989 refs); npm run format:check — pass.
  • UI verification not run: pure import-path rewrite, no rendered output changed (the only touched component file, display-text.ts, is a string helper — no JSX/markup).
  • Retrieval/answer evals not run: no retrieval, ranking, selection, chunking, or answer-contract logic changed — the rag modules are byte-identical and only moved location, so the offline/live evals are unaffected. No dependency change.

Risk and rollout

  • Risk: low — a large but purely mechanical diff (renames + import/path strings). Verified behaviour-preserving by the full type+test gate; git log --follow traces history through the renames.
  • Rollback: revert the PR (single squash commit; no data or schema effect).
  • Provider or production effects: None.

Clinical Governance Preflight

This is a byte-identical relocation of the rag modules (git mv + import-path rewrites); no clinical behaviour, data flow, or configuration changed. Each item is preserved by construction:

  • 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

Notes

Next natural step (separate PR) is X3 — decompose rag.ts behind the maintainability budget, now that its siblings already sit in src/lib/rag/. This PR intentionally does not touch logic.


Generated by Claude Code

Summary by CodeRabbit

  • Documentation
    • Updated clinical safety hazard analysis, privacy/tenancy reviews, observability/SLO notes, and deployment architecture docs with clearer, up-to-date references.
    • Marked the completed retrieval-domain extraction work order as done.
  • Refactor
    • Reorganized retrieval and answer-processing modules to align with the new internal layout (intended to keep endpoint behavior unchanged).
  • Improvements
    • Enhanced clinical answer text sanitization and monitoring, including improved capture of hybrid retrieval failures and source-only degradation reasons.
  • Tests
    • Updated test wiring and coverage to match the reorganized module structure.

@supabase

supabase Bot commented Jul 20, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cc545c08-57a4-48e9-8dd2-4fab740fded1

📥 Commits

Reviewing files that changed from the base of the PR and between 6171b2d and 19ef1bf.

📒 Files selected for processing (2)
  • docs/maturity-backlog-workorders.md
  • scripts/eval-retrieval.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/eval-retrieval.ts

📝 Walkthrough

Walkthrough

This PR moves RAG modules under src/lib/rag/, adds supporting RAG utilities, rewires application, script, worker, and test imports, updates maintainability and deployment references, and refreshes documentation citations and hazard-analysis content.

Changes

RAG domain-directory extraction

Layer / File(s) Summary
RAG modules and entrypoint wiring
src/lib/rag/*
Adds answer-text sanitization, comparison, context selection, contracts, document-summary loading, evaluation, diagnostics, provider handling, query guards, cache matching, and versioning modules; the rag.ts entrypoint and related helpers use the nested module paths.
Application and script call sites
src/app/api/**, worker/main.ts, src/lib/*, scripts/*
Updates route, worker, shared-library, evaluation, cache-warming, and maintainability references to the reorganized RAG modules without changing surrounding behavior.
Tests and documentation
tests/*, docs/*
Updates test imports and mocks, corrects RAG path citations, records the completed extraction, and expands clinical hazard and injection-threat descriptions.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: moving the RAG cluster into src/lib/rag/ as part of X2.
Description check ✅ Passed The description matches the template sections and includes summary, verification, risk, governance preflight, and notes with concrete details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/maturity-phase-4-rag-extraction

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

@BigSimmo
BigSimmo marked this pull request as ready for review July 20, 2026 14:34
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@BigSimmo
BigSimmo enabled auto-merge (squash) July 20, 2026 14:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/lib/rag/rag-query-guard.ts (1)

19-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate decision tree between the two exported guards.

shouldShortCircuitUnsupportedSearch and isUnsupportedSoftTailAnalysis run the identical 4-condition chain, only inverting the first two branches' return value. Any change to the pattern guards must be mirrored in both functions with reversed polarity, which is easy to get wrong.

♻️ Suggested extraction of the shared classification
+type UnsupportedSearchClassification =
+  | "pattern_guard"
+  | "soft_tail_ineligible"
+  | "soft_tail_consumer"
+  | "soft_tail_confidence_gate_pass"
+  | "soft_tail_confidence_gate_fail";
+
+function classifyUnsupportedSearch(query: string, analysis: ClinicalQueryAnalysis): UnsupportedSearchClassification {
+  if (unavailableDocumentNoisePattern.test(query)) return "pattern_guard";
+  if (clearlyOutsideCorpusMedicalPattern.test(query) && analysis.documentTitleTerms.length === 0) return "pattern_guard";
+  if (!unsupportedSoftTailEligible(analysis)) return "soft_tail_ineligible";
+  if (clearlyNonClinicalConsumerPattern.test(query)) return "soft_tail_consumer";
+  return analysis.confidence <= 0.42 && analysis.expandedTerms.length <= 5
+    ? "soft_tail_confidence_gate_pass"
+    : "soft_tail_confidence_gate_fail";
+}
+
 export function shouldShortCircuitUnsupportedSearch(query: string, analysis: ClinicalQueryAnalysis) {
-  if (unavailableDocumentNoisePattern.test(query)) return true;
-  if (clearlyOutsideCorpusMedicalPattern.test(query) && analysis.documentTitleTerms.length === 0) return true;
-  if (!unsupportedSoftTailEligible(analysis)) return false;
-  if (clearlyNonClinicalConsumerPattern.test(query)) return true;
-  return analysis.confidence <= 0.42 && analysis.expandedTerms.length <= 5;
+  const classification = classifyUnsupportedSearch(query, analysis);
+  return classification === "pattern_guard" || classification === "soft_tail_consumer" || classification === "soft_tail_confidence_gate_pass";
 }

 // True only for queries that would short-circuit via the soft tail itself, not a pattern guard.
 export function isUnsupportedSoftTailAnalysis(query: string, analysis: ClinicalQueryAnalysis) {
-  if (unavailableDocumentNoisePattern.test(query)) return false;
-  if (clearlyOutsideCorpusMedicalPattern.test(query) && analysis.documentTitleTerms.length === 0) return false;
-  if (!unsupportedSoftTailEligible(analysis)) return false;
-  if (clearlyNonClinicalConsumerPattern.test(query)) return false;
-  return analysis.confidence <= 0.42 && analysis.expandedTerms.length <= 5;
+  return classifyUnsupportedSearch(query, analysis) === "soft_tail_confidence_gate_pass";
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/rag/rag-query-guard.ts` around lines 19 - 34, Extract the shared
classification logic from shouldShortCircuitUnsupportedSearch and
isUnsupportedSoftTailAnalysis into a single private helper that distinguishes
pattern-guard matches from soft-tail eligibility. Update both exported functions
to reuse that helper while preserving their current behavior: the first should
short-circuit on any guard or soft-tail match, and the second should return true
only for the soft-tail match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/clinical-hazard-analysis.md`:
- Around line 47-55: Update the applyNumericVerification references in the
hazard analysis to link to its actual definition in src/lib/answer-verification
and its invocation in src/lib/rag/rag.ts around the cited call-site range,
replacing the incorrect rag.ts definition and outcome line links. Keep the
surrounding behavior description unchanged.

In `@docs/rag-injection-threat-model.md`:
- Around line 15-38: Update the documentation citations for buildRagSourceBlock
and its related prompt-field locations to reference the extracted
rag-source-block.ts module, including the sections at 70–86, 99–101, 112–125,
and 135–148. Retain rag.ts citations only for answerInstructions and
buildAnswerInput/answer-input assembly, and ensure all referenced line ranges
match the extracted implementation.

---

Nitpick comments:
In `@src/lib/rag/rag-query-guard.ts`:
- Around line 19-34: Extract the shared classification logic from
shouldShortCircuitUnsupportedSearch and isUnsupportedSoftTailAnalysis into a
single private helper that distinguishes pattern-guard matches from soft-tail
eligibility. Update both exported functions to reuse that helper while
preserving their current behavior: the first should short-circuit on any guard
or soft-tail match, and the second should return true only for the soft-tail
match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f344ebf5-2be1-48d1-81e6-9faf55e28cda

📥 Commits

Reviewing files that changed from the base of the PR and between 6d20560 and 6171b2d.

📒 Files selected for processing (114)
  • docs/clinical-hazard-analysis.md
  • docs/codebase-index.md
  • docs/deployment-architecture.md
  • docs/maturity-backlog-workorders.md
  • docs/observability-slos.md
  • docs/openai-cross-border-basis.md
  • docs/privacy-impact-assessment.md
  • docs/process-hardening.md
  • docs/rag-hybrid-findings-and-todo.md
  • docs/rag-injection-threat-model.md
  • docs/redesign/06-verification.md
  • docs/search-rag-master-context.md
  • docs/search-rag-master-plan.md
  • docs/tenancy-defense-in-depth-review.md
  • scripts/check-maintainability-budgets.mjs
  • scripts/eval-answer-quality.ts
  • scripts/eval-quality.ts
  • scripts/eval-rag.ts
  • scripts/eval-retrieval.ts
  • scripts/eval-search-api.ts
  • scripts/eval-search.ts
  • scripts/eval-utils.ts
  • scripts/warm-retrieval-cache.ts
  • src/app/api/answer/route.ts
  • src/app/api/answer/stream/route.ts
  • src/app/api/documents/[id]/labels/route.ts
  • src/app/api/documents/[id]/reviews/route.ts
  • src/app/api/documents/[id]/route.ts
  • src/app/api/documents/[id]/summarize/route.ts
  • src/app/api/documents/[id]/table-facts/route.ts
  • src/app/api/documents/bulk/reindex/route.ts
  • src/app/api/documents/bulk/route.ts
  • src/app/api/search/route.ts
  • src/components/clinical-dashboard/display-text.ts
  • src/lib/answer-verification.ts
  • src/lib/clinical-safety.ts
  • src/lib/cross-document-synthesis.ts
  • src/lib/rag/rag-answer-support.ts
  • src/lib/rag/rag-answer-text.ts
  • src/lib/rag/rag-cache-utils.ts
  • src/lib/rag/rag-cache.ts
  • src/lib/rag/rag-candidate-sources.ts
  • src/lib/rag/rag-claim-support.ts
  • src/lib/rag/rag-comparison.ts
  • src/lib/rag/rag-context-selection.ts
  • src/lib/rag/rag-contracts.ts
  • src/lib/rag/rag-document-summary-context.ts
  • src/lib/rag/rag-eval-cases.ts
  • src/lib/rag/rag-eval-diagnostics.ts
  • src/lib/rag/rag-extractive-answer.ts
  • src/lib/rag/rag-provider.ts
  • src/lib/rag/rag-query-guard.ts
  • src/lib/rag/rag-quote-verification.ts
  • src/lib/rag/rag-retrieval-variants.ts
  • src/lib/rag/rag-route-budget.ts
  • src/lib/rag/rag-routing.ts
  • src/lib/rag/rag-source-block.ts
  • src/lib/rag/rag-versioning.ts
  • src/lib/rag/rag.ts
  • src/lib/semantic-rerank.ts
  • src/lib/universal-search.ts
  • tests/anonymous-answer-cache-policy.test.ts
  • tests/answer-prose-runons.test.ts
  • tests/answer-ranking.test.ts
  • tests/answer-responsiveness-gate.test.ts
  • tests/api-validation-contract.test.ts
  • tests/architecture-boundaries.test.ts
  • tests/corpus-grounding.test.ts
  • tests/cross-tenant-staging-config.test.ts
  • tests/document-admin-rate-limit.test.ts
  • tests/document-mutation-routes.test.ts
  • tests/eval-utils.test.ts
  • tests/extractive-answer-formatting.test.ts
  • tests/private-access-routes.test.ts
  • tests/private-rag-access.test.ts
  • tests/public-access-deep.test.ts
  • tests/rag-abort-signal.test.ts
  • tests/rag-answer-fallback.test.ts
  • tests/rag-answer-support.test.ts
  • tests/rag-answer-text.test.ts
  • tests/rag-cache-invalidation.test.ts
  • tests/rag-cache-utils.test.ts
  • tests/rag-chunk-load-cache.test.ts
  • tests/rag-claim-support.test.ts
  • tests/rag-classifier-memo.test.ts
  • tests/rag-comparison.test.ts
  • tests/rag-content-accuracy.test.ts
  • tests/rag-context-budget.test.ts
  • tests/rag-document-summary.test.ts
  • tests/rag-eval-cases.test.ts
  • tests/rag-eval-source-governance.test.ts
  • tests/rag-fast-path-ordering.test.ts
  • tests/rag-generation-fingerprint.test.ts
  • tests/rag-injection.test.ts
  • tests/rag-offline-answer.test.ts
  • tests/rag-provider.test.ts
  • tests/rag-query-concurrency.test.ts
  • tests/rag-route-budget.test.ts
  • tests/rag-routing.test.ts
  • tests/rag-score.test.ts
  • tests/rag-second-stage-ranking.test.ts
  • tests/rag-shared-cache.test.ts
  • tests/rag-tail-latency.test.ts
  • tests/rag-trust.test.ts
  • tests/rag-variant-early-exit.test.ts
  • tests/railway-config.test.ts
  • tests/retrieval-access-scope.test.ts
  • tests/retrieval-hydration-scope.test.ts
  • tests/retrieval-query-variants.test.ts
  • tests/semantic-rerank.test.ts
  • tests/source-backed-recovery-cross-reference.test.ts
  • tests/source-review-route.test.ts
  • tests/universal-search.test.ts
  • worker/main.ts

Comment thread docs/clinical-hazard-analysis.md
Comment thread docs/rag-injection-threat-model.md
@BigSimmo
BigSimmo merged commit 83e5eae into main Jul 20, 2026
16 checks passed
@BigSimmo
BigSimmo deleted the claude/maturity-phase-4-rag-extraction branch July 20, 2026 16:32
BigSimmo added a commit that referenced this pull request Jul 20, 2026
BigSimmo added a commit that referenced this pull request Sep 5, 2026
…in gaps (#2634)

* deps: update browserslist to 4.28.8 so the production npm audit high clears (M19)

Defect: the production dependency tree resolved browserslist 4.28.2, which
carries two high advisories (GHSA-c83g-rgw3-j3cx, GHSA-73wf-gq98-2v4g), so
the lockfile-gated `npm audit --omit=dev --audit-level=high` step in the
safety job exits 1 on every lockfile-touching PR and on the weekly scheduled
full run.

Trigger: any PR that changes package-lock.json or .npmrc, or the Sunday
scheduled CI run.

Fix: `npm update browserslist` (owner-approved registry call, no major bump);
the lock now resolves browserslist 4.28.8 and its own in-range data
dependencies. No other dependency was touched. Proof: `npm audit --omit=dev`
no longer lists browserslist; the remaining fast-uri high is a newer advisory
outside this package's approved scope and is reported for owner decision.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* ci: give the worker's Python parsers a vulnerability signal (M20)

Defect: worker/python/requirements.txt and eval/docling/requirements.txt are
hash-locked but nothing reported a published CVE against a pinned parser —
dependabot.yml had no pip ecosystem, and the weekly Trivy scan exited 0
inside a continue-on-error step with its summary written only to the run log.

Trigger: a CVE against PyMuPDF, Pillow, pytesseract or docling, which parse
attacker-supplied uploads in the ingestion worker.

Fix: two pip Dependabot entries (with the hashed-lock regeneration note), and
a follow-up step in docker-image.yml that writes the Trivy summary to the job
summary and exits non-zero on HIGH/CRITICAL outside pull_request/merge_group
runs, so the scheduled and main runs fail and notify-ci-failure.yml delivers
the failure. Pull-request runs stay advisory, keeping
tests/container-ci-contract.test.ts's non-blocking contract intact.

Proof: tests/ci-audit-contracts.test.ts "M20" block.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* ci: notify on Staging tenancy isolation failures (M25)

Defect: notify-ci-failure.yml, the solo-maintainer safety net, enumerated
nine workflows by name and omitted "Staging tenancy isolation", the daily
cross-tenant staging harness, which itself only uploads an evidence artifact.

Trigger: the daily run fails — a real cross-tenant leak on staging, or a
rotated or missing CROSS_TENANT_* secret.

Fix: add the workflow to the watched list; the existing head_repository and
branch guard already admits scheduled runs on main.

Proof: tests/ci-audit-contracts.test.ts "M25" block reads the workflow's
`name:` and asserts it appears in the notifier's list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* ci: point the live Web-Vitals default routes at pages that render (M29)

Defect: the `routes` dispatch default of live-web-vitals.yml still listed
`/therapy-compass`, `/dsm` and `/forms`, which have been 307 redirects onto
`/?mode=<id>` since #2157 and #2308. scripts/summarise-web-vitals.mjs
rejects a report whose final URL differs from the requested one, so 18 of
the 30 default cells were "measured a different page" and the summarise
step could never produce a verdict. The header also told the operator to
record the verdict against #17, closed 2026-07-31.

Trigger: dispatching "Live Web Vitals baseline" without overriding `routes`.

Fix: the default now measures `/`, `/therapy-compass/search`,
`/documents/search`, `/dsm/search` and `/forms/search` — the in-place
result routes lighthouse-budget.json's `$routes` rationale names — and the
header points at the open row via `npm run issues:update`. The summariser's
fixture DEFAULT_ROUTES (and its cell names) moved with it; the summariser
itself is unchanged.

Proof: tests/ci-audit-contracts.test.ts "M29" block — every default route
resolves to a page.tsx that does not call redirect( (the `/` shell's guarded
legacy-parameter redirect excepted), the fixture equals the workflow
default, and the #17 instruction is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* ci: digest-pin the advisory Semgrep image in sast.yml (L36)

Defect: sast.yml ran `semgrep/semgrep:1.168.0` by mutable tag on every push
and pull request while ci.yml's blocking ingestion gate already pinned the
immutable digest of the same triage-verified image.

Trigger: an upstream re-tag or registry compromise of the 1.168.0 tag; the
job runs third-party code with read access to the private source tree.

Fix: reference `semgrep/semgrep:1.168.0@sha256:59fbed61…`, the digest
ci.yml uses (recorded as the 1.168.0 image in
docs/maturity-backlog-workorders.md, X4), so the two references move
together. No registry call was needed.

Proof: tests/ci-audit-contracts.test.ts "L36" block asserts the advisory
image is digest-pinned to the gate's digest; check:github-actions stays
green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* ci: drop unused permissions from the Secret Scan workflow (L37)

Defect: secret-scan.yml granted `pull-requests: read` and
`security-events: write`, leftovers from gitleaks-action@v3's SARIF upload;
the pinned scripts/run-gitleaks-pinned.mjs never touches either API.

Trigger: compromise of a step inside the job on the private repository —
least-privilege only, no functional effect today.

Fix: reduce the workflow to `contents: read`.

Proof: tests/ci-audit-contracts.test.ts "L37" block asserts the permissions
block is exactly `contents: read` and that the runner script emits no
report; `npm run check:gitleaks-pinned` self-test still passes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* ci: gate the @claude workflows on author association (L38)

Defect: claude.yml and claude-backlink.yml admitted any non-bot account
that could comment; the header's "collaborator" claim was not enforced at
workflow level, and claude.yml's job holds contents/pull-requests/issues/
id-token write scopes.

Trigger: a comment author outside the maintainer's trust boundary mentions
@claude on an issue or pull request.

Fix: every trigger arm now also requires the comment or review
author_association to be OWNER, MEMBER or COLLABORATOR, and the header
comments describe the gate that is actually enforced.

Proof: tests/ci-audit-contracts.test.ts "L38" block parses each `if:` arm
of both workflows and asserts the association gate is present.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* deploy: watch check-installed-lock-parity.mjs in both Railway services (L54)

Defect: scripts/check-installed-lock-parity.mjs is COPYed into and executed
by both Dockerfiles during `npm ci` (postinstall --write-stamp) but was not
a watch pattern in railway.app.json or railway.worker.json, unlike its
siblings check-node-engine.cjs and install-git-hooks.mjs.

Trigger: a push that changes only that script does not rebuild either
image, so the deployed image keeps a script version main no longer has
until an unrelated push rebuilds it.

Fix: add the script to both watchPatterns arrays.

Proof: tests/ci-audit-contracts.test.ts "L54" block derives every
`COPY scripts/*` from each Dockerfile and asserts the matching Railway
config watches it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* deps: bring allowScripts in step with the lock (L55)

Defect: package.json allowScripts still approved esbuild@0.28.1 after
Dependabot #2468 pinned esbuild 0.28.2, and omitted @sentry/cli@2.58.6,
which also carries a postinstall — `npm ci` warned that both scripts were
"not yet covered by allowScripts".

Trigger: any install; today an advisory warning, but a strict allowScripts
setting would break every install path including the Railway image builds.

Fix: approve esbuild@0.28.2 and @sentry/cli@2.58.6; no other key changed.

Proof: tests/ci-audit-contracts.test.ts "L55" block asserts every
allowScripts key matches a lock version and every non-optional package with
an install script is covered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* governance: route CODEOWNERS review to src/lib/rag/ (L91)

Defect: .github/CODEOWNERS named `/src/lib/rag.ts` and `/src/lib/rag-*.ts`,
neither of which exists since the RAG stack moved to `src/lib/rag/` in
#994, so the protected directory was covered only by the `*` catch-all.

Trigger: a collaborator joins and review routing on the RAG tree is
expected to apply.

Fix: replace the two dead patterns with `/src/lib/rag/`; the remaining
retrieval/search patterns are unchanged and still match files.

Proof: tests/ci-audit-contracts.test.ts "L91" block asserts every
CODEOWNERS pattern matches an existing surface and that the RAG directory
is named.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* ci: point the Codex auto-resolve high-risk list at the real deployment files (L92)

Defect: the high-risk path pattern in codex-autofix-review-comments.yml
named `Dockerfile`, `railway.json` and `nixpacks.toml`; the repository has
`Dockerfile`, `Dockerfile.worker`, `railway.app.json` and
`railway.worker.json`, and neither `railway.json` nor `nixpacks.toml`.

Trigger: a pull request touching only the worker Dockerfile or either
Railway config was classified low risk for routing.

Fix: `/^(?:Dockerfile(?:\.worker)?|railway\.(?:app|worker)\.json)$/`.

Proof: tests/ci-audit-contracts.test.ts "L92" block extracts the pattern
and asserts it matches each existing deployment file and no longer names
the absent ones; check:codex-autofix-workflow and the existing workflow
guard tests stay green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* deps: drop the dead brace-expansion@2 override and record override rationale (L129)

Defect: package.json overrides carried `brace-expansion@2: ^2.1.4`, which
matches nothing (the lock holds only 1.1.18 and 5.0.9), and the two exact
pins (`esbuild`, `sharp`) had no recorded reason, so the block looked
reviewed when it was not.

Trigger: repository hygiene; the exact `sharp` pin can also turn a routine
Next patch that raises its sharp floor into an install conflict.

Fix: remove the dead override and add an "Overrides rationale" table to
docs/framework-dependency-modernization-checklist.md naming every remaining
override, why it exists and when it can go. The exact pins themselves are
kept, per the package notes; relaxing `sharp` is recorded as the exit
condition.

Proof: tests/ci-audit-contracts.test.ts "L129" block fails on a
major-scoped override with no lock match and on an override missing from
the rationale table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* deps: hold the @types/node major in Dependabot (L20)

Defect: @types/node 26.x is typechecked against a Node 24 runtime (engines,
.nvmrc, both Dockerfiles, Railway images), and dependabot.yml's ignore list
held only typescript and eslint majors, so nothing stopped the next major.

Trigger: a contributor uses a Node-26-only API; tsc accepts it and the
worker or an API route throws at runtime on Node 24.

Fix (partial): add a semver-major ignore for @types/node with a comment
tying it to engines.node. Pinning the devDependency back to the 24.x line
needs a registry call (`npm install -D @types/node@^24`) that this package
was not approved to make; it is reported for owner decision and stays
tracked in docs/framework-dependency-modernization-checklist.md.

Proof: tests/ci-audit-contracts.test.ts "L20" block asserts the ignore
entry is present in the npm ecosystem.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSPY4VSqg7WVukCvmHQP9t

* deps: keep brace-expansion@2 as a recorded CVE pre-pin, and register the new CI suite

Two of this package's own gates contradicted each other. The L129 hygiene rule
removed the brace-expansion@2 override because the lock holds no 2.x copy, but
tests/installed-lock-parity.test.ts pins all three brace-expansion majors to
CVE-2026-14257-patched maintenance releases. Deleting the entry to satisfy the
hygiene rule would quietly drop that protection for the day a transitive bump
reintroduces a 2.x, so the override is restored rather than the CVE guard
relaxed.

The L129 rule is narrowed instead of weakened: a major-scoped override may
outlive its lock match only while its row in the overrides rationale table is
marked pre-pin and states why, and a second case rejects a pre-pin row naming
an override package.json no longer carries, so the exemption cannot rot into a
blanket one. The rationale table gains the brace-expansion@2 row with its exit
condition.

Separately, tests/ci-audit-contracts.test.ts reads workflow files, which
tests/ci-cache-safety.test.ts requires to be listed in test:ci-workflows; it is
now registered there.

Verified: tests/ci-audit-contracts.test.ts, tests/installed-lock-parity.test.ts
and tests/ci-cache-safety.test.ts together, 117 passed (117);
check:installed-lock-parity clean; package-lock.json unchanged.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
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