Skip to content

WS-ART-001-04C1: persist submission bundle durable put intent - #296

Open
Abiorh001 wants to merge 9 commits into
mainfrom
codex/ws-art-001-04c1-durable-put-intent-impl
Open

WS-ART-001-04C1: persist submission bundle durable put intent#296
Abiorh001 wants to merge 9 commits into
mainfrom
codex/ws-art-001-04c1-durable-put-intent-impl

Conversation

@Abiorh001

@Abiorh001 Abiorh001 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

What changed

  • seals the live prepared ZIP and single-use pre-submit pass capability into one process-local custody boundary
  • revalidates exact TASK/PROJECT lineage and consumes fresh opaque AUTH authority before capacity, durable intent, or provider I/O
  • persists immutable SubmissionBundleDurableIntent lineage to the generic ArtifactPutAttempt
  • reuses generic observation and replay with a fresh fully checked source after post-intent process loss
  • extends migration, receipt producer invariants, documentation, and the shared-foundations CI lane

Why

04C1 must prove that durable evidence alone cannot authorize storage and that caller loss after intent commit does not strand or duplicate the artifact operation.

Validation

  • Ruff and diff checks passed
  • 46 focused tests passed after rebase
  • new ART boundary coverage: 91 percent
  • lane inventory collect-only gate passed
  • stale wording, artifact contract, and Markdown link gates passed
  • architecture, security, QA, product/ops, senior, CI, docs, reuse, and test-delta reviews passed

The PostgreSQL migration and DB-backed workflow tests are intentionally delegated to hosted Backend/Agent Gates because WORKSTREAM_TEST_DATABASE_URL is not configured locally.

Summary by CodeRabbit

  • New Features

    • Added durable submission-bundle admission for checked artifacts.
    • Added replay and recovery support for previously committed submissions.
    • Added validation for artifact custody, authorization, evidence, and lineage.
    • Added single-use, securely validated pre-submit pass capabilities.
  • Bug Fixes

    • Prevented invalid, duplicated, or altered submission bundles from being accepted.
  • Documentation

    • Documented submission intent tracking and post-commit publication behavior.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 41 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: eab727ff-f03b-49e8-a0aa-43272c8d5e37

📥 Commits

Reviewing files that changed from the base of the PR and between 7a10dee and aef21ef.

📒 Files selected for processing (3)
  • backend/alembic/versions/0060_submission_bundle_durable_intent.py
  • backend/tests/conftest.py
  • backend/tests/test_alembic.py
📝 Walkthrough

Walkthrough

Adds durable submission-bundle admission with sealed pre-submit capabilities, prepared custody, authorization, immutable evidence-to-put intents, replay validation, and post-commit publication. Database constraints, migrations, integration tests, and architecture specifications cover the new flow.

Changes

Submission bundle durable admission

Layer / File(s) Summary
Persistence and producer contracts
backend/app/db/models.py, backend/app/modules/artifacts/models.py, backend/alembic/versions/0060_submission_bundle_durable_intent.py
Adds SubmissionBundleDurableIntent and extends artifact producer and receipt validation for submission_bundle.
Capability and custody enforcement
backend/app/modules/artifacts/pre_submit_evidence.py, backend/app/modules/artifacts/submission_custody.py, backend/app/modules/tasks/pre_submit_context.py
Restricts capability creation, validates ownership and seals, enforces one-time consumption, and supports optional actor identity locks.
Admission, replay, and publication flow
backend/app/modules/artifacts/schemas.py, backend/app/modules/artifacts/service.py, backend/app/modules/artifacts/submission_admission.py, backend/app/modules/artifacts/submission_authorization.py, backend/app/modules/artifacts/repository.py
Adds transaction-bound admission, custody validation, authorization, durable-intent persistence, exact replay checks, and post-commit artifact publication.
Validation and documentation
backend/tests/test_submission_bundle_admission.py, backend/tests/test_default_pre_submit_execution.py, backend/tests/test_alembic.py, backend/tests/conftest.py, backend/tests/test_artifact_architecture.py, backend/tests/test_ci_test_lanes.py, backend/tests/test_effective_pre_submit_execution.py, backend/tests/test_artifact_authorization.py, backend/scripts/run_test_lanes.py, docs/architecture_data_model.md, docs/spec_artifact_storage_service.md
Adds coverage for custody, replay, concurrency, authorization, migration round trips, test-lane registration, schema state, and durable-intent documentation.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant SubmissionBundleDurablePutService
  participant ArtifactAdmissionService
  participant ArtifactRepository
  participant StorageProvider
  Caller->>SubmissionBundleDurablePutService: admit_in_transaction(request)
  SubmissionBundleDurablePutService->>ArtifactAdmissionService: admit submission bundle
  ArtifactAdmissionService->>ArtifactRepository: lock evidence and persist durable intent
  SubmissionBundleDurablePutService-->>Caller: committed admission
  Caller->>SubmissionBundleDurablePutService: publish_after_commit(...)
  SubmissionBundleDurablePutService->>StorageProvider: publish prepared artifact
  StorageProvider-->>Caller: bounded put result
Loading

Possibly related PRs

Suggested reviewers: abiorh-claw

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the main change and validation, but it omits most required trust-bundle sections and acceptance evidence. Add the required template sections, including scope, design, alternatives, evidence commands and results, acceptance criteria, test delta, reviewer results, risks, and merge ownership.
Docstring Coverage ⚠️ Warning Docstring coverage is 26.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: persisting submission bundle durable put intent.
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 codex/ws-art-001-04c1-durable-put-intent-impl

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.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/modules/artifacts/service.py (1)

1896-1907: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Digest request_digest for pre-existing artifact put attempts before adding pre_submit_evidence_set_id.

_existing_attempt() rejects any mismatch between the recalculated request_digest and the stored value. The new digest input changes null guide and checker_output attempts to include pre_submit_evidence_set_id, so replay fails closed unless persisted rows are backfilled or nulls are excluded from the hash.

🤖 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 `@backend/app/modules/artifacts/service.py` around lines 1896 - 1907, Update
the request_digest construction in _existing_attempt() to preserve replay
compatibility for pre-existing guide and checker_output attempts: exclude
pre_submit_evidence_set_id from the hashed payload when its value is null, or
otherwise use the established persisted-digest/backfill path. Ensure
recalculated digests still match stored rows created before
pre_submit_evidence_set_id was introduced.
🧹 Nitpick comments (13)
backend/app/modules/artifacts/pre_submit_evidence.py (1)

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

Drop the defensive getattr fallback.

__init__ always assigns _live_pass_bindings. The getattr default hides a future initialization bug and makes _claims_pass_binding silently return False instead of failing. Read the attribute directly.

♻️ Proposed change
     def _claims_pass_binding(self, binding: object) -> bool:
         """Recognize only an issuance registered by this live service instance."""
-        return binding in getattr(self, "_live_pass_bindings", set())
+        return binding in self._live_pass_bindings
🤖 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 `@backend/app/modules/artifacts/pre_submit_evidence.py` around lines 480 - 482,
Update _claims_pass_binding to read self._live_pass_bindings directly instead of
using getattr with an empty-set fallback, preserving the existing membership
check and allowing missing initialization to fail visibly.
backend/app/modules/artifacts/models.py (1)

1051-1061: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Remove the redundant single-column indexes.

uq_submission_bundle_intent_evidence and uq_submission_bundle_intent_put_attempt already create unique btree indexes on pre_submit_evidence_set_id and put_attempt_id. The extra index=True adds two duplicate indexes on the same columns. Each insert then maintains four indexes instead of two.

If you drop index=True, also drop ix_submission_bundle_durable_intents_pre_submit_evidence_set_id and ix_submission_bundle_durable_intents_put_attempt_id from backend/alembic/versions/0059_submission_bundle_durable_intent.py (upgrade lines 150-159 and the matching drop_index calls in downgrade), and update the expected index set in backend/tests/test_alembic.py.

♻️ Proposed change
     pre_submit_evidence_set_id: Mapped[str] = mapped_column(
         ForeignKey("pre_submit_evidence_sets.id", ondelete="RESTRICT"),
         nullable=False,
-        index=True,
     )
     put_attempt_id: Mapped[str] = mapped_column(
         ForeignKey("artifact_put_attempts.id", ondelete="RESTRICT"),
         nullable=False,
-        index=True,
     )
🤖 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 `@backend/app/modules/artifacts/models.py` around lines 1051 - 1061, Remove
index=True from the pre_submit_evidence_set_id and put_attempt_id mapped columns
in the relevant model. Remove the corresponding
ix_submission_bundle_durable_intents_pre_submit_evidence_set_id and
ix_submission_bundle_durable_intents_put_attempt_id creation and downgrade calls
in the migration 0059_submission_bundle_durable_intent, then update the expected
index set in test_alembic.py to exclude both redundant indexes.
backend/alembic/versions/0059_submission_bundle_durable_intent.py (1)

87-119: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Consider pinning search_path on the new trigger function.

guard_artifact_receipt_producer_reference resolves artifact_put_attempts through the caller search_path. Add set search_path = pg_catalog, public to the function definition to make resolution deterministic for every session.

♻️ Proposed hardening
         create function guard_artifact_receipt_producer_reference()
-        returns trigger language plpgsql as $$
+        returns trigger language plpgsql
+        set search_path = pg_catalog, public as $$
🤖 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 `@backend/alembic/versions/0059_submission_bundle_durable_intent.py` around
lines 87 - 119, Update the SQL definition of
guard_artifact_receipt_producer_reference to pin its search path to pg_catalog,
public, ensuring artifact_put_attempts resolves deterministically regardless of
the caller session’s search_path.
backend/app/modules/artifacts/schemas.py (2)

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

Consider a typed custody annotation behind TYPE_CHECKING.

custody: object removes static type information from the request contract. The runtime guard in source still enforces the exact type. A TYPE_CHECKING forward reference keeps the cycle broken and restores type checker support.

♻️ Proposed annotation change
+if TYPE_CHECKING:
+    from app.modules.artifacts.submission_custody import SubmissionBundlePreparedCustody
+
 `@final`
 `@dataclass`(frozen=True, slots=True)
 class SubmissionBundleArtifactAdmissionRequest:
     """One passing prepared bundle selected for durable provider intent."""
 
     pre_submit_evidence_set_id: UUID
-    custody: object
+    custody: SubmissionBundlePreparedCustody | object
     replay_durable_intent_id: UUID | None
🤖 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 `@backend/app/modules/artifacts/schemas.py` around lines 41 - 57, Update
SubmissionBundleArtifactAdmissionRequest.custody to use a TYPE_CHECKING-only
forward-reference annotation for SubmissionBundlePreparedCustody, while
retaining the runtime-local import and exact type guard in source. Preserve the
existing runtime behavior and avoid introducing an import cycle.

155-163: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Keep the locked_guide_sha256 binding explicit for the submission authorizer.

locked_guide_sha256 is validated during submission bundle admission, appears in pre-submit evidence digesting, and is present in the locked context. SubmissionBundleDurableIntentAuthorityFacts does not include it, so an authorizer cannot bind that locked guide content hash directly. Add it when the real submission authority is implemented.

🤖 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 `@backend/app/modules/artifacts/schemas.py` around lines 155 - 163, Add a
locked_guide_sha256: str field to SubmissionBundleDurableIntentAuthorityFacts
alongside the existing guide and policy hash bindings. Ensure the field is
populated from the locked context and carried through submission bundle
admission and pre-submit evidence digesting so the submission authorizer can
bind the locked guide content hash explicitly.
backend/tests/test_submission_bundle_admission.py (2)

208-220: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover every field that participates in the replay lineage.

The loop drifts 7 of the 26 fields. It proves those 7 are included. It does not detect a field that is accidentally dropped from _submission_replay_lineage during a later refactor.

Assert the full membership instead. Iterate over the participating field names, drift each one, and assert the lineage changes. Keep an explicit exclusion set for fields that must not participate, so the exclusions are also documented and enforced.

💚 Proposed stronger membership check
-    for field, changed in {
-        "semantic_manifest_id": str(uuid4()),
-        "locked_guide_sha256": _sha("a"),
-        "effective_policy_id": str(uuid4()),
-        "pre_submit_policy_id": str(uuid4()),
-        "catalogue_id": "other.catalogue",
-        "catalogue_version": "2",
-        "catalogue_manifest_sha256": _sha("b"),
-    }.items():
-        drifted = SimpleNamespace(**{**values, field: changed})
-        assert ArtifactAdmissionService._submission_replay_lineage(
-            original
-        ) != ArtifactAdmissionService._submission_replay_lineage(drifted)
+    excluded = {"result_count", "result_manifest_sha256"}
+    baseline = ArtifactAdmissionService._submission_replay_lineage(original)
+    for field, value in values.items():
+        if isinstance(value, bool):
+            changed: object = not value
+        elif isinstance(value, int):
+            changed = value + 1
+        elif value is None:
+            changed = str(uuid4())
+        else:
+            changed = f"{value}.drift"
+        drifted = SimpleNamespace(**{**values, field: changed})
+        lineage = ArtifactAdmissionService._submission_replay_lineage(drifted)
+        if field in excluded:
+            assert lineage == baseline, field
+        else:
+            assert lineage != baseline, field

Adjust excluded to match the intended contract.

🤖 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 `@backend/tests/test_submission_bundle_admission.py` around lines 208 - 220,
Strengthen the test around ArtifactAdmissionService._submission_replay_lineage
by enumerating every field intended to participate, drifting each field and
asserting the lineage changes. Define an explicit excluded set for fields that
must not participate, and assert those exclusions remain unchanged; align the
excluded fields with the intended contract.

223-250: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a barrier so the two consumers actually race.

asyncio.to_thread starts the two calls without synchronization. The second thread often starts after the first has already finished. The assertion sorted(outcomes) == [False, True] then passes on a purely sequential execution, so the test does not prove that consume is atomic under contention.

Release both threads at the same point with a threading.Barrier(2).

💚 Proposed barrier
+import threading
+
     manager, prepared = await _prepared(tmp_path)
     capability = _capability(prepared, uuid4())
+    barrier = threading.Barrier(2)
 
     def consume() -> bool:
+        barrier.wait(timeout=5)
         try:
             capability.consume(
🤖 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 `@backend/tests/test_submission_bundle_admission.py` around lines 223 - 250,
Update test_concurrent_pass_capability_consumption_has_one_winner to create a
threading.Barrier(2), have each consume invocation wait on the barrier
immediately before capability.consume, and then run both consumers through
asyncio.to_thread. Preserve the existing outcome assertion and cleanup while
ensuring both calls are released together to exercise concurrent consumption.
backend/tests/test_default_pre_submit_execution.py (2)

81-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert on the recorded authority facts.

_AllowSubmissionPreparedAuthorization stores facts, but no assertion reads final_authority.facts in the workflow test. The test therefore does not verify the content of SubmissionBundleDurableIntentAuthorityFacts, which is the central new contract of this PR.

Add assertions after the first successful admission. Check at least actor_profile_id, task_id, pre_submit_evidence_set_id, and archive_sha256.

💚 Proposed assertion after the first admission
             provider.execute_committed_put.assert_not_awaited()
             provider.resume_committed_put.assert_not_awaited()
+            assert final_authority.facts is not None
+            assert final_authority.facts.actor_profile_id == actor_id
+            assert final_authority.facts.task_id == request.task_id
+            assert final_authority.facts.pre_submit_evidence_set_id == (
+                first.evidence.evidence_set_id
+            )
+            assert (
+                final_authority.facts.archive_sha256
+                == request.prepared_artifact.commitment.sha256
+            )
🤖 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 `@backend/tests/test_default_pre_submit_execution.py` around lines 81 - 89,
Extend the workflow test after the first successful admission to assert the
recorded facts on final_authority.facts. Verify actor_profile_id, task_id,
pre_submit_evidence_set_id, and archive_sha256 against the expected admission
values, covering the SubmissionBundleDurableIntentAuthorityFacts contract.

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

Close drift_prepared and denied_prepared explicitly in the test.

Both artifacts are never closed by the test body. They are closed only because admit_in_transaction runs await prepared.close() in its except BaseException handler. The test therefore depends on an implementation detail of the code under test for its own cleanup.

manager.close() in the finally block is a backstop, but an explicit close documents the expected ownership. It also keeps the test correct if the service later stops closing on failure.

♻️ Proposed explicit cleanup
                             pass_capability=drift.pass_capability,
                         )
                     )
+            await drift_prepared.close()
             await session.execute(
                             pass_capability=denied.pass_capability,
                         )
                     )
+            await denied_prepared.close()
             durable_counts = {
🤖 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 `@backend/tests/test_default_pre_submit_execution.py` around lines 752 - 795,
Explicitly close both drift_prepared and denied_prepared in the test after their
respective admission attempts, using cleanup that runs even when the expected
exception is raised. Keep manager.close() as the existing backstop, and ensure
cleanup does not rely on admit_in_transaction internally closing the artifacts.
backend/app/modules/artifacts/submission_admission.py (1)

90-103: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider making the two-phase handoff a context manager.

admit_in_transaction returns an open PreparedArtifact. Ownership transfers to the caller. If the caller does not call publish_after_commit, the scratch file stays open until the ArtifactScratchManager closes. The error paths inside this method are correct, but the success path depends on caller discipline.

An async context manager, or a single method that accepts the caller transaction boundary, would make the close guaranteed. This is a design suggestion, not a defect in the current tests.

🤖 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 `@backend/app/modules/artifacts/submission_admission.py` around lines 90 - 103,
The two-phase handoff in admit_in_transaction should guarantee PreparedArtifact
cleanup instead of relying on callers to invoke publish_after_commit. Refactor
this handoff to use an async context manager or accept the caller’s transaction
boundary, ensuring the prepared artifact is closed whenever the post-commit
publication path is not completed while preserving successful publication
behavior.
backend/tests/test_effective_pre_submit_execution.py (1)

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

Consider a shared test helper for minting a pass capability.

backend/tests/test_submission_bundle_admission.py defines an equivalent _capability helper at Lines 52-62 that also calls PreSubmitEvidenceService(SimpleNamespace())._mint_pass_capability(...). Both call sites reach into the same private method.

Move the mint call into a shared test helper module. A single helper keeps both tests aligned if the guarded-construction signature changes.

🤖 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 `@backend/tests/test_effective_pre_submit_execution.py` around lines 92 - 100,
Extract the repeated PreSubmitEvidenceService._mint_pass_capability invocation
into a shared test helper module, then update
test_effective_pre_submit_execution.py and test_submission_bundle_admission.py
to use that helper. Preserve the existing arguments and return value so both
tests remain aligned with the guarded-construction signature.
backend/app/modules/artifacts/service.py (1)

2151-2208: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the paired positional tuples with a field-keyed comparison.

Lines 2162-2203 compare an 18-element prior tuple against an 18-element current tuple. The two literals must stay positionally aligned by hand. A future insertion or reordering in one tuple silently shifts the comparison and weakens the replay fence without a syntax or type error.

test_replay_lineage_includes_policy_catalogue_and_manifest_identity covers _submission_replay_lineage only. It does not cover this inline pair, so a misalignment can pass CI.

Extract two small helpers that build dict[str, object] from prior and from current, then compare the dictionaries. Field names then bind the operands.

🤖 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 `@backend/app/modules/artifacts/service.py` around lines 2151 - 2208, Replace
the positional tuple comparison in the replay validation condition with two
small field-keyed helper mappings for the relevant prior and current fields,
returning dict[str, object]. Compare those dictionaries directly while
preserving the existing field values and conversions, and use the helpers at the
comparison site so field additions or reordering cannot silently misalign
operands.
backend/tests/test_alembic.py (1)

207-246: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Capture every constraint that migration 0059 replaces.

upgrade() replaces four check constraints: producer_request_type, producer_identity, and producer_reference on artifact_put_attempts, plus contract_producer_reference on artifact_operation_receipts. downgrade() restores all four.

This helper reads only producer_request_type. The prior == restored assertion at Line 276 therefore cannot detect a downgrade defect in the other three constraints.

Read all four definitions so the round trip proves full reversibility.

♻️ Proposed helper extension
-            request_type = await connection.scalar(
-                text(
-                    "select pg_get_constraintdef(oid) from pg_constraint "
-                    "where conrelid='artifact_put_attempts'::regclass "
-                    "and conname='producer_request_type'"
-                )
-            )
+            put_constraints = dict(
+                (
+                    await connection.execute(
+                        text(
+                            "select conname, pg_get_constraintdef(oid) from pg_constraint "
+                            "where conrelid='artifact_put_attempts'::regclass "
+                            "and conname in "
+                            "('producer_request_type','producer_identity','producer_reference')"
+                        )
+                    )
+                ).all()
+            )
+            request_type = put_constraints.get("producer_request_type")
+            receipt_reference = await connection.scalar(
+                text(
+                    "select pg_get_constraintdef(oid) from pg_constraint "
+                    "where conrelid='artifact_operation_receipts'::regclass "
+                    "and conname='contract_producer_reference'"
+                )
+            )
             return {
                 "table_exists": table_exists,
                 "request_type": request_type,
+                "put_constraints": put_constraints,
+                "receipt_reference": receipt_reference,
                 "constraints": constraints,
                 "triggers": triggers,
                 "receipt_triggers": receipt_triggers,
             }
🤖 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 `@backend/tests/test_alembic.py` around lines 207 - 246, Extend the migration
test’s pre-migration capture alongside request_type to read and store the
definitions of producer_identity and producer_reference on
artifact_put_attempts, plus contract_producer_reference on
artifact_operation_receipts. Include all four captured definitions in the
existing prior/restored comparison so the downgrade round-trip validates every
constraint replaced by migration 0059.
🤖 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 `@backend/app/modules/artifacts/service.py`:
- Around line 2317-2332: Move the replay-lineage validation in
_submission_bundle_replay_attempt ahead of pass_capability.consume in
_submission_bundle_facts, so failed replay validation cannot consume the
original process-local capability. Preserve immediate consumption only for the
fresh preparation capability and retain the existing consumed-evidence identity
check after validation.

In `@docs/spec_artifact_storage_service.md`:
- Around line 1242-1243: Update the documentation sentence describing the typed
joins around PreSubmitEvidenceSet and ArtifactPutAttempt to state that the
listed actor, identity, project, task, assignment, predecessor, and version
facts are reachable through those joined records, not stored on the
SubmissionBundleDurableIntent join row; keep its four stored fields and
non-duplicated lineage clear.

---

Outside diff comments:
In `@backend/app/modules/artifacts/service.py`:
- Around line 1896-1907: Update the request_digest construction in
_existing_attempt() to preserve replay compatibility for pre-existing guide and
checker_output attempts: exclude pre_submit_evidence_set_id from the hashed
payload when its value is null, or otherwise use the established
persisted-digest/backfill path. Ensure recalculated digests still match stored
rows created before pre_submit_evidence_set_id was introduced.

---

Nitpick comments:
In `@backend/alembic/versions/0059_submission_bundle_durable_intent.py`:
- Around line 87-119: Update the SQL definition of
guard_artifact_receipt_producer_reference to pin its search path to pg_catalog,
public, ensuring artifact_put_attempts resolves deterministically regardless of
the caller session’s search_path.

In `@backend/app/modules/artifacts/models.py`:
- Around line 1051-1061: Remove index=True from the pre_submit_evidence_set_id
and put_attempt_id mapped columns in the relevant model. Remove the
corresponding ix_submission_bundle_durable_intents_pre_submit_evidence_set_id
and ix_submission_bundle_durable_intents_put_attempt_id creation and downgrade
calls in the migration 0059_submission_bundle_durable_intent, then update the
expected index set in test_alembic.py to exclude both redundant indexes.

In `@backend/app/modules/artifacts/pre_submit_evidence.py`:
- Around line 480-482: Update _claims_pass_binding to read
self._live_pass_bindings directly instead of using getattr with an empty-set
fallback, preserving the existing membership check and allowing missing
initialization to fail visibly.

In `@backend/app/modules/artifacts/schemas.py`:
- Around line 41-57: Update SubmissionBundleArtifactAdmissionRequest.custody to
use a TYPE_CHECKING-only forward-reference annotation for
SubmissionBundlePreparedCustody, while retaining the runtime-local import and
exact type guard in source. Preserve the existing runtime behavior and avoid
introducing an import cycle.
- Around line 155-163: Add a locked_guide_sha256: str field to
SubmissionBundleDurableIntentAuthorityFacts alongside the existing guide and
policy hash bindings. Ensure the field is populated from the locked context and
carried through submission bundle admission and pre-submit evidence digesting so
the submission authorizer can bind the locked guide content hash explicitly.

In `@backend/app/modules/artifacts/service.py`:
- Around line 2151-2208: Replace the positional tuple comparison in the replay
validation condition with two small field-keyed helper mappings for the relevant
prior and current fields, returning dict[str, object]. Compare those
dictionaries directly while preserving the existing field values and
conversions, and use the helpers at the comparison site so field additions or
reordering cannot silently misalign operands.

In `@backend/app/modules/artifacts/submission_admission.py`:
- Around line 90-103: The two-phase handoff in admit_in_transaction should
guarantee PreparedArtifact cleanup instead of relying on callers to invoke
publish_after_commit. Refactor this handoff to use an async context manager or
accept the caller’s transaction boundary, ensuring the prepared artifact is
closed whenever the post-commit publication path is not completed while
preserving successful publication behavior.

In `@backend/tests/test_alembic.py`:
- Around line 207-246: Extend the migration test’s pre-migration capture
alongside request_type to read and store the definitions of producer_identity
and producer_reference on artifact_put_attempts, plus
contract_producer_reference on artifact_operation_receipts. Include all four
captured definitions in the existing prior/restored comparison so the downgrade
round-trip validates every constraint replaced by migration 0059.

In `@backend/tests/test_default_pre_submit_execution.py`:
- Around line 81-89: Extend the workflow test after the first successful
admission to assert the recorded facts on final_authority.facts. Verify
actor_profile_id, task_id, pre_submit_evidence_set_id, and archive_sha256
against the expected admission values, covering the
SubmissionBundleDurableIntentAuthorityFacts contract.
- Around line 752-795: Explicitly close both drift_prepared and denied_prepared
in the test after their respective admission attempts, using cleanup that runs
even when the expected exception is raised. Keep manager.close() as the existing
backstop, and ensure cleanup does not rely on admit_in_transaction internally
closing the artifacts.

In `@backend/tests/test_effective_pre_submit_execution.py`:
- Around line 92-100: Extract the repeated
PreSubmitEvidenceService._mint_pass_capability invocation into a shared test
helper module, then update test_effective_pre_submit_execution.py and
test_submission_bundle_admission.py to use that helper. Preserve the existing
arguments and return value so both tests remain aligned with the
guarded-construction signature.

In `@backend/tests/test_submission_bundle_admission.py`:
- Around line 208-220: Strengthen the test around
ArtifactAdmissionService._submission_replay_lineage by enumerating every field
intended to participate, drifting each field and asserting the lineage changes.
Define an explicit excluded set for fields that must not participate, and assert
those exclusions remain unchanged; align the excluded fields with the intended
contract.
- Around line 223-250: Update
test_concurrent_pass_capability_consumption_has_one_winner to create a
threading.Barrier(2), have each consume invocation wait on the barrier
immediately before capability.consume, and then run both consumers through
asyncio.to_thread. Preserve the existing outcome assertion and cleanup while
ensuring both calls are released together to exercise concurrent consumption.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5d32d02e-49b5-4556-96ce-b3f7f0ac685a

📥 Commits

Reviewing files that changed from the base of the PR and between 6097b56 and c60520b.

📒 Files selected for processing (20)
  • backend/alembic/versions/0059_submission_bundle_durable_intent.py
  • backend/app/db/models.py
  • backend/app/modules/artifacts/models.py
  • backend/app/modules/artifacts/pre_submit_evidence.py
  • backend/app/modules/artifacts/repository.py
  • backend/app/modules/artifacts/schemas.py
  • backend/app/modules/artifacts/service.py
  • backend/app/modules/artifacts/submission_admission.py
  • backend/app/modules/artifacts/submission_authorization.py
  • backend/app/modules/artifacts/submission_custody.py
  • backend/app/modules/tasks/pre_submit_context.py
  • backend/scripts/run_test_lanes.py
  • backend/tests/test_alembic.py
  • backend/tests/test_artifact_architecture.py
  • backend/tests/test_ci_test_lanes.py
  • backend/tests/test_default_pre_submit_execution.py
  • backend/tests/test_effective_pre_submit_execution.py
  • backend/tests/test_submission_bundle_admission.py
  • docs/architecture_data_model.md
  • docs/spec_artifact_storage_service.md

Comment thread backend/app/modules/artifacts/service.py Outdated
Comment thread docs/spec_artifact_storage_service.md
@abiorh-claw
abiorh-claw self-requested a review August 8, 2026 02:04

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/tests/test_default_pre_submit_execution.py (1)

74-91: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Use issuance-observed capabilities here.

_fresh_checked_bundle() returns a new pass_capability each call but stores it only in result, while the workflow’s original response.pass_capability is used later. This makes replay test the original pre-submission pass capability instead of the bundle produced by that fresh execution. Update fresh_checked_bundle() to return the capability, or use fresh.pass_capability at the admission call sites.

🤖 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 `@backend/tests/test_default_pre_submit_execution.py` around lines 74 - 91,
Update _fresh_checked_bundle() and its admission call sites so replay uses the
pass_capability issued by the fresh execution rather than the original
response.pass_capability. Return the capability from _fresh_checked_bundle() or
consistently reference fresh.pass_capability when invoking admission, ensuring
the issuance-observed capability is used.
🤖 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 `@backend/tests/test_default_pre_submit_execution.py`:
- Around line 933-939: Update the cleanup block around manager.close(),
custody_triggers restoration, and engine.dispose() so engine.dispose() executes
from an inner finally block even when the restoration transaction or enable
trigger statement fails. Preserve the existing reversed trigger restoration
behavior while guaranteeing engine disposal on every cleanup path.

---

Outside diff comments:
In `@backend/tests/test_default_pre_submit_execution.py`:
- Around line 74-91: Update _fresh_checked_bundle() and its admission call sites
so replay uses the pass_capability issued by the fresh execution rather than the
original response.pass_capability. Return the capability from
_fresh_checked_bundle() or consistently reference fresh.pass_capability when
invoking admission, ensuring the issuance-observed capability is used.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 410e000b-9aaa-45d7-bb44-7a50b289213a

📥 Commits

Reviewing files that changed from the base of the PR and between c60520b and 4950417.

📒 Files selected for processing (5)
  • backend/alembic/versions/0059_submission_bundle_durable_intent.py
  • backend/tests/conftest.py
  • backend/tests/test_alembic.py
  • backend/tests/test_default_pre_submit_execution.py
  • docs/spec_artifact_storage_service.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/spec_artifact_storage_service.md
  • backend/tests/test_alembic.py

Comment thread backend/tests/test_default_pre_submit_execution.py Outdated
@Abiorh001
Abiorh001 force-pushed the codex/ws-art-001-04c1-durable-put-intent-impl branch from 4950417 to 1b557f4 Compare August 8, 2026 02:31

@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 `@backend/alembic/versions/0060_submission_bundle_durable_intent.py`:
- Around line 139-148: Update the migration creating the durable-intent foreign
key to add a PostgreSQL insert trigger that checks the referenced
artifact_put_attempts row has producer_request_type equal to submission_bundle,
rejecting other producer types. Define the trigger function and trigger during
upgrade, and drop both in downgrade alongside the existing constraints.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 69235f38-9d11-4549-a9a9-41b4e6d1a0eb

📥 Commits

Reviewing files that changed from the base of the PR and between 4950417 and 7a10dee.

📒 Files selected for processing (7)
  • backend/alembic/versions/0060_submission_bundle_durable_intent.py
  • backend/app/modules/artifacts/service.py
  • backend/tests/conftest.py
  • backend/tests/test_alembic.py
  • backend/tests/test_artifact_authorization.py
  • backend/tests/test_default_pre_submit_execution.py
  • docs/spec_artifact_storage_service.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • docs/spec_artifact_storage_service.md
  • backend/tests/conftest.py
  • backend/tests/test_default_pre_submit_execution.py
  • backend/app/modules/artifacts/service.py

Comment thread backend/alembic/versions/0060_submission_bundle_durable_intent.py
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