fix(proof): expose read completeness - #254
Conversation
Add `complete` and `cap_reasons` to every Proof read envelope. Derive the JSON fields, digest header, and summary from the same state so pagination and hard caps cannot disagree. Keep pagination out of `cap_reasons`; `page.has_more` and its cursor describe it. Report primary record, displayed edge, and byte caps as stable, duplicate-free values. Tests: - `pnpm verify` Fixes #252 Change-Id: I4cbd9019dd5ef042482e5e5824211dc95d1e1592
…ess/expose-read-completeness--4cbd9019
|
Tick the box to add this pull request to the merge queue (same as
|
There was a problem hiding this comment.
Review verdict: COMMENT
Issue #252’s complete / cap_reasons fields land correctly on the JSON envelope for the tested singles (happy path, paging-only, each hard-cap reason, CLI bytes). No blocker. Non-test source changed and the coverage plan is still non-empty, so this is not a clean APPROVE.
Highest-signal gaps
- HIGH — No test stacks
bytesonprimary_records/displayed_edgesafter the byte-cap recompute (digest.ts:311). Singles stay green if that recompute regresses when reasons are already non-empty. - MED —
primary_recordsstill setspage.has_more: truewithnext_cursor: null, and YAMLprimary.has_morecan disagree with envelopepage.has_more(pre-existing quirk; does not falsify the new fields). - MED — New hard-cap tests miss YAML/
summary/next_cursorparity; thedisplayed_edgesuniqueness assert is vacuous with one reason. - LOW — CLI spawn covers complete / page / bytes only; add a
proof relationspath fordisplayed_edges(primary_recordsis unit-only becauserender()limit ===CAP_RECORDS).
Coverage plan (ordered)
- Stacked hard caps + byte rebuild → sorted multi
cap_reasons primary_records: assertnext_cursor === null+ YAML/envelopehas_moreparity (or fix policy)displayed_edges: assertpage.has_more === false; replace vacuous Set check- Combined
hasMore+ hard cap - CLI
displayed_edgesspawn assert
Perspectives
correctness-and-contracts · test-coverage-robustness · cli-and-runtime · docs-and-positioning · release-discipline
Models: Cursor Grok 4.5 High (HIGH), Composer 2.5 (MED/LOW). Oven unavailable → Task fallback. Judge: /tmp/review-judge-final.md.
Sent by Cursor Automation: Flatbread PR Review
| ].join('\n'); | ||
| if (Buffer.byteLength(markdown) > CAP_BYTES) { | ||
| reasons.push('bytes'); | ||
| completeness = digestCompleteness(Boolean(input.hasMore), reasons); |
There was a problem hiding this comment.
HIGH — coverage: This recompute is the only place bytes joins an already non-empty reasons list. Current tests cover bytes-only and record/edge singles, not a stacked case.
Add one renderDigest fixture with record (or edge) overflow plus oversized bodies and assert complete === false and cap_reasons deep-equal the sorted multi-reason list (e.g. ['bytes', 'primary_records']). Without that, dropping this recompute when reasons were already set can ship while all existing tests stay green.
| const reasons: ReadCapReason[] = []; | ||
| if (records.length > CAP_RECORDS) reasons.push('primary_records'); | ||
| if (input.edges.length > CAP_EDGES) reasons.push('displayed_edges'); | ||
| let completeness = digestCompleteness(Boolean(input.hasMore), reasons); |
There was a problem hiding this comment.
MED — contract follow-up (pre-existing): primary_records still couples to page.has_more: true with next_cursor only when input.hasMore is set, so a hard-capped read can advertise “more” with no cursor. displayed_edges / bytes do not do this.
The new complete / cap_reasons fields correctly name the hard cap; this does not block #252. Prefer either (a) keep page.has_more false for hard caps, or (b) assert next_cursor === null and document the decision tree so callers do not treat has_more as “fetch next page.”
| served_generation: input.generation, | ||
| consistency: input.consistency, | ||
| complete: completeness.complete, | ||
| cap_reasons: completeness.capReasons, |
There was a problem hiding this comment.
MED — envelope vs digest drift: Envelope page.has_more still ORs records.length > CAP_RECORDS (below), while YAML primary.has_more stays Boolean(input.hasMore) only. A 26-record renderDigest can show JSON has_more: true and digest primary.has_more: false even though complete / cap_reasons now agree across surfaces.
Align YAML primary.has_more with envelope page.has_more, or stop OR-ing the hard cap into page.has_more, and add a parity assert.
| hasMore: boolean, | ||
| reasons: readonly ReadCapReason[] | ||
| ): DigestCompleteness { | ||
| const capReasons = [...new Set(reasons)].sort(); |
There was a problem hiding this comment.
MED — untested multi-reason path: Dedupe + sort here is the machine contract for stacked caps, but no renderDigest test drives ≥2 reasons. CLI cannot hit primary_records today (render() max limit 25 === CAP_RECORDS), so library coverage must prove sort/unique behavior.
Stack caps at the API (private helper) and assert exact sorted cap_reasons.
| }); | ||
| t.false(result.complete); | ||
| t.deepEqual(result.cap_reasons, ['displayed_edges']); | ||
| t.is(new Set(result.cap_reasons).size, result.cap_reasons.length); |
There was a problem hiding this comment.
MED — weak assert: With a single reason, Set size === length is a no-op. This test also never asserts page.has_more === false (unlike the primary-record case, which forces has_more).
Replace with a real multi-reason / parity assert: page.has_more === false, digest YAML complete/cap_reasons, and next_cursor as applicable.
| t.is(cappedResult.code, 0); | ||
| const capped = JSON.parse(cappedResult.stdout); | ||
| t.false(capped.complete); | ||
| t.deepEqual(capped.cap_reasons, ['bytes']); |
There was a problem hiding this comment.
LOW — CLI gap: Spawn covers complete / page-only / bytes. displayed_edges is reachable via proof relations (full edges before record paging) but unasserted at the JSON boundary. primary_records may stay unit-only (limit === cap).
Add one relations (or spawn) assert: complete: false, cap_reasons: ['displayed_edges'].
| full body via the 600/12 excerpt). | ||
|
|
||
| Every read envelope carries `complete` and `cap_reasons`. A page with more | ||
| records has `complete: false`, an empty `cap_reasons`, and |
There was a problem hiding this comment.
LOW — docs: Envelope-first guidance is right. Still missing: (1) primary_records can yield has_more: true with null cursor, (2) cap_reasons may hold several sorted values, (3) § What not to do should also ban parsing summary (not only the digest).
Add a short decision tree and sync the .agents twin via skills:sync.


Fixes #252
What now works
completeandcap_reasons.completeis true only when paging and hard caps did not omit data.complete: false,cap_reasons: [], andpage.has_more: true.