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 @@ -2,7 +2,9 @@

## Status and prerequisite

Proposed and inactive after 12B.
Ready for pre-implementation review from merged main `57c67116`, after 12B.
The exact migration is `0044_project_create_authority` after merged migration
`0043_project_setup_service`.

## Parent initiative

Expand Down Expand Up @@ -30,18 +32,39 @@ P1

```text
backend/app/modules/projects/models.py
backend/app/modules/projects/create_repository.py
backend/app/modules/projects/create_router.py
backend/app/modules/projects/create_service.py
backend/app/modules/projects/repository.py
backend/app/modules/projects/router.py
backend/app/modules/projects/schemas.py
backend/app/modules/projects/service.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/audit/schemas.py
backend/app/api/deps/authorization.py
backend/alembic/versions/<then-current-next>_project_create_authority.py
backend/app/api/router.py
backend/app/db/errors.py
backend/app/modules/authorization/project_role_service.py
backend/alembic/versions/0044_project_create_authority.py
.github/workflows/backend.yml
backend/tests/test_authorization.py
backend/tests/test_audit.py
backend/tests/test_projects.py
backend/tests/project_create_fixtures.py
backend/tests/test_artifact_admission.py
backend/tests/test_artifact_internal_authorization.py
backend/tests/test_artifact_recovery.py
backend/tests/test_guide_bindings.py
backend/tests/test_auth.py
backend/tests/test_alembic.py
backend/tests/test_api_controls.py
backend/tests/test_checkers.py
backend/tests/conftest.py
backend/tests/test_tasks.py
backend/tests/test_outbox.py
backend/scripts/api_contract_e2e.py
docs/spec_authorization_service.md
docs/operations_authorization_service.md
Expand All @@ -58,24 +81,137 @@ project-scoped create authority, or token-role fallback.
- Only an active human with an active identity link and effective system-scoped
Project Manager `project.create` grant may create a project.
- Project-scoped Project Manager, all contributor grants, and every other admin
role deny.
role deny. Every service actor, including `workstream.project.setup`, denies.
- PREP binds actor/link, system grant, request digest, idempotency key, root
transaction, and server-created project identity; evidence and project commit
atomically once.
transaction, a distinct server-created operation identity, and server-created
project identity; evidence, idempotency result, and project commit atomically
once.
- The route generates or recovers the distinct stable operation and project
UUIDs before PREP.
Its canonical request value binds the exact action, route identity, validated
body, actor profile, identity link, UUID idempotency key, operation UUID,
project UUID, and operation generation before `prepare`; final consumption
must reproduce those exact facts.
- `project.create` receives one narrow project-mutation branch in PREP. It must
not be added to `_ADMIN_MUTATIONS`, the authority-mutation service, or any
generic mutation branch. That branch locks and revalidates the active human,
active link, and exact effective system-scoped Project Manager grant through
final consumption; every other planned project mutation remains unavailable.
- Replay, copied/wrong handle, revoked authority, wrong session/transaction,
and duplicate idempotency produce at most one project.
- The route declares exactly one active ActionId and contains no legacy coarse
role-claim authorization helper.
- New project rows record local actor profile, identity link, matched grant,
system scope, action, and decision-event reference; historical rows remain
readable and are not rewritten.
- Every changed authorization/project module remains at least 90 percent
covered. Final pushed head SHA passes `Backend / test` and `Agent Gates`.
- Project-owned idempotency records have a unique actor/action/key namespace,
bind the canonical request digest and both server-owned identities, and return
the original response only for an exact committed replay. A key reused with
different request facts denies without creating a second project. This does
not widen or modify `AuthorityIdempotencyRecord`, `AuthorityMutationService`,
or the closed authority-operation types; persistence stays in the projects
module and migration.
- The project persistence surface is `ProjectCreateIdempotencyRecord` in the
projects model/repository. It stores actor profile, identity link, action,
UUID key, canonical client-request digest, server operation UUID, server
project UUID, generation, and `pending|committed` state. Reservation inserts
or locks this row first; an exact existing row supplies the stable server IDs
used by PREP, mismatch conflicts, and only a committed row may replay the
original Project response. Completion changes `pending` to `committed` in the
same root transaction as project/evidence insertion; rollback removes the
reservation and no pending record may survive a failed request.
- New `Project` provenance columns are nullable only for historical rows:
`created_by_actor_profile_id`, `created_via_identity_link_id`,
`created_by_admin_role_grant_id`, `creation_scope_type`,
`creation_action_id`, and `authorization_decision_event_id`. Foreign keys and
a complete-or-empty check require new authorized rows to carry the exact
actor/link/grant, literal `system` scope, literal `project.create` action, and
allowed decision event. These internal fields are not added to public project
responses.
- Missing or malformed `Idempotency-Key` denies before mutation. Exact committed
replay returns the original 201 response; mismatched reuse returns the
canonical `idempotency_mismatch` conflict. Concurrent exact replay creates one
project, while concurrent mismatched reuse, copied keys across actors/links,
different keys racing on one slug, and failures after reservation leave no
duplicate project, false allowed evidence, or stuck pending record.
- Exact committed replay is response recovery, not a second authorization or
mutation. It validates the actor/action/key/request-digest namespace and the
database-enforced committed custody chain, then returns the original response
without PREP, new allowed evidence, or dependence on authority revoked after
the original commit. Revocation still denies every new key or changed request.
- Allowed decision evidence includes the canonical project-create resource
digest and the operation/project identifiers. Database tests tie that event,
the project provenance, and the idempotency row to the same facts.
- Migration `0044` preserves historical project rows through nullable
provenance, enforces actor/link/grant/action/system-scope/decision references
for newly authorized rows, enforces the replay namespace and digest/identity
shape, and refuses downgrade once 12C provenance or replay data exists.
- OpenAPI declares exactly `project.create` for `POST /projects`; static/API
tests prove the route and service contain no token-role or legacy coarse-role
helper fallback.
- A successful call creates only the draft project shell and its authorization
provenance/replay evidence. It creates no guide, source, setup run, task,
submission, checker, review, revision, contribution, compensation award or
fulfillment, reputation signal, policy, or activation state; denial/replay
tests assert those downstream tables remain untouched.
- The ordered mutation is: validate body/header and resolve the local actor;
reserve or lock project-owned idempotency and obtain stable operation/project
IDs; prepare the exact system authority; consume it against final server
facts; insert project with provenance; mark replay state committed; commit
once in the route-owned root transaction. `ProjectCreateService.create`
must neither authorize from token roles nor commit independently.
- Every new or materially changed AUTH-12C authorization/project boundary
remains at least 90 percent covered. Legacy aggregation modules with
removal-only diffs remain protected by the global baseline and their existing
subsystem gates. Final pushed head SHA passes `Backend / test` and `Agent Gates`.

## Verification commands

Before start, freeze exact isolated-runner, migration round-trip, coverage,
Ruff, API drill, stale-doc, link, and diff commands.
```bash
cd backend
install -d -m 700 .ci
: "${WORKSTREAM_TEST_ADMIN_DATABASE_URL:?set a local Postgres admin URL}"
.venv/bin/python -m ruff check \
app/modules/projects/models.py app/modules/projects/repository.py \
app/modules/projects/router.py app/modules/projects/schemas.py \
app/modules/projects/service.py app/modules/authorization/catalogue.py \
app/modules/authorization/kernel.py app/modules/authorization/prepared.py \
app/modules/authorization/runtime.py app/api/deps/authorization.py \
app/modules/audit/schemas.py \
alembic/versions/0044_project_create_authority.py \
tests/test_authorization.py tests/test_projects.py tests/test_alembic.py \
tests/test_api_controls.py tests/conftest.py scripts/api_contract_e2e.py
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python scripts/run_isolated_tests.py \
--metadata-json .ci/auth12c.json --lane auth12c --timeout-seconds 1200 -- \
.venv/bin/python -m pytest -p pytest_asyncio.plugin -p pytest_cov.plugin -q \
tests/test_authorization.py tests/test_projects.py tests/test_alembic.py \
tests/test_api_controls.py -k 'project_create or 0044_project_create'
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python scripts/run_isolated_tests.py \
--metadata-json .ci/auth12c-coverage.json --lane auth12c_coverage \
--timeout-seconds 1200 -- sh -c \
'.venv/bin/coverage erase && .venv/bin/coverage run \
--source=app.modules.projects,app.modules.authorization,app.modules.audit \
-m pytest -p pytest_asyncio.plugin -q tests/test_authorization.py \
tests/test_projects.py -k "project_create" && .venv/bin/coverage report \
--include="*/app/modules/projects/*,*/app/modules/authorization/*,*/app/modules/audit/*" \
--show-missing --fail-under=90'
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python scripts/run_isolated_tests.py \
--metadata-json .ci/auth12c-api.json --lane auth12c_api \
--timeout-seconds 1500 -- .venv/bin/python scripts/api_contract_e2e.py
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.
The trust bundle must also show each new or materially changed AUTH-12C backend
boundary at or above 90 percent; aggregate package coverage cannot conceal a
new boundary below the threshold. Hosted Backend adds a per-file 90-percent
gate for the focused project-create modules using the combined full-suite coverage artifact; the
Ruff, test, or coverage gate may not be weakened.

## Required reviewers

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# WS-AUTH-001-12C external review response

## Comments addressed

- Made migration constraint naming convention-safe and every custody comparison
NULL-safe; also require a bounded SHA-256 resource-context digest.
- Made concurrent same-key reservation acquisition block on and return the
winning row through a guarded no-op upsert instead of racing an invisible row.
- Added portable asyncpg/direct/psycopg constraint-name extraction for slug
conflicts.
- Corrected fixture authority side effects, count-sensitive assertions, UUID
validation, test ownership, indentation, pending-row coverage, and missing
constraint inspection.
- Removed dead test branches, deduplicated project-create binding comparison,
and included audit schemas in the chunk verification commands.

## Comments deferred

- Returning a session-managed ORM object from a pre-0044 schema was not adopted:
the current ORM mapping selects columns absent from that historical schema.
The helper now returns no object; every caller uses it only for persistence.
- Removing the existing-grant lookup was not adopted because the latest shared
helper is also used for admitted API actors, where grant reuse is reachable.
- An evidence opt-out was not adopted because an attributed project without its
allowed authorization event would violate the 0044 custody contract. Tests
now compare scoped baselines or correct authority totals instead.

## Human decisions needed

None.

## Commands rerun

- Focused Ruff and `git diff --check`: passed.
- AUTH-12C PostgreSQL lane: 12 passed.
- Migration downgrade plus concurrent replay/slug rollback: 3 passed.
- Count-sensitive auth/audit checks: 3 passed; the initially failing artifact
baseline check was corrected and then passed with task/checker regressions.
- Task/checker/artifact fixture regressions: 3 passed.
- Three project-lifecycle regressions exposed by the hosted lane were corrected
by revoking fixture-only system Project Manager authority before exercising
narrower grant and revocation semantics: 3 passed.
- The 0044 unattributed-project proof now observes the intentionally deferred
custody constraint at transaction commit instead of only at statement
execution: 1 passed.
- The next hosted run exposed shared fixtures manufacturing new project-create
evidence for projects that semantically predate 0044. Shared fixtures now
seed explicit historical projects without queued custody events; the one 0044
downgrade-custody test retains the fully attributed fixture. Seven artifact
authority regressions and four historical downgrade regressions passed.
- Updated three stale AUTH test doubles to the resource-context-aware decision
staging signature and proved prepared-dependency denial evidence persists
through its owned rollback/restage/commit path: 3 passed.
- The following hosted run reduced shared-foundation failures to one auth
lifecycle test whose two exact grant totals still included the removed
fixture-only grants. The totals now assert the three real grants at that
lifecycle point, and the complete affected test passes.
- The next hosted run passed project and task lanes and exposed the remaining
direct unattributed project insert in the shared outbox fixture. It now uses
the same explicit historical-project helper; all 72 outbox tests pass.
- The subsequent hosted run passed shared, project, and task lanes. Its three
schema-contract failures were the last direct project inserts inside
migration helpers. A caller-transaction variant of the historical helper now
preserves normal setup transactions, while the outbox race commits historical
setup before opening its writer transaction; all three exact schema tests pass.
- The exact-head rerun passed all four semantic lanes, Agent Gates, and
CodeRabbit. Its per-file cutover gate then reported 87.41 percent for the
prepared dependency composition root. The existing lifecycle test now proves
rollback of a forgotten successful transaction and fail-closed denial-evidence
persistence, covering the four lines required to reach at least 90 percent.
- The next exact-tree retry passed lane custody and measured the composition root
at 90.37 percent. It then exposed that the gate also covered the broad legacy
project repository at 62.46 percent. Project-create reservation/completion now
has a dedicated repository boundary, restoring the legacy repository to its
prior scope; focused branch tests measure the new module at 96.43 percent.
- The following hosted run passed the dedicated repository at 96.43 percent and
exposed the same whole-file mismatch for the broad legacy project router at
58.96 percent. The route and orchestration now join persistence in focused
project-create modules; legacy router/service diffs only remove their obsolete
token-role create path. Sixteen focused PostgreSQL integration tests pass after
the split. Twenty-two focused unit tests also pass, with exact boundary
coverage of 100 percent for the shared database-error helper, 96.43 percent
for the repository, 100 percent for the router, and 94.12 percent for the
service.
- A final hosted Backend rerun is pending this complete boundary correction;
CodeRabbit must also review that exact head.
- Exact-head CodeRabbit review then identified one valid deduplication nit: the
supported-driver constraint-name lookup existed in both project creation and
project-role mutation. A shared database-error helper now owns that lookup;
both callers retain their existing constraint allowlists and re-raise behavior.

## Remaining risks

Hosted full-suite execution and exact per-file coverage remain authoritative.
21 changes: 21 additions & 0 deletions .github/workflows/backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,27 @@ jobs:
working-directory: backend
run: coverage report --include='app/modules/authorization/*' --precision=2 --fail-under=90

- name: Project creation cutover per-file coverage
working-directory: backend
shell: bash
run: |
set -euo pipefail
for source in \
app/api/deps/authorization.py \
app/modules/audit/schemas.py \
app/modules/authorization/catalogue.py \
app/modules/authorization/kernel.py \
app/modules/authorization/prepared.py \
app/modules/authorization/runtime.py \
app/db/errors.py \
app/modules/projects/models.py \
app/modules/projects/create_repository.py \
app/modules/projects/create_router.py \
app/modules/projects/create_service.py
do
coverage report --include="${source}" --precision=2 --fail-under=90
done

- name: Project authorization-read composer coverage
working-directory: backend
run: |
Expand Down
Loading
Loading