Skip to content

fix(result): require durable supersession predecessors - #331

Open
seonghobae wants to merge 32 commits into
mainfrom
fix/result-supersession-predecessor-main-20260823
Open

fix(result): require durable supersession predecessors#331
seonghobae wants to merge 32 commits into
mainfrom
fix/result-supersession-predecessor-main-20260823

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Why

The original protected-main base 3bb873f02d2e1639be49e2bc9ac998c158b48d3d accepted an immutable ResultSnapshot whose supersedes_ref could name a result that did not exist when the successor was inserted. Because result rows are immutable, that left a dangling lineage edge and made a forward-reference cycle constructible through an insufficiently guarded persistence path.

The first failing boundary is Psychometrics Commons-owned result persistence, not scoring math: persist_result_snapshot inserted the caller-supplied predecessor reference without proving that predecessor was already visible, and migration 0007_result_snapshot.sql had no equivalent direct-SQL guard.

TDD / RCA lineage

  • RED 5abdcefcb5b824332ef10d671d6fb67fd3c14d40 adds a real PostgreSQL contract requiring a missing supersession predecessor to fail before any result row is stored.
  • GREEN 0225202de8929d91068bc17e8410e10245a0c2c2 adds typed application-level predecessor validation with InvalidSupersession before insert.
  • Database guard 04d1f399eab9344a77de87631310ac8df9079336 adds a BEFORE INSERT predecessor trigger and makes migration reapplication fail closed on pre-existing dangling or cyclic supersession evidence.
  • Verification contract 7fb300c89e6704e413e02ce316d95c470612180d covers the typed application failure, raw-SQL bypass attempt, migration reapplication over simulated historical cyclic evidence, and database-error propagation from predecessor lookup.
  • CI classification fix 92d52a3a0c940e6f03a13d3ae5f0435016cb1cd5 preserves the existing named self-supersession CHECK as the classifier instead of having the predecessor trigger misreport a self-reference as a missing predecessor.
  • Fixture RED d9051429008d74dc9d78389b454529f1bde47057 proves the fixed-schema integration test mutex is process-local and therefore invisible to a second PostgreSQL session.
  • Fixture GREEN 3d35065b5db17b5ef12831013fe415bde8d0b807 replaces that mutex with a database-session advisory lock, so concurrent Cargo/test processes sharing TEST_DATABASE_URL cannot race the result-supersession schema.
  • Current-main reconciliation eb9cf192633cac288b4dfb2319249f228779af1e is a regular two-parent merge of this exact product/test tree with protected main@8c6b433fc27678d772759720ca5325d3c3f23b4a; no rebase, force-push, or history rewrite was used.

The falsifiable acceptance hypothesis is that every newly inserted supersession edge references a different predecessor row already visible to the writer before the successor insert. A predecessor inserted earlier in the same transaction is intentionally visible and valid; a predecessor still uncommitted in a sibling transaction is rejected fail-closed. The ordering constraint prevents forward references and cycle construction, while migration reapplication rejects pre-existing dangling or cyclic lineage.

Current exact head

eb9cf192633cac288b4dfb2319249f228779af1e is directly ahead of protected main@8c6b433fc27678d772759720ca5325d3c3f23b4a with zero commits behind. The exact diff remains limited to:

  • migrations/0007_result_snapshot.sql
  • src/postgres_result_snapshot.rs
  • tests/postgres_result_supersession_predecessor.rs

Exact-head Runtime CI, Security Scan, SAST Semgrep, SPDX SBOM evidence, and supply-chain provenance were freshly queued after the reconciliation; queued evidence is not passing.

Architecture / documentation impact

This implements the already accepted ADR-0010 rule that corrections create immutable superseding results and supersession-chain integrity is validated. It changes enforcement only: no bounded-context ownership, public/admin operation, lifecycle state, logical entity/cardinality, serialized result shape, or psychometric publication rule changes. Existing ADR-0010 / TRD / UML / ERD semantics therefore remain unchanged; this PR remains IMPLEMENTED_ON_ACTIVE_PR evidence only until this exact head lands on protected main.

Scope

This is result-persistence lineage integrity plus the reliability of its real-PostgreSQL acceptance fixture only. It does not recompute psychometrics, change result observations, require same-session or same-participant supersession semantics, import the stale current-result reload implementation from #157, change public result transport, or touch another bounded context/database. #157 remains a separate reload/read-path concern.

Merge discipline

Do not merge until the unchanged exact head passes Runtime CI including the real PostgreSQL contracts, exact owned statement/branch coverage, rustfmt/Clippy/rustdoc, Security/SAST, SPDX SBOM, supply-chain provenance, has zero valid unresolved findings, and satisfies the live qualifying independent non-author/non-last-pusher approval requirement. The historical OpenCode REQUEST_CHANGES review applies to predecessor head 96e22211691efed5700ef8637549d1d30d60367e; it is not passing evidence for this new head and must not be treated as current approval. Pending, queued, skipped, cancelled, absent, stale, predecessor, synthetic, or model-only evidence is not passing. Never self-approve.


Open in Devin Review

Summary by CodeRabbit

  • 버그 수정
    • 결과 스냅샷의 선행 스냅샷 참조 유효성을 검증합니다.
    • 존재하지 않는 선행 스냅샷과 순환 계보 생성을 차단합니다.
    • 잘못된 참조 저장 시 명확한 오류를 반환합니다.
    • 동시 재저장 시 중복 결과와 관찰 기록이 중복 생성되지 않습니다.
    • 재저장 충돌을 선행 참조 오류보다 우선 정확히 분류합니다.
  • 테스트
    • 애플리케이션 저장 및 직접 SQL 입력의 무결성을 검증합니다.
    • 순환·누락 참조, 데이터베이스 오류, 동시성 및 재저장 시나리오를 검증합니다.

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 31 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c05ed16f-3f1d-4a98-85c5-5eb94cd90999

📥 Commits

Reviewing files that changed from the base of the PR and between 36d8de3 and 47922c4.

📒 Files selected for processing (5)
  • migrations/0007_result_snapshot.sql
  • tests/postgres_result_snapshot_concurrent_replay.rs
  • tests/postgres_result_snapshot_persistence.rs
  • tests/postgres_result_supersession_predecessor.rs
  • tests/postgres_result_supersession_replay_classification.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f0fd589c-9aae-4f88-9778-59be887288b1

📥 Commits

Reviewing files that changed from the base of the PR and between 84c787d and 36d8de3.

📒 Files selected for processing (1)
  • tests/postgres_result_snapshot_concurrent_replay.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

결과 스냅샷의 supersedes_ref 선행 스냅샷 존재 여부와 순환 계보를 검증합니다. 애플리케이션 저장 경로와 PostgreSQL 삽입 경로에 검사를 추가합니다. 재생 분류, 동시 삽입, 데이터베이스 오류 전달을 검증하는 통합 테스트를 추가합니다.

Changes

결과 스냅샷 무결성

Layer / File(s) Summary
데이터베이스 계보 검증
migrations/0007_result_snapshot.sql
기존 계보의 누락 선행 스냅샷과 순환 참조를 검사합니다. supersedes_ref CHECK 제약조건을 복원합니다. 새 result_snapshot 삽입 시 선행 스냅샷 존재 여부를 트리거로 검사합니다.
런타임 저장 검증
src/postgres_result_snapshot.rs
persist_result_snapshot이 기존 스냅샷 존재 여부를 먼저 확인합니다. 기존 스냅샷은 재생 결과로 분류합니다. 새 삽입은 선행 스냅샷을 검증하고, 누락된 참조는 InvalidSupersession으로 반환합니다.
무결성 통합 검증
tests/postgres_result_supersession_predecessor.rs, tests/postgres_result_supersession_replay_classification.rs, tests/postgres_result_snapshot_concurrent_replay.rs, tests/postgres_result_snapshot_persistence.rs
동일 트랜잭션의 삽입 순서, SQL 제약조건, 순환 계보, 누락 선행 스냅샷, 데이터베이스 오류 코드 보존, 재생 분류 및 동시 삽입 결과를 검증합니다. 테스트 격리를 advisory lock과 직접적인 search_path 설정으로 조정합니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: 🔵 Low · up to 36d8d

The persistence API now enforces durable supersession predecessors, but its public documentation still does not clearly define that requirement for callers. This is a bounded integration risk; the PR is otherwise mergeable with explicit owner follow-up.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant persist_result_snapshot
  participant PostgreSQL
  Caller->>persist_result_snapshot: ResultSnapshot 저장 요청
  persist_result_snapshot->>PostgreSQL: result_snapshot_ref 존재 여부 조회
  alt 기존 스냅샷 존재
    PostgreSQL-->>persist_result_snapshot: 기존 행 반환
    persist_result_snapshot-->>Caller: ConflictingReplay 또는 동일 재생 결과 반환
  else 새 스냅샷
    persist_result_snapshot->>PostgreSQL: 선행 스냅샷 조회
    alt 선행 스냅샷 존재
      PostgreSQL-->>persist_result_snapshot: 선행 행 반환
      persist_result_snapshot->>PostgreSQL: 스냅샷 삽입
      PostgreSQL-->>Caller: 저장 성공
    else 선행 스냅샷 없음
      PostgreSQL-->>persist_result_snapshot: 조회 결과 없음
      persist_result_snapshot-->>Caller: InvalidSupersession 반환
    end
  end
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 32 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 결과 스냅샷의 영속적 supersession predecessor 검증 추가라는 주요 변경을 정확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/result-supersession-predecessor-main-20260823

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@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 96e22211691efed5700ef8637549d1d30d60367e.

  • Head SHA: 96e22211691efed5700ef8637549d1d30d60367e

  • Workflow run: 32700481093

  • 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 (2 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (2 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Test: postgres_result_supersession_predecessor.rs"]
  S2 --> I2["regression suite"]
  I2 --> R2["Review risk: Test: postgres_result_supersession_predecessor.rs"]
  R2 --> V2["targeted test run"]
Loading

@opencode-agent

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 96e22211691efed5700ef8637549d1d30d60367e
  • Workflow run: 32700481093
  • 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 96e22211691efed5700ef8637549d1d30d60367e.

  • Head SHA: 96e22211691efed5700ef8637549d1d30d60367e

  • Workflow run: 32700481093

  • 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 (2 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (2 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Test: postgres_result_supersession_predecessor.rs"]
  S2 --> I2["regression suite"]
  I2 --> R2["Review risk: Test: postgres_result_supersession_predecessor.rs"]
  R2 --> V2["targeted test run"]
Loading

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae
seonghobae dismissed opencode-agent[bot]’s stale review August 25, 2026 11:53

Dismissed as predecessor-head evidence only. This REQUEST_CHANGES was explicitly submitted for head 96e22211691efed5700ef8637549d1d30d60367e and workflow run 32700481093. Current PR head is 64262302c230546cd1a43a56d94b090464fafa95; exact-head coverage-evidence and opencode-review are successful, and all current review threads are resolved. This dismissal does not approve the PR or transfer old evidence; all remaining unchanged-head checks and two qualifying independent approvals remain mandatory.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread migrations/0007_result_snapshot.sql Outdated
Comment on lines +316 to +344
IF EXISTS (
WITH RECURSIVE supersession_lineage AS (
SELECT
result_snapshot_ref AS start_ref,
supersedes_ref AS current_ref,
ARRAY[result_snapshot_ref]::text[] AS visited_refs
FROM result_snapshot
WHERE supersedes_ref IS NOT NULL

UNION ALL

SELECT
lineage.start_ref,
predecessor.supersedes_ref,
lineage.visited_refs || predecessor.result_snapshot_ref
FROM supersession_lineage AS lineage
JOIN result_snapshot AS predecessor
ON predecessor.result_snapshot_ref = lineage.current_ref
WHERE lineage.current_ref IS NOT NULL
AND NOT predecessor.result_snapshot_ref = ANY(lineage.visited_refs)
)
SELECT 1
FROM supersession_lineage
WHERE current_ref IS NOT NULL
AND current_ref = ANY(visited_refs)
) THEN
RAISE EXCEPTION 'result snapshot supersession lineage must be acyclic'
USING ERRCODE = '23514';
END IF;

@devin-ai-integration devin-ai-integration Bot Aug 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Cycle-detection CTE is quadratic in chain length

The recursive lineage CTE seeds one path per superseding row and walks each to its root, producing O(N^2) intermediate rows for a linear chain of length N, and it runs on every migration reapply. Fine for short lineages; a large table with long chains could make reapply slow.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration devin-ai-integration 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.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +125 to +129
if result_snapshot_exists(transaction, snapshot_ref)? {
return classify_existing_snapshot(transaction, snapshot, created_at, schema_version);
}

validate_supersession_predecessor(transaction, snapshot.supersedes_ref())?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Extra existence SELECT before every insert

persist_result_snapshot now runs result_snapshot_exists before every insert, adding a round trip to the common new-insert path. The concurrent race stays covered: the insert keeps ON CONFLICT DO NOTHING and falls through to classify_existing_snapshot when inserted==0. Intentional, so existing identities classify before predecessor validation.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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