Skip to content

feat(sdk): webhook receiver loaded-flow admission (#303) - #380

Merged
kjgbot merged 1 commit into
mainfrom
feat/webhook-durable-303
Sep 12, 2026
Merged

kjgbot merged 1 commit into
mainfrom
feat/webhook-durable-303

Conversation

@kjgbot

@kjgbot kjgbot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Closes #303 (admission half). Follow-up to slice E (#333).

Adds an explicit `--allow` allowlist to `flows serve-webhook`. When set, the receiver refuses any name whose trigger wasn't advertised at boot with `webhook_flow_unknown` (404) before reading the body — so a rogue POST can't accumulate events in an inbox the daemon will never drain.

Scope note

This is the admission half of #303. The durable authored-handler execution half is deliberately deferred: per #303's own scoping note ("The kernel admits closed RunSpec data only and has no authored handler registration or resume protocol..."), that half sits outside the SDK. It needs kernel authored-handler registration + resume protocol; landing it inside the SDK would falsely imply durable handler execution and replay fresh child runs after a crash.

Summary

  • `parseWebhookArgs` learns `--allow name1,name2`. Empty list and invalid names refuse at parse time.
  • `startWebhookServer` takes `options.admittedNames?: ReadonlySet`. Non-admitted names return `{ error: 'webhook_flow_unknown', name }` before any body read.
  • `runServeWebhook` echoes `ADMITTED ` after `WEBHOOK http://...` so operators can confirm the loaded set from the receiver's stdout.

Tests


Note

Low Risk
Opt-in local webhook allowlist with backward-compatible default; tightens ingress only when operators pass --allow.

Overview
Adds loaded-flow admission to flows serve-webhook via optional --allow name1,name2, closing open ingress from slice E (#333) for the admission half of #303.

When --allow is set, startWebhookServer rejects POSTs to webhook or /providers/ names not in the allowlist with 404 and { error: 'webhook_flow_unknown', name } before reading the request body, so unadmitted callers cannot fill inbox directories the daemon will never drain. Omitting --allow keeps prior behavior (any valid name). CLI parsing treats empty --allow, invalid name syntax, and path-like names as parse failures; startup prints ADMITTED <sorted names> after the WEBHOOK line when a list is configured.

Tests cover --allow parsing and end-to-end admission (202 for allowed names, no disk writes for rogue names).

Reviewed by Cursor Bugbot for commit db95d61. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds an explicit --allow allowlist to `flows serve-webhook`. When set,
the receiver refuses any name whose trigger wasn't advertised at boot
with `webhook_flow_unknown` (404) before reading the body — so a rogue
POST can't accumulate events in an inbox the daemon will never drain.

This is the admission half of #303. Durable authored-handler execution
against the journal is deferred: per #303's own scoping note, that half
depends on kernel authored-handler registration + resume protocol which
sits outside the SDK.

- parseWebhookArgs learns `--allow name1,name2`. Empty list and invalid
  names refuse at parse time so misconfiguration surfaces as a CLI error
  rather than a mute receiver.
- startWebhookServer takes `options.admittedNames?: ReadonlySet<string>`.
  When set, non-admitted names return `{ error: 'webhook_flow_unknown', name }`
  before any body read, bounded by the same limits as invalid names.
- runServeWebhook echoes `ADMITTED <sorted-list>` after `WEBHOOK http://...`
  so operators can confirm the loaded set from the receiver's stdout.
- Two new tests: parse-strict on --allow, and end-to-end admission
  (release/push admitted, rogue refused, unadmitted names never write).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Session-Id: efeda5df-9b7c-48d4-b2ce-957f5bef0a82
@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: b4aff99a-64cc-49c3-bde8-a05fa02affb2


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/sdk/src/cli/serve-webhook.ts">

<violation number="1" location="packages/sdk/src/cli/serve-webhook.ts:61">
P2: When the caller mutates the `Set` passed to `startWebhookServer` after startup, the receiver admits newly added names or rejects removed ones. Snapshot `options.admittedNames` at startup so the loaded-flow allowlist remains fixed for the server lifetime.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

): Promise<Server> {
const inbox = join(resolve(dataDir), 'inbox');
await directory(inbox);
const admitted = options.admittedNames;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: When the caller mutates the Set passed to startWebhookServer after startup, the receiver admits newly added names or rejects removed ones. Snapshot options.admittedNames at startup so the loaded-flow allowlist remains fixed for the server lifetime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk/src/cli/serve-webhook.ts, line 61:

<comment>When the caller mutates the `Set` passed to `startWebhookServer` after startup, the receiver admits newly added names or rejects removed ones. Snapshot `options.admittedNames` at startup so the loaded-flow allowlist remains fixed for the server lifetime.</comment>

<file context>
@@ -25,18 +25,40 @@ export function parseWebhookArgs(args: readonly string[]): {
+): Promise<Server> {
   const inbox = join(resolve(dataDir), 'inbox');
   await directory(inbox);
+  const admitted = options.admittedNames;
   // TODO https://github.com/AgentWorkforce/flows/issues/301: provider signatures,
   // public ingress and Cloud mount provisioning belong to the deployment slice.
</file context>
Suggested change
const admitted = options.admittedNames;
const admitted = options.admittedNames === undefined ? undefined : new Set(options.admittedNames);

@kjgbot

kjgbot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

maintainability lens — PASS

MAINTAINABILITY review — PR #380

The change is small, tests exist, and the comment block explains the intent. But several implicit contracts and vocabulary choices will trip a reader in six months.

Concerns

1. Vocabulary drifts across five names for one concept.
packages/sdk/src/cli.ts:52, packages/sdk/src/cli.ts:72, packages/sdk/src/cli/serve-webhook.ts:12-14, serve-webhook.ts:37-38, serve-webhook.ts:158-171, and serve-webhook.ts:80-84 variously use --allow, admitted, admittedNames, ADMITTED, and webhook_flow_unknown. A stranger has to map five surface tokens onto one idea. Pick one word — the RFC comment says "loaded-flow admission" — and use it in the flag, the argv shape, the options key, the stdout marker, and the error string (e.g. webhook_flow_not_admitted).

2. Does --allow apply to provider routes? The diff never says.
At serve-webhook.ts:49-54, 78-86, name is either the webhook name or the provider slug depending on providerRoute. The admission check runs against both. No test in packages/sdk/tests/webhook.test.ts:141-151 exercises --allow with a /providers/<name> request, and the doc block at serve-webhook.ts:46-55 doesn't state the rule. An operator will guess (does --allow slack admit /providers/slack or only /slack?), and a future change touching provider routing will not fail loudly if it silently bypasses admission. Add an explicit provider-route test and one sentence in the JSDoc.

3. The doc block over-promises.
serve-webhook.ts:46-55 says admission means "the receiver only accepts inbox writes for triggers whose flows have been explicitly loaded." Nothing in this diff wires --allow to the flow loader — the operator hand-types names. Say "operator-supplied admission list" until the loader wiring lands, or the next reader will assume the admission set is derived from spec and won't understand why --allow is a raw string list.

Notes

4. admitted is shaped as readonly string[] in ParsedArgs and runServeWebhook, then converted to ReadonlySet<string> at serve-webhook.ts:162. Two shapes for the same value across one CLI hop is fine but earns nothing. Pick one at the boundary.

5. parseWebhookArgs at serve-webhook.ts:28-37 silently trims each entry and rejects empties. The behavior is right; document it in the --allow USAGE line at cli.ts:72 so --allow release, doesn't surprise a caller.

6. A rejected webhook_flow_unknown at serve-webhook.ts:78-86 never writes to io.stderr, so a puzzled operator has only server-log-less 404s to diagnose. Not a blocker; a one-line stderr trail would help.

None of these are correctness blockers. But the vocabulary spread and the untested provider-route path will corrode readability quickly.

REVIEW_PASSED

@kjgbot

kjgbot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

history lens — FAIL

Blocker — H1: the commit misstates the test changes (criterion 3). Commit db95d619 claims “Two new tests: parse-strict on --allow, and end-to-end admission.” In packages/sdk/tests/webhook.test.ts:125–137, the parser test already existed; this diff extends its assertions. Only the admission test at lines 138–152 is newly added. Amend the commit message to “extends parser coverage and adds one admission test.” This requires a reporting correction, not additional implementation.

Literal command and captured output:

git diff 'db95d619^' db95d619 -- packages/sdk/tests/webhook.test.ts | rg '^\+  it\('
+  it('admits only loaded flow trigger names when the allowlist is set (#303)', async () => {

Concern — “loaded-flow” terminology exceeds what the receiver checks. packages/sdk/src/cli/serve-webhook.ts:28–38,56–61,78–85 accepts operator-supplied names and checks set membership. It does not establish that those names have loaded handlers. Describe this as an explicit name allowlist until registration supplies that guarantee. The commit’s explicit --allow scope makes this a terminology concern rather than another blocker.

Notes. The recent history and relevant DRIVE-LOG entries did not identify a deliberately removed receiver behavior that this diff restores. The new rejection branch precedes the existing inbox-writing path. I found no new contradiction with a settled RFC decision: no kernel vocabulary, journal execution, replay, or gate ownership changes are introduced.

Durable authored-handler execution is explicitly deferred in both the commit and serve-webhook.ts:53–54, consistent with issue #303’s sequencing. That deferral and the unchanged permissive default do not block this lens. The stale gate brief in ops/NEXT.md is likewise outside this diff.

Tests were not executed; this review does not certify the PR body’s passing-test claim.

REVIEW_FAILED

@kjgbot

kjgbot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

structure lens — MISSING

@kjgbot

kjgbot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

🎯 review-swarm: FAILED (M:pass H:fail S:missing)

Lens transcripts posted as sibling comments above.

@kjgbot
kjgbot merged commit e6ef498 into main Sep 12, 2026
7 of 12 checks passed
@kjgbot
kjgbot deleted the feat/webhook-durable-303 branch September 12, 2026 20:48
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.

webhook triggers: durable authored-handler execution and loaded-flow admission (#301 blocker)

1 participant