Repository navigation
pv: by_entity_type is a hand-written list parallel to Σ — derive it from ontology.yaml, or refuse when an implemented entity type is uncounted #3624
Description
The defect class
crates/aprender-contracts/src/lint/shapes_gate.rs:261 builds by_entity_type from an array literal of (name, count) pairs. contracts/ontology.yaml:134-142 declares the entity types with implemented: true|false. Nothing ties the two lists together.
Measured on PMAT-3577-receipts-under-contract before b88246cf2 (aprender-04's measurement, on the branch):
Σ: {name: parity-receipt, extractor: parity_receipt, implemented: true}
by_shape: parity-receipt-complete = 7
by_entity_type: {"apr-model": 0, "code": 270, "gguf": 8, "lean": 410, "pv-contract": 1736} ← no parity-receipt key
The extractor ran, the shape counted seven focus nodes, Σ said implemented: true — and the report the probe reads had no key. b88246cf2 added the pair by hand. That fixes the instance; the class is still open: the next implemented: true entity type (readme, llm-context, csv — ONT-4c) will land in Σ and in an extractor and be absent from by_entity_type until someone remembers the array literal, and every ONT-001 probe of the form jq -e '.by_entity_type["<name>"] == N' reads ABSENT for it.
An absent key is not zero. The probe fails closed (jq -e on null == 7 is false), so the failure mode is a false RED with a misleading cause — the row looks unbound when the count is right and the report is incomplete. It is the same shape as #3610 one map over, and the same root-cause shape as bashrs#266: two hand-maintained lists, no mechanism tying them together, a test suite that asserted each list in isolation.
Fix (class, not instance)
Either, in this order of preference:
- Derive, don't enumerate.
run_shapes_gatereads Σ'sentity_types(it already loadscontracts/ontology.yamlfor the shapes) and buildsby_entity_typeby mapping eachimplemented: truename to its extractor's count through amatchthat is exhaustive — an implemented name with no counting arm is a compile error or a gate ERROR, never a missing key.implemented: falsetypes are emitted as0or omitted by rule, and the rule is stated in the output. - Failing that, a self-check in the gate: after building the map, every
implemented: truename in Σ must be a key; a miss fails the gate withentity type <name> is registered in Σ as implemented and is not counted in by_entity_type— the report names the fact, so the probe's consumer never has to infer it fromnull.
Plus the test that closes it independently of which is chosen: read contracts/ontology.yaml, collect implemented: true names, run the gate over contracts/, assert every name is a key. Prove it discriminates by temporarily flipping one type's implemented to true without a counting arm and watching it go red.
Why pv and not per row
ONT-001 v4.10 has rows binding to by_entity_type[<name>] for gguf, apr-model, code, lean, parity-receipt, and will for readme/llm-context/csv (ONT-4c). Each is a separate instance of the same unchecked assumption — "registered in Σ" ⇒ "counted in the report". One self-check in the gate closes all of them; per-row tests close one each and the next row re-opens it.
Refs paiml/infra#831 (the probe), #3577 / #3600 (b88246cf2, the instance fix), #3610.
Activity
noahgift commentedon Sep 23, 2026
milestone → 0.71.0 per docs/roadmaps/milestone-triage-2026-09-23.md (#4024), operator approval 2026-09-23 (relayed by aprender-cf). Reason: debt ratchet pillars B/C (#3997: pv at the deepest level, ontology merge), slice 2
noahgift commentedon Sep 23, 2026
triage 2026-09-23 (aprender-dd): kept in 0.70.0 and labelled ont-0.70. Operator, verbatim (relayed by the cop aprender-cf): "the ontology spec MUST be finished in .7". Classified as ontology/SHACL spec work: its title names ONT/ontology/SHACL/capability cells, or its body cites an ONT- row.
noahgift commentedon Sep 23, 2026
Correction to the triage comment above (aprender-dd): this issue was MOVED from 0.71.0 to 0.70.0, not "kept". A failed first attempt applied the milestone before its label step errored, so the second pass read it as already in 0.70.0. The move and the reason stand.
fix(fold 0.70.0): by_entity_type counts cli-surface — #3624 derived i…
fix(car): three reds on f2bed7d — setfit binding rows, bashrs 7.4.1 f…
fix(shapes): by_entity_type keys follow Σ; an implemented type with n…
ONT-4c: count readme/llm-context/csv in by_entity_type; shapes_n 37 n…
noahgift commentedon Oct 12, 2026
evidence for close: #4655 @ 989cb01 (on main), checked by reading main:
crates/aprender-contracts/src/lint/shapes_gate.rsbuildsby_entity_typefromsigma_implemented(contract_dir)(the Σ list incontracts/ontology.yaml), not a hand-written array, and refuses with "entity type {name} is registered in Σ as implemented and is not counted in by_entity_type" (fnby_entity_type, ~L700); the regression test near L1663 pins thejson/release-evidencecase and guards against going vacuous.noahgift commentedon Oct 12, 2026
Cop batch close (FLOW-004 drain, ≤43/d): fix verified on main — merged #4655 ; ancestor of origin/main: 989cb01 . Evidence: #3624 (comment)