Single Source of Truth (SSOT) Policy¶
This repository follows a Single Source of Truth discipline to prevent metadata drift across release-facing files (PyPI, citation files, Zenodo, codemeta, documentation, the LaTeX paper). This document describes which files are sources and which are derived, and how to make changes safely.
If you're a contributor opening a PR, the short version is: never edit a derived file directly. Edit the source, then run the appropriate sync or check script. CI will catch drift either way.
1. The SSOT axes¶
The project tracks seven categories of metadata that must stay consistent:
| Axis | Source of truth | Derived files | Sync mechanism |
|---|---|---|---|
| Project version | git tag (vX.Y.Z) |
pyproject.toml [project].version, CITATION.cff, .zenodo.json, codemeta.json |
make bump VERSION=X.Y.Z (scripts/bump_version.py) |
| Release-facing identifiers (DOIs, repo URL, arXiv ID) | release.toml |
README.md badge, CITATION.cff, .zenodo.json, codemeta.json, paper/main.tex, paper/SUBMISSION.md, demo/README.md, demo/app.py, demo/zerogpu/app.py |
make sync-metadata (scripts/sync_release_metadata.py). Note: Badge and codemeta.json intentionally use the concept DOI (10.5281/zenodo.19972258) while CITATION.cff and BibTeX use the version DOI (10.5281/zenodo.22724258) — this is a deliberate split to provide a stable landing-page DOI for linking while ensuring citations resolve to the specific version. |
| Python version floor | pyproject.toml [project].requires-python |
[tool.ruff].target-version, [tool.mypy].python_version, [tool.basedpyright].pythonVersion, [project].classifiers, .github/workflows/ci.yml matrix |
scripts/check_python_version_consistency.py (validates only — manual fix) |
| Author / license / keywords | pyproject.toml [project] |
CITATION.cff, codemeta.json, .zenodo.json |
scripts/check_metadata_ssot.py (validates only — manual fix) |
| Positioning hero & comparison (Why, table, 30-sec example) | README.md between <!-- ssot:positioning-start --> and <!-- ssot:positioning-end --> |
docs/index.md |
mkdocs-include-markdown-plugin (build-time inclusion) |
| Getting started (install + quickstart) | README.md between <!-- ssot:getting-started-start --> and <!-- ssot:getting-started-end --> |
docs/getting-started.md |
mkdocs-include-markdown-plugin (build-time inclusion) |
LLM environment variables (EMOTIONAL_MEMORY_LLM_*) |
docs/contributing/llm-environment.md |
README.md mirror, CLAUDE.md, CONTRIBUTING.md |
manual mirror (validates by review; see §5.4) |
2. Rules¶
- Never edit a derived file directly. Find its source in the table above and edit there.
- If you find a discrepancy, open an issue or fix the source — do not fix the derived file in isolation, since it will be overwritten on the next sync.
- New SSOT axes: if you introduce a new piece of metadata that ends up in more than one file, add it to this document and either extend the appropriate sync script or add a check script.
- CI enforces drift: the
meta-integrityjob in.github/workflows/ci.ymlruns all the validation scripts above. A red CI on drift is intentional, not noise.
3. Practical workflows¶
Bumping the version¶
make bump VERSION=0.11.0 DATE=2026-06-15
git add -A
git commit -m "chore(release): bump to v0.11.0"
git tag v0.11.0
git push --tags
The bump_version.py script propagates the version into pyproject.toml, CITATION.cff, CHANGELOG.md, codemeta.json, and .zenodo.json.
After a new Zenodo deposit (per-release DOI changes)¶
# 1. Edit release.toml: update version_doi = "10.5281/zenodo.NNNNNNNN"
# 2. Propagate
make sync-metadata
git add -A
git commit -m "chore(release): sync metadata after Zenodo deposit"
Changing the Python minimum version¶
# 1. Edit pyproject.toml: requires-python = ">=3.12"
# 2. See what else needs updating
uv run python scripts/check_python_version_consistency.py --fix-suggest
# 3. Apply suggestions manually (this is a deliberate, audited change):
# - tool.ruff.target-version = "py312"
# - tool.mypy.python_version = "3.12"
# - tool.basedpyright.pythonVersion = "3.12"
# - remove "Python :: 3.11" classifier
# - drop "3.11" from .github/workflows/ci.yml matrix
# - update CONTRIBUTING.md install instructions
# 4. Verify
uv run python scripts/check_python_version_consistency.py
Changing author / license / keywords¶
# 1. Edit pyproject.toml [project]
# 2. Manually update CITATION.cff, codemeta.json, .zenodo.json
# 3. Verify
uv run python scripts/check_metadata_ssot.py
(A future iteration may extend sync_release_metadata.py to do this automatically. For now, validation only — these fields change rarely enough that the cost-benefit favours checking over auto-writing.)
Updating the positioning hero (Why / comparison table / 30-sec example)¶
# 1. Edit README.md between the SSOT markers:
# <!-- ssot:positioning-start -->
# ... your changes ...
# <!-- ssot:positioning-end -->
# 2. Verify locally
uv run mkdocs build --strict
# The docs site will pick up the change automatically on next deploy.
4. Files marked as "managed"¶
The following files contain machine-managed sections. Each carries a header comment naming the source script:
CITATION.cff— version / DOI / date sections managed bysync_release_metadata.py.zenodo.json— version / publication_date managed bysync_release_metadata.pycodemeta.json— managed bysync_release_metadata.pypaper/main.tex— DOI / URL / arxiv_id macros tagged with[ssot:*]comments, managed bysync_release_metadata.pydemo/app.py— constants tagged with[ssot:*]comments, managed bysync_release_metadata.pydemo/zerogpu/app.py— same[ssot:*]constants asdemo/app.pydocs/index.md— positioning hero block included fromREADME.mdat build timedocs/getting-started.md— install + quickstart block included fromREADME.mdat build timeREADME.md/CLAUDE.md/CONTRIBUTING.md—EMOTIONAL_MEMORY_LLM_*tables are mirrors ofdocs/contributing/llm-environment.md(kept in sync manually; deliberate duplication per §5.4)
5. Adding a new SSOT axis¶
When you find yourself copying the same piece of metadata into a second file, stop and ask: should this be SSOT-managed?
Decision tree:
- Does the value change frequently (per release, per quarter)? → yes: invest in a sync script.
- Does it change rarely but its drift would be embarrassing (e.g. author affiliation, license)? → at minimum, add a validator to
check_metadata_ssot.pystyle. - Is it duplicated only because of MkDocs / PyPI / Zenodo schema constraints? → use a build-time include where possible (MkDocs include-markdown, or a Jinja template).
- None of the above → leave it duplicated; not every duplication is harmful. Document the deliberate choice in this file.
Always:
- Add the new axis to the table in §1 of this document.
- Add a CI step under meta-integrity so drift is caught early.
- Add an entry to §4 explaining what file is managed and by which script.