Documentation
¶
Overview ¶
Package grounds is the recorded-grounds vocabulary as DATA: the closed set of three values, the `<token>: <text>` grammar, the parser, the renderer, and the substance floor that refuses a degenerate text.
It is a leaf for the same reason core/issueschema and core/changelog are: two record families record grounds — the intent record (core/intent) and the issue ledger (core/capture) — and the CLI parses a caller's operand before either of them is reached. A vocabulary spelled three times is a vocabulary the three can disagree about, which is how a value one surface writes becomes a value another refuses. The committed-record gate (core/lint) is deliberately NOT among them: it blocks a frontmatter `grounds:` key without judging its value, so it needs no reading of the grammar and imports nothing from here. It imports core/mdrecord — the record-body machinery the `## Grounds` section is read and written through — and core/frontmatter, for the one rule about where a record's frontmatter stops and its body begins; internal/termsafe, whose hidden-rune encoder a written text passes through; and core/mdrender, the site's Markdown renderer, which the constructors ask whether the entry they are about to hand a writer will render; otherwise only the standard library: no filesystem, no transport, no record store.
The grounds name the CONJECTURE being acted on, not the route taken. "Planned it because it is next" restates the decision; "planned it because we expect a stamped identity to survive rewording, which nothing else does" is a conjecture somebody can later find wrong. That distinction is a review property and no machine reads a sentence and knows which it is, so what lives here is a FLOOR — it refuses the degenerate cases and claims nothing more. The substantive requirement is carried by the interview prompts on the plugin surface.
Index ¶
Constants ¶
const Heading = "Grounds"
Heading is the section a record carries its grounds under. It is spelled once for the two that have to agree: the writer that creates the section and the reader that locates it, across both record families, and record-lint names it in its remedies from here.
const MinTextLetters = 20
MinTextLetters is the substance floor in LETTERS. It refuses the degenerate texts — a single word, a shrug, a restated flag name — without pretending to judge whether what clears it names a conjecture. It is deliberately low: a floor that refused real but terse reasoning would push the caller into padding, which records less than a short honest sentence does.
Letters rather than runes, because a rune count is answered by whatever occupies a rune and the padding is exactly what says nothing: three one-letter words and seventeen dots carry twenty runes and three letters of reasoning, and a text of letters that render as nothing carries no reasoning at all (iss-2608301455387735). isTextLetter is which runes count.
const MinTextWords = 3
MinTextWords is the same floor measured in LEXICAL UNITS, and it is the half that does the work. A rune count alone is answerable with nothing at all: twenty zero-width spaces, twenty dots or twenty digits each occupy twenty runes and carry no letters, and a refusal a caller can satisfy with characters that render as emptiness is not a floor (iss-2608301206034359). Three is the lowest count that can carry a subject, a verb and an object — the shape of a claim somebody could later find wrong — and the corpus's shortest real entry runs to thirty-two words, so nothing honest is anywhere near it.
What a unit IS depends on the script, and that is the whole of the fix for iss-2608301301044588: a letter-run is a word only where the script separates words, and counting runs made a Chinese or Japanese text of any length exactly one word. See textUnits for the counting and scriptioContinua for the scripts.
Variables ¶
var Vocabulary = []Token{Pursued, Deferred, Declined}
Vocabulary is the closed set, in the order a surface should offer it. The two refusals that reject a TOKEN -- Parse's grammar refusal and parseToken's -- render it (vocabularyList), so a caller told their token is wrong is told which tokens are right. The package's other refusals render nothing of the kind -- and that is all this comment claims about them. Two earlier spellings each replaced an overclaim with a smaller one: first that every refusal rendered the list, then that the other eleven all speak about the text, which is false of the three in record.go that speak about the record's structure and one of which ends "the grounds text is not the fault" (iss-2608301908284034, iss-2608301918362294).
Every rendering of the set for a reader goes through this package: the two refusals above through vocabularyList, and the help text and usage refusals a caller reads through UsageSpelling and ProseList. Prose documentation is the stated exception and spells the values by hand, which is what prose does.
Earlier spellings of this comment enumerated the places the values were copied. Each correction of that list introduced a fresh error, because a prose enumeration cannot fail and so cannot be maintained (iss-2608301836222858). There is no list here because there is nothing left to list.
Functions ¶
func AppendToRecord ¶
AppendToRecord appends one grounds entry to a record FILE, creating the `## Grounds` section when it is absent, and returns the file to write.
The append and its read-back run over the record BODY, and the result is spliced back onto the frontmatter unchanged. That is what makes the guard a property of the section the reader will actually consult rather than of the whole file: judged over the whole file, the writer matched a frontmatter `# Grounds` comment as its heading, wrote the bullet there, read it back happily, and reported success about a value the body reader could not see (iss-2608301805069999).
It refuses a write that would leave the record LESS readable than it was (iss-2608300927577980) rather than performing it. A grounds text is operator prose, and prose can carry an unclosed `<!--`. The comment mask then runs to end of file: the entry disappears, every line after it disappears with it, and the result still reports success, so nothing anywhere says the record was blinded. The check is two questions asked of the bytes about to be written, before they are written, rather than a pattern ban on the text: a text that leaves a comment open, and a write whose entry does not actually arrive.
The count question subsumes more than the comment one: a bullet swallowed by a fence, by a mask this reader does not yet model, or by anything else, fails to raise the count and is refused for the same reason. It answers for THIS reader only. The site's renderer reads the same record in the same preflight and refuses constructs this reader accepts — an unclosed code span, an image, raw HTML, a reference link — and that question is asked where a Grounds is built (New, NewDerived), of the site's renderer itself, so a Grounds that reaches this writer through either constructor already renders. The comment question is still asked separately because it is the one whose remedy the caller can act on — "close the comment, or drop the marker" — and a refusal that can name the cause is worth more than one that can only say the entry did not arrive.
The refusal names no record family; the caller wraps it with its own.
func Body ¶
Body returns the part of a record FILE the `## Grounds` section lives in — everything after the leading frontmatter block. It is the ONE answer the writer and every reader ask, so the bytes a write is judged over are the bytes a reader will consult.
Handed the whole file instead, a reader looks for its heading in the frontmatter too, where `# Grounds` is a legal YAML comment the block parser skips and an ATX heading pattern matches. Writer and reader could then agree an entry had landed while disagreeing about where (iss-2608301805069999).
Hand it the record FILE. A text with no opening delimiter comes back whole, but that is not a licence to pass a bare body: a body that opens with a thematic break (`---`) reads as a frontmatter opener, and everything down to the next such line is taken as frontmatter and dropped, the section with it (iss-2608301908288212).
func Fold ¶
Fold collapses every run of whitespace to a single space and trims the ends — the normalisation both writers need, held here so neither invents its own.
func ProseList ¶
func ProseList() string
ProseList renders the closed set for running prose — at today's set, `pursued | deferred | declined` — spaced, for a sentence naming the vocabulary where a bracketed placeholder would read as shell syntax.
func UsageSpelling ¶
func UsageSpelling() string
UsageSpelling renders the closed set as the operand placeholder a surface prints in help text and in a usage refusal: at today's set, `<pursued|deferred|declined>` — the alternation a caller types exactly one of, followed by `: <the conjecture being acted on>`.
It exists so that no surface types that string. The accepted cost is that the usage text stops being greppable from the surface that prints it: a search for the literal alternation lands here and in the test that pins this function's output — both of them beside the set the string is rendered from — and otherwise only in the prose documentation.
func ValidateText ¶
ValidateText is the substance floor over an already-folded text. It refuses the empty text, the text carrying a control character the frontmatter serialiser refuses (below U+0020, the class yamlScalar will not write — NOT every character a record cannot hold, because the store holds DEL, C1, the line separator and the bidi overrides quite happily; the wording is narrowed to the check rather than the check widened to the wording, because widening would put this floor out of step with the serialiser it exists to mirror, iss-2608301646042379), the text below either half of the floor — the UNIT count and the LETTER count, neither of which a text of zero-width spaces can satisfy — and the text made only of the vocabulary's own words or the asking verb's name. Everything else passes: what this cannot do is tell a conjecture from a restatement of the decision, and it does not claim to.
A unit is a word where the script separates words and a letter where it does not (textUnits, scriptioContinua), so the floor is a property of the TEXT rather than of the writing system. The UNIT count is asked FIRST. Every unit of a text the scriptio-continua refusal speaks for is a single letter, so such a text has as many letters as units: asking the letter count first would refuse each of them for its letters and that refusal would never be reached.
Both halves of the feature inherit the floor from here — the writing verbs through New, and the intent record reader through ParseSectionAboveFloor — which is why it belongs at this one place: a floor the writer and the reader disagree about is a gate reporting no recorded grounds about a record that visibly carries one.
Types ¶
type Grounds ¶
Grounds is one recorded ground: the disposition plus the free-text conjecture. It is comparable, so a round trip through String and Parse can be asserted whole.
func New ¶
New builds a validated Grounds from a token and a free text, PUTTING THE TEXT TO THE SUBSTANCE FLOOR. It is the writing verb's gate over what a caller supplies; Parse is the reader's gate over what is already written. The text is folded to one line first — both carriers hold a single line — so a text that is only line breaks is refused as the empty text it is.
func NewDerived ¶ added in v0.11.1
NewDerived builds a Grounds whose text is DERIVED from another required value rather than supplied to the argument — a wontfix's `declined:` entry stamped from its reason. It takes the same path New does with the substance floor left off: the text is folded, refused for the control characters ValidateText refuses, hidden runes are encoded, and the entry is refused when the site's renderer would refuse it — a derived entry is as append-only as a supplied one. The floor stays with what a caller supplies, because the value it is derived from has its own contract and a terse reason is a legal one (iss-2608301244450106).
func Parse ¶
Parse reads the `<token>: <text>` grammar — the one spelling written to a `grounds:` frontmatter scalar and to a `- <token>: <text>` bullet alike. It splits on the FIRST colon, because the text is prose and may carry colons of its own.
It checks the GRAMMAR and the VOCABULARY, and deliberately not the substance floor. Parse is what the readers and the committed-record gate use, and those judge values already written: a wontfix stamps its grounds from a reason whose own contract is merely non-empty, so a floor here would make a reader skip records the ledger has always accepted. The floor is an input gate, applied by New at the writing verb over what a caller supplies.
func ParseSection ¶
ParseSection reads a record's recorded grounds, in the order they were written. It is the single reader every consumer asks, so no two of them can disagree about what an entry is.
A bullet that does not parse is not an entry, and the reader says nothing about it: a reader has no verdict to give. The record gate does, and asks MalformedIn, the other half of the same walk, which bullets were skipped.
It checks the GRAMMAR and stops there, which is what lets the ledger use it. A wontfix stamps its grounds from a reason whose own contract is merely non-empty, so a floor here would make the reader skip entries the ledger has always accepted and a gate then report no recorded grounds about a record that visibly carries one. A consumer that claims the floor asks for ParseSectionAboveFloor instead.
func ParseSectionAboveFloor ¶
ParseSectionAboveFloor is ParseSection keeping only the entries that also clear the SUBSTANCE FLOOR, so a consumer that CLAIMS the floor holds a hand-typed bullet to exactly what New holds a supplied one to. Reading the grammar alone let `- pursued: yes` satisfy the intent readiness check, which claims the floor and would then have been enforcing only a colon (iss-2608300930057882).
It is expressed on top of ParseSection rather than beside it: what an ENTRY is stays one definition, and this adds a filter over it rather than a second reading of the section.
type Malformed ¶ added in v0.11.1
type Malformed struct {
// Line is the bullet's 1-based line in the record FILE.
Line int
// Text is the bullet folded onto one line, its `- ` marker removed.
Text string
// Err is the grammar's refusal.
Err error
}
Malformed is a top-level bullet under a record's `## Grounds` heading that the grammar refuses, so ParseSection does not take it as an entry.
func MalformedIn ¶ added in v0.11.1
MalformedIn names every top-level bullet under the `## Grounds` heading of a record FILE that the reader skips, with its file line.
It is the gate's half of the reader's contract. ParseSection declines a bullet that does not parse and says nothing, which is right for a reader with no verdict to give and wrong for the record as a whole: a malformed `grounds:` frontmatter value was a value a gate could judge, and moving grounds into the section made a malformed one silently absent (iss-2608301747001641). A bullet in the section is an entry attempt — the section is the verbs' append-only list — while a paragraph of prose under the heading is not a bullet and is never named here.
type Token ¶
type Token string
Token is one of the three recorded dispositions a ground may carry. The set is closed: a fourth value would be a fourth meaning nothing downstream reads.
const ( // Pursued records the reasoning behind work that went FORWARD — the half of // the record that had no home before and evaporated at the gate. Pursued Token = "pursued" // Deferred records a conjecture considered and left for later. Deferred Token = "deferred" // Declined records deliberate non-action: the wontfix's own type. Declined Token = "declined" )
The vocabulary (the 2026-08-28 decision-log entry): grounds extend the ADR family's decision-granularity reasoning rather than duplicating it, at the finer conjecture granularity, in these three values.