surfaceaudit

package
v0.0.0-...-083aebc Latest Latest
Warning

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

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package surfaceaudit closes a rule against the set of places the rule has to reach, so that a fix can no longer be narrower than the property it claims.

The failure this defends against

Every gate this repository had before it verifies the change that was made. None of them could say the change was too small. That is the difference between the two questions a fix has to answer:

"does the thing I fixed stay fixed?"     — the assertion floor, H-12, the tiers
"did I fix every place this applies?"    — nothing

Four defects reported against v2026.7.12 were each the unfixed half of a fix shipped in the same release. In every case the fix's scope was an enumeration built by hand or by grepping the symptom, and the sites it missed left no trace anywhere: not in the code, not in the tests, not in INVARIANTS.md. A missing site was indistinguishable from a site that did not exist.

  • `auto apply` was not among the thirteen write commands that learned to resolve their target, because the scope was "commands printing `dry-run: would …`" and `auto apply` prints `dry-run: no changes written`. The E2E table that would have caught it lists `script apply` and four deletes — five hand-written rows, and the sixth was the bug.
  • `trace show` still rendered UTC after the timezone fix, because `analyze.shortTimestamp` is a fourth, independent timestamp renderer that never calls `time.Parse` at all, and `analyze.FormatShortTimestamp` is a fifth. A test pinned the UTC-in/UTC-out behaviour as correct.
  • `auto diff`/`auto apply` still refused identifiers `auto ls` prints, although `resolveAutomation` existed and its own doc comment names `diff`/`apply` as callers. INVARIANTS.md H-17 asserted they resolved correctly as background fact.
  • `device ls --pattern` lost case-insensitivity in a *consistency* commit, harmonised toward the sibling with no stake in the answer, while the three filter flags beside it in the same function kept it.

The mechanism

A Surface is the set of [Site]s a rule must reach, derived mechanically from the source or from the live command tree — never typed out. A manifest binds every site to a Disposition. Check fails when a site has none.

The property that matters is not that every site is proven. It is that no site can be *silent*. A site is proven, or knowingly exempt with a reason, or recorded as debt in a file a reviewer reads — and a site nobody has considered fails the build the day it appears. Debt is legal, invisible debt is not.

Three failure modes are therefore all hard errors:

  • unclassified — the site exists and the manifest does not mention it. This is the one that makes a new command red by default.
  • stale — the manifest mentions a site that no longer exists, so the ledger stops describing the code.
  • phantom — a disposition names a proof that no test in the repository defines. This is what rots an "Enforced by:" list into decoration.

Debt entries are reported, not failed, but a surface may not carry more of them than its recorded ceiling. Raising a ceiling is a one-line, greppable, reviewable act; forgetting a site is not an act at all. That asymmetry is the whole design.

Index

Constants

View Source
const InvariantsFile = "INVARIANTS.md"

InvariantsFile is the law book this audit reads.

View Source
const ManifestDir = "dev/surfaces"

ManifestDir is where the ledgers live. They are deliberately not under a testdata/ directory: they are not fixtures, they are the project's running account of where each rule does and does not hold, and they are meant to be read by a person deciding what to work on next.

View Source
const MinReason = 25

MinReason is the shortest Exempt or Debt reason the gate accepts. It exists for the same purpose as testaudit's: to stop `exempt: n/a` from becoming the idiom. A reason nobody can be bothered to write is a reason nobody had.

Variables

View Source
var ErrNoWriteCommands = errors.New(
	"no write command reached the write-back surface — the --confirm census is empty, so the cobra walk has stopped matching")

ErrNoWriteCommands is returned when the census handed to WriteBackSurface is empty.

It is an error rather than an empty surface because an extractor that has stopped matching is the one failure a closure gate cannot survive: it passes, forever, proving nothing. The gate checks for emptiness too; this makes the derivation itself refuse to produce a vacuous answer, so a future caller cannot lose the property by forgetting the check.

Functions

func PhantomCitations

func PhantomCitations(invs []Invariant, proofExists func(string) bool) []string

PhantomCitations returns the citations that name no test in the repository.

This is the check that keeps an "Enforced by:" list from decaying into decoration. A list is only evidence while every name on it resolves; the moment one does not, the list has stopped tracking the code and nobody found out, because reading a document is not a build step.

func TransportBoundedByFlag

func TransportBoundedByFlag(e ast.Expr) bool

TransportBoundedByFlag reports whether an expression reads the per-request timeout the --timeout flag writes.

It is exported for the gate's own use: the manifest records what each site does, and this answers the one question a manifest entry cannot check for itself — whether the value in the literal traces back to the flag at all.

Types

type Disposition

type Disposition struct {
	Kind   DispositionKind
	Detail string // test name for Proven; prose for Exempt and Debt
	Line   int    // line in the manifest, for the report
}

Disposition is one manifest line's claim.

type DispositionKind

type DispositionKind int

DispositionKind is what a manifest claims about one site.

const (
	// Proven means a named test asserts the rule for this site. The name must
	// resolve to a test function that exists, or the disposition is phantom.
	Proven DispositionKind = iota
	// Exempt means the rule does not apply here, for a stated reason.
	Exempt
	// Debt means the rule applies, nothing proves it, and that is recorded on
	// purpose. Legal, counted, printed, and capped.
	Debt
)

func (DispositionKind) String

func (k DispositionKind) String() string

type Invariant

type Invariant struct {
	ID    string // "H-17"
	Title string // the law, as stated
	Line  int
	// Cites are the test names the section names as its enforcement.
	Cites []string
}

Invariant is one `## H-n — …` section.

func ParseInvariants

func ParseInvariants(root string) ([]Invariant, error)

ParseInvariants reads INVARIANTS.md.

Citations are taken only from the "Enforced by:" bullet to the end of the section, never from the prose above it. The prose routinely names tests that were deliberately deleted or inverted — H-2's own history renamed TestDashDeleteDryRun out of existence and said so — and a gate that treated those as live citations would report the file's honesty as a defect.

type Manifest

type Manifest struct {
	Path    string
	Ceiling int // maximum Debt entries this surface may carry
	Entries map[string]Disposition
}

Manifest is a surface's ledger, parsed from dev/surfaces/<name>.manifest.

func LoadManifest

func LoadManifest(root, name string) (*Manifest, error)

LoadManifest parses a surface's ledger.

The format is deliberately line-oriented and dumb — one site per line, keyed by prose — so that a diff of this file reads as a list of decisions rather than a reformatting.

#ceiling 3
auto apply = debt: no test asserts the preview refuses an unresolvable id
auto delete = proven: TestE2EDryRunRejectsFabricatedTargetCLI
svc call = exempt: a service call has no target to resolve; HA validates it

type Result

type Result struct {
	Surface string
	Rule    string

	// Unclassified sites exist and the manifest is silent about them. Always
	// a failure: this is the closure property itself.
	Unclassified []Site
	// Stale keys are dispositioned but no longer name a site.
	Stale []string
	// Phantom dispositions claim a proof that does not exist.
	Phantom []string
	// ThinReason dispositions have a reason too short to be one.
	ThinReason []string
	// Debt sites are knowingly unproven. Reported, not failed — unless the
	// surface carries more than its ceiling.
	Debt []string
	// Ceiling is what the manifest allows.
	Ceiling int

	// Proven and Exempt are counted for the summary line, which is the part
	// a reader actually looks at when the gate is green.
	Proven, Exempt int
}

Result is what Check concluded, in the shape a gate reports.

func Check

func Check(s Surface, m *Manifest, proofExists func(string) bool) Result

Check binds a surface to its manifest.

proofExists reports whether a named test function is defined somewhere in the repository. It is a parameter rather than a package-level lookup so the gate decides what counts as a proof: the confirm surface resolves names against every tier including the Docker-gated ones, which is the only scope in which "TestE2E…" is a real name.

func (Result) Failed

func (r Result) Failed() bool

Failed reports whether the gate must go red.

func (Result) Report

func (r Result) Report() string

Report renders a Result for a failing gate. It always ends with the manifest lines the author would have to add, because the cost of doing the right thing is the only lever a gate really has over whether it gets obeyed.

type Site

type Site struct {
	// Key identifies the site stably across refactors of everything except
	// the thing it names. It is what a manifest line is keyed on, so it must
	// read as prose to whoever has to disposition it: "auto apply", not
	// "internal/cmd/auto.go:922".
	Key string
	// File and Line locate it for the report. They deliberately do not take
	// part in the key: a site that moves down a file is the same site.
	File string
	Line int
	// Note is extractor-supplied context printed beside the key, so the
	// report explains itself without the reader opening the file.
	Note string
}

Site is one place a rule has to reach.

type Surface

type Surface struct {
	// Name is the manifest's basename and the report's heading.
	Name string
	// Rule is the one-sentence property every site must satisfy. It is
	// printed at the top of every failure, because a gate that says only
	// "unclassified site" teaches nothing.
	Rule string
	// Sites is the derived set. An extractor that returns an empty surface is
	// itself an error — see [Check] — unless AllowEmpty says otherwise.
	Sites []Site
	// AllowEmpty marks a surface whose sites are *violations* rather than a
	// census, so zero of them is the goal rather than a broken extractor.
	//
	// A census surface (clock, confirm, target) lists every place the rule
	// reaches and can never legitimately be empty; emptiness there means the
	// extractor stopped matching and the gate has been passing while proving
	// nothing. A violation surface reaches zero when the rule holds
	// everywhere, and its extractor is instead guarded by a fixture test that
	// feeds it a known-bad function and requires it to be flagged.
	AllowEmpty bool
}

Surface is the complete set of sites a rule must reach, plus the name the manifest and the report use for it.

func AttributedSurface

func AttributedSurface(root string) (Surface, error)

AttributedSurface is every listing-row field filled from a Go constant instead of from the instance.

Rule (INVARIANTS.md H-28): a field that describes the OBJECT is read from the wire. A constant in that position states a property of the code path that built the row, and a reader has no way to tell the two apart.

The finding

`helper ls` merges two reads: the companion's per-domain YAML files, then every remaining helper-domain entity in /api/states. The second branch set `Source: "storage"` on every row it produced — so the column did not say where a helper is defined, it said which branch had found it. Those agree only on a tidy instance. A helper domain written inline in configuration.yaml is in no `<domain>.yaml`, so the companion returns nothing for it, every helper falls to the second branch, and all 222 helpers on the reference instance were reported as created in the Home Assistant UI — 42 of them wrongly (finding #104). Home Assistant had answered the question in the same payload: `editable` is true for a storage collection and false for a YAML one.

Why a violation surface rather than a census

The sibling surfaces (clock, confirm, target) list every place a rule reaches, and emptiness there means the extractor has stopped matching. This one lists CONSTANTS IN A DESCRIPTIVE POSITION, which is the violation itself, so zero is the goal — the same shape as result.manifest. AllowEmpty says so, and TestAttributedExtractorFlagsAnInventedField guards the extractor against silently matching nothing by feeding it a known-bad literal.

What counts as a site

A composite literal of a row type — the repo's convention is a type name ending in `Row`, one per listing — with a field set to a string constant. That is deliberately syntactic, like boolcell's renderer list: the question "is this value derived from the wire" is not decidable from the AST, but "was it typed into the source" is, and every invented value has that shape.

Empty-string constants are not sites. `Icon: ""` is the absence of a value, which is the honest answer this law asks for, not a claim about the object.

A NAMED constant counts too, and that is not a refinement — it is the whole difference between a gate and a decoration. Naming the literal is the first thing anyone does when they tidy this code: `Source: "yaml"` becomes `Source: helperSourceYAML`, the value is identical, and a rule that matched only string literals would go quietly to zero sites and stay there. So the declared string constants are collected first and an identifier naming one is the same site as the literal it replaced.

func AutomationRefSurface

func AutomationRefSurface(root string) (Surface, error)

AutomationRefSurface is every command entrypoint that takes an automation reference from the caller but never hands it to the one shared resolver.

Rule (docs/decisions.md D-1, INVARIANTS.md H-17): every command that takes an automation identifier accepts every form the family prints — config `id:`, alias, entity_id, object id — which in this codebase means exactly one thing: the reference passes through resolveAutomation. A parallel, narrower lookup is how the past half-fixes happened twice — `auto diff`/`auto apply` still refused the id `auto ls` prints after the resolver existed and its own doc comment named them as callers, and `auto rollback` matched the raw reference against backup filenames that are keyed by config id.

This is a VIOLATION surface: empty is the goal. A new `auto` command whose entrypoint follows the run(ctx, w, autoID) convention is swept in automatically, and doing its own resolution fails the gate until it is dispositioned.

func BoolCellSurface

func BoolCellSurface(root string) (Surface, error)

BoolCellSurface is every place a boolean becomes a cell of a text table.

Rule (INVARIANTS.md H-10): a boolean a table renders for a person reaches `--json` as a JSON boolean, which means the cell is paired with format.Table.SetMachine carrying the bool itself.

A cell is a string because a text table is made of strings, and `--json` re-uses the cells. So `dash ls --json` answered `"admin": "false"` — a non-empty string, and therefore true to the `if row["admin"]` a consumer writes (finding #59). `format.Table.SetMachine` exists for exactly this and two commands already used it, so the defect was never "dash ls forgot": it was that nothing could say which commands had not. Four sites had not.

The surface deliberately includes sites that are correct today. A census of "every bool that becomes a cell" is the only version that catches the fifth site on the day it is written; a census of "every bool that becomes a cell WRONGLY" is a list of known bugs, which is what the manifest's dispositions already are.

func ClockSurface

func ClockSurface(root string) (Surface, error)

ClockSurface is every place the product renders a wall clock a human reads.

Rule: Home Assistant reports timestamps in UTC and hactl's reader is in their own zone, so every site that renders an hour must convert. There is no correct site that does not — which is what makes the surface closable.

func DecodeSurface

func DecodeSurface(root string) (Surface, error)

DecodeSurface is every decode site in the module that the H-14 sweep cannot see.

Rule (H-7): a decode that yields nothing never renders as success — every decode site is poisoned by degeneracy.Check, guarded where its payload has no identity to poison, or dispositioned in dev/surfaces/decode.manifest.

func DomainDecodeSurface

func DomainDecodeSurface(root string) (Surface, error)

DomainDecodeSurface is every place a domain-specific attribute schema can meet a Home Assistant states payload.

Rule (INVARIANTS.md H-21): the set of entities whose attributes a command decodes into a domain-specific schema is a subset of the set it renders.

func InvariantSurface

func InvariantSurface(root string) (Surface, error)

InvariantSurface is every law in INVARIANTS.md.

Rule: an invariant is enforced by a gate that quantifies over the set it speaks about, not by a list of the sites that were fixed when it was written.

Every heading in the file states a universal — "an identifier hactl prints is an identifier hactl accepts", "a preview fails exactly where the confirmed run would", "every decoded field is documented". Each was then enforced by naming the handful of tests written alongside the fix. An enumeration cannot be incomplete, because the list *is* the scope; so the document reads as a proof and functions as a receipt. H-2 lists thirteen commands and thirty-one carry `--confirm`. H-17's own prose asserts that `auto diff`/`apply` resolve correctly, which was false when it was written.

A site here is dispositioned `proven` only by a test that walks a surface. It is the one place in this package where "proven" means something stronger than "a test exists".

func MapRangeSurface

func MapRangeSurface(root string) (Surface, error)

MapRangeSurface is every statement in the module's non-test sources that ranges over a Go map.

Rule (INVARIANTS.md H-16): an answer is a function of the instance, never of map iteration order. The Go runtime randomises map iteration on purpose, so any walk whose order can reach rendered output must be made canonical before rendering — and whether a given walk's order *can* reach output is a judgment no parser can make. The extractor therefore derives the census and the manifest carries the judgment, site by site: `proven` where a test pins the fed output byte-identical across runs, `exempt` where the order is structurally unobservable (a set is built, a slice is sorted before use, a single-key map is guarded by its length). What the gate closes is the census: a new map walk cannot appear silently, which is exactly how `companion wireguard status` shipped — it printed one arbitrary entry of `m.Resolved` for a release while a hand sweep of the module's other map-ranges sat in an audit report nothing re-runs.

Unlike the parser-only surfaces in scan.go this one needs go/types: whether `range x` walks a map is a property of x's type, which for half the sites in the module lives in another file or another package (a struct field, a named map type, a decode target). A syntactic guess would miss exactly the new spellings the gate exists to catch, and a missed site here is silent — the one failure mode this package treats as worse than any other. The cost is that the tree must type-check for the surface to derive, which every tier already requires anyway.

Build tags are handled by loading every configuration the sources declare (see buildTagConfigs) and unioning the walks, then cross-checking against the tag-blind parser scan: a file that compiles under none of the loaded configurations fails the derivation loudly instead of being silently invisible to the sweep.

func PartialScopeSurface

func PartialScopeSurface(root string) (Surface, error)

PartialScopeSurface is every command body that reads a source which can come back incomplete, one site per consuming function.

Rule (INVARIANTS.md H-10, D-7): a source a sweep could not read reaches the caller's answer — stated in the report body, or refused when the answer's medium cannot carry the statement. Never only a log line.

Why this surface exists

D-7 was written twice already, and both times the rule was stated over the wrong set. First it was "a dashboard", and the fix for a silent dashboard left a silent entity registry one function beneath it. Then it was "a source of `ref validate`", and `ref scan` — reading the same walk through the same warn-only path — kept returning three of twenty-four references at exit 0, with the whole config half dropped at slog.Warn (#34). Neither miss left a trace anywhere: the set was in prose, in one command's doc comment.

So the set is derived from the code that produces partial answers, in two passes, and a command that starts reading one of these sources is red until somebody says what it does about a short read:

  1. every function in internal/cmd whose results include a scope type — the dashboard walk's own bookkeeping (dashboardScanScope) and the whole-sweep one (sweepScope);
  2. every companion route whose response type declares a `Skipped` field — the config half is ONE wire call over N files, so a 200 can be a partial answer that looks complete.

A site is any function in internal/cmd that calls one of those. Producers are sites too, on purpose: `countRenameReferences` returned the dashboard scope and threw the config half's `skipped` away, which is exactly the shape a "consumers only" rule would have waved through.

func PreviewSurface

func PreviewSurface(root string) (Surface, error)

PreviewSurface is every command entrypoint that has a --confirm gate but does not build its preview with the shared dryRun() plan.

Rule (H-2, second half): a preview is machine-readable. dryRunPlan.render is the only thing in the package that consults --json, so a preview assembled with Fprintf is prose no matter what the caller asked for.

Nine previews were in exactly that state — `svc call` and `script run` among them, the two an MCP caller reaches for most — while the cited enforcement, TestPreviewJSONIsMachineReadable, exercised `helper create` and nothing else. A tenth, `auto create`, did build a plan but printed a prose validation line to the same writer first, so its stdout did not parse either. That one is not visible here; it is why the gate has an executable half as well.

func ResultSurface

func ResultSurface(root string) (Surface, error)

ResultSurface is every write to a command's own output writer, inside a --confirm-gated entrypoint, that is neither guarded by --json nor rendered through a renderer that consults it.

Rule (H-10, applied to the confirmed branch): `--json` is a machine contract on the path that WROTE, not only on the path that planned. PreviewSurface closed the preview half — no --confirm-gated command may assemble a plan outside dryRun(). Nothing closed the other half, and the result was the symmetric defect one branch over: `svc call --confirm --json` printed `called script.turn_on` in prose, exit 0, immediately after really firing the script, and the same omission sat unnoticed on area/label/floor create and delete, tpl create and delete, ent set-area/set-label, device set-area, script/auto/helper create/delete/apply, dash create/save/delete and rollback — every write command in the tree except the four whose result already went through renderFlowResult or a format.Table.

The two halves are one law and were fixed a release apart precisely because the first fix's scope was "the preview", which is the enumeration this package exists to replace. The extractor is deliberately the mirror image of PreviewSurface's: same set of entrypoints, other branch.

A site is flagged when ALL of the following hold, which is exactly the shape the defect takes:

  • the enclosing function is a `run…` entrypoint that branches on a `flag…Confirm` variable, so it is a write command (H-2 makes --confirm the definition of one);
  • it calls fmt.Fprint/Fprintf/Fprintln on the function's io.Writer parameter, i.e. on the caller's stdout rather than on a buffer or on stderr;
  • no enclosing `if`/`switch` in the same function mentions flagJSON, so the line is printed whatever the caller asked for.

Text a command prints under `if !flagJSON` is not a violation: that is the human branch of a command whose machine branch is elsewhere. Neither is `done(…).text(…).render(w)`, which is a method call and honours --json itself. Both spellings are in the tree and both are correct.

func RetrySurface

func RetrySurface(root string) (Surface, error)

RetrySurface is every place a non-idempotent request is issued.

Rule (INVARIANTS.md H-1): a POST is retried only when the request provably never left the client. A 5xx means the server may have acted, so retrying it can fire a service, create a config entry, or write an automation twice.

func SharedStateSurface

func SharedStateSurface(root string) (Surface, error)

SharedStateSurface is every function in the module's non-test sources that can destroy a file.

Rule (INVARIANTS.md H-26): hactl is never the only caller. The instance directory is shared — a second terminal, a CI job, an MCP server, the multi-agent fleet the findings came from — so a file hactl creates to preserve a state it is about to replace may not overwrite one that is already there, and state hactl reads back may have been written by somebody else.

The census is mechanical and the judgment is per site, because whether a destroyed file mattered is not a property a parser can read: `os.WriteFile` into a caller-named output path is the caller's business, and `os.WriteFile` into `<instance>/backups/` is somebody's only undo. All three backup writers in this module got that wrong the same way — a name at one-second resolution, no existence check — and each was fixed once, in the place it was reported, which is precisely the shape this package exists to stop (see the four defects in the package doc).

The extractor deliberately does NOT try to decide which paths are inside an instance directory. That derivation would be a heuristic over string building, and a heuristic that misses is silent — the one failure mode this package treats as worse than any other. A census of every destroyer is bigger and honest: a new one is red until somebody says which kind it is.

func TargetSurface

func TargetSurface(root string) (Surface, error)

TargetSurface is every command entrypoint that accepts an identifier from the caller and can finish successfully even when that identifier names nothing.

Rule: a command resolves the identifier it was handed before acting on it, so that a target which cannot be resolved is an error rather than a plan, and every command in a family accepts every identifier the family prints.

`auto apply` is the case this exists for. It fetches the remote config with the id it was given, logs the 404 as a WARN, prints "validation: ok" and "dry-run: … use --confirm to apply", and exits 0 — a success-shaped plan for an automation that does not exist, against an endpoint whose POST is create-or-update. Its sibling `script apply` returns the identical error.

func TransportSurface

func TransportSurface(root string) (Surface, error)

TransportSurface is every place in the product where a connection's bounds are set.

Rule: the bound comes from the caller's --timeout (haapi.DefaultTimeout, which the root command writes from the flag), directly or through the shared haapi.HTTPClient. A constant is a bound the caller cannot ask to be smaller.

func TruncationSurface

func TruncationSurface(root string) (Surface, error)

TruncationSurface is every function that shortens a string for a reader.

Rule (INVARIANTS.md H-10): a value shortened to fit a display is shortened by the renderer that knows who is reading, never by the code that assembles the value. Everything else — `--json`, `--full`, `--tokensmax 0`, `log show <id>` — is downstream of the cut and cannot undo it.

This is the surface finding #14 needed and did not have. Six sites in five files each did their own `if len(x) > N { x = x[:N-3] + "..." }` while building a table row, so `log --json --full --tokensmax 0` answered messages of exactly 60 characters for entries whose real text was a multi-kilobyte traceback, and `ent ls --json` answered `"2026-07-31T03:13:..."` for 76 of the reference instance's entities while `ent show --json` answered the whole instant. The report named one of the six.

Sites are keyed by the enclosing function, and routing through format.Clip is how a site leaves the surface: a shortening that has one implementation has one place to be wrong, which is the same reduction the clock surface made when five renderers became three.

func WriteBackSurface

func WriteBackSurface(confirm []Site) (Surface, error)

WriteBackSurface is every command that mutates Home Assistant, one site per command.

Rule (INVARIANTS.md H-12): a write is proven by reading it back from Home Assistant. Read the state from HA directly, write through hactl with `--confirm`, read it back from HA directly, and compare the whole document — with at least one assertion on a field the command never prints, as an independent witness that the document landed whole and that nothing else moved. The dry run is asserted to change nothing, and the restore is asserted too. Reading back *through hactl* does not count: then hactl both writes and verifies, and a shared modelling mistake agrees with itself.

Why this surface exists

H-12 is stated as a universal — "every write family" — and enforced by an "Enforced by:" list of the families that happened to get a round-trip test written for them. That is the defect class this package exists to close: the law quantifies over a set nobody computes, so a write family added tomorrow is covered by no test and leaves no trace anywhere. `dash save` sat in exactly that state until `docs/testing.md` swept it up by hand, and the six registry commands (`area`/`floor`/`label` create+delete) are still verified through `hactl … ls` — hactl proving itself — which the law names as insufficient in its second paragraph.

Where the set comes from, and why not from a second extractor

The census is the confirm surface's: every command in the live cobra tree carrying a `--confirm` flag. H-2 makes `--confirm` the definition of a mutating command ("mutating commands are dry-run by default"), so the two laws quantify over one set, and it is derived once. Deriving it a second time — from the source, from the mutating client calls — would give the enumeration two chances to disagree, and a scope built beside the real one is precisely how `auto apply` came to be missing from the thirteen commands that learned to resolve their target.

The granularity is one site per command, not per write family, because the command is the unit a read-back test drives and therefore the unit a disposition can honestly speak about: `script create`, `script apply` and `script delete` are one family but three writes, and a family-level ledger would have let `tpl delete`'s ghost-cleanup gap hide behind `tpl create`'s proof. Keys are the full command paths the cobra tree yields, unchanged from the confirm census, so one grep across dev/surfaces/ shows every ledger's verdict on the same command.

The blind spot this surface does not close

A command that mutates Home Assistant without a `--confirm` gate is invisible here, because it is invisible to the tree walk. Such a command is an H-2 defect before it is an H-12 one, and nothing in this repository derives "every mutating command carries `--confirm`" yet — the confirm and write-back censuses both start from the flag. Recorded here rather than left to be rediscovered: a known limitation is cheap, an assumed completeness is not.

Jump to

Keyboard shortcuts

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