ADR-018: Two-Layer FQN Scheme & Governed Vocabulary Store¶
Date: 2026-06-25
Status: Accepted
Decision-makers: FCC core maintainers
Consulted: AOME (portal gold standard — KnowledgeGraphNode / kg_nodes), CTO (path_builder, ADR-CTO-055), CONSTEL (namespace-governance pattern), PAOM (persona/archetype consumer)
Informed: All fcc-integration downstream pins; federation registry mirrors (AOME/CTO)
Context and Problem Statement¶
The AOME-parity vision (docs/roadmap/fcc-aome-parity-vision.md) makes FCC a
federating portal over a governed controlled vocabulary. Two foundations must
land first (Train 7.0):
- A fully-qualified name (FQN) for every vocabulary entity that is unique
across project + namespace + type, so an FCC persona/archetype/action/
constitution-rule can be ingested into AOME's
kg_nodesgraph with zero translation, and so FCC can address terms in its own management UI. - A single source of truth for the vocabulary. Today the 176 personas, 26 canonical archetypes, 312 action defs, and the per-persona constitution rules live as read-only packaged YAML — there is no managed, FQN-addressed, definition-synthesizable store.
FCC already has partial identity machinery: FederatedEntity.canonical_id
produces a single-layer {namespace}:{local_id} colon form, and
NamespaceRegistry.resolve_uri produces a {base_uri}{local_id} URI form. Neither
is the two-layer scheme AOME and CTO converged on, and neither carries the
type segment AOME's kg_nodes keys on.
Decision¶
1. Two-layer FQN scheme¶
Adopt both layers the ecosystem standardized on:
- Layer B — federation identity (the controlled-phrase uniqueness key, the
kg_nodesprimary key form):
e.g. fcc:persona:runner_ops_steward, fcc:concept:archetype,
fcc:action:forensic_auditor.scaffold, fcc:attribute:hard_stop.
Invariants (AOME-exact, so FCC nodes stay ingestible):
- lowercase fcc authority prefix; id.startswith("fcc:") enforced;
- exactly two : separators — <type> and <local_id> carry no bare :;
- <type> ∈ the reused AOME node_type set:
persona | concept | attribute | relation | action | constitution
(FCC adds action/constitution; concept covers archetypes/categories,
attribute covers EAV facets — matching AOME's node-type taxonomy);
- <local_id> is the entity's existing stable id (persona id, archetype id,
<persona_id>.<action_type> for actions, a rule slug for constitution).
- Layer A — human catalog path (the CTO
::convention, project identity in the root segment):
e.g. FCC_v2::Personas::Core::RunnerOpsSteward,
FCC_v2::Archetypes::Steward,
FCC_v2::Actions::ForensicAuditor::Scaffold.
<N> is the FCC major version; <EntityType> ∈
Personas | Archetypes | Actions | Constitution; <path> is a
::-joined, PascalCase, human-readable breadcrumb (category → name).
Both forms are derived deterministically from the entity's stable id +
namespace + category — see src/fcc/vocab/fqn.py. FCC owns this authority:
it is already the upstream NamespaceConfig registry AOME and CTO mirror, so FCC
only needs to (a) formalize the type/EntityType segment sets, (b) emit both
forms on every entity, (c) keep the invariants AOME enforces on ingest.
This extends (does not replace) FederatedEntity.canonical_id: the existing
2-part {namespace}:{local_id} remains valid for cross-vocabulary mapping; the
new fcc:<type>:<local_id> is the 3-part graph-node identity layered on top.
2. Governed vocabulary store — SQLite shipped as package data¶
Migrate the 4 core agent-spec namespaces (persona, archetype, action,
constitution — per vision §3) from YAML into a DB that becomes the source of
truth. The store models the AOME vocabulary shape:
vocabulary_root (= namespace: persona/archetype/action/constitution)
└─ category (= Concept; self-FK DAG, e.g. core/vertical groupings)
└─ phrase (= Entity; carries both FQN layers)
├─ phrase_attribute (= EAV facet: tags, dimensions, zachman_cell, …)
└─ phrase_definition (= multi-tier synthesized definition;
length_tier, provider, model, status,
is_canonical, provenance)
- Persistence layer: SQLAlchemy Core (
Table/MetaData, not the ORM — consistent with the project's "dataclasses, not Pydantic" convention). Schema is declared in Python (src/fcc/vocab/schema.py); the runtime read path opens the packaged SQLite file. Postgres later = connection-URL swap behind the sameMetaData(the reason SQLAlchemy Core was chosen over stdlibsqlite3). - Distribution: a generated
data/fcc_vocab.sqliteships as package data (AOME's posture).PersonaRegistryreads it; the YAML and catalog JSONL become build-time exports of the DB (for diffability and back-compat). - Back-compat guard (the central Train-7.0 risk): a build/export step
regenerates the persona YAML + catalog JSONL from the DB, and a CI test asserts
the export matches the previously-packaged data, so the migration introduces
no regression in what downstream consumers (PAOM
fcc_ingest, the catalog) receive.
Consequences¶
- Every vocabulary entity gains a stable, ecosystem-compatible identity; an FCC
kg_nodessample is ingestible into AOME unchanged (dry-run validated in 7.0). - The DB unlocks the rest of the arc: per-namespace CRUD UI (7.1), synthetic definitions with provenance (7.2), and FQN-addressed federation (7.3).
- New core dependency:
sqlalchemy>=2.0. The wheel grows thefcc_vocab.sqlitedata file;*.sqliteis added to the package-data glob. PersonaRegistry's public API is unchanged — callers and plugins are unaffected; the YAML loaders remain for plugin-provided personas and explicit-file loading.- SemVer: additive new public surface (
fcc.vocab) lands in the minor v2.2.0; it joins the ADR-017 stable-API surface.
Alternatives considered¶
- stdlib
sqlite3(collector precedent), no new dep. Lighter wheel, matchesobservability/collector/store.py. Rejected by owner (2026-06-25) in favor of SQLAlchemy Core for schema-as-Python and a clean Postgres path later. - Single-layer FQN only (
fcc:<local_id>). Rejected: AOME'skg_nodeskeys on<type>, and the human catalog needs the CTO::path — both layers are required for zero-translation ingest + a navigable management UI. - Keep YAML as SoT, project a read-only DB. Rejected by owner: the DB must be the source of truth so CRUD + synthesized definitions are first-class, not a cache that the next export overwrites.
References¶
docs/roadmap/fcc-aome-parity-vision.md§3 (namespaces), §4 (FQN), §8 (trains).- CTO ADR-CTO-055 (
path_builder,::catalog convention) — mirrored, not imported. - AOME
KnowledgeGraphNode/kg_nodesid.startswith+ node-type invariants. - ADR-005 (federated KG namespace), ADR-017 (public-API stability).