Skip to content

chore(a2a): FuzeAgent-first prod rollout (DRAFT — needs owner secrets/config) - #93

Merged
izzywdev merged 13 commits into
mainfrom
chore/a2a-prod-rollout-prep
Jul 24, 2026
Merged

chore(a2a): FuzeAgent-first prod rollout (DRAFT — needs owner secrets/config)#93
izzywdev merged 13 commits into
mainfrom
chore/a2a-prod-rollout-prep

Conversation

@izzywdev

Copy link
Copy Markdown
Owner

Prod is GitOps; this is a staged, reviewable rollout — do NOT merge until the checklist is met. Flips deploy/helm/a2a-shared/values-prod.yaml to enabled:true for the FuzeAgent-first bring-up, and adds deploy/helm/a2a-shared/GO-LIVE.md (the owner checklist).

What's staged

  • a2a.enabled: true, FuzeAgent tenant (anthropic provider), OIDC auth + mTLS + card-signing wired to SealedSecret refs (no secret values committed).
  • REPLACE_ME placeholders for the two owner-supplied values: oidcIssuerUrl and the tenant entryRole.

Owner must do before merge (see GO-LIVE.md)

  1. Set oidcIssuerUrl (family Authentik/Keycloak a2a issuer) + the FuzeAgent entryRole.
  2. Seal the secrets in ns fuzeagent: a2a-provider-anthropic + ghcr-pull (required); a2a-mtls-ca, a2a-card-signing, a2a-repos-git (optional — can trim for a minimal bring-up).
  3. FuzeAgent's providesTo stays absent for now (accepts no callers yet — safe); backfill it from FuzeSDLC#54 when the first consumer is enabled.

Image caveat (important)

release.yml builds fuzeagent-a2a on merge-to-main under deploy/helm/a2a-shared/**, but it skipped the prior A2A merges because their squash bodies inherited a [skip ci] from the governance-sync reconcile commits. Merge this PR with a CLEAN squash message (no [skip ci]) so the image builds and the tag: bumps. Verify: gh api user/packages/container/fuzeagent-a2a/versions.

Not done here (by design)

The actual prod flip (you merge after secrets), the exec-tier + spine enable steps, and applying the providesTo backfill — all follow-ons. I did not merge and did not touch prod.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

…RAFT — do not merge)

Stages deploy/helm/a2a-shared/values-prod.yaml to enabled:true for the FuzeAgent-first
bring-up (FuzeAgent tenant, anthropic provider, OIDC auth) with REPLACE_ME placeholders for
owner-supplied config and secretRef names for the SealedSecrets. Adds GO-LIVE.md: the exact
owner checklist (oidcIssuerUrl, SealedSecrets, providesTo backfill, clean-squash-message so
release.yml builds the image). Prod is GitOps — this only takes effect on merge + Argo sync.

Do NOT merge until GO-LIVE.md is satisfied. No secrets are committed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549
…ator role (#94)

Only the oidcIssuerUrl placeholder remains owner-supplied; entryRole now points at the
serving role added in #94. Still DRAFT — do not merge until GO-LIVE.md is satisfied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549
izzywdev added a commit that referenced this pull request Jul 23, 2026
…2a block) (#94)

* feat(a2a): FuzeAgent agent-orchestrator serving role + role-schema a2a block

FuzeAgent had only _base under agent-templates/roles/, so it projected an EMPTY
A2A card and could not be the first tenant. Adds:
- agent-templates/roles/agent-orchestrator/role.json — the serving role (caller hands
  a goal; FuzeAgent dispatches a managed agent and returns the result + artifacts), with
  a2a.examples for discoverability.
- an OPTIONAL `a2a` property on agent-templates/schema/role-manifest.schema.json mirroring
  contracts/a2a/v1/schema/role-a2a-extension.schema.json — the schema follow-on the A2A
  contract flagged (card_generator.py already reads role.a2a.*). additionalProperties stays
  false; no existing constraint weakened.

Unblocks the FuzeAgent-first prod rollout (#93 sets entryRole: agent-orchestrator).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549

* chore(governance): reconcile managed files to FuzeSDLC v1 [skip ci]

* test(a2a): validate the repo's real roles project a valid card + widen a2a-unit paths

- test_repo_cards.py: loads THIS repo's .fuze/manifest.json + agent-templates/roles/
  and asserts generate_cards() yields a schema-valid FuzeAgent card whose skills include
  agent-orchestrator (with examples) and never _base. Guards the actual serving-role set,
  not just fixtures.
- a2a-unit.yml: add agent-templates/roles/** and schema/role-manifest.schema.json to the
  path filters — a role/schema change changes the projected card, so it must run a2a-unit
  (previously it did not, so #94's role addition would have gone unvalidated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549

* test(a2a): sign the projected card (fuze-profile requires the signatures field)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
izzywdev and others added 5 commits July 23, 2026 18:28
…machine-identity dep

oidcIssuerUrl = https://app.fuzefront.com/application/o/fuzefront/ (from FuzeFront
deploy/helm/fuzefront/values-prod.yaml — the family OIDC issuer). Adds GO-LIVE.md §1b:
A2A callers are machine agents, so FuzeFront must register each as an Authentik
client_credentials machine identity and the audience/callerClaim->repo-name mapping must
be confirmed (sub=client_id UUID for client_credentials; authz.md needs the repo name).
Cross-repo item for FuzeFront via @claude.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549
… not JWT) in #93

FuzeFront#364's @claude handler no-op'd, so I read FuzeFront
backend/security/src/services/machine-identity.ts directly. Its machine identity is
INTROSPECTION-based (client_credentials, sub_mode=hashed_user_id, validated via Authentik
introspection) — the current OIDC/JWT auth block (oidcIssuerUrl/audience/callerClaim) does
NOT fit. GO-LIVE.md §1b now records the finding + that this is a CTO/contract decision
(introspection support in the A2A server vs FuzeFront issuing repo-name JWTs). Marked
DO-NOT-MERGE until resolved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549
…e JWTs)

FuzeFront (identity owner) chose Option 2: issue repo-name JWTs, not introspection (fits
the frozen contract, no per-pod introspection creds, no client_id->repo map). Resolved auth:
callerClaim=repo, audience=a2a, iss stays public app.fuzefront.com but JWKS/discovery fetched
in-cluster (authentik-server:9000) to dodge the CF-tunnel hairpin. GO-LIVE §1b now lists the
critical path: FuzeFront M2M-provisioning PR (emit repo/aud claim) -> in-cluster FuzeAgent
registration -> FuzeInfra NetworkPolicy (fuzeagent->authentik-server:9000) -> #93 finalize.
Still DO-NOT-MERGE until those land + a real token is decoded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549
…URL (#93)

authentik-server runs in the fuzefront namespace → in-cluster discovery =
authentik-server.fuzefront.svc.cluster.local:9000. FuzeInfra#372 opened for the
fuzeagent->fuzefront NetworkPolicy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549
…uzeInfra#372)

FuzeInfra handler correctly determined the fuzeagent->authentik-server NetworkPolicy belongs
in FuzeFront's chart (podSelector matches own ns; fuzeinfra AppProject can't target fuzefront),
and flagged that a standalone allow-policy would flip authentik-server to deny-all and break
Traefik->Authentik login (no default-deny baseline). Tracked at FuzeFront#368.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549
…ened go-live

Now that the frozen values-interface exposes auth.oidcDiscoveryUrl (v1.1.0,
FuzeAgent#96), set it in values-prod.yaml to the in-cluster Authentik discovery
URL so the A2A server fetches JWKS in-cluster (no CF-tunnel hairpin) while still
validating iss against oidcIssuerUrl. Keep the hardened (Option B) secret set:
a2a-mtls-ca + a2a-card-signing + a2a-provider-anthropic + ghcr-pull.

FuzeAgent tenant repo is PUBLIC, so drop reposGitTokenSecretRef (anonymous
clone) -> exactly 4 operator SealedSecrets.

GO-LIVE.md rewritten to the hardened sequence: 4-secret list (names+keys), the
FuzeFront NetworkPolicy already enabled (FuzeFront#373), the in-cluster JWKS URL,
the decode-a-real-token step, and the clean-squash-merge requirement (no
[skip ci] so release.yml builds/pushes the image + bumps the tag). DO-NOT-MERGE
guard retained (secrets must exist + token decoded).

Verified: helm lint clean (default + prod); helm template | kubeconform
-strict -ignore-missing-schemas passes (prod 3/3, ci 4/4, default renders empty);
values-prod a2a block validates against the v1.1.0 schema (oidcDiscoveryUrl
accepted). Server oidcDiscoveryUrl support is backend-engineer's slice.

Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549
izzywdev added 2 commits July 24, 2026 02:11
…t-prep

# Conflicts:
#	deploy/helm/a2a-shared/values-prod.yaml
…t-prep

# Conflicts:
#	deploy/helm/a2a-shared/values-prod.yaml
izzywdev and others added 2 commits July 24, 2026 03:47
…rd-signing)

Encrypted for fuzeagent/<name> (kubeseal strict scope, contabo-prod controller).
enabled-gated + sync-wave -1 so they materialise before the Deployment mounts them.
ghcr-pull already exists in the namespace (reused). Completes the Option-B secret set
so #93 can go live; register-a2a-cli (caller-side client) remains a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549
@izzywdev
izzywdev marked this pull request as ready for review July 24, 2026 00:50
@izzywdev
izzywdev merged commit ed65ae6 into main Jul 24, 2026
28 checks passed
izzywdev added a commit that referenced this pull request Jul 24, 2026
…o-live (#115)

fuzeagent-quota capped limits.cpu at 4 and it was fully consumed by the existing
workloads, so a2a-shared (limits.cpu=500m) could not schedule:
  FailedCreate: exceeded quota: fuzeagent-quota ... limited: limits.cpu=4
Only the CPU-limit ceiling was tight (memory 4/8Gi, pods 8/20, requests 450m/2 all
had headroom). 6 fits a2a plus its rolling-update surge; limits are ceilings, not
reservations (requests.cpu cap stays 2). Unblocks the #93 A2A prod bring-up.


Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
izzywdev added a commit that referenced this pull request Jul 24, 2026
…nfigMap) (#117)

The a2a-shared pod was stuck: `MountVolume.SetUp failed ... configmap "a2a-state"
not found`. The `state` volume was wired as an optional READ-ONLY ConfigMap, but
FUZE_STATE_DIR=/state is where the server persists in-flight session state
(run_until_block) — it must be WRITABLE. Switch it to an emptyDir (always mounted).
Ephemeral is fine for v1 (in-flight tasks don't need to survive a pod restart yet).
Unblocks the #93 A2A prod bring-up (pod was Init:0/1 on the failed mount).


Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
izzywdev added a commit that referenced this pull request Jul 24, 2026
…o slug (#119)

Card endpoint 500'd: FileNotFoundError /repos/FuzeAgent/.fuze/manifest.json. The
server's LocalRepoResolver reads /repos/<tenant> (case-preserved tenant identity),
but the repo-sync init container cloned to /repos/<a2a.repoSlug .repo> = /repos/fuzeagent
(lowercased). Clone to /repos/{{ .tenant }} so it matches what the server reads.
Final blocker for the #93 A2A prod bring-up (pod Running + /healthz ok; only the
card projection failed on the path mismatch).


Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
izzywdev added a commit that referenced this pull request Jul 24, 2026
…hema) (#120)

Card endpoint 500'd at runtime: FileNotFoundError /app/contracts/a2a/v1/VERSION.
card_generator.contract_version() reads CONTRACT_ROOT/VERSION (=/app/contracts/a2a/v1)
and the card validator reads schema/ from there, but the Dockerfile only copied the
contract's client/ subtree (pip-installed to /app/contract-client). Add
`COPY contracts/a2a/v1/ /app/contracts/a2a/v1/` so the runtime file reads resolve.
Worked in CI only because that ran from the source checkout. Final #93 bring-up blocker.


Claude-Session-Id: 17fb89fd-3579-433b-a6c4-9c9e7f3ec549

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant