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.
Stale comment
Review verdict: COMMENT
Issue #252’s
complete/cap_reasonsfields 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_reasonsprimary_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 assertPerspectives
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
There was a problem hiding this comment.
Stale comment
Review verdict
COMMENT — Coverage plan is non-empty and non-test sources changed; no BLOCKER and no consensus HIGH after weighting the CLI
primary_recordsgap as a docs/contract clarification (rule: coverage plan non-empty + non-test source → COMMENT).Prior sync @
7602e86gaps look closed atdd87773(stacked caps + byte recompute,page.has_moreno longer ORs primary overflow, CLIdisplayed_edges, stronger asserts).Standing follow-ups (priority)
- CHANGELOG — one Unreleased sentence:
page.has_moreis pagination-only; usecap_reasons/completefor hard caps.- Both
reference.mdcopies — page only whenpage.has_more; hardcap_reasonsthat paging cannot clear → narrow/fail closed; noteprimary_recordsis in-process/defensive after CLI pre-slice ≤25.- Tests — unit
hasMore∩hard-capsummary co-list; CLI page-only assert non-nullnext_cursor; optional edges+bytes rebuild / byteshas_morelock.Coverage plan (abridged)
digest.test.ts— edge: paging∩hard-cap summary co-listdigest.test.ts— edge: stackeddisplayed_edges+bytesafter byte rebuildproof.test.ts— positive:proof list --limit 1assertspage.next_cursorwhenhas_moreproof.test.ts— edge: relations spawn assertssummaryincludesdisplayed_edgesandpaginationReviewer scoreboard
- correctness-and-contracts: 4 findings, 5 gaps, signal:HIGH
- test-coverage-robustness: 3 findings, 5 gaps, signal:HIGH
- cli-and-runtime: 6 findings, 4 gaps, signal:MED
- docs-and-positioning: 4 findings, 4 gaps, signal:MED
- release-discipline: 3 findings, 1 gap, signal:MED
Models: HIGH=
cursor-grok-4.5-high; MED/LOW=composer-2.5. Oven unavailable → Task fallback.Sent by Cursor Automation: Flatbread PR Review
There was a problem hiding this comment.
The one-sentence version
This PR gives every flatbread proof read a machine-readable “did I get everything, and if not, why?” answer. Before, that answer lived only in a prose summary and a Markdown digest — like screenshotting Gmail to see if “Load more” is showing.
It fixes #252. Human review already approved an earlier commit. Six review notes from 19 Aug are still open. None of them block the feature. They ask you to say the “Load more” rule out loud and lock it with two small tests.
Why this exists (product analog)
Think of Proof reads like Gmail search, not like GraphQL content queries.
Gmail has two different “not done” states:
| Gmail | Proof after this PR | What you do |
|---|---|---|
| Load more conversations | page.has_more: true + a next_cursor bookmark |
Fetch the next page |
| “This message is too large to display” | cap_reasons: ["bytes"] (or records/edges) |
Stop. Narrow the query or open one record. Paging will not help. |
| Inbox fully loaded | complete: true |
Trust the artifact |
1.0.1 shipped the first column missing. All four cases — a small proof get, a --limit 1 list, a 51-edge proof relations, a 70 KiB Blob — returned the same JSON keys. page.has_more could be false even when the digest was cut off. The named reasons (displayed_edges, bytes) hid in Markdown.
That is like Slack search saying “50 results” with no “see more” and no “results truncated” banner. A program cannot fail closed. It has to parse English.
This PR adds the missing banner fields and makes the three surfaces agree: JSON envelope, digest YAML header, and summary.
The two kinds of incomplete
Paging is a bookmark. A hard cap is a wall.
flowchart TD
A[Proof read JSON] --> B{complete?}
B -->|true| C[You have everything. Stop.]
B -->|false| D{page.has_more?}
D -->|true| E[Fetch page.next_cursor.
Null cursor = bug. Do not retry the same page.]
D -->|false| F{cap_reasons empty?}
F -->|yes| G[Incomplete with no page and no cap.
Treat as a bug.]
F -->|no| H[Hard cap. Narrow or fail closed.
Do not invent a cursor.]
Three hard-cap names, sorted and de-duplicated:
primary_records— more than 25 records in this digest (in-process safety net; the CLI already slices to 25 before render)displayed_edges— more than 50 links shown (real onproof relations)bytes— digest larger than 64 KiB (real on a hugeproof get)
Paging is not a cap reason. It lives on page only. So a normal --limit 1 list looks like:
{ "complete": false, "cap_reasons": [], "page": { "has_more": true, "next_cursor": "…" } }A too-large Blob looks like:
{ "complete": false, "cap_reasons": ["bytes"], "page": { "has_more": false, "next_cursor": null } }Same split Stripe uses: has_more + starting_after for lists, vs a 413 / truncated payload for a body that will not fit. Do not treat those as the same button.
What already landed
ReadEnvelopealways includescompleteandcap_reasons.- One helper (
digestCompleteness) feeds JSON, YAML, andsummary, so they cannot disagree. - Follow-up commit
dd87773stopped lying about “Load more.”page.has_morenow tracks onlyinput.hasMore. Hitting the 25-record wall no longer setshas_more: truewith a null cursor. That was the Gmail “Load more” button with no next page behind it. - Tests cover complete, page-only, each cap, stacked
bytes+primary_records, and a spawned CLI path fordisplayed_edges.
Open feedback, in plain English
Six threads from the 19 Aug bot review are still open. The 17 Aug threads are resolved. None of the open notes say “the feature is wrong.” They say “the map and the tests still skip one corner.”
flowchart LR
subgraph Closed[Closed 17 Aug]
S1[Stacked bytes+records test]
S2[has_more no longer ORs the record wall]
S3[CLI relations asserts displayed_edges]
end
subgraph Open[Still open 19 Aug]
O1[CHANGELOG: say has_more is paging-only]
O2[Both reference.md copies: do not say page a hard cap]
O3[CLI page-only: assert next_cursor is present]
O4[Unit: paging AND a hard cap in one digest]
end
1. CHANGELOG forgot the button-meaning change (CHANGELOG.md)
The Unreleased note advertises the new badge fields. It does not say page.has_more no longer lights up for the 25-record wall. That is the Stripe-style migration: has_more is pagination only; use cap_reasons / complete for walls. Also do not imply the CLI will ever print primary_records — the read bridge already slices to 25.
2 + 3. The two reference.md copies argue with themselves
The numbered decision tree is right: page only when page.has_more; a hard cap ⇒ narrow or fail closed.
The paragraph above it still says “narrow the query or page” when you hit a cap. On proof relations, paging and displayed_edges can both be true, and paging will not clear the 50-edge wall. Same wording lives in .agents/skills/proof/reference.md and packages/proof/skills/proof/reference.md. Keep them identical.
4. CLI page-only test checks the button, not the bookmark (proof.test.ts)
proof list --limit 1 asserts has_more: true but not a non-null next_cursor. That is testing Instagram “See more” without checking the link. Also assert a null cursor on the bytes path, and that the relations summary names both displayed_edges and pagination when they co-occur.
5. No unit case for both at once (digest.test.ts)
Hard-cap fixtures never set hasMore: true. So summary saying incomplete: displayed_edges, pagination is unproven in the library. Add one renderDigest with a cursor and a hard cap.
6. Optional hardening (digest.ts has_more)
Keep the new rule. Do not put the record wall back into has_more. Optionally refuse hasMore: true without a nextCursor, so a broken “Load more” cannot be emitted from a mis-paired input.
PR risk: 40/100 (Cross-cutting)
- Blast radius: 7 — Proof digest + CLI tests + two skill copies; not schema, codegen, or examples
- Contract stickiness: 12 — Additive `complete` / `cap_reasons`; `page.has_more` now means paging only
- Reversibility: 3 — Revert the PR; digest cache regenerates on the next read
- Proof gap: 8 — Happy, fail, and stacked caps are covered; paging∩hard-cap and CLI `next_cursor` are still open
- Rollout load: 8 — CHANGELOG and both `reference.md` copies still need the paging-only sentence
Review: MED
Next: Add the paging-only `has_more` sentence to CHANGELOG and both `reference.md` copies, then assert `next_cursor` on the page-only CLI spawn.
Raw sum 38, rounded to 40. This is not a schema/codegen train. It is a shipped Proof read-envelope contract with one semantic tighten (has_more) that the changelog still undersells.
Sent by Cursor Automation: Explain this change
Addresses the 19 Aug / 22 Aug grouped notes on #254. - CHANGELOG: page.has_more is pagination-only; use cap_reasons for walls - Both reference.md copies: page only when has_more; hard caps need a narrower query - CLI spawn: assert next_cursor on page-only, null on bytes, summary co-list - Digest unit: paging plus displayed_edges; refuse hasMore without a cursor - renderDigest: has_more requires a non-empty nextCursor Change-Id: I4023a95677e42aee745e3a2e6748852cb8ea1af3 Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
…eness-review-21cd fix(proof): lock paging-only has_more after review
There was a problem hiding this comment.
Stale comment
Review verdict: COMMENT
Architecture and correctness hold at
be2cece. The 19 Aug notes are closed. The remaining ask is Proof journal quality, not the envelope.Prior threads
All six open notes from the last sync are done:
- CHANGELOG now says
page.has_moreis pagination-only.- Both
reference.mdcopies: page only whenpage.has_more; hard caps that paging cannot clear mean narrow or fail closed;primary_recordsis in-process after the CLI slice.- CLI spawn asserts a non-null
next_cursoron page-only list, a null cursor on bytes, and summary namesdisplayed_edgeswithpagination.- Digest unit covers
displayed_edgesplushasMore/nextCursor.renderDigesttreats paging as present only whenhasMoreis true andnextCursoris a non-empty string.
packages/flatbread/src/proof/read.tsalways pairshasMorewithencodeCursor. A barehasMore: truebecomescomplete: trueonly for mis-paired library input. That is the intended refuse, not a CLI lie.Remaining
The completeness contract is a significant event and this PR does journal it. The accepted Decision titles a five-file split, not the product rule. The Finding uses Issue kind
gap. See the two inline notes. Not a merge blocker for the code.Optional later (not required): leftover
nextCursorwhenhasMoreis false; byte-rebuildpage.returnedvs sections actually written; drop the redundantcomplete && !hasMoreguard insummary().Perspectives
architecture · simplify/quality · proof-journal · correctness/edge-cases
Sent by Cursor Automation: Flatbread PR Review
The 22 Aug review of #254 asked to record that page.has_more is pagination-only, not a five-file split, and to stop using Issue kind on the Finding. Co-authored-by: Cursor <cursoragent@cursor.com> Change-Id: I18e831209621f3373523f5c0b2aa3acceafed031
…54-review docs(proof): journal paging-only has_more as the product rule
Keep Proof writes for durable turning points only. Score the information before any mutation or body edit. Existing records do not bypass the gate. Bundle the four eval cases with the skill. Drop PR-lifecycle Proof records from #254 and keep the accepted pagination Decision as the durable rationale. Co-authored-by: Cursor <cursoragent@cursor.com> Change-Id: I9c2eab5cc1df96e6ca2287d21c9f69872d99a072 Co-authored-by: Cursor <cursoragent@cursor.com>
Keep Proof writes for durable turning points only. Score the information before any mutation or body edit. Existing records do not bypass the gate. Bundle the four eval cases with the skill. Drop PR-lifecycle Proof records from #254 and keep the accepted pagination Decision as the durable rationale. Co-authored-by: Cursor <cursoragent@cursor.com> Change-Id: I9c2eab5cc1df96e6ca2287d21c9f69872d99a072 Co-authored-by: Cursor <cursoragent@cursor.com>
Keep Proof writes for durable turning points only. Score the information before any mutation or body edit. Existing records do not bypass the gate. Bundle the four eval cases with the skill. Drop PR-lifecycle Proof records from #254 and keep the accepted pagination Decision as the durable rationale. Co-authored-by: Cursor <cursoragent@cursor.com> Change-Id: I9c2eab5cc1df96e6ca2287d21c9f69872d99a072 Co-authored-by: Cursor <cursoragent@cursor.com>
Git-deleting Proof files leaves dangling edges and breaks the writer contract. Retract tombstones a record in place, strips its id from the same Effort, and drops it from browse reads. proof get still returns the file and reason. Eval 3 now teaches Retract instead of a file-drop path. The 4/4 write gate carves Retract out with other lifecycle mutations. Co-authored-by: Cursor <cursoragent@cursor.com> Change-Id: Idc3e85815ca30398b1843d5a27636e1554fb35d2
Supersede the fifteen-mutation Constraint and record why Retract tombstones files instead of git-deleting them. Co-authored-by: Cursor <cursoragent@cursor.com> Change-Id: Idea69e9bdc016d6f0f18c934f676b5dbee60fabe
…cord stripRelationId removes a retracted record's id from resolved_by, mitigated_by, evidence, rejected_by, and superseded_by on every same-Effort record, but never restores the survivor's status or state. There is no reopen mutation, so the survivor stayed terminal with the pointer gone. Retract now refuses when the target is the last value in any of those closer-pointer fields on a live same-Effort record, and names the dependents in the error. Retracted survivors are skipped, and a target that shares its closer role with others still retracts (the survivor keeps the remaining closers). Adds planner tests for the resolved Issue, mitigated/realized Risk, rejected sibling Decision, and superseded record shapes, plus the multiple-closer and retracted-survivor controls. Change-Id: I31d67dde9cce8354582c6733da4fceaa86444801 Co-authored-by: Erika Ruth Witt <erikawitt@users.noreply.github.com>
…proof-retention-gate/gate-journaling-4-4-retention-score--9c2eab5c docs(proof): gate journaling on a 4/4 retention score
There was a problem hiding this comment.
Stale comment
Review verdict: COMMENT
This sync (
a9c3d61) merges #260 onto the completeness work last approved at55673a2. The read envelope is still sound. Retract is the right named archive mutation. One sole-closer hole remains.Completeness (unchanged since
55673a2)
completeandcap_reasonssit on the envelope.page.has_moreis pagination-only. UnpairedhasMorewithout a non-empty cursor is treated as no page. JSON, digest YAML, andsummaryshare one derived state. Not re-opening that contract.Retract increment
Browse reads omit retracted records.
proof getstill returns the file and reason. Retract refuses when the target is the lastresolved_by/mitigated_by/evidence/rejected_by/superseded_byvalue on a live same-Effort record. That closes the #260 orphan for the common one-id cases.
isSoleClosercounts remaining ids, not remaining Finding-kind evidence.SetRiskStateallows mixedevidenceand only requires some Finding when realizing a Risk. Retracting that Finding while a Decision stays in the list leavesstate: realizedwith no Finding. See the inline onplanner.ts.
reference.mdstill says the writer always strips the id. It does not say Retract can refuse. Agents will retry or hand-edit. See the inline.Proof journal
STRONG. The pagination-only Choice (
dec-treat-page-has-more-as-pagination-only--dv24ta688adf262v) and Retract-as-archive (dec-retract-noise-instead-of-deleting-proof-files--k6jk0d2bdp1m9jw9) are accepted Decisions with context, alternatives, consequences, and reversal. The 4/4 write gate lives in the skill and evals; it does not need its own Decision. Git-deleted #254 review Findings/Issues/Citations left no dangling ids.rejectSiblings: falsestill holds (proposed siblings remain open).Prior threads
All earlier notes stay closed. Not re-raising unpaired-
hasMorelibrary refuse,invalidated_byas a closer (invalidation is not a terminal state), or the leftovercomplete && !hasMoreguard insummary().Domains
- Architecture: COMMENT. Envelope completeness and Retract-as-mutation are the right shapes.
- Correctness: COMMENT. Standing HIGH is the mixed-evidence realized Risk.
- Proof journal: STRONG.
- Simplify / quality: optional LOW only —
summary()still writescomplete && !hasMore.Perspectives: architecture · simplify/quality · proof-journal · correctness-and-edge-cases.
Models: Cursor Grok 4.5 High (architecture, journal, correctness) / Composer 2.5 (quality).Sent by Cursor Automation: Flatbread PR Review
isSoleCloser counted remaining evidence ids, so mixed evidence of a Finding and a Decision let Retract strip the Finding and leave state: realized with no Finding. Refuse Retract when the target is the last live Finding-kind id on a realized Risk. Document the gate in both reference.md copies. Addresses review on #254. Change-Id: I81f3c56b41f483a46d71330b71ea656aaf6453ae Co-authored-by: Erika Ruth Witt <erikawitt@users.noreply.github.com>
…ess/expose-read-completeness--4cbd9019 Change-Id: I56f88b13bec1a003c561c01024a10e4ee7bdc6ad Co-authored-by: Erika Ruth Witt <erikawitt@users.noreply.github.com>
There was a problem hiding this comment.
Review verdict: APPROVE
This sync (765f8ea) merges main into the completeness + Retract stack. Proof digest, planner, and read logic did not change versus the last approval at 3b4a684. The merge only brings brand-name copy and brand marks already on main.
Not a dependency-only update. Four domain reviews (architecture, simplify/quality, Proof journal, correctness) all returned STRONG with no new findings.
Contracts (unchanged)
- Every Proof read envelope has
completeandcap_reasons.page.has_moreis pagination-only and needs a non-emptynext_cursor. Hard caps stay oncomplete/cap_reasons. JSON, digest YAML, andsummaryshare one derived state. - Retract is the named archive mutation. It tombstones in place, strips same-Effort pointers, omits the record from browse reads, and keeps it on
proof get. It still refuses a sole closer and the last live Finding on a realized Risk.
Proof journal
STRONG / complete. dec-treat-page-has-more-as-pagination-only and dec-retract-noise-instead-of-deleting-proof-files already own the product rules. This merge needs no new record (4/4 unique-signal fail). rejectSiblings: false still holds.
Prior threads
All earlier notes stay closed. Not re-raising the leftover complete && !hasMore guard in summary(), or the reused “sole closer” error string on the Finding-kind path.
Domains
- Architecture: STRONG. Completeness vs paging, and Retract vs git-delete, stay the right shapes.
- Simplify / quality: STRONG. The two contracts do not stack into extra control flow.
- Proof journal: STRONG. No new record required.
- Correctness: STRONG. Digest, planner, writer, and CLI tests hold; merge introduced no Proof logic change.
Perspectives: architecture · simplify/quality · proof-journal · correctness-and-edge-cases.
Sent by Cursor Automation: Flatbread PR Review


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.