Skip to content

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

  1. 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_nodes graph with zero translation, and so FCC can address terms in its own management UI.
  2. 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_nodes primary key form):
fcc:<type>:<local_id>

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):
FCC_v<N>::<EntityType>::<path>

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 same MetaData (the reason SQLAlchemy Core was chosen over stdlib sqlite3).
  • Distribution: a generated data/fcc_vocab.sqlite ships as package data (AOME's posture). PersonaRegistry reads 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_nodes sample 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 the fcc_vocab.sqlite data file; *.sqlite is 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, matches observability/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's kg_nodes keys 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_nodes id.startswith + node-type invariants.
  • ADR-005 (federated KG namespace), ADR-017 (public-API stability).