Skip to content

fix(audit-sprint-7a): db:test:verify cleanup leak + attestation landing surface - #175

Merged
ejay-dev merged 1 commit into
mainfrom
fix/audit-sprint-7a-ci-probe-leak-sidebar
May 23, 2026
Merged

fix(audit-sprint-7a): db:test:verify cleanup leak + attestation landing surface#175
ejay-dev merged 1 commit into
mainfrom
fix/audit-sprint-7a-ci-probe-leak-sidebar

Conversation

@ejay-dev

@ejay-dev ejay-dev commented May 23, 2026

Copy link
Copy Markdown
Owner

Summary

Two small follow-ups bundled together.

1. db:test:verify probe-org cleanup leak

Symptom: Sprint 5b discovery — 95 orphaned FormaOS DB Verify <ts> rows in production organizations. Cleanup was silently failing.

Root cause: scripts/check-db-test-verify.mjs:308 cleanupProbe() called .delete().eq(...) without checking the returned error — failed deletes left rows behind with no log line.

Fix:

  • Wrap every cleanup delete in deleteAndLog() that surfaces error.message to console.warn
  • Add sweepOldProbes(admin) at the START of every invocation: removes any FormaOS DB Verify% org older than 1 hour. Bounded by the LIKE pattern + age cutoff so parallel invocations aren't disturbed and real orgs are never touched.
  • First post-deploy run cleans up the historical 95-row backlog; subsequent runs stay at ~0 unless a future regression fires (the new error logging will surface it).

2. Compliance attestation landing surface

Symptom: PR #173 shipped /app/compliance/attestations but no UI entry point. Discoverable only by URL.

Why not the sidebar: Per-industry sidebar navs live in 9 separate arrays in lib/navigation/industry-sidebar.ts. Adding a sub-link to all 9 is churn for one route.

Fix: Add a simple linked card on the /app/compliance landing above the obligations table. The landing is the natural compliance hub.

Validation

  • npm run type-check clean
  • npm run lint 0 errors, 18 warnings (baseline)
  • Manual: after merge, the next CI run with Supabase env should show [sweepOldProbes] removing 95 stale probe org(s) once, then 0 on subsequent runs
  • Manual: /app/compliance shows the new attestations card above the obligations table

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a "Manual attestations" link to the Compliance page for easier navigation to attestation management.

Review Change Stack

…ng surface

Two unrelated follow-ups bundled because each is too small for its own PR.

CI probe leak in check-db-test-verify.mjs
  - cleanupProbe() called delete().eq(...) without checking the
    returned error — every failed delete left a "FormaOS DB Verify
    <ts>" row in organizations. Sprint 5b counted 95 such orphans.
  - Wrap every cleanup delete in deleteAndLog() that surfaces the
    error message to console.warn. Future failures get diagnosed
    instead of accumulating silently.
  - Add sweepOldProbes(admin) that runs at the START of every
    invocation: removes any 'FormaOS DB Verify%' org older than 1
    hour. Bounded by the LIKE pattern + age cutoff so a parallel
    invocation isn't disturbed and real orgs are never touched.
    First invocation cleans up the historical 95-row backlog;
    subsequent invocations stay at ~0 unless a future bug fires
    and the new error logging surfaces it.

Compliance landing → attestations surface
  - Sprint 6c shipped /app/compliance/attestations but it had no
    entry point from the rest of the UI. Per-industry sidebar navs
    (lib/navigation/industry-sidebar.ts) are 9 separate arrays —
    not worth churning for a single sub-link.
  - Add a simple linked card on the /app/compliance landing page
    pointing at the attestations workflow. The landing is the
    natural compliance hub; users browsing the area now see the
    affordance.

Validation
  - tsc -p tsconfig.typecheck.json: clean
  - eslint: 0 errors, 18 warnings (baseline)
  - jest: not run (no test-relevant changes)
  - scripts/check-db-test-verify.mjs not exercised end-to-end here
    (needs Supabase env); shape changes verified via dry read

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 23, 2026 16:09
@vercel

vercel Bot commented May 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
forma-os Building Building Preview, Comment May 23, 2026 4:09pm

Request Review

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces a compliance page navigation link to manual attestations and enhances database test cleanup with improved observability. The compliance page now displays a styled call-to-action link, while the database verification script adds delete logging, probe cleanup enhancements, and a stale probe pre-check sweep.

Changes

Compliance Attestations Navigation

Layer / File(s) Summary
Compliance attestations navigation link
app/app/compliance/page.tsx
Page imports Next.js Link and lucide icons (ClipboardCheck, ArrowRight), then renders a styled link to /app/compliance/attestations with descriptive text and icon.

Database Probe Cleanup Observability

Layer / File(s) Summary
Delete logging utility and enhanced cleanup
scripts/check-db-test-verify.mjs
Introduces deleteAndLog helper that wraps delete queries and warns on failure. Updates cleanupProbe to use this helper for deleting probe rows (form submissions, forms, org membership, organizations, legacy orgs) and logging auth user deletion failures.
Stale probe organization sweep
scripts/check-db-test-verify.mjs
Adds sweepOldProbes function that identifies stale organizations rows matching FormaOS DB Verify% older than 1 hour and deletes related rows in foreign-key dependency order (form submissions → forms → org_members → organizations → legacy orgs) with failure logging.
Main execution flow integration
scripts/check-db-test-verify.mjs
Updates main() to call await sweepOldProbes(admin) before existing table/bucket verification, with cleanupProbe(admin) still running in a finally block.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A link springs forth on compliance's path,
While database sweepers clear the chaff,
Old probes depart with logged goodbye,
New navigation helps users fly,
FormaOS grows more tidy and bright! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes both main changes: fixing a db cleanup leak and adding the attestation landing surface.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/audit-sprint-7a-ci-probe-leak-sidebar

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e59fd913f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* sub-link; the landing page is the natural hub.
*/}
<Link
href="/app/compliance/attestations"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Point attestations card to an existing route

The new card links to /app/compliance/attestations, but there is no matching App Router page in this repo (under app/app/compliance only page.tsx, soc2, cross-map, and frameworks routes exist), so clicking this CTA currently lands users on a 404. Because this is now the primary UI entry point for manual attestations, the broken href makes the surfaced workflow unusable.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
scripts/check-db-test-verify.mjs (1)

370-377: ⚡ Quick win

Paginate stale-probe sweep beyond the first 500 rows.

Line 377 caps cleanup candidates to 500 in a single pass. If backlog exceeds that, stale probe orgs remain after a run. Iterate in batches until fewer than batch size are returned.

♻️ Proposed fix
 async function sweepOldProbes(admin) {
   const cutoff = new Date(Date.now() - 60 * 60 * 1000).toISOString();
-  const { data: orphans, error: listError } = await admin
-    .from('organizations')
-    .select('id, name, created_at')
-    .like('name', 'FormaOS DB Verify%')
-    .lt('created_at', cutoff)
-    .limit(500);
-
-  if (listError) {
-    console.warn(`[sweepOldProbes] list failed: ${listError.message}`);
-    return;
-  }
-
-  const rows = orphans ?? [];
-  if (rows.length === 0) return;
-
-  console.log(`[sweepOldProbes] removing ${rows.length} stale probe org(s)`);
-  const ids = rows.map((r) => r.id);
-
-  // Order matters — FK-respecting teardown.
-  await deleteAndLog(
-    admin,
-    'org_form_submissions',
-    admin.from('org_form_submissions').delete().in('org_id', ids),
-  );
-  await deleteAndLog(
-    admin,
-    'org_forms',
-    admin.from('org_forms').delete().in('org_id', ids),
-  );
-  await deleteAndLog(
-    admin,
-    'org_members',
-    admin.from('org_members').delete().in('organization_id', ids),
-  );
-  await deleteAndLog(
-    admin,
-    'organizations',
-    admin.from('organizations').delete().in('id', ids),
-  );
-  await deleteAndLog(
-    admin,
-    'orgs',
-    admin.from('orgs').delete().in('id', ids),
-  );
+  const batchSize = 500;
+  let total = 0;
+
+  while (true) {
+    const { data: orphans, error: listError } = await admin
+      .from('organizations')
+      .select('id, name, created_at')
+      .like('name', 'FormaOS DB Verify%')
+      .lt('created_at', cutoff)
+      .limit(batchSize);
+
+    if (listError) {
+      console.warn(`[sweepOldProbes] list failed: ${listError.message}`);
+      return;
+    }
+
+    const rows = orphans ?? [];
+    if (rows.length === 0) break;
+
+    total += rows.length;
+    const ids = rows.map((r) => r.id);
+
+    // Order matters — FK-respecting teardown.
+    await deleteAndLog(
+      admin,
+      'org_form_submissions',
+      admin.from('org_form_submissions').delete().in('org_id', ids),
+    );
+    await deleteAndLog(
+      admin,
+      'org_forms',
+      admin.from('org_forms').delete().in('org_id', ids),
+    );
+    await deleteAndLog(
+      admin,
+      'org_members',
+      admin.from('org_members').delete().in('organization_id', ids),
+    );
+    await deleteAndLog(
+      admin,
+      'organizations',
+      admin.from('organizations').delete().in('id', ids),
+    );
+    await deleteAndLog(
+      admin,
+      'orgs',
+      admin.from('orgs').delete().in('id', ids),
+    );
+
+    if (rows.length < batchSize) break;
+  }
+
+  if (total > 0) {
+    console.log(`[sweepOldProbes] removing ${total} stale probe org(s)`);
+  }
 }
🤖 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/check-db-test-verify.mjs` around lines 370 - 377, The sweepOldProbes
function currently limits candidates to 500 once, leaving leftovers; change it
to paginate and repeat deletes until a fetched batch is smaller than the batch
size: extract the current cutoff and batchSize (e.g., 500) and wrap the
query+deletion logic in a loop that calls
admin.from('organizations').select(...).like(...).lt('created_at',
cutoff).limit(batchSize) each iteration, processes/deletes the returned orphans
(the variable orphans) and breaks when orphans.length < batchSize or when no
rows are returned; ensure you still handle listError and deletion errors inside
the loop and avoid infinite loops by preserving the same cutoff and reducing
results via deletion each pass.
🤖 Prompt for all review comments with 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.

Nitpick comments:
In `@scripts/check-db-test-verify.mjs`:
- Around line 370-377: The sweepOldProbes function currently limits candidates
to 500 once, leaving leftovers; change it to paginate and repeat deletes until a
fetched batch is smaller than the batch size: extract the current cutoff and
batchSize (e.g., 500) and wrap the query+deletion logic in a loop that calls
admin.from('organizations').select(...).like(...).lt('created_at',
cutoff).limit(batchSize) each iteration, processes/deletes the returned orphans
(the variable orphans) and breaks when orphans.length < batchSize or when no
rows are returned; ensure you still handle listError and deletion errors inside
the loop and avoid infinite loops by preserving the same cutoff and reducing
results via deletion each pass.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c89aa8f-5e26-45f5-b816-7835cd99e939

📥 Commits

Reviewing files that changed from the base of the PR and between 5b370de and e59fd91.

📒 Files selected for processing (2)
  • app/app/compliance/page.tsx
  • scripts/check-db-test-verify.mjs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR bundles two follow-ups: (1) harden db:test:verify cleanup so probe orgs don’t leak into production, and (2) add a discoverable UI entry point to the compliance attestation workflow from the compliance landing page.

Changes:

  • Add deleteAndLog() and apply it to probe cleanup deletions to surface Supabase delete errors in logs.
  • Add sweepOldProbes(admin) at the start of db:test:verify to remove stale “FormaOS DB Verify%” orgs older than 1 hour.
  • Add a linked “Manual attestations” card on /app/compliance pointing to /app/compliance/attestations.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
scripts/check-db-test-verify.mjs Adds error-logging wrapper for cleanup deletes and a startup sweep for stale probe organizations.
app/app/compliance/page.tsx Adds a card link on the compliance landing page to the manual attestation workflow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +308 to +316
async function deleteAndLog(admin, table, query) {
const { error } = await query;
if (error) {
// Audit Sprint 7a (2026-05-24): previously cleanup errors were
// ignored. Live DB had 95 leftover "FormaOS DB Verify" orgs
// because a failing delete left rows behind silently. Log so the
// operator can investigate without grepping prod for orphans.
console.warn(`[cleanupProbe] ${table} delete failed: ${error.message}`);
}
Comment on lines +308 to +310
async function deleteAndLog(admin, table, query) {
const { error } = await query;
if (error) {
Comment on lines +372 to +415
const { data: orphans, error: listError } = await admin
.from('organizations')
.select('id, name, created_at')
.like('name', 'FormaOS DB Verify%')
.lt('created_at', cutoff)
.limit(500);

if (listError) {
console.warn(`[sweepOldProbes] list failed: ${listError.message}`);
return;
}

const rows = orphans ?? [];
if (rows.length === 0) return;

console.log(`[sweepOldProbes] removing ${rows.length} stale probe org(s)`);
const ids = rows.map((r) => r.id);

// Order matters — FK-respecting teardown.
await deleteAndLog(
admin,
'org_form_submissions',
admin.from('org_form_submissions').delete().in('org_id', ids),
);
await deleteAndLog(
admin,
'org_forms',
admin.from('org_forms').delete().in('org_id', ids),
);
await deleteAndLog(
admin,
'org_members',
admin.from('org_members').delete().in('organization_id', ids),
);
await deleteAndLog(
admin,
'organizations',
admin.from('organizations').delete().in('id', ids),
);
await deleteAndLog(
admin,
'orgs',
admin.from('orgs').delete().in('id', ids),
);
@github-actions

Copy link
Copy Markdown

♿ Accessibility Test Results

PASSED - No critical accessibility issues found

Tests Performed:

  • WCAG 2.1 AA compliance validation
  • Cross-browser accessibility testing
  • Keyboard navigation testing
  • Screen reader compatibility
  • Color contrast validation

Artifacts: Download the accessibility reports from the "Artifacts" section for detailed results.

@ejay-dev
ejay-dev merged commit 60cadca into main May 23, 2026
27 of 31 checks passed
@ejay-dev
ejay-dev deleted the fix/audit-sprint-7a-ci-probe-leak-sidebar branch May 23, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants