Skip to content

[FEATURE] S3 — Deterministic bounded filesystem context grant/denial recovery #178

Description

@Joncallim

Issue Type

Feature — child slice S3 of EPIC #172. Depends on #176 (S1), #177 (S2). Canonical architecture: docs/adr/0009-mcp-admission-contract.md; see also ADR 0008-filesystem-mcp-bounded-context-grants.md.

Problem Statement

Bounded filesystem context grant recovery has real determinism gaps:

  • Denying a required grant burns an execution attempt. requiresFilesystemGrantApproval returns {blocked:false} for any denied effective phase regardless of blocking capabilities (web/lib/mcps/filesystem-grants.ts:315-322). The package is claimed, then effectiveFilesystemGrant returns empty caps for a denied phase and filesystemRuntimeMetadata throws Filesystem MCP context blocked (web/worker/work-package-executor.ts:~938-944,1049-1062,1515), consuming an attempt for a guaranteed failure — exactly the waste failWorkPackageForFilesystemGrant (work-package-handoff.ts:807-867) was built to prevent.
  • Per-task always_allow skips FAILED siblings; the project endpoint recovers them. The per-task route reconciles siblings only in pending/ready/blocked/needs_rework (web/app/api/tasks/[id]/filesystem-grants/route.ts:418-421); the project route also reconciles failed (web/app/api/projects/[id]/filesystem-grant/route.ts:166-243). The same grant state yields different outcomes depending on which endpoint issued it.
  • Handoff's default branch never consults projectMcpConfig (work-package-handoff.ts:896-899), unlike approval (approve/route.ts:167-187) and the executor — a latent approval-vs-handoff divergence when a package is covered by a live project grant but its persisted effective phase is still not_issued.

Desired Outcome

Denial of a required grant is a recorded, named, recoverable decision that holds execution pre-claim (zero attempts). Recovery is deterministic: the same grant state recovers the same package set regardless of endpoint. Project coverage is consulted consistently at approval and handoff.

User Story

As a Forge operator, when I deny or later approve bounded filesystem context, I want the package held (not crashed) and consistently recovered so retries are predictable and never waste attempts.

Requirements

  • requiresFilesystemGrantApproval (filesystem-grants.ts:303-339): do NOT blanket-return {blocked:false} for a denied effective phase when blockingCapabilities is non-empty. Return a distinct terminal outcome (e.g. deniedRequired:true) so the handoff gate holds the package pre-claim. Keep non-blocking only for optional/continue_without_mcp denials.
  • filesystemGrantHandoffBlock (work-package-handoff.ts:876-906): branch on the denied-required outcome to failWorkPackageForFilesystemGrant with a message that names the operator denial ("Filesystem context was denied for required capabilities X; re-approve to proceed") and record it under FILESYSTEM_GRANT_BLOCK_METADATA_KEY so the recovery endpoints can re-open it. Pass project?.mcpConfig into requiresFilesystemGrantApproval in BOTH branches (the default branch at :896-899 currently omits it).
  • Per-task always_allow sibling propagation (tasks/[id]/filesystem-grants/route.ts:418-421): extend selection to include failed grant-blocked siblings (reuse the failed-recovery status transition + task recovery already implemented for the primary package at :455-501), or delegate always_allow reconciliation to the shared routine used by projects/[id]/filesystem-grant/route.ts:166-243. Both endpoints must recover identical package sets.
  • Preserve the confirmed-working baseline: filesystem blocks stay terminalBlock:true with the distinct FILESYSTEM_GRANT_BLOCK_METADATA_KEY (never mcpBroker), so shouldAutoRetryBlockedHandoff (blocked-handoff-retry.ts:67-79) correctly refuses to auto-retry them; both recovery endpoints clear the marker and re-drive via redis lpush forge:approvals.

Acceptance Criteria

  • Denying a required filesystem grant holds the package with a named, recoverable block and consumes zero execution attempts (no Filesystem MCP context blocked throw at the executor for that case).
  • The same always_allow grant recovers the same package set (including failed siblings) whether issued from the per-task or project endpoint.
  • requiresFilesystemGrantApproval is passed projectMcpConfig from handoff in both branches; approval and handoff agree on project coverage.
  • Optional/continue_without_mcp denials remain non-blocking; terminal non-auto-retry behavior preserved.
  • Tests cover denied-required hold, cross-endpoint recovery determinism, and project-coverage agreement. npm run lint, npx tsc --noEmit, npm test, npm run build pass.

Dependencies

Implementation Scope

Large — worker handoff gate, filesystem-grants library, and two recovery API routes.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions