Publish impacted libraries coherently

You want to publish only the artifacts affected by a change, without inventing versions for unchanged ones or rebuilding the entire workspace. A release set makes that sparse publication one immutable, closed snapshot even when its members have different versions.

Publish the impact closure

putnami publish --impacted --channel canary

There is no bootstrap step. An empty channel resolves to a null head, every member is selected, and the release expects no head — the first publication of a channel is an ordinary publication.

Putnami resolves the channel once, before packaging, then republishes every member whose selection fingerprint differs from the head's record, plus every member that depends on one. Everything else is inherited from the head verbatim: neither packaged nor uploaded, and still referenced at its exact previous version. The resulting snapshot is closed — every internal dependency names a version that exists in the same release set.

A member's selection fingerprint is the execution cache key of its package task minus the embedded version. It moves when the sources move, and also when the packager, the toolchain, a task contract, or any upstream task moves — none of which a tree hash would catch.

For example, a downstream-only change produces this mixed-version set:

Library Version in the new set Packaged and uploaded? Exact internal dependency
@putnami/core 0.1.0-aaa No
@putnami/web 0.1.0-bbb Yes @putnami/core@0.1.0-aaa

If @putnami/core changes instead, Putnami selects it and repackages @putnami/web, so the downstream dependency points at the core version in the new snapshot.

Members are keyed by ecosystem and coordinate

A member is (ecosystem, coordinate), not a project. One project yields one member per ecosystem and coordinate it declares, so a service that publishes a Go module, an image, and its configuration appears three times in the same snapshot, and each of those members is selected independently.

Which ecosystems exist is not built into the CLI: every installed extension declares its own ecosystem profile in its putnami.extension.jsonnpm by @putnami/typescript, go by @putnami/go, oci by the extension SDK's shared publisher — and the coordinator admits exactly those.

Advance several channels at once

putnami publish --impacted --channel canary,staging

Impact is measured against the first head; every listed channel is then advanced to the same snapshot, each compare-and-swapped against its own resolved head, in one transaction. A conflict on any one of them writes nothing on any of them and the error names every head that moved.

Publish a tag as one cohort

When HEAD carries its line's tag, putnami publish needs no selection flag and no --channel:

git push origin ts/v0.3.0     # CI checks out the tag and runs putnami publish

Every member of that line is republished at the tag's version, and the release creates an immutable channel named after the tag in the portable encoding (ts/v0.3.0 becomes ts-v0.3.0). That channel accepts one head and refuses every later move, so a tag names one snapshot forever. A dirty tree is refused, and --scope <line> names the line when HEAD carries several tags.

Treat failure as no publication

Sparse release-set publication fails closed when any of these is not proven:

  • one release-set provider is available;
  • every listed channel resolves to a valid answer for the workspace;
  • the impact selection contains every downstream that must be repackaged;
  • every selected artifact is uploaded and verified by its SHA-256 digest;
  • the final snapshot is closed and stored immutably;
  • the compare-and-swap succeeds on every listed channel.

A partial upload or a compare-and-swap conflict is not a successful publication: every channel head is preserved. Fix the cause and replan from the current heads; the command never falls back from --impacted to --all.

Without a release-set provider, putnami publish --all still publishes every member to the registries the workspace declares, with git-derived versions. --channel is refused, and distribution and envs in putnami.ci.json fail putnami ci validate.

Keep channel selection separate from delivery

A channel such as canary is a mutable pointer. A release-set reference is an immutable content address. Successful publication emits one typed data.releaseSet payload:

{
  "protocolVersion": 2,
  "namespace": "putnami",
  "ref": {
    "id": "rs_<64 lowercase hex>",
    "digest": "sha256:<same 64 lowercase hex>"
  },
  "channels": {
    "canary": { "ref": { "id": "rs_<64 hex>", "digest": "sha256:<64 hex>" }, "generation": 12 }
  }
}

channels reports the head each advanced channel now points at, with the monotone generation the provider stamped. A registry applies a projection only when the generation increases, so a delayed event can never restore an older projection and a retry is idempotent. putnami channel status <c> shows the desired head against what each registry has actually applied, and exits 1 while any is behind.

CI must carry both ref.id and ref.digest from that result into deployment. Deployment consumes those exact values and must not resolve canary again: if another publisher moves it between publish and deploy, the deployment still uses the snapshot the publish proved.

The same distinction applies when upgrading a workspace:

putnami upgrade --channel canary          # follow the channel on every registry
putnami upgrade --release rs_<64-hex> --namespace putnami # one immutable release set
putnami upgrade --version 0.1.0-abc123456 # one exact version

--channel reads the native projection each ecosystem publishes — an npm dist-tag, a Go @v/<channel>.info query resolved per module, the put registry's channel projection — so it needs no release-set provider and no namespace, and any workspace can use it. A channel is mutable, so a channel that moves mid-run can mix two publications across ecosystems; the release writes every projection of a channel in one event with one generation to shrink that window. --release is deterministic even if a channel moves later; it takes the publishing --namespace explicitly and needs the release-set provider. See Upgrade Putnami for workspace update details.

Promote and roll back without rewriting artifacts

Release sets and their member artifacts are immutable, so promotion and rollback are the same gesture — move the channel:

putnami channel set latest --from canary        # promote the head canary points at
putnami channel set latest --from rs_<64 hex>   # roll back to an exact snapshot

channel set is one provider call: no publisher runs, no artifact is uploaded, no branch is checked out, and the move is compare-and-swapped against the channel's current head. A channel the repository declares protected in putnami.ci.json moves only this way — putnami publish --channel latest is refused before any provider call.

Roll a consumer back by selecting a previously known release-set id with upgrade --release <id> --namespace <namespace>. Never overwrite or rebuild an old snapshot.

You now have sparse publication that preserves unchanged upstreams, repackages required downstreams, and hands deployment one exact immutable release set.