draft v0.1.0 claude-opus-4-8 pattern · harness

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.

  • content
  • abacus
  • asgard
  • vocabulary
  • pattern:sequential

inputs

namerequireddefault
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>

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).