Skip to content

WS-AUTH-001-12F2: govern manual submission policy drafts - #292

Merged
abiorh-claw merged 23 commits into
mainfrom
codex/ws-auth-001-12f2-manual-submission-policy
Aug 6, 2026
Merged

WS-AUTH-001-12F2: govern manual submission policy drafts#292
abiorh-claw merged 23 commits into
mainfrom
codex/ws-auth-001-12f2-manual-submission-policy

Conversation

@Abiorh001

@Abiorh001 Abiorh001 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Workstream PR Trust Bundle

Chunk

WS-AUTH-001-12F2 - Manual Submission Policy Drafts

Goal

Activate only human Project Manager manual create/update for submission-policy
drafts, with exact sufficiency lineage, non-bypassable Workstream defaults,
append-only replacement, PREP evidence, and replay custody.

What changed

  • Activated create/update under child owner 12F2; derive/approve remain planned.
  • Added a dedicated human/key/PREP API boundary with service concealment.
  • Added exact system-or-project Project Manager admission before protected
    lookups and exact locked PREP before mutation.
  • Replaced in-place updates with a deterministic successor row and atomic
    predecessor supersession.
  • Added replay-first pending/committed handling that reauthorizes stored facts
    without depending on later guide/setup lineage.
  • Removed obsolete self-committing manual create/update service entry points.
  • Expanded focused, fault-injection, OpenAPI, and real API contract coverage.

Scope and behavior

  • Project Managers may create or replace a manual draft only for a covered
    project and its current authoritative sufficiency lineage.
  • Warning-bearing sufficiency requires exact 12E acknowledgement custody.
  • Agent-derived rows remain immutable through the manual route.
  • Derivation, approval, effective/pre-submit compilation, Celery, submission,
    review, revision, payment, and reputation behavior are unchanged.
  • No migration is introduced by this chunk.

Local evidence

Ruff app/tests/scripts: passed
AUTH exact selector: 7 passed
Non-database boundary/schema/replay checks: passed
Project exact selector: 27 tests collected
Hosted submission-policy authority selector: 5 tests passed; service 90.33%, repository 100.00%
CI lane contract: 33 passed
Python compileall: passed
Stale authorization docs: passed
Markdown links: passed
Stale Workstream wording: passed
git diff --check: passed
Branch versus origin/main: 0 ahead / 0 behind before commit

Database-backed project tests and the roughly four-hour full suite are not run
on the user's slow local machine. GitHub Actions must run the PostgreSQL-backed
focused coverage, API E2E, repository-wide 78 percent floor, and changed-
subsystem 90 percent floor on the exact pushed head.

Acceptance proof

  • Only manual create/update are active under 12F2.
  • System and exact-project PM grants admit; wrong-project, contributor,
    service, and role-claim-only callers deny.
  • Exact locked lineage and warning acknowledgement bind PREP and replay.
  • Update is append-only and CAS-bound; agent rows cannot use this path.
  • Exact committed replay returns stored response after later lineage drift.
  • Matching pending replay is retryable; changed/cross-action reuse denies.
  • Named create and update fault boundaries roll back product, replay, and
    allowed evidence together.
  • Hosted PostgreSQL, full coverage, Agent Gates, API E2E, and CodeRabbit
    pass on the exact pushed head.

Human review focus

  • Concealment-only PM admission versus exact durable PREP authority.
  • Stored-fact replay independence from later guide/setup changes.
  • Append-only predecessor/successor identity and transaction atomicity.
  • Default-floor preservation and manual/agent provenance isolation.

Human merge ownership

  • The user explicitly approved this specific PR for merge.

Summary by CodeRabbit

  • New Features

    • Added governed manual creation and updating of project submission policies by authorized human project managers.
    • Updates create immutable successor versions while preserving predecessors.
    • Added idempotent replay handling, structured conflicts, rollback safety, and stale-update protection.
    • Added validation for project scope, policy lineage, warning acknowledgements, and provenance.
    • Agent-derived policies remain protected from manual modification.
  • Documentation

    • Updated authorization and operating guidance for manual policy workflows.
    • Clarified that policy derivation and approval remain planned.

@coderabbitai

coderabbitai Bot commented Aug 5, 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

The PR activates AUTH-12F2 manual submission-policy creation and append-only updates. It adds scoped Project Manager authorization, lineage and warning-custody checks, replay handling, atomic successor supersession, route integration, tests, E2E checks, and updated documentation.

Changes

Manual submission-policy mutation

Layer / File(s) Summary
Contracts and authorization activation
.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/*, backend/app/modules/authorization/*, backend/app/modules/projects/schemas.py, backend/app/modules/projects/service.py, docs/*
AUTH-12F2 owns active create/update actions. Resource contexts require sufficiency facts and successor metadata. Update requests require a predecessor hash and validated successor version.
Mutation service and replay flow
backend/app/modules/projects/submission_policy_mutation_service.py, backend/app/modules/projects/submission_policy_mutation_repository.py, backend/app/modules/projects/repository.py
The service validates Project Manager authority and policy lineage, canonicalizes policy content, handles replay custody, creates manual drafts, and atomically supersedes predecessors.
Route authorization and transaction wiring
backend/app/modules/projects/router.py
Routes validate UUID idempotency keys, require human actors, compose prepared authorization, invoke dedicated mutation methods, and return structured conflicts.
Authorization and lifecycle verification
backend/tests/*, backend/scripts/api_contract_e2e.py, .github/workflows/backend.yml, .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/*
Tests and contract checks cover action activation, grant scope, lineage, replay, rollback, stale CAS, concurrency, project isolation, OpenAPI metadata, and immutable agent policies.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProjectManager
  participant ProjectRouter
  participant SubmissionPolicyMutationService
  participant PreparedAuthorizationService
  participant SubmissionPolicyMutationReplayRepository
  ProjectManager->>ProjectRouter: Submit policy create or successor update
  ProjectRouter->>SubmissionPolicyMutationService: Invoke authorized mutation
  SubmissionPolicyMutationService->>PreparedAuthorizationService: Validate scoped PM grant
  SubmissionPolicyMutationService->>SubmissionPolicyMutationReplayRepository: Reserve or classify replay
  SubmissionPolicyMutationService-->>ProjectRouter: Return policy response or conflict
Loading

Possibly related PRs

Suggested reviewers: abiorh-claw

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description explains the scope and local evidence, but it omits many required trust-bundle sections and contains inconsistent statements about hosted validation. Add the missing template sections, including design, alternatives, scope control, test delta, reviewer results, CI integrity, risks, and follow-up; resolve the hosted-validation status inconsistency.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the workstream chunk and the primary change: governing manual submission policy drafts.
✨ 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-12f2-manual-submission-policy

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.

@abiorh-claw
abiorh-claw self-requested a review August 6, 2026 05:03
abiorh-claw
abiorh-claw previously approved these changes Aug 6, 2026

@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

🧹 Nitpick comments (3)
backend/app/modules/projects/submission_policy_mutation_service.py (2)

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

Narrow the policy_body annotation to dict.

_manual_mutation declares policy_body: dict | None, but line 428 passes the value straight into canonical_manual_submission_policy_body, which calls policy_body.get(...). A None value raises AttributeError instead of a domain error. Both callers already resolve a non-null body: create_manual uses payload.policy_body.model_dump(...), and update_manual falls back to predecessor.policy_body. Tighten the annotation so the type checker enforces the real contract.

♻️ Proposed annotation fix
-        policy_body: dict | None,
+        policy_body: dict,

Also applies to: 428-430

🤖 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/submission_policy_mutation_service.py` at line
405, Change the policy_body parameter annotation in _manual_mutation from dict |
None to dict, preserving the existing callers create_manual and update_manual
that provide non-null bodies and the canonical_manual_submission_policy_body
call.

621-623: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Validate the stored resource context directly instead of re-serializing it.

replay.resource_context_json is already a dict loaded from the JSON column. Line 622 serializes it to a string only so model_validate_json can parse it back. Use model_validate on the dict and remove the round trip. This also removes the only use of the json import in this module.

Note on the static analysis hint for this line: the use-jsonify rule is a false positive here. This value is never written to an HTTP response, so no output-encoding boundary applies.

♻️ Proposed fix
-        resource = ProjectSubmissionArtifactPolicyMutationResourceContext.model_validate_json(
-            json.dumps(replay.resource_context_json)
-        )
+        resource = ProjectSubmissionArtifactPolicyMutationResourceContext.model_validate(
+            replay.resource_context_json
+        )

Then remove the now-unused import json at line 5.

🤖 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/submission_policy_mutation_service.py` around
lines 621 - 623, Update the resource validation in the replay handling flow to
call ProjectSubmissionArtifactPolicyMutationResourceContext.model_validate
directly with replay.resource_context_json instead of serializing and parsing
it. Remove the now-unused json import from the module.

Source: Linters/SAST tools

backend/tests/test_authorization.py (1)

3835-3854: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The wrong-project test does not model a wrong-project grant.

_GuideMutationAuthorityFacts receives grant=None at Line 3844. That is the same fixture used by test_submission_artifact_policy_create_update_missing_pm_grant_denies_bounded at Line 3810 through Line 3814. The comment at Line 3841 states that the repository query filters the unrelated grant, but no unrelated grant is ever supplied. The test therefore asserts the missing-grant path again and cannot detect a regression that makes the scope filter accept a grant scoped to another project.

Supply a grant whose scope_project_id is a different project, so the scope filter is the control under test.

Note: backend/scripts/api_contract_e2e.py does exercise a real wrong-project Project Manager grant at Line 1073 through Line 1082, so the property is covered end to end. This comment addresses the unit-level guard only.

♻️ Proposed fix to model a grant scoped to another project
     project_id = uuid4()
-    # The repository's exact-scope query filters the unrelated grant and returns none.
+    # The repository's exact-scope query must filter this other-project grant.
     facts = _GuideMutationAuthorityFacts(
         context,
-        grant=None,
+        grant=SimpleNamespace(
+            id=uuid4(),
+            status="active",
+            scope_project_id=uuid4(),
+        ),
         permission_id=PermissionId.PROJECT_EFFECTIVE_POLICY_MANAGE,
     )

Confirm that _GuideMutationAuthorityFacts filters by the requested scope before returning the grant. If it returns the grant unconditionally, assert the denial comes from the scope check rather than from an absent grant.

🤖 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 3835 - 3854, Update
test_submission_artifact_policy_create_update_wrong_project_grant_denies to
provide a non-None grant through _GuideMutationAuthorityFacts, using a
scope_project_id different from the requested project_id. Verify
_GuideMutationAuthorityFacts applies the requested scope filter before returning
grants; if it does not, fix that filtering so the test exercises denial of an
unrelated-project grant rather than the missing-grant path.
🤖 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/submission_policy_mutation_service.py`:
- Around line 739-747: Add a private _operation_identity helper that accepts
action, resolved actor, project_id, predecessor_id, and key, builds the shared
stable parts using predecessor_id or the "create" sentinel, and returns both
operation and policy UUIDs. Replace the duplicated derivation logic in
create_manual, update_manual, _manual_mutation, and _existing_manual_replay with
this helper; pass selected_policy_id as predecessor_id only for artifact-policy
updates in _existing_manual_replay, otherwise preserving the current sentinel
behavior.
- Around line 823-825: Update update_manual to validate that predecessor belongs
to the current project/owner immediately after get_submission_artifact_policy
and before reading source_snapshot_id or calling _manual_mutation; treat an
owner/status mismatch the same as predecessor being None by raising
SubmissionArtifactPolicyNotFound, so absent and foreign policy IDs both return
404.

In `@backend/tests/test_api_controls.py`:
- Around line 551-556: Update the route inventory assertions in the test module
to include the new submission-artifact-policy POST and PATCH routes and their
corresponding permissions in route_inventory and protected_inventory.
Recalculate and update both inventory counts and SHA256 digests so the
assertions match the expanded route definitions.

In `@backend/tests/test_projects.py`:
- Around line 12259-12271: Before setting guide_row.status to "active" in the
guide setup, populate the guide’s selected review and revision policy fields by
calling the existing policy mutation endpoints or reusing a fixture that
performs this setup. Ensure the guide activation remains valid under
active_policy_selection_required while preserving the existing
submission-artifact policy patch assertions.

In `@docs/operations_project_operating_manual.md`:
- Around line 41-44: The submission artifact policy sentence in the checklist
must distinguish the current rollout from the later lifecycle: state that
derivation and approval are planned/later actions while 12F2 currently activates
only manual Project Manager create/update, or otherwise mark those actions as
planned. Preserve the existing governed update behavior that appends a successor
without editing agent output or an existing draft in place.

---

Nitpick comments:
In `@backend/app/modules/projects/submission_policy_mutation_service.py`:
- Line 405: Change the policy_body parameter annotation in _manual_mutation from
dict | None to dict, preserving the existing callers create_manual and
update_manual that provide non-null bodies and the
canonical_manual_submission_policy_body call.
- Around line 621-623: Update the resource validation in the replay handling
flow to call
ProjectSubmissionArtifactPolicyMutationResourceContext.model_validate directly
with replay.resource_context_json instead of serializing and parsing it. Remove
the now-unused json import from the module.

In `@backend/tests/test_authorization.py`:
- Around line 3835-3854: Update
test_submission_artifact_policy_create_update_wrong_project_grant_denies to
provide a non-None grant through _GuideMutationAuthorityFacts, using a
scope_project_id different from the requested project_id. Verify
_GuideMutationAuthorityFacts applies the requested scope filter before returning
grants; if it does not, fix that filtering so the test exercises denial of an
unrelated-project grant rather than the missing-grant path.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3a9e6ba0-b344-4d44-8d2c-75ea821fb5bf

📥 Commits

Reviewing files that changed from the base of the PR and between 8f516e6 and 0c51b0c.

📒 Files selected for processing (23)
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DECISIONS.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-12F2-submission-policy-manual-drafts.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12F2-internal-review-evidence.md
  • .agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-12F2-pr-trust-bundle.md
  • backend/app/modules/authorization/catalogue.py
  • backend/app/modules/authorization/prepared.py
  • backend/app/modules/authorization/runtime.py
  • backend/app/modules/projects/router.py
  • backend/app/modules/projects/schemas.py
  • backend/app/modules/projects/service.py
  • backend/app/modules/projects/submission_policy_mutation_repository.py
  • backend/app/modules/projects/submission_policy_mutation_service.py
  • backend/scripts/api_contract_e2e.py
  • backend/tests/test_alembic.py
  • backend/tests/test_api_controls.py
  • backend/tests/test_audit.py
  • backend/tests/test_authorization.py
  • backend/tests/test_projects.py
  • docs/operations_authorization_service.md
  • docs/operations_project_operating_manual.md
  • docs/spec_authorization_service.md

Comment thread backend/app/modules/projects/submission_policy_mutation_service.py Outdated
Comment thread backend/app/modules/projects/submission_policy_mutation_service.py
Comment thread backend/tests/test_api_controls.py
Comment thread backend/tests/test_projects.py Outdated
Comment thread docs/operations_project_operating_manual.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@backend/tests/verified_guide_fixtures.py`:
- Around line 283-305: Update the verified report fixture flow around
existing_usages to load the authoritative existing verified report for
source_snapshot_id and return its ID when present, rather than creating another
report. Only call create_verified_material_fixture and create a new verified
report when no authoritative report exists, while preserving the existing
usage-copy behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b1cdd82f-018c-44e9-aa59-99a5412a42fd

📥 Commits

Reviewing files that changed from the base of the PR and between 0c51b0c and 84dcec1.

📒 Files selected for processing (4)
  • backend/app/modules/projects/submission_policy_mutation_service.py
  • backend/tests/test_projects.py
  • backend/tests/test_tasks.py
  • backend/tests/verified_guide_fixtures.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/modules/projects/submission_policy_mutation_service.py

Comment thread backend/tests/verified_guide_fixtures.py Outdated

@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

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

808-827: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Return the committed replay for overlapping update retries.

At Line 808, update_manual performs the pre-mutation replay lookup. Two identical update requests can both observe no replay. The first request supersedes the predecessor at Line 584 and commits the replay. The second request then reaches the locked lineage check, observes that the predecessor is no longer "draft" at Line 279, and raises PolicyEditBlocked before Line 539 can classify the committed replay.

A retry with the same idempotency key can therefore fail even though the operation already committed. After this locked predecessor conflict, re-run _existing_manual_replay with the same request and resource facts. Return the stored response only when the digests match. Keep PolicyEditBlocked for a different operation, and add an overlapping-request 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/app/modules/projects/submission_policy_mutation_service.py` around
lines 808 - 827, Update update_manual’s locked predecessor-conflict path to
re-run _existing_manual_replay with the same request and resource facts after
detecting the predecessor is no longer draft. Return the committed replay only
when its digests match the current request; otherwise preserve PolicyEditBlocked
for a different operation, and add a test covering overlapping retries with the
same idempotency key.
🤖 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.

Outside diff comments:
In `@backend/app/modules/projects/submission_policy_mutation_service.py`:
- Around line 808-827: Update update_manual’s locked predecessor-conflict path
to re-run _existing_manual_replay with the same request and resource facts after
detecting the predecessor is no longer draft. Return the committed replay only
when its digests match the current request; otherwise preserve PolicyEditBlocked
for a different operation, and add a test covering overlapping retries with the
same idempotency key.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 537f798d-dc55-4374-a5de-6b9caf67ee33

📥 Commits

Reviewing files that changed from the base of the PR and between 5a1cea6 and 1e733a4.

📒 Files selected for processing (6)
  • backend/app/modules/authorization/runtime.py
  • backend/app/modules/projects/repository.py
  • backend/app/modules/projects/submission_policy_mutation_service.py
  • backend/scripts/api_contract_e2e.py
  • backend/tests/test_authorization.py
  • backend/tests/test_projects.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/app/modules/authorization/runtime.py
  • backend/scripts/api_contract_e2e.py

@abiorh-claw
abiorh-claw self-requested a review August 6, 2026 13:15
@abiorh-claw
abiorh-claw merged commit 81f281b into main Aug 6, 2026
11 checks passed
@abiorh-claw
abiorh-claw deleted the codex/ws-auth-001-12f2-manual-submission-policy branch August 6, 2026 13:55
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