Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ stopped.
| `WS-AUTH-001-11C1` | Project Setup Diagnostic Read Cutover | L1 | Merged in PR #216 as `2965a9f9` |
| `WS-AUTH-001-11C2` | Effective Policy And Active Guide Read Cutover | L1 | Merged in PR #221 as `3fc323d7` |
| `WS-AUTH-001-12` | Project Mutation Cutover Planning Parent | L1 | Split before runtime implementation after failed L1 review |
| `WS-AUTH-001-12A` | Project Mutation Catalogue And PREP Foundation | L1 | Proposed after planning merge; ART-owned `0040` prerequisite satisfied |
| `WS-AUTH-001-12A` | Project Mutation Catalogue And PREP Foundation | L1 | In progress; ART-owned `0040` prerequisite satisfied, AUTH `0041` allocated |
| `WS-AUTH-001-12B` | Fixed Project Setup Service Foundation | L1 | Proposed after 12A; zero activation |
| `WS-AUTH-001-12B2` | Project Setup Service Runtime Cutover | L1 | Proposed after 12E, 12F, and 12G |
| `WS-AUTH-001-12C` | Project Creation Cutover | L1 | Proposed after 12B |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,17 @@ setup-run binding findings were repaired.

## Active implementation chunk

None. `WS-AUTH-001-11C2` merged through PR #221 as `3fc323d7` after Backend,
Agent Gates, and required internal review passed. AUTH-12 is planning-only
until its repaired split passes review and a child receives a separate start.
`WS-AUTH-001-12A`; the user started the bounded catalogue, typed resource,
PREP-scope, and PostgreSQL evidence foundation on 2026-07-29. All eighteen new
actions remain planned and unavailable; no product mutation is activated.
Initial architecture and product review found missing setup-service custody and
operation-kind bindings. Those defects were repaired, focused proof passes,
and all nine required internal reviewer tracks now pass. Hosted Backend,
Agent Gates, and external review remain before merge readiness.

## Current review branch

`codex/ws-auth-001-12-project-mutation-cutover` (planning repair).
`codex/ws-auth-001-12a-project-mutation-catalogue`.

## Chunk status

Expand Down Expand Up @@ -168,7 +172,7 @@ until its repaired split passes review and a child receives a separate start.
| `WS-AUTH-001-11C1` | Merged | `codex/ws-auth-001-11c1-setup-diagnostic-reads` | #216 | Setup-diagnostic read hard cutover merged as `2965a9f9` on 2026-07-28. |
| `WS-AUTH-001-11C2` | Merged | `codex/ws-auth-001-11c2-effective-policy-active-guide-reads` | #221 | Effective-policy and active-guide read cutover merged as `3fc323d7` on 2026-07-29. |
| `WS-AUTH-001-12` | Planning repair | `codex/ws-auth-001-12-project-mutation-cutover` | - | Combined runtime contract rejected; planning parent split into 12A-12H plus 12B2/12D2 before code. |
| `WS-AUTH-001-12A` | Proposed | - | - | Planned action/evidence/PREP foundation; ART-owned migration `0040` is merged, so only planning merge and a separate start remain before allocation. |
| `WS-AUTH-001-12A` | In progress | `codex/ws-auth-001-12a-project-mutation-catalogue` | - | Exact 18-action planned catalogue, typed resource/PREP scope, and migration `0041`; zero activation. |
| `WS-AUTH-001-12B` | Proposed | - | - | Fixed project-setup service identity and planned matrix only; zero activation. |
| `WS-AUTH-001-12B2` | Proposed | - | - | Final Celery call-graph cutover after exact product actions activate. |
| `WS-AUTH-001-12C` | Proposed | - | - | System-scoped project creation cutover. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## Status and prerequisite

Proposed and inactive. AUTH-12 planning must be merged; ART-owned `0040` is
merged on trusted main.
the exact AUTH migration revision is frozen from the resulting trusted main.
Implementation and internal review complete; externally inactive. AUTH-12 planning is merged; ART-owned `0040` is
merged on trusted main. The exact AUTH migration revision is frozen as
`0041_project_mutation_evidence` from that trusted head.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Parent initiative

Expand Down Expand Up @@ -37,10 +37,12 @@ backend/app/modules/authorization/runtime.py
backend/app/modules/authorization/kernel.py
backend/app/modules/authorization/prepared.py
backend/app/modules/audit/schemas.py
backend/alembic/versions/<next-after-merged-ART-0040>_project_mutation_action_evidence.py
backend/alembic/versions/0041_project_mutation_action_evidence.py
backend/tests/test_authorization.py
backend/tests/test_alembic.py
backend/tests/conftest.py
docs/spec_authorization_service.md
docs/operations_authorization_service.md
.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/**
```

Expand All @@ -63,17 +65,44 @@ project-table provenance columns, ART behavior, or token-role compatibility.
- PREP scope derivation is explicit for system project creation and exact
project resources. Handles remain opaque, request/session/transaction bound,
non-copyable, non-serializable, and single-use.
- The catalogue contains exactly 96 actions after this chunk: 37 active and 59
planned. All eighteen new actions remain planned, fail with
`action_unavailable` before handle issuance, and produce no allowed evidence.
- `project.create` is the only new system-scoped action. Every other new action
derives exact project scope from its final typed resource context and rejects
partial or cross-project lineage.
- The migration follows merged ART-owned `0040_guide_materialization`; `0040`
is not duplicated or edited.
- Upgrade, downgrade, re-upgrade, typed/SQL parity, and zero-active-delta tests
pass.

## Verification commands

Before start, freeze the actual revision after merged ART `0040` and the exact
isolated-runner, 90% authorization coverage, migration round-trip, Ruff, stale
authorization docs, Markdown-link, and diff commands. Final pushed head SHA
must pass `Backend / test` and `Agent Gates`.
```bash
cd backend
.venv/bin/python -m ruff check \
app/modules/authorization/catalogue.py \
app/modules/authorization/runtime.py \
app/modules/authorization/kernel.py \
app/modules/authorization/prepared.py \
app/modules/audit/schemas.py \
alembic/versions/0041_project_mutation_action_evidence.py \
tests/test_authorization.py tests/test_alembic.py tests/conftest.py
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python scripts/run_isolated_tests.py \
--metadata-json .ci/auth12a.json --lane auth12a --timeout-seconds 1200 -- \
.venv/bin/python -m pytest -p pytest_asyncio.plugin -p pytest_cov.plugin -q \
tests/test_authorization.py tests/test_alembic.py \
-k 'project_mutation or 0041_project_mutation'
cd ..
python3 scripts/check_stale_authorization_docs.py
python3 scripts/check_stale_workstream_wording.py
python3 scripts/check_markdown_links.py
git diff --check
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

Final pushed head SHA must pass `Backend / test` and `Agent Gates`; the hosted
Backend gate owns fresh full-suite coverage and isolated PostgreSQL migration
proof.

## Required reviewers

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# WS-AUTH-001-12A External Review Response

External review: CodeRabbit review on PR #226 at head `232996c3`

## Comments addressed

- Replaced the migration placeholder in the allowed-file boundary with the
exact implemented `0041_project_mutation_action_evidence.py` path. The
revision identifier remains the separately frozen
`0041_project_mutation_evidence` value.
- Added `tests/conftest.py` to the chunk's Ruff verification command so every
changed Python file is covered by the declared lint proof.
- Replaced the tautological `PermissionId` membership assertion with the exact
four pre-existing permissions used by the eighteen project-mutation actions.
- Documented the migration's intentional dependency on PostgreSQL constraint
rendering and its fail-closed drift guards.
- Made the project-mutation resource and target-kind maps immutable.
- Hoisted the static admin action-to-resource map out of the authorization hot
path and froze it.
- Made resource-to-PREP scope derivation explicitly static, removing the test's
`None` receiver workaround.
- Consolidated repeated setup-service custody checks without changing their
validation order or error wording.

## Comments deferred

None. All three actionable comments and five collapsed nitpicks were addressed.

## Human decisions needed

None.

## Commands rerun

- `git diff --check`
- Ruff over `tests/test_alembic.py` and `tests/conftest.py`
- Focused authorization tests: `2 passed, 366 deselected`
- The local isolated PostgreSQL runner refused the configured admin database
with `unsafe_admin_database`; this safety guard was not bypassed, and hosted
CI owns the isolated migration proof.
- Stale authorization wording, stale Workstream wording, and Markdown-link
checks

Hosted `Backend / test` and `Agent Gates` are pending on the corrective pushed
head and remain required before merge readiness.

## Remaining risks

The actions remain planned and externally inactive. Runtime activation and
mutation-service integration remain owned by their separately bounded child
chunks.
129 changes: 129 additions & 0 deletions backend/alembic/versions/0041_project_mutation_action_evidence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""register planned project-mutation action evidence

Revision ID: 0041_project_mutation_evidence
Revises: 0040_guide_materialization
Create Date: 2026-07-29
"""

from __future__ import annotations

from alembic import op
import sqlalchemy as sa


revision = "0041_project_mutation_evidence"
down_revision = "0040_guide_materialization"
branch_labels = depends_on = None

_ACTIONS = (
("project.create", "project.create"),
("project.guide.create", "project.guide.manage"),
("project.guide.update", "project.guide.manage"),
("project.guide_source_snapshot.create", "project.guide.manage"),
("project.review_policy.update", "project.review_policy.manage"),
("project.revision_policy.update", "project.review_policy.manage"),
("project.guide_sufficiency_report.create", "project.guide.manage"),
("project.guide_sufficiency.run", "project.guide.manage"),
("project.guide_sufficiency.warnings.acknowledge", "project.guide.manage"),
("project.submission_artifact_policy.create", "project.effective_policy.manage"),
("project.submission_artifact_policy.derive", "project.effective_policy.manage"),
("project.submission_artifact_policy.update", "project.effective_policy.manage"),
("project.submission_artifact_policy.approve", "project.effective_policy.manage"),
("project.post_submit_checker_policy.approve", "project.effective_policy.manage"),
(
"project.post_submit_checker_policy.correction.request",
"project.effective_policy.manage",
),
("project.post_submit_checker_policy.derive", "project.effective_policy.manage"),
("project.setup_run.update", "project.guide.manage"),
("project.guide.activate", "project.guide.manage"),
)


def _definition() -> str:
return (
op.get_bind()
.execute(
sa.text(
"select pg_get_constraintdef(oid) from pg_constraint "
"where conrelid='audit_events'::regclass "
"and conname='ck_audit_events_authorization_action_evidence'"
)
)
.scalar_one()
)


def _replace(definition: str) -> None:
op.drop_constraint(
"authorization_action_evidence",
"audit_events",
type_="check",
)
op.execute(
"alter table audit_events add constraint "
f"ck_audit_events_authorization_action_evidence {definition}"
)


def _pair_token(action: str, permission: str) -> str:
# Keep this byte-for-byte aligned with PostgreSQL's pg_get_constraintdef
# rendering; _rewrite's marker-count guards fail closed if that format drifts.
return (
f"(((action_id)::text = '{action}'::text) AND "
f"((permission_id)::text = '{permission}'::text))"
)


def _rewrite(*, add: bool) -> None:
definition = _definition()
additions = " OR " + " OR ".join(_pair_token(*pair) for pair in _ACTIONS)
marker = _pair_token("project.active_guide.read", "project.read")
if add:
if definition.count(marker) != 2 or any(
_pair_token(*pair) in definition for pair in _ACTIONS
):
raise RuntimeError("unexpected project-mutation action registry definition")
definition = definition.replace(marker, marker + additions)
else:
if definition.count(additions) != 2:
raise RuntimeError("unexpected project-mutation action registry definition")
definition = definition.replace(additions, "")
_replace(definition)


def _lock_evidence() -> None:
bind = op.get_bind()
bind.execute(sa.text("lock table authority_idempotency_records in share row exclusive mode"))
bind.execute(sa.text("lock table audit_events in access exclusive mode"))


def _has_evidence() -> bool:
actions = [action for action, _ in _ACTIONS]
return bool(
op.get_bind()
.execute(
sa.text(
"select exists(select 1 from audit_events where action_id = any(:actions)) or "
"exists(select 1 from authority_idempotency_records record "
"join audit_events event on event.idempotency_reference=record.id "
"where event.action_id = any(:actions))"
),
{"actions": actions},
)
.scalar_one()
)


def upgrade() -> None:
"""Register the eighteen project-mutation pairs without activating them."""
_lock_evidence()
_rewrite(add=True)


def downgrade() -> None:
"""Remove project-mutation pairs only when no forward evidence exists."""
_lock_evidence()
if _has_evidence():
raise RuntimeError("cannot downgrade non-empty project-mutation action evidence")
_rewrite(add=False)
Loading
Loading