Extensible controlled vocabularies

Workshop for designing how a code-enforced, closed enum field becomes open-ended per installed KB while staying validator-checkable.

Trigger

kb/sources/types/ingest-report.schema.yaml's source_type is a JSON-schema enum — currently 11 values (scientific-paper, practitioner-report, conceptual-essay, design-proposal, tool-announcement, github-issue, conversation-thread, code-repository, court-opinion, news-article, official-statement). The last three were just added because casework in the sibling epistack-casebooks repo hit legal filings and press coverage that had no honest fit and were getting forced into conceptual-essay/conversation-thread, degrading retrieval. That's a PR against this repo's schema to unblock a downstream KB — the same "argue a new category into the closed set" pattern ADR 042 retired for registers, recurring on a different field.

Why this isn't just "do the register move again"

Register was a prose taxonomy — COLLECTION.md headers and a theory note, no code enforcement, so opening it up meant relaxing a claim and adding a catalogue doc. source_type is schema-enforced: a bare JSON-schema enum can't be open-ended without either breaking validation or ceasing to validate anything. "Default profiles, open per-KB" here needs a real mechanism, not just a rename — that mechanism is what this workshop is for.

Extension points inventory (2026-07-09 survey)

A full pass over every *.schema.yaml under kb/ for enum: constraints, filtered by the membership test from first-principles are inherited constraints, not design choices: does the set classify what knowledge can be (content taxonomy — demotes) or what the machinery is (may stay closed)? Tell: can you name a rival vocabulary that would also work under the same consumer/substrate/domain/machinery commitments?

Content-taxonomy-shaped — real candidates:

  • kb/sources/types/ingest-report.schema.yamlsource_type (11 values) — the trigger case above.
  • kb/agent-memory-systems/types/agent-memory-system-review.schema.yamlsource-tier (code-grounded / doc-grounded) — classifies a review's evidentiary basis, the same shape as source_type (source-kind classification) just in a different collection. It's deliberately binary today ("the only authority discriminator," per types/agent-memory-system-review.md) and scripts/build_systems_matrix.py branches on its exact value when building the comparison matrix, so a third tier (e.g. trace-grounded, vendor-doc-grounded) would hit the same wall source_type just hit. Not under growth pressure yet — worth designing this workshop's mechanism generally enough to cover it later, not urgent to fix now.
  • kb/types/note.schema.yamlstatus (seedling / current / speculative / outdated) — already named in a universal knowledge framework demotes content taxonomies to defaults as the theory's "predicted fourth instance." A proposal already exists — assertion force separate from lifecycle status — but it answers a different half: the field fuses lifecycle with first-person assertion-force meaning, and that proposal's fix keeps lifecycle in the type with the four values unchanged ("validators keep checking the enum unchanged"). Its own adoption trigger (a non-endorsed collection needing to disambiguate what current means) technically exists now — the LHC casebook is live — but hasn't actually fired: every casebook note there is still status: seedling (checked directly in the sibling repo), so nobody has hit the ambiguity in practice. That proposal is adjacent, not overlapping: it never asks whether the four lifecycle values themselves should ever grow, which is this workshop's question.

Machinery-shaped — probably stay closed:

  • kb/reference/types/adr.schema.yamlstatus (accepted / superseded / deprecated) — a narrow, standard ADR-lifecycle vocabulary; hard to construct a plausible rival under this framework's own boundary commitments.
  • kb/types/index.schema.yaml / kb/types/tag-readme.schema.yamlindex_source (directory / tag / tag-indexes) — names generation mechanisms Commonplace's own indexing code implements, not knowledge content.
  • kb/reports/types/connect-report.schema.yamldepth (quick / standard / deep) — an operational parameter of the connect skill, not a content classification.

Status is a collapsed cube, not a fourth open-list instance

Sharper analysis from a session working the epistack-casebooks casework directly (2026-07-09), worth carrying here in full because it changes what "fix status" even means. The four status values aren't one axis with a missing option — they're a hand-picked diagonal through at least three bundled axes: maturity (has it been through review), currency (live vs. superseded), and endorsement/assertion-force (does the KB commit to it), plus a promotion/durability flavor smeared into seedling. Off-diagonal cells are simply inexpressible — there's no way to say "reviewed but deliberately conjectural and still being drafted," because that cell doesn't exist in a four-value enum built as a diagonal.

The casebook makes the twist concrete, and it's sharper than "endorsement needs a new value": endorsement is inapplicable there, not differently valued. A casebook note asserts nothing in its own voice, so there's no belief to commit or withhold — seedling means "provisionally mapped," outdated means "no longer an accurate map of the debate," not "no longer believed." One whole axis drops out and gets replaced by a different quality entirely (fidelity-to-contestation). That means assertion-force-separate-from-lifecycle-status's frame — keep the enum, let the contract redefine what current means — may not go far enough: if an axis can be absent, not just re-valued, per-collection meaning-redefinition of a fixed enum can't express that; per-collection axis selection can.

The wider connection to this workshop's own question. "Epistemic strength" is currently smeared across at least four places: status (endorsement), source_type (genre as an evidential-weight proxy — peer-reviewed paper vs. news article vs. tweet), the casebook's citation grounding-layer marker (verbatim/paraphrase/second-hand — provenance fidelity), and independence (the shared-authorship/shared-dataset problem — whether three agreeing papers are three lines of evidence or one). That's not one hidden axis, it's a composite that splits along a line this framework already has a name for: objective/checkable (provenance fidelity, source genre) — these can be fields, gates, and open lists, which is exactly what this workshop is designing for source_type — versus judgment/contextual (evidential weight, independence-adjusted strength) — these are contestable and relational (independence is a property of the citation graph, not of any one artifact) and must never collapse into a stored scalar. The named trap: don't add an epistemic_strength field to fix this — that would smear the objective and judgment parts back together, the identical mistake status already made, one level up. Two casebook doctrines already in the epistack-framework-additions menu enforce the same discipline from two directions: confidence must be attributed, never a mark and adjudication as a separate, labeled, downstream layer.

Unifying read. status and source_type are the same refactor family, and it's the shape ADR-017/041 already established: push config into the collection contract, keep the core machinery generic. But they're two different mechanisms under that shared philosophy, not one fix — source_type needs an open value list within one axis (this workshop's question); status needs axis decomposition (factor into small named axes — lifecycle, currency, assertion-force — and let the collection contract declare which axes apply here and what their values mean, so a collection can drop an axis, not just re-value it). Conflating the two mechanisms would be the same category error the cube already made. Whether status belongs in this workshop's scope or needs its own is an open call — see Design Questions.

The sibling-repo session that produced this offered to write a transferable methodology note ("epistemic strength is not a storable scalar: factor into checkable provenance facts, an open genre list, attributed judgments, and a relational independence graph") plus log a backlog entry. Per the backlog-to-commonplace protocol — build-local-first in the casework, prove it, log the need, let this repo watch and promote what survives — that write-up belongs in epistack-casebooks/backlog-to-commonplace.md first, not authored directly here. Not yet written as of this note.

Borrowing candidates for these axes (not ready)

Whether any of the axes above have real first-principles backing, or are just distinctions that felt right on contact, was raised directly in discussion (2026-07-09). None of the following are adopted or even proposed — this is a scouting list for philosophy-borrowing to evaluate later, against its existing bar: a borrowing earns adoption by changing a concrete operation, not by supplying vocabulary (see programming patterns get a fast pass but other borrowed ideas must earn first-principles support).

  • Assertion-force-as-inapplicable ↔ speech-act theory's illocutionary force plus the use-mention distinction (asserting X commits the speaker to X; reporting that someone asserted X does not). A second, sharper application of the theory already sitting as a candidate in speech-acts.md, currently scoped only to document-types-as-actions.
  • Independence (shared-authorship/shared-dataset discounting) ↔ Bayesian confirmation theory's correlated-evidence problem — don't multiply likelihoods from non-independent sources. Closer to the KB's "formal systems get a fast pass" bucket than a human-field analogy.
  • Genre-as-evidential-weight (source_type as a proxy) ↔ evidence law / historical source criticism (primary vs. hearsay, admissibility vs. weight). Compelling structural match, but lands in the same "compelling but untested" bucket the fast-pass note already assigns to legal drafting generally — no concrete technique from law has been successfully applied in this KB yet.
  • Currency/maturity — probably doesn't need external grounding at all; more likely already "machinery" (a lifecycle existing at all is inherited from machinery coherence, per first-principles items 5–6), not a content taxonomy needing a borrowed theory.

None of this is ready to promote, and it deliberately stays here rather than in philosophy-borrowing/ until one of these changes a concrete operation.

Exception: verifiable quotes. One piece is more ready than the rest, and on reflection it isn't really an external borrowing at all — it's already covered by this KB's own inherited derived-copy rule (first-principles, item 7): "a copy of information recomputable from a ground-truth source must be machine-checked against that source or not exist." A casebook citation marked verbatim (per the citation grounding-layer marker) is exactly that — a copy of a source-snapshot substring. Right now it's a hand-maintained-and-trusted copy: an agent asserts "verbatim" and nothing checks it, which is precisely the trap a derived copy of recomputable truth must be checked or absent warns against. The machinery this needs already has a name and a deferred slot: factored dependency pairs for review freshness names source-as-gate — a (note, source) pair checking "a derived note's consistency with the source snapshot it distills" — as the next factored-pair instance after COLLECTION.md-as-gate shipped (ADR 041). A verbatim-quote check (does the quoted text still appear in the cited snapshot?) is a specific instance of exactly that. Not building this now — flagging it as the one candidate in this whole thread that's already first-principled today, not speculative, with a mechanism already designed and waiting.

Candidate directions (unevaluated)

A. Extensible vocabulary file. The list lives as a YAML file under kb/ — a symbolic artifact, not a schema literal — so an installed KB's own agents can edit it locally without a framework schema change. Rough shape, not yet designed: shipped defaults ship as one file; a consuming KB's local additions live in the same file or a sibling; the JSON schema's enum is either generated from that file at validation time or replaced by a lookup against it. Exact placement, file format, generation vs. runtime-lookup, and interaction with commonplace-init/upgrade are all open. Gives structured tracking (a real shared default list, promotion discipline) at the cost of new machinery.

B. Per-constraint severity downgrade. ADR 024 already ships per-constraint severity (fail/warn), read generically off error.schema for any schema keyword — confirmed in src/commonplace/lib/validation.py (_schema_error_message, ~line 291), not enum-specific. So source_type: {enum: [...], severity: warn} would make out-of-list values a warning, not a validation failure, today, with a one-line schema annotation and zero new code. Materially lighter than A. Trade-off: no shared, inspectable default-list growth or promotion tracking — an agent can type any string and it just warns, which may be too loose for a controlled vocabulary that wants "these are the known categories, extend deliberately," the same tension the register profile's worked-case guard exists to manage.

A and B are not mutually exclusive — B could be the enforcement floor while A (or something like the register profile catalogue) supplies the documented default list a KB extends against.

Design Questions

  • Where would an extensible-vocabulary file (direction A) live — kb/types/ next to the schema, kb/sources/ (collection-local, since source_type is currently sources-only), or a new global location now that source-tier is a second candidate?
  • Generated enum (a build/validate-time step regenerates the schema's enum from the YAML) vs. runtime lookup (the validator reads the YAML directly, no enum in the schema at all)? Trade the commonplace-validate architecture cost against keeping the schema self-describing.
  • Does severity downgrade (direction B) alone satisfy the need, or does an open-but-unchecked vocabulary proliferate the same way ADR 042's "guards and defaults" section warns open link/register sets would without a worked-case brake?
  • Does an installed KB's local addition need the same worked-case-first promotion guard the register profiles got (ADR 019/042), or is that overkill for a controlled vocabulary that's cheaper to add and remove than a whole text contract?
  • What's the shipped-defaults vs. local-additions split: does Commonplace ship a starter list and let each KB append freely, or does it ship categories plus a documented extension mechanism with no implied hierarchy?
  • Do the per-source_type "Limitations Standards" lenses in ingest-report.md's authoring instructions extend the same way (each new category needs its own lens paragraph), or is that a separate, harder problem — the schema field is data, the lens is prose guidance keyed to it?
  • Should the mechanism be designed generally enough to also cover source-tier from day one, or is source_type alone enough to prove it out first (build-local-first, one field at a time)?
  • Relationship to vocabulary-governance: that workshop is about definitional terms (prose vocabulary like "register", collection/type scope for word meanings); this is about enum values in a schema field. Adjacent, not the same — check whether one mechanism could serve both before designing two.
  • Does status's axis-decomposition problem belong in this workshop's scope, or is it big enough (a different mechanism — collection-selected axes, not an open value list) to need its own workshop once the backlog write-up lands? Leaning toward separate — the two share a philosophy (push config into the contract) but not an implementation.
  • Is verifiable-quote checking worth building before the axis-decomposition and open-list questions are settled? Now written up as Verifiable quotes — deliberately designed as a narrower, Level A deterministic check, distinct from source-as-gate's Level B judgment.

Closure

Close this workshop when there's a concrete mechanism proposal that answers: where the vocabulary file lives, how it's validated against, whether local additions need a promotion guard, and how the per-category prose guidance (the Limitations Standards lenses) is meant to extend alongside it. That becomes a proposal in kb/reference/proposals/, same as the register case, then an ADR once (if) it's exercised.

Grounding