Repository navigation
feat: generic EventSource/EventTrigger webhooks + cron, replacing ai-triage/ai-review hardcoding (ADR 0048) - #305
Merged
Merged
Conversation
…0048) Adds the two generic event CRDs, readOnly/params on AgentRun and readOnly on ToolRun, the event-trigger contract (docs/event-triggers.md) and the cross-engine CEL parity fixtures. Removes the IntegrationRoute API and controller. Reconcilers are scaffolds; behavior lands in follow-up commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
…0048) - core-controller: EventSource reconciler (webhook secret/CEL readiness, cron -> owned CronJob ticking the gateway), EventTrigger reconciler (CEL + ref resolution), AGENT_READ_ONLY / AGENT_PARAMS / TOOL_READ_ONLY env. - @controller-agent/event-triggers: shared TS envelope, CEL (cel-js), params, templates and trigger selection, with cross-engine parity fixtures. - agent-orchestrator: EventTrigger registry, /invoke ignored/422/202 contract, forced skill/agent/tool targets, sticky per-session readOnly, write credentials withheld on read-only turns, Temporal forwarding. - temporal engine: cel-go trigger matching for direct callers, forced tool turns, sticky readOnly in the conversation workflow, readOnly/params on every AgentRun/ToolRun. - integration-gateway: POST /webhooks/:source for any EventSource (hmacSha256 / slackV0 / bearer), sender verification, cron ticks, and declarative github/http replies; label/triage/review code removed. - SWE agents: read-only from AGENT_READ_ONLY; the SWE-ENFORCED-MODE goal sentinel is gone. github tool refuses writes under TOOL_READ_ONLY. - charts: IntegrationRoute templates replaced by example EventSources and EventTriggers (production reproduces ai-triage/ai-review), RBAC and env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
…h engines Signers for every verification scheme, a fake-sink reply recorder, per-test EventSource/EventTrigger fixtures, and specs for GitHub triage/review, read-only enforcement (incl. sticky sessions), filtering, verification, http replies (orchestrator/skill/tool targets), cron ticks, programmatic /invoke, trigger precedence, and status conditions. rbac specs cover the new kinds; up.sh deploys either engine via E2E_ENGINE. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
…lout skaffold stamps a fresh run-id on every pod template, so each e2e deploy rolls NATS and Qdrant with the orchestrator. Retry the NATS connects like Redis already is, and let skaffold's status check tolerate the transient exit until its deadline instead of failing the run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
…llow-up specs A toolRef trigger is a function call, so the Temporal engine now fails the turn when the forced tool fails instead of answering "I couldn't complete that" as a success -- reply.http callers see status failed, matching LangGraph. The e2e generic-hmac specs reset fake-sink first so neither reads the other's reply, and the session-page follow-up spec waits for the turn to finish and asserts nothing it launched is writable (Temporal's read gate refuses a follow-up that names no verified repository). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
…tub reply
The shipped slack-mention-chat EventTrigger can now POST its outcome via
eventTriggers.slackMentionChat.reply.{url,secretRef} like the ci and
generic-hmac examples (chart 0.3.1). bridged-agent-workflow.e2e.ts no longer
mistakes the trigger's ack comment for the agent's reply.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
Running the suite on LangGraph for the first time surfaced two gaps that predate event triggers: the TS dispatcher has no knowledge-base dispatch kind, and its device-flow link resume parks on a failed long-poll. Both are now skipIf(!isTemporal()) with the reason inline instead of failing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
Drop the chart-generated shared tick token. Helm `lookup` returns nothing under `helm template`/Argo CD, so the token was regenerated on every render. core-controller now gives each cron EventSource an owned ServiceAccount (eventsource-<name>, no automount, no RoleBinding). The tick CronJob runs as it with only a projected token for audience integration-gateway.events. The gateway TokenReviews the bearer and accepts a tick for source X only from system:serviceaccount:<ns>:eventsource-X, so one source can never tick another. There is nothing to generate, store or rotate. The gateway gets a ClusterRole with only `create` on tokenreviews. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
The 13 example EventSources/EventTriggers are now bare manifests in examples/event-triggers/ (plus a kustomization and a README). They are not part of any chart, and community-components drops its eventSources/ eventTriggers templates and values. - Production installs its GitHub source and four ai-triage/ai-review triggers through the agent-controller chart's extraManifests, generated from the examples. It renders exactly the same 5 objects as before. - e2e applies the examples through a kustomize overlay (e2e/manifests/event-triggers) that only swaps in stubs, e2e Secrets and fake-sink replies. It renders exactly the same 13 objects as values-e2e.yaml did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
8 tasks done
imaustink
added a commit
that referenced
this pull request
Oct 14, 2026
…ching cost - Read-only is the session's mode, set by its latest trigger turn (writable included); non-trigger turns inherit it and can only restrict. One review no longer leaves an issue's triage read-only for good. Both engines. - LangGraph read-only turns can't write: no forwarding into a writable agent run (launch mode recorded on the session), MCP tools refused, and tools that act on GitHub as the caller refused instead of falling back to the Tool template's own GITHUB_TOKEN. Matches Temporal. - Temporal /invoke refuses dispatch fields in the body (400); an event always goes through trigger matching. The orchestrator's matched-trigger hop travels as a signed, session- and prompt-bound x-orchestrator-dispatch-assertion. - Temporal /invoke with no triggers configured is 200 ignored, not 503. - integration-gateway skips the GitHub sender lookup when no trigger for the source could match before sender resolution (read-only EventTrigger watch). - A trigger is Ready only when its condition's observedGeneration is current. - Cron event ids carry a random suffix. - EventTrigger/EventSource controllers skip no-op status writes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
imaustink
added a commit
that referenced
this pull request
Oct 14, 2026
TokenReview needs a ClusterRole, and the CI deployer cannot write ClusterRoles: the #305 deploy failed on exactly that, and the rollback that followed took production's webhooks down. The gateway now verifies a tick's projected ServiceAccount token itself, against the API server's issuer discovery + /openid/v1/jwks (readable by every ServiceAccount by default): signature, issuer, audience, exp, and a lifetime of at most an hour. An unknown key id refetches the keys once, rate limited. The chart no longer ships a ClusterRole for the gateway. Trade-off: a ServiceAccount deleted after its token was minted is not noticed; the tick token's 10-minute life bounds that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
imaustink
added a commit
that referenced
this pull request
Oct 14, 2026
…igest The #305 deploy failed before Helm wrote a revision, so `helm rollback 0` went one release too far (0.6.4 -> 0.6.3). That restarted every pod, and with `:latest` + Always they pulled the new code and ran it on the old config: the gateway 404'd every webhook, the orchestrator crash-looped. - rollback-deploy.sh rolls back to the last revision with status `deployed`, and does nothing when the failed upgrade never wrote one. - scripts/pin-image-digests.sh resolves the three Deployments' `:latest` to its digest (GHCR, anonymous, curl + jq) and the deploy passes it as `tag: latest@sha256:...`, so the release records the image and a rollback restores it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs
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.
Summary
Webhook behaviour is now defined entirely by two CRDs, replacing
IntegrationRouteand the hard-coded ai-triage/ai-review logic. ai-triage and ai-review are now just exampleEventTriggerinstances. ADR 0048 supersedes 0024 and amends 0025. The contract is indocs/event-triggers.md.EventSource: where events come from.POST /webhooks/<name>. The gateway verifies the raw bytes withhmacSha256,slackV0orbearerbefore parsing anything.senderblock resolves and checks who sent the event (for GitHub: team or collaborator membership, and bots are dropped).EventTrigger: what an event does.match.celdecides whether it fires, andparamsextracts values with CEL.promptTemplate, asessionKeytemplate,priority, andreadOnly.reply.github(ack, session page, result, removeLabel) andreply.http(an optionally signed callback).cel-jsand Temporal usescel-go. A shared fixture suite (packages/event-triggers/fixtures/cel-parity.json) checks they agree.readOnlyon AgentRun and ToolRun, passed to agents asAGENT_READ_ONLYandTOOL_READ_ONLY.SWE-ENFORCED-MODE: review-onlyprompt sentinel is deleted.examples/event-triggers/, outside every chart: 5 sources (GitHub, Slack, CI, generic HMAC, nightly cron) and 8 triggers covering all four target types. Apply withkubectl apply -n <ns> -k examples/event-triggers.extraManifests. They are generated from the examples, render the same 5 objects the chart did, and keep the GitHub webhook URL unchanged.e2e/manifests/event-triggers) that only swaps in stubs, e2e Secrets and fake-sink replies.Behaviour changes
failedwhen the tool fails, in both engines./invokewith an event returns200 ignoredwhen no trigger matches (no turn starts),422whenparamsfail, and202with{trigger: {name, sessionId, reply}}on a match.Deploy notes
release.ymldeletes the retiredintegrationroutesCRD.createontokenreviews. TokenReview is cluster-scoped, so a namespaced Role can't grant it.Test plan
packages/event-triggers(122 tests including the CEL parity fixtures), the Temporal engine'sgo test ./..., core-controller envtest, and lint.Known failures that predate this branch
mcp-broker-contractfails: its expectations went stale when main'se4436ffadded hash suffixes to MCP tool ids./invokerecord stayspendingforever.🤖 Generated with Claude Code
https://claude.ai/code/session_01Q4FyzpR8xiataUPf68J4qs