Skip to content

[dev] [Marfuen] mariano/cs-312-feature-add-personnel-employment-events-date-tracking#2880

Closed
github-actions[bot] wants to merge 95 commits into
mainfrom
mariano/cs-312-feature-add-personnel-employment-events-date-tracking
Closed

[dev] [Marfuen] mariano/cs-312-feature-add-personnel-employment-events-date-tracking#2880
github-actions[bot] wants to merge 95 commits into
mainfrom
mariano/cs-312-feature-add-personnel-employment-events-date-tracking

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented May 20, 2026

This is an automated pull request to merge mariano/cs-312-feature-add-personnel-employment-events-date-tracking into dev.
It was created by the [Auto Pull Request] action.


Summary by cubic

Add employment event tracking and a guided offboarding workflow. Teams can record onboard/offboard dates, complete a checklist with evidence, confirm per‑vendor access revocations, and export audit-ready bundles.

  • New Features

    • Track onboardDate and offboardDate on people; offboard date auto-sets on deactivation.
    • Offboarding checklist: configurable template, evidence uploads, completion tracking, and per‑vendor access revocation confirmations.
    • Evidence export: zip for a single employee and bulk export for all offboarded employees, with CSV summaries and attached files.
    • Overview: offboarding banner and quick actions list for pending offboardings.
    • People table: new Onboarded/Offboarded columns.
    • API: endpoints for checklist template, member checklist, access revocations (incl. confirm-all), pending offboardings, and evidence export.
    • Sync: backfills onboard dates from providers (e.g., Google Workspace creation time) and accepts startDate in the generic employee schema.
    • OpenAPI docs updated.
    • UI settings to manage the checklist template under People settings.
    • Bumped @trycompai/design-system to 1.1.19.
  • Migration

    • Run database migrations.
    • Rebuild and restart the API and app.
    • Trigger an employee sync to backfill onboard dates from providers.
    • Optional: review and adjust the offboarding checklist template in People settings.

Linked Linear issue CS-312: Implements employment event date tracking and supporting evidence capture to streamline auditor review.

Written for commit a1d26ff. Summary will update on new commits. Review in cubic

Marfuen and others added 30 commits May 19, 2026 11:26
…ent types

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a new "Employment Evidence" tab to the employee detail page that
allows uploading/downloading/deleting onboarding and offboarding evidence
documents. The tab appears between Device and Background Check, and
evidence sections are conditionally shown based on whether onboardDate
or offboardDate is set on the member.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds EmploymentEvidence and EmploymentEvidenceSection components and
wires a new "Employment Evidence" tab into Employee.tsx between Device
and Background Check. Evidence sections appear based on onboardDate and
offboardDate values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…auto-set offboardDate on deactivation

Onboarded column falls back to member.createdAt when no explicit date is set.
Offboarded column is blank for active members.
Deactivating a member now auto-sets offboardDate if not already set.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes calendar squished layout, missing hover/selection states, and
Button className override bug.

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

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

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Evidence-required items expand to show a drag-and-drop zone instead of
silently opening a file picker on checkbox click.

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

The wrapper div between Collapsible and CollapsibleTrigger caused
base-ui to render the panel as a sibling outside the border.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Marfuen and others added 22 commits May 19, 2026 11:27
Shows when evidence files exist. Opens all evidence download URLs
for auditors to collect offboarding proof.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ders and CSVs

Adds a new GET endpoint that generates a zip file containing all offboarding
evidence for a member, organized for auditor review. The zip includes a summary
CSV, vendor access revocations CSV with evidence files, and numbered checklist
item folders with their evidence. The frontend Export button now downloads this
zip via a Next.js proxy route instead of opening individual files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…iant when evidence exists

Shows as primary/filled when evidence exists, disabled outline when no
evidence yet. Always visible so auditors can find it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New API endpoint GET /v1/offboarding-checklist/export-all returns a zip
  with a subfolder per offboarded employee containing their checklist
  summary CSV, vendor revocations CSV, and all evidence files
- Menu button next to Add User with "Export all offboarding data" option
- Next.js proxy route supports ?all=true for the bulk export

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single pending member links directly to their offboarding tab.
Multiple links to the people page where Quick Actions shows the list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
onboardDate should only show a value when explicitly set — createdAt
reflects when they joined Comp AI, not when they joined the company.
Table shows "—" for members without an onboard date.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added startDate field to SyncEmployeeSchema
- Google Workspace: maps creationTime → onboardDate
- Rippling: maps start_date → onboardDate
- JumpCloud: maps created → onboardDate
- GenericEmployeeSyncService: passes startDate → onboardDate on member creation
- Rebuilt integration-platform package

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a sync encounters an existing member with no onboardDate and the
provider has a date, it now updates the member. This means the next
sync after deployment will populate onboardDate for all existing
members across Google Workspace, Rippling, JumpCloud, and dynamic
integrations.

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

1. handleFileUpload now checks isProcessing before starting, preventing
   duplicate uploads from rapid interactions
2. Export proxy route wraps fetch in try/catch, returns 502 on network failure

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

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

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

DS components don't accept these props. Move styling to inner wrapper
divs instead.

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

Base-ui's Menu.Trigger renders a <button>, so wrapping a <Button> inside
it creates invalid nested buttons. Use the render prop to replace the
trigger element directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DS PopoverContent already renders bg, shadow, ring, and rounded-lg.
Extra wrapper divs with duplicate styling created a visible double-card
effect. Removed wrappers and let the DS popup handle the chrome.

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

linear Bot commented May 20, 2026

CS-312

@vercel
Copy link
Copy Markdown

vercel Bot commented May 20, 2026

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

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment May 20, 2026 3:05pm
comp-framework-editor Ready Ready Preview, Comment May 20, 2026 3:05pm
portal Ready Ready Preview, Comment May 20, 2026 3:05pm

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.

cubic analysis

23 issues found across 55 files

Confidence score: 2/5

  • There is a high-risk authorization gap in apps/api/src/offboarding-checklist/offboarding-checklist.controller.ts: revoke/undo flows can operate on memberId values outside the current organizationId, which creates cross-tenant write risk.
  • apps/api/src/people/people.controller.ts has concrete data-integrity risks (unscoped deletion ownership checks and unvalidated date query params) that can lead to unintended deletions or runtime errors.
  • Auditor-facing evidence retention appears fragile in packages/db/prisma/schema/offboarding-checklist.prisma (revokedBy/completedBy delete behavior), and apps/api/src/offboarding-checklist/offboarding-export.service.ts still needs CSV formula-injection hardening; together these are meaningful compliance and security concerns.
  • Pay close attention to apps/api/src/offboarding-checklist/offboarding-checklist.controller.ts, apps/api/src/people/people.controller.ts, packages/db/prisma/schema/offboarding-checklist.prisma, and apps/api/src/offboarding-checklist/offboarding-export.service.ts - tenant scoping, deletion safety, evidence retention, and export sanitization are the key merge risks.
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/[employeeId]/components/Employee.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/Employee.tsx:77">
P2: Tab query validation does not respect feature/data-gated tabs, so some `?tab=` values can select a tab that is not rendered and leave the page in a blank tab state.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/OffboardingChecklist.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/OffboardingChecklist.tsx:73">
P2: Use `noopener`/`noreferrer` when opening download URLs in a new tab to prevent reverse-tabnabbing via `window.opener`.</violation>
</file>

<file name="apps/app/src/hooks/use-offboarding-checklist.ts">

<violation number="1" location="apps/app/src/hooks/use-offboarding-checklist.ts:133">
P3: This adds duplicate file-to-base64 conversion logic that already exists in other hooks; extracting a shared helper would prevent drift and keep file-upload behavior consistent.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/overview/components/TodosOverview.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/overview/components/TodosOverview.tsx:47">
P2: Failed API requests are rendered as a success empty state (`All clear — no pending items`), which can hide real pending offboarding work.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeDetails.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeDetails.tsx:73">
P2: Validate date order before saving so offboard dates cannot be earlier than onboard dates; otherwise invalid employment-event timelines can be persisted and break date-based review filtering.</violation>
</file>

<file name="apps/api/src/people/people.controller.ts">

<violation number="1" location="apps/api/src/people/people.controller.ts:121">
P2: Validate date query params before building filters; invalid date strings currently propagate to Prisma and can cause server errors.</violation>

<violation number="2" location="apps/api/src/people/people.controller.ts:613">
P1: Deletion is not scoped to the requested member/event type; verify attachment ownership before deleting.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/people/all/components/TeamMembersClient.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/all/components/TeamMembersClient.tsx:151">
P2: Pending invitation rows are incorrectly kept when date filters are active, so onboarding/offboarding filters still show irrelevant non-member records.</violation>

<violation number="2" location="apps/app/src/app/(app)/[orgId]/people/all/components/TeamMembersClient.tsx:579">
P2: Preset date ranges use a time-of-day `from` boundary, which can incorrectly exclude records on the starting day.</violation>
</file>

<file name="apps/api/src/offboarding-checklist/offboarding-checklist.service.ts">

<violation number="1" location="apps/api/src/offboarding-checklist/offboarding-checklist.service.ts:337">
P2: Pending-offboarding detection can be incorrect because it counts completions for disabled checklist items when comparing against enabled template count.</violation>
</file>

<file name="packages/db/prisma/schema/offboarding-checklist.prisma">

<violation number="1" location="packages/db/prisma/schema/offboarding-checklist.prisma:33">
P1: Use `SetNull` (with an optional FK) for `completedBy` so deleting a user does not delete offboarding completion evidence.

According to linked Linear issue CS-312, these records are part of auditor-facing employment-event evidence and should be retained.</violation>

<violation number="2" location="packages/db/prisma/schema/offboarding-checklist.prisma:52">
P1: Do not cascade-delete access revocation evidence when a user is removed; make `revokedBy` nullable and use `onDelete: SetNull`.

According to linked Linear issue CS-312, termination evidence must remain available for auditor review.</violation>
</file>

<file name="apps/api/src/offboarding-checklist/offboarding-checklist.controller.ts">

<violation number="1" location="apps/api/src/offboarding-checklist/offboarding-checklist.controller.ts:253">
P2: Reject partial evidence payloads instead of silently discarding them; otherwise revocation can succeed while intended supporting evidence is lost.

According to linked Linear issue CS-312, supporting evidence capture is a core requirement for these employment events.</violation>

<violation number="2" location="apps/api/src/offboarding-checklist/offboarding-checklist.controller.ts:256">
P1: Validate that `memberId` belongs to `organizationId` before revocation writes; current flow allows cross-tenant member IDs to be used in revoke/undo operations.</violation>
</file>

<file name="apps/api/src/integration-platform/services/generic-employee-sync.service.ts">

<violation number="1" location="apps/api/src/integration-platform/services/generic-employee-sync.service.ts:198">
P2: Validate `employee.startDate` before converting it to `Date`; malformed provider values can make sync writes fail.</violation>
</file>

<file name="apps/api/src/people/utils/member-queries.ts">

<violation number="1" location="apps/api/src/people/utils/member-queries.ts:26">
P2: The response DTO is not updated for the newly selected `onboardDate`/`offboardDate` fields, so API typing and generated schema remain out of sync with actual returned data.</violation>

<violation number="2" location="apps/api/src/people/utils/member-queries.ts:73">
P2: The new date-range filters accept unvalidated `Date` objects; invalid query dates can flow into Prisma and fail at runtime instead of being rejected as bad request input.</violation>
</file>

<file name="apps/api/src/offboarding-checklist/offboarding-export.service.ts">

<violation number="1" location="apps/api/src/offboarding-checklist/offboarding-export.service.ts:115">
P2: Attachment filenames are inserted into ZIP paths without sanitization, which can create unsafe/nested archive entries and filename collisions.</violation>

<violation number="2" location="apps/api/src/offboarding-checklist/offboarding-export.service.ts:166">
P2: `exportAllOffboardings` uses only `safeName` for folder paths, so members with identical names can overwrite each other’s exported evidence.</violation>

<violation number="3" location="apps/api/src/offboarding-checklist/offboarding-export.service.ts:196">
P2: Attachment download errors are swallowed, causing silent data loss in exported evidence bundles.</violation>

<violation number="4" location="apps/api/src/offboarding-checklist/offboarding-export.service.ts:203">
P1: CSV export is vulnerable to formula injection because fields are quote-escaped but not formula-sanitized.

(Based on your team's feedback about CSV formula injection hardening.) [FEEDBACK_USED]</violation>
</file>

<file name="apps/api/src/offboarding-checklist/access-revocation.service.ts">

<violation number="1" location="apps/api/src/offboarding-checklist/access-revocation.service.ts:143">
P2: Undoing a revocation should delete its uploaded evidence to avoid orphaned attachment records/files.</violation>

<violation number="2" location="apps/api/src/offboarding-checklist/access-revocation.service.ts:204">
P2: Use direct count queries in completion sync instead of calling `getAccessRevocations`, which does expensive attachment loading.</violation>
</file>

Linked issue analysis

Linked issue: CS-312: [Feature] Add personnel Employment events date tracking + supporting evidence upload section

Status Acceptance criteria Notes
Add optional onboardDate and offboardDate fields to Member (DB + API) Database schema and migration add the columns and attachment enum values; API DTOs and prisma schema updated to expose the fields.
Treat blank values for imported employees as pre-existing (avoid mandatory backfill) Sync/import logic only writes onboardDate when not already set and when a source date is available, leaving blanks for pre-existing/imported users.
Allow filtering/sorting of employees by onboard/offboard date (backend + API) Member queries and PeopleService accept onboard/offboard filter parameters and openapi includes the query params, enabling backend filtering support.
Set offboardDate when a member is deactivated PeopleService updates offboardDate when marking a member deactivated, so offboard events are recorded automatically on deactivation.
Add evidence upload endpoints and attachment types for employment events New attachment enum values and client/server hooks and DTOs support uploading employment evidence for onboard/offboard events.
Add an offboarding checklist / evidence UI tied to employment events and access revocation tracking A full offboarding-checklist module was added (controller/service/module), many frontend components/hooks for checklist items, access revocation lists, summary card, settings, and export are present.
Provide export capability for auditor review of member evidence Services and an API route exist to assemble and stream an archive of offboarding evidence for a member or all members.
Surface pending offboarding items / prompt admins to complete evidence (auditor-friendly UI) UI changes include an OffboardingBanner, ToDo/Todos overview additions, and ToDo tabs that surface pending offboarding items to admins.

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment on lines +613 to +615
this.resolveEventType(eventType);
await this.peopleService.findById(memberId, organizationId);
await this.attachmentsService.deleteAttachment(organizationId, attachmentId);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 20, 2026

Choose a reason for hiding this comment

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

P1: Deletion is not scoped to the requested member/event type; verify attachment ownership before deleting.

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

<comment>Deletion is not scoped to the requested member/event type; verify attachment ownership before deleting.</comment>

<file context>
@@ -529,6 +556,66 @@ export class PeopleController {
+    @Param('attachmentId') attachmentId: string,
+    @OrganizationId() organizationId: string,
+  ) {
+    this.resolveEventType(eventType);
+    await this.peopleService.findById(memberId, organizationId);
+    await this.attachmentsService.deleteAttachment(organizationId, attachmentId);
</file context>
Suggested change
this.resolveEventType(eventType);
await this.peopleService.findById(memberId, organizationId);
await this.attachmentsService.deleteAttachment(organizationId, attachmentId);
const entityType = this.resolveEventType(eventType);
await this.peopleService.findById(memberId, organizationId);
const attachments = await this.attachmentsService.getAttachments(
organizationId,
memberId,
entityType,
);
if (!attachments.some((attachment) => attachment.id === attachmentId)) {
throw new BadRequestException('Attachment not found for this employment event');
}
await this.attachmentsService.deleteAttachment(organizationId, attachmentId);
Fix with Cubic

organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
member Member @relation(fields: [memberId], references: [id], onDelete: Cascade)
vendor Vendor @relation(fields: [vendorId], references: [id], onDelete: Cascade)
revokedBy User @relation("AccessRevocationRevokedBy", fields: [revokedById], references: [id], onDelete: Cascade)
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 20, 2026

Choose a reason for hiding this comment

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

P1: Do not cascade-delete access revocation evidence when a user is removed; make revokedBy nullable and use onDelete: SetNull.

According to linked Linear issue CS-312, termination evidence must remain available for auditor review.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/prisma/schema/offboarding-checklist.prisma, line 52:

<comment>Do not cascade-delete access revocation evidence when a user is removed; make `revokedBy` nullable and use `onDelete: SetNull`.

According to linked Linear issue CS-312, termination evidence must remain available for auditor review.</comment>

<file context>
@@ -0,0 +1,57 @@
+  organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
+  member       Member       @relation(fields: [memberId], references: [id], onDelete: Cascade)
+  vendor       Vendor       @relation(fields: [vendorId], references: [id], onDelete: Cascade)
+  revokedBy    User         @relation("AccessRevocationRevokedBy", fields: [revokedById], references: [id], onDelete: Cascade)
+
+  @@unique([memberId, vendorId])
</file context>
Fix with Cubic

organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
member Member @relation(fields: [memberId], references: [id], onDelete: Cascade)
templateItem OffboardingChecklistTemplate @relation(fields: [templateItemId], references: [id], onDelete: Cascade)
completedBy User @relation("OffboardingChecklistCompletedBy", fields: [completedById], references: [id], onDelete: Cascade)
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 20, 2026

Choose a reason for hiding this comment

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

P1: Use SetNull (with an optional FK) for completedBy so deleting a user does not delete offboarding completion evidence.

According to linked Linear issue CS-312, these records are part of auditor-facing employment-event evidence and should be retained.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/prisma/schema/offboarding-checklist.prisma, line 33:

<comment>Use `SetNull` (with an optional FK) for `completedBy` so deleting a user does not delete offboarding completion evidence.

According to linked Linear issue CS-312, these records are part of auditor-facing employment-event evidence and should be retained.</comment>

<file context>
@@ -0,0 +1,57 @@
+  organization Organization                 @relation(fields: [organizationId], references: [id], onDelete: Cascade)
+  member       Member                       @relation(fields: [memberId], references: [id], onDelete: Cascade)
+  templateItem OffboardingChecklistTemplate @relation(fields: [templateItemId], references: [id], onDelete: Cascade)
+  completedBy  User                         @relation("OffboardingChecklistCompletedBy", fields: [completedById], references: [id], onDelete: Cascade)
+
+  @@unique([memberId, templateItemId])
</file context>
Fix with Cubic

const evidence = body?.fileName && body?.fileType && body?.fileData
? { fileName: body.fileName, fileType: body.fileType, fileData: body.fileData }
: undefined;
return this.offboardingChecklistService.revokeVendorAccess({
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 20, 2026

Choose a reason for hiding this comment

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

P1: Validate that memberId belongs to organizationId before revocation writes; current flow allows cross-tenant member IDs to be used in revoke/undo operations.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/offboarding-checklist/offboarding-checklist.controller.ts, line 256:

<comment>Validate that `memberId` belongs to `organizationId` before revocation writes; current flow allows cross-tenant member IDs to be used in revoke/undo operations.</comment>

<file context>
@@ -0,0 +1,283 @@
+    const evidence = body?.fileName && body?.fileType && body?.fileData
+      ? { fileName: body.fileName, fileType: body.fileType, fileData: body.fileData }
+      : undefined;
+    return this.offboardingChecklistService.revokeVendorAccess({
+      organizationId,
+      memberId,
</file context>
Fix with Cubic

}

function escapeCsvField(value: string): string {
return value.replace(/"/g, '""');
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 20, 2026

Choose a reason for hiding this comment

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

P1: CSV export is vulnerable to formula injection because fields are quote-escaped but not formula-sanitized.

(Based on your team's feedback about CSV formula injection hardening.)

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/offboarding-checklist/offboarding-export.service.ts, line 203:

<comment>CSV export is vulnerable to formula injection because fields are quote-escaped but not formula-sanitized.

(Based on your team's feedback about CSV formula injection hardening.) </comment>

<file context>
@@ -0,0 +1,204 @@
+}
+
+function escapeCsvField(value: string): string {
+  return value.replace(/"/g, '""');
+}
</file context>
Fix with Cubic

const buffer = await this.getAttachmentBuffer(organizationId, file.id);
if (!buffer) continue;
archive.append(buffer, {
name: `${prefix}vendor-access-revocations/evidence/${file.name}`,
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 20, 2026

Choose a reason for hiding this comment

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

P2: Attachment filenames are inserted into ZIP paths without sanitization, which can create unsafe/nested archive entries and filename collisions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/offboarding-checklist/offboarding-export.service.ts, line 115:

<comment>Attachment filenames are inserted into ZIP paths without sanitization, which can create unsafe/nested archive entries and filename collisions.</comment>

<file context>
@@ -0,0 +1,204 @@
+        const buffer = await this.getAttachmentBuffer(organizationId, file.id);
+        if (!buffer) continue;
+        archive.append(buffer, {
+          name: `${prefix}vendor-access-revocations/evidence/${file.name}`,
+        });
+      }
</file context>
Fix with Cubic

});
if (!attachment) return null;
return await this.attachmentsService.getObjectBuffer(attachment.url);
} catch {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 20, 2026

Choose a reason for hiding this comment

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

P2: Attachment download errors are swallowed, causing silent data loss in exported evidence bundles.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/offboarding-checklist/offboarding-export.service.ts, line 196:

<comment>Attachment download errors are swallowed, causing silent data loss in exported evidence bundles.</comment>

<file context>
@@ -0,0 +1,204 @@
+      });
+      if (!attachment) return null;
+      return await this.attachmentsService.getObjectBuffer(attachment.url);
+    } catch {
+      return null;
+    }
</file context>
Fix with Cubic

throw new NotFoundException('Revocation record not found');
}

await db.offboardingAccessRevocation.delete({
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 20, 2026

Choose a reason for hiding this comment

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

P2: Undoing a revocation should delete its uploaded evidence to avoid orphaned attachment records/files.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/offboarding-checklist/access-revocation.service.ts, line 143:

<comment>Undoing a revocation should delete its uploaded evidence to avoid orphaned attachment records/files.</comment>

<file context>
@@ -0,0 +1,233 @@
+      throw new NotFoundException('Revocation record not found');
+    }
+
+    await db.offboardingAccessRevocation.delete({
+      where: { id: revocation.id },
+    });
</file context>
Fix with Cubic

return;
}

const { totalVendors, revokedCount } = await this.getAccessRevocations(
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 20, 2026

Choose a reason for hiding this comment

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

P2: Use direct count queries in completion sync instead of calling getAccessRevocations, which does expensive attachment loading.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/offboarding-checklist/access-revocation.service.ts, line 204:

<comment>Use direct count queries in completion sync instead of calling `getAccessRevocations`, which does expensive attachment loading.</comment>

<file context>
@@ -0,0 +1,233 @@
+      return;
+    }
+
+    const { totalVendors, revokedCount } = await this.getAccessRevocations(
+      organizationId,
+      memberId,
</file context>
Fix with Cubic

};
}

function fileToBase64(file: File): Promise<string> {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 20, 2026

Choose a reason for hiding this comment

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

P3: This adds duplicate file-to-base64 conversion logic that already exists in other hooks; extracting a shared helper would prevent drift and keep file-upload behavior consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/hooks/use-offboarding-checklist.ts, line 133:

<comment>This adds duplicate file-to-base64 conversion logic that already exists in other hooks; extracting a shared helper would prevent drift and keep file-upload behavior consistent.</comment>

<file context>
@@ -0,0 +1,143 @@
+  };
+}
+
+function fileToBase64(file: File): Promise<string> {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
</file context>
Fix with Cubic

@Marfuen Marfuen closed this May 20, 2026
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.

1 participant