From 4400f59730fbd24efc5f4c54adda828506f3835b Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:05:22 +0800 Subject: [PATCH 1/6] ops: reconcile hosted configuration presence --- docs/operator-backlog.md | 18 ++- docs/outstanding-issues.md | 231 ++++++++++++++-------------- scripts/check-env-parity.mjs | 250 +++++++++++++++++++++++-------- scripts/check-local-presence.mjs | 28 +++- tests/local-presence.test.ts | 6 + tests/repo-hygiene.test.ts | 58 +++++++ 6 files changed, 398 insertions(+), 193 deletions(-) diff --git a/docs/operator-backlog.md b/docs/operator-backlog.md index 7985b677e6..ef6c153040 100644 --- a/docs/operator-backlog.md +++ b/docs/operator-backlog.md @@ -42,14 +42,16 @@ Findings inventory for handover: [audit-handover-2026-07-14.md](audit-handover-2 Each environment gets **separate** service-role + OpenAI keys (per-env blast radius). Placement is a dashboard/CLI action, never committed. -| Secret / config | Status | Where | Notes | -| ------------------------------------------ | ---------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Local fillable HMAC/probe secrets | βœ… local | `.env.local` (gitignored) | Use `npm run check:local-presence` / `--fill` for distinct local `OPENAI_SAFETY_IDENTIFIER_SECRET`, `RAG_QUERY_HASH_SECRET`, and `HEALTH_DEEP_PROBE_SECRET`. Never commit values. Hosted stores are separate. | -| `RAG_QUERY_HASH_SECRET` (prod) | πŸ”Ž verify | Railway runtime secret | GitHub repo secret present since 2026-07-10 (CI green); confirm the SAME value is set in Railway runtime. PIA-2 fail-closed guard requires it at boot (min 16 chars) | -| `HEALTH_DEEP_PROBE_SECRET` (prod + GitHub) | ⚠️ partial | Railway runtime + GitHub repo secret | Railway production was set and the authorized deep probe returned healthy on 2026-07-19. GitHub remains pending: set the same value as a repo secret, set `PROD_HEALTH_URL`, then enable the ops-digest schedule. Cloud Agent re-probe 2026-07-26 blocked (GitHub secrets/vars 403; no Railway CLI/token; Railway MCP unavailable). | -| `SUPABASE_SERVICE_ROLE_KEY` (per env) | ⏳ pending | Railway runtime secret | accepts the `sb_secret_…` key | -| `OPENAI_API_KEY` (per env) | ⏳ pending | Railway runtime secret | `RAG_PROVIDER_MODE=auto` | -| OpenAI DPA / ZDR execution | ⏳ pending | OpenAI account + legal | app endpoints are ZDR-eligible; execution is operator + legal β€” see [openai-cross-border-basis.md](openai-cross-border-basis.md) | +| Secret / config | Status | Where | Notes | +| ------------------------------------------ | ---------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Local fillable HMAC/probe secrets | βœ… present | primary `.env.local` (gitignored) | Correct production project identity was verified and three distinct local-only values were filled on 2026-07-27. `check:local-presence -- --root ` supports safe cross-worktree checks; values were not printed or committed. | +| `RAG_QUERY_HASH_SECRET` (prod) | βœ… present | Railway runtime + GitHub repo secret | Names-only presence passed in both stores on 2026-07-27. GitHub does not expose the value, so equality cannot be proven read-only; do not rotate solely for comparison because that would break query-pseudonym continuity. | +| `HEALTH_DEEP_PROBE_SECRET` (prod + GitHub) | βœ… present | Railway runtime + GitHub repo secret | Names-only presence and the `PROD_HEALTH_URL` repository variable passed on 2026-07-27. The Ops Digest workflow is active and its 2026-07-26 scheduled run succeeded. The last separately authorized deep probe was healthy on 2026-07-19. | +| `OPENAI_SAFETY_IDENTIFIER_SECRET` (prod) | βœ… active | Railway app runtime secret | A distinct 256-bit production value was stored without an immediate deploy on 2026-07-25; the app's later successful 2026-07-26 `main` deployment proves that stored configuration has entered the active deployment generation. | +| `SUPABASE_SERVICE_ROLE_KEY` (production) | βœ… present | Railway app + worker; GitHub secret | Names-only presence passed for the production app, worker, and repository on 2026-07-27. Values were neither emitted nor compared. Staging remains separately tracked by #056. | +| `OPENAI_API_KEY` (production) | βœ… present | Railway app + worker; GitHub secret | Names-only presence passed for the production app, worker, and repository on 2026-07-27. `RAG_PROVIDER_MODE=auto`; no OpenAI request was dispatched for this configuration check. Staging remains separately tracked by #056. | +| Supabase schedules / Vault names | βœ… present | `Clinical KB Database` | Read-only 2026-07-27 proof found active retention/performance/auto-toggle schedules and the `cron_ingestion_jwt` / `indexing_v3_agent_secret` Vault names. The deliberately absent document-change webhook activation secret remains tracked by #025. | +| OpenAI DPA / ZDR execution | ⏳ pending | OpenAI account + legal | App endpoints are ZDR-eligible; execution is operator + legal β€” see [openai-cross-border-basis.md](openai-cross-border-basis.md). This legal/provider decision remains #053 and is not implied by config presence. | ## Disaster-recovery re-creation (does NOT survive a schema restore) diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index a7b8bb02fa..26b2b7549b 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -48,36 +48,35 @@ removed after current-main verification; it is not missing recommended work. database/RAG/clinical/privacy expertise; Operator = named provider/product/legal authority. - **Estimate:** focused active time, excluding approval, hosted runtime, soak, and review waits. -| Order | ID(s) | Acuity | Capability | When | Estimate | Outcome, gate, verification, and stopping condition | -| ----: | ---------------------- | -------- | ------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 | `#059` | A1 | Operator security + independent reviewer | Immediate approved security window | 1–3 hours plus verification | Verify every reported exposed credential (GitHub, OpenAI, Supabase service role/database, E2E) is retired; rotate anything still valid and update only intended secret stores. Never record values; stop before provider action without approval. | -| 2 | `#053` | A1 | Operator β€” legal/privacy | Start now; finish before real patient use/privacy-approved release | 4–8 hours internal; 1–6 weeks elapsed | Execute DPAs; decide ZDR/residency; obtain cache behavior in writing; review subprocessors; obtain APP 8 and APP 5/1 counsel sign-off. Do not change public copy before approval. | -| 3 | `#069` | A3 | Specialist β€” retrieval latency | After hosted apply of PR #1133 migrations; approval-gated live profile | 30–60 min | Operator applies `20260724120000`/`20260724130000`/`20260724130100`, then re-profiles `match_document_table_facts_text` (~70ms-class plans). Stop without mutating ranking or unpaid evals. Cloud agent blocked: no DB URL / MCP auth; live profile hit Unregistered API key. | -| 4 | `#019` | A2 | Specialist β€” RAG answer pipeline | Local reproducer now; behavior change after `#051`/`#023` | 0.5–1 day reproducer | Reproduce admission-source loss in the fallback layer using PR #1096’s source shape. Any behavior change needs protected review and an approved baseline/post canary; stop if independently non-reproducible. | -| 5 | `#054` | A2 | Operator hosted | After local presence tool; next approved hosted secret window | 1–2 hours hosted | Local/dev fillable gaps are handled by `npm run check:local-presence` (optionally `--fill`). Hosted remaining: Railway/GitHub query-hash, deep-probe, service-role, OpenAI, and schedule settings with distinct per-environment values. Never record values; stop on ambiguous identity. | -| 6 | `#022` | A2 | Operator β€” clinical governance + Specialist | Decision-ready | 1–2 hours policy; 0.5–1 day first ten | Decide BMJ attestation policy and review the ten highest-impact local documents. Record reviewer/evidence/time; stop after ten and remeasure warning debt. | -| 7 | `#051`, `#023` | A2 | Specialist β€” RAG diagnostics | After scheduled 2026-07-26 run | 2–4 hours | With GitHub-read approval, compare structured canary/browser/irrelevant-at-10 artifacts without dispatching a rerun. Record deterministic/provider/latency deltas and disposition residuals; stop without spending. | -| 8 | `#018` | A2 | Specialist β€” clinical RAG/retrieval | After `#051`/`#023`, one mechanism at a time | 1–2 days diagnosis | Give lithium, ADHD, and metabolic residuals separate current-main reproducers and candidates. Behavior canaries require approval; stop any item without a deterministic reproducer or on regression. | -| 9 | `#029` | A2 | Specialist β€” answer quality/clinical safety | After `#051`/`#023` and `#018` | 0.5–1 day inventory; 1–3 days per fix | Re-enumerate current fallback stubs and fix one causal cluster at a time without weakening grounding/citation gates. Stop if a change merely makes the metric easier to pass. | -| 10 | `#001` | A2 | Specialist β€” retrieval/ranking | After `#051`/`#023` and rollout approval | 0.5–1 day plus canary | Keep semantic reranking off unless an approved ambiguity comparison preserves 36/36, recall 1.0, zero per-case regressions, and shows measured gain; otherwise record keep-off and stop. | -| 11 | `#025` | A2 | Operator β€” Railway/GitHub/chat/Supabase | Next approved observability window | 1–3 hours/channel | Choose owned deployment, CI, ingestion, and SLO alerts; mock first, then one approved controlled provider event/channel. The merged Supabase trigger remains inert until its verified inputs are configured. Stop without an accountable responder. | -| 12 | `#055` | A2 | Specialist release owner + Operator | Before next full-confidence release/handoff | 2–4 hours plus runtime | On one exact SHA, run local/provider gates, Firefox/WebKit, required hosted CI, and close actionable GitHub threads. Stop at first failure and rerun only the repaired smallest gate. | -| 13 | `#056` | A2 | Operator β€” Supabase/Railway + Specialist | After cost/ownership approval | 0.5–1 day | Provision isolated `Clinical KB Staging` with synthetic data and distinct secrets. Verify identity, schema, indexing, health, and data boundary; never copy production clinical documents. | -| 14 | `#057` | A2 | High β€” release/SRE + Operator | After `#056` | 2–4 hours plus soak | Run documented staging soak and rollback against an exact candidate. Retain latency/error/rollback evidence; stop on unsafe data, identity mismatch, or unowned rollback. | -| 15 | `#058` | A2 | Operator β€” production data + Specialist | Next approved production verification window | 30–60 min read-only; 1–2 hours if needed | Verify registry/differentials/medications are non-empty before writing; seed only confirmed gaps idempotently. Stop when healthy or owner/project identity is ambiguous. | -| 16 | `#011` | A3 | Operator β€” Supabase capacity | Immediately before first compute scale-up | 30–60 min plus observation | Switch Auth to percentage allocation, record before/after, and run approved advisor/health checks. Stop if no scale-up is planned. | -| 17 | `#017` | A3 | High β€” performance/browser | Before `#012`/`#013`/`#016`; approved live-site window | 1–2 hours | Capture reproducible mobile/desktop Lighthouse/Web-Vitals evidence and decide whether payload work is justified. Stop if metrics are acceptable or evidence is too noisy. | -| 18 | `#033` | A3 | Specialist β€” prompt/source governance | After `#022` and `#051`/`#023` | 1–2 days plus approved eval | Design unknown-vs-adverse metadata wording and prompt tests. Require no supported-grounding drop and zero citation failures; stop on broad over-caveating or degradation. | -| 19 | `#037` | A3 | Operator β€” clinical/product + Standard | Next trust-policy review | 30–60 min; up to 0.5 day | Decide whether routine claims cap at medium trust. Record policy; if accepted, change only the flag/expectations and run focused tests. | -| 20 | `#012`, `#013`, `#016` | A3 | High β€” bundling/runtime performance | After `#017` or equivalent evidence | 0.5–2 days/route | Optimize only a production route with measured payload/render/motion harm. Require material gain plus focused, `verify:cheap`, and browser evidence; stop on small gain. | -| 21 | `#035` | A3 | Specialist β€” evidence rules | After a demonstrated missed conflict | 0.5–1 day design; code separate | Define a clinically reviewed conflict class with positive and negative fixtures. Stop if no bounded class can be shown; behavior change requires protected review. | -| 22 | `#027` | Optional | Operator β€” SRE/provider | When an owned external alert path is wanted | 1–2 hours | Decide vendor/cost/privacy/owner; if accepted, prove one non-PHI outage and recovery alert. Stop when no responder owns it. | -| 23 | `#028` | Optional | Specialist privacy/observability + Operator | After privacy/ownership/cost approval | 1–3 days | Define vendor/region/retention/redaction/sampling/source-map envelope before SDK work. Prove no clinical text, identifiers, or secrets leave; stop if unacceptable. | -| 24 | `#038` | Optional | High β€” product/design architecture | When a new comparison surface is approved | 0.5–1 day | Define a shared interaction contract without flattening mode-specific content. Stop when no concrete new surface exists. | -| 25 | `#040` | Optional | High β€” visual QA/accessibility | When baseline owner/update workflow exists | 1–2 days | Establish a small stable desktop/mobile/accessibility baseline set. Do not make it blocking if flake or maintenance cost outweighs detection value. | -| 26 | `#039` | Optional | High β€” frontend architecture | During a concrete catalogue-toolbar project | 0.5–1 day inventory; 1–3 days code | Converge only repeated toolbar behavior without flattening search semantics. Stop when there is no bounded implementation target. | -| 27 | `#065` | A2 | High β€” document-viewer UI | Only when the user explicitly resumes the paused task | 0.5–1.5 days | Finish the compact source-text accordion, citation/search auto-open, print restoration, and 320/390/1280 px coverage. Keep the preserved branch untouched until explicit resume; no provider calls. | -| 28 | `#079` | Optional | High β€” repository hygiene | In explicitly scheduled batches | 30–60 minutes per batch | Disposition at most ten retained worktrees per pass using owner, PR, review-ledger, ancestry, and patch evidence. Preserve every dirty, active, secret-bearing, post-freeze, or ambiguous worktree and stop rather than broad-cleaning. | +| Order | ID(s) | Acuity | Capability | When | Estimate | Outcome, gate, verification, and stopping condition | +| ----: | ---------------------- | -------- | ------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | `#059` | A1 | Operator security + independent reviewer | Immediate approved security window | 1–3 hours plus verification | Verify every reported exposed credential (GitHub, OpenAI, Supabase service role/database, E2E) is retired; rotate anything still valid and update only intended secret stores. Never record values; stop before provider action without approval. | +| 2 | `#053` | A1 | Operator β€” legal/privacy | Start now; finish before real patient use/privacy-approved release | 4–8 hours internal; 1–6 weeks elapsed | Execute DPAs; decide ZDR/residency; obtain cache behavior in writing; review subprocessors; obtain APP 8 and APP 5/1 counsel sign-off. Do not change public copy before approval. | +| 3 | `#069` | A3 | Specialist β€” retrieval latency | After hosted apply of PR #1133 migrations; approval-gated live profile | 30–60 min | Operator applies `20260724120000`/`20260724130000`/`20260724130100`, then re-profiles `match_document_table_facts_text` (~70ms-class plans). Stop without mutating ranking or unpaid evals. Cloud agent blocked: no DB URL / MCP auth; live profile hit Unregistered API key. | +| 4 | `#019` | A2 | Specialist β€” RAG answer pipeline | Local reproducer now; behavior change after `#051`/`#023` | 0.5–1 day reproducer | Reproduce admission-source loss in the fallback layer using PR #1096’s source shape. Any behavior change needs protected review and an approved baseline/post canary; stop if independently non-reproducible. | +| 6 | `#022` | A2 | Operator β€” clinical governance + Specialist | Decision-ready | 1–2 hours policy; 0.5–1 day first ten | Decide BMJ attestation policy and review the ten highest-impact local documents. Record reviewer/evidence/time; stop after ten and remeasure warning debt. | +| 7 | `#051`, `#023` | A2 | Specialist β€” RAG diagnostics | After scheduled 2026-07-26 run | 2–4 hours | With GitHub-read approval, compare structured canary/browser/irrelevant-at-10 artifacts without dispatching a rerun. Record deterministic/provider/latency deltas and disposition residuals; stop without spending. | +| 8 | `#018` | A2 | Specialist β€” clinical RAG/retrieval | After `#051`/`#023`, one mechanism at a time | 1–2 days diagnosis | Give lithium, ADHD, and metabolic residuals separate current-main reproducers and candidates. Behavior canaries require approval; stop any item without a deterministic reproducer or on regression. | +| 9 | `#029` | A2 | Specialist β€” answer quality/clinical safety | After `#051`/`#023` and `#018` | 0.5–1 day inventory; 1–3 days per fix | Re-enumerate current fallback stubs and fix one causal cluster at a time without weakening grounding/citation gates. Stop if a change merely makes the metric easier to pass. | +| 10 | `#001` | A2 | Specialist β€” retrieval/ranking | After `#051`/`#023` and rollout approval | 0.5–1 day plus canary | Keep semantic reranking off unless an approved ambiguity comparison preserves 36/36, recall 1.0, zero per-case regressions, and shows measured gain; otherwise record keep-off and stop. | +| 11 | `#025` | A2 | Operator β€” Railway/GitHub/chat/Supabase | Next approved observability window | 1–3 hours/channel | Choose owned deployment, CI, ingestion, and SLO alerts; mock first, then one approved controlled provider event/channel. The merged Supabase trigger remains inert until its verified inputs are configured. Stop without an accountable responder. | +| 12 | `#055` | A2 | Specialist release owner + Operator | Before next full-confidence release/handoff | 2–4 hours plus runtime | On one exact SHA, run local/provider gates, Firefox/WebKit, required hosted CI, and close actionable GitHub threads. Stop at first failure and rerun only the repaired smallest gate. | +| 13 | `#056` | A2 | Operator β€” Supabase/Railway + Specialist | After cost/ownership approval | 0.5–1 day | Provision isolated `Clinical KB Staging` with synthetic data and distinct secrets. Verify identity, schema, indexing, health, and data boundary; never copy production clinical documents. | +| 14 | `#057` | A2 | High β€” release/SRE + Operator | After `#056` | 2–4 hours plus soak | Run documented staging soak and rollback against an exact candidate. Retain latency/error/rollback evidence; stop on unsafe data, identity mismatch, or unowned rollback. | +| 15 | `#058` | A2 | Operator β€” production data + Specialist | Next approved production verification window | 30–60 min read-only; 1–2 hours if needed | Verify registry/differentials/medications are non-empty before writing; seed only confirmed gaps idempotently. Stop when healthy or owner/project identity is ambiguous. | +| 16 | `#011` | A3 | Operator β€” Supabase capacity | Immediately before first compute scale-up | 30–60 min plus observation | Switch Auth to percentage allocation, record before/after, and run approved advisor/health checks. Stop if no scale-up is planned. | +| 17 | `#017` | A3 | High β€” performance/browser | Before `#012`/`#013`/`#016`; approved live-site window | 1–2 hours | Capture reproducible mobile/desktop Lighthouse/Web-Vitals evidence and decide whether payload work is justified. Stop if metrics are acceptable or evidence is too noisy. | +| 18 | `#033` | A3 | Specialist β€” prompt/source governance | After `#022` and `#051`/`#023` | 1–2 days plus approved eval | Design unknown-vs-adverse metadata wording and prompt tests. Require no supported-grounding drop and zero citation failures; stop on broad over-caveating or degradation. | +| 19 | `#037` | A3 | Operator β€” clinical/product + Standard | Next trust-policy review | 30–60 min; up to 0.5 day | Decide whether routine claims cap at medium trust. Record policy; if accepted, change only the flag/expectations and run focused tests. | +| 20 | `#012`, `#013`, `#016` | A3 | High β€” bundling/runtime performance | After `#017` or equivalent evidence | 0.5–2 days/route | Optimize only a production route with measured payload/render/motion harm. Require material gain plus focused, `verify:cheap`, and browser evidence; stop on small gain. | +| 21 | `#035` | A3 | Specialist β€” evidence rules | After a demonstrated missed conflict | 0.5–1 day design; code separate | Define a clinically reviewed conflict class with positive and negative fixtures. Stop if no bounded class can be shown; behavior change requires protected review. | +| 22 | `#027` | Optional | Operator β€” SRE/provider | When an owned external alert path is wanted | 1–2 hours | Decide vendor/cost/privacy/owner; if accepted, prove one non-PHI outage and recovery alert. Stop when no responder owns it. | +| 23 | `#028` | Optional | Specialist privacy/observability + Operator | After privacy/ownership/cost approval | 1–3 days | Define vendor/region/retention/redaction/sampling/source-map envelope before SDK work. Prove no clinical text, identifiers, or secrets leave; stop if unacceptable. | +| 24 | `#038` | Optional | High β€” product/design architecture | When a new comparison surface is approved | 0.5–1 day | Define a shared interaction contract without flattening mode-specific content. Stop when no concrete new surface exists. | +| 25 | `#040` | Optional | High β€” visual QA/accessibility | When baseline owner/update workflow exists | 1–2 days | Establish a small stable desktop/mobile/accessibility baseline set. Do not make it blocking if flake or maintenance cost outweighs detection value. | +| 26 | `#039` | Optional | High β€” frontend architecture | During a concrete catalogue-toolbar project | 0.5–1 day inventory; 1–3 days code | Converge only repeated toolbar behavior without flattening search semantics. Stop when there is no bounded implementation target. | +| 27 | `#065` | A2 | High β€” document-viewer UI | Only when the user explicitly resumes the paused task | 0.5–1.5 days | Finish the compact source-text accordion, citation/search auto-open, print restoration, and 320/390/1280 px coverage. Keep the preserved branch untouched until explicit resume; no provider calls. | +| 28 | `#079` | Optional | High β€” repository hygiene | In explicitly scheduled batches | 30–60 minutes per batch | Disposition at most ten retained worktrees per pass using owner, PR, review-ledger, ancestry, and patch evidence. Preserve every dirty, active, secret-bearing, post-freeze, or ambiguous worktree and stop rather than broad-cleaning. | @@ -89,94 +88,94 @@ removed after current-main verification; it is not missing recommended work. > > **RAG reconciliation correction (2026-07-23):** fresh current-main live evidence supersedes the broad diagnosis in #018. The three named misses are not one composer defect. Lithium reproduced an unrelated-table retrieval fast-path defect; ADHD still retrieves a relevant chart-heavy CAMHS source but exhausts the extractive route budget; metabolic retrieves the correct AKG source but selects schedule-free prose; #019 remains post-retrieval comparison source selection. A narrow lithium subject-evidence guard improved its targeting result from 0 to 1 with golden recall 1.0 and no reciprocal-rank regressions, but was reverted and rejected because the required full canary failed. Keep #029 open for the remaining fallback-stub cases. Do not combine these residuals or change ranking scores, comparator ordering, aliases, clamps, or semantic reranking without a separate reproducer and passing canary pair. -| ID | Pri | Type | Summary | Detail / next action | Source | Added | -| ---- | --- | ----- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| #059 | P1 | task | Verify containment of every credential reported exposed in chat | **Outcome:** every reported exposed credential is rejected or retired. **Next:** in approved security windows, verify and revoke or rotate the GitHub token, OpenAI key, Supabase service-role JWT, database password, and E2E credential; create replacements only when required and update only intended secret stores. **Success:** provider evidence confirms the old credentials cannot authenticate, replacements are distinct and minimally scoped, presence/readiness checks pass, and secret scans remain clean. **Stop:** no provider or secret-store action without approval; never print or paste values into Git, logs, issues, or chat. | session 2026-07-24 security reconciliation; AI Agent Target Manifest | 2026-07-24 | -| #065 | P2 | task | Complete the paused compact document source-text accordion | **Outcome:** the document viewer uses compact nested disclosures while retaining complete text, citation/search navigation, print behavior, and composer clearance. **Next:** only when the user explicitly resumes, reconcile `codex/chat-document-text-accordion-7cb4` with current `main` and complete the focused 320/390/1280 px tests. **Success:** default disclosures are closed; deep links and search open only the active passage; printing expands/restores state; no overflow. **Verify:** focused document-viewer Playwright, `verify:cheap`, `verify:ui`, and static production-readiness. **Stop:** remain paused until explicit user return; no provider calls. | paused document-viewer task; `codex/chat-document-text-accordion-7cb4` | 2026-07-24 | -| #051 | P2 | task | Stabilise the live answer-quality canary before more RAG tuning | Diagnostics landed in PR #1095: structured JSON/Markdown artifacts now record the actual checked-out SHA, run identity and latency context, and the offline trend tool separates content, provider-route and latency outcomes. First validating run `30018289898` recorded the expected tree and cost, with 36/36 retrieval green, but one report cannot establish variability; PR #1097 prevents a single failure being mislabeled as repeated. Next: compare the scheduled 2026-07-26 structured report with this run. Do not spend on an immediate retry or reapply the archived lithium guard before that comparison. | PR #1095; run `30018289898`; PR #1097; archive ref `refs/archive/rejected-rag/20260723/monitoring-subject-gate` | 2026-07-23 | -| #001 | P2 | task | Semantic reranking still gated off | `RAG_SEMANTIC_RERANK_ENABLED=false` from PR #901. Do not enable until the provider-backed 36/36 retrieval-quality gate **and** an ambiguity-focused canary are explicitly approved and recorded. | `docs/process-hardening.md` (Semantic reranking rollout debt); PR #901 | 2026-07-21 | -| #069 | P3 | task | Live-profile table-facts plpgsql+EXECUTE latency | Migration `20260724120000_table_facts_plpgsql_execute.sql` plus P3 follow-ups (`20260724130000_*`, `20260724130100_*`) are in PR #1133. Hosted apply is blocked in this environment (no `SUPABASE_DB_URL`; Supabase MCP `needsAuth`). A live `profile:retrieval --rpc match_document_table_facts_text --analyze` attempt returned `Unregistered API key` against the injected service-role secret. **Next:** operator applies the three pending migrations on the live target project, then re-runs approval-gated `profile:retrieval` / `explain_retrieval_rpc` and confirms ~70ms-class plans with no ranking change. **Stop** without mutating ranking. | session 2026-07-24 Database interface audit; PR #1133 | 2026-07-24 | -| #053 | P1 | task | Execute cross-border privacy/legal package | Execute OpenAI and Railway DPAs; decide ZDR and Australian data residency; obtain prompt-cache behavior in writing; review subprocessors; obtain APP 8 and APP 5/1 counsel sign-off. Do not represent the release as privacy-approved or alter final public privacy wording before sign-off. | `docs/openai-cross-border-basis.md`; `docs/privacy-impact-assessment.md` | 2026-07-24 | -| #054 | P2 | task | Reconcile local and hosted secrets/config | **Local (2026-07-24):** `npm run check:local-presence` (+ optional `--fill`) presence-checks safety-identifier / query-hash / deep-probe (names + length buckets only), fails closed on ambiguous project identity, and fills only those three local HMAC/probe gaps into gitignored `.env.local`. Production-readiness now warns on the same local gaps. Provider keys and hosted Railway/GitHub stores were not touched. **Hosted probe (2026-07-26):** blocked in Cloud Agent β€” GitHub Actions secrets/variables API returns 403 (`Resource not accessible by integration`); Railway CLI/`RAILWAY_API_TOKEN` absent; Railway MCP discovery failed. No hosted secret values were read or written. **Next (operator):** from an account with repo-secret + Railway project access, presence-check names only then set confirmed gaps with distinct per-environment values: `RAG_QUERY_HASH_SECRET` (Railway must match GitHub if already set), `HEALTH_DEEP_PROBE_SECRET` + `PROD_HEALTH_URL` (enable/verify ops-digest), `SUPABASE_SERVICE_ROLE_KEY`, `OPENAI_API_KEY`, optional `OPENAI_SAFETY_IDENTIFIER_SECRET`, and schedule/Vault settings. Never record secret values. Stop on ambiguous project identity. | `.env.example`; `scripts/check-local-presence.mjs`; production-readiness; `docs/operator-backlog.md` | 2026-07-24 | -| #055 | P2 | task | Run one exact-SHA full release and PR gate | Before the next full-confidence release/handoff, record the candidate/PR SHA and run the local/provider release gates, Firefox/WebKit, required hosted CI, and actionable GitHub review-thread closure once. Stop at the first actionable failure and rerun only the repaired smallest gate. | `docs/launch-operator-runbook.md`; `docs/codex-review-protocol.md` | 2026-07-24 | -| #056 | P2 | task | Provision isolated staging environment | After explicit cost/ownership approval, provision `Clinical KB Staging` Supabase and Railway tiers with distinct secrets and synthetic/non-clinical data. Verify identity, schema, indexing, health, and the production-data boundary. | `docs/staging-setup.md`; `docs/operator-backlog.md` | 2026-07-24 | -| #057 | P2 | task | Complete staging soak and rollback rehearsal | After #056, run the documented soak and rollback against an exact candidate; retain latency/error/rollback evidence. Stop on unsafe data, identity mismatch, or an unowned rollback decision. | `docs/launch-operator-runbook.md`; `docs/capacity-review.md` | 2026-07-24 | -| #058 | P2 | task | Verify production content before any seed write | Against `the live target project`, verify registry, differentials, and medications surfaces are non-empty before writing. Seed only confirmed gaps idempotently with approved owner/project identity and confirmation flags. | `docs/launch-operator-runbook.md`; `docs/operator-backlog.md` | 2026-07-24 | -| #005 | P3 | rec | `finalScore` saturates at clamp ceiling | Base + ~40 stacked boosts routinely exceed 1.0, so strong matches tie at 1.0 and order by an arbitrary `document_id` tiebreak. If ranking is ever revisited, break ties by the **pre-clamp** score rather than raising the `[0,1]` ceiling (downstream gates assume `[0,1]`). Ordering already sorts by the unbounded pre-clamp `rankScore` (`clinical-search.ts:1735,1927,1950-1955`), so the clamp confines only the reported confidence value, not result order. Not a defect on the current golden set; any change here is a protected RAG surface (canary required). | `docs/rag-hybrid-findings-and-todo.md` P1 item 4; `src/lib/clinical-search.ts:1735` | 2026-07-21 | -| #011 | P3 | task | Auth DB-connection allocation is operator-only | Supabase Auth (GoTrue) is capped at ~10 absolute DB connections (Supabase perf advisor). Switch to **percentage-based** allocation in the Supabase **dashboard** before the first compute scale-up β€” **not settable via SQL/MCP** (operator-owned). Verify via a staging soak + an approval-gated read-only advisor re-check. | `docs/auth-connection-cap-runbook.md`; `docs/process-hardening.md` (Known follow-up debts) | 2026-07-21 | -| #012 | P3 | rec | Slim the lazy cross-mode differentials chunk | `cross-mode-differentials.ts` is dynamically imported (correctly code-split **out** of the initial/dashboard bundle β€” verified), but it pulls the full ~860 KB differentials snapshot (~125 KB gzip lazy chunk) just to build a tiny `{slug,title,clinicalHinge}` + presentations + aliases catalog. A precomputed lightweight index (generator + drift check, like the `specifiers-content` split / medications `fields=index`) would cut that lazy chunk ~5–10β”œΓΉ. Not a bundle leak β€” an M-effort slim. | `src/lib/cross-mode-differentials.ts`; `src/components/clinical-dashboard/cross-mode-links.tsx:150`; session 2026-07-21 (build:analyze) | 2026-07-21 | -| #013 | P3 | rec | Route-chunk + mockup catalogue JSON weight | `build:analyze`: `/specifiers` ships `specifiers-search-index.json` (~180 KB parsed), `/forms` ships `forms-catalog.json` (~132 KB), `/formulation` ships `formulation-content.json` (~52 KB, client-side local search β€” needs index/full split or a search endpoint, architectural). All route-scoped (not initial bundle). Also `*-mockups.tsx` (~100 KB across chunks) build though `/mockups` 404s in prod β€” exclude from the prod artifact. | session 2026-07-21 (build:analyze) | 2026-07-21 | -| #016 | P3 | rec | "Big but not easy" structural + motion perf | Deferred larger levers: (a) nonce-CSP forces every product route to `β•žΓ† Dynamic` (zero static generation) β€” evaluate Partial Prerendering / static shells for the static clinical catalogues (DSM/differentials/therapy/specifiers/formulation); (b) sidebar expand/collapse animates `grid-template-columns` (biggest smoothness cost, motion-gated β€” needs a transform-overlay rethink); (c) Therapy Compass fetches 692 KB / 2.5 MB JSON client-side (defer until interaction + confirm brotli); (d) settings/setup/admin dialogs static-imported into the home chunk (`next/dynamic` them). | session 2026-07-21 (build route table + design audit) | 2026-07-21 | -| #017 | P3 | task | Field Web-Vitals baseline via live Lighthouse | In-sandbox runtime vitals were blocked (prod server hard-requires Supabase secrets; dev-mode CLS measured excellent at 0.00–0.04, content-first pages 0.000). Run Lighthouse against `psychiatry.tools` for real LCP/INP/CLS to prioritize #012–#016 by measured impact rather than reasoning. | session 2026-07-21 (measurement pass) | 2026-07-21 | -| #018 | P2 | task | Split the lithium, ADHD and metabolic residuals by mechanism | Revalidated on current main 2026-07-23: these are not one composer defect. Lithium reproduced an unrelated-table retrieval fast-path defect; ADHD retrieves a relevant chart-heavy CAMHS source but exhausts the extractive route budget; metabolic retrieves the correct AKG source but selects schedule-free prose. The narrow lithium subject-evidence guard improved targeting from 0 to 1 with golden recall 1.0 and no reciprocal-rank regressions, but it was reverted because the full canary failed. After #051 stabilises the canary, add independent current-main reproducers and assess each mechanism separately. Do not widen the matcher or combine these into a broad ranking/composer change. | runs `30007833352` and `30009207429`; PR #1093; session 2026-07-23 | 2026-07-21 | -| #019 | P2 | task | Admission doc dropped after deterministic comparison packing | Reconfirmed on merged-main run `30018289898`: golden retrieval remained 36/36 and retrieved `MHSP.AdmissionCommunityPts.pdf`, but the answer's top five sources retained only `MHSP.Discharge.pdf` after `generation_fallback:generation_quality_failed; comparison_source_extractive_fallback`. PR #1096 replays the live score/order shape and proves deterministic answer ranking plus cross-document packing retain both admission and discharge evidence, so retrieval scores, aliases and comparator ordering are not the fix. Next: create a red fallback-layer unit reproducer using the live source shape; any behavior change still needs the existing baseline and a passing post canary. | run `30018289898`; PR #1096; session 2026-07-23 | 2026-07-21 | -| #021 | P3 | rec | E-3d H2 residual: strong/comparison generation discards | approx. 6 generation attempts per full 44-case run still fail the final quality gate and fall to extractive on strong-route comparison/complex shapes (the designed-conservative outcome). PARKED: weakest cost/benefit on the queue β€” a wave (approx. $2-4 pair + reviewer cycle) to shave seconds off a few hard cases. Revisit only if latency/waste complaints or a cheaper lever appears. | E-3c design record; runs #59-#61 diagnostics | 2026-07-21 | -| #022 | P2 | task | Source-governance metadata refresh (operator) | **Worklist generated 2026-07-22 ($0, read-only): `docs/source-governance-refresh-worklist-2026-07-22.md`.** Reframed - this is NOT 59 clinical reviews. Of the 124 documents surfacing in canary top results, 59 are review-required, and **38 (64 pct) are the BMJ published-reference tier all sitting at `clinical_validation_status: unverified`** - one attestation-policy decision, not 38 reviews. The remaining 21 are genuine local WA health-service reviews (FSH 7, NMHS 4, CAMHS 3, AKG 2, KEMH 2, RPBG 2, RKPG 1), mostly `document_status: review_due`. Burn-down: top-10 documents clear 44 pct of flagged slots, top-20 clear 66 pct. Next: decide the BMJ attestation policy, then attest local docs by visibility (start `Clozapine Management by GP (NMHS)`, 22 slots at rank 1). | runs #61/#57 Source Governance data; `docs/source-governance-refresh-worklist-2026-07-22.md` | 2026-07-21 | -| #023 | P2 | task | Read Sunday 2026-07-26 scheduled-run artifacts | The 18:00 UTC scheduled runs deliver three free datapoints at once: first full-44 weekly canary (validates the #1044 ANSWER_CASE_LIMIT raise), browser-matrix flake second datapoint (webkit ui-route-coverage now reproduced + root-caused 2026-07-22 Ξ“Γ₯Γ† see #024; firefox ui-formulation:91 still awaits a datapoint), and the irrelevant@10 labeling-audit artifact (§3.1 human-decision class). Read all three, then disposition. | sessions 2026-07-20/21; branch-review-ledger convergence notes | 2026-07-21 | -| #025 | P2 | task | Activate the three webhooks (operator secrets) | Merged (#968/#1100) + deployed but inert β€” verified live: `POST /api/webhooks/railway` returns `503 webhook_not_configured`; the Supabase document-change trigger exists but lacks both activation inputs. To turn on: (1) Railway Ξ“Γ₯Γ† set `RAILWAY_WEBHOOK_SECRET` + add the `?token=…` webhook URL; (2) set `SLACK_WEBHOOK_URL`/`DISCORD_WEBHOOK_URL` in BOTH the Railway **app/server env** and **GitHub repo secrets**; (3) set one matching document-change secret in the Railway app env as `SUPABASE_INGESTION_WEBHOOK_SECRET` and in Supabase Vault as `ingestion_webhook_secret`, then set the per-environment database GUC `app.ingestion_webhook_base_url` to the deployed app origin. Each path fails closed until fully configured, so this is pure ops. See `docs/webhooks.md` for verification and rotation. | sessions 2026-07-22/24; PRs #968/#1100; docs/webhooks.md | 2026-07-22 | -| #027 | P3 | rec | External uptime monitor independent of GitHub/Railway | `live-domain-monitor.yml` runs on GitHub's cron, so it won't run in exactly the outage it should catch (Actions or the deploy itself down). Add an off-platform synthetic monitor (UptimeRobot / Better Stack / Checkly) hitting `/api/health` with a webhook alert. Provider setup, not code. | session 2026-07-22 webhook review | 2026-07-22 | -| #028 | P3 | rec | Runtime error tracking (Sentry or similar) | No error tracking in the repo β€” production exceptions on `psychiatry.tools`, including how often `RAG_PROVIDER_MODE=auto` silently degrades to source-only, are invisible. Weigh adding `@sentry/nextjs` (dependency + DSN secret + instrumentation) vs cost; alert Ξ“Γ₯Γ† chat/issue. Provider-backed; needs explicit sign-off before adding the dependency. | session 2026-07-22 webhook review | 2026-07-22 | -| #029 | P2 | issue | 12 of 30 answer-quality cases return the fallback stub | run #61 --dump-answers: 12/30 quality cases emit the source_backed_review_fallback boilerplate with answer_sections: [], all grounded with 4-6 citations. Some still PASS targeting because the stub echoes query keywords (the contraindication/document_lookup matchers need only a keyword), so the targeting metric MASKS the problem for those intents. Superset of #018 β€” fix in the extractive composer, validate with the provider-backed answer eval. | run #61 dump artifact; session 2026-07-22 | 2026-07-22 | -| #033 | P3 | rec | Source governance metadata absent from the LLM prompt | `buildRagSourceBlock` omits `document_status`, `clinical_validation_status`, and `extraction_quality`, so the model cannot self-caveat during generation and governance is enforced only post-hoc. Generation-surface change: needs `eval:rag` plus `eval:quality --rag-only` (grounded-supported must not drop, citation-failure 0) and explicit approval. Carries the same "unknown ≥ bad" hazard as #032 β€” on a partially-enriched corpus the model would likely over-caveat correct sources, so design the prompt wording before spending an eval. | `src/lib/rag/rag-source-block.ts:126-198`; PR #1051 audit item 8 | 2026-07-22 | -| #035 | P3 | rec | Threshold-conflict detection covers only 3 params | `detectThresholdDisagreements` checks only ANC, WBC, and platelets paired with withholding verbs, so cross-source conflicts on medication doses, lithium/thyroid levels, or vital signs go undetected. Deliberately narrow (see the comment at `:469-474`). Broadening changes when an answer is classified `conflicting` and adds warnings β€” real false-positive risk. Needs new fixtures plus a behaviour review before any change. | `src/lib/evidence.ts:469-574`; PR #1051 audit item 7 | 2026-07-22 | -| #036 | P3 | rec | No explicit `is_public` visibility flag on documents | Public-corpus visibility is implicit: `owner_id IS NULL` on an `indexed` document (`resolveSearchScope`). The `metadata.public_corpus` marker is written by the promotion migrations but never used as a retrieval filter. Promotion is unconditional on `clinical_validation_status`, so unverified documents are publicly searchable β€” compensated by keeping `unverified_source` in the frontend-visible warning set. A hard schema flag touches RLS and the clinical-risk-gated retrieval RPCs; weigh against the existing compensating control before acting. | `supabase/schema.sql:61-108`; `src/lib/search-scope.ts:181-236`; PR #1051 audit item 3 | 2026-07-22 | -| #037 | P3 | rec | D5 trust-cap-all-claims flag parked OFF | `NEXT_PUBLIC_RAG_TRUST_CAP_ALL_CLAIMS` extends authority gating from high-risk claims to **all** supported claims (`deriveTrust`). Ships OFF by design; flipping it caps trust to `medium` for routine claims across the board β€” a product/clinical-UX decision, not a defect. Both states are test-pinned. Next action: product decision, then flip and re-baseline the UI expectations. | `src/lib/answer-render-policy.ts:159-177`; PR #1051 audit item 11 | 2026-07-22 | -| #038 | P3 | rec | Consolidate shared comparison behavior | Several clinical modes expose comparison workflows with similar selection, empty-state and mobile-dock needs. Define one shared behavioral contract before another comparison surface is added; keep mode-specific clinical content separate. This is a design-system recommendation, not a current defect. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -| #039 | P3 | rec | Consolidate catalogue toolbar patterns | Catalogue/search pages have independently evolved filter, sort, result-count and mobile toolbar behavior. Inventory the existing implementations and converge only the repeated interaction contract; do not flatten mode-specific search semantics. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -| #040 | P3 | rec | Add targeted visual-regression baselines | Keep a small approved baseline set for high-value desktop/mobile surfaces and accessibility modes instead of screenshotting every route. Start with account/settings, document viewer, mode homes and bottom-composer interactions; define an intentional-update workflow before enabling blocking comparisons. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | -| #079 | P3 | task | Disposition retained worktrees in bounded cleanup batches | **Outcome:** the retained reconciliation tail is gradually classified without another disruptive all-worktree sweep. **Next:** process no more than ten worktrees per explicitly scheduled pass using current owner/process metadata, open-PR state, exact review-ledger coverage, ancestry, and cherry-pick-aware content proof. **Success:** remove only clean, inactive, bundled worktrees whose content is merged or explicitly rejected; record every disposition and retain recovery evidence. **Stop:** preserve dirty, active, secret-bearing, post-freeze, paused, or ambiguous work and never use reset, force deletion, broad clean, or process killing. | final reconciliation inventory retained 104 independent worktrees; session 2026-07-24 | 2026-07-24 | -| #080 | P3 | rec | Re-test the removed admission-to-discharge alias widening | **Outcome:** decide with measured evidence whether the user-approved 2026-07-21 widening (NMHS admission-to-discharge titles satisfying the admission expectation) belongs back on the wide-tier `AdmissionCommunityPts` list. #030 removed it so one document could not fill both comparison slots, but coverage now enforces distinct document identities by maximum matching, so the widening is no longer a false-pass route and its removal may instead cost legitimate admission credit. **Next:** with approval, run an eval-canary baseline/post pair over the admission-discharge comparison cases with and without those two titles. **Success:** restore only on measured non-regression (recall pinned 1.0, zero per-case reciprocal-rank regressions); otherwise record keep-removed and close. **Stop:** never restore on offline reasoning alone. | `src/lib/eval-document-matching.ts`; #030 archive row; session 2026-07-25 | 2026-07-25 | -| #082 | P2 | issue | Bot branch-sync heads leave required checks unapproved | **Outcome:** an open PR stops stalling when the `github-actions[bot]` sync merge updates its branch. Every bot-authored head produces `action_required` runs for CI, SAST and Secret Scan, so the three required checks never report: PR #1220 was blocked through five successive syncs, and both a normal and an `--admin` merge were refused. Heads pushed by an agent or human still run normally, so the mitigation is the `skip-branch-sync` label plus an own-authored head (cleared PR #1217). **Next:** pick one fix β€” allow the sync bot in the repository Actions approval setting, push the sync with a token whose runs auto-execute, or drop auto-sync. **Success:** a bot-synced PR reaches green required checks with no manual push. **Stop:** never widen approval settings for fork/untrusted actors or clear the block by bypassing required checks. | GitHub #1235; PR #1220/#1217 2026-07-25 (bot heads action_required); session 2026-07-25 | 2026-07-25 | +| ID | Pri | Type | Summary | Detail / next action | Source | Added | +| ---- | --- | ----- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| #059 | P1 | task | Verify containment of every credential reported exposed in chat | **Outcome:** every reported exposed credential is rejected or retired. **Next:** in approved security windows, verify and revoke or rotate the GitHub token, OpenAI key, Supabase service-role JWT, database password, and E2E credential; create replacements only when required and update only intended secret stores. **Success:** provider evidence confirms the old credentials cannot authenticate, replacements are distinct and minimally scoped, presence/readiness checks pass, and secret scans remain clean. **Stop:** no provider or secret-store action without approval; never print or paste values into Git, logs, issues, or chat. | session 2026-07-24 security reconciliation; AI Agent Target Manifest | 2026-07-24 | +| #065 | P2 | task | Complete the paused compact document source-text accordion | **Outcome:** the document viewer uses compact nested disclosures while retaining complete text, citation/search navigation, print behavior, and composer clearance. **Next:** only when the user explicitly resumes, reconcile `codex/chat-document-text-accordion-7cb4` with current `main` and complete the focused 320/390/1280 px tests. **Success:** default disclosures are closed; deep links and search open only the active passage; printing expands/restores state; no overflow. **Verify:** focused document-viewer Playwright, `verify:cheap`, `verify:ui`, and static production-readiness. **Stop:** remain paused until explicit user return; no provider calls. | paused document-viewer task; `codex/chat-document-text-accordion-7cb4` | 2026-07-24 | +| #051 | P2 | task | Stabilise the live answer-quality canary before more RAG tuning | Diagnostics landed in PR #1095: structured JSON/Markdown artifacts now record the actual checked-out SHA, run identity and latency context, and the offline trend tool separates content, provider-route and latency outcomes. First validating run `30018289898` recorded the expected tree and cost, with 36/36 retrieval green, but one report cannot establish variability; PR #1097 prevents a single failure being mislabeled as repeated. Next: compare the scheduled 2026-07-26 structured report with this run. Do not spend on an immediate retry or reapply the archived lithium guard before that comparison. | PR #1095; run `30018289898`; PR #1097; archive ref `refs/archive/rejected-rag/20260723/monitoring-subject-gate` | 2026-07-23 | +| #001 | P2 | task | Semantic reranking still gated off | `RAG_SEMANTIC_RERANK_ENABLED=false` from PR #901. Do not enable until the provider-backed 36/36 retrieval-quality gate **and** an ambiguity-focused canary are explicitly approved and recorded. | `docs/process-hardening.md` (Semantic reranking rollout debt); PR #901 | 2026-07-21 | +| #069 | P3 | task | Live-profile table-facts plpgsql+EXECUTE latency | Migration `20260724120000_table_facts_plpgsql_execute.sql` plus P3 follow-ups (`20260724130000_*`, `20260724130100_*`) are in PR #1133. Hosted apply is blocked in this environment (no `SUPABASE_DB_URL`; Supabase MCP `needsAuth`). A live `profile:retrieval --rpc match_document_table_facts_text --analyze` attempt returned `Unregistered API key` against the injected service-role secret. **Next:** operator applies the three pending migrations on the live target project, then re-runs approval-gated `profile:retrieval` / `explain_retrieval_rpc` and confirms ~70ms-class plans with no ranking change. **Stop** without mutating ranking. | session 2026-07-24 Database interface audit; PR #1133 | 2026-07-24 | +| #053 | P1 | task | Execute cross-border privacy/legal package | Execute OpenAI and Railway DPAs; decide ZDR and Australian data residency; obtain prompt-cache behavior in writing; review subprocessors; obtain APP 8 and APP 5/1 counsel sign-off. Do not represent the release as privacy-approved or alter final public privacy wording before sign-off. | `docs/openai-cross-border-basis.md`; `docs/privacy-impact-assessment.md` | 2026-07-24 | +| #055 | P2 | task | Run one exact-SHA full release and PR gate | Before the next full-confidence release/handoff, record the candidate/PR SHA and run the local/provider release gates, Firefox/WebKit, required hosted CI, and actionable GitHub review-thread closure once. Stop at the first actionable failure and rerun only the repaired smallest gate. | `docs/launch-operator-runbook.md`; `docs/codex-review-protocol.md` | 2026-07-24 | +| #056 | P2 | task | Provision isolated staging environment | After explicit cost/ownership approval, provision `Clinical KB Staging` Supabase and Railway tiers with distinct secrets and synthetic/non-clinical data. Verify identity, schema, indexing, health, and the production-data boundary. | `docs/staging-setup.md`; `docs/operator-backlog.md` | 2026-07-24 | +| #057 | P2 | task | Complete staging soak and rollback rehearsal | After #056, run the documented soak and rollback against an exact candidate; retain latency/error/rollback evidence. Stop on unsafe data, identity mismatch, or an unowned rollback decision. | `docs/launch-operator-runbook.md`; `docs/capacity-review.md` | 2026-07-24 | +| #058 | P2 | task | Verify production content before any seed write | Against `the live target project`, verify registry, differentials, and medications surfaces are non-empty before writing. Seed only confirmed gaps idempotently with approved owner/project identity and confirmation flags. | `docs/launch-operator-runbook.md`; `docs/operator-backlog.md` | 2026-07-24 | +| #005 | P3 | rec | `finalScore` saturates at clamp ceiling | Base + ~40 stacked boosts routinely exceed 1.0, so strong matches tie at 1.0 and order by an arbitrary `document_id` tiebreak. If ranking is ever revisited, break ties by the **pre-clamp** score rather than raising the `[0,1]` ceiling (downstream gates assume `[0,1]`). Ordering already sorts by the unbounded pre-clamp `rankScore` (`clinical-search.ts:1735,1927,1950-1955`), so the clamp confines only the reported confidence value, not result order. Not a defect on the current golden set; any change here is a protected RAG surface (canary required). | `docs/rag-hybrid-findings-and-todo.md` P1 item 4; `src/lib/clinical-search.ts:1735` | 2026-07-21 | +| #011 | P3 | task | Auth DB-connection allocation is operator-only | Supabase Auth (GoTrue) is capped at ~10 absolute DB connections (Supabase perf advisor). Switch to **percentage-based** allocation in the Supabase **dashboard** before the first compute scale-up β€” **not settable via SQL/MCP** (operator-owned). Verify via a staging soak + an approval-gated read-only advisor re-check. | `docs/auth-connection-cap-runbook.md`; `docs/process-hardening.md` (Known follow-up debts) | 2026-07-21 | +| #012 | P3 | rec | Slim the lazy cross-mode differentials chunk | `cross-mode-differentials.ts` is dynamically imported (correctly code-split **out** of the initial/dashboard bundle β€” verified), but it pulls the full ~860 KB differentials snapshot (~125 KB gzip lazy chunk) just to build a tiny `{slug,title,clinicalHinge}` + presentations + aliases catalog. A precomputed lightweight index (generator + drift check, like the `specifiers-content` split / medications `fields=index`) would cut that lazy chunk ~5–10β”œΓΉ. Not a bundle leak β€” an M-effort slim. | `src/lib/cross-mode-differentials.ts`; `src/components/clinical-dashboard/cross-mode-links.tsx:150`; session 2026-07-21 (build:analyze) | 2026-07-21 | +| #013 | P3 | rec | Route-chunk + mockup catalogue JSON weight | `build:analyze`: `/specifiers` ships `specifiers-search-index.json` (~180 KB parsed), `/forms` ships `forms-catalog.json` (~132 KB), `/formulation` ships `formulation-content.json` (~52 KB, client-side local search β€” needs index/full split or a search endpoint, architectural). All route-scoped (not initial bundle). Also `*-mockups.tsx` (~100 KB across chunks) build though `/mockups` 404s in prod β€” exclude from the prod artifact. | session 2026-07-21 (build:analyze) | 2026-07-21 | +| #016 | P3 | rec | "Big but not easy" structural + motion perf | Deferred larger levers: (a) nonce-CSP forces every product route to `β•žΓ† Dynamic` (zero static generation) β€” evaluate Partial Prerendering / static shells for the static clinical catalogues (DSM/differentials/therapy/specifiers/formulation); (b) sidebar expand/collapse animates `grid-template-columns` (biggest smoothness cost, motion-gated β€” needs a transform-overlay rethink); (c) Therapy Compass fetches 692 KB / 2.5 MB JSON client-side (defer until interaction + confirm brotli); (d) settings/setup/admin dialogs static-imported into the home chunk (`next/dynamic` them). | session 2026-07-21 (build route table + design audit) | 2026-07-21 | +| #017 | P3 | task | Field Web-Vitals baseline via live Lighthouse | In-sandbox runtime vitals were blocked (prod server hard-requires Supabase secrets; dev-mode CLS measured excellent at 0.00–0.04, content-first pages 0.000). Run Lighthouse against `psychiatry.tools` for real LCP/INP/CLS to prioritize #012–#016 by measured impact rather than reasoning. | session 2026-07-21 (measurement pass) | 2026-07-21 | +| #018 | P2 | task | Split the lithium, ADHD and metabolic residuals by mechanism | Revalidated on current main 2026-07-23: these are not one composer defect. Lithium reproduced an unrelated-table retrieval fast-path defect; ADHD retrieves a relevant chart-heavy CAMHS source but exhausts the extractive route budget; metabolic retrieves the correct AKG source but selects schedule-free prose. The narrow lithium subject-evidence guard improved targeting from 0 to 1 with golden recall 1.0 and no reciprocal-rank regressions, but it was reverted because the full canary failed. After #051 stabilises the canary, add independent current-main reproducers and assess each mechanism separately. Do not widen the matcher or combine these into a broad ranking/composer change. | runs `30007833352` and `30009207429`; PR #1093; session 2026-07-23 | 2026-07-21 | +| #019 | P2 | task | Admission doc dropped after deterministic comparison packing | Reconfirmed on merged-main run `30018289898`: golden retrieval remained 36/36 and retrieved `MHSP.AdmissionCommunityPts.pdf`, but the answer's top five sources retained only `MHSP.Discharge.pdf` after `generation_fallback:generation_quality_failed; comparison_source_extractive_fallback`. PR #1096 replays the live score/order shape and proves deterministic answer ranking plus cross-document packing retain both admission and discharge evidence, so retrieval scores, aliases and comparator ordering are not the fix. Next: create a red fallback-layer unit reproducer using the live source shape; any behavior change still needs the existing baseline and a passing post canary. | run `30018289898`; PR #1096; session 2026-07-23 | 2026-07-21 | +| #021 | P3 | rec | E-3d H2 residual: strong/comparison generation discards | approx. 6 generation attempts per full 44-case run still fail the final quality gate and fall to extractive on strong-route comparison/complex shapes (the designed-conservative outcome). PARKED: weakest cost/benefit on the queue β€” a wave (approx. $2-4 pair + reviewer cycle) to shave seconds off a few hard cases. Revisit only if latency/waste complaints or a cheaper lever appears. | E-3c design record; runs #59-#61 diagnostics | 2026-07-21 | +| #022 | P2 | task | Source-governance metadata refresh (operator) | **Worklist generated 2026-07-22 ($0, read-only): `docs/source-governance-refresh-worklist-2026-07-22.md`.** Reframed - this is NOT 59 clinical reviews. Of the 124 documents surfacing in canary top results, 59 are review-required, and **38 (64 pct) are the BMJ published-reference tier all sitting at `clinical_validation_status: unverified`** - one attestation-policy decision, not 38 reviews. The remaining 21 are genuine local WA health-service reviews (FSH 7, NMHS 4, CAMHS 3, AKG 2, KEMH 2, RPBG 2, RKPG 1), mostly `document_status: review_due`. Burn-down: top-10 documents clear 44 pct of flagged slots, top-20 clear 66 pct. Next: decide the BMJ attestation policy, then attest local docs by visibility (start `Clozapine Management by GP (NMHS)`, 22 slots at rank 1). | runs #61/#57 Source Governance data; `docs/source-governance-refresh-worklist-2026-07-22.md` | 2026-07-21 | +| #023 | P2 | task | Read Sunday 2026-07-26 scheduled-run artifacts | The 18:00 UTC scheduled runs deliver three free datapoints at once: first full-44 weekly canary (validates the #1044 ANSWER_CASE_LIMIT raise), browser-matrix flake second datapoint (webkit ui-route-coverage now reproduced + root-caused 2026-07-22 Ξ“Γ₯Γ† see #024; firefox ui-formulation:91 still awaits a datapoint), and the irrelevant@10 labeling-audit artifact (§3.1 human-decision class). Read all three, then disposition. | sessions 2026-07-20/21; branch-review-ledger convergence notes | 2026-07-21 | +| #025 | P2 | task | Activate the three webhooks (operator secrets) | Merged (#968/#1100) + deployed but inert β€” verified live: `POST /api/webhooks/railway` returns `503 webhook_not_configured`; the Supabase document-change trigger exists but lacks both activation inputs. To turn on: (1) Railway Ξ“Γ₯Γ† set `RAILWAY_WEBHOOK_SECRET` + add the `?token=…` webhook URL; (2) set `SLACK_WEBHOOK_URL`/`DISCORD_WEBHOOK_URL` in BOTH the Railway **app/server env** and **GitHub repo secrets**; (3) set one matching document-change secret in the Railway app env as `SUPABASE_INGESTION_WEBHOOK_SECRET` and in Supabase Vault as `ingestion_webhook_secret`, then set the per-environment database GUC `app.ingestion_webhook_base_url` to the deployed app origin. Each path fails closed until fully configured, so this is pure ops. See `docs/webhooks.md` for verification and rotation. | sessions 2026-07-22/24; PRs #968/#1100; docs/webhooks.md | 2026-07-22 | +| #027 | P3 | rec | External uptime monitor independent of GitHub/Railway | `live-domain-monitor.yml` runs on GitHub's cron, so it won't run in exactly the outage it should catch (Actions or the deploy itself down). Add an off-platform synthetic monitor (UptimeRobot / Better Stack / Checkly) hitting `/api/health` with a webhook alert. Provider setup, not code. | session 2026-07-22 webhook review | 2026-07-22 | +| #028 | P3 | rec | Runtime error tracking (Sentry or similar) | No error tracking in the repo β€” production exceptions on `psychiatry.tools`, including how often `RAG_PROVIDER_MODE=auto` silently degrades to source-only, are invisible. Weigh adding `@sentry/nextjs` (dependency + DSN secret + instrumentation) vs cost; alert Ξ“Γ₯Γ† chat/issue. Provider-backed; needs explicit sign-off before adding the dependency. | session 2026-07-22 webhook review | 2026-07-22 | +| #029 | P2 | issue | 12 of 30 answer-quality cases return the fallback stub | run #61 --dump-answers: 12/30 quality cases emit the source_backed_review_fallback boilerplate with answer_sections: [], all grounded with 4-6 citations. Some still PASS targeting because the stub echoes query keywords (the contraindication/document_lookup matchers need only a keyword), so the targeting metric MASKS the problem for those intents. Superset of #018 β€” fix in the extractive composer, validate with the provider-backed answer eval. | run #61 dump artifact; session 2026-07-22 | 2026-07-22 | +| #033 | P3 | rec | Source governance metadata absent from the LLM prompt | `buildRagSourceBlock` omits `document_status`, `clinical_validation_status`, and `extraction_quality`, so the model cannot self-caveat during generation and governance is enforced only post-hoc. Generation-surface change: needs `eval:rag` plus `eval:quality --rag-only` (grounded-supported must not drop, citation-failure 0) and explicit approval. Carries the same "unknown ≥ bad" hazard as #032 β€” on a partially-enriched corpus the model would likely over-caveat correct sources, so design the prompt wording before spending an eval. | `src/lib/rag/rag-source-block.ts:126-198`; PR #1051 audit item 8 | 2026-07-22 | +| #035 | P3 | rec | Threshold-conflict detection covers only 3 params | `detectThresholdDisagreements` checks only ANC, WBC, and platelets paired with withholding verbs, so cross-source conflicts on medication doses, lithium/thyroid levels, or vital signs go undetected. Deliberately narrow (see the comment at `:469-474`). Broadening changes when an answer is classified `conflicting` and adds warnings β€” real false-positive risk. Needs new fixtures plus a behaviour review before any change. | `src/lib/evidence.ts:469-574`; PR #1051 audit item 7 | 2026-07-22 | +| #036 | P3 | rec | No explicit `is_public` visibility flag on documents | Public-corpus visibility is implicit: `owner_id IS NULL` on an `indexed` document (`resolveSearchScope`). The `metadata.public_corpus` marker is written by the promotion migrations but never used as a retrieval filter. Promotion is unconditional on `clinical_validation_status`, so unverified documents are publicly searchable β€” compensated by keeping `unverified_source` in the frontend-visible warning set. A hard schema flag touches RLS and the clinical-risk-gated retrieval RPCs; weigh against the existing compensating control before acting. | `supabase/schema.sql:61-108`; `src/lib/search-scope.ts:181-236`; PR #1051 audit item 3 | 2026-07-22 | +| #037 | P3 | rec | D5 trust-cap-all-claims flag parked OFF | `NEXT_PUBLIC_RAG_TRUST_CAP_ALL_CLAIMS` extends authority gating from high-risk claims to **all** supported claims (`deriveTrust`). Ships OFF by design; flipping it caps trust to `medium` for routine claims across the board β€” a product/clinical-UX decision, not a defect. Both states are test-pinned. Next action: product decision, then flip and re-baseline the UI expectations. | `src/lib/answer-render-policy.ts:159-177`; PR #1051 audit item 11 | 2026-07-22 | +| #038 | P3 | rec | Consolidate shared comparison behavior | Several clinical modes expose comparison workflows with similar selection, empty-state and mobile-dock needs. Define one shared behavioral contract before another comparison surface is added; keep mode-specific clinical content separate. This is a design-system recommendation, not a current defect. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | +| #039 | P3 | rec | Consolidate catalogue toolbar patterns | Catalogue/search pages have independently evolved filter, sort, result-count and mobile toolbar behavior. Inventory the existing implementations and converge only the repeated interaction contract; do not flatten mode-specific search semantics. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | +| #040 | P3 | rec | Add targeted visual-regression baselines | Keep a small approved baseline set for high-value desktop/mobile surfaces and accessibility modes instead of screenshotting every route. Start with account/settings, document viewer, mode homes and bottom-composer interactions; define an intentional-update workflow before enabling blocking comparisons. | design audit reconciliation; session 2026-07-22 | 2026-07-22 | +| #079 | P3 | task | Disposition retained worktrees in bounded cleanup batches | **Outcome:** the retained reconciliation tail is gradually classified without another disruptive all-worktree sweep. **Next:** process no more than ten worktrees per explicitly scheduled pass using current owner/process metadata, open-PR state, exact review-ledger coverage, ancestry, and cherry-pick-aware content proof. **Success:** remove only clean, inactive, bundled worktrees whose content is merged or explicitly rejected; record every disposition and retain recovery evidence. **Stop:** preserve dirty, active, secret-bearing, post-freeze, paused, or ambiguous work and never use reset, force deletion, broad clean, or process killing. | final reconciliation inventory retained 104 independent worktrees; session 2026-07-24 | 2026-07-24 | +| #080 | P3 | rec | Re-test the removed admission-to-discharge alias widening | **Outcome:** decide with measured evidence whether the user-approved 2026-07-21 widening (NMHS admission-to-discharge titles satisfying the admission expectation) belongs back on the wide-tier `AdmissionCommunityPts` list. #030 removed it so one document could not fill both comparison slots, but coverage now enforces distinct document identities by maximum matching, so the widening is no longer a false-pass route and its removal may instead cost legitimate admission credit. **Next:** with approval, run an eval-canary baseline/post pair over the admission-discharge comparison cases with and without those two titles. **Success:** restore only on measured non-regression (recall pinned 1.0, zero per-case reciprocal-rank regressions); otherwise record keep-removed and close. **Stop:** never restore on offline reasoning alone. | `src/lib/eval-document-matching.ts`; #030 archive row; session 2026-07-25 | 2026-07-25 | +| #082 | P2 | issue | Bot branch-sync heads leave required checks unapproved | **Outcome:** an open PR stops stalling when the `github-actions[bot]` sync merge updates its branch. Every bot-authored head produces `action_required` runs for CI, SAST and Secret Scan, so the three required checks never report: PR #1220 was blocked through five successive syncs, and both a normal and an `--admin` merge were refused. Heads pushed by an agent or human still run normally, so the mitigation is the `skip-branch-sync` label plus an own-authored head (cleared PR #1217). **Next:** pick one fix β€” allow the sync bot in the repository Actions approval setting, push the sync with a token whose runs auto-execute, or drop auto-sync. **Success:** a bot-synced PR reaches green required checks with no manual push. **Stop:** never widen approval settings for fork/untrusted actors or clear the block by bypassing required checks. | GitHub #1235; PR #1220/#1217 2026-07-25 (bot heads action_required); session 2026-07-25 | 2026-07-25 | ## Resolved / archive Move resolved rows here with the resolution date and a one-line outcome. Keep them β€” do not delete. -| ID | Type | Summary | Outcome | Resolved | -| ---- | ----- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| #064 | task | Reconcile the preserved browser and contrast patch | Landed via PR #1250 squash `b91b4600171be08198e92bcf19b7d67e8207cb2f`. Opacity-free disabled Previous/Continue styling plus native-disabled/focus/axe Playwright coverage is on `main`. Historical `agent/formulation-disabled-contrast` remained unrecovered; conflicted PRs #1219/#1223/#1226/#1231/#1249 were closed without merge. | 2026-07-26 | -| #024 | issue | WebKit e2e `_rsc` prefetch access-control errors | Fixed in PR #1205 after current-main reproducers showed catch-all interception plus duplicate viewport navigation racing same-origin WebKit RSC prefetches. The harness now intercepts external HTTP(S) only, directly proves blocking/recording, navigates each route once, and leaves `pageerror` unfiltered. On `64e46036`, the local Chromium/WebKit matrix passed 24/24 and `verify:pr-local` passed 3,431 tests, build/bundle checks, and 36 offline RAG fixtures. | 2026-07-25 | -| #081 | issue | Open PR #1196 would undo the #030 alias tightening | Closed as no longer live: PR #1196 was closed 2026-07-25 as superseded by #913/current `main` (~680 commits behind, conflicting), and its successor #1198 does not touch `src/lib/eval-document-matching.ts`. The generalized alias-disjointness and single-document contracts landed in PR #1215 fail closed if any later branch re-adds the dual-listed admission aliases, so the regression route is guarded rather than watched. | 2026-07-25 | -| #077 | issue | Concurrent tasks can re-dirty the canonical primary checkout | Added cooperative primary-checkout write lease with dirty/operation fail-closed checks, stale-owner recovery, and lifecycle start/cleanup wiring; focused concurrency tests refuse a second primary writer while read-only/feature worktrees stay unblocked. | 2026-07-25 | -| #078 | task | Generate a deterministic reconciliation evidence pack | Added report-only atomic evidence pack with dispositions, markers, archive refs, bundle verify/hash, worktree counts, and local/base equality; fixture tests prove determinism/redaction and no false completion record on interrupt. | 2026-07-25 | -| #066 | task | Land and prove the streamlined six-item sidebar | Proven on `origin/main` via PR #1174 (`4dc76306 Land streamlined six-item sidebar`). Six-item rail shipped; open ledger row was stale post-merge. | 2026-07-25 | -| #067 | issue | Reconciliation preflight test times out under full-suite load | Fixed in PR #1191 (`e2488dbb`) by calling `collectReconciliationState()` in-process; PR #1203 further injects a fixture `repositoryRoot` so the contract no longer scales with the live worktree farm. No global timeout raise or heavy-test lock bypass. | 2026-07-25 | -| #007 | rec | `/tools` vs `/?mode=tools` parallel Tools entry points | Resolved as `/tools` canonical (PT-11 already documented on `/applications` redirect). Sidebar, appModeHomeHref, universal-search, prefetch, sitemap, and reachability now use `/tools`; `/?mode=tools` remains a dashboard-mode alias. Reachability allowlist entry removed. | 2026-07-24 | -| #030 | issue | Wide-tier alias lets one doc satisfy both comparison slots | Fixed on `cursor/search-correctness-030-075-6273`: removed dual-listed Admission-to-Discharge titles from AdmissionCommunityPts so one retrieved source cannot make allHit true for both comparison slots; fail-closed contracts in `tests/eval-document-matching.test.ts`. RAG impact: no retrieval behaviour change β€” eval matching only. Hardened after merge: coverage dedupes by document identity and assigns by maximum matching (#080). | 2026-07-24 | -| #075 | issue | Search-scope label enumeration can truncate after 1,000 rows | Fixed on `cursor/search-correctness-030-075-6273`: `loadScopeLabels` pages document_labels with deterministic order/batching past the Supabase 1k cap; multi-page >1000 contracts in `tests/search-scope.test.ts`. Isolated from mixed PR #1132. RAG impact: no retrieval behaviour change β€” label pagination only. | 2026-07-24 | -| #009 | rec | Confirm `/api/jobs` is intentionally server/ops-only | Kept as deliberate administrator/ops listing: no client `fetch("/api/jobs")` (UI uses `/api/ingestion/jobs`); documented in `docs/api-jobs-ops-surface.md` plus wiring/codebase-index/site-map notes. Not abandoned β€” do not remove without updating API contract tests. | 2026-07-24 | -| #010 | task | Un-built "Coming soon" controls across forms/favourites | Audited forms/favourites/presentation placeholders: all use honest `disabled` or `aria-disabled` + coming-soon copy (or presentational `ToggleSwitch` without `onToggle`). No fake-interactive controls; leave unwired until features land. Recorded in `docs/wiring-conventions.md`. | 2026-07-24 | -| #032 | rec | Governance ranking weighting: REFUTED, not debt | Reinforced as guardrail only in `docs/rag-behaviour/refuted-approaches.md` (Refutation 3), README, and safeguards β€” do **not** implement `review_due`/unknownCurrentness ranking penalties or boosts. No retrieval/ranking code changed. RC8 filter path remains the only revisit route behind canary gates. | 2026-07-24 | -| #041 | rec | Extend the existing Factsheets reading model | Brief recorded in `docs/factsheets-reading-model-brief.md`: extend Easy Read/Standard on existing Factsheets routes; reject a second patient-facing Factsheets mode unless concrete need + source-governance plan exist. | 2026-07-24 | -| #063 | rec | Define β€œCurrent Clinical Work” before implementation | Product/privacy/persistence brief recorded in `docs/current-clinical-work-brief.md`. Default v0 = no new storage (tab/URL resume); Class C free text needs privacy clearance. Stop without demand evidence. No UI/schema implemented. | 2026-07-24 | -| #076 | task | Reproduce malformed fallback PDF image/table crops | Reproduced truncated page-edge `table_crop`s on current-main with `worker/python/fixtures/malformed-table-crop-page-edge.pdf`. Root cause: `pymupdf_find_tables` stops at the last fully detected row; fix extends the candidate from contiguous cell drawings, recovers the on-page score-5 remnant, and emits `table_crop_edge_incomplete` / `crop_completeness=0.9` when content continues past the page. PR #1176. Broad PR #1129 retention/padding changes not merged. | 2026-07-24 | -| #070 | issue | Presentation mobile tabs misroute Overview/Map/Related | Fixed in PR #1135: Overview/Map/Related deep-link to diagnosis `?tab=` sections; Compare stays on the presentation page. Regression in `tests/mobile-interaction-regressions.test.ts`. (Provisional PR-branch IDs `#068`–`#072` were renumbered after `main` claimed `#068`/`#069`.) | 2026-07-24 | -| #071 | issue | Evidence/Clinical Notes Add fakes success without persistence | Fixed in PR #1135: sticky Add controls use the focusable coming-soon placeholder pattern instead of optimistic `setAdded(true)`. | 2026-07-24 | -| #072 | issue | Tools hub exposes false Sort/More affordances | Fixed in PR #1135: Sort is a status label, More filter targets coordination/saved without a fake menu chevron, and the favourites shortcut is labelled Saved/Favourites. | 2026-07-24 | -| #073 | issue | Presentation compare dock CTA is a self-link no-op | Fixed in PR #1135: dock shows non-link "Comparing (N)" status while already comparing. | 2026-07-24 | -| #074 | issue | Mode-action popup hard-reloads internal clinical routes | Fixed in PR #1135: `master-search-header` uses `router.push` for DSM/Specifiers/Formulation actions and mode href fallback. | 2026-07-24 | -| #068 | task | Regenerate full drift-manifest snapshot after schema hygiene | Full Docker `npm run drift:manifest` replay succeeded on a Docker-capable host; `supabase/drift-manifest.json` now carries live `def_hash` values for the plpgsql table-facts body (offline generator_note removed). | 2026-07-24 | -| #052 | issue | Reindex can overlap a fresh agent-enrichment pass | PR #1143 retained the friendly full/retry preflight and closed its check-then-enqueue race with an owner-scoped transactional RPC. Reindex enqueue and the agent claim path serialize on the document row; disposable PostgreSQL proved both interleavings, and exact-head migration replay/unit/build/Chromium/policy/security checks passed. | 2026-07-24 | -| #062 | issue | Upload crash can strand a queued document without a job | Aged owner-scoped `queued`-without-open-job rows are detected by `reindex:health`; the six-hour autopilot raises a durable alert, and guarded recovery uses PR #1143's transactional RPC so enqueue is owner-scoped, idempotent and atomic. `recover:ingestion --include-stranded-queued` remains dry-run/confirmation-first; scheduled production mutation is not enabled. | 2026-07-24 | -| #060 | issue | Safety Plan Generator contradicted the privacy contract | PR #1119 removed patient identifier entry, leaves the post-export name line blank, and aligned tool, privacy and PIA copy. DOM/privacy tests and Chromium copy/print/network coverage prove working content remains in React memory with no fetch/XHR; hosted Production UI, build, unit, policy, safety, static-analysis and secret checks passed. Support-contact details remain classified as sensitive local-only working content. | 2026-07-24 | -| #061 | issue | Missing answer relevance metadata was treated as source-backed | PR #1125 now requires explicit source-backed relevance for trusted/grounded presentation and prevents visual tables, clinical-note sections and quotes, and comparison metadata from bypassing the render model. Three actionable P2 review paths were fixed; focused policy/DOM tests, offline RAG, production-readiness, build, unit, static, security, and Production UI gates passed. No retrieval, ranking, generation, provider, or data behavior changed. | 2026-07-24 | -| #034 | issue | Answer cache can serve stale governance metadata | Current-source verification found direct route coverage already asserts RAG-cache invalidation on document PATCH, source review, label, bulk, and reindex mutation paths. The residual test recommendation is already met; changing the protected cache key is unnecessary. | 2026-07-24 | -| #014 | rec | Realize the `next/image` win on signed previews | Superseded: `SignedImage` uses `next/image` for layout and sizing but deliberately sets `unoptimized`, preventing bearer signed URLs from entering the unauthenticated optimizer cache where cached content could outlive the token. No optimization task remains unless private-image delivery changes. | 2026-07-24 | -| #026 | task | Wire the Supabase document-change trigger | PR #1100 merged after disposable PostgreSQL replay and hosted migration replay. Production migration history and read-only catalog proof confirm the enabled metadata trigger, security-definer function, pinned search path and denied anonymous/authenticated execution; `npm run check:drift` reports no unexpected live drift. Delivery remains intentionally inert until the operator inputs tracked in #025 are configured. | 2026-07-24 | -| #031 | issue | Populate canary Source Governance table | The answer-quality step now consumes the preceding `golden-retrieval.json` only for source-governance reporting. Offline replay of run `30018289898` populated 338 top results, including 202 review-required entries, while retaining zero retrieval cases and no additional threshold failures. Retrieval and ranking behavior are unchanged. | 2026-07-24 | -| #020 | task | Validate eval:quality cost readout post-fix | Confirmed on merged-main canary run `30018289898`: Answer Metrics reported 9 nonzero-cost cases and an estimated answer cost of `$0.234736`; the structured report retained the same value. The PR #1050 estimator fix is operationally proven. | 2026-07-23 | -| #003 | task | Staging tenancy release evidence outstanding | Ran GitHub Action and validated isolation | 2026-07-21 | -| #002 | task | Process-ownership fix not yet isolated on `main` | Fixed process isolation using child.pid termination | 2026-07-21 | -| #008 | rec | Dead href builders in `document-flow-routes.ts` | Not dead code (false positive): `documentReaderHref`/`documentEvidenceHref` are live via the mock wrappers in `src/components/document-search-mockups.tsx` + `src/components/master-document-flow-mockups.tsx` (rendered under `src/app/mockups/document-search/`) and covered by `tests/document-flow-routes.test.ts`; removing breaks the build. Only the production non-mock hrefs are unlinked from prod UI β€” a wiring gap, not dead code. | 2026-07-22 | -| #015 | task | Content-first fallback regression tests | Added `tests/registry-record-loader.dom.test.tsx` (8) + `tests/medication-record-page.dom.test.tsx` (6) covering content-first fallback paint, live swap-in, spinner/skeleton, error + not-found/unauthorized states, and the invariant that no authoritative verification badge shows before live governance reconciles (registry fixture-flag neutralization + medication governance-drop-on-error). | 2026-07-22 | -| #004 | rec | Rescope provider-gated RAG safety ideas | Closed obsolete β€” rescue source (754-line RAG-safety worktree) unrecoverable/pruned across all refs; answer-quality thresholds + deep-health already shipped on `main` (#585/#587); only cost-cap preflight was genuinely missing and, per session decision, dropped rather than re-filed. | 2026-07-22 | -| #006 | issue | Globe "Language & region" button had no handler | Resolved on main with the repository's disabled "Coming soon" placeholder convention and button-wiring coverage. Future language/region work remains a feature request, not an inert-control defect. | 2026-07-22 | -| #042 | issue | Invalid optional credentials fell into anonymous access | PRs #1078/#1079 introduced `absent \| valid \| invalid`, return 401 for presented invalid credentials, preserve authoritative header precedence and prefer the current-project session cookie. The archived anonymous-upload metadata patch was rejected as stale because uploads are already administrator-only before duplicate lookup. | 2026-07-22 | -| #043 | issue | Readiness could report healthy or throw on Supabase errors | PR #1080 now fails readiness closed for returned and thrown dependency failures, preserves recognized actionable messages, and prevents raw dependency-error disclosure. | 2026-07-22 | -| #044 | issue | Publication approval was not bound to immutable reviewed state | PR #1081 added a canonical reviewed-state digest, row locks, active-job rejection and a new forward migration with replay/schema/type/drift evidence. | 2026-07-22 | -| #045 | issue | Bulk reindex discarded partial-success results | PR #1084 reserves preflight conflicts for non-2xx responses; completed mixed batches return per-item success/failure/missing results, and the UI refreshes successful work. | 2026-07-22 | -| #046 | issue | DOCX extraction lacked explicit resource budgets | PR #1085 added pre-inflate declared-size checks and post-read fail-safes for artifact count, per-artifact bytes, aggregate media, Word XML and extracted UTF-8 text. | 2026-07-22 | -| #047 | issue | XLSX extraction could construct unbounded results | PR #1086 bounds worksheets, non-empty rows, rendered cells and UTF-8 output while preserving sparse-column rendering. | 2026-07-22 | -| #048 | issue | Account copy overstated sync/privacy and enabled unavailable SSO | PR #1087 now maps copy to actual favourites/preferences persistence, identifies browser-session recents, removes the contradictory "never shared" claim and clearly disables unavailable providers using the accessible placeholder contract. | 2026-07-22 | -| #049 | issue | Process diagnostic exposed a Cursor worker API key | The exact worker was stopped, the key was revoked server-side, both local encrypted worker-secret records were removed, and authorized repository/backup scans found no plaintext copy. Follow-up guardrails now prevent repository process inventory from serializing command lines and redact heavyweight-lock command text before persistence or errors. | 2026-07-23 | -| #050 | issue | Next.js 16.2.10 remained in a high-severity security range | Upgraded `next` and `@next/env` to 16.2.11, regenerated the npm lockfile, confirmed the production dependency audit is clean, and passed focused framework checks, `verify:cheap`, and the full Chromium UI gate. | 2026-07-23 | +| ID | Type | Summary | Outcome | Resolved | +| ---- | ----- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| #054 | task | Reconcile local and hosted secrets/config | Completed production names-only reconciliation on 2026-07-27. The correctly identified primary checkout received distinct gitignored local safety/query-hash/deep-probe values. A hardened checker now pins GitHub to `BigSimmo/Database` and Railway to the live production project/environment plus `Database`/`worker`, catches multiline schema and `.env.example` drift, and verifies GitHub secrets/variables and per-service Railway contracts without emitting provider values. All required names passed; the Ops Digest workflow is active with a successful scheduled run; both Railway services have later successful deployments; Supabase names-only proof found the expected cron/Vault configuration. Value equality remains deliberately unobservable, staging stays #056, webhook activation stays #025, and legal/ZDR work stays #053. | 2026-07-27 | +| #064 | task | Reconcile the preserved browser and contrast patch | Landed via PR #1250 squash `b91b4600171be08198e92bcf19b7d67e8207cb2f`. Opacity-free disabled Previous/Continue styling plus native-disabled/focus/axe Playwright coverage is on `main`. Historical `agent/formulation-disabled-contrast` remained unrecovered; conflicted PRs #1219/#1223/#1226/#1231/#1249 were closed without merge. | 2026-07-26 | +| #024 | issue | WebKit e2e `_rsc` prefetch access-control errors | Fixed in PR #1205 after current-main reproducers showed catch-all interception plus duplicate viewport navigation racing same-origin WebKit RSC prefetches. The harness now intercepts external HTTP(S) only, directly proves blocking/recording, navigates each route once, and leaves `pageerror` unfiltered. On `64e46036`, the local Chromium/WebKit matrix passed 24/24 and `verify:pr-local` passed 3,431 tests, build/bundle checks, and 36 offline RAG fixtures. | 2026-07-25 | +| #081 | issue | Open PR #1196 would undo the #030 alias tightening | Closed as no longer live: PR #1196 was closed 2026-07-25 as superseded by #913/current `main` (~680 commits behind, conflicting), and its successor #1198 does not touch `src/lib/eval-document-matching.ts`. The generalized alias-disjointness and single-document contracts landed in PR #1215 fail closed if any later branch re-adds the dual-listed admission aliases, so the regression route is guarded rather than watched. | 2026-07-25 | +| #077 | issue | Concurrent tasks can re-dirty the canonical primary checkout | Added cooperative primary-checkout write lease with dirty/operation fail-closed checks, stale-owner recovery, and lifecycle start/cleanup wiring; focused concurrency tests refuse a second primary writer while read-only/feature worktrees stay unblocked. | 2026-07-25 | +| #078 | task | Generate a deterministic reconciliation evidence pack | Added report-only atomic evidence pack with dispositions, markers, archive refs, bundle verify/hash, worktree counts, and local/base equality; fixture tests prove determinism/redaction and no false completion record on interrupt. | 2026-07-25 | +| #066 | task | Land and prove the streamlined six-item sidebar | Proven on `origin/main` via PR #1174 (`4dc76306 Land streamlined six-item sidebar`). Six-item rail shipped; open ledger row was stale post-merge. | 2026-07-25 | +| #067 | issue | Reconciliation preflight test times out under full-suite load | Fixed in PR #1191 (`e2488dbb`) by calling `collectReconciliationState()` in-process; PR #1203 further injects a fixture `repositoryRoot` so the contract no longer scales with the live worktree farm. No global timeout raise or heavy-test lock bypass. | 2026-07-25 | +| #007 | rec | `/tools` vs `/?mode=tools` parallel Tools entry points | Resolved as `/tools` canonical (PT-11 already documented on `/applications` redirect). Sidebar, appModeHomeHref, universal-search, prefetch, sitemap, and reachability now use `/tools`; `/?mode=tools` remains a dashboard-mode alias. Reachability allowlist entry removed. | 2026-07-24 | +| #030 | issue | Wide-tier alias lets one doc satisfy both comparison slots | Fixed on `cursor/search-correctness-030-075-6273`: removed dual-listed Admission-to-Discharge titles from AdmissionCommunityPts so one retrieved source cannot make allHit true for both comparison slots; fail-closed contracts in `tests/eval-document-matching.test.ts`. RAG impact: no retrieval behaviour change β€” eval matching only. Hardened after merge: coverage dedupes by document identity and assigns by maximum matching (#080). | 2026-07-24 | +| #075 | issue | Search-scope label enumeration can truncate after 1,000 rows | Fixed on `cursor/search-correctness-030-075-6273`: `loadScopeLabels` pages document_labels with deterministic order/batching past the Supabase 1k cap; multi-page >1000 contracts in `tests/search-scope.test.ts`. Isolated from mixed PR #1132. RAG impact: no retrieval behaviour change β€” label pagination only. | 2026-07-24 | +| #009 | rec | Confirm `/api/jobs` is intentionally server/ops-only | Kept as deliberate administrator/ops listing: no client `fetch("/api/jobs")` (UI uses `/api/ingestion/jobs`); documented in `docs/api-jobs-ops-surface.md` plus wiring/codebase-index/site-map notes. Not abandoned β€” do not remove without updating API contract tests. | 2026-07-24 | +| #010 | task | Un-built "Coming soon" controls across forms/favourites | Audited forms/favourites/presentation placeholders: all use honest `disabled` or `aria-disabled` + coming-soon copy (or presentational `ToggleSwitch` without `onToggle`). No fake-interactive controls; leave unwired until features land. Recorded in `docs/wiring-conventions.md`. | 2026-07-24 | +| #032 | rec | Governance ranking weighting: REFUTED, not debt | Reinforced as guardrail only in `docs/rag-behaviour/refuted-approaches.md` (Refutation 3), README, and safeguards β€” do **not** implement `review_due`/unknownCurrentness ranking penalties or boosts. No retrieval/ranking code changed. RC8 filter path remains the only revisit route behind canary gates. | 2026-07-24 | +| #041 | rec | Extend the existing Factsheets reading model | Brief recorded in `docs/factsheets-reading-model-brief.md`: extend Easy Read/Standard on existing Factsheets routes; reject a second patient-facing Factsheets mode unless concrete need + source-governance plan exist. | 2026-07-24 | +| #063 | rec | Define β€œCurrent Clinical Work” before implementation | Product/privacy/persistence brief recorded in `docs/current-clinical-work-brief.md`. Default v0 = no new storage (tab/URL resume); Class C free text needs privacy clearance. Stop without demand evidence. No UI/schema implemented. | 2026-07-24 | +| #076 | task | Reproduce malformed fallback PDF image/table crops | Reproduced truncated page-edge `table_crop`s on current-main with `worker/python/fixtures/malformed-table-crop-page-edge.pdf`. Root cause: `pymupdf_find_tables` stops at the last fully detected row; fix extends the candidate from contiguous cell drawings, recovers the on-page score-5 remnant, and emits `table_crop_edge_incomplete` / `crop_completeness=0.9` when content continues past the page. PR #1176. Broad PR #1129 retention/padding changes not merged. | 2026-07-24 | +| #070 | issue | Presentation mobile tabs misroute Overview/Map/Related | Fixed in PR #1135: Overview/Map/Related deep-link to diagnosis `?tab=` sections; Compare stays on the presentation page. Regression in `tests/mobile-interaction-regressions.test.ts`. (Provisional PR-branch IDs `#068`–`#072` were renumbered after `main` claimed `#068`/`#069`.) | 2026-07-24 | +| #071 | issue | Evidence/Clinical Notes Add fakes success without persistence | Fixed in PR #1135: sticky Add controls use the focusable coming-soon placeholder pattern instead of optimistic `setAdded(true)`. | 2026-07-24 | +| #072 | issue | Tools hub exposes false Sort/More affordances | Fixed in PR #1135: Sort is a status label, More filter targets coordination/saved without a fake menu chevron, and the favourites shortcut is labelled Saved/Favourites. | 2026-07-24 | +| #073 | issue | Presentation compare dock CTA is a self-link no-op | Fixed in PR #1135: dock shows non-link "Comparing (N)" status while already comparing. | 2026-07-24 | +| #074 | issue | Mode-action popup hard-reloads internal clinical routes | Fixed in PR #1135: `master-search-header` uses `router.push` for DSM/Specifiers/Formulation actions and mode href fallback. | 2026-07-24 | +| #068 | task | Regenerate full drift-manifest snapshot after schema hygiene | Full Docker `npm run drift:manifest` replay succeeded on a Docker-capable host; `supabase/drift-manifest.json` now carries live `def_hash` values for the plpgsql table-facts body (offline generator_note removed). | 2026-07-24 | +| #052 | issue | Reindex can overlap a fresh agent-enrichment pass | PR #1143 retained the friendly full/retry preflight and closed its check-then-enqueue race with an owner-scoped transactional RPC. Reindex enqueue and the agent claim path serialize on the document row; disposable PostgreSQL proved both interleavings, and exact-head migration replay/unit/build/Chromium/policy/security checks passed. | 2026-07-24 | +| #062 | issue | Upload crash can strand a queued document without a job | Aged owner-scoped `queued`-without-open-job rows are detected by `reindex:health`; the six-hour autopilot raises a durable alert, and guarded recovery uses PR #1143's transactional RPC so enqueue is owner-scoped, idempotent and atomic. `recover:ingestion --include-stranded-queued` remains dry-run/confirmation-first; scheduled production mutation is not enabled. | 2026-07-24 | +| #060 | issue | Safety Plan Generator contradicted the privacy contract | PR #1119 removed patient identifier entry, leaves the post-export name line blank, and aligned tool, privacy and PIA copy. DOM/privacy tests and Chromium copy/print/network coverage prove working content remains in React memory with no fetch/XHR; hosted Production UI, build, unit, policy, safety, static-analysis and secret checks passed. Support-contact details remain classified as sensitive local-only working content. | 2026-07-24 | +| #061 | issue | Missing answer relevance metadata was treated as source-backed | PR #1125 now requires explicit source-backed relevance for trusted/grounded presentation and prevents visual tables, clinical-note sections and quotes, and comparison metadata from bypassing the render model. Three actionable P2 review paths were fixed; focused policy/DOM tests, offline RAG, production-readiness, build, unit, static, security, and Production UI gates passed. No retrieval, ranking, generation, provider, or data behavior changed. | 2026-07-24 | +| #034 | issue | Answer cache can serve stale governance metadata | Current-source verification found direct route coverage already asserts RAG-cache invalidation on document PATCH, source review, label, bulk, and reindex mutation paths. The residual test recommendation is already met; changing the protected cache key is unnecessary. | 2026-07-24 | +| #014 | rec | Realize the `next/image` win on signed previews | Superseded: `SignedImage` uses `next/image` for layout and sizing but deliberately sets `unoptimized`, preventing bearer signed URLs from entering the unauthenticated optimizer cache where cached content could outlive the token. No optimization task remains unless private-image delivery changes. | 2026-07-24 | +| #026 | task | Wire the Supabase document-change trigger | PR #1100 merged after disposable PostgreSQL replay and hosted migration replay. Production migration history and read-only catalog proof confirm the enabled metadata trigger, security-definer function, pinned search path and denied anonymous/authenticated execution; `npm run check:drift` reports no unexpected live drift. Delivery remains intentionally inert until the operator inputs tracked in #025 are configured. | 2026-07-24 | +| #031 | issue | Populate canary Source Governance table | The answer-quality step now consumes the preceding `golden-retrieval.json` only for source-governance reporting. Offline replay of run `30018289898` populated 338 top results, including 202 review-required entries, while retaining zero retrieval cases and no additional threshold failures. Retrieval and ranking behavior are unchanged. | 2026-07-24 | +| #020 | task | Validate eval:quality cost readout post-fix | Confirmed on merged-main canary run `30018289898`: Answer Metrics reported 9 nonzero-cost cases and an estimated answer cost of `$0.234736`; the structured report retained the same value. The PR #1050 estimator fix is operationally proven. | 2026-07-23 | +| #003 | task | Staging tenancy release evidence outstanding | Ran GitHub Action and validated isolation | 2026-07-21 | +| #002 | task | Process-ownership fix not yet isolated on `main` | Fixed process isolation using child.pid termination | 2026-07-21 | +| #008 | rec | Dead href builders in `document-flow-routes.ts` | Not dead code (false positive): `documentReaderHref`/`documentEvidenceHref` are live via the mock wrappers in `src/components/document-search-mockups.tsx` + `src/components/master-document-flow-mockups.tsx` (rendered under `src/app/mockups/document-search/`) and covered by `tests/document-flow-routes.test.ts`; removing breaks the build. Only the production non-mock hrefs are unlinked from prod UI β€” a wiring gap, not dead code. | 2026-07-22 | +| #015 | task | Content-first fallback regression tests | Added `tests/registry-record-loader.dom.test.tsx` (8) + `tests/medication-record-page.dom.test.tsx` (6) covering content-first fallback paint, live swap-in, spinner/skeleton, error + not-found/unauthorized states, and the invariant that no authoritative verification badge shows before live governance reconciles (registry fixture-flag neutralization + medication governance-drop-on-error). | 2026-07-22 | +| #004 | rec | Rescope provider-gated RAG safety ideas | Closed obsolete β€” rescue source (754-line RAG-safety worktree) unrecoverable/pruned across all refs; answer-quality thresholds + deep-health already shipped on `main` (#585/#587); only cost-cap preflight was genuinely missing and, per session decision, dropped rather than re-filed. | 2026-07-22 | +| #006 | issue | Globe "Language & region" button had no handler | Resolved on main with the repository's disabled "Coming soon" placeholder convention and button-wiring coverage. Future language/region work remains a feature request, not an inert-control defect. | 2026-07-22 | +| #042 | issue | Invalid optional credentials fell into anonymous access | PRs #1078/#1079 introduced `absent \| valid \| invalid`, return 401 for presented invalid credentials, preserve authoritative header precedence and prefer the current-project session cookie. The archived anonymous-upload metadata patch was rejected as stale because uploads are already administrator-only before duplicate lookup. | 2026-07-22 | +| #043 | issue | Readiness could report healthy or throw on Supabase errors | PR #1080 now fails readiness closed for returned and thrown dependency failures, preserves recognized actionable messages, and prevents raw dependency-error disclosure. | 2026-07-22 | +| #044 | issue | Publication approval was not bound to immutable reviewed state | PR #1081 added a canonical reviewed-state digest, row locks, active-job rejection and a new forward migration with replay/schema/type/drift evidence. | 2026-07-22 | +| #045 | issue | Bulk reindex discarded partial-success results | PR #1084 reserves preflight conflicts for non-2xx responses; completed mixed batches return per-item success/failure/missing results, and the UI refreshes successful work. | 2026-07-22 | +| #046 | issue | DOCX extraction lacked explicit resource budgets | PR #1085 added pre-inflate declared-size checks and post-read fail-safes for artifact count, per-artifact bytes, aggregate media, Word XML and extracted UTF-8 text. | 2026-07-22 | +| #047 | issue | XLSX extraction could construct unbounded results | PR #1086 bounds worksheets, non-empty rows, rendered cells and UTF-8 output while preserving sparse-column rendering. | 2026-07-22 | +| #048 | issue | Account copy overstated sync/privacy and enabled unavailable SSO | PR #1087 now maps copy to actual favourites/preferences persistence, identifies browser-session recents, removes the contradictory "never shared" claim and clearly disables unavailable providers using the accessible placeholder contract. | 2026-07-22 | +| #049 | issue | Process diagnostic exposed a Cursor worker API key | The exact worker was stopped, the key was revoked server-side, both local encrypted worker-secret records were removed, and authorized repository/backup scans found no plaintext copy. Follow-up guardrails now prevent repository process inventory from serializing command lines and redact heavyweight-lock command text before persistence or errors. | 2026-07-23 | +| #050 | issue | Next.js 16.2.10 remained in a high-severity security range | Upgraded `next` and `@next/env` to 16.2.11, regenerated the npm lockfile, confirmed the production dependency audit is clean, and passed focused framework checks, `verify:cheap`, and the full Chromium UI gate. | 2026-07-23 | diff --git a/scripts/check-env-parity.mjs b/scripts/check-env-parity.mjs index 0e7869324c..8ce5cf1f47 100644 --- a/scripts/check-env-parity.mjs +++ b/scripts/check-env-parity.mjs @@ -1,20 +1,19 @@ #!/usr/bin/env node /** * check-env-parity β€” reconcile environment-variable NAMES across the places this - * repo declares them, without ever reading or printing a single value. + * repository declares them. Values are never emitted or persisted. * - * Config truth is scattered: the canonical Zod schema in src/lib/env.ts, the CI - * browser-env checker (scripts/check-ci-env.mjs), GitHub repo secrets, and Railway - * runtime vars. A name present in one place but missing in another has broken main - * CI before (e.g. RAG_QUERY_HASH_SECRET). This diffs the name sets and reports gaps. + * Offline by default: + * npm run check:env-parity * - * Offline by default (parses env.ts + check-ci-env.mjs only). Live sources are - * opt-in and names-only: - * --gh run `gh secret list` (names only; values are write-only anyway) - * --railway run `railway variables` (names only) if the CLI is available + * Explicit provider-backed reads: + * npm run check:env-parity -- --gh + * npm run check:env-parity -- --railway * - * Never prints a value. Exit 1 only when a hard parity problem is found (an - * expected secret is absent from a queried live source), else 0. + * GitHub reads are pinned to BigSimmo/Database. Railway reads are pinned to the + * live Database project, production environment, and named app/worker services. + * Railway's JSON format contains raw values, so the response is captured only + * in memory and immediately reduced to Object.keys(). */ import { execFileSync } from "node:child_process"; import { readFileSync } from "node:fs"; @@ -23,9 +22,13 @@ import { fileURLToPath } from "node:url"; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); -// Vars that MUST be supplied as deployment/CI secrets (never committed). Each is -// asserted to exist in the canonical name set below, so this list cannot silently -// drift from the schema. +const GITHUB_REPOSITORY = "BigSimmo/Database"; +const RAILWAY_TARGET = { + project: "5deaad0b-675a-4c13-978e-5ca2b5b877f9", + environment: "6aa16f7b-d3e8-4aa2-9854-ee9ead9fcbd4", +}; +const PROVIDER_QUERY_TIMEOUT_MS = 30_000; + export const EXPECTED_GITHUB_SECRETS = [ "SUPABASE_SERVICE_ROLE_KEY", "OPENAI_API_KEY", @@ -35,27 +38,61 @@ export const EXPECTED_GITHUB_SECRETS = [ "E2E_USER_PASSWORD", ]; +export const EXPECTED_GITHUB_VARIABLES = ["PROD_HEALTH_URL"]; + export const EXPECTED_RAILWAY_SECRETS = [ "SUPABASE_SERVICE_ROLE_KEY", "OPENAI_API_KEY", + "OPENAI_SAFETY_IDENTIFIER_SECRET", "RAG_QUERY_HASH_SECRET", "HEALTH_DEEP_PROBE_SECRET", ]; -/** Zod schema keys from env.ts: lines shaped like ` NAME: z.…`. */ +export const EXPECTED_RAILWAY_APP_VARIABLES = [ + "NEXT_PUBLIC_SUPABASE_URL", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY", + "SUPABASE_PROJECT_REF", + "SUPABASE_PROJECT_NAME", + ...EXPECTED_RAILWAY_SECRETS, +]; + +export const EXPECTED_RAILWAY_WORKER_VARIABLES = [ + "NEXT_PUBLIC_SUPABASE_URL", + "SUPABASE_PROJECT_REF", + "SUPABASE_PROJECT_NAME", + "SUPABASE_SERVICE_ROLE_KEY", + "OPENAI_API_KEY", +]; + +const EXPECTED_ENV_EXAMPLE_NAMES = [ + ...new Set([ + ...EXPECTED_RAILWAY_APP_VARIABLES, + ...EXPECTED_RAILWAY_WORKER_VARIABLES, + "SUPABASE_STAGING_PROJECT_REF", + "SUPABASE_STAGING_PROJECT_NAME", + "RAG_PROVIDER_MODE", + ]), +]; + +/** Zod schema keys from env.ts, including declarations where `.enum` begins on the next line. */ export function parseEnvSchemaNames(envTsText) { - return [...envTsText.matchAll(/^\s*([A-Z][A-Z0-9_]*)\s*:\s*z\./gm)].map((m) => m[1]); + return [...envTsText.matchAll(/^\s*([A-Z][A-Z0-9_]*)\s*:\s*z\s*\./gm)].map((match) => match[1]); } /** UPPER_SNAKE names referenced in check-ci-env.mjs (quoted literals + process.env.X). */ export function parseCiEnvNames(ciEnvText) { const names = new Set(); - for (const m of ciEnvText.matchAll(/"([A-Z][A-Z0-9_]*)"/g)) names.add(m[1]); - for (const m of ciEnvText.matchAll(/process\.env\.([A-Z][A-Z0-9_]*)/g)) names.add(m[1]); + for (const match of ciEnvText.matchAll(/"([A-Z][A-Z0-9_]*)"/g)) names.add(match[1]); + for (const match of ciEnvText.matchAll(/process\.env\.([A-Z][A-Z0-9_]*)/g)) names.add(match[1]); return [...names]; } -/** Pure diff of live secret names against expectations + the known-name universe. */ +/** Active or commented KEY= declarations from the committed example file. */ +export function parseEnvExampleNames(envExampleText) { + return [...envExampleText.matchAll(/^\s*#?\s*([A-Z][A-Z0-9_]*)\s*=/gm)].map((match) => match[1]); +} + +/** Pure diff of live config names against expectations + the known-name universe. */ export function computeParity({ canonical, liveNames, expectedSecrets }) { const canon = new Set(canonical); const live = new Set(liveNames); @@ -65,7 +102,27 @@ export function computeParity({ canonical, liveNames, expectedSecrets }) { }; } -/** Extract Railway variable names from the CLI's JSON object without exposing values. */ +export function githubListArgs(kind) { + if (kind !== "secret" && kind !== "variable") throw new Error(`Unsupported GitHub config kind: ${kind}`); + return [kind, "list", "--repo", GITHUB_REPOSITORY, "--json", "name"]; +} + +export function railwayVariableArgs(service) { + if (service !== "Database" && service !== "worker") throw new Error(`Unsupported Railway service: ${service}`); + return [ + "variable", + "list", + "--json", + "--project", + RAILWAY_TARGET.project, + "--environment", + RAILWAY_TARGET.environment, + "--service", + service, + ]; +} + +/** Extract Railway variable names from the CLI JSON object without retaining values. */ export function parseRailwayVariableNames(raw) { const parsed = JSON.parse(raw); if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") { @@ -74,72 +131,135 @@ export function parseRailwayVariableNames(raw) { return Object.keys(parsed); } -function ghSecretNames() { - const raw = execFileSync("gh", ["secret", "list", "--json", "name"], { encoding: "utf8" }); - return JSON.parse(raw).map((s) => s.name); +function githubNames(kind) { + const raw = execFileSync("gh", githubListArgs(kind), { + encoding: "utf8", + timeout: PROVIDER_QUERY_TIMEOUT_MS, + windowsHide: true, + }); + return JSON.parse(raw).map((entry) => entry.name); } -function railwayVarNames() { - const raw = execFileSync("railway", ["variable", "list", "--json"], { encoding: "utf8" }); +function railwayNames(service) { + const raw = execFileSync("railway", railwayVariableArgs(service), { + encoding: "utf8", + timeout: PROVIDER_QUERY_TIMEOUT_MS, + windowsHide: true, + }); return parseRailwayVariableNames(raw); } +function reportSource({ enabled, flag, label, getter, expected, canonical, problems }) { + if (!enabled) { + console.log(`(${label}: skipped β€” pass ${flag} to check; names only in output)`); + return; + } + + let liveNames; + try { + liveNames = getter(); + } catch (error) { + problems.push(`${label}: could not query (${error.message.split("\n")[0]})`); + return; + } + + const { missingSecrets, unknownLive } = computeParity({ canonical, liveNames, expectedSecrets: expected }); + console.log(`\n${label}: ${liveNames.length} names.`); + if (missingSecrets.length > 0) problems.push(`${label}: missing expected name(s): ${missingSecrets.join(", ")}`); + if (unknownLive.length > 0) { + console.log( + ` info: ${unknownLive.length} provider/workflow-managed name(s) are outside this checked contract: ${unknownLive.join(", ")}`, + ); + } +} + function main() { const useGh = process.argv.includes("--gh"); const useRailway = process.argv.includes("--railway"); const envTs = readFileSync(path.join(root, "src/lib/env.ts"), "utf8"); const ciEnv = readFileSync(path.join(root, "scripts/check-ci-env.mjs"), "utf8"); - const canonical = new Set([...parseEnvSchemaNames(envTs), ...parseCiEnvNames(ciEnv)]); - + const envExample = readFileSync(path.join(root, ".env.example"), "utf8"); + const envExampleNames = parseEnvExampleNames(envExample); + const canonical = [ + ...new Set([ + ...parseEnvSchemaNames(envTs), + ...parseCiEnvNames(ciEnv), + ...envExampleNames, + ...EXPECTED_GITHUB_VARIABLES, + ]), + ]; const problems = []; - // Self-consistency: every expected secret must be a name the app/CI actually knows. - const expectedSecrets = new Set([...EXPECTED_GITHUB_SECRETS, ...EXPECTED_RAILWAY_SECRETS]); - const unknownExpected = [...expectedSecrets].filter((name) => !canonical.has(name)); + const exampleSet = new Set(envExampleNames); + const missingFromExample = EXPECTED_ENV_EXAMPLE_NAMES.filter((name) => !exampleSet.has(name)); + if (missingFromExample.length > 0) { + problems.push(`Required config names missing from .env.example: ${missingFromExample.join(", ")}`); + } + + const expectedNames = new Set([ + ...EXPECTED_GITHUB_SECRETS, + ...EXPECTED_GITHUB_VARIABLES, + ...EXPECTED_RAILWAY_APP_VARIABLES, + ...EXPECTED_RAILWAY_WORKER_VARIABLES, + ]); + const unknownExpected = [...expectedNames].filter((name) => !canonical.includes(name)); if (unknownExpected.length > 0) { - problems.push( - `Expected-secret names not found in env.ts/check-ci-env (typo or drift): ${unknownExpected.join(", ")}`, - ); + problems.push(`Expected config names not found in the repository contract: ${unknownExpected.join(", ")}`); } - console.log(`Known env names: ${canonical.size} (env.ts schema + check-ci-env).`); + console.log(`Known config names: ${canonical.length} (env.ts + CI/ops config + .env.example).`); + console.log( + `Required .env.example names: ${EXPECTED_ENV_EXAMPLE_NAMES.length - missingFromExample.length}/${EXPECTED_ENV_EXAMPLE_NAMES.length}.`, + ); console.log(`Expected GitHub secrets: ${EXPECTED_GITHUB_SECRETS.join(", ")}`); - console.log(`Expected Railway secrets: ${EXPECTED_RAILWAY_SECRETS.join(", ")}`); - - for (const [flag, enabled, label, getter, sourceExpectedSecrets] of [ - ["--gh", useGh, "GitHub secrets", ghSecretNames, EXPECTED_GITHUB_SECRETS], - ["--railway", useRailway, "Railway variables", railwayVarNames, EXPECTED_RAILWAY_SECRETS], - ]) { - if (!enabled) { - console.log(`(${label}: skipped β€” pass ${flag} to check; names only, no values)`); - continue; - } - let liveNames; - try { - liveNames = getter(); - } catch (error) { - problems.push(`${label}: could not query (${error.message.split("\n")[0]})`); - continue; - } - const { missingSecrets, unknownLive } = computeParity({ - canonical: [...canonical], - liveNames, - expectedSecrets: sourceExpectedSecrets, - }); - console.log(`\n${label}: ${liveNames.length} names.`); - if (missingSecrets.length > 0) problems.push(`${label}: missing expected secret(s): ${missingSecrets.join(", ")}`); - if (unknownLive.length > 0) { - console.log(` ⚠ present but not in env.ts (possible stale/typo): ${unknownLive.join(", ")}`); - } - } + console.log(`Expected GitHub variables: ${EXPECTED_GITHUB_VARIABLES.join(", ")}`); + console.log(`Expected Railway app variables: ${EXPECTED_RAILWAY_APP_VARIABLES.join(", ")}`); + console.log(`Expected Railway worker variables: ${EXPECTED_RAILWAY_WORKER_VARIABLES.join(", ")}`); + + reportSource({ + enabled: useGh, + flag: "--gh", + label: "GitHub secrets", + getter: () => githubNames("secret"), + expected: EXPECTED_GITHUB_SECRETS, + canonical, + problems, + }); + reportSource({ + enabled: useGh, + flag: "--gh", + label: "GitHub variables", + getter: () => githubNames("variable"), + expected: EXPECTED_GITHUB_VARIABLES, + canonical, + problems, + }); + reportSource({ + enabled: useRailway, + flag: "--railway", + label: "Railway Database variables", + getter: () => railwayNames("Database"), + expected: EXPECTED_RAILWAY_APP_VARIABLES, + canonical, + problems, + }); + reportSource({ + enabled: useRailway, + flag: "--railway", + label: "Railway worker variables", + getter: () => railwayNames("worker"), + expected: EXPECTED_RAILWAY_WORKER_VARIABLES, + canonical, + problems, + }); if (problems.length > 0) { - console.error("\nEnv parity problems:"); - for (const p of problems) console.error(`- ${p}`); + console.error("\nEnvironment parity problems:"); + for (const problem of problems) console.error(`- ${problem}`); process.exit(1); } - console.log("\nEnv parity OK (names only; no values were read)."); + console.log("\nEnvironment parity OK (names only in output; provider values were not emitted or persisted)."); } const invokedDirectly = process.argv[1]?.endsWith("check-env-parity.mjs"); diff --git a/scripts/check-local-presence.mjs b/scripts/check-local-presence.mjs index 9f847b37c0..c4691a77ad 100644 --- a/scripts/check-local-presence.mjs +++ b/scripts/check-local-presence.mjs @@ -9,13 +9,14 @@ * * npm run check:local-presence * npm run check:local-presence -- --fill + * npm run check:local-presence -- --root C:\path\to\checkout [--fill] */ import { randomBytes } from "node:crypto"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; -const root = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); +const defaultRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); /** Local-only secrets this tool may generate into `.env.local`. */ export const FILLABLE_LOCAL_SECRETS = [ @@ -97,7 +98,7 @@ export function parseEnvFile(text) { return out; } -export function loadLocalEnv({ cwd = root, processEnv = process.env } = {}) { +export function loadLocalEnv({ cwd = defaultRoot, processEnv = process.env } = {}) { const fromFiles = {}; const filesPresent = []; for (const name of ENV_FILES) { @@ -241,6 +242,18 @@ export function generateLocalSecret(bytes = 32) { return randomBytes(bytes).toString("hex"); } +export function resolveTargetRoot(argv = process.argv.slice(2)) { + const index = argv.indexOf("--root"); + if (index === -1) return defaultRoot; + const value = argv[index + 1]; + if (!value || value.startsWith("--")) throw new Error("--root requires a checkout path"); + const resolved = path.resolve(value); + if (!existsSync(path.join(resolved, "package.json"))) { + throw new Error(`--root is not a repository checkout: ${resolved}`); + } + return resolved; +} + /** * Merge fillable gaps into `.env.local` text. Skips keys that already meet * minLength. For present-but-too-short keys, removes stale KEY=value lines @@ -304,7 +317,14 @@ function printPresenceReport({ filesPresent, assessment }) { function main() { const fill = process.argv.includes("--fill"); - const { merged, filesPresent } = loadLocalEnv(); + let targetRoot; + try { + targetRoot = resolveTargetRoot(); + } catch (error) { + console.error(`STOP: ${error.message}`); + process.exit(1); + } + const { merged, filesPresent } = loadLocalEnv({ cwd: targetRoot }); const assessment = assessLocalPresence(merged); printPresenceReport({ filesPresent, assessment }); @@ -336,7 +356,7 @@ function main() { return; } - const envLocalPath = path.join(root, ".env.local"); + const envLocalPath = path.join(targetRoot, ".env.local"); const existingText = existsSync(envLocalPath) ? readFileSync(envLocalPath, "utf8") : ""; const { text, filled } = mergeFillIntoEnvLocal(existingText, gaps); writeFileSync(envLocalPath, text, { encoding: "utf8", mode: 0o600 }); diff --git a/tests/local-presence.test.ts b/tests/local-presence.test.ts index dc11f8fb9b..7e8d2ab73f 100644 --- a/tests/local-presence.test.ts +++ b/tests/local-presence.test.ts @@ -8,9 +8,15 @@ import { mergeFillIntoEnvLocal, parseEnvFile, REPORT_ONLY_KEYS, + resolveTargetRoot, } from "../scripts/check-local-presence.mjs"; describe("check-local-presence", () => { + it("accepts an explicit checkout root for cross-worktree inspection", () => { + expect(resolveTargetRoot(["--root", process.cwd()])).toBe(process.cwd()); + expect(() => resolveTargetRoot(["--root"])).toThrow("--root requires a checkout path"); + }); + it("parses env assignments without exposing values in helpers", () => { const parsed = parseEnvFile( ["# comment", 'RAG_QUERY_HASH_SECRET="abc"', "OPENAI_API_KEY=xyz", "not valid", ""].join("\n"), diff --git a/tests/repo-hygiene.test.ts b/tests/repo-hygiene.test.ts index 515cc4ee5f..c66dda7d78 100644 --- a/tests/repo-hygiene.test.ts +++ b/tests/repo-hygiene.test.ts @@ -2,9 +2,15 @@ import { describe, expect, it } from "vitest"; import { computeParity, EXPECTED_GITHUB_SECRETS, + EXPECTED_GITHUB_VARIABLES, + EXPECTED_RAILWAY_APP_VARIABLES, EXPECTED_RAILWAY_SECRETS, + EXPECTED_RAILWAY_WORKER_VARIABLES, + githubListArgs, parseCiEnvNames, + parseEnvExampleNames, parseEnvSchemaNames, + railwayVariableArgs, } from "../scripts/check-env-parity.mjs"; import { hasCompletedCleanupReview, parseLedgerBranches } from "../scripts/sweep-branch-ledger.mjs"; @@ -15,6 +21,9 @@ describe("check-env-parity name parsing", () => { " NEXT_PUBLIC_SUPABASE_URL: z.string().url().optional(),", " SUPABASE_SERVICE_ROLE_KEY: z.string().optional(),", " OPENAI_MAX_OUTPUT_TOKENS: z.coerce.number().default(16000),", + " RAG_PERSIST_RAW_QUERY_TEXT: z", + ' .enum(["true", "false"])', + ' .default("false"),', " notAKey: 3,", "});", ].join("\n"); @@ -22,6 +31,7 @@ describe("check-env-parity name parsing", () => { expect(names).toContain("NEXT_PUBLIC_SUPABASE_URL"); expect(names).toContain("SUPABASE_SERVICE_ROLE_KEY"); expect(names).toContain("OPENAI_MAX_OUTPUT_TOKENS"); + expect(names).toContain("RAG_PERSIST_RAW_QUERY_TEXT"); expect(names).not.toContain("notAKey"); }); @@ -41,6 +51,13 @@ describe("check-env-parity name parsing", () => { expect(parity.unknownLive).toEqual(["LEFTOVER_OLD_KEY"]); }); + it("extracts active and documented optional names from .env.example-style text", () => { + const names = parseEnvExampleNames( + ["OPENAI_API_KEY=replace-with-key", "#OPENAI_SAFETY_IDENTIFIER_SECRET=", "# explanation"].join("\n"), + ); + expect(names).toEqual(["OPENAI_API_KEY", "OPENAI_SAFETY_IDENTIFIER_SECRET"]); + }); + it("keeps CI-only E2E credentials out of Railway expectations", () => { expect(EXPECTED_GITHUB_SECRETS).toEqual( expect.arrayContaining(["E2E_USER_EMAIL", "E2E_USER_PASSWORD", "HEALTH_DEEP_PROBE_SECRET"]), @@ -49,12 +66,53 @@ describe("check-env-parity name parsing", () => { expect.arrayContaining([ "SUPABASE_SERVICE_ROLE_KEY", "OPENAI_API_KEY", + "OPENAI_SAFETY_IDENTIFIER_SECRET", "RAG_QUERY_HASH_SECRET", "HEALTH_DEEP_PROBE_SECRET", ]), ); expect(EXPECTED_RAILWAY_SECRETS).not.toEqual(expect.arrayContaining(["E2E_USER_EMAIL", "E2E_USER_PASSWORD"])); }); + + it("covers app, worker, and scheduled-health configuration separately", () => { + expect(EXPECTED_RAILWAY_APP_VARIABLES).toEqual( + expect.arrayContaining([ + "NEXT_PUBLIC_SUPABASE_URL", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY", + "SUPABASE_PROJECT_REF", + "SUPABASE_PROJECT_NAME", + "OPENAI_SAFETY_IDENTIFIER_SECRET", + ]), + ); + expect(EXPECTED_RAILWAY_WORKER_VARIABLES).toEqual( + expect.arrayContaining([ + "NEXT_PUBLIC_SUPABASE_URL", + "SUPABASE_PROJECT_REF", + "SUPABASE_PROJECT_NAME", + "SUPABASE_SERVICE_ROLE_KEY", + "OPENAI_API_KEY", + ]), + ); + expect(EXPECTED_RAILWAY_WORKER_VARIABLES).not.toContain("HEALTH_DEEP_PROBE_SECRET"); + expect(EXPECTED_GITHUB_VARIABLES).toEqual(["PROD_HEALTH_URL"]); + }); + + it("pins GitHub and Railway reads to the intended repository and production services", () => { + expect(githubListArgs("secret")).toEqual( + expect.arrayContaining(["secret", "list", "--repo", "BigSimmo/Database", "--json", "name"]), + ); + expect(railwayVariableArgs("Database")).toEqual( + expect.arrayContaining([ + "--project", + "5deaad0b-675a-4c13-978e-5ca2b5b877f9", + "--environment", + "6aa16f7b-d3e8-4aa2-9854-ee9ead9fcbd4", + "--service", + "Database", + ]), + ); + expect(railwayVariableArgs("worker")).toContain("worker"); + }); }); describe("sweep-branch-ledger parsing", () => { From e90417484a2aa33e3dc224917ffd044be5c44a61 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:06:50 +0800 Subject: [PATCH 2/6] docs: record configuration reconciliation review --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index db89d3b156..8148e6ad03 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -1110,3 +1110,4 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-27 | `codex/phone-bottom-band-root-20260727` | `a8a72a43d` | CI follow-up review of calculator dock hide lifecycle | APPROVE. Hosted production Chromium exposed a fast-close race where effect cleanup could cancel the queued focus-latch reset, plus a paint-contract journey coupled to natural short-page geometry. The reset now survives rapid sheet teardown, actual input focus is asserted before hide, and explicit runway isolates the paint contract from the anti-clamp boundary tests. No P0-P3 finding remains. | Exact locked Next 16.2.11 / Playwright 1.61.1 production Chromium repeat PASS (20/20); `verify:cheap` PASS (393 files; 3519 passed / 2 skipped); no provider-backed checks. | | 2026-07-27 | `codex/phone-bottom-band-root-20260727` | `cc63ee90928d402eb9c5dd241e411ed88cc01f27` | Hosted Applications search hydration follow-up review | APPROVE. The final hosted Chromium failure was a test synchronization race: server-rendered content was visible before the controlled global-search input had its React onChange handler, so hydration could discard an immediate fill. Both shared-search tests now wait for the React handler and assert the accepted value before checking filtering. No product behavior changed and no P0-P3 finding remains. | Exact locked Next 16.2.11 / Playwright 1.61.1 production Chromium repeat PASS (20/20); `verify:cheap` PASS (25 gates; 393 files; 3519 passed / 2 skipped); no non-GitHub provider-backed checks. | | 2026-07-27 | `codex/phone-bottom-band-root-20260727` | `a4802b9373404a00549a3479d86340398e978cc8` | Automated review follow-up for phone viewport fallback layering | APPROVE. Verified the review finding and separated the baseline 100vh declarations from the supported 100svh override, while retaining the later 100dvh override as the preferred dynamic viewport size. This removes duplicate properties without changing the intended fallback order. The ledger date remains the Australia/Perth task completion date. No P0-P3 finding remains. | Focused viewport-shell static contract PASS (8/8); `git diff --check` PASS; prior full `verify:cheap` and hosted required CI were green before this CSS-only declaration-layering follow-up; no non-GitHub provider-backed checks. | +| 2026-07-27 | `codex/config-reconciliation-current-20260727` | `4400f59730fbd24efc5f4c54adda828506f3835b` | Protected-main review of #054 production configuration reconciliation | APPROVE. GitHub reads are repository-pinned; Railway reads are pinned to the live project, production environment and explicit app/worker services; each provider call has a 30-second bound; output is names-only even though Railway JSON is reduced from values in memory. Multiline Zod and `.env.example` drift are guarded. The correct primary checkout received only three generated gitignored local HMAC/probe values. No P0-P3 finding remains. Residual staging, webhook activation and legal/ZDR work remain #056, #025 and #053 rather than being overstated as complete. | Focused parity/local-presence 22/22 PASS; `verify:cheap` PASS (25 gates; 393 files; 3523 passed / 2 skipped); `verify:pr-local` PASS (same unit matrix + 36 offline RAG fixtures; build correctly skipped as unaffected); production-readiness READY (8 PASS, 2 checkout-file-location warnings); exact provider names-only GitHub/Railway parity PASS; Ops Digest active + latest schedule SUCCESS; Railway app/worker latest deploy SUCCESS; Supabase read-only cron/Vault-name proof; no OpenAI request or live RAG evaluation. | From ada836d167f6a03f2a6514d56d3aee6304c6276c Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:11:49 +0800 Subject: [PATCH 3/6] fix: persist cross-worktree local secret fills --- scripts/check-local-presence.mjs | 10 ++++++++-- tests/local-presence.test.ts | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/scripts/check-local-presence.mjs b/scripts/check-local-presence.mjs index c4691a77ad..ba8f239a94 100644 --- a/scripts/check-local-presence.mjs +++ b/scripts/check-local-presence.mjs @@ -117,6 +117,11 @@ export function loadLocalEnv({ cwd = defaultRoot, processEnv = process.env } = { return { merged, filesPresent, fromFiles }; } +/** Fill mode must persist file gaps even when the invoking shell has temporary overrides. */ +export function presenceEnvForMode({ fill, merged, fromFiles }) { + return fill ? fromFiles : merged; +} + export function extractUrlRef(url) { if (!isPresent(url)) return null; try { @@ -324,8 +329,9 @@ function main() { console.error(`STOP: ${error.message}`); process.exit(1); } - const { merged, filesPresent } = loadLocalEnv({ cwd: targetRoot }); - const assessment = assessLocalPresence(merged); + const { merged, filesPresent, fromFiles } = loadLocalEnv({ cwd: targetRoot }); + const assessment = assessLocalPresence(presenceEnvForMode({ fill, merged, fromFiles })); + assessment.identity = classifyProjectIdentity(merged); printPresenceReport({ filesPresent, assessment }); diff --git a/tests/local-presence.test.ts b/tests/local-presence.test.ts index 7e8d2ab73f..78a4e4c681 100644 --- a/tests/local-presence.test.ts +++ b/tests/local-presence.test.ts @@ -7,6 +7,7 @@ import { lengthBucket, mergeFillIntoEnvLocal, parseEnvFile, + presenceEnvForMode, REPORT_ONLY_KEYS, resolveTargetRoot, } from "../scripts/check-local-presence.mjs"; @@ -17,6 +18,20 @@ describe("check-local-presence", () => { expect(() => resolveTargetRoot(["--root"])).toThrow("--root requires a checkout path"); }); + it("does not let caller-only secrets suppress persistent --root fill gaps", () => { + const processOnly = Object.fromEntries( + FILLABLE_LOCAL_SECRETS.map(({ name }) => [name, "caller-only-secret-that-must-not-count-as-persisted"]), + ); + const merged = { ...processOnly }; + const fromFiles = {}; + + const reportAssessment = assessLocalPresence(presenceEnvForMode({ fill: false, merged, fromFiles })); + const fillAssessment = assessLocalPresence(presenceEnvForMode({ fill: true, merged, fromFiles })); + + expect(reportAssessment.fillable.every((row) => row.status === "ok")).toBe(true); + expect(fillAssessment.fillable.every((row) => row.status === "gap")).toBe(true); + }); + it("parses env assignments without exposing values in helpers", () => { const parsed = parseEnvFile( ["# comment", 'RAG_QUERY_HASH_SECRET="abc"', "OPENAI_API_KEY=xyz", "not valid", ""].join("\n"), From 8d81e3176e8012ebf484885190fe563fda4e12a4 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:12:13 +0800 Subject: [PATCH 4/6] docs: record configuration review follow-up --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 8148e6ad03..413be38f92 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -1111,3 +1111,4 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-27 | `codex/phone-bottom-band-root-20260727` | `cc63ee90928d402eb9c5dd241e411ed88cc01f27` | Hosted Applications search hydration follow-up review | APPROVE. The final hosted Chromium failure was a test synchronization race: server-rendered content was visible before the controlled global-search input had its React onChange handler, so hydration could discard an immediate fill. Both shared-search tests now wait for the React handler and assert the accepted value before checking filtering. No product behavior changed and no P0-P3 finding remains. | Exact locked Next 16.2.11 / Playwright 1.61.1 production Chromium repeat PASS (20/20); `verify:cheap` PASS (25 gates; 393 files; 3519 passed / 2 skipped); no non-GitHub provider-backed checks. | | 2026-07-27 | `codex/phone-bottom-band-root-20260727` | `a4802b9373404a00549a3479d86340398e978cc8` | Automated review follow-up for phone viewport fallback layering | APPROVE. Verified the review finding and separated the baseline 100vh declarations from the supported 100svh override, while retaining the later 100dvh override as the preferred dynamic viewport size. This removes duplicate properties without changing the intended fallback order. The ledger date remains the Australia/Perth task completion date. No P0-P3 finding remains. | Focused viewport-shell static contract PASS (8/8); `git diff --check` PASS; prior full `verify:cheap` and hosted required CI were green before this CSS-only declaration-layering follow-up; no non-GitHub provider-backed checks. | | 2026-07-27 | `codex/config-reconciliation-current-20260727` | `4400f59730fbd24efc5f4c54adda828506f3835b` | Protected-main review of #054 production configuration reconciliation | APPROVE. GitHub reads are repository-pinned; Railway reads are pinned to the live project, production environment and explicit app/worker services; each provider call has a 30-second bound; output is names-only even though Railway JSON is reduced from values in memory. Multiline Zod and `.env.example` drift are guarded. The correct primary checkout received only three generated gitignored local HMAC/probe values. No P0-P3 finding remains. Residual staging, webhook activation and legal/ZDR work remain #056, #025 and #053 rather than being overstated as complete. | Focused parity/local-presence 22/22 PASS; `verify:cheap` PASS (25 gates; 393 files; 3523 passed / 2 skipped); `verify:pr-local` PASS (same unit matrix + 36 offline RAG fixtures; build correctly skipped as unaffected); production-readiness READY (8 PASS, 2 checkout-file-location warnings); exact provider names-only GitHub/Railway parity PASS; Ops Digest active + latest schedule SUCCESS; Railway app/worker latest deploy SUCCESS; Supabase read-only cron/Vault-name proof; no OpenAI request or live RAG evaluation. | +| 2026-07-27 | PR #1271 / `codex/config-reconciliation-current-20260727` | `ada836d167f6a03f2a6514d56d3aee6304c6276c` | Automated-review follow-up for cross-worktree local fill persistence | APPROVE. The P2 was valid: caller-only process secrets could hide missing target-file values during `--root --fill`. Fill mode now computes persistent gaps from target env files while project identity still uses the merged file/process view; report mode retains its existing process override behavior. A dedicated contract proves all caller-only fillable values remain target-file gaps. No other P0-P3 finding remains. | Focused `tests/local-presence.test.ts` PASS (10/10); exact primary `check:local-presence -- --root C:\Dev\Apps\Database` PASS; Prettier + `git diff --check` PASS; earlier exact-tree `verify:cheap` and `verify:pr-local` remain the broad baseline; hosted required checks will rerun on this follow-up. | From 3321c1eb1f2d1ac4294caf40e09a63b74fe1f713 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:22:27 +0800 Subject: [PATCH 5/6] fix: validate local presence fill targets --- scripts/check-local-presence.mjs | 27 +++++++++++++++++++++------ tests/local-presence.test.ts | 29 +++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/scripts/check-local-presence.mjs b/scripts/check-local-presence.mjs index ba8f239a94..5180665ec5 100644 --- a/scripts/check-local-presence.mjs +++ b/scripts/check-local-presence.mjs @@ -17,6 +17,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; const defaultRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); +const EXPECTED_PACKAGE_NAME = "prompt-for-codex-medical-knowledge-base"; /** Local-only secrets this tool may generate into `.env.local`. */ export const FILLABLE_LOCAL_SECRETS = [ @@ -117,9 +118,14 @@ export function loadLocalEnv({ cwd = defaultRoot, processEnv = process.env } = { return { merged, filesPresent, fromFiles }; } -/** Fill mode must persist file gaps even when the invoking shell has temporary overrides. */ -export function presenceEnvForMode({ fill, merged, fromFiles }) { - return fill ? fromFiles : merged; +/** Fill mode uses file-only state for writable gaps but preserves merged report-only/identity truth. */ +export function assessPresenceForMode({ fill, merged, fromFiles }) { + const mergedAssessment = assessLocalPresence(merged); + if (!fill) return mergedAssessment; + return { + ...mergedAssessment, + fillable: assessLocalPresence(fromFiles).fillable, + }; } export function extractUrlRef(url) { @@ -253,9 +259,19 @@ export function resolveTargetRoot(argv = process.argv.slice(2)) { const value = argv[index + 1]; if (!value || value.startsWith("--")) throw new Error("--root requires a checkout path"); const resolved = path.resolve(value); - if (!existsSync(path.join(resolved, "package.json"))) { + const packagePath = path.join(resolved, "package.json"); + if (!existsSync(packagePath)) { throw new Error(`--root is not a repository checkout: ${resolved}`); } + let packageName; + try { + packageName = JSON.parse(readFileSync(packagePath, "utf8")).name; + } catch { + throw new Error(`--root has an unreadable package.json: ${resolved}`); + } + if (packageName !== EXPECTED_PACKAGE_NAME) { + throw new Error(`--root is not a Database checkout: ${resolved}`); + } return resolved; } @@ -330,8 +346,7 @@ function main() { process.exit(1); } const { merged, filesPresent, fromFiles } = loadLocalEnv({ cwd: targetRoot }); - const assessment = assessLocalPresence(presenceEnvForMode({ fill, merged, fromFiles })); - assessment.identity = classifyProjectIdentity(merged); + const assessment = assessPresenceForMode({ fill, merged, fromFiles }); printPresenceReport({ filesPresent, assessment }); diff --git a/tests/local-presence.test.ts b/tests/local-presence.test.ts index 78a4e4c681..8f01e40ec3 100644 --- a/tests/local-presence.test.ts +++ b/tests/local-presence.test.ts @@ -1,13 +1,16 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; import { describe, expect, it } from "vitest"; import { assessLocalPresence, + assessPresenceForMode, classifyProjectIdentity, FILLABLE_LOCAL_SECRETS, lengthBucket, mergeFillIntoEnvLocal, parseEnvFile, - presenceEnvForMode, REPORT_ONLY_KEYS, resolveTargetRoot, } from "../scripts/check-local-presence.mjs"; @@ -18,18 +21,32 @@ describe("check-local-presence", () => { expect(() => resolveTargetRoot(["--root"])).toThrow("--root requires a checkout path"); }); + it("rejects an unrelated Node project as a fill target", () => { + const unrelatedRoot = mkdtempSync(path.join(tmpdir(), "local-presence-unrelated-")); + try { + writeFileSync(path.join(unrelatedRoot, "package.json"), JSON.stringify({ name: "unrelated-project" })); + expect(() => resolveTargetRoot(["--root", unrelatedRoot])).toThrow("--root is not a Database checkout"); + } finally { + rmSync(unrelatedRoot, { recursive: true, force: true }); + } + }); + it("does not let caller-only secrets suppress persistent --root fill gaps", () => { - const processOnly = Object.fromEntries( - FILLABLE_LOCAL_SECRETS.map(({ name }) => [name, "caller-only-secret-that-must-not-count-as-persisted"]), - ); + const processOnly = { + ...Object.fromEntries( + FILLABLE_LOCAL_SECRETS.map(({ name }) => [name, "caller-only-secret-that-must-not-count-as-persisted"]), + ), + OPENAI_API_KEY: "caller-only-provider-key", + }; const merged = { ...processOnly }; const fromFiles = {}; - const reportAssessment = assessLocalPresence(presenceEnvForMode({ fill: false, merged, fromFiles })); - const fillAssessment = assessLocalPresence(presenceEnvForMode({ fill: true, merged, fromFiles })); + const reportAssessment = assessPresenceForMode({ fill: false, merged, fromFiles }); + const fillAssessment = assessPresenceForMode({ fill: true, merged, fromFiles }); expect(reportAssessment.fillable.every((row) => row.status === "ok")).toBe(true); expect(fillAssessment.fillable.every((row) => row.status === "gap")).toBe(true); + expect(fillAssessment.reportOnly.find((row) => row.name === "OPENAI_API_KEY")?.status).toBe("ok"); }); it("parses env assignments without exposing values in helpers", () => { From 2d698f80de0e3bbd0b4072fa8d21254898f6bcb9 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:23:18 +0800 Subject: [PATCH 6/6] docs: record local fill review follow-up --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 413be38f92..229f131b1d 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -1112,3 +1112,4 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-27 | `codex/phone-bottom-band-root-20260727` | `a4802b9373404a00549a3479d86340398e978cc8` | Automated review follow-up for phone viewport fallback layering | APPROVE. Verified the review finding and separated the baseline 100vh declarations from the supported 100svh override, while retaining the later 100dvh override as the preferred dynamic viewport size. This removes duplicate properties without changing the intended fallback order. The ledger date remains the Australia/Perth task completion date. No P0-P3 finding remains. | Focused viewport-shell static contract PASS (8/8); `git diff --check` PASS; prior full `verify:cheap` and hosted required CI were green before this CSS-only declaration-layering follow-up; no non-GitHub provider-backed checks. | | 2026-07-27 | `codex/config-reconciliation-current-20260727` | `4400f59730fbd24efc5f4c54adda828506f3835b` | Protected-main review of #054 production configuration reconciliation | APPROVE. GitHub reads are repository-pinned; Railway reads are pinned to the live project, production environment and explicit app/worker services; each provider call has a 30-second bound; output is names-only even though Railway JSON is reduced from values in memory. Multiline Zod and `.env.example` drift are guarded. The correct primary checkout received only three generated gitignored local HMAC/probe values. No P0-P3 finding remains. Residual staging, webhook activation and legal/ZDR work remain #056, #025 and #053 rather than being overstated as complete. | Focused parity/local-presence 22/22 PASS; `verify:cheap` PASS (25 gates; 393 files; 3523 passed / 2 skipped); `verify:pr-local` PASS (same unit matrix + 36 offline RAG fixtures; build correctly skipped as unaffected); production-readiness READY (8 PASS, 2 checkout-file-location warnings); exact provider names-only GitHub/Railway parity PASS; Ops Digest active + latest schedule SUCCESS; Railway app/worker latest deploy SUCCESS; Supabase read-only cron/Vault-name proof; no OpenAI request or live RAG evaluation. | | 2026-07-27 | PR #1271 / `codex/config-reconciliation-current-20260727` | `ada836d167f6a03f2a6514d56d3aee6304c6276c` | Automated-review follow-up for cross-worktree local fill persistence | APPROVE. The P2 was valid: caller-only process secrets could hide missing target-file values during `--root --fill`. Fill mode now computes persistent gaps from target env files while project identity still uses the merged file/process view; report mode retains its existing process override behavior. A dedicated contract proves all caller-only fillable values remain target-file gaps. No other P0-P3 finding remains. | Focused `tests/local-presence.test.ts` PASS (10/10); exact primary `check:local-presence -- --root C:\Dev\Apps\Database` PASS; Prettier + `git diff --check` PASS; earlier exact-tree `verify:cheap` and `verify:pr-local` remain the broad baseline; hosted required checks will rerun on this follow-up. | +| 2026-07-27 | PR #1271 / `codex/config-reconciliation-current-20260727` | `3321c1eb1f2d1ac4294caf40e09a63b74fe1f713` | Second automated-review follow-up for safe local fill targeting/reporting | APPROVE. Two valid P2 findings were fixed: an explicit root must now carry the Database package identity before any fill, and fill mode applies file-only state solely to writable HMAC/probe gaps while preserving merged process/file truth for report-only provider rows and project identity. Tests cover an unrelated package root, caller-only fillable values, and caller-only provider reporting. Zero unresolved local findings remain. | Focused `tests/local-presence.test.ts` PASS (11/11); exact primary presence PASS; unrelated-root CLI rejection PASS; Prettier PASS; hosted required checks and automated review must rerun on this head before merge. |