diff --git a/AGENTS.md b/AGENTS.md index 6316e00ceb..fde46d9522 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -143,19 +143,16 @@ Read and follow [External skill precedence](docs/agents/native-startup/external- # Supabase project safety - This repo targets the live Supabase project `PsychSift Production`. -- **No database change without the owner's explicit permission.** No AI tool, script or automation may change the live database or its settings unless the owner has said yes in words to that specific change. This covers migrations reaching `main`, SQL run against live, roles, grants and RLS policies, Supabase project and dashboard settings (auth, storage, API, network, backups, extensions, branching, integrations), database passwords, keys and access tokens, `supabase/drift-allowlist.json` entries, migration history repair, and resealing migrations (`npm run migrations:reseal`, `ALLOW_MIGRATION_RESEAL`, or any edit to an existing entry in `supabase/applied-migration-hashes.json`). Drafting a migration in an unmerged PR is allowed; a blanket or earlier yes does not cover a later change. See [the decision](docs/decisions/2026-10-15-database-changes-need-owner-permission.md). +- **No database change without the owner's explicit permission.** No AI tool, script or automation may change the live database or its settings unless the owner has said yes in words to that specific change. This covers migrations reaching `main`, SQL run against live, roles, grants and RLS policies, Supabase project/dashboard settings (auth, storage, API, network, backups, extensions, branching, integrations), database passwords/keys/tokens, `supabase/drift-allowlist.json` entries, migration history repair, and resealing migrations (`npm run migrations:reseal`, `ALLOW_MIGRATION_RESEAL`, or editing `supabase/applied-migration-hashes.json`). Drafting a migration in an unmerged PR is allowed; a blanket or earlier yes does not cover a later change. See [the decision](docs/decisions/2026-10-15-database-changes-need-owner-permission.md). - **MERGING TO `main` DEPLOYS TO PRODUCTION.** The Supabase GitHub integration has **"Deploy to production" ENABLED** on branch **`main`** (confirmed 2026-08-21). Any migration merged to `main` applies to the live clinical database automatically within seconds (34 s in `docs/audit/live-drift-forensics-2026-08.md` §3.7), with no separate deploy step or hold window: - **Treat merge approval as production-deploy approval.** Never merge a PR touching `supabase/migrations/**` outside an approved window, and never enable auto-merge on one. - - **Never write PR metadata promising a deferred deploy.** Phrases like "AWAITING DEPLOY WINDOW", "deployed manually after merge", "deployment is pending operator approval", and "not yet applied" describe a control this repo lacks and invite premature merges (as on PR #2502). State the merge gate instead ("merge only inside the approved window"). `scripts/pr-policy.mjs` hard-blocks deferred-deploy claims on PRs touching `supabase/migrations/**` (title always in scope; body statements must name the database subject; pre-merge approval phrasing and non-DB deploy constraints remain allowed). + - **Never write PR metadata promising a deferred deploy.** Phrases like "AWAITING DEPLOY WINDOW", "deployed manually after merge", "deployment is pending operator approval", and "not yet applied" invite premature merges (as on PR #2502). State the merge gate instead ("merge only inside the approved window"). `scripts/pr-policy.mjs` hard-blocks deferred-deploy claims on PRs touching `supabase/migrations/**` (title always in scope; body statements must name the database subject; pre-merge approval phrasing and non-DB deploy constraints remain allowed). - **After such a PR merges, the schema-application gate is the post-merge `live-drift` workflow** (`.github/workflows/live-drift.yml`), which must complete with BOTH `npm run check:drift` and `npm run check:migration-history` green. `supabase migration list` reads recorded history only and cannot detect no-statements rows (`#Q5JHBJ`), whereas `check:drift` compares the live schema. A manual `supabase migration list --linked --project-ref sjrfecxgysukkwxsowpy` read is an optional provider-backed supplement requiring explicit user confirmation first. - **Transactional execution only.** Each migration runs in one transaction, so bare `CREATE INDEX CONCURRENTLY` fails; use operator-prebuild + a validate-only guard migration (`20260804110240` pattern below). - **Automatic branching is ON** (one preview DB per `supabase/**` PR, limit 3; Branching Compute is **not covered by the Spend Cap**). CI's `Migration replay` job (`db-reset-verify`, `supabase migration up --local`) independently replays the chain on every DB PR. -- Expected project ref: `sjrfecxgysukkwxsowpy`. -- Older unused project ref `qjgitjyhxrwxsrydablr` belongs to `Database`; treat it as stale and do not use it. -- Hosted migrations, `supabase/schema.sql`, `supabase/roles.sql`, CI, and deployment tooling must target role `postgres`; never assume a platform-reserved role. The single older applied migration is immutable and pinned by `npm run check:migration-role`. -- Bare-image storage scaffolding must discover its local schema owner at runtime and must never be reused as hosted migration SQL. -- Run `npm run check:migration-role` after changing Supabase SQL, migration tooling, CI replay, or disaster-recovery instructions. -- Run `npm run check:supabase-project` after changing Supabase env values. +- Expected project ref: `sjrfecxgysukkwxsowpy`. Older unused project ref `qjgitjyhxrwxsrydablr` belongs to `Database`; treat it as stale and do not use it. +- Hosted migrations, `supabase/schema.sql`, `supabase/roles.sql`, CI, and deployment tooling must target role `postgres`; never assume a platform-reserved role. The single older applied migration is immutable and pinned by `npm run check:migration-role`. Bare-image storage scaffolding must discover its local schema owner at runtime and must never be reused as hosted migration SQL. +- Run `npm run check:migration-role` after changing Supabase SQL, migration tooling, CI replay, or disaster-recovery instructions, and `npm run check:supabase-project` after changing Supabase env values. - **Guard-migration contract.** Any mark-applied version, `supabase migration repair --status applied`, hand-applied SQL recorded as a migration, or history repair MUST ship a fail-fast validation guard migration in the same change, following `20260804110240_restore_rag_search_health_indexes.sql` (validates presence + `indisvalid`/`indisready` + normalized definition, never builds, `set local` timeouts, one `raise exception`). `schema_drift_snapshot()` v2 (`20260818090000`) reports no-statement `supabase_migrations` versions; `check:drift` fails unless `supabase/drift-allowlist.json` has a reviewed `migration_history` entry pointing at its guard (`guard.class` `validation` mandatory from 2026-08-18; `superseded`/`no_ddl` pre-contract only; never allowlist bare or widen class). Enforced by `tests/migration-history-guards.test.ts` and `tests/search-health-index-coverage.test.ts` + `supabase/search-health-unmonitored-indexes.json` (`required_indexes` changes via migration only). See `docs/database-drift-detection.md`. ## Supabase merge caution @@ -204,13 +201,11 @@ surface, read `docs/rag-behaviour/` (README → behaviour-map → refuted-approa # Railway project safety -- This repo deploys to the live Railway project `PsychSift` (`5deaad0b-675a-4c13-978e-5ca2b5b877f9`) in workspace `bigsimmo's Projects`. Full topology: `docs/deployment-architecture.md` §1. -- Production services `PsychSift` (Next.js app tier, serves `https://psychiatry.tools`) and `worker` (ingestion) auto-deploy from `BigSimmo/PsychSift` pushes to `main`; the `staging` environment runs the `app` service. -- The older Railway project `clinical-kb` (`4361c04f-dd3c-4ee9-9e97-49e4e5707b70`) is superseded with zero active deployments; treat it as stale — never `railway link` to it or deploy there. -- The similarly named Supabase project `PsychSift Production` is the database/auth tier, not a Railway project; see "Supabase project safety" above. -- Railway CLI token auth uses `RAILWAY_API_TOKEN` (personal account token; see `.env.example`). The project-scoped `RAILWAY_TOKEN` is for CI deploys only and cannot list or link projects; Cloud runtime acceptance no longer installs or probes the CLI, so that substitution rule is documentation-enforced until an operator workflow reintroduces CLI checks. Desktop/CLI MCP uses the secret-free `railway` entry (enable in `$CODEX_HOME/config.toml` or via a never-committed local edit — never commit `enabled = true`) plus `codex mcp login railway`; neither repository MCP file activates a hosted ChatGPT/Codex app. +- This repo deploys to the live Railway project `PsychSift` (`5deaad0b-675a-4c13-978e-5ca2b5b877f9`) in workspace `bigsimmo's Projects` (`docs/deployment-architecture.md` §1). Production services `PsychSift` (Next.js app, `https://psychiatry.tools`) and `worker` (ingestion) auto-deploy from `BigSimmo/PsychSift` pushes to `main`; `staging` runs `app`. +- The older Railway project `clinical-kb` (`4361c04f-dd3c-4ee9-9e97-49e4e5707b70`) is superseded (zero active deployments) — never `railway link` or deploy there. Supabase `PsychSift Production` is the DB/auth tier, not Railway. +- Railway CLI token auth (`.env.example`): prefer the project-scoped `RAILWAY_TOKEN` (one environment) for deploys, redeploys and logs in CI or remote shells; use `RAILWAY_API_TOKEN` (workspace over account scope) only for `railway list`/`link`, which a project token cannot do. Desktop/CLI MCP uses the secret-free `railway` entry (enable locally in `$CODEX_HOME/config.toml` — never commit `enabled = true`) plus `codex mcp login railway`. - Railway deploys and mutations fall under the "API and provider confirmation boundary" below; verify target project/environment IDs before any mutation. -- **Safe, fast iteration.** Keep automatic Railway PR Environments disabled. Iterate locally with focused checks, use an explicitly requested isolated app preview when useful, and keep production deployment from protected `main`. Never copy production database/provider credentials or an ingestion worker into a routine PR preview. Any future automatic previews require an isolated base and Focused PR Environments, using the existing service watch paths. Follow [the preview policy and verified settings](docs/deployment-architecture.md#selective-railway-pr-previews); keep required CI and Supabase controls intact. +- **Safe, fast iteration.** Keep automatic Railway PR Environments disabled. Iterate locally with focused checks, use an explicitly requested isolated app preview when useful, and keep production deployment from protected `main`. Never copy production database/provider credentials or an ingestion worker into a routine PR preview. Follow [the preview policy and verified settings](docs/deployment-architecture.md#selective-railway-pr-previews); keep required CI and Supabase controls intact. diff --git a/data/repo-awareness-snapshot.json b/data/repo-awareness-snapshot.json index 2c59eeee5a..ab9d70907c 100644 --- a/data/repo-awareness-snapshot.json +++ b/data/repo-awareness-snapshot.json @@ -24783,6 +24783,14 @@ "outcome": "MERGED after #1268 (lockfile order). Dev bumps: bundle-analyzer/eslint-config-next 16.2.12, playwright 1.62, prettier 3.9.6.", "checks": "hosted-pr-required,static,unit,build,circleci" }, + { + "date": "2026-10-17", + "ref": "docs/audit-remediation-20261014", + "head": "def6fabcc8235e8592fea2613d07170e23dd9836", + "scope": "prlanded", + "outcome": "Verified PR #3544 squash-merged at def6fabcc8235e8592fea2613d07170e23dd9836 with zero content diff against final PR head f519ae94fafab3e2ce7b221a054faa7d66939eea and zero orphaned commits", + "checks": "gh pr view 3544, git rev-list --parents -n 1 def6fabcc8235e8592fea2613d07170e23dd9836, git diff --stat def6fabcc8235e8592fea2613d07170e23dd9836 f519ae94fafab3e2ce7b221a054faa7d66939eea" + }, { "date": "2026-07-14", "ref": "worktrees (6244, 8ba3, b6ff, e6b5, repo-improvement-review-09945c)", diff --git a/docs/branch-review-index.md b/docs/branch-review-index.md index 7f7215782d..db277b338d 100644 --- a/docs/branch-review-index.md +++ b/docs/branch-review-index.md @@ -15,14 +15,14 @@ Retention and rotation: [`branch-review-archival-policy.md`](branch-review-archi ## Summary -- Records: 711 -- Distinct ref cells: 457 -- Distinct reviewed heads: 693 +- Records: 712 +- Distinct ref cells: 458 +- Distinct reviewed heads: 694 - Date range: `2026-08-12` to `2026-10-17` Records per date, newest first: -- `2026-10-17` — 5 records +- `2026-10-17` — 6 records - `2026-10-16` — 7 records - `2026-10-15` — 3 records - `2026-10-14` — 1 record @@ -79,6 +79,7 @@ Scope and outcome are truncated; follow the record link for the full six-cell ro | Date | Ref | Scope | Outcome | Record | | --- | --- | --- | --- | --- | +| 2026-10-17 | docs/audit-remediation-20261014 | prlanded | Verified PR #3544 squash-merged at def6fabcc8235e8592fea2613d07170e23d… | [0dbb74fa](branch-review-records/0dbb74fa24e427d3d012744657555374a0da336ad41ba1439663c24cf88e5342.record.md) | | 2026-10-17 | codex/cloud-policy-clarity | prlanded | PR #3581 (a5b118f952) and PR #3587 (7c719b2bc4) squash-merged cleanly… | [16df9092](branch-review-records/16df90928a34e2941ef9c2892b7f48d92e09e12f470af635ef15e4ee3677b0b9.record.md) | | 2026-10-17 | claude/rag-ingestion-hardening | prlanded | PR #3596 squash-merged cleanly into main at 45fc04c42f0867bc9802ffc93f… | [1ae2c7f8](branch-review-records/1ae2c7f8f7762d8e1d81898ed83a701be7cbd007c80be5a3d83e39470ced9461.record.md) | | 2026-10-17 | claude/rag-ingestion-hardening | RAG retrieval/summary/cache generation isolation and ingestion worker/edge hardening | resolved | [5349150e](branch-review-records/5349150e82f255e42866975fa7aa5e9fe76601433ebc427c75226677c5bef854.record.md) | diff --git a/docs/branch-review-records/0dbb74fa24e427d3d012744657555374a0da336ad41ba1439663c24cf88e5342.record.md b/docs/branch-review-records/0dbb74fa24e427d3d012744657555374a0da336ad41ba1439663c24cf88e5342.record.md new file mode 100644 index 0000000000..67bf1cbbf7 --- /dev/null +++ b/docs/branch-review-records/0dbb74fa24e427d3d012744657555374a0da336ad41ba1439663c24cf88e5342.record.md @@ -0,0 +1 @@ +| 2026-10-17 | docs/audit-remediation-20261014 | def6fabcc8235e8592fea2613d07170e23dd9836 | prlanded | Verified PR #3544 squash-merged at def6fabcc8235e8592fea2613d07170e23dd9836 with zero content diff against final PR head f519ae94fafab3e2ce7b221a054faa7d66939eea and zero orphaned commits | gh pr view 3544, git rev-list --parents -n 1 def6fabcc8235e8592fea2613d07170e23dd9836, git diff --stat def6fabcc8235e8592fea2613d07170e23dd9836 f519ae94fafab3e2ce7b221a054faa7d66939eea | diff --git a/docs/decisions/2026-10-15-coderabbit-stays-but-does-not-block-merges.md b/docs/decisions/2026-10-15-coderabbit-stays-but-does-not-block-merges.md index c52cf09e2e..c0813b6df6 100644 --- a/docs/decisions/2026-10-15-coderabbit-stays-but-does-not-block-merges.md +++ b/docs/decisions/2026-10-15-coderabbit-stays-but-does-not-block-merges.md @@ -2,6 +2,7 @@ - **Status:** decided 2026-10-15 - **Source:** the owner, in chat +- **Implemented in:** `docs/agents/pull-request-workflow.md`, find "never blocks merges or delays the merge queue" CodeRabbit stays as a review bot, but its reviews must stop acting as a barrier to merging. diff --git a/scripts/maintain-docs-system.mjs b/scripts/maintain-docs-system.mjs index b11273b53f..3a9816faaf 100644 --- a/scripts/maintain-docs-system.mjs +++ b/scripts/maintain-docs-system.mjs @@ -177,7 +177,11 @@ export function classifyRepositoryPath(relativePath, options = {}) { lower === "src/app/api/search" || /^docs\/(rag|answer|search(?!-chrome)|query)/.test(lower) || (lower.startsWith("docs/") && - /\b(rag|retrieval|grounding|rerank|citation|citations|query-classification)\b/.test(base)) + /\b(rag|retrieval|grounding|rerank|citation|citations|query-classification)\b/.test(base)) || + // No `search-` prefix: app-experience's explicit `tests/search-*` glob owns those tests. + /^tests\/(rag|answer|query-classification|retrieval|grounding|rerank|semantic-rerank|citation|citations|evidence|corpus-grounding|eval)(-|\.)/.test( + lower, + ) ) { return buildResult( "search-and-answers", @@ -201,7 +205,12 @@ export function classifyRepositoryPath(relativePath, options = {}) { lower === "src/lib/ingest" || lower.startsWith("src/app/sources/") || lower === "src/app/sources" || - /^docs\/(source|ingest|document)/.test(lower) + /^docs\/(source|ingest|document)/.test(lower) || + // No `document-` prefix: app-experience's `tests/document-*` glob owns those tests, and + // source-intake claims its document tests by explicit globs (e.g. `tests/document-enrichment-*`). + /^tests\/(ingest|ingestion|upload|reindex|chunk|chunking|extract|extracted|docling|ocr|pdf|xlsx|docx|table|indexing|enrichment|storage-cleanup|storage-upload)(-|\.)/.test( + lower, + ) ) { return buildResult( "sources-and-ingestion", @@ -220,7 +229,11 @@ export function classifyRepositoryPath(relativePath, options = {}) { /^src\/app\/api\/(cme|roster|teaching|work)(\/|$)/.test(lower) || /^src\/components\/(cme|roster|my-day|teaching|work-frame)(\/|$)/.test(lower) || /^src\/lib\/(cme|roster|teaching|work-frame)(\/|$)/.test(lower) || - /^docs\/(personal|workspace|diary|cme)/.test(lower) + /^docs\/(personal|workspace|diary|cme)/.test(lower) || + // No bare `work-` prefix: Work tests span several systems (App experience, data platform, + // design system), so an unmatched one falls back to the generic delivery owner until a + // manifest rule names its real owner. + /^tests\/(cme|roster|my-day|teaching|open-shifts|diary|workspace|calendar|handover|notes)(-|\.)/.test(lower) ) { return buildResult( "personal-practice", @@ -242,6 +255,9 @@ export function classifyRepositoryPath(relativePath, options = {}) { ) || /^docs\/(clinical(?!-safety|-governance|-hazard|-sign-off)|medication|therapy|psychiatry|formulation|diagnosis|dsm|differential|dictionary|care-plan|scales|calculators|ect|sleep|ranzcp)/.test( lower, + ) || + /^tests\/(medications|medicine|medicines|therapy|psychiatry|formulation|diagnosis|dsm|differential|differentials|dictionary|care-plan|calculator|calculators|forms|services|mha|specifier|specifiers|tall-man|clozapine|lithium|ect|scales|sleep|ranzcp|new-starters|on-call)(-|\.)/.test( + lower, ) ) { return buildResult( @@ -265,7 +281,10 @@ export function classifyRepositoryPath(relativePath, options = {}) { lower, ) || lower === "scripts/maintain-docs-system.mjs" || - lower === "tests/maintain-docs-system.test.ts" + lower === "tests/maintain-docs-system.test.ts" || + /^tests\/(decisions|retired-rules|instruction|ledger|outstanding-issues|branch-review|database-skills|check-docs)(-|\.)/.test( + lower, + ) ) { return buildResult( "knowledge", @@ -282,12 +301,16 @@ export function classifyRepositoryPath(relativePath, options = {}) { lower === "src/app/design-tokens.css" || /^docs\/(design|brand|ui|search-chrome|search-results-bar|filter-contract|wiring-conventions|frontend-architecture|pwa|navigation)/.test( lower, + ) || + /^tests\/(design-system|type-scale|icon-scale|theme|brand|ui-style-contract|phone-chrome|search-chrome|search-results-bar|navigation|header|dock|pwa)(-|\.)/.test( + lower, ) ) { const isDesignTokensOrSystem = lower === "src/app/globals.css" || lower === "src/app/design-tokens.css" || - /^docs\/(design|brand|ui)/.test(lower); + /^docs\/(design|brand|ui)/.test(lower) || + /^tests\/(design-system|type-scale|icon-scale|theme|brand|ui-style-contract)(-|\.)/.test(lower); return buildResult( "shell-and-design", isDesignTokensOrSystem ? "design-system" : "app-experience", diff --git a/tests/maintain-docs-system.test.ts b/tests/maintain-docs-system.test.ts index 0d598203d4..b230b9c7fb 100644 --- a/tests/maintain-docs-system.test.ts +++ b/tests/maintain-docs-system.test.ts @@ -165,6 +165,41 @@ describe("maintain-docs-system pipeline", () => { expect(result.system).toBe("search-and-answers"); expect(result.systemFile).toBe("docs/organisation/systems/search-and-answers.json"); }); + + it("routes new domain-specific tests/* paths to their owning system area instead of falling through to delivery", () => { + const testCases = [ + { path: "tests/query-classification-new.test.ts", expectedResolved: "answer-engine" }, + { path: "tests/xlsx-sheet-dictionary-projection-new.test.ts", expectedResolved: "source-intake" }, + { path: "tests/cme-accreditation-summary-new.test.ts", expectedResolved: "personal-practice" }, + { path: "tests/calculators-lithium-toxicity-new.test.ts", expectedResolved: "clinical-content" }, + { path: "tests/branch-review-ledger-new.test.ts", expectedResolved: "knowledge" }, + { path: "tests/design-system-token-new.test.ts", expectedResolved: "design-system" }, + { path: "tests/phone-chrome-scroll-new.test.ts", expectedResolved: "app-experience" }, + ]; + + for (const tc of testCases) { + const result = classifyRepositoryPath(tc.path); + expect(result.resolvedSystem).toBe(tc.expectedResolved); + expect(result.resolvedSystemFile).toBe(`docs/organisation/systems/${tc.expectedResolved}.json`); + } + }); + }); + + it("leaves an unmatched work-* test with the generic delivery owner rather than auto-claiming it for personal practice", () => { + // Work tests span several systems, so the bare prefix cannot identify the owner. + expect(classifyRepositoryPath("tests/work-side-nav-scroll.test.ts").resolvedSystem).toBe("delivery"); + // Exact manifest rules still decide existing Work tests. + expect(classifyRepositoryPath("tests/work-side-nav.test.ts").resolvedSystem).toBe("app-experience"); + expect(classifyRepositoryPath("tests/work-calendar-entries.test.ts").resolvedSystem).toBe("personal-practice"); + }); + + it("lets explicit manifest globs win over the domain test prefixes for new tests", () => { + // app-experience owns tests/search-* and tests/document-* by glob; source-intake owns + // tests/document-enrichment-*. The classifier prefixes deliberately do not compete with them. + expect(classifyRepositoryPath("tests/search-new-widget.test.ts").resolvedSystem).toBe("app-experience"); + expect(classifyRepositoryPath("tests/document-new-viewer.test.ts").resolvedSystem).toBe("app-experience"); + expect(classifyRepositoryPath("tests/document-enrichment-new.test.ts").resolvedSystem).toBe("source-intake"); + expect(classifyRepositoryPath("tests/calculators-new-score.test.ts").resolvedSystem).toBe("clinical-content"); }); describe("placeUnplacedFiles", () => {