A geometric red cardinal carrying an envelope to deliver

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.

Manual, multi-target Per-target lifecycle Agent-dispatched Human-pressed, always

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["โœ‰๏ธ
Email
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.
One field map worth knowing. When an artifact publishes to the work-item tracker (Azure DevOps), each artifact type maps to a work-item type โ€” a risk becomes a Risk with severity and probability, a decision becomes a Decision, an ADR becomes an ADR carrying its review stage, and so on. The exact map is itself a recorded decision an engagement can override.

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.

Nothing reaches a customer by accident. Between "an agent drafted this" and "a customer received this" stand a human approval, a separation-of-duties check, a sensitivity-label check, and โ€” for email โ€” a manual Send. The guarantee is structural, not a matter of good behaviour.

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 โ†’