Skip to content

fix(durable): enforce lifecycle progress invariants - #148

Merged
seonghobae merged 33 commits into
mainfrom
fix/durable-progress-invariants
Aug 12, 2026
Merged

fix(durable): enforce lifecycle progress invariants#148
seonghobae merged 33 commits into
mainfrom
fix/durable-progress-invariants

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Data-integrity gap

Issue #135 identified two durable lifecycle defects: a single provider observation could carry completed + failed > total, and independent monotonic GREATEST(...) updates could combine individually valid observations into an impossible stored projection.

The branch now also carries the smallest repository-owned repair lane for a current required-Strix finding on db.apply_schema(): the low-level helper still accepts an arbitrary caller-selected SQL file path even though the supported CLI always applies the packaged schema and the helper docstring itself describes packaged-schema authority. This is being handled test-first on this already-authoritative db.py branch rather than creating a competing writer.

Test-first implementation

  • RED 4a8ba6f138bbddf6f27c7d4e1a01e59d725708b3 added focused regressions for same-observation inconsistency and the combined monotonic SQL invariant.
  • GREEN c0f278d584fae33f5fab4001119f8aa8be8f1982 validates explicit provider counts before PostgreSQL access and adds one atomic ON CONFLICT ... DO UPDATE ... WHERE guard so monotonic completed/failed enrichment cannot exceed the monotonic total.
  • Subsequent branch work distinguishes known versus unknown totals, preserves the public 17-field lifecycle projection while retaining total_requests_known as persistence-internal evidence, reports skipped guarded updates by rereading the durable row, and makes package/container schema mirrors migration-safe.
  • Current protected-main integration was merged non-destructively into this branch; no predecessor check or review evidence is treated as current acceptance.
  • Next RED proves arbitrary caller-selected SQL execution through apply_schema(..., schema_path) remains possible before the narrow authority fix.

The bounded lifecycle contract preserves sparse provider observations, tenant/RLS identity, terminal-state monotonicity, public return shape, and existing schema identity. The schema-application hardening does not add release authority or change the supported init-db CLI target: python -m pg_llm_batch init-db already calls db.apply_schema(dsn) without a path override.

Current exact gate boundary

At the latest inspection, exact-head repository CI, Security Scan, SAST Semgrep, Release Acceptance, coverage evidence, and required review/runtime checks were terminal-success except required Strix. Strix's quick scan experienced provider 429s but ultimately emitted one substantive HIGH finding: arbitrary caller-controlled schema_path can select SQL text executed by Psycopg. Repository inspection confirms apply_schema is not exported from pg_llm_batch.__init__, the CLI exposes no schema-path argument, and repository documentation describes packaged-schema application. The finding is therefore being treated as a valid least-authority defect rather than dismissed as infrastructure noise.

Merge boundary

Keep Draft while the new RED→GREEN schema-authority repair and exact-head gates are incomplete. Promote only when the unchanged exact head is mergeable against the then-live protected base, every required current-head workflow/security/coverage/package/provenance/release gate is terminal-success, review threads are clear, and live rules are satisfied. Queued, pending, cancelled, skipped-required, absent, neutral-required, stale, predecessor, synthetic, status-only, author-only, infrastructure-only, rate-limited, or failed evidence is not acceptance.

Refs #135.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 11 minutes

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

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 76c086d2-f643-4a29-93b0-e828cc178c51

📥 Commits

Reviewing files that changed from the base of the PR and between ac1157c and 7667078.

📒 Files selected for processing (2)
  • pg_llm_batch/db.py
  • tests/test_db.py
📝 Walkthrough

Walkthrough

원격 배치 상태가 total_requests_known으로 전체 요청 수의 신뢰 상태를 추적합니다. 알려진 전체 요청 수를 초과하는 진행률은 저장 전에 거부합니다. Upsert와 PostgreSQL 스키마에 같은 제약을 적용하고 회귀·통합 테스트를 추가합니다.

Changes

원격 배치 진행률 불변성

Layer / File(s) Summary
스냅샷 요청 수 정규화
pg_llm_batch/db.py, tests/test_remote_batch_progress_invariant.py
요청 수를 정규화하고, 알려진 전체 요청 수보다 완료·실패 요청 수의 합이 크면 ValueError를 발생시킵니다. 갱신 거부 시 저장 상태를 다시 조회합니다. 현재 조회 열에는 total_requests_known이 누락되어 행 길이 검증 오류가 발생합니다.
Upsert 진행률 가드
pg_llm_batch/db.py, tests/test_integration_pg.py
Upsert가 total_requests_known을 논리합으로 병합합니다. 누적 완료·실패 요청 수가 알려진 누적 전체 요청 수를 초과하는 갱신을 차단합니다. 통합 테스트가 오래된 관측값, 미확정 값, 잘못된 값과 유효한 값으로의 전환을 검증합니다.
스키마 불변성 및 마이그레이션
pg_llm_batch/schema.sql, docker/postgres/init/02_schema.sql, tests/test_remote_batch_progress_invariant.py
두 스키마에 total_requests_known 열과 진행률 검사 제약 조건을 추가합니다. 기존 행을 백필하고 DEFAULT FALSE, NOT NULL을 적용한 뒤 제약 조건을 검증합니다. 스키마 일치와 손상 진행률 처리를 테스트합니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 내구성 있는 생명주기 진행률 불변 조건을 강제하는 주요 변경 사항을 정확하고 간결하게 설명합니다.
✨ 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/durable-progress-invariants

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.

@seonghobae
seonghobae marked this pull request as ready for review August 12, 2026 08:37

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_remote_batch_progress_invariant.py (1)

94-118: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

SQL 문자열 검사만으로 upsert 동작을 검증하지 마세요.

Line 94-118은 한 번만 호출하므로 INSERT 경로만 실행합니다. _Cursor.execute는 SQL을 기록할 뿐 PostgreSQL을 실행하지 않으며, 충돌한 기존 행도 모델링하지 않습니다. 따라서 이 테스트는 guard 문자열의 존재만 검증합니다.

기존 값과 신규 값의 누적 progress가 total을 초과할 때 UPDATE가 거부되는지, 경계값에서는 UPDATE가 수행되는지 PostgreSQL 통합 테스트 또는 conflict-aware 테스트 하네스로 검증하세요.

근거: 이 테스트의 _Cursor.execute 구현과 단일 persistence 호출 경로입니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_remote_batch_progress_invariant.py` around lines 94 - 118, Replace
the SQL-string-only assertion in
test_upsert_guards_combined_monotonic_progress_invariant with a conflict-aware
PostgreSQL integration test or harness that models an existing row and executes
the ON CONFLICT UPDATE path. Verify updates are rejected when the merged
completed and failed progress exceeds the merged total, and accepted at the
boundary; retain the test’s existing monotonic-progress setup and symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pg_llm_batch/db.py`:
- Around line 390-394: Update the request-count aggregation around
_provider_count and the SQL upsert guard so missing or invalid total values are
distinguished from an explicitly known total of zero. Ensure progress updates
are persisted when no known total exists, while retaining the inconsistency
check for valid totals and preserving invalid-total-to-zero behavior with
regression coverage.

---

Nitpick comments:
In `@tests/test_remote_batch_progress_invariant.py`:
- Around line 94-118: Replace the SQL-string-only assertion in
test_upsert_guards_combined_monotonic_progress_invariant with a conflict-aware
PostgreSQL integration test or harness that models an existing row and executes
the ON CONFLICT UPDATE path. Verify updates are rejected when the merged
completed and failed progress exceeds the merged total, and accepted at the
boundary; retain the test’s existing monotonic-progress setup and symbols.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5b5f4e36-a04f-4a83-a0f1-4a7f76c34251

📥 Commits

Reviewing files that changed from the base of the PR and between e0888d3 and d282b4a.

📒 Files selected for processing (2)
  • pg_llm_batch/db.py
  • tests/test_remote_batch_progress_invariant.py

Comment thread pg_llm_batch/db.py
@seonghobae
seonghobae marked this pull request as draft August 12, 2026 10:21
@seonghobae
seonghobae marked this pull request as ready for review August 12, 2026 15:38

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🧹 Nitpick comments (1)
pg_llm_batch/db.py (1)

587-587: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

내부 전용 필드 제거를 한 곳으로 모으세요.

두 공개 래퍼가 각각 total_requests_known 을 제거합니다. 새 공개 래퍼가 추가되면 제거를 누락할 위험이 있습니다. _persist_remote_batch_state 가 공개 스냅샷을 만들어 반환하도록 하거나, 공용 헬퍼로 정리 로직을 분리하세요.

♻️ 정리 헬퍼 예시
+_INTERNAL_SNAPSHOT_FIELDS = ("total_requests_known",)
+
+
+def _public_remote_batch_snapshot(snapshot: Dict[str, Any]) -> Dict[str, Any]:
+    """Return one snapshot without internal-only persistence fields."""
+    for field in _INTERNAL_SNAPSHOT_FIELDS:
+        snapshot.pop(field, None)
+    return snapshot

Also applies to: 601-610

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pg_llm_batch/db.py` at line 587, 두 공개 래퍼에 분산된 `total_requests_known` 제거 로직을
`_persist_remote_batch_state`가 공개 스냅샷을 반환하도록 통합하거나 공용 정리 헬퍼로 추출하세요. 각 래퍼에서는 개별
`snapshot.pop` 호출을 제거하고 중앙화된 경로를 사용해 새 공개 래퍼에서도 내부 전용 필드가 항상 제외되도록 하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docker/postgres/init/02_schema.sql`:
- Around line 152-165: Update the backfill around total_requests_known in
docker/postgres/init/02_schema.sql (152-165) and pg_llm_batch/schema.sql
(152-165) so rows where completed_requests + failed_requests exceeds
total_requests have their progress corrected or are explicitly reported for
migration, rather than only being marked FALSE. Apply identical logic in both
files and keep their schema contents synchronized.
- Around line 191-207: In docker/postgres/init/02_schema.sql lines 191-207 and
pg_llm_batch/schema.sql lines 191-207, update the migration around
ck_llm_remote_batch_jobs_known_progress so existing violating rows are corrected
before validation, then run VALIDATE CONSTRAINT only when
pg_constraint.convalidated is FALSE; apply the same conditional repair and
validation logic in both schema files.

In `@pg_llm_batch/db.py`:
- Around line 516-532: Update the upsert flow around the `ON CONFLICT DO UPDATE`
execution to inspect `cur.rowcount` after `cur.execute`. When the progress guard
skips the update, do not return the unpersisted snapshot as if it succeeded;
instead reread and return the stored job row, or explicitly mark the returned
snapshot as not applied, using the existing job lookup/serialization symbols in
the surrounding function.

In `@tests/test_integration_pg.py`:
- Around line 163-245: Update persist_remote_batch_state and its PostgreSQL
upsert guard so skipped updates return the existing persisted snapshot rather
than the rejected input snapshot. Extend the skipped-update cases for
known_batch_id and sparse_batch_id to capture and assert the method’s return
value, matching the database state verified by _read_remote_progress; preserve
the existing behavior for accepted updates.

---

Nitpick comments:
In `@pg_llm_batch/db.py`:
- Line 587: 두 공개 래퍼에 분산된 `total_requests_known` 제거 로직을
`_persist_remote_batch_state`가 공개 스냅샷을 반환하도록 통합하거나 공용 정리 헬퍼로 추출하세요. 각 래퍼에서는 개별
`snapshot.pop` 호출을 제거하고 중앙화된 경로를 사용해 새 공개 래퍼에서도 내부 전용 필드가 항상 제외되도록 하세요.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c65bb9b-a229-4a55-aaa9-6bbd8184cb54

📥 Commits

Reviewing files that changed from the base of the PR and between d282b4a and ddc783f.

📒 Files selected for processing (5)
  • docker/postgres/init/02_schema.sql
  • pg_llm_batch/db.py
  • pg_llm_batch/schema.sql
  • tests/test_integration_pg.py
  • tests/test_remote_batch_progress_invariant.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_remote_batch_progress_invariant.py

Comment thread docker/postgres/init/02_schema.sql
Comment thread docker/postgres/init/02_schema.sql
Comment thread pg_llm_batch/db.py
Comment thread tests/test_integration_pg.py

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pg_llm_batch/db.py`:
- Around line 568-584: Update the stored-state SELECT projection in
pg_llm_batch/db.py lines 568-584 to include total_requests_known immediately
after total_requests, matching _REMOTE_BATCH_STATE_FIELDS order. Update
tests/test_remote_batch_progress_invariant.py lines 138-156 to add the
corresponding total_requests_known value to stored_row.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ab2f9a1-982e-4f21-b52b-693023f31c88

📥 Commits

Reviewing files that changed from the base of the PR and between ddc783f and ac1157c.

📒 Files selected for processing (5)
  • docker/postgres/init/02_schema.sql
  • pg_llm_batch/db.py
  • pg_llm_batch/schema.sql
  • tests/test_integration_pg.py
  • tests/test_remote_batch_progress_invariant.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/test_integration_pg.py
  • pg_llm_batch/schema.sql
  • docker/postgres/init/02_schema.sql

Comment thread pg_llm_batch/db.py
@seonghobae
seonghobae marked this pull request as draft August 12, 2026 21:09
@seonghobae
seonghobae marked this pull request as ready for review August 12, 2026 22:10
@seonghobae
seonghobae merged commit cd873be into main Aug 12, 2026
36 checks passed
@seonghobae
seonghobae deleted the fix/durable-progress-invariants branch August 12, 2026 22:12
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