# AGENTS.md

Guidance for AI coding agents working in this repository. Follows the [agents.md](https://agents.md) open specification: a single, predictable entry point for agent-relevant context. Human-facing onboarding lives in `README.md` and `CONTRIBUTING.md`; agent-specific tooling files (`CLAUDE.md`, etc.) re-export this file rather than duplicating it.

## What gridctl is

Gridctl is an MCP (Model Context Protocol) gateway with a built-in skills and agents registry. A user declares a stack of MCP servers (containerized stdio, SSE/HTTP, OpenAPI-backed, local processes, SaaS proxies) in `stack.yaml`, runs `gridctl apply`, and gridctl orchestrates the containers, fans tool calls to the right server, aggregates downstream prompts and resources with registry skills, and surfaces every active `SKILL.md` to upstream clients as an MCP prompt. A projection engine (`pkg/project`, one lockfile at `~/.gridctl/project.lock.yaml`) also places skills, agents, global-context rules, and gateway wiring onto disk for file-reading clients, and packs (`gridctl-pack.yaml`) import skills, agents, rules, and wiring from one git repo, may select skills and agents from declared external git sources pinned by commit (`gridctl pack lock` writes `gridctl-pack.lock.yaml`), can start an optional carried stack from a pinned checkout under `~/.gridctl/packs/`, and can compose booster fragments onto that base daemon. The same process embeds a React web UI on `:8180`. Inspired by Containerlab.

Registry skills also expose `io.modelcontextprotocol/skills` through the existing MCP endpoints.
Handshake sessions opt in; stateless discovery declares it while the registry provider is available.
The global skill policy governs manifests and reads, including grouped endpoints. CLI skill lists
report local manifest summaries, including a per-row error when one skill cannot be hashed; status
reports declared extensions. The compatibility baseline is
`tests/integration/skills-extension-baseline.md`.

## Build and run

Task (https://taskfile.dev) is the entry point for development builds and Go/frontend checks; tasks live in `Taskfile.yml`. Run `task --list` for the full catalog and `task --summary <name>` for per-task notes. Install with `brew install go-task/tap/go-task` (or `npm install -g @go-task/cli`, or `go install github.com/go-task/task/v3/cmd/task@latest`). A transitional Makefile shim forwards the old `make <target>` names. Common tasks:

| Task | Notes |
|---|---|
| `task build` | Builds the web frontend (`web/dist` → `cmd/gridctl/web/dist`), then builds the Go binary with `-tags embed_web` so the UI is embedded. Produces `./gridctl` in the repo root. |
| `task build:go` | Backend only. Skips the embed tag if `cmd/gridctl/web/dist` is absent (UI 404s in that case). |
| `task build:web` | Builds the frontend and stages `web/dist` in `cmd/gridctl/web/dist` for embedding; does not compile Go. |
| `task dev` | Runs the Vite dev server (`web/`) against a separately-running backend. |
| `task test` | `go test -race ./...` with `TMPDIR` normalized to its physical path (unit tests only, same race detector CI runs). Human-readable loop; not needed before `task verify`. |
| `task test:verify` | Unit-lane JSON capture plus `cmd/scenarioverify` against `tests/adversarial/index.yaml` (same path Gatekeeper uses). Included in `task verify`. |
| `task verify` | Canonical local gate. Runs `test:verify`, `lint`, `test:frontend`, and the Python policy tests once each and stops at the first failure. Gatekeeper lanes start together and each designated lane prints a slowest-tests summary; raw captures stay on the runner. |
| `task scenarios` | Prints designated scenario IDs and escaped focused `-run` commands. Focused reruns do not replace whole-suite acceptance. |
| `task test:integration` | `go test -tags=integration -race -timeout 20m ./tests/integration/...`. The full suite requires Docker (or Podman); selected HTTP/subprocess suites need no container runtime. All use real dependencies per Article IV of `CONSTITUTION.md`; mocks are disallowed in `tests/integration/`. |
| `task test:frontend` | `cd web && npm test` (Vitest). |
| `task examples:refs` | Checks example stacks for unpinned or unassessed in-scope image and package selectors (`scripts/check-example-refs.sh`). Requires `./gridctl`. |
| `task images:python-runtime` | Validates the Python MCP runtime recipe, pins, and OCI workflow policy. Hosted amd64/arm64 image acceptance is separate. |
| `task lint` | `golangci-lint run` plus `npm run lint` in `web/` (both CI-gated). |
| `task generate` | Regenerates `go.uber.org/mock` mocks under `pkg/mcp/` and `pkg/runtime/`. Required after touching the interfaces they're generated from. |
| `task mock:servers` | Builds and runs the example mock MCP servers in `examples/_mock-servers/` (HTTP on PORT, SSE on PORT+1; `PORT=9001` default). Pair with `task mock:clean`. |

Always test local changes with `task build` followed by `./gridctl …`. The `gridctl` binary on `$PATH` is typically a brew-installed release and will not reflect your changes.

`gridctl serve` and `gridctl apply` daemonize by default. If you need a process you can ctrl-C (or that a test script can kill), pass `-f` / `--foreground`.

`task verify` is the local gate before a pull request. It runs the unit-lane capture and scenario verification, backend and frontend lint, frontend tests, and the Python policy tests (Python 3.11+, jsonschema 4.23.0, PyYAML 6.0.3), each once, and stops at the first failure. `task test` is the human-readable loop and is not needed before `task verify`. Gatekeeper starts the test, integration, conformance, podman-integration, and frontend jobs together; the required job still aggregates every gate. A new push to the same pull request cancels the in-progress run. Other events, including pushes to main and the release workflow's Gatekeeper call, are not canceled. Each designated lane prints a slowest-tests summary of names and elapsed seconds. A missing capture, a jq failure, or a sort failure prints a one-line notice and does not fail the job. Raw captures stay on the runner and are not uploaded.

Run a single Go test:

```bash
go test -v -run TestFunctionName ./pkg/runtime/...
go test -v -race -tags=integration -run TestToolGroups_Authentication ./tests/integration/...
```

Gateway authentication lifecycle changes also require `go test -race -tags=integration -run 'TestGatewayAuth_RealRestart|TestToolGroups_Authentication' -count=1 -timeout 3m ./tests/integration` (Docker, non-Windows; the restart fixture builds child binaries with `-race`). Real-browser acceptance lives in `web/gateway-auth.browser.mjs`: run `node web/gateway-auth.browser.mjs <path-to-playwright/index.mjs>` from the repository root with Playwright and its Chromium installed separately. Set `PLAYWRIGHT_BROWSERS_PATH` when using a non-default browser cache. It uses isolated local fixtures and is separate from Vitest and `task test`.

OAuth resource-server acceptance also uses `go test -race -tags=integration -run '^TestOAuthResource_Dex$' -count=1 -timeout 4m ./tests/integration` with real digest-pinned Dex containers on Docker. It covers JWT validation, metadata, claim-derived scope, and cross-principal session refusal. It does not establish hosted-issuer compatibility or an actual MCP host login; those remain manual acceptance.

Server Card and public discovery route changes also require `go test -race -tags=integration -run 'TestServerCard' -count=1 -timeout 4m ./tests/integration` (real HTTP and a race-built CLI child; no Docker).

MCP execution changes require the real race-enabled `TestExecution_` integration suites on Docker and Podman. Hardened admission currently needs Linux, a local Unix daemon endpoint, and instance-bound `/proc` and cgroup v2 observations; missing required evidence must refuse routing. Rootless Podman acceptance must actually execute the positive fixtures. Run `node web/execution.browser.mjs <path-to-playwright/index.mjs>` from the repository root for the separate real-browser Execution checks, using an independently installed Chromium and `PLAYWRIGHT_BROWSERS_PATH` when needed.

Capability and sensitive-observation primitives have race coverage in `pkg/mcp`,
including real HTTP overlap and OTLP disclosure checks. Their REST observation
check uses a real MCP subprocess without Docker:
`go test -race -tags=integration -run '^TestCapabilityDiagnostics_' -count=1 -timeout 3m ./tests/integration`.
Focused checks supplement the required whole-suite gates.

A2A wire and adapter acceptance uses real HTTP listeners, pin storage, and CLI/daemon
subprocesses without Docker:
`go test -race -tags=integration -run '^TestA2A(Adapter|Wire)_' -count=1 -timeout 4m ./tests/integration`.
After `task build`, set `GRIDCTL_A2A_FIXTURE_BINARY="$PWD/gridctl"` on this command
to exercise the local binary in the CLI fixture; daemon fixtures still build
race-enabled children. These checks do not establish hosted-agent compatibility
or shared-agent isolation.
`docs/a2a-verification.md` records the September 21, 2026 genuine generic 1.0 and
hosted AgentCore bearer rehearsal, including target identities and limitations.
Final exact-PR-head external acceptance remains required before merge or
manual-merge handoff; neither the rehearsal nor fixture suites close that gate.

Deferred tool discovery acceptance uses real HTTP mock subprocesses without Docker:
`go test -race -tags=integration -run '^TestDeferredDiscovery_' -count=1 -timeout 4m ./tests/integration`.
`docs/deferred-discovery-verification.md` records the host procedure. Real-host
interoperability remains required before merge; the fixture suite does not close that gate.

Lint:

```bash
golangci-lint run                # backend (gosec is enabled; see .golangci.yml for the curated exclusions)
cd web && npm run lint           # frontend; zero-error baseline, enforced by the gatekeeper frontend CI job
```

Release tooling has separate Python policy/scanner tests: `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s scripts -p 'test_*.py' -v` (Python 3.11+, jsonschema 4.23.0, PyYAML 6.0.3, Bash, and jq). These are not included in `task test`. They run once as part of `task verify`. Cask-generation changes also require the explicit pinned GoReleaser exercise on Linux-x86_64; it is intentionally outside discovered `test_*.py` files. Changes to the declarative post-install step or its Homebrew version floor require real Homebrew load and installation acceptance on the declared floor and current stable, including macOS behavior and the Linux platform guard. `.github/workflows/release.yaml` reuses all five exact-commit Gatekeeper jobs, assembles a draft with GoReleaser, validates the generated cask contract and Ruby syntax before preparation and signing, verifies it on Linux/macOS, publishes, and only then advances Homebrew. See `docs/release-verification.md` for the generator command, external verification, immutable-mode prerequisites, and recovery; local fixtures do not replace hosted acceptance. The Python runtime image recipe is validated by `task images:python-runtime`; hosted amd64/arm64 acceptance is `.github/workflows/mcp-runtime-python.yaml`.

## Code architecture

The shape of the codebase from the outside in:

```
cmd/gridctl/        Cobra CLI entry points, one file per subcommand (apply, serve, call, tools, link, var, skill, ctx, pack, project, optimize, runs, …).
                    embed.go pulls in cmd/gridctl/web/dist via go:embed under the embed_web build tag.
cmd/scenarioverify/ Repository test utility that checks designated Go test identities against a completed `go test -json` capture. Not a gridctl subcommand.
internal/api/       REST handlers backing the web UI (one file per resource: stack, skills, vault, pins, telemetry, traces, runs, tools_call, tools_discover, …).
                    Python source validation, resolution previews, generated files, and status provenance live in
                    python_sources.go. The Server struct in api.go wires together every pkg/* subsystem the UI needs.
                    Server.Handler assembles HTTP routes and CORS/Host/auth middleware; wrapAuth selects
                    shared-token auth.go or JWT auth_oauth.go to protect operational
                    namespaces, including grouped MCP/SSE. UI files, probes, terminal preflight, the exact
                    state-validated downstream OAuth callback, and the enumerated public discovery GETs
                    (`/mcp/server-card`, `/groups/{name}/mcp/server-card`, `/.well-known/ai-catalog.json`) do not
                    require the gateway token. Those three are GET-only and are not configurable from the stack file.
                    auth_oauth.go adds issuer JWT authentication and public RFC 9728 metadata at
                    `/.well-known/oauth-protected-resource`, its `/mcp` suffix, and `/groups/{name}/mcp` suffix
                    in OAuth mode only. Metadata GET/OPTIONS use wildcard CORS and retain Host validation,
                    including the preflight that terminates before ordinary Host middleware.
                    Gateway credential denials carry Gridctl-Auth-Rejected: 1; reload_errors.go supplies shared
                    restart_required (409) and invalid_candidate (400) envelopes, including save-first callers.
internal/probe/     Ephemeral MCP tool-list probe for the "add server" wizard (not registered with the gateway).
                    A2A config inputs receive an explicit unsupported-source diagnostic; the REST probe DTO has no A2A field.
internal/scenarioverify/ Post-suite Go JSON execution verifier for tests/adversarial/index.yaml. Not a product API.
pkg/catalog/        MCP server catalog behind `gridctl search` / `gridctl add`: curated embedded entries plus the
                    official MCP Registry, with install-shape mapping into stack.yaml server blocks.
pkg/config/         stack.yaml schema, defaults and validation, variable/env expansion, plan diffing, health-check parsing.
                    compose.go validates stack fragments and writes the composed stack header. Pure merge; no stores or expansion.
                    a2a.go owns experimental Agent Card declarations and validation. A2A is external/non-container,
                    rejects replicas > 1, autoscale, and execution. Its flag is off by default.
                    export.go owns the shared non-resolving ExportStack projection for CLI/API exports, with bounded
                    sensitive-literal rejection and source ancestry for CLI destination checks. A discovered
                    sibling override is named, not merged, unless the caller passes an override path.
                    override.go owns the allowlisted merge, after extends and before expansion. Runtime loaders stay separate.
                    mutablerefs.go is the CLI-only advisory path for literal image and npx/uvx selectors; default
                    ValidateWithIssues is unchanged.
pkg/depcheck/       Pure literal image and npx/uvx classifier used by advisory mutable-ref diagnostics. No I/O,
                    expansion, secret substitution, or rewriting.
pkg/stackpolicy/    Offline declaration-policy evaluator for `gridctl validate --policy`. Strict versioned policy
                    parsing, root-confined candidate capture, and pure structural rules. No API/UI/runtime hooks.
pkg/execution/      Presence-aware MCP execution declarations, normalized per-replica contracts, and value-free reports.
                    Docker-compatible admission and instance-bound Linux kernel observations live in pkg/runtime/docker/.
                    MCP clients gate dispatch on evidence; retirement closes owned processes and cancels obsolete scaling.
pkg/runtime/        Container orchestration. Orchestrator is the WorkloadRuntime + Builder front; it prepares one desired
                    source image per logical MCP server before reconciling replicas. pkg/runtime/docker is the Docker
                    implementation. Local image cache matches digest and tag-plus-digest references against RepoDigests,
                    and unqualified tags against exact RepoTags plus Podman's localhost-prefixed names. Runtime
                    auto-detected (docker → podman) unless --runtime is set.
pkg/builder/        Image building from git or local Dockerfiles and generated Python builds for exact public PyPI releases
                    or packaged git/local projects, with resolved build plans, isolated Git worktrees, content-addressed
                    image tags, label-verified cache reuse, and non-secret provenance labels. Also owns bounded public-PyPI
                    resolution, static Python package/project metadata inspection, supported-interpreter selection,
                    console-script resolution, and deterministic digest-pinned uv Dockerfile generation.
pkg/mcp/            MCP protocol: gateway (router, tool aggregation, downstream prompt and resource aggregation, handshake tools/list_changed notifications, and stateless subscriptions/listen), stdio/SSE/streamable transports, OpenAPI-as-MCP,
                    autoscaler, code mode sandbox (goja), deferred tool discovery (`search_tools`, `call_tool`), replica sets, schema pinning hooks, live tool discovery,
                    and typed call outcomes for canonical REST/CLI dispatch.
                    Container, local-process, and HTTP/SSE readers classify peer requests and notifications before response
                    correlation. Stdio and process replies use the same context-bounded, serialized writer as outbound calls.
                    HTTP and SSE replies are a separate POST that closes the response body without reading it and does not
                    update the session. Handshake upstream sessions are notified when their visible tool list
                    changes. The event is a payload-free `notifications/tools/list_changed` on the GET stream,
                    coalesced per session. Stateless clients receive that tool event, plus prompt and resource
                    list-changed events, on `subscriptions/listen`, each tagged with the listen request id.
                    Deferred discovery fingerprints each session and listen subscriber from its effective mode.
                    Downstream handshake notifications on stdio, process, and HTTP POST streams refresh that
                    server and then follow the same path. Stateless HTTP servers that declare `listChanged` are
                    consumed on `subscriptions/listen`, including an SSE URL pinned to stateless. Stateless stdio
                    and process servers are not. HTTP servers are not given a standalone GET listener.
                    a2a_capabilities.go owns the gateway's shared CapabilityStore, atomic reservations, and teardown
                    accounting; a2a_authority.go supplies private generation-bound authority and send/cancel slots.
                    These are internal primitives with no capability tuning option. card_trust.go
                    owns separate approved/pending immutable pin snapshots and generation/revision-bound approval.
                    Registration and reconnect verify mandatory snapshot trust before optional legacy tool pins.
                    a2a_client.go supplies the outbound adapter, generation-bound trust/admission, advisory tools,
                    and atomic bounded envelopes. a2a_status.go projects local trust and aggregate authority only.
                    sensitive.go supplies
                    payload-free observations, safe errors, and shared code-mode sensitivity. Trusted construction
                    metadata selects this path; raw observers, configured counters, and format conversion are skipped.
pkg/a2aclient/      Bounded outbound A2A 1.0/0.3 JSON-RPC codecs, card compatibility, origin/redirect policy,
                    and registration-local freshness with a 30-second cap. AgentClient lives in pkg/mcp; no SDK dependency.
                    Versioned SDK reference fixtures and attribution live in testdata/.
pkg/servercard/     SEP-2127 Server Cards and the draft AI Catalog: build, validate, and a bounded fetch
                    that sends no credentials and refuses redirects. Advisory metadata only; not a trust
                    or credential source. The vendored schema and NOTICE live in testdata/.
pkg/mcpauth/        Downstream OAuth 2.1 brokering for external servers (discovery, dynamic client registration,
                    token store, callback listener). Backed by `gridctl auth`.
pkg/resourceauth/   Inbound OAuth resource-server JWT verification through go-oidc, MCP SDK issuer discovery,
                    cached JWKS, bounded discovery retries, required scopes, and raw identity claims.
                    Server.Close cancels verifier-owned JWKS work. No token issuance, refresh, or user store.
pkg/registry/       Skills registry: discovers SKILL.md files, parses frontmatter, validates, and serves MCP prompts.
                    Tracks content generations; supplies rooted, symlink-refusing manifest reads and deterministic MIME types.
pkg/skillsext/      Registry Skills over MCP provider: generation-cached canonical documents, complete manifests,
                    supporting-file reads, and directory children. pkg/mcp owns global policy, URI validation,
                    handshake opt-in, stateless declaration, and protocol error mapping. No downstream skills aggregation.
pkg/skills/         Skill and agent management: git import, local directory import, client skill-location import, lockfile, fingerprinting, updater, security scan.
pkg/project/        Unified projection engine: one lockfile (~/.gridctl/project.lock.yaml), flock, hashing, backups,
                    drift states, and migrate-on-read. The kind packages below are its tenants.
pkg/skillsync/      Projects active registry skills into native client skill directories (`gridctl skill project`).
pkg/agentsync/      Projects imported agents to clients: identity copy for Claude Code, rendered dialects for
                    OpenCode, Copilot, and Gemini CLI (`gridctl skill project --kind agent`).
pkg/contexts/       Global agent-context projection (`gridctl ctx`): one canonical file, or opt-in rule fragments
                    with multi-file and compiled per-client assembly.
pkg/wiring/         Key-level ownership of gateway entries merged into client MCP configs (`gridctl project`, link/unlink).
pkg/pack/           gridctl-pack.yaml manifest schema, including named external `sources`, a scalar or mapping `stack` (`file` and optional `extends`), optional `requires.gridctl`, and `gridctl-pack.lock.yaml`; pkg/packops owns add, lock honoring, and composition against the lockfile and launcher, including per-source clone and auth, and checks `requires.gridctl` on add, apply, and preview. The CLI injects stack launch and reload; REST leaves the launcher nil and does not start, stop, or reload a carried daemon, and it does not write the pack lock.
pkg/skillpins/      TOFU pins over skill documents (per-file digests, findings); the `gridctl skill pins` store.
                    ComputeManifest is the shared complete hash walk for pins and Skills over MCP, excluding links,
                    dotfiles, temporary files, and nested skills. Publication limits live in pkg/skillsext, not hashing.
pkg/runs/           Metadata-only persisted dispatch records (`gridctl runs`). Opt-in stack-level JSONL with a bounded queue, single writer, retention, and coordinated wipe.
pkg/service/        User-scoped systemd and launchd units (`gridctl service`): render, lockfile ownership (kind `service`), an allowlisted unit environment, and supervisor control.
pkg/limits/         Enforces the `limits:` block: token-bucket rate limits on the tool-call dispatch path.
pkg/toolpolicy/     Enforces the optional `policy:` block: last-match-wins call rules on the tool-call dispatch path, ahead of rate limits.
pkg/provisioner/    LLM-client config writers (claude, claudecode, cursor, windsurf, gemini, antigravity, opencode, grok, goose,
                    cline, anythingllm, lmstudio, roo, zed, continue, vscode). JSON and TOML helpers in json.go / toml.go.
                    Backed by `gridctl link` / `gridctl unlink`. `projectsources.go` discovers project MCP files for `gridctl import`.
pkg/secrets/        Host resolution of `${secret:<provider>/<path>}` at apply and reload. `exec` runs
                    stack-authored argv; `onepassword` runs `op read`. Load, validate, plan, and export
                    do not call it. Resolved values stay in memory except the gateway token already stored
                    in the daemon state file.
pkg/vault/          Encrypted variable store (XChaCha20-Poly1305 + Argon2id). The `gridctl var` and (deprecated) `gridctl vault` CLIs. `GRIDCTL_VAULT_PASSPHRASE_FILE` unlocks from a user-owned file; gridctl never creates it.
pkg/varrun/         Explicit stored-variable delivery to child processes, including output redaction and signal forwarding.
pkg/varscan/        Exact stored-secret scanning for working-tree files and staged Git blobs.
pkg/pins/           TOFU schema pinning for tool definitions; drift surfaces in pkg/pins + `gridctl pins`.
                    card_snapshot.go and card_store.go supply immutable digest-only card/identity evidence and
                    fail-closed, cross-process-locked card transactions using the existing pin-file format.
pkg/secreport/      Passive security evidence report DTO and assembler for `gridctl doctor --security` and GET /api/security-report.
pkg/optimize/       Usage analysis: feeds `gridctl optimize` and the UI's findings panel with token-denominated findings.
pkg/metrics/        In-memory usage accounting and observers, including the OTLP dispatch observer. Sensitive
                    observations record local numeric estimates without client attribution. Read via
                    /api/metrics/tokens and /api/tools/usage.
pkg/telemetry/      Opt-in log, metric, and trace persistence, inventory, and wipe. Inventory is GET
                    /api/telemetry/inventory; DELETE /api/telemetry wipes persisted signals.
pkg/tracing/        OTLP trace exporter, meter provider, semantic attribute keys, and the in-memory trace
                    buffer for `gridctl traces` and the UI traces panel. `gateway.tracing` and
                    `gateway.metrics.otlp` are read at startup.
pkg/reload/         Stack hot-reload (file watcher + diff-and-apply path). security.go owns effective bind/insecure
                    precedence and immutable startup-security comparison. Reload and stackless Initialize run
                    preflight before no-op detection or mutation; rejected saved YAML is retained on disk.
                    Accepted execution transitions retire old routes before source preparation; failed replacement retains
                    desired intent, and execution-only recreation preserves schema pins, including combined pool tuning.
                    A2A edits and removal preserve card pins; external-source handling skips container operations.
                    Handler.Plan compares the stack file with the loaded configuration and does not apply, preflight,
                    or serialize resolved values. Reload resolves `${secret:}` references before preflight and the
                    no-change return. Plan does not.
pkg/controller/     Application composition root: builds the gateway, mounts the API server, embedded UI, and MCP transports
                    (gateway_builder.go), and owns deploy/daemonize orchestration for `gridctl apply` and `gridctl serve`.
                    Foreground apply and serve record a daemon state file so status and destroy see them.
                    Supplies the startup security snapshot and installs manual reload independently of --watch.
                    gateway_builder.go installs the OAuth verifier independently of downstream brokering.
                    OAuth state records auth_type and issuer host, with no token or auth header; CLI requests
                    prefer non-empty GRIDCTL_GATEWAY_TOKEN as Bearer in every auth mode.
                    execution.go binds admission callbacks to the normalized contract, actual container ID, and inspected
                    IPv4 loopback HTTP endpoint.
                    a2a.go maps declarations into runtime client config. Mandatory card storage is installed on the
                    gateway and API independently of legacy pin switches, including stackless startup.
pkg/logging/        Shared structured log handlers and redaction, including a process-wide exact-value registry
                    used by the slog handler and RedactString, and typed capability strings in messages,
                    field/group names, and nested JSON-compatible values. Diagnostic copies do not alter policy inputs.
pkg/token/, pkg/format/, pkg/output/, pkg/jsonrpc/, pkg/state/, pkg/git/, pkg/dockerclient/   Supporting libs.

web/                React 19 + Vite + TypeScript. Tailwind v4 (postcss plugin). Zustand stores in src/stores/, route map in
                    src/routes.tsx, feature components grouped under src/components/<workspace>/. Nine workspaces:
                    Stack, Library, Vault (Variables), Tools, Metrics, Pins, Logs, Traces (Runs tab), Connections. The Detached*Page
                    files are popout windows that mirror specific panels.
                    src/lib/gatewayRequest.ts owns same-origin, no-redirect credential-bearing fetch, including
                    legacy SSE negotiation. credentials.ts owns versioned storage and Fetch header validation;
                    AuthBoundary covers the shell and all six detached routes with generation-guarded re-entry.

tests/adversarial/  Declarative scenario index (`index.yaml`) for mandatory-execution accounting. Not a runner.
tests/integration/  Real-runtime suites (build tag `integration`). Cover gateway lifecycle, hot reload, autoscaler,
                    replicas, transports (incl. Podman), private git auth, generated Python source builds,
                    downstream prompt and resource aggregation,
                    the Python MCP runtime base and its derivatives, digest-cache lookup, optimize heuristics,
                    and CLI/REST live tool invocation. Skills extension tests use real HTTP and a race-built foreground
                    daemon for manifests, pin agreement, both protocol generations, reporting, and disk refresh.
                    Grouped auth tests use real HTTP and a subprocess MCP backend.
                    auth_restart_test.go verifies actual process restart, saved/live-state rejection, CLI exits,
                    sessions/streams, preserved Docker identities, and the widened-bind origin refusal, with race-built child binaries.
                    oauth_resource_test.go uses real Dex containers for upstream OAuth, raw subject aliases,
                    handshake/stateless/REST scoping, and principal-bound POST/GET/DELETE sessions.
                    server_card_test.go checks the public card and catalog GETs, unlisted-origin CORS, and
                    race-built `gridctl add` consume and refusal paths, without Docker.
                    capability_diagnostics_test.go checks REST log/trace/usage/run observations with a real MCP subprocess.
                    deferred_discovery_test.go checks deferred `tools/list`, `search_tools`, `call_tool`, per-link mode, and both notification generations with real HTTP mock subprocesses.
                    a2a_wire_test.go checks both wire dialects, origin-bound credentials, sessions, and redirects with real HTTP.
                    a2a_*_test.go adapter suites cover authority, atomic delivery, policy, trust, observation sinks,
                    overlapping cancel, and actual daemon restart/reload with real HTTP and subprocesses.
                    pack_stack_test.go (`integration && !windows`) drives a real pack-carried local-process daemon through add, CLI launch, status, replace, and remove.
                    pack_composition_test.go (`integration && !windows`) drives booster composition through a real local-process daemon.
                    pack_lock_test.go (`integration && !windows`) covers lock honoring across homes against a local git HTTP fixture.
                    secret_providers_test.go covers exec apply, redaction, export, plan, rotation reload, failure, and recovery with real subprocesses.
examples/           Example stack YAMLs grouped by surface (getting-started, transports, openapi, a2a, mcp-apps, registry, secrets-vault,
                    code-mode, platforms, tracing, access-control, autoscale, declarative-link, gateways, portable-stack,
                    portable-pack, booster-pack, python-sources, python-runtime, execution, security-evidence, stack-configs, stack-declaration-policy, runs).
                    examples/_mock-servers/ is the source for `task mock:servers`.
scripts/            Build/test helpers and release tooling: release.py owns gate, inventory, verification, draft/public,
                    and tap policy; release-tools.py pins executables and the SPDX schema; check_goreleaser_cask.py runs
                    the explicit production-derived cask generator regression, while validate_generated_cask.py owns its
                    output contract; release-acceptance.py exercises authorized sandbox releases. mcp_runtime_python.py
                    owns the Python runtime image recipe, tag, and evidence policy. Discovered test_*.py files cover local
                    policy and validator regressions without invoking GoReleaser.
                    check-example-refs.sh enforces pinned or excepted example image and package selectors after task build:go, including unassessed in-scope references.
                    run-verified-tests.sh captures go test JSON and runs cmd/scenarioverify for designated Gatekeeper lanes.
docs/               User-facing documentation (cli-reference, config-schema, api-reference, concepts, skills, packs, tools-workspace,
                    global-context, scaling, usage-observability, installation, release-verification,
                    project-status, troubleshooting, execution, mcp-runtime-python, security/threat-model, security-evidence,
                    stack-declaration-policy, adversarial-regression-gates, capability-primitives, a2a-verification,
                    deferred-discovery-verification,
                    security/practical-guide).
images/             OCI recipes that are not the gateway binary. images/mcp-runtime-python is the Python 3.12 runtime base.
```

End-to-end request flow for an upstream HTTP MCP tool call: client → HTTP listener built by `pkg/controller` (gateway_builder.go) → `internal/api.Server.Handler` (CORS, Host validation, configured auth, and route/group selection) → `pkg/mcp` Streamable HTTP transport (Host/Origin checks and protocol handling) → `mcp.Gateway` router → per-server `mcp.AgentClient` (process/stdio/SSE/HTTP/OpenAPI/A2A) → response, with telemetry, tracing, optional run recording, schema pinning, and (optional) output-format conversion attached on the way back. A2A dispatch additionally validates capabilities and card trust, uses payload-free observations, and bypasses format conversion for its atomic JSON envelope. Legacy SSE routes return a negotiation hint rather than dispatching tools.

End-to-end for `gridctl call` and `gridctl tools search`: CLI → non-empty `GRIDCTL_GATEWAY_TOKEN` as Bearer, otherwise state-recorded credentials, on the selected daemon origin (redirects refused) → `POST /api/tools/call` or `GET /api/tools/discover` on `internal/api.Server.Handler` → `mcp.Gateway` canonical dispatch or `DiscoverTools` → versioned envelope. Discovery does not dispatch tools. REST success is not proof of upstream MCP protocol negotiation.

In OAuth mode, the verified raw identity claim controls client scope on MCP and REST tool paths. Exact raw profile aliases precede normalized matches; declared client values stay display-only. Handshake sessions bind to their creator, and a different principal receives 404 `session expired` on POST, GET, or DELETE without changing the session. Run access labels, traces, and usage use the principal subject to existing observation bounds and sensitive-call exclusions. Bearer/API-key and unauthenticated identity resolution remain self-declared. All five OAuth fields join the restart-only security snapshot.

OAuth client rate limits use the resolved profile key, with exact raw alias
precedence, while GateCall retains the raw access ID. Unlisted principals match
no client-scoped bucket; server and tool limits still apply.

Gateway diagnostics sanitize recognizable typed capability strings before log,
trace, usage, and run recording. Code-mode failure logs contain local categories
without source excerpts or thrown values, while caller-delivered execution
errors and console output remain available. Sensitive downstream Go errors use
safe categories with cancellation/deadline identity; ordinary source errors keep
their caller-facing text. See `docs/capability-primitives.md` for lifecycle,
counting, and privacy boundaries.

End-to-end for the web UI: component or React store action → shared gatewayRequest transport beneath endpoint parsers in `src/lib/api.ts` → `/api/...` handler in `internal/api/` → method on `Server` → call into the relevant `pkg/*` subsystem → JSON response → current-generation component or store state update → component re-render. Credential drafts verify against protected `/api/status` before active replacement/persistence. Gateway rejection pauses protected reads; verification resumes eligible reads without replaying mutations. The Stack spec view's Export YAML action calls `/api/stack/export` and downloads the non-resolving projection; raw spec retrieval and editing remain separate and may contain authored credentials. Verbose apply uses bounded controller diagnostic summaries, not resolved stack JSON.

## Constitution

`CONSTITUTION.md` is binding for every change. Articles that most often catch a refactor by surprise:

- **III (Test-first):** every exported behavior gets a test before merge; bug fixes need a regression test. A trivial accessor or pass-through needs no test of its own when the behavior it exposes is covered.
- **IV (Integration tests use real dependencies):** anything under `tests/integration/` uses real dependencies (containers, network connections, or subprocesses as applicable) and must pass `-race`. Mocks are unit-test only. A real process or HTTP server standing in for an external service that cannot run in CI is allowed when the transport and process boundary are real; the PR names the manual acceptance that remains.
- **V (No panics in `pkg/` or `internal/`):** return errors. CLI init in `cmd/` is the only place panic is allowed.
- **VI (Context propagation):** any I/O, blocking, or external call takes `context.Context` as the first arg and respects cancellation.
- **VIII (Semantic versioning):** breaking means a previously valid stack file or documented invocation fails, or a machine-readable contract changes: stack schema, `--format json` and other structured output, exit codes, REST shapes, MCP wire behavior, and documented file formats such as exports and lockfiles. Human-readable text, log wording, and the UI are not versioned. A fix that matches existing documentation is a bug fix and says so in its changelog entry. During a release-candidate series, such changes ship in the next candidate.
- **IX (Stack YAML back-compat):** new `stack.yaml` fields are optional with a default that preserves existing behavior. Renames and removals are breaking changes.
- **X (Machine-readable CLI output):** structured commands need machine-readable output (`--format json` or equivalent) and meaningful exit codes: `0` for success, nonzero for failure. Check each command's contract; export uses `0`/`1`, while several command families distinguish `0`/`1`/`2`.
- **XII (Secure defaults):** loopback bind by default. A listener reachable beyond loopback requires authentication and an explicit origin allowlist, and startup refuses without them unless the named insecure override is set. The allowlist governs credential-bearing requests; an enumerated public discovery document (OAuth protected resource metadata, an MCP server card) may be served to any origin when it carries no credential, session, or user data. Encrypted stores load locked.
- **XIV (Structured logging):** use `log/slog` in library code; no `fmt.Println` / `log.Printf`.
- **XV (Changelog discipline):** every user-visible change lands an entry under `[Unreleased]` in `CHANGELOG.md` in the same PR.
- **XVI (Ownership before mutation):** client destinations (skill directories, context files, client configs) are claimed in the lockfile before any write, hand-edited drift is changed only by an explicit user command, and no credential material lands on a managed path. A file the user hands gridctl as input (a stack file, an override file, a lock file in their own checkout) is not a client destination: an explicit command may rewrite it in place, preserving what it does not understand; reconciliation may not.

`CONTRIBUTING.md` covers branch prefixes, commit format, and the PR/CI process; follow it rather than re-deriving conventions.
