diff --git a/.agent/plans/2026-06-05-missions-ps-governance/implementation-plan.md b/.agent/plans/2026-06-05-missions-ps-governance/implementation-plan.md new file mode 100644 index 0000000..69a7c00 --- /dev/null +++ b/.agent/plans/2026-06-05-missions-ps-governance/implementation-plan.md @@ -0,0 +1,797 @@ +# Missions & PS Governance — Implementation Plan + +## Overview + +Bring the .NET SDK, samples, and docs into alignment with the AAuth spec's +**mission** model and the **Person Server as the contextual policy evaluation +point**. Fix the divergent `Mission` model, add the mission cryptographic +binding through the token chain, implement the PS governance endpoints +(client + minimal server seams), then update samples and docs to showcase the +end-to-end mission flow, with a final multi-subagent review. + +See [research.md](research.md) for the full spec model and gap inventory +(G1–G20). Every phase below cites the governing spec section in +`aauth-spec/draft-hardt-oauth-aauth-protocol.md`. + +## Context + +- **Spec:** `draft-hardt-oauth-aauth-protocol` — §Missions, §Mission Approval, + §Mission Management, §Mission Status Errors, §Resource Token, §Auth Token, + §Authorization Endpoint Request (signed `aauth-mission`), §PS Token Endpoint, + §Clarification Chat, §Permission/Audit/Interaction Endpoints, §Person Server + Metadata, §Policy Evaluation Points, §Why Missions Are Not a Policy Language. +- **Branch:** TBD. +- **Sequencing:** Phases 1→5 are SDK; Phase 6 samples; Phase 7 docs; Phase 8 review. + Samples (6) and docs (7) are intentionally separate phases per the agreed + workflow. + +## Cross-Cutting Decisions + +The spec and SDK are both still in **draft** and backward compatibility is **not** +a concern. Breaking changes to existing types, method signatures, DI registration, +and the HTTP-signature covered-components contract are acceptable; callers are +updated in place. This resolves prior open questions Q2 and Q3 directly: + +- **D1 — `Mission` replacement (resolved):** Replace `Mission` in place with the + spec blob. No rename, no `[Obsolete]` shim, no dual model — update all callers. +- **D2 — `aauth-mission` signing (resolved):** Auto-cover `aauth-mission` in + `AAuthSigningHandler` when the header is present. No explicit + `AdditionalComponentsKey` fallback path is required. +- **D3 — Server governance depth** (research Q4): SDK ships DTOs + serialization + + minimal endpoint mappers + store/relay interfaces; full policy stays in + MockPersonServer. (Scope choice, unaffected by compat.) +- **D4 — Verbatim mission bytes** (research Q1): model retains raw approval body + bytes for `s256`; no re-serialization. (Correctness requirement for `s256`.) + +--- + +## Phase 1 — Mission model & `s256` identity + +**Goal:** Replace the stale `Mission` model with the spec mission blob, store the +verbatim approval bytes, and compute/verify `s256`. Fixes G1–G4, G17. + +**Spec:** §Mission Approval (blob fields; `s256` = base64url(SHA-256(exact body +bytes)), store bytes "no re-serialization"); §Mission Management (states +`active`/`terminated`); §Mission Approval (`capabilities` union into +`AAuth-Capabilities`). + +### Files + +| File | Action | +|------|--------| +| `src/AAuth/Agent/Mission.cs` | **Rewrite** — spec blob fields + raw bytes + `s256` | +| `src/AAuth/Agent/MissionState.cs` | **New** — `active`/`terminated` | +| `src/AAuth/Agent/MissionTool.cs` | **New** — `{name, description}` record | +| `src/AAuth/Crypto/` (existing hash util) or `Agent/Mission.cs` | **Modify/Use** — SHA-256 + base64url over raw bytes | +| `src/AAuth/Agent/AAuthCapabilitiesHeader.cs` | **Modify** — `Union(missionCapabilities, agentCapabilities)` | +| `tests/AAuth.Conformance/Missions/MissionModelTests.cs` | **New** | +| `tests/AAuth.Conformance/Missions/MissionS256Tests.cs` | **New** | + +### API Surface (illustrative) + +```csharp +public sealed class Mission +{ + public required string Approver { get; init; } + public required string Agent { get; init; } + public required DateTimeOffset ApprovedAt { get; init; } + public required string Description { get; init; } // Markdown + public IReadOnlyList ApprovedTools { get; init; } + public IReadOnlyList Capabilities { get; init; } + public required string S256 { get; init; } // computed identity + public ReadOnlyMemory RawBytes { get; } // verbatim approval body + + public static Mission FromApprovalBytes(ReadOnlySpan body); // parse + compute s256 + public bool VerifyS256(string expected); +} +``` + +### Implementation Decisions + +- D1: replace `Mission` in place; update all callers (no shim). +- D4 byte source: parse from the `mission_endpoint` response body bytes. + +### Definition of Done + +- [x] `Mission` exposes spec blob fields; non-spec fields removed. +- [x] States limited to `active`/`terminated` (§Mission Management). +- [x] `s256` computed as base64url(SHA-256(raw bytes)); raw bytes stored verbatim. +- [x] `VerifyS256` round-trips against a known-good blob fixture. +- [x] `AAuthCapabilitiesHeader.Union` merges mission ∪ agent capabilities (deduped). +- [x] Model + `s256` tests pass; no re-serialization in the hash path. +- [x] All existing callers updated to the new model (no shim). + +--- + +## Phase 2 — Mission binding through the token chain + +**Goal:** Carry `{approver, s256}` as the `mission` claim in resource and auth +tokens, surface it on verification, and ensure `aauth-mission` is covered by the +HTTP signature. Fixes G9–G12. + +**Spec:** §Resource Token Structure (`mission` claim when `AAuth-Mission` +present; `agent_jkt`); §Resource Token Verification step 7; §Auth Token +Structure (`mission` claim); §Auth Token Verification; §Authorization Endpoint +Request (~L619, add `aauth-mission` to signed components). + +### Files + +| File | Action | +|------|--------| +| `src/AAuth/Tokens/ResourceTokenBuilder.cs` | **Modify** — first-class `Mission` ({approver,s256}) claim | +| `src/AAuth/Tokens/AuthTokenBuilder.cs` | **Modify** — first-class `Mission` claim | +| `src/AAuth/Tokens/TokenVerifier.cs` | **Modify** — surface `mission` on auth-token verify result | +| `src/AAuth/Tokens/VerifiedToken*.cs` | **Modify** — expose parsed `mission` | +| `src/AAuth/HttpSig/AAuthSigningHandler.cs` | **Modify** — auto-cover `aauth-mission` when header present (D2) | +| `src/AAuth/HttpSig/AAuthVerifier.cs` | **Modify** (added) — accept `mission` param + validate `aauth-mission` covered component | +| `src/AAuth/Server/Verification/AAuthVerificationMiddleware.cs` | **Modify** (added) — pass `AAuth-Mission` header into the verifier | +| `src/AAuth/Tokens/MissionClaim.cs` | **New** (added) — `{approver, s256}` value carried in tokens | +| `tests/AAuth.Conformance/Missions/MissionClaimTests.cs` | **New** (placed under `Missions/`) | +| `tests/AAuth.Conformance/HttpSignatures/MissionSignedComponentTests.cs` | **New** | + +### Implementation Decisions + +- D2 (resolved): auto-cover `aauth-mission` in `AAuthSigningHandler` when the + header is present; signing method signatures may change as needed. +- D5 (resolved, user-approved): the verifier side (`AAuthVerifier` + + `AAuthVerificationMiddleware`) is extended in Phase 2 so signed mission + requests round-trip; without it every mission-context request would fail + HTTP-signature verification. +- D6 — covered-component ordering (resolved per spec): `aauth-mission` is the + **last** covered component, after `signature-key` (spec §Authorization + Endpoint Request example, mission context). The pre-existing `authorization` + handling (appended after `signature-key`) is left unchanged — re-aligning it + to the spec's `authorization`-before-`signature-key` example is out of Phase 2 + scope. Verifier accepts the optional trailing pair `authorization` then + `aauth-mission`, in that order. + +### Definition of Done + +- [x] `ResourceTokenBuilder` emits `mission` claim when a mission is present (§Resource Token). +- [x] `AuthTokenBuilder` emits `mission` claim when a mission is present (§Auth Token). +- [x] `TokenVerifier` exposes the verified `mission` claim on auth tokens. +- [x] When `AAuth-Mission` header present, `aauth-mission` appears in + `Signature-Input` covered components (§Authorization Endpoint Request). +- [x] Covered-components contract updated; token/signature tests adjusted to match. + +--- + +## Phase 3 — PS token-request params, clarification chat, mission errors + +**Goal:** Complete the PS token endpoint client surface: missing request +parameters, the clarification chat loop, and `mission_terminated` handling. +Fixes G13, G14, G16. + +**Spec:** §Agent Token Request (params `justification`, `login_hint`, `tenant`, +`domain_hint`, `platform`, `device`); §Clarification Chat (`requirement= +clarification`; agent responses: `clarification_response` POST / updated +`resource_token` POST / `DELETE` cancel; round limit; sanitization is PS-side); +§Mission Status Errors (`403 mission_terminated`). + +### Files + +| File | Action | +|------|--------| +| `src/AAuth/Agent/TokenExchangeRequest.cs` | **Modify** — add `Justification`, `LoginHint`, `Tenant`, `DomainHint`, `Platform`, `Device`, `OnClarificationRequired`, `MaxClarificationRounds` | +| `src/AAuth/Agent/TokenExchangeClient.cs` | **Modify** — emit new params; clarification loop; `mission_terminated` classification | +| `src/AAuth/Agent/ClarificationExchange.cs` | **New** — `ClarificationResponse` decision object + respond / update / cancel actions + round tracking | +| `src/AAuth/Agent/AAuthInteractionExceptions.cs` | **Modify** — add `AAuthClarificationCancelledException`, `AAuthClarificationLimitException` | +| `src/AAuth/Headers/ClarificationRequirement.cs` | **New** — parse `{clarification, timeout?, options?}` | +| `src/AAuth/Errors/TokenError.cs` | **Modify** — add `mission_terminated` | +| `src/AAuth/Errors/AAuthMissionTerminatedException.cs` | **New** | +| `tests/AAuth.Conformance/Missions/ClarificationChatTests.cs` | **New** | +| `tests/AAuth.Conformance/Missions/MissionTerminatedTests.cs` | **New** | +| `tests/AAuth.Conformance/Missions/TokenRequestParamsTests.cs` | **New** — covers the six token-request params | + +> **Deviation:** `DeferredPoller.cs` was **not** modified. POST/DELETE to the +> pending URL live in `ClarificationExchange` (its own `HttpClient`), and the +> clarification stop reuses the existing `DeferredPollerOptions.StopWhenAccepted` +> predicate (composed via `ComposePollerOptions`) — see research Part 5, Phase 3. + +### Definition of Done + +- [x] All six token-request params serialized into the POST body (§Agent Token Request). +- [x] `requirement=clarification` parsed into a typed model (question/timeout/options). +- [x] Agent can `clarification_response` POST, updated-`resource_token` POST, and + `DELETE`-cancel against the pending URL (§Agent Response to Clarification). +- [x] Clarification round limit enforced (default 5) (§Clarification Limits). +- [x] `403 mission_terminated` → `AAuthMissionTerminatedException` across PS calls + (§Mission Status Errors). +- [x] New tests pass; existing deferred/interaction tests unaffected. + +--- + +## Phase 4 — PS governance clients + metadata discovery + +**Goal:** Add agent-side clients for `mission_endpoint`, `permission_endpoint`, +`audit_endpoint`, `interaction_endpoint`, with DTOs, and parse the missing +metadata endpoints. Fixes G5–G8, G15, G19. + +**Spec:** §Mission Creation; §Permission Endpoint; §Audit Endpoint; +§Interaction Endpoint; §Person Server Metadata. Audit **requires** a mission; +permission/interaction optional. Reuse the deferred/`202` loop from Phase 3. + +### Files + +| File | Action | +|------|--------| +| `src/AAuth/Discovery/ServerMetadata.cs` | **Modify** — parse `permission_endpoint`, `audit_endpoint` | +| `src/AAuth/Agent/Governance/MissionClient.cs` | **New** — propose/approve (handles `202` review) | +| `src/AAuth/Agent/Governance/PermissionClient.cs` | **New** — `{action,description?,parameters?,mission?}` → granted/denied | +| `src/AAuth/Agent/Governance/AuditClient.cs` | **New** — fire-and-forget `201` (requires mission) | +| `src/AAuth/Agent/Governance/InteractionClient.cs` | **New** — interaction/payment/question/completion | +| `src/AAuth/Agent/Governance/*Request.cs` / `*Response.cs` | **New** — DTOs (MissionProposal, PermissionRequest/Result, AuditRecord, InteractionRequest/Result) | +| `src/AAuth/Agent/Governance/GovernanceExchange.cs` | **New (deviation)** — shared signed-POST + deferred-`202` loop + endpoint origin-pinning; `GovernanceOptions` | +| `tests/AAuth.Conformance/Missions/GovernanceClientTests.cs` | **New** (12 tests) | + +### Definition of Done + +- [x] `ServerMetadata` parses all four governance endpoints (§Person Server Metadata). +- [x] `MissionClient.ProposeAsync` returns an approved `Mission` (verifies `s256`, + handles `202` review/clarification) (§Mission Creation). +- [x] `PermissionClient.RequestAsync` returns granted/denied, honoring + `approved_tools` short-circuit and deferred responses (§Permission Endpoint). +- [x] `AuditClient.RecordAsync` is fire-and-forget, requires a mission, expects + `201` (§Audit Endpoint). +- [x] `InteractionClient` supports all four `type` values incl. `completion` + terminate/continue (§Interaction Endpoint). +- [x] `mission_terminated` surfaces from each client (Phase 3 exception). +- [x] Client tests pass against a stub PS. + +--- + +## Phase 5 — PS server-side governance seams + mission log + +**Goal:** Provide minimal SDK primitives so a PS (e.g. MockPersonServer) can +serve the governance endpoints without hand-rolling parsing: request parsing, +response helpers, store/relay interfaces, and a mission-log seam. Fixes G18, G20 +(per decision D3 — thin seams, not a full PS). + +**Spec:** §PS Governance Endpoints; §Mission Log; §Mission Status Errors; +§Policy Evaluation Points (PS evaluates against mission intent + log). + +### Files + +| File | Action | +|------|--------| +| `src/AAuth/Server/Governance/IMissionStore.cs` | **New** — persist blob bytes + state; `StoredMission` record | +| `src/AAuth/Server/Governance/InMemoryMissionStore.cs` | **New (deviation)** — default in-memory store (mirrors `InMemoryJtiStore`) | +| `src/AAuth/Server/Governance/IPermissionDecider.cs` | **New** — `PermissionDecision` + reason enum + context | +| `src/AAuth/Server/Governance/IAuditSink.cs` | **New** | +| `src/AAuth/Server/Governance/IInteractionRelay.cs` | **New** | +| `src/AAuth/Server/Governance/IMissionLog.cs` | **New** — ordered append; read; prior-consent lookup | +| `src/AAuth/Server/Governance/InMemoryMissionLog.cs` | **New (deviation)** — default in-memory log | +| `src/AAuth/Server/Governance/GovernanceEndpoints.cs` | **New** — request parsers + `mission_terminated` helper | +| `src/AAuth/DependencyInjection/AAuthGovernanceServiceCollectionExtensions.cs` | **New** — `AddAAuthGovernance` registers storage seams | +| `tests/AAuth.Conformance/Missions/GovernanceServerTests.cs` | **New** (17 tests) | + +### Implementation Decisions + +- D3 boundary: SDK = DTO parse + helpers + interfaces; policy/UI in mock. +- `IPermissionDecider` returns a typed decision **with a reason** (in-scope / + prior consent / `approved_tools` / out-of-scope → prompt) so a PS can both act + on it and surface it to UIs; the SDK provides the inputs + reason enum, the PS + owns the policy (§Agent Token Request L385/L784/L828, §Permission L1017). + +### Definition of Done + +- [x] Request parsers for permission/audit/interaction/mission-create map to DTOs. +- [x] `mission_terminated` helper emits spec `403` body (§Mission Status Errors). +- [x] `IMissionStore` stores verbatim blob bytes + `active`/`terminated` state. +- [x] `IMissionLog` appends token/permission/audit/interaction/clarification + entries in order, and supports a prior-consent read keyed by + `(s256, resource, scope)` (§Mission Log, §Agent Token Request L784). +- [x] `IPermissionDecider` is invoked with mission + log context for the consent + decision (§Person Server L385). +- [x] DI registration wires the seams. +- [x] Server-seam tests pass. + +--- + +## Phase 5.5 — Shared deferred transport + governance facade + +**Goal:** Remove the duplication between `TokenExchangeClient` and the Phase 4 +`GovernanceExchange` by extracting a single internal deferred-HTTP transport +(T1 + T2), and hide governance-client construction behind a public facade and a +builder factory so callers don't hand-wire the signed `HttpClient` + four +clients (A + B). No behavioural change — the existing 417 conformance + 371 unit +tests are the regression gate. + +**Spec:** §User Interaction; §Clarification Chat; §Mission Status Errors; +§PS Governance Endpoints; §Person Server Metadata. (Pure refactor — same wire +behaviour, same spec citations as Phases 3–5.) + +**Rationale (from feasibility review):** `GovernanceExchange` and +`TokenExchangeClient` share ~120 lines of identical logic — endpoint origin-pin, +the `202` deferred loop (interaction + clarification), `ComposePollerOptions`, +`BufferBodyAsync` / `ReadJsonBodyAsync` / `ExtractRequirement` / `ResolveLocation`, +the `mission_terminated` reader, and `AddIfPresent`. `TokenExchangeClient` is +never exposed — `AAuthClientBuilder` constructs it internally and runs it behind +a `ChallengeHandler` (`AAuthClientBuilder.cs` ~L495). Governance operations are +*deliberate* (not challenge-driven) so they can't hide behind a handler, but +their construction can be hidden the same way the builder already hides the +token client's `(signedClient, metadata)` pair. + +### Implementation Decisions + +- **D8 (T1+T2 — single transport):** Introduce `internal sealed class + DeferredExchange` (in `AAuth.Agent`) as the one transport: `ResolveEndpointAsync` + + `PostAsync(endpoint, body, DeferredExchangeOptions, ct)` returning the + terminal `HttpResponseMessage` (caller parses + disposes), throwing + `AAuthMissionTerminatedException` on a terminal `403 mission_terminated`. It owns + all the shared helpers and the `AAuth.DeferredPoll` activity. `GovernanceExchange` + is **deleted**; governance clients use `DeferredExchange` directly, adapting + `GovernanceOptions` → `DeferredExchangeOptions`. `TokenExchangeClient.ExchangeAsync` + keeps its token-specific concerns (body builder + capability inference + the + `AAuth.TokenExchange` activity + `access_denied` classification + `auth_token` / + token-error reading) and delegates the transport to `DeferredExchange.PostAsync`. + `access_denied` moves from inside the poll loop to a post-`PostAsync` 403 + classifier (a `403 access_denied` body is not `mission_terminated`, so + `PostAsync` returns it unthrown — order preserved). `TokenExchangeRequest` and + the public `TokenExchangeClient` API are **unchanged**. +- **D9 (A+B — facade + factory):** Add public `AAuthGovernanceClient` bundling + `Mission` / `Permission` / `Audit` / `Interaction` over one signed `HttpClient` + + `MetadataClient` (ctor `(HttpClient signedClient, MetadataClient metadata)`; + the four sub-clients stay public for advanced use). Add + `AAuthClientBuilder.BuildGovernance()` returning an `AAuthGovernanceClient` + built from the **same** exchange pipeline the builder already constructs + (`AAuthClientBuilder.cs` ~L480–L495); factor that `(HttpClient signed, + MetadataClient metadata)` construction into a private helper shared by + `BuildHandler` and `BuildGovernance`. DI (`AddAAuthAgentGovernance`) is + **out of scope** here — deferred until a sample needs it (Phase 6). + +### Files + +| File | Action | +|------|--------| +| `src/AAuth/Agent/DeferredExchange.cs` | **New** — shared transport + `DeferredExchangeOptions` + all shared helpers (absorbs `GovernanceExchange`) | +| `src/AAuth/Agent/Governance/GovernanceExchange.cs` | **Delete** — replaced by `DeferredExchange`; `GovernanceOptions` moves to its own file | +| `src/AAuth/Agent/Governance/GovernanceOptions.cs` | **New** — public `GovernanceOptions` (moved out of the deleted file) | +| `src/AAuth/Agent/TokenExchangeClient.cs` | **Modify** — delegate transport to `DeferredExchange`; keep token-specific body/error/diagnostics | +| `src/AAuth/Agent/Governance/{Mission,Permission,Audit,Interaction}Client.cs` | **Modify** — use `DeferredExchange`; adapt `GovernanceOptions` → `DeferredExchangeOptions` | +| `src/AAuth/Agent/Governance/AAuthGovernanceClient.cs` | **New** — public facade bundling the four clients | +| `src/AAuth/AAuthClientBuilder.cs` | **Modify** — extract shared `(signed HttpClient, MetadataClient)` build helper; add `BuildGovernance()` | +| `tests/AAuth.Conformance/Missions/GovernanceFacadeTests.cs` | **New** — facade construction + `BuildGovernance()` wiring | + +### Definition of Done + +- [x] Single `DeferredExchange` transport; `GovernanceExchange.cs` deleted; no + duplicated deferred-loop / buffer / requirement helpers remain. +- [x] `TokenExchangeClient` delegates transport to `DeferredExchange`; its public + API and wire behaviour are unchanged (`access_denied`, `mission_terminated`, + token-error codes, diagnostics activities all preserved). +- [x] `AAuthGovernanceClient` facade exposes mission/permission/audit/interaction + over one signed client; sub-clients remain public. +- [x] `AAuthClientBuilder.BuildGovernance()` returns a facade wired from the same + signed exchange pipeline as `BuildHandler()` (shared private helper). +- [x] Full conformance (417 → **422** with new facade tests) + unit (371) suites + pass unchanged; new facade tests pass; SDK + full solution build 0/0. + +**Deviations (as built):** + +- The token-only `access_denied` classification and the token-only + fail-fast-without-callback behaviour are preserved through two + `DeferredExchangeOptions` seams rather than living in `TokenExchangeClient`: + `RequireInteractionCallback` (token = `true`, governance = `false`) reproduces + the token-exact "no onInteractionRequired callback" message, and + `OnPolledResponse` (token only) runs the `403 access_denied` classifier *after* + an interaction-branch poll (not after a clarification poll or the + initial/direct response), matching the original placement. +- `ResolveEndpointAsync(personServer, field, ct)` emits generic `'{field}'` + error text that is byte-identical to the original `'token_endpoint'` messages + when `field == "token_endpoint"`. +- The shared signed-channel helper is `BuildSignedChannel(provider, innerHandler)`. + `BuildHandler` passes `new HttpClientHandler()` (preserving the prior exchange + signer's exact inner handler); `BuildGovernance` passes + `_innerHandler ?? new HttpClientHandler()` so tests can inject a stub. +- `BuildGovernance()` requires an explicit signing mode (`_provider`); it does + **not** reconstruct the lazy-refresh token-holder pipeline (that path stays + exclusive to `BuildHandler`). Throws `InvalidOperationException` otherwise. +- `AAuth.DeferredPoll` now also fires for governance polls (additive + observability via the shared `DeferredExchange`, not a wire change). +- DI (`AddAAuthAgentGovernance`) remains out of scope — deferred to Phase 6. + +--- + +## Phase 6 — Samples + +**Goal:** Showcase the end-to-end mission flow across actors. Update +MockPersonServer to a real governance PS and add an agent-side mission demo. + +**Spec:** §Missions; §PS Governance Endpoints; §Call Chaining (mission context); +§Agent Token Request (consent decision: L385, L784, L828); §Permission Endpoint +(`approved_tools`: L1017); the three worked flows in research (single-resource, +multi-resource + permission/audit/interaction, multi-hop chaining). + +Missions are an **optional, orthogonal** governance layer (§Overview L141, L201; +§Missions L397). Existing samples demonstrate valid no-mission flows and are +**not** spec-incorrect; the mission flow is **added alongside** them, not a +rewrite of existing flows. + +### Files + +| File | Action | +|------|--------| +| `samples/MockPersonServer/Program.cs` | **Modify** — serve `mission_endpoint`, `permission_endpoint`, `audit_endpoint`, `interaction_endpoint`; embed `mission` claim in issued auth tokens; compute `s256`; implement the three-gate consent decision and record a decision reason (in-scope / prior consent / `approved_tools` / out-of-scope) in the mission log so samples can display it; expose a minimal "terminate mission" demo hook; maintain mission log via Phase 5 seams | +| `samples/MockPersonServer/README.md` | **Modify** | +| `samples/MissionAgent/` | **New** — dedicated CLI agent: propose mission → operate under it → permission + audit + interaction → completion | +| `samples/Orchestrator/Program.cs` | **Modify** — show mission-governed downstream hop (call chaining) | +| `samples/SampleApp/Components/Pages/Mission.razor` | **New** — golden one-page mission example; visualizes all three consent gates, each labelled **prompt** vs **silent (in scope)** | +| `samples/SampleApp/Components/Pages/Home.razor` | **Modify** — add mission page link/card | +| `samples/GuidedTour/TourOptions.cs` | **Modify** — add `TourMode.Mission` | +| `samples/GuidedTour/TourSession.cs` | **Modify** — multi-step mission plan that drives each gate through **both** outcomes: (1) mission approval prompt; (2) in-scope token request that resolves **silently**; (3) out-of-scope token request that **prompts**; (4) `approved_tools` permission that resolves **silently**; (5) non-pre-approved permission that **prompts**; then audit / interaction / complete | +| `samples/GuidedTour/CodeSnippets.cs` | **Modify** — mission client snippets | +| `samples/GuidedTour/Components/SequenceDiagram.razor` (+ `EntityHighlighter`) | **Modify** — render mission interactions; mark each step **prompt** vs **silent** and show the decision reason (in-scope / prior consent / `approved_tools`) | +| `samples/GuidedTour/playwright-tests/mission.spec.ts` | **New** — drives `TourMode.Mission`; asserts each gate's **prompt** vs **silent** outcome + decision reason | +| `samples/SampleApp/playwright-tests/mission.spec.ts` | **New** — exercises `Mission.razor`; asserts prompt vs silent gate labels | +| `tests/e2e/playwright.config.ts` | **Modify (if needed)** — mission specs reuse existing `guided-tour`/`sample-app` projects + booted backends; add a PS env/policy toggle only if the silent-vs-prompt scenario needs pre-seeded `approved_tools` | +| `tests/e2e/README.md` | **Modify** — document the mission specs | +| `tests/AAuth.Tests/Integration/MissionAgentFlowTests.cs` (or a dedicated integration test project) | **New** — **.NET integration test**: boot MockPersonServer + WhoAmI + MockAgentProvider, run `samples/MissionAgent`, and assert **every consent permutation** (see Consent Test Matrix) plus the `mission_terminated` path | +| `Makefile` | **Modify** — add `demo-mission` target and an `e2e-mission` target | +| `tests/e2e/package.json` | **Modify** — add `test:mission` script (Blazor mission specs) | +| `samples/README.md` | **Modify** — index the mission demo | + +### Implementation Decisions + +- **Test split:** Blazor apps (GuidedTour, SampleApp) are covered by **Playwright + e2e** in `tests/e2e/`; the `MissionAgent` CLI is covered by a **.NET + integration test** under `tests/` (spawns servers + CLI), run via `dotnet test`. +- **Sample shape:** new dedicated `samples/MissionAgent/` (not extending + AgentConsole) for a legible showcase; SampleApp gets a new `Mission.razor` + page; GuidedTour gets a new `TourMode.Mission` with a multi-step plan + (mirroring the federated multi-step style). Existing flows untouched. +- **`make demo` integration:** separate `make demo-mission` target (like + `demo-keycloak`) to keep the default `make demo` bundle uncluttered. +- **Consent decision (three gates)** — the PS prompts the user only when needed, + not on every deferred point (§Agent Token Request L385/L784/L828, §Permission + L1017): + 1. **Mission approval** — always prompt once at mission proposal + (§Mission Creation). + 2. **Token request** — silent when the resource+scope is within the approved + mission intent or matches a remembered prior consent in the mission log; + otherwise prompt (L385, L784, L828). + 3. **Permission request** — silent when the action matches `approved_tools`; + otherwise prompt (L1017). + Prior-consent memory keyed by `(mission s256, resource, scope)`. The decision + is mock PS policy implemented over the Phase 5 `IPermissionDecider` / + `IMissionStore` / mission-log seams — not SDK behavior. +- **Sub-phasing (agreed 2026-06-05):** Phase 6 executes in three committable + sub-phases, each independently buildable + tested: + - **6a — Backend foundation (DONE):** MockPersonServer governance endpoints + `s256` + / mission claim emission + three-gate consent + terminate hook; new + `samples/MissionAgent/` CLI; the 12-row Consent-Matrix .NET integration test. + - **6b — Blazor + e2e (DONE):** SampleApp `Mission.razor` (+ Home link); GuidedTour + `TourMode.Mission` (+ snippets, sequence diagram); the two Playwright specs. + - **6c — Glue (DONE, except deferred hop):** `make demo-mission` / `agent-mission` + (pulled into 6a); READMEs updated (samples index + MockPersonServer governance + section + GuidedTour Mission mode); call-chain samples annotated that mission + governance is optional/orthogonal and omitted (§Call Chaining no-mission path). + **Deferred (2026-06-06, user decision):** the Orchestrator mission-governed + downstream hop is NOT implemented; instead the call-chain samples carry comments + explaining missions are optional and left out of the demo. The existing + Orchestrator already follows the spec's valid "no mission, `iss` is a PS" path. +- **Deterministic consent scripting (agreed 2026-06-05 — option A):** the + integration test drives mission-approval / token / permission outcomes by + extending the existing unsigned `/admin/*` demo pattern (e.g. + `/admin/mission-decision`, `/admin/permission-decision`, plus pre-seeding + `approved_tools` / prior-consent), mirroring today's `/admin/consent`. No + config/convention-encoded policy. + + +#### Consent Test Matrix (CLI integration test) + +The `MissionAgent` integration test MUST cover **all** permutations of the three +gates — both decision outcomes (approve/deny) and both PS paths (prompt/silent): + +| # | Gate | Scenario | Expected | +|---|------|----------|----------| +| 1 | Mission approval | user **approves** the proposed mission | `active` mission, `s256` bound | +| 2 | Mission approval | user **denies** the proposed mission | no mission; agent aborts cleanly | +| 3 | Token request | resource+scope **within** approved mission intent | **silent**, reason = in-scope | +| 4 | Token request | resubmit same `(s256, resource, scope)` after prior consent | **silent**, reason = prior consent | +| 5 | Token request | **out-of-scope** resource/scope → user **approves** | prompt → auth token issued | +| 6 | Token request | **out-of-scope** resource/scope → user **denies** | prompt → access denied | +| 7 | Token request (clarification) | user asks a question; agent responds; user approves | clarification round then issue | +| 8 | Token request (clarification) | agent **cancels** via `DELETE` | pending `410 Gone`; no token | +| 9 | Permission | action **in** `approved_tools` | **silent**, reason = approved_tools | +| 10 | Permission | action **not** pre-approved → user **approves** | prompt → granted | +| 11 | Permission | action **not** pre-approved → user **denies** | prompt → denied | +| 12 | Termination | mission terminated mid-flow, agent retries | `403 mission_terminated` surfaced | + +The PS consent decisions are driven deterministically in the test (scripted +approve/deny + pre-seeded `approved_tools` / prior-consent state) so each row is +reproducible without manual interaction. Each row also asserts the recorded +mission-log **decision reason**. + +### Definition of Done + +- [x] MockPersonServer serves all four governance endpoints (§PS Governance). _(6a)_ +- [x] MockPersonServer embeds `{approver, s256}` in issued auth tokens (§Auth Token). _(6a)_ +- [x] MockPersonServer implements the three-gate consent decision: mission approved + once, then resource/tool access proceeds without re-prompting unless outside + approved scope / `approved_tools` (§Agent Token Request, §Permission Endpoint). _(6a)_ +- [x] MockPersonServer exposes a minimal "terminate mission" hook so the + `mission_terminated` path is exercised end-to-end (§Mission Status Errors). _(6a)_ +- [x] `samples/MissionAgent/` proposes a mission, accesses ≥1 resource under it, + requests a permission, records an audit entry, relays an interaction, and + completes it. _(6a)_ +- [x] SampleApp `Mission.razor` page renders the flow and labels each consent gate + as **prompt** or **silent (in scope)**; Home links to it; existing pages + unchanged. _(6b)_ +- [x] GuidedTour `TourMode.Mission` drives every gate through **both** outcomes + (mission approval prompt; in-scope token silent vs out-of-scope token prompt; + `approved_tools` permission silent vs non-pre-approved permission prompt) and + surfaces the PS decision reason for each; existing modes unchanged. _(6b)_ +- [x] The PS decision reason (in-scope / prior consent / `approved_tools` / + out-of-scope) is visible in both samples so the contrast between prompted + and silent gates is observable. _(6b)_ +- [ ] Orchestrator demonstrates a mission-governed downstream hop (§Call Chaining). + _(6c — DEFERRED by user decision 2026-06-06; call-chain samples instead carry + comments noting mission governance is optional/orthogonal and omitted. The + Orchestrator follows the spec's valid "no mission, `iss` is a PS" path.)_ +- [x] `make demo-mission` boots the mission demo; existing `make demo` unchanged. + _(pulled forward from 6c; also added `agent-mission` runner)_ +- [x] New GuidedTour + SampleApp mission Playwright **e2e** specs pass under the + existing `guided-tour`/`sample-app` projects; existing specs still pass. + _(6b — full suite 29 passed / 1 skipped locally)_ +- [x] **.NET integration test** for the `MissionAgent` CLI covers **all 12 rows** + of the Consent Test Matrix (every gate × approve/deny × prompt/silent), + including clarification and `mission_terminated`, each asserting the + recorded decision reason. _(6a — `MissionAgentFlowTests`, 12/12)_ +- [x] `make e2e` (Blazor) and `dotnet test` (CLI integration) green locally. + _(CLI integration 12/12; Blazor e2e full suite 29 passed / 1 skipped locally; CI not separately run)_ +- [x] Sample READMEs updated. _(MissionAgent README shipped in 6a; in 6c: + samples/README.md index adds MissionAgent + mission Quick Start, + MockPersonServer README adds the governance-endpoints section, + GuidedTour README adds Mission mode; call-chain samples (Orchestrator + Program.cs, SampleApp CallChain.razor) annotated that mission governance + is optional/orthogonal and intentionally omitted, per §Call Chaining + no-mission path)_ + +#### Phase 6a additions (spec-driven, beyond the original file list) + +- **Mission-aware resource (SDK):** `AAuthMissionHeader.TryParseStructured`, + `ChallengeOptions.MissionAware`, and `AAuthChallengeMiddleware` copying the + parsed `{approver, s256}` into `ResourceTokenBuilder.Mission` so a resource can + surface the mission claim in the resource token it issues (§Terminology, + §Auth Token). Demonstrated by WhoAmI `/jwt/mission`. Covered by +3 conformance + tests (`ChallengeMiddlewareTests`, total 425). +- **Interactive mission-creation consent screen:** `/mission` defers (202 + + interaction) to a real browser consent screen when running interactively + (`MissionConsentScript.InteractiveBrowser`), via the same deferred path the + token/permission gates use (SDK `MissionClient.ProposeAsync` already routes + through `DeferredExchange`). The PS `/interaction` page now renders all three + consent screens — mission creation (description + tools), out-of-scope token + (mission + tools + resource/scope), and out-of-tool permission (mission + + tools + action) — keeping the demo faithful to §Mission Creation / + §Permission Endpoint (`action` per-call vs mission `approved_tools`). + Scripted mode (the 12-row test) is unaffected (`InteractiveBrowser = false`). + +#### Phase 6b amendments (2026-06-06, spec-driven, added mid-phase) + +These refine the consent UX and **add the missing out-of-mission scope gate**. +The original Phase 6 plan (file list, line "out-of-scope token request that +**prompts**") always intended a prompted **token/scope** gate, but 6b shipped +only the prompted **tool** gate (`delete_inbox`). These steps close that gap so +both halves of the spec's gate model — a prompted *scope* (§Agent Token Request +gate 3) **and** a prompted *tool* (§Permission Endpoint) — are demonstrated. + +**Spec grounding:** + +- **Tools are declared; scopes are evaluated** (§Mission Creation L1233 — proposal + is `description` + optional `tools` only, no scopes; §Mission Approval L1299–1303 + — blob carries `approved_tools`, never scopes). The mission proposal lists no + scopes; the PS determines required scopes **per request, over the mission's + whole life** (§Scopes L1793 "The PS evaluates requested scopes against mission + context"; §Concurrent Token Requests L828 "some requests may be resolved + without user interaction … while others may require consent"). +- **Out-of-mission scope ⇒ prompt, not auto-deny** (§Agent Token Request gate 3; + §Scopes L1793). Only an explicit user deny (or `mission_terminated`, gate 1) + yields `access_denied`. + +**Decisions (agreed 2026-06-06 via interview):** + +- **D6 — New mission-aware endpoint + new scope (not reuse `whoami:admin`).** + WhoAmI gains a second mission-aware endpoint guarded by a **new** resource + scope so the out-of-mission scenario is clearly distinct from the existing + non-mission `/jwt/admin` step-up demo. Proposed: scope `whoami:history` + ("See your full account/profile history") at endpoint `/jwt/history`, wired + with `ChallengeForMission(ScopeWhoamiHistory)`. Under the seeded inbox mission + (in-scope = `whoami` only), requesting `whoami:history` falls outside the + mission → PS prompts (gate 3). _(final names confirmed at implementation.)_ +- **D7 — Add as a NEW gate (5 gates total), existing gates unchanged.** Final + order: (1) mission approval **PROMPT** → (2) `whoami` token **SILENT** (in + scope) → (3) `whoami:history` token **PROMPT** (out-of-mission scope) → (4) + `send_email` tool **SILENT** (pre-approved) → (5) `delete_inbox` tool + **PROMPT** (not pre-approved). +- **D8 — "Agent console app" = `samples/MissionAgent/`** (the CLI), not + `samples/AgentConsole/` (which has no mission support and no mermaid). Its + README sequence diagram gains the new out-of-mission scope consent block. +- **D9 — Consent-screen UX refinements (already applied in 6b):** PS + `/interaction` shows **scopes and tools as separate lists**; a spec-grounded + **tool (local) vs scope (remote)** definition box; the **creation screen lists + no scopes** (only a note that the PS determines them per-request from the + mission description); post-creation gates relabel the scope list **"Granted so + far"** (accrual), with empty state "nothing yet — this is the first request". + +### Amendment files + +| File | Action | +|------|--------| +| `samples/WhoAmI/Program.cs` | **Modify** — add scope `whoami:history` (+ `scope_descriptions` entry, scope policy) and a mission-aware endpoint `/jwt/history` via `ChallengeForMission`; exclude `/jwt/history` from the baseline `/jwt` branch; list it in the index payload | +| `samples/WhoAmI/README.md` | **Modify** — document the new scope + endpoint | +| `samples/SampleApp/Components/Pages/Mission.razor` | **Modify** — insert the out-of-mission **scope** gate (gate 3) between the silent `whoami` token and the tool gates; client + resource panels show `/protected_endpoint` requesting the elevated scope; 5-gate narrative | +| `samples/GuidedTour/TourSession.cs` | **Modify** — extend `MissionPlan` with an out-of-mission scope token cycle (challenge → 202 PROMPT → approve → poll → exchange); renumber steps + approval/poll constants | +| `samples/GuidedTour/CodeSnippets.cs` | **Modify** — add the out-of-mission scope snippet | +| `samples/GuidedTour/Components/Pages/Tour.razor` | **Modify** — mission lane/flow text reflects the new scope gate | +| `samples/MockPersonServer/Program.cs` | **Modify (done in 6b amendments)** — separate scope/tool lists, definition box, creation screen drops scopes + adds determined-per-request note, "Granted so far" relabel | +| `samples/MissionAgent/Program.cs` | **Modify** — add a step that requests the out-of-mission scope under the mission (prompted token gate) | +| `samples/MissionAgent/README.md` | **Modify** — add the out-of-mission scope consent block to the mermaid sequence diagram + the gate table / "Scope (remote) vs tool (local)" prose | +| `docs/concepts.md` | **Modify (done)** — tools declared vs scopes evaluated; per-request, lifetime-long scope determination | +| `samples/SampleApp/playwright-tests/mission.spec.ts` | **New/Modify** — assert the 5th gate (out-of-mission scope **prompt**) | +| `samples/GuidedTour/playwright-tests/mission.spec.ts` | **New/Modify** — assert the out-of-mission scope **prompt** step | + +### Amendment Definition of Done + +- [x] WhoAmI exposes a new resource scope (`whoami:elevated_scope`) on a + mission-aware endpoint (`/jwt/mission/elevated`); `scope_descriptions` + + scope policy updated; baseline `/jwt/mission` branch excludes it + (§Resource Metadata, §Scopes). _(6b — final names landed as + `whoami:elevated_scope` / `/jwt/mission/elevated`, not the proposed + `whoami:history` / `/jwt/history`; D6 deferred names to implementation)_ +- [x] Under the seeded inbox mission, requesting the new scope is **out of + mission** → PS **prompts** (gate 3), and on approval issues the auth token; + the granted scope then shows under "Granted so far" on later screens + (§Agent Token Request, §Scopes L1793). _(6b)_ +- [x] SampleApp `Mission.razor` shows **5 gates** incl. the out-of-mission scope + prompt, distinct from the out-of-tool permission prompt. _(6b)_ +- [x] GuidedTour `TourMode.Mission` drives the out-of-mission scope cycle + (challenge → prompt → approve → exchange) with the decision reason visible. + _(6b)_ +- [x] `samples/MissionAgent/` requests the out-of-mission scope under the mission; + its README mermaid diagram + gate prose include the new consent block. _(6b)_ +- [x] Consent-screen UX refinements (D9) reflected and live-verified. _(6b)_ +- [x] Playwright specs assert the new prompted-scope gate; existing specs pass. + _(6b)_ + +--- + +## Phase 7 — Docs + +**Goal:** Rewrite the stale missions doc and add mission/PS-governance docs +reflecting the implemented surface. Separate phase from samples per the agreed +workflow. + +**Spec:** §Missions; §Mission Approval; §Mission Log; §Policy Evaluation Points; +§Why Missions Are Not a Policy Language; §Permission/Audit/Interaction Endpoints; +§Clarification Chat; §Mission Status Errors. + +> **Scope revised (2026-06-06).** A read-only audit of the shipped API against +> the existing docs (research.md "Phase 7 — doc audit") showed the original +> six-file list under-covered what Phases 1–6 actually built. `docs/advanced/ +> missions.md` describes a **non-existent** model (`Mission.Id`/`Status`/ +> `Requirements`/`FromJson`, `AAuthMissionHeader.Format(missionId)`), and the +> entire **agent-side governance client** surface, **clarification chat**, +> **server governance seams**, **`mission_terminated`**, **`ChallengeOptions. +> MissionAware`**, and **`AddAAuthGovernance`** have **zero** doc coverage. The +> file list and DoD below are expanded accordingly. `docs/concepts.md` (the +> tools-declared-vs-scopes-evaluated split) and `docs/workflows/call-chaining.md` +> (mission forwarding via `WithCallChaining`) were already corrected in Phase 6 +> and need only cross-link/index touch-ups. + +### Implemented surface the docs must match (audited 2026-06-06) + +- **Mission model** (`AAuth.Agent`): `Mission { Approver, Agent, ApprovedAt, + Description, ApprovedTools, Capabilities, S256, RawBytes, State }`, + `FromApprovalBytes`, `VerifyS256`, `ComputeS256`; `MissionState + { Active, Terminated }`; `MissionTool(Name, Description?)`. +- **Header / claim**: `AAuthMissionHeader.FormatStructured(approver, s256)` + + `TryParseStructured(...)`; `MissionClaim(Approver, S256)` in tokens. +- **Agent governance clients** (`AAuth.Agent.Governance`): facade + `AAuthGovernanceClient { Mission, Permission, Audit, Interaction }`; + `MissionClient.ProposeAsync`; `PermissionClient.RequestAsync` (missionless + + mission-scoped pre-approved-tool short-circuit); `AuditClient.RecordAsync`; + `InteractionClient.SendAsync` / `RelayInteractionAsync`; DTOs `MissionProposal`, + `PermissionRequest`/`PermissionResult`/`PermissionGrant`, `AuditRecord`, + `InteractionRequest`/`InteractionResult`/`InteractionType`, `GovernanceOptions`. +- **Builder**: `AAuthClientBuilder.BuildGovernance()`; + `MissionForwardingHandler`; `WithCallChaining(...)`. +- **Clarification chat**: `ClarificationExchange` (rounds, `DefaultMaxRounds=5`), + `ClarificationResponse` (`Respond`/`Update`/`Cancel`), `ClarificationRequirement`. +- **Token-exchange mission params** (`TokenExchangeRequest`): `Justification`, + `LoginHint`, `Tenant`, `DomainHint`, `Platform`, `Device`, + `OnClarificationRequired`, `MaxClarificationRounds`. +- **Errors**: `AAuthMissionTerminatedException` (`mission_terminated`). +- **Mission-aware resource**: `ChallengeOptions.MissionAware`. +- **Server seams** (`AAuth.Server.Governance`): `IMissionStore`/`StoredMission`/ + `InMemoryMissionStore`; `IPermissionDecider`/`PermissionDecisionContext`/ + `PermissionDecision`/`PermissionOutcome`/`PermissionDecisionReason`; + `IAuditSink`; `IInteractionRelay`/`InteractionRelayResult`; `IMissionLog`/ + `MissionLogEntry`/`MissionLogEntryKind`/`InMemoryMissionLog`; + `GovernanceEndpoints` parsers. DI: `AddAAuthGovernance`. + +### Files + +| File | Action | +|------|--------| +| `docs/advanced/missions.md` | **Rewrite** — replace the stale model with the spec blob (`Approver`/`Agent`/`ApprovedAt`/`Description`/`ApprovedTools`/`Capabilities`/`S256`/`RawBytes`/`State`), `s256` identity, two states, `FromApprovalBytes`/`VerifyS256`, `AAuthMissionHeader.FormatStructured`/`TryParseStructured`, the `MissionClaim` binding chain through resource→auth tokens, and `ChallengeOptions.MissionAware`; fix the lifecycle mermaid (agent-proposes-to-PS, not resource-proposes) | +| `docs/advanced/mission-governance-clients.md` | **New** — agent-side `AAuthGovernanceClient` facade + `Mission`/`Permission`/`Audit`/`Interaction` clients, their DTOs, `BuildGovernance()`, and the tool-declared (permission endpoint, `approved_tools` short-circuit) vs scope-evaluated split | +| `docs/advanced/clarification-chat.md` | **New** — `ClarificationRequirement`, `ClarificationExchange` rounds + `DefaultMaxRounds`, `ClarificationResponse` (`Respond`/`Update`/`Cancel`), the token-exchange `OnClarificationRequired`/`MaxClarificationRounds` hooks (§Clarification Chat) | +| `docs/server/mission-governance.md` | **New** — PS as contextual policy point; server seams `IMissionStore`/`IMissionLog`/`IPermissionDecider`/`IAuditSink`/`IInteractionRelay`, `GovernanceEndpoints` parsers, the three-gate decision + reasons, prior-consent lookup, `mission_terminated` (§PS Governance, §Mission Log, §Why Missions Are Not a Policy Language) | +| `docs/workflows/mission-governed-access.md` | **New** — end-to-end walkthrough: propose → operate (silent in-scope + prompted out-of-mission scope) → permission (silent tool + prompted action) → audit → interaction → completion, with the binding chain | +| `docs/server/token-issuance.md` | **Modify** — add `s256` verify + `mission` claim emission/echo on resource + auth tokens | +| `docs/server/challenge-middleware.md` | **Modify** — document `ChallengeOptions.MissionAware` (mission-aware resource copies the `AAuth-Mission` claim into the resource token) | +| `docs/advanced/error-handling.md` | **Modify** — add `AAuthMissionTerminatedException` (`mission_terminated`) | +| `docs/reference/dependency-injection.md` | **Modify** — add `AddAAuthGovernance` (server seams) + governance-client wiring | +| `docs/workflows/call-chaining.md` | **Modify (light)** — cross-link mission forwarding to the new mission docs (forwarding itself already accurate) | +| `docs/concepts.md` | **Modify (light)** — cross-link the new mission docs (tools-vs-scopes split already corrected in Phase 6) | +| `docs/README.md` | **Modify** — index the new docs; add API-Map rows for `AAuthGovernanceClient`, the four governance clients, clarification types, and the server seams | + +### Definition of Done + +- [x] `docs/advanced/missions.md` matches the implemented model (no stale + `Id`/`Status`/`Requirements`/`FromJson`/`Format(missionId)`; correct blob + fields, two states, `s256`, structured header, binding chain). +- [x] Agent-side governance clients doc covers the facade + four clients + DTOs + + `BuildGovernance()`, with a runnable propose→permission→audit→interaction + example. +- [x] Clarification-chat doc covers requirement parsing, the round loop + + `DefaultMaxRounds`, and `Respond`/`Update`/`Cancel` (§Clarification Chat). +- [x] Server governance doc explains the deterministic-vs-contextual split + (§Why Missions Are Not a Policy Language) and documents every seam + + `mission_terminated`. +- [x] Walkthrough doc covers create → operate (silent + prompted) → permission + (silent + prompted) → audit → interaction → completion, with the binding chain. +- [x] Touch-ups landed: `token-issuance.md` (`s256`/mission claim), + `challenge-middleware.md` (`MissionAware`), `error-handling.md` + (`mission_terminated`), `dependency-injection.md` (`AddAAuthGovernance`). +- [x] All doc code samples compile against the shipped Phase 1–6 API + (exact type/member names). +- [x] docs index/README updated; API-Map rows added; cross-links valid. + +--- + +## Phase 8 — Review + +**Goal:** Validate each logical change set against the spec and the plan with a +dedicated subagent per set, then remediate findings. + +### Review subagents (one per change set) + +| # | Change set | Scope | +|---|------------|-------| +| R1 | Mission model + `s256` (Phase 1) | spec §Mission Approval/Management fidelity | +| R2 | Token binding + HTTPSig (Phase 2) | §Resource/Auth Token, §signed `aauth-mission` | +| R3 | Token params + clarification + errors (Phase 3) | §Agent Token Request, §Clarification Chat, §Mission Status Errors | +| R4 | Governance clients + metadata (Phase 4) | §Permission/Audit/Interaction, §PS Metadata | +| R5 | Server seams + mission log (Phase 5) | §PS Governance, §Mission Log | +| R6 | Samples (Phase 6) | flows run end-to-end; spec-faithful | +| R7 | Docs (Phase 7) | accuracy vs implemented API + spec | + +### Definition of Done + +- [x] Each review subagent produces severity-graded findings with spec citations. + _(R1–R7 run 2026-06-06; R7 PASS, R1–R6 PASS-WITH-NITS — see research Phase 8.)_ +- [x] All critical/high findings remediated or explicitly deferred (with rationale). + _(Remediated: removed non-spec `Capabilities.Mission`, fixed `§14.1` + citations, added untrusted-Markdown remark to `Mission.Description`. + Deferred with rationale: R3 pre-existing `capabilities` body param — + flagged to user; R4 lenient audit status codes; R2/R4 test-coverage gaps; + R5 dev-grade in-memory store growth.)_ +- [x] Full solution builds; `AAuth.Tests` + `AAuth.Conformance` green. + _(Post-remediation: build 0/0, unit 383, conformance 425.)_ +- [x] e2e mission flow green. _(guided-tour + sample-app mission specs: 4 passed.)_ +- [x] research.md updated with any spec/behavior corrections discovered. + _(Phase 8 section added.)_ + +--- + +## Out of Scope + +| Item | Reason | +|------|--------| +| Full AS implementation in SDK | SDK ships builders + verification; AS stays in mock/Keycloak | +| Mission revocation / delegation-tree queries / admin APIs | Spec defers to a companion specification (§Mission Management) | +| Payment settlement (x402/MPP) beyond surfacing `402` | Out of AAuth core scope; already surfaced as exception | +| Cross-PS agent correlation / multi-device regrouping | PS-side concern per bootstrap spec; not SDK | +| Pairwise `sub` directed-identifier generation strategy | Existing PS-asserted work; not mission-specific. Mock reuses its existing `sub` generation as-is; the `mission` claim is additive alongside it | diff --git a/.agent/plans/2026-06-05-missions-ps-governance/research.md b/.agent/plans/2026-06-05-missions-ps-governance/research.md new file mode 100644 index 0000000..1e733ae --- /dev/null +++ b/.agent/plans/2026-06-05-missions-ps-governance/research.md @@ -0,0 +1,876 @@ +# Missions & PS Governance — Research + +## Problem Statement + +The AAuth protocol defines **missions** (scoped authorization contexts for agent +governance) and positions the **Person Server (PS)** as the *contextual* policy +evaluation point — distinct from the deterministic policy enforced by resources +and access servers. The .NET SDK (`src/AAuth/`), samples (`samples/`), and docs +(`docs/`) implement only fragments of this surface, and the central `Mission` +model is materially divergent from the specification. + +This document captures the spec model, the current SDK/sample/doc state, and the +gap inventory. It contains **no** implementation steps — those live in +[implementation-plan.md](implementation-plan.md). + +## Source Documents + +| Document | Location | Relevant Sections | +|----------|----------|-------------------| +| AAuth Protocol | `aauth-spec/draft-hardt-oauth-aauth-protocol.md` | §Policy Evaluation Points; §Agent Governance; §Missions (overview + normative); §PS Governance Endpoints; §Person Server; §PS Token Endpoint; §Clarification Chat; §Permission Endpoint; §Audit Endpoint; §Interaction Endpoint; §Resource Token; §Auth Token; §Upstream Token Verification; §Call Chaining; §Person Server Metadata; rationale (Why Missions Are Not a Policy Language / Two States) | +| AAuth Bootstrap | `aauth-spec/draft-hardt-aauth-bootstrap.md` | PS lazy user-binding on first interaction; `ps` claim | +| Upcoming changes | `aauth-spec/upcoming-changes-02.md` | `capabilities` in PS token body; `prompt`/`provider_hint` params | + +> Spec section anchors are cited by `{#anchor}` name and approximate line where a +> stable anchor is absent. Line numbers reference the current revision of +> `draft-hardt-oauth-aauth-protocol.md` and may drift on spec updates. + +--- + +## Part 1 — Spec Model + +### 1.1 Policy Evaluation Points (§Policy Evaluation Points, ~L380) + +Policy is **distributed**; no single party is the decision point. Each of the +four server roles re-evaluates the agent's activity from its own vantage point, +and token lifetimes provide a natural re-evaluation cadence: + +- **Agent Provider** — issues/refuses agent tokens (device posture, attestation). +- **Person Server** — decides whether to issue an auth token for a resource/scope + based on **user consent** and, under a mission, the **mission intent + log** + against the PS governance policy. +- **Access Server** — decides issuance on behalf of the resource (resource policy, + PS-provided claims, deferred requirements). +- **Resource** — *decides what is required* when issuing a resource token, and + *enforces* the auth token at access time. + +### 1.2 The Mission as Contextual Governance (§Why Missions Are Not a Policy Language, ~L2789) + +The spec deliberately separates two authorization kinds: + +- **Deterministic policy** — scopes, resource tokens, AS policy. Machine-evaluable. +- **Contextual governance** — missions, justifications, clarification at the PS. + *Not* machine-evaluable; concentrated at the PS, the only party with the mission + content, the user relationship, and the full action history. + +Consequence: mission **content never leaves the PS**. Only the mission **hash** +(`s256`) travels in tokens/headers. Distributing mission content would be "a +privacy leak and a false promise of enforcement." + +### 1.3 Mission Object & Identity (§Mission Approval, ~L1259) + +The approved **mission blob** is JSON: + +| Field | Req? | Meaning | +|-------|------|---------| +| `approver` | MUST | HTTPS URL of approving entity (currently always the PS) | +| `agent` | MUST | Agent identifier `aauth:local@domain` | +| `approved_at` | MUST | ISO 8601 approval timestamp (makes `s256` globally unique) | +| `description` | MUST | Markdown describing approved scope | +| `approved_tools` | MAY | `[{name, description}]` usable without per-call permission | +| `capabilities` | MAY | e.g. `["interaction","payment"]` the PS can provide; agent unions into `AAuth-Capabilities` | + +**Identity** — `s256` = base64url(SHA-256(**exact approved response body bytes**)). +The agent MUST store the body bytes verbatim — *no re-serialization* — and +verifies by recomputing over those bytes (§Mission Approval, ~L1275). + +### 1.4 Mission Lifecycle + +- **Creation** (§Mission Creation, ~L1228): agent POSTs `{description, tools}` to + the PS `mission_endpoint` (signed, agent token via `Signature-Key: sig=jwt`). + PS MAY return `202` for human review + clarification; approved blob MAY differ + from the proposal. +- **States** (§Mission Management, ~L1322): exactly **two** — `active`, + `terminated`. No suspended state (§Why Missions Have Only Two States, ~L2805). +- **Errors** (§Mission Status Errors, ~L1331): a request referencing a + non-active mission → `403` `{error:"mission_terminated", mission_status:"terminated"}`. +- **Completion** (§Mission Completion, ~L1318): agent sends `type=completion` + to the interaction endpoint with a summary; user accepts (terminate) or + follows up (continues). +- **Mission Log** (§Mission Log, ~L1310): ordered record of *all* agent↔PS + interactions (token requests + justifications, permission req/resp, audit + records, interaction requests, clarification chats). PS-maintained. + +### 1.5 Cryptographic Binding Chain + +Mission binding is **by hash reference**, layered on top of **key-bound +proof-of-possession** and the **`act` delegation chain**: + +1. Agent in mission context adds `aauth-mission` to the **signed** HTTPSig + covered components (§Authorization Endpoint Request, ~L619). The `s256` + reference is thus covered by the agent's signature. +2. Mission-aware **resource** embeds `{approver, s256}` as the `mission` claim in + the **resource token** it signs (§Resource Token Structure, ~L780). Resource + token also binds `agent_jkt` (RFC 7638 thumbprint). +3. PS/AS embeds `{approver, s256}` as the `mission` claim in the **auth token** + it signs (§Auth Token Structure, ~L1560). Auth token binds `cnf.jwk` (PoP) + and `act` (RFC 8693 actor chain). +4. **Delegation**: in call chaining the PS nests the upstream `act` inside a new + `act` identifying the intermediary (§Upstream Token Verification, ~L1621), + preserving the full chain for downstream authorization decisions. + +**Integrity & provenance are cryptographic; appropriateness is a PS policy +decision** — the resource embeds the mission reference it received but does not +re-evaluate mission fitness. Only the PS resolves `s256` → content against the +mission log. + +### 1.6 PS Endpoints (§Person Server, ~L810) + +| Endpoint | Spec | Purpose | Mission? | +|----------|------|---------|----------| +| `token_endpoint` | §PS Token Endpoint ~L814 | Exchange resource token → auth token; consent; three/four-party | optional | +| `mission_endpoint` | §Mission Creation ~L1228 | Propose/approve missions | — | +| `permission_endpoint` | §Permission Endpoint ~L1013 | Pre-action governance for non-resource actions (tool calls) | optional | +| `audit_endpoint` | §Audit Endpoint ~L1077 | Fire-and-forget action logging (`201`) | **required** | +| `interaction_endpoint` | §Interaction Endpoint ~L1131 | Relay interaction/payment/question/completion to user | optional | + +**Token request params** (§Agent Token Request, ~L830): `resource_token` (req), +`upstream_token`, `justification`, `login_hint`, `tenant`, `domain_hint`, +`platform`, `device`. `capabilities`/`prompt` per upcoming-changes-02. + +**Clarification chat** (§Clarification Chat, ~L906): PS returns `202` +`requirement=clarification` with `{clarification, timeout?, options?}`. Agent +responds with one of: `clarification_response` POST, updated `resource_token` +POST, or `DELETE` to cancel. Round limit recommended ≤5; agent responses are +untrusted and MUST be sanitized by the PS before display. + +**Permission** (§Permission Endpoint): POST `{action, description?, parameters?, +mission?}` → `{permission: granted|denied, reason?}` or deferred. `approved_tools` +short-circuit the call. **Audit** (§Audit Endpoint): POST `{mission(req), +action, description?, parameters?, result?}` → `201`. **Interaction** +(§Interaction Endpoint): POST `{type, description?, url?, code?, question?, +summary?, mission?}`; `question` → `{answer}`, `completion` → terminate/continue. + +**PS Metadata** (§Person Server Metadata, ~L2199): publishes the optional +`mission_endpoint`, `permission_endpoint`, `audit_endpoint`, +`interaction_endpoint`. + +--- + +## Part 2 — Current SDK State + +### 2.1 What works (keep) + +| Capability | Evidence | Spec | +|------------|----------|------| +| `AAuth-Mission` structured header format | `AAuthMissionHeader.FormatStructured` ([Agent/Mission.cs](../../../src/AAuth/Agent/Mission.cs)) | §AAuth-Mission Header | +| Mission forwarding on downstream calls | [Agent/MissionForwardingHandler.cs](../../../src/AAuth/Agent/MissionForwardingHandler.cs); wired in [AAuthClientBuilder.cs](../../../src/AAuth/AAuthClientBuilder.cs) L556 | §Call Chaining | +| `act` chain build/read/validate | `AuthTokenBuilder.UpstreamAct`, [Tokens/ActChainBuilder.cs](../../../src/AAuth/Tokens/ActChainBuilder.cs), [Tokens/ActChainReader.cs](../../../src/AAuth/Tokens/ActChainReader.cs), [Tokens/UpstreamTokenValidator.cs](../../../src/AAuth/Tokens/UpstreamTokenValidator.cs); depth limit in [Tokens/TokenVerifier.cs](../../../src/AAuth/Tokens/TokenVerifier.cs) | §Upstream Token Verification | +| Deferred `202` loop, interaction, Retry-After/Prefer, `402`, polling errors | [Agent/DeferredPoller.cs](../../../src/AAuth/Agent/DeferredPoller.cs), [Agent/TokenExchangeClient.cs](../../../src/AAuth/Agent/TokenExchangeClient.cs) | §User Interaction; §Deferred Responses | +| `mission.approver` constraint on resource token | `TokenVerifier.VerifyResourceToken` (~L512) when `expectedApprover` supplied | §Resource Token Verification step 7 | +| Call-chaining router (mission.approver → PS) | [Server/CallChaining/CallChainingRouter.cs](../../../src/AAuth/Server/CallChaining/CallChainingRouter.cs) | §Call Chaining | +| PS metadata **emission** of all 4 governance endpoints | [Server/Metadata/AAuthPersonServerMetadataOptions.cs](../../../src/AAuth/Server/Metadata/AAuthPersonServerMetadataOptions.cs), [Server/Metadata/WellKnownEndpoints.cs](../../../src/AAuth/Server/Metadata/WellKnownEndpoints.cs) L178-184 | §Person Server Metadata | + +### 2.2 Gap Inventory + +Severity: **C** critical (incorrect/non-compliant behavior), **H** high (missing +core capability), **M** medium (missing convenience / ecosystem coverage). + +| # | Sev | Gap | Spec | Current state | +|---|-----|-----|------|---------------| +| G1 | C | `Mission` model uses 4 states `pending/approved/denied/completed` | §Mission Management (2 states) | [Agent/Mission.cs](../../../src/AAuth/Agent/Mission.cs) L17 | +| G2 | C | `Mission` missing required blob fields `approver`,`agent`,`approved_at`; carries non-spec `Id`,`Requirements`,`StatusUrl`,`InteractionUrl` | §Mission Approval | Agent/Mission.cs L12-44 | +| G3 | C | `Mission.FromJson` parses `mission_id` (throws if absent), wrong keys | §Mission Approval | Agent/Mission.cs L32-43 | +| G4 | C | No `s256` compute over exact body bytes; no verbatim byte storage; no verify | §Mission Approval ~L1275 | absent | +| G5 | H | No `mission_endpoint` client (propose/approve, 202 review) | §Mission Creation | absent | +| G6 | H | No `permission_endpoint` client | §Permission Endpoint | absent | +| G7 | H | No `audit_endpoint` client (fire-and-forget) | §Audit Endpoint | absent | +| G8 | H | No `interaction_endpoint` client (relay/question/completion) | §Interaction Endpoint | absent | +| G9 | H | `ResourceTokenBuilder` cannot emit `mission` claim | §Resource Token Structure ~L780 | [Tokens/ResourceTokenBuilder.cs](../../../src/AAuth/Tokens/ResourceTokenBuilder.cs) | +| G10 | H | `AuthTokenBuilder` cannot emit `mission` claim (only `AdditionalClaims`) | §Auth Token Structure ~L1560 | [Tokens/AuthTokenBuilder.cs](../../../src/AAuth/Tokens/AuthTokenBuilder.cs) | +| G11 | H | `TokenVerifier.VerifyAuthToken` does not surface `mission` claim | §Auth Token | TokenVerifier.cs L161-268 | +| G12 | H | HTTPSig does not auto-cover `aauth-mission` when header present | §Authorization Endpoint Request ~L619 | [HttpSig/AAuthSigningHandler.cs](../../../src/AAuth/HttpSig/AAuthSigningHandler.cs) L40 (fixed base components) | +| G13 | H | Clarification chat fully absent (parse, response POST, updated-request POST, DELETE cancel, round limit) | §Clarification Chat | DeferredPoller is GET-only | +| G14 | H | Token request missing params `justification`,`login_hint`,`tenant`,`domain_hint`,`platform`,`device` | §Agent Token Request ~L830 | [Agent/TokenExchangeRequest.cs](../../../src/AAuth/Agent/TokenExchangeRequest.cs) | +| G15 | H | `ServerMetadata` does not parse `permission_endpoint`/`audit_endpoint` | §Person Server Metadata | [Discovery/ServerMetadata.cs](../../../src/AAuth/Discovery/ServerMetadata.cs) L43-44 | +| G16 | H | No `mission_terminated` error code / typed exception / handling | §Mission Status Errors | not in [Errors/TokenError.cs](../../../src/AAuth/Errors/TokenError.cs) | +| G17 | M | No `capabilities` union (mission blob ∪ agent) into `AAuth-Capabilities` | §Mission Approval; §AAuth-Capabilities | [Agent/AAuthCapabilitiesHeader.cs](../../../src/AAuth/Agent/AAuthCapabilitiesHeader.cs) format/parse only | +| G18 | M | No PS-side governance handlers (mission/permission/audit/interaction) or DI seams | §PS Governance Endpoints | absent; MockPersonServer hand-rolls partial flows | +| G19 | M | No governance DTOs (Permission/Audit/Interaction/MissionCreate req+resp) | §PS Governance Endpoints | absent | +| G20 | M | No mission-log abstraction (store seam) | §Mission Log | absent | + +### 2.3 SDK role scope (context for plan) + +The SDK is **client/agent + resource-verification** focused; it ships token +**builders** but not full PS/AS servers. [samples/MockPersonServer/Program.cs](../../../samples/MockPersonServer/Program.cs) +hand-rolls token issuance and consent UI (`GET /interaction`, `POST +/interaction/{approve,deny}`) but no spec governance endpoints, no mission +extraction, no `s256`, no mission claim in issued tokens. Server-side governance +helpers (G18/G19/G20) should follow the existing pattern: thin SDK primitives +(DTOs + parse/format + optional minimal endpoint mappers / DI seams) that the +mock servers consume, rather than a full PS implementation. + +--- + +## Part 3 — Samples & Docs State + +### 3.1 Samples + +0 of 9 samples demonstrate mission creation, permission, audit, interaction +relay, or completion. Orchestrator forwards the `AAuth-Mission` header only. + +| Sample | Mission-aware | Governance demo | +|--------|---------------|-----------------| +| WhoAmI (resource) | no | no | +| Orchestrator (call chain) | header forward only | no | +| MockPersonServer | no mission claim/s256 | consent UI only | +| MockAgentProvider / MockAccessServer | n/a | no | +| GuidedTour / SampleApp / AgentConsole / LiveWhoAmITest | no | no | + +### 3.2 Docs + +| File | Status | +|------|--------| +| [docs/advanced/missions.md](../../../docs/advanced/missions.md) | **STALE** — mirrors broken model (4 states, `mission_id`/`status_url`/`interaction_url`); no `s256`, no blob fields, no governance endpoints | +| [docs/workflows/call-chaining.md](../../../docs/workflows/call-chaining.md) | partial — mentions forwarding, no mission blob/governance | +| [docs/workflows/ps-asserted-access.md](../../../docs/workflows/ps-asserted-access.md) | aligned for consent; no mission context | +| [docs/workflows/deferred-consent.md](../../../docs/workflows/deferred-consent.md) | aligned; no mission/permission context | +| [docs/workflows/federated-access.md](../../../docs/workflows/federated-access.md) | partial; no mission governance | +| [docs/server/token-issuance.md](../../../docs/server/token-issuance.md) | mentions `mission.approver`; no `s256`/lifecycle | +| docs/server/{permission,audit,interaction} | **absent** | + +Docs index: [docs/README.md](../../../docs/README.md) (slot new governance docs +under `docs/server/` + refresh `docs/advanced/missions.md` + add a +`docs/workflows/` mission-governance walkthrough). + +--- + +## Part 4 — Gaps & Open Questions + +1. **`s256` over which bytes?** Spec: exact response body bytes of the mission + approval. SDK must retain the raw `byte[]`/`string` from the `mission_endpoint` + response (and any persisted blob) — model must expose a verbatim-bytes + accessor, not a re-serialized `JsonObject`. (§Mission Approval ~L1275) +2. **Auto-signing `aauth-mission`.** Should `AAuthSigningHandler` auto-detect the + `AAuth-Mission` request header and add `aauth-mission` to covered components, + or should the mission-aware client layer set `AdditionalComponentsKey`? + Leaning auto-detect for correctness (G12), but must confirm it does not + double-add when callers also set it. (§Authorization Endpoint Request ~L619) +3. **Backward compatibility of `Mission`.** Replacing the model is a breaking + change. Confirm whether to rename (e.g. `ApprovedMission`) + keep a + `[Obsolete]` shim, or replace outright. (Affects docs + any sample usage.) +4. **Server governance depth.** How much PS-side to put in the SDK vs. the mock? + Proposed: DTOs + serialization + minimal endpoint mappers + store/relay + interfaces (`IMissionStore`, `IPermissionDecider`, `IAuditSink`, + `IInteractionRelay`); full policy lives in MockPersonServer. (§PS Governance) +5. **Clarification scope.** Implement the full agent-side three-action loop + (respond / update / cancel) plus a server-side helper, or agent-side only in + round one? (§Clarification Chat) +6. **`mission_terminated` propagation.** Where to surface — `TokenExchangeClient`, + governance clients, or a shared error mapper consumed by all PS calls? + (§Mission Status Errors) + +> **Update (2026-06):** Initial research complete. Open questions above to be +> resolved as Implementation Decisions per phase before coding. + +## Part 5 — Phase Findings + +### Phase 1 — Mission model & `s256` (2026-06-05, complete) + +- **Decisions resolved.** Q1 (verbatim bytes): `Mission.RawBytes` + (`ReadOnlyMemory`) stores the exact approval body; `FromApprovalBytes` + hashes those bytes directly (no `JsonNode` re-serialization). Q3 (compat): + given draft status + no back-compat constraint, the old `Mission` model was + **replaced in place** — no rename, no `[Obsolete]` shim. +- **Old model had zero external references.** `Mission` (with `Id`/`Status`/ + `Requirements`/`StatusUrl`/`InteractionUrl`) and `Mission.FromJson` were not + referenced anywhere in `src/`, `tests/`, or `samples/`. The rewrite therefore + broke no callers — confirmed by a full-solution build (0 warnings/errors). +- **`AAuthMissionHeader` kept as-is.** `FormatStructured(approver, s256)` already + matched spec; only the dead `Format(string missionId)` overload was removed. + `MissionForwardingHandler` (the sole consumer) is unaffected. +- **`s256` is byte-sensitive.** A test confirms pretty-printed vs compact JSON of + the same logical content produce **different** `s256` — reinforcing the + "store verbatim, never re-serialize" requirement (§Mission Approval). +- **Capabilities union** added as `AAuthCapabilitiesHeader.Union(mission, agent)` + (mission-first, order-preserving, case-sensitive dedupe). +- **New files:** `Mission.cs` (rewritten), `MissionState.cs`, `MissionTool.cs`. + **Tests:** `Missions/MissionModelTests.cs`, `Missions/MissionS256Tests.cs`, + plus `Union` cases in `CapabilitiesHeaderTests.cs`. +- **Validation:** 364 conformance + 371 unit tests green; full solution builds. +- **No new open questions or design choices for Phase 1.** `VerifyS256` uses a + fixed-time comparison (defensive; the value is not secret but the helper is + cheap and avoids early-exit surprises). + +### Phase 2 — Mission binding through the token chain (2026-06-05, complete) + +- **Mission claim shape.** Introduced `MissionClaim(string Approver, string + S256)` (`src/AAuth/Tokens/MissionClaim.cs`) as the `{approver, s256}` value + carried in tokens. `ResourceTokenBuilder` and `AuthTokenBuilder` gained an + optional `Mission` property; the `mission` claim is emitted **only when set** + (§Resource Token Structure, §Auth Token Structure). +- **Verification surface.** `TokenVerifier.VerifiedToken` exposes a computed + `Mission` property (parses `payload.mission`; `null` when absent/malformed). + The existing `expectedApprover` constraint in `VerifyResourceTokenAsync` + (step 7) is unchanged. +- **DISCOVERY (mid-phase, surfaced to user).** Adding `aauth-mission` on the + signing side alone would **break** every mission-context request: the + production verifier `AAuthVerifier.Verify` rigidly rejected any covered + component beyond the base 4 + optional `authorization` (threw when + `components.Count > 5`). This required extending the verifier + + `AAuthVerificationMiddleware`, two files **not** in the original Phase 2 file + list. **User approved** adding them (design decision D5). +- **Covered-component ordering (D6, resolved per spec).** Spec + §Authorization Endpoint Request shows mission context as + `("@method" "@authority" "@path" "signature-key" "aauth-mission")` — i.e. + `aauth-mission` is the **last** component, after `signature-key`. The signer + appends it after the (pre-existing) `authorization` block, so the verifier + accepts the optional trailing pair `authorization` then `aauth-mission`. + **Pre-existing deviation noted:** the spec's §AAuth-Access example places + `authorization` *before* `signature-key`, but the SDK appends it *after*; + re-aligning that is out of Phase 2 scope (would churn existing tests). +- **No double-cover.** `aauth-mission` is added to the signer's `seen` set so an + explicit `AdditionalComponentsKey` request for it is ignored (covered once via + header auto-detection). Test asserts a single occurrence. +- **Header value consistency.** Signer covers the verbatim `AAuth-Mission` header + value (`approver="..."; s256="..."` via `AAuthMissionHeader.FormatStructured`). + Middleware passes `req.Headers["AAuth-Mission"].FirstOrDefault()`; single-valued + in practice so producer and verifier see identical bytes. +- **Files:** `MissionClaim.cs` (new); modified `ResourceTokenBuilder.cs`, + `AuthTokenBuilder.cs`, `TokenVerifier.cs`, `AAuthSigningHandler.cs`, + `AAuthVerifier.cs`, `AAuthVerificationMiddleware.cs`. **Tests:** + `Missions/MissionClaimTests.cs` (6), `HttpSignatures/MissionSignedComponentTests.cs` + (5) — note `MissionClaimTests` placed under `Missions/` (no `Tokens/` folder + exists in the conformance project). +- **Validation:** 375 conformance (+11) + 371 unit tests green; full solution + builds 0/0. + +### Phase 3 — PS token-request params, clarification chat, mission errors (2026-06-05, complete) + +- **Token-request params (§Agent Token Request).** Added six optional `string?` + properties to `TokenExchangeRequest` — `Justification`, `LoginHint`, `Tenant`, + `DomainHint`, `Platform`, `Device` — serialized into the POST body as + `justification`, `login_hint`, `tenant`, `domain_hint`, `platform`, `device` + via a new `AddIfPresent` helper that omits unset/empty values. +- **Clarification model (§Clarification Chat).** `ClarificationRequirement` + (`src/AAuth/Headers/ClarificationRequirement.cs`) parses the `202` body + `{clarification, timeout?, options?}` for `requirement=clarification`, + modeled on the existing `ClaimsRequirement`. Throws `FormatException` when the + `clarification` string is missing. +- **Clarification API design (D7, user-approved).** The agent supplies a + callback `OnClarificationRequired` on `TokenExchangeRequest` (mirrors + `OnInteractionRequired`) that returns a `ClarificationResponse` *decision* + object. `ExchangeAsync`'s response handling was rewritten into a + `while (StatusCode == 202)` loop that resolves the requirement, dispatches + interaction vs. clarification, applies the decision, and re-polls. +- **ClarificationResponse + ClarificationExchange.** `ClarificationResponse` + (nested `Kind { Respond, Update, Cancel }`) carries the agent's choice; the + factories are `Respond(markdown)`, `Update(resourceToken, justification?)`, + `Cancel()`. `ClarificationExchange` performs the wire calls against the pending + URL: `clarification_response` POST, updated `resource_token` POST, and `DELETE` + cancel (which surfaces `AAuthClarificationCancelledException`). +- **Round limit (§Clarification Limits).** Default `MaxRounds = 5` (configurable + via `TokenExchangeRequest.MaxClarificationRounds`); `Respond`/`Update` consume a + round, `Cancel` does not. Exceeding the limit throws + `AAuthClarificationLimitException`. +- **DEVIATION FROM PLAN FILE LIST.** The plan listed `DeferredPoller.cs` as + **Modify — allow POST/DELETE to pending URL**. In implementation the POST/DELETE + calls live entirely in `ClarificationExchange` (using its own `HttpClient`), + and the clarification-stop during polling reuses the **existing** + `DeferredPollerOptions.StopWhenAccepted` predicate (composed via + `ComposePollerOptions`). `DeferredPoller.cs` was therefore **not** modified. +- **Mission-terminated (§Mission Status Errors).** Added `TokenErrorCode. + MissionTerminated` (`mission_terminated`, round-trips through `TokenErrorResponse`) + and `AAuthMissionTerminatedException` (with `MissionStatus`). `ExchangeAsync` + classifies a terminal `403` body `{error:"mission_terminated", mission_status}` + via `TryReadMissionTerminatedAsync` — both on the direct token response and on a + `403` surfaced during polling (the poller returns the unrecognized `403` rather + than throwing, so the client classifies it). A shared `BufferBodyAsync` lets the + `access_denied`, `mission_terminated`, and auth-token readers all re-read the body. +- **Files:** new `Headers/ClarificationRequirement.cs`, `Agent/ClarificationExchange.cs` + (holds `ClarificationResponse` + `ClarificationExchange`), + `Errors/AAuthMissionTerminatedException.cs`; modified `Agent/TokenExchangeRequest.cs`, + `Agent/TokenExchangeClient.cs`, `Agent/AAuthInteractionExceptions.cs` (added + `AAuthClarificationCancelledException`, `AAuthClarificationLimitException`), + `Errors/TokenError.cs`. **Tests:** `Missions/ClarificationChatTests.cs` (8), + `Missions/MissionTerminatedTests.cs` (3), and an **added** (not in original plan + list) `Missions/TokenRequestParamsTests.cs` (2) covering the six params. +- **Validation:** 388 conformance (+13) + 371 unit tests green; full solution + builds 0/0. + +### Phase 4 — PS governance clients + metadata discovery (2026-06-05, complete) + +- **Metadata (§Person Server Metadata, ~L2199).** `ServerMetadata.FromJson` + already parsed `mission_endpoint` + `interaction_endpoint`; added + `permission_endpoint` and `audit_endpoint`, so all four governance endpoints + (all OPTIONAL in spec) are now surfaced. The clients resolve an endpoint by + fetching the PS `aauth-person.json`, validating https-or-loopback, and + origin-pinning the returned URL to the PS authority. +- **Shared exchange (DEVIATION — added beyond plan file list).** A new + `Agent/Governance/GovernanceExchange.cs` holds the common signed-POST + + deferred-`202` loop + `mission_terminated` classification + endpoint + origin-pinning, plus a public `GovernanceOptions` + (`OnInteractionRequired`, `OnClarificationRequired`, `MaxClarificationRounds`, + `PollerOptions`). This mirrors `TokenExchangeClient`'s deferred/clarification + loop. **Design note for user:** `GovernanceExchange` duplicates some + `TokenExchangeClient` logic; `TokenExchangeClient` was deliberately left + untouched (zero regression risk). A future shared-helper refactor is possible + if desired. +- **MissionClient (§Mission Creation ~L399/L1228, §Mission Approval ~L1265).** + `ProposeAsync(personServer, MissionProposal, options?, ct)` posts + `{description, tools?}` to `mission_endpoint`, handles the `202` review/ + clarification loop, then reads the approval body **verbatim** and calls + `Mission.FromApprovalBytes`. It parses the `AAuth-Mission` header's `s256` and + verifies it against the recomputed blob hash (throws on mismatch/missing). +- **PermissionClient (§Permission Endpoint ~L1013).** `RequestAsync` posts + `{action, description?, parameters?, mission?}` → `200 {permission, reason?}`. + An overload taking a `Mission` short-circuits to `Granted` + ("Pre-approved tool on the active mission.") when the action matches + `mission.ApprovedTools`, **without** calling the PS (spec `approved_tools`). +- **AuditClient (§Audit Endpoint ~L1077).** `RecordAsync` posts + `{mission, action, description?, parameters?, result?}` (mission REQUIRED), + returns on `201`/`200`/`204` (fire-and-forget); `mission_terminated` surfaces + via `GovernanceExchange`. +- **InteractionClient (§Interaction Endpoint ~L1131).** `SendAsync` posts + `{type, ...}` for all four `type` values; `question` → `Answer` from + `body["answer"]`, `completion` → `Terminated` when `mission_status != "active"`. + Convenience helpers: `RelayInteractionAsync`, `RelayPaymentAsync`, + `AskQuestionAsync`, `ProposeCompletionAsync`. (DEVIATION — added + `InteractionResult.cs` DTO not explicitly in plan list.) +- **DTOs.** `MissionProposal`, `PermissionRequest`/`PermissionResult` + (`PermissionGrant` enum), `AuditRecord`, `InteractionRequest` + (`InteractionType` enum)/`InteractionResult`. Each request DTO has an + `internal ToJsonObject()`; `PermissionResult.FromJson` maps granted/denied. +- **Layering decision.** Agent DTOs own serialization (`ToJsonObject`); the + server side owns parsing (Phase 5 `GovernanceEndpoints.Parse*`). Agent + governance clients are constructed directly (like `TokenExchangeClient`) and + are **not** DI-registered. +- **Files:** modified `Discovery/ServerMetadata.cs`; new + `Agent/Governance/{GovernanceExchange,MissionProposal,MissionClient, + PermissionRequest,PermissionResult,PermissionClient,AuditRecord,AuditClient, + InteractionRequest,InteractionResult,InteractionClient}.cs`. **Tests:** + `Missions/GovernanceClientTests.cs` (12) against a stub PS. +- **Validation:** 399 conformance (+11) + 371 unit green; SDK + full solution + build 0/0. + +### Phase 5 — PS server-side governance seams + mission log (2026-06-05, complete) + +- **Decision boundary (D3).** The SDK ships thin server-side seams — request + parsers, a `mission_terminated` helper, storage interfaces + in-memory + defaults, and the policy/relay interfaces — so a PS can serve the governance + endpoints without hand-rolling parsing. Policy and UI live in the PS + (MockPersonServer, Phase 6). +- **Request parsers (§PS Governance Endpoints ~L463).** + `GovernanceEndpoints.Parse{Permission,Audit,Interaction,MissionProposal}` + map a `JsonObject` to the Phase 4 DTOs, throwing `FormatException` on missing + required fields (`action`; `mission`+`action`; `type`; `description`) and on + unknown interaction `type`. Mission objects are read via + `MissionClaim.FromPayload`. This keeps parsing **server-side** rather than + adding `FromJson` to the agent DTOs (clean agent-serializes / server-parses + split). +- **Mission-terminated helper (§Mission Status Errors ~L1331).** + `MissionTerminatedStatus = 403`; `MissionTerminatedBody(missionStatus = + "terminated")` emits `{error:"mission_terminated", mission_status}` (error code + from `AAuthMissionTerminatedException.ErrorCode`); `MissionTerminated(...)` + returns an `IResult` via `Results.Json(..., statusCode: 403)`. +- **Mission store (§Mission Approval — verbatim bytes).** `IMissionStore` + + `StoredMission(S256, Approver, Agent, Blob)` with `MissionState State` + (default `Active`). `InMemoryMissionStore` (DEVIATION — added in-memory default, + mirrors `InMemoryJtiStore`) stores the blob bytes verbatim and transitions + state via `existing with { State = state }`. +- **Mission log (§Mission Log ~L1310, §Agent Token Request ~L784).** `IMissionLog` + + `MissionLogEntry(S256, Kind, Timestamp)` with `MissionLogEntryKind` + {Token, Permission, Audit, Interaction, Clarification} and optional + Resource/Scope/Action/Granted/Detail. `InMemoryMissionLog` (DEVIATION — added) + appends in order, `ReadAsync` preserves order, and `HasPriorConsentAsync(s256, + resource, scope)` returns true only for `Token` entries with `Granted == true` + matching `(s256, resource, scope)` — the prior-consent context a PS uses to + skip re-prompting. +- **Decider seam (§Person Server ~L385, §Permission ~L1017).** + `IPermissionDecider.DecideAsync(PermissionDecisionContext, ct)` is invoked with + the request + resolved `StoredMission` + ordered log, and returns a + `PermissionDecision(Outcome, Reason, Message?)` where `PermissionOutcome` + {Granted, Denied, Prompt} and `PermissionDecisionReason` {InScope, PriorConsent, + ApprovedTool, OutOfScope}. The SDK supplies the inputs + reason taxonomy; the + PS owns the policy. `IAuditSink` and `IInteractionRelay` are the audit/relay + seams. +- **DI.** `AddAAuthGovernance` (`Microsoft.Extensions.DependencyInjection` + namespace) `TryAddSingleton`s `IMissionStore`→`InMemoryMissionStore` and + `IMissionLog`→`InMemoryMissionLog`; the policy seams (decider/sink/relay) are + left for the PS to register. +- **Files:** new `Server/Governance/{IMissionStore,InMemoryMissionStore, + IMissionLog,InMemoryMissionLog,IPermissionDecider,IAuditSink,IInteractionRelay, + GovernanceEndpoints}.cs` and + `DependencyInjection/AAuthGovernanceServiceCollectionExtensions.cs`. **Tests:** + `Missions/GovernanceServerTests.cs` (17). +- **Validation:** 417 conformance (+18 across Phases 4+5) + 371 unit green; SDK + + full solution build 0/0. + +### Phase 5.5 — Shared deferred transport + governance facade (2026-06-05, complete) + +- **Why (duplication).** `GovernanceExchange` (Phase 4) and + `TokenExchangeClient` (Phase 3) shared ~120 lines: endpoint origin-pin, the + `202` deferred loop (interaction + clarification), `ComposePollerOptions`, + `BufferBodyAsync` / `ReadJsonBodyAsync` / `ExtractRequirement` / + `ResolveLocation`, the `mission_terminated` reader, and `AddIfPresent`. Pure + refactor — same spec citations (§User Interaction, §Clarification Chat, + §Mission Status Errors, §PS Governance Endpoints, §Person Server Metadata). +- **Single transport (D8).** `internal sealed class DeferredExchange` + (`AAuth.Agent`) now owns the transport: `ResolveEndpointAsync(personServer, + field, ct)` (metadata fetch + https-or-loopback + same-origin pin, generic + `'{field}'` errors byte-identical to the old `'token_endpoint'` ones) and + `PostAsync(endpoint, body, DeferredExchangeOptions, ct)` (the `202` deferred + loop, returning the terminal `HttpResponseMessage` for the caller to parse + + dispose, throwing `AAuthMissionTerminatedException` on terminal `403 + mission_terminated`). It owns the `AAuth.DeferredPoll` activity and every + shared helper. `GovernanceExchange.cs` is **deleted**; `GovernanceOptions` + moved to its own file with `internal DeferredExchangeOptions ToExchangeOptions()` + (`RequireInteractionCallback = false`, no `OnPolledResponse`). The four + governance clients now hold a `DeferredExchange`. +- **Preserving token-only behaviour through two option seams.** Rather than + leaving token-specific branches inside `TokenExchangeClient`, two + `DeferredExchangeOptions` knobs reproduce them exactly: (1) + `RequireInteractionCallback` (token = `true`) throws the token-exact + "no onInteractionRequired callback" message on **any** non-clarification `202` + with no callback, whereas governance (`false`) only throws when an interaction + requirement is actually present; (2) `OnPolledResponse` (token only) runs the + `403 access_denied` → `AAuthInteractionDeniedException` classifier **after** an + interaction-branch poll (not after a clarification poll, not on the + initial/direct response), matching the original call-site placement. The + initial/direct/clarification `403`s still flow to `ReadAuthTokenAsync` as token + errors. `TokenExchangeRequest` and the public `TokenExchangeClient` API are + unchanged. +- **Facade + factory (D9).** Public `AAuthGovernanceClient` bundles + `Mission` / `Permission` / `Audit` / `Interaction` over one signed `HttpClient` + + `MetadataClient` (ctor `(HttpClient signedClient, MetadataClient metadata)`, + `ArgumentNullException` guards; sub-clients stay public). + `AAuthClientBuilder.BuildGovernance()` builds it from a shared private + `BuildSignedChannel(provider, innerHandler)` helper that also backs + `BuildHandler`'s exchange channel. `BuildHandler` passes `new + HttpClientHandler()` (preserving the prior exchange signer's inner handler); + `BuildGovernance` passes `_innerHandler ?? new HttpClientHandler()` for + testability. `BuildGovernance` **requires an explicit signing mode** + (`UseHwk`/`UseJwt`/`UseJwksUri`/`UseJktJwt`/`UseProvider`) and throws + `InvalidOperationException` otherwise — it does not reconstruct the lazy-refresh + token-holder pipeline (that stays exclusive to `BuildHandler`). +- **Observability note.** `AAuth.DeferredPoll` now also fires for governance + polls (additive, via the shared transport) — not a wire change; the token + diagnostics tests still observe `AAuth.TokenExchange` + `AAuth.DeferredPoll`. +- **DI deferred.** `AddAAuthAgentGovernance` is still out of scope; it will land + in Phase 6 only if a sample needs DI-resolved governance. +- **Files:** new `Agent/DeferredExchange.cs`, + `Agent/Governance/{GovernanceOptions,AAuthGovernanceClient}.cs`; deleted + `Agent/Governance/GovernanceExchange.cs`; modified `Agent/TokenExchangeClient.cs`, + the four governance clients, and `AAuthClientBuilder.cs`. **Tests:** + `Missions/GovernanceFacadeTests.cs` (5). +- **Validation:** 422 conformance (417 + 5 facade) + 371 unit green, both + unchanged from before the refactor; SDK + full solution build 0/0 — the + existing suites were the regression gate and caught nothing. + +### Phase 6a — Samples backend foundation (2026-06-05, complete) + +- **MockPersonServer governance.** `MissionGovernance.cs` adds the scriptable + decision model (`MissionConsentScript`), per-mission policy snapshot + (`MissionPolicyStore`), approval blob builder, and the + decider/sink/relay sample implementations; `Program.cs` wires + `AddAAuthGovernance` + the four governance endpoints, the three-gate `/token` + mission gate, and `/admin/mission-script` + `/admin/mission-terminate` (option + A deterministic scripting). The `/token` gate reads + `MissionClaim.FromPayload(verified resource token)` and decides terminated → + `403`, in-scope/prior-consent → silent issue, else prompt (live `202` + deferred), logging the decision reason each time. +- **12-row Consent Matrix.** `tests/AAuth.Tests/Integration/ + MissionAgentFlowTests.cs` covers every gate × approve/deny × prompt/silent + (incl. clarification respond/cancel and `mission_terminated`) in-process via + `WebApplicationFactory` + the SDK governance/exchange clients, asserting the + recorded mission-log reason per row. +12 unit tests (371 → 383). +- **SPEC-DRIVEN ADDITION — mission-aware resource (§Terminology ~L177:** "a + mission-aware resource includes the mission object from the AAuth-Mission + header in the resource tokens it issues"; §Mission flow ~L423/L429; signed + `aauth-mission` component §HTTP signing ~L619). Promoted this from a sample + hack to a **first-class SDK feature** so the chain — agent `AAuth-Mission` + header → mission-aware resource copies `{approver, s256}` into the resource + token → PS reads it from the verified resource token → embeds it in the auth + token — works for *any* resource, not just the mock. SDK: `AAuthMissionHeader. + TryParseStructured`; `ChallengeOptions.MissionAware` (opt-in, default false); + `AAuthChallengeMiddleware` sets `ResourceTokenBuilder.Mission` when enabled and + the header parses. WhoAmI gains a dedicated `GET /jwt/mission` endpoint + (`ChallengeForMission(ScopeWhoami){MissionAware=true}`) so the mission-aware + path is demoed independently of the plain three-party `/jwt`. +3 conformance + tests (422 → 425). +- **MissionAgent CLI (`samples/MissionAgent/`).** New standalone console driving + the full lifecycle against the **live** mock servers (AP:5301 → PS:5100 → + WhoAmI:5000 `/jwt/mission`): enrol → propose mission → access the mission-aware + resource (out-of-scope **prompt**, then prior-consent **silent**) → pre-approved + permission (silent short-circuit) → non-pre-approved permission (prompt) → + audit → question → completion/terminate. Each resource access **refreshes the + agent token** (`AgentProviderClient.RefreshAsync` → fresh `jti`) — required + because the live resource's default JTI replay detection rejects a re-presented + agent token; this also mirrors real agent token rotation. +- **DEVIATION — genuine browser interactivity (user chose "interactive").** + Rather than only printing the consent URL while the PS auto-resolves, the mock + PS now supports a real browser decision, gated by + `MissionConsentScript.InteractiveBrowser` (default false, so the 12-row test's + scripted auto-resolve is unaffected). When set: the mission-pending / + permission-pending GETs hold at `202` (re-emitting the interaction header) + until a `MissionPendingEntry.Decision` is recorded, and the browser + `/interaction` GET + `/interaction/approve` + `/interaction/deny` handlers now + recognise mission-pending codes and set that decision. `/permission` emits the + interaction header when interactive; `/admin/mission-script` accepts + `{interactive}`. The CLI defaults to interactive (`--auto` opts back into + scripted). Verified live end-to-end both ways (auto full run; interactive via + out-of-band `POST /interaction/approve` for the step-3 token and step-6 + permission prompts). +- **Files:** new `samples/MissionAgent/{MissionAgent.csproj, Program.cs, + README.md}` (already in `AAuth.slnx`); modified `samples/MockPersonServer/ + {MissionGovernance.cs, Program.cs}`, `samples/WhoAmI/Program.cs`, + `src/AAuth/Agent/Mission.cs`, `src/AAuth/Server/Challenge/{ChallengeOptions, + AAuthChallengeMiddleware}.cs`; new test `tests/AAuth.Tests/Integration/ + MissionAgentFlowTests.cs`; modified `tests/AAuth.Conformance/HttpSignatures/ + ChallengeMiddlewareTests.cs`. +- **Validation:** Conformance 425 (+3), AAuth.Tests 383 (+12); `AAuth.slnx` + builds 0 warnings / 0 errors; MissionAgent live smoke (auto + interactive) green. + +#### Phase 6a legibility follow-ups (2026-06-05, complete) + +- **Interactive mission-creation screen.** Mission approval is the most important + consent in the model, so `/mission` now defers (`202` + interaction) to a real + browser consent screen when `InteractiveBrowser` is set — the same deferred + path the token/permission gates use. SDK `MissionClient.ProposeAsync` already + routes through `DeferredExchange`, so no SDK change was needed; only the mock + PS gained `MissionPendingKind.Mission` + `MissionPendingEntry.Proposal`, a new + `GET /mission-create-pending/{id}` that builds+saves the approval blob on + approve (or `403` on decline), and the consent page now branches on creation + (heading "start a new mission"; shows description + `Tools`; no `s256`/resource + yet). `MissionAgent.ProposeAsync` passes `GovernanceFor(...)` so the browser + opens. Scripted mode (the 12-row test) keeps `InteractiveBrowser = false` and + the synchronous auto-approve path → unchanged, 12/12. +- **Consent-screen tool context.** `MissionPolicyStore.ApprovedTools(s256)` added; + the token and permission consent screens now show the mission's approved tools + as context, so the human sees the full mission a request sits under. On the + permission screen this makes the gate self-explanatory: `Approved tools: + send_email, summarize` next to `Action: delete_inbox`. +- **Spec wording confirmed (§Permission Endpoint L1015–1017, L1303).** Per-call + permission requests carry an **`action`**; the mission pre-approves + **`approved_tools`** (tool objects). The agent calls the permission endpoint + only for actions not covered by a pre-approved tool. Consent-screen labels and + the README sequence diagram use this distinction. +- **README sequence diagram** now draws all three browser consent screens + (mission creation, out-of-scope token gate, out-of-tool permission) plus the + silent pre-approved-tool path, with the `action` vs `approved_tools` wording. +- **Files (this follow-up):** modified `samples/MockPersonServer/ + {MissionGovernance.cs, Program.cs}`, `samples/MissionAgent/{Program.cs, + README.md}`. No SDK/test changes; suites unchanged (425 / 383). + +### Phase 6b — Blazor apps + consent-UX refinements + out-of-mission scope gate (2026-06-06, in progress) + +- **GuidedTour + SampleApp mission flows (built, live-verified).** GuidedTour + `TourMode.Mission` (14-step raw-HTTP walkthrough) and SampleApp `Mission.razor` + (one-page 4-gate run) both drive: mission approval **prompt** → in-scope + `whoami` token **silent** → pre-approved `send_email` tool **silent** → + non-pre-approved `delete_inbox` tool **prompt**. Live-verified end-to-end. +- **BUG FOUND + FIXED — `whoami` proposed as a tool.** GuidedTour's proposal + listed `whoami` in `tools`, which is wrong: `whoami` is a **resource scope** + (remote, §Scopes), not a local tool (§Permission Endpoint). After the + separate scope/tool lists were added it showed `whoami` in BOTH the consent + screen's scope list and tool list. Fixed the proposal to `summarize` + + `send_email`. Confirms the **tool-vs-scope** distinction matters in the demo + data, not just the docs. +- **Consent-UX refinements (MockPersonServer `/interaction`, all live-verified):** + - **Separate scope + tool lists** (§Scopes vs §Permission Endpoint) so the + user sees both halves of the mission's authority distinctly. + - **Definition box** grounding the two words: *resource scope* = remote access + via auth token; *tool* = local action via the permission endpoint. + - **Creation screen lists NO scopes.** Per §Mission Creation (L1233) a proposal + carries only `description` + optional `tools` — never scopes. The creation + screen now shows the description + tools and a note that **the PS will + determine the resource scopes the mission needs from its description, + per-request, as the agent works** (§Scopes L1793 "The PS evaluates requested + scopes against mission context"; §Concurrent Token Requests L828). This + corrected an earlier draft that wrongly implied scopes were pre-determined + at creation. + - **CLARIFIED SPEC SEMANTICS — scopes are determined dynamically over the + mission's whole life, never fixed at creation.** A mission is a standing + natural-language context; the PS is a per-request judge (§Overview L141 + "every resource access is evaluated in context"; §Token endpoint L784 — the + PS *remembers* prior consent within a mission so decisions accrue rather than + being declared up front). Out-of-mission scope ⇒ **gate 3 prompt**, not + auto-deny; only an explicit user deny (or `mission_terminated`) yields + `access_denied`. + - **Post-creation gates relabel the scope list "Granted so far"** (accrual + framing) with empty state "nothing yet — this is the first request"; the + token-gate request note now reads "Not yet covered by this mission — approve + to grant this scope (the agent may reuse it for the rest of the mission)", + teaching that the grant is remembered. + - Removed the now-unused `MissionConsentScript script` DI param from the + `/interaction` GET handler. +- **docs/concepts.md** Governance section rewritten to convey the asymmetry: + **tools are declared, scopes are evaluated** (per-request, lifetime-long). + MissionAgent README gained the same distinction. + +- **NEW WORK (designed 2026-06-06, decisions D6–D9 in plan) — out-of-mission + scope gate.** The original Phase 6 plan intended a prompted **token/scope** + gate ("out-of-scope token request that prompts") but 6b shipped only the + prompted **tool** gate. Gap confirmed by reading `MissionPlan` (GuidedTour) and + `Mission.razor` — neither exercises an out-of-mission *scope*. To close it: + - **WhoAmI** gains a **new resource scope** (`whoami:history`, proposed) on a + **new mission-aware endpoint** (`/jwt/history`) via + `ChallengeForMission(...)`. Decision **D6**: new endpoint + new scope (not + reuse the existing non-mission `/jwt/admin` step-up) so the out-of-mission + scenario is unambiguous. WhoAmI already has the levers: `ChallengeForMission` + helper, `AddAAuthScopePolicy`, and `ScopeDescriptions` metadata — the new + path just needs excluding from the baseline `/jwt` branch. + - **Gate model becomes 5 gates** (decision **D7**): mission approval prompt → + `whoami` token silent → `whoami:history` token **PROMPT (out-of-mission + scope)** → `send_email` tool silent → `delete_inbox` tool prompt. Under the + seeded inbox mission (in-scope = `whoami` only), `whoami:history` naturally + falls outside → the existing `/token` gate-3 prompt path fires (no PS logic + change needed — the seed already excludes it). + - **Apps to update:** SampleApp `Mission.razor` (insert gate 3), GuidedTour + `TourSession.cs` MissionPlan + step methods + approval/poll constants + + `CodeSnippets.cs` + `Tour.razor`, and **`samples/MissionAgent/`** (decision + **D8**: the "agent console app" = MissionAgent, which has a mermaid sequence + diagram to extend with the out-of-mission scope consent block) + its README. + - **Playwright specs** assert the new prompted-scope gate. +- **AgentConsole is NOT in scope** (subagent-confirmed): it has no mission + support and its README has **no mermaid diagrams** (only tables + example + invocations). The user's "agent console app" referred to MissionAgent. +- **Status:** consent-UX refinements + bug fix + concept doc DONE and + live-verified; out-of-mission scope gate DESIGNED (this entry) and pending + implementation. SampleApp consent-URL reorder (amendment 3) built, not yet + live-verified. Playwright specs pending. Builds: MockPersonServer / SampleApp / + GuidedTour all 0/0. + +### Phase 6b — e2e validation + jti-replay fix (2026-06-06, complete) + +- **e2e specs written.** `samples/GuidedTour/playwright-tests/mission.spec.ts` + (20-step, three-cycle lifecycle + elevated-deny) and + `samples/SampleApp/playwright-tests/mission.spec.ts` (five-gate run + elevated-deny). + Both projects boot fresh via Playwright's `webServer` array. +- **BUG FOUND via e2e + FIXED — GuidedTour mission flow hung at the elevated + cycle.** Root cause was a **jti replay**: `TourSession` reused a single + `_agentToken` (fixed `jti`) for BOTH the `/jwt/mission` (cycle 1) and + `/jwt/mission/elevated` (cycle 2) signed requests. WhoAmI's `InMemoryJtiStore` + recorded the `jti` on the first access and rejected its reuse on the second → + a **bare 401** (no `AAuth-Requirement`) → `_resourceToken` stayed stale + (`scope=whoami`) → the elevated `/token` exchange evaluated as in-scope and + returned **200 silent** instead of the spec-required **202 prompt** (§Agent + Token Request gate 3) → `_userApproved` was never reset to `false` → + `StepUserApprovesPlaceholder()` no-oped (it only throws when `!_userApproved`) + → the run-all dispatch loop never advanced past step 12 → infinite redispatch. + Both observed symptoms (silent-grant + infinite loop) had this single cause. +- **FIX (spec §Agent Token, replay protection):** added + `TourSession.RefreshAgentToken()` (re-mints `_agentToken` with a fresh `jti` + via `AgentTokenBuilder.Build()`) and call it at the top of + `StepMissionResourceChallengeAsync` AND `StepMissionElevatedChallengeAsync`, + so each signed resource access presents a distinct agent token — exactly as + `MissionAgent` (and SampleApp's `AccessMissionResourceAsync`) already did via a + per-access refresh. SampleApp was already correct; GuidedTour was the only + app missing the refresh. +- **TEST-HARNESS LESSON (not a product bug).** WhoAmI mints an ephemeral + resource signing key (`AAuthKey.Generate()`) at every startup, so restarting + WhoAmI alone while a long-running PS keeps its cached JWKS yields + `401 invalid_resource_token: JWT signature verification failed`. Always boot + the whole AAuth backend set together (let Playwright's `webServer` boot all, + or `pkill -f "dotnet run --project samples"` first). `jti` replay state also + accumulates across e2e runs against a long-lived WhoAmI — prefer fresh + full-stack boots. Recorded in repo memory. +- **DIAG removed.** All temporary `[DIAG-CHAL]` (SDK + `AAuthChallengeMiddleware.cs`) and `[DIAG]` (`TourSession.cs`) tracing removed. +- **Validation:** `AAuth.slnx` builds 0/0. `AAuth.Tests` 383/383, + `AAuth.Conformance` 425/425. GuidedTour mission specs (lifecycle + deny) green; + SampleApp mission specs (five-gate + deny) green. + +### Phase 7 — doc audit (2026-06-06) + +Read-only audit of every `docs/**` file that mentions missions/governance +against the shipped Phase 1–6 public API, to re-scope Phase 7 before writing +(user: "Phase 7 may need updates as we touched new things during +implementation"). Findings drive the revised Phase 7 file list + DoD in the +implementation plan. + +- **`docs/advanced/missions.md` is fundamentally stale.** It documents a + **non-existent** model: `Mission.Id`, `Mission.Status` (pending/approved/ + denied/completed), `Mission.Requirements` (JsonArray), `Mission.StatusUrl`/ + `InteractionUrl`, `Mission.FromJson(JsonObject)`, and + `AAuthMissionHeader.Format(string missionId)` — plus a resource-proposes-mission + lifecycle mermaid. The shipped model is the spec **approval blob**: + `Mission { Approver, Agent, ApprovedAt, Description, ApprovedTools, + Capabilities, S256, RawBytes, State(Active|Terminated) }`, + `FromApprovalBytes`/`VerifyS256`/`ComputeS256`, and + `AAuthMissionHeader.FormatStructured(approver, s256)`/`TryParseStructured`. + Requires a full rewrite, not a patch. +- **Agent-side governance clients have ZERO doc coverage.** `AAuthGovernanceClient` + facade + `MissionClient.ProposeAsync`, `PermissionClient.RequestAsync` (both + overloads), `AuditClient.RecordAsync`, `InteractionClient.SendAsync`/ + `RelayInteractionAsync`, all DTOs (`MissionProposal`, `PermissionRequest`/ + `PermissionResult`/`PermissionGrant`, `AuditRecord`, `InteractionRequest`/ + `InteractionResult`/`InteractionType`, `GovernanceOptions`), and + `AAuthClientBuilder.BuildGovernance()` — none documented. +- **Clarification chat has ZERO doc coverage.** `ClarificationExchange` + (`DefaultMaxRounds = 5`), `ClarificationResponse` (`Respond`/`Update`/`Cancel`), + `ClarificationRequirement`, and the `TokenExchangeRequest`/`GovernanceOptions` + `OnClarificationRequired` + `MaxClarificationRounds` hooks. +- **Server governance seams have ZERO doc coverage.** `IMissionStore`/ + `StoredMission`/`InMemoryMissionStore`, `IPermissionDecider`/ + `PermissionDecisionContext`/`PermissionDecision`/`PermissionOutcome`/ + `PermissionDecisionReason`, `IAuditSink`, `IInteractionRelay`/ + `InteractionRelayResult`, `IMissionLog`/`MissionLogEntry`/`MissionLogEntryKind`/ + `InMemoryMissionLog`, `GovernanceEndpoints`, and `AddAAuthGovernance` DI. +- **Smaller gaps.** `AAuthMissionTerminatedException` (`mission_terminated`) is + absent from `error-handling.md`; `ChallengeOptions.MissionAware` (the + mission-aware resource that copies the `AAuth-Mission` claim into the resource + token) is absent from `challenge-middleware.md`; `README.md`'s API Map lacks + rows for every governance client + server seam; the six mission token-exchange + params (`Justification`/`LoginHint`/`Tenant`/`DomainHint`/`Platform`/`Device`) + are undocumented. +- **Already correct (Phase 6 / earlier work) — no rewrite needed.** + `docs/concepts.md` (tools-declared-vs-scopes-evaluated split, corrected in + 6b), `docs/workflows/call-chaining.md` (mission forwarding via + `WithCallChaining`/`MissionForwardingHandler`), and `docs/server/ + token-issuance.md` (the `mission.approver` constraint) only need cross-link / + index touch-ups. +- **Design choices recorded (D10–D11, user-approved 2026-06-06).** D10: the + agent-side governance clients get a dedicated `docs/advanced/ + mission-governance-clients.md` (keeps `docs/server/mission-governance.md` + focused on PS/server seams). D11: clarification chat gets its own + `docs/advanced/clarification-chat.md`; all smaller touch-ups land in Phase 7. + +### Phase 8 — multi-subagent review (2026-06-06, complete) + +Seven review subagents (R1–R7), one per change set, produced severity-graded +findings with spec citations. Baseline before remediation: build 0/0, unit 383, +conformance 425, mission e2e 4/4. Verdicts: R7 PASS; R1–R6 PASS-WITH-NITS. + +**Spec corrections / remediations applied:** + +- **R1 (mission model) — non-spec capability value removed.** `AAuthCapabilities + Header.Capabilities.Mission = "mission"` was an out-of-spec fourth capability + value. §AAuth-Capabilities Request Header (~L1766) defines exactly three: + `interaction`, `clarification`, `payment`. The constant had **zero production + usages** (only an arbitrary-token `Union` dedup test). Removed the constant; + updated the test to use a real value; fixed stale `§14.1` citations to + `§AAuth-Capabilities` across `AAuthCapabilitiesHeader.cs`, + `AAuthSigningHandler.cs`, and `CapabilitiesHeaderTests.cs`. +- **R1 / R3 / R7 — untrusted-Markdown hardening.** Added an XML-doc remark to + `Mission.Description` stating it is server-supplied untrusted content that + consumers MUST sanitize before rendering (§Markdown ~L192). The SDK already + never renders it; this is defense-in-depth guidance for consumers. (Blazor + samples already never render the description — confirmed by R6.) + +**Findings explicitly deferred (with rationale), NOT auto-remediated:** + +- **R3 — `capabilities` array in the PS token-request body.** §AAuth-Capabilities + (~L1776) says the *header* "is not used on requests to PS endpoints — the PS + learns the agent's capabilities through the mission approval flow." The SDK + sends a `capabilities` **body** parameter (not the header) on the token + request. This is **pre-existing** behavior (commits `d8cf70a` / `1f235a4`, + "live interop fixes"), validated against `person.hello.coop`, and fills a real + gap: for the non-mission deferred-consent flow the PS otherwise has no way to + know the agent can handle a `202` interaction redirect. It is a draft-02 + token-endpoint extension, outside the mission change sets. Decision: flag to + the user as a spec-alignment question; do not change unilaterally (removing it + risks breaking deferred consent + live interop). +- **R4 — `AuditClient` accepts `200`/`204` as well as `201`.** §Audit Endpoint + specifies `201 Created`. Accepting other 2xx is lenient for a fire-and-forget + audit call; low risk, kept as-is. +- **R2 / R4 — minor test-coverage gaps** (aauth-mission anti-double-coverage + edge case; Interaction/Payment relay deferred-poll path). Implementations are + correct; gaps noted for a future test pass, not blocking. +- **R5 — unbounded growth of the in-memory store/log.** By design: the SDK + defaults are documented dev-grade; production PSes register durable stores via + the `TryAdd` seams. + +**Post-remediation gate:** build 0/0, unit 383, conformance 425, mission e2e 4/4 +— all green. + diff --git a/.agent/plans/2026-06-06-mission-api-refactor/implementation-plan.md b/.agent/plans/2026-06-06-mission-api-refactor/implementation-plan.md new file mode 100644 index 0000000..021b4df --- /dev/null +++ b/.agent/plans/2026-06-06-mission-api-refactor/implementation-plan.md @@ -0,0 +1,926 @@ +# Mission API Refactor — Implementation Plan + +## Overview + +Streamline the AAuth .NET SDK's mission/governance surface into an API that can be +constructed three ways — **static factories**, **fluent builders**, and +**DI registration** — for both agent (client) and resource (PS) sides, update every +mission sample and doc to the new surface, add a combined **clarification + +mission + call-chain** SampleApp example, and land two small spec-hardening fixes. + +The API surface is built in **two passes**: a first pass (Phase 1) that gets a +working end-to-end surface, then a consistency pass (Phase 2) that learns from the +first and fine-tunes naming/shape to match the conventions already used elsewhere in +the SDK. The closing phases independently audit samples, docs, and spec compliance. + +See [research.md](research.md) for the full current-state, pain-point, and gap +inventory (Parts A–G) and the recorded design decisions (Open Design Choices). +Significant issues and spec deviations are logged in +[issues-and-deviations.md](issues-and-deviations.md). Every phase below cites the +governing spec section. + +> **R3 (Rich Resource Requests)** is out of scope here — tracked in its own +> initiative, `.agent/plans/2026-06-06-r3-rich-resource-requests/`. + +## Working Agreement (2026-06-06) + +Directives captured from the user for this initiative: + +- **Two-pass API design.** Phase 1 does a first pass at the surface; Phase 2 learns + from it and fine-tunes for consistency with existing SDK patterns. +- **Construction triad.** Support **static factories**, **fluent builders**, and + **DI-friendly** registration for the mission/governance API. +- **No regressions.** None of the existing flows may break — old behavior is + preserved; only the API shape changes. +- **New sample has an e2e spec.** The combined SampleApp page ships with a Playwright + spec. +- **Closing audits use subagents.** The final sample and doc validation phases each + use a dedicated subagent to surface inconsistencies and readability issues + (especially docs, GuidedTour code snippets, and SampleApp). +- **Independent spec reviewer is the last phase.** A separate reviewer subagent + validates every change against the AAuth spec for 100% compliance. Fix the SDK + where it is not spec compliant. +- **Deviation tracking.** Significant issues/deviations are recorded in + [issues-and-deviations.md](issues-and-deviations.md); research is always updated + with new findings. +- **Gated execution.** Ask the user for permission before starting each phase. Do + **not** commit until the user says so. Surface major decisions at the end for + input; refactoring afterward is acceptable. + +## Context + +- **Spec:** `aauth-spec/draft-hardt-oauth-aauth-protocol.md` — §Agent Governance, + §Mission Creation/Approval, §Permission Endpoint, §Audit Endpoint, §Interaction + Endpoint, §Clarification Chat, §Call Chaining, §AAuth-Capabilities, §Person Server + Metadata, §Agent Token Request. +- **Upcoming:** `aauth-spec/upcoming-changes-02.md` (F1 capabilities body — already + correct; F5/F6 wait on draft-02). +- **Branch:** `feat/missions-ps-governance` (continue). +- **Sequencing:** Phase 1 API first pass (agent + resource) → Phase 2 API + consistency pass → Phase 3 spec hardening → Phase 4 sample migration → Phase 5 new + combined sample + e2e → Phase 6 mission convenience seam (`WithMission`) → Phase 7 + docs → Phase 8 samples audit (subagent) → Phase 9 docs audit (subagent) → Phase 10 + independent spec-compliance review (subagent). + +## Cross-Cutting Decisions + +The SDK is pre-1.0 and backward compatibility is **not** a concern (confirmed +2026-06-06). The mission API is a **breaking refactor**: low-level signatures change +and all call-sites are updated in place — no `[Obsolete]` shim, no dual surface. + +- **DC1 — Breaking refactor (no shim).** Replace the low-level mission client/ + resource surface; migrate all callers (research Part C inventory). +- **DC2 — Both client + resource ergonomics.** Address agent pain points + (PT-A1…A6) and resource pain points (PT-R1…R4). +- **DC3 — Align with existing conventions.** Fluent builder like + `AAuthClientBuilder`; DI like `AddAAuthDiscovery`; app-builder mapper like + `MapAAuthResource` (research Part B). +- **DC4 — One combined sample page.** Clarification + mission + call-chain in a + single SampleApp page reusing Orchestrator + WhoAmI as hops. +- **DC5 — Construction triad.** Every primary entry point is reachable via a static + factory, a fluent builder, and DI registration, mirroring how `AAuthClient`, + `AddAAuthAgent`, and `MapAAuthResource` already coexist (research Part B). +- **DC6 — No regressions.** All existing mission/clarification/call-chain flows keep + working; the four mission e2e specs stay green throughout. +- **DC7 — Independent closing review.** Sample audit, doc audit, and spec-compliance + review are separate final phases, each driven by a dedicated subagent; findings + are adjudicated against spec text and logged in `issues-and-deviations.md`. + +--- + +## Phase 1 — API surface: first pass (agent + resource) + +**Goal:** Get a working end-to-end mission/governance surface across **both** the +agent (client) and resource (PS) sides in one pass. Naming and shape need not be +final here — Phase 2 refines them. Fixes the agent pain points (PT-A1…A6) and the +resource pain points (PT-R1…R4), and lands the construction triad (DC5). + +**Spec:** §Agent Governance (governance clients call PS endpoints); §Mission +Creation/Approval (mission carried as `{approver, s256}`); §Permission/Audit/ +Interaction Endpoints (per-call mission claim, request/response shapes, +`mission_terminated` 403); §Clarification Chat (deferred 202 handling); §Person +Server Metadata (endpoint advertisement). + +### Approach — agent side + +- **Bind the PS once.** `AAuthClientBuilder.WithPersonServer(...)` already exists; + `BuildGovernance()` returns an `AAuthGovernanceClient` bound to that PS so + per-call `personServer` params are removed (PT-A2). +- **Mission session auto-threads the claim.** `Mission.ProposeAsync(...)` returns a + `MissionSession` that wraps the approved `Mission` + the bound client and exposes + `Permission`/`Audit`/`Interaction` calls that inject `{approver, s256}` + automatically (PT-A1, PT-A5). +- **Construction triad (DC5).** Static factory (`AAuthGovernanceClient.Create(...)`), + fluent builder (`AAuthClientBuilder…BuildGovernance(...)`), and DI + (`AddAAuthGovernanceClient(name, Action)` mirroring `AddAAuthAgent`) + (PT-A4). +- **Default callbacks.** Bound `GovernanceOptions` defaults set once on the builder, + overridable per call (PT-A6). + +### Approach — resource side + +- **`MapAAuthGovernance(...)`** app-builder mapper (mirrors `MapAAuthResource`) maps + `/mission`, `/permission`, `/audit`, `/mission-interaction` and their pending/poll + routes, using `GovernanceEndpoints` parsers + the registered seams (PT-R1, PT-R2). +- **Default no-op seams** registered via `TryAdd` in `AddAAuthGovernance(configure?)` + so a PS overrides only what it needs (PT-R3). +- **Resource governance builder** for mission-aware challenge config, replacing the + bare `ChallengeOptions.MissionAware` bool (PT-R4). + +### Files + +| File | Action | +|------|--------| +| `src/AAuth/Agent/Governance/AAuthGovernanceClient.cs` | **Modify** — bind PS URL + default `GovernanceOptions`; drop per-call PS param; add `Create(...)` factory | +| `src/AAuth/Agent/Governance/MissionSession.cs` | **New** — mission-scoped facade auto-threading the claim | +| `src/AAuth/Agent/Governance/MissionClient.cs` | **Modify** — `ProposeAsync(proposal, options?)` → `MissionSession` | +| `src/AAuth/Agent/Governance/PermissionClient.cs` | **Modify** — drop PS param; mission injected by session | +| `src/AAuth/Agent/Governance/AuditClient.cs` | **Modify** — drop PS param; mission injected | +| `src/AAuth/Agent/Governance/InteractionClient.cs` | **Modify** — drop PS param; mission injected | +| `src/AAuth/AAuthClientBuilder.cs` | **Modify** — `BuildGovernance()` binds PS + default options | +| `src/AAuth/DependencyInjection/AAuthGovernanceClientServiceCollectionExtensions.cs` | **New** — `AddAAuthGovernanceClient(...)` | +| `src/AAuth/DependencyInjection/AAuthGovernanceApplicationBuilderExtensions.cs` | **New** — `MapAAuthGovernance(Action?)` | +| `src/AAuth/DependencyInjection/AAuthGovernanceServiceCollectionExtensions.cs` | **Modify** — `AddAAuthGovernance(configure?)` + default no-op `IPermissionDecider`/`IAuditSink`/`IInteractionRelay` | +| `src/AAuth/Server/Governance/GovernanceEndpoints.cs` | **Modify** — promote per-endpoint handlers (carrier-token check, parse, mission lookup, state check) | +| `src/AAuth/Server/Governance/AAuthGovernancePipelineOptions.cs` | **New** — route prefix + deferred/pending config | +| `src/AAuth/Server/Challenge/ChallengeOptions.cs` | **Modify** — keep `MissionAware`; surface via resource builder | +| `tests/AAuth.Conformance/Missions/GovernanceClientBuilderTests.cs` | **New** | +| `tests/AAuth.Conformance/Missions/GovernanceEndpointMapperTests.cs` | **New** | +| `tests/AAuth.Tests/Governance/MissionSessionTests.cs` | **New** | + +### API Surface (illustrative) + +```csharp +// Agent — fluent builder + bound PS + mission session +AAuthGovernanceClient governance = AAuthClientBuilder + .SelfIssuing(key).As(issuer, agentId) + .WithPersonServer(ps) // PS bound once + .WithChallengeHandling() + .BuildGovernance(o => o.MaxClarificationRounds = 3); + +MissionSession mission = await governance.Mission.ProposeAsync( + new MissionProposal("Keep the inbox under control") { Tools = [...] }); + +// claim + PS auto-threaded: +PermissionResult r = await mission.Permission.RequestAsync("send_email"); +await mission.Audit.RecordAsync("send_email", result: ...); +bool done = await mission.ProposeCompletionAsync("Inbox triaged."); + +// Resource — DI + mapper +builder.Services.AddAAuthGovernance(o => o.UseInMemoryStores()); +builder.Services.AddSingleton(); +app.MapAAuthGovernance(); // maps the 4 endpoints + pending polls +``` + +### Implementation Decisions + +- DC1: no shim; `MissionSession` replaces manual `MissionClaim` extraction. +- DC3: mapper follows the `MapAAuthResource` precedent; seams keep policy in the PS. +- The bound `AAuthGovernanceClient` remains usable mission-lessly for permission + requests that carry no mission (§Permission Endpoint — mission optional). +- Default `IInteractionRelay` returns `Pending` (no user channel) so a bare PS still + compiles and behaves predictably. + +### Definition of Done + +- [x] `BuildGovernance()` binds the PS URL and default `GovernanceOptions`. +- [x] `MissionSession` injects `{approver, s256}` into permission/audit/interaction. +- [x] Per-call `personServer` parameters removed from the governance clients. _(done in Phase 2, D1 — bound client is the only path)_ +- [x] Client reachable via static factory, fluent builder, and `AddAAuthGovernanceClient(...)`. +- [x] `MapAAuthGovernance()` maps mission/permission/audit/interaction + poll routes. _(mission-creation via `IMissionApprover`; deferred 202 + poll via `IDeferredConsentStore`, Phase 2 D3)_ +- [x] `AddAAuthGovernance(configure?)` registers default no-op seams via `TryAdd`. +- [~] `mission_terminated` 403 + carrier-token checks centralized in the mapper. _(403 termination centralized; carrier-token checks pending Phase 2)_ +- [x] New unit + conformance tests pass; full suite green (build 0/0). + + +--- + +## Phase 2 — API surface: consistency pass + +**Goal:** Learn from the Phase 1 first pass and fine-tune the surface so it reads +consistently with the conventions already used elsewhere in the SDK. No new +capability — naming, shape, and ergonomics only. Confirms the construction triad +(DC5) behaves uniformly across agent and resource sides. + +**Spec:** as cited in Phase 1 (no new spec surface; shape alignment only). + +### Approach + +- **Convention diff.** Compare the Phase 1 surface against `AAuthClientBuilder`, + `AddAAuthDiscovery`/`AddAAuthAgent`, and `MapAAuthResource` (research Part B); + list naming/return-type/option-pattern divergences before changing anything. +- **Normalize the triad.** Ensure the static factory, fluent builder, and DI + registration share parameter names, option types, and defaults so the three paths + are interchangeable and predictable (DC5). +- **Tighten names/return types.** Align method names, async suffixes, option-bag + shapes (`Action` vs records), and nullability with the rest of the SDK. +- **Symmetry check.** Agent-side and resource-side builders/options use the same + vocabulary (e.g. `With…` / `Use…` / `Map…`) as their non-mission counterparts. +- **D1 — remove the transitional dual surface.** Drop the per-call `personServer` + parameters from `MissionClient` / `PermissionClient` / `AuditClient` / + `InteractionClient`; the bound client + `MissionSession` become the only path + (with sample migration completing in Phase 4). Reaches DC1's no-shim end state. +- **D2 — keep `MissionSession` flat.** Confirm flat methods + (`RequestPermissionAsync`, `RecordAuditAsync`, `AskQuestionAsync`, + `ProposeCompletionAsync`); no nested facades. +- **D4 — typed action POCO (replace bare strings).** Introduce a small + `MissionAction` POCO for the invoked action so callers pass a value object + instead of a `string`. Today `action` is a bare `string` on `PermissionRequest`, + `AuditRecord`, `MissionSession.RequestPermissionAsync/RecordAuditAsync`, and + `PermissionClient`. **Decision (2026-06-06):** model the *invocation* as a + distinct `MissionAction` rather than reusing `MissionTool` — the spec's `action` + is broader than a tool (covers file writes, message sends), and a dedicated type + avoids the redundant `MissionTool.Description` on the invocation path. Named + `MissionAction` (not bare `Action`) to avoid the `System.Action` clash. Keep + `MissionTool` as the *catalog* entry (proposal / `approved_tools`); `MissionAction` + is the *specific invocation*. Serialize the wire `action` field from + `MissionAction.Name`; add an implicit `string → MissionAction` conversion so terse + call sites (`"WebSearch"`) still compile. Update the `DefaultPermissionDecider` + match to compare `MissionAction.Name` against `ApprovedTools[].Name`. +- **D3 — promote PS mission machinery into the SDK (closes DEV-1/DEV-2).** Move the + approval-blob builder out of the sample into the SDK and add an + `IMissionApprover` seam so `MapAAuthGovernance` can map mission creation; add a + deferred/pending consent abstraction so a `Prompt` outcome returns a 202 deferred + response instead of a denial. `DefaultPermissionDecider`/relay remain conservative + no-ops but the deferred path becomes available to PS implementers. + +### Files + +| File | Action | +|------|--------| +| Phase 1 source files | **Modify** — rename/reshape per the convention diff | +| `src/AAuth/Agent/Governance/*Client.cs` | **Modify** — remove per-call `personServer` params (D1) | +| `src/AAuth/Agent/MissionAction.cs` (new) + `PermissionRequest`/`AuditRecord`/`MissionSession`/`PermissionClient` | **Add/Modify** — accept `MissionAction`; implicit `string` conversion (D4) | +| `src/AAuth/Server/Governance/*` (approval builder, `IMissionApprover`, pending/deferred seam) | **Add/Modify** — promote from sample (D3) | +| `src/AAuth/.../MapAAuthGovernance` | **Modify** — map mission creation + deferred 202 (D3) | +| `samples/MockPersonServer/*` | **Modify** — consume promoted SDK pieces where it reduces sample-local code | +| `tests/AAuth.Conformance/Missions/*` | **Modify** — update to the finalized names; cover mission-create + deferred | +| `tests/AAuth.Tests/Governance/*` | **Modify** — update to the finalized names | + +### Implementation Decisions + +- DC5: the finalized names from this pass are the public contract migrated in + Phases 4–6; record any rename decisions in research before applying. +- **D1/D2/D3 confirmed by the user (2026-06-06):** additive-then-remove, flat + `MissionSession`, and promote the mission machinery + deferred consent into the + SDK. See [issues-and-deviations.md](issues-and-deviations.md). +- Divergences that cannot be reconciled with existing conventions are logged in + [issues-and-deviations.md](issues-and-deviations.md) with rationale. + +### Definition of Done + +- [x] Convention diff recorded in research (Part B / Open Design Choices). +- [x] Factory, builder, and DI paths share names/options/defaults across both sides. +- [x] Public names align with `AAuthClientBuilder` / `AddAAuth*` / `MapAAuth*`. +- [x] Per-call `personServer` params removed; bound client is the only path (D1). +- [x] Action passed as a `MissionAction` POCO (implicit `string` for terse call sites) (D4). +- [x] Mission creation mapped by `MapAAuthGovernance` via `IMissionApprover` (D3, DEV-2). +- [x] `Prompt` outcome returns a deferred 202 via the pending-consent seam (D3, DEV-1). +- [x] All Phase 1 tests updated and green; full suite green (build 0/0). + + +--- + +## Phase 3 — Spec hardening (F3 + F4) + +**Goal:** Tighten audit response handling and add `device` validation. + +**Spec:** §Audit Endpoint (PS returns `201 Created`); §Agent Token Request +(`device` MUST be UTF-8 printable, ≤64 chars). + +### Files + +| File | Action | +|------|--------| +| `src/AAuth/Agent/Governance/AuditClient.cs` | **Modify** — accept only `201 Created` (F3) | +| `src/AAuth/Agent/TokenExchangeRequest.cs` | **Modify** — validate `device` (printable ASCII 32–126, ≤64) (F4) | +| `tests/AAuth.Conformance/Missions/AuditResponseTests.cs` | **New/Modify** | +| `tests/AAuth.Conformance/TokenExchange/DeviceValidationTests.cs` | **New** | + +### Definition of Done + +- [x] `AuditClient` rejects non-201 acknowledgments. +- [x] `device` rejects control chars and lengths > 64 with a clear exception. +- [x] Tests cover boundary cases; full suite green. + +--- + +## Phase 4 — Migrate mission samples to the new API + +**Goal:** Update all mission sample call-sites (research Part C) to the Phase 1–2 +surface. No behavior change; API shape only. + +**Spec:** as cited in Phases 1–2. + +### Files + +| File | Action | +|------|--------| +| `samples/MissionAgent/Program.cs` | **Done** — bound PS via `WithMission`; `MissionSession`; no manual `MissionClaim` (folded into Phase 6) | +| `samples/MockPersonServer/Program.cs` | **Kept hand-wired** — see DEV-4; agent-facing parsing already on SDK (`GovernanceEndpoints` + `MissionApprovalBuilder`) | +| `samples/MockPersonServer/MissionGovernance.cs` | **No change** — see DEV-4 | +| `samples/SampleApp/Components/Pages/Mission.razor` | **Done** — `MissionSession` (`ProposeMissionAsync` → `session.RequestPermissionAsync`/`RecordAuditAsync`); gates preserved | +| `samples/GuidedTour/TourSession.cs` + `CodeSnippets.cs` | **Done** — teaching snippets on the session API; raw-wire steps preserved (pedagogy) | +| `samples/WhoAmI/Program.cs` | **No change** — `ChallengeOptions { MissionAware = true }` is the canonical resource seam (DEV-5) | + +### Definition of Done + +- [x] All mission samples build and run against the new API. _(SampleApp builds 0/0; agent-side call-sites on the session API.)_ +- [x] Mission e2e specs (4) pass unchanged in behavior. _(SampleApp + GuidedTour mission specs: 4/4 green after migration.)_ +- [x] No leftover manual `MissionClaim`/PS-URL threading in samples. _(Agent-side clean; server-side parses incoming claims, which is correct. DEV-4/DEV-5 record the two server-side line-items intentionally not rewritten.)_ + +--- + +## Phase 5 — New combined SampleApp example (clarification + mission + call-chain) + +**Goal:** Add one SampleApp page demonstrating a clarification round during mission +approval, then a mission-governed multi-hop call chain. Fills the two sample gaps +(research Part D). + +**Spec:** §Clarification Chat (202 + `AAuth-Requirement: clarification`; bounded +rounds; untrusted text → sanitize); §Call Chaining (mission present → forward +`AAuth-Mission` each hop; per-hop PS re-evaluation; `act` nesting). + +### Files + +| File | Action | +|------|--------| +| `samples/SampleApp/Components/Pages/MissionCallChain.razor` | **New** — combined flow page | +| `samples/SampleApp/Components/Pages/Home.razor` | **Modify** — add card/link | +| `samples/MockPersonServer/MissionGovernance.cs` | **Modify** — script a clarification round during mission approval | +| `samples/Orchestrator/Program.cs` | **Modify (if needed)** — ensure mission forwarding hop is exercised | +| `tests/e2e/` (Playwright spec) | **New** — combined-flow spec | + +### Implementation Decisions + +- DC4: single page; Orchestrator + WhoAmI as downstream hops. +- Clarification text is rendered only after sanitization (untrusted input). + +### Definition of Done + +- [x] Page shows a clarification round (respond/update/cancel) during mission approval. _(`MissionCallChain.razor` step 2: `OnClarificationRequired` surfaces the sanitized question, agent answers, then the user approves the out-of-mission elevated scope.)_ +- [x] Mission is forwarded through the orchestrator; downstream hop is governed. _(step 3: `WithMission(...)` carries the mission to the Orchestrator `/mission` endpoint, which forwards `AAuth-Mission` to the WhoAmI `/jwt/mission` hop — chain result asserts `downstream.mode == "three-party"`, `agent == aauth:orchestrator@localhost:5200`, `mission` truthy.)_ +- [x] Mission log/trail surfaced in the UI. _(PS-held `/admin/mission-log` rendered in the `[data-test="mission-log"]` table, including the clarification entry.)_ +- [x] New Playwright spec passes; full backend stack boots via webServer array. _(`samples/SampleApp/playwright-tests/mission-call-chain.spec.ts` green; full `sample-app` suite 15 passed + 1 pre-existing skip, two consecutive clean CI runs.)_ + +--- + +## Phase 6 — Mission convenience seam (`WithMission`) + +**Goal:** Close PT-A7 (research Part A.2). Add an +`AAuthClientBuilder.WithMission(Mission)` seam that auto-emits the `AAuth-Mission` +header from an agent's own approved mission, so a mission-holding agent composes +`WithMission(...) + WithChallengeHandling() + WithInteractionHandling()` and the +entire resource-access leg (header + 401→exchange→retry) collapses to one signed +`SendAsync`. Retrofit the non-pedagogical sample (`MissionAgent`) to the seam; +leave the step-by-step teaching surfaces (`SampleApp/Mission.razor`, GuidedTour, +and the Phase 5 combined page) deliberately explicit so each gate stays visible. + +**Spec:** §Mission Context at Resources — "The agent includes the `AAuth-Mission` +header when sending requests to resources, unless the mission is already conveyed in +an auth token"; §HTTP Message Signatures — "When the agent is operating in a mission +context, it includes the `AAuth-Mission` header and adds `aauth-mission` to the +signed components." The SDK already auto-covers `aauth-mission` whenever the header +is present ([AAuthSigningHandler](../../../src/AAuth/HttpSig/AAuthSigningHandler.cs)), +so the seam only needs to set the header. + +### Approach + +- **`MissionHeaderHandler` (new).** A small `DelegatingHandler` that sets + `AAuth-Mission` from a directly-held `Mission` (`{approver, s256}`) on each + outbound request, mirroring `MissionForwardingHandler` but sourcing the mission + directly instead of extracting it from an upstream token. The signing handler + beneath it covers the `aauth-mission` component automatically. +- **`AAuthClientBuilder.WithMission(Mission)`.** Stores the mission and inserts the + handler at the top of the pipeline (above interaction/refresh/challenge), so the + header is present before the request is signed. Composes with + `WithChallengeHandling()` / `WithInteractionHandling()`. Idempotent with the + existing header — never emit `AAuth-Mission` twice (skip if the caller already set + it, matching the call-chaining carve-out). +- **Carve-out honored.** `WithMission(...)` is for the **originating** agent that + holds its own approved mission; call-chaining intermediaries keep using + `MissionForwardingHandler` (mission extracted from the upstream token). The two are + mutually exclusive on a given client. +- **Retrofit `MissionAgent`.** Replace the manual header + challenge cycle in + `AccessMissionResourceAsync` with a `WithMission(...)`-composed client; preserve + the per-request agent-token refresh (replay `jti`) behavior. + +### Files + +| File | Action | +|------|--------| +| `src/AAuth/Agent/MissionHeaderHandler.cs` | **New** — emits `AAuth-Mission` from a held `Mission` | +| `src/AAuth/AAuthClientBuilder.cs` | **Modify** — add `WithMission(Mission)`; insert handler in `BuildHandler()` | +| `samples/MissionAgent/Program.cs` | **Modify** — collapse `AccessMissionResourceAsync` onto the seam | +| `tests/AAuth.Conformance/Missions/MissionHeaderSeamTests.cs` | **New** — header emitted + signed; not duplicated; carve-out | + +### Implementation Decisions + +- **D5 — originating-agent seam (2026-06-06).** `WithMission(...)` sources the + mission directly; `MissionForwardingHandler` stays the call-chaining path. The + "unless conveyed in an auth token" carve-out remains the agent's decision — + `WithMission(...)` is only wired when the agent holds an approved `Mission` and is + the originator. Spec-backed by research Part A.2 PT-A7 update. +- Teaching surfaces stay explicit by design (DC4 pedagogy): only `MissionAgent` is + collapsed this phase; `Mission.razor`, the combined page, and GuidedTour keep the + visible gate-by-gate flow. + +### Definition of Done + +- [x] `WithMission(Mission)` emits a spec-correct `AAuth-Mission` header that the + signing handler covers as `aauth-mission`. +- [x] Header is not emitted twice when already present (call-chaining carve-out). +- [x] `MissionAgent.AccessMissionResourceAsync` collapsed onto the seam; behavior + unchanged (per-request refresh + replay `jti` preserved). +- [x] New conformance test covers emit + signature coverage + de-dup; full suite + green (build 0/0). + +--- + +## Phase 7 — Docs update + +**Goal:** Bring mission docs to the new API. + +**Spec:** as cited above. + +### Files + +| File | Action | +|------|--------| +| `docs/advanced/missions.md` | **Modify** — new surface | +| `docs/advanced/mission-governance-clients.md` | **Modify** — `MissionSession` lifecycle | +| `docs/advanced/clarification-chat.md` | **Modify** — link the new combined sample | +| `docs/server/mission-governance.md` | **Modify** — `MapAAuthGovernance()` + default seams | +| `docs/server/challenge-middleware.md` | **Modify** — resource governance builder | +| `docs/workflows/mission-governed-access.md` | **Modify** — updated walkthrough | + +### Definition of Done + +- [x] All mission docs reflect the new API; code blocks compile against the surface. _(Rewrote the stale faceted/per-call-PS examples in `mission-governance-clients.md`, `mission-governed-access.md`, `clarification-chat.md`, and `error-handling.md` to the bound `AAuthGovernanceClient` + `MissionSession` surface; added `MapAAuthGovernance()` + the no-op default seams and `AddAAuthDeferredConsent()` to `server/mission-governance.md`; `challenge-middleware.md`'s `ChallengeOptions { MissionAware = true }` is already the canonical resource seam per DEV-5.)_ +- [x] `WithMission(...)` convenience seam documented alongside `WithChallengeHandling`. _(New "Carrying your own mission with `WithMission`" section in `missions.md`, and the resource-access step of `mission-governed-access.md` now composes `WithMission(...)` + `WithChallengeHandling()`; the combined sample is linked from both `missions.md` and `clarification-chat.md`.)_ + +--- + +## Phase 8 — Samples consistency audit (subagent) + +**Goal:** With fresh eyes, validate that **every** sample uses the new API surface +and reads cleanly. A dedicated subagent surfaces inconsistencies, leftover old-API +usage, and readability problems across the sample projects; findings are adjudicated +and remediated. + +**Spec:** as cited in Phases 1–5 (the audit confirms samples match those citations). + +### Approach + +- **Subagent sweep.** Launch an exploration subagent scoped to `samples/**` to list + every mission/clarification/call-chain call-site, flag any that still use the old + surface, manual `MissionClaim` threading, repeated PS URLs, or inconsistent + construction styles, and rank readability issues. +- **Adjudicate + remediate.** Triage findings against the finalized Phase 2 surface; + fix in place. Log anything that turns out to be a genuine SDK gap or deviation in + [issues-and-deviations.md](issues-and-deviations.md). + +### Definition of Done + +- [x] Subagent report captured; each finding marked fixed / deferred / not-an-issue. _(Audit found zero stale faceted calls and zero manual `MissionClaim` constructions across `samples/**`; every remaining manual `AAuthMissionHeader.FormatStructured` is either a deliberately-explicit teaching surface — `Mission.razor`, `MissionCallChain.razor` snippet, GuidedTour `TourSession.cs`/`CodeSnippets.cs` — or the MockPersonServer acting as the legitimate header producer. All marked not-an-issue.)_ +- [x] No sample retains old-API mission usage or manual claim/PS threading. _(Confirmed: all governance call sites use the PS-bound `AAuthGovernanceClient` ctor → `ProposeMissionAsync` → flat `MissionSession` methods; call-chaining intermediaries `AgentConsole`/`Orchestrator` correctly use `WithCallChaining`, never `WithMission`.)_ +- [x] Significant issues logged in `issues-and-deviations.md`; research updated. _(No new issues — audit was clean; nothing to log beyond DEV-6/7/8 already recorded.)_ +- [x] All samples build/run; mission + combined e2e specs green. _(MissionAgent Phase-6 seam collapse verified clean; combined sample-app suite 15 passed + 1 pre-existing skip, two consecutive CI runs.)_ + +--- + +## Phase 9 — Docs & code-snippet consistency audit (subagent) + +**Goal:** Validate that all docs and embedded code snippets — especially GuidedTour +snippets and SampleApp walkthroughs — use the new API and read cleanly. A dedicated +subagent surfaces inconsistencies; findings are adjudicated and remediated. + +**Spec:** as cited in Phases 1–6 (the audit confirms docs match those citations). + +### Approach + +- **Subagent sweep.** Launch an exploration subagent scoped to `docs/**` plus + GuidedTour/SampleApp snippet sources to flag old-API code blocks, stale prose, + broken cross-links, and readability issues. +- **Adjudicate + remediate.** Update docs/snippets to the finalized surface; verify + code blocks compile against it. Log SDK gaps/deviations in + [issues-and-deviations.md](issues-and-deviations.md). + +### Definition of Done + +- [x] Subagent report captured; each finding marked fixed / deferred / not-an-issue. _(One file flagged — `docs/reference/dependency-injection.md`: stale "no dedicated DI extension" prose, a `BuildGovernance()` snippet missing `.WithPersonServer(...)` (would throw at runtime), and prose omitting the PS requirement. All three FIXED. All other mission/governance/clarification/call-chain docs verified clean against the GuidedTour `CodeSnippets.cs` ground truth.)_ +- [x] All mission docs + GuidedTour/SampleApp snippets reflect the new API. _(GuidedTour `CodeSnippets.cs` already compiles against the surface and was used as ground truth; docs now match it.)_ +- [x] Code blocks compile against the surface; cross-links resolve. _(`dependency-injection.md` now documents `AddAAuthGovernanceClient(...)` + PS-bound `BuildGovernance()`; cross-links/anchors `token-issuance.md#mission-claims`, `error-handling.md#mission-termination`, `challenge-middleware.md#mission-aware-resources` and the `MissionCallChain.razor` sample path all resolve.)_ +- [x] Significant issues logged in `issues-and-deviations.md`; research updated. _(Doc-only fix, no SDK gap — nothing new to log beyond DEV-6/7/8.)_ + +--- + +## Phase 10 — Independent spec-compliance review (subagent) + +**Goal:** A separate reviewer subagent independently validates **each change** in +this initiative against the AAuth spec to confirm 100% compliance. Where the SDK is +found non-compliant, fix it (DC6 still holds — fixes must not break existing flows). + +**Spec:** `aauth-spec/draft-hardt-oauth-aauth-protocol.md` (all cited sections); +`aauth-spec/upcoming-changes-02.md` for F1/F5/F6 context. + +### Approach + +- **Independent review.** Launch a reviewer subagent that walks the diff of this + initiative phase by phase and checks every behavior against the governing spec + section, with no assumption that prior phases were correct. +- **Adjudicate against spec text.** Each flagged item is confirmed against the spec + before any change; genuine non-compliance is fixed in the SDK, deviations that are + intentional or pending draft-02 are documented. +- **Final ledger.** All significant findings recorded in + [issues-and-deviations.md](issues-and-deviations.md); research updated with the + closing compliance summary. + +### Definition of Done + +- [x] Reviewer subagent report captured; every finding adjudicated against spec text. _(Reviewer walked all six areas — `AAuth-Mission` header + signed-component coverage, mission claim shape + verbatim-bytes hash, the four endpoint request/response shapes, the deferred-consent 202 flow incl. the DEV-6 bug fixes, clarification round-trip + limits, `mission_terminated` surfacing, and originator-vs-intermediary header rules — each cited to a spec section. One genuine non-compliance (NC-1) found; everything else COMPLIANT; DEV-5 reconfirmed intentional.)_ +- [x] SDK non-compliance fixed without breaking existing flows (DC6). _(NC-1 → DEV-9: `interaction`/`payment` now honor `InteractionRelayResult.Pending` by parking on the deferred-consent store and answering `202` + poll `Location`, degrading to a synchronous `200` when no store is registered. Agent side already polled correctly; no client change. DEV-10 (completion synchronous review) adjudicated as an intentional, spec-tolerable simplification.)_ +- [x] `dotnet build AAuth.slnx` 0/0; unit + conformance + mission/combined e2e green. _(SDK build 0/0; `GovernanceDeferredConsentMapperTests` 12/12 incl. 4 new NC-1 cases — full suites run below.)_ +- [x] `issues-and-deviations.md` finalized; research updated; plan DoD ticked. _(DEV-9 + DEV-10 logged.)_ +- [ ] Major open decisions surfaced to the user for input. + +--- + +## Phase 11 — Post-commit review remediation (two subagents) + +**Goal:** After the initiative was committed, run two independent review subagents — +one grounding **every SDK change against the spec**, the other grounding **all docs +and sample snippets against the SDK/repo** — then remediate the findings. Each fix is +spec- or source-grounded; major cross-cutting decisions are surfaced, not rushed. + +**Spec:** `aauth-spec/draft-hardt-oauth-aauth-protocol.md` (§Polling Error Codes, +§Error Responses, §Token Endpoint Error Codes, §Interaction Response); SDK source of +truth `src/AAuth/`. + +### Approach + +- **SDK-vs-spec reviewer.** Walked the `origin/main..HEAD` diff under `src/AAuth/`, + grounding each mission/governance/clarification/interaction/call-chaining behavior + in a cited spec section. Verdict: **COMPLIANT-WITH-FINDINGS** — no CRITICAL/HIGH; + four findings (S-1 `access_denied`→`denied`, S-2 carrier-token 401 shape, S-3 + `user_unreachable` forward-looking, S-4 = the already-adjudicated DEV-10). +- **Docs/samples-vs-SDK reviewer.** Read all 12 changed docs + sample snippets and + verified every referenced symbol against `src/AAuth/`. Verdict: + **GROUNDED-WITH-FINDINGS** — seven drift items (D-1..D-7), the samples themselves + build 0/0 (grounded by construction; the docs had diverged from the SDK). +- **Remediate.** Doc drift fixed against the SDK (the source of truth). SDK findings + adjudicated against spec text: S-1 surfaced as a decision (cross-cutting, spans the + out-of-scope AccessServer path), S-2/S-3/S-4 documented as intentional. + +### Definition of Done + +- [x] Two reviewer subagents run; both reports captured and every finding adjudicated. +- [x] Doc/snippet drift fixed and grounded against `src/AAuth/` (DEV-11): `MissionAction` + bare-string snippets, `MyPermissionDecider` comparison, `AAuthAgentOptions` + examples + tables (dependency-injection + configuration), `TokenErrorCode` listing, + the `/mission-interaction` path comment, and the no-op-seam prose. +- [x] SDK findings adjudicated against spec: DEV-12 (S-1) surfaced as needs-decision; + DEV-13 (S-2) and DEV-14 (S-3) logged intentional; S-4 already DEV-10. +- [x] `issues-and-deviations.md` updated (DEV-11..DEV-14); `dotnet build AAuth.slnx` + 0/0 (only `.md` files changed — no code touched). +- [x] DEV-12 (`access_denied`→`denied`) full rename executed (user-approved, no alias): + all SDK emits/classifiers, AS path, sample mocks, SampleApp/GuidedTour narration, + conformance/integration tests, Playwright specs, and the docs snippet now use + `denied`; `dotnet build AAuth.slnx` 0/0. + +--- + +## Phase 12 — Post-rename SDK hardening: PS role + four spec-shape fixes + +**Goal:** Land the five-item improvement backlog from the 2026-06-07 deep review +([research.md](research.md) Part H): promote the **Person Server** into a first-class +one-call SDK role and tighten four spec-shape gaps in the governance/interaction +surface. Every item is grounded in a cited spec section and the current SDK state. +All five are in scope; one sub-task (the F5 PS emit) is intentionally gated on +draft-02 and split from its unblocked agent-side half. + +**Spec:** `aauth-spec/draft-hardt-oauth-aauth-protocol.md` (§PS-asserted access / +§Incremental adoption L162, §Auth Token Delivery, §Interaction Response L1212, +§Error Responses L1998 + L2108, §Interaction Endpoint); `aauth-spec/upcoming-changes-02.md` +§2 (F5). + +> **Correction folded in.** The Access Server is **already** a first-class SDK role +> (`MapAAuthAccessServer` in `src/AAuth/Access/AAuthAccessServerEndpoints.cs`); there +> is **no "Mission Manager" party** in the spec or code. The genuine additive gap is +> the **Person Server**, which is the only server role without a one-call mapper. + +### Implementation Decisions + +- **W1 seam shape.** Add `IIdentityClaimsAsserter` mirroring `IAccessPolicy` + (directed `sub` + asserted claims + silent/consent/deny), plus a PS-side + pending/consent store mirroring `IAccessPendingStore`. The SDK keeps all crypto + (resource-token verification, AS federation via `AccessServerClient`, the §Auth + Token Delivery 7-step check, and the auth-token mint via `AuthTokenBuilder`). +- **W1 scope guard (extends DEV-4; revised 2026-06-07 per user direction).** + `MapAAuthPersonServer` packages **both** the three-party collapsed mint and the + four-party federation branch (keyed off the resource-token `aud`), **and** the + mission three-gate *token-issuance mechanics*: gate-1 terminated rejection + (`IMissionStore`), gate-2a/2b silent grant (in-approved-intent / prior-consent via + the asserter + `IMissionLog`), and gate-3 park-and-prompt (`202 requirement=interaction` + + a PS pending entry). The mission scope/consent *policy* decision is the + `IIdentityClaimsAsserter`'s job; the SDK keeps the `IMissionStore`/`IMissionLog` + mechanics and the mission-bound mint. **Still host-mapped (not in the SDK):** the + interactive consent / clarification UI page itself (the `MissionConsentScript` + scripted chat is test scaffolding) and the pending-verdict resolution — exactly how + `MapAAuthAccessServer` delegates its `InteractionLoginPath` page to the host. + `MockPersonServer`'s existing hand-wired interactive path stays as-is (DC6, no + regressions); the mapper is the additive one-call alternative. +- **W2 split.** Ship the **agent-side** typed-exception classification now (replace + the generic `HttpRequestException` in `DeferredExchange` with a terminal typed + exception). The **PS emit** of `400 user_unreachable` stays gated on draft-02 + (emitting today diverges from the authoritative L1213 `interaction_required` + wording) — DEV-14. +- **W3.** Reuse the DEV-9 park-and-poll machinery for the `completion` arm; keep the + synchronous 200 fallback when no `IDeferredConsentStore` is registered. +- **W4 decision = Option B.** Return **403** `{error:"invalid_carrier_token"}` for + the mission carrier-type mismatch (authz refusal, not a 401 signature failure), per + the H.4 analysis. Update the two pinning tests (`GovernanceDeferredConsentMapperTests`, + `MockPersonServerTests`) to match — DEV-13. +- **W5.** Add a `DelegateInteractionRelay` (lambda relay) alongside the no-op + `DefaultInteractionRelay`; pure ergonomics, no spec change — DEV-3. + +### Work items + +- **W1 — `MapAAuthPersonServer(...)` (additive, largest).** New mapper + options + + `IIdentityClaimsAsserter` seam + PS pending store, wrapping the existing + `AuthTokenBuilder` / `AuthTokenResponseValidator` / `AccessServerClient` / + `TokenVerifier`. Mirrors `MapAAuthAccessServer`. Closes the PS role-symmetry gap; + unblocks external adopters running a real PS in one call. +- **W2 — `user_unreachable` agent classification (DEV-14, partial).** Throw a typed + terminal exception (not `HttpRequestException`) from the no-callback deferred path + in `src/AAuth/Agent/DeferredExchange.cs`. PS emit deferred to draft-02. +- **W3 — deferred `completion` review (DEV-10).** Honor `InteractionRelayResult.Pending` + in the `Completion` arm of `HandleInteractionAsync`; park + 202 + poll when a store + exists, synchronous 200 otherwise. +- **W4 — mission carrier-type 401→403 (DEV-13).** Change `HandleMissionAsync`'s + carrier guard to 403; update the two pinning tests. +- **W5 — `DelegateInteractionRelay` (DEV-3).** Lambda-friendly relay so a PS supplies + a user channel without a full class. + +- **W6 — docs / samples / snippets sync (grounded against the SDK).** Every W1–W5 + change that is observable to an adopter is reflected in the docs, sample READMEs, + and GuidedTour/SampleApp narration, then a docs-vs-SDK grounding pass (mirroring + Phase 11 / DEV-11) confirms no snippet drift. Impacted surfaces (from a workspace + scan — confirm and extend during the phase): + - **W1 (new public API):** add a PS token-issuance page covering `MapAAuthPersonServer` + + `AAuthPersonServerOptions` + `IIdentityClaimsAsserter` — [docs/server/token-issuance.md](../../../docs/server/token-issuance.md), + cross-linked from [docs/workflows/ps-asserted-access.md](../../../docs/workflows/ps-asserted-access.md) + and [docs/workflows/federated-access.md](../../../docs/workflows/federated-access.md) + (it sits beside `MapAAuthAccessServer` at L117/L135); register the new seam in + [docs/reference/dependency-injection.md](../../../docs/reference/dependency-injection.md) + and [docs/reference/configuration.md](../../../docs/reference/configuration.md). + Optionally migrate `MockPersonServer`'s non-interactive path + README to the mapper + (interactive consent page stays hand-wired per DEV-4). + - **W2:** `TokenErrorCode.UserUnreachable` / terminal-vs-non-terminal classification — + [docs/advanced/error-handling.md](../../../docs/advanced/error-handling.md) (still + flagged forward-looking until draft-02, DEV-14). + - **W3:** completion now returns a deferred `202`/poll when the relay is pending — + [docs/server/mission-governance.md](../../../docs/server/mission-governance.md) + (`InteractionRelayResult` table L247) and [docs/workflows/mission-governed-access.md](../../../docs/workflows/mission-governed-access.md) + (the propose-completion step L131/L135). + - **W4:** mission carrier-type mismatch now `403` (not `401`) — any error-shape table + in [docs/server/mission-governance.md](../../../docs/server/mission-governance.md) + / [docs/server/authn-authz.md](../../../docs/server/authn-authz.md). + - **W5:** `DelegateInteractionRelay` as the lambda alternative to a full + `IInteractionRelay` class — [docs/server/mission-governance.md](../../../docs/server/mission-governance.md) + (L36/L46/L251) and [docs/reference/dependency-injection.md](../../../docs/reference/dependency-injection.md) + (L412/L420). + +### Spec validation (verbatim quotes) + +Each work item is validated against the authoritative spec text below +(`aauth-spec/draft-hardt-oauth-aauth-protocol.md` unless noted). Quotes are verbatim; +line numbers are current as of 2026-06-07. + +**W1 — `MapAAuthPersonServer`.** The PS role this mapper packages is exactly the +PS-asserted (three-party) and federated (four-party) issuer the spec defines. + +- §Overview L162: *"Issuing resource tokens to the agent's person server enables + PS-asserted access (three-party): the PS asserts identity claims about the user + (`sub`, optionally `email`, `tenant`, `groups`, `roles`) and confirms user consent + for the scope the resource requested; the resource applies its own policy on the + resulting claims."* → drives the `IIdentityClaimsAsserter` seam (directed `sub` + + optional claims + consent decision). +- §PS-AS Federation L1466: *"The PS is the only entity that calls AS token endpoints… + If `aud` matches the PS's own identifier, the PS issues an auth token asserting + identity and consent for the requested scope (three-party). If `aud` identifies a + different server (an AS)… the PS… calls the AS's `token_endpoint` (four-party)."* + → the mapper's two branches (collapsed mint via `AuthTokenBuilder` vs. federation + via `AccessServerClient`) are spec-mandated, keyed off the resource token `aud`. +- §Auth Token Delivery L1439 (the 7-step check the SDK keeps, not the PS): *"When the + AS issues an auth token (`200` response), the PS MUST verify the auth token before + returning it to the agent: 1. Verify the auth token JWT signature… 2. Verify `iss`… + 3. Verify `aud`… 4. Verify `agent`… 5. Verify `cnf.jwk`… 6. Verify `act`… 7. Verify + `scope` is consistent with what was requested — not broader than the scope in the + resource token."* → `AuthTokenResponseValidator.ValidateAsync` already implements + all seven; the mapper wires it into the federation branch. +- §Claims Required L1450: *"A server MUST use `requirement=claims` with a `202 Accepted` + response when it needs identity claims… The recipient MUST provide the requested + claims (including a directed user identifier as `sub`)…"* → the AS-side `OnClaimsRequired` + callback the PS mapper surfaces. **Verdict: COMPLIANT — the mapper packages existing + spec-conformant primitives; no new wire behavior.** +- §Agent Token Request L812 (mission gating, folded into the mapper per the revised + scope guard): *"the PS evaluates the request against mission scope, handles user + consent if needed, and uses the same requirement response patterns."* and §Resource + Tokens L784: *"The PS SHOULD remember prior consent decisions within a mission so the + user is not re-prompted when the agent resubmits a request for the same resource and + scope."* → the mapper's gate-2a (in-approved-intent) and gate-2b (prior consent via + `IMissionLog`) silent grants, gate-1 terminated rejection, and gate-3 park-and-prompt + (`202 requirement=interaction`) are spec-mandated; the interactive consent page that + resolves gate-3 stays host-mapped. **Verdict: COMPLIANT — the mapper packages the + three-gate model over existing `IMissionStore`/`IMissionLog` primitives.** + +**W2 — `user_unreachable` (agent classification now; PS emit gated).** This code is +**not yet** in the authoritative draft. + +- Authoritative §Interaction Response L1213 (today): *"If the PS cannot reach the user + and the agent does not have the `interaction` capability, the PS returns + `interaction_required`."* — so emitting `user_unreachable` now would **contradict** + the authoritative text. +- `aauth-spec/upcoming-changes-02.md` §2: *"Add `user_unreachable` as a distinct + terminal error… `user_unreachable` | 400 | Terminal | PS has no channel to the user + AND the agent didn't declare `interaction` capability."* and *"Error classification + (Gap E) should treat `user_unreachable` as a terminal, non-retryable error distinct + from `interaction_required`."* **Verdict: agent-side terminal classification is + COMPLIANT with the agreed draft-02 direction and changes no wire output; the PS + emit stays DEFERRED until draft-02 lands (DEV-14) to avoid contradicting L1213.** + +**W3 — deferred `completion` review.** + +- §Interaction Response L1212: *"For `completion` type, the PS presents the summary to + the user. The user either accepts — the PS terminates the mission and returns + `200 OK` — or responds with follow-up questions via clarification, keeping the + mission active. **The PS returns a deferred response while the user reviews.**"* +- §Interaction Response L1199 (the parallel the interaction arm already follows): *"For + `interaction` and `payment` types, the PS relays the interaction to the user and + returns a deferred response. The agent polls until the user completes the interaction."* + **Verdict: today's synchronous `Completion` arm is spec-tolerable but not spec-shaped; + honoring `Pending` (park + 202 + poll) makes it match the highlighted sentence. The + 202/poll mechanics reuse §Deferred Responses, already implemented for DEV-9.** + +**W4 — mission carrier-type guard (401→403).** + +- §Error Responses / Authentication Errors L1998: *"A `401` response from any AAuth + endpoint uses the `Signature-Error` header."* +- §Verification (Server) L2108: *"When a server receives a signed request, it MUST + perform the following steps. Any failure MUST result in a `401` response with the + appropriate `Signature-Error` header."* — the carrier-type check is **not** one of + these signature-verification steps (the signature already verified), so a bare + `401 {error:"invalid_carrier_token"}` JSON response sits outside the spec's 401 + contract. **Verdict: returning `403` (an authorization refusal, not a signature + failure) is the spec-correct shape — Option B — keeping every actual `401` bound to + the `Signature-Error` header per L1998/L2108.** + +**W5 — `DelegateInteractionRelay`.** + +- §Interaction Endpoint L1131: *"The interaction endpoint enables the agent to reach + the user through the PS… The agent uses this endpoint to forward interaction + requirements from resources that it cannot handle directly, to ask the user + questions, to relay payment approvals, or to propose mission completion."* — the + spec defines the endpoint behavior; **how** the PS reaches the user is an + implementation concern. **Verdict: COMPLIANT — adding a lambda-based relay alongside + the no-op default is a pure ergonomic SDK seam with no protocol effect.** + +### Definition of Done + +- [x] **W1:** `MapAAuthPersonServer` + `AAuthPersonServerOptions` + `IIdentityClaimsAsserter` + + PS pending store land; covered by conformance/integration tests; the three-party + collapsed mint and four-party federation branches both exercised. _(New SDK files + `src/AAuth/Person/{IIdentityClaimsAsserter,IPersonPendingStore,AAuthPersonServerEndpoints}.cs`; + 8 conformance tests in `tests/AAuth.Conformance/Person/PersonServerMapperTests.cs` — + three-party silent mint + agent-key binding, carrier-type 403, missing-resource-token 400, + deny→403, NeedsConsent→202→poll→mint, mission terminated→403, mission in-scope mint+grant-logged, + and the four-party untrusted-AS routing guard. Mapper packages both branches + the mission + three-gate token-issuance mechanics per the 2026-06-07 user-directed scope revision.)_ +- [x] **W1:** `MockPersonServer` interactive flows remain hand-wired and e2e-green + (no DEV-4 regression). _(MockPersonServer endpoints untouched by W1; the README now points + to the SDK one-call helper as the non-interactive alternative while the sample keeps its + interactive consent/mission screens hand-wired. MockPersonServer integration tests green in + the 387 unit baseline.)_ +- [x] **W2:** agent no-callback deferred path throws a typed terminal exception; PS + emit remains gated on draft-02 (DEV-14 note updated, not closed). _(DeferredExchange throws + `AAuthTokenExchangeException(UserUnreachable, statusCode:400, isTerminal:true)`; documented in + `docs/advanced/error-handling.md` with the forward-looking draft-02 PS-emit note.)_ +- [x] **W3:** `Completion` arm defers via the store (202 + poll) and degrades to + synchronous 200; new mapper test covers both; DEV-10 status flipped to fixed. _(GovernanceDeferredConsentMapperTests + 16/16; documented in `docs/workflows/mission-governed-access.md` propose-completion step.)_ +- [x] **W4:** mission carrier-type mismatch returns 403; the two pinning tests + updated; DEV-13 status flipped to fixed. _(Documented in `docs/server/mission-governance.md` + carrier-type guard note; MockPersonServer 4× 401→403; MockPersonServerTests 7/7.)_ +- [x] **W5:** `DelegateInteractionRelay` added with a test; DEV-3 status flipped to fixed. + _(`AddAAuthInteractionRelay(...)` documented in `docs/server/mission-governance.md` + + `docs/reference/dependency-injection.md`.)_ +- [x] **W6:** docs, sample READMEs, and GuidedTour/SampleApp narration updated for every + observable W1–W5 change; a docs-vs-SDK grounding pass (Phase 11 / DEV-11 style) + confirms every snippet compiles against `src/AAuth/` with no drift. _(token-issuance.md + one-call PS section + AAuthPersonServerOptions/IIdentityClaimsAsserter tables; configuration.md + + dependency-injection.md seam registration; ps-asserted-access.md + federated-access.md cross-links; + MockPersonServer README note. Every new snippet hand-verified against the SDK surface read this + phase — no automated snippet harness exists; GuidedTour `CodeSnippets.cs` unaffected.)_ +- [x] `dotnet build AAuth.slnx` 0/0; unit + conformance + relevant e2e green. _(Solution 0/0; + unit 387, conformance 480 — both green. e2e not re-run: no e2e-observable behavior changed + (W4 status codes have integration coverage; W1 is additive SDK surface).)_ +- [x] `issues-and-deviations.md` updated (DEV-3/10/13/14 dispositions; any new W1 DEVs); + research Part H cross-checked. _(DEV-3→fixed (W5), DEV-10→fixed (W3), DEV-13→fixed (W4), + DEV-14 stays forward-looking with the W2 agent-side note; new DEV-15 records the + user-directed W1 scope expansion.)_ + +--- + +## Phase 13 — GuidedTour combined "Mission Call Chain" flow + +**Goal:** Add a new `TourMode.MissionCallChain` flow to the GuidedTour sample so the +step-by-step raw-HTTP walkthrough demonstrates the same combined use case the +SampleApp `MissionCallChain.razor` page already shows: **one human-approved mission +governs (a) a clarification round on an out-of-mission elevated scope and (b) a +mission-forwarded delegated call chain**, then surfaces the PS-held mission log. The +GuidedTour today has *separate* Mission (`TourMode.Mission`) and Call-Chain +(`TourMode.CallChain`) flows but no combined one; this closes that gap and gives the +tour parity with the SampleApp's `/mission-call-chain` page. Ships with a guided-tour +Playwright spec mirroring `samples/SampleApp/playwright-tests/mission-call-chain.spec.ts`. + +**Spec:** `aauth-spec/draft-hardt-oauth-aauth-protocol.md` §Clarification Chat +(out-of-mission scope triggers a PS question before the prompt), §Mission Context at +Resources + §Call Chaining (the `AAuth-Mission` header is forwarded hop-to-hop so the +mission governs every hop), §Mission Log (the PS holds the ordered governed trail). +No SDK or spec change — this is an additive **sample** flow over the existing engine. + +### Implementation Decisions + +- **D1 — Additive over the existing engine.** Reuse the modular `TourSession` mode + machinery (one enum value + `IsXxxMode` property + `TotalSteps`/`Plan` cases + a + step switch + `PrepareConsentStateAsync` branch + a Tour.razor picker option + a + sequence-diagram lane set). No existing flow is changed (DC6, no regressions). +- **D2 — Mirror the SampleApp three-pillar shape.** The combined flow demonstrates + three pillars under one mission: (1) mission creation (PROMPT), (2) an elevated + out-of-mission scope that triggers a **clarification round** (§Clarification Chat) + before the user prompt, and (3) a **mission-forwarded call chain** (the + `AAuth-Mission` header carried to the Orchestrator and forwarded to its WhoAmI hop) + that resolves **silently** because both chain scopes are seeded in-scope — then the + mission log. Rendered as raw-HTTP micro-steps (the tour's idiom), not three macro + cards (the SampleApp's idiom). +- **D3 — Clarification is new to the tour engine.** The existing `TourMode.Mission` + flow has no clarification round; the combined flow adds the raw-HTTP clarification + exchange (the PS answers the out-of-mission token request with a clarification + challenge, the agent posts an answer, then the normal 202 + interaction prompt + runs). Scripted via the existing MockPersonServer `requireClarification` / + `clarificationQuestion` mission-script fields (already used by the SampleApp page; + `MissionGovernance.cs` already models `ClarificationQuestion` + `SeedInScope`). +- **D4 — Reuse the MockPersonServer admin scripting verbatim.** `PrepareConsentStateAsync` + for the new mode posts `/admin/reset` + `/admin/mission-script` with + `requireClarification=true`, a `clarificationQuestion`, and **both** chain scopes + seeded in-scope (`{WhoAmIUrl, whoami}` and `{OrchestratorUrl, orchestrate}`), so the + chain hops resolve silently. The mission log is read from `/admin/mission-log/{s256}`. + No new MockPersonServer endpoints — the SampleApp page already exercises all of them. +- **D5 — e2e parity.** Add `samples/GuidedTour/playwright-tests/mission-call-chain.spec.ts` + driving the new flow end-to-end (propose → approve, elevated clarification + approve, + silent forwarded chain, mission-log assertions), reusing the shared e2e helpers + (`fixtures`, `blazor`, `consent`). The Playwright `webServer` array already boots PS + + AP + Orchestrator + WhoAmI for the existing guided-tour mission/call-chain specs. + +### Work items + +- **W1 — Engine: `TourMode.MissionCallChain`.** Add the enum value; `IsMissionCallChainMode`; + `TotalSteps` + `Plan` cases; the `MissionCallChainPlan` step array; the step-dispatch + switch in `RunNextAsync`; the clarification-round raw-HTTP helper; the mission-forwarded + chain step(s); the mission-log fetch/render step; `PrepareConsentStateAsync` branch. +- **W2 — UI: Tour.razor.** Add the picker `