Skip to content

[comp] Production Deploy#3308

Merged
tofikwest merged 32 commits into
releasefrom
main
Jun 30, 2026
Merged

[comp] Production Deploy#3308
tofikwest merged 32 commits into
releasefrom
main

Conversation

@github-actions

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

Show integration-imported devices correctly across People, hold non‑success dynamic checks as pending until the self‑heal agent reveals genuine failures, auto‑grant one free pentest credit to new orgs so they can run a first test without a card, and let orgs turn off employee auto‑sync without disconnecting any integration.

  • New Features

    • Display imported devices with provider labels and a “Not tracked” compliance badge; agent-only online status.
    • Devices table: added Source column and filter (keyed by a stable source id); details panel shows provider and “Last synced”.
    • API people/agent-devices now returns source (device_agent/fleet/integration) and integrationProvider; drops imported rows that duplicate an agent serial.
    • Intune/JumpCloud device sync: definitions live in the DB; removed in-repo DSL, tests, and apply script.
    • Self-heal: added POST /integrations/connections/:connectionId/reveal to persist a genuine fail (never held) and sync the task to failed when applicable.
    • Dynamic checks: non-success runs are stored as inconclusive (pending) with identity-only failing findings; the self-heal agent is the sole classifier.
    • Pentest: POST /v1/pentest-credits/grant-initial idempotently grants one free credit to new (onboarding) orgs; org-create flows call it. Prevents surprise trial charges.
    • People: added “Don’t auto‑sync” in the employee sync source dropdown to stop scheduled imports without disconnecting the integration; confirms with a toast.
  • Bug Fixes

    • Imported devices no longer affect compliance rollups, charts, CSV totals, or the people score.
    • Prefer agent > fleet > integration when selecting a member device; imports no longer suppress Fleet.
    • Device APIs: enforce RBAC (member:read) on people/agent-devices and people/fleet-hosts routes.
    • CSV: mark imported devices as not_tracked with n/a checks; added Source column; use shared tracking logic so Fleet exports real checks; hardened against formula injection in provider names.
    • A11y and clarity: keyboard-focusable device names, labeled actions menu, live-status dot only for agent devices; suppress exceptions column for untracked checks.
    • Self-heal: error-only dynamic runs keep tasks pending; rerun/reveal validate the task belongs to the same org; reveal flips only active workflow statuses to failed.
    • ISMS: back link returns to ISO 27001 tab; approver/owner and party/source show names (not IDs); status labels render human‑readable in UI and exports; hide linked-party ID input; allow null interestedPartyId in create/update.
    • People: lock the sync-source dropdown while disabling auto‑sync to prevent race conditions.

Written for commit 3a6d4aa. Summary will update on new commits.

Review in cubic

github-actions Bot and others added 16 commits June 29, 2026 21:04
…ure is pending

New model: comp is pure plumbing. A dynamic check that doesn't succeed — a real
finding, a customer/transport error, or a thrown execution error — is held as
'inconclusive' ("pending", hidden from the customer) and handed to the self-heal
agent, which is the ONLY thing that decides our-bug (fix) vs real fail (show).

- decideRunStatus: dynamic + non-success → 'inconclusive'; no error-code logic.
- splitFailuresByDisposition: returns all-held (nothing decided on the comp side).
- The error-code classifier is now unreferenced (to be deleted in cleanup).

WIP: reveal-real-fail endpoint + the agent decision rewrite + spec updates still
to come. Not deployed.

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

The agent calls /reveal when it verdicts a held check as a GENUINE fail (the
customer's creds/config are wrong, or a real compliance finding). Unlike /rerun
(which applies the dynamic hold rule and may re-hold as 'inconclusive'), /reveal
persists the TRUE status — success if it now passes, 'failed' with the real
findings shown (failedCount > 0) otherwise — so the customer sees the red instead
of a silent "pending". Mirrors rerunAndPersistCheck; never holds, never disables.

tsc clean. Not deployed.

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

Per the design: comp must do ZERO judging of customer-vs-us. Removed every bit of
pre-classification so the only place that decides is the self-heal agent.

- Deleted check-failure-classifier.ts (the error-code customer/our_side/finding
  judging) + its spec.
- Removed failureSignalsFromEvidence (HTTP-status/error-text extraction),
  splitFailuresByDisposition, ClassifiableFailure, FailureDisposition from
  task-check-evaluation.ts. decideRunStatus is now just:
  success → success; dynamic non-success → 'inconclusive' (pending); else failed.
- Run paths (scheduled + manual) and rerun/reveal now record findings by identity
  only ({connectionId, checkId, resourceId}); a dynamic failure is always held
  pending for the agent. No signals, no patterns, no guessing.
- Rewrote the run-status spec for the new rule; deleted the split/signals specs.

my changed files compile; task-check-evaluation tests pass (19). Pre-existing
worktree spec failures (sync-gws / variables / credential-vault) are unrelated.
Not deployed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014PAsijjUQ1bMJuw8NuC1oR
…ne/JumpCloud device sync

Devices imported from dynamic integrations now render correctly in the People
tab instead of being mislabeled as failing agent devices.

- agent-devices route + types: pass the real device source (agent/fleet/
  integration) and the importing provider; drop integration rows that duplicate
  an agent serial
- People > Devices table: Source column, source filter, "Not tracked" compliance
  for imported devices (no false-red checks), agent-only online status
- Employee detail page, People compliance roll-up, compliance chart, CSV export,
  and people score: treat imported devices as inventory-only, not
  compliance-tracked, so they no longer skew compliance or suppress Fleet
- tools/device-sync-definitions: version-controlled Intune + JumpCloud
  deviceSyncDefinition DSL, an offline mock test harness, and an apply script
  (the DSL otherwise lives only in the DB)

Tests: devices suite, people-score helper, and 13 definition mock tests pass.

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

- lib/device-source.ts: shared sourceLabel + isComplianceTracked (used by the
  table and CSV so they can't drift); isComplianceTracked now treats Fleet as
  tracked — only integration imports are "Not tracked"
- DeviceDetails: show live online/offline status only for agent devices
- DeviceAgentDevicesList: keep the source filter visible while a non-default
  filter is active so users can't get stuck on an empty table
- DeviceListCells: aria-label on the device-actions menu trigger
- intune.mjs: fail loudly if pagination exceeds the page cap instead of
  importing a partial fleet as success
- jumpcloud.mjs: stable sort on the systems + users pagination to avoid
  missing/duplicating records when data shifts mid-sync
- test.mjs: make the harness await async tests so throw-cases are reliably
  caught and counted
- devices-csv.test.ts: regression test proving an integration provider name
  can't reopen CSV formula injection (existing escaping already neutralizes it)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015iDU78gxNH9Wp9sex1BDLS
…eld error-runs keep task pending

- P1: reveal/rerun now assert the taskId belongs to the SAME org as the
  connection before persisting a run (shared assertTaskBelongsToOrg helper), so a
  wrong/forged internal call can't contaminate another tenant's task history.
- P2: count HELD runs (not held findings) toward heldCount — an error-only
  dynamic run (inconclusive, no findings) now keeps the task pending instead of
  letting it slip to 'done' while unresolved. Both run paths (scheduled + manual).

my files: tsc clean; task-check-evaluation tests pass (19).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014PAsijjUQ1bMJuw8NuC1oR
New orgs are granted a single pentest credit at creation so the owner can
run their first penetration test without entering a card. Once that credit
is spent, running another pentest is an explicit, opt-in purchase — removing
the surprise auto-charges from the Stripe free-trial flow that were driving
refund requests.

- PentestCreditsService.grantInitialTrial(): idempotent per-org grant into
  the billing-credits wallet (billing_credit_balances) — the store the
  run-creation fallback reads — reusing the existing initialTrialAmount=1.
- POST /v1/pentest-credits/grant-initial (owner, pentest:create): thin,
  idempotent endpoint the org-create flow calls.
- create-organization{,-minimal} call the grant after org activation,
  mirroring the trust-portal bootstrap (non-fatal, covers both branches).
- No frontend change needed: getPentestAllowance already sums wallet
  credits, so a credit>0 org sees "New Scan" and the run consumes the credit
  via the existing tryConsumeCreditFallback path.

Re-introduces (now against billing_credit_balances) the free credit that was
removed in the Stripe-trial migration (f494299 -> f7e5e9f), deliberately
reversing that decision because the trial is what caused the refunds.

Tests: grantInitialTrial (grant args + stable idempotency key + no-op),
controller endpoint delegation, and the app helper (POST + non-fatal).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017JEEY9LtgmJRRFhLiSDogW
A revealed genuine fail persisted a 'failed' run but left the task green/pending —
a false success signal. Now a reveal that resolves to 'failed' sets the linked
task to 'failed' too (mirrors the run paths). A reveal that PASSES does not force
'done' (the task spans other checks; recomputed on the next scheduled run).

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

- jumpcloud.mjs: only swallow a per-user 404 on the bindings call; rethrow
  auth/permission/5xx so a systemic failure can't report a "successful" sync
  with every device silently missing its owner
- DeviceListCells: render the live-status dot only for agent devices (spacer
  otherwise, so imports aren't shown as offline agents); make the device name a
  focusable button so details are reachable by keyboard, not just mouse
- DeviceDetails: suppress the Exception column for untracked checks
- test.mjs: JumpCloud mock now honors skip/limit (real pagination contract);
  added multi-page pagination + binding 401-rethrow / 404-skip tests

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

Extract the shared mock harness into harness.mjs and the provider tests into
intune.test.mjs / jumpcloud.test.mjs; test.mjs is now a thin runner. It imports
the two suites sequentially via dynamic import because they use top-level await
and stub the global fetch — static imports would interleave and clobber it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015iDU78gxNH9Wp9sex1BDLS
Address review (cubic P1): POST /v1/pentest-credits/grant-initial was guarded
only by pentest:create, so any owner/admin/API-key holder on an EXISTING org
could mint themselves a one-time free pentest credit (they lack the
initial-pentest-trial:<orgId> idempotency key since existing orgs weren't
backfilled).

grantInitialTrial now reads organization.onboardingCompleted and only grants
while the org is still in the creation/onboarding phase. onboardingCompleted
defaults to false and only ever flips false->true (set by complete-onboarding),
so established orgs are permanently ineligible and a pentest:create holder can
no longer mint a credit for an existing org. Combined with the existing stable
per-org idempotency key, issuance stays one-time per new org.

Tests: added "already-onboarded org -> no grant" and "missing org -> no grant".

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

Match the Intune retry coverage — assert a transient failure on the first
systems fetch is retried and then succeeds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015iDU78gxNH9Wp9sex1BDLS
Previously the reveal flipped any non-failed task to failed, which would resurrect
a human-set not_relevant (dismissed) or in_review task. Restrict it to active
workflow statuses (todo / in_progress / done) so a reveal never overrides a
dismissed or under-review task.

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

feat(devices): show integration-imported devices in People tab + Intune/JumpCloud device sync
…status formatting

## Problem

Multiple UI and data handling issues across ISMS documents:
- Back button returns to wrong tab (Company Forms instead of ISO 27001)
- Approver and owner fields display raw member IDs instead of names
- Party tags show internal identifiers instead of party names
- Status enums render unformatted in UI and exports (e.g. `on_track` instead of "On track")
- Organization and mission sections missing from editable view
- Editing interested parties triggers validation error on interestedPartyId
- Linked party ID field unnecessarily exposed to user

## Root cause

- IsmsDocumentShell back href missing tab query parameter
- RequirementsClient.toPayload sends interestedPartyId as null for existing rows; register validation rejects null
- Member/party name resolution missing in display layer (ApprovalDialog, ObjectiveEditor, IsmsSourceBadge)
- Enum status values not humanized in objectives template and PDF rendering
- ContextOfOrganizationClient omits org profile and mission section editors
- Interested parties edit drawer exposes internal linked party ID field

## Fix

- Preserve tab parameter in back navigation href
- Initialize interestedPartyId from existing row data before mutation
- Add member and party name lookups to approver, owner, and badge displays
- Map status enum to human labels in UI and export template
- Add organization and mission editors to context of organization view
- Hide linked party ID from edit interface (system-managed field)

## Explicitly NOT touched

- Register data structure or API contract
- Party and member lookup service implementations.
- Document approval workflow logic.

## Verification

✅ Back button from any ISMS document returns to ISO 27001 tab
✅ Approver selection dialog and approval display show member name
✅ Owner field in objectives shows member name
✅ Party tags resolve to party names
✅ Status field shows human-readable labels ("On track", "Not started", etc.)
✅ Status exports with human labels in PDF and DOCX
✅ Organization and mission sections visible and editable in main view
✅ Editing interested parties row preserves interestedPartyId and passes validation
✅ Linked party ID hidden from edit drawer
@vercel

vercel Bot commented Jun 30, 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 30, 2026 8:56pm
comp-framework-editor (staging) Ready Ready Preview, Comment Jun 30, 2026 8:56pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal (staging) Skipped Skipped Jun 30, 2026 8:56pm

Request Review

tofikwest and others added 2 commits June 30, 2026 15:52
…c DB-only

The Intune + JumpCloud deviceSyncDefinitions are authored and stored in the
DynamicIntegration DB rows (via the internal dynamic-integrations endpoint),
exactly like the employee syncDefinition and the checks. The in-repo source
copies + apply script + tests duplicated that DB state and broke convention
(a drift risk), so remove them. The live staging/prod definitions are unchanged
and remain editable through the builder/internal endpoint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015iDU78gxNH9Wp9sex1BDLS
chore(devices): remove tools/device-sync-definitions — keep device sync DB-only

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

5 issues found and verified against the latest diff

Confidence score: 2/5

  • apps/app/src/app/api/people/agent-devices/route.ts is missing a server-side RBAC check, so the endpoint could be called directly by any active-org session and expose device/integration-provider data despite page-level hiding. Add an API-level authorization guard before querying and returning data prior to merge.
  • apps/app/src/app/(app)/[orgId]/people/devices/lib/devices-csv.ts marks fleet devices as untracked, which can produce incorrect compliance status/check columns in exports and mislead downstream reporting. Switch to the shared isComplianceTracked(d) helper and validate CSV output against the UI before merging.
  • apps/app/src/app/(app)/[orgId]/people/devices/components/DeviceAgentDevicesList.tsx uses display labels as filter identifiers, so different sources with the same label can collapse into one option and hide valid filter choices. Use stable source/provider keys for option values and keep labels display-only.
  • apps/app/src/app/(app)/[orgId]/people/devices/components/DeviceListCells.tsx and apps/app/src/app/(app)/[orgId]/people/devices/components/DeviceDetails.tsx duplicate presentation/tooltip logic, increasing drift risk in stale thresholds, check labels, and “Not tracked” copy between views. Extract shared constants/helpers to keep behavior consistent and reduce regression risk.

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

Re-trigger cubic

Comment thread apps/app/src/app/api/people/agent-devices/route.ts
Comment thread apps/app/src/app/(app)/[orgId]/people/devices/lib/devices-csv.ts Outdated
@vercel vercel Bot temporarily deployed to staging – portal June 30, 2026 20:16 Inactive
…issues

fix(isms): resolve back nav, approver/owner display, party tags, and status formatting
…3308)

- agent-devices + fleet-hosts routes: enforce RBAC with requireApiPermission
  (member:read, matching the People page) instead of a session-only check, so
  the device/integration data can't be read by an active-org session that lacks
  people access (P1)
- CSV export: use the shared isComplianceTracked() so Fleet devices aren't
  wrongly marked "not_tracked"/"n/a" — keeps CSV consistent with the UI (P2)
- DRY: move shared device presentation helpers (PLATFORM_LABELS, CHECK_FIELDS,
  formatTimeAgo, isDeviceOnline, stale + not-tracked copy) into device-source,
  and a shared NotTrackedBadge — used by both the list and details views (P3)
- Source filter: key options by a stable source id (sourceKey) instead of the
  display label, so two providers sharing a name don't collapse into one (P3)

Tests: devices suite + agent-devices route (81) green; typecheck clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015iDU78gxNH9Wp9sex1BDLS
fix(devices): address cubic review on device-import display (#3308)
@vercel vercel Bot temporarily deployed to staging – portal June 30, 2026 20:38 Inactive
CS-576 (re-open): customers could select an employee sync source in the
People tab but had no way to turn it back off. The only "off" levers were
disconnecting the integration (which also kills its compliance checks and is
labelled "remove all data") or an email-filter workaround — neither is a real
disable. The org-level `employeeSyncProvider` already supports null end-to-end
(`POST /employee-sync-provider {provider:null}` + `setSyncProvider(null)`), and
the daily 7 AM cron skips orgs whose provider is null — it was just never wired
to a UI control.

This adds a "Don't auto-sync" item to the sync-source dropdown that clears the
provider (stopping the scheduled sync) without disconnecting the integration or
touching already-imported people. Applies to every sync provider, not just
Entra. Selecting it routes to disable (never triggers a sync), and the hook now
confirms with a toast. Gated by `canManageMembers`; backend still enforces
`integration:update`.

Tests: cover setSyncProvider(null) disabling + provider selection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6zcF9bvPP1UwZgzK21cw1
tofikwest and others added 2 commits June 30, 2026 16:48
Addresses cubic P2: the "Don't auto-sync" path called setSyncProvider(null)
without any busy flag, so the dropdown stayed interactive during the request
and a follow-up provider selection could race it to an inconsistent state.

Add an isDisablingSync flag (mirroring isSyncing): set it around the disable
request, include it in the Select's disabled guard, and bail out of the handler
if a disable is already in flight.

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

feat(people): add "Don't auto-sync" option to employee sync source
@vercel vercel Bot temporarily deployed to staging – portal June 30, 2026 20:52 Inactive
@tofikwest tofikwest merged commit f0c51ae into release Jun 30, 2026
12 of 13 checks passed
claudfuen pushed a commit that referenced this pull request Jun 30, 2026
# [3.94.0](v3.93.1...v3.94.0) (2026-06-30)

### Bug Fixes

* **devices:** address cubic review on device-import display + sync definitions ([dda64db](dda64db))
* **devices:** address cubic review on the device-import display PR ([#3308](#3308)) ([9056d1b](9056d1b))
* **devices:** second cubic pass — JumpCloud error handling, a11y, untracked rendering ([ea93f29](ea93f29))
* **isms:** resolve back nav, approver/owner display, party tags, and status formatting ([c9a6697](c9a6697))
* **pentest:** gate initial free credit to new (un-onboarded) orgs only ([199e890](199e890))
* **people:** lock sync-source dropdown while disabling auto-sync ([541a72f](541a72f))
* **self-heal:** address cubic — validate task org on reveal/rerun + held error-runs keep task pending ([3cecc79](3cecc79))
* **self-heal:** reveal also syncs the task status (cubic P1) ([9bbf38b](9bbf38b))
* **self-heal:** reveal task-sync only flips ACTIVE statuses (cubic P2) ([5e3c6e3](5e3c6e3))

### Features

* **devices:** show integration-imported devices in People tab + Intune/JumpCloud device sync ([923a9e9](923a9e9))
* **pentest:** auto-grant one free pentest credit to new organizations ([a5f815f](a5f815f))
* **people:** add "Don't auto-sync" option to employee sync source ([ae88ec6](ae88ec6))
* **self-heal:** add /reveal endpoint — persist the real fail (customer-side / finding) ([e2f1fc0](e2f1fc0))
@claudfuen

Copy link
Copy Markdown
Contributor

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