unit_kind vocab governance — dot_config prefix vocab.unit_kind.* (2026-05-27)
03 — unit_kind Vocab Governance
Live source of truth
The accepted set of unit_kind values at CUT time is defined data-side in dot_config rows whose key matches the prefix vocab.unit_kind.. There is no companion tac_unit_kind_vocab FK table.
Snapshot at 2026-05-27 (post-fix)
SELECT key, value, description FROM dot_config WHERE key LIKE 'vocab.unit_kind.%' ORDER BY key;
| key | value | description |
|---|---|---|
vocab.unit_kind.design_doc_section |
design_doc_section |
Section trong design document — proposed_pilot |
vocab.unit_kind.law_unit |
law_unit |
p3d-phase4-vocab-20260511-082717 |
Total: 2 entries. Snapshot UNCHANGED by this macro (vocab not widened).
Code path that consults this vocab
public.fn_iu_create
|- v_uk := public.fn_iu_resolve_default(
p_unit_kind,
'iu_create.default_unit_kind',
'vocab.unit_kind.' -- prefix lookup in dot_config
);
IF v_uk->>'status' NOT IN ('explicit','default','auto_single')
THEN RAISE EXCEPTION 'unit_kind: %', v_uk->>'message';
So fn_iu_create will accept exactly the values present in vocab.unit_kind.<value> keys, plus an iu_create.default_unit_kind fallback when input is NULL.
Code path that consults this vocab (after fix)
public.fn_cut_mark_staged_file (cut_manifest_piece_schema_v1, NEW)
|- FOR each piece:
PERFORM 1 FROM public.dot_config
WHERE key = 'vocab.unit_kind.' || v_piece->>'unit_kind';
IF NOT FOUND THEN RAISE EXCEPTION ... ;
public.fn_iu_verify_mark (NEW Axis D)
|- SELECT count(*) ... WHERE EXISTS (
SELECT 1 FROM public.dot_config
WHERE key = 'vocab.unit_kind.' || (p->>'unit_kind')
);
All three sites (fn_iu_create, fn_cut_mark_staged_file, fn_iu_verify_mark) now consult the same dot_config prefix — a single source of truth. Adding a new unit_kind value is a single INSERT into dot_config and instantly propagates to MARK gate, VERIFY_MARK Axis D, and CUT.
Governance pattern parity
This matches the proven pattern for section_type:
| Vocab | dot_config prefix | tac_ FK table | Validated at MARK gate | Validated at VERIFY_MARK |
|---|---|---|---|---|
section_type |
vocab.section_type. (18 keys) |
tac_section_type_vocab (17 keys) |
yes (pre-existing) | Axis B (non-empty), now also piece-level dot_config lookup at MARK |
unit_kind |
vocab.unit_kind. (2 keys) |
none | NEW | NEW Axis D |
Note the asymmetry: section_type also has a tac_ FK table that backstops information_unit.section_type; unit_kind is enforced ONLY by dot_config plus fn-body checks (no FK on information_unit.unit_kind). This is fragile and is flagged as carry-forward CF-3 in section 07.
When to add a new unit_kind
If a new document kind needs its own unit_kind value (e.g. runbook_step, policy_clause):
- Get governance approval (a Điều/runbook entry explicitly enumerating the value).
- Execute:
INSERT INTO dot_config(key, value, description, updated_at) VALUES ('vocab.unit_kind.<value>', '<value>', '<governance ref>', now()); - NO function body change required.
- MARK callers can now emit
unit_kind=<value>immediately.
Why semantic labels are not first-class here
law_section, design_doc_subsection, policy_step, etc. are semantic labels meaningful to authors but coarser than runtime unit_kind. Two design options exist for handling them:
| Option | Storage | Trade-off |
|---|---|---|
| Strict (chosen) | Caller emits runtime value (law_unit). Manifest stores law_unit. |
Simple. Loses semantic distinction in the manifest. Forces caller-side normalization. |
| Semantic mapping (rejected) | Caller emits law_section. fn_cut_mark_staged_file resolves via dot_config mapping.unit_kind.<sem> = <runtime>, rewrites piece, stores runtime value. |
Centralizes mapping; permits semantic labels in caller code. Lossy at storage time. Adds a second config namespace. Operator rejected. |
If a future macro reintroduces semantic mapping, the recommended placement is:
dot_config mapping.unit_kind.<sem> = <runtime>(data-side)- New helper
fn_cut_resolve_unit_kind(text) RETURNS text fn_cut_mark_staged_filerewritesp_piecesbefore callingfn_iu_op_mark_file- Axis D refuses unresolvable values only
But that is explicitly out of scope for this macro.
Conclusion
unit_kind runtime vocab governance is now consistently enforced at three sites (fn_iu_create, fn_cut_mark_staged_file, fn_iu_verify_mark) reading from one dot_config prefix. Vocab additions remain data-side (single INSERT, no DDL).