KB-2E12

03 — Topic Axis Model: Dynamic Depth (Workstream B)

6 min read Revision 1
registries-pivottopic-axisdynamic-depthtaxonomyuniversal-edgesno-hardcode2026-06-03

03 — Topic Axis Model: Dynamic Depth (Workstream B)

Goal: define topic levels (chủ đề cấp cao / chủ đề con / chủ đề cháu) without hardcoding cấp 1/2/3.

Core concept

A topic is an axis node. Topic hierarchy is dynamic-depth. Layer 1 = approved root nodes of the selected topic axis/subgraph. Layer 2+ = children per the axis relation. A topic may have multiple parents.

This is the same rule as the live Layer canon: count>1 ⇒ a layer exists; depth is decided by data, never by naming convention. Topic depth is therefore a property of the graph, not of columns named topic_level_1/2/3 (which are forbidden).

Mapping to live substrate (reuse, not new)

Model element Live store How
Topic axis taxonomy_facets row FAC-08 "Chủ đề nội dung?" (active) the axis itself
Topic node taxonomy rows where facet_id = 8 (FAC-08) code, name, name_en, depth, status, replaced_by, sort
Primary hierarchy (one parent) taxonomy.parent_id tree edge within FAC-08
Many-to-many parents + broader/narrower/related universal_edges edge kinds broader/narrower/related between FAC-08 nodes
Topic ↔ IU assignment iu_metadata_tag (tag_key='topic:…', confidence) uncertain axis, confidence-bearing
Topic ↔ document assignment knowledge_documents.tags + (future) axis_assignment doc tagging
Topic ↔ workflow / DOT / event universal_edges governed edges

No new topic table. A topic is a taxonomy row under facet FAC-08; its graph is parent_id (primary) ∪ universal_edges (secondary, many-to-many). This is exactly the "no one-off topic island" anti-pattern avoidance from the open-axis model.

Why two relation stores, not one

  • taxonomy.parent_id gives a single canonical parent (good for the default breadcrumb and the tree-shaped legacy surface).
  • universal_edges gives additional parents and lateral relations (broader/narrower/related) so a topic like knowledge_graph can sit under both architecture and governance. The UI shows the canonical parent in the breadcrumb and flags has_multiple_parents=true (doc 07).

Layer resolution (dynamic, no hardcoded depth)

  1. Layer 1 = FAC-08 nodes with status='active' AND no active parent (root set of the chosen subgraph). Today: 0 (FAC-08 empty) → surface reports count_status honestly, not faked.
  2. Layer N→N+1 = for a node with child_count > 1 and a valid grouping dimension (its children via parent_id/edges), produce the child layer. child_count = 1 or no further grouping ⇒ go to final substrate.
  3. PIVOT_MISSING is emitted (never a silent hardcoded fallback) when a node should have a child layer but no grouping pivot / parent graph exists.

Final substrate of a topic node (answer to macro Q7)

When drill terminates, the topic node's final substrate is the real governed objects of that topic, assembled (not a UI page):

  • Documents linked to the topic (knowledge_documents via tag/axis_assignment)
  • Information pieces linked (iu_metadata_tag topic:*)
  • Workflows linked (universal_edgesworkflows)
  • DOTs / Agents / events linked (universal_edgesdot_tools/agents/event_type_registry)
  • Governance owner/candidate/status (governance_object_ownership)
  • Birth record (birth_registry)
  • Lifecycle state (taxonomy.status / replaced_by)

This is delivered by a substrate resolver function fn_topic_node_substrate(topic_code) (design; see doc 06 generic resolver), analogous to the live fn_registries_pivot_node_substrate.

Reuse / Extend / New decision (explicit)

  • REUSE: taxonomy (FAC-08), taxonomy_facets, universal_edges, iu_metadata_tag(+registry), knowledge_documents, governance_object_ownership, birth_registry. The topic node store, hierarchy, relations, assignment-with-confidence, lifecycle, ownership, and birth all already exist.
  • EXTEND (additive, owner-gated): populate FAC-08 nodes; reconcile the 7 ungoverned topic:* tags into FAC-08 candidate nodes; add topic pivots (doc 05); add a graph-aware companion surface (doc 07).
  • NEW (2 objects only, owner-gated): axis_registry (so the topic axis is a registered governed axis, not a special case) + axis_assignment (generalize confidence-bearing assignment beyond IU to documents/workflows). Both designed additive in the open-axis pack; pilot = AX-TOPIC.

Proof reuse is sufficient (Forbidden #1 satisfied): 6 of the 7 axis components (node, relation, assignment, projection, issue, lifecycle) are already in PG; only the registry and a generalized assignment are missing. We therefore add exactly 2 objects, not "many new collections."

Anti-hardcode compliance

No topic_level_* columns. Depth = taxonomy.depth (data). Layer 1 = root predicate over the graph (data). Grouping threshold = rp_grouping_policy row (data). A hardcoded topic list anywhere (Nuxt, SQL CASE, pivot literal) = hardcode_violation.

Workstream B (model) completion: topic axis model is scalable, dynamic-depth, graph-shaped, and 100% expressible on live substrate + 2 additive objects.

Back to Knowledge Hub knowledge/dev/reports/architecture/information-piece-topic-axis-registries-pivot-design-2026-06-03/03-topic-axis-model-dynamic-depth.md