Conversation
* 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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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 }, |
There was a problem hiding this comment.
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.`} |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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>
| ).rejects.toThrow('Task not found'); | |
| ).rejects.toMatchObject({ message: 'Task not found', status: 404 }); |
|
🎉 This PR is included in version 3.35.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
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
/v1/policies/:id/evidence-tasksand GET/v1/tasks/:taskId/policies./v1/policies/:id/controlsnow includes framework names derived fromrequirementsMapped.usePolicyEvidenceTasks,useTaskPolicies,PolicyEvidenceTasks, andTaskPolicies.@trycompai/design-systemtables with per-cell links (open in new tab).Bug Fixes
policy:readandtask:read.organizationId.Written for commit 5e30bfb. Summary will update on new commits. Review in cubic