Documentation
¶
Overview ¶
Package store is hyper's record: the orphan branch §7 states, and git as the subprocess that reads and writes it.
The Store is a branch of the repository the artefacts sit in, written by every environment that runs — the laptop and the runner alike (ADR-0006). Writing to it invokes no Operation, consumes no Capability and passes no two-key check: it sits beneath the layer Providers exist at, and it is not a Target.
What milestone 4 landed is the branch's creation (issue #126), the canonical encoding every file on it is written in (issue #127), the path grammar every file on it is named by (issue #128), the five shapes it holds (issue #129) — a Record version, run.json, a Step file, outcome.json and a closing write, each with a schema version of its own — the read half (issue #130): the sync that puts the branch in hand, the series the branch holds, the Head derived from a listing and the case fold a collision is decided under — the removal (issue #131): Compaction's predicate, the one commit it writes, and the push that re-applies an unpushed set of path operations onto a fetched tip — and the Journal reader (issue #132): the entries the branch holds, the classification that says how each one ended, the Disposition read from a file or from a silence, and the backward scan through the date partitions. The git layer they all go through is here already, unexported, and stays that way until a caller outside this package earns it.
Milestone 5 added the one piece of this package that is not the branch: the lock a Run holds on the Store for its duration, shared or exclusive, under `.git/hyper/` and never on the branch at all (issue #138, lock.go). It sits here because it is a lock on this, and because §7 puts hyper's own local state where git ignores it by construction (ADR-0075).
The Journal reader had no CLI consumer in milestone 4 by construction. It is what milestone 5's Run and milestone 8's renderings both stand on, and it is here because the Journal is milestone 4's; `hyper run` is now the first caller of the backward scan, of Concluded and of the Head derivation alike.
The shapes are the encoder's own case: §7 states rules no command reached while they were written, so they are verified at this package's own seam rather than through a command that did not exist yet.
Index ¶
- Constants
- Variables
- func Encode(v Value) []byte
- func IdentityDigest(names []string) string
- func InstantText(t time.Time) string
- func InteriorVersions(count int) string
- func Names(names []string) []string
- func NumberText(literal string) (string, bool)
- func RecordPath(id Identity, run RunID, step int) string
- func Sync(repoRoot string, now time.Time) error
- func Unframed(v Value) []byte
- type Account
- type Answer
- type Answered
- type Array
- type Bool
- type Cause
- type ClosedBy
- type Closer
- type Compaction
- type Disposition
- type Dispositions
- type Entry
- type Evidence
- type Executor
- type Form
- type HTTPAnswer
- type Identities
- type Identity
- type Initialised
- type JournalEntry
- type Kind
- type Listed
- type Lock
- type LockMode
- type Mapping
- type Metadata
- type Number
- type OpenEntry
- type Outcome
- type OutcomeFile
- type Pair
- type Path
- type Pattern
- type Provenance
- type RecordType
- type RecordVersion
- type RefusalMember
- type Removed
- type Retention
- type RunFile
- type RunID
- type RunProvenance
- type SchemaUnsupported
- type Selector
- type Series
- type ShellAnswer
- type Standing
- type StepCode
- type StepFile
- type StepProvenance
- type Store
- func (s *Store) Append(writes []Write, message string) error
- func (s *Store) Collisions(ids []Identity) (map[Identity]Identity, error)
- func (s *Store) Compact(retention Retention) (Compaction, error)
- func (s *Store) Contents(versions []Version) ([]RecordVersion, error)
- func (s *Store) Dispositions(entry Entry) (Dispositions, error)
- func (s *Store) Entries() ([]Entry, error)
- func (s *Store) Entry(run RunID) (Entry, bool, error)
- func (s *Store) Head(id Identity) (Version, bool, error)
- func (s *Store) Listing(wanted func(Entry) bool) ([]Listed, error)
- func (s *Store) OpenEntries() ([]OpenEntry, error)
- func (s *Store) Publish() error
- func (s *Store) Read(version Version) (RecordVersion, error)
- func (s *Store) Readable(pairs []Pair) (Unreadable, bool, error)
- func (s *Store) Records() ([]Series, error)
- func (s *Store) Rehearsals(runs []RunID) (map[RunID]bool, error)
- func (s *Store) Scan(id string) iter.Seq2[Evidence, error]
- func (s *Store) Series(id Identity) (Series, error)
- func (s *Store) Standing() (Standing, error)
- func (s *Store) SuppressedFields(versions []Version) ([][]string, error)
- type String
- type Timestamp
- type Trigger
- type Unreadable
- type Value
- type Version
- type Write
Constants ¶
const ( CommitName = "hyper" CommitEmail = "hyper@hyper.invalid" )
The identity every commit `hyper` writes carries, author and committer alike.
It is `hyper`'s own constant and is never read from the repository's git configuration, for two reasons that arrive from opposite ends (§7, issue #124): a runner whose checkout never set `user.email` would otherwise be unable to write the record at all, and *who ran something* is already the Journal's `trigger.actor` — a second, weaker copy of it on every commit would be a fact with two spellings and no reader.
The address is under `.invalid`, which RFC 2606 reserves for exactly this: an address that is well-formed, obviously not a mailbox, and could never be delivered to by accident.
const ( // RecordSchemaVersion is a Record version's, Tombstones included: a // Tombstone is an ordinary version of the series and not a shape of its // own (§7). RecordSchemaVersion = 1 // RunSchemaVersion is run.json's. RunSchemaVersion = 1 // StepSchemaVersion is a Step file's. StepSchemaVersion = 1 // OutcomeSchemaVersion is outcome.json's. OutcomeSchemaVersion = 1 // ClosedBySchemaVersion is a closed-by/ file's. ClosedBySchemaVersion = 1 )
The five schema versions, one per shape the Store holds — and five rather than one because a shape's version says that *that* shape moved (§7, ADR-0028). One integer across the Store would move a Record version's number when a Step file's shape moved, and an older binary would then Refuse a Record file it could read perfectly.
Each starts at 1 and each moves alone. They are five constants rather than one array or one map because five independent integers that could be indexed by a shape are five integers with a place to be moved together from.
`STORE.md` carries none, being prose written once.
const ( BranchName = "hyper-store" Ref = "refs/heads/" + BranchName )
The branch, and the ref it is. The name is fixed rather than chosen: there is no setting for it, no flag, and no file it could be configured from (ADR-0014). One repository has one Store, and finding it is knowing the name.
The two are spelled once and derived, because `git checkout hyper-store` is what §7 promises a reader and `refs/heads/hyper-store` is what every plumbing call names — and the two must not be able to come apart.
const Introduction = "# The `hyper` Store\n" +
"\n" +
"This branch is the record. Every other file on it is machine-written: `hyper`\n" +
"put it there, no file here was authored by hand, and none is meant to be.\n" +
"\n" +
"The branch is `hyper`'s account of the world, not part of it. Nothing on it is\n" +
"configuration, nothing on it is reviewed, and nothing on it changes what a Run\n" +
"does — it is what the Runs that have already happened left behind.\n" +
"\n" +
"Editing it by hand is editing evidence.\n"
Introduction is STORE.md, entire. It is written once, when the Store is created, and never again — a second `init` that rewrote it would be the one rewrite append-only forbids, arriving through the file that looks least dangerous to touch (§7, §12, ADR-0011).
§7 fixes its three claims and not its words: that every other file on the branch is machine-written, that the branch is `hyper`'s account of the world rather than part of it, and that editing it by hand is editing evidence. What it carries beyond them is nothing — no schema version, no timestamp, no repository-specific fact — so it is byte-identical in every repository that has ever run `hyper store init`, which is also what makes it a golden.
const IntroductionPath = "STORE.md"
IntroductionPath is where the branch introduces itself, and it is the one path in the whole Store that carries no Run id: every other path names the Run that wrote it, and this file is written by no Run (§12, ADR-0076).
It is a constant rather than a constructor because it is written once, by an act that is not a Run, and takes nothing to build.
const Location = "the record is the " + BranchName + " branch of this repository — never checked out, and it travels with a clone"
Location is where the record is, in one sentence, for the surfaces that render the record's *content* and would otherwise never say where it is held (§7, §9, ADR-0113, issue #233).
**It says three things and each of them was a wrong answer somebody gave.** The branch, because nothing an agent may call names it. *Never checked out*, because the Store sits nowhere locally (*Where the Store sits locally*, §7, ADR-0075) and a reader who goes looking finds a clean `git status`, no `.hyper/` and no `store/` — the search that returns empty and reads as *absent*. And *travels with a clone*, because that is the claim the sealed acceptance run got backwards in front of a human: an agent that had just run two Procedures reported that a clone would get the Procedure and not the history, which is false of every Store there has ever been.
It is spelled here rather than at either surface because both write it — `runs` and `records` on their page, and the tools carrying them in their text block — and a sentence maintained twice is a sentence that disagrees with itself the first time either is edited (internal/cli's runsPage and recordsPage, internal/mcp's answerText).
It is prose and carries no member on the wire. What it states is a constant of the design and never a result — the branch is fixed (BranchName above), so a row repeating it would be a row every consumer parses on every call to be told what §7 already fixed. The page is where a reader is, and the page is where it goes.
const RemoteName = "origin"
RemoteName is the remote the Store is looked for on and pushed to. It is `origin` and it is not configurable, for BranchName's own reason.
const SchemaUnsupportedCode = "store-schema-unsupported"
SchemaUnsupportedCode is the error_code a Run Refuses with where a file it must read was written in a shape above the one this binary knows (§12).
const SecretMarker = "<secret>"
SecretMarker stands where a value a Manifest declared secret would have gone: no digest, no length, no sibling list of what was suppressed, so no secret reaches the Store at all (§7, ADR-0007).
It is a constant, which is what keeps "a version is minted only where the bytes moved" honest: a rotated secret writes these same bytes and correctly mints nothing.
Variables ¶
var ErrAbsent = errors.New("the repository holds no " + BranchName + " branch; the Store is created by `hyper store init` and never by a Run")
ErrAbsent is the Store that is not there: neither in the clone nor, where a remote is configured, on it. It is the condition a caller renders as `store-absent` (§12); this package holds no Run and renders no Refusal.
The remedy it names is the only one there is. The branch is created by an explicit act and never by a Run, read-only Runs included, because a fetch that failed mid-flight and a branch that never existed look identical from the inside (§7).
var ErrContended = errors.New("another Run holds the " + BranchName + " lock in this repository; a Run holds it for its duration, and this one takes it or stops")
ErrContended is another Run holding the Store. It is a condition this package reports and never a code it maps: its caller is the one that knows a Run is `failed` at 75 here, and that this is neither a Refusal nor a failure of the work (§9, §12, ADR-0061).
var ErrNoRepository = errors.New("the repository root holds no git repository; the Store is an orphan branch of the repository the artefacts sit in")
ErrNoRepository is the repository root that holds no git repository. It is a sentinel rather than a message because it is the one fault here that is a usage error and not the world resisting: there is no branch to create and no repository to refuse on behalf of, so its caller exits 2 (§9, issue #124).
#124 said this answers "on resolveRepoRoot's existing message", and it does not, deliberately. That message is the *walk* finding no git root and it ends *pass --repo-dir or set HYPER_REPO_DIR* — which is the one remedy that cannot apply here, since the only way to reach this fault at all is to have named a root already: with neither global set, the walk resolves the git root or the command never gets this far. A message telling a caller to do the thing they just did is worse than the code it carries.
It names no path, which is the other half: it is written to stderr and compared byte for byte by the corpus, and a repository root is an absolute path that differs on every machine.
var ErrPushExhausted = errors.New("the push was rejected three times running; what was written stands locally and goes out with the next push")
ErrPushExhausted is the push that could not complete in three attempts: the remote moved under each of them, and what this clone holds is a branch ahead of the one on the remote.
It is a condition this package reports and never a code it maps. Where the caller is a Run this is `failed` at 75; where the caller is `compact` or `store init` — commands with no outcome triple to map onto — it is the world resisting at 1 (§9, §12, issue #131).
What it leaves behind is stated rather than hidden: every commit stands locally, re-applied onto the last tip that was fetched, and goes out with the next push that gets through. Nothing is unwound.
var ErrUnreadable = errors.New("a file under this Journal entry would not decode")
ErrUnreadable is a file under a Journal entry that would not decode: written in a shape above this binary's ceiling, or holding bytes no decoder here accepts (ADR-0028).
It is named so that a caller can tell it from the other way a read of the Journal stops. A file that will not decode is a **file** this binary cannot read, and §6 puts a gate one place in a Run's order whose whole job is to report exactly that over the Journal whole (Readable). A path that disagrees with the file standing at it is something else — a directory `hyper` did not write — and no gate reports it, so nothing may treat it as tolerable.
Functions ¶
func Encode ¶
Encode writes a value in §7's canonical encoding: UTF-8, LF endings, a trailing LF, two-space indent, keys sorted by Unicode code point, and no trailing whitespace on any line.
The encoding is a property of a value and a file is the case where the value is the whole file (ADR-0079). A value encoded on its own is encoded exactly as it would be were it that file's whole content — an array alone opens at no indent and writes its elements two spaces in — which is what makes the identity digest computable at all.
This is not §8's row stream. That wire is compact, keyed in the renderer's order and hashed by nobody; it lives in internal/render and neither encoding builds the other. A single **value** does cross, through Unframed below, and that is the whole of the traffic between them.
func IdentityDigest ¶
IdentityDigest is the digest of a set of names: sha256: over the canonical encoding of the sorted array, trailing LF included — the array as it would be written alone, at no indent, and never as it sits inside a Journal entry where members carries four spaces of it (§7, ADR-0079).
Sorting is by Unicode code point, the same rule the encoding already uses for keys and §6 uses for an Expansion, which makes the digest a fact about the set rather than about the order a response happened to arrive in.
A name repeated is one name: the argument carries a set, Go having no type that says so, and a duplicate that reached the digest would give one set two digests — which is a spurious version minted on the next Run, the exact failure the digest exists to prevent.
func InstantText ¶
InstantText writes an instant in the one timestamp form the Store holds: RFC 3339, UTC, Z mandatory, milliseconds always to three digits.
It is exported because a surface that *reports* a Store instant writes the same string the Store holds — `compact`'s `written_at` names a version by when it was written, and a second formatting of one instant is two spellings of a fact §12 fixes at one (issue #131).
The fraction is cut rather than rounded, which is what Format does and what the fixed width wants: a rounded 23:59:59.9999 would name the next day, and a timestamp in the Store is when something happened.
func InteriorVersions ¶
InteriorVersions is what a count of removed versions is called, in the words the predicate is stated in: they are interior, they are Observations, and they are versions rather than Records.
It is exported because the commit message and the command's own line count one thing and must count it in one phrase — the message being a rendering with a reader, and the line being the same fact on the other surface (§7, §9).
func Names ¶
Names is a set of identities as this package holds one: sorted by Unicode code point, and a name repeated kept once.
It is exported because the count is read where the set is built and cannot be read back off what is written: an identity set whose digest did not move carries no members at all, and §8's `RECORDS` column is the size of the set rather than of what the entry happened to write (§7, §8, ADR-0030). A caller that counted its own list before handing it over would be counting a different set the day two members of an Expansion resolved to one name.
The empty set is `[]` and never nil: nil is the absence one key over, and the two are one value everywhere else in this package and two here.
func NumberText ¶
NumberText is the text a decimal renders as, and it is the rule above read from outside: an integer exactly and at whatever width it takes, every other number as ECMAScript's Number::toString would write it. ok is false where literal is neither.
It is exported because §12 states the text form of the `number` scalar type as §7's canonical-JSON rule *read out there rather than a second rule minted beside it* — so the reading a value gets at a declared position (internal/schema, ADR-0081) is this function rather than a second implementation that agrees until it does not. It admits a leading zero, which JSON does not and an authored scalar may carry: `0755` reads as the integer 755 at a declared `integer` position (§3, ADR-0078), and what a leading zero must never do is survive into the text form.
func RecordPath ¶
RecordPath is where one version of a Record is written: records/<target>/<definition>/<name>/<run-id>-<nnnn>.json.
The file name carries both the Run and the Step because <nnnn> names a Record version as well as a Step file (§12): two Steps of one Run writing one identity write two paths rather than one path twice, which is what keeps the series' versions a listing rather than an overwrite (ADR-0011).
The identity it takes is the unencoded one. Encoding is this package's and happens here, at the one boundary where a Manifest-declared name — hostile input, and the reason the encoding exists at all — becomes a filename (§7).
func Sync ¶
Sync brings the Store branch down from the remote, and is what puts it in hand on a clone that lacks it — which is every runner.
What it decides is whether there is a Store to bring at all; how it is brought, and the depth that decision fixes forever, is bringBranch's (§7, ADR-0074). What comes down lands on the remote-tracking ref, and the local branch is pointed at it only where that loses nothing — see adopt, which is where a Run that wrote and could not push is kept whole.
A repository with no remote configured reaches no network at all and reads the branch it has. The absent remote is not a failure: a repository that has never had one is not a repository whose Store is missing. Neither is a remote that holds no branch while this clone does — the Store here stands, and publishing it is the next push's.
It answers ErrAbsent where neither side holds the branch, ErrNoRepository where repoRoot holds no git repository, and an ordinary error where the world resisted.
**What a failure costs is entirely the caller's**, and the two Runs spend it differently: an effectful Run is `failed` at 75, its sync being the push of its own open entry, and a read-only Run tolerates the failure outright and proceeds against whatever branch the clone holds (§7, ADR-0083). Which of the two this call is being made for is nothing it can see, which is the other reason it is a call of its own; internal/cli's locateStore is where the Kinds are already in hand to decide it.
func Unframed ¶
Unframed is a value handed to a surface that writes JSON of its own: the canonical bytes above with the file's trailing LF off, and nothing else changed. It is named for what it drops rather than for where it goes, and it is not Compaction — that is the act one file over, which removes versions from the branch.
It is the one door between the two encodings, and what it exists to stop is a second reading of a value rather than a second encoder. The two *shapes* are and remain different — §8's row is compact and keyed in the renderer's order, §7's file is indented and hashed — but a value is not a shape: a Refusal's `declared` is one number in the entry and the same number on the row, and a surface deriving its own would be a second answer to *what is this value*, which is how a row and the entry it was read from come to state different things (§7, §8, ADR-0026).
Every value that crosses today is a scalar and occupies one line. A container would cross as the canonical indented form, which §8's stream compacts on its way out — so no caller has to know which of the two it was handed.
Types ¶
type Account ¶
type Account int
Account is how a Journal entry ended: which of the four the files present under its directory make it.
It is a classification and never a member. There is no state key to leave stale and no growing file to rewrite, because closing has two *forms* rather than two writes to one path (§7, ADR-0011).
const ( // AccountOpen is an entry holding no account at all — neither an // outcome.json its own Run wrote nor a closing write another Run wrote // — and that absence is the whole representation. The Run may be in // flight or its process may be gone; hyper never guesses which, and // neither does this reader. AccountOpen Account = iota + 1 // AccountOwn is outcome.json alone: the Run gave an account of its own // end, and the entry's outcome is that file's. AccountOwn // AccountReaped is one or more closing writes and no outcome.json. The // Run really did not come back: the entry is failed, and the close // instant is the earliest inference among the closers. AccountReaped // AccountContested is both, and it is what a reap of a Run that was // alive after all leaves behind. The entry's outcome is the owner's // observation, the inference stays true of the Run that drew it, and // both files stand — hyper picks no side between two accounts of what // the world did, and holding both is what keeps this from being that // (§7, ADR-0076). AccountContested )
type Answer ¶
type Answer struct {
// contains filtered or unexported fields
}
Answer is an integer a call may not have given back: an HTTP status, a shell exit code. The zero value is *no answer arrived*, which is the case §7 states the key is absent for, and Arrived is the one door a value comes through — so a caller cannot mean 0 by leaving a field unset.
type Answered ¶
type Answered interface {
// contains filtered or unexported methods
}
Answered is what an effectful Step's call gave back where it did not give the ordinary answer. Its presence is the fact that something other than the ordinary answer decided this Step, and which of §6's three cases it was is read from the Disposition beside it.
It is effectful-only. A read's status is the answer, and the answer belongs in the Record wherever its Manifest projected it; a Journal copy would add only a claim that hyper thought a 503 was untoward, which on a read it does not (§6, ADR-0010).
The set is closed by the unexported method for the reason Value's is: two Capabilities, two member sets, and no third.
type Array ¶
type Array []Value
Array is a JSON array. It writes one element per line at the same indent, so a set that gains a member gains a line and a git diff of it names what moved rather than reporting that one long line changed (§7).
type Cause ¶
type Cause string
Cause is what caused a Run: §12's closed pair. A dispatched workflow run is CauseManual on the Actions executor, which is why the cause and the executor are two fields and not one.
type ClosedBy ¶
type ClosedBy struct {
// EndedAt is the closing Run's instant on the closing Run's clock,
// which is why a reaped entry renders no duration at all: subtracting
// the dead Run's started_at from it is a cross-entry subtraction (§7).
EndedAt time.Time
// Step is the Step the dead Run went quiet on: the one after the
// highest <nnnn> its entry holds. Which Step that is is not a guess —
// run.json names the Procedure and the repository revision to load it
// at — but the answer depends on that revision resolving.
Step int
// StepCode is the Step's id and its code facts where the dead Run's
// revision resolves them, and the zero value where it does not, which
// is every Run that recorded repo_dirty.
StepCode
}
ClosedBy is a closing write: one Run's account of another Run's entry, drawn from a silence. It carries what a reaper knows and omits what it cannot establish (§7, ADR-0076).
It carries no member naming its author — its path is that member — and never `started_at`: the reaper does not know when the Step began, and filling it would be hyper asserting something about a Run it did not perform, on the surface built to hold what happened.
It carries no `outcome` either. The entry's outcome is a question about the **entry**, which this file's existence answers in full; a Disposition is a fact about a **Step** that this file is the only carrier of.
func DecodeClosedBy ¶
DecodeClosedBy reads a closed-by/ file back to the value it was written from.
Its `disposition` is checked rather than read: the one value that can appear there is written by the encoder, so a file carrying another was written by something that is not hyper — and reading it would let a crashed Step arrive wearing a Disposition §6's rule has nowhere to land on.
func (ClosedBy) Encode ¶
Encode writes a closed-by/ file in §7's canonical encoding.
Its `disposition` is DispositionAttemptedOutcomeUnknown and no other value can appear there, so it is written by this encoder rather than taken from the caller: without it §6's rule has nowhere to land and the crashed Step reads as never reached, which re-runs an effect nobody vouched for. A field would be a way to write something else there.
func (ClosedBy) Reading ¶
Reading is what this closing write records about the Step the dead Run went quiet on, in the shape a Step file records one — which is what lets §8 read Dispositions generically across all seven values, and what makes *attempted, outcome unknown* evidence for run-once whichever of the two files carries it (§6, §7).
It is a reading and not a file, and the difference is in the value rather than in a comment: StartedAt is zero because the reaper does not know when the Step began, and the Provenance, the identity set and the rest are absent because it could not establish them. What that leaves would panic if it were encoded, a Step file carrying members this one has none of — so a reading cannot quietly become a file nobody wrote.
type Closer ¶
type Closer struct {
ClosedBy
// Run is the Run that wrote this file: the one making the claim.
Run RunID
}
Closer is one closing write as the reader answers it: another Run's inference about this entry, and the Run that drew it.
The Run comes from the file's name and not from a member — a closing write carries none naming its author, its path being that member (§7, ADR-0076) — which is why the two are one value here rather than a ClosedBy a caller has to carry an id beside.
type Compaction ¶
type Compaction struct {
// Removed is every version taken off the branch, in path order.
Removed []Removed
// Untouched is the number of series nothing was removed from.
Untouched int
}
Compaction is what one Compaction did: the versions it removed, and how many series it left alone.
Untouched counts every Record series the branch holds that lost nothing — an Asset series, a series of one version, a series whose every interior version is younger than the policy. It is reported beside the count because the two answer the question an operator actually asks: *what did this leave*.
type Disposition ¶
type Disposition string
Disposition is what became of one Step: §12's closed seven, each with the wire spelling the Store writes.
Six of the seven are borne by a Step file. DispositionNeverReached is read from the absence of one inside a closed entry — a forty-Step Procedure that halted at Step 3 would otherwise write thirty-seven files saying that nothing happened — and it is here because §8 reads Dispositions generically across all seven values and a reaper's file writes one of them by name.
const ( // DispositionRan is a Step invoked and reaching a conclusion hyper // recorded. DispositionRan Disposition = "ran" // DispositionSkippedAsAlreadyRecorded is skip-if-recorded finding the // Asset still standing. The skip test concluded about the identity it // read, so the Step carries an identity set holding every member (§7, // ADR-0056). DispositionSkippedAsAlreadyRecorded Disposition = "skipped-as-already-recorded" // DispositionSkippedByCondition is a `when:` that did not hold. It ran no // test and reached no Target, so it says nothing about what the world // holds and carries no identity set, which is why it is not the value // above. DispositionSkippedByCondition Disposition = "skipped-by-condition" // DispositionRefused is a guardrail declining before any effect reached // the world. DispositionRefused Disposition = "refused" // DispositionNeverReached is the Run ending before the Step. It is read // from a silence and written by no Step file. DispositionNeverReached Disposition = "never-reached" // DispositionAttemptedOutcomeUnknown is a call that went out with no // answer coming back. It attaches the uncertainty to the attempt rather // than to the thing, and it is the one value a ClosedBy file carries. DispositionAttemptedOutcomeUnknown Disposition = "attempted-outcome-unknown" // DispositionAttemptedWorldUntouched is a request that provably never // left. Effectful-only, and only where no call this Step made reached // the world (ADR-0062). DispositionAttemptedWorldUntouched Disposition = "attempted-world-untouched" )
type Dispositions ¶
type Dispositions struct {
// Entry is the entry these records were read from.
Entry Entry
// Steps is what the entry recorded about its Steps, in the Run's own
// written order — the Step files it holds, and, where a reaper closed
// it, the reading its earliest closing write carries beside them.
//
// A record is not always a file: a reaped entry's account of the Step
// the dead Run went quiet on is a closing write, and it is here in the
// shape a Step file records one because §8 reads Dispositions
// generically across all seven values (§7, ClosedBy.Reading).
//
// There is **one record per Step**, and where a contested entry holds
// two accounts of one Step the record is the owner's: an outcome.json
// and the Step files beside it are the Run's own observations, and a
// closing write is another Run's inference drawn from a silence. The
// inference is not removed — it stands in the entry's Closers, where
// §7 puts it and where nothing here touches it — it is simply not a
// second account of what became of one Step.
Steps []StepFile
}
Dispositions is what became of the Steps of one entry: the records the entry holds, and the entry itself.
The two are one value because the seventh Disposition is read from an absence, and an absence means one thing inside a closed entry and something else inside an open one. A slice of Step files alone cannot answer *what became of this Step*, and a caller holding the two apart is one join away from answering it wrong.
func (Dispositions) Of ¶
func (d Dispositions) Of(id string) (Disposition, bool)
Of answers what became of the Step authored under id, and whether the entry says anything about it at all.
It reads the Disposition from the Step's own record where the entry holds one, and answers *never reached* where the entry is **closed** and holds none — the seventh value, borne by no file, which is what keeps a forty-Step Procedure that halted at Step 3 from writing thirty-seven files saying that nothing happened.
It answers **nothing at all** for a Step absent from an **open** entry. There the absence means something different — the Step may be running, or the Run's process may be gone — and guessing between them is exactly what §7 forbids.
type Entry ¶
type Entry struct {
// RunFile is what the entry's own run.json holds, `dry_run` among it —
// exposed on every entry so that each of the four consumers of Journal
// evidence can filter rehearsals out, and filtered here on none of
// their behalves (§7, ADR-0001).
RunFile
// Owner is the account the entry's own Run gave of its end, and the
// zero value where it gave none. An outcome.json is written by the Run
// whose entry it is and by no other.
Owner OutcomeFile
// Closers is every closing write the entry holds, earliest inference
// first. All of them stand and none is discarded, however many landed —
// and the ordering is the rule rather than a second lookup, the account
// of a reaped entry being drawn from the earliest (§7).
Closers []Closer
}
Entry is one Journal entry as a listing answers it: what its own run.json says, the account its own Run gave where it gave one, and every inference another Run drew about it.
It carries no Step file. Reading an entry whole is this and Dispositions together — a listing of a year of Runs holds one of these each and opens the Step files of the entry a caller went on to ask about, which is the same split Version and RecordVersion are two shapes for (§7).
It carries no account *field* either: how the entry ended is Account's, and deriving it from the files present is what leaves nothing to go stale.
func (Entry) Account ¶
Account answers how this entry ended, from the files present under it and from nothing else.
func (Entry) Duration ¶
Duration answers how long the Run took, and whether one derives at all.
It derives inside one entry or not at all. Every file stamps the instant it was written and no duration is stored anywhere, so this is a subtraction — and on a **reaped** entry the two instants come from two clocks, the closing Run's and the dead Run's, which is the cross-entry subtraction §7 forbids wearing one entry's directory. **The entry's account being a closing write is what says so**, and there is no second flag: the fact and the flag arrive together here, which is what keeps them from coming apart in each rendering that would otherwise rediscover it.
A contested entry derives one normally. There the account is the owner's, written on the owner's clock inside the owner's entry, and the closing write beside it is not an endpoint of anything.
func (Entry) Ended ¶
Ended answers when the entry closed, and whether it closed at all.
On a reaped entry it is the **earliest** `ended_at` among the closers — the first inference, later ones adding nothing but their own existence — and that instant is on the closing Run's clock, which is why Duration answers nothing there (§7).
func (Entry) Outcome ¶
Outcome answers the entry's outcome, and whether the entry has one at all.
**It is the owner's wherever one exists** — on a contested entry included: an outcome.json is its own Run's observation and a closing write is another Run's inference drawn from a silence, and where the two disagree the observation is what happened (§7). A reaped entry is `failed`. An open entry has none, and nothing here infers one.
type Evidence ¶
type Evidence struct {
// Entry is the entry the record sits in.
Entry Entry
// Step is what that entry recorded about the Step — its own file, or
// the reading a closing write carries where that is the only record
// there is.
Step StepFile
}
Evidence is what one Run's entry holds about one Step, as the backward scan answers it: the record of the Step, and the entry it sits in.
The word is §6's own — *run-once refuses on evidence rather than on suspicion, and the evidence is what the Journal holds for that Step* — and it is what both of the scan's consumers are asking the Journal for.
The entry travels with the record because both of them need it. Whether a rehearsal counts is the consumer's and is a fact about the Run rather than about the Step, and the identity digest's comparand is a Run rather than a file (§6, §7, ADR-0001, ADR-0055).
func (Evidence) Comparable ¶
Comparable answers whether this evidence stands as a record of the Step authored under path in procedure — which is the filter **both** readings of a Step's identity set apply, and the reason it is stated here rather than at either of them (§6, §7, ADR-0001, ADR-0055).
The walk itself filters nothing: Scan matches on the authored id and on nothing else, and states that which entries a reading keeps is its own. What the two readings keep is the same three facts, and a difference between them would be a Run writing a digest against one set and a reader resolving it against another.
**A rehearsal is out.** An entry a dry-run wrote is evidence that a rehearsal happened and evidence of nothing else, and every consumer of Journal evidence filters it out (§7, ADR-0001).
**So is another Procedure's entry.** An authored id is unique inside one Procedure and says nothing across two, so a `status` Step in `watch-status` and a `status` Step in `watch-many` are two Steps that would otherwise share a digest — each reading the other's set as its own.
**And so is another invocation chain's.** One Run holds the Steps of every Procedure it invokes, so two nested Procedures may each declare a `status` and both be Steps of one Run — told apart by the `path` their files carry beside that id (§7).
type Executor ¶
type Executor string
Executor is where a Run happened: §12's other closed pair. hyper fills it by reading the environment it finds itself in and branches on nothing it finds — recording which executor ran is not an authority axis, and behaving differently on one would be (§5).
type Form ¶
type Form int
Form is which of §12's six path forms a path is. The zero value is no form: a Path is answered by ParsePath or it is not answered at all.
const ( // FormIntroduction is STORE.md, the one path in the Store carrying no // Run id, because no Run writes it. FormIntroduction Form = iota + 1 // FormRecord is one version of a Record. FormRecord // FormRun is an entry's run.json, written at Run start. FormRun // FormStep is one Step of a Run reaching a Disposition. FormStep // FormOutcome is an entry's outcome.json, written by its own Run. FormOutcome // FormClosedBy is a closing write by a Run that does not own the entry. FormClosedBy )
type HTTPAnswer ¶
HTTPAnswer is the http Capability's: the host reached and the status it gave. Where no response arrived the status is absent, on the rule §3's response object carries (ADR-0050), so a Step that is *attempted, world untouched* writes the host alone. It says the request did not arrive and never which of ADR-0018's members stopped it.
type Identities ¶
type Identities struct {
// Digest is sha256: over the canonical encoding of the sorted array —
// the array as it would be written alone, at no indent, and never as it
// sits inside a Step file, where Members carries four spaces of it
// (ADR-0079).
Digest string
// Members is the sorted set in full, written whenever the digest moved
// and absent where it did not. Nil is that absence; the empty slice is
// a set that moved to empty and is written `[]`, which is one of the
// two exceptions to the absence rule and earns it — absence here
// already means *the digest did not move*, so a reader would otherwise
// decode *we looked and saw nothing* from recognising a constant.
Members []string
}
Identities is what a Step concluded a recorded conclusion about — what it projected from a response under read and mutate, and what it confirmed destroyed under destroy. It is not what the Step wrote and not what it saw: a Record that came back unchanged mints no file and is in the set, which is the case the whole mechanism exists for (ADR-0030).
func Concluded ¶
func Concluded(names []string, previous string) Identities
Concluded is the identity set a Step carries, given the names it concluded about and the digest the last Run in which that Step carried a set held.
previous is the empty string where there is no such Run: a Step's first, and a Step whose authored id moved, which is a different Step with no digest behind it and writes its set in full like any other first Run (ADR-0055). Finding that digest is the caller's — it is a backward walk over the Journal, and which Dispositions carry a set at all is §6's.
The names are a set, Go having no type that says so, and a name repeated is one name: a duplicate that reached the digest would give one set two digests, which is a spurious version minted on the next Run.
type Identity ¶
Identity is a Record's identity: its Target, its Definition and its name (§2). The three are one path segment each under the grammar, and they travel together because they are one fact — which series is this — rather than three arguments that happen to be adjacent.
It is the same identity IdentityDigest is taken over, at a different grain: the digest is over the names of an Expansion's members, which are one Record identity each under a Step's own Definition and Target (ADR-0070), and this is one whole identity, which is what a path needs and a digest does not.
func Folded ¶
Folded is an identity under the fold: the three components each folded, and still three, because joining them into one string would need a separator no component is guaranteed to be free of — and a joining rule that is not injective makes two identities one that nothing could ever tell apart (§7, and the `shell` projection's own argument in §12).
It is exported because the Store is not the only comparand: two members of one Expansion are compared against each other before either has ever been written, and that comparison is this one (§6, issue #139).
type Initialised ¶
type Initialised struct {
// Created says this call created the Store: it minted the parentless
// root and wrote STORE.md into it. It is false in both of the other two
// cases, because in both of them the Store already existed and no file
// was written — the branch was already here, or it was on the remote and
// came down from there.
Created bool
// Pushed says the branch went to the remote. It is only ever true of the
// root this call minted: a branch fetched from the remote is already
// there, and a branch that was already local is not looked at.
Pushed bool
}
Initialised is what Init did: whether it created the branch in this repository, and whether it pushed it to the remote.
The two are separate answers rather than one summary because they are the two facts the command's row carries, and either can stand without the other: a branch fetched from a remote that already held it is created here and pushed nowhere, and a repository with no remote configured creates one and pushes nothing.
func Init ¶
func Init(repoRoot string, now time.Time) (Initialised, error)
Init creates the Store, and does nothing else. No configuration is written, no example Definition is scaffolded, and no file in the working tree is touched — the branch is a parentless commit built from git objects and nothing about it is ever checked out, so it runs against a dirty tree like any read command (§9, ADR-0075).
It looks before it creates, and the order is the load-bearing rule (§7, ADR-0074): the local ref, then — where a remote is configured — `origin`'s. A branch already here is created again by nothing; a branch on `origin` and not here is fetched, the tip and no history, the ref named explicitly; and only where neither holds it is a parentless root built. Skipping that second look makes two clones each mint an orphan root, which produces two histories that can never fast-forward into one another and a second operator whose every push fails forever with nothing to diagnose it by.
Creating is not the whole of what it does, and the second half is the one an operator cannot perform any other way: **where a remote is configured and does not hold the branch, it is pushed there** — including where the branch was already local and this call created nothing. A runner's clone never holds the Store and fetches it from the remote (§7), so a Store that exists only on the laptop that ran `init` refuses every scheduled Run forever, and no command in §9's tree other than this one would ever send it. That case is reachable without anybody doing anything wrong: an `init` whose push was rejected leaves exactly that state, and if a second `init` were a no-op on finding the branch underfoot there would be no way back from it at all.
The cost is stated rather than hidden: where a remote is configured, this reaches it on every invocation, so a second `init` on a laptop with no network answers that the world resisted rather than that there is already a Store. That is the honest answer — the postcondition is a Store here *and* on the remote, and only one of the two could be checked.
It does not compare the two branches, only ask whether the remote has one. A local branch ahead of the remote's is a sync rather than a creation, and belongs to the Run that wrote the commits (§7).
now is the clock the caller threaded, and both of the commit's dates come from it: a fixture's branch is then reproducible, and `git log` on the Store is honest about when the record began.
It answers ErrNoRepository where repoRoot holds no git repository, which its caller reads as a usage error; every other error is the world resisting.
type JournalEntry ¶
JournalEntry is where one Run's entry sits: the Run's id, and the instant it started, which the date partition is the UTC date of.
The two travel together because the entry's directory is built from both, and a path form that took them apart would let a caller file a Run under a date that is not its own. It carries no account of how the Run ended: an entry's account is a classification over the files present under this directory, and classifying is milestone 4.6's (§7).
func (JournalEntry) ClosedByPath ¶
func (e JournalEntry) ClosedByPath(closer RunID) string
ClosedByPath is journal/<yyyy>/<mm>/<dd>/<run-id>/closed-by/<closer-run-id>.json, written by a Run closing an entry it does not own.
It names both Runs: the entry it speaks about, in the directory, and the Run speaking, in the file name. That is what makes an entry holding two accounts two files rather than one contested one — the reaper's inference and the owner's observation both stand, and hyper picks no side (§7, ADR-0076).
func (JournalEntry) OutcomePath ¶
func (e JournalEntry) OutcomePath() string
OutcomePath is journal/<yyyy>/<mm>/<dd>/<run-id>/outcome.json, written when the Run ends — by the Run itself, and by nobody else. A later Run that infers this one died writes ClosedByPath instead, which is what leaves no path in the Store two Runs can reach (ADR-0076).
func (JournalEntry) RunPath ¶
func (e JournalEntry) RunPath() string
RunPath is journal/<yyyy>/<mm>/<dd>/<run-id>/run.json, written at Run start.
func (JournalEntry) StepPath ¶
func (e JournalEntry) StepPath(step int) string
StepPath is journal/<yyyy>/<mm>/<dd>/<run-id>/steps/<nnnn>.json, one per Step reaching a Disposition. The number is the Step's position in the Run's written order — a nested Procedure's Steps counted in that order, the invocation itself being no Step and writing no file (§7, §12).
type Kind ¶
type Kind string
Kind is what an Operation does: §12's closed three, declared per Operation in a Manifest and never inferred from the Operation's name (ADR-0025).
const ( // KindRead observes and changes nothing. It writes Observations. KindRead Kind = "read" // KindMutate brings something into existence or changes something that // already stands. It writes Assets. KindMutate Kind = "mutate" // KindDestroy removes something. It writes Tombstones. KindDestroy Kind = "destroy" )
type Listed ¶
type Listed struct {
// Entry is everything the entry's own files say: its run.json, the
// account its own Run gave, and every inference another Run drew.
Entry
// Targets is what this Run bound, each once and in Unicode code-point
// order, and nothing at all where it bound none. It is a set read down
// a cell rather than a sequence of events, so it is not in the Run's
// written order.
Targets []string
}
Listed is one Journal entry as a listing of Runs answers it: the entry, and the Targets its Run bound.
The two travel together because one surface needs both and reading them apart costs a second walk of the branch. §9's `runs` row is the entry's own facts with the Targets beside them, and a Target is the one member of that row no run.json carries.
type Lock ¶
type Lock struct {
// contains filtered or unexported fields
}
Lock is the Store lock, held. It is released by Release and by the process ending, whichever comes first.
func Acquire ¶
Acquire takes the lock in the mode named, and answers ErrContended where another Run holds it in a mode that excludes this one.
**It is an advisory lock the kernel holds, not a file whose existence is the lock**, and the difference is the one property a Run cannot do without: a `hyper` killed outright — the second interrupt §6 states, an executor's grace period running out, a laptop shutting its lid — releases it on the spot. A lock whose existence were the lock would be one a crash left behind forever, and §6 states in as many words that there is no reaper, no daemon and no heartbeat to clear one. So `.git/hyper/lock` outlives the Run as an empty file and the **lock** outlives nothing: what a Run holds ends when the Run does, which is what its removal was for.
It never blocks. A Run that waited would be a Run whose Cadence silently became *whenever the other one finishes*, and §6 makes contention an outcome rather than a queue.
It answers ErrNoRepository where repoRoot holds no git repository — the same fault the rest of this package answers for the same cause, and the one here that is the invocation being wrong rather than another Run being alive.
func (*Lock) Release ¶
Release gives the lock up.
It is safe to call on a lock already released and on one that was never taken. Its caller is a `defer` written beside the Acquire it pairs with, and the day a path releases early and unwinds through that defer as well is the day a second release has to be nothing rather than a fault about a lock nobody holds.
It answers an error because releasing can fail and a caller that wants to know may ask. `hyper run` does not: a Run whose lock would not come off has already done everything it was going to do, and the process ending releases it whatever this answers.
type LockMode ¶
type LockMode int
LockMode is which of the two locks a Run takes. Both land together because which one a Run takes is one decision, read off the Kinds before any Step runs — and only the shared one is exercised while every Step this binary performs is a `read`.
const ( // at once: none of them changes the world, and what each writes is a // path only it can reach (ADR-0076). Shared LockMode = iota // Exclusive is the lock of a Run carrying any effectful Step. It // excludes every other Run, read-only ones included: an effectful Run // closes another Run's open entry (§6), and a read-only Run reading the // Journal underneath one would be reading a record mid-write. Exclusive )
type Mapping ¶
Mapping is a JSON object. Its keys are written in Unicode code point order rather than in any order a caller could arrange, so that two writers of one shape agree on the bytes without agreeing on anything else (§7).
type Metadata ¶
type Metadata struct {
// Identity is the series this version belongs to, unencoded and in
// full. It is restated here rather than read back out of the path
// because the path is lossy — the grammar truncates an over-long
// segment and suffixes a hash (§12) — and because the working tree must
// describe itself (ADR-0011).
Identity Identity
// RecordType says which of the two this is.
RecordType RecordType
// Run, Step and Operation are what wrote this version. On a Tombstone
// the Operation is the one that destroyed the Asset, which is the one
// place in the Store it and Fields describe different calls.
Run RunID
Step int
Operation string
// Path is the invocation chain where the Step that wrote this version
// was reached through a nested Procedure invocation, and empty on a
// top-level Step — the same member a Step file carries.
Path string
// WrittenAt is when this version was written, and on a Tombstone when
// destruction was confirmed. The Head is derived by ordering a series'
// versions on it, ties broken by the file name, so nothing in the Store
// points at the current version and two environments writing one series
// contend over nothing (§7, ADR-0011).
WrittenAt time.Time
// Provenance is the whole of it. A version file saying only *see Run
// abc* would be unreadable in a browser and in a diff, which is exactly
// where this field set is read.
Provenance Provenance
// Tombstone marks the destruction. A Tombstone is an ordinary version
// of the series carrying this marker, the previous Head's Fields copied
// forward, and the Operation, Run and Step every version carries
// anyway.
//
// A Tombstone opening a series carries no Fields at all — a `values:`
// member named nothing the Store held, so there is no previous Head to
// copy forward, and the absence means *hyper destroyed this and never
// observed what it was*. It is the one version whose Fields can be
// missing for no other reason, so the absence needs no marker beside it
// (ADR-0033).
Tombstone bool
}
Metadata is everything a version says about itself but its content: which series it belongs to, what wrote it, when, and whether it is the destruction.
The word is §7's own — *a Record version is one file, holding that version's projected content and its metadata together* — and the split here is that sentence's two halves made two values.
It is a value of its own because that is the grain the reader answers at. Ordering a series and naming a version need every member of this and no byte of the content, so a listing of a thousand versions holds a thousand of these and reads the content of the one a caller went on to ask for (§7, issue #130).
type Number ¶
type Number struct {
// contains filtered or unexported fields
}
Number is a JSON number, held as the literal text it was decoded from and re-emitted rather than round-tripped through a float where it need not be: an integer past a float64's exact range is a Record identity on plenty of upstreams, and one that moved under a re-encode would mint a version on every Run.
The zero Number is zero, so a Number that reached the encoder unset writes a number rather than nothing at all — otherwise the one way a total encoder could produce bytes that are not JSON.
func Int ¶
Int is a Number from an integer hyper counted itself — a schema version, a Step's position — where there is no literal to parse and no error to answer.
func ParseNumber ¶
ParseNumber reads a JSON number literal. It is the door a decoded value comes through, and the grammar it checks is JSON's own rather than Go's: strconv accepts `0x1p3`, `Inf` and `1_000`, none of which a JSON decoder ever handed anybody, and all of which would reach the Store as something else.
It answers an error where the literal is not a JSON number, and where it is one this package could only write down as something else: `1e400` overflows every float64, and saying so at the door is better than writing `+Inf` onto a branch that is hashed.
That second check is the float path's and is asked only of a literal that takes it. An integer of four hundred digits overflows a float64 too and is accepted, because an integer is written from its digits and never through a float at all — refusing it would be this package declining to write a number it can write exactly.
func (Number) Text ¶
Text is the number as the Store writes it. §7 says "the shortest decimal that round-trips", which is under-determined at the exponent threshold where `1e+06` is shorter than `1000000` and both round-trip. #124 resolves it: an integer is written as its decimal digits, exactly and at whatever width it takes, and every other number as ECMAScript's Number::toString would write it — the form the browser shows the reviewer §7 wrote the whole encoding for.
The two agree wherever both could apply: toString of 1e3 is `1000` and of 1.0 is `1`. So this is one convention with the exactness kept where a float64 would lose it, rather than two conventions meeting at a seam.
Above a float64's exact range they part, and one integer spelled two ways is then written two ways: `1e30` is written `1e+30` and the same magnitude in digits is written in digits. That is what holding a number as its literal costs, and it costs nothing on a Run — each form re-encodes to itself, so neither mints a version, and an upstream that changed which form it sends is an upstream whose bytes moved.
It is exported because a predicate compares a stored number against an authored operand, and the comparison reads this text: an Expansion is the one caller outside this package holding a Number it did not write (§6, §12, issue #139).
type OpenEntry ¶
type OpenEntry struct {
// RunFile is the entry's own run.json: the Procedure the dead Run was
// performing and the **repository** revision to load it at, which is
// what makes *which Step was it* derived rather than guessed. It is
// `repo_revision` and never `procedure_revision` — reconstructing the
// Step sequence means loading every Procedure the top-level one
// invokes, which a commit resolves and a blob id cannot (§7).
RunFile
// Last is the highest step ordinal the entry holds — the last Step that
// finished — and zero where the Run wrote no Step file at all, which is
// a Run that went quiet on Step 1.
//
// It is the highest ordinal **present** and never a count of the files.
// The two agree on every entry `hyper` writes, and any future change
// that wrote a Step file out of order, or wrote one before its Step
// concluded, breaks this arithmetic silently (§7, issue #147).
Last int
}
OpenEntry is one entry holding no account at all, and everything a reaper establishes about it before it loads a line of code.
It carries no account members and could not: an entry that held either is not open, and a value with a place to put one would be a value a caller could read a contest off before the contest existed (§7).
type Outcome ¶
type Outcome string
Outcome is one of §12's terminal triple: exactly one per Run, and no fourth value and no partial one.
const ( // OutcomeCompleted is every Step reaching a terminal Disposition with // none refused and none failed — a Run whose every Step skipped // included. OutcomeCompleted Outcome = "completed" // OutcomeRefused is a guardrail declining before any effect reached the // world, most often before any Step existed. OutcomeRefused Outcome = "refused" // OutcomeFailed is the world resisting, the Run being stopped, or the // Run losing the Store. OutcomeFailed Outcome = "failed" )
type OutcomeFile ¶
type OutcomeFile struct {
// Outcome is the triple's member this Run reached.
Outcome Outcome
// EndedAt is when the Run ended, on the Run's own clock.
EndedAt time.Time
// Refusal is the checks that declined, in the order check prints them,
// and is present exactly where Outcome is OutcomeRefused. What a
// terminal line names is the first member's ErrorCode, derived there
// and stored nowhere (§7, ADR-0061).
Refusal []RefusalMember
}
OutcomeFile is outcome.json: the account a Run gives of its own end.
It is written by the Run whose entry it is and by no other, so it carries no member naming its author — the <run-id> in its path is that member, and another Run's account of the entry is a ClosedBy file (§7, ADR-0076).
It carries no exit code and no duration. Both derive — the exit code from the outcome by §12's mapping, the duration from the instants the entry already holds — and a stored one is a second representation that can disagree.
func DecodeOutcomeFile ¶
func DecodeOutcomeFile(data []byte) (OutcomeFile, error)
DecodeOutcomeFile reads outcome.json back to the value it was written from.
It answers SchemaUnsupported where the file was written above this binary's ceiling, which the caller renders as a Refusal naming the path it read (§7, ADR-0028).
func (OutcomeFile) Encode ¶
func (f OutcomeFile) Encode() []byte
Encode writes outcome.json in §7's canonical encoding.
It declines to write a Refusal that is not one: `refusal` is an ordered array of at least one member and is present exactly where the outcome is refused (§7). Neither halves of that can arrive from the world — the outcome is hyper's own triple and the members are hyper's own checks — so a mismatch is hyper's arithmetic being wrong, which paths.go answers the same way.
func (OutcomeFile) Head ¶
func (f OutcomeFile) Head() string
Head is what §8's terminal line and outcome row name: the first member's error_code, and the empty string where the Run did not refuse.
It is derived here and stored nowhere. A stored head is a second representation of the array's first member and the two can disagree, which is the reason no exit code, no duration and no Head marker is stored either (§7).
type Pair ¶
type Pair struct{ Target, Definition string }
Pair is one (Definition, Target) pair a Procedure makes: the scope both the schema test here and §6's credential pass are quantified over.
It is §6's own noun. It is deliberately not called a binding: CONTEXT.md keeps that word off a Definition, and internal/run already spends it on something else — the artefacts one Step resolved to, which is a Manifest, an Operation and a Target declaration rather than two names.
It is a pair rather than a Step because a Procedure of ten Steps against one Definition and one Target makes one pair, and the walk is over what the Run reads rather than over what it does.
type Path ¶
type Path struct {
// Form is which of the six forms this is.
Form Form
// Run is the Run that wrote the path, on every form but the
// introduction, which no Run wrote — the invariant the whole grammar is
// arranged around (ADR-0076). On a closing write it is the Run speaking,
// never the entry's own.
Run RunID
// Entry is the Run whose Journal entry the path sits in: the same Run on
// the three a Run writes into its own entry, the dead Run on a closing
// write, and the zero id on the two forms that are not Journal entries.
//
// It is written even where it repeats Run, rather than left to a caller
// to derive from the form. *Who wrote this* and *whose entry is this*
// are two questions, they part on exactly one form, and a field a caller
// has to know when to read is a field that is read wrong on the form it
// was written for.
Entry RunID
// Step is the Step's position on the two forms carrying one — a Record
// version and a Step file — and zero on the rest, a Step's positions
// beginning at one.
Step int
// Partition and Dir are where a Journal path sits: the date partition,
// `journal/<yyyy>/<mm>/<dd>`, and the entry's own directory under it,
// with the Run id on the end. Both are empty on the two forms that are
// not Journal entries.
//
// They are the two axes the Journal is read along — a backward scan
// walks partitions and a read of one entry lists a directory — and they
// are answered here rather than cut out of the path by a caller: nothing
// outside this file takes a Store path apart, for the reason nothing
// outside it puts one together (§12).
Partition, Dir string
}
Path is what a Store path says about itself: which form it is, which Run wrote it, whose entry it sits in, and which Step it names where the form has one.
What it does not carry is an identity. A Record version's three segments are percent-encoded and an over-long one is truncated, so decoding them recovers a name only where none was cut — and a reader that decoded them anyway would have two answers to *which series is this*, one of them wrong on exactly the identities the encoding exists to survive. The answer is inside the file (§7).
func ParsePath ¶
ParsePath reads a Store path back to its shape, and refuses everything outside §12's six forms rather than guessing at it. The Store holds these paths and no others, so a path that is not one of them is not a path this package can answer about — and answering anyway is how a file nothing wrote acquires a meaning nothing gave it.
type Pattern ¶
Pattern is hyper's own account of the work, supplied by no Provider (ADR-0018): a retry's attempts, a paginated read's pages, a poll's iterations. It is what makes *attempted, outcome unknown* after five attempts a different fact on the page from the same Disposition after one.
It is written where a Pattern did more than the trivial single call and absent otherwise — except on DispositionAttemptedOutcomeUnknown, where it is written whenever a Pattern was declared at all. How many times hyper may have touched the world is the fact that Disposition exists to carry, and *one attempt* and *no retry declared* are the same silence everywhere else and must not be here. Which of the two rules applies is the writer's, and the zero value of each member is that member's absence.
type Provenance ¶
type Provenance struct {
Run RunProvenance
Step StepProvenance
}
Provenance is the whole of it, and only a Record version carries the whole. A version file saying only *see Run abc* would be unreadable in a browser and in a diff, which is exactly where this field set is read.
type RecordType ¶
type RecordType string
RecordType is what a Record version is a version of: an Observation hyper read, or an Asset hyper is accountable for having made (§2, ADR-0025).
const ( // RecordObservation is what a read Operation projects. RecordObservation RecordType = "observation" // RecordAsset is what a mutate Operation projects, and what a destroy // Operation Tombstones — a Tombstone's record_type is asset because // hyper's effect reached the thing. RecordAsset RecordType = "asset" )
type RecordVersion ¶
type RecordVersion struct {
Metadata
// Fields is the projected content, nested under its own key rather than
// sitting beside the metadata: a projected field's name is a Provider
// author's to choose, and flat would need a reserved list of metadata
// names for it to steer around — a list that cannot grow safely on a
// branch no rule may rewrite. Nested, the two namespaces are disjoint
// forever and there is no check to state or forget (§7, ADR-0011).
//
// A field a Manifest declares secret is Secret here, which writes the
// marker in the position the value would occupy (ADR-0007). The Store
// holds nothing else about it, so a decode answers that marker as the
// string it is — §7's own rule that a projected value reading the same
// is not a case hyper disambiguates.
//
// Nil and empty are one value: a version carrying no projected content
// at all, and the key is not written. A decode answers nil.
//
// Two versions reach that state and neither is a defect. A Tombstone
// opening the series it ends has no previous Head to copy forward, and
// the absence means *hyper destroyed this and never observed what it
// was* (§7, ADR-0033). An ordinary version reaches it where **every**
// path its Manifest projected resolved to nothing, which is the
// ordinary field absence §6 states applied to all of them at once: a
// `shell` command that could not be started at all answers `command`
// and nothing else, and the built-in Provider projects `exit_code`,
// `stdout` and `stderr` (§12, issue #142). The two are never confused
// for each other — `tombstone` is a written marker and not the
// absence — and what the second says is exactly what it looks like:
// hyper made the call and read nothing back off it.
Fields Mapping
}
RecordVersion is one version of one Record: that version's projected content and its metadata together, one artefact that is diffable and canonical at once (§7).
A version is written only where the bytes moved. An Operation returning what the head version already holds mints nothing, and the canonical encoding is what makes *the bytes moved* an exact test rather than an approximate one.
There are no binary Records, no streaming writes and no appending inside a version: a Record is the projection its Manifest declared, and a blob nobody reviews has no business on a branch whose whole point is that it can be read.
func DecodeRecordVersion ¶
func DecodeRecordVersion(data []byte) (RecordVersion, error)
DecodeRecordVersion reads a Record version back to the value it was written from, a Tombstone included — a Tombstone being an ordinary version of the series and not a shape of its own.
The one thing it does not read back is which fields were suppressed. A secret is written as a constant string and the Store holds nothing else about it, so a decode answers the marker as the string it is — which is §7's own rule that a projected value reading the same is not a case hyper disambiguates.
func (RecordVersion) Encode ¶
func (v RecordVersion) Encode() []byte
Encode writes a Record version in §7's canonical encoding.
type RefusalMember ¶
type RefusalMember struct {
// ErrorCode is the member of §12's closed set naming the check.
ErrorCode string
// File is what the check cites: a reviewed artefact on most codes, a
// generated workflow on projection-stale, a Store file on
// store-schema-unsupported. It is `file` and not `artefact` for those
// last two (§7).
File string
// Line is the 1-indexed line in File, and Field a path into it in §8's
// remediation notation. Either is absent where the check has none.
Line int
Field string
// Message is free text describing the fault.
Message string
// Step and StepID are the Step the check cites, where it cites one.
// Step is an artefact coordinate and never an execution fact: a Step it
// names may have no file in the entry at all.
Step int
StepID string
// Declared and Observed are the two values the check compared, where it
// compared two. Nothing is invented to fill either: a check that
// compared nothing writes neither.
Declared, Observed Value
}
RefusalMember is one check that declined a Run: what a check problem row carries — the error_code, the file, the line, the field and the message — plus what a Run adds, the Step it cites and the two values it compared.
A Refusal and a check problem are one shape because they are one thing arriving through two commands: what check reports offline is what stops a Run online. It is stated here in its own members rather than by embedding §12's problem row because the Store's member carries no column — that one rides on §8's wire and is read back out of no file — and a shape that dropped a member on the way in would not read back equal.
type Removed ¶
type Removed struct {
Metadata
// File is the Store path the version sat at, which is what names it to
// git and what a report of the removals is ordered by.
File string
}
Removed is one version a Compaction took off the branch: what the file said about itself, and where it sat.
It carries no ordinal, and the absence is the rule rather than an omission. A version's ordinal is its position in a series' ordering, and Compaction is precisely the thing that moves one — removing an interior version renumbers every version above it — so a value that carried one here would be handing a renderer the one number nothing may name a version by (ADR-0049). A removed version is named by its Run and its Step, which are the two segments of its file name and are stable forever.
type Retention ¶
type Retention struct {
// Declared is the artefact's own spelling of the policy, `90d`.
Declared string
// Age is what it names. A version is removable where it is older than
// this, so a version exactly this old stands: the policy is a length of
// time the repository agreed to keep, and the boundary is inside it.
Age time.Duration
}
Retention is the policy a Compaction acts under: what the Repository declaration says, and what that means as an age.
Both halves are carried because both are read. The age is what the predicate measures against the clock, and the declared text is what the commit message and the command's own line name — a policy rendered as `2160h0m0s` where the artefact says `90d` would send a reader looking for a line nobody wrote (§3, §8).
type RunFile ¶
type RunFile struct {
// Run is the Run's own id, restated here though the entry's path
// carries it: the working tree describes itself, and a file read out of
// a browser is read without its path in hand.
Run RunID
// Procedure is the top-level Procedure's name, which is also what
// Provenance's ProcedureRevision is the revision of.
Procedure string
// Trigger is what caused this Run and where it happened.
Trigger Trigger
// StartedAt is when the Run began, and the instant the entry's date
// partition is the UTC date of.
StartedAt time.Time
// DryRun says this Run was a rehearsal. It is written on every entry,
// false included, and is the one marker in the Store that does not
// follow the absence rule: four independent readers filter rehearsals
// out, and one that takes absence for false refuses every run-once Step
// in the Procedure it rehearsed, permanently, with nothing but an
// artefact edit left (§6, §7, §8, ADR-0001).
DryRun bool
// Provenance is the Run-wide half and never the whole: a Step file one
// directory over carries the Step's, and neither restates the other's.
Provenance RunProvenance
}
RunFile is run.json: written at Run start, before any Step has been reached.
It carries no account of how the Run ended. An entry's account is a classification over the files present under its directory — an outcome.json its own Run wrote, a ClosedBy file another Run wrote, or neither — and nothing in this file moves once it is written (§7, ADR-0011).
func DecodeRunFile ¶
DecodeRunFile reads run.json back to the value it was written from.
func (RunFile) At ¶
func (f RunFile) At() JournalEntry
At is where this Run's entry sits: the coordinate §12's grammar builds every path under the entry from, built from what the file itself says rather than from the path it was found at. Every reader of an entry holds the two to agreeing, so this is that agreement used rather than restated.
type RunID ¶
type RunID struct {
// contains filtered or unexported fields
}
RunID is a Run's id: a UUIDv7, lowercase and hyphenated (§12).
It holds its text unexported, on ParseRunID and MintRunID being its only two doors — the shape internal/store already uses for a Number, and for the same reason. A path is built from one of these without being handed an error to answer, so what makes a path well formed is checked once, where the id enters, rather than at every position that writes one down.
func MintRunID ¶
MintRunID mints a Run id at the instant it is handed: a UUIDv7 over that instant's millisecond, and random bits under it (RFC 9562).
The clock is threaded rather than read here, as every other clock in hyper is: the instant a Run started is the Run's, and the id and the Journal date partition are two renderings of it that must not be able to come apart.
Two mints at one instant are two ids — 74 random bits stand beneath the millisecond — which is what makes a Run id mintable by the laptop and the runner alike with nothing between them to agree with (ADR-0006). It answers no error: crypto/rand does not fail without the process failing with it, and a caller minting an id has nothing to do with one.
func ParseRunID ¶
ParseRunID reads a Run id, and refuses everything a UUIDv7 is not: an uppercase spelling, an unhyphenated one, a UUIDv4, a UUID carrying no variant bits. The Store is written by two environments and read by both, so an id that reached a path unchecked would be a directory nothing could ever find again by looking for the id it was told.
func ReadIdentitySet ¶
ReadIdentitySet answers the set a Step concluded about, from the entry in hand and as many earlier ones as it takes — and **which Run supplied the members**, which is the fact `hyper show` renders as *unchanged since* that Run (§9, issue #163).
scan yields Evidence newest first, beginning with the entry the set is being read off — the order a backward scan through the Journal's date partitions gives (§7). It yields whatever the scan reaches and this walk selects: a record is a candidate where its authored id is the one asked for and it carries an identity set at all, three of §12's seven Dispositions carrying none and a fourth writing no file. Which entries a reading keeps — a rehearsal, another Procedure's, another invocation chain's — is the caller's, that being a fact about the Run and not about the Step (§6, §7, ADR-0001, ADR-0055).
It walks Evidence rather than Step files because the supplying Run is the **entry's** and no Step file carries one: the id of the Run that wrote a Journal file is in its path (ADR-0076), so a walk over the files alone can answer the members and never where they came from. That is one widened return and not a second walk — the entry the walk stops at is already in hand when the members are.
The Run it answers is the entry the members were read off, which on an entry holding its own set is the entry in hand. A caller renders *unchanged since* by comparing it against the entry it asked about, so the two states are one comparison rather than a flag this walk has to be right about.
It reads no further than the entry holding the set, so a set read off a recent entry costs one file and one off an old one costs the entries between.
type RunProvenance ¶
type RunProvenance struct {
// HyperVersion is always a release string. The pin gate refuses any
// binary whose version differs from the repository's in either
// direction (§11), so there is no development form to write.
HyperVersion string
// ProcedureRevision is the git blob id of the **top-level** Procedure —
// the file run.json's `procedure` names. A Run spans nested Procedures
// as one Run, so that is the only reading with exactly one value, and
// it has one for every Run (ADR-0036, ADR-0048).
ProcedureRevision string
// RepoRevision is the commit at HEAD. It is what a reaper loads the
// Procedure sequence at, which a blob id could not do (§7).
RepoRevision string
// RepoDirty is true where any reviewed artefact the Run read differs
// from HEAD or is untracked — exactly the file set §8's catch-all row
// counts the moved lines of. It follows the ordinary absence rule
// rather than dry_run's exception: one renderer reads it, and reading
// it wrong costs a `git diff` that does not reproduce.
RepoDirty bool
}
RunProvenance is the Run-wide half: the members that have exactly one value across a Run, however many Definitions its Steps span. It is what makes a Run that wrote no Record still say which code performed it.
type SchemaUnsupported ¶
type SchemaUnsupported struct {
// Written is the version the file carries, and Known the highest this
// binary reads. Written is always above Known: at or below it, the file
// decodes.
Written, Known int
}
SchemaUnsupported is the condition a decode answers instead of a guess: the file says it was written in a shape this binary does not know, and reading it anyway is reading a shape nobody defined (ADR-0028).
It is a condition rather than a Refusal because the decoder is not where a Run declines. This package holds no Run, renders no terminal line and knows no path: the caller read the file, so the caller names it and the caller renders the row, carrying SchemaUnsupportedCode into it.
func (SchemaUnsupported) Error ¶
func (e SchemaUnsupported) Error() string
type Selector ¶
type Selector struct {
// Declared is the selector as authored, in whichever of §12's three
// `over:` forms it was written. It is nil on a Step carrying no
// selector, which resolved none and holds none.
Declared Value
// ExpandedTo is what the Expansion resolved to, in **Expansion order**
// and not sorted: on a serial destroy it is the only place the halt
// point is legible, and *which three of the five* is read off it by
// position (§6, ADR-0044).
//
// It is written whenever a selector exists, the empty list included —
// the other exception to the absence rule, for the reason Members is:
// an Expansion that resolved to nothing is not a Step with no selector.
// Nil and empty are therefore one value here, and a decode answers the
// empty list.
ExpandedTo []string
// Bound is what the Expansion was counted against, and zero where the
// Step declared none — a read Step carries no Bound, having nothing for
// one to guard (§4).
Bound int
}
Selector is the selector a Step resolved, held as authored beside what it resolved to, so that what a Step reached is readable back from the entry long after the Run without a checkout at the revision its Provenance names.
type Series ¶
Series is one Record's versions, in the order the ordering puts them: oldest first, so a version's Ordinal is its position in this list.
A series the Store does not hold is this with no versions rather than an error. *hyper has never recorded this* is the answer every first Run of every Step reads, and it is not a fault.
func (Series) Head ¶
Head is the current version of the Record, and whether the series holds one at all.
It is the last of the ordering and not a marker anybody wrote. A series whose Head carries Tombstone reads dead; a further version above it makes it read alive again, which is what makes destroy-then-recreate behave as §6 states under `skip-if-recorded` (§7). Nothing here editorialises beyond that: what the Head is and what it says about itself are the two facts, and reading them together is the caller's.
type ShellAnswer ¶
ShellAnswer is the shell Capability's: the command it ran and the code it exited with, the code absent where the command could not be started at all. Its threshold is 0 rather than 2xx, and it covers two of §6's three cases rather than all of them, there being no 404 for a command to answer with.
Command is written rather than left to the identity set beside it: a destroy projects nothing and declares no identity, so on the Kind where this key matters most there is no projected command anywhere in the entry — and it is what keeps the key from ever being written empty, a failed exec otherwise leaving an `answered: {}` the encoding suppresses outright.
type Standing ¶
type Standing struct {
// contains filtered or unexported fields
}
Standing is the branch's Record series folded onto their keys: the comparand itself, read once and asked afterwards.
It exists because the two callers of the fold ask at different moments. An Expansion resolves its identities before the Step's first call and asks Collisions above over all of them at once; a Step whose `identity:` reads from a response has no such list — the names arrive one call at a time — and what it needs is the branch **as it stood before its own first version went down**, asked per identity as the Expansion is walked. One value read once answers both, where an enumeration per identity would both cost a read of the branch per member and read a branch this Step is already writing to (§6, §7).
It is a snapshot and knows nothing of what is written after it. That is the whole of what it is for.
func Held ¶
Held is the comparand over a stated set of series: Standing's own constructor, exported because a comparand is a value and a caller that already knows which series stand has no branch to read. internal/run's tests are where that caller is today — a collision decided over two named identities is a fact about the fold and not about a Store.
The order it is handed is the order Collision answers in where more than one series folds onto one key — a state that takes a Store already holding a collision — so a caller that hands identity order gets identity order back.
func (Standing) Collision ¶
Collision answers the identity the branch already holds that is one with this one under the fold, and whether it holds any.
An identity the Store holds exactly is not a collision: that is the series itself, and a further version of it is an ordinary write. Where the branch holds both spellings the other one is still the answer, the two being one identity under the fold.
The zero Standing collides with nothing, which is the comparand a Step that never needed one carries: an Operation whose `identity:` resolves before the call has had both comparands run over it at the Expansion already (§6).
type StepCode ¶
type StepCode struct {
// ID is the Step's authored id — what a later Run matches this Step by
// when it looks for the last identity set it carried (ADR-0055).
ID string
// Definition, Operation, Provider, Target and Kind are what the Step
// was going to do. Provider is the Provider's **name** where a
// ManifestDigest is the Provider's **bytes**: the digest identifies
// what ran and answers nothing about what it was, and deriving the name
// instead costs git objects a shallow clone does not have.
Definition string
Operation string
Provider string
Target string
Kind Kind
}
StepCode is a Step's code facts: its authored id, and what that id resolved to at the Run's revision. They are one shape because two files carry them — a Step file, which holds them rather than resolving a Manifest at read time, and a ClosedBy file, whose reaper resolves them from the dead Run's revision or writes none of them at all (§7, §8).
type StepFile ¶
type StepFile struct {
// Step is the Step's position in the Run's written order, the first
// Step 1. A nested Procedure's Steps are counted in that order, the
// invocation itself being no Step and writing no file.
Step int
// Path is the invocation chain where this Step was reached through a
// nested Procedure invocation, `retire.probe`, and empty on a top-level
// Step.
Path string
// StepCode is what this Step was going to do, held rather than
// resolved.
StepCode
// Disposition is what became of the Step, and it is one of the six
// §12 values a file can bear. DispositionNeverReached is the seventh
// and is read from the absence of a file inside a closed entry, so it
// is the one value this member can never hold (§7).
Disposition Disposition
// StartedAt and EndedAt are the instants this Step began and ended. No
// duration is stored: it derives, and only within one entry, the laptop
// and the runner not sharing a clock.
StartedAt, EndedAt time.Time
// Provenance is the Step's half. This file sits beside run.json and
// reads the Run-wide members one file over, so it carries none of them.
Provenance StepProvenance
// Identities is what the Step concluded about, absent on the three
// Dispositions that conclude about nothing.
Identities Identities
// Selector is the selector as authored beside what it expanded to,
// absent where the Step declared none.
Selector Selector
// Pattern is hyper's own account of the work.
Pattern Pattern
// Answered is what an effectful call gave back where it did not give
// the ordinary answer, and nil otherwise.
Answered Answered
// ProjectionFailedPath is the path that failed to project on a Step
// halted by a projection that did not resolve (§6). The identity set
// beside it is then partial and this path is what says so — the digest
// says nothing about partiality either way. It is held here and nowhere
// else: a rendering goes to a terminal that scrolls, and no surface
// shows the response it failed against (ADR-0017).
ProjectionFailedPath string
}
StepFile is one Step of a Run reaching a Disposition: what the Step was, what became of it, and the three things a Disposition holds.
Its code facts are held rather than resolved from a Manifest at the Run's revision. A Journal whose Dispositions cannot be read without fetching three artefacts is evidence with a dependency, and the Kind in particular is the fact §8's third table exists to report as moving — read back from a Manifest it would be today's Kind wearing that Run's date.
func DecodeStepFile ¶
DecodeStepFile reads a Step file back to the value it was written from.
type StepProvenance ¶
type StepProvenance struct {
// DefinitionRevision is the git blob id of the Definition file:
// content-addressed, computable offline, unmoved by a rebase, and equal
// exactly where the content is.
DefinitionRevision string
// ManifestDigest is SHA-256 over the Manifest's exact bytes — the file
// in providers/ for an installed or locally authored Provider, the
// embedded bytes for a built-in. Over the bytes rather than a canonical
// form of what they parse to, so that a reader checks it with
// `sha256sum` and never by re-encoding a parse tree.
ManifestDigest string
// OriginDigest is the registry digest install verified. It is absent
// for a built-in Provider and for a locally authored one, neither
// having an upstream to have come from (§11, ADR-0073). It is not
// ManifestDigest under another name even where both are present: that
// one covers the file as it stands, this one the published bytes.
OriginDigest string
}
StepProvenance is the Step's half: the members a Step has exactly one value for, a Step naming one Definition, one Operation and one Provider. Nothing at Run level names a Definition, so a Procedure whose Steps span several has nothing to disambiguate.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a handle on the record as it stands: the repository the branch is a branch of, and the commit the handle was opened at.
Every answer it gives is read out of that commit's tree, freshly, with nothing cached between two of them and nothing derived kept anywhere. §7 permits local state under `.git/hyper/` that makes a Head lookup faster and states that no answer depends on one existing; this builds none, so *the files are authoritative* is what the code does rather than what it promises.
The commit is resolved once so that two answers about one Store are answers about one Store. A Run that syncs, reads a series and then reads another is reading the branch it found, not the branch a second environment pushed to in between.
func Open ¶
Open answers a handle on the Store the clone holds, and ErrAbsent where it holds none.
It reaches no network and never creates anything: putting the branch in hand is Sync's act and creating one is `hyper store init`'s. A caller that wants both syncs first and opens after, which is also the order in which their two failures mean different things.
now is the clock the caller threaded. It is the environment every git subprocess in this package is run with, so the commits a write makes through this handle take both their dates from it, and it is the instant a Compaction measures retention against — one clock, read once, for everything this handle goes on to answer (§7).
func (*Store) Append ¶
Append puts the files on the branch as **one commit** and pushes nothing.
Every path must be one the branch does not already hold. That is append-only arriving at the one call that could break it (§7, ADR-0011): every Store path carries the id of the Run that wrote it, so two Runs cannot write one path and one Run writing one path twice is hyper's own arithmetic being wrong — which this answers the way every other impossible path does, by refusing to produce the bytes rather than by handing a caller an error to decide about.
A write of no files writes no commit and leaves the branch byte-identical, which is the ordinary case rather than an edge: an Operation returning what the head version already holds mints nothing (§7), and a Step whose every Record came back unchanged is a Step with nothing to commit.
message is what `git log` on the branch says about the write. The branch is hyper's account of the world and its commits are part of the account a human reads there (§7, §13), so the caller names the act rather than this supplying a constant that would describe every write the same way.
The branch is moved with its old value named, which is update-ref's own guard: a branch that moved between the read and the write is a second writer, and overwriting it is the act append-only forbids arriving at the ref. The handle is then pointed at what it wrote, so a read taken through it afterwards answers about the branch that now stands — which is what lets a Step read the Head of a series an earlier Step of the same Run wrote (§6).
func (*Store) Collisions ¶
Collisions answers, for each identity handed, the identity the Store already holds that is one with it under the fold — and nothing at all for the members that collide with none.
An identity the Store holds *exactly* is not a collision: that is the series itself, and a further version of it is an ordinary write. What this reports is two identities that must be distinct being one under the fold, which is `record-identity-collision` (§12).
It is a set rather than one identity because §6 runs the check **once** over the identities an Expansion resolved rather than at each member's turn, and asking per member would be one enumeration of the branch per member — no index exists here or under `.git/hyper/` (§7), so the read is the cost. A caller that cannot name its identities up front asks Standing below for the same one enumeration and decides them one at a time against it.
The answer is keyed by the identity handed rather than ordered, the order the members are decided in being the caller's: an Expansion has one (§6, ADR-0044) and this has none. Where more than one stored identity folds onto one handed — which takes a Store that already holds a collision — the first in identity order is the answer, so two reads of one branch report one identity.
func (*Store) Compact ¶
func (s *Store) Compact(retention Retention) (Compaction, error)
Compact removes the interior Observation versions the policy permits, commits the removal, and publishes it.
**The predicate, stated once.** For every Observation series the branch holds, a version is removable exactly where it is not the Head, not the series' first version, and its `written_at` is older than the policy measured against the clock this handle was opened at. Everything else stands.
It is one commit, and the commit message is its account: `git log` on the branch is what says what a Compaction removed (§7, §13), and it survives a push re-application intact. No Journal entry is written — Compaction is not a Run — and a Compaction that finds nothing to remove writes no commit at all, so a repository whose every interior version is young reaches this and leaves the branch byte-identical.
The push follows every other write's path and its failures are the caller's to map: a push exhausted after three attempts answers ErrPushExhausted, which a command that is not a Run reports as the world resisting (§9).
func (*Store) Contents ¶
func (s *Store) Contents(versions []Version) ([]RecordVersion, error)
Contents answers the versions named, whole, in the order they were named, in one batch read.
It is Read's shape for many versions rather than one, and it exists for the reason SuppressedFields does: a caller reading the content of a hundred versions one at a time pays a subprocess a hundred times, where the door behind both of them reads the lot in one (readVersions). §8's Comparison is the caller — it reads the two endpoint versions of every eligible identity, so the count is a Run's identity set rather than one Record.
The versions are named by the files a listing found them at, so a caller cannot ask for one this Store never answered.
func (*Store) Dispositions ¶
func (s *Store) Dispositions(entry Entry) (Dispositions, error)
Dispositions answers what became of the Steps of one entry: every record it holds, in the Run's own written order.
The entry is the one a listing answered, so its directory is built from what its own run.json says rather than from a path a caller supplied.
func (*Store) Entries ¶
Entries answers every Journal entry the branch holds, newest first.
The order is each entry's own `started_at`, ties broken by the Run id, which is the Head's rule at the Journal's grain: the instant the file carries, and the name where two share one. It is not the listing's order and not the commits' — a year-old Run is a read of the tip like any other.
It opens every entry's run.json, outcome.json and closing writes, and no Step file. That is what makes a listing of a year of Runs one batch read rather than one per Step, and Dispositions the door to the rest.
func (*Store) Entry ¶
Entry answers one entry by the id of the Run whose entry it is, and whether the branch holds it.
It is the entry alone. Reading one *whole* is this and Dispositions together, for the reason Entries opens no Step file: the Step files are the bulk of a Journal, and a caller asking which Runs there were is not asking what each of their forty Steps did.
func (*Store) Head ¶
Head answers the current version of one Record, and whether the Store holds the series at all. It is Series with the ordering read off it, which is the query almost every caller makes and the one §6 asks per member of an Expansion.
func (*Store) Listing ¶
Listing answers every Journal entry `wanted` keeps, newest first, with the Targets each Run bound.
**It is a second door beside Entries rather than a member on one, because which door a caller needs is a cost.** A Target is a fact only a Step file carries, so this opens the Step files of the entries it answers where Entries opens none. §9 gives the Targets to one surface — `runs` — and every other reader of the Journal is served by the listing that does not pay for them.
**`wanted` is applied after the accounts are read and before the Step files are**, which is what makes the parameters §9 gives `runs` to narrow the time axis with actually cheap: a caller who named `--since` pays the Step files of the window and not of the Journal. A nil predicate keeps everything.
The predicate reaches the entry and never the Targets, and that is the line between what is cheap here and what is not — a caller filtering on a Target is asking a question only the Step files answer, and it applies that filter to what this hands back.
**What it does not do is cap.** A `--limit` cuts the rows a caller sees, and reading the Step files of the first N alone would hand back entries whose `Targets` are empty because nothing read them, indistinguishable from a Run that bound none — a set this listing does not hold, presented as though it did. So an unnarrowed listing of a long Journal pays that Journal's Step files, which is the honest cost of a column §9 puts on every row.
The read is one batch over every entry kept rather than one per entry, which is the trade accountsOf already makes: a year of Runs is a cost in bytes rather than in processes.
It is the **records** an entry holds and not its Step files alone, so a reaped entry's account of the Step the dead Run went quiet on binds the Target the reaper resolved — that reading being a record of that Step in the shape a Step file records one (§7, ADR-0076). A Step whose record names no Target — a closing write the dead Run's revision did not resolve — binds none, and the absence is what it is.
func (*Store) OpenEntries ¶
OpenEntries answers every entry on the branch holding no account at all, newest first.
**It answers every one of them and never a subset.** A rule that reaped some would need a criterion, and the only candidates are age and liveness — both of them the guess §6 declines. So there is no threshold here, no clock read against an entry's `started_at`, and nothing that asks whether a process is alive (§6, ADR-0076). This is where that rule is *enforced*, by there being nothing here to enforce it against.
It answers ErrUnreadable where a file it opened would not decode, and an ordinary error where the branch itself could not be read or where an entry does not sit at the path its own run.json builds. The two are told apart because only the first is a condition a Run's own gates go on to report (§6, entries.go).
The order is Entries' own at this grain: the instant each entry's own run.json carries, ties broken by the Run id. Every entry it answers is reaped, so the order decides nothing about **which** — what it decides is that two reads of one branch answer the same sequence, which is what keeps the closing writes of one reap a set a case can state.
func (*Store) Publish ¶
Publish sends the branch to the remote, re-applying onto a tip that moved and retrying, three times, after which it answers ErrPushExhausted (§7).
It is the same push every other write in this package goes out on, exported here because a Run decides *when* rather than *how*: a read-only Run batches its pushes to its end and an effectful one pushes after every Step, and both are this call at two rhythms (§6, ADR-0006).
A repository with no remote configured publishes nothing and answers no error. What was written stands locally either way, which is the sentence §7 makes true of every push that did not land.
**The handle is pointed at what the branch now holds, whichever way the push went.** A push the remote moved under re-applies this clone's unpushed commits onto the fetched tip, which moves the branch out from under the commit this handle was holding — and an effectful Run goes on writing after its pushes rather than stopping at one, so an Append that built on the old commit would be refused by the ref guard and lose the file it was writing (§7, ADR-0076, push.go).
The re-resolution's own failure is answered only where the push did not fail first: what a caller acts on is the push, and a second error about the ref it left behind would name the wrong cause.
func (*Store) Read ¶
func (s *Store) Read(version Version) (RecordVersion, error)
Read answers one version whole: its metadata and the content a listing does not carry.
It opens the file a second time rather than every listing holding every Record's content in memory, which is the trade the two shapes exist for. The version is named by the file a listing found it at, so a caller cannot ask for one this Store never answered.
func (*Store) Readable ¶
func (s *Store) Readable(pairs []Pair) (Unreadable, bool, error)
Readable holds every Store file this Run must read to the schema versions this binary knows, and answers the first it cannot read.
The order is the walk's and the walk's order is fixed: the Journal, whole and in path order, and then the Record series under each pair, the pairs sorted and each series in path order. §7 states that a Refusal's array is ordered and that the terminal line names its first member, and a walk whose order came off a map would name a different file on two runs of one command.
Every file is **decoded** rather than having its `schema_version` read off in passing. The decoders are where a shape's ceiling is stated, and a second reader of that number is a second place for it to be wrong.
Decoding brings the rest of a decode with it — a file that is not JSON, a key the shape does not have, bytes that do not re-encode to themselves — and that is a consequence rather than a second check this gate is making. Each of those already stops the Run at whatever read first meets it; what changes is that they now stop it at Run start, before a Step, which is where a Store this binary cannot read is cheapest to find out about (§7, ADR-0079).
None of them is a Refusal. `store-schema-unsupported` names a check that declined and the ceiling is that check; a file this binary cannot parse at all named no check and is answered as an error, which is `failed` and not `77` (§12, ADR-0061).
func (*Store) Records ¶
Records answers every Record series the branch holds, each with its versions ordered, sorted by identity.
It reads every Record file on the branch, which is what an enumeration over files costs where no index exists — and none does, here or under `.git/hyper/`: §7 permits derived state that makes this faster and states that no answer depends on one existing, so every answer is a fresh read (§7, issue #124).
The order is the identity's own — Target, then Definition, then name, by code point — and never the listing's. Escaping drags every escaped character to the left of every unreserved one, so a path order is an order over the encoding rather than over the names anybody wrote (§12, ADR-0044).
func (*Store) Rehearsals ¶
Rehearsals answers what kind of Run wrote the versions a caller is about to render: one member per Run named that the branch holds an entry for, carrying that entry's `dry_run` — the bare `false` included, the marker being written on every entry and never on rehearsals alone (§7).
**It takes the Runs it is asked about rather than answering the whole Journal.** `records` cuts its answer before it asks, so a listing of one Record opens one entry and never a year of them. It is the trade Listing already makes with its predicate: what a narrowing buys is files not opened, and the listing of the branch that finds them is one call either way. An entry whose *path* names a Run nobody asked about is not opened, and each one that is opened is still held to sitting where its own contents put it — so a file filed under the wrong Run is caught by the reader that asks for the Run it claims to be.
**It is a map and never a set, because absence has to mean the other thing.** A key that is missing says *the branch holds no entry for this Run*, which is a Store that has lost evidence rather than a Run that was not a rehearsal — and the one rule §7 states about this marker is that a reader taking its absence for `false` gets a permanent wrong answer. A set would spell those two the same way.
**It is a third door beside Entries and Listing, and it is here for their reason: which door a caller needs is a cost.** It opens one file per entry asked for — the run.json, and never the outcome.json, the closing writes or the Step files — so a Run whose *end* nobody can read still answers what kind of Run it was. That is narrower in what it opens and not laxer in what it admits: an entry it does open answers this reader's two rules or the read faults, exactly as every other reader of the Store's files does (§9, ADR-0114).
func (*Store) Scan ¶
Scan walks the Journal backward — newest entry first, across every date partition — and yields every Run in which the Step authored under id did something.
It is the second workload the whole layout exists to serve, and it is a scan: no index is kept here or under `.git/hyper/`, so this is one listing of the branch and then the files of each entry it visits, in order, until the caller stops. **Stopping is what makes it cheap** — a set read off a recent entry costs one entry's files and one off an old one costs the entries between — and both of its callers stop: run-once Repeatability at the first Run recording this Step as *ran* or *attempted, outcome unknown*, and the identity digest's comparand at the last Run in which the Step carried a set at all (§6, §7, ADR-0055).
Within one entry the records run backward too, the Run's own order reversed, so a walk that never leaves the newest entry still runs newest first.
It matches on the **authored id** and on nothing else. A Step whose id moved is a different Step with no Run behind it, and it writes its set in full on its first Run like any other (ADR-0055).
It filters no entry on any consumer's behalf. A rehearsal is reached like every other entry and reported with its `dry_run` marker, because which readings exclude one is each of the four consumers', not this walk's (§7, ADR-0001).
func (*Store) Series ¶
Series answers one Record's versions, ordered.
It lists the one directory the identity names and reads every file in it, which is what deriving a Head costs: a version's `written_at` sits inside the file. The identity is then read back out of each file rather than trusted from the directory it was found in — the encoding is lossy in the direction that matters, so a file's own account of which series it belongs to is the only one.
func (*Store) SuppressedFields ¶
SuppressedFields answers, for each version handed over, the names of the fields whose value is the secret marker — the presence-only fact `records` renders as a row's `secret_fields` (§9).
The answer is positional: one entry per version, in the order they were handed over, so a caller pairs it with the listing it already holds rather than looking a version up by a key it would have to build. A version that suppressed nothing answers nil, which is the absence a row omits its member on rather than an empty list it would state one against (§7).
It is a door of its own beside Read for the reason a Version carries no content: it hands back the names instead of the fields they were read out of, so a listing of a thousand rows never holds a thousand Records' content. Read is the door for one version whole; this is the door for one derivation over many.
**It opens every one of these files a second time, and that is the honest cost rather than an oversight.** The listing that found them read them all already and kept only the metadata (readListing), because `written_at` sits inside the file and ordering a series opens it anyway. Holding the content through that listing to spare this read would put every Record's `fields` in memory for every caller, which is the trade the Version/RecordVersion split exists to refuse — and the second read is **one** batch for the whole answer, so what it costs is bytes and never a subprocess per row (readVersions).
type Timestamp ¶
Timestamp is an instant, written as the one timestamp form the Store holds: RFC 3339, UTC, Z mandatory, milliseconds always to three digits. It is a Value of its own rather than a String a caller formatted, because the width being fixed is what makes lexicographic order over a timestamp chronological order — the fact the Head derivation rests on — and a caller formatting it itself is a caller that can format it differently.
type Trigger ¶
type Trigger struct {
// Cause and Executor are §12's two closed pairs, and both are always
// written.
Cause Cause
Executor Executor
// Actor is written on both executors — the Actions actor, or the
// operating system user.
Actor string
// Host is the machine, and is written on ExecutorLocal only. §8's
// header renders `igor@thinkpad` from it and Actor, so both forms come
// from stored facts with nothing invented at render.
Host string
// ExecutorRun, Attempt and JobURL are the occasion on Actions: the
// executor's own run id, its run attempt, and the URL of the job. They
// are what links an entry to the narration that produced it — without
// them a Run id and the job that emitted it are unrelatable.
//
// ExecutorRun is the executor's id and never a hyper RunID: it is a
// decimal counter GitHub minted, and typing it as one would be this
// package claiming a UUIDv7 where a foreign id stands.
ExecutorRun string
Attempt int
JobURL string
}
Trigger names what caused a Run, which executor it happened on, and which occasion on that executor.
It is a mapping rather than a string: four facts whose shape differs by executor do not pack into one without a grammar and a parser, and a job URL carries every separator such a packing would use.
func (Trigger) Text ¶
Text is the Trigger as one line: a clock or a person, which is what §7 says a Trigger names and the whole of what a surface reading one down a column renders of it.
A `cron` Run renders the cause, because there is nobody to name: the clock that fired is the executor's, and the actor an executor happens to have set on a scheduled occasion is not who caused the Run. Everything else renders the person, with the machine beside them where the entry carries one — `igor@thinkpad` on a laptop and `igor` on a runner.
It is a derivation over the entry's own stored facts and lives here beside Entry.Outcome and Entry.Ended for the same reason they do: §8's Comparison header and §9's `runs` column render one fact, and two compositions of it are two chances to disagree about who caused a Run. What a surface still decides is where the string goes — `show` reads one entry whole and renders the four members an executor writes, its job being the parts.
type Unreadable ¶
type Unreadable struct {
File string
SchemaUnsupported
}
Unreadable is one Store file the Run must read that this binary cannot: the path it sits at, and the condition its decode answered.
It carries the path because SchemaUnsupported does not: the decoders answer the condition and this package's readers name the file, which is the same split every other read here makes. §8 states that this is the one Refusal whose subject is **evidence rather than an artefact**, so the path is what the Refusal cites and there is no line and no field beneath it.
func (Unreadable) Error ¶
func (u Unreadable) Error() string
type Value ¶
type Value interface {
// contains filtered or unexported methods
}
A Value is a decoded JSON value: a mapping, an array, a string, a number or a boolean, and nothing else. There is no null — a field's presence is a fact stated by a predicate operator and never by a nullable value (§12), and a field a file does not carry is absent from it with nothing standing in its place (§7).
The set is closed by the unexported method: no type outside this package can be a Value, which is what makes Encode total. An encoder that could be handed something it did not understand would have to answer with an error on the one path where a caller has nothing useful to do with one — the bytes are already being written into a git blob by then.
func Always ¶
Always marks a value written even where the absence rule would drop the key holding it. Three keys reach for it and each is argued for on its own terms: members, whenever the identity digest moved and the empty list included, because absence there already means the digest did not move; expanded_to, whenever a selector exists and the empty list included, because an Expansion that resolved to nothing is not a Step with no selector; and dry_run, always and including false, because a reader that takes its absence for false permanently refuses every run-once Step in the Procedure it rehearsed.
§7 counts two exceptions and #124 counts three, and both are right: the absence rule is stated over an empty mapping and an empty list, which only the first two ever are. dry_run is a boolean the rule never reached, marked here with them because a shape saying which of its keys survive emptiness in one place is a shape that cannot be read two ways.
The encoder holds the rule and not the three names. Which key is an exception is a fact about the shape being written, and a list of names compiled in here would be a second place for a shape to disagree with itself.
func Secret ¶
Secret is the value SecretMarker is written in place of. It takes the secret and returns a value that has forgotten it — there is no field to read it back out of and no method that returns it — so a value routed through here cannot reach the bytes by some later path that only meant to be helpful.
Which fields are secret is a Manifest's fact and arrives here as an input rather than a derivation. The encoder holds the constant and nothing else.
type Version ¶
type Version struct {
Metadata
// File is the Store path this version sits at, which is what names it
// to git and what breaks a tie in the ordering.
File string
// Ordinal is this version's position in the ordering, from 1. It is
// derived here, stored nowhere, and unstable by construction: a version
// arriving beneath one already rendered moves every ordinal above it,
// and so does a Compaction. Nothing takes one as input — naming a
// version is naming its Run (ADR-0049).
Ordinal int
}
Version is one version of a series as a listing answers it: what the file says about itself, where it sits, and where it falls in the ordering.
It is not the file. RecordVersion is the file — the metadata and the content together, which is what an encode writes and a decode reads — and this is what the branch holds one of, at the grain the Head is derived at. Read is the door from one to the other.
It carries no `fields`. Ordering a series and naming a version need every member of the metadata and no byte of the content, so a listing of five hundred versions holds five hundred of these and Read opens the one a caller went on to ask for.
type Write ¶
Write is one file to put on the branch: the path §12's grammar built, and the bytes §7's canonical encoding produced.
The two travel together and neither is derived from the other. A path is lossy — an over-long identity segment is truncated and suffixed — so the file restates its own identity, and a writer that built one from the other would be the second representation the Store is written to avoid (§7, §12).