Shay
|
c6a9bb0096
|
feat(ADR-0168/F1): FrameClaim ratification handler (Tier 1.5) (#389)
Implements the FrameClaim ratification handler per ADR-0168 doctrine
and the ADR-0168.1 MathFrameClaimProposal adapter. Mirrors the
ADR-0167 W2-D LexicalClaim template (apply_lexical_claim) but lifts
the safe surface from drain-class lexical entries to allowlisted
frame-opening categories — the next sub-type up the wrong=0 hazard
ladder.
teaching/math_frame_proposal.py
+ MathFrameClaimProposal dataclass (ADR-0168.1 §"Data shape")
+ MathReaderRefusalEvidencePointer with source pinned to "math_audit"
+ build_evidence_pointer() — only sanctioned constructor; rejects None
missing_operator
+ build_frame_claim_proposal() — enforces:
• surface_form non-empty after normalization
• frame_category in the ADR-0168 allowlist
• polarity in {affirms, falsifies}
• ≥1 evidence pointer with source="math_audit"
• source="corpus" rejected as schema-illegal (ADR-0168.1
§"Evidence floor")
+ compute_claim_signature() / compute_proposal_id() / canonical_bytes()
— deterministic identity per ADR-0168 §"Replay obligations" #1
teaching/math_frame_ratification.py
+ SAFE_FRAME_CATEGORIES = {increment_frame, decrement_frame,
transfer_frame, remainder_frame} — no other categories
+ Error hierarchy: RatificationError, WrongClaimSubType,
WrongZeroViolationCandidate, AlreadyRatified, EvidenceTampering,
UnknownCategory, InvalidPolarity, EvidenceLaundering
+ FrameRatificationReceipt dataclass with before/after SHA + evidence_hash
+ apply_frame_claim(claim, frame_category, polarity, reviewer, pack_root,
evidence_source="math_audit"):
• rejects evidence_source != "math_audit" (ADR-0168.1 §"Evidence floor")
• rejects polarity outside {affirms, falsifies}
• rejects claim.sub_type != "frame"
• rejects evidence_hash tampering (recomputes from audit_row)
• rejects frame_category outside SAFE_FRAME_CATEGORIES
• writes language_packs/data/en_core_math_v1/frames/{category}.jsonl
• idempotent: same (surface, category, polarity, evidence_hash)
raises AlreadyRatified
• duplicate evidence appends evidence_hash to existing row
(ADR-0168.1 §"Idempotency" path #1)
• polarity=falsifies records non-opener; never appends to compiled
lexicon or manifest
language_packs/data/en_core_math_v1/frames/.gitkeep
Directory scaffold for the reviewed frame source files.
workbench/readers.py
_HANDLER_DISPATCH gains "frame_reclassification" → "FrameClaim".
GET /math-proposals/{id} detail and POST /math-proposals/{id}/ratify
now return suggested_cli pointing at apply_frame_claim().
core/cli.py
teaching test-suite tuple gains tests/test_math_frame_ratification.py.
tests/test_math_frame_ratification.py — 14 tests:
1. SAFE_FRAME_CATEGORIES is exactly the ADR-0168 allowlist
2. apply writes a frame entry for a safe category
3. receipt records before/after sha + evidence_hash
4. idempotent same-evidence → AlreadyRatified
5. rejects non-frame sub_type → WrongClaimSubType
6. rejects categories outside SAFE_FRAME_CATEGORIES → WrongZeroViolationCandidate
7. rejects invalid polarity → InvalidPolarity
8. rejects evidence_hash tampering → EvidenceTampering
9. rejects source="corpus" → EvidenceLaundering (ADR-0168.1 §"Evidence floor")
10. case 0050 hazard pin — after ratification, case 0050 still refuses
11. polarity=falsifies branch records non-opener; affirms+falsifies coexist
12. duplicate evidence appends evidence_hash, does not create a second row
13. manifest.json checksum unchanged by frame ratification
14. alphabetical sort by surface_form preserved across writes
Suite verification
core test --suite teaching -q → 47 passed (was 33; +14 new)
core test --suite runtime -q → 20 passed
tests/test_math_lexical_ratification.py → 15 passed (untouched, regression-clean)
tests/test_adr_0172_w4_workbench_e2e.py → 7 passed (existing dispatch tests still hold)
Doctrine invariants preserved
- wrong=0: case 0050 still refuses after ratification
- replay equivalence: claim_signature and proposal_id are deterministic
(sha256 of canonical identity, clock-time-independent)
- refusal-first: no runtime mutation; handler is the only mutation
boundary and writes only the reviewed frames/ source tree
- ADR-0167 partition: math-audit evidence stays math-domain; corpus
evidence is rejected loudly
Brief-correction note: the brief named the scaffold path
"packs/en_core_math_v1/frames/.gitkeep" but the existing math pack lives
at language_packs/data/en_core_math_v1/ (no top-level packs/en_core_math_v1
exists). Scaffold placed at language_packs/data/en_core_math_v1/frames/
to mirror the existing lexicon/ source-tree convention; apply_frame_claim
defaults pack_root to that location.
|
2026-05-27 14:10:43 -07:00 |
|