Skip to content

Enterprise audit remediation v2: CRITICAL/HIGH fixes + financial-services & mental-health verticals - #204

Merged
ejay-dev merged 10 commits into
mainfrom
fix/audit-remediation-v2-2026-06-01
Jun 1, 2026
Merged

Enterprise audit remediation v2: CRITICAL/HIGH fixes + financial-services & mental-health verticals#204
ejay-dev merged 10 commits into
mainfrom
fix/audit-remediation-v2-2026-06-01

Conversation

@ejay-dev

@ejay-dev ejay-dev commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Summary

Acts on a full enterprise audit of FormaOS: ships the CRITICAL + HIGH fixes, three MEDIUM correctness/ops fixes, and two greenfield builds the owner requested (financial-services evaluator suite + a brand-new Mental Health Services / NSMHS industry vertical).

Stacked on fix/enterprise-audit-remediation so the diff is exactly the 8 commits below. Can be retargeted to main if preferred (that view also carries 3 inherited base-branch commits).

What's included

Area Change
C1 · CRITICAL Stripe dispute handlers were dead in prod — dispute.charge is a string in webhooks, so the typeof === 'object' guard never matched. Now resolves the customer via charge / payment-intent retrieval; chargebacks correctly pause entitlements.
H1/H2 past_due is admitted during the 3-day grace window (was hard-locked on day 0, making the grace module unreachable); read-only enforced after grace via an assertOrgCanWrite chokepoint in requirePermission.
H3 Single canonical compliance score — the dashboard snapshot now reads the persisted evaluator-overlay verdict instead of a divergent heuristic (the auditor-facing export no longer shows two different numbers). Removed dead unified-score.ts.
H4 Team seat check (if (limit)if (limit != null)) — was granting unlimited seats at limit 0; aligned with the API path.
H5 Wired the dead "Add Medication" button — createMedication action + inline form.
H6 playwright test --list crash fixed (0 → 3375 tests) by extracting a pure pack-registry module out of the server-only chain.
H8/M9/M10 PR gate runs Jest + --max-warnings; STRIPE_PRICE_SCALE wired into deploy env; post-deploy health checks now fail on outage.
M3 Compliance graph persistedgraph_nodes/graph_wires migration (FORCE RLS, append-only, service-role writes / member reads) + GET /api/v1/compliance/graph.
M4/M8 Automation orchestrator scheduled in vercel.json; data-retention cron round-robins via a last_retention_at cursor (no longer starves orgs past the first 250).
M5 (financial) financial-services-au evaluator suite — 20 controls (7 DB-signal, 13 honest manual attestation; never false-passes).
New vertical Mental Health Services (NSMHS 2010) end-to-end: framework pack (10 standards / 14 controls) + 14 evaluators, industry nav, dashboard widgets, onboarding, marketing page + SEO.
misc canceled/cancelled status normalization; annotated two legitimate single-org admin reads to keep the tenant-isolation ratchet flat.

Verification (all green, this session)

  • Production next build — success (exit 0)
  • Typecheck — clean · Lint — 0 errors (7 warnings, under ceiling) · Jest5505 passed / 0 failed
  • check:framework-packs 12 packs · check:app-links 382/0 broken · check:admin-nav OK · check:security-baseline pass=8/0/0
  • check-rls-current-setting 0 violations · RLS contracts static scan PASS (226 tables) · tenant-isolation ratchet passes (263 baseline)
  • playwright test --list 3375 tests (was crashing at 0)

Needs the maintainer (can't be done from CI)

  1. Apply 2 migrations to staging/prod: 20260624074 (retention cursor) and 20260624075 (graph tables). Both written deploy-safe; the graph table's live _audit_rls_status can only be validated post-apply.
  2. Set repo secrets/vars: E2E_GATE_STRICT=true (makes the E2E gate blocking) and STRIPE_PRICE_SCALE.
  3. Staging smoke of paths not exercisable here: dispute webhook (C1), past_due grace flow (H1/H2), and the new MH vertical end-to-end.

Deliberately out of scope (flagged, not done)

Wiring the props-only ComplianceGraph SVG to the new API (new UI surface — needs sign-off); a clinical-domain review of the NSMHS evaluator predicates (noted in register.ts).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Mental Health Services industry: marketing pages, sitemap, onboarding, dashboards, widgets, and compliance pack.
    • Add medication creation flow and inline "Add Medication" UI on participant pages.
    • New API to retrieve persisted compliance graph.
  • Improvements

    • Deployment checks and security validations now fail fast to block bad deployments.
    • Data-retention job now cycles organizations more evenly.
    • Billing grace-period now grants temporary access.
  • Bug Fixes

    • Subscription status spelling/consistency fixes across the app.

ejay-dev and others added 8 commits June 1, 2026 18:17
- C1 (billing): resolve dispute customer from string charge / payment_intent;
  the typeof==='object' guard never matched a webhook payload, so both
  charge.dispute.created and .closed handlers were dead in production.
- H4 (billing): team seat check used `if (limit)` — skipped enforcement at
  limit 0; now `if (limit != null)`, matching the /api/v1/members/invite path.
- H6 (tests): extract pure framework pack-registry (no server-only import) so
  `playwright test --list` no longer crashes (0 tests -> 3370).
- H8 (CI): run Jest + lint --max-warnings 25 on the PR quality-gate.
- M4 (ops): schedule /api/automation/cron (entitlement-drift + automation
  triggers) hourly in vercel.json.
- M8 (ops): data-retention cron round-robins via last_retention_at cursor
  (+ migration + best-effort stamp) instead of starving orgs past the first 250.
- M9 (CI): pass STRIPE_PRICE_SCALE to deployment-gates env.
- M10 (CI): post-deploy health/security curls now fail the job on outage
  (-Lf, --retry, follow apex->www).
- canceled-normalization: write 'canceled' from customer.deleted / admin-sync /
  account-delete; reader tolerates legacy 'cancelled'.

Verified: typecheck clean, 404 affected unit tests pass, playwright --list ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
H1: /app layout admits past_due *during* the grace window (isReadOnly===false)
instead of hard-locking on day 0. past_due is the one Stripe-recoverable state
(card retry); billing-004's allowlist had made the existing 3-day grace module
unreachable. All other unpaid states stay redirected.

H2: enforce read-only after grace at a chokepoint — requirePermission() now
calls assertOrgCanWrite() for mutating permissions (EDIT_CONTROLS,
UPLOAD/APPROVE/REJECT_EVIDENCE, RESOLVE_COMPLIANCE_BLOCK, GENERATE_CERTIFICATIONS,
MANAGE_USERS, DRAFT_AI_POLICIES). Read/export permissions stay available so a
read-only org can still view and extract its data. Previously only evidence +
attestations enforced this.

Verified: typecheck clean, 262 gated-action unit tests pass, no circular import,
no write-permission checks in page render paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The posture-dashboard snapshot recomputed a heuristic-only score with no
evaluator overlay, so it disagreed with the framework-evaluation page — and
audit-package emitted BOTH numbers in the same auditor-facing export.

- get-org-compliance-snapshot now overlays the persisted evaluator-aware
  status (org_control_evaluations.control_type='framework_control') onto its
  heuristic when an evaluation has run for a control. Reading the persisted
  verdict (not re-running ~253 evaluators live) keeps the dashboard cheap
  while making it report the same number as the framework page.
- Deleted lib/compliance/unified-score.ts + test — a third, unused scoring
  formula with zero runtime importers; updated the stale builder-page comment.

Verified: typecheck clean, 447 compliance/snapshot/audit/executive tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The org_medications table + RLS + administer route already existed, but the
prominent "Add Medication" CTA had no handler (server component, no onClick)
and there was no other path to add a medication.

- Add createMedication server action (mirrors createIncident; inserts org_id
  — the table scopes by org_id, not organization_id — verifies the participant
  belongs to the org, validates route, writes a MEDICATION_CREATED audit
  event, revalidates the page).
- MedicationChart gains a collapsible inline add-form bound to the action
  (reuses the existing inline-form pattern; no new modal primitive per the
  standing UI-surface guidance). Removed the dead header button.

Verified: typecheck clean, 38 care unit tests pass, lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rols)

The pack was installed but had zero evaluators, so its 20 controls only got
the evidence heuristic. Adds the evaluator directory + registry wiring.

- 7 DB-signal evaluators (AFS-003/004, CPS-001/002/004, AML-001, AFCA-002)
  reading org_policies cadence / org_registers (conflict_of_interest, BCP,
  complaint) / org_risks / org_audit_logs. All return manual-attestation or
  fail (never a false pass) when finance-tagged rows are absent.
- 13 manual-attestation evaluators with control-specific messages. AFS-006 and
  AML-004 deliberately fell back from DB-signal: org_regulatory_notifications'
  CHECK constraint excludes ASIC/AUSTRAC and requires an incident_id FK, so
  reading it would surface unrelated NDIS rows (false signal).
- FrameworkSlug union + register.ts wiring (registry key financial-services-au/<code>).

Verified: typecheck clean, check:framework-packs OK (11 packs), evaluator
suite 14/14 (107 tests) pass, 20 evaluators registered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "compliance graph" (node-wire) was built in memory on login, logged, and
discarded — no backing store, no consumer. Now it's persisted and queryable.

- Migration 20260624075: graph_nodes + graph_wires (FK to organizations
  ON DELETE CASCADE, node_type/wire_type CHECK, unique keys, org-scoped
  indexes). ENABLE+FORCE RLS, member-only SELECT, append-only RESTRICTIVE
  write policies (service-role writes bypass; no app.* GUC).
- lib/compliance-graph.ts: rebuildOrgGraph() derives nodes/wires from tenant
  tables and upserts idempotently via the service-role org client;
  getComplianceGraph() reads via the member session client; initialize/repair
  now persist. Existing exports + return shapes preserved.
- Registered graph_nodes/graph_wires in TENANT_TABLE_SCOPES.
- GET /api/v1/compliance/graph: rate-limited, org-guarded, member-session read.
- No new UI (the props-driven SVG component is left for separate sign-off).

Verified: typecheck clean, compliance-graph + org-scoped tests 25/25,
check-rls-current-setting 0 violations, RLS-contracts static scan includes
both tables. DB-gated: live _audit_rls_status needs the migration applied
to staging to fully validate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full industry vertical anchored on the National Standards for Mental Health
Services (NSMHS) 2010, end-to-end across the platform.

Compliance/data layer:
- framework-packs/mental-health-au.json: 10 standards / 14 controls
  (Standard 10 split 10.1-10.5); manifest rehashed (12 packs).
- pack-registry entry + FrameworkSlug union.
- 14 evaluators (4 DB-signal: org_incidents/org_registers/org_policies/
  org_risks; 10 manual attestation) wired into register.ts.

App / dashboard / onboarding:
- industry-sidebar: 'mental_health' IndustryType + MENTAL_HEALTH_NAV (all
  hrefs resolve to existing /app routes), isCareIndustry + label.
- command-center + 3 industry widgets (reuse existing endpoints) + labels.
- WelcomeStep + industry-selector options; industry-packs, roadmap,
  IndustryFeatureHighlights entries.

Marketing / SEO:
- /mental-health-compliance page + content + opengraph-image (NSMHS-specific,
  enterprise-toned; no fabricated personas/metrics/backstory, Adelaide HQ).
- Listed in IndustryVerticals / IndustriesContent / homepage Industries.
- sitemap, llms.txt, llms-full.txt.
- Updated industry-roadmaps test (10 industries); shortened marketing
  subheadline to satisfy the strict copy budget.

Verified: typecheck clean, full Jest 5505 passed/0 failed, lint 0 errors,
check:framework-packs 12 packs, marketing-enterprise-audit --strict clean,
playwright --list 3375 tests, RLS GUC guard 0 violations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The framework-limit enforcement in org-frameworks.ts (getOrgFrameworkLimit +
existing-frameworks read) pairs the admin client with .eq('organization_id'),
which tripped the tenant-isolation ratchet (265 vs baseline 263) without a
justification. Both are single-org reads during provisioning sync with a
server-derived orgId — annotated with eslint-disable + rationale per the
rule's guidance. Ratchet back to baseline (passes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 1, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
forma-os Ready Ready Preview, Comment Jun 1, 2026 10:31am

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 06c4f766-3bca-4f4a-bbda-5cf8cfbcbeb1

📥 Commits

Reviewing files that changed from the base of the PR and between 1454486 and 3431cbc.

📒 Files selected for processing (1)
  • .github/workflows/formaos-quality-gates.yml

📝 Walkthrough

Walkthrough

Adds mental-health and financial-services compliance packs with many evaluators, persists the compliance graph (tables + RLS + rebuild/upsert), refactors pack registry, tightens deployment gates, and expands marketing, UI, billing, retention, permissions, and tests.

Changes

CI / Deployment Gates

Layer / File(s) Summary
Deployment health & security checks
.github/workflows/deployment-gates.yml
Adds STRIPE_PRICE_SCALE env var and hard-failing health/security checks using strict curl flags and explicit status validation.
Quality gate lint config
.github/workflows/formaos-quality-gates.yml
Caps lint warnings in core gate (--max-warnings 25).

Compliance Graph Persistence & API

Layer / File(s) Summary
DB migrations & org-scoped registration
supabase/migrations/20260624075_*.sql, lib/supabase/org-scoped.ts
Adds graph_nodes/graph_wires tables with uniqueness, FKs, indexes, RLS policies, and registers tables as org-scoped.
Graph types & rebuild/persistence
lib/compliance-graph.ts
Exports GraphNode/Wire types and persisted shapes; implements rebuildOrgGraph with idempotent upserts and ID resolution; persists during init/repair.
API route & tests
app/api/v1/compliance/graph/route.ts, __tests__/lib/compliance-graph.test.ts
Adds GET /api/v1/compliance/graph; tests assert upsert behavior, persisted reads via session client, and mapping of persisted node/wire fields.

Framework Packs & Evaluators

Layer / File(s) Summary
Pack manifest & mental-health pack
framework-packs/mental-health-au.json, framework-packs/manifest.json
Adds mental-health-au pack with domains and controls and updates manifest.
Pure pack-registry & installer re-exports
lib/frameworks/pack-registry.ts, lib/frameworks/framework-installer.ts, e2e/full-platform-matrix.spec.ts
Creates client/edge-safe pack registry with lookup helpers; re-exports from installer; e2e tests import PACK_SLUGS from new registry.
Evaluator registration & types
lib/compliance/evaluators/register.ts, lib/compliance/evaluators/types.ts
Registers financial-services-au and mental-health-au evaluator metas and extends FrameworkSlug union.
financial-services-au evaluators
lib/compliance/evaluators/financial-services-au/*
Adds shared helpers and many AFS/AML/CPS/AFCA evaluator modules.
mental-health-au evaluators
lib/compliance/evaluators/mental-health-au/*
Adds shared helpers and MHS 1–10.5 evaluator modules using manual attestation and DB-driven checks.

Marketing, Discovery & Product

Layer / File(s) Summary
Marketing page, metadata & OG
app/(marketing)/mental-health-compliance/*
Adds MentalHealthComplianceContent page, SEO metadata, FAQ schema, and next/og ImageResponse generator.
Homepage & industries content
app/(marketing)/components/homepage/Industries.tsx, app/(marketing)/industries/*
Adds Mental Health industry entries and Brain icon to homepage/industries lists.
LLMs docs & sitemap
app/llms.txt/route.ts, app/llms-full.txt/route.ts, app/sitemap.ts
Adds Mental Health entry and NSMHS mention to discovery docs and sitemap.
Navigation, widgets & onboarding
lib/navigation/industry-sidebar.ts, components/dashboard/*, lib/onboarding/industry-roadmaps.ts, components/onboarding/*
Adds mental_health nav, selector card, labels, dashboard widgets, onboarding roadmap, and feature highlights.

Billing, Retention, Permissions & Medication

Layer / File(s) Summary
Stripe dispute resolution & status normalization
app/api/billing/webhook/route.ts, app/app/admin/actions.ts, app/api/v1/account/delete/route.ts
Adds resolveDisputeCustomerId helper, writes canceled status verbatim, and normalizes cancel spelling handling.
Layout grace access & system-state
app/app/layout.tsx, lib/system-state/server.ts
Includes past_due grace-period access and treats legacy 'cancelled' status as canceled in module state.
Retention ordering & cron
app/api/cron/data-retention/route.ts, lib/data-governance/retention.ts, supabase/migrations/20260624074_*.sql, vercel.json
Order retention sweep by last_retention_at with fallback, stamp last_retention_at after runs, add migration, schedule hourly cron.
Write enforcement & medication feature
app/app/actions/rbac.ts, app/app/actions/team.ts, app/app/actions/care-operations.ts, components/care/medication-chart.tsx, app/app/participants/[id]/medications/page.tsx
Adds assertOrgCanWrite gating for write permissions, fixes seat-limit enforcement for zero, implements createMedication server action and client form, updates medication UI.

Scoring & Snapshot Overlay

Layer / File(s) Summary
Remove unified-score & persisted overlay
lib/compliance/unified-score.ts, lib/compliance/get-org-compliance-snapshot.ts
Deletes unified-score module and overlays persisted evaluator verdicts from org_control_evaluations into snapshot status computation.
Audit comments update
app/app/dashboard/builder/page.tsx
Updates widget comments to reflect persisted overlay as canonical scoring source and evaluator status synonyms.

Reports

Layer / File(s) Summary
GDPR & SOC2 report artifacts
tests/compliance/reports/*.json
Regenerates GDPR and SOC2 JSON reports with updated timestamps and environment-unavailable results.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

"🐰 I hopped through code with nibbling cheer,
New mental-health packs now appear,
Graphs persist, the registry sings,
Gates are stricter, cron clock rings,
A tiny carrot for reviewers here."

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/audit-remediation-v2-2026-06-01

@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: d19d62c71a

ℹ️ 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".

// READ path: getComplianceGraph uses the member-facing session client
// (no service-role exposure); the org-membership SELECT RLS policy on
// graph_nodes/graph_wires gates row visibility.
const { nodes, wires } = await getComplianceGraph(orgId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Backfill persisted graphs before serving only graph tables

For organizations that already existed before this migration, graph_nodes/graph_wires start empty and the existing-login path only calls validateComplianceGraph, not rebuildOrgGraph (repo-wide search shows rebuild is only called during new-org initialization and repair). In that context this new endpoint will return { nodes: [], wires: [] } indefinitely for otherwise populated orgs until some manual repair path runs, so the newly exposed graph API is blank for existing customers; add a migration/backfill or rebuild-on-empty fallback before relying solely on the persisted read.

Useful? React with 👍 / 👎.

Comment thread lib/compliance-graph.ts
Comment on lines +260 to +265
const { data: upsertedNodes, error: nodeError } = await admin
.from('graph_nodes')
.upsert(nodePayload, {
onConflict: 'organization_id,node_type,source_id',
})
.select('id, node_type, source_id');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove stale graph rows during rebuild

When source rows are deleted or unlinked after a graph has been persisted (for example a task/evidence row or member is removed), this rebuild only upserts the currently-derived rows and never deletes graph_nodes/graph_wires whose source rows are no longer present. Because the graph tables reference only their own persisted node ids, those obsolete nodes and edges remain visible through the new graph API even after subsequent rebuilds; prune rows not refreshed in this run or otherwise mark/delete stale edges.

Useful? React with 👍 / 👎.

@ejay-dev
ejay-dev changed the base branch from fix/enterprise-audit-remediation to main June 1, 2026 09:47
@ejay-dev ejay-dev closed this Jun 1, 2026
@ejay-dev ejay-dev reopened this Jun 1, 2026
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

🔒 Compliance Testing Results

Test Date: Mon Jun 1 09:54:17 UTC 2026
Test Type: all

GDPR Compliance

⚠️ ERROR - Unable to complete GDPR testing

SOC2 Compliance

⚠️ ERROR - Unable to complete SOC2 testing

Compliance Reports: Available in the artifacts section below

⚠️ ATTENTION REQUIRED: Compliance issues found that must be addressed before production deployment.

@github-actions

github-actions Bot commented Jun 1, 2026

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.

Guard the 8 graph_nodes/graph_wires CREATE POLICY statements with
DROP POLICY IF EXISTS so the migration is idempotent (the rest already
uses IF NOT EXISTS). Applied to prod via MCP this session; this keeps a
future `supabase db push` from erroring on "policy already exists".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

🔒 Compliance Testing Results

Test Date: Mon Jun 1 10:20:08 UTC 2026
Test Type: all

GDPR Compliance

⚠️ ERROR - Unable to complete GDPR testing

SOC2 Compliance

⚠️ ERROR - Unable to complete SOC2 testing

Compliance Reports: Available in the artifacts section below

⚠️ ATTENTION REQUIRED: Compliance issues found that must be addressed before production deployment.

@github-actions

github-actions Bot commented Jun 1, 2026

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.

The duplicate `npm test -- --ci` added to formaos-quality-gates' Core Build
Gate flaked on an order-sensitive hook test (useFeatureUsage/hasHighUsage)
under that job's worker scheduling — it passes locally (5505/0) and in the
qa-pipeline run. Unit tests are ALREADY gated on PRs by the qa-pipeline
"Unit & Integration Tests" job (npm run test:coverage, blocking on home-repo
PRs), so the duplicate added no coverage. Keep the --max-warnings 25 lint
hardening (the real H8 gap for this workflow).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

🔒 Compliance Testing Results

Test Date: Mon Jun 1 10:30:48 UTC 2026
Test Type: all

GDPR Compliance

⚠️ ERROR - Unable to complete GDPR testing

SOC2 Compliance

⚠️ ERROR - Unable to complete SOC2 testing

Compliance Reports: Available in the artifacts section below

⚠️ ATTENTION REQUIRED: Compliance issues found that must be addressed before production deployment.

@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.

Actionable comments posted: 5

🧹 Nitpick comments (3)
lib/frameworks/pack-registry.ts (1)

1-14: 💤 Low value

Module is not fully client/edge-safe.

The comment (lines 6-9) claims this module is "client/edge-safe" and can be consumed "from any context," but getPackFileForSlug uses Node.js-only APIs (path module and process.cwd()). While the other exports (PACK_REGISTRY, PACK_SLUGS, getFrameworkCodeForSlug, getFrameworkSlugForCode) are indeed safe, importing this module in a browser or edge runtime will fail if getPackFileForSlug is called.

The current usage (e2e tests only import PACK_SLUGS) avoids the problem, but the comment is misleading.

Suggested improvements

Consider one of these options:

  1. Update the comment to clarify that only specific exports are client-safe:
 /**
  * Pure framework-pack registry + slug/code lookups.
  *
- * This module deliberately has **no** `server-only` / admin-client imports so
- * it can be consumed from any context (tests, edge, client-safe code) without
- * dragging in the Supabase admin client. `framework-installer.ts` re-exports
- * everything here for back-compat and adds the server-only install routines.
+ * Most exports (PACK_REGISTRY, PACK_SLUGS, getFrameworkCodeForSlug, 
+ * getFrameworkSlugForCode) are client/edge-safe. `getPackFileForSlug` uses
+ * Node.js APIs and is server-only. This module avoids importing Supabase admin
+ * client so test collection doesn't crash. `framework-installer.ts` re-exports
+ * everything here for back-compat and adds the server-only install routines.
  1. Move getPackFileForSlug to framework-installer since it's server-only anyway.

Also applies to: 75-79

🤖 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 `@lib/frameworks/pack-registry.ts` around lines 1 - 14, The module comment is
inaccurate because getPackFileForSlug uses Node-only APIs (path import and
process.cwd()), so either remove Node-specific code from this file and move the
server-only function getPackFileForSlug (and any use of path/process.cwd()) into
the server-only framework-installer module, leaving only PACK_REGISTRY,
PACK_SLUGS, getFrameworkCodeForSlug and getFrameworkSlugForCode here; and/or
update the top comment to explicitly state which exports are client/edge-safe
and that getPackFileForSlug is server-only so consumers won’t be surprised.
Ensure any imports of path or process references are removed from this file when
you move the function.
lib/compliance-graph.ts (1)

803-805: 💤 Low value

Consider logging the rebuild result for observability consistency.

initializeComplianceGraph logs a warning when rebuildOrgGraph fails, but repairComplianceGraph silently discards the result. Adding a similar warning would help diagnose persistence issues after repairs.

🔧 Suggested fix
     // Re-derive and persist the graph so the repaired wires (newly-linked
     // tasks, role assignments) are reflected in graph_nodes/graph_wires.
-    await rebuildOrgGraph(organizationId, userId);
+    const persisted = await rebuildOrgGraph(organizationId, userId);
+    if (!persisted.success) {
+      graphLogger.warn('graph_persist_warning_after_repair', {
+        organizationId,
+        error: persisted.error,
+      });
+    }
🤖 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 `@lib/compliance-graph.ts` around lines 803 - 805, repairComplianceGraph
currently calls rebuildOrgGraph but ignores its result; mirror
initializeComplianceGraph by checking the rebuildOrgGraph result and logging a
warning if it fails (or catching and logging thrown errors). Update
repairComplianceGraph to inspect the return value from
rebuildOrgGraph(organizationId, userId) and emit a warning (including the
returned result or caught error details) using the same logger used elsewhere so
persistence failures are observable, matching the behavior in
initializeComplianceGraph.
app/(marketing)/mental-health-compliance/MentalHealthComplianceContent.tsx (1)

890-931: ⚡ Quick win

Align FAQ schema with displayed FAQ content.

The FAQ content displayed here differs from the faqSchema in page.tsx:

  • Schema has 5 questions; display has 7
  • Question 4 wording differs ("worker screening for clinical staff" vs "state-specific worker screening")
  • Two questions appear only in display, not in schema

For optimal SEO, the FAQ structured data should mirror the visible FAQ section. Search engines expect consistency between schema markup and user-visible content.

♻️ Recommended fix

Update the faqSchema in page.tsx to include all 7 FAQ items with exact question wording from this component, or vice versa—ensure both sources are in sync.

🤖 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 `@app/`(marketing)/mental-health-compliance/MentalHealthComplianceContent.tsx
around lines 890 - 931, The FAQ structured data is out of sync with the visible
FAQs in this component; update the source of truth so they match exactly. Locate
the faqSchema definition referenced in page.tsx and either (A) replace its list
with the seven FAQ entries and exact question/answer strings used in the
IndustryFAQ instantiation here (questions like "How does FormaOS handle
state-specific worker screening?" and the two additional items shown), or (B)
change the IndustryFAQ props to match the existing faqSchema—ensuring identical
wording and the same count (7) so the schema and visible content are exact
matches for SEO; keep the unique identifiers faqSchema and IndustryFAQ to find
the code.
🤖 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.

Inline comments:
In `@app/`(marketing)/mental-health-compliance/page.tsx:
- Around line 81-88: The page metadata in
app/(marketing)/mental-health-compliance/page.tsx is overriding the generated
route image by hardcoding openGraph.images and twitter.images to
`${siteUrl}/og-image.png`; remove the explicit openGraph.images and
twitter.images fields from the exported metadata (or change their URL to
`${siteUrl}/mental-health-compliance/opengraph-image`) so Next.js can use the
generated app/(marketing)/mental-health-compliance/opengraph-image.tsx; locate
the metadata object in page.tsx and update the openGraph and twitter properties
accordingly, keeping the siteUrl variable usage consistent.

In `@app/app/actions/care-operations.ts`:
- Around line 425-440: createMedication currently skips the write-permission
check and can create medications during an org's read-only grace period; update
createMedication to call requirePermission (or at minimum assertOrgCanWrite)
using the resolved organization id/membership before performing any DB writes so
the grace-period/readonly enforcement is applied (locate createMedication in
app/app/actions/care-operations.ts and add the same
requirePermission/assertOrgCanWrite invocation pattern used elsewhere in this
file to validate the org can write prior to the insert).

In `@components/onboarding/IndustryFeatureHighlights.tsx`:
- Around line 99-119: In IndustryFeatureHighlights, update the route values for
the mental-health related feature cards so they match the sidebar canonical path
'/app/participants' (currently set to '/app/patients'); locate the objects with
title 'Consumer Management' and 'Incident & Restrictive Practice Tracking' (and
any other mental-health card entries) and change their route property to
'/app/participants' to ensure onboarding links align with the mental-health
navigation.

In `@lib/compliance/evaluators/financial-services-au/_shared.ts`:
- Around line 460-491: The current status calculation can yield 'pass' even when
there are stale elevated risks (gaps contains 'stale_elevated_risks'); change
the logic that sets status (the variable named status computed from overall) to
gate a 'pass' on both overall >= 0.9 AND no stale elevated risks (i.e.,
elevated.length === elevatedFresh); if elevated.length > elevatedFresh then
downgrade what would have been 'pass' to at least 'partial' (keep the existing
thresholds otherwise). Update the computation near the return that builds
controlCode, status, gaps, confidence, and reason so status reflects this
additional elevated-risk check.

In `@tests/compliance/reports/gdpr-compliance-report.json`:
- Around line 4-6: The committed GDPR baseline was overwritten with
environment-outage output (empty arrays for "dataProtection", "userRights",
"consent") instead of real evaluation results; update the report-generation flow
so that when an environment/connection error occurs it fails the generation and
does not write/update gdpr-compliance-report.json, and instead writes a separate
diagnostics/outage file or throws a non-zero error. Locate the code that
populates those keys ("dataProtection", "userRights", "consent") and add
explicit error-checking for environment errors (capture the exception or check
the error flag), prevent writing the canonical report if any control results are
missing due to outage, and write diagnostics with details (timestamp, error
message, affected controls) to a separate file or log for debugging.

---

Nitpick comments:
In `@app/`(marketing)/mental-health-compliance/MentalHealthComplianceContent.tsx:
- Around line 890-931: The FAQ structured data is out of sync with the visible
FAQs in this component; update the source of truth so they match exactly. Locate
the faqSchema definition referenced in page.tsx and either (A) replace its list
with the seven FAQ entries and exact question/answer strings used in the
IndustryFAQ instantiation here (questions like "How does FormaOS handle
state-specific worker screening?" and the two additional items shown), or (B)
change the IndustryFAQ props to match the existing faqSchema—ensuring identical
wording and the same count (7) so the schema and visible content are exact
matches for SEO; keep the unique identifiers faqSchema and IndustryFAQ to find
the code.

In `@lib/compliance-graph.ts`:
- Around line 803-805: repairComplianceGraph currently calls rebuildOrgGraph but
ignores its result; mirror initializeComplianceGraph by checking the
rebuildOrgGraph result and logging a warning if it fails (or catching and
logging thrown errors). Update repairComplianceGraph to inspect the return value
from rebuildOrgGraph(organizationId, userId) and emit a warning (including the
returned result or caught error details) using the same logger used elsewhere so
persistence failures are observable, matching the behavior in
initializeComplianceGraph.

In `@lib/frameworks/pack-registry.ts`:
- Around line 1-14: The module comment is inaccurate because getPackFileForSlug
uses Node-only APIs (path import and process.cwd()), so either remove
Node-specific code from this file and move the server-only function
getPackFileForSlug (and any use of path/process.cwd()) into the server-only
framework-installer module, leaving only PACK_REGISTRY, PACK_SLUGS,
getFrameworkCodeForSlug and getFrameworkSlugForCode here; and/or update the top
comment to explicitly state which exports are client/edge-safe and that
getPackFileForSlug is server-only so consumers won’t be surprised. Ensure any
imports of path or process references are removed from this file when you move
the function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 02824f0c-d553-4792-9688-5dd7ce93139e

📥 Commits

Reviewing files that changed from the base of the PR and between 32a59ea and 1454486.

📒 Files selected for processing (90)
  • .github/workflows/deployment-gates.yml
  • .github/workflows/formaos-quality-gates.yml
  • __tests__/lib/compliance-graph.test.ts
  • __tests__/lib/compliance/unified-score.test.ts
  • __tests__/lib/onboarding/industry-roadmaps.test.ts
  • app/(marketing)/components/homepage/Industries.tsx
  • app/(marketing)/industries/IndustriesContent.tsx
  • app/(marketing)/industries/components/IndustryVerticals.tsx
  • app/(marketing)/mental-health-compliance/MentalHealthComplianceContent.tsx
  • app/(marketing)/mental-health-compliance/opengraph-image.tsx
  • app/(marketing)/mental-health-compliance/page.tsx
  • app/api/billing/webhook/route.ts
  • app/api/cron/data-retention/route.ts
  • app/api/v1/account/delete/route.ts
  • app/api/v1/compliance/graph/route.ts
  • app/app/actions/care-operations.ts
  • app/app/actions/rbac.ts
  • app/app/actions/team.ts
  • app/app/admin/actions.ts
  • app/app/dashboard/builder/page.tsx
  • app/app/layout.tsx
  • app/app/participants/[id]/medications/page.tsx
  • app/llms-full.txt/route.ts
  • app/llms.txt/route.ts
  • app/sitemap.ts
  • components/care/medication-chart.tsx
  • components/dashboard/command-center.tsx
  • components/dashboard/industry-labels.ts
  • components/dashboard/industry-selector.tsx
  • components/dashboard/industry-widgets.tsx
  • components/onboarding/IndustryFeatureHighlights.tsx
  • components/onboarding/steps/WelcomeStep.tsx
  • e2e/full-platform-matrix.spec.ts
  • framework-packs/manifest.json
  • framework-packs/mental-health-au.json
  • lib/compliance-graph.ts
  • lib/compliance/evaluators/financial-services-au/AFCA-001.ts
  • lib/compliance/evaluators/financial-services-au/AFCA-002.ts
  • lib/compliance/evaluators/financial-services-au/AFS-001.ts
  • lib/compliance/evaluators/financial-services-au/AFS-002.ts
  • lib/compliance/evaluators/financial-services-au/AFS-003.ts
  • lib/compliance/evaluators/financial-services-au/AFS-004.ts
  • lib/compliance/evaluators/financial-services-au/AFS-005.ts
  • lib/compliance/evaluators/financial-services-au/AFS-006.ts
  • lib/compliance/evaluators/financial-services-au/AFS-007.ts
  • lib/compliance/evaluators/financial-services-au/AFS-008.ts
  • lib/compliance/evaluators/financial-services-au/AML-001.ts
  • lib/compliance/evaluators/financial-services-au/AML-002.ts
  • lib/compliance/evaluators/financial-services-au/AML-003.ts
  • lib/compliance/evaluators/financial-services-au/AML-004.ts
  • lib/compliance/evaluators/financial-services-au/AML-005.ts
  • lib/compliance/evaluators/financial-services-au/CPS-001.ts
  • lib/compliance/evaluators/financial-services-au/CPS-002.ts
  • lib/compliance/evaluators/financial-services-au/CPS-003.ts
  • lib/compliance/evaluators/financial-services-au/CPS-004.ts
  • lib/compliance/evaluators/financial-services-au/CPS-005.ts
  • lib/compliance/evaluators/financial-services-au/_shared.ts
  • lib/compliance/evaluators/mental-health-au/MHS-1.ts
  • lib/compliance/evaluators/mental-health-au/MHS-10.1.ts
  • lib/compliance/evaluators/mental-health-au/MHS-10.2.ts
  • lib/compliance/evaluators/mental-health-au/MHS-10.3.ts
  • lib/compliance/evaluators/mental-health-au/MHS-10.4.ts
  • lib/compliance/evaluators/mental-health-au/MHS-10.5.ts
  • lib/compliance/evaluators/mental-health-au/MHS-2.ts
  • lib/compliance/evaluators/mental-health-au/MHS-3.ts
  • lib/compliance/evaluators/mental-health-au/MHS-4.ts
  • lib/compliance/evaluators/mental-health-au/MHS-5.ts
  • lib/compliance/evaluators/mental-health-au/MHS-6.ts
  • lib/compliance/evaluators/mental-health-au/MHS-7.ts
  • lib/compliance/evaluators/mental-health-au/MHS-8.ts
  • lib/compliance/evaluators/mental-health-au/MHS-9.ts
  • lib/compliance/evaluators/mental-health-au/_shared.ts
  • lib/compliance/evaluators/register.ts
  • lib/compliance/evaluators/types.ts
  • lib/compliance/get-org-compliance-snapshot.ts
  • lib/compliance/unified-score.ts
  • lib/data-governance/retention.ts
  • lib/frameworks/framework-installer.ts
  • lib/frameworks/org-frameworks.ts
  • lib/frameworks/pack-registry.ts
  • lib/industry-packs.ts
  • lib/navigation/industry-sidebar.ts
  • lib/onboarding/industry-roadmaps.ts
  • lib/supabase/org-scoped.ts
  • lib/system-state/server.ts
  • supabase/migrations/20260624074_audit_2026_06_01_org_last_retention_at.sql
  • supabase/migrations/20260624075_audit_2026_06_01_compliance_graph_persistence.sql
  • tests/compliance/reports/gdpr-compliance-report.json
  • tests/compliance/reports/soc2-compliance-report.json
  • vercel.json
💤 Files with no reviewable changes (2)
  • lib/compliance/unified-score.ts
  • tests/lib/compliance/unified-score.test.ts

Comment on lines +81 to +88
images: [
{
url: `${siteUrl}/og-image.png`,
width: 1200,
height: 630,
alt: 'Mental Health Compliance Software by FormaOS',
},
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if opengraph-image.tsx exists and confirm the correct OG path
fd -t f 'opengraph-image\.tsx' app/\(marketing\)/mental-health-compliance/

Repository: ejay-dev/FormaOS

Length of output: 121


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant metadata section in the page
sed -n '1,140p' app/'(marketing)'/mental-health-compliance/page.tsx | nl -ba | sed -n '40,120p'

# Confirm where siteUrl and openGraph images are used (quick grep)
rg -n "openGraph\\.images|twitter\\.images|og-image\\.png" app/'(marketing)'/mental-health-compliance/page.tsx

# Check whether a global og-image.png exists (public folder)
ls -ლა public 2>/dev/null | head -n 50 || true
test -f public/'og-image.png' && echo "public/og-image.png exists" || echo "public/og-image.png missing"

Repository: ejay-dev/FormaOS

Length of output: 103


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="app/(marketing)/mental-health-compliance/page.tsx"

# Print the metadata block area (around the reported snippet)
awk 'NR>=60 && NR<=110 {printf "%5d:%s\n", NR, $0}' "$FILE"

# Show exact occurrences for the image URLs
rg -n "openGraph\.images|twitter\.images|og-image\.png" "$FILE" || true

# Check whether a global og-image exists in public/
if [ -f "public/og-image.png" ]; then
  echo "public/og-image.png exists"
else
  echo "public/og-image.png missing"
fi

Repository: ejay-dev/FormaOS

Length of output: 2056


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="app/(marketing)/mental-health-compliance/opengraph-image.tsx"

echo "---- file info ----"
wc -l "$FILE"
echo "---- top of file ----"
sed -n '1,200p' "$FILE" | cat -n

Repository: ejay-dev/FormaOS

Length of output: 4355


🌐 Web query:

Next.js 15 opengraph-image.tsx precedence when metadata openGraph.images is explicitly set

💡 Result:

In Next.js 15, when you define an opengraph-image.tsx (or other metadata file conventions) in a route segment, Next.js automatically treats it as the source for that segment's Open Graph image [1][2]. If you explicitly define openGraph.images within the metadata object of your page.tsx or layout.tsx, this metadata configuration will take precedence over the automatically generated image from the file convention [3]. When images are provided in the metadata object, Next.js expects you to provide the full URL (or absolute path relative to your metadataBase) [4][5], whereas opengraph-image.tsx handles the generation and URL construction for you automatically [1][2]. Key takeaways: 1. Precedence: Explicitly setting openGraph.images in your metadata object overrides the default behavior of the opengraph-image.tsx file convention [3]. 2. Manual vs. Automatic: If you want to use the dynamic capabilities of opengraph-image.tsx (e.g., ImageResponse), you should generally avoid defining openGraph.images in your metadata object for that same route segment, or ensure your manual configuration points to the desired image URL [6]. 3. MetadataBase: Note that metadataBase is used by Next.js to resolve relative URLs in metadata. There have been reported edge cases where metadataBase might behave inconsistently when mixing file conventions and manual metadata, so always ensure your URLs are correctly constructed if you choose to manually override the default file-based approach [7]. In summary, the metadata object is the "source of truth" for the final rendered HTML; the file-based convention is a convenience feature that Next.js uses to populate that metadata unless you manually override it [2][8].

Citations:


Fix Open Graph/Twitter OG image override for this route

app/(marketing)/mental-health-compliance/page.tsx hardcodes openGraph.images and twitter.images to ${siteUrl}/og-image.png (lines 81-88, 90-96), which overrides the route-specific app/(marketing)/mental-health-compliance/opengraph-image.tsx. Remove the explicit images fields to let Next.js use the generated image, or point them at ${siteUrl}/mental-health-compliance/opengraph-image.

🤖 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 `@app/`(marketing)/mental-health-compliance/page.tsx around lines 81 - 88, The
page metadata in app/(marketing)/mental-health-compliance/page.tsx is overriding
the generated route image by hardcoding openGraph.images and twitter.images to
`${siteUrl}/og-image.png`; remove the explicit openGraph.images and
twitter.images fields from the exported metadata (or change their URL to
`${siteUrl}/mental-health-compliance/opengraph-image`) so Next.js can use the
generated app/(marketing)/mental-health-compliance/opengraph-image.tsx; locate
the metadata object in page.tsx and update the openGraph and twitter properties
accordingly, keeping the siteUrl variable usage consistent.

Comment on lines +425 to +440
export async function createMedication(formData: FormData) {
try {
const supabase = await createSupabaseServerClient();

const {
data: { user },
} = await supabase.auth.getUser();
if (!user) redirect('/auth/signin');

const { data: membership } = await supabase
.from('org_members')
.select('organization_id')
.eq('user_id', user.id)
.maybeSingle();

if (!membership) throw new Error('No organization found');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

CRITICAL: Grace period write enforcement bypassed.

createMedication doesn't call requirePermission or assertOrgCanWrite, so it allows medication creation even when the org is in read-only state after the 3-day grace period. This contradicts the PR's core goal (H2) of enforcing read-only via assertOrgCanWrite in requirePermission.

Add a permission check to block writes during grace period:

🔒 Proposed fix to enforce grace period
 export async function createMedication(formData: FormData) {
   try {
     const supabase = await createSupabaseServerClient();
 
     const {
       data: { user },
     } = await supabase.auth.getUser();
     if (!user) redirect('/auth/signin');
 
     const { data: membership } = await supabase
       .from('org_members')
       .select('organization_id')
       .eq('user_id', user.id)
       .maybeSingle();
 
     if (!membership) throw new Error('No organization found');
+
+    // Enforce grace period: block writes when org is read-only
+    await requirePermission('UPLOAD_EVIDENCE');
 
     const participantId = (formData.get('participant_id') as string) || '';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function createMedication(formData: FormData) {
try {
const supabase = await createSupabaseServerClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) redirect('/auth/signin');
const { data: membership } = await supabase
.from('org_members')
.select('organization_id')
.eq('user_id', user.id)
.maybeSingle();
if (!membership) throw new Error('No organization found');
export async function createMedication(formData: FormData) {
try {
const supabase = await createSupabaseServerClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) redirect('/auth/signin');
const { data: membership } = await supabase
.from('org_members')
.select('organization_id')
.eq('user_id', user.id)
.maybeSingle();
if (!membership) throw new Error('No organization found');
// Enforce grace period: block writes when org is read-only
await requirePermission('UPLOAD_EVIDENCE');
🤖 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 `@app/app/actions/care-operations.ts` around lines 425 - 440, createMedication
currently skips the write-permission check and can create medications during an
org's read-only grace period; update createMedication to call requirePermission
(or at minimum assertOrgCanWrite) using the resolved organization id/membership
before performing any DB writes so the grace-period/readonly enforcement is
applied (locate createMedication in app/app/actions/care-operations.ts and add
the same requirePermission/assertOrgCanWrite invocation pattern used elsewhere
in this file to validate the org can write prior to the insert).

Comment on lines +99 to +119
{
icon: Users,
title: 'Consumer Management',
description:
'Track consumers with care status, risk levels, and safety flags. Every interaction becomes compliance evidence.',
route: '/app/patients',
},
{
icon: Calendar,
title: 'Service Delivery Scheduling',
description:
'Schedule service delivery with automatic audit trails. No double entry—session logs become compliance evidence.',
route: '/app/visits',
},
{
icon: Activity,
title: 'Incident & Restrictive Practice Tracking',
description:
'Log incidents and restrictive practices with severity classification, authorisation, and review. Reporting-ready.',
route: '/app/patients',
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align mental-health onboarding routes with the sidebar canonical route.

The new mental-health feature cards point to /app/patients, while the mental-health navigation uses /app/participants. This inconsistency can send users to the wrong surface from onboarding.

Suggested fix
   {
     icon: Users,
     title: 'Consumer Management',
@@
-    route: '/app/patients',
+    route: '/app/participants',
   },
@@
   {
     icon: Activity,
     title: 'Incident & Restrictive Practice Tracking',
@@
-    route: '/app/patients',
+    route: '/app/participants',
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
icon: Users,
title: 'Consumer Management',
description:
'Track consumers with care status, risk levels, and safety flags. Every interaction becomes compliance evidence.',
route: '/app/patients',
},
{
icon: Calendar,
title: 'Service Delivery Scheduling',
description:
'Schedule service delivery with automatic audit trails. No double entry—session logs become compliance evidence.',
route: '/app/visits',
},
{
icon: Activity,
title: 'Incident & Restrictive Practice Tracking',
description:
'Log incidents and restrictive practices with severity classification, authorisation, and review. Reporting-ready.',
route: '/app/patients',
},
{
icon: Users,
title: 'Consumer Management',
description:
'Track consumers with care status, risk levels, and safety flags. Every interaction becomes compliance evidence.',
route: '/app/participants',
},
{
icon: Calendar,
title: 'Service Delivery Scheduling',
description:
'Schedule service delivery with automatic audit trails. No double entry—session logs become compliance evidence.',
route: '/app/visits',
},
{
icon: Activity,
title: 'Incident & Restrictive Practice Tracking',
description:
'Log incidents and restrictive practices with severity classification, authorisation, and review. Reporting-ready.',
route: '/app/participants',
},
🤖 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 `@components/onboarding/IndustryFeatureHighlights.tsx` around lines 99 - 119,
In IndustryFeatureHighlights, update the route values for the mental-health
related feature cards so they match the sidebar canonical path
'/app/participants' (currently set to '/app/patients'); locate the objects with
title 'Consumer Management' and 'Incident & Restrictive Practice Tracking' (and
any other mental-health card entries) and change their route property to
'/app/participants' to ensure onboarding links align with the mental-health
navigation.

Comment on lines +460 to +491
const gaps: ControlGap[] = [];
if (elevated.length > elevatedFresh) {
gaps.push({
code: 'stale_elevated_risks',
message: `${elevated.length - elevatedFresh}/${elevated.length} elevated risks not reviewed within 90 days.`,
severity: 'high',
});
}
if (routine.length > routineFresh) {
gaps.push({
code: 'stale_routine_risks',
message: `${routine.length - routineFresh}/${routine.length} routine risks not reviewed within 12 months.`,
severity: 'medium',
});
}

const status: ControlResult['status'] =
overall >= 0.9 ? 'pass' : overall >= 0.5 ? 'partial' : 'fail';

return {
controlCode,
status,
evidenceRefs: risks.slice(0, EVIDENCE_CAP).map((r) => ({
source: 'org_risks',
ref: r.id,
capturedAt: r.updated_at ?? r.created_at ?? undefined,
})),
gaps,
confidence: round2(0.5 + 0.4 * overall),
reason: `elevated ${elevatedFresh}/${elevated.length} fresh (90d); routine ${routineFresh}/${routine.length} fresh (365d).`,
evaluatedAt,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

pass can be returned alongside a high-severity elevated-risk gap.

With overall >= 0.9, a single stale critical/high risk (e.g. 1 of 10 elevated stale → elevatedRatio 0.9, routineRatio 1 → overall 0.95) yields status: 'pass' while gaps still contains the high-severity stale_elevated_risks entry. A clean pass that ships an unreviewed elevated risk is misleading for CPS 230. Consider gating pass on zero stale elevated risks.

♻️ Suggested gating
-  const status: ControlResult['status'] =
-    overall >= 0.9 ? 'pass' : overall >= 0.5 ? 'partial' : 'fail';
+  const elevatedAllFresh = elevated.length === elevatedFresh;
+  const status: ControlResult['status'] =
+    overall >= 0.9 && elevatedAllFresh
+      ? 'pass'
+      : overall >= 0.5
+        ? 'partial'
+        : 'fail';
🤖 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 `@lib/compliance/evaluators/financial-services-au/_shared.ts` around lines 460
- 491, The current status calculation can yield 'pass' even when there are stale
elevated risks (gaps contains 'stale_elevated_risks'); change the logic that
sets status (the variable named status computed from overall) to gate a 'pass'
on both overall >= 0.9 AND no stale elevated risks (i.e., elevated.length ===
elevatedFresh); if elevated.length > elevatedFresh then downgrade what would
have been 'pass' to at least 'partial' (keep the existing thresholds otherwise).
Update the computation near the return that builds controlCode, status, gaps,
confidence, and reason so status reflects this additional elevated-risk check.

Comment on lines +4 to +6
"dataProtection": [],
"userRights": [],
"consent": [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not replace baseline compliance evidence with environment-outage output.

This report now encodes localhost unavailability (empty control results + environment error) instead of GDPR evaluation outcomes. That makes the committed artifact non-actionable for audit comparison and can hide actual compliance regressions. Prefer failing report generation in this state (or storing outage diagnostics separately) rather than overwriting the canonical report JSON.

Also applies to: 11-13, 17-17, 21-22

🤖 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 `@tests/compliance/reports/gdpr-compliance-report.json` around lines 4 - 6, The
committed GDPR baseline was overwritten with environment-outage output (empty
arrays for "dataProtection", "userRights", "consent") instead of real evaluation
results; update the report-generation flow so that when an
environment/connection error occurs it fails the generation and does not
write/update gdpr-compliance-report.json, and instead writes a separate
diagnostics/outage file or throws a non-zero error. Locate the code that
populates those keys ("dataProtection", "userRights", "consent") and add
explicit error-checking for environment errors (capture the exception or check
the error flag), prevent writing the canonical report if any control results are
missing due to outage, and write diagnostics with details (timestamp, error
message, affected controls) to a separate file or log for debugging.

@github-actions

github-actions Bot commented Jun 1, 2026

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 42966ae into main Jun 1, 2026
30 of 36 checks passed
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.

1 participant