diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md
index 5c1dd843c..26aa45ca0 100644
--- a/docs/release-control/v6/internal/subsystems/ai-runtime.md
+++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md
@@ -4223,7 +4223,12 @@ projection over alert-owned operational records, transition timelines,
evidence envelopes, and recovery-owned protection postures. It may order,
filter, paginate, summarize, and shape selected Assistant context, but it must
not create a second writable lifecycle or infer a healthy state when lifecycle
-coverage is unavailable. `internal/ai/patrol_metrics.go` exports only
+coverage is unavailable. The projection also owns customer-facing attention
+titles: provider alarm messages yield their bounded alarm subject, generic
+resource/storage incident types become plain issue language, and known
+acronyms such as CPU, ZFS, and I/O retain their canonical casing. Clients must
+not receive machine-title-cased placeholders such as `Resource Incident` and
+repair them independently. `internal/ai/patrol_metrics.go` exports only
low-cardinality lifecycle-state counts, queue age, acknowledgement time, and
calm-evaluation age; raw resource IDs are forbidden as metric labels.
diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md
index 44429a2f5..a4ff1cf7d 100644
--- a/docs/release-control/v6/internal/subsystems/api-contracts.md
+++ b/docs/release-control/v6/internal/subsystems/api-contracts.md
@@ -3924,7 +3924,10 @@ next selection from the refreshed typed membership. It must not optimistically
author lifecycle state, keep acknowledged/suppressed records in the active
inbox, or turn review progress into a second queue API. Newly returned active
items may be appended to the in-progress review session without changing their
-server-authored identity or trust fields.
+server-authored identity or trust fields. Visible queue position and calm-state
+evaluation age must remain reactive to those refreshed typed projections; a
+client must not capture either accessor as mount-time copy and then display a
+stale position or evaluation timestamp.
Lifecycle read failure returns a typed unavailable response and never a
synthetic zero or calm state. Protection lookup failure may return lifecycle
diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
index 237ab5456..320512976 100644
--- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md
+++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
@@ -2653,7 +2653,13 @@ announces the remaining count and advances to the next current item, and the
final settled item returns to the calm inbox. Lifecycle copy must explain that
review removes one occurrence from today's inbox while suppression is bounded;
generic backend verbs must not leave the user guessing whether the alert was
-resolved, hidden permanently, or merely recorded as seen.
+resolved, hidden permanently, or merely recorded as seen. Both the position
+label and calm evaluation age are reactive text nodes so detail navigation and
+refresh cannot leave their first-render values frozen. The selected metadata
+keeps latest-observation age visible, action verification copy stays generic
+across capabilities, and the list itself is not a broad live region; only
+errors, calm/empty transitions, and explicit completion announcements receive
+scoped assistive announcement semantics.
The selected attention detail may compose the shared Actions review for an
eligible backend-authored offer. The detail owns only the expected
@@ -5772,7 +5778,10 @@ universal audit records originating from Patrol, Assistant, MCP, or manual
controls. While that route is open, desktop and mobile navigation select
Actions and the browser title identifies `Actions`. The first-class navigation
entry composes the existing shared review dialog, route, API client, and durable
-action identity rather than creating another action client.
+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.
### Protection posture presentation boundary
diff --git a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md
index 3109d2080..e78fb6fee 100644
--- a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md
+++ b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md
@@ -1246,7 +1246,9 @@ keeps the canonical resource identifier behind a copy affordance, suppresses
impact text that merely repeats the summary, and groups evidence, protection,
and lifecycle history behind one deliberate disclosure. Occurrence controls
remain visible, while durable threshold and finding-management guidance is
-secondary disclosure. This is progressive presentation of the same typed
+secondary disclosure. Selected metadata includes the latest observation age,
+and action-result wording remains capability-neutral rather than assuming every
+offer is a restart or container operation. This is progressive presentation of the same typed
record, not a loss of evidence or lifecycle truth.
Plan-locked Watch-only installs do not render an empty mode disclosure after
@@ -1256,13 +1258,17 @@ choice controls. The empty Protection mode offers bounded example outcomes
that prefill the existing objective form without creating an objective until
the operator reviews and submits it. Activity keeps Actions and Patrol records
as separate subordinate handoffs, but the Patrol-record handoff does not expose
-the legacy findings count beside the canonical decision count.
+the legacy findings count beside the canonical decision count. Its supporting
+copy explicitly distinguishes raw finding/run history from Inbox decisions, and
+its collapsed container must not leave a decorative border artifact behind.
Selecting the first item starts a bounded review session over the loaded
decision order. The selected context shows its current position and exposes
previous/next navigation on desktop and mobile; deterministic identity breaks
otherwise equal priority ties so polling cannot reshuffle equal decisions
-arbitrarily. The session retains its review order while fresh list responses
+arbitrarily. Position copy is a live projection of the selected identity and
+loaded queue length, not a one-time string captured when detail first mounts.
+The session retains its review order while fresh list responses
remove settled items and append genuinely new work. Acknowledging an occurrence
is presented as `Mark reviewed`: on success the item leaves the active inbox,
Patrol announces the remaining decision count, and advances to the next current
@@ -2238,11 +2244,12 @@ signal from the most recent completed run via
`PatrolRecencyPresentation.resourcesChecked`, populated by
`getPatrolRecencyPresentation` from
`PatrolRunRecord.resources_checked`. The render reads "Last full
-patrol: 3m ago — verified 47 resources" so operators see both
+patrol: 3m ago — checked 47 resources" so operators see both
temporal recency and coverage in one line. The field stays optional
(omitted when zero) so a degenerate run that completed without
-checking any resources does not render a misleading "verified 0
-resources" line.
+checking any resources does not render a misleading "checked 0
+resources" line. Coverage never uses `verified`, which is reserved for
+confirmed action outcomes rather than inventory traversal.
The expanded finding card must also expose a "Copy summary" action
that produces a paste-ready Markdown summary of the finding (severity,
title, resource header, description, impact, recommendation,
diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md
index 309644ed7..e698bdf5f 100644
--- a/docs/release-control/v6/internal/subsystems/unified-resources.md
+++ b/docs/release-control/v6/internal/subsystems/unified-resources.md
@@ -1695,7 +1695,10 @@ served clones. Proof: `TestClonedResourcesPreservePlatformAdmission` and
`migrateActionAuditsSchema`) and round-tripped through
`scanActionAuditRecord`. Origin identifies which internal surface
proposed the action (e.g. Patrol) so decisions and terminal outcomes
- can be reconciled back onto that surface's records. It is set only by
+ 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
plan options; the public `POST /api/actions/plan` body must never be
able to claim a first-party origin. `NormalizeActionOrigin` trims
@@ -1720,6 +1723,11 @@ served clones. Proof: `TestClonedResourcesPreservePlatformAdmission` and
`TestSQLiteStoreActionAuditOriginRoundTrip`,
`TestSQLiteActionAuditOriginOperationalRecordReader`, and the matching
memory/batch reader tests in `internal/unifiedresources/store_test.go`.
+ Incident next-step fallback follows the same canonical resource boundary:
+ storage guidance is reserved for storage, physical-disk, network-share,
+ PBS, and Ceph resources. Network and every other non-storage type receive
+ neutral resource-health guidance rather than being misclassified as
+ storage merely because they are not a host or VM.
27. Keep API-added TrueNAS systems keyed by the configured connection,
never by snapshot-reported identity. `systemSourceID` in
`internal/truenas/provider.go` scopes the system source ID (and every
diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json
index dd2cfefe4..191c668f8 100644
--- a/frontend-modern/browser-verification.json
+++ b/frontend-modern/browser-verification.json
@@ -1,25 +1,29 @@
{
"version": 1,
- "base_sha": "95c253c4860d704dfdc8b3d743b482182d749fff",
- "verified_at": "2026-08-18T10:49:01Z",
+ "base_sha": "ac4116e09b85055e281aee3895ba65012e747215",
+ "verified_at": "2026-08-18T11:35:39Z",
"result": "passed",
"changed_paths": [
+ "frontend-modern/src/features/actions/ActionDecisionPacket.tsx",
+ "frontend-modern/src/features/actions/actionPresentation.ts",
"frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx",
- "frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx",
"frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx",
- "frontend-modern/src/features/patrol/PatrolObjectivesPanel.tsx"
+ "frontend-modern/src/pages/Actions.tsx",
+ "frontend-modern/src/utils/patrolSummaryPresentation.ts"
],
"content_sha256": {
- "frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx": "42d0e595053546270727bcfe57de37b1f9c6e4c721a2bbc6175335136a4e03b2",
- "frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx": "eeaecb901bac67f4b2ba2c442a5f669be50ee2a1bb01928340eef4d83da5532d",
- "frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx": "90a0e1b7b7b75567a6c026d4d6c115b82d06c1f86412f971c0c8989a60562471",
- "frontend-modern/src/features/patrol/PatrolObjectivesPanel.tsx": "fc03c1905afae9aaa684aecd759cd6e409eee5fb987abcdee5e2c83d850352d1"
+ "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"
},
- "routes": ["/patrol"],
+ "routes": ["/patrol", "/actions"],
"viewports": [
{
- "width": 1440,
- "height": 900
+ "width": 1280,
+ "height": 720
},
{
"width": 390,
@@ -27,20 +31,20 @@
}
],
"states": [
- "Inbox default queue with resource-first decision titles and no selected detail",
- "Inbox selected decision with compact trust metadata and evidence collapsed",
- "Inbox selected decision with evidence, protection, and timeline expanded",
- "Protection empty state with starter outcomes",
- "Protection create-objective dialog prefilled from a starter outcome",
- "Activity with verified outcomes, Actions handoff, and Patrol records disclosure",
- "Watch-only header with its mode explanation and no empty automation disclosure"
+ "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"
],
"interactions": [
- "Used the live in-app Browser to switch between Inbox, Protection, and Activity",
- "Selected the highest-priority decision and confirmed Assistant, Copy ID, Open resource, lifecycle controls, and progressive disclosure",
- "Expanded Evidence and history and confirmed the latest evidence, older-observation reveal, protection posture, and timeline",
- "Used a Protection starter outcome and confirmed the objective dialog opened with the chosen brief prefilled",
- "Used Claude Code Playwright verification at desktop and phone widths to inspect layout, focus visibility, copy, console output, and dialog behaviour",
- "Confirmed there are no user-visible semicolons or em dashes and no console errors at either viewport"
+ "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"
]
}
diff --git a/frontend-modern/src/features/actions/ActionDecisionPacket.tsx b/frontend-modern/src/features/actions/ActionDecisionPacket.tsx
index c0c7c5d0f..55fc51c17 100644
--- a/frontend-modern/src/features/actions/ActionDecisionPacket.tsx
+++ b/frontend-modern/src/features/actions/ActionDecisionPacket.tsx
@@ -6,6 +6,7 @@ import {
formatEvidenceClass,
formatPolicyAuthority,
formatPolicyReason,
+ getActionOriginLabel,
getActionResourcePresentation,
verificationTruthLabel,
} from './actionPresentation';
@@ -19,6 +20,7 @@ export const ActionDecisionPacket: Component<{
const result = () => props.audit.result?.actionResultV2;
const apt = () => getAPTActionPresentation(props.audit);
const firstEvidence = () => result()?.verification.evidence?.[0];
+ const originLabel = () => getActionOriginLabel(props.audit.origin);
const resource = createMemo(() =>
getActionResourcePresentation(props.audit.request.resourceId, props.audit.resource),
);
@@ -57,6 +59,14 @@ export const ActionDecisionPacket: Component<{
{summary()?.coverageState === 'partial'
- ? 'The lifecycle queue is empty, but protection context is incomplete. Pulse is not treating that gap as proof of health.'
- : 'Choose another lifecycle filter or refresh the current evaluation.'}
+ ? 'The Inbox is empty, but protection context is incomplete. Pulse is not treating that gap as proof of health.'
+ : 'Refresh the Inbox to check the current evaluation.'}
>
}
@@ -665,7 +671,8 @@ function AttentionEmptyState(props: { hasQuietWork: boolean }) {
? 'Patrol can continue with the current issues under this mode.'
: 'The current operational evaluation has no active items.'}
- {(evaluatedAt) => ` Checked ${formatRelativeTime(evaluatedAt(), { compact: true })}.`}
+ {' Checked '}
+ {formatRelativeTime(summary()!.evaluatedAt, { compact: true })}.
@@ -796,7 +803,7 @@ function AttentionDetail(props: {
- {(position) => `Decision ${position()} of ${props.queueCount()}`}
+ Decision {props.queuePosition()} of {props.queueCount()}
@@ -878,6 +885,10 @@ function AttentionDetail(props: {
·
+ ·
+
+ Last seen {formatRelativeTime(loaded().item.lastObservedAt, { compact: true })}
+
{loaded().item.plainLanguageSummary}
@@ -1335,11 +1346,11 @@ function ActionVerificationMessage(props: { state: AttentionItem['verificationSt
case 'pending':
return 'The action is awaiting a decision, execution, or verification.';
case 'succeeded':
- return 'The restart postcondition was confirmed. This issue stays open until fresh health evidence shows the container is healthy.';
+ return 'The action postcondition was confirmed. This issue stays open until fresh health evidence confirms recovery.';
case 'failed':
- return 'The restart did not satisfy its postcondition. The issue remains open.';
+ return 'The action did not satisfy its postcondition. The issue remains open.';
case 'unknown':
- return 'Pulse could not conclusively verify the restart. The issue remains open.';
+ return 'Pulse could not conclusively verify the action. The issue remains open.';
default:
return '';
}
diff --git a/frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx b/frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx
index 25da94f04..93c82ce9c 100644
--- a/frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx
+++ b/frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx
@@ -195,7 +195,7 @@ export function PatrolIntelligenceSurface() {
ref={findingsPanel}
id="patrol-operational-records"
tabindex="-1"
- class="rounded-lg border border-border bg-surface"
+ class={`rounded-lg bg-surface ${findingsOpen() ? 'border border-border' : 'border-0'}`}
open={findingsOpen()}
onToggle={(event) => setFindingsOpen(event.currentTarget.open)}
>
@@ -203,6 +203,10 @@ export function PatrolIntelligenceSurface() {
+
+ Patrol records keep raw check findings and run history. The Inbox separately lists
+ items that currently need a decision.
+