RUL-030 — Generated artifacts belong in git

Ruling

Generated artifacts belong in git (user ruling, 2026-08-13).

A generated artifact is a snapshot of project state at a commit. If it is not in the commit, the commit does not describe the project: a reader cannot tell whether a missing AST snapshot means "not regenerated yet" or "we do not keep those", and two checkouts of the same SHA can disagree about what the codebase contains.

The boundary

"Generated" cannot mean every machine-written byte. reports/test-durations.ndjson is 282 MB of append-only per-run telemetry, and *.warmcache is a machine-specific pickle whose bytes differ per host at an identical commit. Committing those would bloat the repo without describing it. A ruling with no boundary is unenforceable, so the boundary is mechanical — three properties, all three required:

  1. Derived — reproducible by running a named generator; never hand-authored.
  2. Read — something consumes it: a gate, a doc @import, a report, a human.
  3. A function of the commit — regenerating it at the same commit on another machine yields the same content.

Property 3 is what does the work. It excludes machine-local caches, per-run telemetry, wall-clock logs and runtime locks, and it does so on a test anyone can apply rather than on taste.

Anything excluded is listed in NOT_ARTIFACTS in the gate with a reason, so the boundary is visible where it is enforced instead of implicit in a .gitignore line nobody reads.

Why

The dispute was concrete and recurring. plans/ast-*.json and reports/run-tests-latest.json were committed by some sessions (66cd4191, 6626dee4) and deliberately withheld by others under an earlier handover convention. The result was a working tree that carried a permanent diff nobody could resolve, resurfacing on every ast-sync, with each session re-litigating it.

Applying the ruling immediately found a live violation the convention had created: docs/CODEBASE_MAP_FULL.md was gitignored (.gitignore:223, "too big to track/import"). It is derived, it is read (grep -n <symbol> plus ranged reads — the documented way to look up a symbol), and it is a function of the commit. It is 1.6 MB. The old exclusion conflated tracking it with @importing it, and only the latter was ever the problem: the two-tier map exists precisely so the big tier is not imported.

What the gate checks

For every registered artifact:

A tracked artifact passes whether or not it is currently dirty. Regeneration cadence is a workflow question, not this ruling; requiring clean would turn every session red the moment a session-start hook regenerates the codebase map.

The gate refuses to report a pass it cannot justify. An empty registry, or a registry whose entries all resolve to nothing on disk, exits 2 (INVALID) rather than 0, and every run prints how many artifacts it examined. That is a checker must prove it looked applied to this gate itself — a "0 violations" that could equally mean "0 files examined" is exactly the defect class this project keeps paying for.

Enforcement