diff --git a/CLAUDE.md b/CLAUDE.md index 77003cb08..aa2737823 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,10 +31,11 @@ The system is orchestrated by `src/cli.ts` and managed by `src/docker-manager.ts **4. Unified Enclaves (optional)** — `containers/enclave/` - Enabled by declaring `enclaves` entries in the AWF config file - One AWF-owned MCP server (`enclave-mcp-server`) exposes enabled enclave executors only through compiler-launched `gh-aw-mcpg`; the primary agent gets no direct enclave socket, wrapper binary, capability, or private transport -- `enclave_run_script` launches a no-network, read-only, single-use Python executor and returns one canonical JSON result -- `enclave_run_agent` launches a single-use Copilot enclave on the dedicated `internal` `awf-enclave-agent` network whose sole peer is the dedicated API proxy; Squid, the primary agent, the general API proxy, safe outputs, and the MCP gateway are excluded +- `enclave_run_script` launches a no-network, read-only, single-use Python enclave and returns one canonical JSON result +- `enclave_run_agent` launches a single-use Copilot enclave on the dedicated `internal` `awf-enclave-agent` network. Its mandatory peer is the dedicated API proxy; when GitHub access is configured (static `agent.tools.github` / legacy `issues-read-v1`, or a dynamic entry), compiler-owned shared mcpg is also attached at `172.31.0.40` under alias `awf-enclave-github-mcp`. Squid, the primary agent, the general API proxy, safe outputs, and `enclave-mcp-server` itself are excluded, and AWF asserts the exact membership set before launch - Script and agent executors are configured as a top-level `enclaves` array of `script`/`agent` entries whose merged `repos` lists form one trusted repository catalog, sharing one per-run information ledger and one AWF-owned admission lane -- Rollout depends on the compiler handoff contract in `github/gh-aw#50920` and late backend rediscovery in `github/gh-aw-mcpg#10784`, which requires MCP Gateway spec 1.15.0 and the first mcpg release after v0.4.8 containing it +- **Dynamic repository enclaves** additionally admit repositories at runtime through mcpg's `github-repository-delegation-v1` controller. AWF's control client runs in the **host process** (the control endpoint is published to host loopback only) and `enclave-mcp-server` reaches it over a `0700` AWF-private file channel, not a network. That control listener is guarded by capability authentication alone — see `github/gh-aw#59268`, closed as not planned +- Rollout depends on the compiler handoff contract in `github/gh-aw#50920` and late backend rediscovery in `github/gh-aw-mcpg#10784`, which requires MCP Gateway spec 1.15.0 and the first mcpg release after v0.4.8 containing it. That floor covers static entries; **dynamic repository admission requires mcpg v0.4.18 or newer**, whose `internal/delegation/wire.go` encodes delegation durations as whole seconds (v0.4.17 read the same fields as `time.Duration` nanoseconds) - While the gateway backend is still coming up, AWF retries retryable HTTP `503 backend_unavailable` responses within `AWF_ENCLAVE_MCP_READINESS_TIMEOUT_MS` - See [docs/enclaves-architecture.md](docs/enclaves-architecture.md) and [docs/awf-config-spec.md](docs/awf-config-spec.md) §14 diff --git a/docs/awf-config-spec.md b/docs/awf-config-spec.md index 83046b327..7b3d7f5f4 100644 --- a/docs/awf-config-spec.md +++ b/docs/awf-config-spec.md @@ -2033,7 +2033,9 @@ server itself remain excluded. The base compiler handoff from `github/gh-aw#50920` and late backend rediscovery from `github/gh-aw-mcpg#10784` are present in mcpg v0.4.15, which reports MCP Gateway spec 1.16.0. The base floor remains spec 1.15.0 and a -post-v0.4.8 mcpg release. +post-v0.4.8 mcpg release. That floor covers static entries only; dynamic +repository admission requires mcpg v0.4.18 or newer for the whole-second +delegation duration encoding (see §14 and `docs/enclaves-architecture.md`). GitHub access additionally requires compiler support for mcpg multi-agent identities and policies, tracked by `github/gh-aw#57787`. The compiler MUST gate diff --git a/docs/enclaves-architecture.md b/docs/enclaves-architecture.md index f8eb47a57..a545a943a 100644 --- a/docs/enclaves-architecture.md +++ b/docs/enclaves-architecture.md @@ -6,11 +6,19 @@ Layer 5 establishes one `enclaves` subsystem, one AWF-owned MCP server, and mcpg Dynamic repository admission described below is implemented and version-gated. It runs only when the gh-aw compiler starts mcpg's -`github-repository-delegation-v1` controller (mcpg v0.4.17 or newer) and hands +`github-repository-delegation-v1` controller (mcpg v0.4.18 or newer) and hands AWF its loopback-only control endpoint and AWF-only control capability; every other combination fails closed before execution. Static entries continue to declare a non-empty `repos` list and use immutable seeds. +v0.4.18 is the floor, not v0.4.17, because the delegation duration fields are +whole seconds on the wire. v0.4.17 declared `max_identity_ttl` and +`requested_ttl` as Go `time.Duration` and so decoded them as **nanoseconds**; +v0.4.18 added `internal/delegation/wire.go`, which decodes both as `int64` +seconds. Pairing current AWF with v0.4.17 would silently reinterpret a +120-second TTL as 120 nanoseconds. gh-aw pins the matching default in +`pkg/constants/version_constants.go` (`DefaultMCPGatewayVersion`). + ## Architecture AWF stages immutable repository seeds on the host, starts one AWF-owned `enclave-mcp-server`, and exposes enabled executors only through `gh-aw-mcpg`. @@ -101,7 +109,9 @@ error immediately instead of entering an unbounded fixed-timing queue. The base MCP handoff and late backend rediscovery are present in mcpg v0.4.15, which reports MCP Gateway spec 1.16.0. The earlier minimum remains spec 1.15.0 -and a post-v0.4.8 mcpg release. +and a post-v0.4.8 mcpg release. That minimum applies to static entries only; +dynamic repository admission requires v0.4.18 or newer, as described under +Status above. The optional GitHub path additionally requires compiler support for mcpg multi-agent identities and policies (tracked by `github/gh-aw#57787`). The @@ -408,6 +418,14 @@ plane is therefore protected by **authentication**: every request must carry the AWF-only capability, which is never placed in any container's environment or mount, and mcpg rejects anything else with `403 delegation_access_denied`. +Relying on authentication alone here is a deliberate, reviewed choice rather +than an oversight: `github/gh-aw#59268` proposed adding a topological control +and was closed as not planned, with the reasoning recorded on the issue. mcpg +verifies the capability in constant time against a stored SHA-256 digest, +before any control routing, on a handler separate from the executor-facing data +plane — so holding a valid executor bearer does not reach control operations +even from a co-attached network. + The broker asks the host for admission over an AWF-private request/response directory inside the `0700` enclave private root that is bind-mounted only into the broker: diff --git a/src/enclave/delegation-control-client.ts b/src/enclave/delegation-control-client.ts index 8ba15ccac..bd9bf9bfb 100644 --- a/src/enclave/delegation-control-client.ts +++ b/src/enclave/delegation-control-client.ts @@ -1,7 +1,12 @@ /** - * Strict client for mcpg v0.4.17's `github-repository-delegation-v1` control + * Strict client for mcpg v0.4.18's `github-repository-delegation-v1` control * channel (`github/gh-aw-mcpg` `internal/proxy/delegation.go`, - * `internal/delegation/{identity,store,envelope}.go`). + * `internal/delegation/{identity,store,envelope,wire}.go`). + * + * v0.4.18 is the minimum: it introduced `internal/delegation/wire.go`, which + * decodes `requested_ttl` and `max_identity_ttl` as `int64` whole seconds. + * v0.4.17 typed them as Go `time.Duration` and so read the same JSON as + * nanoseconds. * * Wire contract, reproduced exactly because mcpg decodes with * `DisallowUnknownFields` and rejects anything outside it: diff --git a/src/enclave/dynamic-delegation-contract.test.ts b/src/enclave/dynamic-delegation-contract.test.ts index f7c596790..050668911 100644 --- a/src/enclave/dynamic-delegation-contract.test.ts +++ b/src/enclave/dynamic-delegation-contract.test.ts @@ -9,10 +9,12 @@ * `pkg/workflow/enclaves.go` (PR #59046): the exported control endpoint, * the fixed control API base path, the controller name, the envelope's * `run_id` and `enclave_backend`. - * - `github/gh-aw-mcpg` v0.4.17 `internal/proxy/delegation.go` and + * - `github/gh-aw-mcpg` v0.4.18 `internal/proxy/delegation.go`, + * `internal/delegation/wire.go`, and * `internal/delegation/{identity,store,selector}.go` (PR #12605): the * operation paths, the `CreateOrConfirmRequest`/`IdentityResult` JSON key - * sets, the status/reconcile/revoke shapes, and the closed tool set. + * sets, the whole-second duration encoding, the status/reconcile/revoke + * shapes, and the closed tool set. */ import * as http from 'http'; @@ -98,7 +100,7 @@ describe('gh-aw handoff contract', () => { }); }); -describe('mcpg v0.4.17 wire contract', () => { +describe('mcpg v0.4.18 wire contract', () => { const captured: Record[] = []; let server: http.Server; let client: DelegationControlClient; diff --git a/src/enclave/dynamic-delegation-handoff.ts b/src/enclave/dynamic-delegation-handoff.ts index 147a8e405..01f2ada3a 100644 --- a/src/enclave/dynamic-delegation-handoff.ts +++ b/src/enclave/dynamic-delegation-handoff.ts @@ -44,7 +44,7 @@ export const ENCLAVE_GITHUB_DELEGATION_CONTROL_CAPABILITY_ENV = 'AWF_ENCLAVE_GITHUB_DELEGATION_CONTROL_CAPABILITY'; /** - * Fixed control API base path served by mcpg v0.4.17 + * Fixed control API base path served by mcpg v0.4.18 * (`internal/proxy/delegation.go`: `delegationControlPath`). Operations are * siblings of the controller name, not children of it. */