Skip to content

fix(result): fail closed on cyclic session supersession - #157

Open
cursor[bot] wants to merge 19 commits into
mainfrom
cursor/bc-6faf48ca-02bf-4b48-b7a4-e1bec74889bd-a83a
Open

fix(result): fail closed on cyclic session supersession#157
cursor[bot] wants to merge 19 commits into
mainfrom
cursor/bc-6faf48ca-02bf-4b48-b7a4-e1bec74889bd-a83a

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Why

PR #137 loads the unique non-superseded result for the session a participant is viewing. If two snapshots each claim to supersede the other, that query returns no rows. Treating the empty tip set as "no score yet" lets a restarted worker invent a result.

This successor keeps #137's contiguous observation_order and current-tip load, and fails closed when the session has snapshots but no unique tip.

What this PR does

Out of scope

Test plan

  • cargo test --lib reference_guard_tests
  • cargo clippy --all-targets -- -D warnings
  • cargo test --test documentation_architecture_contract --test traceability_active_pr_contract --test result_snapshot_reload_contract
  • cargo test --test postgres_result_snapshot_persistence (needs TEST_DATABASE_URL)

Operator next action

Prefer this head over #137 and #119. After exact-head Runtime CI, coverage, and an independent last-push review, merge this slice. Then call load_current_result_snapshot_for_session with the session the participant is viewing so a restarted worker returns the stored tip or fails closed on a corrupt supersession graph.

Do not merge until exact-head checks and independent last-push approval are satisfied. Never self-approve.

Open in Web View Automation 

cursoragent and others added 5 commits August 16, 2026 15:29
A published result must survive process restart without calling the
scoring engine again. Reconstruct stored provenance and copied
observations so operators can serve the same snapshot, exact persist
replay stays Duplicate, and gapped or noncanonical evidence fails closed.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Reject gapped observation_order on reload and return the unique
non-superseded published tip for the session a participant is viewing.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
A persist-created A↔B supersession cycle made the current-tip query
return no rows. Treating that as absent looked like "no score yet" and
would let a restarted worker invent a result. Fail closed when the
session has snapshots but no unique tip.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review of 106a89b (cyclic session supersession)

The latest fix is the right fail-closed rule for this slice. After restart, call load_current_result_snapshot_for_session with the session the participant is viewing. None now means the session has no stored result rows. A supersession cycle (or any graph that leaves zero tips while rows exist) returns InconsistentEvidence, so a worker must not treat that as "score now".

The unit matrix (0,false) → Absent, (0,true) → fail closed, (1,true) → load, plus the PostgreSQL A↔B persist-then-load test, matches the buyer-visible bug from #137.

Do not merge this head yet. It is Draft, last-push CI is still queued, and an independent reviewer who did not author 106a89b still has to review the exact head. Prefer #157 over #137/aef54c9 and #119. Do not open another overlapping result-reload PR.

Leave on a later slice (not this PR):

  • Persist still accepts an A↔B pair (persist_ok in the new test). Load now fails closed; persist-time cycle rejection and a physical unique-current-tip constraint are not in this head.
  • No product HTTP/worker caller. Result-serving transport stays Target.
  • Do not fold response-snapshot reload (#151), item-delivery reload (#110), or session HTTP (#149) into this branch.

Independent reviewer: review 106a89b after Runtime CI, coverage, and required review workflows finish on this exact SHA. If those stay green and the persist-time residual is accepted as follow-up, this is the result-reload landing vehicle.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Comment thread src/postgres_result_snapshot.rs Outdated
Protected-main #81 requires claim_deadline_at on processing
integration_consumption rows. Recovery fixtures that omit the column
fail constraint integration_consumption_claim_deadline_shape.
@seonghobae
seonghobae marked this pull request as ready for review August 16, 2026 22:19
@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

seonghobae and others added 9 commits August 17, 2026 10:33
The isolated EXISTS ? after an empty tip list was unexecuted on Linux
coverage. Tip and snapshot counts now come from one query, and a blank
stored construct still fails closed through observation reconstruction.
Append-only observation triggers blocked the reconstruct fixture.
DISABLE TRIGGER ALL is fixture-only, matching other corrupt-row loads.
Immutability triggers and the construct-ref check both blocked the
blank-construct row. Fixture-only DROP CONSTRAINT plus DISABLE TRIGGER
ALL lets load reconstruct fail closed.
COUNT(*) cannot be negative at runtime, so the isolated try_from map_err
arm stayed unexecuted. stored_nonnegative_count now owns that fail-closed
conversion and the lib test takes the negative arm.
Linux llvm-cov left load_current_result_snapshot_for_session as an
unexecuted instantiation because only integration tests called it.
The lib test takes the missing-relation Database arm on that copy.
Linux llvm-cov left the isolated query_one ? on the unique-session tip
path uncovered. Classify that lookup through a match and instantiate the
Database arm from the library.
Linux llvm-cov still left one production line after the isolated
query_one ? was moved. Classify the count query through a match and
load a stored unique tip from a library test so the Ok arms execute
in the same instantiation as the Database miss.
@seonghobae

Copy link
Copy Markdown
Contributor

Hourly loop: Linux line coverage on 998851d3 was src/postgres_result_snapshot.rs:195 isolated query_one(...)?. Pushed d9e5b10a then ec1781bb — classify the count/tip lookups through match arms and instantiate the unique-tip Ok path from the library. Do not merge until exact-head checks and independent last-push APPROVE succeed. This comment is not approval.

@seonghobae

Copy link
Copy Markdown
Contributor

Hourly loop: exact head ec1781bb is rust/line/branch/opencode-review/noema-review/coverage-evidence SUCCESS. Last-push author is this implementer, so this is not independent last-push APPROVE and the PR is not mergeable here. Keep #146 draft until #138 lands. Never self-approve.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Blocking exact-head review: the new read-only reload paths inherit the persistence-only READ COMMITTED restriction. persist_result_snapshot needs READ COMMITTED because its ON CONFLICT DO NOTHING replay classifier relies on a fresh command snapshot after a concurrent unique-key winner; load_result_snapshot and load_current_result_snapshot_for_session do not. Rejecting REPEATABLE READ / SERIALIZABLE for pure reads unnecessarily breaks callers using stronger isolation and is inconsistent with the analogous audit reload contract. Remove the write-specific isolation precondition from both load paths (or narrowly prove a read-specific necessity), and replace the rejection test with success/absence/corruption coverage at stronger isolation. Keep persistence itself fail-closed at READ COMMITTED. The cycle/two-tip fail-closed semantics otherwise look sound on this head.

Persist still requires READ COMMITTED for ON CONFLICT replay
classification. load_result_snapshot and
load_current_result_snapshot_for_session are read-only and now succeed
under REPEATABLE READ and SERIALIZABLE, matching the audit reload
contract. Absence, unique-tip success, and cyclic-tip corruption stay
fail-closed at those levels.
@seonghobae

Copy link
Copy Markdown
Contributor

Hourly loop: addressed CHANGES_REQUESTED on load-path isolation. Persist still fail-closed at READ COMMITTED. cd35e606 lets load_result_snapshot and load_current_result_snapshot_for_session succeed under REPEATABLE READ / SERIALIZABLE, with absence, unique-tip, and cyclic-tip coverage at those levels. Do not merge without exact-head checks and independent last-push APPROVE. This comment is not approval.

@seonghobae

Copy link
Copy Markdown
Contributor

Hourly loop: exact head remains cd35e606 — rust/line/branch/opencode/noema/coverage-evidence SUCCESS. Isolation CHANGES_REQUESTED from ec1781bb is addressed on this head (loads succeed under REPEATABLE READ / SERIALIZABLE; persist stays READ COMMITTED). Last-push author is this implementer, so this is not independent last-push APPROVE and the PR is not mergeable here. Keep #146 draft. Never self-approve.

Keep session-result reload as Active PR #157. Persist stays READ
COMMITTED; read-only reload still succeeds under stronger isolation.
@seonghobae

Copy link
Copy Markdown
Contributor

Hourly loop: merged protected main after #244 (821b6a48) so the conflict is gone. Isolation CHANGES_REQUESTED remains addressed: persist stays READ COMMITTED; reloads succeed under REPEATABLE READ / SERIALIZABLE. Last-push is this implementer, so this is not independent last-push APPROVE. Keep #146 draft. Never self-approve.

@seonghobae

Copy link
Copy Markdown
Contributor

Hourly loop: exact head remains 821b6a48 — still CHANGES_REQUESTED with last-push this implementer, so not mergeable here. Isolation fix and #244 merge are already on this head. Keep #146 draft. Never self-approve.

Copy link
Copy Markdown
Contributor

@opencode-agent Please re-review exact unchanged head 821b6a48ddd79a26a4ae885535dc04825b99d436 against protected main aac99d0b523b8f765846fc6edad834e5cf903ca8. Current-head check evidence has no failed check-runs; the existing OpenCode CHANGES_REQUESTED review was produced from earlier failed coverage-evidence. Re-evaluate the current exact-head coverage/review evidence and approve only if the live policy, code findings, and independent-review requirements are actually satisfied.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 821b6a48ddd79a26a4ae885535dc04825b99d436.

  • Head SHA: 821b6a48ddd79a26a4ae885535dc04825b99d436

  • Workflow run: 32045151574

  • Workflow attempt: 1

Coverage evidence

Coverage Decision

  • Result: FAIL
  • Test evidence: not proven passing
  • Docstring evidence: not proven passing when configured
  • Failure count: 1

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (4 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (4 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Docs (2 files)"]
  S2 --> I2["operator or user guidance"]
  I2 --> R2["Review risk: Docs (2 files)"]
  R2 --> V2["docs review"]
  Evidence --> S3["Test (3 files)"]
  S3 --> I3["regression suite"]
  I3 --> R3["Review risk: Test (3 files)"]
  R3 --> V3["targeted test run"]
Loading

@opencode-agent

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 821b6a48ddd79a26a4ae885535dc04825b99d436
  • Workflow run: 32045151574
  • Workflow attempt: 1
  • Gate result: REQUEST_CHANGES (approval step)

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 821b6a48ddd79a26a4ae885535dc04825b99d436.

  • Head SHA: 821b6a48ddd79a26a4ae885535dc04825b99d436

  • Workflow run: 32045151574

  • Workflow attempt: 1

Coverage evidence

Coverage Decision

  • Result: FAIL
  • Test evidence: not proven passing
  • Docstring evidence: not proven passing when configured
  • Failure count: 1

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (4 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (4 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Docs (2 files)"]
  S2 --> I2["operator or user guidance"]
  I2 --> R2["Review risk: Docs (2 files)"]
  R2 --> V2["docs review"]
  Evidence --> S3["Test (3 files)"]
  S3 --> I3["regression suite"]
  I3 --> R3["Review risk: Test (3 files)"]
  R3 --> V3["targeted test run"]
Loading

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.

2 participants