Documentation
¶
Overview ¶
Package capability is the two effects hyper performs on a Manifest's behalf, and it is the only package in the tool that touches a network or a child process (§5, issue #133). A Manifest declares the Capabilities it requires and a Target declaration grants them; what an Operation's request block says and what performing it does are one subject, and this is where both live.
Both halves are here. The `http` one is a Manifest's http: block read, its template holes filled from an Operation's resolved inputs, the request performed, and the response object §12 closes at five members assembled back (issue #133). The `shell` one is an argv exec'd directly and the object §12 closes at four assembled out of what the child did (issue #142) — shallower by a long way, and deliberately so: `hyper`'s own shell Provider is the only one that may declare that Capability (ADR-0039) and it knows nothing whatever about the command, so there is no request block to read and the words are the Step's.
It is the milestone's deep module in the one sense that matters: everything above it is handed an object with named members and never a socket or a process. Nothing outside this package performs a Capability — opens a connection, reads a status line, parses a body, looks at a certificate, or waits on a child an artefact named — and the projection above it (internal/projection) reads paths against the object rather than against the bytes that came back (§3, ADR-0040).
The git subprocesses internal/store runs are the exception that states the rule: their argv is compiled in and they are the record's transport rather than anything an artefact asked for, so they are not a Capability and do not come through here (§7, ADR-0006, cli.Process.Exec).
Neither performer is reached for. Dial and Exec are threaded from cli.Process, which is what lets a case exercise a real handshake against a server standing in the test process and a real child against a script a fixture checked in, with the name resolution the only thing a fixture supplies (issues #134, #142).
Index ¶
- Constants
- func CompactJSON(v any) ([]byte, error)
- func Deadline(ctx context.Context, seconds *int) (context.Context, context.CancelFunc)
- func Fill(position, template string, inputs map[string]schema.Scalar) (string, error)
- func NeverSent(err error) bool
- type Auth
- type BodyKind
- type BodyMember
- type BodyNode
- type Call
- type Command
- type Credential
- type Dial
- type Environment
- type Exec
- type Member
- type Object
- type Parameter
- type Request
Constants ¶
const ( // SchemeHeader is `header:` — parameters `name:` and `prefix:`, and one // slot, `token`. SchemeHeader = "header" // SchemeBasic is `basic:` — no parameters, and two slots, `username` // and `password`. SchemeBasic = "basic" )
The two schemes, spelled as a Manifest's `auth:` names them. They are constants here as well as in internal/artefact because the two packages ask different questions of the same closed set: that one refuses a Manifest naming neither, and this one fills the one it named.
const ( MemberHost = "host" MemberStatus = "status" MemberHeaders = "headers" MemberBody = "body" MemberTLS = "tls" )
The five members of the `http` response object, in §12's own order, which is the order they are assembled and rendered in. They are named here rather than spelled at each site because a projection path names them too — a Manifest writes $.tls.days_left — so a second spelling of one of them is a path that silently resolves to nothing. The `shell` object's four are named beside it, on the same rule (shell.go).
const ( MemberNotAfter = "not_after" MemberDaysLeft = "days_left" MemberSubject = "subject" MemberIssuer = "issuer" )
The four members of the tls member, in §12's order.
const ( MemberCommand = "command" MemberExitCode = "exit_code" MemberStdout = "stdout" MemberStderr = "stderr" )
The four members of the shell response object, in §12's own order, which is the order they are assembled and rendered in. They are named here for the reason the http object's five are: the built-in Provider's projection writes $.exit_code, so a second spelling of one of them is a path that silently resolves to nothing.
const ( CapabilityHTTP = "http" CapabilityShell = "shell" )
The two Capabilities a response object belongs to, named as §12 names them. A supplied object is read against the one the Operation's request block declares, and never against a Capability the caller chose.
const Scheme = "https"
Scheme is the one scheme hyper requests, and there is no second one (ADR-0082). §12 fixes tls as present where the scheme was HTTPS, and since this is the only scheme, tls is present on every response that arrived and absent only where none did. Nothing in the authoring format chooses it: a hosts: grant enumerates hosts and carries no scheme, so there is no plain HTTP host to grant and no artefact that could name one (§3).
Variables ¶
This section is empty.
Functions ¶
func CompactJSON ¶
CompactJSON is one value's JSON as hyper writes it anywhere a machine reads it: compact, and with HTML escaping **off** so that a value carrying an & or a < is one a consumer reads back as it was written (§8, internal/render).
It is exported because it is the encoding of everything this package assembles, and the packages above it write the same values on the same wire: internal/projection renders a projected value with it, and a Probe's page and its row stream carry both. encoding/json's encoder is the only door to that switch, and it ends every value with a newline, which a member of an object may not carry.
It is not the Store's encoding, which is indented and sorts a mapping's keys by code point because a git diff of it is read by a human (§7, ADR-0079). Nothing this writes is ever hashed or compared as bytes.
func Deadline ¶
Deadline bounds a call by the Operation's own `deadline:` and by nothing else. There is no whole-invocation deadline and no flag: the bound is the Manifest author's, declared beside the request it bounds (§3, §6).
seconds is what the Operation declared, and nil where hyper could not read one. A call bounded by nothing is what that answers, rather than a number substituted here: `deadline:` is mandatory and its absence is `schema-mismatch`, which is check's to report and never a performer's to paper over (§4, ADR-0064).
It lives here rather than at either caller because the deadline is a fact about the request: reaching one kills the call, and on a `mutate` or `destroy` that is the ambiguity *attempted, outcome unknown* exists to carry (§6). A Probe and a Run's Step both bound their call this way, and two spellings of one deadline is where the day comes that they differ.
It is named for the thing an artefact declared and never *Bound*: a Bound is the maximum number of Records an effectful Step may affect (§5), and a second use of that word for a length of time would put the tool's one blast-radius noun on a clock.
func Fill ¶
Fill renders one template against the resolved inputs: every hole replaced by the text form §12 fixes for the type its input declares, and the rest of the value left exactly as authored. position names the key in the error, so a Manifest with a hole nothing fills says which of its lines it was.
Every position but body: is text on the wire, so there is nothing to type into here and a composition and a whole hole render identically — which is the difference §3 draws at the one sink that has types (ADR-0078).
It is exported for the one hole outside a request that fills the same way: a `record:`'s `identity:` where it is a template rather than a response path resolves against the same inputs by the same rule (§3, §12). There is one hole syntax in every artefact, so there is one filler.
func NeverSent ¶
NeverSent reports whether err is a failure that provably preceded the request — the closed class ADR-0018 retries: a refused connection, a name that did not resolve, a handshake that failed, and, the same fact one Capability over, a child that could not be started at all.
It answers false for everything else, a status included: a status is an answer and never an error (ADR-0050), so nothing carrying one ever reaches here at all.
Types ¶
type Auth ¶
type Auth struct {
// Scheme is SchemeHeader, SchemeBasic, or "" where none was declared.
Scheme string
// Name is `header:`'s `name:` parameter, and Prefix its `prefix:`,
// which is optional and absent meaning empty. Both are empty under
// `basic:`, which takes no parameters at all.
Name, Prefix string
}
Auth is the scheme a Manifest names and the parameters it supplied: which header the credential lands in, and what goes in front of it.
Scheme is "" where the Manifest declares no `auth:` at all, which is a Provider that sends no credential — an uptime check against a public host — and is what `local` is. Absence is not a third member of the set: a scheme is a way of authenticating a request, and not authenticating one is not a way of doing it (§12).
func ReadAuth ¶
ReadAuth reads a Manifest's `auth:` block off its own root: the scheme it names, and the parameters it supplied.
It judges nothing and drops what it cannot read, which is the rule every reader in this package and in internal/artefact follows: an `auth:` naming neither of §12's two schemes, or naming a header `hyper` computes for itself, is `check`'s to report and never a performer's to guess at (§4, ADR-0064). A Run re-runs `check` in full before its first Step, so nothing that reaches here has gone unreviewed (§6).
func (Auth) Credential ¶
func (a Auth) Credential(slots map[string]string) Credential
Credential composes the header this scheme sends from the values its slots resolved to, and answers the empty Credential where the Manifest declared no scheme.
A slot the mapping does not hold composes into the empty string rather than declining, because declining here would be a second reading of a question §6's credential pass already answered: presence is resolved once, before Step 1, and every unfilled slot Refuses there — `credential-absent` where the environment does not hold the variable, `credential-empty` where it holds it and sets it to nothing. By the time a request is being built there is nothing left to find.
That second code is what keeps this composition honest. A slot resolving to the empty string used to reach here and compose into a header that was present and blank — `Bearer ` with nothing after it — which the endpoint answered `401` and hyper recorded as the world resisting. What had happened is that the invocation was never ready, and the gate says so now (§6, §9, ADR-0145).
func (Auth) Slots ¶
Slots is the credential slots this scheme requires, in §12's own order, and none where the Manifest declared no scheme.
It is the scheme's list and never the Target declaration's: a Target may carry more slots than any one Provider needs, which is what lets one declaration serve a `header:` Provider and a `basic:` Provider at once (§3).
type BodyMember ¶
BodyMember is one entry of a body mapping: its authored key and its value.
type BodyNode ¶
type BodyNode struct {
// Kind is which of the three this node is.
Kind BodyKind
// Members is a mapping's entries in the order they were authored. A key
// is always a literal string: a hole may not fill one (hole-illegal,
// §3, §12).
Members []BodyMember
// Items is a list's members, in order.
Items []BodyNode
// Scalar is a scalar's text, and Tag the YAML tag its spelling resolved
// to — which is what types a literal, YAML 1.2 core being what keeps the
// Norway problem out (§3, ADR-0078).
Scalar string
Tag string
}
BodyNode is one node of a body: tree — a mapping, a list, or a scalar (§3). It is a tree of its own rather than a yaml.Node because what a body position means is this package's: a literal carries its YAML 1.2 core type onto the wire and a hole carries its input's declared type, and neither is a fact the parse tree states (ADR-0078).
type Call ¶
type Call struct {
Host string
Method string
Path string
Query []Parameter
Headers []Parameter
// Body is the serialised body, and nil where the Operation declares
// none. Where it is non-nil hyper writes Content-Type and
// Content-Length for it and nobody else may.
Body []byte
}
Call is a request with every hole filled: what leaves, exactly. Host is the one host the candidate set and the grant intersected to, which is the value the Host header is derived from and the value the grant was checked against (§3, ADR-0029).
func (Call) Perform ¶
func (c Call) Perform(ctx context.Context, dial Dial, now time.Time, credential Credential) (Object, error)
Perform makes the call and assembles the response object §12 closes at five members. now is the instant the invocation fixed, and it is what tls.days_left counts from (ADR-0034).
credential is the Auth scheme's header, already composed, and the zero value where the Provider names no scheme. It is a parameter rather than a member of Call because a Call is a value a caller may hold, compare and describe, and a credential is none of those: it arrives at the one call that puts bytes on the wire and reaches nothing else (§7, ADR-0007).
The object is always usable: where no response arrived at all it is host and nothing else, which is the answer a read records rather than a failure it halts on (§6, §12, ADR-0050). That answer is about the far end, and it is check's job to see that nothing else arrives at it: a method: that is not a token used to fail in request below and be recorded here as a host that answered nothing, so a Manifest fault and a silent host wrote one Record — which is why the token is read offline (manifest-inconsistent, ADR-0156, issue #285).
The error beside it says what went wrong and is narration's alone — no member of the object says it, that being the catch-all bucket ADR-0017 closed, and a surface that wrote it into one would be minting a sixth member.
func (Call) URL ¶
URL is the address the call reaches, and it is where the one scheme is written. It is a method rather than a field so that no caller can hold a Call whose scheme says one thing and whose host says another.
Path is url.URL's Path and not its RawPath, so what a Manifest wrote is text and the percent-encoding is hyper's: a ? or a # in it is escaped into the path rather than opening a query or a fragment. An authored one is manifest-inconsistent and never reaches here (§3, §4, ADR-0107); one that arrives in a filled hole does, and is escaped like any other text, which is what leaves a path segment holding a literal ? reachable at all.
type Command ¶
type Command struct{ Argv []string }
Command is one shell Operation's request, filled: the argv as it will be exec'd, first word first.
It is the whole of the request. There is no method, no path and no body, because a `shell:` block carries no keys at all — the block's key *is* the Capability, and the words are the Step's rather than the Manifest's (§3).
func (Command) Perform ¶
func (c Command) Perform(ctx context.Context, start Exec, root string, environment Environment) (Object, error)
Perform execs the argv and assembles the response object §12 closes at four members. root is the repository root, which is the working directory every child runs in — fixed rather than authored, so that a laptop and a runner agree without a line saying so (§3).
**Nothing stands between the artefact and the process.** There is no shell here, so a pipe, a redirection, a glob and an `&&` reach the child as literal argv words and join §13's limits (ADR-0051).
The object is always usable: where the command could not be started at all — no such binary, not executable — it is `command` and nothing else, which is the no-answer case one Capability over and what a `read` records as the answer it is (§6, §12, ADR-0050). The error beside it says what went wrong and is narration's alone; no member of the object says it, that being the catch-all bucket ADR-0017 closed.
**A non-zero exit is an answer and never an error.** The code is recorded and nothing stands beside it, so a check script whose exit status *is* the finding is describable without a second declaration saying what success means (§6, ADR-0050). The one error this answers past a start that failed is the **deadline**, which is `hyper` stopping rather than the command answering.
func (Command) Text ¶
Text is §12's `command` member: the argv as run, JSON-encoded on one line.
JSON rather than a joining rule because it must be injective — `[echo, "a b"]` and `[echo, a, b]` are two commands and must be two identities, and a joining rule silently makes them one series that `record-identity-collision` could never catch, the two names being genuinely equal.
It is a fact about the call rather than about the answer, which is what lets a `read` record a command that never started: it is `host`'s member argument one Capability over, present because an Operation whose answer carries no identity of its own has nowhere else to project one from.
type Credential ¶
type Credential struct {
// contains filtered or unexported fields
}
Credential is one request's credential, composed: the header `hyper` writes, and what it writes into it.
Every member is unexported and there is no accessor, no String and no MarshalJSON. It is handed to Perform and reaches the wire, and there is no route by which it reaches a file, a row, a rendering or a log line — which is ADR-0007 held by the shape of the value rather than by every surface remembering. It is deliberately **not** a member of Call: a Call is what a caller may hold, compare and describe, and a credential is neither.
type Dial ¶
Dial is how a connection to a host is made: the read cli.Process threads, and the one this package is handed rather than reaches for.
It answers a connection that is already past its TLS handshake, which is what makes the scheme above a property of the process rather than of a switch: there is no plaintext path to configure because there is no plaintext dialer to supply. It is wired as http.Transport's DialTLSContext, so the certificate the peer presented is a real one off a real handshake and reaches the response object through http.Response.TLS.
type Environment ¶
type Environment struct {
// contains filtered or unexported fields
}
Environment is what a `shell` Operation's child inherits: the invoking environment with every variable the repository names removed — every credential slot, and every name in a Target declaration's `withhold:` list (§3, §11, ADR-0144).
It is a type of its own rather than a []string so that the removal cannot be skipped by a caller holding the process's own environment: what it carries is unexported, so the only way to fill one is Inherited below. That is ADR-0007's rule at this position — a credential is suppressed by the position it occupies rather than by every caller remembering to.
Its **zero value is not an empty environment**, and Perform will not start a child under one. os/exec reads a nil Env as *inherit the parent's*, and the parent's is the environment `hyper` was invoked in, credential slots included — so *nobody composed one* and *the composition came out empty* are two states this must be able to tell apart.
func Inherited ¶
func Inherited(environ []string, withheld []string) Environment
Inherited composes that environment: environ as the process holds it, less every variable in withheld.
The withheld set is **every** name any Target declaration in the repository carries — credential slot or `withhold:` entry — and not only those a Run resolved, so it is decided offline and does not turn on which Steps a Run reached (§11). `hyper` knows a credential's name by position (§3), which is the same knowledge that lets it suppress one rather than scan for one, used here to keep the credentials it resolved out of a process it cannot describe. A `withhold:` entry is the same removal for a variable `hyper` has no position for and never resolves — a secret the invocation's own wrapper left behind, which is a name an author knows and `hyper` cannot (ADR-0144).
Everything else is the command's, and `hyper` neither reads it nor records it; §13 states what that costs. What is *not* here is an authored `env:`, which would route a secret through an argument list — the working directory, stdin and the environment are fixed rather than authored, each because a key whose only legal content is the one `hyper` requires is a second spelling that can only ever disagree with the first (§3).
func (Environment) Composed ¶
func (e Environment) Composed() bool
Composed says this Environment was filled by Inherited rather than left at its zero value. It is what Perform reads before it starts anything.
type Exec ¶
Exec is how a child process is started: the read cli.Process threads, and the one this package is handed rather than reaches for — `http`'s Dial one Capability over.
It answers the child that argv names, ready to run, carrying the two launch decisions that belong to the process rather than to a Capability: the child starts in its own process group, and cancelling ctx kills that whole group with SIGKILL and no grace period (§5, §6). What this package sets on the answer is everything the Capability decides — the directory, the environment, the streams — and never a process attribute, which is what keeps the process group decided in one place.
type Member ¶
Member is one named member of an Object. Value is what the member holds: a string, an int, a nested Object, a mapping of header name to value, or — under body — whatever the response's JSON parsed to.
type Object ¶
type Object []Member
Object is a response object: named members in the order §12 states them.
It is ordered rather than a Go map for one reason, and it is the reason a response object exists at all: the object is the answer, and a surface renders it — the raw response beside the projection a Probe writes (§9, ADR-0017), and the `response` member of the probe_result row beside it. §12 states each Capability's members in an order — five for `http` and four for `shell` — and a rendering that sorted them or emitted them in whatever order a map iterated would be stating the tool's answer in an order nothing fixed.
A member the object does not carry is absent from it entirely rather than held as null — the ordinary absence rule (§7) — which is what makes *resolved to nothing* and *resolved to null* two answers a projection tells apart (§12, internal/projection).
func ReadObject ¶
ReadObject reads a supplied response object for one Capability, and answers the fault a caller is owed where it cannot.
Every member is optional but one. A response that carried no body, no headers and no certificate is an ordinary answer — a site that is down answers with a status and nothing else — and absence is a value a projection reads (§12). What the object may not do is carry a member no Capability has, or hold one at a shape the assembled object never holds: both are a path root that would resolve here and nowhere else.
func (Object) Lookup ¶
Lookup answers what one member holds, and false where the object does not carry it. It is the first hop of every projection path, and the distinction it draws is the one §12 turns on: absent is not null.
func (Object) MarshalJSON ¶
MarshalJSON writes the object compact and in its own member order: §8's wire encoding, which is the renderer's key order rather than the Store's code point order, and which nothing hashes or compares as bytes (§7, ADR-0079).
HTML escaping is off, on internal/render's own rule: what came back off the wire renders as it came back, an & and a < included.
type Parameter ¶
type Parameter struct{ Name, Value string }
Parameter is one query parameter or one header: its name, and its value.
The value is a template on a Request, where it may still carry holes, and it is what that template rendered to on a Call, where it may not. It is one field under one name rather than two types differing in a word, because a query parameter is a name and a value at both ends and the filling is Build's act rather than a change of subject.
type Request ¶
type Request struct {
// Method is the verb, and it is a literal: a hole here is one of the
// two §12 refuses outright, so what is read is what leaves (§3, §12,
// hole-illegal, ADR-0155). check holds it to RFC 9110's token as well,
// which is the reading net/http performs on it one layer down — so a
// Manifest that checks clean cannot reach request below with a verb the
// standard library will refuse (manifest-inconsistent, ADR-0156).
Method string
Host string
Path string
Query []Parameter
Headers []Parameter
// Body is the body: tree, and nil where the Operation declares none.
Body *BodyNode
// HostInput is the host-input: scalar naming the one input that carries
// a whole host where the candidate set and the grant intersect to
// several, and "" where the Operation declares none (§3, ADR-0029).
HostInput string
}
Request is one Operation's http: block, read (§3): the method, the host template, the path, and the optional query, headers and body. It is the declaration and not the call — every string here but Method may still carry template holes, and Build is what fills them.
Query and Headers are ordered rather than mappings because a request is bytes: §3 fixes a body's keys in the order they were authored, and a query string is the same fact one position over. Nothing downstream sorts them, so what a Manifest wrote is what leaves.
func ReadRequest ¶
ReadRequest reads an Operation's http: block off the node that Operation is declared by, and false where it declares no legible one — a shell Operation, or a Manifest check has already refused. Which node that is is internal/artefact's to answer (artefact.OperationNode): this package knows what an http: block means and not where one lives.
It judges nothing and drops what it cannot read, which is the rule every reader in internal/artefact follows: what is wrong with a Manifest is check's to report, and a reader that guessed would be a second opinion about an artefact nobody reviewed (ADR-0064).
func (Request) Build ¶
Build fills every hole from the Operation's resolved inputs and answers what leaves. host is the one host the intersection resolved to, supplied rather than read out of the template: the candidate set, the grant and their intersection are the caller's three steps and the grant is checked there (§3, ADR-0029, ADR-0042).
An error names the one hole that could not be filled. Every hole in a request names an Operation input and every declared input is supplied (ADR-0081), so a hole with nothing behind it is a Manifest check has already refused — and filling it with the empty string would put a request on the wire that no artefact describes.
Method is copied through rather than filled, and that is the whole of what its position means here: §12 refuses a hole in method: outright, so there is nothing to fill and no source that could have filled it. A Fill call on this line would be the other exit — the verb decided by a value from outside the artefact while the Kind stayed declared inside it (ADR-0155, issue #279).