Skip to content

[comp] Production Deploy#2994

Merged
tofikwest merged 24 commits into
releasefrom
main
Jun 2, 2026
Merged

[comp] Production Deploy#2994
tofikwest merged 24 commits into
releasefrom
main

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented Jun 2, 2026

This is an automated pull request to release the candidate branch into production, which will trigger a deployment.
It was created by the [Production PR] action.


Summary by cubic

Adds always-on SoA justifications, improves background checks with admin retry/cancel/delete and an hourly reconciliation, and adds Admin Finding Templates management. Implements CS‑424, CS‑416, CS‑475, and CS‑473.

  • New Features

    • Background Checks: Admin endpoints and UI to retry (free), cancel, and delete; retries increment rerunCount and use per-attempt idempotency keys keyed to the record; ignore vendor webhooks after local cancel; hourly job polls for stale in‑flight checks and updates statuses and report snapshots.
    • SoA: Adds family‑based default inclusion justifications with a generic fallback; stores and shows justification for both YES and NO; allows editing for both states and keeps the dialog open on save failure.
    • Admin: Adds Finding Templates management under Admin → Finding Templates; list with search/filter/pagination, create/edit sheet, and delete dialog; uses use-admin-finding-templates over GET /v1/finding-template.
  • Bug Fixes

    • SoA: Fixes missing justification on YES defaults and inconsistent display in table/mobile views.
    • People: Excludes auditor‑only members from background‑check requirements and hides the Background Check tab; members with auditor plus another role are still required.
    • Background Checks: Moves admin Retry/Cancel/Delete into the status card footer for clarity via a new actions slot.

Written for commit dcd4b4d. Summary will update on new commits.

Review in cubic

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 2, 2026

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

Project Deployment Actions Updated (UTC)
app (staging) Ready Ready Preview, Comment Jun 2, 2026 9:41pm
comp-framework-editor (staging) Ready Ready Preview, Comment Jun 2, 2026 9:41pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal (staging) Skipped Skipped Jun 2, 2026 9:41pm

Request Review

@linear
Copy link
Copy Markdown

linear Bot commented Jun 2, 2026

CS-424

CS-416

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 9 files

Confidence score: 3/5

  • There is a concrete behavior risk in apps/api/src/soa/utils/soa-answer-parser.ts: forcing a default justification for YES answers conflicts with the SOA rule that allows empty justification, and could hide when the model returned no justification.
  • A smaller UI/data-handling issue in apps/app/src/app/(app)/[orgId]/documents/statement-of-applicability/components/SOATableRow.tsx uses || instead of ??, so intentionally saved empty strings can be overwritten by defaults.
  • Given the medium-severity backend rule mismatch (5/10, high confidence) plus a low-severity frontend fallback issue, this looks mergeable with caution but carries real regression risk.
  • Pay close attention to apps/api/src/soa/utils/soa-answer-parser.ts and apps/app/src/app/(app)/[orgId]/documents/statement-of-applicability/components/SOATableRow.tsx - justification emptiness semantics may be unintentionally changed across parsing and display.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/src/soa/utils/soa-answer-parser.ts">

<violation number="1" location="apps/api/src/soa/utils/soa-answer-parser.ts:192">
P2: YES answers are now forced to store a default justification instead of allowing null/blank, which conflicts with the SOA rule to allow empty justification for applicable controls and can mask when the model provided no rationale.

(Based on your team's feedback about allowing blank justification for YES SOA answers.) [FEEDBACK_USED].</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/documents/statement-of-applicability/components/SOATableRow.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/documents/statement-of-applicability/components/SOATableRow.tsx:80">
P3: Using `||` instead of `??` causes empty-string justification values to be silently discarded, falling back to the column mapping default instead of preserving the user's explicitly saved empty value.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

? llmJustification
: llmJustification && !isInsufficientDataAnswer(llmJustification)
? llmJustification
: (getInclusionJustification(closure) ?? DEFAULT_INCLUSION_JUSTIFICATION);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jun 2, 2026

Choose a reason for hiding this comment

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

P2: YES answers are now forced to store a default justification instead of allowing null/blank, which conflicts with the SOA rule to allow empty justification for applicable controls and can mask when the model provided no rationale.

(Based on your team's feedback about allowing blank justification for YES SOA answers.) .

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/soa/utils/soa-answer-parser.ts, line 192:

<comment>YES answers are now forced to store a default justification instead of allowing null/blank, which conflicts with the SOA rule to allow empty justification for applicable controls and can mask when the model provided no rationale.

(Based on your team's feedback about allowing blank justification for YES SOA answers.) .</comment>

<file context>
@@ -163,12 +172,24 @@ export function parseAndProcessSOAAnswer(
+      ? llmJustification
+      : llmJustification && !isInsufficientDataAnswer(llmJustification)
+        ? llmJustification
+        : (getInclusionJustification(closure) ?? DEFAULT_INCLUSION_JUSTIFICATION);
 
   send({
</file context>
Fix with cubic

displayIsApplicable === false
? (answerData.answer ?? question.columnMapping.justification ?? null)
: null;
answerData.answer || question.columnMapping.justification || null;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jun 2, 2026

Choose a reason for hiding this comment

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

P3: Using || instead of ?? causes empty-string justification values to be silently discarded, falling back to the column mapping default instead of preserving the user's explicitly saved empty value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/documents/statement-of-applicability/components/SOATableRow.tsx, line 80:

<comment>Using `||` instead of `??` causes empty-string justification values to be silently discarded, falling back to the column mapping default instead of preserving the user's explicitly saved empty value.</comment>

<file context>
@@ -77,9 +77,7 @@ export function SOATableRow({
-      displayIsApplicable === false
-        ? (answerData.answer ?? question.columnMapping.justification ?? null)
-        : null;
+      answerData.answer || question.columnMapping.justification || null;
   } else {
     // Normal logic: processedResult / column mapping until user saves (then branch above)
</file context>
Suggested change
answerData.answer || question.columnMapping.justification || null;
answerData.answer ?? question.columnMapping.justification ?? null;
Fix with cubic

… (CS-416) (#2995)

Auditor-only members are external reviewers and aren't subject to
people-security requirements. Exclude them from the background-check
requirement in the people score and hide the Background Check tab/header
on their detail page (the people-list row was already gated).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Marfuen and others added 2 commits June 2, 2026 16:02
* feat(background-checks): add rerunCount to BackgroundCheckRequest

* feat(background-checks): vary Identity idempotency key by attempt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(background-checks): add missing attempt field to spec callers

Three pre-existing direct calls to `BackgroundCheckIdentityClient.createBackgroundCheck()`
in the spec file were missing the newly required `attempt` field, causing TS2345 type
errors after Task 2 made `attempt: number` required in the client signature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(background-checks): add cancelForMember with state guard

* feat(background-checks): add free reset-in-place retryForMember

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(background-checks): extract cancelForMember/retryForMember to bring service under 300 lines; add missing not-found test

- Extract `cancelForMember` and `retryForMember` to `background-check-retry.ts`
  following the existing `background-check-report-snapshot.ts` helper pattern,
  bringing `background-checks.service.ts` from 376 to 293 lines
- Add missing `throws when no check exists` test to the `retryForMember` suite,
  matching the parallel coverage in `cancelForMember` (NotFoundException at
  service.ts:304 was previously untested)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(background-checks): add deleteForMember hard delete

* fix(background-checks): ignore webhooks for cancelled checks

* feat(background-checks): add retry/cancel/delete endpoints

* feat(background-checks): admin retry/cancel/delete actions component

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(background-checks): surface admin actions in employee tab

* fix(bg-check): remove redundant guard and add admin-actions wiring tests

- Remove inner `{backgroundCheck && ...}` wrapper inside the
  `if (backgroundCheck)` branch — the inner condition was always truthy.
- Fix the `onChange` callback type mismatch (void vs Promise<void>).
- Add two tests that pass a non-null `initialBackgroundCheck` and assert
  that Retry/Cancel buttons from `BackgroundCheckAdminActions` render.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(background-checks): apply prettier formatting to all touched files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(background-checks): address Cubic ultrareview findings

- Key Identity idempotency on the record id (not memberId) so a delete +
  re-request creates a fresh vendor check instead of colliding (P1)
- Retry failure restores the prior status instead of forcing 'failed',
  preserving the cancelled webhook terminal-guard (P1)
- Clear the pending delete confirmation when Retry/Cancel is clicked (P2)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…73) (#2996)

* feat(background-checks): hourly reconciliation for stuck checks (CS-473)

Background check status is normally driven by Identity webhooks; when one
is missed the check stays stuck (e.g. Identity:Pending forever). Add an
hourly Trigger.dev scheduled task that polls Identity for stale in-flight
checks and applies any status it reports (same fields the webhook writes).
Parses the Identity GET response defensively and no-ops when status can't
be determined.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(background-checks): address Cubic review on reconciliation job

- parseIdentityCheckState parses status/statuses independently so a
  malformed statuses object no longer drops a valid status (P2)
- refresh sub-statuses even when the top-level status is unchanged, and
  never null out sub-statuses the GET omitted (P2 — the CS-473 symptom)
- write via updateMany guarded on status IN non-terminal, so a check that
  became terminal/cancelled between select and write isn't resurrected (P1)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(background-checks): maxDuration is in seconds, not ms (CS-473)

Trigger.dev maxDuration is specified in seconds; 1000*60*30 was ~20 days
instead of 30 minutes. Use 30*60.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(background-checks): base stale cutoff on actual run time (CS-473)

Use Date.now() instead of the scheduled payload.timestamp so a late cron
start doesn't narrow the reconciliation window and delay recovery.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to staging – portal June 2, 2026 20:02 Inactive
@vercel vercel Bot temporarily deployed to staging – app June 2, 2026 20:02 Inactive
tofikwest and others added 2 commits June 2, 2026 16:08
Port the Finding Templates CRUD UI from the deprecated cx-dashboard app
into the main app's Admin panel, next to Timeline Templates. Consumes the
existing /v1/finding-template API (mutations gated by PlatformAdminGuard);
no backend, schema, or migration changes.

- New admin route /[orgId]/admin/finding-templates: list with search,
  category filter, and pagination; create/edit Sheet (react-hook-form +
  zod); delete confirmation (AlertDialog)
- use-admin-finding-templates SWR hook over GET /v1/finding-template
- Finding Templates sidebar entry under Admin
- Access auto-gated by the existing admin layout (role === 'admin')

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 27 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/api/src/frameworks/frameworks-people-score.helper.ts">

<violation number="1" location="apps/api/src/frameworks/frameworks-people-score.helper.ts:22">
P2: Background-check eligibility is gated by a hardcoded `'auditor'` role string instead of RBAC role metadata, which can break custom-role behavior.</violation>
</file>

<file name="apps/api/src/background-checks/background-check-retry.ts">

<violation number="1" location="apps/api/src/background-checks/background-check-retry.ts:117">
P2: The retry error handler performs a stale status write that can clobber a successful concurrent retry.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

.split(',')
.map((r) => r.trim())
.filter(Boolean);
return roles.length > 0 && roles.every((r) => r === 'auditor');
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jun 2, 2026

Choose a reason for hiding this comment

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

P2: Background-check eligibility is gated by a hardcoded 'auditor' role string instead of RBAC role metadata, which can break custom-role behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/frameworks/frameworks-people-score.helper.ts, line 22:

<comment>Background-check eligibility is gated by a hardcoded `'auditor'` role string instead of RBAC role metadata, which can break custom-role behavior.</comment>

<file context>
@@ -8,6 +8,20 @@ const COMPLETED_BACKGROUND_CHECK_STATUSES = [
+    .split(',')
+    .map((r) => r.trim())
+    .filter(Boolean);
+  return roles.length > 0 && roles.every((r) => r === 'auditor');
+}
+
</file context>
Fix with cubic

// check rather than colliding with a prior attempt's idempotency key.
idempotencyKey: `comp-background-check:${existing.id}:${attempt}`,
});
} catch (error) {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jun 2, 2026

Choose a reason for hiding this comment

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

P2: The retry error handler performs a stale status write that can clobber a successful concurrent retry.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/background-checks/background-check-retry.ts, line 117:

<comment>The retry error handler performs a stale status write that can clobber a successful concurrent retry.</comment>

<file context>
@@ -0,0 +1,145 @@
+      // check rather than colliding with a prior attempt's idempotency key.
+      idempotencyKey: `comp-background-check:${existing.id}:${attempt}`,
+    });
+  } catch (error) {
+    // Restore the prior status (retry is only allowed from 'failed' or
+    // 'cancelled'). Forcing 'failed' here would strip a cancelled check of the
</file context>
Fix with cubic

feat(admin): add Finding Templates management to admin panel
#2998)

The Retry/Cancel/Delete buttons rendered as a stray row between the exempt
toggle and the status card. Render them inside the status card at the bottom
(below a divider) via a new actions slot on BackgroundCheckStatusView, so the
actions sit with the check they act on. The divider lives in
BackgroundCheckAdminActions so it only shows when there are buttons.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to staging – portal June 2, 2026 21:36 Inactive
@tofikwest tofikwest merged commit 54b5645 into release Jun 2, 2026
14 checks passed
@claudfuen
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.67.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants