Skip to content

Open Vocabularies, Composable Catalogs and Orphaned Keys — ADR-095

Open Vocabularies, Composable Catalogs and Orphaned Keys — ADR-095

Date: 2026-08-04 · Written in English per the 2026-08-01 convention change.

Provenance: building the BRP skill catalog. ADR-071 assumed a cartridge’s attribute vocabulary is finite and known when the cartridge is published. Twenty-two of Basic Roleplaying’s fifty-seven skills are trained by specialty — a language, a science, a craft — and a specialty is player-invented. The assumption is not merely inconvenient here; it is false, and it was false for every ruleset with an open vocabulary.

This file continues the R-numbered sequence for filing purposes only.


ADR-095 — A Vocabulary May Be Open at Its Edges; a Catalog Is Composed, Not Published; and an Unrecognised Key Is Preserved Rather Than Discarded

Status: Accepted · Date: 2026-08-04 · Amends: 071 · Depends-on: 046 (premise validation), 055 (cartridge major pinning) · interacts with 020, 037

Context

ADR-071 made attribute definitions governed artefacts: a lifecycle with a retired tombstone, a two-release deprecation, publish-time refusal against a reverse-dependency index. Its enforcement point is simple — the host refuses a write to a key the cartridge has not declared — and that is what makes the tombstone mean something, because a retired attribute cannot be resurrected by spelling it.

Implementing a real ruleset produced three cases the rule cannot express.

A specialty cannot be declared in advance. s.language/aklo is a legitimate skill on a legitimate investigator, and no one can enumerate it when the cartridge is compiled. Under ADR-071 as written, every write to it is refused. The rule is correct and its scope was wrong.

A campaign’s vocabulary outgrows its cartridge’s. A scenario introduces a skill; a keeper invents a house rule; a published module ships a compendium. None of these are cartridge releases, and requiring them to be would mean a keeper cannot add a skill to their own table without a publisher shipping a version.

A character arrives carrying keys this room has never heard of. Character portability across campaigns is a product requirement, not an edge case. An imported investigator with s.weird_science meets a room whose catalog has no such skill. The naive handling — drop what you do not recognise — destroys a player’s character during an operation labelled load.

Decision

  1. A declaration may name a key or open a family. AttributeDefinition gains a kind: Exact (the default and the pre-existing meaning) or Prefix. A prefix declaration of s.language/ admits s.language/aklo and does not admit s.language/ itself — a family has members; it is not one.

    An exact declaration always beats the family. That is how a single member is deprecated or retired while the family stays open, and it is the only tombstone an open vocabulary can still enforce.

  2. The tombstone weakens inside a live family, and that is the price. A retired member of a live family can be resurrected by spelling it, because the family is what grants it. Retiring the family still works. Stated rather than discovered: this is a real reduction in what ADR-071 guarantees, bounded to the families a cartridge deliberately opens, accepted because the alternative — declaring only the specialties a cartridge ships with — ends player agency over their own character’s languages, crafts and sciences.

    A second cost, smaller and more likely to be met: a misspelt specialty is now a new skill rather than a refusal. s.language/latni is admitted and becomes a second, separate language. Nothing below the sheet can catch it.

  3. Q-077 bounds an attribute key at 64 bytes. Before this ADR every writable key was spelled out by a cartridge, so key length was bounded by the cartridge’s own vocabulary. A prefix family makes the suffix caller-supplied, which turns key naming into client-controlled input to a quota — the shape §3.1 exists to prevent. Q-060 still bounds the payload in aggregate; this bounds what any one key may take out of it.

  4. A skill catalog is composed from tiers, not published as one artefact. Active = Core + Scenario + GM Custom. A later tier may add or override; no tier may remove, because removal orphans every character who trained the skill — clause 5’s case — by an action that looks like configuration. Provenance survives the merge, so a sheet can say modified by this scenario rather than presenting a house rule as the rulebook.

  5. An unrecognised key is preserved, refused, and surfaced. Never deleted. The three are one rule with three faces:

    PathBehaviour
    Hydration / importRetains every key unconditionally.
    MutationRefused — the key is not declared.
    Rule resolutionRefused — the cartridge will not roll it.
    PresentationListed to the keeper as an orphan awaiting authorisation.

    The payload is schema-less by design and the hydration path rehydrates rather than validating, so preservation is structural rather than checked — there is nowhere for the key to be dropped. That property is asserted by a test anyway, because “the design makes this impossible” is exactly the claim that stops being true when someone adds a tidy-up pass.

  6. A keeper may authorise a declaration into a room, and it is an event. The room’s own stream records who authorised what and when. Replay reconstructs the set, so an authorisation survives a cold start with no scenario present.

    The host primitive is an attribute declaration, not a skill. A CustomSkillAuthorised event would have put a Basic Roleplaying noun into the host’s event schema and every future ruleset would have inherited it. The host does not know what a skill is; it knows what a declaration is, because ADR-071 made that a thing it understands.

  7. An authorisation may add to a cartridge’s vocabulary and may never override it. The cartridge is consulted first and its answer stands whenever it has one, so a keeper cannot undo an ADR-071 tombstone — a governance decision made by someone with no visibility of why it was placed. Authorisations are scoped to the ruleset they were granted for, so a room running two cartridges does not leak one vocabulary into the other.

  8. Selective import is a consequence of clause 6, not a separate mechanism. Because authorisation is one event on the room’s own stream, taking a single custom skill out of a scenario is one event: there is no blob to install, nothing to diff, and no all-or-nothing import. A scenario becomes a manifest a keeper reads from rather than a package a room installs.

Alternatives Considered and Why Rejected

  • Declare only the specialties a cartridge ships with. Rejected — it makes an invented language a content release. The player agency lost is the substance of character ownership, not a convenience.
  • Nest specialties in a bag under one declared key. Rejected — it costs a Q-062 depth level and leaves the inner keys undeclared, which is clause 1 with the enforcement point moved rather than a different answer.
  • Let any key be written and validate at read time. Rejected — it abandons ADR-071 entirely to solve a case at its edges, and the tombstone becomes unenforceable everywhere rather than inside opened families.
  • Delete unrecognised keys on import, with a warning. Rejected — the warning is read by nobody and the data is gone. This is the “GM’s only warning is a changelog they did not read” failure ADR-071 itself rejected, applied to a player’s character.
  • Make authorisation configuration rather than an event. Rejected — configuration does not replay, so a room would depend on a scenario still being installed to understand its own history, and the audit trail for “who let this in” would not exist.
  • Let a scenario import be a package install. Rejected by clause 8. A keeper who wants one NPC should not inherit a compendium, and a room whose vocabulary is a function of installed packages cannot be reasoned about from its own stream.

Consequences (including negative)

  • Negative, and the largest: ADR-071’s central guarantee is now conditional. “A retired attribute cannot be resurrected by spelling it” holds for exact declarations and not inside a live family. Anyone reasoning about attribute governance must now ask which kind of declaration before the answer is knowable.
  • Negative: a room’s writable vocabulary is no longer a function of its cartridge. It is the cartridge plus the room’s authorisation history, so answering “may this key be written” requires the room. Tooling that reasoned about a cartridge in isolation becomes incomplete rather than wrong, which is the harder failure to notice.
  • Negative: the reverse-dependency index gets a new blind spot. ADR-071 already recorded that a macro building keys by string concatenation escapes the index. A prefix family escapes it by construction, because its members do not exist until written.
  • Negative: a keeper is now a governance actor. Clause 6 gives a human the power to extend a vocabulary. Clause 7 bounds it to addition, and the event trail makes it answerable, but the blast radius of a careless authorisation is a campaign.
  • Positive: character portability becomes expressible. A sheet can cross campaigns and lose nothing, which is what players expect of a character they own and what no amount of schema strictness was going to deliver.

Enforcement

  • AttributeSchema refuses a key over Q-077, refuses a prefix with no room for a member, and resolves the longest matching family first so a narrow retired family is not rescued by a broad live one.
  • RoomAuthorisedSchema consults the cartridge first, so clause 7 is structural rather than reviewed; a test asserts an authorisation cannot resurrect a retired key.
  • Room.AuthoriseDeclaration refuses a key the cartridge already declares, refuses an authorisation with no authoriser, and bounds the set per room.
  • AttributeDeclarationAuthorised is replayed by Room.Apply, and a test asserts a room reconstructs its vocabulary from its own stream.
  • Descent.IntegrationTests asserts an imported orphaned key survives hydration — clause 5’s structural property, tested because it is the kind that quietly stops being true.
  • SkillCatalog.TryCompose refuses a malformed or oversized composition and records provenance per definition.

§11 Index Line

ADR-095: An attribute declaration may name a key or open a prefix family, so a player-invented specialty is writable without being enumerable — at the cost of the tombstone inside a live family, which is stated rather than discovered; keys are bounded at Q-077's 64 bytes because a family makes the suffix caller-supplied; a skill catalog is composed from Core + Scenario + GM Custom tiers where no tier may remove; an unrecognised key is preserved on hydration, refused on mutation and resolution, and surfaced to the keeper as an orphan; a keeper authorises a declaration into a room as an event on its own stream, may add and never override, and selective import is a consequence of that rather than a separate mechanism. (Amends ADR-071.)