Skip to content

Publishing

The publishing module turns a run's outcome into the places people actually look: issue trackers, documentation and chat. It's the last layer of the framework, and the one that decides whether automation saves time or just produces more reports nobody reads.

Publishing moduleRun events flow into a publisher router, which uses routing rules to fan them out to pluggable sinks: issue tracker, documentation, chat and run storage. Documentation changes go to a human for approval.Run eventsstarted, passed, failedPublisher routerRouting rules (YAML)One interface per sinkIssue trackerLinear, Jira, GitHub IssuesDocumentationConfluence, Notion, docs PRChatSlack, Microsoft TeamsRun storageReports, dashboardsHuman approvalBefore docs go liveFrameworkPluggable sinksHuman gate

What was built, and what's design

The original framework published to an issue tracker (the tracking ticket and its bugs), object storage and test reports picked up by dashboards. The router, documentation sinks and chat hooks on this page are the design I'd build today on the same interface.

Events, not reports

Instead of producing one report at the end, the framework emits events as a run moves through its lifecycle:

Event When Typical use
run.started A trigger is accepted and provisioning begins Open the tracking ticket, post a "running" message
run.passed All checks passed and teardown has started Update the ticket, propose a documentation change
run.failed Any check failed and the run has paused Open bugs, alert the owning team
run.resumed Triage is done and teardown is resumed Close the loop on the ticket and in chat
run.destroyed Infrastructure is gone Record final cost and duration

Every event carries the same run result: the triggers, topology, resolved infrastructure details, check results and links to stored artefacts. Sinks pick the fields they need.

One interface per sink

Each destination implements a small interface, so adding Slack or swapping Jira for Linear is a new class, not a change to the framework:

class Publisher(Protocol):
    name: str
    def handles(self, event: RunEvent) -> bool: ...
    def publish(self, event: RunEvent) -> PublishOutcome: ...

A publisher that fails (an API is down, a token has expired) returns a failed outcome. It never fails the run. The result is still in run storage, and the publish can be retried from there.

Routing rules

Which events go where is configuration, not code:

routes:
  - on: [run.started, run.passed, run.failed, run.resumed]
    to: tracker            # Linear, Jira or GitHub Issues
  - on: [run.failed]
    to: chat
    channel: "#platform-support"
  - on: [run.passed]
    to: docs               # proposes a change; a person approves it
  - on: ["*"]
    to: storage            # everything, always

Teams can subscribe to the topologies or triggers they own, without touching the framework.

The sinks

Issue tracker

The tracker stays the system of record: one ticket per run, and one linked bug per failing check. Linear, Jira and GitHub Issues all work through their APIs. The publisher updates a single ticket throughout the run, rather than opening a new one for each event.

Documentation

On run.passed, the documentation publisher proposes the change. It never publishes it:

  • Confluence or Notion: update a draft of the support page, or a "pending listings" table, and notify the page owner.
  • Docs as code: open a pull request against the docs repository that adds the new row to the support matrix, with links to the run's evidence.

The pull request route fits best with the matrix as data. If support states live in a structured file, the documentation publisher only changes data, and reviewing it is a normal code review.

Keep a human on the publish button

A support listing is a promise to customers. Automation gathers the evidence and drafts the change, and a person approves it.

Chat: Slack and Microsoft Teams

Chat is for attention, not records. Messages are short and link back to the ticket and the stored evidence:

  • On failure: the failing checks, the topology and triggers, and links to the ticket and logs, posted to the owning team's channel.
  • On pass: an optional one-line summary in a quieter channel.
  • Actions: chat apps support interactive buttons, so a failure message can carry "Resume teardown" or "Keep for 24 hours". That turns the paused fail path into a one-click decision, instead of something left in a ticket.

Slack and Teams each start with incoming webhooks for one-way messages, and move to their app platforms when interactive actions are needed.

Run storage and dashboards

Everything goes to object storage, whatever the routing rules say. It's the source the other sinks link to, the place failed publishes are retried from, and the input for dashboards that show trends across runs.

What makes publishing worth it

Useful Noise
One ticket per run, updated as it progresses A new ticket for every event
A chat message only when someone needs to act A message for every passing run in a busy channel
A drafted doc change with linked evidence A doc page edited by automation with no review
Links to stored artefacts Pasted logs