WS-XINT-003-02B: activate guide-bound policy mutations - #248
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughThe change activates guide-bound review-policy and revision-policy ChangesGuide-bound policy mutation activation
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant PolicyMutationRouter
participant ProjectPolicyMutationService
participant PreparedAuthorizationService
participant PolicyMutationReplayRepository
participant ProjectRepository
Client->>PolicyMutationRouter: PUT guide-bound policy with Idempotency-Key and If-Match
PolicyMutationRouter->>ProjectPolicyMutationService: replace policy
ProjectPolicyMutationService->>PolicyMutationReplayRepository: reserve or classify replay
ProjectPolicyMutationService->>PreparedAuthorizationService: prepare and consume authorization
ProjectPolicyMutationService->>ProjectRepository: append immutable policy version
ProjectPolicyMutationService->>PolicyMutationReplayRepository: complete committed replay
PolicyMutationRouter-->>Client: policy response or structured conflict
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
d6e136d to
f19540c
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (10)
backend/app/modules/projects/policy_mutation_router.py (2)
51-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDocument why
keyis a parameter that is immediately deleted.
policy_authorization_actordeclareskeyonly to force FastAPI to resolverequire_policy_mutation_keybefore actor resolution and rate-control consumption.del keythen discards it. A future reader can remove the parameter as dead code. That change would allow an invalidIdempotency-Keyto consume rate-control budget and resolve an actor before the 422 is raised.Add a one-line comment stating the ordering intent.
🤖 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/policy_mutation_router.py` around lines 51 - 59, Document the ordering intent in policy_authorization_actor with a one-line comment immediately before del key: retain key solely to force require_policy_mutation_key to run before actor resolution and rate-control consumption, then discard the validated value.
110-113: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueType the
authorizationdependency tuple.Both routes annotate the dependency as a bare
tupleand then unpack it into three names. A type checker cannot verify the arity or the element types of the unpack against whatpolicy_authorizationreturns.Annotate it as
tuple[UUID, ResolvedActor, PreparedAuthorizationService]in both routes, and add the matching return annotation topolicy_authorization.Also applies to: 134-137
🤖 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/policy_mutation_router.py` around lines 110 - 113, Update both route handlers’ authorization dependency annotations to tuple[UUID, ResolvedActor, PreparedAuthorizationService], and add the same tuple return annotation to policy_authorization. Preserve the existing three-value unpacking and ensure the required UUID, ResolvedActor, and PreparedAuthorizationService symbols are imported or reused from their existing definitions.backend/tests/test_project_policy_mutations.py (2)
622-622: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the exact exception type.
pytest.raises(Exception, match="only draft guides")passes for any exception whose message contains that substring. The service raisesGuideEditBlocked, which is importable fromapp.modules.projects.service. This file already importsProjectNotFoundfrom that module (Line 40).💚 Proposed fix to tighten the assertion
- with pytest.raises(Exception, match="only draft guides"): + with pytest.raises(GuideEditBlocked, match="only draft guides"):Update the import at Line 40:
from app.modules.projects.service import GuideEditBlocked, ProjectNotFound🤖 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_project_policy_mutations.py` at line 622, Update the test’s service import to include GuideEditBlocked, then change the pytest.raises assertion around the draft-guide mutation to expect GuideEditBlocked instead of the broad Exception type while preserving the existing message match.
113-118: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
_Replay.reservealways returnsclaimed, leaving three service branches uncovered.The fake always returns
"claimed"._replaceinpolicy_mutation_service.pybranches on thereservedisposition at Lines 295-300 and handles three other outcomes:mismatchraisesidempotency_mismatch,pendingraisesidempotency_pending, andreplayedreturns the stored response. No test drives_replacethrough any of them.
test_policy_service_denies_stale_guide_and_replay_mismatchreachesidempotency_mismatchthrough the_existingfast path, not throughreserve.test_replay_repository_owns_claim_classification_and_completionverifies the repository classification in isolation, not the service reaction to it.Add a parameterized disposition to the fake so the three branches are exercised.
As per coding guidelines: "New or materially changed backend subsystems must maintain at least 90% test coverage".
🤖 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_project_policy_mutations.py` around lines 113 - 118, Update the test fake’s _Replay.reserve method to return a configurable disposition and corresponding record, then parameterize the relevant policy mutation service tests to exercise mismatch, pending, and replayed outcomes through _replace. Assert each branch’s expected service behavior while preserving the existing claimed behavior and repository classification coverage.Source: Coding guidelines
backend/app/modules/projects/policy_mutation_service.py (1)
153-153: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAnnotate
_existing.
_existingdeclares seven parameters and a return value with no type annotations. Every other method onProjectPolicyMutationServiceis annotated.response_typein particular is a Pydantic model class used formodel_validate, and the untyped signature hides that contract.Add annotations consistent with
_replace.🤖 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/policy_mutation_service.py` at line 153, Annotate the `_existing` method signature with parameter and return types, matching the established annotations used by `_replace`. Explicitly type `response_type` as the Pydantic model class contract required by `model_validate`, and annotate all remaining parameters and the async return value consistently with the service’s existing conventions.backend/scripts/api_contract_e2e.py (1)
647-654: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDocument the arguments of
configure_policy_boundaries.The docstring is a single summary line.
request_jsonandcreate_policy_bundle_for_guidein this same file document every argument in anArgs:section.guide_versionis the least obvious parameter here, because it is used only for the directPaymentPolicyinsert and not for the two HTTP calls.Add an
Args:section.🤖 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 647 - 654, Expand the docstring for configure_policy_boundaries with an Args: section documenting client, token, project_id, guide_id, and guide_version, explicitly noting that guide_version is used for the direct PaymentPolicy insert. Keep the existing summary line and match the argument-documentation style used by request_json and create_policy_bundle_for_guide.backend/tests/test_alembic.py (1)
11884-11901: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the selector constraint expression, not only its presence.
The test asserts
selector_constraint: Truefor both the upgraded and the downgraded state.policy_selection_shapeexists in both states, so this assertion passes regardless of the migration's actual change. The behavioral change is the relaxation from "both policy selections set or both null" to "each selection independently set or null" (migration Lines 86-100, inverted at Lines 391-402).That relaxation is a precondition for the two independent
PUTroutes, because the first route call leaves one selection populated and the other null. The round-trip test does not currently prove it.Compare
pg_get_constraintdefforpolicy_selection_shapeacross the two states, or assert the partial-selection write succeeds after upgrade and fails after downgrade.🤖 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 11884 - 11901, The test’s selector_constraint assertions only verify presence, not the migration’s changed constraint behavior. Update the assertions around the upgraded and downgraded shape checks to compare the actual policy_selection_shape expression via pg_get_constraintdef, or add equivalent partial-selection writes that succeed after upgrade and fail after downgrade, while preserving the existing state checks.backend/app/modules/projects/policy_mutation_replay_repository.py (2)
22-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd docstrings to the public repository methods.
find,reserve, andcompletehave no docstrings. Every public method in the siblingProjectRepositoryinbackend/app/modules/projects/repository.pycarries one.reservein particular returns an untyped disposition string with four possible values (claimed,mismatch,pending,replayed), and the meaning of each is only discoverable by reading the caller.Document the return contract of
reserveat minimum.Also applies to: 94-111
🤖 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/policy_mutation_replay_repository.py` around lines 22 - 31, Add docstrings to the public methods find, reserve, and complete in the policy mutation replay repository, matching the documentation style used by ProjectRepository. Document each method’s purpose and return behavior, including reserve’s four possible disposition values: claimed, mismatch, pending, and replayed.
33-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a typed disposition instead of bare strings.
reservereturnstuple[str, PolicyMutationIdempotencyRecord]. The caller inpolicy_mutation_service.py(Lines 295-300) branches on the literal values"claimed","mismatch", and"pending", and treats every other value as replayed. A typo in either module produces a silent behavior change with no type error.Use
Literal["claimed", "mismatch", "pending", "replayed"]as the return annotation, or a small enum.🤖 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/policy_mutation_replay_repository.py` around lines 33 - 92, Update PolicyMutationReplayRepository.reserve to use a typed disposition—prefer Literal["claimed", "mismatch", "pending", "replayed"] or a small enum—instead of str, and update the policy mutation service branching to consume that type while preserving the existing four outcomes.backend/alembic/versions/0048_review_revision_policy_authority.py (1)
103-155: 🚀 Performance & Scalability | 🔵 TrivialConsider an index for the ledger lookup by
policy_id.The deferred custody trigger queries
policy_mutation_idempotency_recordsbypolicy_id,action_id,policy_generation, andstatus(Line 279-281). The table has no index onpolicy_id, so each policy append performs a sequential scan. Volume is low today, but the scan cost grows with the ledger, which is append-only and never truncated.Add a supporting index if the ledger is expected to grow.
🤖 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/0048_review_revision_policy_authority.py` around lines 103 - 155, Add a composite index for the deferred custody lookup on policy_mutation_idempotency_records, covering policy_id, action_id, policy_generation, and status. Define it alongside the table constraints in the migration and ensure the downgrade removes it with the table.
🤖 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/0048_review_revision_policy_authority.py`:
- Around line 354-366: Update the downgrade flow in
0048_review_revision_policy_authority.py to acquire SHARE ROW EXCLUSIVE locks on
policy_mutation_idempotency_records, review_policies, and revision_policies
before calculating has_custody. Match the locking pattern used by the earlier
authority migrations, then retain the existing custody check and RuntimeError
behavior.
- Around line 167-183: Update the commit-transition comparison in the policy
mutation guard to include created_at in both the new and old immutable column
tuples. Keep the existing status transition and all other identity, digest, and
lineage comparisons unchanged.
In `@backend/tests/test_artifact_admission.py`:
- Around line 572-643: Update suspend_historical_product_custody to allow the
review_policies/review_policy_mutation_custody and
revision_policies/revision_policy_mutation_custody table-trigger pairs used by
this test. Preserve the existing allow-list entries and ensure the helper
accepts these calls before the session.add_all inserts in the custody context.
In `@backend/tests/test_projects.py`:
- Around line 2054-2097: Update the policy handling in create_guide so
review_policy and revision_policy are copied before removing compatibility keys.
Preserve the existing request values and defaults, but ensure
values.pop("sla_hours", None) and values.pop("auto_reject_after_limit", None)
operate on independent dictionaries rather than caller-owned payload data.
In `@docs/operations_project_operating_manual.md`:
- Around line 118-123: Update the policy PUT route guidance to explicitly
instruct clients to construct the replacement If-Match selector from the
returned policy ID, generation, and canonical digest with its sha256 prefix
removed, formatted as a quoted
"<id>.<generation>.<policy_hash_without_sha256_prefix>" value; do not imply that
the API returns the selector directly.
---
Nitpick comments:
In `@backend/alembic/versions/0048_review_revision_policy_authority.py`:
- Around line 103-155: Add a composite index for the deferred custody lookup on
policy_mutation_idempotency_records, covering policy_id, action_id,
policy_generation, and status. Define it alongside the table constraints in the
migration and ensure the downgrade removes it with the table.
In `@backend/app/modules/projects/policy_mutation_replay_repository.py`:
- Around line 22-31: Add docstrings to the public methods find, reserve, and
complete in the policy mutation replay repository, matching the documentation
style used by ProjectRepository. Document each method’s purpose and return
behavior, including reserve’s four possible disposition values: claimed,
mismatch, pending, and replayed.
- Around line 33-92: Update PolicyMutationReplayRepository.reserve to use a
typed disposition—prefer Literal["claimed", "mismatch", "pending", "replayed"]
or a small enum—instead of str, and update the policy mutation service branching
to consume that type while preserving the existing four outcomes.
In `@backend/app/modules/projects/policy_mutation_router.py`:
- Around line 51-59: Document the ordering intent in policy_authorization_actor
with a one-line comment immediately before del key: retain key solely to force
require_policy_mutation_key to run before actor resolution and rate-control
consumption, then discard the validated value.
- Around line 110-113: Update both route handlers’ authorization dependency
annotations to tuple[UUID, ResolvedActor, PreparedAuthorizationService], and add
the same tuple return annotation to policy_authorization. Preserve the existing
three-value unpacking and ensure the required UUID, ResolvedActor, and
PreparedAuthorizationService symbols are imported or reused from their existing
definitions.
In `@backend/app/modules/projects/policy_mutation_service.py`:
- Line 153: Annotate the `_existing` method signature with parameter and return
types, matching the established annotations used by `_replace`. Explicitly type
`response_type` as the Pydantic model class contract required by
`model_validate`, and annotate all remaining parameters and the async return
value consistently with the service’s existing conventions.
In `@backend/scripts/api_contract_e2e.py`:
- Around line 647-654: Expand the docstring for configure_policy_boundaries with
an Args: section documenting client, token, project_id, guide_id, and
guide_version, explicitly noting that guide_version is used for the direct
PaymentPolicy insert. Keep the existing summary line and match the
argument-documentation style used by request_json and
create_policy_bundle_for_guide.
In `@backend/tests/test_alembic.py`:
- Around line 11884-11901: The test’s selector_constraint assertions only verify
presence, not the migration’s changed constraint behavior. Update the assertions
around the upgraded and downgraded shape checks to compare the actual
policy_selection_shape expression via pg_get_constraintdef, or add equivalent
partial-selection writes that succeed after upgrade and fail after downgrade,
while preserving the existing state checks.
In `@backend/tests/test_project_policy_mutations.py`:
- Line 622: Update the test’s service import to include GuideEditBlocked, then
change the pytest.raises assertion around the draft-guide mutation to expect
GuideEditBlocked instead of the broad Exception type while preserving the
existing message match.
- Around line 113-118: Update the test fake’s _Replay.reserve method to return a
configurable disposition and corresponding record, then parameterize the
relevant policy mutation service tests to exercise mismatch, pending, and
replayed outcomes through _replace. Assert each branch’s expected service
behavior while preserving the existing claimed behavior and repository
classification coverage.
🪄 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: ca896127-1a97-475a-a91a-e5bb5d5a28b3
📒 Files selected for processing (33)
.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12D2-guide-bound-policy-mutations.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/ACTION_CUSTODY.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/DISCOVERY.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/REVIEW_LOG.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/STATUS.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/chunks/WS-XINT-003-02B-policy-mutation-activation.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/reviews/WS-XINT-003-02B-internal-review.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/reviews/WS-XINT-003-02B-pr-trust-bundle.mdbackend/alembic/versions/0048_review_revision_policy_authority.pybackend/app/api/router.pybackend/app/modules/authorization/catalogue.pybackend/app/modules/authorization/kernel.pybackend/app/modules/authorization/prepared.pybackend/app/modules/authorization/runtime.pybackend/app/modules/projects/models.pybackend/app/modules/projects/policy_mutation_replay_repository.pybackend/app/modules/projects/policy_mutation_router.pybackend/app/modules/projects/policy_mutation_service.pybackend/app/modules/projects/repository.pybackend/app/modules/projects/schemas.pybackend/scripts/api_contract_e2e.pybackend/tests/test_alembic.pybackend/tests/test_artifact_admission.pybackend/tests/test_authorization.pybackend/tests/test_policy_identity_lineage.pybackend/tests/test_project_policy_mutations.pybackend/tests/test_projects.pybackend/tests/test_tasks.pydocs/operations_authorization_service.mddocs/operations_project_operating_manual.mddocs/operations_roles_permissions.mddocs/spec_authorization_service.mddocs/spec_review_lifecycle.md
There was a problem hiding this comment.
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/app/modules/projects/policy_mutation_service.py`:
- Around line 277-298: Revalidate the guide version after acquiring the project
guide lock and before prepared.consume(...). In the locked-guide validation
alongside the existing selector precondition, reject when guide.version differs
from guide_snapshot.version, preserving the existing conflict behavior so the
PREP resource cannot use a stale guide_version.
🪄 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: 28a1d657-f11e-4634-8c51-0d0b1bae6b3d
📒 Files selected for processing (40)
.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12D2-guide-bound-policy-mutations.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/ACTION_CUSTODY.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/DISCOVERY.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/REVIEW_LOG.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/STATUS.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/chunks/WS-XINT-003-02B-policy-mutation-activation.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/reviews/WS-XINT-003-02B-external-review-response.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/reviews/WS-XINT-003-02B-internal-review.md.agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/reviews/WS-XINT-003-02B-pr-trust-bundle.mdbackend/alembic/versions/0048_review_revision_policy_authority.pybackend/app/api/router.pybackend/app/modules/authorization/catalogue.pybackend/app/modules/authorization/kernel.pybackend/app/modules/authorization/prepared.pybackend/app/modules/authorization/runtime.pybackend/app/modules/projects/models.pybackend/app/modules/projects/policy_mutation_replay_repository.pybackend/app/modules/projects/policy_mutation_router.pybackend/app/modules/projects/policy_mutation_service.pybackend/app/modules/projects/repository.pybackend/app/modules/projects/schemas.pybackend/scripts/api_contract_e2e.pybackend/scripts/run_test_lanes.pybackend/tests/conftest.pybackend/tests/project_create_fixtures.pybackend/tests/test_alembic.pybackend/tests/test_api_controls.pybackend/tests/test_artifact_admission.pybackend/tests/test_audit.pybackend/tests/test_auth.pybackend/tests/test_authorization.pybackend/tests/test_policy_identity_lineage.pybackend/tests/test_project_policy_mutations.pybackend/tests/test_projects.pybackend/tests/test_tasks.pydocs/operations_authorization_service.mddocs/operations_project_operating_manual.mddocs/operations_roles_permissions.mddocs/spec_authorization_service.mddocs/spec_review_lifecycle.md
🚧 Files skipped from review as they are similar to previous changes (28)
- backend/app/api/router.py
- .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12D2-guide-bound-policy-mutations.md
- .agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/REVIEW_LOG.md
- .agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/ACTION_CUSTODY.md
- .agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/reviews/WS-XINT-003-02B-internal-review.md
- backend/app/modules/projects/repository.py
- backend/app/modules/authorization/catalogue.py
- docs/operations_authorization_service.md
- .agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/DISCOVERY.md
- .agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/STATUS.md
- backend/app/modules/projects/schemas.py
- backend/app/modules/authorization/prepared.py
- docs/operations_project_operating_manual.md
- backend/tests/test_artifact_admission.py
- backend/app/modules/authorization/kernel.py
- backend/app/modules/authorization/runtime.py
- backend/tests/test_policy_identity_lineage.py
- backend/tests/test_authorization.py
- .agent-loop/initiatives/WS-XINT-003-rev-auth-end-to-end/chunks/WS-XINT-003-02B-policy-mutation-activation.md
- backend/app/modules/projects/models.py
- backend/tests/test_projects.py
- backend/tests/test_alembic.py
- docs/spec_authorization_service.md
- backend/app/modules/projects/policy_mutation_router.py
- backend/tests/test_tasks.py
- backend/app/modules/projects/policy_mutation_replay_repository.py
- docs/operations_roles_permissions.md
- backend/scripts/api_contract_e2e.py
PR Trust Bundle: WS-XINT-003-02B
Chunk
WS-XINT-003-02B— Guide-bound policy mutation activation.Goal and human-approved intent
Activate exactly
project.review_policy.updateandproject.revision_policy.updateso a covered Project Manager can append andselect immutable policy versions for one exact draft guide. Do not activate the
review or revision lifecycle.
What changed and why
PUTroutes and one policy mutation service.predecessor, replay record, authority evidence, actor/link/grant, and digest.
retained explicitly historical incomplete fixtures only where required.
This removes direct or embedded policy writes and makes the authorized path the
sole live configuration boundary.
Design and alternatives rejected
The design uses opaque exact
If-Matchselectors, UUID idempotency keys,replay classification before PREP, locked selector revalidation, single-use
transaction-bound PREP consumption, append-only rows, and atomic evidence.
Raw AuthorizationContext authority, role-only fallback, mutable policy rows,
digest-only selectors, and a second authorization protocol were rejected.
Scope and product behavior
Only draft-guide policy configuration changes. Reviewer queues, leases,
findings, decisions, contributor revisions, artifacts, payments, contribution
records, and reputation remain unavailable or unchanged.
Acceptance proof and tests
Markdown links, and
git diff --check: passed.GitHub Actions as required; no local full-suite run was performed.
Test delta and CI integrity
No tests were removed, skipped, or weakened. Live project/task/E2E fixtures now
use the real routes. Historical artifact fixtures remain explicitly
legacy_incomplete. No workflow, lane, threshold, or failure behavior changed.Reviewer results
Architecture, security, product/operations, docs, and CI integrity passed. QA,
senior engineering, reuse/dedup, and test-delta passed with low non-blocking
risks. Every blocking first-round finding was fixed and re-reviewed.
External review
GitHub
Backend / test,Agent Gates / agent-gates, and CodeRabbit must passon the exact final head. Valid findings must be corrected before human merge.
Remaining risks and follow-up
The API may later expose the opaque replacement selector as a response ETag.
The next REV/AUTH lifecycle chunk remains separate and requires a new explicit
start after this PR is human-merged.
Human review focus
Confirm replay-before-PREP ordering, exact successor/predecessor custody,
Project Manager scope, append-only behavior, denial side-effect ordering, and
the absence of review/revision lifecycle activation.
Human merge ownership
Only the human may merge this PR.
Summary by CodeRabbit
New Features
Documentation
Bug Fixes