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¶
- Every configuration of Details satisfies every rule of Details.
- This repository satisfies the rules Links and Manual tests,
examples/andbuild/excluded. - A violation names the rule, the configuration, the file, and the line where the rule has one. One run reports every violation.
make invariant-testchecks requirements 1 and 2.- The pipeline of this repository has an optional job
invariant-testin the stagesmokethat runsmake invariant-test.
Non-Functional Requirements¶
None.
Technical Requirements¶
- The invariants run without Docker and without a tool beyond the Go toolchain and Git.
- 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-localdo - 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¶
- Every configuration of Details: no violation of any rule.
- This repository: no violation of the rules Links and Manual tests.
- 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 |
- Two planted faults in different files: the report names both.
- After
make invariant-test, passed or failed:git status --shortlists nothing new. .gitlab-ci.ymlof this repository: jobinvariant-testin stagesmoke, run manually and allowed to fail, runsmake invariant-test.
Decisions¶
- 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
Makefilelayout, stay where they are. - 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.
- 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.
- 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 teststays fast. Consequence: a fault shows only when someone runsmake invariant-testor the job.