Problem
agentlab klaus-gateway-test drives klaus-gateway's web channel (GET /web/agents, POST /web/messages) and runs gsoci.azurecr.io/giantswarm/klaus-gateway:1.5.0 by default. The Slack-only release (giantswarm/klaus-gateway#319) removed the web channel; the current line is 3.x. The proof cannot exercise any current gateway build, so a klaus-gateway change cannot be proven in the lab.
The component half breaks too: the meta chart's components.klaus-gateway range is >=2.0.0 <4.0.0, the 3.x chart's values schema is closed and has no web key, so the lab's klausGateway.web.enabled: true fails the HelmRelease, and step 9 drives the component's web channel.
What stands in the way of driving Slack instead is the identity: the Slack channel forwards only a linked person's token (no service-account fallback), and a real OBO sign-in cannot complete in the lab (docs/klaus-gateway.md, "What the lab cannot prove").
Proposed solution
- Drive the Slack adapter. The proof runs a fake Slack Web API (
--slack-api-base): auth.test answers the bot user, users.info the lab user's e-mail, everything else {"ok":true,"ts":…}, and it records chat.postMessage, chat.update, chat.startStream/appendStream/stopStream, reactions.*, agents.sessions.setStatus per thread, so the assertions read what the thread would show. Messages are Events API callbacks to POST /channels/slack/events, signed with the run's signing secret (v0= HMAC-SHA256 over v0:<ts>:<body>); Approve and Deny are Block Kit clicks on POST /channels/slack/interactions.
- Identity: seed the link store with the person's cached token. The proof writes a link for its Slack user through the gateway's own store package (
pkg/auth/musterlink) — the Dex subject and e-mail, the lab user's Dex id_token as the link's cached id_token, its exp as expiry, a placeholder refresh token. Linker.TokenFor serves a cached, unexpired id_token without calling muster, the path a linked person's turn takes between refreshes, so the gateway runs unmodified. Host mode seals it into the run's bolt link store; the component into its link Secret with the lab's store-key (step 7 already writes there).
- Rejected: a link whose refresh yields the lab user's token. It needs a muster refresh token issued to the gateway's OAuth client. muster identifies that client by CIMD and refuses a CIMD
client_id on the lab's private-IP hostnames, so it takes a DCR client at muster and a headless walk through muster's authorize, the Dex login form and the callback: lab-only machinery for a refresh the gateway's unit tests already cover.
- Rejected: a lab-only token source in the gateway (a token-file flag): product code that exists for the lab only, next to the rule that forbids a Slack turn any credential but the person's.
- Consequence: the refresh leg (muster's token endpoint, rotation) stays out of the lab's reach and is documented so; the proof requires the token to outlive the run and asserts no
token_refresh record.
- The component drops
web.enabled; a lab postRenderer points its KLAUS_GATEWAY_SLACK_API_BASE at a selector-less Service the proof aims at its fake during the run. Step 9 becomes one signed event through a port-forward, answered in the fake thread over the in-cluster target. The component's pods reach the fake as a container on the kind network (the proof's own binary), never through a port on the host. A probe pod checks the path first.
- Default image: the current release,
klaus-gateway:3.5.1.
Acceptance criteria
Problem
agentlab klaus-gateway-testdrives klaus-gateway'swebchannel (GET /web/agents,POST /web/messages) and runsgsoci.azurecr.io/giantswarm/klaus-gateway:1.5.0by default. The Slack-only release (giantswarm/klaus-gateway#319) removed the web channel; the current line is 3.x. The proof cannot exercise any current gateway build, so a klaus-gateway change cannot be proven in the lab.The component half breaks too: the meta chart's
components.klaus-gatewayrange is>=2.0.0 <4.0.0, the 3.x chart's values schema is closed and has nowebkey, so the lab'sklausGateway.web.enabled: truefails the HelmRelease, and step 9 drives the component's web channel.What stands in the way of driving Slack instead is the identity: the Slack channel forwards only a linked person's token (no service-account fallback), and a real OBO sign-in cannot complete in the lab (docs/klaus-gateway.md, "What the lab cannot prove").
Proposed solution
--slack-api-base):auth.testanswers the bot user,users.infothe lab user's e-mail, everything else{"ok":true,"ts":…}, and it recordschat.postMessage,chat.update,chat.startStream/appendStream/stopStream,reactions.*,agents.sessions.setStatusper thread, so the assertions read what the thread would show. Messages are Events API callbacks toPOST /channels/slack/events, signed with the run's signing secret (v0=HMAC-SHA256 overv0:<ts>:<body>); Approve and Deny are Block Kit clicks onPOST /channels/slack/interactions.pkg/auth/musterlink) — the Dex subject and e-mail, the lab user's Dex id_token as the link's cachedid_token, itsexpasexpiry, a placeholder refresh token.Linker.TokenForserves a cached, unexpired id_token without calling muster, the path a linked person's turn takes between refreshes, so the gateway runs unmodified. Host mode seals it into the run's bolt link store; the component into its link Secret with the lab'sstore-key(step 7 already writes there).client_idon the lab's private-IP hostnames, so it takes a DCR client at muster and a headless walk through muster's authorize, the Dex login form and the callback: lab-only machinery for a refresh the gateway's unit tests already cover.token_refreshrecord.web.enabled; a lab postRenderer points itsKLAUS_GATEWAY_SLACK_API_BASEat a selector-less Service the proof aims at its fake during the run. Step 9 becomes one signed event through a port-forward, answered in the fake thread over the in-cluster target. The component's pods reach the fake as a container on thekindnetwork (the proof's own binary), never through a port on the host. A probe pod checks the path first.klaus-gateway:3.5.1.Acceptance criteria
--gateway-binarybuild of klaus-gateway main:@bot /agentlists the fixture's display name and not the unadmitted template;/agent <unadmitted>answers withno Harness admitsand creates no AgentInstance; an unlinked Slack user is shown the Sign in button and reaches no controller.instance_boundnames the thread's only AgentInstance;turn_dispatchcarries the lab user's e-mail; muster logs the forwarded id_token accepted and the tool calls under the person's subject.hitl_approve) resumes the paused task in place to completed, the card readsApproved by; in a second thread Deny (hitl_deny) ends the task without the tool call reaching muster, the card readsDenied by./stopin the thread:⏹ Stopped., the taskTASK_STATE_CANCELEDat the controller, the next turn answers.token_refreshrecord in the run.platform.klausGatewaythe component installs on the current meta chart; steps 6–8 unchanged; step 9 is a Slack turn through the pod.