Documentation
¶
Overview ¶
Package cutjournal is the record a release cut leaves in the registry so a cut that is interrupted after its pushes can be finished instead of burned.
Why it exists ¶
`forge env build <env> --release <v>` pushes every image the env declares under the tag <v>, then records the release in the env's ledger. The two writes are not atomic. A machine that dies between them (control-plane v1.7.94-rc.1, 2026-11-04: every image pushed, the host rebooted before the ledger write) leaves immutable tags the ledger never saw, and the local build state that held their digests died with the checkout. Every retry of <v> then fails at its first push, so the version was burned and the set had to be re-identified by hand as the next version, from the same commits.
The registry still holds everything the cut needs except the PROOF that the tagged bytes came from the commit being cut. The journal is that proof. The cut writes it right after its last push and before its ledger record: version, env, project, the forge that built it, the commit and tree it built from, every image digest it pushed under <v>, and the exact build state the cut harvests. A retry of <v> from the same clean commit, with the same forge, finds it, checks each image tag still names the digest the journal recorded, restores the build state, and records the release over the images already pushed: nothing is rebuilt. Anything else (another commit, a dirty tree, another forge, a tag that moved) is refused, exactly as before.
Where it lives ¶
One OCI artifact, tagged <v>.forge-cut (Tag), in the first push destination the env declares (sorted), which the writer and the reader resolve the same way from the same render. The image tags themselves are left alone: the journal names them, it never moves one.
What it cannot do: resume a cut interrupted DURING its pushes. Some images are then tagged and some are not, and no journal was written; that cut is refused as it always was.
Index ¶
- Constants
- Variables
- func CheckImages(ctx context.Context, j Journal, ...) []string
- func Encode(j Journal) ([]byte, error)
- func Location(destinations []string) string
- func Open(repository string) (*remote.Repository, error)
- func Push(ctx context.Context, t Target, tag string, j Journal) (string, error)
- func StateFileName(name string) bool
- func Tag(version string) (string, error)
- type Expect
- type File
- type Image
- type Journal
- type Target
Constants ¶
const ( // Schema names the journal document's version. Schema = "forge.dev/cut-journal/v1" // ArtifactType is the OCI artifactType of a journal manifest. ArtifactType = "application/vnd.forge.cut-journal.v1" // LayerMediaType is its single layer: the journal document. LayerMediaType = "application/vnd.forge.cut-journal.v1+json" // TagSuffix is appended to the release version to make the journal's tag. TagSuffix = ".forge-cut" )
Variables ¶
var ErrNotFound = errors.New("no cut journal")
ErrNotFound is returned by Fetch when no journal is tagged for the version.
Functions ¶
func CheckImages ¶
func CheckImages(ctx context.Context, j Journal, open func(repository string) (content.Resolver, error)) []string
CheckImages asks the registry about each image the journal recorded, and lists every one that is not as recorded: a version tag that no longer names the recorded digest (or is gone), or a reused image whose digest is gone. open returns the repository an image lives in.
func Location ¶
Location is the repository the journal of a cut is written to and read from: the first of the env's push destinations, sorted. "" when there is none.
func Open ¶
func Open(repository string) (*remote.Repository, error)
Open is the repository at `<host>/<path>` (no tag), authenticated from the ambient docker credential store — what `docker push` to it would present.
func Push ¶
Push writes j to t under tag and returns the manifest digest. The tag is set last, so it never names an incomplete artifact.
func StateFileName ¶
StateFileName reports whether name is a build-state file a cut harvests: a bare `build-*.json` or `sourcemaps-*.json`.
Types ¶
type Image ¶
type Image struct {
Repository string `json:"repository"`
Tag string `json:"tag,omitempty"`
Digest string `json:"digest"`
}
Image is one image the cut records: <Repository>@<Digest>. Tag is set when the cut pushed it under the release version (<Repository>:<Tag> named Digest then), and empty for an image the cut reused under another tag.
type Journal ¶
type Journal struct {
Schema string `json:"schema"`
// Project, Env and Version identify the cut.
Project string `json:"project"`
Env string `json:"env"`
Version string `json:"version"`
// ForgeVersion is the forge that built and would have recorded it. The
// build state below is that forge's format, so only it resumes the cut.
ForgeVersion string `json:"forge_version"`
// Commit, Tree and Dirty are the project checkout the cut built.
Commit string `json:"commit"`
Tree string `json:"tree,omitempty"`
Dirty bool `json:"dirty"`
// Images are the images the cut pushed under the release version.
Images []Image `json:"images"`
// State is the build state the cut harvests, by file name under the
// project's state directory, byte for byte.
State []File `json:"state"`
// Outputs are build outputs the cut reads besides images (the metrics
// schema), by path relative to the build output directory.
Outputs []File `json:"outputs,omitempty"`
}
Journal is one cut's record of what it pushed and from where.
func Fetch ¶
Fetch reads the journal tagged tag in t, verifying every byte against its descriptor. ErrNotFound when the tag does not exist.
func (Journal) Mismatches ¶
Mismatches lists every way j does not describe the cut want would make: empty means the journal's images are this cut's images, as far as the journal can say. The registry's side (does each tag still name its digest) is CheckImages.
func (Journal) PushedUnder ¶
PushedUnder reports whether the cut pushed any image under version — the only case in which a retry would collide with its tags.