Carved out of the #317 epic. The delegated token + connection resolution landed (#327 + #329); this is the remaining Forge-side half — the runtime auth-required gate that turns "no grant yet" from a hard failure into a pause that resumes once the user consents. Forge, every mode (§18.4).
Problem
Today a type: user MCP call with no platform grant for the requesting user returns ErrNoToken — the tool call fails. #317's contract is that it should pause: the executor parks, the user is prompted to consent, and the call resumes with their token once the grant exists. Without this, a delegated tool is unusable until the user has separately consented out-of-band.
today: BearerToken(type=user, no grant) → ErrNoToken → tool call fails
target: BearerToken(type=user, no grant) → PARK the executor
→ deliver an auth-required prompt (platform consent / standalone loopback)
→ user consents → platform has a grant → RESUME → call proceeds
Scope (Forge, every mode)
Generalize the DEFER park/resume engine (R4c, deferpolicy.Engine / Handle.WaitCtx) into a reusable "pause the tool call, request X, resume on signal" primitive, and add an auth-required variant:
- Auth-required gate — when a delegated resolver returns
ErrNoToken (no grant yet), park the executor on a Handle keyed by {task/session, server, subject} instead of failing. Resume when a grant becomes available (a signal / re-poll succeeds) or time out → fail.
- Signal to resume — a resume endpoint (mirrors
POST /tasks/{id}/decisions) the platform calls when consent completes; or a bounded re-poll of the delegated resolver.
- Delivery is mode-split (§18.4):
- Managed: the platform prompts (Slack DM / console chat), hosts the callback, and signals resume — Forge only parks/resumes. (Delivery itself is platform-side.)
- Standalone default: Forge's own loopback callback +
state binding + AuthLinkDeliverer (Slack ephemeral / A2A auth-required artifact) — the interactive resolver.
Acceptance (the #317 boxes this closes)
Already in place (build on these)
Out of scope (elsewhere)
Related
Epic: #317 · tokens #327 · connections #329 · broker #318 · AARM R10 #319 · authority design-tool-registry.md §18.4/§18.5.
Problem
Today a
type: userMCP call with no platform grant for the requesting user returnsErrNoToken— the tool call fails. #317's contract is that it should pause: the executor parks, the user is prompted to consent, and the call resumes with their token once the grant exists. Without this, a delegated tool is unusable until the user has separately consented out-of-band.Scope (Forge, every mode)
Generalize the DEFER park/resume engine (R4c,
deferpolicy.Engine/Handle.WaitCtx) into a reusable "pause the tool call, request X, resume on signal" primitive, and add an auth-required variant:ErrNoToken(no grant yet), park the executor on a Handle keyed by {task/session, server, subject} instead of failing. Resume when a grant becomes available (a signal / re-poll succeeds) or time out → fail.POST /tasks/{id}/decisions) the platform calls when consent completes; or a bounded re-poll of the delegated resolver.statebinding +AuthLinkDeliverer(Slack ephemeral / A2Aauth-requiredartifact) — the interactive resolver.Acceptance (the #317 boxes this closes)
type: usertool call lacking a grant pauses (not fails) and resumes after consent.statebinding rejects cross-session / replayed / expired callbacks (standalone default).Already in place (build on these)
delegatedTokenSource,ErrNoToken= auth-required signal).subjectConnPool, materializedtype: userservers).forge-core/security/deferpolicy,forge-cli/runtime/defer.go(park/resume +POST /tasks/{id}/decisionstemplate).auth.IdentityFromContextreaches the MCP layer (used by the delegated resolver).Out of scope (elsewhere)
POST /mcp/tokengrant endpoint — initializ (aip/mcp-delegated-identity-broker.md).Related
Epic: #317 · tokens #327 · connections #329 · broker #318 · AARM R10 #319 · authority
design-tool-registry.md§18.4/§18.5.