Skip to content

Capability 015: Output Invariants

Status: Accepted

Context

A generated repository must work as generated (mission statement). Most tests of the generator check one example: a file of one profile contains a line. They find what their author thought of. Faults in template content, such as a link to a file that a module does not generate or a glob that misses a directory, show up in profiles and module selections that no example covers. The golden manifests show that the output changed, not whether it is right.

An invariant is a rule that holds for every generated file of its kind, in every profile. This capability defines these rules, the configurations they are checked against, and how a violation is reported.

References

Details

Configurations, each for every profile and both values of ci:

Configuration modules Further fields
Default absent absent
Minimal [scaffold] absent
Full every module documentation: extended, requirements: external, assistants: [claude, copilot]

In addition, one mono-repository with every profile, per value of ci, in the configurations Default and Full.

Rules:

Rule Applies to Holds when
Rendered Every file No file name ends in .tmpl, and no file contains <no value>
Final newline Every text file The file ends with a line break
Parses *.json, *.yml, *.yaml, *.toml, *.xml The file parses; JSON with comments where its tool reads them, such as tsconfig.json and *.jsonc
Links Every Markdown file Every relative link and image points to an existing file or directory, or to a page the docs site builds from one; the anchor is not checked
Linted Every Markdown file A glob of a markdownlint configuration of the repository or of its artifact matches it; an ignore is a deliberate exclusion
Scripts *.sh The file starts with #! and is executable
Stable The repository After a commit, repo check lists no file
Manual tests Every capability specification Every file it refers to as manual test test/manual/<file> exists

Functional Requirements

  1. Every configuration of Details satisfies every rule of Details.
  2. This repository satisfies the rules Links and Manual tests, examples/ and build/ excluded.
  3. A violation names the rule, the configuration, the file, and the line where the rule has one. One run reports every violation.
  4. make invariant-test checks requirements 1 and 2.
  5. The pipeline of this repository has an optional job invariant-test in the stage smoke that runs make invariant-test.

Non-Functional Requirements

None.

Technical Requirements

  1. The invariants run without Docker and without a tool beyond the Go toolchain and Git.
  2. The configurations are generated into the temporary directory of the operating system and removed after the test, also when it fails. The working tree stays unchanged.

Error Handling

None.

Out of Scope

  • Running the linters of the generated repositories; the smoke tests and make ci-local do
  • Rules for one kind of file that one capability owns, such as the layout of the Makefile; they stay with the scenarios of that capability
  • Combinations of modules other than those of Details
  • Links to the internet

Acceptance Criteria

  1. Every configuration of Details: no violation of any rule.
  2. This repository: no violation of the rules Links and Manual tests.
  3. A configuration with a planted fault, per rule: the report names the rule, the configuration, and the file.
Rule Planted fault
Rendered README.md contains <no value>
Final newline Makefile ends without a line break
Parses devbox.json ends after {
Links README.md links to missing.md
Linted notes/a.md outside every glob
Scripts A *.sh file without the executable bit
Stable A generator-owned file changed after the commit
Manual tests A specification names test/manual/missing.md
  1. Two planted faults in different files: the report names both.
  2. After make invariant-test, passed or failed: git status --short lists nothing new.
  3. .gitlab-ci.yml of this repository: job invariant-test in stage smoke, run manually and allowed to fail, runs make invariant-test.

Decisions

  1. Rules over every file, not per capability. A rule that holds for every file of a kind finds faults in profiles nobody thought of. A rule about one capability's file says more where that capability is specified. Consequence: the scenarios that check one file across profiles, such as the Makefile layout, stay where they are.
  2. The invariants check structure, the linters check style. The invariants run without Docker on every machine. markdownlint, ShellCheck, and the linters of each language need their images. Consequence: the rule Linted checks that a linter sees each Markdown file; whether the file passes shows the pipeline of the generated repository.
  3. Three configurations, not every combination. The modules and the further fields give hundreds of combinations. A dangling reference shows with the fewest modules, a collision with the most. Consequence: a fault that needs one specific combination of modules stays undetected.
  4. An own target and an optional job, not part of make test. Every configuration is generated and checked one after the other, so the run grows with every profile. make test stays fast. Consequence: a fault shows only when someone runs make invariant-test or the job.