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
-
A declaration may name a key or open a family.
AttributeDefinitiongains a kind:Exact(the default and the pre-existing meaning) orPrefix. A prefix declaration ofs.language/admitss.language/akloand does not admits.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.
-
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/latniis admitted and becomes a second, separate language. Nothing below the sheet can catch it. -
Q-077bounds 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-060still bounds the payload in aggregate; this bounds what any one key may take out of it. -
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. -
An unrecognised key is preserved, refused, and surfaced. Never deleted. The three are one rule with three faces:
Path Behaviour Hydration / import Retains every key unconditionally. Mutation Refused — the key is not declared. Rule resolution Refused — the cartridge will not roll it. Presentation Listed 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.
-
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
CustomSkillAuthorisedevent 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. -
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.
-
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-062depth 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
AttributeSchemarefuses a key overQ-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.RoomAuthorisedSchemaconsults the cartridge first, so clause 7 is structural rather than reviewed; a test asserts an authorisation cannot resurrect a retired key.Room.AuthoriseDeclarationrefuses a key the cartridge already declares, refuses an authorisation with no authoriser, and bounds the set per room.AttributeDeclarationAuthorisedis replayed byRoom.Apply, and a test asserts a room reconstructs its vocabulary from its own stream.Descent.IntegrationTestsasserts an imported orphaned key survives hydration — clause 5’s structural property, tested because it is the kind that quietly stops being true.SkillCatalog.TryComposerefuses 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.)