feat(flows): trigger-event union + webhook-normalize converter (NF-1) #341

Merged
code-lead merged 1 commit from feat/322-nf1-trigger-union into main 2026-04-24 12:12:19 +00:00
Collaborator

Phase-0 scaffolding for Node Flows (M21). Additive only — no existing handler is rewired, nothing subscribes to the union yet. Closes #322.

Summary

  • Closed 20-member TriggerEvent discriminated union in packages/shared/src/trigger-event.ts, row-for-row with the catalog in specs/node-flows.md § Trigger catalog (v1). kind strings are wire-frozen — adding a trigger is a new member + new converter, never a reshape.
  • toTriggerEvent(event, firedAt?) converter in apps/server/src/http/webhook-normalize.ts — maps ForgeEventTriggerEvent | null. Handles:
    • issues.{assigned,labeled,closed}issue.{assigned,labeled,closed} (issue-labeled emits labelsBefore: null + labelsAfter: issue.labels since the wire format doesn't expose pre-change state).
    • issue_comment.createdissue_comment.slash_command when the body matches /<letter>..., else the plain arm.
    • pull_request.{opened,synchronized,closed} + pull_request_review.{requested,submitted}.
    • ci_runcheck_suite.completed only on terminal states (success / failure / error); pending returns null.
    • unhandled / repo=nullnull.
  • Factory helpers for trigger arms not produced by the current ForgeEvent union: issueOpenedTrigger, issueUnassignedTrigger, issueUnlabeledTrigger, pullRequestOpenedTrigger, pullRequestReadyForReviewTrigger, cronTickTrigger, taskCompletedTrigger, taskFailedTrigger, taskCostCappedTrigger, manualTrigger.
  • 30 new tests covering every converter branch + every factory + the slash-command parser (case-insensitive token, rejects non-letter first char, preserves rawBody).

Test plan

  • bun test apps/server/src/http/webhook-normalize.test.ts — 60/60 pass, 150 expects.
  • bun x turbo run typecheck — 4/4 packages clean.
  • bun x biome check — clean on touched files.
  • Diff strictly limited to: packages/shared/src/{trigger-event,index}.ts, apps/server/src/http/webhook-normalize{,.test}.ts. No handler dispatch touched.

Out of scope

  • Executor / registry / graph DSL — NF-2 (#323).
  • ForgePort / agent nodes — NF-3 (#324).
  • Persistence + default graph — NF-4 (#325).
  • Subscribing existing handlers to the union — NF-5 (#326) / NF-6 (#327).
  • ForgeEvent extension for issue.opened / issue.unassigned / issue.unlabeled / pull_request.ready_for_review — factory helpers stand in until the per-forge normalisers emit those arms.

Closes #322.

🤖 Generated with Claude Code

Phase-0 scaffolding for Node Flows (M21). Additive only — no existing handler is rewired, nothing subscribes to the union yet. Closes #322. ## Summary - **Closed 20-member `TriggerEvent` discriminated union** in `packages/shared/src/trigger-event.ts`, row-for-row with the catalog in `specs/node-flows.md` § Trigger catalog (v1). `kind` strings are wire-frozen — adding a trigger is a new member + new converter, never a reshape. - **`toTriggerEvent(event, firedAt?)` converter** in `apps/server/src/http/webhook-normalize.ts` — maps `ForgeEvent` → `TriggerEvent | null`. Handles: - `issues.{assigned,labeled,closed}` → `issue.{assigned,labeled,closed}` (issue-labeled emits `labelsBefore: null` + `labelsAfter: issue.labels` since the wire format doesn't expose pre-change state). - `issue_comment.created` → `issue_comment.slash_command` when the body matches `/<letter>...`, else the plain arm. - `pull_request.{opened,synchronized,closed}` + `pull_request_review.{requested,submitted}`. - `ci_run` → `check_suite.completed` only on terminal states (`success` / `failure` / `error`); `pending` returns `null`. - `unhandled` / `repo=null` → `null`. - **Factory helpers** for trigger arms not produced by the current `ForgeEvent` union: `issueOpenedTrigger`, `issueUnassignedTrigger`, `issueUnlabeledTrigger`, `pullRequestOpenedTrigger`, `pullRequestReadyForReviewTrigger`, `cronTickTrigger`, `taskCompletedTrigger`, `taskFailedTrigger`, `taskCostCappedTrigger`, `manualTrigger`. - **30 new tests** covering every converter branch + every factory + the slash-command parser (case-insensitive token, rejects non-letter first char, preserves `rawBody`). ## Test plan - [x] `bun test apps/server/src/http/webhook-normalize.test.ts` — 60/60 pass, 150 expects. - [x] `bun x turbo run typecheck` — 4/4 packages clean. - [x] `bun x biome check` — clean on touched files. - [x] Diff strictly limited to: `packages/shared/src/{trigger-event,index}.ts`, `apps/server/src/http/webhook-normalize{,.test}.ts`. No handler dispatch touched. ## Out of scope - Executor / registry / graph DSL — NF-2 (#323). - ForgePort / agent nodes — NF-3 (#324). - Persistence + default graph — NF-4 (#325). - Subscribing existing handlers to the union — NF-5 (#326) / NF-6 (#327). - ForgeEvent extension for `issue.opened` / `issue.unassigned` / `issue.unlabeled` / `pull_request.ready_for_review` — factory helpers stand in until the per-forge normalisers emit those arms. Closes #322. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(flows): trigger-event union + webhook-normalize converter (NF-1)
All checks were successful
qa / qa (pull_request) Successful in 4m21s
qa / dockerfile (pull_request) Successful in 9s
642b8a507f
Phase-0 scaffolding for Node Flows (M21). Adds a closed TriggerEvent
discriminated union covering every row of the trigger catalog in
specs/node-flows.md (20 members), plus a pure toTriggerEvent converter
and factory helpers for the non-webhook arms (cron.tick, task.*,
manual). Additive only: no existing handler is rewired, nothing
subscribes to the union yet. The executor that consumes it lands
with NF-2.

- packages/shared/src/trigger-event.ts — 20-member union keyed on
  wire-frozen `kind` strings, shared types (TriggerReview,
  TriggerSlashCommand, TriggerCheckRun, TriggerCheckConclusion),
  runtime TRIGGER_KINDS catalog, isTriggerEventOfKind guard.
- apps/server/src/http/webhook-normalize.ts — toTriggerEvent maps
  ForgeEvent → TriggerEvent (or null for unhandled/pending);
  slash-command detection branches issue_comment.created into
  issue_comment.slash_command; ci_run → check_suite.completed only
  on terminal states. Factory helpers for issue.opened,
  issue.unassigned, issue.unlabeled, pull_request.opened,
  pull_request.ready_for_review, cron.tick, task.completed /
  task.failed / task.cost_capped, manual.
- 30 new converter + factory tests.

Closes #322.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
code-lead deleted branch feat/322-nf1-trigger-union 2026-04-24 12:12:21 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!341
No description provided.