6.7.10 Publishing Artifacts Reliably

recommended

Publishing is the first stage in this release path that deliberately changes a mutable external system. Uploading an object, assigning a version, or moving a tag may succeed even when the publisher crashes before recording success. A safe retry therefore cannot mean “run the same command again and hope the destination handles it.” It must recover the intent, inspect what actually happened, and converge on one declared result.

Begin with an immutable handoff

6.7.4 From Build to Release Candidate supplies the publication input: a technically eligible candidate whose artifacts and evidence are reachable and identified by digest. Signing may add another required condition through 6.7.8 Signing and Verifying Artifacts. The publisher must consume that existing subject; a retry must never rebuild it or quietly select newer bytes under the same filename.

Authenticate the handoff before accepting that publication intent. The publisher identity from 6.7.5 Build Identities and Secrets should have only the authority needed for the intended destination, while its credential remains in the external publisher runtime rather than a Bazel action, action input, or retained manifest. Keep a non-secret principal ID and request identity in the journal for attribution; never retain the token, private key, or authorization header that proved identity.

Represent publication as an explicit intent rather than as an event callback:

publication intent
  subject digest: sha256:8b7...f21
  destination:    registry.example/reports
  release name:   2026.08.25
  policy version: production-v4
  principal ID:   publisher-prod

The subject digest answers which bytes. The destination and release name answer which external mutation. The policy or request version distinguishes a genuinely new operation from delivery of an older request. Together these fields form a stable operation key. A BEP event identifier is not sufficient: it is unique within one build, while publication must recognize the same destination intent across consumer restarts, event redelivery, and deliberate replay of a completed build.1 Retain the principal ID as audit evidence, but do not make a changing worker session or credential value redefine the logical operation.

Keep the destination request identity stable as well. A worker attempt number, message offset, or retry timestamp may identify one execution of the publisher, but none identifies the logical mutation. If the destination offers an idempotency token or conditional-write key, derive it from the publication operation key and reuse it across attempts.

Do not build a second eligibility engine in the publisher. Consume the decision made by 6.7.4 From Build to Release Candidate as an opaque handoff containing at least the immutable subject digest, the upstream eligibility decision, and a locator for its retained decision record. Named signer or policy results may also be required, but the publisher retains them as handoff facts rather than reinterpreting raw build, test, or artifact status.

Validate delivery of that handoff. Missing required facts are partial; a required fact explicitly reported unavailable is unknown; a complete upstream decision that does not authorize publication is rejected; and two values for the same logical fact are conflict, even under different fragment IDs. A complete, internally consistent handoff whose upstream decision is eligible is ready. Delayed contradiction must revoke readiness before publication instead of overwriting the earlier fact.

Store the intent before beginning the side effect. After ready, give publication progress a state such as applying, reconciling, published, or conflict, and keep the immutable evidence used to make each transition. These names are an application contract, not Bazel states. Their purpose is to make “we do not yet know whether the destination changed” representable instead of forcing that uncertainty into success or failure.

Before calling the destination, durably append an attempt record containing the operation key, retained destination request identity, intended precondition, and request fingerprint. That record means “the call may have been attempted,” not “the call succeeded.” It is the recovery evidence a replacement worker needs when no local success checkpoint exists.

Treat event delivery as evidence, not execution authority

A BEP stream may be useful input to a publisher, but it is not required. A CI orchestrator can hand over a candidate manifest directly; a result service can emit a notification; a human-approved release controller can enqueue the same immutable intent. The publisher's correctness must come from its durable record and the destination contract, not from assuming one transport.

When BEP or BES supplies that input, transport identity, semantic event identity, and the downstream consumer checkpoint remain different from the publication operation key. Delivery replay can therefore repeat evidence without authorizing a repeated destination mutation. 6.5.8 Complete CI Results with BEP/BES explains how to decide whether the BEP/BES result set and its referenced artifacts are complete; this publisher only consumes the resulting handoff.1

The Wix publishing pipeline illustrates why this separation matters. Its first publisher parsed BEP only after the build and invoked Bazel again to push artifacts. The later design polled a result store and began transferring individual artifacts as they became available, while a separate promotion service waited for build success and aggregated multi-target artifacts.2 The durable lesson is not that every publisher should poll a result store. It is that transfer readiness, complete candidate eligibility, and promotion are different facts and may arrive at different times.

If early transfer is worthwhile, upload immutable content under its digest while the build is still running, but do not expose the mutable release name until the whole eligibility contract is satisfied. Failed builds can leave unreferenced blobs for lifecycle cleanup; they must not leave an artifact appearing released.

Make the mutation convergent

Idempotence is a property of the complete destination operation, not of the retry loop. Before changing the destination, ask what it currently records for the stable operation key and immutable subject.

Three outcomes are enough to guide the decision:

Destination observationPublisher decision
No matching publication existsAttempt the mutation and retain its request identity.
The intended release already resolves to the same subject digestRecord success without repeating the mutation.
The release name or operation key resolves to different bytes or intentStop in conflict; do not overwrite or declare success automatically.

Prefer a destination's native conditional creation, compare-and-set, or idempotency facility when it has one. Bind that facility to the stable publication intent, not to a transient worker attempt. If the protocol cannot atomically create or verify the intended state, model its intermediate states explicitly and provide a destination-specific reconciliation procedure. The generic publisher must not pretend that every registry, package repository, and release API has the same atomicity guarantees.

The mutable name deserves special care. Uploading digest-addressed bytes can already be convergent while assigning stable or 2026.08.25 remains a separate mutable operation. Reconcile both. “Blob exists” does not prove the requested release name points to it, and “tag exists” does not prove it points to the intended digest.

Close the crash window with reconciliation

No local checkpoint can eliminate the most important uncertainty:

destination accepts mutation
             |
             | publisher crashes here
             v
local success checkpoint is never written

After restart, the local record says applying, but the destination may already be correct. Repeating the side effect blindly can publish twice; marking it successful blindly can hide a failed mutation. Move the operation to reconciling, query the destination using the retained request and subject identities, and choose among:

  1. Confirmed: the exact subject occupies the intended destination state; record published with destination evidence.
  2. Absent: evidence shows the mutation did not take effect; retry under the same operation key.
  3. Conflicting: another subject or intent occupies the name; stop for the destination's repair policy.
  4. Unknown: the destination cannot establish any of the above; retain uncertainty and retry observation, not mutation.

This is why a message acknowledgment is not the publication boundary. Acknowledge or advance the input checkpoint only after the publication is confirmed or its reconciliation state is durably recoverable. Conversely, delaying acknowledgment does not itself provide exactly-once behavior; it merely guarantees another delivery.

think

Decide: A history build for an older subject finishes after a newer subject already occupies the release name. Its handoff is complete and technically eligible. May the worker publish because its message arrived now?

Reveal

No. Reconcile the destination and preserve the conflict. Arrival and completion order do not grant authority to supersede a different immutable subject; an explicit promotion policy must decide whether any replacement is allowed. H.11.5 Promotion defines that ordering and repair authority.

For artifacts composed from several pieces, record readiness per required component and publish only when every expected handoff fact is present. Wix provides concrete evidence for this split: its promotion service aggregated multi-target artifacts after individual transfers had become available.2

Prove recovery, not just the happy path

Build a publisher fixture around one immutable candidate and a destination double or test repository whose state can be inspected. Then inject failures at every durable boundary:

  1. omit the subject digest, upstream decision, or decision-record locator and retain partial;
  2. deliver an explicit unavailable required fact and retain unknown;
  3. deliver a complete upstream ineligible decision and require rejected;
  4. deliver eligible, then a contradictory decision under a new fragment ID;
  5. include raw build, test, or artifact facts and prove the publisher does not reinterpret them;
  6. deliver accepted handoff facts out of order and replay duplicates;
  7. crash before issuing the destination mutation;
  8. crash after the destination accepts it but before the local checkpoint;
  9. lose the consumer checkpoint, create a fresh journal, and replay the full handoff;
  10. pre-create the intended release name with the same digest;
  11. pre-create it with a different digest; and
  12. make destination observation unavailable during recovery.

Partial and unknown handoffs remain non-publishable. An upstream rejection stays rejected, while contradictory delivery stays conflict rather than becoming last-write- wins. Replay, checkpoint loss, and an already matching destination must converge without a second mutation. A conflicting destination remains visible without an automatic overwrite, and an observation outage stays unknown or reconciling rather than causing a speculative mutation. No case may rebuild the subject.

Retain evidence that joins the input invocation or manifest, operation key, subject digest, destination request, observations, attempts, and terminal state. That record lets an operator distinguish duplicate delivery from duplicate external mutation and lets a later audit explain why the destination was considered correct.

The replay-safe publisher fixture keeps its durable journal separate from an inspectable destination double. Its handoff validator distinguishes partial, unknown, rejected, ready, and conflict without recalculating upstream eligibility. The publication state machine reconciles before mutation and checkpoints a prepared attempt before the call. The negative controls cover upstream rejection and delivery contradiction, replay after checkpoint loss, matching and conflicting existing state, observation outage, and crashes around the mutation. The destination is deliberately generic: a production adapter still has to supply the selected repository's real conditional-write and observation contract.

Destination-specific details still matter. A Maven repository, OCI registry, app store, and internal promotion database expose different conditional operations and repair procedures. Their specializations should define those protocols. This article's portable requirement is the state machine: immutable intent, durable progress, destination reconciliation, and a visible conflict or unknown state when convergence cannot be proved.

key takeaway

Reliable publication consumes an already eligible, immutable subject and represents its destination mutation as an authenticated durable intent. Evaluate ready, partial, unknown, rejected, and conflicting handoff delivery explicitly without recomputing upstream eligibility; use a least-authority external publisher while retaining non-secret principal identity.

Transport or event identity is not the publication operation key, and the publisher does not have to consume BES. Derive its key from the subject and destination purpose, not from a transport or worker attempt identity.

Before every retry, reconcile the retained intent with external state. Confirmation finishes the operation, proven absence permits retry under the same key, and conflict or unavailable evidence remains visible. No transport-independent design can promise generic exactly-once publication. Test duplicate delivery and the crash after mutation but before checkpoint: both must converge on one correct destination state without rebuilding the artifact or blindly repeating an unverified side effect.

Check your understanding · 3 questions

1.A publisher restarts with an applying checkpoint after the destination may already have accepted its request. What should it do first?

Select one answer

2.Match each destination observation to the publisher decision:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
The intended release already resolves to the subject digest
The destination proves that no matching publication exists
The release name resolves to a different subject digest
The destination cannot currently be inspected

3.Classify these handoff, transport, and authority claims.

Choose True or False for each sentence

A publisher may consume an explicit manifest without consuming BES.
A BEP/BES event identity is automatically a publication operation key.
Missing an upstream decision-record locator leaves the handoff partial.
The publisher should recompute upstream eligibility from raw test results.
A complete upstream ineligible decision remains rejected by the publisher.
A retained principal ID can support attribution without retaining its credential.
0 of 3 answered

Footnotes

  1. Build Event Protocol — build-scoped event identifiers, event-graph completeness, opaque BES transport, and referenced artifact availability 1 2

  2. Bazel Parallel and Asynchronous Artifact Publishing Pipeline — parallel build completion, history builds, multi-target aggregation, early artifact transfer, and serialization by artifact identity 1 2