Skip to content

fix(proof): expose read completeness - #254

Merged
tonyketcham merged 14 commits into
mainfrom
stack/toeknee-figma/fix/proof-read-completeness/expose-read-completeness--4cbd9019
Aug 22, 2026
Merged

fix(proof): expose read completeness#254
tonyketcham merged 14 commits into
mainfrom
stack/toeknee-figma/fix/proof-read-completeness/expose-read-completeness--4cbd9019

Conversation

@toeknee-figma

@toeknee-figma toeknee-figma commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Fixes #252

What now works

  1. Every Proof read envelope includes complete and cap_reasons.
  2. complete is true only when paging and hard caps did not omit data.
  3. Ordinary paging returns complete: false, cap_reasons: [], and page.has_more: true.
  4. Record, edge, and byte caps return stable, duplicate-free reasons.
  5. The JSON envelope, digest header, and summary use the same completeness state.

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
@tonyketcham
tonyketcham marked this pull request as ready for review August 17, 2026 20:26
@tonyketcham
tonyketcham self-requested a review August 17, 2026 20:26
@mergify

mergify Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale 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

  1. HIGH — No test stacks bytes on primary_records / displayed_edges after the byte-cap recompute (digest.ts:311). Singles stay green if that recompute regresses when reasons are already non-empty.
  2. MEDprimary_records still sets page.has_more: true with next_cursor: null, and YAML primary.has_more can disagree with envelope page.has_more (pre-existing quirk; does not falsify the new fields).
  3. MED — New hard-cap tests miss YAML/summary/next_cursor parity; the displayed_edges uniqueness assert is vacuous with one reason.
  4. LOW — CLI spawn covers complete / page / bytes only; add a proof relations path for displayed_edges (primary_records is unit-only because render() limit === CAP_RECORDS).

Coverage plan (ordered)

  1. Stacked hard caps + byte rebuild → sorted multi cap_reasons
  2. primary_records: assert next_cursor === null + YAML/envelope has_more parity (or fix policy)
  3. displayed_edges: assert page.has_more === false; replace vacuous Set check
  4. Combined hasMore + hard cap
  5. CLI displayed_edges spawn 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.

Open in Web View Automation 

Sent by Cursor Automation: Flatbread PR Review

Comment thread packages/proof/src/digest.ts Outdated
Comment thread packages/proof/src/digest.ts Outdated
Comment thread packages/proof/src/digest.ts
Comment thread packages/proof/src/digest.ts
Comment thread packages/proof/src/__tests__/digest.test.ts Outdated
Comment thread packages/flatbread/src/cli/proof.test.ts
Comment thread packages/proof/skills/proof/reference.md Outdated
Apply the reviewed pagination contract, stacked cap tests, CLI edge coverage, and synced read guidance as a fast-forward follow-up to PR #254.\n\nFixes #252

Change-Id: I74d0516d4279254bced9cc4e81970ed1cc498de8

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_records gap as a docs/contract clarification (rule: coverage plan non-empty + non-test source → COMMENT).

Prior sync @ 7602e86 gaps look closed at dd87773 (stacked caps + byte recompute, page.has_more no longer ORs primary overflow, CLI displayed_edges, stronger asserts).

Standing follow-ups (priority)

  1. CHANGELOG — one Unreleased sentence: page.has_more is pagination-only; use cap_reasons / complete for hard caps.
  2. Both reference.md copies — page only when page.has_more; hard cap_reasons that paging cannot clear → narrow/fail closed; note primary_records is in-process/defensive after CLI pre-slice ≤25.
  3. Tests — unit hasMore∩hard-cap summary co-list; CLI page-only assert non-null next_cursor; optional edges+bytes rebuild / bytes has_more lock.

Coverage plan (abridged)

  • digest.test.ts — edge: paging∩hard-cap summary co-list
  • digest.test.ts — edge: stacked displayed_edges+bytes after byte rebuild
  • proof.test.ts — positive: proof list --limit 1 asserts page.next_cursor when has_more
  • proof.test.ts — edge: relations spawn asserts summary includes displayed_edges and pagination

Reviewer 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.

Open in Web View Automation 

Sent by Cursor Automation: Flatbread PR Review

Comment thread CHANGELOG.md Outdated
Comment thread .agents/skills/proof/reference.md
Comment thread packages/proof/skills/proof/reference.md
Comment thread packages/flatbread/src/cli/proof.test.ts
Comment thread packages/proof/src/__tests__/digest.test.ts
Comment thread packages/proof/src/digest.ts Outdated
@tonyketcham tonyketcham added the Explain Get a breakdown of the PR label Aug 22, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.]
Loading

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 on proof relations)
  • bytes — digest larger than 64 KiB (real on a huge proof 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

  1. ReadEnvelope always includes complete and cap_reasons.
  2. One helper (digestCompleteness) feeds JSON, YAML, and summary, so they cannot disagree.
  3. Follow-up commit dd87773 stopped lying about “Load more.” page.has_more now tracks only input.hasMore. Hitting the 25-record wall no longer sets has_more: true with a null cursor. That was the Gmail “Load more” button with no next page behind it.
  4. Tests cover complete, page-only, each cap, stacked bytes + primary_records, and a spawned CLI path for displayed_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
Loading

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.

Open in Web View Automation 

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
@tonyketcham
tonyketcham enabled auto-merge August 22, 2026 17:02

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. CHANGELOG now says page.has_more is pagination-only.
  2. Both reference.md copies: page only when page.has_more; hard caps that paging cannot clear mean narrow or fail closed; primary_records is in-process after the CLI slice.
  3. CLI spawn asserts a non-null next_cursor on page-only list, a null cursor on bytes, and summary names displayed_edges with pagination.
  4. Digest unit covers displayed_edges plus hasMore / nextCursor.
  5. renderDigest treats paging as present only when hasMore is true and nextCursor is a non-empty string.

packages/flatbread/src/proof/read.ts always pairs hasMore with encodeCursor. A bare hasMore: true becomes complete: true only 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 nextCursor when hasMore is false; byte-rebuild page.returned vs sections actually written; drop the redundant complete && !hasMore guard in summary().

Perspectives

architecture · simplify/quality · proof-journal · correctness/edge-cases

Open in Web View Automation 

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
cursor[bot]
cursor Bot approved these changes Aug 22, 2026
tonyketcham added a commit that referenced this pull request Aug 22, 2026
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>
tonyketcham added a commit that referenced this pull request Aug 22, 2026
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>
tonyketcham and others added 5 commits August 22, 2026 13:05
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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Review verdict: COMMENT

This sync (a9c3d61) merges #260 onto the completeness work last approved at 55673a2. The read envelope is still sound. Retract is the right named archive mutation. One sole-closer hole remains.

Completeness (unchanged since 55673a2)

complete and cap_reasons sit on the envelope. page.has_more is pagination-only. Unpaired hasMore without a non-empty cursor is treated as no page. JSON, digest YAML, and summary share one derived state. Not re-opening that contract.

Retract increment

Browse reads omit retracted records. proof get still returns the file and reason. Retract refuses when the target is the last resolved_by / mitigated_by / evidence / rejected_by / superseded_by value on a live same-Effort record. That closes the #260 orphan for the common one-id cases.

isSoleCloser counts remaining ids, not remaining Finding-kind evidence. SetRiskState allows mixed evidence and only requires some Finding when realizing a Risk. Retracting that Finding while a Decision stays in the list leaves state: realized with no Finding. See the inline on planner.ts.

reference.md still 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: false still holds (proposed siblings remain open).

Prior threads

All earlier notes stay closed. Not re-raising unpaired-hasMore library refuse, invalidated_by as a closer (invalidation is not a terminal state), or the leftover complete && !hasMore guard in summary().

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 writes complete && !hasMore.

Perspectives: architecture · simplify/quality · proof-journal · correctness-and-edge-cases.
Models: Cursor Grok 4.5 High (architecture, journal, correctness) / Composer 2.5 (quality).

Open in Web View Automation 

Sent by Cursor Automation: Flatbread PR Review

Comment thread packages/proof/src/planner.ts
Comment thread packages/proof/skills/proof/reference.md Outdated
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>
cursor[bot]
cursor Bot approved these changes Aug 22, 2026
…ess/expose-read-completeness--4cbd9019

Change-Id: I56f88b13bec1a003c561c01024a10e4ee7bdc6ad

Co-authored-by: Erika Ruth Witt <erikawitt@users.noreply.github.com>
@tonyketcham
tonyketcham merged commit a1ef93d into main Aug 22, 2026
21 checks passed
@tonyketcham
tonyketcham deleted the stack/toeknee-figma/fix/proof-read-completeness/expose-read-completeness--4cbd9019 branch August 22, 2026 23:21

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

  1. Every Proof read envelope has complete and cap_reasons. page.has_more is pagination-only and needs a non-empty next_cursor. Hard caps stay on complete / cap_reasons. JSON, digest YAML, and summary share one derived state.
  2. 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.

Open in Web View Automation 

Sent by Cursor Automation: Flatbread PR Review

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

Labels

Explain Get a breakdown of the PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

proof: expose completeness and cap reasons in read envelopes

3 participants