Skip to content

[dev] [carhartlewis] lewis/comp-findings-fixes#2598

Merged
carhartlewis merged 7 commits into
mainfrom
lewis/comp-findings-fixes
Apr 19, 2026
Merged

[dev] [carhartlewis] lewis/comp-findings-fixes#2598
carhartlewis merged 7 commits into
mainfrom
lewis/comp-findings-fixes

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented Apr 19, 2026

This is an automated pull request to merge lewis/comp-findings-fixes into dev.
It was created by the [Auto Pull Request] action.


Summary by cubic

Adds general risk/vendor/policy findings, exposes evidence submission as a target, surfaces the original legacy people-scope in Finding details, and removes duplicate create/delete activity entries. Also tightens validation, fixes notifications and risk links, improves admin pickers, and speeds up the People page.

  • New Features

    • General areas: risks, vendors, policies. Shown in CreateFindingSheet; list/detail label them and link to /risk, /vendors, /policies.
    • CreateFindingSheet adds “Evidence submission” to the target dropdown.
    • FindingDetailSheet shows “Originally logged against …” using findingScope from the creation AuditLog; FindingHistoryEntry.data.findingScope typed optional.
    • Admin: CreateFindingSheet supports endpoint overrides and hidden target kinds; admin FindingsTab points pickers at org-scoped endpoints and hides kinds without admin APIs.
  • Bug Fixes

    • Deduped "created/deleted" activity entries by relying on the global AuditLog only.
    • Validation: reject empty target IDs with @IsNotEmpty() in the create finding DTO.
    • Notifications: normalize evidence form type labels; when created by a platform admin, notify org owners/admins on ready_for_review; exclude deactivated users from recipients.
    • Routing: fix risk links to /risk/:id.
    • People page: moved compliance queries into TeamMembers for faster tab switches; Tasks tab always renders.
    • Tests: add FindingSeverity to the @db jest mock to stabilize DTO initialization.

Written for commit 320d053. Summary will update on new commits.

The unified_findings migration (20260419120000) mapped every legacy
`scope IN (people, people_tasks, people_devices, people_chart)` row
onto `area = 'people'` so they stayed queryable. That's misleading in
the new model — a finding that used to have scope `people_chart` was
about the org chart, not the people directory. Reading the table now
makes those rows look like first-class people-area findings.

Fix:
- Add `'other'` to the `FindingArea` enum (20260419140000) — separate
  migration because Postgres 12+ disallows using a newly-added enum
  value in the same transaction it was added.
- Reclassify `area='people' AND createdAt < 2026-04-19 12:00:00 UTC`
  rows to `area='other'` (20260419140001). The cutoff matches the
  unified_findings migration timestamp: before that point the `area`
  column didn't exist, so any `area='people'` row pre-dating it can
  only be a backfill artefact. Genuine `area='people'` findings
  created after the cutoff are preserved.

Prod safety: migrations run back-to-back during a single deploy with
no app activity in between, so there is no window for a real
`area='people'` finding to be created before the reclass runs. For
staging, which has already applied the unified migration, any finding
created between then and the reclass deploy will have `createdAt`
after the cutoff and will not be touched.

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

vercel Bot commented Apr 19, 2026

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

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Apr 19, 2026 3:42pm
comp-framework-editor Ready Ready Preview, Comment Apr 19, 2026 3:42pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped Apr 19, 2026 3:42pm

Request Review

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.

No issues found across 3 files

Requires human review: This PR contains database migrations and schema changes, which require human review according to safety guidelines.

…eserve origin

Previous version used a hard-coded `createdAt < 2026-04-19 12:00:00`
cutoff to decide which `area='people'` rows were backfilled legacy
rows vs. genuine post-migration people-area findings. That's broken
for prod:

- Prod will keep using the old `FindingScope` code path until the
  deploy that runs 20260419120000.
- Findings created on prod between now and that deploy have
  `createdAt > cutoff` but are still legacy scope rows.
- Migration 1 backfills them to `area='people'`. Migration 3 would
  leave them alone because their `createdAt` is past the cutoff —
  wrong.

Switch the signal to the creation AuditLog. The old
finding-audit.service always wrote the source scope into
`data.findingScope` on the creation entry. Presence of that field is
a precise, environment-agnostic marker of "created via the old
scope-based flow". The new service writes `targetKind`/`area` instead,
so genuine post-migration findings won't match and will be left alone.

Also appends a human-readable footer to `content` so the detail sheet
still shows the original scope ("Originally logged against People ›
Devices"). The content field is preserved verbatim and renders in the
existing read-only paragraph for non-auditors.

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

The previous reclass migration (20260419140001) would have moved legacy
people-scope findings to area='other' and appended a content footer.
That reclass breaks the /people deep-link that owners/admins currently
rely on (targetHref returns null for area='other'), so it's worse for
operability than leaving rows at area='people'.

Do it in the UI instead:
- `FindingHistoryEntry.data` typed with optional `findingScope`.
- `legacyScopeLabelFromHistory` reads the creation AuditLog entry and
  maps `findingScope` → "People › Devices" / "Directory" / "Tasks" /
  "Org chart".
- `FindingDetailSheet` renders a muted "Originally logged against …"
  line below the target label when a legacy scope is present.

No DB migration required. The `FindingArea` enum retains `other` for
future use / manual categorisation, but no rows are force-reclassified.
Legacy rows keep area='people' and their /people link, so owners/admins
can still drill in to fix what the auditor found.

Drops migration 20260419140001 (never applied). Keeps 20260419140000
(just adds the `other` enum value — safe no-op for existing rows).

Co-Authored-By: Claude Opus 4.7 (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.

0 issues found across 3 files (changes from recent commits).

Requires human review: This PR includes a database migration and schema changes (adding a value to the FindingArea enum), which require manual human review per safety guidelines.

…ctivity

Two issues Paul flagged:

1. "Create Finding" sheet required picking a specific Risk / Vendor /
   Policy — no way to log "no risks tracked" or "missing policy" when
   the row doesn't exist yet. Auditors were forced to pick a random
   target just to get past the "Please select a target" validation.
2. Every finding's activity log showed two "created" entries at the
   same timestamp — "created a finding" (from the global
   AuditLogInterceptor) and "created this finding" (from
   FindingsService.create calling findingAuditService.logFindingCreated).
   Same for deletions.

Fixes:

- Extend `FindingArea` enum with `risks` / `vendors` / `policies`
  (migration 20260419150000). Each new value is its own ALTER TYPE
  ADD VALUE statement because Postgres 12+ disallows using a newly-
  added enum value in the same transaction.
- Surface the new values in `CreateFindingSheet` as "Risks (general)",
  "Vendors (general)", "Policies (general)" under the existing "Area"
  target type. Detail sheet + list row render the label verbatim and
  link general findings to `/risks`, `/vendors`, `/policies` so the
  owner/admin can drill in.
- Remove the explicit `findingAuditService.logFindingCreated` and
  `logFindingDeleted` calls from `FindingsService`. The global
  `AuditLogInterceptor` already writes those via
  `extractFindingDescription`. Status / type / content-change audits
  are kept — the interceptor can't derive those deltas.

Co-Authored-By: Claude Opus 4.7 (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.

0 issues found across 6 files (changes from recent commits).

Requires human review: This PR includes database migrations (extending enums) and modifies audit logging logic, which requires human review to ensure data integrity and compliance.

carhartlewis and others added 2 commits April 19, 2026 14:38
…pickers)

- create-finding.dto.ts: add @isnotempty() to every optional target ID
  (taskId, evidenceSubmissionId, policyId, vendorId, riskId, memberId,
  deviceId, templateId). Empty strings now get rejected with 400 before
  reaching resolveTarget.
- finding-notifier.service.ts:
  - normalize evidenceFormType via toExternalEvidenceFormType in
    findingLabel so email/Novu labels show "board-meeting" instead of
    the DB enum "board_meeting".
  - When a finding was created by a platform admin (createdById null,
    createdByAdminId set), fall back to notifying org owners/admins on
    ready_for_review transitions so the review isn't silently dropped.
  - includeAdmins now resolves the primary recipient through
    db.member.findFirst with deactivated=false + isActive=true so
    deactivated/ex-members are excluded, matching getOwnersAndAdmins.
- FindingDetailSheet: correct risk deep link — route is /risk/:id
  (singular), not /risks. Covers both specific risk targets and the
  general "Risks" area.
- CreateFindingSheet: new `endpointOverrides` and `disabledTargetKinds`
  props so the admin surface can point pickers at admin-scoped
  endpoints and hide target types with no admin equivalent.
- admin FindingsTab: supplies admin endpoints for task/policy/vendor/
  evidenceFormType and hides risk/member/device until admin equivalents
  exist. Pickers now show the correct org's data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tab switches on /people were 700ms-2.2s because the RSC for page.tsx
blocked on four sequential awaits (session, currentUserMember,
complianceMembers findMany, filterComplianceMembers) before returning
the shell. Every query-param change — including switching to Chart or
Devices, which don't need compliance data — ran the whole chain.

Move the two heavy queries into <TeamMembers>, which is already
Suspense-wrapped. The page shell now only waits on session +
currentUserMember (needed for tab/permission gating), so tab
navigation paints immediately and compliance bookkeeping streams in
underneath.

Drops the `complianceMemberIds` prop on <TeamMembers> — it already
computes the ID list from its own members fetch via
filterComplianceMembers, so the page-level query was duplicated work.
Also removes the `showEmployeeTasks` gate; the Tasks tab now always
renders (empty state handles zero-employee orgs), which avoided the
prior need to count employees server-side on every navigation.

Co-Authored-By: Claude Opus 4.7 (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.

1 issue found across 8 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/app/src/app/(app)/[orgId]/people/page.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/page.tsx:55">
P2: The Tasks tab is now unconditionally shown to all organizations. Previously it was hidden when there were no compliance-obligated employees (`employees.length > 0`). If this is intentional, no action needed — but if orgs with zero employees shouldn't see an empty Tasks tab, consider computing the visibility signal here (e.g. a lightweight count query) or deferring it with Suspense.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

showRoleMapping={false}
roleMappingContent={null}
showEmployeeTasks={showEmployeeTasks}
showEmployeeTasks
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 19, 2026

Choose a reason for hiding this comment

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

P2: The Tasks tab is now unconditionally shown to all organizations. Previously it was hidden when there were no compliance-obligated employees (employees.length > 0). If this is intentional, no action needed — but if orgs with zero employees shouldn't see an empty Tasks tab, consider computing the visibility signal here (e.g. a lightweight count query) or deferring it with Suspense.

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]/people/page.tsx, line 55:

<comment>The Tasks tab is now unconditionally shown to all organizations. Previously it was hidden when there were no compliance-obligated employees (`employees.length > 0`). If this is intentional, no action needed — but if orgs with zero employees shouldn't see an empty Tasks tab, consider computing the visibility signal here (e.g. a lightweight count query) or deferring it with Suspense.</comment>

<file context>
@@ -62,17 +43,16 @@ export default async function PeoplePage({ params }: { params: Promise<{ orgId:
       showRoleMapping={false}
       roleMappingContent={null}
-      showEmployeeTasks={showEmployeeTasks}
+      showEmployeeTasks
       canInviteUsers={canInviteUsers}
       canManageMembers={canManageMembers}
</file context>
Fix with Cubic

- Add `evidenceSubmission` to CreateFindingSheet target dropdown so
  users can actually reach the free-text submission-ID picker the form
  already supports.
- Add `FindingSeverity` to the `@db` jest mock; CreateFindingDto
  imports it at module load time, so its absence risked breaking
  module initialization in tests.

Co-Authored-By: Claude Opus 4.7 (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.

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

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

@carhartlewis carhartlewis merged commit 9e81604 into main Apr 19, 2026
11 checks passed
@carhartlewis carhartlewis deleted the lewis/comp-findings-fixes branch April 19, 2026 15:47
@claudfuen
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.24.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.

2 participants