Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLIPublic
Assigneeszack

Relationships

#3211 Deliver a signal by key through swamp serve

Opened by zack · 10/9/2026· Shipped 10/9/2026

Problem

Part 3 of 6 of keyed signal waits. swamp-club#3210 lets the local command signal by workflow and key. Over the network a signal still needs a wait ID, and the only way to find one is workflow.waits, which needs read. A device or service that holds a credential granted only signal therefore cannot signal a run it was not told about, which is the case a fixed key exists for.

Proposal

  • The WebSocket request workflow.signal accepts a workflow and a key in place of waitId. An HTTP form does the same. swamp workflow signal --server passes the key form. Every transport calls deliverSignalForCaller (src/serve/signal_delivery.ts) and so the one acceptance use case.
  • Authorization comes first. The caller needs signal on the named workflow, and that is decided before any claim, registration or workflow definition is read. By ID the registration says which workflow a wait belongs to; by key the request names it, so nothing has to be looked up to authorize.
  • An unknown workflow, an undeclared key and a caller who may not signal get the same answer: not_found over WebSocket and 404 over HTTP, with one fixed message. The denial is audited with the workflow's name; the reply never carries it.
  • A declared key with no open wait is answered to an authorized caller with its own refusal (no_open_wait). A caller who may signal the workflow already learns whether a wait is open from whether a delivery succeeds, so this gives away nothing more.
  • The reply carries the wait ID and the receipt. The workflow, run, job and step, awaitingResume and the resume command are added only for a caller who may also read the workflow, as today.
  • The by-key forms pass the same gates as the by-ID route: the rate limits, the payload and body caps, --restricted-commands, the audit fail-secure check and --signal-requires-explicit-grant.
  • The audit event workflow.signal.delivered carries the key. The payload is never logged or audited.
  • Serve resolves a key from claim records only. It does not scan run records, as it does not for a wait ID.
  • Continuation after a delivery is unchanged (continueAfterSignal).
  • Against a server that predates the key form, the CLI reports that the server needs an upgrade (requestNewerServerResponse).

Open decisions

  1. The HTTP route's shape. Workflow names may contain @ and /, so the workflow and key probably travel in the body, not the path.
  2. The HTTP status of no_open_wait.

Acceptance criteria

  • A credential granted only signal on the workflow delivers by key over HTTP and over WebSocket with no earlier lookup, and the run resumes when the workflow's auto-resume policy allows.
  • The same credential gets one and the same answer for another workflow's key, an undeclared key and an unknown workflow.
  • Each refusal has a documented WebSocket error and HTTP status.
  • With --signal-requires-explicit-grant, a run grant alone does not deliver by key.
  • Signalling by wait ID through serve is unchanged.

Tests

  • Additions to integration/serve_signal_test.ts for both transports: delivery, each refusal, the same-answer rule, the reply split between a signal-only caller and a reader.
  • integration/signal_wait_records_rules_test.ts holds the new forms to the one use case.

Depends on

swamp-club#3210 (and through it swamp-club#3209).

References

swamp-club#3094, the Signal paragraphs of design/primitives/serve.md, the signal action in design/enablers/access-control.md.

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 20 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/9/2026, 9:22:26 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
zack assigned zack10/9/2026, 6:15:42 PM

Sign in to post a ripple.