audit fixes (P8a): close the CI, deployment and dependency-supply-chain gaps - #2634
Conversation
…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
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
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
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
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
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
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
…s (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
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
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
…t 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
…tionale (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
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
…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
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: 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. Comment |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Bugbot couldn't run - usage limit reachedBugbot 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_c5824d96-7dcc-4046-9b2e-4ad6bda31d7d) |
…rts directly check:knip failed on this branch with "Unlisted dependencies (1): @sentry/core tests/sentry-ignore-errors.test.ts", which is why Static PR checks was red — it was red before the main merge too, so it is this package's own failure and not a merge artefact. The L8 test imports stringMatchesSomePattern from @sentry/core so that it pins the ignoreErrors list against Sentry's real matching implementation rather than a reimplementation of it. @sentry/core is a direct dependency of the declared @sentry/nextjs and so is always installed, but it was never declared here, and @sentry/nextjs does not re-export the helper. Declaring it is the honest fix; adding it to a knip ignore list would hide a real unlisted import, and rewriting the test to match patterns itself would drop the property that makes it worth having. Ownership note: package.json and package-lock.json are otherwise owned by the P8a package (PR #2634) in this remediation programme. This is one added line in each, in the alphabetical devDependencies list and its lock entry, disjoint from P8a's overrides/allowScripts/scripts edits. Verified: check:knip clean, check:installed-lock-parity clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
CI triageCI failed on this PR. Automated classification of the 2 failed job(s):
Compared with main CI run #15629 (cancelled). Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger. |
…aring it here Reverts the devDependency declaration added a commit ago. Declaring it was the honest fix for the unlisted import, but it turned this PR into a dependency-change PR, which flips CI's dependency audit from advisory to blocking — and that audit is red on main today for two unrelated high advisories (browserslist and fast-uri, both fixed by open Dependabot PRs #2568, #2570 and #2616). A documentation-and-privacy package should not be gated on those. Instead the import is allowed narrowly in knip.json. @sentry/core is a direct dependency of the declared @sentry/nextjs, so it is always installed; the import is in one test that pins the ignoreErrors list against Sentry's real matching implementation. Scoped to that one package name, so every other unlisted import still fails the gate. Remove this entry once @sentry/core is declared properly — that belongs in the P8a package (PR #2634), which owns package.json and package-lock.json and is already a dependency-change PR. Verified: check:knip clean; package.json and package-lock.json byte-identical to the previous commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
CI's safety job runs `npm audit --omit=dev --audit-level=high`, which is advisory on most pull requests but blocking on this one because it changes dependency files. It was failing on fast-uri: four advisories cover 3.0.0 through 3.1.5 (host confusion via skipped IDN canonicalization and via percent-encoded scheme normalization, plus SSRF via malformed IPv6 normalization and via repeated hostname percent-decoding). Both copies come from ajv under ajv-formats and schema-utils, which declare ^3.0.1, so the floor stays inside the same major. @xmldom/xmldom is the moderate finding in the same report (GHSA-6gmq-8vp8-gcm6, XML fragment injection during requireWellFormed serialization, <= 0.8.14). It does not on its own fail an --audit-level=high gate, but the one copy is mammoth's DOCX parser declaring ^0.8.6, so 0.8.15 is a patch inside the declared range and there is no reason to leave it. Both entries carry a rationale row in the dependency checklist, which tests/ci-audit-contracts.test.ts (audit L129) requires. npm audit --omit=dev --audit-level=high now reports: found 0 vulnerabilities. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
Bugbot couldn't run - usage limit reachedBugbot 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_3171f593-2fb2-4fd2-80c4-9e09fae973bb) |
Bugbot couldn't run - usage limit reachedBugbot 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_02a7c17c-a141-47fe-976e-291d845c3cdf) |
Summary
Audit remediation package P8a — CI, deployment and dependency supply chain, from
docs/audit/full-repository-audit-2026-09-02.md(PR #2573). Findings:M19,M20,M25,M29,L20,L36,L37,L38,L54,L55,L91,L92,L129.The two with the most security weight are
79cc751e9(the@claudeworkflows now check author association before running) and20f9df948(a Staging tenancy-isolation failure now actually notifies, instead of failing silently).The last two commits are corrections, and they are the part to read
0b48f6650implementedL129by removing thebrace-expansion@2override, correctly observing that the lock resolves only 1.1.18 and 5.0.9 so the entry matches nothing. Buttests/installed-lock-parity.test.tspins all three brace-expansion majors to CVE-2026-14257-patched maintenance releases. The two gates in this same package therefore contradicted each other, and the package's own test run was red.Resolved in the safer direction. The override is restored, not the CVE guard relaxed: an entry that matches nothing today still protects the day a transitive bump reintroduces a 2.x, and deleting it would drop that silently.
The
L129hygiene rule is narrowed rather than weakened. A major-scoped override may now outlive its lock match only while its row in the overrides rationale table is markedpre-pinand states why, and a second new case rejects apre-pinrow naming an overridepackage.jsonno longer carries — so the exemption cannot rot into a blanket one. The rationale table gains thebrace-expansion@2row with its exit condition.1c00b546calso registerstests/ci-audit-contracts.test.tsintest:ci-workflows, whichtests/ci-cache-safety.test.tsrequires of any suite that reads workflow files.9f85af522closes the CI failure this package caused for itself. The safety job runsnpm audit --omit=dev --audit-level=high, which is advisory on most pull requests but blocking on this one because it changes dependency files — and it was failing onfast-uri. Four advisories cover 3.0.0 through 3.1.5 (host confusion via skipped IDN canonicalization and via percent-encoded scheme normalization; SSRF via malformed IPv6 normalization and via repeated hostname percent-decoding). Both copies come fromajvunderajv-formatsandschema-utils, which declare^3.0.1, so the floor stays inside the same major.@xmldom/xmldomis the moderate finding in the same report (GHSA-6gmq-8vp8-gcm6, XML fragment injection duringrequireWellFormedserialization,<= 0.8.14); it does not on its own fail an--audit-level=highgate, but the one copy ismammoth's DOCX parser declaring^0.8.6, so 0.8.15 is a patch inside the declared range. Both entries carry a rationale row in the overrides table thattests/ci-audit-contracts.test.tsrequires.RAG impact: none
Verification
npm run verify:pr-localon the head carrying the dependency floors —- completed: check:runtime, check:installed-lock-parity, format:changed, check:diff-integrity, check:npm-ci-dry-run, sitemap:check, check:repo-awareness-snapshot, docs:check-index, docs:check-inventory, docs:check-scripts, docs:check-links, check:branch-review-ledger, check:outstanding-issues, check:ledger-write-discipline, check:github-actions, check:ci-scope, check:gitleaks-pinned, check:ci-triage, check:pr-policy, check:gate-manifest, check:skills, check:pr-mergeability, check:verification-plan, check:codex-autofix-workflow, lint, typecheck, test, build, check:rag:fixtures, check:medication-interactions, check:medication-lexicon-report·- failed: (none)·- not reached: (none)·Test Files 1149 passed | 1 skipped (1150)·Tests 16881 passed | 2 expected fail | 3 skipped (16886)npm audit --omit=dev --audit-level=high— the CI safety job's exact command — run locally against the updated lock:found 0 vulnerabilities, exit 0. Before the floors it reported2 vulnerabilities (1 moderate, 1 high)and exited 1, which is what turned theSafety and config checksjob red.npm run check:npm-ci-dry-runandnpm run check:installed-lock-parity— both in the completed list above, and both matter here because this is the only package in the programme that changespackage.jsonandpackage-lock.json.npm run check:diff-integrity—[diff-integrity] PASS — 2 changed test file(s), 46 -> 70 test case(s), against base d63790dcb.Net-additive; no floor raised.tests/ci-audit-contracts.test.ts,tests/installed-lock-parity.test.ts,tests/dependency-report.test.ts—Test Files 3 passed (3) / Tests 65 passed (65); and earlier withtests/ci-cache-safety.test.ts—Test Files 3 passed (3) / Tests 117 passed (117).A note on how this was verified, because it is unusual for this programme: the lockfile change means this branch's dependency tree differs from every other package's. An earlier gate attempt failed on
check:installed-lock-parity—full tree: install stamp belongs to a different package-lock.json— because the worktree shared another checkout'snode_modules. A dedicatednpm ci --include=devwas run for this branch before each gate above, so the results reflect this lockfile, not a neighbouring one.Verification not run:
npm run verify:ui— browser proof left to CI; no narrowed run is claimed as the full gate.Verification not run:
npm run verify:release, and every provider-backed gate — all work was offline.Risk and rollout
L129/CVE contradiction above was resolved by restoring the pin rather than by relaxing the test.L36's digest pin freezes an advisory scanner at one image; it will need deliberate bumping. The@types/nodemajor hold inL20is a deliberate freeze that must trackengines.node, not drift behind it. Thefast-uriand@xmldom/xmldomfloors overlap with open Dependabot PRs chore(deps): bump @xmldom/xmldom from 0.8.13 to 0.8.15 #2569, chore(deps): bump fast-uri from 3.1.5 to 3.1.7 #2570 and chore(deps): combine the fast-uri, xmldom, and browserslist bumps #2616, which bump the same transitives directly; an override is a floor, so it becomes a no-op rather than a conflict once those land.1c00b546cshould be reverted with0b48f6650, never alone, or the CVE guard and the hygiene rule go back to contradicting each other. Reverting9f85af522reopens the blockingnpm audithigh.fbeada01aaffects which pushes trigger a redeploy of the existing services; it adds a file to the watch list and removes none.Notes
0e2f3831caddssrc/lib/rag/to CODEOWNERS review routing, which is a governance tightening on the repository's most protected surface. It changes no retrieval, ranking or selection code.package.jsonandpackage-lock.json, and three findings in other packages (L45,L131in P20, and P15's script-comment rewords) were deliberately deferred because of that ownership. They unblock when this merges.🤖 Generated with Claude Code
https://claude.ai/code/session_01DHSyfuC6mS98ystWFiitAR
Note
Medium Risk
Changes CI enforcement, workflow permissions, and dependency overrides rather than product code, but mistakes weaken security gates (Claude trust boundary, image CVE blocking) or block merges via stricter audit/lock policy.
Overview
This PR implements audit package P8a — it tightens CI, deployment wiring, and npm supply-chain controls without changing application runtime behavior.
Security and workflow gates: The
@claudeworkflows now require owner, member, or collaboratorauthor_associationbefore running (same for the backlink workflow), so untrusted commenters cannot trigger jobs with write/OIDC scopes. Secret Scan drops unusedpull-requestsandsecurity-eventspermissions. Advisory Semgrep is digest-pinned to match the blocking gate inci.yml.Vulnerability signal and alerting: Dependabot gains pip entries for
worker/pythonandeval/docling, plus an npm ignore on@types/nodesemver-major so types stay aligned with Node 24. Docker image builds fail on Trivy HIGH/CRITICAL on main/schedule/manual (PRs stay advisory) and surface results in the job summary; Staging tenancy isolation failures now route throughnotify-ci-failure.yml.Dependencies and docs: New
fast-uriand@xmldom/xmldomoverride floors fix blockingnpm audit --omit=dev --audit-level=high;allowScriptsand esbuild 0.28.2 sync with the lock; an Overrides rationale table documents every override (includingbrace-expansion@2as a documented pre-pin). Railway watch patterns addcheck-installed-lock-parity.mjs.Live Web Vitals: Default Lighthouse routes switch to
* /searchpaths that render in place (replacing mode homes that 307 away), with matching test updates.Regression tests: New
tests/ci-audit-contracts.test.tspins these contracts and is wired intotest:ci-workflows. CODEOWNERS and Codex high-risk paths are updated forsrc/lib/rag/and real deployment filenames.Reviewed by Cursor Bugbot for commit 60288bf. Configure here.