run

package
v0.0.2-alpha Latest Latest
Warning

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

Go to latest
Published: Sep 3, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Package run is the engine: what happens between `hyper run <procedure>` and the Journal entry it leaves behind (§6, issue #136).

It takes a loaded repository, a Store handle, the reads the process supplies and the Capabilities' performers, and answers a value the CLI renders. **It reaches no process fact of its own**: no clock, no randomness, no environment variable. Every one of them is threaded through Request, which is what makes a Run's every path in the Store a checked-in constant rather than a value normalised out of a golden (§8, ADR-0047), and run_test.go holds it over this package's own source.

That is a claim about **this package** and not about the tool beneath it, and the difference is worth stating rather than leaving to be discovered. Two packages under it start a git subprocess with the process's own environment, deliberately: the git hyper shells out to is the same git that resolves the credential a checkout left behind (§7, §11), and internal/git holds the one rule that keeps such a subprocess acting on the repository the caller named. So the engine performs no read of its own; what it reaches through does, and where it reaches is the repository root it was handed.

It renders nothing either. What a Step table looks like, what the terminal line says and which exit code an outcome maps onto are §8's and §9's, and they live in internal/cli beside every other surface. What is here is what happened; what is said about it is one package up (ADR-0026).

**The order is §6's fixed order**, and no Step starts until all of it has happened: the pin gate, the lock and the Store's location, all three the CLI's and all three declining before a Run is identified at all; then `run.json`; then the Store schema test, `check` re-run in full, the credential pass and the Secret sink, each of which declines into the entry that already exists; then Step 1. Perform states the order and gates.go states the four in the middle (issues #137, #138).

The lock is the CLI's for the reason the Store's location is: it is a lock on the Store, a Run that cannot take it never opens one, and a Run with no Store has no entry to decline into. Which of the two locks it takes is this package's, though — it is read off the Kinds and nothing else, which is lock.go.

Index

Constants

View Source
const (
	// CodeCredentialAbsent is a credential a Target declaration names and
	// the environment does not hold.
	CodeCredentialAbsent = "credential-absent"
	// CodeSecretSinkAbsent is an invocation supplying no Secret sink where
	// the Procedure reaches a Step whose Operation declares secret output.
	CodeSecretSinkAbsent = "secret-sink-absent"
)

The two codes §9 contributes, alike in being the **occasion's** supply rather than the environment's or the artefacts', both checked before a Run's first Step and both reported exhaustively rather than at the first (§12, ADR-0007).

They are spelled here because this is where the checks that carry them are. internal/store reports the schema condition and does not name a code, and §4's thirty-two arrive already carrying theirs.

View Source
const CodeBoundExceeded = "bound-exceeded"

CodeBoundExceeded is §6's half of the code §4 already fires where the count is authored: an Expansion resolving to more Records than the Step's declared Bound.

It is one code because it is one check — what names a Refusal is the check that declined, never the moment it ran — and it is spelled here rather than imported from internal/artefact for CodePredicateTypeMismatch's reason above: §4's constant names a length a reader counts off the page and this one a count no file holds, and the string is the contract the two sites reach independently, as §4's and §6's own texts do (§4, §5, §6).

View Source
const CodePredicateTypeMismatch = "predicate-type-mismatch"

CodePredicateTypeMismatch is §6's half of the code §4 already fires where the fault is authored: an operator handed a **stored** value it cannot compare.

The two halves are one check at two moments, which is why they are one code (ADR-0035). It is spelled here rather than imported from internal/artefact because that package's constant names an authored fault and this one names a value nobody wrote — the string is the contract and the two sites reach it independently, as §4's and §6's own texts do.

View Source
const CodeRecordIdentityCollision = "record-identity-collision"

CodeRecordIdentityCollision is the code both of the Expansion's identity comparands carry: two members of one Expansion resolving to one Record identity, and a resolved identity colliding with a series the Store already holds (§6, §7, §12, ADR-0070, ADR-0075).

View Source
const CodeRunOnceRecorded = "run-once-recorded"

CodeRunOnceRecorded is §6's second run-time check: a run-once Step the Journal already holds as *ran* or as *attempted, outcome unknown*.

It is one of the few members of the closed set that require a Step to have been reached at all, and like every one of them it declines **before a call goes out** — a guardrail that declines after one is a halt and has no `error_code` to carry (§12, ADR-0072).

Variables

View Source
var ErrInterrupted = errors.New("interrupted: no further Step was started, and this Run closed its own entry")

ErrInterrupted is what stopped a Run that drained: the Run's own Fault, and what the surface reads to know that the code §12 fixes for this stop is the signal's rather than the outcome's (§6, §12, ADR-0015).

It names no signal. Which one arrived decides `130` from `143` and that is §12's mapping, made where the signal was caught; what this package holds is that the Run was stopped rather than resisted — the two are both `failed`, and a fault a reader can act on is the whole of what a `failed` Run says beyond its outcome (§9).

View Source
var ErrSyncFailed = errors.New("the Store could not be synced: this Run's entry did not reach the remote, and an effectful Run does not touch the world against a record it cannot write")

ErrSyncFailed is an effectful Run whose push of `run.json` did not land: its Store sync, and the earliest moment such a Run could have known it would be able to record what it does (§7, ADR-0083).

It is the third way a Run loses the Store, beside the lock and the push, and it is `failed` at `75` for their reason: what it would take to clear it is time rather than an act of anyone's, which is exactly what sorts `75` from a Refusal's `77` (§12, ADR-0061). That mapping is the surface's, one package up — this one holds no exit code and maps none — and what stands here is the condition a reader can act on.

A read-only Run never reaches it. Its sync is the fetch one layer up, which it attempts and tolerates, and it pushes nothing until its end (ADR-0083).

Functions

func LockMode

func LockMode(loaded repository.Loaded, procedure string) store.LockMode

LockMode is the lock the named Procedure's Run holds for its duration: **exclusive where it contains any effectful Step, shared where every Step is `read`**. So a five-minute monitoring cadence is not starved behind a forty-minute provision, and two monitoring cadences are not starved behind each other.

It is decided from the Kinds `check` already computes and **before any Step runs** — before the Store is reached at all, in fact, since the lock is a lock on the Store and a Run that cannot take it never opens one. That is what makes this a walk over reviewed text with no world in it: the Kind is read off the Operation the Step binds and never off the Step, a Kind being declared per Operation in a Manifest and never inferred (ADR-0025).

**A Step whose Kind cannot be read is exclusive**, and so is a Procedure whose Steps this walk could not reach in full. An unresolvable binding leaves no Kind to judge; an invocation naming nothing, and a cycle, leave Steps the walk never saw (sequence.go) — and a Run whose blast radius cannot be read is not a Run that may share the Store. None of them ever gets as far as its first Step: every one is `check`'s to refuse at Run start. The lock is taken before that, so what it does with them is a fact of its own rather than one that path makes unreachable.

A Procedure name that resolves to nothing is exclusive on the same reading. It is unreachable from the CLI, which resolves the positional against the namespace first (§9, ADR-0060), and the safe answer costs nothing where nothing can reach it.

Types

type Answer

type Answer struct {
	// Started is the instant the Run began, which is the instant every
	// relative predicate in it resolved against (ADR-0034). It is here
	// because §8's Refusal renders the gloss — `older_than: 14d resolved
	// to …` — and the supply for one is a Run's start and nothing else: a
	// surface with no Run renders no gloss, and a surface with one derives
	// it rather than reading a member no file holds.
	Started time.Time
	// Run is the Run's id, and Identified says an entry was written under
	// it. The pair is §8's *where no entry was written the id is absent, and
	// its absence is the fact*: two paths decline before a Run is identified
	// at all, and on both the terminal line names nothing to look up.
	Run        store.RunID
	Identified bool
	// Outcome is §12's triple, and every Run reaching this package ends in
	// exactly one member of it.
	Outcome store.Outcome
	// Steps is one entry per Step that reached a Disposition, in the Run's
	// written order — which is the Step table's order and the `<nnnn>` the
	// entry names its files by (§8, §12).
	Steps []Step
	// Withheld is the position of the Step a rehearsal stopped at, and zero
	// where the Run withheld nothing — which is every Run that is not a
	// `--dry-run`, and every rehearsal that reached the end of a Procedure
	// with no effectful Step in it (§9, ADR-0010).
	//
	// It is the position and not a Disposition of its own, because §12's
	// seven are closed and the withheld Step is one of them: the Run ended
	// before it, it wrote no file, and *never reached* is what an entry
	// reads back from that silence (§7). What this carries is *it stopped,
	// and here is where* — which the entry holds as the first Step of the
	// run of silent ones and the surface would otherwise have to re-derive.
	//
	// **Every surface reads it from here**, through Withholds below: the
	// page's sentence beneath the Step table, and the `withheld` member on
	// that Step's row, which is what carries the same fact to `--json` and
	// to MCP (§8, §9, ADR-0091, issue #206).
	Withheld int
	// Provenance is the Run-wide half: what `run.json` carries, and what the
	// Run-wide `provenance` row renders (§7, §8, ADR-0043).
	Provenance store.RunProvenance
	// Refusal is the checks that declined, in the order `check` prints them,
	// and is non-empty exactly where Outcome is OutcomeRefused. It is one
	// array and never several Refusals: a Run has at most one Refusal ever,
	// the outcome being terminal, and the members are the checks one phase
	// evaluated together (§7, §8, ADR-0061).
	//
	// What the terminal line and the `outcome` row name is the **first**
	// member's `error_code`, derived where it is rendered and stored nowhere
	// (§7, §8).
	Refusal []Refusal
	// Fault is what stopped the Run, and nil where nothing did. It is
	// narration's — a failure carries no `error_code` (§9, §12) — and it is
	// the whole of what the surface has to say about a `failed` Run beyond
	// the outcome itself.
	Fault error
}

Answer is what a Run did, in the shape the CLI renders (§8).

It carries no exit code and no rendered line. §12 maps an outcome onto a code and §8 states what the line says; a value that carried either would be a second representation of a fact one layer up, which is the rule the Store itself is written under (§7).

func Perform

func Perform(request Request) Answer

Perform runs the Procedure and answers what it did.

**The order is §6's, and it is the whole of this function.** What the milestone has not built declines first, before anything is minted; then the code branch is read, because a Run that cannot name a revision has no Provenance to write and nothing to write it on; then the Run is identified and `run.json` goes down; then the Steps, in written order; then `outcome.json`; then the push.

It answers rather than returning an error, because a Run that stopped is still a Run that happened: what it did before it stopped lives in its Records and its Dispositions rather than in its outcome (§6), and every one of them is on the Answer.

func (Answer) Withholds

func (a Answer) Withholds(step Step) bool

Withholds is *this is the Step the rehearsal stopped at*, asked of one Step of this Answer's own Steps (§9, ADR-0091, issue #206).

It is a method rather than a comparison each surface writes because the page and the row must be **one fact and not two that have to agree**: the sentence beneath the Step table and the `withheld` member on the row are two renderings of this predicate, and a copy of it is a place they can drift apart (§8, ADR-0026).

A Run that withheld nothing carries zero, and no Step is at position zero — so a Run the world resisted answers false everywhere, however many *never reached* Steps it left behind it. That is the discrimination the fact exists to make: the withheld Step's Disposition is *never reached* like theirs, and nothing but this says which one it was.

type Narrator

type Narrator interface {
	// Began is the Run naming itself, before its first Step. It is the one
	// place a Run's identity reaches an operator whose process is killed
	// outright, which is why it is narrated rather than left to the
	// terminal line (§9).
	//
	// A surface where that is not the one place sends nothing, which is a
	// rendering like any other: a Narrator decides what an event says, and
	// saying nothing is one of the answers.
	Began(run store.RunID)
	// Uncommitted is the Run saying the code it is about to perform is in
	// no commit: an artefact it read differs from `HEAD` or is untracked,
	// which is the same fact `repo_dirty` marks on the entry (§7).
	//
	// It fires **before the first Step** and where that fact stands, and
	// nowhere else. What it costs is what the entry cannot say for itself
	// in time: every revision this Run is about to record is a blob id
	// nothing ever wrote, so a later `review` of any of those artefacts
	// opens at nothing and the Comparison's baseline is gone (§8,
	// ADR-0119, issue #239).
	Uncommitted()
	// Reached is one Step boundary: the Step's position, how many the Run
	// holds, and its authored id.
	Reached(position, of int, id string)
}

Narrator is a Run's progress as it happens, and it is three events because §9's narration is three lines: the Run naming itself before its first Step, the warning where the code it is about to run is in no commit, and one line per Step boundary, in both modes, always on.

**The middle one is the only conditional line**, and it is an event rather than a member of the first because that is what keeps the surface's job the surface's: the engine has read the code branch by the time it fires and knows the fact, and whether the fact is worth a line — and what the line says — is a rendering (§9, ADR-0119).

It is an interface rather than a stream this package writes to, because what those lines *say* is a rendering and renderings live in internal/cli. The engine reports the boundary; the surface writes the words. It carries no machine contract and has no `--json` variant, so nothing here derives from it and nothing reads it back.

**The second implementation is what the interface was for.** On the MCP surface a Step boundary is a `notifications/progress` and the Run naming itself is nothing at all, because the id reaches that caller in the summary line and in `run_id`; the engine reports the same two events either way and knows about neither rendering (§9, ADR-0092, cli.notifications).

type Narrowing

type Narrowing struct {
	// Line is where the operand is authored and Field its path in §8's
	// remediation notation. They are the predicate's own coordinate rather
	// than the Bound's: the two rows of `EDIT ONE OF` are two edits, and
	// they are on different lines.
	Line  int
	Field string
	// From is the operand as authored and To the rung proposed.
	From, To string
	// Expansion is what the proposal resolved to — the count the page
	// carries, and the whole reason this read was performed.
	Expansion int
}

Narrowing is the narrowed selector as the `EDIT ONE OF` table renders it and the `remediation` row carries it: where the operand is, what it says now, what it would say, and what that would expand to (§8).

It is the **first relative operand** the selector carries and no other. §12's eleven operators are not all narrowable in a direction a tool can name — an `equals` has no next rung, and a `starts_with` narrowed by a character is a guess about names rather than arithmetic — where a duration has an order and a direction that shrinks the set. So the proposal exists where the arithmetic does, and where it does not the table renders the Bound row alone rather than inventing a second one.

type Refusal

type Refusal struct {
	store.RefusalMember
	// Operation and Target are the binding of the Step this check cites,
	// and are empty on a Refusal that cites no Step.
	Operation, Target string
	// Narrowed is the second remediation §8's `EDIT ONE OF` table renders
	// beside a Bound, and nil on every check that has no second edit to
	// offer — which is every one of them but `bound-exceeded`, and that one
	// only where its selector carries an operand a rung can be proposed for
	// (§8, narrow.go).
	//
	// It rides here rather than in the entry for the reason `resolved` rides
	// on a row: it is derived, it is a hypothetical, and a Store holding one
	// would be a Store holding a count no Run ever acted on (§7, ADR-0034).
	Narrowed *Narrowing
}

Refusal is one check that declined a Run: everything its Store counterpart carries, plus the two members a row adds where it cites a Step (§7, §8).

The two are on the wire and never in the entry, and that is §7's own split rather than an omission: `operation` and `target` are what the Step the check cites was bound to, and the entry already holds them on the Step's own file wherever a Step file exists. A Refusal before Step 1 writes none, so a consumer reading the row is handed the binding and a consumer reading the entry reads the artefact that made it.

type Request

type Request struct {
	// Repository is the load every name in the Run resolves against, and
	// RepoRoot is where it was read from — the second being what the code
	// branch's revisions are read out of.
	Repository repository.Loaded
	RepoRoot   string
	// Store is the record, already located. Locating it is the caller's,
	// because a Run that cannot find one Refuses `store-absent` before it
	// has an id to refuse under (§6, §7).
	Store *store.Store
	// Procedure is the top-level Procedure's name, already resolved against
	// the repository: a positional matching nothing is a usage error and
	// never reaches here (§9, ADR-0060).
	Procedure string
	// Trigger is what caused this Run and where it happened, already read
	// off the environment. It is filled rather than derived here for the
	// reason this package reads no environment variable at all — and
	// nothing in the engine reads it back, recording where a Run happened
	// being a fact about the occasion rather than an authority axis (§5,
	// §7).
	Trigger store.Trigger
	// DryRun says this Run is a rehearsal. It is carried into the entry on
	// every Run, false included (§7, ADR-0001).
	//
	// **A rehearsal performs the reads it reaches and stops at the first
	// effectful Step** rather than simulating one (§9, ADR-0010). Nothing
	// about that Step is simulated, predicted or described: `hyper` has no
	// plan, and a surface saying what a `mutate` *would* do would be the
	// prospective counterpart the Comparison deliberately is not. The stop
	// is Perform's, and Withheld on the Answer is the Step it stopped at
	// (issue #155).
	//
	// The places the marker is read back are already built, and both read
	// it off an **entry** rather than off this member: an entry a rehearsal
	// wrote is evidence that a rehearsal happened and evidence of nothing
	// else, so a Step's identity digest filters it out (step.go) and so does
	// run-once Repeatability (once.go). Getting the second wrong is what the
	// exception to the absence rule is bought against — a rehearsal counted
	// as evidence would permanently refuse every run-once Step in the
	// Procedure it rehearsed (§7, §8, ADR-0001).
	DryRun bool
	// Version is the binary's, and it is Provenance's `hyper_version`. It is
	// always a release string: the pin gate refuses any binary whose version
	// differs from the repository's in either direction, so there is no
	// development form to write (§7, §11).
	Version string
	// Now is the clock. Every instant the Run records comes through it, so a
	// fixture's entry is reproducible and the dates a golden holds are the
	// case's own.
	Now func() time.Time
	// Mint mints the Run id at the instant it is handed. It is threaded
	// rather than called for the reason the clock is, and one more: a Run id
	// lands on the terminal line, in the `outcome` row, in `run.json` and in
	// every Store path a Run writes, so an id minted here would make every
	// golden of a Run unassertable (§8, ADR-0047).
	Mint func(now time.Time) store.RunID
	// LookupEnv is the environment, read. It is threaded for the reason the
	// clock is — the engine reaches no process fact of its own — and it is
	// reached exactly once per credential slot the Run's bindings require,
	// at the credential pass §6 puts before Step 1 (§6, ADR-0007).
	//
	// It answers a value **and whether the variable is set at all**, which
	// is os.LookupEnv's shape rather than os.Getenv's: presence is the whole
	// of what the gate asks, and a variable set to the empty string is
	// present (§9).
	LookupEnv func(string) (string, bool)
	// Environ is the environment read whole, and it is reached for exactly
	// one thing: a `shell` Operation's child inherits it, less every
	// credential-slot variable in the repository (§3, §11, issue #142).
	//
	// It is a second read of one subject rather than a widening of the
	// first. LookupEnv answers *what does this name hold*, which is the
	// whole of what the credential pass asks; composing a child's
	// environment is a subtraction, and a set of names nothing enumerates is
	// not a set anything can subtract from.
	Environ func() []string
	// SecretSink is the path `--secret-out` named, and "" where the
	// invocation supplied none. It is the **occasion's** supply rather than
	// the environment's or the artefacts', which is why it arrives here
	// beside DryRun rather than being read off anything (§6, §9, ADR-0008).
	//
	// The engine reads its presence and never its value: what a Run does
	// with a sink is the milestone that writes one's, and #133 flags the ADR
	// that has to state the file's format first.
	SecretSink string
	// Dial and Exec are the two Capabilities' performers. Neither is reached
	// for: internal/capability is handed one, so a case exercises a real
	// handshake against a server standing in the test process and a real
	// child against a script a fixture checked in (§5, issues #133, #142).
	Dial capability.Dial
	Exec capability.Exec
	// Interrupted says the first interrupt has arrived, and it is the whole
	// of what this package knows about signals: which signals are watched
	// for, what they exit with and when a second one kills the process are
	// the surface's, one package up (§6, §9, ADR-0015, issue #145).
	//
	// It is a function for the reason every other process read here is one
	// — the engine reaches no process fact of its own — and it answers
	// rather than blocks, because the one question a Run asks about a stop
	// is *has one arrived by now*: it is asked where the next Step would
	// start and nowhere else, so a Step in flight is never asked to stop
	// and never told to (Perform).
	//
	// **Being a predicate rather than a signal is what let a second surface
	// arrive without a second mechanism.** A `run` reached through the MCP
	// server has no terminal behind it and catches no signal at all; what
	// stops it is the client cancelling the request, and the mapping is
	// this predicate reading that call's context. The composition of the
	// two is the surface's, one package up, and nothing here can tell which
	// of them answered (§6, §9, ADR-0015, ADR-0092, cli.stopping).
	//
	// It may be nil, which is a Run nobody can stop: a `hyper` compiled
	// into a test binary has no handler installed and no call to cancel.
	Interrupted func() bool
	// Narrator is where progress goes as it happens. It may be nil, which is
	// a Run nobody is watching.
	Narrator Narrator
}

Request is everything a Run is performed from.

The four process reads are functions rather than resolved values, which is cli.Process's own rule one layer down: a read a Run never makes is a read that never happens, and a Run of one `read` Step starts no child and dials once.

type Step

type Step struct {
	// Position is the Step's place in the Run's written order, the first
	// Step 1.
	Position int
	// ID is the Step's authored `id:`, Kind its Operation's declared Kind
	// held rather than resolved, and Disposition what became of it (§7).
	ID          string
	Kind        store.Kind
	Disposition store.Disposition
	// Path is the invocation chain this Step was reached through, and empty
	// on a top-level Step. It is the same member the Step file carries, and
	// it is what the Step table renders a nested Step under (§7, §8).
	Path string
	// Records is the size of the identity set — what the Step concluded
	// about, and never the versions it wrote (ADR-0030) — and Concluded says
	// a set exists at all. Three of §12's seven Dispositions conclude about
	// nothing, and the dash that renders for them is what tells them from
	// the *ran* Step whose set is written empty (§8).
	Records   int
	Concluded bool
	// Expanded is how many Record identities the Step's calls **reached**,
	// and it is written **only where the Step stopped short of them** — a
	// `read` Expansion that drained and then halted (§6). It is what
	// `n of m` is read against: `n` is Records and `m` is this, and the
	// Records between them are the ones unaccounted for (§7, §8, issue
	// #140). A Step carrying no `over:` resolved no selector and makes its
	// call under a set of one, so a halted one is `0 of 1`.
	//
	// It counts Records and never the Expansion's members, which are the
	// same number only where an Operation projects one Record per member.
	// A `series` response whose tenth member's identity path did not resolve
	// is one member of an Expansion and ten Records reached, and the column
	// reads `9 of 10` — the entry says expanded to one beside it,
	// `expanded_to` being what the *selector* resolved to and this being
	// what the answers held (issue #144).
	//
	// Zero is *nothing stood short*, which is every Step that accounted for
	// everything it reached. It is written from the drain rather than
	// derived from Records, because *unaccounted for* is a fact about
	// Records that were never concluded about and the two counts are equal
	// for other reasons too — an identity two members resolve to is one
	// member of the set and two Records reached, which is a collision §6
	// halts on rather than a Step that stopped short (ADR-0070, ADR-0072).
	//
	// It is a count and never the names. Which Records are unaccounted for
	// is `expanded_to`'s and nowhere else, and a Step value carrying them
	// would be the second place a surface could read them from (§7, §8).
	Expanded int
	// Provenance is the Step's half: what the Step file carries, and what
	// that Step's `provenance` row renders (§7, ADR-0043).
	Provenance store.StepProvenance
}

Step is one Step of a Run as a surface reads it back.

Jump to

Keyboard shortcuts

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