Skip to content

[comp] Production Deploy#2692

Merged
Marfuen merged 2 commits intoreleasefrom
main
Apr 29, 2026
Merged

[comp] Production Deploy#2692
Marfuen merged 2 commits intoreleasefrom
main

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented Apr 29, 2026

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 read-only views that link policies, controls, and tasks so users can see policy evidence and which policies a task implements. Also redesigns the Control Mappings UI with framework badges and a safer unlink flow. Addresses Linear SALE-48.

  • New Features

    • New API: GET /v1/policies/:id/evidence-tasks and GET /v1/tasks/:taskId/policies.
    • /v1/policies/:id/controls now includes framework names derived from requirementsMapped.
    • New hooks and UI: usePolicyEvidenceTasks, useTaskPolicies, PolicyEvidenceTasks, and TaskPolicies.
    • Mappings rendered as @trycompai/design-system tables with per-cell links (open in new tab).
    • Control Mappings uses Popover + Command to add, and a confirm dialog to unlink.
  • Bug Fixes

    • Both join endpoints require policy:read and task:read.
    • Guarded task-based endpoint with task access checks; all joins scoped by organizationId.
    • Unified visibility: drafts included; only archived rows are excluded.
    • Improved empty/error states and tab fallbacks to avoid blank pages.

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

github-actions Bot and others added 2 commits April 28, 2026 20:38
* docs(sale-48): add design spec for policy evidence tasks

Transitive Policy<->Task linking via Control. Read-only surfaces on
Policy Overview and Task Overview, no schema changes.

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

* docs(sale-48): add implementation plan for policy evidence tasks

TDD task-by-task plan covering the 2 API endpoints, 2 hooks, and 2
components, with integration and verification steps.

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

* feat(api): add GET /v1/policies/:id/evidence-tasks

* feat(api): add GET /v1/tasks/:taskId/policies

* feat(app): add usePolicyEvidenceTasks hook

* feat(app): add useTaskPolicies hook

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

* feat(app): add PolicyEvidenceTasks component

* feat(app): add TaskPolicies component

* feat(app): render PolicyEvidenceTasks on policy overview

* feat(app): render TaskPolicies on task overview

* fix(app): show visible-policy count and pluralize collapse labels

* fix(sale-48): address cubic review feedback

- Guard getTaskPolicies with hasTaskAccess to prevent cross-assignment leakage
- Scope nested control/task/policy joins by organizationId for defense in depth
- Render error state in PolicyEvidenceTasks/TaskPolicies on fetch failure
- Dedupe visible policy count by policy ID
- Drop unreachable ternary in PolicyEvidenceTasks

* feat(app): clean up Evidence Tasks UI with design-system primitives

Rewrites PolicyEvidenceTasks and TaskPolicies to use Section, Item/ItemGroup,
Heading, Empty, and Badge primitives from @trycompai/design-system. Each
control sub-group now has a proper bordered header with count badge, and rows
render as outlined Item links instead of bare anchors. The page-level empty
state uses Empty with a Document icon instead of plain Text.

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

* fix(api): require both policy:read and task:read on join endpoints

Both /policies/:id/evidence-tasks and /tasks/:taskId/policies cross
resource boundaries (return data from the *other* side). Single-resource
permission gating let an employee with policy:read but no task:read read
task fields through the policy join endpoint, and vice versa. Switch to
RequirePermissions to require both.

* style(app): tighten Evidence Tasks visual hierarchy

Wrap PolicyEvidenceTasks and TaskPolicies blocks in a Card surface so
the content no longer floats. Replace H5 control headings with small
uppercase muted captions and a count separator. Hide empty control
groups from the main render and aggregate them into a single muted
footer line ("Controls without tasks: ..."). Drop the oversized Empty
icon for the page-level empty state in favor of a single muted line.
Tighten group gap from 6 to 4.

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

* fix(app): show draft policies on task overview

The published-only filter hid Draft policies from the task page even
though the user can see drafts on the policy list. Now both endpoints
treat policy status uniformly: only archived rows are excluded.

* feat(app): move task policies to dedicated Mappings tab

* feat(app): redesign control mappings to separate view from unmap

Clicking a mapped control now navigates to the control detail page.
Removing a mapping requires clicking the × and confirming via dialog.
Replaces SelectPills (@trycompai/ui legacy) with native design-system
primitives: Popover + Command palette for adding, chips with explicit
remove buttons, AlertDialog for confirmation.

* style(app): polish Mappings tab — titles above cards, rename Controls, drop empty-group footer

* fix(app): repair Add controls trigger + lighten Evidence Tasks list

Apply Button class names to PopoverTrigger directly instead of base-ui
render prop, which wasn't propagating styles. Drop card chrome from
task/policy rows in favor of a flat list with hover, muted status text,
and inline frequency separator.

* style(app): wrap each Mappings group in a bordered list container

Hairline dividers were too faint at /40 opacity. Wrap each control
group's row list in a single rounded border with full-opacity dividers
between rows. No per-row card chrome.

* style(app): convert mapped controls to list to match Evidence Tasks

Same bordered container + hairline dividers + click-to-navigate row
pattern as the Evidence Tasks list. × button appears on hover only.
The Mappings tab now reads as one consistent surface.

* feat(app): unify policy Mappings into one nested list

Collapse the redundant Controls + Evidence Tasks sections into a single
list grouped by Control. Each control row expands to show its tasks
indented underneath, with colored status pills. Eliminates the two
duplicate renderings of the same control names.

* feat(app): convert Mappings to flat DS tables

Three flat DS Tables matching the rest of the app's chrome (policies
index, risks, members): Controls and Evidence Tasks on the policy
Mappings tab; Policies on the task Mappings tab. Drops the unified
nested list in favor of clear column-aligned data.

* style(app): tighten Controls table header — drop Tasks column, inline Link control action

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

* style(app): unlink action — broken-chain icon, destructive color, "Unlink" wording

Replace Close icon with Carbon Unlink, tint destructive red, and update
the AlertDialog title/copy/confirm button to match the new verb.

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

* style(app): tighten Mappings section descriptions

* fix(app): wrap Mappings table title cell in real Link for reliable navigation

* style(app): breathing room between search input and control list in Link control popover

* fix(app): use variant prop on AlertDialogAction (className not accepted)

* style(app): move Mappings tab after Automations on evidence page

* fix(app): gate evidence Mappings tab behind policy:read

The /v1/tasks/:taskId/policies endpoint requires both task:read AND
policy:read. Hiding the Mappings tab from users without policy:read
prevents them from opening a tab that fails to load.

Also drop a stale 'status: published' filter from the implementation
plan doc to match what was actually shipped.

* feat(api): include framework names per control in /v1/policies/:id/controls

Extend the controls endpoint to derive each control's enabled-framework set
from RequirementMap -> FrameworkInstance, so the Policy Mappings tab can
render framework badges without a follow-up round-trip.

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

* feat(app): show framework badges in Controls table

Add a Frameworks column to the Policy Mappings tab Controls table so users
can see at a glance which org-enabled frameworks each control supports.

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

* feat(app): make each entity column on Mappings tables independently clickable

Each name cell is a Link with target=_blank and a Launch icon that
fades in on hover. Removes the row-level onClick (now redundant) so
users can click Task to go to task, Control to go to control, etc.,
each opening in a new tab.

* fix(app): guard frameworks rendering against missing field

* style(app): always show Launch icon and make full table cell clickable

Switch each entity-name Link from inline-flex to flex justify-between so
the entire cell becomes the click target. Drop the hover-fade on the
Launch icon — keep it always visible to clearly signal the cell is a
link, with a subtle color shift on hover.

* fix(app): fall back to overview if requested tab is unavailable

Bookmarked URLs like ?tab=mappings would render a blank page for users
without policy:read (the tab's trigger and content are hidden behind a
permission check). Validate the requested tab against availability and
fall back to overview otherwise. Same logic applied to ?tab=automations
for manual tasks.

* fix(app): don't gate Mappings tab content on async permission data

Custom-role users with policy:read got stuck on Overview when reopening
a bookmarked ?tab=mappings link, because the fallback fired before
permissions resolved. Drop the URL fallback and the TabsContent gate.
Trigger remains hidden until canReadPolicy resolves, but bookmarked
URLs render the tab content directly. Users without permission see
TaskPolicies' own error state if they hit the URL by accident.

---------

Co-authored-by: Mariano <marfuen98@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

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

Project Deployment Actions Updated (UTC)
app (staging) Ready Ready Preview, Comment Apr 29, 2026 2:09pm
comp-framework-editor (staging) Ready Ready Preview, Comment Apr 29, 2026 2:09pm
portal (staging) Ready Ready Preview, Comment Apr 29, 2026 2:09pm

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.

3 issues found across 18 files

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="docs/superpowers/plans/2026-04-24-policy-evidence-tasks.md">

<violation number="1" location="docs/superpowers/plans/2026-04-24-policy-evidence-tasks.md:126">
P2: The `getTaskPolicies` query does not filter out draft policies, despite the plan requiring draft/archived filtering.</violation>

<violation number="2" location="docs/superpowers/plans/2026-04-24-policy-evidence-tasks.md:1140">
P3: The displayed policy count uses unfiltered API `count` instead of the filtered visible policy total, which can produce inconsistent UI.</violation>
</file>

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

<violation number="1" location="apps/api/src/tasks/tasks.controller.spec.ts:610">
P3: The test claims to validate `NotFoundException` but only asserts the error message, so it can pass on the wrong exception type/status.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

select: expect.objectContaining({
id: true,
controls: expect.objectContaining({
where: { archivedAt: null },
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: The getTaskPolicies query does not filter out draft policies, despite the plan requiring draft/archived filtering.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/superpowers/plans/2026-04-24-policy-evidence-tasks.md, line 126:

<comment>The `getTaskPolicies` query does not filter out draft policies, despite the plan requiring draft/archived filtering.</comment>

<file context>
@@ -0,0 +1,1368 @@
+      select: expect.objectContaining({
+        id: true,
+        controls: expect.objectContaining({
+          where: { archivedAt: null },
+        }),
+      }),
</file context>

return (
<Section
title="Policies"
description={`${count} polic${count === 1 ? 'y' : 'ies'} whose controls this task demonstrates.`}
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.

P3: The displayed policy count uses unfiltered API count instead of the filtered visible policy total, which can produce inconsistent UI.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/superpowers/plans/2026-04-24-policy-evidence-tasks.md, line 1140:

<comment>The displayed policy count uses unfiltered API `count` instead of the filtered visible policy total, which can produce inconsistent UI.</comment>

<file context>
@@ -0,0 +1,1368 @@
+  return (
+    <Section
+      title="Policies"
+      description={`${count} polic${count === 1 ? 'y' : 'ies'} whose controls this task demonstrates.`}
+    >
+      <Stack gap="4">
</file context>


await expect(
controller.getTaskPolicies(orgId, 'tsk_404', authContext),
).rejects.toThrow('Task not found');
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.

P3: The test claims to validate NotFoundException but only asserts the error message, so it can pass on the wrong exception type/status.

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

<comment>The test claims to validate `NotFoundException` but only asserts the error message, so it can pass on the wrong exception type/status.</comment>

<file context>
@@ -512,6 +516,151 @@ describe('TasksController', () => {
+
+      await expect(
+        controller.getTaskPolicies(orgId, 'tsk_404', authContext),
+      ).rejects.toThrow('Task not found');
+    });
+
</file context>
Suggested change
).rejects.toThrow('Task not found');
).rejects.toMatchObject({ message: 'Task not found', status: 404 });

@Marfuen Marfuen merged commit e4ca764 into release Apr 29, 2026
14 checks passed
@claudfuen
Copy link
Copy Markdown
Contributor

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