[comp] Production Deploy#2597
Conversation
Findings were scattered across ~7 entry points (Tasks, People directory,
People Tasks, People Devices, People Chart, Documents/evidence form types,
Questionnaire context, Admin). Consolidate everything into a dedicated
`/overview/findings` route so auditors raise findings in one place and
everyone else sees one list with deep-links back to the linked item.
Highlights
- Schema: drop `FindingScope`; add `FindingSeverity`, `FindingArea`; add
nullable FKs for policy / vendor / risk / member / device alongside
existing task / evidence-submission / evidence-form-type targets.
Migration backfills legacy `scope` rows onto `area = 'people'`.
- API: rewrite FindingsService, audit service, notifier, and controllers
around the unified target validator (exactly one of 8 targets or an
area). Single `GET /v1/findings` supports filtering by status, severity,
area, and any target FK. Admin controller reuses the same DTO.
- Permissions: `finding:create` and `finding:delete` are auditor-only;
owners/admins keep `read` + `update` so they can transition status.
- Frontend
- New `/overview/findings` route with Link-based tabs so the dashboard
no longer loads findings data. Open-count badge on the Findings tab.
- DS-styled `FindingsTab` table with search + status / severity /
framework filters, DS `Empty` state, 360px balanced columns, and
truncated content with a hover tooltip.
- `CreateFindingSheet`: target-type picker (task / policy / vendor /
risk / person / device / document type / area) with live comboboxes
backed by existing endpoints, capitalised labels, and
auto-selection of the org's framework when only one is configured.
- `FindingDetailSheet`: edit content (auditor-only), change status /
severity, add revision note, view activity log, and delete. Content
renders as read-only for non-auditors.
- `use-findings-api` collapsed to `useOrganizationFindings`,
`useFindingHistory`, `useFindingTemplates`, `useFindingActions`.
- Deletions: `tasks/[taskId]/components/findings/` (whole folder),
`PeopleFindingsUnifiedList`, `DocumentFindingsSection`,
`FindingsOverview`, plus call-site cleanup in SingleTask,
CompanyFormPageClient, people/page, and PeoplePageTabs.
- Admin: old `FindingForm` removed; admin `FindingsTab` now reuses the
unified `CreateFindingSheet` via a `createFn` override that targets
`/v1/admin/organizations/:orgId/findings`.
- People perf: wrap `TeamMembers` RSC in `<Suspense>` with a skeleton so
the tabs paint immediately; `DevicesTabContent` renders agent devices
right away and shows a small loading pill while the slower Fleet SWR
resolves. Dev-only 3s delay in `useFleetHosts` for local testing.
- Audit log: drop the "Admin"/"Auditor" prefix from finding descriptions
in `extractFindingDescription` — the actor name already says it.
- DX: `packages/db/scripts/seed-load-test.ts` seeds ~100 members, 25
devices, and a 10×10 org-chart grid for local load testing.
- Misc: auditor view "Export All Evidence" swapped from broken
Popover-over-render-Button to a proper DS `Sheet`; local DS patch
adding `nativeButton={false}` to pagination `ButtonPrimitive`s while
the upstream fix lands.
- Tests: placeholder specs replaced with real coverage for the target
validator, notifier recipient resolution, admin findings tab, and the
unified FindingsTab (empty state + auditor-gated create).
…b functionality - Modified the deep link format in `finding-notifier.service.ts` to direct users to the Findings page with the finding sheet pre-opened. - Enhanced `FindingsTab.tsx` to support deep linking by auto-opening the specified finding's sheet when the page loads, and removing the query parameter to prevent reopening on refresh. - Integrated `useRouter` and `useSearchParams` from Next.js for improved navigation and state management.
… sharing functionality - Implemented an AlertDialog for confirming the deletion of findings, enhancing user experience by preventing accidental deletions. - Added a "Copy link" button to share the finding's link, allowing users to easily share findings with their team. - Updated the delete button to open the confirmation dialog instead of directly deleting the finding.
`getTaskRecipients` was using `user.role != 'admin'` (better-auth platform admin) in the where clause, which effectively matched every active non-platform-admin member of the org. That meant a task-scoped finding would email every employee/contractor/auditor/custom-role member with a 200-char preview of the finding content plus a deep link — rather than just the people who can actually act on it. This mirrors what the policy/vendor/risk/device branches already do: fetch the target's assignee and union with org owners/admins via `includeAdmins`. Finding content is now only disclosed to stakeholders of that specific task. Pre-existing behaviour (not introduced by the unified-findings PR) but worth fixing here since it's a concrete over-share of audit content. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[dev] [carhartlewis] lewis/comp-people-fix
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
There was a problem hiding this comment.
10 issues found across 59 files
Note: This PR contains a large number of files. During the trial, cubic reviews up to 50 files per PR. Paid plans get a higher limit.
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]/overview/components/CreateFindingSheet.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/overview/components/CreateFindingSheet.tsx:69">
P2: `evidenceSubmission` is handled in code but missing from the target dropdown, so users cannot create findings linked to an evidence submission from this form.</violation>
</file>
<file name="packages/db/prisma/schema/finding.prisma">
<violation number="1" location="packages/db/prisma/schema/finding.prisma:50">
P1: The model documents an "exactly one target or area" rule, but the schema/migration do not enforce it, allowing ambiguous or targetless findings to be persisted.</violation>
</file>
<file name="apps/api/src/findings/findings.service.ts">
<violation number="1" location="apps/api/src/findings/findings.service.ts:341">
P2: Normalize the finding before notifying on create, otherwise notification labels can expose DB enum values instead of external form-type names.</violation>
<violation number="2" location="apps/api/src/findings/findings.service.ts:448">
P2: Pass normalized form-type values to status-change notifications to avoid user-facing DB enum strings.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/overview/components/FindingDetailSheet.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/overview/components/FindingDetailSheet.tsx:74">
P2: Risk finding links point to `/risks/...` but the actual route is `/risk/...`, so the linked item navigation breaks for risk targets.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/admin/organizations/[adminOrgId]/components/FindingsTab.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/admin/organizations/[adminOrgId]/components/FindingsTab.tsx:219">
P1: `CreateFindingSheet` is wired into the admin org page, but it fetches task/policy/vendor/etc. options from current-org endpoints instead of the selected admin organization, causing incorrect target selection context.</violation>
</file>
<file name="apps/api/src/findings/dto/create-finding.dto.ts">
<violation number="1" location="apps/api/src/findings/dto/create-finding.dto.ts:39">
P1: The new target ID fields allow empty strings, which can bypass target/entity validation and then fail at DB write time.</violation>
</file>
<file name="apps/api/src/findings/findings.service.spec.ts">
<violation number="1" location="apps/api/src/findings/findings.service.spec.ts:35">
P2: Add `FindingSeverity` to the `@db` mock; `CreateFindingDto` uses it at import time and the current mock can break module initialization.</violation>
</file>
<file name="apps/api/src/findings/finding-notifier.service.ts">
<violation number="1" location="apps/api/src/findings/finding-notifier.service.ts:158">
P2: `ready_for_review` notifications are dropped for findings created by platform admins because the new logic returns early when `createdById` is null.</violation>
<violation number="2" location="apps/api/src/findings/finding-notifier.service.ts:433">
P2: `includeAdmins` bypasses org active-member checks for the primary recipient, which can send notifications to deactivated users.</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.
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>
…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>
…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>
…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>
- 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>
[dev] [carhartlewis] lewis/comp-findings-fixes
|
`@IsEnum(FindingArea)` captures the Prisma-generated enum object at decorator-eval time. If the API dev server was started before the last `prisma generate` picked up the new `risks` / `vendors` / `policies` / `other` values, class-validator keeps rejecting them with "area must be one of the following values: people, documents, compliance" even though the schema and generated client are up to date. Swap to an explicit @isin([...]) list so the DTO's accepted values don't depend on the exact prisma client the process booted with. Note: the DB enum still needs migrations 20260419140000 + 20260419150000 applied for the insert to succeed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Long risk/policy/template titles overflowed the SelectTrigger and spilled outside the sheet. Wrap the selected-option label in a `block truncate` span so overly long titles get ellipsis'd inside the trigger. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SelectTrigger doesn't accept className. The previous commit added it for truncation and broke the app build. Move the truncation onto the inner span via `block max-w-full truncate` instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[dev] [carhartlewis] lewis/comp-findings-fixes-v2
There was a problem hiding this comment.
5 issues found across 62 files
Note: This PR contains a large number of files. During the trial, cubic reviews up to 50 files per PR. Paid plans get a higher limit.
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/findings/finding-audit.service.ts">
<violation number="1" location="apps/api/src/findings/finding-audit.service.ts:11">
P2: `logFindingCreated` remains an unused code path, so the newly added target mapping is dead code and can diverge from real audit behavior.</violation>
</file>
<file name="apps/api/src/findings/findings.service.ts">
<violation number="1" location="apps/api/src/findings/findings.service.ts:131">
P2: `listForOrganization` does not support filtering by severity, so callers cannot retrieve findings by severity even though severity is now persisted.</violation>
</file>
<file name="packages/db/prisma/schema/finding.prisma">
<violation number="1" location="packages/db/prisma/schema/finding.prisma:55">
P1: The schema documents an "exactly one target or area" invariant, but it is not enforced at the database level, so invalid findings (zero or multiple targets) can be persisted.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/overview/components/FindingDetailSheet.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/overview/components/FindingDetailSheet.tsx:329">
P2: Filter status options by role before rendering; the current dropdown exposes backend-forbidden statuses and leads to predictable 403 save failures.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/overview/components/CreateFindingSheet.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/overview/components/CreateFindingSheet.tsx:147">
P2: `extractOptions` does not handle object-map responses, so the document-type target picker can render empty for evidence-form endpoints that return keyed objects.</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.
- Filter status dropdown by auditor/platform-admin so users can't pick backend-forbidden statuses and hit predictable 403s on save - Drop dead `logFindingCreated` audit method (creation is logged via the global AuditLogInterceptor; the helper had drifted from real behavior) - Add `severity` query param to GET /v1/findings list endpoint - Stop pointing the admin findings picker at `/v1/admin/.../evidence-forms` (returns a status map, not form definitions); fall back to the static `/v1/evidence-forms` so the document-type picker actually populates Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The status dropdown was using `permissions.finding.includes('create')` as
a proxy for auditor, but the API gates transitions on the literal role
string. A custom role granting `finding:create` would have been
mis-classified as auditor and seen the wrong status options. Expose
`roles` from `usePermissions` and check `roles.includes('auditor')` to
mirror the server.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[dev] [carhartlewis] lewis/comp-findings-fixes-v3
There was a problem hiding this comment.
4 issues found across 63 files
Note: This PR contains a large number of files. During the trial, cubic reviews up to 50 files per PR. Paid plans get a higher limit.
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/findings/findings.controller.spec.ts">
<violation number="1" location="apps/api/src/findings/findings.controller.spec.ts:4">
P2: Replace this placeholder test with a real controller assertion. As written, the spec always passes and provides no coverage for `FindingsController` regressions.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/people/devices/components/DevicesTabContent.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/devices/components/DevicesTabContent.tsx:43">
P2: This agent-only loading gate can show a false empty-state message while Fleet is still loading. Keep the skeleton until Fleet resolves when there is no agent data yet.</violation>
</file>
<file name="apps/api/src/findings/finding-notifier.service.ts">
<violation number="1" location="apps/api/src/findings/finding-notifier.service.ts:383">
P1: Scope recipient lookup queries by `organizationId`; current ID-only lookups can resolve cross-tenant recipients and leak finding notifications.</violation>
</file>
<file name="apps/app/src/app/(app)/[orgId]/overview/components/FindingsTab.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/overview/components/FindingsTab.tsx:145">
P2: Deep-link handling does not switch to the requested finding when a different finding is already selected.</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.
|
Approved by @Marfuen |
|
🎉 This PR is included in version 3.24.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
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
Unifies findings into a single overview with create/detail sheets and a unified API, adds severity and area support (including general buckets), surfaces legacy scope in the UI, tightens permissions and notifications, and fixes enum validation, select label overflow, and role-gated status options.
New Features
/overview/findingswith search, filters, open-count tab, deep-linking (auto-opens the sheet), and create/detail sheets.GET /v1/findingswith filters (status, severity, area, targets). Admin reuses the same DTOs and service.risks/vendors/policies.Sheet; People/Devices tabs render faster with skeletons and non-blocking Fleet load. Truncated long select labels and fixed a SelectTrigger type error. DTO hardening: accepts newFindingAreavalues even if the process started before a Prisma client regen. Dropped a redundant creation audit log to prevent duplicate activity entries.Migration
FindingSeverity,FindingArea(includingother,risks,vendors,policies), and nullable FKs for policy/vendor/risk/member/device; dropsFindingScopeand backfills legacy rows toarea='people'(kept as-is; legacy scope is shown in the UI).finding:create/deleteremains; owners/admins keep read/update.Written for commit 6f2474b. Summary will update on new commits.