From 81d1bca9b39f4d4c3b226489e196f2c267dd5734 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 25 Jul 2026 14:12:53 +0000 Subject: [PATCH 1/2] fix: make reconciliation tooling deterministic and multi-task safe Inject a fixture repositoryRoot into preflight collection so the contract no longer scales with the live worktree farm (#067). Add a cooperative primary checkout write lease (#077) and a secret-safe atomic reconciliation evidence pack (#078), wired into lifecycle planning and the outstanding-issues ledger. Co-authored-by: BigSimmo --- docs/outstanding-issues.md | 69 ++-- docs/process-hardening.md | 10 +- docs/productivity-workflows.md | 8 +- docs/reconciliation-playbook.md | 15 + package.json | 3 + scripts/primary-checkout-lease.mjs | 403 +++++++++++++++++++++ scripts/productivity-core.mjs | 19 +- scripts/reconciliation-evidence-pack.mjs | 276 ++++++++++++++ scripts/reconciliation-preflight.mjs | 31 +- tests/primary-checkout-lease.test.ts | 187 ++++++++++ tests/productivity-workflow.test.ts | 12 + tests/reconciliation-evidence-pack.test.ts | 155 ++++++++ tests/reconciliation-preflight.test.ts | 88 ++++- 13 files changed, 1217 insertions(+), 59 deletions(-) create mode 100644 scripts/primary-checkout-lease.mjs create mode 100644 scripts/reconciliation-evidence-pack.mjs create mode 100644 tests/primary-checkout-lease.test.ts create mode 100644 tests/reconciliation-evidence-pack.test.ts diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index 1d424f49e..530963869 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -52,39 +52,36 @@ removed after current-main verification; it is not missing recommended work. | ----: | ---------------------- | -------- | --------------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 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 | `#067` | A3 | High ΓÇö test reliability | Next flake-hardening window | 1ΓÇô2 hours | Reproduce the load-sensitive reconciliation-preflight subprocess timeout, instrument its lifecycle, and make the smallest deterministic harness fix. Do not raise the global timeout or bypass the shared heavy-test lock without causal proof. | -| 4 | `#030`, `#075` | A2 | High ΓÇö search correctness | Decision-ready | 2ΓÇô4 hours each | Handle as separate PRs: #030 requires distinct source identities; #075 reproduces more than 1,000 labels and adds bounded pagination. Run focused contracts and `verify:cheap`; stop before alias, retrieval, or ranking changes without protected evidence. | -| 5 | `#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. | -| 6 | `#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. | -| 7 | `#054` | A2 | Standard locally; Operator hosted | Local safety identifier now; hosted next approved window | 15ΓÇô30 min local; 1ΓÇô2 hours hosted | Presence-check and fill confirmed secret/config gaps with distinct per-environment values. Never record values. Require clean readiness/secret checks; stop on ambiguous environment or project identity. | -| 8 | `#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. | -| 9 | `#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. | -| 10 | `#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. | -| 11 | `#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. | -| 12 | `#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. | -| 13 | `#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. | -| 14 | `#064` | A2 | High ΓÇö frontend/browser | After higher-acuity local fixes; before the release UI gate | 4ΓÇô8 hours | Preserve the isolated dirty formulation/contrast patch, reconcile its intent against current `main`, and run focused Playwright plus `verify:ui`. Stop rather than overwriting unrelated work or weakening access-control assertions. | -| 15 | `#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. | -| 16 | `#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. | -| 17 | `#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. | -| 18 | `#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. | -| 19 | `#007` | A3 | Operator decision + Standard frontend | When product chooses canonical Tools route | 15ΓÇô30 min decision; 0.5 day | Align navigation, redirect, sitemap, and reachability around one entry point. Stop while the standalone page has an unresolved requirement. | -| 20 | `#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. | -| 21 | `#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. | -| 22 | `#024` | A3 | High ΓÇö Next.js/Playwright/WebKit | After `#051`/`#023` or real Safari reproduction | 0.5ΓÇô1 day | Distinguish test interception from a Safari defect. Apply test-only correction only with a discriminating repro; keep access-control assertions meaningful. | -| 23 | `#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. | -| 24 | `#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. | -| 25 | `#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. | -| 26 | `#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. | -| 27 | `#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. | -| 28 | `#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. | -| 29 | `#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. | -| 30 | `#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. | -| 31 | `#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. | -| 33 | `#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. | -| 34 | `#077` | A3 | High ΓÇö workflow safety | Next workflow-hardening window | 1ΓÇô2 hours | Add a cooperative primary-checkout write lease/check that prevents a second task from writing, switching, or synchronizing the canonical checkout while another owner or dirty state exists. Keep read-only work and independent feature worktrees unblocked. | -| 35 | `#078` | A3 | Standard ΓÇö reconciliation tooling | After `#067`; before another broad reconciliation | 2ΓÇô4 hours | Generate one deterministic, secret-safe evidence pack from the reconciliation lifecycle: disposition rows, operation markers, archive refs, bundle verification, hashes, worktree counts, and local/base equality. Never fetch, call providers, or delete implicitly. | -| 36 | `#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. | +| 3 | `#030`, `#075` | A2 | High ΓÇö search correctness | Decision-ready | 2ΓÇô4 hours each | Handle as separate PRs: #030 requires distinct source identities; #075 reproduces more than 1,000 labels and adds bounded pagination. Run focused contracts and `verify:cheap`; stop before alias, retrieval, or ranking changes without protected evidence. | +| 4 | `#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. | +| 5 | `#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 | `#054` | A2 | Standard locally; Operator hosted | Local safety identifier now; hosted next approved window | 15ΓÇô30 min local; 1ΓÇô2 hours hosted | Presence-check and fill confirmed secret/config gaps with distinct per-environment values. Never record values. Require clean readiness/secret checks; stop on ambiguous environment or project identity. | +| 7 | `#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. | +| 8 | `#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. | +| 9 | `#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. | +| 10 | `#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. | +| 11 | `#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. | +| 12 | `#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. | +| 13 | `#064` | A2 | High ΓÇö frontend/browser | After higher-acuity local fixes; before the release UI gate | 4ΓÇô8 hours | Preserve the isolated dirty formulation/contrast patch, reconcile its intent against current `main`, and run focused Playwright plus `verify:ui`. Stop rather than overwriting unrelated work or weakening access-control assertions. | +| 14 | `#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. | +| 15 | `#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. | +| 16 | `#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. | +| 17 | `#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. | +| 18 | `#007` | A3 | Operator decision + Standard frontend | When product chooses canonical Tools route | 15ΓÇô30 min decision; 0.5 day | Align navigation, redirect, sitemap, and reachability around one entry point. Stop while the standalone page has an unresolved requirement. | +| 19 | `#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. | +| 20 | `#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. | +| 21 | `#024` | A3 | High ΓÇö Next.js/Playwright/WebKit | After `#051`/`#023` or real Safari reproduction | 0.5ΓÇô1 day | Distinguish test interception from a Safari defect. Apply test-only correction only with a discriminating repro; keep access-control assertions meaningful. | +| 22 | `#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. | +| 23 | `#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. | +| 24 | `#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. | +| 25 | `#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. | +| 26 | `#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. | +| 27 | `#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. | +| 28 | `#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. | +| 29 | `#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. | +| 30 | `#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. | +| 31 | `#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. | +| 32 | `#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. | @@ -102,7 +99,6 @@ removed after current-main verification; it is not missing recommended work. | #064 | P2 | task | Reconcile the preserved browser and contrast patch | **Outcome:** the isolated dirty formulation/contrast patch is safely landed or explicitly dispositioned. **Next:** rebase its intent against current `main` without overwriting the worktree, then run focused Playwright coverage and `verify:ui`. **Success:** intended disabled/contrast behavior is accessible, browser assertions remain meaningful, and unrelated work is preserved. **Stop:** do not discard or auto-merge the dirty worktree; pause on ambiguous ownership or scope. | `agent/formulation-disabled-contrast`; session 2026-07-24 | 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 | | #066 | P2 | task | Land and prove the streamlined six-item sidebar | **Landed/proven on feature branch (not yet on `main`).** PR #1174 tip; current `origin/main` was merged into the feature branch. Six-item rail: Answer, Documents, Services, Medications, Factsheets, Tools (Favourites in Your library). **Remaining:** merge PR #1174 and prove the exact content SHA is on `origin/main`. **Stop:** do not merge without explicit ask; no provider calls. | feature `cursor/sidebar-six-item-land-cfa0`; PR #1174; session 2026-07-25 | 2026-07-24 | -| #067 | P2 | issue | Reconciliation preflight test times out under full-suite load | **Outcome:** the reconciliation preflight subprocess test is deterministic under the repository's serialized heavy-test workflow. During PR #1119 validation, the 30-second test timeout occurred twice under loaded full-suite execution, while the isolated file passed 5/5 and a separate `verify:cheap` full suite passed. **Next:** reproduce with timing around subprocess startup, output and teardown, then fix the smallest proven harness lifecycle cause. **Success:** repeated focused and full-suite runs complete without extending the global timeout. **Stop:** do not hide the cause by raising broad timeouts, adding retries, or bypassing the shared test lock. | `tests/reconciliation-preflight.test.ts`; PR #1119 validation; session 2026-07-24 | 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 | @@ -135,8 +131,6 @@ removed after current-main verification; it is not missing recommended work. | #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 | -| #077 | P2 | issue | Concurrent tasks can re-dirty the canonical primary checkout | **Outcome:** `C:\Dev\Apps\Database` remains a clean synchronization target while write work happens in task-owned worktrees. **Next:** add a cooperative owner/lease check to task-start and lifecycle transitions; before a primary write, branch switch, fast-forward, or cleanup, report owner, dirty state, and Git operation markers and fail closed on an active owner. Include stale-lease recovery. **Success:** a focused concurrency test refuses a second primary writer while read-only commands and separate worktrees continue normally. **Stop:** do not add an OS-wide lock, kill processes, discard existing dirty files, or serialize independent feature worktrees. | primary re-dirtied by another active task immediately after reconciliation proof; session 2026-07-24 | 2026-07-24 | -| #078 | P3 | task | Generate a deterministic reconciliation evidence pack | **Outcome:** one report-only command produces the final local evidence now assembled manually. **Next:** extend the reconciliation lifecycle with an explicit output path and include the frozen base/HEAD, per-worktree dispositions, operation markers resolved through Git, archive refs, bundle path/size/SHA-256/verify result, retained worktree count, and local/base tree equality. Accept remote PR state only as explicit approved input. **Success:** fixture tests prove deterministic output and redaction; an interrupted run leaves no false completion record. **Stop:** never fetch, call GitHub/providers, inspect secret values, mutate refs, or delete work implicitly. | `scripts/reconciliation-preflight.mjs`; `docs/reconciliation-playbook.md`; session 2026-07-24 | 2026-07-24 | | #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 | ## Resolved / archive @@ -145,6 +139,9 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th | ID | Type | Summary | Outcome | Resolved | | ---- | ----- | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| #067 | issue | Reconciliation preflight test times out under full-suite load | Fixture-injected repositoryRoot makes the preflight contract independent of the live worktree farm; repeated focused runs stay well under the 30s budget without timeout bumps or lock bypass. | 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 | | #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. | 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 | diff --git a/docs/process-hardening.md b/docs/process-hardening.md index 2466a10d4..d99c73c7b 100644 --- a/docs/process-hardening.md +++ b/docs/process-hardening.md @@ -12,8 +12,14 @@ The reusable procedure is [`docs/reconciliation-playbook.md`](reconciliation-pla cleanup. It uses cached Git refs, never fetches, and reports primary/worktree dirty state, detached worktrees, ahead/behind counts, and operation markers. Add `--include-processes` only when ownership could block cleanup; that path emits metadata/counts and never raw command lines. -- `workflow:lifecycle -- --phase reconcile` selects the preflight locally and lists remote fetch as - a separate approval-required action. +- `node scripts/reconciliation-evidence-pack.mjs --output ` writes one deterministic secret-safe + local evidence pack (atomic) covering dispositions, markers, archive refs, bundle verification, + hashes, worktree counts, and local/base tree equality without fetching or deleting. +- `node scripts/primary-checkout-lease.mjs --check` is the cooperative primary-write gate: refuse a + second primary writer while another live owner or dirty/operation state exists; keep read-only + work and independent feature worktrees unblocked. +- `workflow:lifecycle -- --phase reconcile` selects the preflight and evidence pack locally and lists remote fetch as + a separate approval-required action. `start`/`cleanup` select the primary-checkout lease check. - Candidate filtering is cheap-first: owner/open-PR/review-ledger/ancestry before patch comparison; `merge-tree` remains a last resort. This avoids repeating the slow all-ref sweep that dominated the historical reconciliation. diff --git a/docs/productivity-workflows.md b/docs/productivity-workflows.md index bdd263559..8624e7bce 100644 --- a/docs/productivity-workflows.md +++ b/docs/productivity-workflows.md @@ -24,9 +24,13 @@ The repository exposes seven offline-first workflow planners. Each planner inspe - Use `-- --files pathA,pathB` to plan an explicit proposed change before editing. - Use `workflow:triage -- --log ` to classify a captured failure. - Use lifecycle phase `reconcile` for broad multi-worktree work. It selects the report-only - `node scripts/reconciliation-preflight.mjs` locally and keeps `git fetch --prune origin` approval-gated. Add + `node scripts/reconciliation-preflight.mjs` and + `node scripts/reconciliation-evidence-pack.mjs --output .local/reconciliation-evidence/pack.json` + locally and keeps `git fetch --prune origin` approval-gated. Add `--include-processes` to the preflight only when process ownership may block cleanup; it never - serializes raw command lines. + serializes raw command lines. Lifecycle `start`/`cleanup` select + `node scripts/primary-checkout-lease.mjs --check` so primary writes fail closed under another + owner or dirty/operation state without blocking read-only or feature worktrees. The existing shared `workflow:run`, `workflow:status`, `workflow:verify`, `workflow:deps`, `workflow:clean-state`, `workflow:export`, and `workflow:handoff` commands now resolve their shared implementation through the repository's Git common directory. This keeps them portable in linked and detached Codex worktrees. Set `CODEX_LOCAL_WORKFLOW_ROOT` only when the shared tools live somewhere non-standard. diff --git a/docs/reconciliation-playbook.md b/docs/reconciliation-playbook.md index 8d7c4abaf..100679891 100644 --- a/docs/reconciliation-playbook.md +++ b/docs/reconciliation-playbook.md @@ -9,12 +9,27 @@ focused verification path. ```powershell npm run workflow:lifecycle -- --phase reconcile --write-evidence node scripts/reconciliation-preflight.mjs +node scripts/reconciliation-evidence-pack.mjs --output .local/reconciliation-evidence/pack.json ``` The preflight is local, read-only, and cached-ref-only. It inventories the primary checkout, worktrees, dirty entries, detached state, ahead/behind counts, and Git operation markers. It never fetches, scans providers, deletes anything, or treats a preserved branch as approved. +The evidence pack writes one deterministic JSON report (atomic rename) with frozen base/HEAD, +per-worktree dispositions, operation markers, archive refs, optional bundle path/size/SHA-256/verify, +retained worktree counts, and local/base tree equality. Secrets are redacted; interrupted runs leave +only a `.partial` file, never a false completion record at the final path. + +Before any primary checkout write, branch switch, fast-forward, or cleanup: + +```powershell +node scripts/primary-checkout-lease.mjs --check +``` + +That cooperative lease refuses a second primary writer while another live owner or dirty/operation +state exists. Read-only commands and independent feature worktrees stay unblocked. + When process ownership may block cleanup, add the explicit process check: ```powershell diff --git a/package.json b/package.json index f44e49405..5ddde068f 100644 --- a/package.json +++ b/package.json @@ -180,6 +180,9 @@ "workflow:rag-lab": "node scripts/productivity-workflow.mjs rag-lab", "workflow:operator-closeout": "node scripts/productivity-workflow.mjs operator-closeout", "workflow:lifecycle": "node scripts/productivity-workflow.mjs lifecycle", + "reconciliation:preflight": "node scripts/reconciliation-preflight.mjs", + "reconciliation:evidence-pack": "node scripts/reconciliation-evidence-pack.mjs", + "check:primary-checkout-lease": "node scripts/primary-checkout-lease.mjs --check", "skills": "node scripts/list-database-skills.mjs", "skills:sync": "node scripts/sync-skills.mjs", "check:skills": "node scripts/list-database-skills.mjs --check", diff --git a/scripts/primary-checkout-lease.mjs b/scripts/primary-checkout-lease.mjs new file mode 100644 index 000000000..f68343a29 --- /dev/null +++ b/scripts/primary-checkout-lease.mjs @@ -0,0 +1,403 @@ +#!/usr/bin/env node + +/** + * Cooperative primary-checkout write lease (#077). + * + * Prevents a second task from writing, switching, or synchronizing the + * canonical primary checkout while another live owner holds the lease, or while + * the primary is dirty / mid-operation. Read-only inspection and independent + * feature worktrees do not acquire this lease and stay unblocked. + * + * This is intentionally not an OS-wide lock and never kills processes or discards work. + */ + +import { randomUUID } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { execFileSync } from "node:child_process"; +import { redactSensitiveText } from "./sensitive-text.mjs"; +import { reconciliationPreflightInternals } from "./reconciliation-preflight.mjs"; + +const defaultRepositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const tokenEnvironmentKey = "CLINICAL_KB_PRIMARY_LEASE_TOKEN"; +const pathEnvironmentKey = "CLINICAL_KB_PRIMARY_LEASE_PATH"; +const { operationMarkers, normalizePath } = reconciliationPreflightInternals; + +function processIsAlive(pid) { + if (!Number.isInteger(pid) || pid <= 0) return false; + try { + process.kill(pid, 0); + return true; + } catch (error) { + return error?.code === "EPERM"; + } +} + +function git(args, cwd, { allowFailure = false } = {}) { + try { + return execFileSync("git", args, { + cwd, + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }).trim(); + } catch (error) { + if (allowFailure) return ""; + throw error; + } +} + +export function resolvePrimaryCheckoutPath(repositoryRoot = defaultRepositoryRoot) { + const porcelain = git(["worktree", "list", "--porcelain"], repositoryRoot, { allowFailure: true }); + const match = porcelain.match(/^worktree (.+)$/m); + if (!match) return path.resolve(repositoryRoot); + return path.resolve(match[1]); +} + +function resolveGitCommonDirectory(primaryPath) { + const common = git(["rev-parse", "--path-format=absolute", "--git-common-dir"], primaryPath); + if (!common) throw new Error("Could not resolve the shared Git directory for the primary-checkout lease."); + return path.resolve(common); +} + +function leaseFilePath({ gitCommonDirectory, baseDirectory }) { + if (baseDirectory) { + mkdirSync(baseDirectory, { recursive: true }); + return path.join(baseDirectory, "primary-checkout-lease.json"); + } + return path.join(gitCommonDirectory, "clinical-kb-primary-checkout-lease.json"); +} + +function readLease(leasePath) { + try { + return JSON.parse(readFileSync(leasePath, "utf8")); + } catch { + return null; + } +} + +export function inspectPrimaryCheckoutState(primaryPath) { + const resolved = path.resolve(primaryPath); + const status = git(["status", "--porcelain=v1", "--untracked-files=normal"], resolved, { allowFailure: true }); + const gitDirectory = git(["rev-parse", "--path-format=absolute", "--git-dir"], resolved, { allowFailure: true }); + const head = git(["rev-parse", "HEAD"], resolved, { allowFailure: true }) || null; + const branch = git(["branch", "--show-current"], resolved, { allowFailure: true }) || null; + const statusEntries = status ? status.split(/\r?\n/).filter(Boolean).length : status === "" ? 0 : null; + const operations = + gitDirectory && existsSync(gitDirectory) + ? operationMarkers.filter((marker) => existsSync(path.join(gitDirectory, marker))) + : []; + return { + path: resolved, + head, + branch: branch || null, + statusEntries, + dirty: typeof statusEntries === "number" ? statusEntries > 0 : true, + operations, + inspectionFailed: statusEntries === null || !gitDirectory, + }; +} + +function ownerIsLive(owner) { + return Boolean(owner && processIsAlive(owner.pid)); +} + +/** + * @param {{ + * primaryPath?: string, + * repositoryRoot?: string, + * baseDirectory?: string, + * environment?: Record, + * processId?: number, + * }} [options] + */ +export function inspectPrimaryCheckoutLease({ + primaryPath, + repositoryRoot = defaultRepositoryRoot, + baseDirectory, + environment = process.env, + processId = process.pid, +} = {}) { + const resolvedPrimary = path.resolve(primaryPath ?? resolvePrimaryCheckoutPath(repositoryRoot)); + const gitCommonDirectory = resolveGitCommonDirectory(resolvedPrimary); + const leasePath = leaseFilePath({ gitCommonDirectory, baseDirectory }); + const primary = inspectPrimaryCheckoutState(resolvedPrimary); + const owner = readLease(leasePath); + const inheritedToken = environment[tokenEnvironmentKey]; + const inheritedPath = environment[pathEnvironmentKey]; + const sameOwner = + Boolean(inheritedToken) && + normalizePath(inheritedPath) === normalizePath(leasePath) && + owner?.token === inheritedToken && + ownerIsLive(owner); + const liveForeignOwner = Boolean(owner && ownerIsLive(owner) && !sameOwner); + const staleOwner = Boolean(owner && !ownerIsLive(owner)); + const blockers = []; + + if (primary.inspectionFailed) { + blockers.push({ + code: "primary-inspection-failed", + detail: "The primary checkout could not be inspected completely.", + }); + } + if (primary.dirty) { + blockers.push({ + code: "primary-dirty", + detail: "The primary checkout has tracked or untracked changes.", + }); + } + if (primary.operations.length > 0) { + blockers.push({ + code: "active-git-operation", + detail: `Active Git operation marker(s): ${primary.operations.join(", ")}.`, + }); + } + if (liveForeignOwner) { + blockers.push({ + code: "lease-held", + detail: `Primary write lease held by PID ${owner.pid} (${redactSensitiveText(owner.purpose ?? "unknown")}).`, + }); + } + + return { + primaryPath: resolvedPrimary, + leasePath, + primary, + owner: owner + ? { + ...owner, + purpose: redactSensitiveText(owner.purpose ?? ""), + alive: ownerIsLive(owner), + stale: staleOwner, + } + : null, + sameOwner, + writeAllowed: blockers.length === 0 || sameOwner, + readOnlyAllowed: true, + featureWorktreesUnaffected: true, + blockers, + inspectedByPid: processId, + }; +} + +/** + * @param {{ + * primaryPath?: string, + * repositoryRoot?: string, + * baseDirectory?: string, + * environment?: Record, + * processId?: number, + * ownerLabel?: string, + * purpose?: string, + * }} [options] + */ +export function acquirePrimaryCheckoutLease({ + primaryPath, + repositoryRoot = defaultRepositoryRoot, + baseDirectory, + environment = process.env, + processId = process.pid, + ownerLabel = "task", + purpose = "primary-write", +} = {}) { + const inspection = inspectPrimaryCheckoutLease({ + primaryPath, + repositoryRoot, + baseDirectory, + environment, + processId, + }); + + if (inspection.sameOwner && inspection.owner) { + return { + path: inspection.leasePath, + owner: inspection.owner, + reentrant: true, + environment: { ...environment }, + release() {}, + }; + } + + if (inspection.blockers.some((item) => item.code !== "lease-held")) { + const detail = inspection.blockers.map((item) => item.detail).join(" "); + throw new Error(`Primary checkout write refused: ${detail}`); + } + + if (inspection.owner && inspection.owner.alive) { + throw new Error( + `Primary checkout write refused: lease held by PID ${inspection.owner.pid} (${redactSensitiveText(inspection.owner.purpose ?? "unknown")}).`, + ); + } + + if (inspection.owner && !inspection.owner.alive && existsSync(inspection.leasePath)) { + rmSync(inspection.leasePath, { force: true }); + } + + const token = randomUUID(); + const owner = { + pid: processId, + token, + ownerLabel: redactSensitiveText(ownerLabel), + purpose: redactSensitiveText(purpose), + primaryPath: inspection.primaryPath, + startedAt: new Date().toISOString(), + }; + writeFileSync(inspection.leasePath, `${JSON.stringify(owner, null, 2)}\n`, "utf8"); + + let released = false; + return { + path: inspection.leasePath, + owner, + reentrant: false, + environment: { + ...environment, + [tokenEnvironmentKey]: token, + [pathEnvironmentKey]: inspection.leasePath, + }, + release() { + if (released) return; + released = true; + const current = readLease(inspection.leasePath); + if (current?.token === token) rmSync(inspection.leasePath, { force: true }); + }, + }; +} + +export function assertPrimaryWriteAllowed(options = {}) { + const inspection = inspectPrimaryCheckoutLease(options); + if (!inspection.writeAllowed) { + const detail = inspection.blockers.map((item) => `${item.code}: ${item.detail}`).join("; "); + throw new Error(`Primary checkout write refused (${detail})`); + } + return inspection; +} + +function releasePrimaryCheckoutLeaseByToken({ primaryPath, repositoryRoot, baseDirectory, token }) { + const inspection = inspectPrimaryCheckoutLease({ primaryPath, repositoryRoot, baseDirectory, environment: {} }); + const current = readLease(inspection.leasePath); + if (!current) return { released: false, detail: "no-lease" }; + if (current.token !== token) throw new Error("Primary checkout lease token does not match the active owner."); + rmSync(inspection.leasePath, { force: true }); + return { released: true, path: inspection.leasePath }; +} + +function parseArgs(argv) { + const options = { + check: false, + acquire: false, + release: false, + json: false, + ownerLabel: "task", + purpose: "primary-write", + primaryPath: undefined, + token: undefined, + help: false, + }; + for (let index = 0; index < argv.length; index += 1) { + const token = argv[index]; + const next = () => { + const value = argv[index + 1]; + if (!value || value.startsWith("-")) throw new Error(`Missing value for ${token}`); + index += 1; + return value; + }; + if (token === "--check") options.check = true; + else if (token === "--acquire") options.acquire = true; + else if (token === "--release") options.release = true; + else if (token === "--json") options.json = true; + else if (token === "--owner") options.ownerLabel = next(); + else if (token === "--purpose") options.purpose = next(); + else if (token === "--primary") options.primaryPath = next(); + else if (token === "--token") options.token = next(); + else if (token === "--help" || token === "-h") options.help = true; + else throw new Error(`Unknown option: ${token}`); + } + const modes = [options.check, options.acquire, options.release].filter(Boolean).length; + if (!options.help && modes === 0) options.check = true; + if (!options.help && modes > 1) throw new Error("Use only one of --check, --acquire, or --release."); + return options; +} + +function main() { + const options = parseArgs(process.argv.slice(2)); + if (options.help) { + console.log( + "Usage: node scripts/primary-checkout-lease.mjs [--check|--acquire|--release] [--json] [--primary ] [--owner