03 — Topic Axis Model: Dynamic Depth (Workstream B)
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_idgives a single canonical parent (good for the default breadcrumb and the tree-shaped legacy surface).universal_edgesgives additional parents and lateral relations (broader/narrower/related) so a topic likeknowledge_graphcan sit under botharchitectureandgovernance. The UI shows the canonical parent in the breadcrumb and flagshas_multiple_parents=true(doc 07).
Layer resolution (dynamic, no hardcoded depth)
- Layer 1 = FAC-08 nodes with
status='active'AND no active parent (root set of the chosen subgraph). Today: 0 (FAC-08 empty) → surface reportscount_statushonestly, not faked. - Layer N→N+1 = for a node with
child_count > 1and a valid grouping dimension (its children viaparent_id/edges), produce the child layer.child_count = 1or no further grouping ⇒ go to final substrate. - 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_documentsvia tag/axis_assignment) - Information pieces linked (
iu_metadata_tag topic:*) - Workflows linked (
universal_edges→workflows) - DOTs / Agents / events linked (
universal_edges→dot_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.