Skip to content

[comp] Production Deploy#3392

Merged
tofikwest merged 14 commits into
releasefrom
main
Jul 10, 2026
Merged

[comp] Production Deploy#3392
tofikwest merged 14 commits into
releasefrom
main

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

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 an internal-organization flag so platform admins are excluded in customer orgs but included as real members in internal orgs, and introduces a new ISMS “Roles, Responsibilities & Authorities” document (ISO 27001 5.3) with seeded roles, member assignments, audit-route selection, and PDF/DOCX exports. Applies the participation rule consistently across assignments, approvers, notifications, compliance, device views, and Trigger jobs, and gates Roles submission with server/client validation.

  • New Features

    • Internal org participation: DB Organization.isInternal (default false) with admin toggle + confirmation; single rule via isOrgParticipant in @trycompai/auth/participation with dependency‑free mirrors for Trigger bundles (API + app) used across API/services, schedules/notifiers/device queries, and SelectAssignee; helpers orgParticipantMemberWhere/orgParticipantMemberWhereForFlag and getOrgIsInternal.
    • ISMS Roles (CS-698): new roles_and_responsibilities type with four seeded governance roles, custom roles, member assignments with clause‑7.2 competence, Internal Auditor route (in_house/external/training_planned) with validation, team‑size band (1–3 vs 4+), idempotent seeding and submit‑for‑approval gate; new services (IsmsRoleService, IsmsRoleAssignmentService), UI page/components, and export integration (PDF/DOCX). DB adds IsmsRole/IsmsRoleAssignment, enums (IsmsCompetenceBasis, IsmsAuditRoute), and a unique (documentId, roleKey) index; wizard generation order updated.
  • Bug Fixes

    • Trigger.dev deploy: replace @trycompai/auth/participation imports in API notifiers with a local, dependency‑free mirror; add a drift‑guard spec and Jest mapper for @trycompai/auth/participation to source to keep tests green.
    • Security: reject assignee IDs that aren’t members of the org across tasks, task items, policies, risks, and vendors; keep approver/assignee checks consistent and require active members where needed.
    • Notifications/schedules/automation: use the shared participation where‑fragments, make mention filters spread‑safe, align browser‑automation and consolidate org lookups; add regression tests to ensure automation‑failure notifiers apply the internal‑org participant filter.
    • Compliance/devices/digests: respect internal orgs; skip the internal‑flag query when no platform admins are present.
    • Utilities/tests: strict YYYY‑MM‑DD parsing for date inputs; serialize Roles submit to avoid races; scope Roles lookups to the new doc type; add PointerEvent test shim and new unit tests.

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

Review in cubic

tofikwest and others added 11 commits July 8, 2026 19:43
Adds `Organization.isInternal` so platform-operated organizations can run
compliance with their platform-admin staff as real participants — assignable,
counted toward compliance progress, and included in notifications and device
rollups. Behavior is unchanged for every other organization (flag defaults to
false; admin-only toggle on the org admin screen).

Centralizes the rule behind a single `isOrgParticipant` predicate
(`@trycompai/auth/participation`, mirrored for the Trigger.dev bundle),
replacing the scattered inline `user.role === 'admin'` participation checks
across assignment/approval, compliance counting, notifications, reminders, and
device queries. Platform-admin access guards are intentionally untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWG1YQ8KsjefFAvRV7pdtb
…idation, keep dep boundary

- run-browser-automation: use the shared `orgParticipantMemberWhere` helper
  instead of an inline copy, so it matches the other task notifiers (uses the
  role constant + owner exception) and still includes everyone in internal orgs.
- update-admin-organization DTO: reject an explicit `null` isInternal at
  validation (400) instead of letting it reach the non-null column (500).
- org-participation (app): stop re-exporting `isOrgParticipant` from the module
  that imports `@db/server`; consumers import it from the dependency-free
  `org-participation-rule` so server-only code can't leak into client/Trigger bundles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWG1YQ8KsjefFAvRV7pdtb
…rule, guard boundaries

- generate-vendor-mitigation: fail closed — only assign the author when the
  member actually exists in this org (add `author &&` guard) so a missed lookup
  can never write an unknown/cross-org member id.
- SelectAssignee: use the shared `isOrgParticipant` predicate instead of an
  inline copy, so the picker can't drift from the backend rule.
- lib/org-participation.ts: add `import 'server-only'` (matches compliance.ts /
  permissions.server.ts) so this DB-backed helper can't be pulled into a client bundle.
- Add a drift guard test asserting the app-local participation rule stays
  identical to `@trycompai/auth/participation`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWG1YQ8KsjefFAvRV7pdtb
…r, skip needless query

- orgParticipantMemberWhere: make it a faithful Prisma translation of
  isOrgParticipant — for non-internal orgs exclude only platform admins
  (include null roles; drop the owner carve-out, which the isInternal flag now
  supersedes). Removes the split source of truth between the predicate and the
  query fragment.
- generate-vendor-mitigation: add `deactivated: false` to the author lookup so a
  deactivated member can't be auto-assigned (matches the other trigger tasks).
- filterComplianceMembers (app + api): skip the `getOrgIsInternal` query when the
  member list has no platform admins — the flag can't change the result there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWG1YQ8KsjefFAvRV7pdtb
…ternal toggle

- org-participation: return the participant where-fragment wrapped in `AND`
  (via new `orgParticipantMemberWhereForFlag`) so spreading it no longer
  overwrites a caller's existing `user` filter. The two mention notifiers
  constrain `user: { id: { in } }`, which the bare `user` key clobbered, so
  notifications reached every non-admin member instead of the mentioned ones.
  Adds a spread-safety unit test.
- OrganizationDetail: refresh the server layout (router.refresh) after toggling
  isInternal for the current org so OrgInternalProvider isn't stale, and require
  a confirmation dialog before changing this org-wide setting.
- run-browser-automation: select `isInternal` in the existing org query and use
  the pure where-builder, dropping the redundant getOrgIsInternal query.
- Normalize participation onto isOrgParticipant everywhere: drop the per-member
  owner carve-out from weekly-task-reminder, task-schedule, policy-schedule and
  the task-item assignment notifier (matches the API where-fragment and the
  predicate); remove now-dead role selections and stale comments.
- tests: add a PointerEvent shim to the vitest setup (jsdom lacks it, base-ui
  Switch needs it), fix admin-org test mocks missing the new isInternal field,
  and cover the internal-toggle confirmation flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzEVwhhoFghng6SDMGecMq
…rticipation

# Conflicts:
#	apps/app/src/app/api/people/agent-devices/route.ts
The assignee guards only threw when a member was found in the org and failed the
participant check. When the org-scoped `member.findFirst` returned null — the id
belongs to another org or doesn't exist — the guard was skipped and the id was
persisted, since assignee columns don't enforce same-org membership. This gap is
pre-existing (the prior `member?.user.role === 'admin'` check had the same null
hole); hardened here while the participation refactor already touches these
guards. Every assignee guard now rejects a non-member with a clear 400.

Covers tasks (bulk + single update), task items (create + update), policies
(create), vendors, and risks. Approver guards already rejected null members.

Also consolidate task-notifier's automation-failure paths, which fetched the org
twice (once for isInternal via orgParticipantMemberWhere, once for name), into a
single `{ name, isInternal }` lookup + orgParticipantMemberWhereForFlag.

Adds a regression test for the cross-org assignee rejection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XzEVwhhoFghng6SDMGecMq
feat(org): treat platform admins as members in internal organizations
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
comp-framework-editor (staging) Ready Ready Preview, Comment Jul 10, 2026 8:00pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
app (staging) Skipped Skipped Jul 10, 2026 8:00pm
portal (staging) Skipped Skipped Jul 10, 2026 8:00pm

Request Review

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 45 files

Confidence score: 3/5

  • In apps/app/src/app/(app)/[orgId]/admin/organizations/[adminOrgId]/components/OrganizationDetail.tsx, the update path hits an API route gated by a hardcoded global admin role, so custom roles can be incorrectly blocked from a sensitive setting; merging as-is risks breaking delegated admin workflows for orgs using custom RBAC. Route this mutation through the existing API permission guard before merging.
  • In apps/api/src/tasks/task-notifier.service.ts, the new internal-org participant filter lacks regression tests, so future changes to orgParticipantMemberWhereForFlag could silently misroute or suppress automation-failure notifications. Add internal-organization and customer-organization coverage to de-risk this behavior.
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/tasks/task-notifier.service.ts">

<violation number="1" location="apps/api/src/tasks/task-notifier.service.ts:1093">
P3: Automation-failure notifications have no regression coverage for the new internal-org participant filter. Add cases for internal and customer organizations so a change to `orgParticipantMemberWhereForFlag` cannot silently route notifications to the wrong recipients.</violation>
</file>

<file name="apps/app/src/app/(app)/[orgId]/admin/organizations/[adminOrgId]/components/OrganizationDetail.tsx">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/admin/organizations/[adminOrgId]/components/OrganizationDetail.tsx:58">
P2: Custom roles cannot be granted access to this sensitive organization setting because this mutation reaches an API route gated by the hardcoded global `admin` role. Route this update through the API permission guard with an explicit RBAC permission instead of `PlatformAdminGuard`'s role-string check.</violation>
</file>

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

Fix all with cubic | Re-trigger cubic

Comment thread apps/api/src/tasks/task-notifier.service.ts
…8) (#3391)

* feat(isms): add Roles, Responsibilities & Authorities document (CS-698)

Add a new ISMS > Roles sub-page (ISO 27001 Clause 5.3): four seeded
governance roles (Top Management, SPO, Deputy SPO, Internal Auditor) with
pre-filled text, member assignments with per-member competence evidence
(Clause 7.2), an Internal Auditor route picker with an independence
soft-warning, 1-3 / 4+ team-size bands, and generate-time validation. The
Clause 5.3 document renders to branded PDF + DOCX through the existing ISMS
export pipeline, including the two auto-generated governance rows and an
operational-responsibilities summary read from per-artifact owners.

Mirrors the existing ISMS register pattern; reuses the evidence permission
resource and the is-isms-enabled flag gate. Role seeding is idempotent
(never destructive) so member assignments survive a regenerate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bx4urA8n2E6zzBAjEuxwR

* fix(isms): address CS-698 review — validation, concurrency, UI hardening

- Enforce Clause 5.3 completeness server-side on submit-for-approval (not just
  the client), so an incomplete Roles doc can't be published by calling the API
  directly. Validation now iterates the required seeded roles, catching an
  entirely-missing row, not only present-but-unassigned ones (client + server).
- Add a unique (documentId, roleKey) index + skipDuplicates so concurrent
  first-load provisioning can't double-seed governance roles.
- Make the role-assignment idempotency check run inside the per-document lock,
  so concurrent duplicate adds return the existing row instead of hitting the
  unique index.
- Extract a shared parseOptionalDate util (was duplicated in both role services).
- Neutralise the small-team note so it no longer asserts an external audit route
  that can contradict the customer's chosen route.
- Audit-route member pickers: drop the free-text fallback (a member id is
  required); disable the select when no members exist.
- Swallow already-handled mutation rejections in the add-member and delete-role
  handlers to avoid unhandled promise rejections.
- Fix stale "all six ISMS documents" JSDoc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bx4urA8n2E6zzBAjEuxwR

* fix(isms): harden roles submit gate + strict date parsing (CS-698 review 2)

- Server-side clause-5.3 gate now counts only assignments that resolve to an
  active member, so a required role assigned solely to a deactivated/removed
  member no longer passes submit-for-approval.
- parseOptionalDate now validates a strict YYYY-MM-DD format and round-trips the
  parsed value, rejecting ambiguous/rolled-over input (e.g. 2026-02-30) instead
  of relying on permissive Date parsing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bx4urA8n2E6zzBAjEuxwR

* fix(isms): close CS-698 review round 3 — submit race, active-member + route validation, doc-type scoping

- Serialize submit-for-approval: validate completeness and flip status inside one
  transaction under the per-document lock, and take that same lock on role/
  assignment update+remove, so an edit can't invalidate the doc between the
  check and the needs_review transition (TOCTOU).
- Enforce active (non-deactivated) members everywhere: role audit-route member,
  assignment member, and the completeness gate count only active-member
  assignments; the client validation mirrors it via the active-member set so the
  Submit button and the server agree.
- Route-specific Internal Auditor validation (client + server): external requires
  firm/person + evidence reference; in-house requires a member; training-planned
  requires member + course + due date.
- Scope role + assignment register lookups to roles_and_responsibilities
  documents so role rows can't attach to unrelated ISMS documents.
- Generated governance table renders only the four seeded roles + two auto rows
  (custom roles are managed on the page but not part of the standardized 5.3
  table).
- AuditRoutePicker: swallow the already-handled save rejection (no unhandled
  promise rejection).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bx4urA8n2E6zzBAjEuxwR

* fix(isms): audit-route validation counts active members + trims required text (CS-698 review 4)

- The Internal Auditor's in-house/training-planned member must now resolve to an
  ACTIVE member (same active-member set used for assignments), on both the client
  gate and the server completeness check — a deactivated auditor no longer passes.
- Required audit text is trimmed before the check: external firm/person + evidence
  and the training-planned course can't be satisfied by whitespace via the API.
- Unify the active-member logic in roleValidationMessages (assignments + audit
  member) so client and server agree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bx4urA8n2E6zzBAjEuxwR

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation (#3393)

The API's Trigger.dev deploy bundles task notifiers with esbuild, which failed
with "Could not resolve @trycompai/auth/participation". The auth package's dist
is not built in the trigger deploy workflow (it builds db/email/integration-
platform only), and the API's org-participation code — reachable from the task
notifiers — imported that subpath, so esbuild had nothing to resolve.

Mirror the app's approach for Trigger.dev-bundled code: use a dependency-free
local copy of the tiny participation predicate (`org-participation-rule.ts`)
instead of importing `@trycompai/auth`. This keeps the trigger bundle free of
the auth package (which also pulls better-auth, unbundleable). A drift-guard
spec asserts the mirror stays identical to `@trycompai/auth/participation`, and
a jest moduleNameMapper resolves that subpath to source so the guard runs
without needing the auth dist built.


Claude-Session: https://claude.ai/code/session_01XzEVwhhoFghng6SDMGecMq

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e notifiers (#3394)

Adds regression cases for `notifyAutomationFailures` and
`notifyBulkAutomationFailures` asserting the recipient `member.findMany` query
applies `orgParticipantMemberWhereForFlag`: platform admins are excluded for
customer orgs (isInternal false) and included for internal orgs (isInternal
true). Guards against a future change to the participation helper silently
routing automation-failure notifications to the wrong recipients (cubic P3 on
the release PR #3392).


Claude-Session: https://claude.ai/code/session_01XzEVwhhoFghng6SDMGecMq

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to staging – app July 10, 2026 19:58 Inactive
@vercel vercel Bot temporarily deployed to staging – portal July 10, 2026 19:58 Inactive
@tofikwest tofikwest merged commit 7bbe9c9 into release Jul 10, 2026
14 checks passed
@claudfuen

Copy link
Copy Markdown
Contributor

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