delivery.pending v1
A post approved for manual delivery still owes the board its live link (charter: the decision envelope covers questions and follow-ups §3). Pushed when the gate is resolved approve_manual, and read back by the carrier’s sweep from GET /v1/boards/:id/deliveries/pending, from one builder. Mirrors SuperPipeline’s DeliveryPendingBody (apps/api/src/board/views.ts). Readers ignore properties they do not know.
CloudEvents
Section titled “CloudEvents”Every event below carries dataschema https://raw.githubusercontent.com/SuperJackfruitLabs/treaty/main/schemas/superpipeline/delivery.pending.v1.json.
Body event |
CloudEvents type |
Subject |
|---|---|---|
delivery.pending |
com.superjackfruit.superpipeline.delivery.pending.v1 |
evt.<workspace>.superpipeline.delivery.pending |
The subject is the NATS subject the relay files it under; <workspace> is the board’s workspace. See the envelope and subjects.
Who sends it, who reads it
Section titled “Who sends it, who reads it”- Produced by SuperPipeline, through the relay.
- Consumed by:
- SuperExchange: Posts it to the board’s room, for the boards it carries. Receives it through the relay.
- AgentPod: The hub’s board chat posts it, for the boards it still carries.
- SuperDispatch: Takes it as a nudge to look at the board soon, on a board opted into scheduling. Reads nothing from its data.
Fields
Section titled “Fields”Readers ignore properties they do not know, so a field may be added without a new version.
| Field | Type | Required | Description | Constraints |
|---|---|---|---|---|
event |
"delivery.pending" |
yes | ||
boardId |
string or null | yes | Null only on a board not yet initialised; a carrier needs it to route the event. | |
boardName |
string | yes | Empty string when the board has no name, never absent. | |
cardId |
string | yes | ||
cardTitle |
string | yes | ||
gateId |
string | yes | ||
stageKey |
string | yes | ||
approvalSubject |
object | yes | A subject as a decision envelope carries it (gate.v2 §3): identity, expiry and the kind’s bounded preview. The canonical payload stays behind the authenticated route. | |
approvalSubject.id |
string | yes | ||
approvalSubject.digest |
string | yes | ||
approvalSubject.schema |
string | yes | ||
approvalSubject.revision |
integer | yes | at least 0 | |
approvalSubject.expiresAt |
string or null | yes | RFC 3339 with seconds and an explicit offset, or null. The pattern states it for a validator that does not assert format. | format date-time; pattern ^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d+)?(Z|[+-]([01]\d|2[0-3]):[0-5]\d)$ |
approvalSubject.preview |
array of object (type = “text”) or object (type = “field”) or object (type = “link”) | yes | ||
approvalSubject.preview[].type |
"text" |
yes | in the type = "text" shape |
|
approvalSubject.preview[].label |
string | no | in the type = "text" shape |
|
approvalSubject.preview[].text |
string | yes | in the type = "text" shape |
|
approvalSubject.preview[].type |
"field" |
yes | in the type = "field" shape |
|
approvalSubject.preview[].label |
string | yes | in the type = "field" shape |
|
approvalSubject.preview[].value |
string | yes | in the type = "field" shape |
|
approvalSubject.preview[].type |
"link" |
yes | in the type = "link" shape |
|
approvalSubject.preview[].label |
string | yes | in the type = "link" shape |
|
approvalSubject.preview[].url |
string | yes | in the type = "link" shape |
|
approvalSubject.previewComplete |
boolean | yes | False when the preview was cut to its bounds; no approving option may then be offered. | |
options |
array of object | yes | record_live_url always; switch_to_automatic only when the subject’s preview was complete. | |
options[].id |
one of "record_live_url", "switch_to_automatic" |
yes | ||
options[].label |
string | yes | ||
options[].meaning |
one of "approve", "reject", "revise", "choose", "defer" |
yes | What choosing the option does (gate.v2 §2). | |
options[].needsComment |
false |
yes | ||
options[].input |
object | no | What choosing the option asks the person to type. | |
options[].input.kind |
one of "text", "url" |
yes | ||
options[].input.label |
string | yes | ||
options[].input.required |
boolean | yes | ||
options[].input.maxLength |
integer | yes | at least 1 | |
options[].delivery |
"automatic" |
no | ||
deadline |
object | no | When the decision is reminded about and escalated. Absent when its stage sets no timeout. | |
deadline.remindAt |
string | no | format date-time |
|
deadline.escalateAt |
string | no | format date-time |
|
ts |
string | yes | When the gate was resolved. | format date-time |
Example
Section titled “Example”From fixtures/superpipeline-events/examples.json, case “both-options”:
{ "event": "delivery.pending", "boardId": "brd_134a0ddb6d434478", "boardName": "Fixture board", "cardId": "crd_0f3c9a2b7d4e4a11", "cardTitle": "Sanitized approval fixture", "gateId": "gat_5d1e2f3a4b5c6d7e", "stageKey": "approve", "approvalSubject": { "id": "aps_2b7c9d0e1f2a3b4c", "digest": "sha256:9f2c1a7e5b3d4c6a8e0f1b2d3c4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e", "schema": "social-publish/v1", "revision": 1, "expiresAt": "2030-01-02T03:04:05Z", "preview": [ { "type": "text", "label": "Post", "text": "Shipping the new board view today." } ], "previewComplete": true }, "options": [ { "id": "record_live_url", "label": "I posted it — add the link", "meaning": "defer", "needsComment": false, "input": { "kind": "url", "label": "Live link", "required": true, "maxLength": 2000 } }, { "id": "switch_to_automatic", "label": "Post it automatically instead", "meaning": "approve", "needsComment": false, "delivery": "automatic" } ], "ts": "2026-11-10T09:30:00.000Z"}The corpus has 2 valid cases in all:
cut-preview-with-deadline
{ "event": "delivery.pending", "boardId": "brd_134a0ddb6d434478", "boardName": "Fixture board", "cardId": "crd_0f3c9a2b7d4e4a11", "cardTitle": "Sanitized approval fixture", "gateId": "gat_5d1e2f3a4b5c6d7e", "stageKey": "approve", "approvalSubject": { "id": "aps_2b7c9d0e1f2a3b4c", "digest": "sha256:9f2c1a7e5b3d4c6a8e0f1b2d3c4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e", "schema": "social-publish/v1", "revision": 1, "expiresAt": null, "preview": [ { "type": "text", "label": "Post", "text": "Shipping the new board view today." } ], "previewComplete": false }, "options": [ { "id": "record_live_url", "label": "I posted it — add the link", "meaning": "defer", "needsComment": false, "input": { "kind": "url", "label": "Live link", "required": true, "maxLength": 2000 } } ], "ts": "2026-11-10T09:30:00.000Z", "deadline": { "remindAt": "2026-11-10T10:30:00.000Z", "escalateAt": "2026-11-10T13:30:00.000Z" }}Refused
Section titled “Refused”Each of these must fail. The corpus says why:
- the event names itself
- an option is record_live_url or switch_to_automatic
- a follow-up always carries its subject
- a subject’s expiry has seconds
- a subject’s expiry has an offset with a colon
Source
Section titled “Source”- Schema:
schemas/superpipeline/delivery.pending.v1.json - In the package:
import schema from "@superjackfruitlabs/treaty-event-schemas/superpipeline/delivery.pending.v1.json" with { type: "json" };