Documentation
¶
Overview ¶
Package check implements six of the nine record-layer checks behind nova-check; the other three live elsewhere because none is about one self repo. dogfood is internal/dogfood, because its records are receipts about the family's tools; hygiene is internal/hygiene, because its subject is a branch's range and the accept gate and the merge lane run the same function over it; convergence is internal/converge, because its records are readings of the work itself rather than of a self repo. Each function returns (result, failures, error): failures mean the check ran and said NO (exit 1 at the CLI); a non-nil error means the check could not run at all (exit 2). See SPEC.md for what each check asserts and, as importantly, what it deliberately does not.
Index ¶
- Constants
- func Attest(home, manifest string) (Attestation, []Failure, error)
- func FloorDenyExts() ([]string, error)
- func FloorDenyNames() (names map[string]bool, prefixes []string, err error)
- func ParseDenyList(spec string) ([]string, error)
- func ParseLedger(raw []byte) ([]Anchor, []Failure, error)
- func ResolveRoot(root string) (abs, resolved string, err error)
- type Anchor
- type Attestation
- type BatchReply
- type BrokenLink
- type CatFileBatch
- type Failure
- func Corpus(root, ledgerPath string, minAnchors int, as []Anchor) ([]Failure, error)
- func Floors(core, source string) (floors int, failures []Failure, err error)
- func Kernel(file string, maxBytes int64) (measured int64, failures []Failure, err error)
- func KernelTokens(file string, maxTokens int64, bytesPerToken float64) (measured, tokens int64, failures []Failure, err error)
- func NoCode(opts NoCodeOptions) (scanned int, findings []Failure, err error)
- type LinksResult
- type NoCodeOptions
- type StagedRecord
Constants ¶
const ( DenyFloor = "floor-list" DenyReplaced = "--deny-ext" DenyExtended = "floor-list+--deny-ext-add" )
Deny-list provenance, reported with every finding so that neither a red nor a green hides the basis it was reached on.
These labels are read in two places: in a finding's REASON -- `code extension .py (floor-list)` -- and on the `deny-list=` and `source=` FIELDS of nova-check's summary lines. SPEC.md's rule is that a field is ONE token, so each label IS one token, with no space and no "=": the same spelling reads the same in both places, and the field needs no escape to be one (a label with a space printed as `floor\x20list`, which a scanner could count and a reader had to decode). The caller still renders them through oneline.Field, which leaves a token like these unchanged.
Variables ¶
This section is empty.
Functions ¶
func Attest ¶
func Attest(home, manifest string) (Attestation, []Failure, error)
Attest verifies that every file listed in the manifest exists under home, is a regular file, and is non-empty, and computes the boot attestation: file count, total bytes, and a SHA-256 binding paths, order, and contents. Any failure leaves the attestation zero-valued; a partial self must not produce a pasteable line.
func FloorDenyExts ¶ added in v0.5.0
FloorDenyExts returns the embedded floor deny-list, sorted.
It returns an error rather than an empty list if the data is unreadable or empty: a guard that cannot determine what it forbids must refuse, not pass.
func FloorDenyNames ¶ added in v0.6.0
FloorDenyNames returns the embedded floor name list as an exact-basename set and an ordered list of path prefixes.
It returns an error rather than empty results if the data is unreadable or empty, for the same reason FloorDenyExts does: a guard that cannot determine what it forbids must refuse, not pass.
func ParseDenyList ¶ added in v0.5.0
ParseDenyList reads a deny-list specification: either a comma-separated list of extensions, or "@path" naming a file with one extension per line.
func ParseLedger ¶ added in v0.7.0
ParseLedger reads the anchor rows out of a ledger's markdown.
THE TABLE DECLARES ITS OWN SHAPE; THIS DOES NOT GUESS AT ONE. That is the whole parsing rule, and it replaced two earlier attempts that each failed in one direction: requiring outer pipes silently dropped rows a renderer accepts, and then accepting any pipe-bearing line read ordinary prose and unrelated tables as anchors. Both are the same mistake — a heuristic about what a table looks like — so the heuristic is gone.
A run is a block of consecutive non-blank lines outside any fence, at least one of which bears pipes. A run is an ANCHOR TABLE only when its second line is a separator whose cell count matches its first line's, and that count is four. Everything else is somebody else's table — a two-column glossary, a prose line that happens to contain pipes — and is left alone, because a protection check that reddens on a glossary is one people learn to silence.
Header and separator are recognized by SHAPE and POSITION, never by the words in them: no column title is special to this tool, and a line may title its columns in its own language. A ledger may hold any number of tables.
What IS reported, because each would otherwise be a silent loss inside the ledger's own anchor table: a separator in the body, a row whose column count is not four, a table whose separator disagrees with its header, a pipe-led block with no separator at all (markdown will not render it as a table, so none of its rows would ever be checked), a lone four-column row adrift outside any table, and an unterminated fence.
AN EMPTY LEDGER IS AN ERROR, NEVER A GREEN. A ledger with no rows guards nothing, and "everything present" and "nothing checked" must never print the same line.
func ResolveRoot ¶ added in v0.7.0
ResolveRoot validates --root and resolves it once. Exported so a caller can refuse a bad root BEFORE printing any finding: a FAIL line emitted by a run that then exits 2 tells a scanner about findings from a run that did not happen. It returns the root twice: made absolute, and additionally symlink-resolved. BOTH are needed and mixing them is a real defect — every path this check builds must be joined onto the ABSOLUTE root, while the containment comparison happens against the RESOLVED one. Joining onto the caller's raw spelling and comparing against a resolved root made every anchor under a relative --root report as reached through a symlink.
Types ¶
type Anchor ¶ added in v0.7.0
type Anchor struct {
Fragment string
Home string // slash-separated, relative to --root
Given string
By string
Line int // 1-based line in the ledger, so a finding can be found
}
Anchor is one ledger row: a verbatim fragment, the file it lives in, and the provenance columns that make the ledger readable as prose.
type Attestation ¶
Attestation is the successful result of Attest: what a full boot read.
type BatchReply ¶
BatchReply is one cat-file --batch header line's worth of answer.
- Status: "missing" or "ambiguous" for the failure cases -- both are one-line answers with NO body and the reader must not steal a byte of the next record (SPEC.md 926-928).
- Status: the type, "blob", "tree", "commit", "tag" -- for normal answers.
Size is the byte length of the body, zero for missing/ambiguous.
type BrokenLink ¶
type BrokenLink struct {
File string // path of the containing .md file, relative to the scanned dir
Line int // 1-based line number; 0 when the whole file is the finding
Target string // the link target as written; empty when the whole file is the finding
Reason string
}
BrokenLink is one relative markdown link that does not resolve — or, when Line is 0 and Target is empty, one whole .md file that could not be read.
func Links ¶
func Links(dir string) (mdFiles, checked int, broken []BrokenLink, err error)
Links walks dir for .md files (skipping .git) and verifies that every relative inline link target resolves to an existing file or directory inside the tree. It returns the number of markdown files seen, the number of relative links checked, and every broken link found.
type CatFileBatch ¶
type CatFileBatch struct {
// contains filtered or unexported fields
}
CatFileBatch is one `git cat-file --batch` child held open. The caller feeds OIDs on b.in and reads frames off b.out; stdout and stderr are separated at the OS pipe level so an ambiguous reply's `error:` and `hint:` lines do NOT enter the framed reader -- they were measured to desync exactly the framing this card demands (SPEC.md 942).
func OpenCatFileBatch ¶
func OpenCatFileBatch(dir string) (*CatFileBatch, error)
OpenCatFileBatch starts one `git -C dir cat-file --batch` child under a bounded budget. The caller MUST call Close; the child is killed on Close and the half-held pipes are released.
Stderr is split from stdout with an os.Pipe so the framework's diagnostic lines can NEVER reach the framed reader. Without that split the reader desyncs on its very first ambiguous reply -- the SPEC measured the failure (SPEC.md 941-944).
The context's cancel is kept on the returned struct and called only at Close, NOT through a defer here. A deferred cancel fires on successful return -- which is when the long-lived batch must stay ticking -- and would otherwise kill git before the first ReadHead.
func (*CatFileBatch) Close ¶
func (b *CatFileBatch) Close() error
Close releases the child process and its pipes. Calling Close on a nil batch is a no-op so callers can chain a defer unconditionally.
The timer context is cancelled at the start of Close so a wedged child is killed before its Wait(); the half-held pipes are then released in order so the child cannot see the parent close a pipe before it sees its stdin gone.
func (*CatFileBatch) ReadHead ¶
func (b *CatFileBatch) ReadHead(oid string, n int) (BatchReply, error)
ReadHead asks the batch for `oid` and returns up to n bytes of body.
A missing or ambiguous reply has NO body and the reader leaves the pipe in step (SPEC.md 927). A normal reply returns the type and size in the header; the body is read up to n bytes, then the rest is dropped with io.CopyN(io.Discard, ...) so the stream is in step for the next call. A 1 MiB blob whose only role is to decide whether the first two bytes are a shebang stays exactly two-decoded bytes in memory; the rest is charged but not decoded.
The wall deadline is the batch's whole budget, so a wedged git cannot hold the reader open against a deadline it cannot see -- and the caller is told via error, not via a possibly-valid header line that would silently drop the framing.
type Failure ¶
Failure is one named NO from a check: the subject that failed and why.
func Corpus ¶ added in v0.7.0
Corpus verifies every anchor is present in its home file under root, and that the ledger still holds at least minAnchors rows.
It returns every failure rather than the first: the losses this exists to catch arrive in batches — a move, a restore, a consolidation pass — and a one-at-a-time report would take as many runs as there were losses.
err is reserved for the check being unable to run at all: a --root that is absent or is not a directory. That distinction is not pedantry here. A typo'd root would otherwise fire this tool's loudest alarm — "the words were lost in place" — once per anchor, for an invocation mistake, which is the fastest way to teach a caller to ignore the alarm.
func Floors ¶ added in v0.2.0
Floors asserts that the door (SEED-CORE.md) and the source (SEED.md) declare the same floor set — the one pinned in floorTable. floors is the registry size, for the OK line. Every divergence is a Failure (exit 1 at the CLI); err is reserved for the check being unable to run at all.
func Kernel ¶
Kernel enforces a BYTE budget on the kernel file. maxBytes must be positive; there is no default budget and zero does not mean unlimited. A missing kernel and an empty kernel are both failures, not errors: the check ran, and the answer is NO.
Bytes are a proxy. What a context window actually spends is tokens, and the bytes-per-token ratio is a property of the writing, not of the format — see KernelTokens for the honest denomination.
func KernelTokens ¶ added in v0.3.0
func KernelTokens(file string, maxTokens int64, bytesPerToken float64) (measured, tokens int64, failures []Failure, err error)
KernelTokens enforces a TOKEN budget, derived from the measured bytes and a divisor the caller states.
The divisor is not a constant this tool can know: bytes per token varies by tokenizer, by language, and by how a given writer writes. It is a MEASUREMENT the caller makes on their own text — count the tokens of a representative sample with the tokenizer that will actually read the kernel, divide by its bytes, and pass that number. There is deliberately no default: a guessed divisor would make the whole answer a guess while looking like an instrument.
The derivation rounds UP (ceiling). A size check must never report fewer tokens than its own estimate, and rounding down would let a kernel sit one token over budget and read as exactly at it.
AND IT MUST NOT REPORT A NUMBER THE CONVERSION INVENTED. The estimate is a float64 and the count is an int64, and Go leaves an out-of-range conversion between them to the hardware: arm64 saturates to MaxInt64, amd64 yields the integer-indefinite value MinInt64 -- which is less than every budget, so a divisor of 1e-20 printed `KERNEL OK tokens=-9223372036854775808 budget=400` and exited 0 on three of the five shipped targets while the other two failed on the same file. The divisor is a hand-typed measurement and `1e-20` is a scientific-notation typo away from `1e20`, so the range is checked BEFORE the conversion and an estimate that cannot be counted is over budget, said in those words. The gate now gives the same verdict on every GOARCH.
func NoCode ¶
func NoCode(opts NoCodeOptions) (scanned int, findings []Failure, err error)
NoCode reports every file that is machinery living inside a prose-only tree.
A file is flagged when its extension is on the effective deny-list, when it carries an executable bit, or when it begins with a shebang. The three catch different things: the extension is the auditable common case, the mode bit catches a chmod +x on anything at all, and the shebang is the tell that survives renaming — a script with no extension is still a script.
A symlink is never dereferenced — a gate that can be walked out of the tree it guards is not a gate — but its own NAME is still classified, because a link called run.sh is machinery by the same argument that catches a file called run.sh, and reading its name requires no dereference.
A file that cannot be read produces a finding rather than a pass. Making a file less readable must not make this gate greener.
type LinksResult ¶ added in v1.2.3
type LinksResult struct {
MDFiles int
Checked int
Excluded int
Broken []BrokenLink
}
LinksResult is the full accounting of a links walk: the markdown files scanned, the relative links checked, the whole files skipped under an explicit --exclude prefix, and every broken link found.
func LinksExcluding ¶ added in v1.2.3
func LinksExcluding(dir string, exclude []string) (res LinksResult, err error)
LinksExcluding is Links with an explicit set of path prefixes to leave unscanned: a file under an excluded prefix is not opened, and a link that resolves into an excluded prefix is skipped rather than checked or reported. The Excluded count is the number of .md files under those prefixes.
func LinksFiles ¶ added in v1.2.3
func LinksFiles(dir string, files []string, exclude []string) (res LinksResult, err error)
LinksFiles is the single-/few-file form of Links: it checks exactly the listed markdown files and nothing else, so a two-file review does not expand to the whole tree. dir is still the resolution root — root-relative targets and the "escapes the tree" judgement resolve against it — and the reported paths stay repo-relative, exactly as the full walk reports them. A relative path is joined to dir; an absolute one is used as-is.
type NoCodeOptions ¶ added in v0.5.0
type NoCodeOptions struct {
Dir string // root of the tree being guarded (required)
Allow []string // path prefixes where machinery may live; empty by default
DenyExt []string // effective deny-list; empty means the floor list
DenySource string // provenance; required when DenyExt is set
Stage bool // read the index, not the working tree
}
NoCodeOptions configures the self/machinery separation check.
Allow starts EMPTY and stays empty unless the caller narrows scope. Every scope narrowing is the caller's, stated per run — the same law the skip and exempt lists elsewhere in this repo obey. The deny-list runs the other way: it is a floor that ships with the tool, because a narrowing that goes missing fails open and a floor that ships cannot.
Stage selects the read substrate: false (the default) walks the tree under Dir; true reads the staged content of Dir as the index carries it. The two paths share the same floor lists, the same --allow prefixing, and the same NAME/LOCATION/EXTENSION rules — the substrate is only what feeds the executable-bit check (the walk's perm, the index's DstMode) and the shebang check (the walk's Peek(2) on the file, the index's first two bytes through ONE cat-file --batch pipe).
type StagedRecord ¶
type StagedRecord struct {
SrcMode string
DstMode string
SrcOID string
DstOID string
Status string
Path string
}
StagedRecord is one row of `git diff-index -z --cached`. After the leading `:`, five fields appear in this exact order: srcmode, dstmode, srcOID, dstOID, status. The path follows the first NUL.
The DST -- "destination" -- OID is the FOURTH field. The third is the SOURCE OID, ALL-ZERO for every file added in this commit (SPEC.md 935). The two are not interchangeable under --batch: the third answers "missing" at exit 0 even when there is a real blob at the fourth.
func StageRecords ¶
func StageRecords(dir string) ([]StagedRecord, error)
StageRecords runs `git -C dir diff-index -r --ignore-submodules=none --cached -z HEAD --` and parses the NUL-separated records.
The flags are not negotiable. `-r` expands the sparse-index 040000 records into blob records, so `git add out/deep/evil.sh` is named rather than collapsed into a directory (SPEC.md 891). `--cached` is the index. `--ignore-submodules=none` keeps 160000 gitlink records visible to a check that inherits its subject's configuration (SPEC.md 881). `-z` keeps the path through C-quoting intact instead of being C-escaped (SPEC.md 654 in laneread.go is the same load-bearing half). The trailing `--` keeps a file named HEAD out of the parser's revision syntax (SPEC.md 904).
`-M` is deliberately NOT passed: a rename arrives as D of the old path and A of the new (SPEC.md 945).