KB-2DF6
02 — Root Cause Classification
4 min read Revision 1
dieu44dieu39root-causecontract-gapverify-mark-bug
02 — Root Cause Classification
root_cause:
primary:
class: contract_gap_between_MARK_VERIFY_CUT
detail: |
fn_cut_mark_staged_file (operational wrapper) validated only
content_text and canonical_address per piece (added in mig 053).
It did NOT validate source_position, piece_role, section_type,
or local_piece_id — fields that fn_iu_verify_mark Axis A
(dense source_position) and Axis B (piece_role/section_type
non-null) require, and that fn_iu_op_cut downstream needs to
build addressable IUs.
Result: a manifest with only {content_text, canonical_address,
section_type, piece_index} fields was accepted and reached
status='marked'. VERIFY_MARK was then guaranteed to reject —
but the rejection was masked by a secondary bug (below).
secondary:
class: verify_error_handling_bug
detail: |
fn_iu_verify_mark used `v_problems := v_problems || 'literal'`
where v_problems is text[]. PostgreSQL operator resolution
picked array_concat(text[], text[]) over array_append(text[], text),
casting the bare string literal to text[]. That cast requires the
string to be in PG array-literal form `{...}`, so it raised
`malformed array literal: "Axis B: piece_role or section_type
missing on at least one piece"` from inside the verify function,
surfacing as a PL/pgSQL hard error instead of a structured
`{ok:false, verdict:rejected, problems:[...]}` JSON response.
The Axis A line escaped this bug because its expression
`'literal' || v_min || 'literal' || ...` resolves to text, not unknown.
tertiary:
class: wrapper_validation_incomplete
detail: |
fn_iu_op_mark_file (alias) is a pure pass-through — it embeds
whatever pieces the caller hands it into the manifest. The contract
gate therefore has to live in fn_cut_mark_staged_file (the
operational wrapper above the alias) or earlier in the caller.
Since the alias contract is governance-locked (alias body md5 is
pinned across packs), the right place is the operational wrapper.
not_root:
DOT_copy:
verdict: not_root
evidence: |
COPY_TO_CUT_ZONE produced source_hash a732962665691c620a050265de246050
and source_bytes 23394 deterministically. The fix re-marked from
the same source_copy payload (no new copy) and produced a valid
manifest. The copy was always correct.
document_complexity:
verdict: not_root
evidence: |
Điều 39 is 23 KB / 16 sections — well within the size envelope
already passing for Điều 37 (17 pieces) and Điều 38 (8 pieces).
The same source body produced a valid v1 manifest once the
caller emitted the required fields.
Codex_execution:
verdict: contributing_but_not_root
evidence: |
The Codex caller emitted incomplete pieces. But Codex cannot be
expected to enforce a contract that the boundary does not
publish or check. With the boundary now enforced, future Codex
invocations are guided by structured RAISE messages naming
cut_manifest_piece_schema_v1 — the system is self-correcting.
Why the two-bug interaction matters
Either bug alone would have been noisier and easier to catch:
- If MARK had validated full schema, the caller would have failed at MARK with a clear
piece[N].source_position is requiredmessage and the operator would have re-issued. - If verify had returned a structured verdict on the missing fields, the operator would have seen
Axis A/B failedand known the manifest needed regeneration.
Together, MARK silently accepted the bad manifest, advanced the cut_request to marked, and verify_mark blew up with a cryptic malformed array literal — which reads like a verify bug, not a contract gap. This pack closes both ends.