Add initial documentation skeleton

This commit is contained in:
2026-09-06 10:52:38 -05:00
parent a10bb53628
commit 18524330fc
8 changed files with 1663 additions and 0 deletions

2
AGENTS.md Normal file
View File

@@ -0,0 +1,2 @@
Please review `docs/development.md` for initial orientation in this repository
and follow its task-specific reading guide.

View File

@@ -0,0 +1,861 @@
# ConfigKit — Initial Design and Architecture
## Status
**Design note for initial implementation.**
Audience: future maintainers of ConfigKit, including LLM coding agents assisting with implementation.
This document captures the current architectural and policy decisions for ConfigKit. The goal is to establish a sufficiently opinionated v1 design that can be implemented consistently across the application suite without prematurely becoming a general-purpose configuration framework.
---
## 1. Purpose
ConfigKit is a shared Go library for configuration loading and resolution across applications such as Seriatim, Audita, Narratio, WeatherFeeder, WeatherReporter, Distributor, and related tools.
Its purpose is to provide a consistent, blessed configuration lifecycle for applications that use YAML configuration files.
ConfigKit should solve recurring infrastructure concerns once:
- file loading;
- YAML parsing and strictness;
- deterministic imports / `conf.d` composition;
- duplicate-definition detection;
- intentional higher-precedence overlays such as profiles;
- presence tracking;
- source/provenance tracking;
- typed decoding;
- standardized diagnostics;
- effective-configuration inspection;
- support for application defaults, derived defaults, and validation.
ConfigKit is **not** intended to become a universal configuration framework.
---
## 2. Design Principles
### 2.1 YAML is syntax; ConfigKit defines semantics
The YAML parser must not implicitly define ConfigKit behavior for:
- composition;
- precedence;
- duplicate keys;
- sequence merging;
- `null`;
- imports;
- defaults;
- profile overlays.
These behaviors must be explicit ConfigKit policy.
### 2.2 Preserve information until late in the pipeline
ConfigKit should retain the parsed YAML tree and source metadata through composition and overlay processing.
Do **not** immediately reduce configuration to `map[string]any` or typed Go structs.
The implementation should preserve enough information to answer:
> Why does this effective value have this value, and where did it come from?
### 2.3 Keep application-facing Go types ordinary
Applications should normally use idiomatic types:
```go
type Config struct {
Enabled bool
Workers int
Timeout time.Duration
}
```
ConfigKit should not require pervasive `*bool`, `*int`, or `Optional[T]` fields merely to distinguish absence from explicit zero values.
Parser-level presence is metadata owned by ConfigKit.
Application-level optionality should remain explicit in the application's Go type where it is genuinely part of the application's semantics.
---
## 3. Responsibility Boundary
### ConfigKit owns
- configuration source loading;
- YAML parsing;
- YAML policy enforcement;
- import discovery;
- deterministic import ordering;
- strict composition;
- overlay application;
- duplicate-definition detection;
- source location tracking;
- presence tracking;
- definition history / provenance;
- typed decoding;
- standard diagnostics;
- effective-config rendering;
- config source reporting;
- config fingerprinting, if implemented;
- redaction mechanisms for secrets.
### Consuming applications own
- their `Config` Go types;
- static default values;
- derived/defaulted values;
- semantic validation;
- schema meaning;
- schema migrations;
- runtime initialization;
- runtime resource validation;
- hot reload and resource replacement.
---
## 4. Configuration Resolution Model
The conceptual lifecycle is:
```text
source discovery
↓
YAML parsing
↓
YAML policy validation
↓
strict base composition
↓
profile overlay
↓
static defaults / typed decode
↓
derived defaults / normalization
↓
semantic validation
↓
immutable effective configuration
```
A useful precedence model is:
```text
LOW PRECEDENCE
application defaults
↓
base configuration
├─ main file
└─ imports / conf.d ← strict composition
↓
selected profile ← intentional overlay
HIGH PRECEDENCE
```
Future layers such as environment-variable or CLI value overrides may be added later, but are not required for v1.
---
## 5. YAML Policy
YAML remains the configuration format.
ConfigKit should support a conservative YAML subset rather than arbitrary YAML semantics.
Recommended v1 policy:
- one YAML document per file;
- mappings, sequences, and scalars;
- string mapping keys;
- comments;
- unique mapping keys;
- reject unknown fields when decoding into typed configuration;
- reject custom YAML tags;
- reject YAML merge keys (`<<`);
- strongly consider rejecting anchors / aliases;
- reject ambiguous or unsupported constructs rather than guessing.
The intended model is approximately:
> JSON's data model, with comments and more pleasant human syntax.
The underlying YAML implementation must remain an internal implementation detail. Public ConfigKit APIs should not expose parser-specific node or error types.
---
## 6. Presence Semantics
ConfigKit must distinguish at least these states:
| State | Meaning |
|---|---|
| key absent | no opinion; a lower-precedence/default value may apply |
| `key: false`, `key: 0`, `key: ""` | explicitly configured value |
| `key: null` | explicitly null; not equivalent to absent |
| ordinary nonzero value | explicitly configured value |
Presence information must survive configuration resolution even after the final typed Go value is produced.
Applications should be able to determine, when necessary, whether an effective value was explicitly configured or defaulted.
Conceptual metadata API:
```go
result.Config
result.Metadata.IsExplicit("transcription.concurrency")
result.Metadata.Origin("transcription.concurrency")
result.Metadata.History("transcription.concurrency")
```
Exact API names are not yet fixed.
---
## 7. Defaults
### 7.1 Static defaults
Applications define static defaults, for example:
```go
func DefaultConfig() Config {
return Config{
Enabled: true,
Workers: 4,
Timeout: 30 * time.Second,
}
}
```
Explicit YAML values such as `false` and `0` must override those defaults.
Absent YAML values leave defaults intact.
### 7.2 Derived defaults
Defaults that depend on other resolved fields should be applied by an application-owned resolver after typed decoding.
Example:
```go
func ResolveConfig(cfg *Config, meta configkit.Metadata) error {
if !meta.IsExplicit("timeout") {
cfg.Timeout = cfg.PollInterval / 10
}
return nil
}
```
ConfigKit should provide a standard lifecycle hook for this but should not own application-specific derived-default logic.
---
## 8. `null`
`null` is **not** equivalent to absence.
Recommended v1 rule:
> `null` is an explicit value and is valid only where the application schema permits null.
For ordinary scalar fields such as:
```go
Workers int
Enabled bool
```
this should be invalid:
```yaml
workers: null
```
Nullable application semantics may be represented using suitable Go types such as pointers.
Do not use `null` in v1 to mean "reset this value to the application default."
If profile-level reset-to-default semantics are eventually required, add an explicit ConfigKit mechanism rather than overloading YAML `null`.
---
## 9. Strict Composition
Base configuration may be assembled from a main file plus imported fragments, including Debian-style `conf.d` directories.
Composition means:
> Multiple files contribute non-conflicting portions of one logical configuration.
A duplicate semantic value path across composed files is an error.
Example allowed:
```yaml
# 10-server.yaml
server:
host: localhost
```
```yaml
# 20-server.yaml
server:
port: 8080
```
These define different paths:
```text
server.host
server.port
```
Example rejected:
```yaml
# A
server:
port: 8080
```
```yaml
# B
server:
port: 9000
```
Both define:
```text
server.port
```
The diagnostic should identify both source locations.
### 9.1 Mapping behavior
Mappings compose recursively by semantic path.
### 9.2 Sequence behavior
Sequences do not implicitly compose or merge.
If two composed files define the same sequence path, that is a collision.
When configuration needs independently composable named entries, prefer mappings keyed by stable identifiers rather than lists.
---
## 10. Intentional Overlay
Overlay is a separate operation from composition.
Overlay means:
> A higher-precedence layer intentionally replaces values from a lower-precedence layer.
For mappings, overlay recursively by key.
For scalar values, the higher-precedence value wins.
For sequences, the higher-precedence sequence replaces the lower-precedence sequence in full.
Do not implement implicit sequence append, union, or index-wise merging in v1.
---
## 11. Profiles
Profiles are the first intended overlay mechanism.
Example layout:
```text
config/
├── narratio.yml
├── conf.d/
│ ├── 10-transcription.yml
│ ├── 20-artifacts.yml
│ └── 30-distribution.yml
└── profiles/
├── production.yml
└── testing.yml
```
Default invocation loads only the composed base configuration.
Example:
```text
narratio
```
Explicit profile selection:
```text
narratio --config-profile=testing
```
produces:
```text
application defaults
↓
main config + imports
↓
profiles/testing.yml
```
### 11.1 `default` profile
"Default" should mean **no profile overlay**.
Do not require a literal `profiles/default.yml`.
### 11.2 Profile names
`--config-profile` should accept a profile name, not an arbitrary path.
Profile names should use a constrained character set, e.g.:
```text
[a-zA-Z0-9_-]+
```
The selected profile should resolve deterministically relative to the configuration root.
### 11.3 Profiles should not redefine the configuration graph
Profiles should modify application configuration values, not change how ConfigKit discovers configuration.
Profile overlays should therefore not introduce new imports or redefine import behavior.
The base configuration graph is discovered and composed first; the selected profile is then applied to the resulting configuration tree.
---
## 12. Import Policy
Import behavior must be deterministic and explicitly specified.
Recommended v1 rules:
- relative imports resolve relative to the file containing the import;
- glob results are sorted deterministically;
- duplicate semantic values across imported fragments are errors;
- explicit missing imports are errors;
- an optional empty `conf.d` glob may resolve to zero files;
- import cycles are errors;
- cycle diagnostics should show the full cycle;
- define file identity carefully with respect to symlinks;
- consider constraining imports to an allowed configuration root;
- do not support remote/network imports in v1.
Remote imports create separate concerns around availability, caching, authentication, integrity, startup latency, and freshness and should remain out of scope.
---
## 13. Provenance and Definition History
Provenance is a first-class design requirement.
ConfigKit should retain, for each effective semantic value path:
- whether it was absent or explicitly configured;
- its effective value source;
- source file;
- line and column where practical;
- configuration layer;
- previous definitions overridden by higher-precedence layers.
Conceptually:
```go
type Definition struct {
Layer Layer
Source Source
Value NodeRef
}
type FieldHistory struct {
Definitions []Definition
}
```
Possible layers:
```go
const (
LayerDefault Layer = iota
LayerBase
LayerProfile
)
```
Future layers may include environment or CLI overrides.
This metadata should enable diagnostics such as:
```text
transcription.concurrency = 1
history:
application default 4
conf.d/10-transcription.yml:14 8
profiles/testing.yml:7 1
effective source:
profiles/testing.yml:7
```
---
## 14. Typed Decoding
After composition and overlays produce one effective presence-aware configuration tree, ConfigKit decodes it into the application's typed Go `Config`.
Requirements:
- unknown fields are errors;
- type mismatches are errors;
- diagnostics should preserve source location where possible;
- parser-specific errors should be converted into ConfigKit diagnostics.
Do not expose raw YAML parser types through the public API.
---
## 15. Semantic Validation
Application-specific validation remains application-owned.
ConfigKit should provide a standard validation hook and diagnostic type.
Validation should normally collect multiple independent errors rather than aborting after the first.
Example output:
```text
configuration invalid:
weather.sources.nws.timeout
must be > 0
config.yml:19:5
outputs.public
duplicate definition
config.yml:42:3
conf.d/90-output.yml:8:3
```
---
## 16. Schema Versioning
Applications should support explicit configuration schema versions.
Example:
```yaml
schema_version: narratio.v1
```
The version identifies the application's configuration schema, not the ConfigKit library version.
ConfigKit may later provide lifecycle support for application-provided migrations, but automatic migration is not required for the initial implementation.
The design should leave a clean pipeline location for schema-version checking and migration.
---
## 17. Secrets and Redaction
ConfigKit will eventually process configuration containing secrets.
Redaction must be considered before implementing:
- effective-config output;
- diagnostics;
- config diffs;
- logging;
- config fingerprinting.
Potential application annotation:
```go
APIKey string `yaml:"api_key" configkit:"secret"`
```
Exact mechanism is TBD.
Human-facing tooling must never accidentally emit raw secrets.
If fingerprints include secret values, the raw values must still never be exposed.
---
## 18. Diagnostics and Operability
ConfigKit should establish recognizable diagnostics across the application suite.
Potential standardized commands enabled by ConfigKit:
```text
<app> config validate
<app> config effective
<app> config sources
<app> config explain <path>
```
These commands do not all need to exist in the initial skeleton, but the metadata model should make them straightforward to implement later.
`config effective` must support secret redaction.
---
## 19. Non-Goals for v1
Do not include these in the initial implementation unless a concrete application requirement appears:
- hot reload;
- runtime resource replacement;
- remote/network imports;
- arbitrary configuration providers;
- TOML/JSON/HCL/CUE support;
- Vault/S3 configuration providers;
- sophisticated environment-variable overlays;
- arbitrary CLI field overrides;
- sequence append/union merge strategies;
- arbitrary custom merge policies;
- profile inheritance;
- reset-to-default operators;
- application runtime validation such as checking whether an S3 bucket exists.
ConfigKit should remain opinionated.
---
## 20. Initial Internal Architecture
A reasonable internal model is:
```text
Source
↓
Document
├─ parsed tree
└─ source locations
↓
Composer
strict combination
duplicate definitions = error
↓
Overlay
higher layer wins
mappings recurse
sequences replace
↓
Metadata / Provenance
presence
origins
definition history
↓
Decoder[T]
effective tree → typed Config
↓
Resolver[T]
application-derived defaults / normalization
↓
Validator[T]
application semantic validation
↓
Result[T]
```
Conceptual result:
```go
type Result[T any] struct {
Config T
Metadata Metadata
Sources []Source
Diagnostics []Diagnostic
Fingerprint string // optional / future
}
```
---
## 21. Possible Package Skeleton
Exact names are not settled, but an initial repository could look roughly like:
```text
configkit/
├── configkit.go
├── source.go
├── document.go
├── compose.go
├── overlay.go
├── metadata.go
├── decode.go
├── diagnostic.go
├── profile.go
├── import.go
├── schema.go
├── secret.go
└── internal/
└── yaml/
```
Public API should remain small.
A future happy path might resemble:
```go
result, err := configkit.Load[Config](
configkit.File(path),
configkit.WithImports(),
configkit.WithProfile(profile),
configkit.WithDefaults(DefaultConfig()),
configkit.WithResolver(ResolveConfig),
configkit.WithValidator(ValidateConfig),
)
```
This is illustrative only. Do not prematurely lock in option names or signatures.
---
## 22. Suggested Initial Implementation Sequence
### Stage 1 — Core document model
Implement:
- local file source;
- YAML parsing;
- conservative YAML validation;
- source locations;
- semantic path traversal;
- internal presence-aware tree representation.
### Stage 2 — Strict composition
Implement:
- recursive mapping composition;
- duplicate semantic-path detection;
- deterministic import ordering;
- useful duplicate diagnostics.
### Stage 3 — Profile overlay
Implement:
- constrained profile name resolution;
- recursive mapping overlay;
- scalar replacement;
- sequence replacement;
- provenance history across layers.
### Stage 4 — Typed decoding
Implement:
- generic `Load[T]` or equivalent;
- strict unknown-field rejection;
- typed errors converted to ConfigKit diagnostics.
### Stage 5 — Defaults and resolution hooks
Implement:
- static application defaults;
- explicit-value preservation for `false`, `0`, and empty strings;
- metadata query for explicitness;
- derived-default / resolver hook.
### Stage 6 — Semantic validation
Implement:
- application validation hook;
- multi-error diagnostics.
### Stage 7 — Introspection
Implement or prototype:
- source listing;
- effective-config rendering;
- `explain(path)`;
- secret redaction.
---
## 23. Tests That Define Policy
ConfigKit should have policy-focused tests, not merely implementation tests.
At minimum, test:
- absent bool receives default;
- explicit `false` overrides `true` default;
- absent integer receives default;
- explicit `0` overrides nonzero default;
- explicit empty string remains explicit;
- `null` differs from absent;
- duplicate key within one YAML file fails;
- duplicate semantic path across composed files fails;
- sibling mapping paths compose successfully;
- profile scalar overrides base scalar;
- profile mapping recursively overrides only specified children;
- profile sequence replaces base sequence;
- base sequence collision across composed files fails;
- profile cannot redefine import graph;
- imports are deterministically ordered;
- import cycles fail with useful diagnostics;
- unknown typed-config fields fail;
- provenance identifies effective source;
- provenance retains overridden definitions;
- defaults are distinguishable from explicit equal-valued configuration;
- secrets are redacted from introspection output.
These tests should become executable documentation for ConfigKit policy.
---
## 24. Key Decisions Summary
For v1:
1. YAML remains the configuration language.
2. ConfigKit defines a conservative YAML policy.
3. Parsed configuration remains presence-aware until after composition and overlays.
4. Application-facing Go structs remain idiomatic and strongly typed.
5. Absent, explicit zero/false/empty, and `null` are distinct.
6. Base imports use strict composition.
7. Duplicate semantic values across composed files are errors.
8. Profiles use explicit higher-precedence overlay semantics.
9. Mapping overlays recurse.
10. Sequence overlays replace.
11. "Default profile" means no profile overlay.
12. Profiles are names, not arbitrary file paths.
13. Profiles do not redefine the import graph.
14. Static defaults are application-owned.
15. Derived defaults are application-owned and presence-aware.
16. Provenance and definition history are first-class metadata.
17. Semantic validation is application-owned but follows a common lifecycle.
18. Parser implementation details remain private.
19. Secrets must be redactable.
20. Hot reload, remote providers, generalized merge strategies, and broad extensibility are non-goals until concrete requirements exist.
The core architectural objective is:
> **Build one deterministic, explainable configuration resolution model for the entire application suite.**

54
docs/development.md Normal file
View File

@@ -0,0 +1,54 @@
# Development
This is the contributor entry point for thsi application. Use the task-specific
reading guide below before making changes. Canonical architecture, contracts,
component behavior, and policies remain in their owning documents.
## Initial Orientation
Before starting work:
1. inspect the working tree and preserve unrelated changes;
2. read the architecture policy for code or design work;
3. read the policy, contract, and internal documents listed for the task;
4. inspect the relevant implementation and tests before deciding how to change
them.
Start with:
- [Architecture policy](policy/architecture.md) for system boundaries,
invariants, and non-goals;
- [Internal component overview](internal/overview.md) for the current package
and component map;
- [Documentation policy](policy/documentation.md) before changing
documentation;
- [Testing policy](policy/testing.md) before adding, rewriting, or deleting
tests.
## Task-Specific Reading Guide
| Task | Read before changing |
| --- | --- |
| Repository orientation or component responsibility | [Internal component overview](internal/overview.md) and [architecture policy](policy/architecture.md) |
| Examples or copyable assets | The owning contract for the demonstrated behavior and the related files under `examples/` |
| Architecture decisions or future work | The [documentation policy](policy/documentation.md) and relevant accepted ADRs |
For cross-cutting changes, follow every applicable row. Internal component
documents own detailed subsystem change recipes.
## Baseline Validation
Use focused checks while iterating, then run validation proportionate to the
change and the risks described by the testing policy.
The repository-level baseline for code changes is:
```bash
go test ./...
go vet ./...
go build ./cmd/scriptorium
```
Documentation-only work does not require the full Go suite unless it changes
commands, examples, generated output, or another behavior that the suite
validates. Always check changed links, paths, examples, and canonical ownership.

13
docs/internal/overview.md Normal file
View File

@@ -0,0 +1,13 @@
# Internal Component Overview
## Purpose
This is the inventory of this application's implemented components for contributors.
The [architecture policy](../policy/architecture.md) owns normative boundaries
and invariants; public behavior belongs in the linked contracts.
TODO: Add tables below, using the following format:
| Component | Implemented responsibility | References |
| --- | --- | --- |
| | | |

View File

@@ -0,0 +1,30 @@
# Architecture Policy
**Base Policy Version:** 1.0
**Last Updated**: 2026-09-06
## Base Policy Template
This document is the base template for the canonical architecture policy,. Repositories that implement this policy should:
1. Revise this document to include an architectural policy tailored to the implementing repository, including a description of the repository's purpose, overall intended system shape, and any architectural and/or engineering invariants that must be maintained during future development;
2. Ensure that any supporting documents referenced in the architecture policy are present on the filesystem, even if the initial content is simply `TODO`;
3. Update the `Last Updated` field to the current date; and
4. Remove this heading (`Base Policy Template`) and the associated text.
As compared to other base policy documents, the Architecture Policy is the document that should be most heavily customized and tailored to the intended architecture of a particular application.
## Purpose
This policy defines the purpose of this repository and the architectural principles that contributors
must follow during development.
TODO: Complete this document.
Non-limiting examples of topics that may be addressed by an architectural policy include:
- Adherence to a hexagonal architecture, including whether external integrations/APIs must be encapsulated within internal adapters (strongly recommended)
- Whether a library is intended to provide a highly flexible API that supports a variety of downstream consumers, or a strongly opinionated API that is intended to enforce consistency across downstream consumers
- Whether an application should support a full DAG, or alternatively a strongly ordered sequence of discrete stages
- Whether the application should use specific external libraries to provide key functionality (e.g., FeedKit, ConfigKit, PromptKit)
- Any explicit architectural non-goals

View File

@@ -0,0 +1,171 @@
# Documentation Policy
**Base Policy Version:** 1.0
**Last Updated**: 2026-09-06
## Base Policy Template
This document is the base template for the canonical documentation policy. Repositories that implement this policy should:
1. Review the list of documents under the `Canonical Ownership` heading, remove any entries that do not currently apply and are not expected to apply to the current project, and add any additional entries that may be required by the current project;
2. Ensure that each referenced document is present on the filesystem, even if the initial content is simply `TODO`;
3. Update the `Last Updated` field to the current date; and
4. Remove this heading (`Base Policy Template`) and the associated text.
## Purpose
This policy assigns each documentation topic to one canonical owner. Its goal is
to keep this application's documentation accurate, concise, discoverable, and resistant
to drift for users, operators, developers, integrators, and LLM coding agents.
## Core Rules
### One Canonical Owner
Each authoritative fact belongs in one document. A non-owning document may give
a short, stable summary for orientation, but it must link to the canonical owner
instead of repeating volatile details.
Volatile details include commands, flags, configuration fields and defaults,
module keys, schemas, file names, paths, status codes, retry behavior, and
runtime guarantees. If readers could reasonably treat a statement as a
contract, maintain it only in the owning document.
### Current And Future Behavior
Outside `docs/roadmap/`, documentation describes implemented behavior only.
Partial features may be described only to their implemented boundary.
ADRs are the narrow exception: an ADR may record an accepted architectural
decision before implementation, but acceptance must not be presented as proof
that the behavior exists. The roadmap owns implementation status and sequencing
until the decision is implemented. Current architecture, user, operator,
integration, and internal documentation are updated when the behavior lands.
### Audience And Detail
Write for the document's stated audience and include only the detail needed for
its owned topic. User and operator docs should not expose implementation detail.
Developer docs should link to user-facing and external contracts rather than
restate them.
### Examples
Complete copyable files belong in `examples/`. Documentation may use the
smallest illustrative snippet needed to explain its owned topic, but should link
to maintained examples instead of embedding a second complete copy.
Examples must be valid, secret-free, and tested where practical. Commands and
configuration used in documentation should match the application.
### Security And Privacy
Documentation and examples must not contain real credentials, private keys,
private environment dumps, sensitive source material, or private infrastructure
details unless intentionally public. Document secret-handling mechanisms, not
secret values.
## Canonical Ownership
| Topic | Canonical owner | Owned content | Content owned elsewhere |
| --- | --- | --- | --- |
| Product orientation and minimal end-to-end quickstart | `README.md` | What this application is, why it is useful, one shortest successful invocation, and links onward. | Complete command reference, configuration reference, operational procedures, implementation detail. |
| Contributor entry point | `docs/development.md` | Task-oriented reading guide, minimal contributor orientation, baseline validation commands, and links to canonical docs. | Package inventory, architecture rules, subsystem behavior, detailed change recipes. |
| Application architecture policy | `docs/policy/architecture.md` | System shape, normative ownership, dependency direction, architectural boundaries, invariants, safety properties, and non-goals. | Concrete package inventory, implementation mechanics, contributor procedures, decision history, future work. |
| Documentation policy | `docs/policy/documentation.md` | Documentation ownership, audience boundaries, maintenance rules, and ADR/document lifecycle. | Application architecture or product behavior. |
| Testing policy | `docs/policy/testing.md` | Test philosophy, risk-based sufficiency, test boundaries, doubles, coverage guidance, regression-test policy, and criteria for adding, rewriting, or deleting tests. | Subsystem behavior, application contracts, subsystem-specific test inventories, and implementation plans. |
| Release policy | `docs/policy/release.md` | Versioning and compatibility rules, release integrity and immutability requirements, release-documentation policy, required gates, publication controls, verification requirements, and exception policy. | Repository-specific commands, product installation guidance, CLI version semantics, historical release summaries, and CI implementation detail. |
| Repository-specific release procedure | `docs/release.md` | Maintainer prerequisites, candidate preparation, concrete validation and publication commands, repository and registry guards, deliverable publication, consumer-facing verification, and failure recovery. | General release requirements, product installation guidance, historical release summaries, and current compatibility contracts. |
| CLI contract (if applicable) | `docs/cli.md` | Commands, arguments, flags, invocation semantics, and exit codes. | End-to-end operating procedures, configuration field definitions, runtime filesystem layout, module implementation details. |
| Configuration contract (if applicable) | `docs/config.md` | Discovery and precedence, file schema, fields, defaults, environment overrides, validation rules, and user-selectable module or validator keys. | Complete example files, CLI syntax, runtime state lifecycle, module implementation details. |
| Operations (if applicable) | `docs/operations.md` | Runtime workflows, physical filesystem and state layout, output, cache, and debug handling, resume, cleanup, permissions, recovery, and operational limits. | CLI flag syntax, configuration field definitions, logical output schemas, implementation mechanics. |
| Public HTTP contract (if applicable) | `docs/api.md` | Routes, authentication, media types, request and response schemas, status codes, pagination, caching, idempotency, rate limits, and HTTP retry semantics. | Client walkthroughs, upstream or downstream integration internals, implementation detail. |
| Consumer guidance for the public Go API (if applicable) | `docs/consumers/` | Task-oriented use of the public interface, minimal client examples, and consumer responsibilities. | HTTP wire semantics, external protocol contracts, internal implementation detail. |
| Integration contracts for external components (if applicable) | `docs/integrations/` | External file formats and protocols, upstream and downstream contracts, logical output bundle paths and schemas, media types, and compatibility behavior. | Physical runtime placement and lifecycle, internal transformations, CLI syntax, configuration defaults. |
| Inventory of implemented internal packages | `docs/internal/overview.md` | Current packages and components, their implemented responsibilities, and links to focused internal docs. | Normative architecture, contributor reading policy, external contracts. |
| Behavior and architecture of internal packages | Other files under `docs/internal/` | Implementation flow, internal collaborators and state transitions, package-local guarantees and failures, and relevant tests. | Global architecture invariants, configuration definitions and defaults, external schemas, operator procedures. |
| Architectural decision records | `docs/adr/` | Significant decisions, context, alternatives, rationale, consequences, and supersession history. | Current behavior reference, implementation status, task sequencing. |
| Future work and implementation status | `docs/roadmap/` | Proposed, accepted, deferred, or rejected work; implementation status; sequencing; and task breakdowns. | Implemented behavior reference and architectural decision rationale. |
| Release-note history | `docs/releases/` | One checked-in historical summary for each source release made under the procedure. The note at the immutable tag is that release's record. | Current commands, behavior, contracts, and compatibility definitions. |
| Complete copyable artifacts | `examples/` | Maintained configuration, inputs, and other files intended to be copied or run. | Field-by-field reference, command reference, prose explanation. |
Documents that do not exist are required only when the corresponding interface
or responsibility exists. Do not create placeholder API, consumer, integration,
or operations documents for behavior the application does not have.
## Boundary Rules
### Orientation
The README owns product orientation. The developer guide routes contributors.
Architecture owns normative structure. Internal overview owns the current
concrete component map. These documents may link to one another but should not
maintain parallel package or behavior descriptions.
### Commands, Configuration, And Operations
CLI documentation answers how to invoke the application. Configuration
documentation answers what settings mean. Operations answers what happens to
runtime state and how to operate or recover the application. When a workflow
crosses these topics, choose the document that owns the task and link to the
other contracts.
### Releases
The release policy defines requirements and invariants that every release must
satisfy. The repository-specific release procedure defines the commands and
workflow maintainers use to satisfy that policy. Release notes are immutable
historical summaries, not current-state contract owners: the checked-in note at
an immutable tag records that release, while current canonical documentation
must change with the behavior it describes. Do not use a release procedure or
release note to weaken policy, redefine current behavior, or defer required
documentation updates.
### Contracts And Implementation
Integration and API documents define externally observable shapes and
semantics. Internal documents explain how this application implements or consumes those
contracts. Internal docs may name a field, file, or protocol to identify a
dependency, but must link to its canonical contract for the definition.
### Security Topics
This policy owns what documentation and examples may contain. Architecture owns
application security invariants. Configuration owns credential-supply
mechanisms. Operations owns permissions and handling of sensitive runtime
artifacts. Internal docs own implementation mechanisms only.
## Architecture Decision Records
Use sequentially numbered ADR filenames such as
`0001-record-architecture-decisions.md`. Follow the lightweight Nygard format:
1. title;
2. status;
3. date;
4. context;
5. decision;
6. alternatives considered;
7. consequences.
Treat the decision content of an accepted ADR as immutable. When a decision
changes, create a new ADR and update the earlier ADR's status to superseded.
Rejected architectural alternatives belong in the ADR; rejected product ideas
belong in the roadmap.
## Maintenance
When behavior changes, update its canonical owner in the same change. If
ownership moves, remove the old definition and replace it with a link where
navigation remains useful.
Before completing documentation work:
- verify affected behavior and examples;
- check commands, flags, fields, defaults, schemas, and paths against their
implementation;
- keep unimplemented behavior in the roadmap, subject to the ADR exception;
- remove stale references and validate links;
- confirm that non-owning documents summarize and link rather than redefine;
- confirm that no secrets or sensitive private data were added.

222
docs/policy/release.md Normal file
View File

@@ -0,0 +1,222 @@
# Release Policy
**Base Policy Version:** 0.1.0
**Last Updated**: 2026-09-06
## Base Policy Template
This document is the base template for the canonical release policy.
Repositories that implement this policy should:
1. Retain the substantive requirements of this policy unless a documented
repository constraint requires an exception;
2. Define the repository-specific release procedure, validation commands,
supported deliverables, and verification commands in `docs/release.md`;
3. Ensure that every document and script required by the adopted policy and
procedure is present on the filesystem;
4. Add stricter repository-specific requirements where the product, delivery
channels, or threat model require them;
5. Record every exception under `Policy Exceptions`, including its rationale
and compensating controls;
6. Update the `Last Updated` field to the current date; and
7. Remove this heading (`Base Policy Template`) and the associated text.
The base policy is a standard, not a collection of optional recommendations.
An implementing repository should customize its release mechanics without
silently weakening the common integrity, validation, documentation,
verification, or immutability guarantees below.
## Purpose
This policy defines the minimum requirements for selecting, validating,
publishing, verifying, and correcting releases. Its goal is to make every
release intentional, reproducible from an exact source revision, documented
for consumers, and safe from retrospective replacement.
The repository-specific commands that implement these requirements belong in
`docs/release.md`. Historical release summaries belong in `docs/releases/`.
Neither document may weaken this policy.
## Release Model
A release consists of:
- one exact source commit reachable from the repository's primary branch;
- one immutable version tag identifying that commit;
- one version-matched, checked-in release note; and
- every artifact, image, package, registry entry, or other deliverable the
repository declares for that release.
The repository's primary branch is `main` unless an adopted policy documents
an exception. A release candidate must be committed and pushed to that branch
before its release tag is published.
The source tag is the authoritative release identity. Published deliverables
must be traceable to the tagged source and must agree with its version. A
repository may publish source only. When it publishes additional deliverables,
its release procedure must enumerate them and define how each is built,
identified, published, and verified.
Release automation may validate or publish a release only when its exact
responsibility is documented. Automation must not silently change the source
candidate, move a published tag, replace a published deliverable, or repair a
release in place.
## Versioning And Compatibility
Stable releases use Semantic Versioning in the form `vMAJOR.MINOR.PATCH`, with
no leading zeroes. Each version must be unused locally and in every configured
remote or publication channel before publication begins.
Unless an adopted policy explicitly supports and defines prereleases,
prerelease versions are not permitted.
For releases at or after `v1.0.0`:
- a major release may make intentional incompatible changes;
- a minor release may add backward-compatible behavior; and
- a patch release may make backward-compatible fixes.
Before `v1.0.0`, a minor release may change a documented public, operational,
configuration, data, integration, or compatibility contract when the release
note explains the impact and required consumer action. A patch release must
not intentionally break those contracts within its minor line.
A repository with additional compatibility promises must define them in its
adopted policy or canonical contract documentation. A release note may explain
a compatibility change, but it must not redefine the current compatibility
contract.
## Release Documentation
Every release made under this policy must include
`docs/releases/<version>.md` in the tagged source tree. The filename must use
the complete version tag, and the document must begin with a title containing
the product name and that exact version.
Every new release note must contain these sections:
```markdown
# <Product> vMAJOR.MINOR.PATCH
This release ...
## Summary
## Compatibility
## Upgrade
## Changes
```
Use an explicit statement such as "No action required" when a section has no
substantive content.
Release notes are immutable historical summaries. They should link to current
canonical documentation for exact behavior. Every affected canonical document
must be updated in the same candidate; a release note must not substitute for
current documentation or defer required documentation changes.
## Candidate Preparation And Validation
Each repository must provide one documented release-check command or script
that exercises the complete set of automated source-candidate gates. The
repository-specific procedure must state all additional manual gates.
The required gates are:
- dependency or module hygiene appropriate to the language and ecosystem;
- the complete deterministic, offline default test suite;
- race, concurrency, or equivalent safety checks where the implementation can
be meaningfully checked for those risks;
- static analysis and linting used by the repository;
- production builds for every supported target that can be built in the
release environment;
- formatting and whitespace checks;
- validation of maintained examples, configuration, schemas, generated
sources, and documentation commands where applicable;
- review of changed local documentation links;
- removal of obsolute roadmap documents under `docs/roadmap/` that describe
completed work included in the release;
- review for unintended files, generated output, credentials, private data,
and unrelated changes; and
- any additional security, compatibility, migration, packaging, or platform
checks required by the product.
Unavailable tooling or an inapplicable check must be addressed explicitly in
the adopted policy or release procedure; it must not disappear silently from
the candidate gates.
Run the complete checker while preparing the candidate. Commit the release
note and all associated code, tests, documentation, and configuration, then
run the complete checker again against the exact commit intended for release.
Do not publish a release from uncommitted or unvalidated content.
## Publication Guards
Immediately before publication, the maintainer or release automation must
fetch the current primary branch and tags from the authoritative remote and
verify all of the following:
- the version has the required format and is not already published;
- the current branch is the primary release branch;
- the worktree and index are clean;
- the current commit is the recorded, validated candidate;
- the authoritative remote branch points to that same commit;
- the matching release note exists in that commit and has the required title
and sections;
- all required approvals and automated gates succeeded for that commit; and
- no local or remote tag already uses the selected version.
Create the version tag against the guarded commit and verify its target before
publishing it. Publish only the exact intended ref and deliverables. Broad
operations such as `git push --tags` are discouraged because they can publish
unreviewed refs unrelated to the release.
The repository-specific procedure must make these guards executable or give
copyable commands for performing them.
## Immutability And Correction
Published version tags and published deliverables are immutable. Do not move,
overwrite, recreate, or delete a published tag. Do not replace an artifact,
image, package, checksum, signature, provenance statement, or release note
under an already published version.
If validation fails before publication, correct the candidate, rerun the
complete gates, and repeat the publication guards. An unpublished local tag
may be removed after confirming that it was never published or used by a
release channel.
If publication or post-publication verification reveals a defect, leave the
published release intact. Correct the defect on the primary branch, choose a
new patch version or other semantically appropriate new version, write a new
matching release note, and perform the complete release procedure again.
A distribution channel's supported yank, deprecation, or advisory mechanism
may warn consumers away from a defective release, but it must not replace that
release's contents or reuse its version.
## Repository Release Procedure
Every adopting repository must maintain `docs/release.md` as the canonical
maintainer runbook for implementing this policy. It must define, in execution
order:
1. maintainer prerequisites and required access;
2. version selection and compatibility review;
3. release-note creation;
4. candidate preparation and the complete validation command;
5. candidate commit, push, and exact commit recording;
6. publication guards;
7. exact tag and deliverable publication commands;
8. authoritative remote and consumer-facing verification; and
9. failure handling and correction.
The procedure should use copyable, fail-fast commands where practical. It must
name the primary branch, authoritative remote, tag form, published
deliverables, supported build targets, release automation responsibilities,
and consumer verification path. It must link to this policy instead of
restating or redefining its requirements.

310
docs/policy/testing.md Normal file
View File

@@ -0,0 +1,310 @@
# Testing Policy
**Base Policy Version:** 1.0
**Last Updated**: 2026-09-06
## Base Policy Template
This document is the base template for the canonical testing policy,. Repositories that implement this policy should:
1. Review the policy, remove any rules that must not apply to the current project, and add any additional rules that are required by the current project;
2. Update the `Last Updated` field to the current date; and
3. Remove this heading (`Base Policy Template`) and the associated text.
Most projects will not require any revisions to the substantive testing policy rules as written below.
## Purpose
Our tests exist to make **incorrect changes expensive and correct changes cheap**.
We do not optimize for test count, line coverage, exhaustive isolation, or the fewest possible tests. We optimize for sufficient confidence in important behavior while imposing as little unnecessary friction as possible on future development.
## Every test has a cost
Testing is not an unqualified good. Every test imposes both an immediate cost and a continuing lifetime cost.
A test must be:
- written and reviewed;
- understood by future maintainers and coding agents;
- executed in local and CI workflows;
- diagnosed when it fails;
- updated when legitimate behavior changes;
- maintained as fixtures, APIs, and dependencies evolve; and
- removed or rewritten when it becomes redundant, brittle, misleading, or obsolete.
Tests also create cognitive and architectural friction. They can constrain refactoring, duplicate policy, slow feedback loops, add noise to failures, and cause harmless implementation changes to require unrelated edits across the suite.
A test is warranted only when the confidence it provides justifies these costs.
Apply this cost-benefit analysis at two levels:
1. **Per test:** What realistic defect does this test detect, how consequential would that defect be, and is that protection worth the test's lifetime cost?
2. **Across the suite:** Does this collection provide materially more confidence than a smaller, simpler suite would?
The preferred test suite is a **lean suite that provides sufficient confidence in the risks that matter, without redundant or low-value tests**. We seek sufficient confidence with the least unnecessary testing friction, not the fewest possible tests.
Some friction is intentional. Tests should make dangerous changes—such as breaking compatibility, corrupting data, violating security boundaries, or reintroducing subtle bugs—require deliberate review. They should not make ordinary internal changes needlessly expensive.
The cost of a test is not a reason to omit testing by default. Do not cite maintenance cost abstractly. When omitting a plausible test, be able to state why the protected failure is low-risk, already covered, obvious, reversible, or cheaper to detect elsewhere. For consequential, subtle, or difficult-to-observe behavior, the presumption should favor testing.
## Default testing style
Use a **classical/Detroit-style** approach:
- Test observable behavior, resulting state, contracts, and invariants.
- Use real internal collaborators when they are fast and deterministic.
- Use fakes, stubs, or mocks primarily at expensive, nondeterministic, destructive, or external boundaries.
- Prefer package-level behavioral tests over tests coupled to private helpers or internal call sequences.
- Treat exact collaborator interactions as testable behavior only when the interaction itself is a requirement.
Examples of appropriate seams include clocks, randomness, subprocesses, remote APIs, object storage, email, and paid LLM calls.
## Test execution requirements
Tests in the default suite must be deterministic, offline, and independent of real credentials. They must not invoke paid APIs or depend on mutable external services. Tests that require live infrastructure must be explicitly opt-in and clearly separated from the default suite.
Control clocks, randomness, environment variables, and other process-global or machine-specific state when they affect behavior. Tests should be safe to run repeatedly and alongside other tests without depending on execution order or state left by an earlier test.
## What deserves tests
Prioritize tests for:
1. Public and package-level contracts.
2. Domain rules and important invariants.
3. Boundary conditions and malformed input.
4. Failure handling, cancellation, retries, recovery, and partial success.
5. Serialization, schemas, compatibility, and round trips.
6. Previously observed or plausible regressions.
7. Representative integration and end-to-end workflows.
A package-level contract is behavior relied upon by another package or major collaborator, not every observable detail of a package implementation.
For behavior involving **data integrity, destructive operations, compatibility, security, concurrency, idempotency, or recovery**, presume that durable tests are required unless the behavior is already credibly protected at another layer.
Do not add tests merely because a function, branch, or line exists. Do not add a test when the same meaningful risk is already adequately protected elsewhere.
## Choose the right test boundary
Test through the narrowest stable boundary that expresses the behavior clearly.
This is often the package API, but it may instead be:
- a smaller pure function when dense domain logic is most clearly isolated there;
- a package-level operation when several internal collaborators jointly produce the behavior; or
- a larger integration boundary when correctness emerges from interaction with a real dependency.
Do not force all behavior through oversized end-to-end tests. Do not test every private helper merely because it exists. Choose the boundary that gives durable confidence with the least incidental coupling.
## Test behavior, not implementation
A test should protect a decision, contract, or invariant—not memorialize the current implementation.
Before adding or retaining a test, ask:
> What realistic defect would this test catch?
A test is suspect when its main purpose is to detect that someone:
- changed an internal constant;
- renamed or split a private helper;
- reordered equivalent internal operations;
- changed incidental formatting;
- replaced one correct algorithm with another; or
- refactored internal object structure without changing behavior.
Refactoring should normally require no test edits unless the refactored structure is itself part of the contract.
A test can be factually correct and still have negative value. Accurately describing current behavior is not enough; the protected behavior must be important enough to justify the future friction.
## Expected effects of different changes
Use the following expectations when evaluating test failures and test maintenance:
| Change | Expected effect on tests |
|---|---|
| Internal refactor that preserves behavior | Existing tests should normally remain unchanged and continue to pass. |
| Change to an internal default with no contractual significance | Behavioral tests should normally remain unchanged; tests should derive expectations from configuration or relationships rather than duplicate the old value. |
| Intentional change to public behavior, policy, schema, or compatibility guarantees | The relevant tests should be reviewed and changed deliberately. |
| Accidental violation of a contract or invariant | Tests should fail; fix the production code rather than rewriting the tests to accept the defect. |
A test failing is not the same as a test needing to be edited. Many tests may correctly fail because of one production defect. The maintenance smell is a correct internal change that requires unrelated expectation updates throughout the suite.
## Separate mechanism from policy
Configurable thresholds and defaults must not be duplicated throughout the test suite.
For example, do not encode an internal concurrency limit indirectly:
```go
// Production policy:
const maxConcurrency = 4
// Brittle test:
err := startProcesses(5)
require.Error(t, err)
```
Instead, test the mechanism relationally:
```go
const limit = 2
runner := NewRunner(limit)
require.NoError(t, runner.Start(limit))
require.ErrorIs(t, runner.Start(limit+1), ErrTooMuchConcurrency)
```
The test should prove:
- the configured limit is accepted; and
- one beyond the configured limit is rejected.
The production default should be tested exactly only when its literal value is itself a public, operational, safety, protocol, or compatibility requirement.
Apply the same rule to limits, timeouts, capacities, retry counts, and ranges: test relationships and behavior, not duplicated literals.
For concurrency limits, test both kinds of behavior when relevant:
1. **Configuration enforcement:** invalid or excessive requested values are handled correctly.
2. **Runtime enforcement:** observed peak concurrency never exceeds the configured limit.
Use a test-controlled limit and measure the behavior relative to that limit. Do not merely assert today's default value.
## Avoid semantic duplication across layers
Each behavior should have a clear test owner.
- Parser tests own parsing cases.
- Validator tests own validation rules.
- Domain tests own transformations and invariants.
- Adapter tests own external integration behavior.
- Orchestrator tests own coordination and failure propagation.
- CLI tests own argument and configuration mapping.
- End-to-end tests prove that representative assembled workflows work.
Higher-level tests should not repeat every lower-level case. A single intentional policy change should not require unrelated edits across many test files.
Tests that are individually reasonable may still be collectively redundant. Evaluate the marginal value of each additional test in light of the protection already provided by the rest of the suite.
## Use test doubles deliberately
Choose the least elaborate test double that provides the required control or observation.
As a default:
1. Prefer real collaborators when they are fast and deterministic.
2. Use small in-memory fakes when realistic stateful behavior is helpful.
3. Use stubs when a dependency only needs to provide controlled responses.
4. Use mocks when the interaction itself is contractual.
Mocks are appropriate when the contract includes facts such as:
- a notification is sent exactly once;
- a transaction is committed only after successful writes;
- cancellation reaches a subprocess;
- an expensive API is called no more than once; or
- a security audit event is emitted.
Do not use mocks merely to isolate every object or reproduce the implementation's call graph.
## Go-specific guidance
Use:
- table-driven tests for meaningful behavioral categories and boundaries;
- `t.TempDir()` for real filesystem behavior;
- `httptest.Server` for realistic HTTP interactions;
- fuzz tests for parsers, normalization, path handling, and broad input spaces;
- golden files only when the complete output is intentionally stable;
- integration tests where correctness depends on component interaction; and
- a small number of representative end-to-end tests.
Avoid exact error-string assertions unless the wording is itself contractual. Prefer `errors.Is`, `errors.As`, typed errors, or structured error fields.
At CLI boundaries, prefer exit classifications, structured output, and the smallest stable semantic fragment needed to identify the error. Do not snapshot complete diagnostic wording unless it is contractual.
Golden-file updates must require an explicit local flag. CI must not update golden files automatically, and reviewers must inspect the semantic diff before accepting an update.
Keep tests readable and direct. Test helpers and fixture frameworks must earn their own maintenance cost; do not build elaborate test infrastructure for small or isolated needs.
## Coverage
Coverage is a diagnostic, not a target.
Use it to find untested critical branches and unexpectedly weak packages. Do not write low-value tests solely to increase a percentage, and do not infer test quality from coverage alone.
Pure domain logic will often warrant higher coverage than CLI wiring or external adapters. Uneven coverage is acceptable when it reflects risk.
Increasing coverage is valuable only when the newly covered behavior protects a meaningful risk at an acceptable cost.
## Regression tests
A bug fix should normally include a regression test that fails before the fix and passes afterward.
Retain the test when the defect could realistically recur and its consequences justify the ongoing cost. Prefer the narrowest durable test of the violated contract or invariant; do not preserve accidental implementation details from the original bug.
Not every historical bug requires a permanent test. If the underlying design has made recurrence impossible, the test has become redundant, or a stronger invariant test now subsumes it, remove or consolidate it.
## Deleting or rewriting tests
Tests are maintained code, not permanent historical artifacts.
Delete or rewrite a test when its maintenance cost exceeds the confidence it provides.
Strong candidates include tests that:
- require updates after harmless internal changes;
- directly assert private constants without protecting a real contract;
- duplicate the same policy across several layers;
- verify mock choreography rather than outcomes;
- snapshot large amounts of incidental output;
- test trivial private helpers already exercised through stable package behavior;
- protect risks already covered more effectively elsewhere;
- are flaky, misleading, obsolete, or disproportionately expensive to diagnose; or
- no longer correspond to a plausible failure mode.
Several brittle tests may encode one genuine requirement. Replace them with one durable behavior-level or invariant test rather than preserving all of them.
Deleting a low-value test can improve the quality of the suite by reducing noise, maintenance burden, and friction around legitimate change.
## Reviewing a proposed test
Use the following questions when the value, boundary, or durability of a proposed test is not self-evident. Significant test additions should be reviewable against them, but written answers are not required for every routine test.
1. What realistic defect would it catch?
2. How likely is that defect?
3. How consequential would it be?
4. Is the behavior already protected elsewhere?
5. At which layer should this behavior be owned?
6. Does the test assert a durable contract or an incidental implementation detail?
7. Could the implementation be refactored without changing the behavior and without editing this test?
8. What should cause this test to fail?
9. What legitimate changes should not cause this test to fail?
10. What ongoing maintenance, execution, and diagnostic cost will the test impose?
11. Is there a smaller or more direct test that protects the same risk?
Do not add the test when its expected lifetime cost exceeds its expected protective value.
When deciding not to test plausible behavior, record or be able to explain why the risk is low, already protected, obvious, reversible, or cheaper to detect elsewhere.
## Definition of sufficient
A test suite is sufficient when:
- important contracts and invariants are protected;
- meaningful boundaries and failure modes are exercised;
- realistic and consequential regressions are credibly protected against silent recurrence;
- behavior involving data integrity, destructive operations, compatibility, security, concurrency, idempotency, and recovery is credibly protected;
- important external boundaries have realistic integration coverage;
- representative complete workflows are tested;
- failures provide useful signal rather than redundant noise;
- legitimate internal changes usually do not require test edits; and
- additional tests would mostly repeat existing protection or preserve inconsequential implementation details.
Sufficiency is a risk judgment, not a coverage percentage or test count. Reassess it as the application, its users, and the consequences of failure evolve.
The governing rule is:
> Test heavily where failure is consequential, subtle, or difficult to detect after the fact. Test lightly where failure is obvious, reversible, and inexpensive—and retain no test whose lifetime cost exceeds the confidence it provides.