Skip to content

worker: recover File publication idempotently - #59

Merged
stone16 merged 1 commit into
mainfrom
codex/issue-27-file-publication-recovery
Jul 23, 2026
Merged

worker: recover File publication idempotently#59
stone16 merged 1 commit into
mainfrom
codex/issue-27-file-publication-recovery

Conversation

@stone16

@stone16 stone16 commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Closes #27

Summary

  • persist deterministic File publication checkpoints at acquired, prepared, indexed, and activated boundaries
  • reclaim expired jobs with generation-bound signed leases and reject stale recovery attempts
  • recover ready and interrupted publications without exposing partial revisions
  • add migration backfill/downgrade guards, FORCE-RLS evidence, manifest coverage, HTTP seam tests, and ADR-0041

Verification

  • make db-reset && make check
    • unit: 1106 passed
    • catalog: 116 passed
    • process smoke: 4 passed
    • integration: 296 passed, 4 deselected
    • security gate: 120 passed; M0 SECURITY PASS
  • independent spec review: passed
  • independent engineering/security review: passed

Summary by CodeRabbit

  • New Features

    • Added durable recovery for interrupted file publications at key processing checkpoints.
    • Added auditable publication and recovery event history.
    • Preserved previously published content until replacement content is fully activated.
    • Added stronger lease generation checks to prevent stale work from resuming or modifying publications.
  • Bug Fixes

    • Improved recovery handling for concurrent workers, failed processing, and reclaimed leases.
    • Added validation to prevent invalid or mismatched recovery artifacts from being published.
  • Documentation

    • Documented the durable file publication recovery workflow and related lifecycle guarantees.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 38 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ce667b0-bdb5-40d2-a075-82eda245b44e

📥 Commits

Reviewing files that changed from the base of the PR and between d6d2d67 and 70eed6b.

📒 Files selected for processing (21)
  • CONTEXT.md
  • docs/decisions/0041-recover-file-publication-by-durable-boundary.md
  • docs/decisions/README.md
  • docs/design/2026-07-18-context-engine-implementation-design.md
  • engine/persistence/__init__.py
  • engine/persistence/file_imports.py
  • engine/persistence/schema_security_manifest.yaml
  • engine/persistence/worker_jobs.py
  • engine/supply/jobs.py
  • eval/catalogs/m0-security-evidence.yaml
  • migrations/versions/20260723_0015_file_publication_recovery.py
  • scripts/security_gate/rls.py
  • tests/integration/test_file_import_tracer.py
  • tests/integration/test_m0_security_gate_rls.py
  • tests/integration/test_migrations.py
  • tests/integration/test_zz_file_content_noop.py
  • tests/integration/test_zz_file_publication_recovery.py
  • tests/integration/test_zz_file_revision_replacement.py
  • tests/unit/test_m0_rls_inventory.py
  • tests/unit/test_schema_security_manifest.py
  • tests/unit/test_worker_lease.py
📝 Walkthrough

Walkthrough

This change adds durable file-publication recovery using lease generations, checkpoint and event tables, generation-fenced PostgreSQL procedures, worker interruption injection, and integration/security coverage for recovery, concurrency, isolation, and migration behavior.

Changes

File publication recovery

Layer / File(s) Summary
Lease-generation contracts and recovery semantics
CONTEXT.md, docs/decisions/*, docs/design/*, engine/supply/jobs.py, engine/persistence/worker_jobs.py, engine/persistence/__init__.py
Defines recovery checkpoints and immutable job events, upgrades file-import tokens to carry signed lease generations, and exposes interruption-related worker symbols.
Durable database schema and worker protocol
migrations/versions/20260723_0015_file_publication_recovery.py, engine/persistence/schema_security_manifest.yaml
Adds recovery and event tables, durable job states, RLS/function permissions, generation-aware lease and publication procedures, checkpoint transitions, and downgrade safeguards.
Worker interruption and resumable publication
engine/persistence/file_imports.py
Adds boundary-specific interruption injection, records interruption events, polls concurrent publication ownership, and executes acquisition, preparation, indexing, and activation with lease-generation parameters.
Recovery, migration, and security validation
tests/integration/test_zz_file_publication_recovery.py, tests/integration/test_migrations.py, tests/integration/test_file_import_tracer.py, tests/integration/test_zz_file_revision_replacement.py, tests/unit/*, scripts/security_gate/rls.py, eval/catalogs/m0-security-evidence.yaml
Covers boundary recovery, exactly-once effects, concurrent reclaim/redemption, organization isolation, visibility preservation, RLS denial, migration behavior, and updated lease/security evidence.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

  • stone16/context-engine#45 — Establishes WorkerLease binding and replay validation extended here with lease-generation claims.
  • stone16/context-engine#56 — Provides the structural-v2 publication flow refactored here for checkpointed recovery.
  • stone16/context-engine#58 — Introduces the replacement staging and activation mechanisms now threaded with lease-generation fencing.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.18% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the main change: idempotent recovery for File publication jobs.
Linked Issues check ✅ Passed The PR implements deterministic recovery at the named boundaries, lease reclaim, concurrency exclusion, audit history, and cross-Org safeguards required by #27.
Out of Scope Changes check ✅ Passed The changes stay focused on File publication recovery, tests, migration, security manifest, and ADR support for #27 without unrelated feature work.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-27-file-publication-recovery

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6d2d67d3f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

AND fragment.projection_kind = 'body'
)
)
AND NOT EXISTS (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject no-ops with extra index candidates

If an active revision has any extra exact_phrase_candidate rows, this new no-op validation still succeeds because it only proves each expected phrase from the freshly compiled artifact exists and never compares the total candidate set. The previous v1/v2 no-op checks rejected that case, and accepting it here means a stale or corrupted exact-phrase index can be treated as an unchanged active artifact instead of failing closed.

Useful? React with 👍 / 👎.

@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: 2

🧹 Nitpick comments (1)
engine/persistence/file_imports.py (1)

496-525: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: the concurrent-winner wait is a bounded blocking busy-poll.

_await_concurrent_publication occupies the worker thread for up to _CONCURRENT_PUBLICATION_WAIT_SECONDS and opens a fresh begin()/acquire transaction every 10ms until the winner's recovery row reaches completed. It's correct and bounded, but under many simultaneous contended imports this both pins worker threads and adds per-poll transaction churn on context_worker_acquire_file_publication. If contention volume grows, consider surfacing wait duration/poll count as a metric and/or a small backoff so the hot path is observable and tunable without a code change.

🤖 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 `@engine/persistence/file_imports.py` around lines 496 - 525, Make the bounded
wait in _await_concurrent_publication observable and less transaction-intensive
by tracking wait duration or poll count and applying a tunable backoff between
context_worker_acquire_file_publication attempts. Preserve the existing
deadline, retry behavior, and _rejection(token) outcomes.
🤖 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 `@engine/persistence/schema_security_manifest.yaml`:
- Line 163: Update the idempotencyBinding entry in the schema security manifest
to remove lease_generation while retaining organization_id, job_id, source_id,
resource_ref, revision_id, content_identity_digest, and
publication_payload_digest; lease_generation must remain available only for
fencing and authorization.
- Around line 149-162: The worker-owned recovery manifest entry must not include
context_worker_issue_file_import_lease, which belongs to the Control phase.
Remove that function from the databaseFunctions list, preserving lease issuance
as an explicit context_engine_control operation before Worker execution.

---

Nitpick comments:
In `@engine/persistence/file_imports.py`:
- Around line 496-525: Make the bounded wait in _await_concurrent_publication
observable and less transaction-intensive by tracking wait duration or poll
count and applying a tunable backoff between
context_worker_acquire_file_publication attempts. Preserve the existing
deadline, retry behavior, and _rejection(token) outcomes.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ce63c2c4-60c0-45d9-911b-05d2b413575f

📥 Commits

Reviewing files that changed from the base of the PR and between 852fa66 and d6d2d67.

📒 Files selected for processing (21)
  • CONTEXT.md
  • docs/decisions/0041-recover-file-publication-by-durable-boundary.md
  • docs/decisions/README.md
  • docs/design/2026-07-18-context-engine-implementation-design.md
  • engine/persistence/__init__.py
  • engine/persistence/file_imports.py
  • engine/persistence/schema_security_manifest.yaml
  • engine/persistence/worker_jobs.py
  • engine/supply/jobs.py
  • eval/catalogs/m0-security-evidence.yaml
  • migrations/versions/20260723_0015_file_publication_recovery.py
  • scripts/security_gate/rls.py
  • tests/integration/test_file_import_tracer.py
  • tests/integration/test_m0_security_gate_rls.py
  • tests/integration/test_migrations.py
  • tests/integration/test_zz_file_content_noop.py
  • tests/integration/test_zz_file_publication_recovery.py
  • tests/integration/test_zz_file_revision_replacement.py
  • tests/unit/test_m0_rls_inventory.py
  • tests/unit/test_schema_security_manifest.py
  • tests/unit/test_worker_lease.py

Comment thread engine/persistence/schema_security_manifest.yaml
Comment thread engine/persistence/schema_security_manifest.yaml Outdated
@stone16
stone16 force-pushed the codex/issue-27-file-publication-recovery branch from d6d2d67 to 70eed6b Compare July 23, 2026 04:47
@stone16
stone16 merged commit 755fd88 into main Jul 23, 2026
2 checks passed
@stone16
stone16 deleted the codex/issue-27-file-publication-recovery branch July 23, 2026 04:50

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70eed6bc40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

OR recovery_row.compiler_version <>
requested_compiler_version
OR recovery_row.config_version <> requested_config_version
OR recovery_row.publication_payload_digest <> ({payload_digest})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Recover from the durable artifact, not the live file

If a file import reaches a durable recovery checkpoint and then the underlying file is edited or removed before the lease is reclaimed, the resumed worker recompiles the current file and this digest check rejects it instead of resuming the already-recorded publication. Because context_worker_fail_file_import also refuses to fail jobs that have a recovery row, the job remains in running/prepared/ready and the non-completed recovery row keeps later imports for the same resource in the contended path until they time out. Recovery needs to use stored publication data or otherwise complete/fail the durable row without depending on the mutable live file contents.

Useful? React with 👍 / 👎.

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.

worker: recover File publication idempotently after interruption

1 participant