Skip to content

fix(kanban): make transition emit payload classifiable by the webhook adapter - #22

Merged
cwest merged 1 commit into
cwest/integrationfrom
topic/kanban-emit-event-type
Jul 1, 2026
Merged

fix(kanban): make transition emit payload classifiable by the webhook adapter#22
cwest merged 1 commit into
cwest/integrationfrom
topic/kanban-emit-event-type

Conversation

@cwest

@cwest cwest commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Problem

The 4c transition-emit bridge POSTs a kanban lifecycle transition to the loopback webhook route (/webhooks/kanban-transition) so the orchestrator wakes as an agent run. The webhook adapter filters incoming events against the route's events allowlist, resolving the event type from — in precedence order:

X-GitHub-Event header → X-GitLab-Event header → body["event_type"] → body["type"] → "unknown"

The emitted payload carried the transition kind only as kind and in the X-Kanban-Event header — neither of which the adapter consults for classification. On a loopback POST (no GitHub/GitLab header), extraction fell through to "unknown", which is not in the route allowlist ([blocked, completed]), so the adapter returned {"status": "ignored"} with a 200 and never spawned the run.

Observed symptom (live)

kanban notifier: delivered blocked event for t_… to discord/… on board default
POST /webhooks/kanban-transition HTTP/1.1" 200
kanban transition emit: POST … -> 200 (task t_…, blocked)

— a healthy 200 on the emit, but no inbound message: platform=webhook … / agent run, unlike the github-prs route.

Root cause

Contract mismatch between the emitter and the adapter's event-extraction. The payload lacked a body field (event_type/type) the adapter reads to classify a headerless loopback POST.

Fix

Add event_type (mirroring kind) to the payload body so the adapter classifies the transition and dispatches the run. One additive key; no adapter/core change.

Tests (TDD)

  • RED→GREEN unit test replicating the adapter's exact extraction precedence against the built payload: was 'unknown', now the transition kind.
  • E2E HTTP proof hardened: the receiving handler now classifies the body the way the real adapter does and asserts event_type == 'blocked' — closing the gap the previous permissive dummy handler masked.
  • Full emit suite green (11 passed); the touched test target passes in isolation.

Why the previous E2E test missed it

The E2E test used a dummy handler that accepted any event, so it proved the POST sent correctly but never proved the real adapter would classify and dispatch it. The hardened test now guards that wire contract.

… adapter

The 4c transition-emit bridge POSTs a kanban lifecycle transition to the
loopback webhook route so the orchestrator wakes as an agent run. The route
filters incoming events against its `events` allowlist, resolving the event
type from (in order) the X-GitHub-Event / X-GitLab-Event headers, then the
body fields `event_type` / `type`.

The emitted payload carried the transition kind only as `kind` and in the
X-Kanban-Event header — neither of which the adapter consults for
classification. On a loopback POST (no GitHub/GitLab header), extraction fell
through to "unknown", which is not in the route allowlist, so the adapter
returned {"status": "ignored"} with a 200 and never spawned the run. The
symptom was a healthy 200 on the emit with no orchestrator agent run.

Add `event_type` (mirroring `kind`) to the payload body so the adapter
classifies the transition and dispatches. Regression coverage:
- a unit test replicating the adapter's extraction precedence against the
  built payload (was 'unknown', now the kind), and
- the E2E HTTP proof now classifies the received body the way the adapter
  does, guarding the wire contract the previous permissive dummy handler
  masked.
@cwest
cwest marked this pull request as draft July 1, 2026 01:44
@cwest
cwest marked this pull request as ready for review July 1, 2026 01:47

@cwest cwest left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bug is real and the fix is right. I checked the adapter at head SHA d034b26: gateway/platforms/webhook.py:533-538 resolves the event type as X-GitHub-Event header, then X-GitLab-Event header, then body event_type, then body type, then "unknown", and webhook.py:540-541 drops anything not in the route's events list. The loopback emitter sends X-Kanban-Event (not consulted) and no GitHub/GitLab header, so before this change the classifier landed on "unknown" and the route returned status ignored with a 200 and never spawned a run. Adding event_type mirroring kind fills the exact slot the adapter reads, so a headerless loopback POST now classifies as its transition kind.

Ran the emit suite in a throwaway clone against the base branch (10 passed) and against the PR head (11 passed), with the added test the only difference. Then deleted the event_type key from the source and reran: both the new unit test and the hardened HTTP test fail with "adapter classified the loopback POST as 'unknown'" — the production symptom — which confirms the guard is a genuine red-to-green, not a test that passes regardless. No regressions.

@cwest
cwest merged commit 8bbf8b8 into cwest/integration Jul 1, 2026
8 checks passed
@cwest
cwest deleted the topic/kanban-emit-event-type branch July 1, 2026 01:52
cwest added a commit that referenced this pull request Jul 1, 2026
…(default-off)

Optional, default-OFF bridge that lets a kanban lifecycle transition wake the
orchestrator as an agent RUN (not merely a chat ping) by POSTing the transition
to a loopback webhook route — mirroring how a GitHub pull_request event triggers
a review run. New module gateway/kanban_transition_emit.py adds pure decision
logic (should_emit_transition, build_transition_payload with a stable
(board,task_id,kind,event_id) idempotency key) plus a fail-safe emit_transition
coroutine that HMAC-signs and POSTs; it NEVER raises. The payload is classifiable
by the webhook adapter, and transition wakes route back to the origin
thread/session. Guarded by kanban.transition_emit.enabled (default OFF); when
disabled the notifier path is byte-for-byte unchanged. No new core tool, no new
model surface, no user-facing HERMES_* config var.

upstream-pending: fork PR #21, #22, #23
cwest added a commit that referenced this pull request Jul 26, 2026
…(default-off)

Optional, default-OFF bridge that lets a kanban lifecycle transition wake the
orchestrator as an agent RUN (not merely a chat ping) by POSTing the transition
to a loopback webhook route — mirroring how a GitHub pull_request event triggers
a review run. New module gateway/kanban_transition_emit.py adds pure decision
logic (should_emit_transition, build_transition_payload with a stable
(board,task_id,kind,event_id) idempotency key) plus a fail-safe emit_transition
coroutine that HMAC-signs and POSTs; it NEVER raises. The payload is classifiable
by the webhook adapter, and transition wakes route back to the origin
thread/session. Guarded by kanban.transition_emit.enabled (default OFF); when
disabled the notifier path is byte-for-byte unchanged. No new core tool, no new
model surface, no user-facing HERMES_* config var.

upstream-pending: fork PR #21, #22, #23
(cherry picked from commit d79d702)
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