Uh oh!
There was an error while loading. Please reload this page.
fix(service-automation): report a durable http callout as unmeasured, not acted (#7882) - #7962
Conversation
… not acted (#7882) The `http` node's durable path returned `metrics: { acted: 1 }` for an enqueue. `enqueueHttp()` returns the id of a `pending` sys_http_delivery row — the HttpDispatcher decides the outcome afterwards, including dead-lettering — so a run summary could claim `acted: 1` for a callout the durable record shows as `dead`. It now reports `unmeasuredEffect`: the platform's existing qualifier for an effect whose outcome is not yet knowable, and not a bare `acted: 0`, which connector.zod.ts forbids and which would trip the broken-sweep alert on every healthy durable callout. Smaller than the sibling fix at `notify` (#7747): `emit()` hid two outcomes behind one call and needed EmitResult split; `enqueueHttp()` has no such ambiguity, so nothing in service-messaging changed. The inline path keeps its measured counts. Fixes#7882 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016U4fTquAhViZT7RcMfcmSq
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7882
The defect
http-nodes.ts:139returnedmetrics: { acted: 1 }for a delivery that had only been enqueued.enqueueHttp()hands back the id of apendingsys_http_deliveryrow;HttpDispatcherdecides the real outcome afterwards, and that outcome includes dead-lettering the callout (non-retriable status, or an exhausted retry budget). So an operator reading the run summary could seeacted: 1for a callout the durable record shows asdead— the summary asserting an effect that never happened.The fix
The durable path now reports
unmeasuredEffect. That is the platform's existing word for "an effect happened but its outcome is not yet knowable" (the same qualifierconnector_actionuses), and pointedly not a bareacted: 0—connector.zod.ts:613forbids that, and it would trip the documented broken-sweep alert (selected > 0 AND acted = 0 AND unmeasured = 0) on every healthy durable callout. A pending delivery now suppresses that alert without asserting success.The step still succeeds and its output is still
{ deliveryId, enqueued: true }: the flow did everything it can do synchronously, and not blocking on the callout is the whole point of durable mode. No synchronous wait was added.Did #7875's shape apply? Partly — the ruling, not the structure
#7875 needed
EmitResultsplit intodeliveredvsenqueuedinsideservice-messaging, becauseMessagingService.emit()hides two outcomes behind one call: inline (P0) fan-out, which really does know the result, and the P1 outbox, which does not. The node could not tell them apart without the service-side narrowing.enqueueHttp()has no such ambiguity. It returns a row id and nothing else, and the row it writes is unconditionallypending— there is nothing to split. The two-path structure already sits in the node:durable: trueenqueues, everything else callsfetch()inline, and the inline half already reported honestly (acted: 0for a read,acted: 1for an accepted mutation,unmeasuredEffectfor a rejected/timed-out one). So the whole disagreement was one branch of one node. Nothing inservice-messagingchanged.Reverse-verification on
origin/mainNew test run against unmodified
origin/mainnode code:Every durable-record assertion (
sys_http_deliveryrow isdead/pending/success,responseCode: 404,attempts: 0) passed onmain— the defect is real and lives only in the summary. After the fix: 4/4 green, and the full package suite is 940 passed (79 files).The test wires the real
MessagingService+MemoryHttpOutbox+HttpDispatcherand ticks the dispatcher manually, for the same reason #7875 did: the outcome is decided after the run settles, which a fake answering in one shot cannot express. Assertions are on the two durable operator-facing records — the folded run summary and the outbox row — never on call counts. Both directions are covered so the fix cannot degenerate into "dead-lettered deliveries are special": the enqueued-then-succeeded case reports the sameunmeasured, and the two inline cases keep their measured counts.Consumer check
Grepped
deliveryId,enqueued, and the durableactedclaim across the repo:{ deliveryId, enqueued: true }output — read by nothing outside the node and its own tests.plugin-webhooks'deliveryIdis an unrelated request-body field on the redeliver endpoint.metrics— folded generically byrun-summary.ts; no consumer special-cases thehttpnode.content/docs/automation/flows.mdx:737documentedhttp, durable: true → acted: 1 — the outbox row is a real, durable effect. That row is now corrected — it was the one place the old behavior was promised. Updated in this PR.packages/spec/src/**file was touched, so nogen:schema/gen:docsregeneration is needed (io-node-config.zod.ts's doc comment describes the{ deliveryId }output, which is unchanged, and makes noactedclaim).check:doc-authoring,check:doc-anchors,check:docs-audit-scopeandtypecheckare green locally. Changeset:.changeset/http-node-enqueued-not-acted.md.Generated by Claude Code