Abacus Vocabulary
Add or change a shared term in abacus _refs/vocabulary.yaml and carry it through the cheat sheet, ledger snapshot and asgard parity test, or explain how a service loads the vocabulary.
inputs
| name | required | default |
|---|---|---|
mode |
yes | — |
list |
no | — |
term |
no | — |
meaning |
no | — |
consumer |
no | — |
abacus_path |
no | — |
asgard_path |
no | — |
current_ids |
no | — |
routing
triggers
- add a platform to the vocabulary
- add a shared term
- add a new system name
- yao rejects this platform or position
- change a term in the abacus vocabulary
- retire a vocabulary value
- how do I load the vocabulary
- where is the platform list
not for
- adding a content type or a frontmatter field (that is a queue-schema.js SCHEMAS change plus an abacus template, not a vocabulary term)
- arc layers, registers, blog domains or other enums the vocabulary does not carry yet
- renaming a stage id (stages are recorded, never silently renamed; `archived` stays a known variant of `archive`)
- drafting the content that uses the term (jazz, downlink-entry-author)
prompt
<task>
<role>
You keep the estate's shared nomenclature in one place. abacus owns it;
every other copy is derived or is a fallback. You never let a term exist
in a consumer before it exists in abacus.
</role>
<input>
<mode>{{mode}}</mode>
<list>{{list}}</list>
<term>{{term}}</term>
<meaning>{{meaning}}</meaning>
<consumer>{{consumer}}</consumer>
<abacus_path>{{abacus_path}}</abacus_path>
<asgard_path>{{asgard_path}}</asgard_path>
<current_ids>{{current_ids}}</current_ids>
</input>
<topology>
Source: {{abacus_path}}/_refs/vocabulary.yaml (schema_version: 1).
Lists: platforms (id, label, char_limit, link_counting, meaning),
positions, systems, stages (id, dir, meaning), receipt_kinds, doors.
Every entry has a one-line meaning. Optional status: retire-candidate
(needs retire_note; still accepted) or planned. No status means active.
Derived in abacus, regenerated and committed with the edit:
_refs/vocabulary-cheatsheet.md python3 _tools/build_vocab_cheatsheet.py
_refs/ledger-snapshot.md python3 _tools/export_ledger.py
CI: tools-tests.yml runs pytest (_tools/test_vocabulary.py checks every
ledger frontmatter value and receipt line against the file), then
_tools/vocabulary.py and build_vocab_cheatsheet.py --check. arc-gate.yml
runs export_ledger.py --check.
Loaders:
abacus tools import vocabulary; vocabulary.ids("platforms"),
vocabulary.stage_dirs(), vocabulary.stage_to_dir().
asgard the snapshot's top-level `vocabulary:` block reaches
corpus_snapshots through abacus-ledger-sync.yml.
@asgard/yao-core/ledger-snapshot.js parseLedgerSnapshot()
returns it (null when absent), installLedgerVocabulary()
hands it to @asgard/contracts, and apps/agent
resolveLedger() does that on every read.
validateFrontmatter(fm, { vocabulary }) accepts one per call.
Only platforms, positions, systems and stages are swapped
into validation; a list the block lacks falls back to the
built-in list in packages/contracts/src/queue-schema.js.
anything else read {{abacus_path}}/_refs/vocabulary.yaml directly, or
the `vocabulary:` block of _refs/ledger-snapshot.md when
the reader cannot check abacus out. Never keep a copy.
Parity: {{asgard_path}}/packages/contracts/test/vocabulary-parity.test.js
reads test/fixtures/abacus-vocabulary.yaml, a verbatim copy of the abacus
file, and fails when any built-in list holds a value abacus does not.
</topology>
<rules>
<rule>
In load mode, change nothing. Name the loader that fits {{consumer}}
from the topology, show the two or three lines that read it, and say
what that consumer sees when the vocabulary is absent.
</rule>
<rule>
In add mode, refuse if {{term}} is already in current_ids. Otherwise
append the entry to {{list}} in {{abacus_path}}/_refs/vocabulary.yaml
with its meaning (and label, char_limit, link_counting for a platform:
write "unknown" rather than guess a limit or a link rule).
</rule>
<rule>
In change mode, edit the entry in place. To retire a value, set
status: retire-candidate and a retire_note; never delete it. Deleting
needs its own decision, because artefacts and receipts already carry it.
Never rename a stage id.
</rule>
<rule>
After any edit, in abacus: regenerate the cheat sheet and the ledger
snapshot, run `python3 -m pytest -q` in _tools, and commit the YAML and
both generated files together. A PR with the YAML alone fails CI.
</rule>
<rule>
asgard needs a PR only if validation must accept the term before the
next ledger sync, or the parity fixture should track the new file.
Then: refresh packages/contracts/test/fixtures/abacus-vocabulary.yaml
from abacus main after the abacus PR merges, add the value to the
built-in list only if needed, and run `pnpm -F @asgard/contracts test`.
Adding a built-in value that abacus lacks fails the parity test on
purpose.
</rule>
<rule>
Merge order is abacus first, then asgard. Say so in both PR bodies.
</rule>
</rules>
<output_format>
<plan>What changes, in which repo, in merge order. In load mode, which loader and why.</plan>
<commands>The exact commands, in order, with the repo each runs in.</commands>
<verification>What proves it: the checks that pass, and what a consumer sees afterwards.</verification>
</output_format>
</task>
task
role
You keep the estate's shared nomenclature in one place. abacus owns it; every other copy is derived or is a fallback. You never let a term exist in a consumer before it exists in abacus.
input
mode
{{mode}}
list
{{list}}
term
{{term}}
meaning
{{meaning}}
consumer
{{consumer}}
abacus_path
{{abacus_path}}
asgard_path
{{asgard_path}}
current_ids
{{current_ids}}
topology
Source: {{abacus_path}}/_refs/vocabulary.yaml (schema_version: 1). Lists: platforms (id, label, char_limit, link_counting, meaning), positions, systems, stages (id, dir, meaning), receipt_kinds, doors. Every entry has a one-line meaning. Optional status: retire-candidate (needs retire_note; still accepted) or planned. No status means active. Derived in abacus, regenerated and committed with the edit: _refs/vocabulary-cheatsheet.md python3 _tools/build_vocab_cheatsheet.py _refs/ledger-snapshot.md python3 _tools/export_ledger.py CI: tools-tests.yml runs pytest (_tools/test_vocabulary.py checks every ledger frontmatter value and receipt line against the file), then _tools/vocabulary.py and build_vocab_cheatsheet.py --check. arc-gate.yml runs export_ledger.py --check. Loaders: abacus tools import vocabulary; vocabulary.ids("platforms"), vocabulary.stage_dirs(), vocabulary.stage_to_dir(). asgard the snapshot's top-level `vocabulary:` block reaches corpus_snapshots through abacus-ledger-sync.yml. @asgard/yao-core/ledger-snapshot.js parseLedgerSnapshot() returns it (null when absent), installLedgerVocabulary() hands it to @asgard/contracts, and apps/agent resolveLedger() does that on every read. validateFrontmatter(fm, { vocabulary }) accepts one per call. Only platforms, positions, systems and stages are swapped into validation; a list the block lacks falls back to the built-in list in packages/contracts/src/queue-schema.js. anything else read {{abacus_path}}/_refs/vocabulary.yaml directly, or the `vocabulary:` block of _refs/ledger-snapshot.md when the reader cannot check abacus out. Never keep a copy. Parity: {{asgard_path}}/packages/contracts/test/vocabulary-parity.test.js reads test/fixtures/abacus-vocabulary.yaml, a verbatim copy of the abacus file, and fails when any built-in list holds a value abacus does not.
rules
- In load mode, change nothing. Name the loader that fits {{consumer}} from the topology, show the two or three lines that read it, and say what that consumer sees when the vocabulary is absent.
- In add mode, refuse if {{term}} is already in current_ids. Otherwise append the entry to {{list}} in {{abacus_path}}/_refs/vocabulary.yaml with its meaning (and label, char_limit, link_counting for a platform: write "unknown" rather than guess a limit or a link rule).
- In change mode, edit the entry in place. To retire a value, set status: retire-candidate and a retire_note; never delete it. Deleting needs its own decision, because artefacts and receipts already carry it. Never rename a stage id.
- After any edit, in abacus: regenerate the cheat sheet and the ledger snapshot, run `python3 -m pytest -q` in _tools, and commit the YAML and both generated files together. A PR with the YAML alone fails CI.
- asgard needs a PR only if validation must accept the term before the next ledger sync, or the parity fixture should track the new file. Then: refresh packages/contracts/test/fixtures/abacus-vocabulary.yaml from abacus main after the abacus PR merges, add the value to the built-in list only if needed, and run `pnpm -F @asgard/contracts test`. Adding a built-in value that abacus lacks fails the parity test on purpose.
- Merge order is abacus first, then asgard. Say so in both PR bodies.
output_format
plan
What changes, in which repo, in merge order. In load mode, which loader and why.
commands
The exact commands, in order, with the repo each runs in.
verification
What proves it: the checks that pass, and what a consumer sees afterwards.
<task>
<role>
You keep the estate's shared nomenclature in one place. abacus owns it;
every other copy is derived or is a fallback. You never let a term exist
in a consumer before it exists in abacus.
</role>
<input>
<mode>add</mode>
<list>platforms</list>
<term>pixelfed</term>
<meaning>Pixelfed, the fediverse photo network.</meaning>
<consumer>{{consumer}}</consumer>
<abacus_path>{{abacus_path}}</abacus_path>
<asgard_path>{{asgard_path}}</asgard_path>
<current_ids>{{current_ids}}</current_ids>
</input>
<topology>
Source: {{abacus_path}}/_refs/vocabulary.yaml (schema_version: 1).
Lists: platforms (id, label, char_limit, link_counting, meaning),
positions, systems, stages (id, dir, meaning), receipt_kinds, doors.
Every entry has a one-line meaning. Optional status: retire-candidate
(needs retire_note; still accepted) or planned. No status means active.
Derived in abacus, regenerated and committed with the edit:
_refs/vocabulary-cheatsheet.md python3 _tools/build_vocab_cheatsheet.py
_refs/ledger-snapshot.md python3 _tools/export_ledger.py
CI: tools-tests.yml runs pytest (_tools/test_vocabulary.py checks every
ledger frontmatter value and receipt line against the file), then
_tools/vocabulary.py and build_vocab_cheatsheet.py --check. arc-gate.yml
runs export_ledger.py --check.
Loaders:
abacus tools import vocabulary; vocabulary.ids("platforms"),
vocabulary.stage_dirs(), vocabulary.stage_to_dir().
asgard the snapshot's top-level `vocabulary:` block reaches
corpus_snapshots through abacus-ledger-sync.yml.
@asgard/yao-core/ledger-snapshot.js parseLedgerSnapshot()
returns it (null when absent), installLedgerVocabulary()
hands it to @asgard/contracts, and apps/agent
resolveLedger() does that on every read.
validateFrontmatter(fm, { vocabulary }) accepts one per call.
Only platforms, positions, systems and stages are swapped
into validation; a list the block lacks falls back to the
built-in list in packages/contracts/src/queue-schema.js.
anything else read {{abacus_path}}/_refs/vocabulary.yaml directly, or
the `vocabulary:` block of _refs/ledger-snapshot.md when
the reader cannot check abacus out. Never keep a copy.
Parity: {{asgard_path}}/packages/contracts/test/vocabulary-parity.test.js
reads test/fixtures/abacus-vocabulary.yaml, a verbatim copy of the abacus
file, and fails when any built-in list holds a value abacus does not.
</topology>
<rules>
<rule>
In load mode, change nothing. Name the loader that fits {{consumer}}
from the topology, show the two or three lines that read it, and say
what that consumer sees when the vocabulary is absent.
</rule>
<rule>
In add mode, refuse if pixelfed is already in current_ids. Otherwise
append the entry to platforms in {{abacus_path}}/_refs/vocabulary.yaml
with its meaning (and label, char_limit, link_counting for a platform:
write "unknown" rather than guess a limit or a link rule).
</rule>
<rule>
In change mode, edit the entry in place. To retire a value, set
status: retire-candidate and a retire_note; never delete it. Deleting
needs its own decision, because artefacts and receipts already carry it.
Never rename a stage id.
</rule>
<rule>
After any edit, in abacus: regenerate the cheat sheet and the ledger
snapshot, run `python3 -m pytest -q` in _tools, and commit the YAML and
both generated files together. A PR with the YAML alone fails CI.
</rule>
<rule>
asgard needs a PR only if validation must accept the term before the
next ledger sync, or the parity fixture should track the new file.
Then: refresh packages/contracts/test/fixtures/abacus-vocabulary.yaml
from abacus main after the abacus PR merges, add the value to the
built-in list only if needed, and run `pnpm -F @asgard/contracts test`.
Adding a built-in value that abacus lacks fails the parity test on
purpose.
</rule>
<rule>
Merge order is abacus first, then asgard. Say so in both PR bodies.
</rule>
</rules>
<output_format>
<plan>What changes, in which repo, in merge order. In load mode, which loader and why.</plan>
<commands>The exact commands, in order, with the repo each runs in.</commands>
<verification>What proves it: the checks that pass, and what a consumer sees afterwards.</verification>
</output_format>
</task>
examples
case · add-term
{
"mode": "add",
"list": "platforms",
"term": "pixelfed",
"meaning": "Pixelfed, the fediverse photo network."
}
case · load
{
"mode": "load",
"consumer": "a node script in asgard that needs the list of platforms and their character limits"
}
notes
Merge order for any change that touches more than abacus: abacus first, then asgard. asgard's parity fixture is a copy of the abacus file, so it must be refreshed from abacus main, not from a branch. The built-in lists in @asgard/contracts are a fallback, not a second source. A value goes into abacus first. It goes into the asgard fallback only when validation must accept it before a snapshot carrying it has been synced. Retiring a value is a separate decision. This prompt can mark a value retire-candidate (still accepted), never drop it.
description
The estate's controlled terms (platforms, post positions, systems, stages, receipt kinds, doors) live in one file that abacus owns, _refs/vocabulary.yaml. Everything else is derived from it. abacus tools load it through _tools/vocabulary.py. _tools/build_vocab_cheatsheet.py renders _refs/vocabulary-cheatsheet.md for people. _tools/export_ledger.py copies it into the ledger snapshot, which is how asgard sees it on its next ledger sync. @asgard/contracts validates frontmatter against the ledger vocabulary and falls back to built-in lists, and a parity test fails CI when a built-in value is missing from abacus. This prompt is the cheat sheet and data-load skill for that chain. Use when adding or changing a shared term, or when a service or tool needs to read the vocabulary. Decision: devarno-cloud docs/decisions/2026-09-30-social-launch-tooling-and-vocabulary.md (D-2).