cutjournal

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

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

View Source
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

View Source
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 Encode

func Encode(j Journal) ([]byte, error)

Encode validates j and returns its canonical JSON.

func Location

func Location(destinations []string) string

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

func Push(ctx context.Context, t Target, tag string, j Journal) (string, error)

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

func StateFileName(name string) bool

StateFileName reports whether name is a build-state file a cut harvests: a bare `build-*.json` or `sourcemaps-*.json`.

func Tag

func Tag(version string) (string, error)

Tag is the journal tag for a release version. It refuses a version whose journal tag the registry would not accept (a `+`, or too long), so a cut learns that before it builds, not after.

Types

type Expect

type Expect struct {
	Project, Env, Version, ForgeVersion string
	Commit, Tree                        string
	Dirty                               bool
}

Expect is the cut a journal must describe to be resumed by it.

type File

type File struct {
	Name string `json:"name"`
	Data []byte `json:"data"`
}

File is a file's name and exact content.

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.

func (Image) Ref

func (img Image) Ref() string

Ref is the image's full reference: `<repository>[:<tag>]@<digest>`.

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 Decode

func Decode(raw []byte) (Journal, error)

Decode parses and validates a journal document.

func Fetch

func Fetch(ctx context.Context, t Target, tag string) (Journal, string, error)

Fetch reads the journal tagged tag in t, verifying every byte against its descriptor. ErrNotFound when the tag does not exist.

func (Journal) Mismatches

func (j Journal) Mismatches(want Expect) []string

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

func (j Journal) PushedUnder(version string) bool

PushedUnder reports whether the cut pushed any image under version — the only case in which a retry would collide with its tags.

type Target

Target is the slice of a registry repository the journal is written to and read from. oras's remote.Repository satisfies it, and so does its in-memory store.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL