Skip to content

Omission candidates: classify the fields of single objects and entries of nested lists, not only top-level list entries (#3934) - #4395

Open
realmarcin wants to merge 4 commits into
mainfrom
fix/3934-omission-candidates-nested
Open

realmarcin wants to merge 4 commits into
mainfrom
fix/3934-omission-candidates-nested

Conversation

@realmarcin

@realmarcin realmarcin commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #3934

What changes

replicate_structure.entry_omission_candidates (#3880) classified only the keyed entries of top-level lists of class-ranged slots. It skipped any slot holding one object. This PR adds nested_omission_candidates, the same receipt-backed reading one level further down:

  • fields of objects: a field some replicates fill and others leave empty, in an object every replicate holds at that identity. This covers a slot holding one object (human_subject_research.irb_approval) and objects reached further down.
  • entries of nested lists: an identified object entry of a list below the first level that some replicates carry and others do not (creators[*].affiliations[*]).

The walk goes down only by two kinds of step:

  • a single-object step: a field of an object every replicate holds;
  • a keyed-list step: an entry every replicate holds, identified by receipts._entry_key and its occurrence, the join the first level already uses. _entry_key strips the key's value and reads a resolver URL of a declared prefix as its CURIE. Nothing else is normalised, so a name in other words is another entry.

Nothing is read below a keyless entry, a node some replicate lacks, a value that is a list in one replicate and not in another (a list/object or list/scalar disagreement), or a commentary field.

A row is a candidate only when a holder's verified receipt path, resolved into its final record (resolve_verified), sits on the node or below it. Resolution follows the path by identity where the run left a phase-1 snapshot. Where it left none, the path is read as written (an index join). The path must be the node's own path in that replicate, so a receipt only counts through the same chain of entries. A receipt at another holder's path for the node credits nothing when its own replicate holds something else there.

scripts/arm_comparison.py adds the section "Receipt-backed omission candidates below the first level (#3934)". It also adds a _replicate_resolved helper that both entry sections now use, and rewords the top-level-only caveat. notes/arm_comparison.md is regenerated by the script (--no-figures).

Decisions the brief left open

  1. Fields of keyed entries are included, not only fields of top-level single objects. The issue asks for every path whose chain is single/key. Rows carry basis (single / key, as in compare_nested) so the two kinds can be told apart.
  2. String items of nested lists are counted (values), never classified. Their only identity is their exact text, and below the first level they are mostly prose. For example, version_access.versions_available holds "v1.1, published 2025-01-17, DOI …" in one v6 VOICE replicate and "1.1 (17 January 2025), doi:…" in another. By exact text, 775 of the 871 nested entry rows were such items, as were 47 of the 71 nested-entry candidates on the receipted arms. This mirrors the first level, which leaves the items of top-level lists of values unclassified. Whether such a list is filled at all is still a field row.
  3. Credit on or below only. A receipt on the object or entry holding a node does not credit it. A receipt on a list covers only the list (A receipt on a list slot covers every entry beneath it #721). Measured effect: 17 rows (v6: 9 fields; v8: 5 nested entries and 3 fields).
  4. Commentary fields are the receipts instrument's exempt keys less source_caveats: conforms_to_class, conforms_to_schema and notes. They are rows with status commentary. source_caveats is skipped at any depth. conforms_to and conforms_to_standard are not exempt, because they carry facts from the bundle (receipts.py). They are classified like any other field. On v5 agentic CM4AI, two conforms_to and two conforms_to_standard rows are such fields, held by rep3 only and unmeasured.

The top-level results did not move

  • A test pins entry_omission_candidates' full output on a fixture that has structure below its first level. The pinned literal was produced by origin/main's module (bc1f41d). Since review round 1, the fixture also holds a string entry in a class-ranged top-level list, with a receipt on it (The first-level pin test omits the one input where the shared _keyed switch changes the first-level reading #4437). Main keys such an entry by its text and classifies it, and the branch still does.
  • A corpus comparison ran origin/main's module against this branch's on the same records and resolved receipts. Across 24 arm × project groups and 2,854 first-level entry rows, the output was identical. Round 1 changed no code that computes a row or a count. It changed only a comment, docstrings and the new section's legend.
  • A render from unmodified origin/main equals the committed note byte for byte, so main had not moved. Against it, the regenerated note changes one line (the caveat) and adds the new section. No existing row moves.

New rows per arm

Fields are shown as candidates / not / unmeasured / commentary; nested entries as candidates / not / unmeasured.

arm fields in some replicates nested entries keyless / values
v4 API 73 (no receipts; 12 commentary) 0 0 / 163
v5 API 57 (no receipts; 17 commentary) 0 0 / 157
v5 agentic 97 (no receipts; 4 commentary) 46 (unmeasured) 0 / 160
v6 agentic 143: 50 / 89 / 0 / 4 38: 21 / 17 / 0 0 / 171
v7 API production 146: 10 / 89 / 0 / 47 0 0 / 83
v8 API production 75: 15 / 8 / 0 / 52 12: 3 / 9 / 0 0 / 95

Every v6 agentic group reads its receipt paths as written, because the agentic path leaves no phase-1 snapshot. Its verified snippets are all no_snapshot: 1,282 (AI_READI), 316 (CHORUS), 967 (CM4AI) and 727 (VOICE). So 50 of the 75 field candidates and 21 of the 24 nested-entry candidates rest on that index join. The v7 and v8 groups have no no_snapshot path: their paths were followed through the phase-1 snapshot (receipts.remap_path), as the #3880 table's basis column shows.

Both of the issue's examples are rows: human_subject_research.irb_approval (v4 CM4AI, unmeasured) and creators[*].affiliations[*] (v6 VOICE, a candidate that rep3 lacks). A corpus test pins both.

Tests

No canary or model call was made.

Review round 1

All six findings are fixed in this PR. Commit 0f31845 has the code, docstring and test changes, and 293cdbe regenerates the note. Against the round-0 note, only the new section's legend line changed. No number moved.

Follow-ups filed

Round 1 filed nothing new. #4435's verification noted that CLAUDE.md's "Two CI lanes" section is out of date, since every PR runs the corpus lane. #3863 already tracks that.

🤖 Generated with Claude Code

realmarcin and others added 2 commits October 5, 2026 03:32
…ies of nested lists (#3934)

`entry_omission_candidates` (#3880) classified only the keyed entries of
top-level lists of class-ranged slots, and skipped any slot holding one
object. `nested_omission_candidates` applies the same receipt-backed
reading below that: from the class-ranged slots every replicate fills,
single objects included, it follows only single-object steps (a field of
an object every replicate holds) and keyed-list steps (an entry every
replicate holds, identified as the first level identifies one), and
classifies the fields some replicates fill and others do not and the
object entries of nested lists some replicates carry and others do not.

A row is a candidate only where a holder's verified receipt path,
resolved into its final record, is on the node or below it at that
node's own path in that replicate: the same chain of entries. A receipt
on the object or entry above a node, or on the list holding it (#721),
does not credit it. Keyless objects and the string items of nested lists
are counted, never classified: a string item's only identity is its
exact text, and below the first level most are prose items of lists of
values, where a rewording would read as an omission (775 of 871 nested
entry rows before this rule). Commentary fields are counted as such;
`source_caveats` is skipped at any depth.

`entry_omission_candidates` now shares its keyed join and status with
the new reading (`_keyed`, `_status`, `_key_label`, `_held_in_all`). A
test pins its full output, as origin/main produced it, on a fixture with
structure below the first level; on the corpus, main's module and this
one give identical output on all 24 arm x project groups (2,854 rows).

`scripts/arm_comparison.py` adds the section, a `_replicate_resolved`
helper both entry sections use, and rewords the top-level-only caveat.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…3934)

Written by `scripts/arm_comparison.py --no-figures` at 30a2ded (the
figures do not read the omission sections and are left as they are). A
render of the note from unmodified origin/main equals the committed note
byte for byte, so nothing here comes from main having moved; against it,
this changes one line (the first-level entry table's top-level-only
caveat, now pointing at the new table) and adds the section "Receipt-
backed omission candidates below the first level (#3934)". No existing
row moves. A second, independent render of the same commit matches the
script's output byte for byte.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
realmarcin and others added 2 commits October 5, 2026 08:37
…#4434-#4439)

Review round 1 of PR #4395.

- #4434: the nested section's legend called every `conforms_to_*` field
  commentary, but the walk classifies `conforms_to` and
  `conforms_to_standard`, which the receipts instrument does not exempt.
  The legend now builds both key lists from the code: `EXCLUDED_SLOTS` is
  skipped, and the rest of `COMMENTARY_KEYS` is commentary. The same
  shorthand is corrected in the script docstring and in the
  `COMMENTARY_KEYS` comment. New tests: a nested `conforms_to_standard`
  is a candidate beside a commentary `conforms_to_class`, and the legend
  names the computed keys.
- #4435: new tests for a node held at different indices, where each
  holder's receipt sits at the other holder's path. It credits nothing,
  both nested and at the first level, which shares `_status`.
- #4436: in the shape-disagreement test, the list/object pair now
  differs below the shared identity, and a list/string pair is added.
  `_deep_group`'s `m` gets the same change. A walk that reads through the
  disagreement now fails.
- #4437: `_deep_group` gains a string entry in a class-ranged top-level
  list. That is the one input the shared `_keyed`'s `objects_only` switch
  reads differently. The first-level pin is retaken from origin/main's
  module (bc1f41d).
- #4438: the docstring now says receipt paths are followed by identity
  only where a phase-1 snapshot exists. Where none exists, they are read
  as written (an index join).
- #4439: the legend now says a keyed identity is the key's value as
  `receipts._entry_key` reads it (stripped, with a resolver URL read as
  its CURIE), not its exact value. A new test pins the URL/CURIE join.

No reading changes. In replicate_structure.py only a comment and a
docstring change. In arm_comparison.py only the module docstring and the
nested section's legend change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rendered by `scripts/arm_comparison.py --no-figures` at 0f31845. Only
one line changes: the legend of "Receipt-backed omission candidates below
the first level (#3934)". It now names the commentary keys from the code
(#4434) and says how a keyed identity is read (#4439). No row or number
moves.

Against origin/main the note still differs in one changed line (the #3880
caveat) and the added section.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Omission candidates for fields of single objects and for nested lists (below one level)

1 participant