80000x · 06 — Three-axis requirements (Axis A source order, Axis B professional, Axis C parent-child-grandchild)
06 — Three-axis requirements (A, B, C)
The three axes are the IU-core's invariant model. Every cut piece must satisfy all three. MARK proposes them; CUT writes them; VERIFY confirms them.
1. Why three axes
A single piece needs to be reachable along three independent dimensions:
- Axis A — Original source reconstruction. Given an article label, can we reproduce the exact original text by concatenating its pieces in source order?
- Axis B — Professional / content axis. What kind of document is this piece part of? What section type? What discipline/topic tags? This is the filter "show me all rules about X".
- Axis C — Structural axis. Where does this piece sit in the parent/child/grandchild hierarchy? This is the filter "show me the children of this clause".
The three axes are orthogonal in the data model: an Axis B query (section_type = 'definition') does not constrain Axis A or Axis C, and vice versa.
2. Axis A — Original source reconstruction
2.1 Invariants
A1 Every piece carries source_position (int) within its article.
A2 source_position is dense: values are 1..N with no gaps.
A3 source_position is monotonic: sorted ascending reproduces source order.
A4 source_position is unique within an article.
A5 Concatenating piece text by source_position ascending, with the article's
inter-piece separator and the article's normalization rule, yields a string
whose sha256 equals manifest.articles[].original_text_hash.
A6 fn_iu_reconstruct_source('<article_label>') returns the pieces in order,
each resolvable to a live information_unit row.
2.2 Code path (live, 70000x)
SELECT *
FROM fn_iu_reconstruct_source('DIEU-37')
ORDER BY source_position;
-- expect: N rows; source_position dense+monotonic; every iu_id non-null
T01 of ops/iu-core-six-flow-test-readiness/ validates this at 36 pieces for DIEU-35 (PASS at run 20260525T081115Z).
2.3 Manifest fields driving Axis A
articles[].pieces[].axis_a:
source_position: <int 1..N>
source_url: <article URL>
source_hash: <sha256 of full source body>
2.4 VERIFY check V1
After CUT, the verifier runs fn_iu_reconstruct_source and asserts the reconstructed-normalized hash equals manifest.articles[].original_text_hash. Mismatch ⇒ rollback.
3. Axis B — Professional / content axis
3.1 Invariants
B1 Every piece is tagged in at least one Axis-B kind.
B2 Axis-B kinds = {legal_document, section_type, unit_kind} (per iu_metadata_tag_registry).
B3 No piece's Axis-B field re-encodes information already carried elsewhere
(no second source of truth — e.g., do NOT also tag section_type='article'
if information_unit.section_type already says 'article').
B4 professional_tags[] (extras) come from the source, not from Agent imagination.
B5 The full Axis-B vocabulary is enforced by DB CHECK constraints — discover
live with `pg_get_constraintdef`, do not hardcode.
3.2 Code path (live, 70000x)
SELECT DISTINCT tag_kind
FROM iu_metadata_tag_registry
WHERE active=true;
-- expect 3 Axis-B kinds: legal_document, section_type, unit_kind
SELECT *
FROM v_iu_metadata_envelope
WHERE iu_id = '<id>';
-- read all tags attached to a piece
T02 of the six-flow runner validates Axis-B (PASS).
3.3 Manifest fields driving Axis B
articles[].pieces[].axis_b:
legal_document: <e.g., "luat-xyz-2024" or null+flag>
section_type: <substrate vocab term>
unit_kind: design_doc_section | law_unit
professional_tags: [<string>, …] # optional extras
3.4 VERIFY check V2
For each piece created by CUT, assert iu_metadata_tag rows exist covering {section_type, unit_kind} at minimum, plus legal_document if the manifest set one.
4. Axis C — Parent / child / grandchild
4.1 Invariants
C1 Exactly one piece per article has parent_local_piece_id = null (the title/root).
C2 Every non-root piece has parent_local_piece_id resolvable within the same article.
C3 depth(piece) = depth(parent(piece)) + 1; root depth = 0.
C4 Max depth ≤ 2 by default (root → clause → sub-point). Deeper ⇒ explicit operator sign-off.
C5 The Axis-C graph is acyclic.
C6 fn_iu_subtree(root_iu_id) returns root + descendants with relative_depth ∈ {0,1,…}.
4.2 Code path (live, 70000x)
SELECT *
FROM fn_iu_subtree('<root_iu_id>')
ORDER BY relative_depth, subtree_position;
-- expect: root + children + grandchildren; no orphans; no cycles
T03 of the six-flow runner validates Axis-C (PASS, root cb211ee6-…, max_depth=2, 8 descendants).
4.3 Manifest fields driving Axis C
articles[].pieces[].axis_c:
parent_local_piece_id: <local_piece_id of parent, or null for root>
depth: <int 0|1|2>
subtree_position: <int 1-based among siblings>
At CUT time the operator's fn_iu_compose call materializes Axis C via iu_piece_membership rows ordered by piece_order, with iu_tree_path populated by the trigger.
4.4 VERIFY check V3
After CUT, run fn_iu_subtree(article_root_iu_id). Assert:
- Row count = manifest.articles[].pieces[].length;
relative_depthdistribution matches the manifest'sdepthdistribution;- Parent-child pairs match the manifest's
parent_local_piece_id → iu_idtranslation.
5. Joint invariants across axes
J1 No piece may exist in any axis but not the others — every piece appears in
Axis A (source_position), Axis B (≥1 tag), and Axis C (parent or root).
J2 Axis A reconstruction must succeed for the article as a whole AND for every
Axis-C subtree (i.e., a clause's children concatenate to that clause's body).
J3 Axis B tags do not duplicate Axis-C structure (don't tag a piece with
section_type='paragraph' AND set it as a sub-point of a paragraph — pick one).
J4 Inserting a new piece via fn_iu_collection_add_piece preserves all three
axes (T04 of the six-flow runner — PASS).
6. Mapping table — manifest field → DB column
| Manifest field | DB target |
|---|---|
axis_a.source_position |
iu_piece_membership.source_position |
axis_a.source_url |
iu_metadata_tag (kind=source_url, optional) |
axis_a.source_hash |
iu_metadata_tag (kind=source_hash, optional) |
axis_b.legal_document |
iu_metadata_tag (kind=legal_document) |
axis_b.section_type |
information_unit.section_type |
axis_b.unit_kind |
information_unit.unit_kind |
axis_b.professional_tags[] |
iu_metadata_tag (kind=tag-specific) |
axis_c.parent_local_piece_id (resolved) |
iu_piece_membership.parent_iu_id + iu_tree_path |
axis_c.depth |
derived from iu_tree_path |
axis_c.subtree_position |
iu_piece_membership.piece_order within parent |
7. Forbidden during MARK across axes
- Inventing a
source_url(Axis A) the user did not provide. - Inventing a
legal_documenttag (Axis B) when the source does not name one. - Inferring deep grandchildren (Axis C beyond depth 2) without an explicit marker in the source.
- Computing a "professional tag" the source does not justify.
When in doubt, leave the field null and add an uncertainty_flag. Operator decides.
8. Reference
- T01, T02, T03 SQL:
ops/iu-core-six-flow-test-readiness/test_01..03_*.sql - KB:
knowledge/dev/laws/dieu44-trien-khai/v0.6-iu-core-70000x-full-test-readiness-six-user-flows-open-goal/04-p3-six-flow-evidence.md - Live vocab discovery:
pg_get_constraintdefoninformation_unit,iu_sql_link,iu_piece_membership.