Skip to content

[comp] Production Deploy#3142

Merged
tofikwest merged 10 commits into
releasefrom
main
Jun 15, 2026
Merged

[comp] Production Deploy#3142
tofikwest merged 10 commits into
releasefrom
main

Conversation

@github-actions

@github-actions github-actions Bot commented Jun 15, 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

Ships ISO 27001 (ISMS) foundational documents end to end: deterministic generation from platform data, approvals, and PDF/DOCX export, with a clean UI and framework-editor mappings. Also hardens OAuth token refresh with per-connection single-flight (short, ownership-checked lease, bounded timeout, fail-safe reuse) and keeps the Integration Builder test dialog from hanging via an independent polling backstop.

  • New Features

    • Six documents: 4.1 Context, 4.2 Parties & Requirements, 4.3 Scope, 5.1 Leadership, 6.2 Objectives — derived from platform data with drift detection and preserved manual overrides (CS-437, CS-438).
    • Approvals lifecycle with versioned snapshots; any content edit reverts approved docs to draft.
    • Branded exports to PDF and DOCX (jspdf + jspdf-autotable, docx) with rich cover and metadata.
    • Framework Editor: ISMS templates with framework-scoped requirement mapping and control-template links; org documents inherit links on setup.
    • Registers: generic create/update/delete endpoints across all four registers, concurrency-safe position allocation, Zod-validated payloads.
    • App UI: overview and detail pages for all six docs (drift banner, approval, linked controls, export) and a setup wizard for non-derivable inputs; the ISO 27001 tab appears when ISO 27001 is active and the is-isms-enabled flag is on.
  • Migration

    • Enable is-isms-enabled in PostHog for target orgs and ensure ISO 27001 is active.
    • Optional: in Framework Editor, map ISMS templates to requirements and link control templates; org docs will inherit links on setup.
    • Run the ISMS setup wizard and Generate All (or regenerate per document) to seed content.

Written for commit 08313a5. Summary will update on new commits.

Review in cubic

github-actions Bot and others added 3 commits June 15, 2026 13:34
…tomation"

The Integration Builder test poller only treated COMPLETED (with output) and
FAILED as terminal Trigger.dev run statuses. Every other terminal status
(TIMED_OUT, CRASHED, SYSTEM_FAILURE, EXPIRED, CANCELED) — and a run stuck in
the queue — fell through to a 1s re-poll with no ceiling, so the panel spun on
"Running your automation" forever. This reproduces even with a no-op script,
because the hang is about how the loop handles the END state, not the script.

- Treat COMPLETED as success regardless of output payload
- Surface a clear, status-specific error for every terminal-but-not-COMPLETED
  status (and treat unknown statuses as terminal — fail-safe)
- Add an absolute polling deadline backstop so the UI can never spin forever
- Add regression tests for all terminal states, unknown status, and the cap

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(isms): add CS-437 foundational documents design spec

Placement (framework-grouped Documents page, ISO 27001 (ISMS) tab),
IsmsDocument substrate following the SOA pattern, clause-requirement
linkage, per-document data models, PDF reuse + net-new DOCX export,
feature-flagged all-or-nothing release, and slice-first build sequence.

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

* feat(isms): add IsmsDocument data model + Context register (CS-437)

IsmsDocument + IsmsDocumentVersion (versioned narrative, source snapshot for
drift, pdf/docx urls, sign-off) and IsmsContextIssue (clause 4.1 register),
mirroring the SOADocument pattern. Links the global FrameworkEditorFramework +
org + ISO clause requirement. Back-relations on Organization,
FrameworkEditorFramework, FrameworkEditorRequirement, and Member.

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

* feat(isms): add ISMS documents API module with PDF/DOCX export (CS-437)

NestJS isms module (@controller path:'isms' v1) mirroring SOA: ensure-setup,
get document, deterministic Context-of-Organization (4.1) generation from
platform data, context-issue CRUD with derived->manual override, sign-off
(submit/approve/decline), drift detection vs approved snapshot, and branded
PDF + net-new DOCX export (docx@9.7.1). All endpoints gated
@RequirePermission('audit', ...) to match SOA. 63 Jest tests. openapi regen.

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

* feat(isms): framework-grouped Documents IA + Context of the Organization (4.1) (CS-437)

Add framework-grouped Documents tabs with a flag-gated, ISO-27001-conditional
"ISO 27001 (ISMS)" tab (IsmsOverview: 6 foundational-doc cards + the moved SOA
card). Full Context of the Organization (4.1) detail page: useIsmsDocument hook,
generate-from-platform-data, editable internal/external issues register with
derived->manual override, drift banner, submit/approve/decline sign-off, and
PDF/DOCX export. Mutations gated on audit:update; readers can view + export.
isIsmsEnabled defaults off (PostHog) until the full pack ships. Vitest tests.

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

* refactor(isms): gate on evidence resource, drop feature flag (CS-437)

- Permissions: switch ISMS endpoints from `audit` to the `evidence` resource
  (reads incl. ensure-setup -> evidence:read; mutations -> evidence:update).
  `evidence` matches the Documents route gate and is the correct semantic fit:
  owner/admin author + sign off, auditor read-only (review + export), others
  excluded. Frontend mutation gating moved to evidence:update.
- Drop the isIsmsEnabled runtime flag. The ISO 27001 (ISMS) tab is now purely
  framework-conditional (visible when ISO 27001 is active); the unmerged branch
  is the release gate. SOA card now lives solely in the ISMS tab.
- Spec updated to match (permissions + merge-gated release).

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

* feat(isms): add register tables for Interested Parties, Requirements, Objectives (CS-437)

Add IsmsInterestedParty (4.2a), IsmsInterestedPartyRequirement (4.2b/c, linked
to a party), and IsmsObjective (6.2) register tables + IsmsObjectiveStatus enum,
all reusing the shared IsmsContextSource derived/manual flag. Scope (4.3) and
Leadership (5.1) are singletons stored in IsmsDocumentVersion.narrative.
Additive migration only.

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

* feat(isms): add api for all six foundational documents via type dispatch (CS-437)

Refactor generate/drift/export to dispatch by IsmsDocumentType to per-document
handler modules (documents/registry.ts). Add deterministic derivation, register
CRUD, and branded PDF/DOCX export for: Interested Parties (4.2a), Requirements &
Treatment (4.2b/c, linked to parties), Objectives (6.2), plus singleton narrative
docs Scope (4.3) and Leadership (5.1) stored zod-validated in version.narrative.
Single platform-data source; per-type drift snapshots; manual rows preserved on
regenerate. All endpoints @RequirePermission('evidence', ...). 138 Jest tests.

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

* feat(isms): detail pages for the 5 remaining foundational documents (CS-437)

Generalize useIsmsDocument (generic createRow/updateRow/deleteRow + saveNarrative)
and the [type] route dispatch, enable all six overview cards, and add functional
detail pages on the proven Context pattern:
- Interested Parties Register (4.2a), Requirements & ISMS Treatment (4.2b/c),
  Information Security Objectives Plan (6.2) - editable registers
- ISMS Scope Statement (4.3, certificate sentence first-class) and Leadership
  Commitment (5.1, clause 5.1 a-h) - narrative forms via saveNarrative
Each reuses DriftBanner + IsmsApprovalSection, gates mutations on evidence:update,
and offers PDF/DOCX export. Vitest tests per document (admin vs read-only).

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

* feat(isms): add IsmsProfile for wizard inputs (CS-438)

One profile per org+framework holding the ~12 un-derivable wizard answers as a
Zod-validated JSON blob that feeds document generation. Additive migration.

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

* feat(isms): document-creation wizard (CS-438)

API: IsmsProfile get-or-init/save/complete + generate-all, a shared Zod
WizardAnswers schema, computed defaults for pre-population (capabilities from
Types of Services, certificate sentence, default objectives/outcomes, cloud
split), and threading wizard answers through derivation (insurance/regulators/
contractors/EU-rep -> Interested Parties & Requirements; certificate sentence +
cloud split + capabilities -> Scope; deputy SPO -> Leadership; confirmed
objectives -> 6.2).

Frontend: 6-step wizard (12 questions, confirm-or-edit, pre-filled from defaults)
with partial saves per step and complete -> generate-all on finish; "Run setup
wizard" entry point on the ISMS overview. RHF+Zod, design-system only.

189 API jest tests; wizard Vitest tests; all ISMS checks green.

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

* feat(isms): wire "Run setup wizard" entry point into ISMS overview (CS-438)

Adds the evidence:update-gated wizard launch button to IsmsOverview
(missed by the prior wizard commit's add path).

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

* feat(isms): model ISMS document types as framework-editor parts (CS-437)

Add FrameworkEditorIsmsDocumentTemplate (one per doc type, with default clause)
and FrameworkEditorIsmsDocumentRequirementLink (framework-scoped template->clause
mapping, editable in the Framework Editor), plus IsmsDocument.templateId
provenance. Additive migration.

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

* feat(isms): map ISMS document types as framework-editor parts (CS-437, Paul A)

Seed the 6 ISMS doc templates; add a framework-editor CRUD/mapping API
(framework-editor/isms-document-template: list, update, link/unlink a clause
requirement per framework, PlatformAdminGuard); rework ensure-setup to be
template-driven (requirement = framework-scoped link ?? clause-match ?? seeded
defs fallback) with IsmsDocument.templateId provenance; and a Framework Editor
"ISMS Documents" tab/page to author the template->requirement mappings.

238 api jest tests; framework-editor + api typecheck clean.

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

* feat(isms): add document<->control link junctions (CS-437, Paul)

FrameworkEditorControlIsmsDocumentLink (template: control template <-> ISMS doc
template, framework-scoped) and IsmsDocumentControlLink (org: document <-> control),
mirroring how policies/document-types link to controls. Back-relations on
FrameworkEditorFramework/ControlTemplate/IsmsDocumentTemplate, Control, IsmsDocument.
Additive migration.

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

* feat(isms): link documents to individual controls (CS-437, Paul)

Template level (Framework Editor): link/unlink control templates to ISMS doc
templates + a "Controls" mapping cell on the ISMS Documents page.
Org level: POST/DELETE /v1/isms/documents/:id/controls (like policies),
getDocument returns controlLinks, ensure-setup auto-derives a doc's org control
links from its template's control mapping. A "Linked controls" section
(IsmsControlMappings) on all 6 document detail pages, mirroring PolicyControlMappings.

257 api jest tests; api/framework-editor/app typecheck clean.

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

* feat(isms): polish the ISMS area to be design-system-only and auditor-grade (CS-437)

Establish a shared ISMS presentation kit (IsmsStatusBadge, IsmsDocumentCard,
IsmsPageHeader, IsmsSummaryRow, IsmsEmptyState, IsmsRegisterShell, IsmsSourceBadge,
IsmsRowActions) and restyle the whole area with it: overview (summary stats +
Section/Grid of document cards + SOA section + Empty state), the 6 detail pages
(consistent header + Section composition), register tables (DS Table + Empty
states), drift (Alert), control mappings, approval, and the wizard (Progress +
Field steps). One status language, no hand-rolled badges. Strictly design-system
components only. Tests + typecheck green.

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

* fix(isms): resolve 11 QA findings from the live walkthrough (CS-437)

Wizard: Select shows the human label via items prop (#8) + controlled-from-first-
render value, no console warning (#9); Finish validation jumps to the first
invalid step (#6); form re-seeds when the profile loads post-mount (#7).
Shared: source badge reads "Manual" not "Edited" (#1); approval section is
status-aware — approved/declined/pending states instead of a bare submit (#2);
compact empty state (#4); delete confirmation dialog on register rows (#5).
Detail pages: content-first ordering across all 6 (#3).
API: certificate-sentence whitespace normalized (#11); cloud-split/capabilities
feed scope correctly (#10). Plus QA findings doc + openapi regen.

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

* feat(isms): restyle register UIs as read-first cards to match the overview (CS-437)

Replace the dense always-on-textarea tables (Context internal/external issues,
Interested Parties, Requirements, Objectives) with calm read-first cards: source
chip + provenance leading, the primary field prominent, secondary fields as
muted labelled values, count-badged sections, compact empty states, and an
explicit Edit affordance (Save/Cancel) with the delete-confirm preserved. New
shared kit: IsmsRegisterCard, IsmsCardActions, IsmsAddCard. Design-system only;
register component props/behaviour unchanged. 42 ISMS tests pass.

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

* feat(isms): make the issues registers denser (CS-437)

Read mode is now a compact two-line row — issue prominent, effect muted beneath,
source chip + provenance on the right, hover-reveal actions, tighter padding —
instead of an airy card with a labelled effect field. Roomy labelled form kept
for editing. Tighter row spacing. Cuts ~half the vertical space per item for
long lists. DS-only; behaviour unchanged.

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

* feat(isms): refine register source provenance display (CS-437)

Replace the loud uppercase AUTO-DERIVED pill + raw "framework:GDPR" key with a
quiet muted line + small icon and a humanized source ("GDPR framework", "Vendor
register", "Workforce", "Setup wizard"). Manual rows keep a small badge so human
overrides still stand out. Shared component, so all four registers benefit.

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

* feat(isms): source as a tag pill under the description (CS-437)

Drop the misleading auto/ML icon and the disconnected right-floating label;
render the humanized source as a plain pill beneath each entry's description
(GDPR framework / Vendor register / Workforce / Manual). Reads like a tag on the
entry rather than floating metadata.

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

* feat(isms): drop redundant "framework" suffix on source pills (CS-437)

framework:GDPR -> "GDPR" (not "GDPR framework"); the name alone is clearer.

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

* feat(isms): move approval banner to the top of each document (CS-437)

The submit-for-approval / approved banner now renders directly under the header,
above drift/content/linked-controls, so the sign-off state and action are the
first thing you see on a document rather than buried at the bottom. Applied
identically across all six detail pages.

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

* feat(isms): drop redundant source pill from interested-parties cards (CS-437)

The auto-derive provenance pill (WORKFORCE / GDPR / HIPAA) just restated the
party name sitting next to it. Removed it; the category badge (CUSTOMER /
SUPPLIER / REGULATOR) stays since that's real classification, not a restatement.
Made IsmsRegisterCard.header optional so edit mode renders without it (actions
stay right-aligned via ml-auto).

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

* feat(isms): render context-of-organization as an auditor-ready document (CS-439)

Redesign the ISO 27001 Context of the Organization PDF/DOCX export to match the
hand-authored reference document, and capture the data it needs.

- db: add `category` to IsmsContextIssue (4.1 grouping) + migration
- derivation: assign each derived issue an ISO 4.1 category; export the
  external/internal category taxonomies
- export: pull org overview (onboarding Q&A), mission (company description) and
  intended outcomes (wizard answers / defaults) at export time; build rich
  metadata (document code, clause, classification, owner, next review, issue date)
- builder: buildContextSections now emits the full 7-section structure — purpose,
  organization overview, mission + intended outcomes, categorised external and
  internal issue tables (Category / Issue / Effect), linkage, review
- renderers: rebuild PDF (jspdf + jspdf-autotable) and DOCX with a cover block,
  metadata table, numbered sections, real bordered tables, bullet lists and a
  footer with classification + page numbers; split PDF into its own module
- ui: add an ISO 4.1 category picker to the issue add form + edit row, show the
  category as a pill in read mode
- tests: API builder/derivation/service + app picker coverage

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

* feat(documents): gate ISMS tab behind a flag, move SOA to general docs (CS-437)

Wrap the ISO 27001 (ISMS) tab in the `is-isms-enabled` PostHog flag so it can be
privately tested per-org (matching the is-*-enabled convention). The tab now
shows only when ISO 27001 is active AND the flag is on; local development falls
through so it stays visible without PostHog configured.

Move the Statement of Applicability card out of the ISMS tab and back to the top
of the general Documents list (gated on ISO 27001 presence), while we privately
test the ISMS area.

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

* refactor(isms): consolidate register CRUD into 3 generic endpoints (CS-437)

The four ISMS registers (context issues, interested parties, requirements,
objectives) each had a create/update/delete trio — 13 near-identical endpoints
for what is one operation with a different payload. Collapse them into a single
register-routed trio and switch updates to PATCH:

  POST   /v1/isms/documents/:id/registers/:register
  PATCH  /v1/isms/registers/:register/:rowId
  DELETE /v1/isms/registers/:register/:rowId

A register registry maps each key to its service + a zod schema (mirroring the
old DTOs), validated off req.body to dodge the ValidationPipe nested-JSON issue.
Unknown registers 400. The per-register services are unchanged; the frontend
hook already called these generically and now points at the new routes. ISMS
endpoint surface drops 23 → 14.

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

* fix(isms): address cubic review findings on PR #2992 (CS-437)

Auth/tenancy:
- approve()/decline() now require needs_review status + the assigned approver
- requirement create/update validate interestedPartyId belongs to the document
- useIsmsWizard SWR key now includes organizationId (no cross-org cache bleed)

Data integrity:
- narrative edits on an approved doc reset it to draft (require re-approval)
- leadership form preserves commitments beyond the canonical a–h rows
- useIsmsDocument revalidates from server instead of caching partial responses
- register next-position uses max(position)+1 (survives deletes); ensureProfile upsert

Correctness/determinism:
- drift now detects wizard-answer, vendor-category, department and member changes
- objectives 6.2 export includes plan/measurement; approvalLine: declined wins
- deterministic context-answer + requirement-link selection
- dedupe sector regulators across 4.2b/4.2c
- approver gating uses RBAC (evidence:update) not hardcoded role strings
- control picker fetches all pages; register forms keep input on failed save

API contract:
- add @ApiProperty/@ApiPropertyOptional to the @Body-bound ISMS DTOs

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

* fix(isms): derive ensure-setup org from session; skip blank objectives (CS-437)

Resolves the remaining actionable cubic findings on PR #2992:
- ensure-setup no longer accepts organizationId in the request body; it derives
  the org from the authenticated session via @organizationId() (membership-
  validated, consistent with every other ISMS endpoint), closing the cross-tenant
  gap. Both callers drop organizationId from the body.
- objectives derivation filters blank-objective wizard rows so empty entries
  never produce blank 6.2 rows (falls through to the standard objectives).

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

* fix(isms): resolve cubic re-review follow-ups (CS-437)

- drift: isms_scope + leadership_commitment now include wizardAnswers (both
  derive from wizard inputs)
- requirements: normalize empty/whitespace interestedPartyId to null so a blank
  id can't skip validation or persist as an invalid reference
- narrative save: approval-invalidation + version write are now atomic ($transaction)
- generate: preserve an existing (user-edited) narrative on regenerate instead
  of overwriting it
- wizard: a saved empty objectives array is respected instead of being
  overwritten by defaults on reload

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

* fix(isms): seed narrative when empty on regenerate (CS-437)

Refine the previous override-preserving guard: preserve only a non-empty
narrative. An absent or empty ({}) value — e.g. a snapshot-only version — is
still seeded with the derived narrative, so narrative documents can't be left
permanently empty.

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

* refactor(isms): backend red-team remediation — phase 1 (CS-437)

- approval integrity: a shared invalidateApprovalIfNeeded() helper now reverts an
  approved document to draft on ANY content edit — applied (in a transaction) to
  every register create/update/remove and the narrative save (was narrative-only)
- ensure-setup least privilege: derives the caller's write ability and only
  provisions documents when they have evidence:update; read-only callers list
  existing docs with zero writes. Creation is idempotent (createMany skipDuplicates)
- approve() now re-derives inside its transaction so the approved rows and the
  snapshot baseline come from one pass
- DRY: deleted the 8 register DTO classes (duplicated the zod schemas); services
  use z.infer types from the registry. Register endpoints document a body via @ApiBody
- objectives ownerMemberId is validated against the org (like the approver)
- drift: requirements now detects manual Parties-register edits via a stable
  parties fingerprint; deleted dead diffSnapshots/ContextSourceSnapshot
- generateAll collects platform data once and generates in a deterministic order

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

* refactor(isms): frontend red-team remediation — phase 2 (CS-437)

- de-duplicate the six detail clients: new IsmsDocumentShell (render-prop) owns
  useIsmsDocument + drift + lifecycle handlers + header/approval/drift/controls;
  each client drops from ~210 to ~70 lines
- drift is now a single reactive useIsmsDrift hook (was copy-pasted useSWR x6);
  removed the unused getDrift/refresh/mutate surface from useIsmsDocument
- IsmsOverview surfaces load error (Alert + retry) and loading (Spinner) instead
  of a silently zeroed summary
- migrate the inline edit rows (issue/party/requirement/objective) to RHF + Zod,
  sharing one schema per register with the Add forms (project rule: no useState
  for form values); Save gated on dirty+valid, input preserved on failed save
- exportIsmsDocument now goes through apiClient (auth + org header) instead of a
  hand-rolled fetch

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

* test(isms): add coverage flagged by the red-team review — phase 3 (CS-437)

API: docx-renderer (real render -> valid ZIP buffer), data-source
(collectPlatformData aggregation + order-insensitive parties fingerprint),
version-snapshot (update/create branches).
App: IsmsApprovalSection approve/decline/submit interactions; a real-api-client
useIsmsDocument test asserting the register verb+URL+body (incl. the
PATCH /registers/:register/:rowId routes); a mutation-through-to-hook client test.

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

* fix(isms): resolve post-merge cubic findings (CS-437)

- clients: every create/update/delete handler re-throws after toast so a failed
  save keeps the row/form open with the user's input (regression from the shell
  refactor)
- route gating: new server layout gates the whole /documents/isms/* segment on
  the is-isms-enabled flag (+ ISO 27001 active), so wizard/detail pages aren't
  reachable by direct URL during private testing
- wizard: stable row keys in WizardObjectivesEditor (was array index); breadcrumb
  href matches its link
- single-source the objective zod schema (ObjectivesForm imported the shared one)
- framework-editor: useIsmsDocumentRows re-syncs local state when templates prop changes
- @ApiBody on the isms-profile @Req endpoint
- respect an explicitly-emptied wizard objectives / intended-outcomes array
  instead of reseeding defaults (only fall back when never set)

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

* fix(isms): resolve cubic findings — concurrency, constraint, schemas (CS-437)

- atomic read+write: narrative latest-version read and register nextPosition now
  run inside their transactions (no stale-version write / position TOCTOU)
- control-link add/remove invalidate approval (revert approved doc to draft),
  consistent with register/narrative edits
- DB: partial unique index enforces at most one isLatest=true version per document
- shared zod schemas trim before min(1) (whitespace-only no longer passes); the
  last three forms (interested-parties, requirements, add-issue) import the shared
  schema instead of duplicating it
- wizard page derives org from the [orgId] route param, not session active org
- ScopeClient keys the narrative form on the stable version id (no remount on save)
- framework-editor link handlers guard against double-linking

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

* fix(isms): serialize register position allocation; gate control-link approval (CS-437)

- register creates take a per-document Postgres advisory xact lock before
  allocating position, so concurrent creates can't read the same max(position)
  and persist duplicate ordering keys (a moved-read-into-tx alone doesn't
  serialize under READ COMMITTED). Shared lockDocumentForPositions helper.
- control-link add/remove now invalidate approval only when a row actually
  changes (createMany/deleteMany count > 0), so an idempotent re-link / no-op
  unlink no longer downgrades an approved document.

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

* chore(isms): stop tracking local plan/QA docs

These are working notes for the local branch, not product code:
- docs/isms-qa-findings.md
- docs/specs/2026-05-29-isms-foundational-documents-design.md

Kept on disk via a local git exclude; removed from version control so
they don't ship in the PR.

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

* chore(db): squash ISMS migrations into one (CS-437)

The 7 incremental ISMS migrations created during development are replaced
by a single consolidated migration. The SQL is the exact concatenation of
the originals in timestamp order (additive DDL only — CREATE TYPE/TABLE/
INDEX + ALTER TABLE), so it is equivalent to applying them sequentially and
runs safely as one transaction.

Placed after the latest migration so it applies last; ISMS tables only
reference long-existing tables (organization, framework, control, member),
not main's recent department/trust/device migrations, so ordering is safe.

Verified by replaying every migration from empty into a scratch DB
(migrate deploy) and diffing the result against the schema (empty diff =
zero drift).

Note: this will desync already-applied local dev DBs; run
`bunx prisma migrate reset` in packages/db to rebuild locally. Fresh
deploys (and main after merge) apply the single migration cleanly.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
app (staging) Ready Ready Preview, Comment Jun 15, 2026 4:20pm
comp-framework-editor (staging) Ready Ready Preview, Comment Jun 15, 2026 4:20pm
portal (staging) Ready Ready Preview, Comment Jun 15, 2026 4:20pm

Request Review

Concurrent refreshes of the same connection (the integration-checks
scheduler fans out one job per task, plus the unconditional Workspace
sync) made Google reject the refresh token (invalid_grant), surfacing as
"OAuth token expired. Please reconnect." across GCP and Google Workspace
connections platform-wide (prod: 52 GCP / 158 Workspace orgs in error;
AWS, which has no OAuth refresh, unaffected).

Add a per-connection distributed single-flight via a short DB lease
(refreshLeaseUntil + compare-and-swap acquire/release): only one worker
refreshes at a time; others wait and reuse its fresh token. A lease is
used instead of a Postgres advisory lock so no DB connection is held
during the external token HTTP call (avoids pool exhaustion at the
scheduler's thundering herd); it auto-expires so a crashed worker cannot
wedge refreshes.

Force-refresh semantics are preserved: a solitary caller (401 retry or
forceRefresh) always refreshes; coalescing only reuses a token produced
after the call began. Release is in a finally; lease errors degrade to a
direct refresh.

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

@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.

3 issues found across 205 files

Confidence score: 3/5

  • In apps/app/src/app/(app)/[orgId]/documents/isms/hooks/useIso27001FrameworkId.ts, returning null before /v1/frameworks finishes causes loading/error states to look like “framework not active,” which can hide ISMS UI and show the wrong empty state to users on slow or failed requests — separate loading/error from inactive-state return values before merging.
  • In apps/framework-editor/app/(pages)/frameworks/[frameworkId]/isms-documents/page.tsx, the route can be opened directly without enforcing ISMS-enabled/active-ISO rollout checks, so users may access ISMS documents when rollout should block it — add server/client guard validation on route entry before merging.
  • In apps/framework-editor/app/(pages)/frameworks/[frameworkId]/isms-documents/page.tsx, authorization relies on a hardcoded admin-role helper instead of RBAC permission checks, which can deny valid custom-role users and drift from the API access model — switch to RBAC permission-based gating to align behavior before merging.
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]/documents/isms/hooks/useIso27001FrameworkId.ts">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/documents/isms/hooks/useIso27001FrameworkId.ts:34">
P2: Hook conflates loading/error with "framework not active" by returning null before `/v1/frameworks` resolves. This can hide ISMS UI and show incorrect empty-state messaging during slow/failed requests.</violation>
</file>

<file name="apps/framework-editor/app/(pages)/frameworks/[frameworkId]/isms-documents/page.tsx">

<violation number="1" location="apps/framework-editor/app/(pages)/frameworks/[frameworkId]/isms-documents/page.tsx:12">
P2: Route authorization uses a hardcoded admin-role helper instead of RBAC permission checks. This blocks custom-role permission grants and violates the API RBAC access model.</violation>

<violation number="2" location="apps/framework-editor/app/(pages)/frameworks/[frameworkId]/isms-documents/page.tsx:17">
P2: ISMS documents page is reachable without validating ISMS-enabled/active-ISO rollout conditions. Users can access this route directly even when ISMS rollout should be disabled.</violation>
</file>

Partial review: This PR has more than 100 files, so cubic reviewed the highest-priority files first.
For a deeper review of large PRs, comment @cubic-dev-ai ultrareview. Learn more.

Fix all with cubic | Re-trigger cubic

);

return useMemo(() => {
const frameworks = data?.data ?? [];

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

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.

P2: Hook conflates loading/error with "framework not active" by returning null before /v1/frameworks resolves. This can hide ISMS UI and show incorrect empty-state messaging during slow/failed requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/documents/isms/hooks/useIso27001FrameworkId.ts, line 34:

<comment>Hook conflates loading/error with "framework not active" by returning null before `/v1/frameworks` resolves. This can hide ISMS UI and show incorrect empty-state messaging during slow/failed requests.</comment>

<file context>
@@ -0,0 +1,41 @@
+  );
+
+  return useMemo(() => {
+    const frameworks = data?.data ?? [];
+    const match = frameworks.find(
+      (instance) =>
</file context>
Fix with cubic


const { frameworkId } = await params;

const templates = await serverApi<IsmsDocumentTemplate[]>(

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

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.

P2: ISMS documents page is reachable without validating ISMS-enabled/active-ISO rollout conditions. Users can access this route directly even when ISMS rollout should be disabled.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/framework-editor/app/(pages)/frameworks/[frameworkId]/isms-documents/page.tsx, line 17:

<comment>ISMS documents page is reachable without validating ISMS-enabled/active-ISO rollout conditions. Users can access this route directly even when ISMS rollout should be disabled.</comment>

<file context>
@@ -0,0 +1,22 @@
+
+  const { frameworkId } = await params;
+
+  const templates = await serverApi<IsmsDocumentTemplate[]>(
+    `/isms-document-template?frameworkId=${frameworkId}`,
+  );
</file context>
Fix with cubic

}: {
params: Promise<{ frameworkId: string }>;
}) {
const isAllowed = await isAuthorized();

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

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.

P2: Route authorization uses a hardcoded admin-role helper instead of RBAC permission checks. This blocks custom-role permission grants and violates the API RBAC access model.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/framework-editor/app/(pages)/frameworks/[frameworkId]/isms-documents/page.tsx, line 12:

<comment>Route authorization uses a hardcoded admin-role helper instead of RBAC permission checks. This blocks custom-role permission grants and violates the API RBAC access model.</comment>

<file context>
@@ -0,0 +1,22 @@
+}: {
+  params: Promise<{ frameworkId: string }>;
+}) {
+  const isAllowed = await isAuthorized();
+  if (!isAllowed) redirect('/auth');
+
</file context>
Fix with cubic

tofikwest and others added 3 commits June 15, 2026 10:53
…l loop

Harden the deadline: the previous inline `Date.now() >= deadline` check only
ran after a poll request resolved, so a single status request that itself hung
(API stalls mid-request) would still spin forever. Replace it with an
independent backstop timer plus a single-resolution `settled` guard, so the run
always resolves exactly once — whether stuck in the queue or stuck on a hung
request. Adds a regression test for the never-resolving-request case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(automation-builder): stop test dialog hanging on "Running your automation"
tofikwest and others added 3 commits June 15, 2026 11:30
Address review (cubic P2): releaseRefreshLease cleared the lease without
verifying ownership, so a holder whose work outlived the TTL could wipe a
lease another worker had since acquired. Add a per-refresh fencing token
(randomUUID) recorded on acquire and required in the release WHERE clause.

Also harden the lease so that window can't open:
- 20s AbortController timeout on the token request (worst case ~42s, well
  under the 180s TTL); timeouts are transient (retried, not marked expired).
- On a lease-coordination failure (DB error), fail safe — return the stored
  token instead of an unserialized refresh that would fan correlated callers
  into the provider at once.

Tests: +3 (timeout-as-transient, lease-unavailable fail-safe, post-acquire
coalesce); 6/6 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(integration-platform): serialize OAuth token refresh per connection
@claudfuen

Copy link
Copy Markdown
Contributor

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

3 participants