| name | contract-invariant |
| description | Before modifying src/citeformer/grammar/, src/citeformer/core.py, or src/citeformer/verify/report.py, remember these are §10 contract files. Touching them requires ceremony — not silent edits. |
Contract invariant — §10 ceremony
Three files carry §10 contracts. Edits require a ceremony, not a silent commit.
§10.1 — src/citeformer/grammar/
Changes the grammar shape (cite-id rule) or the semantics of a policy (required / quotes_only / auto).
Ceremony:
- Regenerate
tests/unit/test_grammar_builder.py snapshots via pytest --force-regen tests/unit/test_grammar_builder.py.
- Classify the change:
- Adding a new policy → minor bump.
- Changing an existing policy's semantics → major bump.
- Changing the
cite-id rule shape → major bump.
- Add a "Contracts (§10)" section to
CHANGELOG.md [Unreleased] noting the change.
- Fill the PR template's "Invariant touched?" section with §10.1.
§10.2 — src/citeformer/core.py (Source.metadata type) + src/citeformer/render/csl.py
Changes the expected CSL-JSON shape that Source.metadata must conform to (and that the home-grown formatters in src/citeformer/render/formatters/ consume — see ADR-004).
Ceremony:
- Regenerate the CSL rendering snapshots:
tests/unit/test_render_csl.py (4 core CSL types × 6 styles) and tests/unit/test_csl_suite.py (50-case fixture × 6 formatters = 300 snapshots) via pytest --force-regen tests/unit/test_render_csl.py tests/unit/test_csl_suite.py; check tests/unit/test_render_styles.py (name registry + classification) still passes.
- Classify:
- Passing through a new optional CSL field → minor.
- Renaming / removing a field we read → major.
- CHANGELOG + PR template as above.
§10.3 — GenerationResult + VerificationReport
Pydantic models carrying schema_version (currently 3). Any shape change bumps schema_version AND requires PR-description callout.
Ceremony:
- Bump
schema_version on the owning model (3 → 4, etc.).
- Update the canonical-instance snapshots in
tests/integration/test_schemas.py (covers both models; regen via pytest --force-regen tests/integration/test_schemas.py).
- Document migration path in CHANGELOG if the change is breaking.
- CHANGELOG + PR template as above.
The meta-rule
If you're editing one of these files and you haven't thought about which contract you're touching — stop. Run /contract-check first. "I didn't think it was a contract change" is how breaking changes ship as patches and ruin everyone's week.