reading

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package reading assembles the input a cold reading is handed.

Blindness is a property of the input, not a promise the reader makes. The include table below is the whole of what a reading may see: what it does not name is absent, including a record family invented after the table was written, and including this instrument's own output (itd-183, spc-61).

The package is cobra-free and stdout-free like every sibling under internal/core (adr-23): it takes a structured request and returns a structured result, and the front doors format it.

Index

Constants

View Source
const (
	BundleFileName   = "bundle.json"
	ManifestFileName = "manifest.json"
)

BundleFileName and ManifestFileName are the two artefacts an assembly writes: separate files, so the assembled input can go to a reader while the manifest stays with the auditor.

View Source
const (
	MarkerBegin = "<!-- BEGIN GENERATED: reading-include-table -->"
	MarkerEnd   = "<!-- END GENERATED: reading-include-table -->"
)

Marker delimiters for the rendered table in the readings charter.

View Source
const (
	// OutputType is the only _type the ingest accepts.
	OutputType = "abcd.reading.output/1"
	// RunType tags the run-metadata record, which is the commit marker.
	RunType = "abcd.reading.run/1"
	// RefusalType tags the record a list-level refusal leaves behind.
	RefusalType = "abcd.reading.refusal/1"
	// StageType tags the write-aside marker an in-flight ingest parks.
	StageType = "abcd.reading.ingest-stage/1"
)

The three artefact type tags this file reads and writes. They are carried in the documents themselves, exactly as the bundle's and the manifest's are, so a reader of a loose file can tell them apart without their filenames.

View Source
const (
	RunFileName     = "run.json"
	RefusalFileName = "refusal.json"
)

The three filenames under a run's durable directory, and the stage's lock.

View Source
const (
	RegimeGenerative   = "generative"
	RegimeExplicative  = "explicative"
	RegimeEvaluative   = "evaluative"
	RegimeRegistrative = "registrative"
)

The four supply regimes, named where the gate branches on them so a literal never drifts out of the table it came from.

View Source
const (
	BundleType   = "abcd.reading.bundle"
	ManifestType = "abcd.reading.manifest"
)

The two artefact type tags. They are carried in the documents themselves so a reader of a loose file can tell the two apart without its filename.

View Source
const AssemblerVersionCore = "1.2.0"

AssemblerVersionCore is the hand-set semver of the assembly contract: the include table, the projection, the bundle shape and the manifest shape together. It moves when the contract moves, which a digest cannot detect — a rewritten rule text moves the rendering without changing what the assembler promises, and a projection change alters the promise without touching the table (spc-61, ruling (12); spc-68).

View Source
const CharterPath = ".abcd/development/readings/README.md"

CharterPath is the readings family's charter, which renders the table.

View Source
const DefaultRunDir = ".abcd/.work.local/scratch/reading-runs"

DefaultRunDir is the local-tier parent an unnamed run is parked under.

View Source
const DefinitionsDir = "agents"

DefinitionsDir is where the four reading definitions live. The assembler never reads one — a definition is the reader's, and this package denies the whole directory to every assembly — but the status render reports which are present, because a missing definition is the difference between an instrument that can be dispatched and one that cannot.

View Source
const IngestStageDir = ".abcd/.work.local/scratch/reading-ingest"

IngestStageDir is the local-tier write-aside area one ingest stages into. It is deliberately in the ephemeral tier: a stage is evidence of an ingest in flight, never a record, and a crash must leave it somewhere no commit can pick it up.

View Source
const LintConfigPath = ".abcd/record-lint.json"

LintConfigPath is the record-lint configuration the record scan reads its stores from. Enumeration comes from that scan and nowhere else: there is one parser of the record's shape in this binary.

View Source
const MaxFileBytes = 4 << 20

MaxFileBytes bounds one admitted file. A file past the cap is a refusal, not a truncation: a silently shortened item would be an assembled input no re-run could reproduce from the manifest's hash.

View Source
const PatternField = "pattern"

PatternField is the envelope's provenance field: the pattern the reading read under. It is the reading RECORD's own field name (issueschema.ReadingRequired) and the key the four definitions instruct, so the payload, the record and the instruction all say one word. itd-185 and spc-63 call the same field the pattern named; there is one field, and this is its wire name.

View Source
const PresetConfigPath = ".abcd/config/reading-presets.json"

PresetConfigPath is the committed preset configuration. It is the ONE place a repository path may be named, and it joins the record-lint configuration in the dirty set by the same argument: an uncommitted edit to it reshapes an assembly as surely as an edit to a record does.

View Source
const PresetSchemaVersion = 1

PresetSchemaVersion is the preset file's own shape version, separate from the artefacts' SchemaVersion: the configuration and the output are different shapes with different reasons to move.

View Source
const ReadingsRecordDir = ".abcd/development/readings"

ReadingsRecordDir is the durable home of a run's own artefacts: the promoted manifest, the run metadata, and a refusal.

View Source
const RunIDFamily = "rdg"

RunIDFamily is the readings family's id prefix. It satisfies the mint's ^[a-z]+$ bound, and the mint reads no maximum, so two checkouts assembling in the same window cannot converge on one run id (adr-45).

View Source
const SchemaVersion = 4

SchemaVersion is the shape version of both artefacts an assembly writes.

It is ONE constant for two shapes, so a change to either restamps both. At version 2 the manifest item gained a kind and the bundle's shape did not move; the bundle was restamped anyway. At version 3 BOTH shapes moved, the bundle gaining the scope a reading was given and the manifest gaining the effective scope, its hash and the override stamp — so at that version the shared constant cost nothing. At version 4 the manifest item gained its byte length, so the shared constant restamps the bundle again. That is a known consequence of the shared constant, accepted rather than fixed inside a change that needed only one half of it — splitting the two is a larger change, and making the split silently is how a shape version stops meaning anything (spc-68).

Variables

View Source
var ClosedVocabularies = map[string][]string{
	"claim_type": {"criterion", "causal", "context"},
}

ClosedVocabularies are the body fields whose value set is closed. The definitions instruct them and spc-63 tables them; without a check here the instruction is the only thing enforcing them, which makes it a suggestion.

View Source
var Exclusions = []Exclusion{
	{Rule: "field projection", Signal: "frontmatter key", Detail: "origin"},
	{Rule: "field projection", Signal: "frontmatter key", Detail: "production_mode"},
	{Rule: "field projection", Signal: "heading", Detail: "Audit Notes"},
	{Rule: "field projection", Signal: "heading", Detail: "Open Questions"},
	{Rule: "field projection", Signal: "heading", Detail: "Why This Matters"},
	{Rule: "a reading's object excludes what it exists to change", Signal: "heading", Detail: "Scope Condition Dispositions"},
	{Rule: "absent from the positive walk", Signal: "directory", Detail: ".abcd/development/brief/03-evidence"},
	{Rule: "absent from the positive walk", Signal: "directory", Detail: ".abcd/development/decisions"},
	{Rule: "absent from the positive walk", Signal: "directory", Detail: ".abcd/development/roadmap/rfcs"},
	{Rule: "absent from the positive walk", Signal: "directory", Detail: ".abcd/development/intents/superseded"},
	{Rule: "absent from the positive walk", Signal: "directory", Detail: ".abcd/development/plans"},
	{Rule: "absent from the positive walk", Signal: "directory", Detail: ".abcd/development/research/notes"},
	{Rule: "no include names a directory containing a record family", Signal: "directory", Detail: ".abcd/work/issues"},
	{Rule: "absent from the positive walk", Signal: "file", Detail: ".abcd/work/DECISIONS.md"},
	{Rule: "absent from the positive walk", Signal: "record type in a denied path", Detail: "the lapse log"},
	{Rule: "absent from the positive walk", Signal: "record type in a denied path", Detail: "admission and selection grounds"},
	{Rule: "the instrument's own output is never its input", Signal: "directory", Detail: ".abcd/development/readings"},
	{Rule: "the instrument's own output is never its input", Signal: "directory", Detail: "agents"},
	{Rule: "the instrument's own output is never its input", Signal: "directory", Detail: "evals"},
	{Rule: "the instrument's own output is never its input", Signal: "directory", Detail: "internal/core/reading"},
	{Rule: "the store sits outside the repository tree", Signal: "unreachable path", Detail: "the session-transcript store"},
	{
		Rule:      "a reading's object excludes what it exists to change",
		Signal:    "directory",
		Detail:    ".abcd/development/intents/drafts",
		Positions: []Position{PositionWidening, PositionComparative, PositionDetection},
	},
	{
		Rule:      "a reading's object excludes what it exists to change",
		Signal:    "directory",
		Detail:    ".abcd/development/intents/planned",
		Positions: []Position{PositionWidening, PositionComparative, PositionDetection},
	},
}

Exclusions is the exclusion floor: every field, heading and directory the assembler refuses, each with the signal by which a reader detects it. It is asserted into every manifest so a reader can check the exclusions rather than trust a disclosure.

The floor is a DECLARATION a reader checks, and the assembler additionally refuses to emit an item under any path-shaped entry, so the two cannot part company (see assertExclusions).

View Source
var ReservedNames = map[string][]string{
	RegimeEvaluative:   {"order", "rank", "recommended", "score"},
	RegimeRegistrative: {"fix", "remedy", "resolution"},
	RegimeExplicative:  {"disposition", "status"},
}

ReservedNames is the per-regime reserved-name table. A payload naming one of these is refused with the field named and the licence stated.

`generative` has no row and needs none: its body schema is two fields, so any other key is refused as an unknown field anyway, and the generative licence is the widest — the constraint on it falls at admission, not here.

View Source
var Signatures = []Signature{
	{
		ID: "RG-EVAL-ORDERING", Regime: RegimeEvaluative, Mode: SignatureEnforce,
		Licence: regimeLicence[RegimeEvaluative],
		Pattern: regexp.MustCompile(`(?i)\b(?:ranks?|ranked|rates?|rated|scores?|scored)\s+` +
			`(?:it\s+|them\s+|this\s+)?(?:first|second|third|last|highest|lowest|above|below)\b` +
			`|\bin\s+order\s+of\s+(?:merit|preference|strength|quality)\b` +
			`|\b(?:the\s+)?(?:strongest|weakest|best|worst)\s+(?:candidate|option|choice)\b`),
	},
	{
		ID: "RG-EVAL-RECOMMENDATION", Regime: RegimeEvaluative, Mode: SignatureEnforce,
		Licence: regimeLicence[RegimeEvaluative],
		Pattern: regexp.MustCompile(`(?i)\b(?:we|i)\s+recommend\b` +
			`|\brecommend(?:ation)?\s+(?:is|that)\b` +
			`|\bshould\s+be\s+(?:chosen|selected|adopted|preferred|picked)\b`),
	},
	{
		ID: "RG-REG-FIXPROPOSAL", Regime: RegimeRegistrative, Mode: SignatureEnforce,
		Licence: regimeLicence[RegimeRegistrative],
		Pattern: regexp.MustCompile(`(?i)\bthe\s+(?:fix|remedy|resolution)\s+is\b` +
			`|\bto\s+fix\s+(?:this|it|that)\b` +
			`|\bpropos(?:e|es|ed|ing)\s+(?:a\s+|the\s+)?(?:fix|remedy|resolution)\b` +
			`|\bshould\s+be\s+(?:changed|replaced|rewritten|removed|deleted)\s+to\b`),
	},
	{
		ID: "RG-EXPL-DISPOSITION", Regime: RegimeExplicative, Mode: SignatureEnforce,
		Licence: regimeLicence[RegimeExplicative],
		Pattern: regexp.MustCompile(`(?i)\b(?:this\s+claim\s+is|the\s+claim\s+is|it\s+is)\s+` +
			`(?:already\s+)?(?:accepted|rejected|declined|settled|resolved)\b` +
			`|\balready\s+(?:accepted|rejected|declined|settled|resolved)\b` +
			`|\bdisposition\s*[:=]`),
	},
}

Signatures is the registry. It is deliberately small and conservative: whether these lint cleanly in practice is itd-185's recorded open question, and a noisy signature costs a reading its findings.

View Source
var Table = []Row{
	{
		Positions: allPositions,
		Source:    ".abcd/development/brief/01-product",
		Match:     []string{".md"},
		Kind:      KindBriefSection,
		Rule: "adr-55: the construal as it presently stands is committed record, " +
			"admissible to every reader including a cold reading",
	},
	{
		Positions: allPositions,
		Source:    ".abcd/development/brief/02-constraints",
		Match:     []string{".md"},
		Kind:      KindBriefSection,
		Rule: "The constraints chapter states the platform, the dependency stance, " +
			"the invariants and the naming a reading reads against",
	},
	{
		Positions: allPositions,
		Source:    ".abcd/development/brief/glossary",
		Match:     []string{".md"},
		Kind:      KindGlossaryTerm,
		Rule: "adr-55: the glossary's committed terms are committed record; " +
			"superseded terms and the reasoning that settled them are not",
	},
	{
		Positions: allPositions,
		Source:    ".abcd/development/intents/disciplines",
		Match:     []string{".md"},
		Store:     "itd",
		Bucket:    "disciplines",
		Kind:      KindDiscipline,
		Rule: "A discipline is a standing commitment the record already holds, " +
			"named individually inside the intent family",
	},
	{
		Positions: allPositions,
		Source:    ".abcd/development/intents/shipped",
		Match:     []string{".md"},
		Store:     "itd",
		Bucket:    "shipped",
		Fields:    intentProjection,
		Kind:      KindIntentProjection,
		Rule: "Assembler rule 2: a shipped intent travels as its claim record, " +
			"so the Audit Notes and dispositions it also carries stay behind",
	},
	{
		Positions: allPositions,
		Source:    ".abcd/development/specs",
		Match:     []string{".md"},
		Store:     "spc",
		Kind:      KindSpec,
		Rule:      "The design record a capability was built against",
	},
	{
		Positions: []Position{PositionEntailment},
		Source:    ".abcd/development/intents/drafts",
		Match:     []string{".md"},
		Store:     "itd",
		Bucket:    "drafts",
		Fields:    intentProjection,
		Kind:      KindIntentProjection,
		Rule: "Assembler rule 2: articulation precedes selection, so entailment sees " +
			"the candidate set and the reading asked to widen it does not",
	},
	{
		Positions: []Position{PositionEntailment},
		Source:    ".abcd/development/intents/planned",
		Match:     []string{".md"},
		Store:     "itd",
		Bucket:    "planned",
		Fields:    intentProjection,
		Kind:      KindIntentProjection,
		Rule: "Assembler rule 2: articulation precedes selection, so entailment sees " +
			"the candidate set and the reading asked to widen it does not",
	},
	{

		Positions:   allPositions,
		Source:      ".",
		MatchSuffix: []string{"_test.go"},
		Kind:        KindTest,
		Rule: "Assembler rule 1: the shipped tree is source and tests, counted apart " +
			"because tests are the largest single class and admitted identically",
	},
	{
		Positions: allPositions,
		Source:    ".",
		Match:     []string{".go"},
		Kind:      KindSource,
		Rule: "Assembler rule 1: the shipped tree is source and tests, with the record, " +
			"the definitions, the evals and the assembler's own package denied structurally",
	},
	{
		Positions: allPositions,
		Source:    ".",
		Match:     []string{".md"},
		Kind:      KindDoc,
		Rule: "Assembler rule 1: the shipped tree is the delivered documentation and the " +
			"root prose, with the record denied structurally",
	},
	{
		Positions: allPositions,
		Source:    ".",
		Match:     []string{".json", ".yml", ".yaml", ".toml", ".mod", ".sum", "Makefile"},
		Kind:      KindConfig,
		Rule: "Assembler rule 1: the shipped tree is the delivered configuration and build " +
			"files, with the record denied structurally",
	},
}

Table is the include table: the single source of truth for what a cold reading may see. It is rendered into the readings charter under a test asserting the two agree, on the idiom internal/core/lifeboat/mapping.go carries for the brief.

Order is significant only for rendering and for tie-breaking a path two rows admit: the first row that reaches a path owns the projection applied to it.

Functions

func Admits

func Admits(p Position, rel string) bool

Admits reports whether any row of the table admits rel at position p. It is the whole of the assembler's answer to "may a reading see this file".

func AssemblerVersion

func AssemblerVersion() string

AssemblerVersion is the core semver with the rendered include table's digest as semver build metadata. The digest is computed, not declared, so a table change moves the stamped version whether or not anyone notices: a manifest cannot name a version that does not describe the table it was built from.

This is structural where the previous gate was advisory. That gate compared the rendering's digest to a standalone literal and never read the version at all, so changing the table and restating the literal was green with the version unmoved (iss-2608311949385350) — an attestation asserting more than its examination establishes, which brief invariant 16 forbids.

The digest is carried WHOLE, not truncated. A short digest would have been easier to read and would not have supported the sentence above: Row.Rule is free prose of unbounded length inside the digested input, so a truncation is a collision an author can grind rather than one they would have to be unlucky to hit. The claim this function makes is absolute, so the evidence behind it is too — brief invariant 16 is the rule that an attestation states no more than its examination establishes, and this function IS an attestation.

func DefinitionPath

func DefinitionPath(p Position) string

DefinitionPath returns p's definition file, repo-relative. The filename is derived from the position rather than looked up, which is what lets a run's position resolve to its definition by construction.

func EncodeBundle

func EncodeBundle(b Bundle) ([]byte, error)

EncodeBundle renders the assembled input as canonical JSON.

func EncodeManifest

func EncodeManifest(m Manifest) ([]byte, error)

EncodeManifest renders the manifest as canonical JSON.

func ManifestHash

func ManifestHash(m Manifest) (string, error)

ManifestHash is the manifest's own content hash over its canonical bytes. It is the reference an ingest cites back.

func Render

func Render() string

Render renders the include table and the exclusion floor as the markdown the charter carries between the markers.

Types

type AssembleRequest

type AssembleRequest struct {
	// RepoRoot is the repository the assembly reads.
	RepoRoot string
	// Position is the reading position. The set is closed.
	Position Position
	// Target is "HEAD" or a hexadecimal commit sha of 7 to 40 digits. Branch
	// names and tags are refused as mutable: the manifest's re-runnability rests
	// on a reference that cannot move.
	Target string
	// OutDir is the operator-named directory the two artefacts are written to.
	// Empty means the default local-tier run directory.
	OutDir string
	// OutDirLabel is how the OPERATOR spelled OutDir, used in refusal messages so
	// a path they did not type is never quoted back at them. The front door
	// resolves a relative --out against the working directory before calling in,
	// and scrubPaths cannot redact the result when the working directory is not a
	// prefix of it. Empty means OutDir is the operator's own spelling.
	OutDirLabel string
	// Scope names what this reading is ABOUT: a record id, a material kind, or
	// a committed preset. It is required, and it is a closed form — the
	// invocation carries no prose (adr-58).
	Scope string
	// DryRun writes nothing into the repository's own tiers. With OutDir set the
	// artefacts still land there; with OutDir empty nothing is written at all
	// and the result is rendered only.
	DryRun bool
}

AssembleRequest is one assembly: a position, a target commit, and where the two artefacts go. It carries no free-text operand of any kind, because there is no channel through which ledger content may travel in the framing of a request (ruling (5)).

type AssembleResult

type AssembleResult struct {
	RunID            string     `json:"run_id"`
	Position         Position   `json:"position"`
	TargetCommit     string     `json:"target_commit"`
	AssemblerVersion string     `json:"assembler_version"`
	ItemCount        int        `json:"item_count"`
	ManifestHash     string     `json:"manifest_hash"`
	Scope            Scope      `json:"scope"`
	Size             SizeReport `json:"size"`
	OutDir           string     `json:"out_dir,omitempty"`
	Artefacts        []string   `json:"artefacts"`
	Written          bool       `json:"written"`

	Bundle   Bundle   `json:"-"`
	Manifest Manifest `json:"-"`
}

AssembleResult is what one assembly produced. The bundle and the manifest are carried for a caller that wants them in memory, and the artefacts are named by their basenames rather than by a full path.

OutDir is whatever the CALLER passed in, echoed back so it can find what it asked for. The core takes a relative one against the repository root, which suits the default it computes itself; the CLI resolves an operator's relative --out against the working directory before calling in, and then puts the operator's own string back on the result, so no absolute path nobody typed reaches the success surface. Neither ARTEFACT carries an output path at all.

func Assemble

func Assemble(req AssembleRequest) (AssembleResult, error)

Assemble walks the repository under the include table at the given position and produces the assembled input and its manifest.

type Bundle

type Bundle struct {
	Type          string   `json:"_type"`
	SchemaVersion int      `json:"schema_version"`
	Position      Position `json:"position"`
	// Scope is what THIS run was given, and it is the reading's own fact
	// rather than the auditor's. A reader told its object is the shipped tree
	// and handed a tenth of it will report the missing nine tenths as a
	// tension against the claim record, with every gate green.
	//
	// It carries NO repository path under any scope, and no provenance. See
	// BundleScope: it is a projection rather than the resolved scope precisely
	// because the obvious implementation carried a path, and which token the
	// operator typed — and whether that departed from the presets — is the
	// auditor's business and lives on the manifest.
	Scope BundleScope  `json:"scope"`
	Items []BundleItem `json:"items"`
}

Bundle is the assembled input: the reading's entire working set.

It carries no run identifier and no timestamp, so two assemblies of one repository state at one commit are byte-identical — the property itd-187's eval falsifies independently, and the reason the run identifier lives on the manifest alone.

type BundleItem

type BundleItem struct {
	ItemKey string `json:"item_key"`
	Kind    Kind   `json:"kind"`
	Text    string `json:"text"`
}

BundleItem is one passed item as the reading receives it: a key, a material class, and the text. It carries NO repository path, by construction — the key is an ordinal and the kind names a class, never a location (invariant 15).

type BundleScope

type BundleScope struct {
	Kinds   []Kind   `json:"kinds,omitempty"`
	Records []string `json:"records,omitempty"`
	// LocationNarrowings counts the location-based narrowings applied. It is a
	// count and never a list, because the list would be the paths.
	LocationNarrowings int `json:"location_narrowings,omitempty"`
}

BundleScope is the scope as a READING sees it, and it is deliberately NOT the Scope the manifest carries.

The manifest may name repository paths; the bundle may not, by brief invariant 15 — the assembled input is the reading's entire working set and no repository path enters its context. A scope's Path selectors ARE repository paths, so writing one Scope type into both artefacts put a path into the reading's own working set. That is what this split exists to prevent, and it was a live breach before it was caught (iss-2608312058244357).

A reading still has to know it was handed a subset: told its object is the shipped tree and given a tenth of it, it reports the missing nine tenths as a finding. So it is told the kinds and the records it was scoped to, and that a narrowing by LOCATION applied — never where. That is enough to know the bundle is not the whole object, and it carries no location.

type Definition

type Definition struct {
	// Position is the position the definition holds.
	Position Position `json:"position"`
	// Regime is the supply regime the definition states, verbatim.
	Regime string `json:"regime"`
	// Path is the definition file, repo-relative and slash-separated.
	Path string `json:"path"`
	// SHA256 is the hex digest of the whole file, frontmatter included.
	SHA256 string `json:"sha256"`
}

Definition is one cold-reading definition as this binary resolves it: where it is, which position it holds, the regime it states, and the hash of the bytes that were read. The hash is the instrument's identity for a run — two runs at one position under one definition hash read under the same instructions.

func LoadDefinition

func LoadDefinition(repoRoot string, p Position) (Definition, error)

LoadDefinition resolves p to its definition under repoRoot and reports what the file states. The root is a parameter rather than a discovered value so a caller — the ingest verb, or a test over a temporary tree — decides which repository is being read.

A missing file is returned wrapped around os.ErrNotExist, so a caller that treats absence as a state can say so with errors.Is. Every other fault is a fault: a definition present but silent about its position or its regime is worse than an absent one, because it reports an instrument that is not there.

func LoadDefinitions

func LoadDefinitions(repoRoot string) ([]Definition, error)

LoadDefinitions resolves every position's definition under repoRoot, in the order Positions renders them, skipping the positions whose file is absent.

Absence is a state: a repository with no definitions has none, and reporting that is the status render's job. A definition that IS present and does not parse is a fault, and stops the whole resolution — the alternative is a render that quietly lists three instruments where four were meant.

type Exclusion

type Exclusion struct {
	// Rule is the assembler rule the exclusion instances.
	Rule string `json:"rule"`
	// Signal is how a reader detects it: a frontmatter key, a heading, a
	// directory absent from the positive walk, or a store outside the tree.
	Signal string `json:"signal"`
	// Detail names the excluded thing.
	Detail string `json:"detail"`
	// Positions are the positions the exclusion binds at. An empty Positions
	// binds at every position.
	Positions []Position `json:"-"`
}

Exclusion is one refused source and the signal that detects it.

func ExclusionsFor

func ExclusionsFor(p Position) []Exclusion

ExclusionsFor returns the exclusions binding at p, in table order.

type IngestRequest

type IngestRequest struct {
	RepoRoot string
	// OutputPath is the reading's returned JSON. The front door resolves a
	// relative path against the working directory before it arrives here.
	OutputPath string
}

IngestRequest is one ingest: a repository and one payload file.

There is no position operand and no regime operand. The position is the payload's claim, checked against the manifest of the run it names; the regime is the definition's, and nothing an operator types can reach it.

type IngestResult

type IngestResult struct {
	RunID        string                     `json:"run_id"`
	Position     Position                   `json:"position"`
	Regime       string                     `json:"regime"`
	Records      []capture.ReadingRecordRef `json:"records"`
	RefusedItems []ItemRefusal              `json:"refused_items,omitempty"`
	// RefusedCount is how many items were refused in total. RefusedItems is
	// capped — the item count is payload-chosen — so the two differ when a run
	// refused more than the cap, and the count is what nothing truncates.
	RefusedCount  int          `json:"refused_count,omitempty"`
	ReviewFlags   []ReviewFlag `json:"review_flags,omitempty"`
	RunRecordPath string       `json:"run_record,omitempty"`
	RefusalPath   string       `json:"refusal_record,omitempty"`
	ClearedStages []string     `json:"cleared_stages,omitempty"`
	// RolledBack names the reading records the sweep REMOVED from the committed
	// ledger, because their run never reached its commit marker. A delete in the
	// committed tier is reported by id: "cleared an orphaned stage" does not tell
	// an operator that records left the ledger with it.
	RolledBack []string `json:"rolled_back_records,omitempty"`
	Redacted   int      `json:"redacted,omitempty"`
	Degraded   string   `json:"redaction_degraded,omitempty"`
}

IngestResult is what an ingest did.

func Ingest

func Ingest(req IngestRequest) (IngestResult, error)

Ingest validates one reading's output and writes its records.

The order is the protocol, and it is the order for a reason: nothing durable is written until the whole payload validates, the ledger records land as one batch, and the run metadata lands last as the commit marker.

  1. Sweep any orphaned stage a previous invocation left, naming and clearing it.
  2. Read and decode the payload strictly.
  3. Check the envelope, and resolve the parked run's manifest by content hash. Until this passes, the run has no proven identity, so nothing — not even a refusal — can be recorded against it.
  4. Resolve the definition and check the regime and the instrument against it. A refusal from here on writes a refusal record.
  5. Validate every item; an item-level violation refuses that item and lands the rest.
  6. Stage, write the ledger records, promote the manifest, and write the run metadata last.

type Instrument

type Instrument struct {
	Model            string `json:"model"`
	DefinitionSHA256 string `json:"definition_sha256"`
	AssemblerVersion string `json:"assembler_version"`
}

Instrument is the identity of the thing that read: the model, the content hash of the definition it read under, and the version of the assembler that built its input. All three are required, and all three are carried into the run metadata, so two runs claiming the same instrument are provably the same — which is what the closing-run comparison rests on (ruling (12)).

type ItemRefusal

type ItemRefusal struct {
	Ordinal int    `json:"ordinal"`
	Rule    string `json:"rule"`
	Field   string `json:"field,omitempty"`
	Detail  string `json:"detail"`
}

ItemRefusal is one item the run refused, and why. It carries no item body text: a refusal names the ordinal, the rule and the offending field, which is everything a reader needs and nothing a redactor would have to clean.

type Kind

type Kind string

Kind is a bundle item's material class. It names WHAT a passed item is and never WHERE it came from: the vocabulary is closed, and no member of it carries a location. The path mapping lives in the manifest alone, so an auditor can resolve an item to its source and the reading cannot (brief invariant 15).

const (
	KindBriefSection     Kind = "brief-section"
	KindGlossaryTerm     Kind = "glossary-term"
	KindIntentProjection Kind = "intent-projection"
	KindDiscipline       Kind = "discipline"
	KindSpec             Kind = "spec"
	KindSource           Kind = "source"
	KindTest             Kind = "test"
	KindDoc              Kind = "doc"
	KindConfig           Kind = "config"
)

func Kinds

func Kinds() []Kind

Kinds lists the closed material-class vocabulary.

type KindSize

type KindSize struct {
	Kind      Kind `json:"kind"`
	Items     int  `json:"items"`
	Bytes     int  `json:"bytes"`
	TokensEst int  `json:"tokens_est"`
}

KindSize is one material kind's contribution to an assembly's weight.

type Manifest

type Manifest struct {
	Type             string   `json:"_type"`
	SchemaVersion    int      `json:"schema_version"`
	RunID            string   `json:"run_id"`
	Position         Position `json:"position"`
	TargetCommit     string   `json:"target_commit"`
	AssemblerVersion string   `json:"assembler_version"`
	// Scope, ScopeHash and ScopeOverridden are the auditor's account of what
	// this run was about. The hash lets a reader tell two runs apart by their
	// scope rather than by re-deriving it, and it means a preset edited later
	// can never make a past run unreadable. ScopeOverridden is false when the
	// operator named a committed preset — running as reviewed — and true when
	// they named a record or a kind directly, so drift between what is
	// committed and what people actually run is countable rather than
	// invisible.
	Scope           Scope          `json:"scope"`
	ScopeHash       string         `json:"scope_hash"`
	ScopeOverridden bool           `json:"scope_overridden"`
	Items           []ManifestItem `json:"items"`
	Exclusions      []Exclusion    `json:"exclusions"`
}

Manifest enumerates what an assembly passed, by path, by field and by hash, and asserts what it refused. It carries no item content, so committing it needs no redaction.

It carries no timestamp FIELD, but it is not timestamp-free and must not be described as such: RunID embeds a mint stamp by construction (adr-45). So two assemblies of one repository state at one commit produce manifests that differ in RunID and in nothing else — Items and Exclusions are identical, and the bundle beside them is byte-identical. That, not manifest byte-identity, is the determinism a re-run can be checked against, and it is why the manifest sits outside the amnesia eval's comparison rather than inside it.

func DecodeManifest

func DecodeManifest(data []byte) (Manifest, error)

DecodeManifest reads a manifest strictly: unknown fields, trailing content and a schema-version mismatch are all refused. All three are fail-closed on purpose, because a manifest is the evidence a reader judges contamination by.

It has NO front door yet, and that is deliberate rather than an oversight: the verb that reads a manifest back is the ingest, which spc-63 owns and which is not in this delivery. It is exported and tested here because the writer and the reader of one format belong together — a decoder written later, against the file rather than against the encoder, is how the two drift.

type ManifestItem

type ManifestItem struct {
	ItemKey string `json:"item_key"`
	Path    string `json:"path"`
	Field   string `json:"field,omitempty"`
	// Kind is the item's material class, carried so a size report is checkable
	// against the manifest rather than asserted beside it. It is deliberately
	// NOT omitempty: an item without a kind is a defect, and a shape that can
	// omit the field cannot tell that defect from a well-formed item (spc-68).
	Kind Kind `json:"kind"`
	// Bytes is the length of the passed text. Without it the size report was
	// only HALF checkable against the manifest: an auditor could recompute the
	// per-kind item COUNTS and not the per-kind BYTES, which is the figure
	// itd-198 exists to add. The bundle carries the text and goes to the
	// reader; the manifest stays with the auditor, so an auditor holding only
	// the manifest could not corroborate the number the intent promised was
	// corroborable.
	Bytes  int    `json:"bytes"`
	SHA256 string `json:"sha256"`
}

ManifestItem maps one bundle item back to the file and field it came from, with the hash of the passed text. This mapping is the auditor's, and only the auditor's: it is the reason the bundle can be pathless and still checkable.

type Output

type Output struct {
	Type           string                       `json:"_type"`
	RunID          string                       `json:"run_id"`
	Position       Position                     `json:"position"`
	Regime         string                       `json:"regime"`
	ManifestSHA256 string                       `json:"manifest_sha256"`
	Instrument     Instrument                   `json:"instrument"`
	Items          []map[string]json.RawMessage `json:"items"`
}

Output is one reading's whole return, as it arrives.

Items are decoded as raw key/value maps rather than as a typed struct on purpose. A Go struct would need four shapes for the four position bodies, and json's DisallowUnknownFields would then report a licence breach as a bare unknown field. The key set is closed HERE instead, against the position's own body fields, which is strictly stronger: an unknown key is still refused, and a key on the position's reserved-name table is refused with the licence named.

type Position

type Position string

Position is the reading position an assembly is invoked at. The set is closed: an unknown token is refused by name, never defaulted.

const (
	// PositionWidening reads for configurations the record has not considered.
	// It is the one position that must not see the candidate set it is asked to
	// widen, which is why its rows exclude the draft and planned intents.
	PositionWidening Position = "widening"
	// PositionEntailment reads for what the record already commits to. It sees
	// the draft and planned intents because articulation precedes selection.
	PositionEntailment Position = "entailment"
	// PositionComparative reads two or more configurations against the
	// selection-criteria discipline.
	PositionComparative Position = "comparative"
	// PositionDetection registers detections against the object.
	PositionDetection Position = "detection"
)

func AssemblingPositions

func AssemblingPositions() []Position

AssemblingPositions lists the positions an assembly can run at.

It is Positions() minus comparative, whose object is the widening reading's pre-admission output — not repository material, and with no channel supplying it. That position refuses rather than being served a corpus that is not what it is about (itd-199). The two lists are deliberately separate: comparative is still a position, still has a definition and still keys a supply regime; it is only assembly it cannot do.

func ParsePosition

func ParsePosition(s string) (Position, error)

ParsePosition resolves a token to a position, refusing anything else by name.

func Positions

func Positions() []Position

Positions lists every position, in the order the charter renders them.

type PositionScope

type PositionScope struct {
	Kinds   []Kind   `json:"kinds"`
	Records []string `json:"records"`
	Paths   []string `json:"paths"`
}

PositionScope is one position's scope inside a preset.

type Preset

type Preset struct {
	// Extends names the preset this one adds to. It is a UNION and never a
	// replacement, which is what makes "warm is cold plus a delta" a property
	// rather than a review note: a scope added to cold appears in warm without
	// anyone remembering to add it twice, and warm can never be narrower.
	Extends string `json:"extends,omitempty"`
	// Positions maps a position token to its scope. A preset carries a scope
	// PER POSITION rather than one scope every position shares, because the
	// finding this exists to fix is that three of the four positions received
	// a byte-identical item set, and one scope over four near-identical
	// admissions reproduces it exactly.
	Positions map[string]PositionScope `json:"positions"`
}

Preset is one committed, named scope per position.

type PresetFile

type PresetFile struct {
	SchemaVersion int               `json:"schema_version"`
	Presets       map[string]Preset `json:"presets"`
}

PresetFile is the committed configuration.

func LoadPresets

func LoadPresets(repoRoot string) (PresetFile, error)

LoadPresets reads and validates the committed preset configuration.

It is strict on unknown fields, like every other artefact this package reads: a key nobody reads is a scope nobody applies, and a preset that silently does less than it says is the failure this whole intent exists to close.

type RefusalRecord

type RefusalRecord struct {
	Type           string     `json:"_type"`
	SchemaVersion  int        `json:"schema_version"`
	RunID          string     `json:"run_id"`
	Position       Position   `json:"position"`
	Regime         string     `json:"regime"`
	TargetCommit   string     `json:"target_commit"`
	ManifestSHA256 string     `json:"manifest_sha256"`
	Instrument     Instrument `json:"instrument"`
	Reason         string     `json:"reason"`
}

RefusalRecord is what a list-level refusal leaves behind: the run metadata and the named reason, and no items. The event is durable, and a rerun is a new run with a new run id, never an amendment.

type ReviewFlag

type ReviewFlag struct {
	Ordinal     int    `json:"ordinal"`
	SignatureID string `json:"signature_id"`
	Detail      string `json:"detail"`
}

ReviewFlag is a signature hit that did not refuse: the generative regime's path, where the licence is the widest and the constraint falls at admission instead.

type Row

type Row struct {
	// Positions are the reading positions this row is admitted at.
	Positions []Position
	// Source is the repo-relative directory the row reaches, "." for the
	// repository root.
	Source string
	// Match selects files inside Source: an entry beginning with "." is a file
	// extension, any other entry is an exact basename. An empty Match admits
	// every file, which no row uses — inclusion is positive at every grain.
	Match []string
	// MatchSuffix selects files inside Source by basename suffix, matched
	// case-sensitively. It is a separate field rather than a third convention
	// inside Match so the form is named by where it sits rather than inferred
	// from a string's first character: no disambiguation rule against the two
	// Match forms is needed, and none exists to get wrong. The case rule is
	// deliberate and differs from Match's extension form, which folds case:
	// the Go toolchain builds a test only from a lowercase _test.go, so folding
	// here would label material a test that Go does not build as one. The
	// suffix is not the WHOLE of Go's rule — a file named exactly _test.go is
	// ignored by the toolchain for its leading underscore, and files under
	// testdata/ are never built either, yet both are labelled test here. Those
	// are labelling differences on material no build compiles, never admission
	// differences. A file matched by either field is admitted; the two are
	// ORed (spc-68).
	MatchSuffix []string
	// Fields are the named fields projected out of each matched file, in the
	// order they are emitted. A field is resolved as a heading section where the
	// file carries that heading, otherwise as a frontmatter key. An empty Fields
	// passes the whole file as one item.
	Fields []string
	// Store and Bucket, when set, route the row's enumeration through the
	// record graph (internal/core/lint's LoadRecordGraph, the one parser of the
	// record's shape in this binary) rather than through a file walk. Bucket is
	// the lifecycle directory, empty for every bucket of the store.
	Store  string
	Bucket string
	// Kind is the material class every item from this row carries.
	Kind Kind
	// Rule is the rule that admits the row, quoted in the charter.
	Rule string
}

Row is one include-table row: which positions admit this source, what is matched inside it, which fields are projected out of each matched file, the material class the items carry, and the rule that admits the row.

A row's Source is the ONLY directory it reaches. The structural deny is measured from the Source downward, which is exactly why naming a record family's leaf bucket individually is legal while naming a directory that CONTAINS a family is not (assembler rule 1, ruling (18)).

func (Row) AdmittedAt

func (r Row) AdmittedAt(p Position) bool

AdmittedAt reports whether the row is admitted at p.

func (Row) Reaches

func (r Row) Reaches(rel string) bool

Reaches reports whether the row admits the repo-relative file at rel. It is the predicate the walk applies and the predicate the table's own rule tests assert against, so the charter cannot claim a bound the walk does not keep.

type RunRecord

type RunRecord struct {
	Type           string                     `json:"_type"`
	SchemaVersion  int                        `json:"schema_version"`
	RunID          string                     `json:"run_id"`
	Position       Position                   `json:"position"`
	Regime         string                     `json:"regime"`
	TargetCommit   string                     `json:"target_commit"`
	ManifestSHA256 string                     `json:"manifest_sha256"`
	Instrument     Instrument                 `json:"instrument"`
	Records        []capture.ReadingRecordRef `json:"records"`
	RefusedItems   []ItemRefusal              `json:"refused_items"`
	RefusedCount   int                        `json:"refused_count"`
	ReviewFlags    []ReviewFlag               `json:"review_flags"`
}

RunRecord is the run metadata, written LAST as the commit marker: a run without one never happened.

type Scope

type Scope struct {
	// Source is the token the operator gave, echoed so an artefact says what it
	// was asked for and not only what that resolved to.
	Source string `json:"source"`
	// Selectors are the resolved clauses, in a canonical order so one scope
	// hashes to one value.
	Selectors []Selector `json:"selectors"`
	// Overridden records that the run departed from the committed presets.
	// Naming a preset is running as reviewed; naming a record or a kind
	// directly is the departure worth counting.
	Overridden bool `json:"overridden"`
}

Scope is what one run was about: the resolved selectors, and how they were named. The Source and Overridden fields are provenance, not selection.

func ResolveScope

func ResolveScope(pf PresetFile, position Position, token string) (Scope, error)

ResolveScope turns one invocation token into the scope a run had.

The three token forms are tried in a fixed order, but the order is belt and braces rather than the rule: validatePresets refuses a preset whose name collides with a kind or a record-id shape, so at most one form can match.

func (Scope) Hash

func (s Scope) Hash() (string, error)

Hash is the scope's content hash, so a manifest can name the scope a run actually had and a reader can tell two runs apart by it.

type Selector

type Selector struct {
	// Kind selects every item of one material class.
	Kind Kind `json:"kind,omitempty"`
	// Record selects the items whose path names one record.
	Record string `json:"record,omitempty"`
	// Path selects the items at or beneath one repo-relative directory. It is
	// reachable ONLY from a committed preset, never from the invocation.
	Path string `json:"path,omitempty"`
}

Selector is one clause of a scope. A candidate is selected if ANY clause matches it, so a scope is a union of its clauses and an empty scope selects nothing.

type Signature

type Signature struct {
	// ID is the name a refusal cites, so a reader can find the rule that fired.
	ID string
	// Regime is the supply regime this signature polices.
	Regime string
	// Mode is a literal in Go with no configuration seam. Degrading a signature
	// on observed noise is an edit here plus a decision-log entry, which is what
	// makes the weakening from enforced to observed a recorded act.
	Mode SignatureMode
	// Licence is what a hit breaches.
	Licence string
	// Pattern is the detector. Every one is case-insensitive and anchored on a
	// verb phrase rather than on a bare word: a signature that fired on the word
	// "recommend" appearing anywhere would refuse a reading for quoting the
	// material it was handed.
	Pattern *regexp.Regexp
}

Signature is one named detector over an item's body text.

type SignatureMode

type SignatureMode string

SignatureMode is whether a signature refuses or records.

const (
	// SignatureEnforce refuses the item. Every shipped signature is in this mode.
	SignatureEnforce SignatureMode = "enforce"
	// SignatureFlag records the hit on the run record and lands the item. It is
	// the reserved degradation path; no shipped signature uses it.
	SignatureFlag SignatureMode = "flag"
)

type SizeReport

type SizeReport struct {
	ByKind    []KindSize `json:"by_kind"`
	Items     int        `json:"items"`
	Bytes     int        `json:"bytes"`
	TokensEst int        `json:"tokens_est"`
	Basis     string     `json:"basis"`
}

SizeReport is what an assembly would cost a reader, per material kind and in total. It rides on the result and never on the bundle: a reading has no use for its own weight, and itd-198 ac-8 holds the bundle's shape unchanged.

Bytes count the item text that actually travels, not the file on disk, so a projected record is counted at what the reading receives rather than at what the record weighs. No budget is enforced and none is invented: the assembler cannot know what a given reader accepts.

type Status

type Status struct {
	AssemblerVersion string     `json:"assembler_version"`
	SchemaVersion    int        `json:"schema_version"`
	CharterPath      string     `json:"charter_path"`
	Positions        []Position `json:"positions"`
	IncludeRows      int        `json:"include_rows"`
	ExclusionRows    int        `json:"exclusion_rows"`
	Definitions      []string   `json:"definitions"`
	StagedRuns       []string   `json:"staged_runs"`
}

Status is the read-only render behind the bare verb: what this assembler is, what it would admit, which definitions are present, and which runs are staged in the local tier without having been ingested.

func Describe

func Describe(repoRoot string) (Status, error)

Describe reports the assembler's state over a repository. It writes nothing.

Jump to

Keyboard shortcuts

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