Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD

| Date | Branch or ref | Reviewed HEAD | Scope | Outcome | Checks |
| ---------- | -------------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2026-07-21 | main | 71059eba98bc6e335c2c82cb9ab542aad44dfae8 | database audit, drift analysis, and data contract review (/drift /data /audit) | Completed offline audit of database schema, migrations, generated drift manifest, function grants, owner-scope API boundaries, therapy data indexes, and data ingestion logic. Verified drift-manifest byte-identical match to schema.sql (schema_sha256: 50da0978a164...). Found one P2 static check failure: orphaned test tests/check-july8-live-batch.test.ts references deleted script scripts/check-july8-live-batch.ts, causing npm run check:knip and verify:cheap to fail. Live Supabase schema comparison and live ingestion audits were approval-gated and skipped per provider boundary rules. | Local offline checks run: Vitest 339/339 test files passed (3,053/3,054 tests passed, 1 skipped); tests/drift-detection.test.ts (10/10 passed); check:function-grants (28/28 SECURITY DEFINER functions revoked); check:owner-scope (40 API routes clean against 25 owner tables); check:therapy-data-index (205 records OK); check:design-system-contract (520 files clean); strict check:type-scale & check:icon-scale; check:runtime; check:github-actions; check:ci-scope; check:ci-triage; check:pr-policy; check:gate-manifest; check:codebase-index-coverage. Provider checks skipped (approval-gated): check:drift, check:supabase-project, check:migration-history, audit:source-governance. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Correct the contradictory drift-verification claim.

This row says the drift manifest was verified byte-identically, but also records check:drift as skipped; package.json:176 wires that script to the relevant checker. Either remove the verification claim or document the independent comparison and complete hashes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/branch-review-ledger.md` at line 23, Update the 2026-07-21 entry in the
branch review ledger to resolve the contradiction between the byte-identical
drift-manifest claim and the skipped check:drift result. Either remove the
verification claim, or explicitly identify the independent comparison and record
the complete schema and manifest hashes; preserve the separate approval-gated
status for check:drift.

| 2026-07-14 | multiple remote branches (16 refs) | multiple SHAs | remote branch cleanup | Safely deleted 16 fully merged and redundant remote branches on origin (including `claude/canary-gate-fixes`, `claude/codebase-index-coverage`, `claude/design-elevation-e1e2`, `claude/design-sync-fixes-p1`, `claude/docs-script-linter`, `claude/document-image-viewer-review-ox7t11`, `claude/generation-token-starvation-fix`, `claude/github-actions-codex-issue-f4t4s5`, `claude/hero-composer-hydration`, `claude/pdf-signed-url-refresh`, `claude/pt-audit-monitor-marker-fix`, `claude/pt-audit-pr2-variant-early-exit`, `claude/pt-audit-pr4-trust-copy`, `claude/pt-audit-pt17-live-monitor`, `codex/eval-canary-quota-handling`, and `cursor/clean-sentry-lockfile-orphans-74cf`). | Confirmed zero unique commits against origin/main and MERGED/CLOSED status on GitHub via `gh pr list`. |
| 2026-07-14 | worktrees (6244, 8ba3, b6ff, e6b5, repo-improvement-review-09945c) | detached HEADs | local worktree cleanup | Safely removed and unregistered 5 clean, inactive worktrees from the git registry. | Ran `git worktree remove` and verified final active worktrees. |
| 2026-07-14 | main | 570e6ba56ae60bea56a32801b9cc96c5a8dfde4f | branch alignment | Fast-forwarded local `main` to latest `origin/main` commit. | Verified main and origin/main revisions and updated ref locally. |
Expand Down
9 changes: 4 additions & 5 deletions docs/outstanding-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ Durable, cross-session memory of everything still outstanding for this repo: ope
| ID | Pri | Type | Summary | Detail / next action | Source | Added |
| ---- | --- | ----- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------- |
| #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 |
| #002 | P2 | task | Process-ownership fix not yet isolated on `main` | `scripts/run-eval-safe.mjs` still terminates residual repo processes broadly. Isolate the child-owned `terminateOwnedProcessTree(child.pid)` approach + the regression test proving unrelated Vitest/Playwright/Next processes survive; verify statically — do **not** start a provider-backed eval. | `docs/process-hardening.md` (High-priority local process ownership) | 2026-07-21 |
| #003 | P2 | task | Staging tenancy release evidence outstanding | The provider-backed A/B tenancy regression is intentionally outside local + PR gates. Run the standalone manual/nightly workflow and attach a recent green evidence artifact **before release**. | `docs/process-hardening.md`; `docs/staging-tenancy-release-evidence.md` | 2026-07-21 |
| #004 | P3 | rec | Rescope provider-gated RAG safety ideas | If explicitly approved, rescope only the still-relevant preflight utilities/tests (answer-quality thresholds, cost-cap preflight, deep-health) against current `main`; keep live OpenAI/Supabase validation separate. Do **not** replay the stale 754-line retrieval migration/worktree wholesale. | `docs/process-hardening.md` (Provider-gated RAG safety ideas) | 2026-07-21 |
| #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]`). Not a defect on the current golden set. | `docs/rag-hybrid-findings-and-todo.md` P1 item 4; `src/lib/rag/clinical-search.ts:1362` | 2026-07-21 |
| #006 | P2 | issue | Globe "Language & region" button had no handler | Fixed in this PR to the disabled "Coming soon" placeholder convention (`aria-disabled` + sr-only note). Wire to a real language/region settings screen when one exists, then drop the placeholder state. | `src/components/clinical-dashboard/master-search-header.tsx:1829`; session 2026-07-21 | 2026-07-21 |
Expand All @@ -48,6 +46,7 @@ Durable, cross-session memory of everything still outstanding for this repo: ope

Move resolved rows here with the resolution date and a one-line outcome. Keep them — do not delete.

| ID | Type | Summary | Outcome | Resolved |
| ------------ | ---- | ------- | ------- | -------- |
| _(none yet)_ | | | | |
| ID | Type | Summary | Outcome | Resolved |
| ---- | ---- | ------------------------------------------------ | --------------------------------------------------- | ---------- |
| #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 |
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@
"check:supabase-project": "node scripts/run-tsx.mjs scripts/check-supabase-project.ts",
"auth:set-administrator": "node scripts/run-tsx.mjs scripts/set-site-administrator.ts",
"check:indexing": "node scripts/run-tsx.mjs scripts/check-indexing.ts",
"check:m13-migration": "node scripts/run-tsx.mjs scripts/check-m13-migration.ts",
"check:july8-live-batch": "node scripts/run-tsx.mjs scripts/check-july8-live-batch.ts",
"check:type-scale": "node scripts/check-type-scale.mjs --strict",
"check:icon-scale": "node scripts/check-icon-scale.mjs --strict",
"check:design-system-contract": "node scripts/check-design-system-contract.mjs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
isR17IndexUniqueViolation,
normalizeIndexDef,
R17_PROBE_STAGE,
} from "../scripts/check-july8-live-batch";
} from "./check-july8-live-batch";

describe("check-july8-live-batch R17 index definition probe", () => {
it("accepts the canonical partial unique index definition", () => {
Expand Down
114 changes: 18 additions & 96 deletions scripts/run-eval-safe.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,13 @@ import { spawn, spawnSync } from "node:child_process";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";

const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const isWindows = process.platform === "win32";
export const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
export const isWindows = process.platform === "win32";
const [targetScript, ...forwardArgs] = process.argv.slice(2);
const offlineProviderRequested = forwardArgs.some(
(token, index) => token === "--provider-mode" && forwardArgs[index + 1] === "offline",
);

if (!targetScript) {
console.error("Usage: node scripts/run-eval-safe.mjs <script> [args...]");
process.exit(1);
}

function normalizeCommandLine(value) {
return value.toLowerCase().replaceAll("/", "\\");
}

function toDateOrDefault(rawValue) {
if (!rawValue) return null;
const dateValue = Date.parse(rawValue);
Expand All @@ -44,61 +35,7 @@ function toDateOrDefault(rawValue) {
return null;
}

function shouldTerminateCandidate(commandLine) {
const normalizedCommandLine = normalizeCommandLine(commandLine);
const hasRepoScripts = normalizedCommandLine.includes("\\scripts\\");

if (normalizedCommandLine.includes("\\run-eval-safe.mjs")) return false;

if (hasRepoScripts && normalizedCommandLine.includes("\\node_modules\\tsx\\dist\\cli.mjs")) {
if (normalizedCommandLine.includes("\\scripts\\eval-")) return true;
if (normalizedCommandLine.includes("\\scripts\\eval")) return true;
return false;
}

if (hasRepoScripts && normalizedCommandLine.includes("\\scripts\\run-tsx.mjs")) {
return normalizedCommandLine.includes("\\scripts\\eval");
}

if (
hasRepoScripts &&
normalizedCommandLine.includes("\\node_modules\\tsx\\dist\\preflight.cjs") &&
normalizedCommandLine.includes(" --import ")
) {
return true;
}

if (normalizedCommandLine.includes("\\node_modules\\playwright")) {
return true;
}

if (
normalizedCommandLine.includes("\\node_modules\\vitest\\vitest.mjs") &&
normalizedCommandLine.includes("\\tests\\")
) {
return true;
}

if (normalizedCommandLine.includes("\\node_modules\\next\\dist\\bin\\next")) {
return true;
}

if (normalizedCommandLine.includes("\\node_modules\\next\\dist\\server\\lib\\start-server.js")) {
return true;
}

if (normalizedCommandLine.includes("\\node_modules\\next\\dist\\compiled\\jest-worker\\processchild.js")) {
return true;
}

if (normalizedCommandLine.includes("\\.next\\build\\") || normalizedCommandLine.includes("\\.next\\dev\\build\\")) {
return true;
}

return false;
}

function listRepoNodeProcesses() {
export function listRepoNodeProcesses() {
if (!isWindows) return [];

const command = [
Expand Down Expand Up @@ -142,16 +79,7 @@ function listRepoNodeProcesses() {
}
}

function listCandidateProcesses() {
if (!isWindows) return [];

return listRepoNodeProcesses().filter((candidate) => {
if (!candidate?.commandLine) return false;
return shouldTerminateCandidate(candidate.commandLine);
});
}

function getDescendantPids(rootPid, allProcesses = listRepoNodeProcesses()) {
export function getDescendantPids(rootPid, allProcesses = listRepoNodeProcesses()) {
const visited = new Set([rootPid]);
const queue = [rootPid];

Expand All @@ -168,7 +96,7 @@ function getDescendantPids(rootPid, allProcesses = listRepoNodeProcesses()) {
return Array.from(visited);
}

function terminateProcesses(pids, context) {
export function terminateProcesses(pids, context) {
if (!isWindows) return 0;

let killed = 0;
Expand All @@ -190,29 +118,23 @@ function terminateProcesses(pids, context) {
return killed;
}

function cleanupResidualEvaluationProcesses() {
if (!isWindows) return;

const selfPid = process.pid;
const candidates = listCandidateProcesses().filter((candidate) => candidate.pid > 0 && candidate.pid !== selfPid);
if (candidates.length === 0) return;
const pids = candidates.map((candidate) => candidate.pid);
terminateProcesses(pids, "cleanupResidualEvaluationProcesses");
}

function terminateEvalProcessTree(pid) {
if (!isWindows || !pid || pid <= 0) return;
const processSnapshot = listRepoNodeProcesses();
export function terminateOwnedProcessTree(pid, processSnapshot = listRepoNodeProcesses()) {
if (!isWindows || !pid || pid <= 0) return 0;
const descendants = getDescendantPids(pid, processSnapshot);
terminateProcesses(descendants, "terminateEvalProcessTree");
return terminateProcesses(descendants, "terminateOwnedProcessTree");
}
Comment on lines +121 to 125

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Prevent stale-PID termination.

getDescendantPids includes pid even when the snapshot has no matching root. Since this helper is also called after the child close event, Windows can reuse that exited PID before the snapshot; taskkill could then terminate an unrelated process tree. Capture and validate the child creation identity before killing, and skip termination when the current root does not match it. Add a missing-root/reused-PID regression case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/run-eval-safe.mjs` around lines 121 - 125, Update
terminateOwnedProcessTree and its callers to capture the child process creation
identity and validate the current snapshot root before terminating descendants;
skip termination when the root is missing or its identity differs, preventing
reused-PID taskkill operations. Add a regression test covering both a missing
root and a reused PID after the child close event.


function terminateEvalProcess(pid) {
if (!pid || pid <= 0) return;
terminateEvalProcessTree(pid);
terminateOwnedProcessTree(pid);
}

function runEvalScript() {
if (!targetScript) {
console.error("Usage: node scripts/run-eval-safe.mjs <script> [args...]");
process.exit(1);
}

const targetPath = resolve(projectRoot, targetScript);
const command = process.execPath;
const commandArgs = [resolve(projectRoot, "scripts", "run-tsx.mjs"), targetPath, ...forwardArgs];
Expand All @@ -226,8 +148,6 @@ function runEvalScript() {
? {
...process.env,
RAG_PROVIDER_MODE: "offline",
// Empty values prevent @next/env from restoring credentials from an
// env file before eval-quality actively deletes them in the child.
OPENAI_API_KEY: "",
OPENAI_ORG_ID: "",
OPENAI_PROJECT_ID: "",
Expand Down Expand Up @@ -275,5 +195,7 @@ function runEvalScript() {
});
}

cleanupResidualEvaluationProcesses();
runEvalScript();
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1]);
if (isMain) {
runEvalScript();
}
11 changes: 11 additions & 0 deletions scripts/test-cross-tenant-staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,17 @@ async function main() {
auth: { persistSession: false, autoRefreshToken: false, detectSessionInUrl: false },
global: { fetch: crossTenantStagingFetch },
});

// Ensure test users have administrator privileges for mutation routes
await admin.auth.admin.updateUserById(sessionA.userId, { app_metadata: { site_role: "administrator" } });
await admin.auth.admin.updateUserById(sessionB.userId, { app_metadata: { site_role: "administrator" } });
Comment on lines +624 to +625

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## File excerpt\n'
sed -n '600,660p' scripts/test-cross-tenant-staging.ts

printf '\n## Related helper\n'
sed -n '1,220p' scripts/set-site-administrator.ts

printf '\n## Supabase auth-admin type usage in repo\n'
rg -n "updateUserById\\(" -S .

printf '\n## Supabase client package version / types if present\n'
node - <<'NODE'
const fs = require('fs');
for (const p of ['package.json','package-lock.json','pnpm-lock.yaml','yarn.lock']) {
  if (fs.existsSync(p)) {
    console.log(`FOUND ${p}`);
    if (p === 'package.json') {
      const pkg = JSON.parse(fs.readFileSync(p,'utf8'));
      console.log('supabase-js dep:', pkg.dependencies?.['`@supabase/supabase-js`'] || pkg.devDependencies?.['`@supabase/supabase-js`'] || 'not found');
    }
    break;
  }
}
NODE

Repository: BigSimmo/Database

Length of output: 5512


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Relevant lines in scripts/test-cross-tenant-staging.ts\n'
nl -ba scripts/test-cross-tenant-staging.ts | sed -n '620,650p'

printf '\n## Relevant lines in scripts/set-site-administrator.ts\n'
nl -ba scripts/set-site-administrator.ts | sed -n '1,220p'

Repository: BigSimmo/Database

Length of output: 254


🌐 Web query:

Supabase auth.admin.updateUserById returns { data, error } app_metadata merge replace site_role administrator docs

💡 Result:

The supabase.auth.admin.updateUserById method employs shallow merge semantics when updating the app_metadata object [1]. When you provide a new app_metadata object, Supabase merges it with the existing metadata rather than replacing the entire object [1]. Specifically, the merge behavior functions as follows: - Existing keys not included in your update are preserved [1]. - Keys provided in the update overwrite their existing values [1]. - Keys explicitly set to null (or nil) in your update are removed from the object [1]. This means if you want to set a site_role, you can safely update just that field without clearing other existing data in app_metadata [1]. Example of updating the site_role to administrator: // Server-side (Edge Function or Node.js) const { data, error } = await supabase.auth.admin.updateUserById( 'user-uuid', { app_metadata: { site_role: 'administrator' } }); Important implementation notes: - Admin API Only: This method must be called from a secure, server-side context (e.g., Edge Functions, backend server) using the service_role key [2][3][4]. Never expose the service_role key in client-side code [4]. - Client Sync: Updates made via the admin API do not automatically notify or update the user's active session on the client-side [5]. To reflect these changes in the UI or trigger onAuthStateChange listeners, you must call supabase.auth.refreshSession on the client after the admin update is complete [2][5].

Citations:


🌐 Web query:

site:supabase.com auth-admin updateUserById app_metadata replace merge supabase-js

💡 Result:

When using the supabase-js library to update a user's app_metadata via updateUserById, the operation performs a merge by default, not a full replacement [1][2]. In Supabase, app_metadata is stored as a JSONB column [2]. When you provide a partial object to the app_metadata field in an update call, the new values are merged into the existing JSON object [1][3]. For example, if you call the update with { app_metadata: { plan: 'trial' } }, and the user already has { role: 'admin' }, the resulting app_metadata will contain both fields: { plan: 'trial', role: 'admin' }. To effectively "replace" the existing metadata with a new set of values, you must retrieve the current user's metadata first, construct the desired final object, and then perform the update with the complete object. Note that updateUserById must be called from a secure, server-side environment (such as an Edge Function or backend server) using your project's service_role key, as it provides administrative privileges that should never be exposed in client-side code [1][3].

Citations:


Handle admin mutation errors before refreshing sessions. The two updateUserById calls ignore their error values, so the script can continue with stale claims or a half-updated setup if either call fails. Supabase merges app_metadata, so there’s no need to restore existing metadata; just check both results and abort before the re-sign-in step if either update fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/test-cross-tenant-staging.ts` around lines 624 - 625, Update the
admin metadata mutations in the staging test setup to capture and validate the
results of both updateUserById calls. Abort immediately with the relevant error
before any session refresh or re-sign-in when either update fails, while
preserving the existing app_metadata update behavior.


// Refresh sessions to get the new role claim in the JWT
const sessionARefreshed = await signIn(clientA, config.userAEmail, config.userAPassword, "User A");
const sessionBRefreshed = await signIn(clientB, config.userBEmail, config.userBPassword, "User B");
sessionA.token = sessionARefreshed.token;
sessionB.token = sessionBRefreshed.token;

const fixtureA = await createFixture(admin, config, runId, sessionA.userId, "a", (fixture) =>
fixtures.push(fixture),
);
Expand Down
Loading