Capabilities & operations
Publishing.
For: reviewers and operators
Once a human approves an artifact, it can be published โ dispatched to one or more destinations. Publishing is the first capability where Cardinal reaches out of its own database into the world, so it is built around one principle: nothing leaves without a human deciding it should. This page is how that works โ the action, the per-target lifecycle, who delivers it, and the gates that protect a customer.
The shape of a publish
A publish has two levels. A publication is the parent โ the decision to send one artifact out โ and it fans out to one or more targets, one per channel. The artifact is polymorphic: any of the engagement's artifact types (a decision, a risk, an ADR, a report, a brief, a recognition) can publish to any channel. The interesting state lives on each target; the parent carries a rollup.
flowchart TB ART(["โ Approved artifact
decision ยท risk ยท ADR ยท report ยท brief ยท recognition"]):::art GATE{{"๐งโโ๏ธ A human presses
PUBLISH ๐"}}:::gate PUB["๐จ Publication (parent)
rollup: queued ยท partial ยท sent ยท failed"]:::pub subgraph targets ["๐ค Per-channel targets โ each delivered + tracked on its own"] direction LR T1["๐
In-app
permalink"]:::tinapp T2["๐ฌ
Chat
DM"]:::tchat T3["โ๏ธ
draft"]:::tmail T4["๐
Work-item
tracker"]:::tado end ART ==> GATE ==> PUB PUB ==> T1 PUB ==> T2 PUB ==> T3 PUB ==> T4 style targets fill:#fafafa,stroke:#a8a29e,stroke-width:1.5px,color:#1c1917 classDef art fill:#fce4ec,stroke:#b11f4b,stroke-width:2.5px,color:#7a1132 classDef gate fill:#fef3c7,stroke:#d97706,stroke-width:3px,color:#78350f classDef pub fill:#fff7ed,stroke:#ea580c,stroke-width:2px,color:#7c2d12 classDef tinapp fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a classDef tchat fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95 classDef tmail fill:#ccfbf1,stroke:#0d9488,stroke-width:2px,color:#134e4a classDef tado fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#7f1d1d
One artifact, one publication, several targets โ each target delivered and tracked on its own.
The per-target lifecycle
Each target moves through its own lifecycle โ it is not a single linear "artifact status." A target is rendered, sent, and then (for channels that can be answered) waits for an acknowledgement. Failures retry with backoff; a target can be cancelled.
stateDiagram-v2
direction LR
[*] --> queued
queued --> rendering
rendering --> ready_to_send
ready_to_send --> sending
sending --> sent: 200 OK
sent --> awaiting_ack: channel can ack
sent --> [*]: fire-and-forget
awaiting_ack --> acknowledged
awaiting_ack --> rejected
acknowledged --> [*]
rejected --> [*]
sending --> failed: error
failed --> sending: retry ยท backoff
failed --> [*]: gave up ยท max retries
queued --> cancelled: operator cancels
ready_to_send --> cancelled
cancelled --> [*]
classDef pending fill:#f1f5f9,stroke:#64748b,color:#1e293b,stroke-width:2px
classDef active fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px
classDef done fill:#dcfce7,stroke:#16a34a,color:#14532d,stroke-width:2.5px
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px
class queued,rendering,ready_to_send pending
class sending,awaiting_ack active
class sent,acknowledged done
class failed,rejected,cancelled bad
Retry & failure
A failed delivery carries a retry count and a next-attempt time, backing off exponentially. After a handful of attempts it fails permanently rather than retrying forever.
The synchronous exception
An in-app permalink is designed as the one channel delivered synchronously inside the publish action itself โ there's no recipient and no worker, so it would go from queued to sent immediately.
The channels
Each destination is a channel, and each channel is one adapter. Whether a publish is internal, partner, or customer-bound is set per publish and is independent of the channel โ a chat message to a customer contact is a customer-audience publish, and the gates key off that audience, not the channel.
Available now
- Chat DM โ an internal direct message; the first delivery channel built and proven.
Planned channels
- In-app permalink โ a stable link to the artifact in Cardinal, delivered synchronously, with no recipient.
- Customer email draft โ creates a draft only; never auto-sends (see below).
- Work-item tracker (Azure DevOps) โ pushes a work item to Azure DevOps Boards, with state sync back.
- Shared document/slide/spreadsheet exports and team channel โ a further set of channels.
Who delivers it
Delivery is done by an agent, not a cloud function. A resident worker โ the "publish drainer" โ runs on the ring's recurring tick under a single-holder lease, claims the targets that are ready, renders each, delivers it through the relevant channel, and writes the result back. It uses the same claim-and-drain pattern as the scouts: take a batch, work it, record it, advance.
The privacy gates
This is the heart of the page. A customer-bound publish passes through several gates, and there is no code path that bypasses them โ the publish action has no automated or agent caller, so every publication is initiated by a person and recorded against that person.
The gates a customer publish passes
- Human-initiated โ there is no auto-send path, ever
- Approval required โ a customer audience always needs sign-off
- Separation of duties โ the approver cannot be the author
- Sensitivity label โ an unlabelled customer publish halts until one is applied
Email is double-gated
A customer email is created as a draft only. Cardinal never sends it. The operator opens their mailbox and clicks Send themselves โ so a customer email passes both the approval gate and a second, manual human action at the very end.
Acknowledgement & the rejection badge
For channels that can be answered, a target waits for an acknowledgement after it's sent. When a recipient responds, the response is matched back to the target and the target is marked acknowledged or rejected โ a thumbs-up or a positive reply acknowledges; a pushback, a won't-fix, or a negative reply rejects. Silence past a configured window is recorded as "no response" โ which is not a rejection. The matching is done by the conversation classifier.
Passive, never pushy
Matching a reply back to a publish is automatic, but it never auto-messages anyone in return. Acknowledgement updates state; it does not start a conversation on its own.
The rejection badge
If a customer-audience target is rejected, a warning badge appears on the source artifact itself โ a passive, visible signal that something a human sent to a customer was pushed back. It surfaces; it does not notify.
The surfaces it dispatches through โ ยท The trust posture it rests on โ