Skip to content
Merged
26 changes: 14 additions & 12 deletions .github/workflows/ingestion-autopilot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@
# or an explicitly requested recovery fails.
#
# ENABLED IN DRY-RUN: the 6-hourly schedule is active, but recovery only APPLIES when
# BOTH the dispatch input `apply=true` AND repo variable INGESTION_AUTOPILOT_APPLY ==
# BOTH the dispatch payload `apply=true` AND repo variable INGESTION_AUTOPILOT_APPLY ==
# "true". The variable is intentionally unset, so scheduled runs are read-only probes
# that alert on detected stuck/stranded work, unreachable Supabase, or (with apply)
# failed recovery. To allow real recovery after reviewing a dry-run, set
# INGESTION_AUTOPILOT_APPLY=true and dispatch with apply=true.
# INGESTION_AUTOPILOT_APPLY=true and send an `ingestion-autopilot`
# repository dispatch with `client_payload.apply=true`.
name: Ingestion Autopilot

on:
workflow_dispatch:
inputs:
apply:
description: "Apply recovery (requires repo var INGESTION_AUTOPILOT_APPLY=true)"
required: false
default: "false"
# repository_dispatch always loads this workflow from the trusted default
# branch. Do not replace it with workflow_dispatch: manual branch selection
# would let same-repository writers alter a secret-bearing workflow definition.
repository_dispatch:
types: [ingestion-autopilot]
schedule:
# Every 6 hours.
- cron: "0 */6 * * *"
Expand All @@ -35,7 +35,6 @@ env:
SUPABASE_PROJECT_REF: sjrfecxgysukkwxsowpy
SUPABASE_PROJECT_NAME: Clinical KB Database
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: placeholder-ci-anon-key
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}

jobs:
ingestion-autopilot:
Expand All @@ -48,6 +47,8 @@ jobs:
persist-credentials: false

- name: Preflight required secrets
env:
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
run: |
if [ -z "$SUPABASE_SERVICE_ROLE_KEY" ]; then
echo "::error::Ingestion autopilot cannot run — missing repo secret SUPABASE_SERVICE_ROLE_KEY"
Expand All @@ -70,8 +71,9 @@ jobs:
- name: Run autopilot
id: autopilot
env:
# Apply only when BOTH the dispatch input and the repo variable allow it.
APPLY_REQUESTED: ${{ github.event.inputs.apply }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
# Apply only when BOTH the trusted dispatch payload and repo variable allow it.
APPLY_REQUESTED: ${{ github.event.client_payload.apply || 'false' }}
APPLY_ALLOWED: ${{ vars.INGESTION_AUTOPILOT_APPLY }}
run: |
if [ "$APPLY_REQUESTED" = "true" ] && [ "$APPLY_ALLOWED" = "true" ]; then
Expand All @@ -81,7 +83,7 @@ jobs:
echo "Scheduled dry-run; alert if recoverable work is detected."
npm run ingestion:autopilot -- --alert-on-stuck
else
echo "Dry-run (set repo var INGESTION_AUTOPILOT_APPLY=true and dispatch apply=true to recover)."
echo "Dry-run (set repo var INGESTION_AUTOPILOT_APPLY=true and dispatch client_payload.apply=true to recover)."
npm run ingestion:autopilot
fi

Expand Down
2 changes: 2 additions & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-08-02 | claude/ds-v2-values | d33721b436c05014e8ef1f362a945fe9574e676e | DS V2 PR-V values | Wave 2 self-verified before push: 5a close-out (no value deltas remain after decision J2; contract metrics unchanged at rawColorLiterals 2 / literalShadowClasses 0 / legacyTapClasses 0), 5b tap knob 2.75rem to 3rem in @theme with --tap-min reduced to var(--spacing-tap) across 426 *-tap sites and three pins flipped in-commit, 5c --radius-md 8px to 10px across 243 rounded-md sites with the 4px-grid pin naming both half-steps and 14 arbitrary radius literals absorbed. Two carve-outs recorded in SPEC 4.10/4.6: the phone composer stays 44px below 431px, and the 2px mode-nav hairline stays off the ladder. An offline.html tap bump was reverted before push because it would have forced a service-worker CACHE_VERSION bump. Known-red local gate: tests/ui-smoke.spec.ts:2221 phone runway pin fails on unmodified origin/main here too (maxOffset 99 at base vs 111 at this head, reproduced in a pristine capture worktree) while CI run 30720360137 is green on the same tree - plan finding L, CI binding. | typecheck exit 0; verify:pr-local PASS with 4880 passed 3 skipped (4883) in 471 files, production build compiled, client-bundle secret scan passed, eval:rag:offline fixture line plus 574/574 (better than the J4 known-red baseline); verify:ui 346 passed 1 failed (the known-red phone runway pin); verify:phone-chrome --full=never contracts 116/116 and ui-tools 87/87 with the same single known-red test; test:e2e:critical 15/15; design-system contract, branch-review-ledger, outstanding-issues, sitemap, docs link/index/inventory/scripts all exit 0 |
| 2026-08-02 | claude/ds-v2-values | 48012d359b84daae347201697c82f3e433c182c5 | PR #1571 review-and-fix (ds-v2-values tap/radius) | fixed: Tools submit + account-setup close onto h-tap; phone composer input 44px pin; gate 2 demoted to implemented-partial; SPEC 407→426; short-runway/short-answer smoke pins retuned; verify:cheap + verify:pr-local + 2 smoke tests green; CI Production UI in progress | verify:cheap 471/4879; verify:pr-local build+fixtures; test:e2e 2 phone smoke passed; design-system-contract raw 2/0/0 |
| 2026-08-02 | claude/ds-v2-values | d2a29198415292a28ed1dbf1c740c94f6c12ddb3 | PR #1571 review-and-fix (ds-v2-values tap/radius) | fixed+clarified: gate2 objective acknowledges h-10/unwired audit not blocked; overlay 430px assertion scoped to media block; prior tip 48012d35 Production UI+PR required green | verify:cheap; verify:pr-local; 2 smoke; UI overlay contract; CI Production UI (1)(2)(3)+PR required SUCCESS on 48012d35 |
| 2026-08-02 | codex/fix-manual-workflow-service-role-key-exposure / PR #1572 | 6cc0b6ffdae3b515a05a17f2a87e5f51e5347263 | PR review + fix (ingestion-autopilot service-role exposure) | Reviewed secret-hardening: workflow/job-level SUPABASE_SERVICE_ROLE_KEY removed, manual dispatch limited to default branch, checkout pinned to default_branch, secret scoped to Preflight + Run autopilot only. Fixed CI blockers: merged origin/main (branch was ~1144 behind; Gitleaks needed run-gitleaks-pinned.mjs), registered tests/ingestion-autopilot-workflow.test.ts in test:ci-workflows, Prettier + stronger step-only secret assertions. No P0/P1 residual in the hardened workflow; residual risk is intentional inability to dry-run workflow_dispatch from non-default branches until merge. | npm run test:ci-workflows 206/206; focused ci-cache-safety + ingestion-autopilot-workflow 21/21; check:github-actions; prettier --check; git diff --check. No OpenAI/Supabase/provider calls. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| 2026-08-02 | claude/ds-v2-answer-safety | 8ad91e3f0104b89b83a54255687408cae574ee88 | DS V2 PR-E slices 6+7+8: answer safety, form foundation, announcements | Clinical governance review: no P0; 8 findings fixed in-branch; P1-1 strengthened; #208/#209/#210 deferred and recorded. Zero product imports - nothing adopted. | verify:pr-local exit 0 (475 files / 4960 passed, offline RAG 23 suites / 574 passed); verify:ui 342 passed / 5 failed not attributable (no product import); e2e:critical 15 passed |
| 2026-08-02 | claude/ds-v2-architecture | 157373700281f81f136275c56c9a82fb22776d99 | PR-Arch Wave4 motion/z/overlays/print | local gates green; frontend-ui-reviewer API-limited — glance required before auto-merge | unit 4972p; e2e:critical 15p; verify:ui 347p; verify:pr-local 0; eval:rag:offline pass |
| 2026-08-02 | claude/ds-v2-architecture | 93bb4b4756a7fad22f93008325f2c0f72471b0db | PR-Arch Wave4 motion/z/overlays/print | local gates green; frontend-ui-reviewer API-limited — glance required before auto-merge | unit 4972p; e2e:critical 15p; verify:ui 347p; verify:pr-local 0; eval:rag:offline pass |
Expand All @@ -602,3 +603,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-08-02 | codex/mcp-config-hardening-merge | 30aac79e655225f41006d5739fc4e91ef7791514 | MCP Cloud config hardening | Supersedes prior review; synced main and retained parser hardening with Windows Cloud test coverage | check:codex-cloud; Cloud/Python tests 22 passed |
| 2026-08-03 | claude/ds-v2-adopt | 407c8e74a240fbc2e0469b5a8334f55afd61e60d | bugbot PR #1595 | findings: P2 empty-sources fallback fixed; no P0/P1; residuals #217/#224 EmptyState heading, clipboard metadata unwired, #216 AnswerCard deferred | npm test 5061 passed; vitest answer surfaces 112 passed; no cursor[bot] Bugbot threads on PR |
| 2026-08-03 | claude/ds-v2-adopt | cb898bec11082c533aae5f42fbd415de1ff0f5e4 | review-and-fix PR #1595 | fixed P1 stale Open source nav + P2 clipboard metadata + RadioGroup id; dispositioned #225 favourites triple live; merge-tree clean | vitest 111 passed (answer-copy, cited-href, clipboard-product, ui-v2-components, ui-v2-answer-safety) |
| 2026-08-04 | codex/fix-manual-workflow-service-role-key-exposure / PR #1572 | 6cc0b6ffdae3b515a05a17f2a87e5f51e5347263 | PR review + fix (ingestion-autopilot service-role exposure) (supersedes 2026-08-02) | Supersedes the earlier row to record decisive gate results; historical review outcome otherwise unchanged. | PASS: npm run test:ci-workflows — 206/206 passed; PASS: focused ci-cache-safety + ingestion-autopilot-workflow — 21/21 passed; PASS: npm run check:github-actions — GitHub Actions pin check passed; PASS: prettier --check — all matched files use Prettier code style; PASS: git diff --check — clean. No provider calls. |
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"test:coverage": "node scripts/run-vitest.mjs run --coverage",
"test:coverage:node": "node scripts/run-vitest.mjs run --project=node --coverage",
"test:coverage:ui": "node scripts/run-vitest.mjs run --project=jsdom --coverage",
"test:ci-workflows": "node scripts/run-vitest.mjs run tests/ci-cache-safety.test.ts tests/authenticated-live-workflow.test.ts tests/codex-autofix-workflow.test.ts tests/eval-canary-workflow.test.ts tests/container-ci-contract.test.ts tests/test-runner-safety.test.ts tests/installed-lock-parity.test.ts tests/railway-config.test.ts tests/ingestion-autopilot.test.ts tests/check-lighthouse-budget.test.ts tests/offline-release-profile.test.ts",
"test:ci-workflows": "node scripts/run-vitest.mjs run tests/ci-cache-safety.test.ts tests/authenticated-live-workflow.test.ts tests/codex-autofix-workflow.test.ts tests/eval-canary-workflow.test.ts tests/container-ci-contract.test.ts tests/test-runner-safety.test.ts tests/installed-lock-parity.test.ts tests/railway-config.test.ts tests/ingestion-autopilot.test.ts tests/ingestion-autopilot-workflow.test.ts tests/check-lighthouse-budget.test.ts tests/offline-release-profile.test.ts",
"test:e2e": "node scripts/run-playwright.mjs",
"test:e2e:all": "node scripts/run-playwright.mjs",
"test:e2e:accessibility": "node scripts/run-playwright.mjs tests/ui-accessibility.spec.ts --project=chromium",
Expand Down
50 changes: 50 additions & 0 deletions tests/ingestion-autopilot-workflow.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";

import { describe, expect, it } from "vitest";

const workflow = readFileSync(join(process.cwd(), ".github", "workflows", "ingestion-autopilot.yml"), "utf8").replace(
/\r\n/g,
"\n",
);

describe("ingestion autopilot workflow secret handling", () => {
it("does not expose the Supabase service-role key to checkout or install steps", () => {
// Workflow-level and job-level env both use fewer than 10 spaces; only
// step-scoped env under Preflight / Run autopilot should inject the secret.
expect(workflow).not.toMatch(/^(?: {0,6})env:\n(?: {2,8}[^\n]+\n)* {2,8}SUPABASE_SERVICE_ROLE_KEY:/m);

expect(workflow).not.toMatch(/^ workflow_dispatch:/m);
expect(workflow).toContain("repository_dispatch:");
expect(workflow).toContain("types: [ingestion-autopilot]");
expect(workflow).toContain("APPLY_REQUESTED: ${{ github.event.client_payload.apply || 'false' }}");

const secretLines = workflow.split("\n").filter((line) => line.includes("SUPABASE_SERVICE_ROLE_KEY:"));
expect(secretLines).toEqual([
" SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}",
" SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}",
]);

const installIndex = workflow.indexOf(" - name: Install dependencies");
const checkoutIndex = workflow.indexOf(" - name: Checkout");
const preflightIndex = workflow.indexOf(" - name: Preflight required secrets");
const setupIndex = workflow.indexOf(" - name: Setup Node.js");
const autopilotIndex = workflow.indexOf(" - name: Run autopilot");
expect(checkoutIndex).toBeGreaterThan(-1);
expect(preflightIndex).toBeGreaterThan(checkoutIndex);
expect(setupIndex).toBeGreaterThan(preflightIndex);
expect(installIndex).toBeGreaterThan(-1);
expect(autopilotIndex).toBeGreaterThan(installIndex);

const checkoutBlock = workflow.slice(checkoutIndex, preflightIndex);
const preflightBlock = workflow.slice(preflightIndex, setupIndex);
expect(checkoutBlock).not.toContain("SUPABASE_SERVICE_ROLE_KEY");
expect(preflightBlock).toContain("SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}");

const installBlock = workflow.slice(installIndex, autopilotIndex);
expect(installBlock).not.toContain("SUPABASE_SERVICE_ROLE_KEY");

const autopilotBlock = workflow.slice(autopilotIndex);
expect(autopilotBlock).toContain("SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
});
Loading