diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index a4ff1cf7d..dbe9a613c 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -2292,6 +2292,11 @@ a new API state machine, queue contract, or verification-accounting field. the lifecycle normalizes and de-duplicates them before persistence. Model output cannot mint this audit provenance, and the IDs grant no planning, approval, or execution authority. + The frontend action-audit contract projects every broker-owned origin + correlation field, including `operationalRecordId` and `evidenceIds`, without + treating either as operator-authored data. Consumers may build an exact + Patrol return route from `operationalRecordId` only for a recognized Patrol + surface; absence of that field remains an unavailable return route. Authority proof: `TestContract_PatrolActionBrokerKeepsPolicyExecutionCoreOwned` in `internal/api/contract_test.go`. The proposal enters that broker from the investigation-only diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 320512976..cb631a4e2 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -5781,7 +5781,13 @@ entry composes the existing shared review dialog, route, API client, and durable action identity rather than creating another action client. When the trusted audit origin is present, both the queue row and decision packet show bounded product attribution such as `From Patrol`; unknown first-party surfaces fall -back to `From Pulse`, and absent origin remains absent rather than guessed. +back to `From Pulse`, and absent origin remains absent rather than guessed. The +shared action review also exposes `Open Patrol record` only when a Patrol origin +carries its canonical `operationalRecordId`; the link targets the existing +route-backed Patrol attention selection and never derives identity from display +copy, resource IDs, finding IDs, or action reasons. Older correlated Patrol +actions may expose `Open Patrol` to the Patrol home, but never label that +fallback as a record-specific return. ### Protection posture presentation boundary diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index e698bdf5f..3dd837671 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -1697,9 +1697,12 @@ served clones. Proof: `TestClonedResourcesPreservePlatformAdmission` and proposed the action (e.g. Patrol) so decisions and terminal outcomes can be reconciled back onto that surface's records. The Actions queue and decision packet render that trusted surface as bounded product copy such as - `From Patrol`; they do not expose an internal surface token or invent a - return link when origin metadata lacks a canonical route. It is set only by - in-process planning callers through the action lifecycle service's + `From Patrol`. A Patrol action carrying the trusted `operationalRecordId` + exposes an exact return link through the canonical route-backed attention + selection. Older Patrol actions without that identifier may return to the + Patrol home but do not claim an exact record link. Internal surface tokens + remain hidden. Origin is set only by in-process planning callers through + the action lifecycle service's plan options; the public `POST /api/actions/plan` body must never be able to claim a first-party origin. `NormalizeActionOrigin` trims fields, sorts and deduplicates evidence IDs, and collapses an all-empty diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index 191c668f8..af92e1780 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,25 +1,19 @@ { "version": 1, - "base_sha": "ac4116e09b85055e281aee3895ba65012e747215", - "verified_at": "2026-08-18T11:35:39Z", + "base_sha": "bcea56db970cd59f2f703584dc3ad2c88bc40910", + "verified_at": "2026-08-18T12:14:30Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/features/actions/ActionDecisionPacket.tsx", + "frontend-modern/src/features/actions/ActionReviewDialog.tsx", "frontend-modern/src/features/actions/actionPresentation.ts", - "frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx", - "frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx", - "frontend-modern/src/pages/Actions.tsx", - "frontend-modern/src/utils/patrolSummaryPresentation.ts" + "frontend-modern/src/types/actionAudit.ts" ], "content_sha256": { - "frontend-modern/src/features/actions/ActionDecisionPacket.tsx": "4bd177108ab987198c977d1c763a0ca21293d665b71520364eb168a312ac0ac0", - "frontend-modern/src/features/actions/actionPresentation.ts": "924734c195777644da82dfe4190eaa18ba4491d75ad67204b3bb39bd9f3eb1c8", - "frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx": "8b28588b1117a87a7c22944b29446a9b6944abf402b5ea162bbe88635e8a8f35", - "frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx": "edb3703a3f28c562fa16b8762c8907651504d8aff0ae006a130c2d3a3afe98be", - "frontend-modern/src/pages/Actions.tsx": "7649d29a5a32650265fd745c07eab2e5c42f397a2e0a6da3b5563170daade9dd", - "frontend-modern/src/utils/patrolSummaryPresentation.ts": "22416045104e977f1a8ec7995a32a5c5943ae98a02d5bbae5aed0bab29ed2330" + "frontend-modern/src/features/actions/ActionReviewDialog.tsx": "5e489d440ce9b1e9f106a543a79b09d0b4d73aed65c9757688efa39b2d6fa833", + "frontend-modern/src/features/actions/actionPresentation.ts": "776904de782fb0e208f28de7eb6e7d8ea9ac8746c3b2fa1cd7ee9b6ff1bcb985", + "frontend-modern/src/types/actionAudit.ts": "435934beca93e8683ec5177e051b8a8d06f2d3eb4fb09a082e4ead30282bbc32" }, - "routes": ["/patrol", "/actions"], + "routes": ["/actions", "/patrol"], "viewports": [ { "width": 1280, @@ -31,20 +25,17 @@ } ], "states": [ - "Patrol Inbox default queue with canonical checked-resource recency and human-readable incident titles", - "Patrol selected decision at the first and second live queue positions on desktop and mobile", - "Patrol selected detail with latest-observation age, lifecycle controls, and evidence disclosure collapsed", - "Activity with verified outcomes, separate Actions and Patrol-record handoffs, and no collapsed border artifact", - "Expanded Patrol records with explicit raw-record versus Inbox-decision relationship copy", - "Actions Open queue with Patrol origin badges and the route selected in desktop and mobile navigation", - "Actions governed review with Patrol origin repeated in the decision packet" + "Actions Open queue with Patrol-origin records and a selected governed review", + "Older Patrol action review with an honest Open Patrol fallback and no record-specific claim", + "Patrol Inbox opened with a canonical encoded attention record selected in the detail workspace", + "Mobile action review with the Patrol return control visible above the decision packet", + "Mobile Patrol exact-record selection with the intended decision detail visible" ], "interactions": [ - "Used the live in-app Browser to start review and move from Decision 1 to Decision 2 at desktop and 390-pixel phone widths", - "Confirmed previous and next controls, resource-first titles, latest-observation age, and neutral action verification framing", - "Opened Activity, inspected the collapsed handoffs, expanded Patrol records, and confirmed decision/history separation", - "Opened Actions from Patrol, inspected From Patrol badges, and opened a governed action review to confirm its Origin field", - "Verified the mobile document and body widths equal the 390-pixel viewport with no horizontal overflow", - "Confirmed the final stable Patrol and Actions pass produced no new console warnings or errors" + "Opened a Patrol-origin action from the live Actions queue and used Open Patrol to return to the Patrol Inbox", + "Navigated through the canonical encoded attention route and confirmed it selected the exact operational record", + "Repeated the selected action review and exact Patrol selection at a 390 by 844 viewport", + "Verified the mobile dialog remained within the viewport and document width equalled the 390-pixel viewport", + "Confirmed the final browser diagnostics contained no warning or error entries" ] } diff --git a/frontend-modern/src/api/__tests__/actionAudit.test.ts b/frontend-modern/src/api/__tests__/actionAudit.test.ts index 8010dc596..5f6b01c15 100644 --- a/frontend-modern/src/api/__tests__/actionAudit.test.ts +++ b/frontend-modern/src/api/__tests__/actionAudit.test.ts @@ -195,6 +195,12 @@ describe('ActionAuditAPI', () => { name: 'Edge proxy', type: 'app-container', }, + origin: { + surface: 'operational_trust_attention', + findingId: 'finding-1', + operationalRecordId: 'record/one', + evidenceIds: ['evidence-1'], + }, plan: { actionId: 'action/one', requestId: 'request-1', @@ -252,6 +258,12 @@ describe('ActionAuditAPI', () => { name: 'Edge proxy', type: 'app-container', }); + expect(detail.audit.origin).toEqual({ + surface: 'operational_trust_attention', + findingId: 'finding-1', + operationalRecordId: 'record/one', + evidenceIds: ['evidence-1'], + }); expect(detail.audit.request).not.toHaveProperty('resourceName'); expect(detail.readOnly).toBe(true); expect(detail.audit.result?.actionResultV2).toMatchObject({ diff --git a/frontend-modern/src/features/actions/ActionReviewDialog.tsx b/frontend-modern/src/features/actions/ActionReviewDialog.tsx index d438e909d..e08e56bd0 100644 --- a/frontend-modern/src/features/actions/ActionReviewDialog.tsx +++ b/frontend-modern/src/features/actions/ActionReviewDialog.tsx @@ -1,13 +1,18 @@ import { Show, createEffect, createMemo, createSignal, onCleanup, type Component } from 'solid-js'; import XIcon from 'lucide-solid/icons/x'; +import ArrowUpRightIcon from 'lucide-solid/icons/arrow-up-right'; import { ResourceActionsAPI } from '@/api/resourceActions'; -import { Button } from '@/components/shared/Button'; +import { Button, ButtonLink } from '@/components/shared/Button'; import { Dialog } from '@/components/shared/Dialog'; import { notificationStore } from '@/stores/notifications'; import { presentationPolicyIsReadOnly } from '@/stores/sessionPresentationPolicy'; import type { ActionDetailResponse } from '@/types/actionAudit'; import { ActionDecisionPacket } from './ActionDecisionPacket'; -import { formatActionName, getActionResourcePresentation } from './actionPresentation'; +import { + formatActionName, + getActionOriginDestination, + getActionResourcePresentation, +} from './actionPresentation'; import { getAPTActionPresentation } from './aptActionPresentation'; export const ActionReviewDialog: Component<{ @@ -19,6 +24,7 @@ export const ActionReviewDialog: Component<{ const [error, setError] = createSignal(''); const [clock, setClock] = createSignal(Date.now()); const audit = () => props.detail?.audit; + const originDestination = () => getActionOriginDestination(audit()?.origin); const resource = createMemo(() => { const record = audit(); return record @@ -245,6 +251,19 @@ export const ActionReviewDialog: Component<{ {resource().label} ยท {resource().detail}

+ + {(destination) => ( + + {destination().exact ? 'Open Patrol record' : 'Open Patrol'} + + )} +