Adds sub-section schema fields (heading_number / v4_alias_keys /
sub_sections) to MdxSection with defaults so existing 4-positional
constructions remain valid. Introduces _resolve_v4_section_key helper
that resolves a V4 mdx_sections key in exact > alias > None order with
no parent/sibling promotion (axis 7 hybrid lock).
Rewires four runtime V4 lookup sites (lookup_v4_match,
lookup_v4_match_with_fallback, lookup_v4_all_judgments,
lookup_v4_candidates) to accept an optional alias_keys kwarg and go
through the resolver. U1 callers pass empty alias lists so behaviour
is byte-identical to the previous exact-match path; U2 will populate
aliases from MDX heading_number metadata.
Closure callers in run_phase_z2 build section_alias_by_id from
MdxSection.v4_alias_keys and forward into lookup_fn /
candidates_lookup_fn / lookup_v4_all_judgments (Step 7-A trace) and
into _select_template_for_overrides single-section selector.
Step 9 candidate report (post-decision diagnostic) is marked with an
inline English exemption comment per N-R6 — runtime selection goes
through _resolve_v4_section_key, the report path stays a direct
dict-shape lookup to avoid debug_zones schema plumbing.
derive_parent_id now recognises canonical ordinal ids
("03-1-sub-2" -> "03-1") first and keeps the legacy decimal fallback
("04-2.1" -> "04-2") for V4 alias compatibility.
Tests : 8 synthetic cases in tests/test_phase_z2_subsection_schema.py
covering derive_parent_id ordinal/decimal/none and the resolver
exact/alias/no-promote/miss cases. 30/30 PASS combined with the 14
override + 8 fallback baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
2.8 KiB
Python
83 lines
2.8 KiB
Python
"""IMP-08 B-3 sub-section drag/drop — schema + V4 alias resolver tests.
|
|
|
|
Fully synthetic per Codex #7 generalization guardrail:
|
|
NO real catalog template_id / frame_id, NO ``v4_full32_result.yaml`` dependency,
|
|
NO MDX-specific section ids beyond canonical id format.
|
|
|
|
Locked scope (Stage 3 R8) :
|
|
A. ``derive_parent_id`` canonical ordinal recognition + legacy decimal fallback.
|
|
B. ``_resolve_v4_section_key`` exact > alias > None (no parent/sibling promotion).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from src.phase_z2_composition import derive_parent_id
|
|
from src.phase_z2_pipeline import _resolve_v4_section_key
|
|
|
|
|
|
# ─── A. derive_parent_id ────────────────────────────────────────────────────
|
|
|
|
|
|
def test_derive_parent_id_ordinal_sub():
|
|
assert derive_parent_id("03-1-sub-2") == "03-1"
|
|
assert derive_parent_id("04-2-sub-1") == "04-2"
|
|
|
|
|
|
def test_derive_parent_id_decimal_legacy_alias():
|
|
# Legacy V4 decimal id retains existing behaviour for alias path.
|
|
assert derive_parent_id("04-2.1") == "04-2"
|
|
|
|
|
|
def test_derive_parent_id_top_level_none():
|
|
assert derive_parent_id("04-1") is None
|
|
assert derive_parent_id("04") is None
|
|
assert derive_parent_id("nonsense") is None
|
|
|
|
|
|
# ─── B. _resolve_v4_section_key ─────────────────────────────────────────────
|
|
|
|
|
|
def _fake_v4(*keys):
|
|
return {"mdx_sections": {k: {"judgments_full32": []} for k in keys}}
|
|
|
|
|
|
def test_alias_resolver_exact_match_wins():
|
|
v4 = _fake_v4("04-2-sub-1", "04-2.1")
|
|
assert _resolve_v4_section_key(v4, "04-2-sub-1") == "04-2-sub-1"
|
|
assert (
|
|
_resolve_v4_section_key(v4, "04-2-sub-1", alias_keys=["04-2.1"])
|
|
== "04-2-sub-1"
|
|
)
|
|
|
|
|
|
def test_alias_resolver_decimal_alias_when_metadata_present():
|
|
v4 = _fake_v4("04-2.1")
|
|
assert (
|
|
_resolve_v4_section_key(v4, "04-2-sub-1", alias_keys=["04-2.1"])
|
|
== "04-2.1"
|
|
)
|
|
|
|
|
|
def test_alias_resolver_no_parent_promotion():
|
|
# parent V4 entry must not be promoted into a sibling sub-section lookup.
|
|
v4 = _fake_v4("04-2")
|
|
assert _resolve_v4_section_key(v4, "04-2-sub-1") is None
|
|
assert (
|
|
_resolve_v4_section_key(v4, "04-2-sub-1", alias_keys=["04-2"])
|
|
== "04-2"
|
|
) # alias is opt-in; only resolves when caller explicitly provides it
|
|
|
|
|
|
def test_alias_resolver_no_sibling_promotion():
|
|
# sibling sub-section entry must not be auto-promoted without an alias.
|
|
v4 = _fake_v4("04-2-sub-2")
|
|
assert _resolve_v4_section_key(v4, "04-2-sub-1") is None
|
|
|
|
|
|
def test_alias_resolver_miss_returns_none():
|
|
v4 = _fake_v4("99-1")
|
|
assert _resolve_v4_section_key(v4, "04-2-sub-1") is None
|
|
assert (
|
|
_resolve_v4_section_key(v4, "04-2-sub-1", alias_keys=["04-2.1"])
|
|
is None
|
|
)
|