Skip to content

Receipts under contract: parity-receipt-v1 shape + extract:parity-receipt + back-fill the 7 comparator-less receipts (ONT-4c3 re-scoped) #3577

Closed

Description

@noahgift

Operator ruling 2026-09-20, ruling-receipts-under-contract.md. Receipts become an entity type under contract; ONT-4c3 is re-scoped to parity receipts first. One PR, one ticket. Supersedes the split plan of filing #3576, back-filling, and unblocking 4c3 separately — they are one defect.

Receipts are the only artifact family in the tree with no shape, so nothing could have refused a receipt without a comparator. Fixing #3576 with a shell lint while ONT-4c3 stays blocked would build a second validator for exactly the thing pv exists to validate.

Phase-0 [U]s, bound by measurement so nobody re-derives them

Σ parent — no STOP. contracts/ontology.yaml entity_types carries {name: json, extractor: json, implemented: true} with reader ontology/extract/json.rs. ParityReceipt subclasses json. The sigma-parent-absent STOP condition does not fire.

Back-fill denominator — 7, not 119. evidence/parity/**/*.json holds 119 files, but only 7 are parity records (a metrics[] array or a parity key); the other 112 are props-*, derived_expiries.json, thresholds.yaml and similar. Of the 7, 0 carry a comparator field → 7 RED before back-fill, 0 after. That is the PR-body number.

evidence/parity/l0-1/{lambda,gx10}/qwen2.5-coder-{1.5b,7b}-instruct-q4_k_m.json
evidence/parity/l0-1b/gx10/qwen2.5-coder-{1.5b,7b}-instruct-q4_k_m.json
evidence/parity/l0-1/lambda/qwen2.5-1.5b-instruct-q4_k_m.json

The extractor must handle two record shapes — found the hard way. The receipt written for #3574 was not matched by the naive predicate above: it nests the readings under raw.metrics and the verdict under result.parity, while the seven older records put metrics[] and parity at the top level. An extractor keyed to one shape will silently skip the other, and skipping is indistinguishable from passing. Either the extractor handles both, or the new receipt is reshaped to the old layout in this PR — decide it explicitly and say which.

Correction to ruling item 6 — receipt-lint is not an aprender script

The ruling says "receipt-lint is deleted, replaced by pv lint --gate shapes --path evidence/". There is no receipt-lint in this repository. The name belongs to ~/.claude/skills/paiml-implement/scripts/receipt-lint.sh, which lints quorum and dispatch receipts — a different artifact family with different fields. Deleting it would remove quorum-receipt validation that has nothing to do with parity.

The aprender-side counterparts are scripts/check_parity_receipt.sh and scripts/check_receipt_complete.sh. Item 6 should re-aim at check_parity_receipt.sh: fold what it asserts into the shape, list anything the implemented SHACL-Core subset cannot express under Σ not_expressible with a Rust fallback, and delete only what the shape replaces. One validator per artifact family — not one validator for all families.

What lands (unchanged from the ruling)

  1. contracts/parity-receipt-v1.yaml with its shape in the contract's shape: block (the only syntax the gate path parses), exported to contracts/shapes.ttl.
  2. Constraints, using only sh:in, sh:minCount, sh:datatype, sh:node (one level), sh:closed, resolves: — the implemented subset:
property constraint
comparator.kind sh:in (llama_cpp transformers self), minCount 1
comparator.comparator_sha minCount 1 when kind ≠ self; maxCount 0 when kind = self (two shapes over one sh:node — the subset has no sh:or)
comparator.reason minCount 1 when kind = self
threshold_source resolves: a path that exists; the threshold value is not in the receipt
model_sha256 sh:pattern ^[0-9a-f]{64}$
unmeasured minCount 1 — an empty list is a completeness claim and must be written ["none"] deliberately
partially_receipted sh:datatype boolean, required
host, backend, apr_version, generated_at required; backend ∈ (cpu cuda wgpu metal)
sh:closed: true, ignoredProperties: [positions, passed, failed, min_cosine]

PROV-O per the spec's evidence{} block: wasGeneratedBy {command, git_sha}, wasAttributedTo, generatedAtTime — bound, not just specified.

  1. Extractor extract:parity-receipt over evidence/parity/**/*.json → focus nodes, class ParityReceipt.
  2. Positive controls, all RED→GREEN in the PR body: the ARB-APR-3: parity receipt for the decide lane's cell on the agent's host (Qwen3.5-9B-Q4_K_M @ lambda-labs) #3574 receipt passes · all 7 older receipts fail before back-fill, 0 after · plant (remove comparator from a fixture copy) → exactly 1 violation naming focus node and property · mutation (sh:in accepts oracle) → RED · pc_shape fired, shapes_n > 0, focus_nodes_n > 0.
  3. Back-fill in the same PR — the 7 gain comparator: {kind: self, comparator_sha: null, reason: "…"} and partially_receipted: true. A relabel, not a re-measurement: it makes the history honest and the shape green.
  4. Re-aimed as above.
  5. Touches no shared file: the shape is a new contract; census.json is derived per decision 8; no lint-baseline.json count edits. If either is touched, the touches-shared-contracts guard labels it and it merges in a group of one behind slice M.

Fleet reality

pv 0.65.2 fleet-wide has no lint --gate and no extract, so this is a checkout-only gate until the pin lands (#3567). done_when includes "verdict rendered on ≥1 fleet host"; until then the receipt reports checkout-only: true. This row does not wait for that.

Comparator ruling the shape encodes

  • (d) now — re-measure the dense basis against llama.cpp @ llama_pin.toml build_commit (CPU). Two gates result: oracle (CPU vs llama.cpp) and arm-agreement (CUDA vs CPU). The 1.5B cell must stay RED against the oracle or the threshold discriminates nothing. A moved threshold lands with its command; the old one struck [X].
  • (b) this week — Qwen 3.5 gets a pinned transformers reference (version, model revision sha, dtype, device), out of gate, [X], comparator.kind: transformers.
  • (a) at the 0.70 cut — evaluate bumping the llama.cpp pin; pay the re-measurement at a cut, never mid-train.
  • Until (b), Qwen 3.5 receipts are kind: self, partially_receipted: true, and the decide lane is admissible on that stated basis, not an implied one.

STOP conditions

sigma-parent-absent (does not apply — bound to json) · subset-insufficient (list under not_expressible, Rust fallback, never a second linter) · shared-file-touched without the guard label · any threshold typed into the shape instead of resolved from thresholds.yaml.

Refs #3080, #3576, #3574, #3567, #3269 (ONT-4c3)

Activity

  1. added this to the 0.69.0 milestone on Sep 20, 2026
  2. noahgift commented on Sep 20, 2026

    @noahgift
    MemberAuthor

    Amendments (operator, 2026-09-20) — read these before starting

    1. Count-based positive control — the vacuity hole inside the vacuity gate

    An extractor that matches one record shape and silently skips the other is exactly the defect this shape exists to refuse, living inside the shape's own tooling. So skipping must stop being indistinguishable from passing:

    • A committed denominator file records the expected focus-node count, produced by a committed predicate over evidence/parity/** — a file, not the extractor. Today that number is 8 (7 legacy records + the ARB-APR-3: parity receipt for the decide lane's cell on the agent's host (Qwen3.5-9B-Q4_K_M @ lambda-labs) #3574 receipt).
    • The extractor's focus-node count must equal it. Mismatch → Unknown{ExtractorMiss}, exit 2 — never Pass, never a fabricated Fail.
    • Falsifier: add a receipt without updating the denominator → Unknown{ExtractorMiss}. Point the extractor at a subdirectory → same.

    2. Two layouts now, one after this PR

    The fork is real: the 7 legacy records put metrics[] and parity at the top level; the #3574 receipt nests raw.metrics and result.parity. Do not teach the extractor both forever.

    • Declare parity-receipt-v1 (legacy layout) and parity-receipt-v2 (the ARB-APR-3: parity receipt for the decide lane's cell on the agent's host (Qwen3.5-9B-Q4_K_M @ lambda-labs) #3574 layout — raw readings and judgment separated, which is the right shape).
    • The back-fill PR migrates all 7 legacy records to v2 in the same commit as the kind: self relabel. It is already rewriting those files; migrating them costs nothing extra and ends the fork.
    • Result: one layout, one extractor, ignoredProperties empty. An ignoredProperties list is a place for drift to hide; with one layout it is not needed.

    3. Σ parent — json now, Receipt later

    ParityReceipt subclasses json for this row. Note in the contract that it should move under a shared Receipt class once quorum and dispatch receipts join, so the three families inherit common shapes — R-19 materialisation makes that free. Do not create Receipt in this PR.

    4. The next entity type is already decided

    Quorum receipts, same pattern, same session — that is the "receipts with reviewer model unknown" row (301/301 on 09-10). The paiml-implement quorum-receipt linter stays until then; only scripts/check_parity_receipt.sh is in scope here.


    done_when

    (Fixed heading. pmat work add derives neither spec: nor acceptance_criteria:, and pmat work edit cannot set them on a planned ticket — paiml-mcp-agent-toolkit#1414. The ticket PMAT-3577 is your authorization; this heading is your definition of done.)

    • contracts/parity-receipt-v1.yaml and parity-receipt-v2.yaml with their shapes in the contract's shape: block, exported to contracts/shapes.ttl
    • extract:parity-receipt implemented over evidence/parity/**/*.json, class ParityReceipt (parent json)
    • committed denominator file + predicate; extractor focus-node count equals it, else Unknown{ExtractorMiss} exit 2
    • all 7 legacy records migrated to v2 and back-filled with comparator: {kind: self, comparator_sha: null, reason: …} and partially_receipted: true, in one commit
    • pv lint --gate shapes --path evidence/ in the required check
    • PR body shows: 7 RED before back-fill → 0 after · plant (remove comparator) = exactly 1 violation naming focus node and property · mutation (sh:in accepts oracle) → RED · pc_shape fired, shapes_n > 0, focus_nodes_n > 0
    • scripts/check_parity_receipt.sh folded into the shape, or its remainder listed under Σ not_expressible with a Rust fallback — never a second linter
    • ONT-4c3 bound in the ONT-001 ledger with the parity receipt as its focus node; kernel receipts filed as a follow-up sub-row with the gated_rmsnorm fixture
    • checkout-only: true in the receipt until the pv pin lands (The fleet-pinned pv is 0.65.2 and has no 'extract' subcommand and no '--gate' flag — every SHACL gate measured on the fleet binary is unrunnable, not merely unmeasured #3567); then a verdict rendered on ≥1 fleet host
    • ignoredProperties empty after migration

    STOP: subset-insufficient (list under not_expressible, Rust fallback, never a second linter) · shared-file-touched without the touches-shared-contracts label · any threshold typed into the shape instead of resolved from thresholds.yaml · Unknown{ExtractorMiss} at the end of the run.

  3. modified the milestones: 0.69.0, 0.70.0 on Sep 20, 2026
  4. noahgift commented on Sep 20, 2026

    @noahgift
    MemberAuthor

    Correction: the committed denominator is 7, not 8. Measured by the ontology lane on origin/main: 113 files under evidence/parity/, 7 parity records, 0 with a comparator field. My "8" counted the #3574 receipt, which is in PR #3575 and still open — not on main. Counting my own unmerged work as landed; the same stale-reference error four times over today.

    Ruling: commit 7. #3575 carries the bump to 8 when it lands, and that is recorded as a landing obligation on it. If #3575 merges without the bump, this row's gate fires Unknown{ExtractorMiss} and names it — which is the acceptance criterion working as specified, not a defect.

    Also confirmed: model_sha256 is pattern-only with no minCount, deliberately. Six of the seven legacy records have no recoverable model hash, and manufacturing one by hashing the files today would be, in the lane's words, "a claim about a different world wearing a witness's clothes" — a 2026-09-20 measurement attached to a 2026-09-06 receipt. Absent is legal; present must be 64-hex; partially_receipted: true plus an unmeasured entry carries the honesty. That is ONT-4c1's no measurement is not a match.

  5. noahgift commented on Sep 20, 2026

    @noahgift
    MemberAuthor

    Item 6, corrected a second time — my re-aim was itself mis-aimed, one level down

    I corrected the ruling's "delete receipt-lint" to "re-aim at scripts/check_parity_receipt.sh". That was wrong for the same reason the original was, and the ontology lane caught it before a line was written.

    There are two artifact families sharing the word "parity":

    family what it measures where validator
    throughput parity apr vs llama.cpp, tok/s, cross-compute_class rules scripts/lib/parity_receipt_cases/*.json check_parity_receipt.sh — requires instrument, protocol_ref, model, lanes[]; guards the #2696 defect (published apr takes the CPU path and reports 0.099×)
    logit parity CPU vs CUDA, cosine per position evidence/parity/** none, ever

    Verified here on a real record — evidence/parity/l0-1/gx10/qwen2.5-coder-1.5b-instruct-q4_k_m.json carries exactly ['failed','metrics','model','parity','passed','tokens']: not one of the throughput family's four required keys, and never has. check_parity_receipt.sh's callers are check_perf_claims_cite_receipts.sh and the dogfood/host-receipt paths — throughput surfaces.

    So folding it into this shape would delete the #2696 validator and leave the throughput family with nothing — precisely the deletion the ruling refused for receipt-lint, one level further down. Two corrections, same mistake, and I made the second one.

    Ruling

    check_parity_receipt.sh is out of scope and untouched. Stated in the contract and in the PR body, so the next reader does not "finish the job". The done_when line — folded into the shape, or its remainder listed under Σ not_expressible — is satisfied by the honest answer: nothing to fold, because it governs a different family.

    And the ruling's premise gets stronger, not weaker. "Receipts are the only artifact family in the tree with no shape" turns out to be literally true of the logit records: they have had no validator of any kind. That is why seven of them carry no comparator and nobody noticed for months — there was nothing that could have noticed.

    Throughput parity receipts become their own row, after quorum receipts, in the same entity-type sequence.

    The class, named — third instance today

    Same word, different family. A name collision that invites deleting a validator: receipt-lint (quorum/dispatch), check_parity_receipt (throughput), and the logit records this row covers. Each time, the "obvious" consolidation would have removed coverage from a family nobody was looking at. The discriminator is the artifact's required keys, not its filename — one validator per family, and the family is defined by what the records actually contain.

  6. noahgift commented on Sep 20, 2026

    @noahgift
    MemberAuthor

    Applying the key-set discriminator to the "three families" ruling — it over-splits by one

    The rule is right: two validators' key sets differ ⇒ different families. Applied to these two artifacts, measured:

    legacy logit record  ['failed','metrics','model','parity','passed','tokens']
    arb-apr record       ['binary','cell','comparator','date','host','measured_by',
                          'partially_receipted','purpose','raw','result','schema','unmeasured']
      its raw{} keys     ['failed','metrics','model','parity','passed','tokens']
      legacy ⊆ raw ?     True
    

    The arb-apr record is not a different family — it is the same family with the legacy record nested verbatim inside it under raw, plus provenance. Same measurement (CPU vs CUDA cosine per position), same producer (apr parity), same subject. The new keys are all about the measurement — comparator, threshold source, host, binary, what was not measured — not a different measurement.

    So the discriminator says two families, not three:

    family required keys validator
    throughput instrument, protocol_ref, model, lanes[] check_parity_receipt.sh — untouched, guards #2696
    logit model, metrics[] … at v1; + comparator/provenance at v2 parity-receipt-v1 + -v2, this row

    Treating the arb-apr record as a third family would be the mirror of the mistake we just avoided — instead of one validator absorbing two families, two validators would split one family, and the seven legacy records would end up governed by a shape that does not know the eighth is the same artifact enriched. That is also why the back-fill migrates v1 → v2 in one commit and ignoredProperties comes out empty: one family, one layout, after this PR.

    So this row covers the whole logit family — the 7 legacy records and the arb-apr one — and the ruling's "logit gets its own shape only if its keys are already known" is satisfied: they are known, they were measured, and they are a strict subset of v2's. No guessing, no follow-up row needed for it.

    Throughput stays exactly where the ruling put it: check_parity_receipt.sh, untouched, until a shape reproduces every one of its checks — proven by the #2696 fixture staying RED. That fixture is the acceptance, not a code review.

    Quorum and dispatch receipts remain the next entity types, in that order, as separate rows.

  7. 4 remaining items

  8. noahgift commented on Sep 21, 2026

    @noahgift
    MemberAuthor

    ONT-4c3 is bound in the ONT-001 ledger: paiml/infra#897 (PMAT-894), merged as d7317fca. The binding names #3706's merge 52f43da71, not #3600's: at the #3669 squash a877fa056 the row's probe was red on its gate conjunct (pc_extract had no parity-receipt key, because parity_receipt::positive_control was only called by a unit test), and #3706 is what wired it (plus the json and pv-contract controls, with pc_extract tied to Σ). The row's note names #3577, #3600 and a877fa056. R-23 from the pv at 52f43da71: aprender Pass, by_entity_type["parity-receipt"]=7, pc_extract 7/7 fired; rmedia Pass with parity-receipt: 0. That satisfies #3706's keep-open reason for this issue. I haven't checked this issue's other done_when items (--path evidence/ in the required check; the fleet-host verdict once the pv pin lands), so I'm leaving closure to its owner.

  9. modified the milestones: 0.70.0, 0.71.0 on Sep 23, 2026
  10. noahgift commented on Sep 23, 2026

    @noahgift
    MemberAuthor

    milestone → 0.71.0 per docs/roadmaps/milestone-triage-2026-09-23.md (#4024), operator approval 2026-09-23 (relayed by aprender-cf). Reason: model/backend correctness or the certified matrix: Don't Leave Behind (#3994)

  11. modified the milestones: 0.71.0, 0.70.0 on Sep 23, 2026
  12. added
    ont-0.70Ontology spec work that must finish in 0.70 (operator 2026-09-23)
    on Sep 23, 2026
  13. noahgift commented on Sep 23, 2026

    @noahgift
    MemberAuthor

    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.

  14. noahgift commented on Sep 23, 2026

    @noahgift
    MemberAuthor

    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.

  15. noahgift commented on Sep 24, 2026

    @noahgift
    MemberAuthor

    Closing on evidence (pvl-b, 2026-09-24). Landed via #3669 (release/0.69-batch), on origin/main and batch/0.70.0: contracts/parity-receipt-v2.yaml (shape + comparator), v1 is recorded as retired with no instances, extract:parity-receipt (crates/aprender-contracts/src/ontology/extract/parity_receipt.rs) refuses an unmigrated v1 record by name. Measured on batch/0.70.0 @5a70d9195: scripts/parity_receipt_denominator.sh gives PASS, 7 receipts == EXPECTED_RECEIPTS 7, and all 7 carry comparator (0 RED after back-fill, as the issue's PR-body number requires). The PR never said Closes, so the issue stayed open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Critical priorityont-0.70Ontology spec work that must finish in 0.70 (operator 2026-09-23)

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions