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 @@ -3,9 +3,10 @@
The final v0.1 ART catalogue reconciliation, PREP extension, and activation
waves are superseded prospectively by
`../WS-XINT-002-art-auth-end-to-end/`. The counts immediately below are the
trusted pre-reconciliation entry evidence; WS-XINT-002-01 replaces them with
the live 71/78/22/56 catalogue recorded in the ART custody section without
changing runtime availability.
trusted pre-reconciliation entry evidence; at its merge, WS-XINT-002-01
replaced them with the then-live 71/78/22/56 catalogue recorded in the ART
custody section without changing runtime availability. Subsequent AUTH chunks
have advanced the current catalogue to 71/96/37/59.
The pre-reconciliation baseline is trusted `main` commit
`2fb322bd2249a5fe9d3fa706dc63f033074e38ce`: 76 PermissionIds, 81 ActionIds,
22 active actions, and 59 planned actions. Older counts below are explicitly
Expand Down Expand Up @@ -62,8 +63,8 @@ remains planned and
cannot be activated by read/status proof. The historical transfer added no
migration because owner and availability are typed metadata. WS-XINT-002-01
reconciles PostgreSQL parity through migration `0036`; the live catalogue has
71 PermissionIds, 78 ActionIds, 22 active actions, and 56 planned actions, with
seven fixed-service identities and twelve matrix memberships.
71 PermissionIds, 96 ActionIds, 37 active actions, and 59 planned actions, with
eight fixed-service identities and sixteen matrix memberships.

## REV custody transfer

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ 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 | 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-12A` | Project Mutation Catalogue And PREP Foundation | L1 | Merged as PR #226 with AUTH `0041`; zero activation |
| `WS-AUTH-001-12B` | Fixed Project Setup Service Foundation | L1 | Internal review complete; hosted checks pending; identity/matrix registration only, 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 |
| `WS-AUTH-001-12D` | Draft Guide And Source Metadata Cutover | L1 | Proposed after 12C |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ Agent Gates, and external review remain before merge readiness.
| `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` | 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-12A` | Merged | `codex/ws-auth-001-12a-project-mutation-catalogue` | #226 | Exact 18-action planned catalogue, typed resource/PREP scope, and migration `0041`; merged as `64dd9c98` with zero activation. |
| `WS-AUTH-001-12B` | Internal review complete; hosted checks pending | `codex/ws-auth-001-12b-project-setup-service` | - | Fixed project-setup service identity and planned matrix only; zero activation and no actor/link seed. |
| `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. |
| `WS-AUTH-001-12D` | Proposed | - | - | Draft guide and source metadata mutation cutover. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@

## Status and prerequisite

Proposed and inactive after 12A. This child provisions planned matrix facts and
must activate no action or Celery call path.
Implementation and required internal review are complete from merged 12A at
trusted main `64dd9c98`; hosted PR checks remain pending. This child registers
planned matrix facts and activates no action or Celery call path. Its exact
migration is `0043_project_setup_service` after merged ART migration
`0042_guide_extraction`.

## Parent initiative

`WS-AUTH-001` — Workstream Authorization Service

## Goal

Provision one exact project-setup service identity and planned memberships for
Register one exact project-setup service identity and planned memberships for
`project.guide_sufficiency.run`, `project.submission_artifact_policy.derive`,
`project.post_submit_checker_policy.derive`, and `project.setup_run.update`.
Activate none of them here.
Activate none of them here. Registration makes the closed identity available
to the existing controlled service-actor provisioning route only after an
administrator supplies an exact issuer and subject; this chunk seeds no actor
profile or identity link.

## Why this chunk exists

Expand All @@ -33,21 +39,15 @@ P1

```text
backend/app/modules/actors/models.py
backend/app/modules/actors/repository.py
backend/app/modules/actors/service_identities.py
backend/app/modules/actors/service_identity_migration.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/authorization/service_actor_service.py
backend/app/modules/projects/repository.py
backend/app/modules/projects/service.py
backend/app/modules/projects/setup_queue.py
backend/alembic/versions/<then-current-next>_project_setup_service.py
backend/alembic/versions/0043_project_setup_service.py
backend/tests/test_actor_migration_tools.py
backend/tests/test_authorization.py
backend/tests/test_projects.py
backend/tests/test_alembic.py
backend/scripts/api_contract_e2e.py
backend/tests/test_auth.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 @@ -57,33 +57,100 @@ docs/operations_authorization_service.md

Human route activation, Celery cutover, any action activation, generic
setup-service authority, serialized prepared handles, ART/provider behavior,
checker execution, or review/contribution behavior.
checker execution, or review/contribution behavior. Do not change
`authorization/kernel.py`, `authorization/prepared.py`,
`authorization/runtime.py`, `authorization/service_actor_service.py`, any
project module, the setup queue, or the current Celery setup worker module.

## Acceptance criteria

- Immutable identity `workstream.project.setup` has exactly the four parent actions and
no human/admin/project grants.
- Immutable identity `workstream.project.setup` is registered as the eighth
closed service identity and has exactly the four parent actions. Static
membership grants no human/admin/project role, and migration `0043` seeds no
ActorProfile, ActorIdentityLink, admin grant, or project grant.
- All four memberships remain planned and unavailable; the existing Celery
call graph is not switched in this foundation.
- Exact setup run, project, guide, snapshot, generation, effective policy, and
pre-submit checker facts are locked/recomposed as applicable before consume.
- Wrong service/action/project/guide/snapshot/run/generation, stale output,
replay, revocation, copied handle, and transaction/session mismatch deny
before durable mutation or external continuation.
- Own-action attempts fail with `action_unavailable` before actor/resource
locks, handle issuance, or allowed evidence. Every other fixed identity fails
each of these four actions with `permission_not_granted`; the project-setup
identity likewise fails every action owned by another fixed identity.
- Live lock/recomposition, final PREP consumption, stale-output, replay,
revocation, copied-handle, session/transaction, and external-continuation
proof remains owned by 12E, 12F, 12G, and 12B2 after their exact actions
activate. This foundation proves those paths are unreachable while planned.
- The fabricated legacy setup actor remains unchanged until 12B2; this
foundation makes no Celery call-graph or runtime-principal change.
- Matrix tests prove the identity has only these four actions and all-pairs
cross-service denial. Later 12E/12F/12G own product action activation; 12B2
alone owns the final Celery call-graph cutover and setup-run writes.
- The frozen revision-0023 seven-identity migration contract is not edited.
Its operator mapping tool consumes that frozen contract rather than the live
registry. `0043` alone expands the current database constraint, round-trips
cleanly, and refuses downgrade while a project-setup ActorProfile exists.
- Specification and operations docs list the eighth identity and its exact
four planned/unavailable actions while preserving explicitly historical
seven-identity AUTH-09A wording.
- Every changed authorization/project/setup-service module remains at least 90
percent covered. Final pushed head SHA passes `Backend / test` and
`Agent Gates`.

## Verification commands

Before start, freeze the exact isolated-runner command, coverage includes,
Ruff, migration round-trip, fixed-service all-pairs denial proof, stale docs,
links, and diff commands.
```bash
cd backend
install -d -m 700 .ci
.venv/bin/python -m ruff check \
app/modules/actors/models.py \
app/modules/actors/service_identities.py \
app/modules/actors/service_identity_migration.py \
app/modules/authorization/catalogue.py \
alembic/versions/0043_project_setup_service.py \
tests/test_actor_migration_tools.py tests/test_authorization.py \
tests/test_alembic.py tests/test_auth.py tests/conftest.py
.venv/bin/python -m py_compile \
app/modules/actors/models.py \
app/modules/actors/service_identities.py \
app/modules/actors/service_identity_migration.py \
app/modules/authorization/catalogue.py \
alembic/versions/0043_project_setup_service.py \
tests/test_actor_migration_tools.py tests/test_authorization.py \
tests/test_alembic.py tests/test_auth.py tests/conftest.py
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python scripts/run_isolated_tests.py \
--metadata-json .ci/auth12b.json --lane auth12b --timeout-seconds 1200 -- \
.venv/bin/python -m pytest -p pytest_asyncio.plugin -p pytest_cov.plugin -q \
tests/test_actor_migration_tools.py tests/test_authorization.py tests/test_alembic.py \
tests/test_auth.py \
-k 'project_setup_service or controlled_service_actor_provisioning_includes_project_setup or fixed_service_action_matrix or 0043_project_setup or service_identity_migration_contract'
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python scripts/run_isolated_tests.py \
--metadata-json .ci/auth12b-migration-coverage.json \
--lane auth12b_migration_coverage --timeout-seconds 1200 -- sh -c \
'.venv/bin/coverage erase && \
.venv/bin/coverage run --include="*/alembic/versions/0043_project_setup_service.py" \
-m pytest -p pytest_asyncio.plugin -q tests/test_alembic.py \
-k "0043_project_setup" && \
.venv/bin/coverage report \
--include="*/alembic/versions/0043_project_setup_service.py" \
--show-missing --fail-under=90'
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python -m pytest \
-p pytest_asyncio.plugin -p pytest_cov.plugin -q tests/test_actor_migration_tools.py \
--cov=app.modules.actors.service_identity_migration \
--cov-report=term-missing --cov-fail-under=90
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python -m pytest \
-p pytest_asyncio.plugin -p pytest_cov.plugin -q tests/test_authorization.py \
-k 'project_setup_service or fixed_service_action_matrix' \
--cov=app.modules.actors.service_identities \
--cov=app.modules.actors.models \
--cov=app.modules.authorization.catalogue \
--cov-report=term-missing --cov-fail-under=90
Comment thread
Abiorh001 marked this conversation as resolved.
cd ..
python3 scripts/check_stale_authorization_docs.py
python3 scripts/check_stale_workstream_wording.py
python3 scripts/check_markdown_links.py
git diff --check
```

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

## Required reviewers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ invoke is active for that identity.

## Why this chunk exists

Provisioning the service early avoids invented authority, but switching the
Registering the service identity early avoids invented authority, but switching the
call graph early would duplicate or bypass sufficiency and policy provenance.

## Risk class
Expand Down
62 changes: 62 additions & 0 deletions backend/alembic/versions/0043_project_setup_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""register the fixed project-setup service identity

Revision ID: 0043_project_setup_service
Revises: 0042_guide_extraction
Create Date: 2026-07-30
"""

from __future__ import annotations

from alembic import op
import sqlalchemy as sa


revision = "0043_project_setup_service"
down_revision = "0042_guide_extraction"
branch_labels = depends_on = None

_HISTORICAL_IDENTITIES = (
"workstream.artifact.verifier",
"workstream.artifact.put_resolver",
"workstream.artifact.scheduler",
"workstream.artifact.binding",
"workstream.artifact.guide_reader",
"workstream.artifact.materializer",
"workstream.artifact.checker_output",
)
_PROJECT_SETUP_IDENTITY = "workstream.project.setup"


def _tokens(values: tuple[str, ...]) -> str:
return ",".join(f"'{value}'" for value in values)


def _replace_identity_constraint(values: tuple[str, ...]) -> None:
op.drop_constraint("kind_service_identity", "actor_profiles", type_="check")
op.create_check_constraint(
"kind_service_identity",
"actor_profiles",
"(actor_kind='human' and service_identity is null) or "
f"(actor_kind='service' and service_identity in ({_tokens(values)}))",
)


def upgrade() -> None:
"""Admit the eighth closed identity without creating a service actor."""
op.get_bind().execute(sa.text("lock table actor_profiles in access exclusive mode"))
_replace_identity_constraint((*_HISTORICAL_IDENTITIES, _PROJECT_SETUP_IDENTITY))


def downgrade() -> None:
"""Restore the seven-identity constraint only when the new identity is unused."""
bind = op.get_bind()
bind.execute(sa.text("lock table actor_profiles in access exclusive mode"))
in_use = bind.execute(
sa.text(
"select exists(select 1 from actor_profiles where service_identity=:identity)"
),
{"identity": _PROJECT_SETUP_IDENTITY},
).scalar_one()
if in_use:
raise RuntimeError("cannot downgrade project setup service identity")
_replace_identity_constraint(_HISTORICAL_IDENTITIES)
1 change: 1 addition & 0 deletions backend/app/modules/actors/service_identities.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class ServiceIdentity(StrEnum):
ARTIFACT_GUIDE_READER = "workstream.artifact.guide_reader"
ARTIFACT_MATERIALIZER = "workstream.artifact.materializer"
ARTIFACT_CHECKER_OUTPUT = "workstream.artifact.checker_output"
PROJECT_SETUP = "workstream.project.setup"


SERVICE_IDENTITIES = frozenset(ServiceIdentity)
Expand Down
2 changes: 1 addition & 1 deletion backend/app/modules/actors/service_identity_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine

from app.modules.actors.legacy_classification import database_binding_identifier
from app.modules.actors.service_identities import SERVICE_IDENTITIES, ServiceIdentity
from migration_contracts.service_identity_0023 import SERVICE_IDENTITIES, ServiceIdentity

MAPPING_FILE_ENV = "WORKSTREAM_SERVICE_ACTOR_IDENTITY_MAPPING_FILE"
MAX_MAPPING_FILE_BYTES = 64 * 1024
Expand Down
32 changes: 32 additions & 0 deletions backend/app/modules/authorization/catalogue.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,14 @@ def _index_actions(
}
),
ServiceIdentity.ARTIFACT_CHECKER_OUTPUT: frozenset({ActionId.ARTIFACT_CHECKER_OUTPUT_WRITE}),
ServiceIdentity.PROJECT_SETUP: frozenset(
{
ActionId.PROJECT_GUIDE_SUFFICIENCY_RUN,
ActionId.PROJECT_SUBMISSION_ARTIFACT_POLICY_DERIVE,
ActionId.PROJECT_POST_SUBMIT_CHECKER_POLICY_DERIVE,
ActionId.PROJECT_SETUP_RUN_UPDATE,
}
),
}


Expand Down Expand Up @@ -879,6 +887,14 @@ def _index_service_actions(
ServiceIdentity.ARTIFACT_CHECKER_OUTPUT: frozenset(
{ActionId.ARTIFACT_CHECKER_OUTPUT_WRITE}
),
ServiceIdentity.PROJECT_SETUP: frozenset(
{
ActionId.PROJECT_GUIDE_SUFFICIENCY_RUN,
ActionId.PROJECT_SUBMISSION_ARTIFACT_POLICY_DERIVE,
ActionId.PROJECT_POST_SUBMIT_CHECKER_POLICY_DERIVE,
ActionId.PROJECT_SETUP_RUN_UPDATE,
}
),
}
expected_metadata = {
ActionId.ARTIFACT_VERIFICATION_EXECUTE: (
Expand Down Expand Up @@ -929,6 +945,22 @@ def _index_service_actions(
PermissionId.ARTIFACT_BINDING_CREATE,
ActionOwner.XINT_002_07,
),
ActionId.PROJECT_GUIDE_SUFFICIENCY_RUN: (
PermissionId.PROJECT_GUIDE_MANAGE,
ActionOwner.AUTH_12E,
),
ActionId.PROJECT_SUBMISSION_ARTIFACT_POLICY_DERIVE: (
PermissionId.PROJECT_EFFECTIVE_POLICY_MANAGE,
ActionOwner.AUTH_12F,
),
ActionId.PROJECT_POST_SUBMIT_CHECKER_POLICY_DERIVE: (
PermissionId.PROJECT_EFFECTIVE_POLICY_MANAGE,
ActionOwner.AUTH_12G,
),
ActionId.PROJECT_SETUP_RUN_UPDATE: (
PermissionId.PROJECT_GUIDE_MANAGE,
ActionOwner.AUTH_12B2,
),
}
if set(rows) != SERVICE_IDENTITIES:
raise RuntimeError("service action matrix identity mismatch")
Expand Down
2 changes: 1 addition & 1 deletion backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from scripts.run_isolated_tests import LOOPBACK, NAME_RE, ROLE_RE

DDL_LOCK_DIRECTORY = Path("/tmp")
EXPECTED_PUBLIC_SCHEMA_SHA256 = "649ad4f05aa677cc72a8f4cffe04291ed8f41fd6f4be0f1bc4aac6809ca96491"
EXPECTED_PUBLIC_SCHEMA_SHA256 = "5654f83859a318a6f8205215d72934a9b115a155f15d407d54f4200c394b8e3e"
PROTECTED_TEST_TABLES = (
"actor_profile_migration_state",
"alembic_version",
Expand Down
7 changes: 5 additions & 2 deletions backend/tests/test_actor_migration_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

from app.modules.actors import service_identity_migration as identity_migration
from app.modules.actors.legacy_classification import database_binding_identifier
from app.modules.actors.service_identities import SERVICE_IDENTITY_VALUES, ServiceIdentity
from app.modules.actors.service_identity_migration import (
MAPPING_FILE_ENV,
MAX_MAPPINGS,
Expand All @@ -36,6 +35,10 @@
validate_mapping_path,
verify_envelope,
)
from migration_contracts.service_identity_0023 import (
SERVICE_IDENTITY_VALUES,
ServiceIdentity,
)
from scripts import service_actor_identity_mapping as mapping_cli

ISSUER = "https://identity.example.test"
Expand Down Expand Up @@ -99,7 +102,7 @@ def write_private_json(path: Path, value: object) -> None:
os.chmod(path, 0o600)


def test_fixed_service_identity_registry_is_exact() -> None:
def test_service_identity_migration_contract_registry_is_exact() -> None:
assert SERVICE_IDENTITY_VALUES == (
"workstream.artifact.verifier",
"workstream.artifact.put_resolver",
Expand Down
Loading
Loading