This is where the KeepRight spec lives, at some point this will be made public and will become and is the point of truth for the spec. We'll figure out a way to take recommendations and suggestions.
The JSON spec is the source of truth. Generated text, Markdown, XML, and HTML views are derived from it.
The spec is a tree of items. Top-level items have kind: "section"; sections can contain questions, groups, notes, and eventually nested sections. Groups can contain questions, notes, and other groups. Questions are the answerable fields and declarations store answers by question id.
displayCode is intentionally not part of the protocol. Implementers can derive or hide visual numbering in their own UI; the stable machine identifier is id, and the human-facing question text is label.
Important: Only spec.json should be edited by hand. Everything under i18n/ (and anything else produced by the renderer) is generated by scripts/render_spec.py. Those files are a convenience for reading and tooling; they are not authoritative.
This is early and a bit scrappy, but the intent is:
spec.jsonis always the latest working spec at the root of the repo.versions/spec.x.y.z.jsonis a copy of a specific released spec version. Once we put one there, we should treat it as frozen.versions/spec.x.y.latest.json,versions/spec.x.latest.json, andversions/spec.latest.jsonare moving convenience copies for "latest patch in this minor", "latest in this major", and "latest overall".
So if a tool wants the exact spec forever, it can point at something like versions/spec.0.0.2.json. If it wants the latest compatible-looking thing, it can point at versions/spec.0.0.latest.json or versions/spec.0.latest.json. If it wants whatever we are currently throwing around, it can point at spec.json or versions/spec.latest.json.
Declarations should record the exact schemaVersion and specVersion they were created against. That way, even if a tool follows a latest file while making declarations, each saved declaration still says which exact version it actually used.
Schemas follow the same rough idea, but by schemaVersion rather than specVersion. The spec content may change lots of times while the grammar stays the same, so we do not need a new schema file for every spec content release.
When specVersion changes, run:
python3 scripts/version_spec.pyThat script copies spec.json into versions/ using the exact specVersion, refreshes the moving latest spec copies, and does the same for schemas using schemaVersion. If an exact version file already exists but the current file is different, the script stops rather than overwriting it. That usually means the version needs bumping first.
If the schema changes, we still edit the live schema files by hand for now and bump meta.schemaVersion in spec.json. Then scripts/version_spec.py will snapshot those schema files under the new schema version.
Branch protection: We maybe need to do something cool and good here.
Generated locale-specific views live under i18n/<locale>/, for example:
i18n/en-GB/spec.txti18n/en-GB/spec.mdi18n/en-GB/spec.xmli18n/en-GB/spec.htmli18n/en-GB/spec_form.html
Regenerate them locally (optional, same as CI) with:
python3 scripts/render_spec.pyschemaVersion this is the 'shape/grammar' of the spec.json - it deals with which field types exist and so on - basically everything on the left side of the ':' colons.
specVersion is the 'content' of the spec.json - all the stuff on the right of the ':' colons. If we add or edit questions, answers or logic then we bump the specVersion
In theory, if the schema hasn't changed, say we're on 1.0.23 and someone write a declaration against that and specVersion 1.0.5 - then we add more questions and answers and change others for a new release 1.1.0, a validator written to validate a version 1.0.23 could still pass a declaration writen for an earlier release.
But if we added a new field to the spec like data then we'd bump the schemaVersion.
release is a handy human version we can just talk about, even though the various versions may change.
About spec.json's grammar — what tooling needs to know how to parse.
- MAJOR — a tool written for the previous schema may stop working.
- Renaming
answerType→inputType. - Removing support for a
kind(e.g. droppingnote). - Changing how
visibleWhenworks (e.g. requiring an array).
- Renaming
- MINOR — additive only; old tools keep working, just ignore new bits.
- New
answerTypelikedate. - New optional field on a question, e.g.
helpText. - New condition operator like
notEquals.
- New
- PATCH — schema-level cosmetic; comments, formatting, internal docs.
About the spec content — what an editor changes day-to-day.
- MAJOR — a previous declaration may no longer validate.
- Removing a question.
- Renaming an
id(m1→material_kind). - Removing an option from a multi-choice question.
- Making an optional question required.
- MINOR — additive; old declarations still valid.
- Adding a new optional question.
- Adding a new option to a question.
- Adding a follow-up.
- Adding a new section.
- PATCH — meaning-preserving rewording.
- Fixing typos, clarifying labels, reordering options that have no semantic order.
At the moment questions have ids, like m1 etc. If we are fixing a typo in a question or cleaning up the grammer, fixing a translation then the id should stay the same. If we are fundamentally changing a question, we should change the id to a new unique one; basically 'deleting' the old question and adding a new one. ids shouldn't be reused.
I know we can always match a declaration with the version of the spec it was created with to get back to the question, but probably best not to introduce potential confusion.