layered

package
v0.13.3 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package layered is the one layered configuration resolver: a value is taken from the invocation's flag, else the repository's committed file, else the machine's file under ~/.abcd.noindex/, else the bundled default the caller supplies, and it comes back with the layer and the origin that supplied it.

It is the canonical primitive for every configuration that more than one party may set (the one-canonical-primitive rule): the model-tier routing table (itd-2609170822093401, internal/core/oracle), the pace and the sub-agent ceiling (itd-2609201925079472, pace.*), the RepoPrompt review route (itd-6, oracle.review), the runner per role (itd-2609201916056194, roles.<role>.runner), the duplicate-match threshold (itd-2609212137116617, match.threshold) and how a plain-Terminal interview takes a choice from a list (itd-2610030810370060, interview.list). A consumer reads through it rather than opening a file of its own, so the precedence order, the guarded reads and the refusals are spelled once.

Two file families, one shape of resolution (DECISIONS, 2026-09-25):

  • Config: .abcd/config.json in the checkout and ~/.abcd.noindex/config.json on the machine, holding scalar keys under a namespace (pace.work_minutes, oracle.review, roles.<role>.runner, match.threshold). The repository file is shared with the keys ahoy writes (docs, meta, oracle.backend, repo, attribution, rules), so no reader owns the whole file: a consumer CLAIMS its namespace and every key in it, and an unknown key under a claimed namespace is refused.
  • OracleRouting: .abcd/config/oracle-routing.json and ~/.abcd.noindex/oracle-routing.json, the per-agent routing table the model-tier intent names. It carries a top-level schema_version, and its reader claims the whole file.

Loudness is the contract (the loud-staging rule applied to configuration). An absent file is an absent layer, the ordinary case. Every other fault is an error naming the file: a present file that is not a regular file, is a symlink or sits behind one, is writable by others (machine layer), is not valid JSON, holds a key twice, carries trailing content, or declares the wrong schema_version; an unknown key under a claimed namespace; a value in the winning layer that does not decode to the caller's type or fails its check. None of these falls through to a lower layer or to the default, because a configuration that silently does less than it says is the failure this package exists to close.

The package reads and never writes, never reaches a network, and never prints: the front door renders its values and its errors.

Index

Constants

View Source
const (
	InterviewListKey      = "interview.list"
	InterviewListArrows   = "arrows"
	InterviewListNumbered = "numbered"
)

The interview.list setting (spc-2610030911534855, "The numbered fallback"): how a plain-Terminal interview takes a choice from a list. arrows, the bundled default, is the arrow-key list with typing to narrow; numbered reads whole lines, a number or part of a name, and never touches the terminal's modes, which is the mode a screen reader is served by.

View Source
const MaxFileBytes = 1 << 20

MaxFileBytes caps every layer's read. A configuration file is a few hundred bytes; 1 MiB bounds a planted device or an endless file without ever refusing a real one.

Variables

View Source
var (
	// Config is the shared scalar-key file (see the package doc).
	Config = File{RepoRel: ".abcd/config.json", MachineRel: "config.json"}
	// OracleRouting is the per-agent model-tier routing table.
	OracleRouting = File{RepoRel: ".abcd/config/oracle-routing.json", MachineRel: "oracle-routing.json", SchemaVersion: 1}
)

Functions

func BoundKey

func BoundKey(s string) string

BoundKey renders a key or a name for a message, bounded like compact bounds a value: a file may be MaxFileBytes long, and a key it carries must not reach a refusal, a diagnostic or a stderr line whole (review-tier1 F1). It cuts on a rune boundary, so the result stays valid UTF-8.

func Decode

func Decode[T any](raw json.RawMessage) (T, error)

Decode is Get's strict decode, exported so a consumer that selects among Lookup's layers itself (the routing table's activation rule) decodes with the same strictness.

func SwapUserHomeForTest added in v0.13.3

func SwapUserHomeForTest(fn func() (string, error)) (restore func())

SwapUserHomeForTest substitutes the home lookup RootsFor reads the machine layer through and returns the restore. It is exported because the front-door tests that load layered files live in another package. Tests only; never called in production code, and never safe to call from a parallel test.

Types

type File

type File struct {
	// RepoRel is the slash path relative to the checkout root.
	RepoRel string
	// MachineRel is the slash path relative to ~/.abcd.noindex/.
	MachineRel string
	// SchemaVersion, when non-zero, is the top-level schema_version every
	// layer's file must declare.
	SchemaVersion int
}

File names one configuration file family: where it sits in a checkout and where it sits under ~/.abcd.noindex/.

func (File) MachineOrigin

func (f File) MachineOrigin() string

func (File) RepoOrigin

func (f File) RepoOrigin() string

RepoOrigin and MachineOrigin are how a layer's file is named in a value's origin and in every refusal: repo-relative, and in the tilde form so no message carries the caller's home path.

type Found

type Found struct {
	Layer  Layer
	Origin string
	Raw    json.RawMessage
}

Found is one layer's raw value for a key.

type Layer

type Layer int

Layer is where a resolved value came from. The constants ascend in precedence: a higher layer holding a key beats every lower one.

const (
	// None: no layer holds the key and no bundled value applies. Only a
	// consumer with its own activation rule reports it (the routing table
	// applies nothing until a table is accepted).
	None Layer = iota
	// Bundled: the default the binary ships, supplied by the caller.
	Bundled
	// Machine: the file under ~/.abcd.noindex/.
	Machine
	// Repo: the file committed in the checkout the session resolved.
	Repo
	// Flag: the invocation's own override, for one run.
	Flag
)

func (Layer) String

func (l Layer) String() string

String is the layer's name as every surface renders it.

type Roots

type Roots struct {
	Repo string
	Home string
}

Roots are the two places the layers are read from. Repo is the directory the repository layer is read at; "" means there is none and the repo layer is absent. Home is the caller's home directory; "" is refused, because an unresolved home would drop the machine layer without a word. A front door builds Roots with RootsFor; a test builds them by hand.

func RootsFor

func RootsFor(cwd string) (Roots, []string)

RootsFor is the one way a front door resolves Roots for a working directory, so every consumer of a layered file (the bare board, the delegating verbs' --route, and the pace, runner, review-route and match-threshold readers that follow) reads the repository layer from the same place.

The repository root is the rules loader's (rules.Resolve), not git's toplevel, and that is the choice for a reason: .abcd/rules.json, .abcd/guard.json and .abcd/config.json are already read from that root, so a session's injected rules, its shell guard and its layered configuration can never come from two different directories. The two answers differ in two places, and the rules root is the right one in both: a nested .abcd/ inside the working tree (a monorepo member governs its own subtree, as it already does for its rules), and a repository git will not answer for, where the rules root is bounded by the same shape check and ownership gate (a foreign- uid checkout falls back to the working directory with a note, instead of being read on git's say-so or dropped without one). Outside any repository it is the working directory, walked no higher, exactly as the rules loader reads it.

notes are the resolution's refusals (a foreign-owned checkout declined), for the front door to print on stderr; the home is left "" when it cannot be resolved, which Load refuses loudly.

type Stack

type Stack struct {
	// contains filtered or unexported fields
}

Stack is one file family's loaded layers.

func Load

func Load(f File, r Roots) (*Stack, error)

Load reads the repo and machine layers of f. The flag layer starts empty and is filled with SetFlag.

func (*Stack) Claim

func (s *Stack) Claim(namespace string, keys ...string) error

Claim declares that the caller owns namespace and that keys are every key it may hold, in every layer. An unknown key is refused, naming the key, the file it sits in and the keys the namespace takes, so a misspelt key never lets a default apply unannounced. namespace "" claims the file's top level (a file one reader owns outright); a "*" segment claims every member at that level (roles.* claims each role's keys, whatever the role is called). A namespace no layer holds is not an error.

func (*Stack) Lookup

func (s *Stack) Lookup(key string) ([]Found, error)

Lookup returns every layer that holds key, highest precedence first, so the winner is the first entry and a board can render the rest beside it. A path that runs through a non-object value is an error naming the layer's file.

func (*Stack) Members

func (s *Stack) Members(l Layer, key string) ([]string, error)

Members returns the member names of the object at key in one layer, sorted. An absent layer or key returns nil; a non-object value is an error.

func (*Stack) Present

func (s *Stack) Present(l Layer) bool

Present reports whether the layer's file exists (repo, machine) or any flag was set (flag).

func (*Stack) SetFlag

func (s *Stack) SetFlag(key string, v any, origin string) error

SetFlag places v at key in the flag layer, for this invocation only. origin is the flag text as typed (for example "--pace 90/240"), which becomes the value's origin and is how a receipt names an override verbatim.

type Value

type Value[T any] struct {
	V      T
	Layer  Layer
	Origin string
}

Value is a resolved configuration value with its provenance.

func Get

func Get[T any](s *Stack, key string, bundled T, check func(T) error) (Value[T], error)

Get resolves key to a T: the highest layer holding it, else bundled. The winning layer's value must decode strictly into T (unknown fields refused, a fraction refused for an integer, null refused) and pass check, when check is non-nil; if it does not, Get refuses naming the key, the value and its origin, and never moves on to a lower layer or to the default.

func InterviewList added in v0.13.0

func InterviewList(r Roots) (Value[string], error)

InterviewList resolves interview.list from the machine's ~/.abcd.noindex/config.json, else the bundled arrows. It claims the interview namespace, so a misspelt key is refused, and it is the machine's alone: how a person reads a list is theirs to say, so a repository's .abcd/config.json that sets it is refused naming the machine's file, as a value outside the two is, never passed over for the default.

Jump to

Keyboard shortcuts

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