feat(a2a): make the advertised in-cluster endpoint configurable (per-product pods) - #150
Draft
github-actions[bot] wants to merge 4 commits into
Draft
feat(a2a): make the advertised in-cluster endpoint configurable (per-product pods)#150github-actions[bot] wants to merge 4 commits into
github-actions[bot] wants to merge 4 commits into
Conversation
…product pods) A per-product A2A pod could not be deployed, and the reason was one constant. `card_generator.IN_CLUSTER_URL` was returned by `_interface()` as the callable endpoint of every non-external Agent Card, so any second deployment started, passed every probe, served a valid signed card — and that card named the SHARED server. A caller doing the right thing (fetch card, follow `supportedInterfaces[0].url`) was routed to a pod that had never heard of it. Nothing was unhealthy; the pod was simply undiscoverable through its own card. That is why every product's A2A pod shipped disabled. Contract v1.2.0 (additive MINOR) adds optional `a2a.inClusterUrl`, defaulting to the current constant. The values interface is `additionalProperties: false` at every level, so this is a deliberate, versioned schema change. It goes INSIDE the `a2a` block, not the sibling chart-internal `deploy:` block, because `a2a` is what the chart serialises verbatim into the `values.json` the server parses (`config.load_config`) — a key under `deploy:` never reaches the server and would need an invented env var to get there. Substantively it is a card-projection input (the published callable endpoint, like `auth.oidcIssuerUrl`), not a chart knob like `replicas`. No new env var: the container surface is unchanged. - card_generator: `DEFAULT_IN_CLUSTER_URL` (+ `IN_CLUSTER_URL` back-compat alias); `in_cluster_url` threaded through `_interface` / `project_product_card` / `project_exec_card` / `generate_cards`. Ignored for `external` tenants. - config: `ServerConfig.in_cluster_url` parsed from `a2a.inClusterUrl`; empty string treated as unset so a half-templated value can never publish an empty URL. - adapter: passes it into every card it projects, product and exec. No regression to `a2a-shared` (live tenants FuzeAgent, FuzeFront, FuzePlan): the prod values file does not set the key, so the card is byte-identical. Verified by test and by diffing `helm template` for values.yaml / values-prod.yaml — the only render change is a corrected YAML comment on the Service. Single-tenant mode needed no code change: `server._card_tenant` already infers the sole enabled tenant when `?tenant=` is absent, the session store is per-pod and caller-scoped, and `LocalRepoResolver` keys on the tenant name. RPC still requires `params.tenant` — correct and fail-closed, since the card always carries `AgentInterface.tenant` and the shipped `A2AClient` echoes it; locked by a test that documents it, because the rejection is deliberately indistinguishable from an authz denial. Tests: 161 pass (was 145). Contract conformance: 68 pass. `helm lint` clean, and the rendered `values.json` validates against the frozen schema and parses. Docs: new `docs/a2a/per-product-pod.md` — the values a product chart must set, the secrets (SealedSecrets are namespace-scoped and NOT portable from `fuzeagent`), the pod shape, and the two repo files without which the projection fails at boot (`.fuze/manifest.json` with `providesTo`, `roles/<entryRole>/role.json` with a description). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GaPa3JgrVNtWrGvqQEAEqv
This branch's head was pushed by an identity whose pushes do not trigger workflows, so the PR had ZERO checks and could never satisfy branch protection - nothing red to investigate, just nothing at all. A commit from a collaborator-backed identity produces a synchronize event with a real pusher, which is what actually starts CI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A per-product A2A pod could not be deployed, and the reason was one constant.
card_generator.IN_CLUSTER_URLwas returned by_interface()as the callableendpoint of every non-external Agent Card, so any second deployment started,
passed every probe, served a valid signed card — and that card named the SHARED
server. A caller doing the right thing (fetch card, follow
supportedInterfaces[0].url) was routed to a pod that had never heard of it.Nothing was unhealthy; the pod was simply undiscoverable through its own card.
That is why every product's A2A pod shipped disabled.
Contract v1.2.0 (additive MINOR) adds optional
a2a.inClusterUrl, defaulting tothe current constant. The values interface is
additionalProperties: falseatevery level, so this is a deliberate, versioned schema change.
It goes INSIDE the
a2ablock, not the sibling chart-internaldeploy:block,because
a2ais what the chart serialises verbatim into thevalues.jsontheserver parses (
config.load_config) — a key underdeploy:never reaches theserver and would need an invented env var to get there. Substantively it is a
card-projection input (the published callable endpoint, like
auth.oidcIssuerUrl), not a chart knob likereplicas. No new env var: thecontainer surface is unchanged.
DEFAULT_IN_CLUSTER_URL(+IN_CLUSTER_URLback-compat alias);in_cluster_urlthreaded through_interface/project_product_card/project_exec_card/generate_cards. Ignored forexternaltenants.ServerConfig.in_cluster_urlparsed froma2a.inClusterUrl; emptystring treated as unset so a half-templated value can never publish an empty URL.
No regression to
a2a-shared(live tenants FuzeAgent, FuzeFront, FuzePlan): theprod values file does not set the key, so the card is byte-identical. Verified by
test and by diffing
helm templatefor values.yaml / values-prod.yaml — the onlyrender change is a corrected YAML comment on the Service.
Single-tenant mode needed no code change:
server._card_tenantalready infers thesole enabled tenant when
?tenant=is absent, the session store is per-pod andcaller-scoped, and
LocalRepoResolverkeys on the tenant name. RPC still requiresparams.tenant— correct and fail-closed, since the card always carriesAgentInterface.tenantand the shippedA2AClientechoes it; locked by a testthat documents it, because the rejection is deliberately indistinguishable from an
authz denial.
Tests: 161 pass (was 145). Contract conformance: 68 pass.
helm lintclean, andthe rendered
values.jsonvalidates against the frozen schema and parses.Docs: new
docs/a2a/per-product-pod.md— the values a product chart must set, thesecrets (SealedSecrets are namespace-scoped and NOT portable from
fuzeagent), thepod shape, and the two repo files without which the projection fails at boot
(
.fuze/manifest.jsonwithprovidesTo,roles/<entryRole>/role.jsonwith adescription).
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01GaPa3JgrVNtWrGvqQEAEqv