Skip to content

Degrade a partial window to possibly_present instead of refusing it - #397

Merged
lamemustafa merged 1 commit into
claude/voucher-presencefrom
fix/294-partial-window-degrades-to-possibly-present
Sep 15, 2026
Merged

lamemustafa merged 1 commit into
claude/voucher-presencefrom
fix/294-partial-window-degrades-to-possibly-present

Conversation

@lamemustafa

Copy link
Copy Markdown
Owner

Stacked on #294 (base claude/voucher-presence), like #308. It is one commit.

The problem

BookWindow::observed refused construction unless read == WindowRead::Complete, so a window that could not be proven complete produced no verdict at all. The adapter has no source-side control total for a nonempty window, so every nonempty window is Partial — which meant the voucher_presence tool refused every real window that held vouchers, returning presence_window_incomplete instead of answering.

That placement was right about the hazard and wrong about its blast radius. Only one of the three verdicts ever needed a complete window:

verdict needs a proven-complete window?
Present no — an identity match names a row in hand
PossiblyPresent no — it authorises nothing
Absent yes — "I did not find it" means "it is not there" only if you saw everything

The change

The gate moves from window construction to the sole PresenceStatus::Absent production site. BookWindow retains its WindowRead and exposes read(); both states construct. decide checks it last among the existing withholdings — after RemoteIdEvidenceUnavailable, ManualNumberNotSupplied and the party outcomes — and degrades a Partial window's would-be Absent to PossiblyPresent(UndecidedReason::WindowNotProvenComplete). Present and PossiblyPresent are produced from a Partial window exactly as before.

PresenceError::WindowIncomplete is deleted rather than deprecated; nothing produced or consumed it afterwards.

This is the fourth instance of an idiom already in that function, not a new mechanism.

What this does not claim

  • The safety property is unchanged. The duplicate-invoice risk lives entirely in the Absent → "safe to post" path, and Absent is still only reachable from a window proven complete. This narrows what is withheld, not what is guaranteed.
  • No source-completeness evidence now exists. The adapter still cannot prove a nonempty window complete. An empty window is the narrow case the existing emptiness control can still corroborate Complete, and it remains the only shape from which absent is reachable.
  • No cardinality mechanism was found. CMPINFO/VOUCHER is a company-level counter, not window cardinality.

A bug found while documenting it

The voucher_presence description advertised the reason as presence_window_not_proven_complete. That is safe_reason_code's error-path spelling, and this flow no longer errors. UndecidedReason serialises through rename_all = "snake_case", so an item's reason field carries window_not_proven_complete — an agent matching the documented string would have matched nothing, silently, with no error anywhere.

The test meant to catch it was description.contains("presence_window_not_proven_complete"), which verified the documentation against itself and could not fail for the right reason. It is now anchored to reason `window_not_proven_complete` with a negative assertion on the prefixed form, so the two surfaces cannot drift back together silently. Driven to failure and restored byte-identical to confirm it discriminates.

ADR 0017

Updated to match: §2 now states the completeness gate at Absent's production keyed off read() rather than at construction, §4's Absent row requires a window proven read whole, and the three Consequences passages asserting construction-time refusal describe verdict-time degradation. The measured analysis is untouched — the source-side control total remains the only route to nonempty qualification, and the widened re-read stays rejected at a measured 1.95x for a detector that licenses nothing.

docs/adr/0017 is not a pinned compatibility-surface file. The three pinned Rust files that changed are resealed here.

Verification

Run locally under the pinned 1.96.0 toolchain, in CI's order:

check result
cargo fmt --all --check (src-tauri) clean
cargo test --workspace (src-tauri) 1500 passed, 0 failed, 1 ignored
cargo test --workspace --doc (src-tauri) 2 passed, 0 failed
cargo test --workspace (tools) 52 passed, 0 failed
cargo clippy --workspace --all-targets -D warnings (src-tauri) clean
cargo clippy --workspace --all-targets -D warnings (tools) clean
bridge-tally-compatibility gate compatibility_gate_passed:unknown_claims=11:evidenced_claims=0

Zero warning:/error: lines across the whole log. The one ignored test is replay_the_twenty_invoice_engagement, a manual owner-authorized live read that needs the lab reachable.

The invariant is proven by a mirror pair — identical window contents and proposal, Partial withholds Absent, Complete yields it. Without the second, the first could pass because absence had broken generally. Two pre-existing tests encoded the old contract directly and were renamed and inverted rather than deleted.

Not run: frontend/JS tests and the feature-gated protocol and live-read variants CI runs separately, which this change does not touch.

🤖 Generated with Claude Code

`BookWindow::observed` refused a `WindowRead::Partial` read outright
(`PresenceError::WindowIncomplete`). That placement was right about the
hazard and wrong about its blast radius: only `Absent` ever needed a
complete window, but a construction-time refusal withheld `Present` and
`PossiblyPresent` too. Since the adapter has no source-side control total
for a nonempty window, every nonempty window is `Partial` — so the tool
answered nothing at all over any range that held vouchers.

Move the gate to where `Absent` is produced. `BookWindow` now retains its
`WindowRead` and exposes `read()`; both states construct. `decide` checks
it at the sole `PresenceStatus::Absent` site, last among the withholdings,
and degrades a `Partial` window's would-be `Absent` to
`PossiblyPresent(UndecidedReason::WindowNotProvenComplete)`. `Present` and
`PossiblyPresent` are produced from a `Partial` window exactly as before.

`agent_presence.rs` continues past a nonempty window instead of refusing
early, and reports the window's real `read` state rather than a hardcoded
"complete". `agent_catalog.rs`'s `voucher_presence` description states the
new contract.

That description names the reason a caller will actually read, which is not
the one `safe_reason_code` returns. `UndecidedReason` serialises through
`rename_all = "snake_case"`, so an item's `reason` field carries
`window_not_proven_complete`; `presence_window_not_proven_complete` is the
error-path spelling and appears nowhere in a successful response. The
description advertises the serde form, and the schema test now asserts the
anchored string and rejects the prefixed one, so the two cannot drift apart
again without failing.

Tests pin the invariant the type system used to buy: a `Partial` window
withholds `absent` when nothing resembles a proposal, and the identical
contents read `Complete` still issue it — the pair, so the first cannot
pass because `Absent` broke generally.

ADR 0017 is updated to match: the completeness gate is documented at
`Absent`'s production rather than at construction, and the three
Consequences passages asserting construction-time refusal now describe
verdict-time degradation. Its measured analysis is unchanged — the
source-side control total is still the only thing that would close
nonempty qualification, and the widened re-read stays rejected. The ADR is
not a pinned compatibility-surface file; the three touched Rust files are,
and the surface and matrix are resealed here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@lamemustafa

Copy link
Copy Markdown
Owner Author

Evidence behind "no cardinality mechanism was found"

Recording the measurements so that claim in the description rests on data rather than assertion. All from today; the offline parts are reproducible from the captures under 50-projects/audit-sprint-2026-09-14/snap/akash/raw/agentfetch/ in the private vault.

CMPINFO/VOUCHER is not window cardinality

Across 53 captured windows of one book:

DISTINCT CMPINFO/VOUCHER across 53 windows: [458]
actual voucher-tag counts range: 0 .. 304

Exactly one distinct value while actual rows range 0–304 — including an empty day that still reports 458. A company-level counter, invariant to the requested window.

AlterID does not delimit a date range

Present and well-formed: 212 vouchers across 31 daily windows carry 212 ALTERID values, all distinct, range 16778–20873. AGENT_VOUCHER_FETCH requests it explicitly and the tool surfaces it as alter_id.

But it cannot bound a date window. Per 10-domains/11-tally/alterid-locality-decides-corpus-validity.md (verified 2026-07-31), AlterID rises as vouchers are entered, so a date window maps to a compact AlterID band only in a book entered in date order. A bulk-loaded book has no such locality — one day spans the whole range. Correlation is a property of the individual book, not of the protocol, so no AlterID bound generalises.

state: complete from the vouchers tool is a different claim

Verified live against a master build (13514994) on a populated window:

Shape Lab 2025-04-01..04-30 -> isError: False  state: complete  total: 13  963,188 bytes

That complete means the read finished without truncation. It is not the presence adapter's WindowRead::Complete, which means cardinality independently established. agent_presence.rs states the distinction directly: "a well-formed bounded response cannot be promoted to Complete merely because it contains rows." Conflating the two would look like a route to completeness and is not one.

Consequence

A nonempty window cannot be proven complete by any mechanism currently known, which is why the old construction-time refusal made the capability permanently unusable rather than temporarily limited. This change is the complete answer, not a stopgap.

Scope of these measurements

The 53-window and 212-voucher figures are from one book's captures under the fetch lists those reads used. They do not prove Tally exposes no count under some request shape nobody has tried — TDL collection functions in a compute position remain unprobed. They do establish that none of the three candidates actually available to Bridge today serves the purpose.

Two of my own earlier readings of these captures were wrong before this one: a regex using ASCII patterns against BOM-less UTF-16LE, then a bare <ALTERID> tag against a wire form carrying TYPE="Number" and a leading space. Both produced confident zeros. The figures above are from the corrected passes.

@lamemustafa
lamemustafa merged commit 7154923 into claude/voucher-presence Sep 15, 2026
1 check passed
@lamemustafa
lamemustafa deleted the fix/294-partial-window-degrades-to-possibly-present branch September 15, 2026 15:20
lamemustafa pushed a commit that referenced this pull request Sep 15, 2026
replay_the_twenty_invoice_engagement still asserted that a nonempty window
fails closed with presence_window_incomplete and evidence state partial.
#397 deleted that error variant and made a partial window answer, withholding
only Absent, so the assertion named a string the tool can no longer produce.

It compiled because the code is a string literal rather than the enum, and it
never failed because the test is #[ignore]d for the lab. An ignored test
cannot fail, so it rots without telling anyone -- the same shape as the tool
description that advertised the prefixed reason code, caught in review on
#397, and the second instance of it in the same change.

The assertions now mirror the offline equivalent: the window answers, its read
is partial, totals.absent is zero, and no item may come back absent, because
absence is the only verdict that needs to have seen the whole range. Nothing
in the tree references presence_window_incomplete any more.

Not run against the lab: the replay needs BRIDGE_TALLY_LIVE_* set, the
gateway free, and owner authorization. The assertions are corrected but
unexercised.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lamemustafa pushed a commit that referenced this pull request Sep 15, 2026
#397 moved the window-completeness gate from `BookWindow::observed` to
`Absent`'s production site, and #294 then synced to master. Both landed in
this branch's base, conflicting in five files.

Resolutions, none of which drop either side's work:

- `book_presence.rs` (5 hunks). Kept this branch's `ObservedWindow`
  constructor, `ColumnEvidence` and `narration_evidence`; kept the base's
  `read` field and `read()` accessor; deleted the `WindowIncomplete`
  refusal the base removed. The withholding order in `decide` is
  preserved: `skipped_evidence`, `ManualNumberNotSupplied`, the party
  outcomes, then the window read, then `Absent`.
- `agent_catalog.rs`. Both sides rewrote the `voucher_presence`
  description. Replayed this branch's two narration-marker sentences onto
  the base's rewritten contract text at anchors present verbatim in both,
  so neither description silently replaced the other.
- `book_presence_tests.rs` (2 hunks). Took the base's renamed and inverted
  partial-window test through this branch's constructor, and set
  `codes.len()` by counting the `PresenceError` variants the test lists
  rather than by arithmetic on the two sides.
- Both compatibility manifests. Took this branch's 217-pin set, which is a
  strict superset of the base's 216, then resealed with `scripts/reseal.sh`
  (36 hashes changed). Verified directly that no path is missing from
  either parent, since `--verify` passes over a dropped pin.

Two fixes the merge itself required:

`a_partial_window_withholds_absent_even_when_nothing_resembles_the_proposal`
auto-merged cleanly against the pre-`ObservedWindow` positional
constructor and would not have compiled. Ported.

`a_party_difference_echoes_the_source_spelling_not_its_catalog_binding`
passed on this branch and failed after the merge, deterministically.
Cause is master, not either PR: "Rectify unqualified folded ledger
binding" (#331) changed a folded name match from resolving a binding to
only suggesting candidates, so the proposal's `alpha traders` no longer
reached `PartyOutcome::Bound` and the party-difference block is guarded on
`Bound`. An exact spelling would have made the test a duplicate of
`a_party_difference_compares_the_observed_party_field_not_every_ledger`
under a name that no longer described it, so the test moves to the basis
that still decides without byte equality: an identifier embedded in the
master name. Driven to failure by removing that identifier, to prove it is
what carries the test.

ADR 0017 named the type `RemoteIdEvidence`, which this branch renamed to
`ColumnEvidence`, in three places. Corrected. The `RemoteIdEvidenceUnavailable`
reason variant is unchanged.

Verified under the pinned 1.96.0 toolchain: fmt clean; src-tauri workspace
1537 passed, 0 failed, 1 ignored (the manual live replay); doctests 2
passed; tools workspace 55 passed; clippy `-D warnings` clean on both
workspaces with no warning or error lines; `reseal.sh --verify` current.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lamemustafa pushed a commit that referenced this pull request Sep 15, 2026
#308's open thread held that the nonempty-window short-circuit made
`PresenceBasis::NarrationMarker` unreachable: the adapter refused before any
verdict was built, so the basis was contract-complete and operationally dead.
#397 was expected to fix that by letting a `Partial` window answer. Nothing
proved it. The adapter's ADR 0018 tests cover reader/writer agreement, legacy
labels, ambiguous markers, bounds and refusals, but no end-to-end marker
verdict, and `window_xml` carries no `NARRATION` at all -- so the existing
nonempty-window test's two `present` verdicts are both by voucher number.

The reachability also depended on a step below where the reasoning stopped:
the adapter must declare `narration_evidence: ColumnEvidence::Observed`, or
the marker path stays dead with the short-circuit gone. It does.

This drives it. The same nonempty (therefore `Partial`) window carries a
marker in JV-1's narration, under `automatic` numbering so that Tally's
discarding of a supplied number leaves the marker as the only basis that can
produce `present`. It comes back `present` with `basis: narration_marker`.

Proven by mutation rather than by passing:

- Neutralise the marker in the narration and the verdict drops to
  `possibly_present`, so the marker is what decides.
- Reinstate the pre-#397 short-circuit and the identical request returns
  `isError: true` with `presence_window_incomplete` and no verdicts at all,
  so the short-circuit is exactly what made the basis unreachable.

Both restored byte-identical afterwards.

`agent_presence_tests.rs` is pinned, so the surface is resealed: one hash
changed, pin set still 217, no path missing from either parent.

Verified under the pinned 1.96.0 toolchain: fmt clean; src-tauri workspace
1538 passed, 0 failed, 1 ignored (the manual live replay); doctests 2 passed;
tools workspace 55 passed; clippy `-D warnings` clean on both workspaces with
no warning or error lines; `reseal.sh --verify` exit 0, read directly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lamemustafa added a commit that referenced this pull request Sep 15, 2026
`ci.yml` filtered `pull_request` to `branches: [master]`, so a pull request
stacked on another branch triggered no run at all. The consequence was not a
missing badge: such a PR reports `mergeStateStatus: CLEAN`, because the only
check that does run is GitGuardian, and "clean" means "no required check
failed" rather than "the suite ran". A stack accumulated unchecked changes
until its root merged, at which point the first real run met several pull
requests' worth of change at once.

This was reached, not hypothesised. #397 merged into `claude/voucher-presence`
with one check on it; #308 sat on that branch through a base sync, a conflict
resolution and a test rewrite, showing CLEAN throughout. A manual
`workflow_dispatch` on #308's branch was the first Bridge CI run it ever had,
and it passed on macOS and Windows -- so the gap was coverage, not a latent
failure. It could as easily have been the other way.

The `push` trigger stays master-only: a branch worth checking has a pull
request, and that is what triggers the run, so filtering the push side avoids
a second run per branch rather than losing coverage.

`ci.yml` is in the compatibility surface, so the surface and matrix are
resealed: one hash changed, pin set unchanged at 212, `reseal.sh --verify`
exit 0 read directly.

Verified: `check-ci-workflow-consistency.mjs` passes; the compatibility gate
passes; tools workspace 11 binaries, 53 passed, 0 failed. The workflow parses
and keeps its 9 jobs, with `pull_request` now carrying only `types`.

Not changed, and worth a separate decision: `dependency-security.yml` carries
the same `branches: [master]` filter, and `docs/proposed-merge-gate-ci.md`
proposes a merge-gate workflow with it too. Both inherit this gap.

Co-authored-by: t <dev@example.invalid>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant