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
27 changes: 27 additions & 0 deletions app/app/compliance/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Suspense } from 'react';
import Link from 'next/link';
import { ClipboardCheck, ArrowRight } from 'lucide-react';
import { ObligationsTable } from '@/components/compliance/ObligationsTable';
import { CompliancePageHero } from '@/components/compliance/CompliancePageHero';
import { SkeletonCard } from '@/components/ui/skeleton';
Expand All @@ -8,6 +10,31 @@ export default function ComplianceIndexPage() {
<div className="flex flex-col h-full">
<CompliancePageHero />

{/*
* Audit Sprint 7a (2026-05-24): surface the manual-attestation
* workflow (Sprint 6c PR #173) from the compliance landing.
* Per-industry sidebar navs (lib/navigation/industry-sidebar.ts)
* are 9 separate arrays and not worth churning for a single
* 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 👍 / 👎.

className="mx-4 mb-4 flex items-center justify-between rounded-lg border border-slate-800 bg-slate-900/60 px-4 py-3 transition-colors hover:border-slate-700 hover:bg-slate-900 sm:mx-0"
>
<div className="flex items-center gap-3">
<ClipboardCheck className="h-5 w-5 text-slate-400" />
<div>
<p className="text-sm font-medium text-slate-100">
Manual attestations
</p>
<p className="text-xs text-slate-400">
Controls whose evaluator requires a human sign-off.
</p>
</div>
</div>
<ArrowRight className="h-4 w-4 text-slate-500" />
</Link>

<div className="flex-1 overflow-auto -mx-4 px-4 sm:mx-0 sm:px-0">
<Suspense fallback={<SkeletonCard className="h-96" />}>
<ObligationsTable />
Expand Down
112 changes: 106 additions & 6 deletions scripts/check-db-test-verify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -305,19 +305,114 @@ async function verifyAuthenticatedFormsRoundTrip(admin, anon) {
}
}

async function deleteAndLog(admin, table, query) {
const { error } = await query;
if (error) {
Comment on lines +308 to +310
// 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 +316
}

async function cleanupProbe(admin) {
if (cleanup.formId) {
await admin.from('org_form_submissions').delete().eq('form_id', cleanup.formId);
await admin.from('org_forms').delete().eq('id', cleanup.formId);
await deleteAndLog(
admin,
'org_form_submissions',
admin.from('org_form_submissions').delete().eq('form_id', cleanup.formId),
);
await deleteAndLog(
admin,
'org_forms',
admin.from('org_forms').delete().eq('id', cleanup.formId),
);
}
if (cleanup.orgId) {
await admin.from('org_members').delete().eq('organization_id', cleanup.orgId);
await admin.from('organizations').delete().eq('id', cleanup.orgId);
await admin.from('orgs').delete().eq('id', cleanup.orgId);
await deleteAndLog(
admin,
'org_members',
admin.from('org_members').delete().eq('organization_id', cleanup.orgId),
);
await deleteAndLog(
admin,
'organizations',
admin.from('organizations').delete().eq('id', cleanup.orgId),
);
await deleteAndLog(
admin,
'orgs',
admin.from('orgs').delete().eq('id', cleanup.orgId),
);
}
if (cleanup.userId) {
await admin.auth.admin.deleteUser(cleanup.userId);
const { error } = await admin.auth.admin.deleteUser(cleanup.userId);
if (error) {
console.warn(`[cleanupProbe] auth user delete failed: ${error.message}`);
}
}
}

/**
* Audit Sprint 7a (2026-05-24): sweep any "FormaOS DB Verify <ts>"
* orgs older than 1 hour. The script has been accumulating probe
* orgs whenever cleanupProbe silently failed (no error logging
* pre-this-fix). Run at the START of each invocation so a healthy
* env doesn't accumulate orphans even if the new error logging
* surfaces a future bug after-the-fact.
*
* Scoped narrowly:
* - name LIKE 'FormaOS DB Verify%' so we don't touch real orgs
* - created_at < now() - 1 hour so we never delete a probe that's
* currently running in a parallel invocation
*/
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),
);
Comment on lines +372 to +415
}

async function main() {
Expand All @@ -331,6 +426,11 @@ async function main() {

const { admin, anon } = createSupabaseClients();

// Audit Sprint 7a: clean up any stale probe orgs from prior runs
// before doing the current check. Bounded to "FormaOS DB Verify%"
// older than 1h so a parallel invocation isn't disturbed.
await sweepOldProbes(admin);

try {
for (const table of requiredTables) {
await verifyTable(admin, table);
Expand Down
Loading