Skip to content

AUTH-12E: activate guide sufficiency mutations - #263

Merged
abiorh-claw merged 47 commits into
mainfrom
codex/ws-auth-001-12e-guide-sufficiency
Aug 4, 2026
Merged

AUTH-12E: activate guide sufficiency mutations#263
abiorh-claw merged 47 commits into
mainfrom
codex/ws-auth-001-12e-guide-sufficiency

Conversation

@Abiorh001

@Abiorh001 Abiorh001 commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Workstream PR Trust Bundle

Chunk

WS-AUTH-001-12E - Activate guide-sufficiency mutations

Goal

Activate exactly manual sufficiency-report creation, agent-run requests, and
warning acknowledgement. Keep public mutation Project Manager-only and grant
the fixed workstream.project.setup service only the internal run command.

Intent And Planning Context

  • Intent: close the guide-sufficiency durable authorization boundary without
    introducing another authorization protocol.
  • Chunk contract: chunks/WS-AUTH-001-12E-guide-sufficiency-mutations.md.

What Changed

  • Added the three active catalogue actions and exact human/fixed-service guards.
  • Added prepared authorization and final locked revalidation to all durable
    sufficiency mutations.
  • Added migration 0050 for immutable replay custody and complete authorization
    provenance.
  • Connected the setup worker through fresh deterministic service authority.
  • Added runtime, migration, API, replay, transaction, and coverage proof.

Why It Changed

The former guide-sufficiency mutations could not safely become live until the
actor, identity link, grant/service identity, guide lineage, material, request,
transaction, and idempotency facts were bound and revalidated atomically.

Design Chosen

  • Reuse the opaque, process-local, single-use, transaction-bound
    PreparedAuthorizationHandle.
  • Run external agent work without an open prepared handle, then reload and lock
    canonical facts before the protected write.
  • Commit replay completion, product mutation, and allowed evidence atomically.
  • Carry identifiers only through Celery and acquire fresh service authority in
    the worker.

Alternatives Rejected

  • Raw AuthorizationContext as durable authority: not transaction-bound.
  • Serializing prepared handles into Celery: violates process/session custody.
  • ART- or project-local authorization evaluators: duplicate AUTH policy paths.
  • Preserving legacy mutation behavior: no backward compatibility is required.

Scope Control

Allowed Files Changed

  • AUTH catalogue, runtime, PREP, kernel, migration, and tests.
  • Project sufficiency mutation/queue/worker integration and tests.
  • Exact operations, specification, roadmap, and initiative records.
  • Narrow CI coverage assertions for the new subsystem files.

Files Outside Stated Scope

  • ART fixed-service authorization composition was narrowed to the shared AUTH
    principal resolver; its focused adapter tests prove behavior is unchanged.

Product Behavior

  • Product behavior changed: Project Managers can create reports, request an
    agent run, and acknowledge warnings under exact project authority; only the
    fixed project-setup service can execute the internal run command.

Evidence

Commands Run

cd backend && .venv/bin/ruff check .
cd backend && .venv/bin/pytest -q <focused AUTH/ART/API/audit selectors>
cd backend && .venv/bin/alembic upgrade head
git diff --check

Result Summary

Ruff: passed
Project sufficiency selector before hosted review: 31 passed
Authorization selector: 144 passed
Migration 0050 selector: 2 passed
API contract E2E: passed
Semantic collection: 2,928 tests across five hosted lanes
External-review shared-foundation selectors: passed
Canonical schema fingerprint and reset custody: passed

The repository-wide suite and authoritative coverage gates run in GitHub
Actions; the user's machine is not used for the roughly four-hour local suite.

Acceptance Criteria Proof

  • Exactly three sufficiency actions become active.
  • Human and fixed-service authority remain disjoint.
  • Final decisions bind exact actor/link, authority, lineage, material,
    operation, request, idempotency, session, and transaction facts.
  • Replay, copied/wrong handles, stale context, cross-resource use, and
    mid-flight terminal transitions fail closed.
  • Celery stores deterministic task custody before enqueue and never carries
    handles, bytes, credentials, or authorization contexts.
  • Product write, replay completion, and allowed evidence commit atomically.

Test Delta

Tests Added

  • Prepared handle integrity, denial, replay, and service-custody tests.
  • Human routes, worker execution, stale lineage/material, transaction rollback,
    mid-flight terminal race, and deterministic broker identity tests.
  • Migration upgrade/downgrade, append-only replay, provenance-only rollback
    refusal, schema parity, and API contract tests.

Tests Modified

  • Active-action, OpenAPI, audit, operations-count, ART principal-composition,
    and project setup fixtures were updated for the exact activated surface.

Tests Removed Or Skipped

  • None.

Internal Reviewer Results

Reviewed code SHA: aefec9e3703079744441161ea40356c308cd89fb

Reviewed at: 2026-08-03

Reviewer run IDs: 12e_arch_final, 12e_impl_qa, 12e_impl_senior,
12e_product_final, 12e_security_final, 12e_test_delta, plus recorded CI,
docs, and reuse tracks.

Reviewer Result Blocking Findings Notes
Senior engineering PASS None Final implementation review
QA/test PASS None Focused DB proof passed
Security/auth PASS None Replay namespace and custody verified
Product/ops PASS None Mid-flight terminal repair verified
Architecture PASS WITH LOW RISKS None Legacy helper retirement is follow-up
CI integrity PASS None No gate weakening
Docs PASS None Canonical surfaces aligned
Reuse/dedup PASS None Shared PREP/service resolver reused
Test delta PASS WITH LOW RISKS None No skipped or weakened tests

External Review

Source Status Notes
CodeRabbit Re-review pending All actionable first-review findings fixed or explicitly deferred in the external-response record
GitHub checks Rerun pending Initial shared/project failures repaired; exact corrected head pushed

CI And Gate Integrity

  • No workflow weakening.
  • No lint/test/docstring gate weakening.
  • No coverage threshold weakening.
  • No package script weakening.
  • No unpinned new GitHub Action.
  • Checkout credential persistence remains disabled.

Remaining Risks

  • The exact corrected head still requires all hosted lanes, aggregate and
    per-file coverage, Agent Gates, and fresh CodeRabbit review.
  • Large legacy ProjectService sufficiency helpers remain a low-risk future
    retirement item; live routes and workers use the new orchestrator.

Follow-Up Work

  • Add PR #263 to the capability ledger only after human merge.
  • Retire or quarantine legacy commit-owning sufficiency helpers in a separately
    bounded cleanup chunk.

Human Review Focus

  • The three-action activation boundary and human/service separation.
  • Migration 0050 replay/provenance constraints and downgrade refusal.
  • External-agent transaction break and final locked revalidation.
  • Deterministic task identity, terminal fencing, and atomic evidence.

Human Merge Ownership

  • I can explain what changed.
  • I can explain why it changed.
  • I know what could break.
  • I accept the remaining risks.
  • The user explicitly approved this specific PR for merge.

Summary by CodeRabbit

  • New Features
    • Added authorized workflows for creating guide sufficiency reports, running assessments, and acknowledging warnings.
    • Added reliable replay handling, request idempotency, and clear conflict responses.
    • Improved project setup with deterministic task tracking and safeguards against stale or mismatched results.
  • Bug Fixes
    • Strengthened validation of source material, report lineage, authorization, and execution context.
    • Improved handling of blocked, unauthorized, duplicate, and invalid operations.
  • Documentation
    • Updated authorization, operating, architecture, and roadmap documentation for the new workflows.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This change activates guide-sufficiency authorization mutations. It adds prepared authorization, replay custody, provenance persistence, deterministic setup execution, verified material handling, migration safeguards, API wiring, tests, and documentation.

Changes

Guide sufficiency authorization

Layer / File(s) Summary
AUTH-12E contract and workstream state
.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/...
Updates AUTH-12E status, dependencies, activation counts, implementation requirements, discovery records, and review evidence.
Authorization contracts and persistence
backend/app/modules/authorization/*, backend/app/modules/artifacts/authorization.py, backend/app/modules/projects/models.py, backend/alembic/versions/0053_guide_sufficiency_authority.py
Adds sufficiency resource bindings, fixed-service authorization, provenance fields, replay constraints, human-only admission, and migration downgrade protection.
Replay and mutation orchestration
backend/app/modules/projects/service.py, backend/app/modules/projects/setup_queue.py, backend/app/modules/projects/sufficiency_mutation_repository.py, backend/app/modules/projects/sufficiency_mutation_service.py
Adds verified material construction, deterministic task identities, replay reservation, lineage checks, advisory-lock execution, report adoption, and warning acknowledgement.
Mutation routes and setup worker
backend/app/modules/projects/guide_mutation_router.py, backend/app/modules/projects/router.py, backend/app/workers/project_setup.py
Routes human mutations and setup-service execution through shared authorization and mutation services. Handles replay responses, stale deliveries, rollback, and sanitized failures.
Validation, CI, and capability records
backend/tests/*, .github/workflows/backend.yml, backend/scripts/api_contract_e2e.py, docs/*
Adds migration, API, authorization, integration, replay, concurrency, fixture, coverage, and documentation validation.

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

Possibly related PRs

Suggested reviewers: abiorh-claw

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% 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
Title check ✅ Passed The title clearly summarizes the main change: activating guide sufficiency mutations as a core authorization boundary closure.
Description check ✅ Passed The description is comprehensive and follows the template structure. It covers chunk identification, goals, intent, what changed, design rationale, scope control, acceptance criteria, test delta, internal review results, external review status, CI integrity, remaining risks, and follow-up work. All required sections are present and substantively completed.
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-auth-001-12e-guide-sufficiency

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

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/projects/setup_queue.py (1)

82-99: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not dispatch when the setup run row is absent.

Lines 84-86 persist expected_task_id only when setup_run is found. When the row is missing, the function still enqueues the Celery task at lines 88-95. The worker fences deliveries by comparing the delivered task id against ProjectSetupRun.celery_task_id, so that task can never execute. The result is orphaned broker work and no recorded outcome.

The caller backend/app/modules/projects/guide_mutation_router.py:164-171 passes outcome.setup_run_id from a committed mutation, so a missing row means a broken invariant upstream. Fail fast instead of dispatching.

A second point: when the returned task id differs from expected_task_id at line 96, the task is already accepted by the broker. The except block then records status="enqueue_failed" and logs "enqueue failed", which misstates what happened. A distinct error code makes the condition diagnosable.

🛡️ Proposed fix
     expected_task_id = pre_submit_setup_task_id(setup_run_id, setup_generation)
     setup_run = await repository.get_project_setup_run(setup_run_id)
-    if setup_run is not None:
-        setup_run.celery_task_id = expected_task_id
-        await session.commit()
+    if setup_run is None:
+        logger.warning(
+            "project setup run missing before dispatch",
+            extra={"project_id": project_id, "setup_run_id": setup_run_id},
+        )
+        return None
+    setup_run.celery_task_id = expected_task_id
+    await session.commit()
     try:
         task_id = await asyncio.to_thread(
             enqueue_pre_submit_setup_pipeline,
             project_id=project_id,
             guide_id=guide_id,
             source_snapshot_id=source_snapshot_id,
             setup_run_id=setup_run_id,
             setup_generation=setup_generation,
         )
         if task_id != expected_task_id:
             raise ProjectSetupQueueError(
-                "project setup queue returned the wrong task identity"
+                "project setup queue returned the wrong task identity; "
+                "the accepted task cannot execute"
             )
🤖 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/projects/setup_queue.py` around lines 82 - 99, In the
setup queue flow around pre_submit_setup_task_id and get_project_setup_run, fail
fast with ProjectSetupQueueError when setup_run is absent, before calling
enqueue_pre_submit_setup_pipeline. Also handle a returned task ID mismatch as a
distinct post-acceptance identity error rather than recording
status="enqueue_failed" or logging it as an enqueue failure; preserve normal
enqueue failure handling for dispatch exceptions.
🧹 Nitpick comments (12)
backend/alembic/versions/0050_guide_sufficiency_authority.py (2)

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

Simplify the skip condition into one set.

The loop tests created_by_service_identity separately from the four scope/action names. One membership test states the same rule. op.drop_column also drops the column's foreign key in PostgreSQL, so the explicit drop_constraint calls are redundant, but keeping them documents intent.

♻️ Proposed simplification
+    _COLUMNS_WITHOUT_FOREIGN_KEYS = {
+        "created_by_service_identity",
+        "creation_scope_type",
+        "creation_action_id",
+        "warning_acknowledgement_scope_type",
+        "warning_acknowledgement_action_id",
+    }
     for name, _ in reversed((*_CREATION_COLUMNS, *_ACK_COLUMNS)):
-        if name != "created_by_service_identity" and name not in {
-            "creation_scope_type",
-            "creation_action_id",
-            "warning_acknowledgement_scope_type",
-            "warning_acknowledgement_action_id",
-        }:
+        if name not in _COLUMNS_WITHOUT_FOREIGN_KEYS:
             op.drop_constraint(foreign_keys[name], "guide_sufficiency_reports", type_="foreignkey")
         op.drop_column("guide_sufficiency_reports", name)
🤖 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/0050_guide_sufficiency_authority.py` around lines
252 - 260, Update the cleanup loop over _CREATION_COLUMNS and _ACK_COLUMNS to
use one exclusion set containing created_by_service_identity and the four
scope/action names, while preserving the existing foreign-key constraint and
column removal behavior.

176-209: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider blocking TRUNCATE on the replay table.

The row-level trigger covers UPDATE and DELETE only. PostgreSQL does not fire row-level triggers for TRUNCATE, so a truncate guide_sufficiency_mutation_idempotency_records erases all replay evidence without raising. A statement-level trigger closes the gap.

♻️ Proposed statement-level guard
     op.execute(
         """
         create trigger trg_sufficiency_replay_immutable
         before update or delete on guide_sufficiency_mutation_idempotency_records
         for each row execute function reject_sufficiency_replay_mutation()
         """
     )
+    op.execute(
+        """
+        create function reject_sufficiency_replay_truncate() returns trigger
+        language plpgsql as $$
+        begin
+          raise exception 'guide sufficiency replay rows are append-only';
+        end $$
+        """
+    )
+    op.execute(
+        """
+        create trigger trg_sufficiency_replay_no_truncate
+        before truncate on guide_sufficiency_mutation_idempotency_records
+        for each statement execute function reject_sufficiency_replay_truncate()
+        """
+    )

Note: backend/tests/test_alembic.py clears evidence with truncate guide_sufficiency_mutation_idempotency_records. If you add this guard, change that cleanup to delete from.

🤖 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/0050_guide_sufficiency_authority.py` around lines
176 - 209, Extend the replay-table protection around
reject_sufficiency_replay_mutation by adding a statement-level TRUNCATE trigger
that raises the same append-only exception, preventing truncation from removing
evidence. Update the test cleanup in backend/tests/test_alembic.py to use DELETE
instead of TRUNCATE for guide_sufficiency_mutation_idempotency_records.
backend/app/modules/authorization/prepared.py (1)

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

Use the validated model instead of discarding it.

The block constructs ProjectGuideSufficiencyMutationResourceContext only to trigger validation, then discards the instance and carries the raw sufficiency dict into the binding. The two representations hold the same coerced values today, so behavior is correct. Assigning the instance and reading the binding fields from it removes the duplicate mapping and prevents drift if a field is renamed.

validated = ProjectGuideSufficiencyMutationResourceContext(
    resource_type="project_guide_sufficiency_mutation",
    resource_id=sufficiency["report_id"] or sufficiency["snapshot_id"],
    ...
)
sufficiency["validated"] = validated

Then set sufficiency_project_id=validated.scope_project_id, and so on, at lines 604-621.

🤖 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/authorization/prepared.py` around lines 519 - 536, Store
the constructed ProjectGuideSufficiencyMutationResourceContext instance in a
local validated variable instead of discarding it, then update the sufficiency
binding fields to read from validated (including scope_project_id and the other
mapped attributes) rather than the raw sufficiency dictionary. Keep the existing
constructor inputs and validation behavior unchanged.
backend/app/modules/authorization/kernel.py (1)

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

Consider a dedicated denial resource context for sufficiency mutations.

Every other prepare-time denial family uses a narrow *PrepareDenialResourceContext model, for example ProjectGuideMutationPrepareDenialResourceContext and ProjectPolicyMutationPrepareDenialResourceContext. Those models carry only the requested selectors. This change instead accepts the full canonical ProjectGuideSufficiencyMutationResourceContext for denial evidence, which also carries setup_service_custody, material_digest, and stale_output_digest.

The stored evidence is limited to hashes and identifiers, so there is no privacy defect today. Aligning with the established pattern keeps the denial surface minimal and consistent.

🤖 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/authorization/kernel.py` around lines 653 - 662,
Introduce a dedicated prepare-denial resource context for sufficiency mutations
containing only the requested selectors, and use it in the authorization check
alongside ProjectGuideMutationPrepareDenialResourceContext. Update the
corresponding sufficiency denial evidence construction and type references to
use this narrower context, while retaining
ProjectGuideSufficiencyMutationResourceContext for canonical mutation handling.
backend/app/modules/projects/service.py (1)

178-182: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse REPRESENTATIVE_TASK_SOURCE_KINDS instead of the literal "example".

_guide_source_material selects representative items with item.source_kind in REPRESENTATIVE_TASK_SOURCE_KINDS. This new builder hardcodes "example". If the constant gains another kind, the verified path and the legacy path will disagree about representative material, and the agent prompt digest will differ between paths.

♻️ Proposed change
         representative_task_material=RepresentativeTaskMaterialContext(
             items=[
-                item for item in verified_items if item.source_kind == "example"
+                item
+                for item in verified_items
+                if item.source_kind in REPRESENTATIVE_TASK_SOURCE_KINDS
             ]
         ),
🤖 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/projects/service.py` around lines 178 - 182, Update the
representative item filter in the RepresentativeTaskMaterialContext builder to
check item.source_kind against the existing REPRESENTATIVE_TASK_SOURCE_KINDS
constant instead of the literal "example", keeping selection consistent with
_guide_source_material.
backend/app/modules/projects/sufficiency_mutation_service.py (2)

489-498: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct the return annotations on the async context managers.

run_agent and run_setup_service are decorated with @asynccontextmanager and use yield. Their annotations declare -> GuideSufficiencyMutationOutcome, which describes the value yielded, not the function result. Type checkers reject yield in a function annotated with a non-iterator return type.

♻️ Proposed annotation fix
+from collections.abc import AsyncIterator
@@
     async def run_agent(
         self,
         resolved: ResolvedActor,
         prepared: PreparedAuthorizationService,
         key: UUID,
         project_id: UUID,
         guide_id: UUID,
         source_snapshot_id: UUID,
-    ) -> GuideSufficiencyMutationOutcome:
+    ) -> AsyncIterator[GuideSufficiencyMutationOutcome]:
@@
         custody: ProjectSetupServiceCustodyContext,
-    ) -> GuideSufficiencyMutationOutcome:
+    ) -> AsyncIterator[GuideSufficiencyMutationOutcome]:

Also applies to: 515-526

🤖 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/projects/sufficiency_mutation_service.py` around lines
489 - 498, Update the return annotations of the async context manager methods
run_agent and run_setup_service from GuideSufficiencyMutationOutcome to the
appropriate AsyncIterator or AsyncGenerator type representing the yielded
outcome, while preserving their existing yielded values and context-manager
behavior.

687-784: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Extract the duplicated replay-recovery block.

The human replay-recovery block and the setup-service replay-recovery block repeat the same sequence: selector comparison, report provenance comparison, _caller rebuild, _prepare, locked _lineage, consume, _prove_authority, and resource-digest comparison. The two copies already differ in small ways, for example the setup-service copy folds the status and response checks into the mismatch condition while the human copy raises idempotency_pending separately. Divergence in this path weakens replay custody. Extract one helper that takes execution_kind, material_digest, and the custody context.

Also applies to: 900-1000

🤖 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/projects/sufficiency_mutation_service.py` around lines
687 - 784, Extract the duplicated replay-recovery sequence from the human and
setup-service paths into one helper, anchored to the existing replay handling
around _caller, _prepare, _lineage, consume, and _prove_authority. Have the
helper accept execution_kind, material_digest, and the setup-service custody
context, while preserving each path’s required selector, report-provenance,
pending-state, and resource-digest validations and returning the validated
GuideSufficiencyMutationOutcome.
backend/tests/test_projects.py (2)

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

Select the default material result by identity, not by truthiness.

material_result or GuideSufficiencyMaterialResult(...) replaces a caller-supplied result whenever that result is falsy. A caller that passes a deliberately empty GuideSufficiencyMaterialResult then receives the helper's own object instead. Use an identity check so the caller's intent always wins.

♻️ Proposed fix
         async def load(self, _request: object) -> GuideSufficiencyMaterialResult:
             type(self).calls += 1
-            return material_result or GuideSufficiencyMaterialResult(
-                source_items=(), provenance=()
-            )
+            if material_result is None:
+                return GuideSufficiencyMaterialResult(source_items=(), provenance=())
+            return material_result
🤖 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_projects.py` around lines 2877 - 2881, Update the
material-result fallback in the load method to use an identity check against
None rather than truthiness, so any caller-supplied
GuideSufficiencyMaterialResult—including an intentionally empty one—is returned
unchanged; only create the default result when material_result is None.

5970-5975: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Clear the settings cache after the test, not only before it.

Line 5974 patches WORKSTREAM_CELERY_TASK_ALWAYS_EAGER and line 5975 clears the get_settings cache. monkeypatch restores the environment variable at teardown, but the cache keeps the Settings object that was built while the variable was patched. A later test in the same worker can then read the stale eager-mode setting.

test_project_setup_run_rejects_cross_context_worker_updates at lines 6415-6417 has the same gap. The file already uses the correct form at lines 8202 and 8218-8219.

♻️ Proposed teardown
     monkeypatch.setenv("WORKSTREAM_CELERY_TASK_ALWAYS_EAGER", "true")
     get_settings.cache_clear()
+    monkeypatch.setattr(
+        get_settings, "cache_clear", get_settings.cache_clear, raising=False
+    )

A clearer option is a shared autouse fixture that calls get_settings.cache_clear() after every test, which removes the need for per-test teardown at all 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_projects.py` around lines 5970 - 5975, Ensure settings
cache cleanup runs after these tests, not only before them: update
test_verified_worker_composes_fresh_exact_setup_service_authority and
test_project_setup_run_rejects_cross_context_worker_updates to clear
get_settings after execution, preferably via the file’s shared autouse fixture
pattern if available. Preserve the existing pre-test cache clearing where
needed.
docs/roadmap_status.md (1)

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

Cite the merge evidence for the new ledger entry.

Lines 13-15 require code, migrations, tests, and merged history as evidence for an implemented claim. The adjacent bullet at lines 60-64 follows that rule and names PRs #242, #248, #255, and #257. The new entry names no pull request.

Add the PR reference so the entry carries the same evidence as its neighbors.

📝 Proposed wording
 - Project Manager-authorized guide-sufficiency creation, agent-run requests,
   and warning acknowledgement with UUID replay custody; the fixed project-setup
-  service has only the internal sufficiency-run capability.
+  service has only the internal sufficiency-run capability. This merged through
+  PR `#263`.

As per path instructions: "Treat docs/roadmap_status.md as the current capability ledger".

🤖 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 `@docs/roadmap_status.md` around lines 65 - 67, Update the new ledger entry
describing Project Manager-authorized guide-sufficiency creation and related
capabilities to include the merged pull request reference that evidences its
implementation, matching the PR citation style used by the adjacent entries in
docs/roadmap_status.md.

Source: Path instructions

backend/tests/test_authorization.py (1)

5558-5568: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Assert that the handle is single-use.

The docstring claims a single-use handle, and the test proves that a rejected consume does not burn the handle. It does not prove that a second successful consume is refused. Add one trailing call to close that gap:

💚 Proposed assertion
     assert decision.matched_scope_project_id is None
     assert len(evidence.events) == 1
+    with pytest.raises(PreparedAuthorizationHandleInvalid):
+        await prepared.consume(
+            handle,
+            ActionId.PROJECT_GUIDE_SUFFICIENCY_RUN,
+            caller,
+            resource,
+        )
+    assert len(evidence.events) == 1

The equivalent replay assertion already exists for the artifact authority at lines 5725-5727.

🤖 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_authorization.py` around lines 5558 - 5568, Extend the
test around the successful consume of PROJECT_GUIDE_SUFFICIENCY_RUN to call
prepared.consume again with the same handle, caller, and resource, then assert
the replay is refused as required for a single-use handle. Keep the existing
success and evidence assertions intact, matching the equivalent
artifact-authority replay assertion.
backend/tests/test_alembic.py (1)

235-260: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Cover the provenance branch of the downgrade guard.

clear_evidence proves only one of the two refusal branches. The seeded report at lines 172-175 sets no provenance columns, so provenance_count in 0050_guide_sufficiency_authority.downgrade stays 0 for the whole test. After clear_evidence truncates the replay table, replay_count also reaches 0, so the second refusal condition never fires on its own.

Add one case that clears the replay ledger, sets creation or acknowledgement provenance on guide_sufficiency_reports, and asserts the same refusal. That proves attributed product provenance alone blocks rollback, which is the guarantee documented in docs/spec_authorization_service.md lines 1084-1089.

🤖 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 235 - 260, The migration
downgrade test currently covers only replay evidence; extend the test around
clear_evidence and the downgrade assertion to add a separate case that truncates
the replay ledger, sets creation or acknowledgement provenance on the seeded
guide_sufficiency_reports row, and verifies downgrade raises the same
RuntimeError. Ensure the case demonstrates provenance alone blocks rollback,
then preserve cleanup and restoration to HEAD_REVISION.
🤖 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
@.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/ACTIVATION_CUSTODY.md:
- Line 71: Update the custody counts in ACTIVATION_CUSTODY.md to reflect the
trusted-branch state: keep the three 12E actions counted as planned rather than
active until bounded merge is complete. Alternatively, explicitly label the
current counts as post-merge expected state and move the update to the
post-merge record, consistent with STATUS.md.

In
@.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md:
- Around line 492-494: Update the Celery scope statement near the legacy
role-check replacement to say that 12E changes worker admission and
command-boundary custody, while 12B2 owns the full setup-service call-graph
cutover; remove the blanket claim that 12E does not modify Celery workers.

In `@backend/alembic/versions/0050_guide_sufficiency_authority.py`:
- Around line 72-110: The check-constraint names in the migration are subject to
Alembic naming-convention expansion. Update both create_check_constraint calls
for ck_guide_sufficiency_creation_authority_shape and
ck_guide_sufficiency_ack_authority_shape to pass their names through op.f(...),
keeping the suffixes unchanged so upgrade and downgrade constraint names remain
consistent.

In `@backend/app/modules/authorization/kernel.py`:
- Around line 1017-1023: Update the sufficiency-kind guard in the authorization
kernel to require execution_kind == "human", matching the restriction used by
the fixed-service guard. Keep the existing denial conditions unchanged, and
ensure non-human execution kinds are denied before reaching _prove_authority.

---

Outside diff comments:
In `@backend/app/modules/projects/setup_queue.py`:
- Around line 82-99: In the setup queue flow around pre_submit_setup_task_id and
get_project_setup_run, fail fast with ProjectSetupQueueError when setup_run is
absent, before calling enqueue_pre_submit_setup_pipeline. Also handle a returned
task ID mismatch as a distinct post-acceptance identity error rather than
recording status="enqueue_failed" or logging it as an enqueue failure; preserve
normal enqueue failure handling for dispatch exceptions.

---

Nitpick comments:
In `@backend/alembic/versions/0050_guide_sufficiency_authority.py`:
- Around line 252-260: Update the cleanup loop over _CREATION_COLUMNS and
_ACK_COLUMNS to use one exclusion set containing created_by_service_identity and
the four scope/action names, while preserving the existing foreign-key
constraint and column removal behavior.
- Around line 176-209: Extend the replay-table protection around
reject_sufficiency_replay_mutation by adding a statement-level TRUNCATE trigger
that raises the same append-only exception, preventing truncation from removing
evidence. Update the test cleanup in backend/tests/test_alembic.py to use DELETE
instead of TRUNCATE for guide_sufficiency_mutation_idempotency_records.

In `@backend/app/modules/authorization/kernel.py`:
- Around line 653-662: Introduce a dedicated prepare-denial resource context for
sufficiency mutations containing only the requested selectors, and use it in the
authorization check alongside ProjectGuideMutationPrepareDenialResourceContext.
Update the corresponding sufficiency denial evidence construction and type
references to use this narrower context, while retaining
ProjectGuideSufficiencyMutationResourceContext for canonical mutation handling.

In `@backend/app/modules/authorization/prepared.py`:
- Around line 519-536: Store the constructed
ProjectGuideSufficiencyMutationResourceContext instance in a local validated
variable instead of discarding it, then update the sufficiency binding fields to
read from validated (including scope_project_id and the other mapped attributes)
rather than the raw sufficiency dictionary. Keep the existing constructor inputs
and validation behavior unchanged.

In `@backend/app/modules/projects/service.py`:
- Around line 178-182: Update the representative item filter in the
RepresentativeTaskMaterialContext builder to check item.source_kind against the
existing REPRESENTATIVE_TASK_SOURCE_KINDS constant instead of the literal
"example", keeping selection consistent with _guide_source_material.

In `@backend/app/modules/projects/sufficiency_mutation_service.py`:
- Around line 489-498: Update the return annotations of the async context
manager methods run_agent and run_setup_service from
GuideSufficiencyMutationOutcome to the appropriate AsyncIterator or
AsyncGenerator type representing the yielded outcome, while preserving their
existing yielded values and context-manager behavior.
- Around line 687-784: Extract the duplicated replay-recovery sequence from the
human and setup-service paths into one helper, anchored to the existing replay
handling around _caller, _prepare, _lineage, consume, and _prove_authority. Have
the helper accept execution_kind, material_digest, and the setup-service custody
context, while preserving each path’s required selector, report-provenance,
pending-state, and resource-digest validations and returning the validated
GuideSufficiencyMutationOutcome.

In `@backend/tests/test_alembic.py`:
- Around line 235-260: The migration downgrade test currently covers only replay
evidence; extend the test around clear_evidence and the downgrade assertion to
add a separate case that truncates the replay ledger, sets creation or
acknowledgement provenance on the seeded guide_sufficiency_reports row, and
verifies downgrade raises the same RuntimeError. Ensure the case demonstrates
provenance alone blocks rollback, then preserve cleanup and restoration to
HEAD_REVISION.

In `@backend/tests/test_authorization.py`:
- Around line 5558-5568: Extend the test around the successful consume of
PROJECT_GUIDE_SUFFICIENCY_RUN to call prepared.consume again with the same
handle, caller, and resource, then assert the replay is refused as required for
a single-use handle. Keep the existing success and evidence assertions intact,
matching the equivalent artifact-authority replay assertion.

In `@backend/tests/test_projects.py`:
- Around line 2877-2881: Update the material-result fallback in the load method
to use an identity check against None rather than truthiness, so any
caller-supplied GuideSufficiencyMaterialResult—including an intentionally empty
one—is returned unchanged; only create the default result when material_result
is None.
- Around line 5970-5975: Ensure settings cache cleanup runs after these tests,
not only before them: update
test_verified_worker_composes_fresh_exact_setup_service_authority and
test_project_setup_run_rejects_cross_context_worker_updates to clear
get_settings after execution, preferably via the file’s shared autouse fixture
pattern if available. Preserve the existing pre-test cache clearing where
needed.

In `@docs/roadmap_status.md`:
- Around line 65-67: Update the new ledger entry describing Project
Manager-authorized guide-sufficiency creation and related capabilities to
include the merged pull request reference that evidences its implementation,
matching the PR citation style used by the adjacent entries in
docs/roadmap_status.md.
🪄 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: bbe49ac1-b18f-4b0a-85b9-986174e30107

📥 Commits

Reviewing files that changed from the base of the PR and between 1072038 and aefec9e.

📒 Files selected for processing (32)
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/ACTIVATION_CUSTODY.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/STATUS.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12E-guide-sufficiency-mutations.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-internal-review-evidence.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-pr-trust-bundle.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-preimplementation-review-evidence.md
  • .github/workflows/backend.yml
  • backend/alembic/versions/0050_guide_sufficiency_authority.py
  • backend/app/modules/artifacts/authorization.py
  • backend/app/modules/authorization/catalogue.py
  • backend/app/modules/authorization/kernel.py
  • backend/app/modules/authorization/prepared.py
  • backend/app/modules/authorization/runtime.py
  • backend/app/modules/projects/guide_mutation_router.py
  • backend/app/modules/projects/models.py
  • backend/app/modules/projects/router.py
  • backend/app/modules/projects/service.py
  • backend/app/modules/projects/setup_queue.py
  • backend/app/modules/projects/sufficiency_mutation_repository.py
  • backend/app/modules/projects/sufficiency_mutation_service.py
  • backend/app/workers/project_setup.py
  • backend/scripts/api_contract_e2e.py
  • backend/tests/conftest.py
  • backend/tests/test_alembic.py
  • backend/tests/test_authorization.py
  • backend/tests/test_projects.py
  • docs/operations_authorization_service.md
  • docs/operations_project_operating_manual.md
  • docs/roadmap_status.md
  • docs/spec_authorization_service.md

Comment thread .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md Outdated
Comment thread backend/alembic/versions/0054_guide_sufficiency_authority.py
Comment thread backend/app/modules/authorization/kernel.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: 5

Caution

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

⚠️ Outside diff range comments (4)
backend/app/modules/projects/sufficiency_mutation_service.py (1)

342-350: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Two mutation entry points carry return annotations that contradict their call protocol. create_report is a plain coroutine but is annotated as an async iterator, while run_setup_service is an @asynccontextmanager but is annotated with the plain outcome type. Align each annotation with its actual protocol, using run_agent at line 504 as the reference for the context-manager form.

  • backend/app/modules/projects/sufficiency_mutation_service.py#L342-L350: change the return type to GuideSufficiencyMutationOutcome.
  • backend/app/modules/projects/sufficiency_mutation_service.py#L521-L532: change the return type to AsyncIterator[GuideSufficiencyMutationOutcome].
🤖 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/projects/sufficiency_mutation_service.py` around lines
342 - 350, The return annotations in
backend/app/modules/projects/sufficiency_mutation_service.py at lines 342-350
and 521-532 contradict the mutation protocols. Update create_report to return
GuideSufficiencyMutationOutcome, and update run_setup_service to return
AsyncIterator[GuideSufficiencyMutationOutcome], matching the
`@asynccontextmanager` pattern used by run_agent; no other behavior changes are
needed.
backend/app/workers/project_setup.py (1)

294-341: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reset the session before writing a terminal failure state.

If an earlier database operation leaves session rollback-only, service.update_project_setup_run_status(...) can raise PendingRollbackError instead of persisting setup_blocked or failed. Roll back both exception branches before the status update.

Proposed fix
             except ProjectServiceError as exc:
+                await session.rollback()
                 public_error = safe_project_setup_error_summary(str(exc))
@@
             except Exception as exc:
+                await session.rollback()
                 public_error = "unexpected project setup pipeline failure"
🤖 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/workers/project_setup.py` around lines 294 - 341, Roll back the
database session at the start of both the ProjectServiceError and generic
Exception handlers, before calling service.update_project_setup_run_status.
Ensure the rollback resets any rollback-only transaction state while preserving
the existing setup_blocked and failed status updates.
backend/scripts/api_contract_e2e.py (1)

961-968: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Remove the synchronous setup execution path.

asyncio.to_thread() invokes run_pre_submit_setup_pipeline() from an async E2E flow. Expose or call a typed async setup-pipeline entry point and await it directly. Keep a synchronous Celery wrapper only at the worker boundary.

As per coding guidelines, “Execution is async-first; do not document or implement synchronous-first checkers or jobs.”

🤖 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/scripts/api_contract_e2e.py` around lines 961 - 968, Replace the
asyncio.to_thread invocation around run_pre_submit_setup_pipeline with a typed
asynchronous setup-pipeline entry point and await it directly in the E2E flow.
Keep any synchronous Celery adapter limited to the worker boundary, and update
the setup pipeline implementation so the async entry point is the primary
execution path.

Source: Coding guidelines

backend/app/modules/projects/setup_queue.py (1)

100-115: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

The fresh-claim branch builds a non-deterministic task id, so dispatch always fails.

Line 101 builds f"guide-setup-{setup_run_id}-g{setup_generation}". Line 91 computes expected_task_id from pre_submit_setup_task_id, which returns a UUID5 string. The two values can never be equal, so the guard at line 114 raises ProjectSetupQueueError for every run in status queued or enqueue_failed. That is the primary dispatch path, so no setup pipeline is ever enqueued.

backend/tests/test_projects.py confirms the intended value: test_verified_setup_enqueue_failure_is_sanitized_and_retryable asserts body["celery_task_id"] == project_setup_queue_module.pre_submit_setup_task_id(body["id"], body["setup_generation"]).

Use expected_task_id for the new claim.

🐛 Proposed fix for the deterministic task identity
     elif setup_run.status in {"queued", "enqueue_failed"}:
-        deterministic_task_id = f"guide-setup-{setup_run_id}-g{setup_generation}"
+        deterministic_task_id = expected_task_id
         setup_run.status = "dispatch_pending"
         setup_run.current_step = "dispatch"
         setup_run.celery_task_id = deterministic_task_id
🤖 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/projects/setup_queue.py` around lines 100 - 115, Update
the fresh-claim branch in the setup queue flow to assign expected_task_id,
rather than constructing the "guide-setup-..." value, to
setup_run.celery_task_id. Keep the existing identity guard and ensure queued or
enqueue_failed runs use the UUID5 value returned by pre_submit_setup_task_id.
🧹 Nitpick comments (2)
backend/alembic/versions/0051_guide_sufficiency_authority.py (1)

188-237: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Verify trigger and function ownership across repeated upgrade/downgrade cycles.

upgrade() creates reject_sufficiency_replay_mutation() and reject_sufficiency_replay_truncate() with create function, not create or replace function. If a previous partial upgrade left either function present, the migration fails. The downgrade() drops both functions unconditionally, which is only safe while no other object depends on them.

Consider create or replace function and drop function if exists for idempotent re-runs.

🤖 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/0051_guide_sufficiency_authority.py` around lines
188 - 237, Make the trigger-function creation in upgrade() safe across repeated
or partial runs by replacing both create function statements for
reject_sufficiency_replay_mutation and reject_sufficiency_replay_truncate with
create or replace function. Update downgrade() to use drop function if exists
for both functions, preserving dependency-safe ordering with their associated
triggers.
backend/app/modules/projects/sufficiency_mutation_service.py (1)

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

Prefer a public accessor over ProjectService._verified_report_usages.

This call reaches into a private method of another class. A public method on ProjectService, or a shared validation helper, keeps the boundary explicit and prevents silent breakage when ProjectService internals change. The same private call already exists in _run_agent paths, so one public seam covers both.

🤖 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/projects/sufficiency_mutation_service.py` at line 659,
Replace the direct call to the private `_verified_report_usages` method in the
sufficiency mutation flow with a public accessor on `ProjectService` (or shared
validation helper), and update the existing `_run_agent` paths to use the same
public seam so all callers avoid reaching into `ProjectService` internals.
🤖 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
@.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-pr-trust-bundle.md:
- Around line 24-25: Correct all three references to the authorization migration
in this record from 0050 to 0051: the added-migration summary, the migration
selector result, and the replay/provenance constraints statement. Use the
established migration identifier 0051_guide_sufficiency_authority consistently,
without changing references to the separate 0050_guide_source_v2 migration.

In `@backend/alembic/versions/0051_guide_sufficiency_authority.py`:
- Around line 41-52: Update the status list in the upgrade() check constraint
recreation to include 'dispatch_pending', preserving compatibility with
ProjectSetupRun and setup_queue.py. Leave the downgrade() constraint unchanged
so it continues to use the pre-0051 status values without 'dispatch_pending'.

In `@backend/app/modules/projects/setup_queue.py`:
- Around line 160-167: Move the await session.commit() call in the
identity-mismatch handling after the setup_run status conditional so it executes
unconditionally before returning None. Keep the existing status, step,
error-code, and error-summary updates guarded by the dispatch_pending check,
matching the enqueue-failure commit behavior.

In `@backend/tests/test_projects.py`:
- Around line 2688-2702: Remove the stale pre-cutover assertions in
backend/tests/test_projects.py at lines 2688-2702, 2773-2789, and 3932-3937: at
2688-2702 remove the report/policy provenance block and retain report is None
and policy is None; at 2773-2789 remove enqueued[0] access and the
pre_submit_setup_task_id comparison, retaining enqueued == [] and celery_task_id
is None; at 3932-3937 remove the pre_submit_setup_task_id comparison, retaining
celery_task_id is None and status == "queued".

In `@docs/spec_chunk_3_project_guide_foundation.md`:
- Around line 152-156: Add POST
/api/v1/projects/{project_id}/guides/{guide_id}/source-snapshots/{source_snapshot_id}/run-sufficiency-agent
to the protected-route API inventory, placing it beside the existing
source-snapshot route and keeping the inventory consistent with the OpenAPI
contract.

---

Outside diff comments:
In `@backend/app/modules/projects/setup_queue.py`:
- Around line 100-115: Update the fresh-claim branch in the setup queue flow to
assign expected_task_id, rather than constructing the "guide-setup-..." value,
to setup_run.celery_task_id. Keep the existing identity guard and ensure queued
or enqueue_failed runs use the UUID5 value returned by pre_submit_setup_task_id.

In `@backend/app/modules/projects/sufficiency_mutation_service.py`:
- Around line 342-350: The return annotations in
backend/app/modules/projects/sufficiency_mutation_service.py at lines 342-350
and 521-532 contradict the mutation protocols. Update create_report to return
GuideSufficiencyMutationOutcome, and update run_setup_service to return
AsyncIterator[GuideSufficiencyMutationOutcome], matching the
`@asynccontextmanager` pattern used by run_agent; no other behavior changes are
needed.

In `@backend/app/workers/project_setup.py`:
- Around line 294-341: Roll back the database session at the start of both the
ProjectServiceError and generic Exception handlers, before calling
service.update_project_setup_run_status. Ensure the rollback resets any
rollback-only transaction state while preserving the existing setup_blocked and
failed status updates.

In `@backend/scripts/api_contract_e2e.py`:
- Around line 961-968: Replace the asyncio.to_thread invocation around
run_pre_submit_setup_pipeline with a typed asynchronous setup-pipeline entry
point and await it directly in the E2E flow. Keep any synchronous Celery adapter
limited to the worker boundary, and update the setup pipeline implementation so
the async entry point is the primary execution path.

---

Nitpick comments:
In `@backend/alembic/versions/0051_guide_sufficiency_authority.py`:
- Around line 188-237: Make the trigger-function creation in upgrade() safe
across repeated or partial runs by replacing both create function statements for
reject_sufficiency_replay_mutation and reject_sufficiency_replay_truncate with
create or replace function. Update downgrade() to use drop function if exists
for both functions, preserving dependency-safe ordering with their associated
triggers.

In `@backend/app/modules/projects/sufficiency_mutation_service.py`:
- Line 659: Replace the direct call to the private `_verified_report_usages`
method in the sufficiency mutation flow with a public accessor on
`ProjectService` (or shared validation helper), and update the existing
`_run_agent` paths to use the same public seam so all callers avoid reaching
into `ProjectService` internals.
🪄 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: e35540a5-cf80-4b06-b3e5-d0b2a2e71718

📥 Commits

Reviewing files that changed from the base of the PR and between aefec9e and 2673df0.

📒 Files selected for processing (28)
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/ACTIVATION_CUSTODY.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12E-guide-sufficiency-mutations.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-external-review-response.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-pr-trust-bundle.md
  • .github/workflows/backend.yml
  • backend/alembic/versions/0051_guide_sufficiency_authority.py
  • backend/app/modules/authorization/kernel.py
  • backend/app/modules/projects/guide_mutation_router.py
  • backend/app/modules/projects/models.py
  • backend/app/modules/projects/router.py
  • backend/app/modules/projects/service.py
  • backend/app/modules/projects/setup_queue.py
  • backend/app/modules/projects/sufficiency_mutation_service.py
  • backend/app/workers/project_setup.py
  • backend/scripts/api_contract_e2e.py
  • backend/tests/conftest.py
  • backend/tests/test_alembic.py
  • backend/tests/test_api_controls.py
  • backend/tests/test_artifact_internal_authorization.py
  • backend/tests/test_audit.py
  • backend/tests/test_authorization.py
  • backend/tests/test_projects.py
  • docs/architecture_data_model.md
  • docs/glossary.md
  • docs/operations_authorization_service.md
  • docs/spec_authorization_service.md
  • docs/spec_chunk_3_project_guide_foundation.md
💤 Files with no reviewable changes (1)
  • backend/app/modules/projects/guide_mutation_router.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • .github/workflows/backend.yml
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12E-guide-sufficiency-mutations.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/ACTIVATION_CUSTODY.md
  • backend/app/modules/authorization/kernel.py
  • docs/operations_authorization_service.md
  • backend/app/modules/projects/service.py
  • backend/app/modules/projects/router.py
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md

Comment thread backend/alembic/versions/0054_guide_sufficiency_authority.py
Comment thread backend/app/modules/projects/setup_queue.py
Comment thread docs/spec_chunk_3_project_guide_foundation.md

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (4)
backend/app/modules/projects/sufficiency_mutation_service.py (1)

342-350: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Two mutation entry points carry return annotations that contradict their call protocol. create_report is a plain coroutine but is annotated as an async iterator, while run_setup_service is an @asynccontextmanager but is annotated with the plain outcome type. Align each annotation with its actual protocol, using run_agent at line 504 as the reference for the context-manager form.

  • backend/app/modules/projects/sufficiency_mutation_service.py#L342-L350: change the return type to GuideSufficiencyMutationOutcome.
  • backend/app/modules/projects/sufficiency_mutation_service.py#L521-L532: change the return type to AsyncIterator[GuideSufficiencyMutationOutcome].
🤖 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/projects/sufficiency_mutation_service.py` around lines
342 - 350, The return annotations in
backend/app/modules/projects/sufficiency_mutation_service.py at lines 342-350
and 521-532 contradict the mutation protocols. Update create_report to return
GuideSufficiencyMutationOutcome, and update run_setup_service to return
AsyncIterator[GuideSufficiencyMutationOutcome], matching the
`@asynccontextmanager` pattern used by run_agent; no other behavior changes are
needed.
backend/app/workers/project_setup.py (1)

294-341: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reset the session before writing a terminal failure state.

If an earlier database operation leaves session rollback-only, service.update_project_setup_run_status(...) can raise PendingRollbackError instead of persisting setup_blocked or failed. Roll back both exception branches before the status update.

Proposed fix
             except ProjectServiceError as exc:
+                await session.rollback()
                 public_error = safe_project_setup_error_summary(str(exc))
@@
             except Exception as exc:
+                await session.rollback()
                 public_error = "unexpected project setup pipeline failure"
🤖 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/workers/project_setup.py` around lines 294 - 341, Roll back the
database session at the start of both the ProjectServiceError and generic
Exception handlers, before calling service.update_project_setup_run_status.
Ensure the rollback resets any rollback-only transaction state while preserving
the existing setup_blocked and failed status updates.
backend/scripts/api_contract_e2e.py (1)

961-968: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Remove the synchronous setup execution path.

asyncio.to_thread() invokes run_pre_submit_setup_pipeline() from an async E2E flow. Expose or call a typed async setup-pipeline entry point and await it directly. Keep a synchronous Celery wrapper only at the worker boundary.

As per coding guidelines, “Execution is async-first; do not document or implement synchronous-first checkers or jobs.”

🤖 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/scripts/api_contract_e2e.py` around lines 961 - 968, Replace the
asyncio.to_thread invocation around run_pre_submit_setup_pipeline with a typed
asynchronous setup-pipeline entry point and await it directly in the E2E flow.
Keep any synchronous Celery adapter limited to the worker boundary, and update
the setup pipeline implementation so the async entry point is the primary
execution path.

Source: Coding guidelines

backend/app/modules/projects/setup_queue.py (1)

100-115: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

The fresh-claim branch builds a non-deterministic task id, so dispatch always fails.

Line 101 builds f"guide-setup-{setup_run_id}-g{setup_generation}". Line 91 computes expected_task_id from pre_submit_setup_task_id, which returns a UUID5 string. The two values can never be equal, so the guard at line 114 raises ProjectSetupQueueError for every run in status queued or enqueue_failed. That is the primary dispatch path, so no setup pipeline is ever enqueued.

backend/tests/test_projects.py confirms the intended value: test_verified_setup_enqueue_failure_is_sanitized_and_retryable asserts body["celery_task_id"] == project_setup_queue_module.pre_submit_setup_task_id(body["id"], body["setup_generation"]).

Use expected_task_id for the new claim.

🐛 Proposed fix for the deterministic task identity
     elif setup_run.status in {"queued", "enqueue_failed"}:
-        deterministic_task_id = f"guide-setup-{setup_run_id}-g{setup_generation}"
+        deterministic_task_id = expected_task_id
         setup_run.status = "dispatch_pending"
         setup_run.current_step = "dispatch"
         setup_run.celery_task_id = deterministic_task_id
🤖 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/projects/setup_queue.py` around lines 100 - 115, Update
the fresh-claim branch in the setup queue flow to assign expected_task_id,
rather than constructing the "guide-setup-..." value, to
setup_run.celery_task_id. Keep the existing identity guard and ensure queued or
enqueue_failed runs use the UUID5 value returned by pre_submit_setup_task_id.
🧹 Nitpick comments (2)
backend/alembic/versions/0051_guide_sufficiency_authority.py (1)

188-237: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Verify trigger and function ownership across repeated upgrade/downgrade cycles.

upgrade() creates reject_sufficiency_replay_mutation() and reject_sufficiency_replay_truncate() with create function, not create or replace function. If a previous partial upgrade left either function present, the migration fails. The downgrade() drops both functions unconditionally, which is only safe while no other object depends on them.

Consider create or replace function and drop function if exists for idempotent re-runs.

🤖 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/0051_guide_sufficiency_authority.py` around lines
188 - 237, Make the trigger-function creation in upgrade() safe across repeated
or partial runs by replacing both create function statements for
reject_sufficiency_replay_mutation and reject_sufficiency_replay_truncate with
create or replace function. Update downgrade() to use drop function if exists
for both functions, preserving dependency-safe ordering with their associated
triggers.
backend/app/modules/projects/sufficiency_mutation_service.py (1)

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

Prefer a public accessor over ProjectService._verified_report_usages.

This call reaches into a private method of another class. A public method on ProjectService, or a shared validation helper, keeps the boundary explicit and prevents silent breakage when ProjectService internals change. The same private call already exists in _run_agent paths, so one public seam covers both.

🤖 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/projects/sufficiency_mutation_service.py` at line 659,
Replace the direct call to the private `_verified_report_usages` method in the
sufficiency mutation flow with a public accessor on `ProjectService` (or shared
validation helper), and update the existing `_run_agent` paths to use the same
public seam so all callers avoid reaching into `ProjectService` internals.
🤖 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
@.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-pr-trust-bundle.md:
- Around line 24-25: Correct all three references to the authorization migration
in this record from 0050 to 0051: the added-migration summary, the migration
selector result, and the replay/provenance constraints statement. Use the
established migration identifier 0051_guide_sufficiency_authority consistently,
without changing references to the separate 0050_guide_source_v2 migration.

In `@backend/alembic/versions/0051_guide_sufficiency_authority.py`:
- Around line 41-52: Update the status list in the upgrade() check constraint
recreation to include 'dispatch_pending', preserving compatibility with
ProjectSetupRun and setup_queue.py. Leave the downgrade() constraint unchanged
so it continues to use the pre-0051 status values without 'dispatch_pending'.

In `@backend/app/modules/projects/setup_queue.py`:
- Around line 160-167: Move the await session.commit() call in the
identity-mismatch handling after the setup_run status conditional so it executes
unconditionally before returning None. Keep the existing status, step,
error-code, and error-summary updates guarded by the dispatch_pending check,
matching the enqueue-failure commit behavior.

In `@backend/tests/test_projects.py`:
- Around line 2688-2702: Remove the stale pre-cutover assertions in
backend/tests/test_projects.py at lines 2688-2702, 2773-2789, and 3932-3937: at
2688-2702 remove the report/policy provenance block and retain report is None
and policy is None; at 2773-2789 remove enqueued[0] access and the
pre_submit_setup_task_id comparison, retaining enqueued == [] and celery_task_id
is None; at 3932-3937 remove the pre_submit_setup_task_id comparison, retaining
celery_task_id is None and status == "queued".

In `@docs/spec_chunk_3_project_guide_foundation.md`:
- Around line 152-156: Add POST
/api/v1/projects/{project_id}/guides/{guide_id}/source-snapshots/{source_snapshot_id}/run-sufficiency-agent
to the protected-route API inventory, placing it beside the existing
source-snapshot route and keeping the inventory consistent with the OpenAPI
contract.

---

Outside diff comments:
In `@backend/app/modules/projects/setup_queue.py`:
- Around line 100-115: Update the fresh-claim branch in the setup queue flow to
assign expected_task_id, rather than constructing the "guide-setup-..." value,
to setup_run.celery_task_id. Keep the existing identity guard and ensure queued
or enqueue_failed runs use the UUID5 value returned by pre_submit_setup_task_id.

In `@backend/app/modules/projects/sufficiency_mutation_service.py`:
- Around line 342-350: The return annotations in
backend/app/modules/projects/sufficiency_mutation_service.py at lines 342-350
and 521-532 contradict the mutation protocols. Update create_report to return
GuideSufficiencyMutationOutcome, and update run_setup_service to return
AsyncIterator[GuideSufficiencyMutationOutcome], matching the
`@asynccontextmanager` pattern used by run_agent; no other behavior changes are
needed.

In `@backend/app/workers/project_setup.py`:
- Around line 294-341: Roll back the database session at the start of both the
ProjectServiceError and generic Exception handlers, before calling
service.update_project_setup_run_status. Ensure the rollback resets any
rollback-only transaction state while preserving the existing setup_blocked and
failed status updates.

In `@backend/scripts/api_contract_e2e.py`:
- Around line 961-968: Replace the asyncio.to_thread invocation around
run_pre_submit_setup_pipeline with a typed asynchronous setup-pipeline entry
point and await it directly in the E2E flow. Keep any synchronous Celery adapter
limited to the worker boundary, and update the setup pipeline implementation so
the async entry point is the primary execution path.

---

Nitpick comments:
In `@backend/alembic/versions/0051_guide_sufficiency_authority.py`:
- Around line 188-237: Make the trigger-function creation in upgrade() safe
across repeated or partial runs by replacing both create function statements for
reject_sufficiency_replay_mutation and reject_sufficiency_replay_truncate with
create or replace function. Update downgrade() to use drop function if exists
for both functions, preserving dependency-safe ordering with their associated
triggers.

In `@backend/app/modules/projects/sufficiency_mutation_service.py`:
- Line 659: Replace the direct call to the private `_verified_report_usages`
method in the sufficiency mutation flow with a public accessor on
`ProjectService` (or shared validation helper), and update the existing
`_run_agent` paths to use the same public seam so all callers avoid reaching
into `ProjectService` internals.
🪄 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: e35540a5-cf80-4b06-b3e5-d0b2a2e71718

📥 Commits

Reviewing files that changed from the base of the PR and between aefec9e and 2673df0.

📒 Files selected for processing (28)
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/ACTIVATION_CUSTODY.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12E-guide-sufficiency-mutations.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-external-review-response.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-pr-trust-bundle.md
  • .github/workflows/backend.yml
  • backend/alembic/versions/0051_guide_sufficiency_authority.py
  • backend/app/modules/authorization/kernel.py
  • backend/app/modules/projects/guide_mutation_router.py
  • backend/app/modules/projects/models.py
  • backend/app/modules/projects/router.py
  • backend/app/modules/projects/service.py
  • backend/app/modules/projects/setup_queue.py
  • backend/app/modules/projects/sufficiency_mutation_service.py
  • backend/app/workers/project_setup.py
  • backend/scripts/api_contract_e2e.py
  • backend/tests/conftest.py
  • backend/tests/test_alembic.py
  • backend/tests/test_api_controls.py
  • backend/tests/test_artifact_internal_authorization.py
  • backend/tests/test_audit.py
  • backend/tests/test_authorization.py
  • backend/tests/test_projects.py
  • docs/architecture_data_model.md
  • docs/glossary.md
  • docs/operations_authorization_service.md
  • docs/spec_authorization_service.md
  • docs/spec_chunk_3_project_guide_foundation.md
💤 Files with no reviewable changes (1)
  • backend/app/modules/projects/guide_mutation_router.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • .github/workflows/backend.yml
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12E-guide-sufficiency-mutations.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/ACTIVATION_CUSTODY.md
  • backend/app/modules/authorization/kernel.py
  • docs/operations_authorization_service.md
  • backend/app/modules/projects/service.py
  • backend/app/modules/projects/router.py
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md
🛑 Comments failed to post (1)
backend/tests/test_projects.py (1)

2688-2702: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Stale pre-cutover assertions remain beside the new assertions in three tests. Each site keeps both the old expectation and the new, opposite expectation for the same variable, so each test fails deterministically. Remove the stale block in each test and keep the assertions that match the post-cutover behavior.

  • backend/tests/test_projects.py#L2688-L2702: remove the report is not None / policy is not None provenance block and keep report is None and policy is None.
  • backend/tests/test_projects.py#L2773-L2789: remove the enqueued[0] access and the celery_task_id == pre_submit_setup_task_id(...) assertion, and keep enqueued == [] with celery_task_id is None.
  • backend/tests/test_projects.py#L3932-L3937: remove the celery_task_id == pre_submit_setup_task_id(...) assertion and keep celery_task_id is None with status == "queued".
📍 Affects 1 file
  • backend/tests/test_projects.py#L2688-L2702 (this comment)
  • backend/tests/test_projects.py#L2773-L2789
  • backend/tests/test_projects.py#L3932-L3937
🤖 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_projects.py` around lines 2688 - 2702, Remove the stale
pre-cutover assertions in backend/tests/test_projects.py at lines 2688-2702,
2773-2789, and 3932-3937: at 2688-2702 remove the report/policy provenance block
and retain report is None and policy is None; at 2773-2789 remove enqueued[0]
access and the pre_submit_setup_task_id comparison, retaining enqueued == [] and
celery_task_id is None; at 3932-3937 remove the pre_submit_setup_task_id
comparison, retaining celery_task_id is None and status == "queued".

@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 (2)
backend/tests/test_projects.py (1)

2615-2652: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Align the docstring with the measured contract and cover identity mismatches separately.

This test only shows that a source snapshot with no verified material stays at status == "queued" without celery_task_id. enqueue_with_wrong_identity is never called because dispatch does not run, so the docstring should describe the verified-material gate instead of broker task-id acceptance. Add a separate test that reaches the dispatch_pending enqueue path and returns a different task id to exercise enqueue_identity_mismatch.

🤖 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_projects.py` around lines 2615 - 2652, The test docstring
should describe the verified-material gate: source snapshots without verified
material remain queued without a Celery task ID. Add a separate test that
advances setup into the dispatch_pending enqueue path, monkeypatches
enqueue_pre_submit_setup_pipeline to return a different task ID, and asserts the
enqueue_identity_mismatch behavior and resulting setup-run state.
backend/tests/test_alembic.py (1)

477-497: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Restore the head revision in a finally block in every migration-mutating test. These tests change the revision of the shared migrated database without guaranteeing recovery. If an assertion fails, or if a downgrade unexpectedly succeeds instead of raising, the database stays below head and every later test in the session runs against the wrong schema. test_0052_guide_sufficiency_authority_safe_empty_downgrade_and_reupgrade at backend/tests/test_alembic.py Line 104 already uses the correct try/finally pattern.

  • backend/tests/test_alembic.py#L477-L497: wrap the downgrade, state assertion, and second downgrade in try, and call command.upgrade(config, HEAD_REVISION) in finally instead of the trailing command.upgrade(config, "head").
  • backend/tests/test_review_queue_persistence.py#L563-L596: wrap the pytest.raises downgrade attempt in try, and restore the head revision under migration_lock() in finally.
  • backend/tests/test_review_queue_persistence.py#L599-L628: apply the same try/finally restore around the downgrade attempt in this test.
🤖 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 477 - 497, Migration-mutating
tests must always restore the shared database to head, even when assertions or
downgrade expectations fail. In backend/tests/test_alembic.py:477-497, wrap the
downgrade, state assertion, and second downgrade in try/finally and call
command.upgrade(config, HEAD_REVISION) in finally; in
backend/tests/test_review_queue_persistence.py:563-596 and :599-628, apply the
same try/finally restoration around each pytest.raises downgrade attempt while
keeping restoration under migration_lock().
🧹 Nitpick comments (1)
backend/tests/test_projects.py (1)

8369-8473: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the remaining compared report fields.

_validate_adoptable_verified_report compares thirteen report fields. The mismatches table exercises six of them. project_id, guide_id, guide_version, source_snapshot_id, source_snapshot_hash, and agent_material_byte_count stay unexercised, so a dropped comparison on those fields would not fail this test. Add them to the same table; each entry costs one line.

♻️ Proposed additional mismatch cases
     mismatches = (
+        ("project_id", str(uuid4())),
+        ("guide_id", str(uuid4())),
+        ("guide_version", "v2"),
+        ("source_snapshot_id", str(uuid4())),
+        ("source_snapshot_hash", sha256_hash("wrong-snapshot")),
+        ("agent_material_byte_count", 43),
         ("setup_generation", 3),
         ("project_setup_run_id", str(uuid4())),
🤖 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_projects.py` around lines 8369 - 8473, Expand the
mismatches table in
test_setup_service_adoption_requires_exact_report_and_source_provenance to
include invalid values for project_id, guide_id, guide_version,
source_snapshot_id, source_snapshot_hash, and agent_material_byte_count. Keep
the existing loop and validation assertions unchanged so each compared report
field is independently verified to raise sufficiency_report_provenance_mismatch.
🤖 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_review_queue_persistence.py`:
- Around line 585-591: Update the downgrade assertion around downgrade so it
expects the populated review-queue guard error, “cannot downgrade populated
review queue foundation,” instead of the stale guide-sufficiency-authority
message. Preserve the existing downgrade invocation and alembic_version
assertion, and do not attribute this failure to migration 0052.

---

Outside diff comments:
In `@backend/tests/test_alembic.py`:
- Around line 477-497: Migration-mutating tests must always restore the shared
database to head, even when assertions or downgrade expectations fail. In
backend/tests/test_alembic.py:477-497, wrap the downgrade, state assertion, and
second downgrade in try/finally and call command.upgrade(config, HEAD_REVISION)
in finally; in backend/tests/test_review_queue_persistence.py:563-596 and
:599-628, apply the same try/finally restoration around each pytest.raises
downgrade attempt while keeping restoration under migration_lock().

In `@backend/tests/test_projects.py`:
- Around line 2615-2652: The test docstring should describe the
verified-material gate: source snapshots without verified material remain queued
without a Celery task ID. Add a separate test that advances setup into the
dispatch_pending enqueue path, monkeypatches enqueue_pre_submit_setup_pipeline
to return a different task ID, and asserts the enqueue_identity_mismatch
behavior and resulting setup-run state.

---

Nitpick comments:
In `@backend/tests/test_projects.py`:
- Around line 8369-8473: Expand the mismatches table in
test_setup_service_adoption_requires_exact_report_and_source_provenance to
include invalid values for project_id, guide_id, guide_version,
source_snapshot_id, source_snapshot_hash, and agent_material_byte_count. Keep
the existing loop and validation assertions unchanged so each compared report
field is independently verified to raise sufficiency_report_provenance_mismatch.
🪄 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: 5d55fa52-9668-4aad-a493-db385723f768

📥 Commits

Reviewing files that changed from the base of the PR and between 2673df0 and a58d8da.

📒 Files selected for processing (17)
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12E-guide-sufficiency-mutations.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-internal-review-evidence.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-pr-trust-bundle.md
  • backend/alembic/versions/0052_guide_sufficiency_authority.py
  • backend/app/modules/projects/service.py
  • backend/app/modules/projects/setup_queue.py
  • backend/app/modules/projects/sufficiency_mutation_service.py
  • backend/app/workers/project_setup.py
  • backend/tests/conftest.py
  • backend/tests/test_alembic.py
  • backend/tests/test_projects.py
  • backend/tests/test_review_queue_persistence.py
  • docs/architecture_data_model.md
  • docs/operations_authorization_service.md
  • docs/operations_project_operating_manual.md
  • docs/spec_authorization_service.md
💤 Files with no reviewable changes (1)
  • backend/app/modules/projects/service.py
🚧 Files skipped from review as they are similar to previous changes (9)
  • docs/architecture_data_model.md
  • docs/operations_project_operating_manual.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-internal-review-evidence.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12E-guide-sufficiency-mutations.md
  • backend/app/workers/project_setup.py
  • backend/app/modules/projects/setup_queue.py
  • docs/spec_authorization_service.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12E-pr-trust-bundle.md

Comment thread backend/tests/test_review_queue_persistence.py Outdated
@Abiorh001

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Abiorh001 and others added 20 commits August 4, 2026 13:37
…-guide-sufficiency

# Conflicts:
#	backend/tests/test_projects.py
…-guide-sufficiency

# Conflicts:
#	backend/tests/conftest.py
#	backend/tests/test_alembic.py
#	backend/tests/test_review_queue_persistence.py
@Abiorh001

Copy link
Copy Markdown
Collaborator Author

Final exact-head verification for 074c9c5d:

  • CodeRabbit: passed; no unresolved, non-outdated review threads.
  • Agent Gates: passed.
  • All five hosted backend semantic lanes: passed.
  • Aggregate backend/coverage job: passed.
  • sufficiency_mutation_repository.py: 91.18%.
  • sufficiency_mutation_service.py: 93.67%.
  • Protected global 78% and subsystem 90% thresholds remain unchanged.
  • Internal architecture, security, product/ops, QA, and CI-integrity reviews: passed with no blocking findings.

The route now commits stable async dispatch custody before broker publication; exact replay survives queue progress; only the fixed setup service materializes guide content and invokes the sufficiency agent.

@abiorh-claw
abiorh-claw self-requested a review August 4, 2026 21:31
@abiorh-claw
abiorh-claw merged commit b510bc4 into main Aug 4, 2026
9 checks passed
@abiorh-claw
abiorh-claw deleted the codex/ws-auth-001-12e-guide-sufficiency branch August 4, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants