Issue Type
Feature — child slice S5 of EPIC #172. Depends on #176 (S1), #177 (S2). Canonical architecture: docs/adr/0009-mcp-admission-contract.md.
Problem Statement
The task detail page must let an operator answer four questions without reading logs: (1) is this MCP request just planning context? (2) does this package need bounded project filesystem context (show the exact grant)? (3) is an MCP unhealthy/missing (link setup/retry)? (4) is this a deferred-live-MCP feature (say deferred, not broken install)? Today it cannot:
- Grant-preview copy is a 3-way ternary on
decision.status only (web/app/dashboard/tasks/[id]/page.tsx:3171-3175); a deferred GitHub-write capability renders the identical destructive "resolve this MCP issue" copy as a genuinely unhealthy MCP.
statusBadgeClass (:1203-1245) has no neutral bucket for deferred/planning — anything new falls to red/amber.
- Planning-only warnings (
filesystem.project.write) sit in the same amber list as real degradation (:3146-3155).
- Blocked reasons are raw engine strings with no
recoveryAction → CTA mapping; RetryHandoffControls (:1568-1632) always offers "Re-run stalled handoff" even for non-retryable blocks.
- The projects page (
web/app/dashboard/projects/[id]/page.tsx:1035-1052) offers remediation CTAs only for filesystem configuration_required and github auth_required, not unhealthy/missing. The MCPs catalog page (web/app/dashboard/mcps/page.tsx) shows no health and no beta boundary.
- There is no shared UI copy contract, so copy drifts across surfaces — the "preview and handoff must not disagree" failure on the presentation layer.
Desired Outcome
One admission-decision → badge/CTA/copy mapping drives every MCP surface. The four operator questions are answerable on the task page without logs; deferred-live reads as a product boundary, not a broken install; planning-only context is visually separate from hard blockers; and unhealthy/missing MCPs link to setup/retry.
User Story
As a Forge operator, I want the task page to tell me plainly whether a block is planning context, a filesystem grant I can approve, an MCP I need to fix, or a deferred feature — with the right button next to it — so I never have to read logs to know what to do.
Requirements
- Create
web/lib/mcps/admission-copy.ts: a pure map from mode+recoveryAction+status → { statusKey, badgeText, headline, body, cta? } per ADR 0009 §S5 (planning_only → neutral "Planning context", no CTA; bounded_context_required → amber "Needs project context", scroll CTA; bounded_context_approved → green "Context approved"; blocked+install_or_fix_mcp → red, deep-link /dashboard/projects/{projectId}#project-mcps-heading; blocked+revise_plan → red, open Request-changes flow; deferred_live_mcp → neutral slate "Deferred — beta boundary", no CTA). Keep all human strings here.
- Extend
web/lib/mcps/execution-design-metadata.ts decision type/normalizer (:50-75,110-142) to carry mode, recoveryAction, normalizedCapabilities, evidenceRefs; back-derive mode for old artifacts.
tasks/[id]/page.tsx: replace the status-only ternary (:3171-3175) with admissionPresentation(decision); split planning-only warnings into a neutral callout (:3146-3155); stop rendering deferred/unsafe capabilities in the destructive blocked alert (:3135-3144); add deferred/planning neutral buckets to statusBadgeClass (:1203-1245); gate RetryHandoffControls on retryability (pass broker.retryable); surface the bounded-context packet inline in FilesystemGrantControls/ApprovedGrantSnapshot (:1844-1978); route McpGrantCards/BrokerRetrySummary (:1685-1752) through the shared copy helper. Replace the client filesystem reimplementations (:348-444) with the shared helpers or a server-computed grant-state payload.
projects/[id]/page.tsx:1035-1052: add remediation CTAs for unhealthy/missing/disabled from catalog.remediation, plus a one-line "Bounded read-only context, no live tool handles" boundary note (from catalog.runtime.mode/liveTools). Ensure the #project-mcps-heading anchor is the deep-link target.
mcps/page.tsx: add a runtime boundary badge per entry (bounded_context_packet → "Bounded context", external_service → "External service") and a static "No live tool handles (beta)" line.
- The worker (S2) must persist
mode+recoveryAction on BOTH the grantDecisions preview decisions AND the per-package block metadata (blockedReason) so RetryHandoffControls routes correctly.
Acceptance Criteria
Dependencies
Implementation Scope
Large — new copy contract plus changes across three dashboard surfaces and the metadata reader.
Issue Type
Feature — child slice S5 of EPIC #172. Depends on #176 (S1), #177 (S2). Canonical architecture:
docs/adr/0009-mcp-admission-contract.md.Problem Statement
The task detail page must let an operator answer four questions without reading logs: (1) is this MCP request just planning context? (2) does this package need bounded project filesystem context (show the exact grant)? (3) is an MCP unhealthy/missing (link setup/retry)? (4) is this a deferred-live-MCP feature (say deferred, not broken install)? Today it cannot:
decision.statusonly (web/app/dashboard/tasks/[id]/page.tsx:3171-3175); a deferred GitHub-write capability renders the identical destructive "resolve this MCP issue" copy as a genuinely unhealthy MCP.statusBadgeClass(:1203-1245) has no neutral bucket for deferred/planning — anything new falls to red/amber.filesystem.project.write) sit in the same amber list as real degradation (:3146-3155).recoveryAction→ CTA mapping;RetryHandoffControls(:1568-1632) always offers "Re-run stalled handoff" even for non-retryable blocks.web/app/dashboard/projects/[id]/page.tsx:1035-1052) offers remediation CTAs only for filesystemconfiguration_requiredand githubauth_required, notunhealthy/missing. The MCPs catalog page (web/app/dashboard/mcps/page.tsx) shows no health and no beta boundary.Desired Outcome
One admission-decision → badge/CTA/copy mapping drives every MCP surface. The four operator questions are answerable on the task page without logs; deferred-live reads as a product boundary, not a broken install; planning-only context is visually separate from hard blockers; and unhealthy/missing MCPs link to setup/retry.
User Story
As a Forge operator, I want the task page to tell me plainly whether a block is planning context, a filesystem grant I can approve, an MCP I need to fix, or a deferred feature — with the right button next to it — so I never have to read logs to know what to do.
Requirements
web/lib/mcps/admission-copy.ts: a pure map frommode+recoveryAction+status→{ statusKey, badgeText, headline, body, cta? }per ADR 0009 §S5 (planning_only → neutral "Planning context", no CTA; bounded_context_required → amber "Needs project context", scroll CTA; bounded_context_approved → green "Context approved"; blocked+install_or_fix_mcp → red, deep-link/dashboard/projects/{projectId}#project-mcps-heading; blocked+revise_plan → red, open Request-changes flow; deferred_live_mcp → neutral slate "Deferred — beta boundary", no CTA). Keep all human strings here.web/lib/mcps/execution-design-metadata.tsdecision type/normalizer (:50-75,110-142) to carrymode,recoveryAction,normalizedCapabilities,evidenceRefs; back-derivemodefor old artifacts.tasks/[id]/page.tsx: replace the status-only ternary (:3171-3175) withadmissionPresentation(decision); split planning-only warnings into a neutral callout (:3146-3155); stop rendering deferred/unsafe capabilities in the destructiveblockedalert (:3135-3144); adddeferred/planningneutral buckets tostatusBadgeClass(:1203-1245); gateRetryHandoffControlson retryability (passbroker.retryable); surface the bounded-context packet inline inFilesystemGrantControls/ApprovedGrantSnapshot(:1844-1978); routeMcpGrantCards/BrokerRetrySummary(:1685-1752) through the shared copy helper. Replace the client filesystem reimplementations (:348-444) with the shared helpers or a server-computed grant-state payload.projects/[id]/page.tsx:1035-1052: add remediation CTAs forunhealthy/missing/disabled fromcatalog.remediation, plus a one-line "Bounded read-only context, no live tool handles" boundary note (fromcatalog.runtime.mode/liveTools). Ensure the#project-mcps-headinganchor is the deep-link target.mcps/page.tsx: add a runtime boundary badge per entry (bounded_context_packet→ "Bounded context",external_service→ "External service") and a static "No live tool handles (beta)" line.mode+recoveryActionon BOTH thegrantDecisionspreview decisions AND the per-package block metadata (blockedReason) soRetryHandoffControlsroutes correctly.Acceptance Criteria
admission-copy.tsmapsmode+recoveryAction+statusto badge/headline/body/CTA and is consumed by the task page, projects page, and MCPs page.RetryHandoffControlsonly offers retry for retryable (install_or_fix_mcp) blocks.npm run lint,npx tsc --noEmit,npm test,npm run buildpass; UI verified in the preview.Dependencies
Implementation Scope
Large — new copy contract plus changes across three dashboard surfaces and the metadata reader.