Make the publish gate read the authority, not the plan #334

Open
PerishFire wants to merge 1 commit from task/a-gate-that-reads-the-authority into main
Owner

Before projecting cargo, npm, oci or chart, sealed read the capsule off local disk and checked the version written inside it. A capsule is an upload plan — built by assemble, before anything is uploaded — so what that proved was that this machine compiled the version, not that the release it belongs to stands anywhere. The media announce a version to registries the world reads, and the gate in front of them asserted less than the changelog shipped in the same release.

Where it actually matters

Inside the lane the gate was never what held the line:

resolve → build → seal → project
                    │        └ ship <medium> publish
                    └ ship binary publish / verify / activate / verify

project needs seal, so the ordering already guaranteed what the capsule was being asked about. The gate matters where that ordering is absent — a person running plumb ship cargo publish by hand, which is exactly what happened partway through v0.26.0.

The change

It reads the seal back from the public authority and compares the digest, using the same fetch plumb ship binary verify already performs. The capsule already carries that URL and digest, so nothing new is threaded through the lane and no job learns a new environment variable.

Two boundaries kept it honest:

  • It checks the seal alone, not published, which walks every object in the capsule. Five media each re-pulling every archive is a cost the gate does not need to pay.
  • It fires only for a medium the product declares. plumb ship chart publish against a product with no chart still answers that there is no chart attachment, rather than failing on a network read for something it was never going to send.

The check is per-object, so it takes the object rather than the capsule — which is also what stopped a fourth capsule-shaped free function from gathering in verify.

Test covers both paths: the seal fetch succeeding, and a served body that disagrees with the recorded digest refusing as public object drift.

Before projecting cargo, npm, oci or chart, `sealed` read the capsule off local disk and checked the version written inside it. **A capsule is an upload plan** — built by `assemble`, before anything is uploaded — so what that proved was that this machine compiled the version, not that the release it belongs to stands anywhere. The media announce a version to registries the world reads, and the gate in front of them asserted less than the changelog shipped in the same release. ## Where it actually matters Inside the lane the gate was never what held the line: ``` resolve → build → seal → project │ └ ship <medium> publish └ ship binary publish / verify / activate / verify ``` `project` needs `seal`, so the ordering already guaranteed what the capsule was being asked about. The gate matters where that ordering is absent — a person running `plumb ship cargo publish` by hand, which is exactly what happened partway through v0.26.0. ## The change It reads the seal back from the public authority and compares the digest, using the same fetch `plumb ship binary verify` already performs. **The capsule already carries that URL and digest**, so nothing new is threaded through the lane and no job learns a new environment variable. Two boundaries kept it honest: - It checks **the seal alone**, not `published`, which walks every object in the capsule. Five media each re-pulling every archive is a cost the gate does not need to pay. - It fires only for a medium the product **declares**. `plumb ship chart publish` against a product with no chart still answers that there is no chart attachment, rather than failing on a network read for something it was never going to send. The check is per-object, so it takes the object rather than the capsule — which is also what stopped a fourth capsule-shaped free function from gathering in `verify`. Test covers both paths: the seal fetch succeeding, and a served body that disagrees with the recorded digest refusing as `public object drift`.
Make the publish gate read the authority, not the plan
All checks were successful
guard / guard (pull_request) Successful in 4m41s
3ee2f7642b
Before projecting cargo, npm, oci or chart, `sealed` read the capsule off local
disk and checked the version written inside it. A capsule is an upload plan
built by `assemble`, before anything is uploaded, so what that proved was that
this machine compiled the version — not that the release it belongs to stands
anywhere. The media announce a version to registries the world reads; the gate
in front of them asserted less than the changelog shipped in the same release.

Inside the lane the gate was never the thing holding the line: `project` needs
`seal`, and `seal` publishes, verifies, activates and verifies again, so the
ordering already guaranteed what the capsule was being asked about. The gate
matters where the ordering is absent — a person running `plumb ship cargo
publish` by hand, which is exactly what happened partway through v0.26.0.

So it now reads the seal back from the public authority and compares the
digest, using the same fetch `plumb ship binary verify` already performs. The
capsule already carries that URL and digest, so nothing new is threaded through
the lane, and no environment variable is added to a job that would have had to
learn it.

Two boundaries kept it honest. It checks the seal alone rather than reusing
`published`, which walks every object in the capsule: five media each re-pulling
every archive is a cost the gate does not need to pay. And it fires only for a
medium the product actually declares — `plumb ship chart publish` against a
product with no chart still answers that there is no chart attachment, instead
of failing on a network read for something it was never going to send.

The verification is a per-object one, so it takes the object rather than the
capsule, which is also what stopped a fourth capsule-shaped free function from
gathering in `verify`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All checks were successful
guard / guard (pull_request) Successful in 4m41s
This pull request has changes conflicting with the target branch.
  • AGENTS.md
  • crates/cli/src/command/release/truth/verify.rs
  • crates/cli/src/dispatch/ship.rs
  • crates/cli/tests/operator/repository/registry/main.rs
  • plumb.toml
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin task/a-gate-that-reads-the-authority:task/a-gate-that-reads-the-authority
git switch task/a-gate-that-reads-the-authority
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
PerishLab/plumb!334
No description provided.