Repository navigation
refactor(scripts): split UI contract canonicalizer into explicit phases - #204
Conversation
canonicalize() mixed fragment folding, exceptional ID rewrites, request and request-flow construction, capability/constraint/evidence projection, flow sorting, and disabled-affordance mapping in one 497-line function. Extract the already-visible phases into private functions (_fold_fragment_rows, _entry_paths, _build_requests, _request_path, _build_request_flows, _build_dimensions, _build_constraints, _disabled_state_record, _build_disabled_states, _availability_for, _build_affordance, _attach_aliases, _build_disabled_affordances) so each domain special case sits beside the phase that owns it, and leave canonicalize as a six-line orchestrator. Bodies moved verbatim by a rerunnable checked-extraction generator with exhaustive before/after line accounting. Deliberate edits beyond moves: drop the aliases copy and a no-op invariants loop, merge two identical disabled-state literals into one projection, hoist the request_path closure out of the inner loop, and drop seed_for's unused lifecycle parameter. Normalization and independent validation stay separate. Verification: expanded inventory byte-for-byte identical to the pinned pre-change output (57 affordances, 159 request rows, digest checked via load_inventory); scripts.tests.test_normalize_ui_contract_inventory and test_verify_ui_contract_inventory pass 14/14 unchanged with zero skips; verify_ui_contract_inventory.py --source-only PASS; no-code-comments checker 0 violations on the changed file; Node reader scripts/lib/ui-contract-inventory.mjs returns 57 affordances / 159 requests unchanged. Closes #200
matthewpeterkort
left a comment
There was a problem hiding this comment.
Review: PR #204 (f2003c3) — refactor(scripts): split UI contract canonicalizer into explicit phases
Scope: single commit, single file scripts/normalize_ui_contract_inventory.py (473+/428-), base feature/postgres-backend @ 80e8877.
Verdict: APPROVED — no changes required, ready to merge. (Posted as COMMENT because GitHub rejects APPROVE/REQUEST_CHANGES on own-account reviews; this is the approving verdict for AO.)
I treated this as a behavior-preserving refactor and verified equivalence by static line-range diff (git log -L) of every region against the base revision:
- Lines 1-533 and 1212-1324: unchanged (no hunks). seed_for body byte-identical after dropping the unused lifecycle parameter; its sole call site was updated.
- Every moved block is verbatim, including all per-fid special cases (exclude_unmapped_member sequence guard, combine catalog append, flow_specs synthesis with shared evidence refs), the flowId reassignment chain, by_flow reordering with kind_priority, requests.sort keys, finalize_request_record usage, request-flow sequenceOrder/dependency/alternative-group tables, dimensions/constraints/disabled-states/availability projection, and the affordance dict literal. The hoisted _request_path is the same closure body; _disabled_state_record unifies two identical literals.
- The four deliberate non-move edits are behavior-neutral: the aliases copy was only iterated (never mutated) in the old code so _attach_aliases can read ALIASES directly; the removed invariants loop was a no-op pass; the lifecycle parameter was unused in seed_for; closure hoisting is semantically identical.
- Caller compatibility holds: scripts/verify_ui_contract_inventory.py imports only load_inventory (unchanged), and scripts/tests/test_normalize_ui_contract_inventory.py only uses BASE, finalize_request_record, and load_inventory (all unchanged signatures).
I verified this statically; per reviewer constraints I did not execute the script or test suite. The commit message records the runtime evidence: expanded inventory byte-for-byte identical to the pinned output (57 affordances, 159 requests, digest via load_inventory), 14/14 tests, and --source-only PASS.
Optional follow-ups (not required for this PR):
- _build_request_flows still mutates request order for builder.table_route_and_occurrence_selection after finalize_request_record, so requests[] checkpointIds can go stale relative to flow steps. Pre-existing and faithfully preserved here; a future behavior-changing cleanup could recompute checkpointId or reorder before finalize.
- _build_constraints(sources, refs) parameter order is inconsistent with _build_disabled_states(fid, sources, mapping_rows); cosmetic.
- _build_requests is still ~194 lines; splitting flow-spec synthesis from ordering could continue the phase split later.
What
Breaks the 497-line
canonicalize()inscripts/normalize_ui_contract_inventory.pyinto a six-line orchestrator plus the phases that were already visible inside it (issue #200). No behavior, schema, fragment format, or Builder runtime change; normalization and independent validation stay separate modules.Why this reduces reasoning burden (not line count)
Before, changing any one stage required holding all stages in view at once: a contributor touching the population-exclusion request override had to scroll past fragment folding, alias merges, dimension projection, seed policy, and disabled mapping to find it, and had to know which locals (
refs,family,status) were mutated by which earlier stage.After:
canonicalize()says the pipeline: fold → build each affordance → sort → attach aliases → disabled mappings. The ordering facts that matter (fragment read order,out.sortposition, relational-fragment read at the end) are all visible in six lines._build_constraints(sources, refs),_availability_for(primary, status), …). The implicit shared-state trace shrinks:refsis now extended at exactly one boundary (_build_requestsreturns it),familyis renamed in exactly one place,statusnever escapes_build_affordance._build_requests; the group-field summary dimensions live inside_build_dimensions; the per-affordance flow-order/dependency table lives inside_build_request_flows. A change to one workflow no longer renders the other 400 lines as context.Moved-body correspondence
The edit touches 901 lines, so it was produced by a rerunnable checked-extraction generator (reads the pinned base via
git show 80e8877…:scripts/normalize_ui_contract_inventory.py, emits each body only from declared source ranges, asserts anchors on every boundary, classifies every original line 670–1166 exactly once as moved/edited/dropped/replaced, and fails otherwise; rerunning regenerates the committed file byte-identically)._fold_fragment_rowsaliasescopy), blanks_entry_paths_request_pathdef); call site 884 renamed only_build_requests_build_request_flowsorderupdates shared withrequests)_build_dimensions_build_constraintsfor … : pass, no side effects)_disabled_state_record/_build_disabled_states_availability_for_build_affordance_attach_aliasesaliases→ALIASES(the dropped copy was read-only),out→affordances_build_disabled_affordancesOne plumbing edit outside the region:
seed_for's never-readlifecycleparameter removed (call site updated; no external callers or tests reference it).Verification (all against the final committed source)
python3 scripts/normalize_ui_contract_inventory.py --expandoutput iscmp-identical to the pinned pre-change output — 57 affordances, all 159 request rows, order, source paths, evidence, and the SHA-256 expansion digest re-checked byload_inventory.git diff --checkclean;python3 -m unittest scripts.tests.test_normalize_ui_contract_inventory scripts.tests.test_verify_ui_contract_inventory→ Ran 14, OK, 0 skipped, no test edited. This covers the named normalizer-drift, endpoint-inference-refusal, stable-output, cardinality, and verifier-mutation tests unchanged.python3 scripts/verify_ui_contract_inventory.py --source-only→PASS UI contract inventory: 57 affordances, source hashes and file set current, source bindings complete.scripts/check_no_code_comments.pyon the file →violations=0(the oneallowedline is the pre-existing shebang).scripts.tests.test_no_code_comments→ OK with 11 skips, all pre-existing sandbox skips (typescript module not resolvable), unrelated to this file.scripts/lib/ui-contract-inventory.mjsuntouched;readUiContractInventory()still returns 57 affordances / 159 requests.Preserved, deliberately
Exact
SystemExitmessages and first-failure precedence (fragment-missing, method-mismatch, shape-drift, no-source-backed-method/endpoint/payload); unknown endpoints still refused, never guessed; population-exclusion flow keeps selection-revision → exact prior route lookup over pages → attach ordering and stop condition; request/SQL-irrelevant ordering semantics (dict insertion order feedingflow_order,requests.sort, stable flow ordering) unchanged; the checked-in compact manifest is untouched.Not done here
No AO review triggered (coordinator-owned). No changes outside
scripts/normalize_ui_contract_inventory.py;scripts/lib/ui-contract-inventory.mjsandscripts/tests/test_normalize_ui_contract_inventory.pywere audited and needed no change.Closes #200