diff --git a/.agent-loop/LOOP_STATE.md b/.agent-loop/LOOP_STATE.md index 257e7ad7e..97cb8d1b5 100644 --- a/.agent-loop/LOOP_STATE.md +++ b/.agent-loop/LOOP_STATE.md @@ -2,17 +2,20 @@ ## Current State -- Active initiative: `WS-POL-002` - Post-Submit Checker Foundation -- Active planning chunk: none +- Active initiative: `WS-AUTH-001` - Workstream Authorization Service +- Active planning chunk: `WS-AUTH-001-PLAN` - Active implementation chunk: none -- Branch: `main` -- Status: `WS-POL-002-02` merged through PR #88 as `32af6a7` on 2026-07-11 - after internal review, current-head GitHub checks, CodeRabbit, and explicit - human merge approval. +- Branch: `authorization-service` +- Status: WS-AUTH-001 planning is internally reviewed and stopped at the L0 + human checkpoint. D1-D3 are approved; D4-D10 require explicit approval. - Last merged implementation SHA: `67fb3ca` - Last merge commit: `32af6a7` -- Current gate: none; awaiting explicit user start for the next chunk. -- Next chunk: `WS-POL-002-03` is inactive until the user explicitly starts it. +- Current gate: explicit durable human approval of D4-D10. Planning evidence is + recorded at the WS-AUTH-001 internal review evidence path. +- Next chunk: `WS-AUTH-001-01` remains proposed until D4-D10 approval and a + separate implementation start signal. +- Paused initiative: `WS-POL-002`; chunk `WS-POL-002-03` must not start before + the relevant authorization foundation and an explicit resume signal. ## Operating Rule diff --git a/.agent-loop/REVIEW_LOG.md b/.agent-loop/REVIEW_LOG.md index e5b970036..5a31df738 100644 --- a/.agent-loop/REVIEW_LOG.md +++ b/.agent-loop/REVIEW_LOG.md @@ -580,3 +580,32 @@ Trust bundle: `.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation Next gate: `WS-POL-002-01` Post-Submit Provenance And Compiler Contract. No implementation chunk is active until the user explicitly starts it. + +## 2026-07-11 - WS-AUTH-001-PLAN Internal Review Passed + +Required reviewer tracks: + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- docs +- CI integrity +- reuse/dedup +- test delta + +Result: PASS after plan repairs. The reviewed planning tree preserves the +external Flow authentication boundary, replaces token roles only through a +staged local-grant cutover, keeps `/api/v1`, protects intermediate-release +operability, and leaves all runtime implementation inactive. + +Scope: imported and hash-bound eight Workstream reference files; added the +WS-AUTH-001 intent, discovery, decisions, risks, plan, source manifest, status, +16 implementation chunk contracts, and durable WS-POL-002 pause. D1-D3 are +human-approved. D4-D10 remain at the L0 human approval checkpoint. + +Evidence: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-PLAN-internal-review-evidence.md` + +Next gate: stop. Do not activate `WS-AUTH-001-01` without explicit human +approval of D4-D10 and a separate implementation start signal. diff --git a/.agent-loop/WORK_QUEUE.md b/.agent-loop/WORK_QUEUE.md index 635b72149..aa7ab1eec 100644 --- a/.agent-loop/WORK_QUEUE.md +++ b/.agent-loop/WORK_QUEUE.md @@ -1,10 +1,17 @@ # Work Queue +## In Progress + +| Chunk | Title | Risk | Status | +|---|---|---:|---| +| `WS-AUTH-001-PLAN` | Authorization Service Planning | L0 | Internal review passed; explicit D4-D10 human approval pending | + ## Planned Next | Chunk | Title | Risk | Status | |---|---|---:|---| -| `WS-POL-002-03` | Server-Owned Policy Approval And Visibility APIs | L1 | Inactive until explicit user start | +| `WS-AUTH-001-01` | Adopt Authorization Baseline And Repository Contracts | L1 | Proposed after D4-D10 approval and explicit start | +| `WS-POL-002-03` | Server-Owned Policy Approval And Visibility APIs | L1 | Paused behind WS-AUTH-001 and explicit resume | ## Completed @@ -35,7 +42,9 @@ ## Proposed Next -Do not start `WS-POL-002-03` until the user gives an explicit start signal. +Stop at the WS-AUTH-001 planning human checkpoint. Do not activate +`WS-AUTH-001-01` before explicit D4-D10 approval and a separate start signal; +do not resume `WS-POL-002-03` while auth has priority. ## Blocked diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md new file mode 100644 index 000000000..4014e7cf8 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md @@ -0,0 +1,75 @@ +# Chunk Map: WS-AUTH-001 - Workstream Authorization Service + +## Rule + +Only one chunk may be active at a time. Do not start the next chunk until the +current chunk is implemented, verified, internally reviewed, externally +reviewed, merged by explicit human approval, followed by a memory update, and +stopped. + +## Chunks + +| Chunk | Title | Risk | Status | +|---|---|---:|---| +| `WS-AUTH-001-PLAN` | Authorization Service Planning | L0 | Internally reviewed; awaiting human approval | +| `WS-AUTH-001-01` | Adopt Authorization Baseline And Repository Contracts | L1 | Proposed | +| `WS-AUTH-001-02` | Verified Issuer Token And JWKS Boundary | L1 | Proposed | +| `WS-AUTH-001-03` | Legacy Actor Classification Preflight | L1 | Proposed | +| `WS-AUTH-001-04` | Request, Error, And API Control Foundation | L1 | Proposed | +| `WS-AUTH-001-05` | Authority Evidence And Idempotency Foundation | L1 | Proposed | +| `WS-AUTH-001-06` | Canonical Actor Profile And Identity Link | L1 | Proposed | +| `WS-AUTH-001-07` | Authorization Kernel And Permission Registry | L1 | Proposed | +| `WS-AUTH-001-08` | Bootstrap And Administrative Role Grants | L1 | Proposed | +| `WS-AUTH-001-09` | Actor State, Identity Revocation, And Service Actors | L1 | Proposed | +| `WS-AUTH-001-10` | Project Qualification And Contributor Role Grants | L1 | Proposed | +| `WS-AUTH-001-11` | Project Identity, Guide, Source, And Visibility Cutover | L1 | Proposed | +| `WS-AUTH-001-12` | Project Policy And Setup Mutation Cutover | L1 | Proposed | +| `WS-AUTH-001-13` | Task Management And Assignment Cutover | L1 | Proposed | +| `WS-AUTH-001-14` | Submission, Checker, And Audit Visibility Cutover | L1 | Proposed | +| `WS-AUTH-001-15` | Remaining System Worker Cutover And Obsolete Authority Removal | L1 | Proposed | +| `WS-AUTH-001-16` | Conformance, Observability, And Live API Proof | L1 | Proposed | + +## Dependency order + +```text +WS-AUTH-001-PLAN +-> WS-AUTH-001-01 +-> WS-AUTH-001-02 +-> WS-AUTH-001-03 +-> WS-AUTH-001-04 +-> WS-AUTH-001-05 +-> WS-AUTH-001-06 +-> WS-AUTH-001-07 +-> WS-AUTH-001-08 +-> WS-AUTH-001-09 +-> WS-AUTH-001-10 +-> WS-AUTH-001-11 +-> WS-AUTH-001-12 +-> WS-AUTH-001-13 +-> WS-AUTH-001-14 +-> WS-AUTH-001-15 +-> WS-AUTH-001-16 +``` + +## Boundary notes + +- Chunk 02 authenticates tokens but grants no product authority. +- Chunk 03 provides a supported classification gate before schema migration. +- Chunk 04 establishes request/error/rate controls. +- Chunk 05 evolves the shared audit path and idempotency before mutations. +- Chunk 06 establishes canonical actor resolution while preserving only the + enumerated non-authoritative legacy workflow-eligibility consumers required + for intermediate-release operability. +- Chunk 07 provides the single authorization engine before grant APIs. +- Chunks 08-10 establish local grant truth before product cutover. +- Chunks 11-15 migrate bounded complete product/system surfaces. +- Chunk 16 proves the complete initiative; it does not backfill missing audit + or idempotency evidence. +- `WS-POL-002-03` remains paused until the relevant project authorization + cutover is complete and the user explicitly resumes it. + +## Stop condition + +After planning review, stop. `WS-AUTH-001-01` does not become active without +explicit human approval of D4-D10 plus an implementation start signal under the +repository engineering loop. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DECISIONS.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DECISIONS.md new file mode 100644 index 000000000..6edac19a3 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DECISIONS.md @@ -0,0 +1,106 @@ +# Decisions: WS-AUTH-001 - Workstream Authorization Service + +## D1: Adopt WS-AUTH-001 as the authorization authority source + +Status: accepted by the user on 2026-07-11. + +`WS-AUTH-001` supersedes the repository's token-role bootstrap and current +multi-profile authorization semantics. Existing code and docs remain evidence +of current behavior, not the target authority contract. + +## D2: Preserve `/api/v1` + +Status: accepted by the user on 2026-07-11. + +The repository's existing `/api/v1` namespace remains canonical. The imported +specification's `/v1` examples will be reconciled during the baseline-adoption +chunk. Workstream will not expose two versioned route trees as permanent +aliases. + +## D3: Prioritize auth before WS-POL-002-03 + +Status: accepted by the user on 2026-07-11. + +`WS-POL-002` is paused after merged chunk 02. New post-submit approval APIs must +not be built on authority rules that have been declared obsolete. + +## L0 human approval boundary + +The user explicitly approved the authorization direction, source precedence, +API namespace, and priority recorded in D1-D3 on 2026-07-11. The remaining +architecture and data-model choices in D4-D10 are proposed planning decisions, +not autonomous implementation authority. Their explicit human approval must be +recorded durably before `WS-AUTH-001-01` becomes active. Every bounded +implementation chunk is then L1 and retains its own human PR/merge checkpoint. + +## D4: No dual canonical authority + +Status: planning decision derived from the adopted contract. + +Token roles may be retained as non-authoritative diagnostic input during a +bounded migration only. A protected command must never accept either a token +role or a local permission as interchangeable proof. Cutover chunks replace +authorization for complete resource surfaces and remove the old check. + +## D5: Preserve historical actor identifiers where classification is safe + +Status: planning decision. + +Existing `ActorIdentity.actor_id` values for externally verified callers are +UUID5 strings and are referenced throughout tasks, submissions, checker runs, +project provenance, and audit rows. For a classified legacy row, that UUID +becomes the canonical `ActorProfile.id`; the new `ActorIdentityLink` receives a +separate UUID. Migration must validate UUID shape and uniqueness before reuse. +Invalid or ambiguous rows fail preflight and are not silently rewritten. + +The unrelated UUIDs on legacy typed `actor_profiles` rows are workflow-row IDs, +not actor IDs, and never become canonical profile IDs. + +## D6: Existing typed profiles do not become grants + +Status: planning decision. + +Observed or active `worker`, `reviewer`, `admin`, or `project_manager` profile +rows do not create `AdminRoleGrant` or `ProjectRoleGrant` records. Skills and +non-authoritative workflow metadata may be preserved under a clearly named +metadata model only when required by an existing workflow. + +## D7: Internal workers use explicit system authority + +Status: planning decision. + +Project setup, pre-review gating, reconciliation, and repair work use fixed +Workstream system principals and registered system permissions. They do not +receive fabricated human admin roles and do not become normal ActorProfiles. + +## D8: Production issuer details remain configuration + +Status: planning decision. + +The token adapter will require explicit issuer, audience, JWKS URL, algorithm, +scope, clock-skew, and cache configuration and will fail closed when incomplete. +The real issuer values are required for live production-token proof, not for +deterministic adapter implementation with local JWKS fixtures. + +## D9: Authority evidence is foundational + +Status: planning repair after internal review. + +Request/correlation context, canonical idempotency records, and the shared +append-only authority-event writer are introduced before canonical actor +persistence and before any authority-changing public API. Every later authority +mutation commits state, idempotency result, audit event, and invalidation event +atomically. The final evidence chunk verifies completeness; it does not invent +or backfill the evidence model. + +## D10: Legacy classification uses a versioned supported manifest + +Status: planning repair after internal review. + +Non-empty legacy registries require a versioned JSON classification manifest +processed by a supported management/preflight tool. Entries bind exact legacy +actor ID, canonical issuer, opaque subject, and `subject_kind`. The tool rejects +duplicates, stale/missing rows, mismatches, unknown fields, and unsupported +kinds; supports dry-run; emits a checksum-bound report; and never writes grants. +The schema migration consumes only validated staged classification evidence or +fails closed. Manual SQL is not a supported path. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md new file mode 100644 index 000000000..67894ea9f --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/DISCOVERY.md @@ -0,0 +1,158 @@ +# Discovery: WS-AUTH-001 - Workstream Authorization Service + +Discovery is read-only. No application code was changed while producing this +artifact. + +## Current behavior + +### Authentication + +`backend/app/api/deps/auth.py` extracts bearer credentials and delegates to an +`AuthVerifier`. `DevelopmentAuthVerifier` is restricted to explicit +development/test environments. `FlowAuthVerifier` supports a local HS256 +fixture mode and otherwise fails closed because production issuer/JWKS +verification is not configured. + +The verifier returns `ActorContext`, including token-derived roles and a +sanitized claim snapshot. `get_registered_actor()` refreshes local registry +metadata but explicitly returns the token context for route authorization. + +### Actor registry + +`ActorIdentity` is keyed by a deterministic actor ID derived from issuer and +subject. It stores issuer, subject, display metadata, last-seen roles, and a +sanitized claim snapshot. + +`ActorProfile` is currently a repeatable typed workflow row under the identity. +Types include `worker`, `reviewer`, `admin`, `project_manager`, and +`project_owner`; statuses are `observed`, `active`, and `disabled`. These rows +are not the single canonical actor profile required by `WS-AUTH-001`. + +### Authorization + +`backend/app/core/permissions.py::require_any_role()` intersects allowed roles +with `ActorContext.roles`. Project, task, and checker services define their own +role sets and object-level checks. Examples include: + +- `PROJECT_SETUP_ROLES = {"admin", "project_manager"}` +- `TASK_CLAIM_ROLES = {"worker"}` +- `CHECKER_TRIGGER_ROLES = {"admin", "project_manager"}` +- `can_admin_or_task_creator_manage()` + +This is a bootstrap model, not the adopted Workstream-owned grant model. + +### Internal actors + +Project setup and pre-review checker workers create fixed internal +`ActorContext` instances. Project setup currently assigns human-style +`admin/project_manager` roles to its internal actor. The pre-review gate uses a +separate `workstream_system` role and identity checks. + +### Audit and errors + +Task audit events preserve actor ID, external subject/issuer, actor roles, claim +snapshot, event type, state transition, reason, and payload. They do not yet +provide the complete authority-change event envelope, matched-grant evidence, +idempotency key, or correlation ID required by the adopted specification. + +FastAPI errors mostly use the standard `detail` response. Authentication +failures collapse to `Invalid bearer token`. The adopted contract requires +stable structured authentication and authorization codes. + +## Relevant files/modules + +| Path | Purpose | Notes | +|---|---|---| +| `backend/app/interfaces/auth.py` | Auth verifier port | Extend with verified issuer-token contract without SDK leakage. | +| `backend/app/adapters/auth/flow.py` | Flow token adapter | Local HS256 only; production JWKS work is absent. | +| `backend/app/adapters/auth/dev.py` | Development verifier | Must remain fail-closed outside local/test. | +| `backend/app/api/deps/auth.py` | Bearer and registry dependencies | Current dependency returns token roles as authority. | +| `backend/app/schemas/auth.py` | Actor request context and claim sanitation | Role-bearing context must be replaced by verified-token plus local authorization context. | +| `backend/app/core/permissions.py` | Coarse role helper | Must not remain an alternate authority path after cutover. | +| `backend/app/modules/actors/models.py` | Current identity/profile tables | Cardinality and semantics conflict with adopted model. | +| `backend/app/modules/actors/repository.py` | Registry persistence | Useful repository pattern; current upsert mirrors token data. | +| `backend/app/modules/actors/service.py` | Registration/profile activation | Must evolve into actor resolution and administration services. | +| `backend/app/modules/projects/service.py` | Project setup authorization | Contains many token-role checks and approval-role provenance. | +| `backend/app/modules/tasks/authorization.py` | Task object helper | Creator/role semantics must become canonical resource guards. | +| `backend/app/modules/tasks/service.py` | Task/submission authorization | Worker token roles and active worker profile currently gate work. | +| `backend/app/modules/checkers/service.py` | Checker authorization/system actor | Worker/operator visibility and trigger rules need permission-based cutover. | +| `backend/app/modules/tasks/models.py::AuditEvent` | Current audit row | Needs authority evidence or a shared audit evolution. | +| `backend/alembic/versions/0012_actor_identity_profile_registry.py` | Current actor schema migration | Existing rows lack reliable subject-kind classification. | +| `backend/app/api/router.py` | Versioned API composition | `/api/v1` remains canonical by human decision. | +| `backend/tests/test_auth.py` | Auth boundary tests | Covers fail-closed defaults but not production JWKS. | +| `backend/tests/test_actors.py` | Actor registry tests | Encodes the old multiple-profile and token-observation behavior. | +| `backend/tests/test_projects.py` | Project auth/setup tests | Extensive fixtures currently depend on token roles. | +| `backend/tests/test_tasks.py` | Task authorization/lifecycle tests | Extensive worker/operator role fixtures and object-concealment tests. | +| `backend/tests/test_checkers.py` | Checker authorization tests | Covers worker redaction and operator-only actions. | +| `docs/reference_specs/WS-AUTH-001-actor-profile-role-and-authorization-service-specification.md` | Adopted authority contract | `/v1` is overridden to `/api/v1` by human decision. | + +## Current tests + +| Test path | What it covers | Gaps | +|---|---|---| +| `backend/tests/test_auth.py` | Bearer failures, dev guard, local token checks, coarse permission helper | No asymmetric JWKS, subject kind, scope, `jti`, cache refresh, or typed unavailable errors. | +| `backend/tests/test_actors.py` | Identity upsert, observed/active typed profiles, refresh behavior | Tests the superseded profile semantics; no canonical actor/link state or provisioning race. | +| `backend/tests/test_projects.py` | Project role restrictions and setup lifecycle | Uses token `admin/project_manager`; no durable grant scope. | +| `backend/tests/test_tasks.py` | Worker/operator access, assignment ownership, privacy, concurrency | Uses token `worker` plus active worker profile; no exact-project submitter grant. | +| `backend/tests/test_checkers.py` | Checker trigger/read authorization and worker redaction | No registered permission matrix or local revocation proof. | +| `backend/tests/test_alembic.py` | Migration forward/backward behavior | No canonical actor/grant/authority-control constraints. | + +The repository contains 352 backend test functions. Static wording, Markdown +link, agent-gate, loop-memory, and internal-review evidence checks passed during +discovery. The full backend suite was not rerun because this checkout currently +has no `backend/.venv` and local Postgres/Redis services are not running. + +## Dependencies/integrations + +- Identity Issuer final tokens and issuer-owned JWKS. +- FastAPI bearer dependencies. +- Postgres canonical actor, grant, idempotency, and audit state. +- Celery workers that must distinguish system authority from request evidence. +- Redis as Celery delivery infrastructure only, never authority state. +- Existing project, task, submission, and checker resource relationships. + +A maintained JWT/JWK cryptography dependency and async HTTP client will be +required unless already supplied transitively. Production dependency selection +requires explicit review in the token-verifier chunk. + +## Risks discovered + +| Risk | Why it matters | Suggested handling | +|---|---|---| +| Token role escalation | A valid token role currently grants product authority. | Remove role claims from authorization context and prove every protected surface uses local grants. | +| Dual authority during migration | Old and new checks could disagree. | Define a one-way cutover; never accept either path as sufficient. | +| Legacy subject-kind ambiguity | Existing registry rows do not prove human versus service. | Fail closed on unclassified non-empty data and require an audited classification/remediation input. | +| Actor ID rewrite | Historical task/checker/audit attribution uses strings. | Preserve valid actor IDs and avoid broad FK conversion in early chunks. | +| Final administrator race | Concurrent revocation/suspension could remove all access administrators. | Singleton authority-control row plus `FOR UPDATE` and PostgreSQL race tests. | +| Cross-project IDOR | Request bodies can claim a project different from the resource. | Canonical resource loaders derive scope from database relationships. | +| Stale queued authority | Jobs may carry authority that has been revoked. | Re-resolve actor/grant state before actor-attributed commits. | +| Claim/PII leakage | Current registry persists sanitized claim snapshots and role observations. | Minimize persisted identity data; never persist raw token/JWKS material. | +| Namespace drift | New specs say `/v1`; repo uses `/api/v1`. | Record `/api/v1` as the adopted repository override and update generated/reference artifacts coherently. | +| Current initiative interruption | `WS-POL-002` is incomplete after chunk 02. | Mark it paused, do not implement chunk 03 until auth foundation is ready. | + +## Unknowns/questions for human + +No blocking product questions remain for planning. The user explicitly adopted +`WS-AUTH-001`, retained `/api/v1`, and prioritized auth before +`WS-POL-002-03`. + +Production deployment inputs remain externally supplied: + +| Input | Why it matters | Needed before chunk? | +|---|---|---| +| Canonical issuer URL and JWKS URL | Exact verification and live proof | Token adapter supports configuration without values; real-token drill requires values. | +| Allowed signing algorithms | Prevent algorithm confusion | Adapter defaults must fail closed; deployment must configure approved values. | +| Canonical subject-kind and scope claim form | Required token validation | Contract supports the adopted names; issuer integration test requires a fixture or issuer documentation. | +| Introspection/revocation policy | Determines whether signature validation alone is sufficient | May remain disabled only through explicit secure configuration and documented issuer policy. | +| Legacy non-test actor classification manifest | Safe migration of existing rows | Required before applying the actor migration to any non-empty persistent environment; format and tool are defined by chunk 03. | + +## Existing conventions to preserve + +- Thin FastAPI routers, service-owned business rules, repository-owned queries. +- Async SQLAlchemy and Postgres-enforced uniqueness/concurrency. +- Pydantic request models with `extra="forbid"`. +- Fail-closed production defaults and non-production dev auth guard. +- Object-concealing not-found behavior for unauthorized resource reads. +- Append-only audit facts for lifecycle and authority changes. +- Fixed internal system actors for server-owned automation. +- One PR-sized chunk at a time with deterministic evidence and internal review. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md new file mode 100644 index 000000000..7b5c58614 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md @@ -0,0 +1,159 @@ +# Intent: WS-AUTH-001 - Workstream Authorization Service + +## Problem being solved + +Workstream currently verifies an external identity token and then treats token +role claims such as `admin`, `project_manager`, and `worker` as product +authority. Authorization is distributed across module services through +`require_any_role()` and local object checks. The current actor registry also +models one external identity with multiple role-shaped profile rows. + +That bootstrap is incompatible with the adopted +`WS-AUTH-001` authority contract. Workstream must own actor state, +administrative grants, project contributor grants, permission evaluation, +resource guards, revocation, and authorization evidence. + +## Why this work matters + +Review, contribution, compensation, and external adapter work all depend on a +trustworthy local authority spine. Continuing to add APIs to the token-role +bootstrap would expand a security model that has now been explicitly +superseded. + +## Current behavior + +- External issuer and subject identify an `ActorIdentity`. +- Verified token role claims populate `ActorContext.roles`. +- `require_any_role()` uses those claims as route authority. +- Multiple typed `ActorProfile` rows hold observed or active workflow metadata. +- Project-manager authority is not represented by durable project-scoped + grants. +- Production JWKS verification, local authority revocation, actor suspension, + service-subject provisioning, and final-access-administrator protection are + absent. + +## Target behavior + +- Identity Issuer tokens establish authentication and coarse access only. +- One `(issuer, subject)` resolves through one active `ActorIdentityLink` to + one canonical `ActorProfile`. +- Human first access provisions an active Contributor-domain actor without + product authority. +- Service subjects require deliberate provisioning; agent and Space subjects + are denied in v0.1. +- Workstream-owned `AdminRoleGrant` and exact-project `ProjectRoleGrant` + records provide permission candidates. +- One `AuthorizationService` combines registered permissions, grant scope, + canonical resource ownership, actor state, lifecycle state, and explicit + guards. +- Revocation and suspension take effect from database state without requiring + token refresh. +- Sensitive authority changes are idempotent, concurrency-safe, and auditable. +- All product APIs remain under the existing `/api/v1` namespace. + +## Design chosen + +Evolve the existing auth and actor boundary into the adopted model. Preserve +stable historical actor identifiers when the legacy row can be explicitly and +safely classified. Do not add a parallel `ActorProfileV2` or retain token-role +authorization as an alternate path. + +Implementation is split into PR-sized chunks. Token verification and canonical +actor resolution precede grants. Grants precede centralized permission +evaluation. Existing project, task, submission, checker, and worker surfaces +move to the new authorization service in bounded cutover chunks. + +## Alternatives considered + +### Keep token roles and add project grants + +Rejected. Token roles would remain a second authority source and revocation +would still depend on token lifetime. + +### Add a parallel authorization subsystem + +Rejected. Two actor/profile and permission systems would create ambiguous +authority and unsafe partial migrations. + +### Rewrite all protected surfaces in one PR + +Rejected. The blast radius is too large for careful migration, review, and +rollback analysis. + +### Change all APIs to `/v1` + +Rejected by human decision. `/api/v1` is the existing tested compatibility +namespace and remains canonical. + +## Boundaries preserved + +- Workstream verifies external Flow/Identity Issuer tokens; it does not own + login, passwords, primary sessions, or token issuance. +- Postgres remains canonical for product authority. +- Existing project, task, submission, and checker lifecycle rules remain in + their owning services and become authorization resource guards where needed. +- The proven intake path through `review_pending` remains operational and + regression-tested. +- System-owned recovery work uses explicit internal principals and never + fabricates human authority. +- Review, contribution, compensation, frontend, blockchain settlement, and + external source adapters are out of scope. + +## Expected risks + +- Accidentally accepting token roles after local grants are introduced. +- Misclassifying legacy actor rows that lack `subject_kind` provenance. +- Leaving a protected endpoint on the old authorization path. +- Breaking historical actor attribution during schema migration. +- Losing the final Access Administrator under concurrent mutations. +- Cross-project authorization through client-supplied resource scope. +- Leaking identity claims, token material, or hidden resource existence. +- Building new approval endpoints on the obsolete bootstrap during cutover. + +## What must not change + +- External authentication ownership. +- Canonical review decisions: `accept`, `needs_revision`, `reject`. +- Immutable submission/checker provenance. +- Existing task lifecycle behavior except the explicit, audited release of an + exclusive assignment when its actor/link/project authority is invalidated. +- CI, test, documentation, and internal-review gates. +- `WS-POL-002-03` remains paused until the authorization foundation is ready. + +## How this will be proven + +- Unit tests for token, permission, and guard contracts. +- PostgreSQL migration, uniqueness, row-locking, idempotency, and concurrency + tests. +- Table-driven permission matrix tests. +- Negative cross-project, self-grant, self-review, inactive-actor, service, + agent, and Space tests. +- Existing project/task/checker regression suite and API contract drill. +- A live API authorization drill proving bootstrap, grants, revocation with the + same token, suspension/reactivation, and final-admin safety without direct + database edits. + +## Human decisions required + +Resolved: + +- `WS-AUTH-001` is authoritative and supersedes the token-role bootstrap. +- `/api/v1` remains the canonical API namespace. +- `WS-AUTH-001` is prioritized before `WS-POL-002-03`. + +Pending before `WS-AUTH-001-01` activation: + +- Explicit human approval of proposed L0 architecture and data-model decisions + D4-D10 in `DECISIONS.md`. Planning review does not imply that approval. + +External deployment details such as issuer URL, JWKS URL, approved algorithms, +claim names, and introspection policy are configuration inputs. Their absence +does not block deterministic adapter implementation and tests, but it does +block the production live-token proof. + +## Initial risk class + +L0 for initiative direction, auth model, and data-model strategy. D1-D3 are +human-approved; D4-D10 remain at the explicit human approval gate above. +Subsequent bounded implementation chunks are L1 and require their own chunk +contracts, evidence, reviewer fanout, and human checkpoints. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md new file mode 100644 index 000000000..8e5c4723a --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md @@ -0,0 +1,249 @@ +# Plan: WS-AUTH-001 - Workstream Authorization Service + +## Goal + +Replace the token-role bootstrap with the adopted Workstream-owned actor, +grant, permission, scope, guard, revocation, and audit model while preserving +the proven intake lifecycle and historical actor attribution. + +## Adopted authority flow + +```text +Bearer token +-> TokenVerifier +-> VerifiedIssuerToken +-> ActorResolver +-> ActorIdentityLink + ActorProfile +-> request-scoped AuthorizationContext +-> AuthorizationService.require(permission, ResourceContext, uow) +-> AdminRoleGrant / ProjectRoleGrant candidates +-> canonical project and ownership resolution +-> actor, resource, and lifecycle guards +-> allow or stable denial code +``` + +Token roles are never included as product authority. + +## Migration strategy + +### Legacy classification and actor identity + +The final model has one canonical `ActorProfile` and one active-or-revoked +`ActorIdentityLink` per profile. Existing externally verified actor IDs are +UUID5 strings. After explicit classification and UUID validation, that UUID is +preserved as the canonical `ActorProfile.id`; the identity link receives a new +UUID. Existing typed profile row IDs are unrelated workflow IDs and are not +promoted into actor identity or authority grants. + +The actor migration must not guess `subject_kind`. For a non-empty persistent +registry, a versioned JSON classification manifest is validated through a +supported management tool against exact actor ID, issuer, and subject. The tool +rejects stale, missing, duplicate, mismatched, and unknown entries, supports +dry-run, and produces a checksum-bound evidence report. The migration consumes +only validated staged evidence; otherwise deployment fails with a precise +remediation report. Empty and test databases migrate normally. + +Historical actor references remain string identifiers in early chunks because +fixed system actors are not ordinary human/service profiles and a broad FK +rewrite is not required to establish correct authority. + +### Authorization cutover + +The implementation introduces the canonical service before moving product +surfaces. Each cutover chunk migrates a complete resource family and removes +its old token-role authorization. No command accepts token role or local grant +as alternate sufficient proof. + +Project configuration moves first because it is required to create project +grants and later resume `WS-POL-002-03`. Task/submission/checker access follows +after exact-project contributor grants and resource loaders exist. + +Chunk 06 preserves task claim/start/submission operability through an explicitly +named `LegacyWorkflowEligibilityCompatibility` adapter. It reads only +classified legacy workflow metadata, grants no product permission, and is +limited to enumerated task-service call sites. Chunk 13 removes it from task +queue/claim/start, chunk 14 removes its final submission consumer, and chunk 15 +proves the adapter and allowlist removed in chunk 14 remain absent. No merged intermediate state may +require a deleted typed-profile registry. + +### Request context, idempotency, audit, and errors + +Introduce request and correlation context, canonical mutation idempotency, the +shared authority-event writer, and a stable error envelope with canonical actor +persistence before grant-management APIs. Every authority mutation commits its +state, idempotency result, audit event, and invalidation event in one database +transaction. Events record the acting and target profiles, matched grant, +project, reason, idempotency key, and bounded before/after state. Raw tokens, +JWKS documents, and full provider claims are never persisted. + +The shared audit path is append-only through supported application APIs and +database enforcement. Each authority-mutation chunk proves its specified +allowed and denied events when the behavior is introduced; chunk 16 may verify +completeness but may not create or backfill missing evidence. + +### System work + +Internal Celery work uses fixed Workstream principals and explicit registered +system permissions. Requester identity serialized into a job remains audit +evidence only. Actor-attributed mutations reload current profile and grant +state before commit. + +### Ownership and transaction boundaries + +- The existing `AuthVerifier` port/factory evolves in place; there is no second + token-verifier hierarchy. +- Canonical ActorProfile/ActorIdentityLink persistence stays in `actors`. +- Admin/project grants, permission definitions, idempotency, invalidation, and + authorization evaluation stay in `authorization`. Qualification snapshots + are owned by authorization with project ownership enforced through existing + project relationships. +- `ProjectRepository` and `TaskRepository` remain the canonical persistence + loaders for their domain records. Project/task application services or + feature-owned resource loaders compose `ResourceContext` from those records; + persistence repositories do not depend on authorization DTOs, and + authorization does not duplicate project/task queries. +- Existing `AuditEvent` and `AuditRepository` evolve into the shared authority + evidence path; no parallel authority-event ledger is added. +- The injected AsyncSession is the UnitOfWork boundary. Services own + commit/rollback; no generic parallel transaction wrapper is introduced. + +### Test preservation rule + +Current intake lifecycle tests may not be deleted, skipped, xfailed, or have +their lifecycle assertions loosened. As surfaces migrate, positive +authorization fixtures must provision canonical profiles, links, and grants +through supported service/API paths and exercise the real ActorResolver, +FastAPI dependency, and AuthorizationService. Dependency overrides, fabricated +ActorContext objects, token-role fallbacks, and direct database grant inserts +are not valid product-authorization proof. Direct row factories remain limited +to repository and migration tests. Every migrated surface adds a negative test +proving the same token role alone no longer authorizes. + +## Implementation sequence + +1. Adopt and reconcile the specification baseline, API namespace, ADRs, durable + loop state, checksum evidence, and auth operations ownership. +2. Implement the verified issuer-token/JWKS/introspection boundary with + fail-closed configuration and deterministic fixtures. +3. Implement the versioned legacy-actor classification preflight and rollout + evidence workflow. +4. Establish request/correlation context, structured errors, and API rate + controls. +5. Evolve shared audit evidence and add canonical idempotency/invalidation. +6. Migrate to canonical profile/link semantics and first-human resolution. +7. Implement the minimal registered permission and AuthorizationService kernel + before protected authority-management APIs. +8. Implement bootstrap, `AuthorityControl`, and immutable admin-role grants. +9. Implement actor/link state administration and controlled service actors. +10. Implement qualification snapshots and exact-project contributor grants. +11. Cut project identity, guide, source, and visibility queries over to local + permissions. +12. Cut project policy mutations, approvals, activation, and setup operations + over to local permissions. +13. Cut task management, queue, assignment, claim, and start operations over. +14. Cut submission, pre-submit, checker trigger/read, and task audit visibility + over. +15. Cut remaining internal workers over, verify the project-setup cutover, + remove old runtime authority, and enforce a + deterministic stale-authority scanner. +16. Complete privacy/observability, full conformance, concurrency, and live + proof. + +## Alternatives rejected + +- Treating issuer roles as local grants. +- Automatically converting observed worker/reviewer/admin profiles to grants. +- Keeping both `ActorIdentity` and a new canonical `ActorProfileV2` as roots. +- Adding a generic policy-language engine. +- Caching authorization decisions across requests. +- Changing to `/v1` or exposing permanent `/v1` aliases. +- Implementing review lifecycle work before auth proof. + +## Boundaries preserved + +- Existing module organization remains feature-oriented: + `router -> service -> repository -> models/schemas`. +- No repo-wide domain/persistence directory rewrite. +- No Workstream login/session/token issuance. +- No review, contribution, compensation, frontend, blockchain, or external + source-adapter implementation. +- No change to canonical review decision values. +- No weakening of CI, tests, auth defaults, or internal-review requirements. + +## Verification strategy + +### Per chunk + +- Ruff and focused tests. +- Migration forward/backward and constraint proof when schema changes. +- Negative authorization tests for every added permission path. +- Stale wording, Markdown links, and `git diff --check`. +- Required internal reviewers and PR trust bundle. + +For every schema chunk, verification includes an isolated-test-database +`alembic upgrade head -> downgrade -1 -> upgrade head` round trip plus focused +migration assertions. For every chunk, post-review closure separately requires: + +```bash +python3 scripts/check_internal_review_evidence.py +python3 scripts/check_loop_memory_state.py +``` + +These run after reviewer evidence and memory/status updates exist; they are not +pre-review implementation substitutes. + +### Initiative exit + +```bash +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +python3 scripts/check_internal_review_evidence.py +python3 scripts/check_stale_authorization.py +(cd backend && .venv/bin/python -m ruff check app tests scripts) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/auth_api_e2e.py) +(cd backend && .venv/bin/docstr-coverage --config .docstr.yaml) +git diff --check +``` + +The auth live drill must prove first human access, one-time bootstrap, scoped +admin grants, project submitter/reviewer grants, separation of duties, +revocation using the same unexpired token, suspension/reactivation, service +subject handling, cross-project denial, and final-admin concurrency safety. + +## Documentation strategy + +The baseline-adoption chunk adds an ADR before changing implementation and +reconciles README, glossary, architecture lockdown, role/permission operations, +data model, and imported specification route examples. The eight imported files +remain immutable archival planning inputs with durable hash evidence. A +reference README records their status and `/api/v1` override. Canonical +reconciled authorization text is maintained separately; a canonical PDF is +generated only when a deterministic repository build command exists. + +A deterministic documentation scanner uses an explicit historical/archive +allowlist and rejects non-`/api/v1` route references plus obsolete authority +claims in active docs. It does not use bare `rg` success semantics. + +`docs/operations_authorization_service.md` owns issuer/JWKS configuration, +rotation and outage response, cache/introspection policy, bootstrap custody, +legacy classification, staged rollout/rollback, alert response, and live-proof +responsibility. Each implementation chunk updates the relevant section. + +Payment/reputation contradictions discovered in the broader reference set are +recorded but remain outside this auth initiative. They require their own later +ADR and initiative before contribution/compensation implementation. + +## Stop conditions + +Stop the active chunk if: + +- issuer behavior requires trusting a token role or unverified claim; +- actor migration requires guessing legacy subject kind; +- a second canonical actor or authorization path is required; +- project scope cannot be derived from canonical database relationships; +- a migration cannot preserve existing lifecycle data; +- CI or existing intake tests must be weakened; +- production credentials, private keys, or production data are required; +- scope expands into review, compensation, frontend, or external settlement. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/RISKS.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/RISKS.md new file mode 100644 index 000000000..16f6e999f --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/RISKS.md @@ -0,0 +1,50 @@ +# Risks: WS-AUTH-001 - Workstream Authorization Service + +## Classification + +- Initiative direction/auth/data-model strategy: L0, human-led; D1-D3 approved + by the user on 2026-07-11, D4-D10 require explicit recorded approval before + `WS-AUTH-001-01` activation +- Bounded implementation chunks: L1 after the L0 planning gate +- SLA: P1 +- Work type: architecture, authentication, authorization, migration, audit +- Human gate: required for every implementation PR and merge +- Budget posture: deep reasoning and full required reviewer fanout + +## Risk register + +| ID | Risk | Impact | Mitigation | Proof | +|---|---|---|---|---| +| A1 | Token roles remain authoritative on one path | Privilege escalation | Remove role-based helpers surface by surface; repository scan gate | Negative token-role tests and zero runtime `require_any_role` uses at final gate | +| A2 | Parallel actor models survive cutover | Ambiguous identity and authority | One migration design, no `V2` model, explicit legacy metadata disposition | Metadata/model inspection and migration tests | +| A3 | Legacy actors are misclassified | Service actor gains human authority or history breaks | No inference from email/subject/roles; fail-closed classification gate | Non-empty ambiguous migration failure test | +| A4 | Bootstrap races or the final Access Administrator is lost | Multiple trust roots or administrative lockout | Bootstrap and every final-admin-removal path lock `AuthorityControl(id=1) FOR UPDATE` before the effective-admin check and mutation | Different-target concurrent bootstrap plus revoke/suspend/deactivate concurrency tests | +| A5 | Cross-project grant or IDOR | Unauthorized project access | Canonical DB resource resolution and composite ownership constraints | Cross-project read/mutation tests | +| A6 | Revocation loses a race to mutation | Revoked actor commits sensitive change | Revalidate grants and state inside sensitive transaction | Grant-revoke versus command concurrency test | +| A7 | Worker job trusts serialized authority | Stale grant remains effective | Serialized context is evidence only; reload current authority before commit | Revoked queued-command test | +| A8 | JWKS outage weakens verification | Unverifiable token accepted | Pinned algorithms, bounded cache, one refresh, fail closed | Cache/rotation/outage contract tests | +| A9 | Claims or personal data leak | Security/privacy incident | Minimal verified token type, bounded logging, no raw token/JWKS persistence | Log/error/audit redaction tests | +| A10 | Approval provenance breaks | Existing policy activation becomes unreadable or forgeable | Preserve historical values; new actions reference matched local grant | Migration and project approval tests | +| A11 | API namespace forks | Client and documentation drift | Adopt `/api/v1` only and update references together | Route/OpenAPI and stale-reference scan | +| A12 | Existing intake regresses | Project/task/checker pipeline stops | Run full current suite and API drill after actor migration and each cutover surface | Existing backend suite and live drill | +| A13 | Auth initiative becomes one oversized PR | Review failure and hidden coupling | Sixteen bounded implementation chunks, one active at a time | Circuit-breaker and PR-size evidence | +| A14 | WS-POL work resumes on obsolete auth | Rework and inconsistent authority | Keep WS-POL-002-03 paused in durable loop state | Loop-memory gate | +| A15 | Authority mutation ships before durable evidence | Missing provenance cannot be reconstructed | Introduce correlation/idempotency/shared audit with canonical actor persistence | Atomic state+idempotency+event tests in every authority chunk | +| A16 | Identity-link revocation strands final administrator | Administrative lockout despite active grant row | Apply AuthorityControl lock to link revoke plus grant/profile changes | Mixed concurrent link/grant/profile final-admin tests | +| A17 | Canonical actor migration deletes typed-profile workflow eligibility before task/submission cutover | An intermediate merged release cannot claim, start, or submit work | Bounded non-authoritative workflow-eligibility adapter in chunk 06; remove task consumers in 13 and final consumer plus adapter in 14 | Full suite/API drill after chunks 06, 13, 14, and scanner proof in 15 | +| A18 | Authority evidence is mutable or denial events arrive late | Security decisions cannot be reconstructed | Insert/read-only repository API, database append-only enforcement, per-mutation allowed/denied event proof, operational retention controls | Update/delete rejection plus atomic allowed/denied event tests in owning chunks | +| A19 | Authority invalidation releases a needs-revision task as ordinary ready work | Prior findings, context, supersession, or replay obligations are bypassed | Keep the task in needs_revision with a durable unassigned obligation and controlled replacement assignment | Replacement-contributor revision-context, supersession, and high/medium replay tests | + +## Required reviewers + +Every implementation chunk requires: + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- reuse/dedup +- test delta when tests change +- docs when docs or public contracts change +- CI integrity when workflows, scripts, dependencies, or test configuration change diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/SOURCE_MANIFEST.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/SOURCE_MANIFEST.md new file mode 100644 index 000000000..79f4d3752 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/SOURCE_MANIFEST.md @@ -0,0 +1,24 @@ +# Source Manifest: WS-AUTH-001 Planning Inputs + +Copied unchanged from `/home/abiorh/Downloads` on 2026-07-11. + +| File | SHA-256 | +|---|---| +| `WS-ARCH-001-workstream-v0.1-architecture-baseline.pdf` | `547176dcedf41895ed896d5262a6ab8c6c029d71dac50bd17ed9da65f692b9b3` | +| `WS-AUTH-001-actor-profile-role-and-authorization-service-specification.md` | `464a7afb032e950b83e8fdb8c04fafe0b9ebddf549c4475dca684946740869a9` | +| `WS-AUTH-001-actor-profile-role-and-authorization-service-specification.pdf` | `69670ed59006b29cb7f60d12101cedf250c82a3488497f07d6c27e140aef1d2` | +| `WS-CON-001-contribution-record-and-compensation-boundary-specification.pdf` | `34c4337f27e42a5b0ed5e153fe8ccd492ecede202c2764506a930d109aef66c1` | +| `WS-IMP-001-workstream-v0.1-coding-agent-implementation-specification.md` | `e2116bce55fda1cce46a93e64bedcb47133d3898c1d4a51863385803e9dac210` | +| `WS-IMP-001-workstream-v0.1-coding-agent-implementation-specification.pdf` | `12f094e49c5c80f117e42d0f7f962b843f34508ab58d7f1d8def5f50fef532ed` | +| `WS-REV-001-review-lifecycle-specification.md` | `87d2c4c5b2158bd11ed76985f8abdc3303be7dcdf7de470bae6b3bcf92b56fe4` | +| `WS-REV-001-review-lifecycle-specification.pdf` | `d4ab94f2db6ec47dacc6a9063934e481d7089db5bfe142d724aa0b025bc00b52` | + +Verification: + +```bash +sha256sum -c docs/reference_specs/SHA256SUMS +``` + +This manifest proves the imported planning inputs. Later reconciled normative +Markdown/PDF artifacts receive new hashes and must not overwrite this source +evidence silently. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/STATUS.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/STATUS.md new file mode 100644 index 000000000..96f019683 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/STATUS.md @@ -0,0 +1,57 @@ +# Status: WS-AUTH-001 - Workstream Authorization Service + +## Current status + +Planning and all required internal reviewer tracks passed after repair. The +initiative is stopped at the L0 human checkpoint: D1-D3 are approved; D4-D10 +require explicit durable approval before implementation activation. + +## Active planning chunk + +`WS-AUTH-001-PLAN` + +## Active implementation chunk + +None. + +## Current implementation branch + +`authorization-service` + +## Chunk status + +| Chunk | Status | Branch | PR | Notes | +|---|---|---|---:|---| +| `WS-AUTH-001-PLAN` | Internally reviewed | `authorization-service` | - | L0 plan complete; D4-D10 human approval pending. | +| `WS-AUTH-001-01` | Proposed | - | - | Baseline adoption and repository contract reconciliation. | +| `WS-AUTH-001-02` | Proposed | - | - | Verified issuer token and JWKS boundary. | +| `WS-AUTH-001-03` | Proposed | - | - | Legacy actor classification preflight. | +| `WS-AUTH-001-04` | Proposed | - | - | Request, error, and API control foundation. | +| `WS-AUTH-001-05` | Proposed | - | - | Authority evidence and idempotency foundation. | +| `WS-AUTH-001-06` | Proposed | - | - | Canonical actor profile and identity link. | +| `WS-AUTH-001-07` | Proposed | - | - | Authorization kernel and permissions. | +| `WS-AUTH-001-08` | Proposed | - | - | Bootstrap and administrative grants. | +| `WS-AUTH-001-09` | Proposed | - | - | Actor/link states and service actors. | +| `WS-AUTH-001-10` | Proposed | - | - | Project contributor grants. | +| `WS-AUTH-001-11` | Proposed | - | - | Project identity/guide/source/read cutover. | +| `WS-AUTH-001-12` | Proposed | - | - | Project policy/setup mutation cutover. | +| `WS-AUTH-001-13` | Proposed | - | - | Task management and assignment cutover. | +| `WS-AUTH-001-14` | Proposed | - | - | Submission/checker/audit visibility cutover. | +| `WS-AUTH-001-15` | Proposed | - | - | Remaining system worker and obsolete authority removal. | +| `WS-AUTH-001-16` | Proposed | - | - | Conformance and live proof. | + +## Blockers + +None for planning closure. + +`WS-AUTH-001-01` activation remains gated on explicit, durable human approval +of proposed architecture/data-model decisions D4-D10 and a separate +implementation start signal. + +Internal review evidence is recorded at +`reviews/WS-AUTH-001-PLAN-internal-review-evidence.md` and binds reviewed SHA +`5739e1d6fc8df0fa620bd007c45e370530ac8d12`. + +Production issuer configuration and legacy non-test actor classification are +future implementation/live-proof inputs and are tracked explicitly in +`DISCOVERY.md` and chunk stop conditions. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-01-adopt-authorization-baseline.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-01-adopt-authorization-baseline.md new file mode 100644 index 000000000..719b8187f --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-01-adopt-authorization-baseline.md @@ -0,0 +1,163 @@ +# Chunk Contract: WS-AUTH-001-01 - Adopt Authorization Baseline And Repository Contracts + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Make the adopted authorization model, `/api/v1` namespace decision, superseded +bootstrap behavior, and implementation ordering unambiguous in canonical +repository documentation before application code changes. + +## Why this chunk exists + +The architecture lockdown requires an ADR before changing a locked auth +boundary. Imported reference files currently conflict with existing role docs +and use `/v1` examples. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +README.md +docs/reference_specs/** +docs/decision_0012_workstream_authorization_service.md +docs/architecture_lockdown.md +docs/architecture_system_architecture.md +docs/architecture_data_model.md +docs/operations_roles_permissions.md +docs/roles_permissions.md +docs/glossary.md +docs/roadmap_status.md +docs/operations_authorization_service.md +docs/spec_chunk_2_auth_actor_boundary.md +docs/current_system_data_flow.html +docs/roadmap_implementation_backlog.md +docs/architecture_brief/workstream_architecture_brief.md +docs/architecture_brief/workstream_architecture_brief.pdf +docs/spec_authorization_service.md +docs/reference_specs/README.md +docs/architecture_checker_framework.md +docs/architecture_lifecycle_state_machine.md +docs/operations_operator_workflow.md +docs/operations_project_operating_manual.md +docs/operations_queue_policy.md +docs/product_brief.md +docs/product_first_user_flows.md +docs/template_checker_policy.md +docs/template_project_guide.md +docs/template_revision_replay.md +docs/template_submission_artifact_policy.md +docs/decision_0003_project_guides_are_first_class.md +docs/decision_0009_review_decisions_are_canonical.md +docs/decision_0011_submission_artifact_policy_drives_pre_submit.md +docs/diagrams/*.md +docs/risk_register.md +docs/spec_*.md +scripts/check_stale_authorization_docs.py +scripts/test_agent_gates.py +.github/workflows/agent-gates.yml +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +.agent-loop/policies/repository-engineering-policy.md +.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/STATUS.md +.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/CHUNK_MAP.md +``` + +## Not allowed + +```text +backend application code, migrations, backend tests, dependencies +review/contribution/compensation implementation +rewriting payment/reputation architecture beyond recording a later conflict +adding permanent /v1 aliases +``` + +## Acceptance criteria + +- An ADR adopts `WS-AUTH-001` and records `/api/v1` as the repository override. +- ADR 0006 remains authoritative for external authentication ownership; the new + ADR supersedes only token-role and local-profile authorization semantics. +- Explicit human approval of proposed L0 decisions D4-D10 is recorded before + this chunk becomes active; the chunk does not infer approval from plan review. +- Repository engineering policy assigns external token verification to the + existing auth adapter/dependency boundary and canonical local permission, + grant, idempotency, and invalidation ownership to + `backend/app/modules/authorization`; obsolete + `backend/app/core/permissions.py` ownership is removed. +- Canonical docs no longer describe token roles or typed ActorProfiles as + Workstream product authority. +- Every active operational, product-flow, checker-framework, template, + architecture, decision, diagram, and bounded specification document found by + the deterministic authority scan is either reconciled in this chunk or + classified by an explicit reviewed historical/archive allowlist. The scanner + has known-bad fixtures and fails if an active authority claim is omitted. +- The five administrative grants and exact-project contributor grants use + consistent names. +- Imported Markdown/PDF pairs are reconciled or explicitly labeled so they do + not silently disagree. +- All eight imported files remain byte-immutable archival planning inputs. + `docs/reference_specs/README.md` records their hashes/status and the + `/api/v1` override; canonical reconciled text lives separately in + `docs/spec_authorization_service.md`. +- `WS-POL-002-03` is durably paused behind the auth initiative. +- Roadmap status names WS-AUTH-001 as current priority, records POL-002 chunks + 01/02 merged and 03 paused, and defers review implementation until auth proof. +- Canonical vocabulary distinguishes `worker` as a task-lifecycle persona and + attribution term from `submitter` as the persisted exact-project grant. +- Every current operational override/repair command is inventoried and assigned + a precise registered Project Manager or Operator permission for later chunks; + any additive permission is approved in the ADR rather than invented in code. +- An authorization operations runbook assigns ownership for issuer/JWKS + configuration and outages, key rotation, introspection policy, bootstrap + custody, legacy classification, staged rollout/rollback, alerts, and live + proof; later chunks fill in executable commands. + +## Verification commands + +```bash +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +python3 scripts/check_stale_authorization_docs.py +python3 scripts/test_agent_gates.py +sha256sum -c docs/reference_specs/SHA256SUMS +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- docs +- reuse/dedup +- CI integrity +- test delta + +## Human review focus + +Confirm authority precedence, role names, `/api/v1`, and that no unrelated +review/compensation decisions were adopted accidentally. + +## Stop conditions + +Stop if the source PDF cannot be regenerated or labeled without ambiguity, or +if reconciling auth requires changing review/compensation behavior. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-02-verified-issuer-token.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-02-verified-issuer-token.md new file mode 100644 index 000000000..ca4812062 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-02-verified-issuer-token.md @@ -0,0 +1,140 @@ +# Chunk Contract: WS-AUTH-001-02 - Verified Issuer Token And JWKS Boundary + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Implement a fail-closed final-token verifier with pinned issuer, audience, +algorithms, JWKS resolution/refresh, temporal claims, subject kind, coarse +scope, and typed verification errors. This chunk authenticates only; it does +not grant Workstream product authority. + +## Why this chunk exists + +The current production verifier is intentionally unimplemented and the local +HS256 fixture lacks the adopted token contract. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/pyproject.toml +backend/app/core/config.py +backend/app/core/auth.py +backend/app/interfaces/auth.py +backend/app/adapters/auth/** +backend/app/api/deps/auth.py +backend/app/schemas/auth.py +backend/tests/test_auth.py +backend/tests/test_config.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +actor/grant database models or migrations +product permission checks +project/task/checker authorization changes +Workstream token issuance, login, sessions, or passwords +trusting token role claims +``` + +## Acceptance criteria + +- `VerifiedIssuerToken` contains only verified identity/coarse-access claims. +- Signature, issuer, audience, `exp`, `iat`, optional `nbf`, `jti`, + `subject_kind`, and required scope are validated. +- Algorithms are configured and pinned; unknown `kid` triggers at most one + controlled refresh. +- Valid cached keys obey bounded cache policy; unavailable verification fails + closed. +- An explicit introspection/revocation mode is configured. Production rejects + an unspecified mode; disabled mode is explicit and evidence-backed by issuer + policy. Enabled bearer-token introspection uses a separately configured, + allowlisted HTTPS endpoint, POSTs only to that origin, follows no redirects, + has strict connect/read/total timeout and response-size bounds, and redacts + request credentials and responses from logs/errors. A `jti`-only issuer + contract is allowed only when documented and tested explicitly. +- JWKS retrieval and credential-bearing introspection use separate clients and + policies. JWKS requires HTTPS outside deterministic local tests, follows no + redirects, and enforces response-size and key-count bounds. Tests prove a + bearer token never reaches JWKS or any redirect target. +- Human/service scope differs; agent and Space kinds are represented for the + resolver to deny later. +- Token roles do not appear in the verified authority contract. +- The existing `AuthVerifier` protocol/factory is evolved in place; no second + TokenVerifier hierarchy is created. +- Untouched legacy product surfaces receive one explicitly named + `LegacyAuthorizationCompatibilityContext` built only after successful final + token verification. It may expose verified legacy role claims only to an + enumerated shrinking allowlist of unmigrated dependencies. Migrated surfaces + cannot import or consume it, and chunk 15 removes it completely. +- The API drill is updated to emit the final token claim shape (`jti`, + `subject_kind`, scope, issuer/audience/time) while retaining legacy roles only + for the bounded compatibility context. Intake remains executable and no test + treats the canonical verified-token type as role authority. +- Dev/local fixtures remain impossible in staging/production. +- Any production dependency addition is narrowly justified and reviewed. +- No production dependency is added until explicit human approval is durably + recorded; absence of that approval stops the chunk. +- The operations runbook assigns ownership for issuer/JWKS configuration, key + rotation, cache bounds, introspection policy, outage response, and evidence. +- Bounded-cardinality metrics for verification result, JWKS cache hit/refresh, + refresh failure, and introspection result are emitted at the verifier boundary + without issuer subject, token, `jti`, key material, or raw URL labels. + +## Verification commands + +```bash +(cd backend && .venv/bin/python -m ruff check app tests) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Inspect algorithm pinning, JWKS outage behavior, cache refresh, scope/subject +classification, dependency choice, and absence of role authority. + +## Stop conditions + +Stop if verification requires accepting unverified claims, permissive algorithm +selection, a shared production HMAC secret, forwarding bearer tokens anywhere +except the separately approved/configured introspection origin under the rules +above, a second verifier hierarchy, or an unapproved production dependency. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-03-legacy-actor-classification.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-03-legacy-actor-classification.md new file mode 100644 index 000000000..8e26638dd --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-03-legacy-actor-classification.md @@ -0,0 +1,112 @@ +# Chunk Contract: WS-AUTH-001-03 - Legacy Actor Classification Preflight + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Provide the supported, versioned, dry-run-first workflow that classifies every +legacy actor identity before canonical actor migration without inference or +manual SQL. + +## Why this chunk exists + +Current rows have issuer and subject but no trustworthy `subject_kind`. +Migration cannot safely guess human versus service. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/actors/legacy_classification.py +backend/scripts/legacy_actor_classification.py +backend/tests/test_actor_legacy_classification.py +backend/tests/test_actors.py +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +database schema migration +actor/profile persistence changes +authority grants +classification inferred from email, subject syntax, token roles, or profiles +writing production data without an explicit apply operation +``` + +## Acceptance criteria + +- Manifest schema is versioned JSON with exact legacy actor ID, canonical + issuer, opaque subject, and `subject_kind`. +- Unknown fields/kinds, invalid UUIDs, duplicate actor or issuer/subject, + mismatches, missing database rows, and stale extra rows are rejected. +- Dry-run is the default and produces a checksum-bound, privacy-bounded report. +- The tool emits one canonical immutable envelope to an operator-selected path + outside the repository. The envelope contains schema version, sorted actor + classifications, source row-set SHA-256, manifest SHA-256, generated-at, and + a non-secret database binding identifier. +- The row-set digest covers the complete sorted legacy identity projection + needed by migration. Any row change after export changes the digest. +- Alembic later locates the envelope only through + `WORKSTREAM_LEGACY_ACTOR_CLASSIFICATION_FILE`, verifies canonical checksum, + recomputes the live row-set digest inside the migration transaction, and + aborts on TOCTOU mismatch, missing/extra rows, or database-binding mismatch. +- Envelope generation is deterministic and idempotent and never writes grants + or actor state. +- Empty registries need no manifest and produce an explicit empty proof. +- Runbook defines owner, secure manifest custody, dry-run/export sequence, + evidence retention, environment-specific secure path, Alembic handoff, + failure recovery, rollback boundary, and post-migration deletion of the + classification file after its checksum/version are recorded durably. +- Tests prove deterministic output and fail-closed mismatch behavior. + +## Verification commands + +```bash +(cd backend && .venv/bin/python -m ruff check app tests scripts) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review that classification is explicit, complete, checksum-bound, +privacy-bounded, and unusable as a grant or ad hoc migration bypass. + +## Stop conditions + +Stop if any existing subject kind must be inferred or if the only workable path +requires manual SQL or production identity-provider payloads. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-04-request-api-controls.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-04-request-api-controls.md new file mode 100644 index 000000000..53ccdd1ae --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-04-request-api-controls.md @@ -0,0 +1,113 @@ +# Chunk Contract: WS-AUTH-001-04 - Request, Error, And API Control Foundation + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Introduce request/correlation context, the stable structured error envelope, +and Postgres-backed security endpoint rate controls without changing product +authorization. + +## Why this chunk exists + +First-access and authority-management APIs require consistent request evidence, +private errors, and cross-replica abuse controls before they ship. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/main.py +backend/app/api/deps/** +backend/app/core/api_controls.py +backend/app/core/config.py +backend/app/modules/api_controls/** +backend/app/db/models.py +backend/alembic/versions/0015_*.py +backend/tests/test_app.py +backend/tests/test_auth.py +backend/tests/test_alembic.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +actor/profile migration or first-access behavior +authority audit/idempotency/grant models +permission evaluation or product authorization cutover +dependency overrides that bypass the real request path +``` + +## Acceptance criteria + +- Every request has validated request/correlation IDs with safe generation and + propagation rules. +- Errors use a stable envelope without raw exception, token, claims, or PII. +- Postgres-backed rate controls work across replicas, use privacy-safe keys and + database time, and are configurable for first access/admin mutations. +- `backend/app/modules/api_controls` owns feature models, persistence queries, + and the rate-control service. Middleware/dependencies invoke that service; + generic core/API helpers do not own SQL or transaction policy. +- Allowed requests proceed, exceeded limits return stable 429, and unavailable + controls fail closed without bypassing protected operations. +- Migration owns counter constraints/indexes/database-time fields and proves + prior-head upgrade, downgrade, and re-upgrade. +- Full backend suite and the intentionally updated API contract drill preserve + the existing intake lifecycle and error compatibility. + +## Verification commands + +```bash +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic downgrade -1) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && .venv/bin/python -m ruff check app tests scripts) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review error compatibility/privacy, rate-limit keying/failure behavior, +database-time semantics, and unchanged product authority. + +## Stop conditions + +Stop if rate controls require a new production dependency without explicit +human approval or if existing intake assertions must be weakened. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-05-authority-evidence-foundation.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-05-authority-evidence-foundation.md new file mode 100644 index 000000000..c14bd5a06 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-05-authority-evidence-foundation.md @@ -0,0 +1,125 @@ +# Chunk Contract: WS-AUTH-001-05 - Authority Evidence And Idempotency Foundation + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Evolve the existing `audit_events`/`AuditRepository` path with canonical +mutation idempotency and authority invalidation evidence before actor or grant +mutations use it. + +## Why this chunk exists + +Authority-changing APIs cannot ship with temporary provenance that a later +chunk would need to reconstruct. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/audit/** +backend/app/modules/authorization/** +backend/app/db/models.py +backend/app/modules/tasks/models.py +backend/app/modules/tasks/repository.py +backend/app/modules/tasks/service.py +backend/alembic/versions/0016_*.py +backend/tests/test_auth.py +backend/tests/test_alembic.py +backend/tests/test_tasks.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +actor/profile migration or first-access behavior +permission/grant evaluation +admin/project grants +project/task/checker authorization cutover +backfilling fabricated authority events +``` + +## Acceptance criteria + +- Idempotency rows store canonical request hash, operation, actor reference, + status, and bounded committed response reference with unique replay guards. +- Authority events have final versioned fields for acting/target references, + matched grant, project, reason, idempotency key, bounded before/after state, + and database time. +- Existing `AuditEvent` is extended; no parallel AuthorityEvent table/model is + created. `TaskRepository.add_audit_event` delegates to or is consolidated + with `AuditRepository` so one shared writer owns persistence. +- Audit persistence exposes insert/read operations only through supported + application paths. Migration-level enforcement rejects authority-event + update/delete by the application database role; focused tests prove attempts + fail without changing prior rows. The runbook defines retention and the + separately controlled privileged database-maintenance path. +- Authorization-owned idempotency models, repository queries, and service + orchestration remain separate feature modules. Middleware, generic core + helpers, and ORM models do not own replay queries or transaction policy. +- The injected AsyncSession remains the UnitOfWork boundary. Caller services + own commit/rollback; no generic parallel UoW wrapper is introduced. +- Session-bound repository APIs let callers atomically persist business state, + idempotency result, authority event, and invalidation event. +- Migration owns idempotency/audit constraints, indexes, UTC/database-time + defaults, prior-head upgrade, downgrade, and re-upgrade. +- Each later authority-mutation chunk must test its specified successful and + denied audit events atomically when that behavior is introduced; chunk 16 + cannot backfill an omitted event. + +## Verification commands + +```bash +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic downgrade -1) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && .venv/bin/python -m ruff check app tests) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review reuse of AuditEvent/AuditRepository, idempotency uniqueness/replay, +AsyncSession transaction ownership, and unchanged product authorization. + +## Stop conditions + +Stop if final evidence requires a canonical ActorProfile FK before migration; +use stable actor-reference fields rather than introducing a parallel actor. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-06-canonical-actor-profile.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-06-canonical-actor-profile.md new file mode 100644 index 000000000..5ade53e69 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-06-canonical-actor-profile.md @@ -0,0 +1,147 @@ +# Chunk Contract: WS-AUTH-001-06 - Canonical Actor Profile And Identity Link + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Replace the current identity-plus-typed-profile registry with one canonical +ActorProfile, one ActorIdentityLink, atomic first-human provisioning, and +request-time actor resolution using the established evidence foundation. + +## Why this chunk exists + +All later grants and revocation depend on an unambiguous local actor and active +identity link. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/actors/** +backend/app/modules/tasks/service.py +backend/app/modules/tasks/router.py +backend/app/modules/tasks/schemas.py +backend/app/api/deps/auth.py +backend/app/api/routes/auth.py +backend/app/schemas/auth.py +backend/app/db/models.py +backend/app/modules/audit/** +backend/alembic/versions/0017_*.py +backend/tests/test_actors.py +backend/tests/test_auth.py +backend/tests/test_alembic.py +backend/tests/test_projects.py +backend/tests/test_tasks.py +backend/tests/test_checkers.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +admin or project grant creation +product-surface authorization cutover +implicit grants from legacy roles/profiles +guessing human/service kind from email, subject shape, or role claims +review or compensation models +``` + +## Acceptance criteria + +- Exactly one canonical ActorProfile maps to one issuer/subject identity link. +- First valid human access creates profile, link, and audit evidence atomically. +- Concurrent first access creates no duplicate or orphan. +- Unknown service creates nothing and is denied; agent/Space creates nothing. +- Suspended/deactivated schema states exist but administration remains later. +- Existing actor IDs are preserved only through explicit safe classification. +- Classified legacy `ActorIdentity.actor_id` must parse as UUID and becomes the + canonical `ActorProfile.id`; legacy typed profile row IDs never do. +- Non-empty ambiguous legacy data fails with a precise remediation path. +- Typed legacy profiles do not become grants. +- Existing task claim/start/submission eligibility remains operable only through + `LegacyWorkflowEligibilityCompatibility`, which reads classified legacy + workflow metadata and grants no product permission. Its exact task-service + consumers are enumerated in a shrinking allowlist: chunk 13 removes + queue/claim/start, chunk 14 removes submission and deletes the now-unused + adapter/allowlist, and chunk 15 proves no obsolete path remains. +- Until exact-project grants exist, `/api/v1/workers/me/profile` remains an + enumerated compatibility activation path for fresh-database intake. It writes + only classified non-authoritative workflow metadata, cannot populate the + canonical actor or grant models from token roles, and authorizes old workflow + call sites only through `LegacyWorkflowEligibilityCompatibility`. The API + drill proves first-human provisioning plus this supported compatibility path; + chunk 13 removes its queue/claim/start consumers after grant provisioning; + chunk 14 removes the route, activation service/schema, token-role observation + fields, final submission consumer, and compatibility adapter together. +- Provisioning uses the established idempotency/audit/invalidation foundation + and commits the profile, link, and events atomically. +- `/api/v1/actors/me` returns Contributor domain with no implied project/admin + authority. +- `GET /api/v1/actors/me` and `PATCH /api/v1/actors/me` have request, privacy, + unknown-field, negative-state, first-access allow/429, and + rate-control-unavailable tests. +- Migration enforces every ActorProfile/ActorIdentityLink check, unique and + supporting index, one-link ownership constraint, UTC/database-time field, + and immutable-history rule from the adopted contract. +- Tests cover prior-head upgrade, failed ambiguous upgrade, successful + classified upgrade, and preserved attribution. Downgrade/rollback must + succeed after the external classification envelope is deleted post-upgrade, + using only its durably recorded version/checksum and migration state. + +## Verification commands + +```bash +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic downgrade -1) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && .venv/bin/python -m ruff check app tests) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review legacy-data handling, UUID identity preservation, atomic provisioning, +final audit/idempotency ownership, and proof that no old profile row grants +authority. + +## Stop conditions + +Stop if legacy subject kind cannot be established safely, migration would +orphan historical actor references, an intermediate release cannot execute the +existing intake lifecycle, or a parallel canonical profile is needed. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-07-authorization-kernel.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-07-authorization-kernel.md new file mode 100644 index 000000000..cc2577dc3 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-07-authorization-kernel.md @@ -0,0 +1,119 @@ +# Chunk Contract: WS-AUTH-001-07 - Authorization Kernel And Permission Registry + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Implement the deny-by-default AuthorizationService kernel, registered +permissions, canonical ResourceContext, stable AuthorizationDecision, actor +state/global guards, and reusable FastAPI/application dependencies before any +grant-management API. + +## Why this chunk exists + +Every later protected grant and product command must call one service rather +than introducing temporary direct grant queries. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/authorization/** +backend/app/modules/actors/repository.py +backend/app/modules/actors/service.py +backend/app/api/deps/auth.py +backend/app/api/router.py +backend/app/main.py +backend/app/schemas/auth.py +backend/tests/test_auth.py +backend/tests/test_actors.py +backend/tests/test_app.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +docs/spec_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +dynamic/user-authored permissions or policy language +authorization decision cache across requests +direct role queries in routers +project/task/checker surface cutover +review/compensation permissions beyond registered future definitions +``` + +## Acceptance criteria + +- Permission identifiers are a closed registered enum/value set. +- Request context contains verified identity plus current local actor/grant state + and correlation/request IDs. +- Authorization resolves actor/link state, grant candidates, canonical project, + ownership, global guards, and resource state in a defined order. +- Unknown permissions deny. +- System scope is not superuser authority. +- Suspended/deactivated/revoked actors deny before permission expansion. +- Stable structured error envelope and concealment rules are defined. +- Table-driven tests cover every current permission/role/scope candidate. +- Sensitive mutations can revalidate inside the caller transaction/UoW. +- The bootstrap system operation and default-human self permissions are the + only usable candidates before grant tables ship. +- Direct role/grant authorization outside this service is explicitly banned. +- `GET /api/v1/authorization/permissions` and + `GET /api/v1/authorization/admin-role-definitions` return registered, + non-dynamic definitions with authorization/privacy tests. +- `GET /api/v1/actors/me/authorization-context` is introduced here, after the + closed registry exists, with project-scoped capability allowlist, privacy, + unknown-project, and inactive-actor tests. + +## Verification commands + +```bash +(cd backend && .venv/bin/python -m ruff check app tests) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review deny-by-default ordering, permission matrix completeness, canonical +resource scope, concealment behavior, and transaction-local revalidation. + +## Stop conditions + +Stop if the service needs free-form client permissions, cached cross-request +decisions, or a generic policy engine. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-08-bootstrap-admin-grants.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-08-bootstrap-admin-grants.md new file mode 100644 index 000000000..e90834bdd --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-08-bootstrap-admin-grants.md @@ -0,0 +1,134 @@ +# Chunk Contract: WS-AUTH-001-08 - Bootstrap And Administrative Role Grants + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Implement the one-time local bootstrap operation, `AuthorityControl`, immutable +AdminRoleGrants, scope compatibility, grant/revoke APIs, idempotency, and +concurrency-safe final-access-administrator protection. + +## Why this chunk exists + +Workstream needs durable administrative authority before project permissions +or product surfaces can stop using token roles. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/actors/** +backend/app/modules/authorization/** +backend/app/api/router.py +backend/app/db/models.py +backend/scripts/bootstrap_access_administrator.py +backend/app/modules/audit/** +backend/alembic/versions/0018_*.py +backend/tests/test_actors.py +backend/tests/test_auth.py +backend/tests/test_alembic.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +public bootstrap endpoint or shared bootstrap bearer secret +project contributor grants +product surface cutover +self-grant or self-revoke +service actors receiving admin grants +deleting revoked grants +``` + +## Acceptance criteria + +- Bootstrap is a local management operation for an existing active human. +- Bootstrap locks `AuthorityControl(id = 1) FOR UPDATE` before checking for an + effective administrator, then creates the initial grant, one-time state, and + audited success atomically in that transaction. +- Bootstrap succeeds exactly once and records the fixed system bootstrap actor. +- Every later bootstrap attempt fails with the stable conflict and is audited; + bootstrap is never treated as an idempotent replay. +- Five admin roles and their system/project scope compatibility are enforced. +- Only an effective Access Administrator issues/revokes admin grants. +- Admin-grant create/revoke identical replay is idempotent; mismatched replay + is rejected. +- State, idempotency result, authority event, and invalidation event commit + atomically for every grant/revoke operation. +- Self-grant and self-revoke are denied. +- The shared `AuthorityControl` row serializes bootstrap and every operation + that can remove the final effective Access Administrator. +- Migration enforces `AuthorityControl(id = 1)`, role/scope compatibility, + active-grant partial uniqueness, ownership FKs, supporting indexes, and + database-time timestamps. +- A PostgreSQL concurrency test runs simultaneous bootstrap attempts for + different target profiles and proves exactly one grant, one audited success, + and one audited stable conflict. Additional concurrent tests prove at least + one effective Access Administrator remains. +- Bootstrap, self-grant/self-revoke, and final-administrator denials emit their + specified authority events in the owning transaction. +- `POST/GET /api/v1/admin-role-grants`, + `GET /api/v1/actors/{actor_profile_id}/admin-role-grants`, and + `POST /api/v1/admin-role-grants/{grant_id}/revoke` have + allow/deny/scope/privacy/rate-limit/replay tests. +- Migration tests cover immutable revoked history, prior-head upgrade, + downgrade, and preserved attribution. +- Runbook assigns bootstrap custody, environment access, dry-run/evidence, + retry/failure handling, and rollback responsibility. + +## Verification commands + +```bash +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic downgrade -1) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && .venv/bin/python -m ruff check app tests scripts) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review bootstrap exposure, idempotency, role/scope matrix, and the exact lock +used for final-admin safety. + +## Stop conditions + +Stop if bootstrap requires SQL edits, a public secret, or final-admin safety is +implemented by an unlocked count. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-09-actor-state-service-actors.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-09-actor-state-service-actors.md new file mode 100644 index 000000000..768bbfd83 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-09-actor-state-service-actors.md @@ -0,0 +1,115 @@ +# Chunk Contract: WS-AUTH-001-09 - Actor State, Identity Revocation, And Service Actors + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Implement actor suspension/reactivation/deactivation, identity-link +revoke/reactivate, controlled service-actor provisioning, and their audited, +idempotent authority invalidation behavior. + +## Why this chunk exists + +Local role grants are incomplete without immediate actor/link invalidation and +controlled service identity resolution. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/actors/** +backend/app/modules/authorization/** +backend/app/api/router.py +backend/app/modules/audit/** +backend/tests/test_actors.py +backend/tests/test_auth.py +backend/tests/test_alembic.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +docs/spec_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +service actors receiving Contributor domain, AdminRoleGrant, or ProjectRoleGrant +deactivation reversal +deleting actors, links, grants, or historical attribution +product adapter bindings or callback endpoints +``` + +## Acceptance criteria + +- Suspension blocks later business mutations and is reversible. +- Deactivation is terminal and preserves history. +- Identity-link revocation blocks the same issuer subject immediately; link + reactivation restores the same profile without duplication. +- Identity-link revocation for an effective Access Administrator takes the same + `AuthorityControl FOR UPDATE` lock as grant/profile removal. +- Self-suspension/deactivation and final-admin loss are denied under the shared + authority lock. +- Unknown services are denied without persistence. +- Access Administrator can pre-provision a service profile/link with reason. +- Agent and Space subjects remain unsupported and unpersisted. +- Every mutation is idempotent, reasoned, and audited without token material. +- Mixed concurrent link-revoke, grant-revoke, suspend, and deactivate attempts + leave at least one authenticatable effective Access Administrator. +- `GET /api/v1/admin/actors`, actor detail, suspend/reactivate/deactivate, + actor identity-link read, identity-link revoke/reactivate, and service actor + create/list/detail routes from the adopted contract have + allow/deny/privacy/rate-limit/replay tests. +- Suspension/link-revocation events explicitly require consuming lifecycle + reconciliation; task assignment consumption belongs to chunk 13 and review + lease consumption remains deferred to WS-REV-001. + +## Verification commands + +```bash +(cd backend && .venv/bin/python -m ruff check app tests) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review state-machine terminality, service separation, invalidation timing, and +preservation of historical records. + +## Stop conditions + +Stop if a consuming adapter authority must be invented or if a service must be +treated as a human/admin to make tests pass. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-10-project-role-grants.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-10-project-role-grants.md new file mode 100644 index 000000000..fb16eda07 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-10-project-role-grants.md @@ -0,0 +1,143 @@ +# Chunk Contract: WS-AUTH-001-10 - Project Qualification And Contributor Role Grants + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Implement immutable qualification snapshots and exact-project +`ProjectRoleGrant(submitter|reviewer|both)` create, replace, list, and revoke +behavior under scoped Project Manager authority. + +## Why this chunk exists + +Project contributor authority must be durable, explicit, revocable, and +separate from both token claims and administrative roles. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/actors/** +backend/app/modules/authorization/** +backend/app/modules/projects/models.py +backend/app/modules/projects/repository.py +backend/app/api/router.py +backend/app/db/models.py +backend/app/modules/audit/** +backend/alembic/versions/0019_*.py +backend/tests/test_actors.py +backend/tests/test_projects.py +backend/tests/test_auth.py +backend/tests/test_alembic.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +docs/spec_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +automated grants from skills or reputation +self-grant or self-revoke of the issuer's own contributor grant through the +administrative project-grant operation +admin roles satisfying submitter/reviewer permissions +task/review lifecycle implementation +project/task/checker authorization cutover +``` + +## Acceptance criteria + +- Snapshot is immutable, project/contributor-bound, privacy-bounded, and + records unavailable evidence explicitly. +- Qualification snapshots and ProjectRoleGrants are owned by the authorization + module; ActorProfile/IdentityLink remain actor-owned, and ProjectRepository + remains the canonical project loader. No duplicate grant/project repository + is introduced. +- Only a Project Manager whose active grant covers the project can issue or + revoke a contributor grant. +- The same covered `project.role_grant.manage` permission provides a scoped, + paginated contributor-candidate lookup for the grant workflow. It returns + only minimal actor fields, filters unauthorized rows before totals/cursors, + cannot enumerate unrelated project activity, and never exposes issuer + subject, identity-link metadata, contact data, skills, or reputation as + authority. +- Target must be an active human and cannot be the issuing manager. +- A manager who separately holds a contributor grant cannot revoke that grant + through their own administrative request; denial is stable and audited. +- At most one active grant exists per contributor/project. +- Replacement revokes old history and creates a new grant atomically. +- Create, replace, and revoke require canonical request hashing: same key and + same request returns the committed graph; same key with different request is + rejected. +- State, idempotency result, audit event, and invalidation event commit in one + transaction. +- Only manual creation is enabled; automated schema value cannot be emitted. +- Revocation is visible on the next authorization context build. +- Project manager/admin role alone never creates contributor capability. +- PostgreSQL concurrency tests cover identical creates, conflicting creates, + replace versus revoke, and revocation versus authorization. +- `POST/GET /api/v1/projects/{project_id}/role-grants`, grant detail, and grant + revoke routes have multi-role, self-revoke, scope, privacy, rate-limit, + replay, and negative tests. +- Contributor-candidate lookup has covered-manager allow, uncovered/cross- + project deny, pagination/count concealment, minimal-field, rate-limit, and + inactive/non-human exclusion tests; no UUID must be recovered from logs or + direct database access. +- Migration enforces snapshot/grant ownership FKs, checks, partial unique and + supporting indexes, database-time fields, immutability, prior-head upgrade, + downgrade, and preserved history. + +## Verification commands + +```bash +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic downgrade -1) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && .venv/bin/python -m ruff check app tests) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review exact-project scope, self-grant protection, snapshot privacy, replacement +atomicity, and absence of implicit grants. + +## Stop conditions + +Stop if contributor authority depends on a token role, inferred qualification, +or project ID supplied without canonical database resolution. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-11-project-read-cutover.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-11-project-read-cutover.md new file mode 100644 index 000000000..a663db6d7 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-11-project-read-cutover.md @@ -0,0 +1,112 @@ +# Chunk Contract: WS-AUTH-001-11 - Project Identity, Guide, Source, And Visibility Cutover + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Move project identity reads, guide/source reads, setup-status visibility, and +policy-summary queries from token roles to scoped local permissions without +changing project setup mutations. + +## Why this chunk exists + +Separating query/visibility cutover from policy mutations keeps project +authorization reviewable and proves anti-IDOR filtering before write cutover. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/projects/router.py +backend/app/modules/projects/service.py +backend/app/modules/projects/repository.py +backend/app/modules/projects/schemas.py +backend/app/modules/authorization/** +backend/app/api/deps/auth.py +backend/tests/test_projects.py +backend/tests/test_auth.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +project create/update, guide/source mutation, policy approval, activation +task/submission/checker authorization +token role fallback or authorization pagination after unfiltered counts +``` + +## Acceptance criteria + +- Project Managers read only covered projects; system scope covers all projects + only for registered Project Manager permissions. +- Operator, Finance Authority, and Audit Authority receive only their defined + minimal project views. +- Access Administrator and contributor grants do not imply project-management + visibility. +- Active submitter, reviewer, and both grants allow the minimal `project.read` + projection for their exact project, without management fields or permissions. + Each role has exact-project allow, cross-project deny, minimal-field, + concealed-not-found, and pre-filtered count/cursor tests. +- Canonical project scope is resolved before filtering, counts, and cursors. +- ProjectRepository remains the canonical project/guide/source persistence + query owner and returns domain records. The project application service or a + feature-owned resource loader composes ResourceContext; persistence does not + depend on authorization DTOs, and authorization does not re-query project + tables through a parallel repository. +- Hidden project/resource existence is not leaked through errors or totals. +- No migrated query uses `require_any_role()` or token roles. +- Full backend suite and API contract drill pass. + +## Verification commands + +```bash +(cd backend && .venv/bin/python -m ruff check app tests scripts) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review query scope, minimal disclosure, pagination/count concealment, and that +mutation behavior is untouched. + +## Stop conditions + +Stop if safe query cutover requires changing setup mutations or accepting a +token-role fallback. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12-project-mutation-cutover.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12-project-mutation-cutover.md new file mode 100644 index 000000000..a4eed9c3e --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-12-project-mutation-cutover.md @@ -0,0 +1,111 @@ +# Chunk Contract: WS-AUTH-001-12 - Project Policy And Setup Mutation Cutover + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Move project creation, guide/source mutation, policy derivation/approval, +activation, and repair operations to local scoped permissions and matched-grant +provenance. + +## Why this chunk exists + +This completes project authorization and unblocks later resumption of +`WS-POL-002-03` without implementing that paused chunk. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/projects/** +backend/app/modules/authorization/** +backend/app/api/deps/auth.py +backend/app/workers/project_setup.py +backend/alembic/versions/0020_*.py +backend/tests/test_projects.py +backend/tests/test_auth.py +backend/tests/test_alembic.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +WS-POL-002-03 post-submit approval/visibility behavior +task/submission/checker authorization +review or compensation behavior +unscoped project-manager access or token role fallback +``` + +## Acceptance criteria + +- System-scoped Project Manager can create projects; scoped managers mutate + only covered projects. +- Project policy actions use registered permissions and transaction-local grant + revalidation. +- Approval provenance records matched local grant/actor/scope while preserving + historical bootstrap provenance. +- Migration `0020` adds matched local grant/scope provenance and ownership + constraints to project policy approval records without rewriting historical + bootstrap values; prior-head upgrade, downgrade, and re-upgrade preserve + readable history. +- Internal setup worker uses explicit system permission and current context. +- State, idempotency, audit, and invalidation effects remain atomic. +- No project mutation uses `require_any_role()` or token roles. +- Full backend suite and API contract drill pass. + +## Verification commands + +```bash +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic downgrade -1) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/alembic upgrade head) +(cd backend && .venv/bin/python -m ruff check app tests scripts) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review project scope, transaction-time authority, approval provenance, system +worker behavior, and strict exclusion of WS-POL product changes. + +## Stop conditions + +Stop if project scope cannot be derived canonically or post-submit approval +behavior must change. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-13-task-assignment-cutover.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-13-task-assignment-cutover.md new file mode 100644 index 000000000..4071e1b57 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-13-task-assignment-cutover.md @@ -0,0 +1,150 @@ +# Chunk Contract: WS-AUTH-001-13 - Task Management And Assignment Cutover + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Move task creation/screen/release, queue visibility, worker claim, assignment, +and start operations to scoped manager or exact-project submitter permissions +while preserving task lifecycle guards. + +## Why this chunk exists + +Task and assignment authorization can be reviewed independently from submission +and checker visibility. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/tasks/router.py +backend/app/modules/tasks/service.py +backend/app/modules/tasks/authorization.py +backend/app/modules/tasks/repository.py +backend/app/modules/tasks/schemas.py +backend/app/modules/tasks/models.py +backend/app/modules/tasks/lifecycle.py +backend/app/modules/authorization/** +backend/app/api/deps/auth.py +backend/app/workers/authority_reconciliation.py +backend/app/workers/celery_app.py +backend/tests/test_tasks.py +backend/tests/test_auth.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +submission create/read/finalize +checker trigger/read or worker checker results +new terminal task states or unrelated lifecycle redesign +review models or self-review implementation +token role or legacy active-worker-profile fallback +``` + +## Acceptance criteria + +- Scoped Project Manager manages tasks only in covered projects. +- TaskRepository remains the canonical task/assignment persistence loader and + returns domain records. The task application service or a feature-owned + resource loader composes ResourceContext; persistence does not depend on + authorization DTOs, and authorization does not duplicate task queries. +- Submitter/both grant for the exact project is required for queue/claim/start, + plus existing task availability, assignment, ownership, and state guards. +- Administrative roles alone cannot claim contributor work. +- Existing Project Manager task management remains project-scoped. The + reasoned start override is separately authorized as + `operations.task.start_override` for Operator recovery; matched permission, + scope, reason, and audit event distinguish the two paths. +- Operator `operations.status.read` exposes a read-only cross-project task-queue + operational projection with bounded fields; it does not grant task mutation. + Audit Authority `audit.read` exposes only covered task evidence. Both paths + filter before counts/cursors, conceal unauthorized resources, redact worker + details, and have explicit mutation-denial tests. +- ProjectRoleGrant revocation and ActorProfile suspension/deactivation/link + revocation reconcile exclusive submitter assignments idempotently. For + `claimed` or `in_progress`, the active assignment closes as + `authority_revoked`, the task returns to `ready`, `assigned_to` clears, and + immutable prior work/audit history remains. A `needs_revision` task instead + remains `needs_revision` with a durable unassigned revision obligation. A + covered manager may reassign it only to an active exact-project submitter; + the replacement receives the bounded worker-visible prior findings, prepared + revision context, supersession linkage, and replay requirements. Reactivation + does not restore the old assignment. Submitted/evaluation/review-pending + history is not rewritten. +- Reconciliation uses the authority invalidation event plus a durable worker or + same-transaction consuming service with retry/ownership defined in the + runbook. Review-lease reconciliation remains explicitly deferred to + WS-REV-001. +- When the durable worker path is selected, it is explicitly registered in the + existing Celery include list; no second worker registry or scheduler is added. +- Revoked/suspended actors fail on the next request and transaction recheck. +- Cross-project/worker visibility remains concealed. +- No migrated operation uses token roles or `require_any_role()`. +- Task queue/claim/start remove their enumerated + `LegacyWorkflowEligibilityCompatibility` consumers; only the submission + consumer remains for chunk 14. +- The API drill provisions an exact-project submitter/both grant through the + supported service/API path before claim. The legacy workflow-profile route + remains bounded only because chunk 14 still owns the final submission + compatibility consumer; task queue/claim/start no longer depend on it. +- Full backend suite and API contract drill pass. +- Tests cover revoke/suspend/reactivate before claim, while claimed, while in + progress, at needs-revision, after submit, duplicate reconciliation, worker + retry, a second contributor reclaiming released work, and needs-revision + reassignment preserving prior findings, locked/rebased context, submission + supersession, and high/medium finding replay requirements. + +## Verification commands + +```bash +(cd backend && .venv/bin/python -m ruff check app tests scripts) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review exact-project contributor grants, manager scope, assignment guards, and +absence of lifecycle changes. + +## Stop conditions + +Stop if submission/checker behavior or task lifecycle semantics outside the +explicit authority-invalidation assignment release must change. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-14-submission-checker-cutover.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-14-submission-checker-cutover.md new file mode 100644 index 000000000..889dddeed --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-14-submission-checker-cutover.md @@ -0,0 +1,132 @@ +# Chunk Contract: WS-AUTH-001-14 - Submission, Checker, And Audit Visibility Cutover + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Move submission precheck/create/read/finalize, checker trigger/read, checker +worker projections, and task audit visibility to registered permissions and +canonical task/project/assignment guards. + +## Why this chunk exists + +Submission/checker authorization shares locked-task provenance and worker +redaction rules but is separable from task claim and system-worker execution. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/tasks/router.py +backend/app/modules/tasks/service.py +backend/app/modules/tasks/schemas.py +backend/app/modules/actors/** +backend/app/modules/checkers/router.py +backend/app/modules/checkers/service.py +backend/app/modules/authorization/** +backend/app/api/deps/auth.py +backend/tests/test_tasks.py +backend/tests/test_checkers.py +backend/tests/test_auth.py +backend/scripts/api_contract_e2e.py +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +checker execution/routing semantics or task lifecycle redesign +internal Celery worker authority cutover +review queue/lease/decision implementation +token role fallback +legacy active-worker-profile or workflow-eligibility compatibility fallback +``` + +## Acceptance criteria + +- Exact-project submitter/both grant plus active assignment is required to + create submissions; admin roles alone cannot submit. +- Manager repair/checker triggers require covered project permissions. +- Project Manager repair uses covered `project.task.manage`; Operator recovery + uses distinct `operations.submission_gate.repair` and + `operations.checker.retry` permissions. Each path requires a reason and + records matched grant/permission without granting general project authority. +- Worker reads preserve ownership, hidden-result redaction, and concealed + not-found behavior. +- Audit reads expose only permission-appropriate bounded fields before counts. +- Operator `operations.status.read` receives only the bounded submission/checker + operational projection required for recovery. Audit Authority `audit.read` + receives covered task/submission/checker evidence-chain reads. Both are + read-only, filter before counts/cursors, redact artifact bytes and unnecessary + actor data, conceal unauthorized resources, and have mutation-denial tests. +- Revocation/suspension applies on next request and sensitive commit recheck. +- No migrated operation uses token roles or `require_any_role()`. +- Submission removes the final `LegacyWorkflowEligibilityCompatibility` + consumer; eligibility is the exact-project submitter/both grant plus active + assignment and lifecycle guards. +- With the final consumer removed, the legacy `/api/v1/workers/me/profile` + route, typed-profile activation service/schema, and token-role workflow + observation fields plus the now-unused compatibility adapter/allowlist are + removed in the same chunk. Route-removal, + stale-reference, fresh-database intake, and full API drill tests prove no + intermediate release requires deleted metadata. +- For a replacement assignee on a durable `needs_revision` obligation, + submission creation must consume that obligation atomically, link the + superseded submission, and include replay for every prior high/medium finding + under the prepared locked/rebased revision context. Missing linkage, context, + or replay is denied without clearing or weakening the obligation. Tests cover + successful replacement submission, each missing requirement, transaction + failure/rollback, retry, and preservation of prior immutable attempts. +- Full backend suite and API contract drill pass. + +## Verification commands + +```bash +(cd backend && .venv/bin/python -m ruff check app tests scripts) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review assignment ownership, manager repair scope, checker-result redaction, +audit disclosure, and unchanged checker routing. + +## Stop conditions + +Stop if authorization cutover requires changing checker outcomes, task states, +or review lifecycle behavior. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-15-worker-authority-removal.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-15-worker-authority-removal.md new file mode 100644 index 000000000..44d969484 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-15-worker-authority-removal.md @@ -0,0 +1,127 @@ +# Chunk Contract: WS-AUTH-001-15 - Remaining System Worker Cutover And Obsolete Authority Removal + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Verify the project-setup worker cutover completed in chunk 12, move the +remaining pre-review and queued workers to explicit system permissions, +revalidate actor-attributed queued mutations, remove obsolete token-role +runtime helpers/contexts, and add a deterministic stale-authority scanner. + +## Why this chunk exists + +Human/API cutover is incomplete while any remaining internal job fabricates +human roles or runtime code can still consume token roles. This chunk must not +re-open project setup behavior owned by chunk 12. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/workers/** +backend/app/modules/authorization/** +backend/app/modules/actors/** +backend/app/modules/projects/service.py +backend/app/modules/tasks/service.py +backend/app/modules/tasks/router.py +backend/app/modules/tasks/schemas.py +backend/app/modules/checkers/service.py +backend/app/core/permissions.py +backend/app/schemas/auth.py +backend/app/api/deps/auth.py +backend/scripts/api_contract_e2e.py +backend/tests/test_projects.py +backend/tests/test_tasks.py +backend/tests/test_checkers.py +backend/tests/test_auth.py +scripts/check_stale_authorization.py +scripts/test_agent_gates.py +.github/workflows/agent-gates.yml +docs/operations_authorization_service.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +new product lifecycle behavior +human roles on system principals +authorization allowlist broad enough to hide new token-role consumption +review/contribution/compensation implementation +``` + +## Acceptance criteria + +- Internal jobs use fixed system principals and registered system permissions. +- Project setup is verification/removal-only here; its behavioral cutover + remains owned by chunk 12. +- Serialized requester context is evidence only; actor-attributed commits reload + current actor/grant state. +- Token roles and `require_any_role()` are not product authorization inputs and + obsolete runtime paths are removed. +- The scanner proves the `LegacyWorkflowEligibilityCompatibility` adapter, + allowlist, and typed-profile workflow eligibility consumers removed in chunk + 14 remain absent. +- The stale scanner and route tests prove the legacy worker-profile endpoint, + activation service/schema, token-role observation fields, and actor-service + authority checks are absent from runtime code. +- Deterministic AST/allowlist scanner fails on new product consumption of token + roles, role-bearing contexts, or obsolete permission helper calls. +- Scanner has regression tests and runs in CI. +- Scanner regression includes a known-bad fixture for each forbidden authority + pattern and proves the gate fails rather than silently allowlisting it. +- Full backend suite and API contract drill pass. + +## Verification commands + +```bash +python3 scripts/check_stale_authorization.py +python3 scripts/test_agent_gates.py +(cd backend && .venv/bin/python -m ruff check app tests scripts) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review system-principal boundaries, queued revocation behavior, scanner +precision, CI integrity, and complete removal of old authority. + +## Stop conditions + +Stop if a system job requires human authority or scanner success requires a +blanket ignore of product code. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-16-evidence-live-proof.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-16-evidence-live-proof.md new file mode 100644 index 000000000..006d15939 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-16-evidence-live-proof.md @@ -0,0 +1,143 @@ +# Chunk Contract: WS-AUTH-001-16 - Conformance, Observability, And Live API Proof + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Verify authority-event/idempotency completeness, finish privacy and +observability, run exhaustive permission/concurrency tests, and produce an +API-visible live authorization drill. + +## Why this chunk exists + +The initiative is not complete until immediate revocation, separation of +duties, resource scope, bootstrap safety, and absence of direct database edits +are proven end to end. + +## Approved plan reference + +- INTENT: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/INTENT.md` +- PLAN: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/PLAN.md` +- CHUNK_MAP: `.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/CHUNK_MAP.md` + +## Risk class + +L1 + +## SLA + +P1 + +## Allowed files + +```text +backend/app/modules/authorization/** +backend/app/modules/actors/** +backend/app/modules/audit/** +backend/app/api/** +backend/app/main.py +backend/app/workers/** +backend/app/core/config.py +backend/app/core/observability.py +backend/scripts/auth_api_e2e.py +backend/tests/** +backend/pyproject.toml +docs/operations_roles_permissions.md +docs/operations_authorization_service.md +docs/spec_authorization_service.md +docs/architecture_data_model.md +docs/roadmap_status.md +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +``` + +## Not allowed + +```text +review/contribution/compensation implementation +production credentials or private keys in fixtures/evidence +direct database bootstrap or grant edits in live proof +loosening issuer, scope, permission, or resource guards +starting WS-POL-002-03 automatically +``` + +## Acceptance criteria + +- Authority events contain version, correlation/request ID, acting/target + actor, matched grant, project, reason, idempotency key, and bounded state. +- No authority history is backfilled to compensate for a missing earlier + event/idempotency schema; gaps stop the initiative. +- Tokens, raw claims, JWKS bodies, secrets, and unnecessary PII are absent from + logs, errors, audit, and committed evidence. +- Table-driven tests cover every permission role/scope allow and deny case. +- PostgreSQL tests cover provisioning, grant, final-admin, revocation/command, + and idempotency races. +- Live drill proves bootstrap, scoped admin grants, submitter/reviewer grants, + admin/contributor separation, same-token revocation, suspension/reactivation, + service handling, cross-project denial, and final-admin safety. +- Live drill implements the complete adopted specification sequence, including + Finance and Audit Authority capability separation, Project Manager inability + to review by admin role alone, self-grant/self-revoke denials, full authorized + audit export, database constraint evidence, duplicate-profile absence, + request/correlation/error and actor/grant IDs, and proof that no direct + database authority edit occurred. +- The verifier/JWKS/introspection metrics implemented at the adapter boundary in + chunk 02 are verified end to end. Initiative metrics also cover first + access/conflicts, actor/link state, authorization decisions/denials, + admin/project grants, invalidation backlog, and bootstrap attempts with + bounded labels. +- Alerts cover sustained verification failure, unusable JWKS cache, repeated + bootstrap/final-admin denial attempts, unusual grant mutation rate, + invalidation backlog, and abnormal provisioning conflicts. The runbook names + alert owner, diagnosis, recovery, escalation, and evidence retention. +- Existing full backend suite, API contract drill, lint, docstrings, docs gates, + and internal review evidence pass. +- No dependency, test, coverage, lint, or configuration gate is weakened. Any + production dependency change requires separately recorded explicit human + approval before modification. +- No obsolete token-role authorization remains in runtime code. +- Initiative memory records proof and leaves `WS-POL-002-03` inactive until an + explicit user resume signal. + +## Verification commands + +```bash +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +python3 scripts/check_internal_review_evidence.py +(cd backend && .venv/bin/python -m ruff check app tests scripts) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python -m pytest -q) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/api_contract_e2e.py) +(cd backend && WORKSTREAM_DATABASE_URL= .venv/bin/python scripts/auth_api_e2e.py) +(cd backend && .venv/bin/docstr-coverage --config .docstr.yaml) +python3 scripts/check_stale_authorization.py +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- CI integrity +- docs +- reuse/dedup +- test delta + +## Human review focus + +Review the live evidence chain, same-token revocation, final-admin race proof, +permission matrix completeness, privacy, and confirmation that later initiatives +were not started. + +## Stop conditions + +Stop if real proof requires production secrets/data, direct SQL authority edits, +test weakening, an unapproved production dependency, or starting +review/compensation/WS-POL implementation. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-PLAN-authorization-service-planning.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-PLAN-authorization-service-planning.md new file mode 100644 index 000000000..66986acb8 --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/chunks/WS-AUTH-001-PLAN-authorization-service-planning.md @@ -0,0 +1,107 @@ +# Chunk Contract: WS-AUTH-001-PLAN - Authorization Service Planning + +## Parent initiative + +`WS-AUTH-001` - Workstream Authorization Service + +## Goal + +Adopt the user-provided reference specifications as planning inputs, reconcile +them with the current repository, record human decisions, and produce a +migration-aware PR-sized implementation plan without changing runtime code. + +## Why this chunk exists + +Authorization, actor identity, migrations, audit, project scope, and every +protected product surface are affected. Implementation cannot begin safely +without explicit boundaries and dependency order. + +## Approved plan reference + +- Human adoption decisions in `DECISIONS.md` +- Repository engineering loop in `AGENTS.md` + +## Risk class + +L0 - human-led architecture, auth-model, and data-model direction. D1-D3 were +approved by the user on 2026-07-11; D4-D10 remain an explicit approval gate +before implementation activation. The resulting bounded implementation chunks +are L1. + +## SLA + +P1 + +## Allowed files + +```text +docs/reference_specs/** +.gitattributes +.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/** +.agent-loop/LOOP_STATE.md +.agent-loop/WORK_QUEUE.md +.agent-loop/REVIEW_LOG.md +.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/STATUS.md +.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/CHUNK_MAP.md +``` + +## Not allowed + +```text +backend application code, migrations, dependencies, tests, CI +canonical README/architecture/ADR changes before the first implementation chunk +review, contribution, compensation, frontend, or WS-POL implementation +``` + +## Acceptance criteria + +- All eight supplied files are preserved in the repository with matching + checksums recorded in `SOURCE_MANIFEST.md`. +- `docs/reference_specs/README.md` labels the imported files as immutable + archival inputs, records the `/api/v1` override, and points to the future + reconciled canonical specification without changing the eight hashed files. +- Git attributes mark only the imported reference PDFs as binary so repository + diff/review tooling never decodes PDF bytes as UTF-8 text. +- Intent, discovery, decisions, risks, plan, status, and chunk map are explicit. +- Every implementation chunk has allowed files, exclusions, acceptance + criteria, verification, reviewers, human focus, and stop conditions. +- The plan preserves `/api/v1`, prevents dual authority, handles legacy actor + classification fail-closed, and pauses `WS-POL-002-03`. +- Required internal plan reviews pass and all valid findings are resolved or + documented. +- The plan distinguishes approved L0 direction from proposed L0 decisions and + does not activate `WS-AUTH-001-01` without explicit approval of D4-D10. + +## Verification commands + +```bash +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +python3 scripts/check_loop_memory_state.py +python3 scripts/check_internal_review_evidence.py +sha256sum -c docs/reference_specs/SHA256SUMS +git check-attr diff merge text -- docs/reference_specs/*.pdf | awk '$3 != "unset" { exit 1 }' +git diff --check +``` + +## Required reviewers + +- senior engineering +- QA/test +- security/auth +- product/ops +- architecture +- docs +- reuse/dedup +- CI integrity +- test delta + +## Human review focus + +Review the authority precedence, migration safety, chunk size/order, `/api/v1` +decision, WS-POL pause, and proof strategy. + +## Stop conditions + +Stop if planning requires runtime edits, secrets, production data, inferred +legacy subject kind, or a second authority system. diff --git a/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-PLAN-internal-review-evidence.md b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-PLAN-internal-review-evidence.md new file mode 100644 index 000000000..59fce435c --- /dev/null +++ b/.agent-loop/initiatives/WS-AUTH-001-workstream-authorization-service/reviews/WS-AUTH-001-PLAN-internal-review-evidence.md @@ -0,0 +1,119 @@ +# Internal Review Evidence: WS-AUTH-001-PLAN + +## Chunk + +`WS-AUTH-001-PLAN` - Workstream Authorization Service Planning + +open sub-agent sessions: none + +valid findings addressed: yes + +## Reviewed Revision + +Reviewed code SHA: 5739e1d6fc8df0fa620bd007c45e370530ac8d12 + +Reviewed at: 2026-07-11T14:08:40Z + +Reviewer run IDs: senior-engineering=/root/pdf_attr_engineering_review; QA/test=/root/pdf_attr_quality_review; security/auth=/root/pdf_attr_ci_docs_security_review; product/ops=/root/pdf_attr_quality_review; architecture=/root/pdf_attr_engineering_review; docs=/root/pdf_attr_ci_docs_security_review; CI-integrity=/root/pdf_attr_ci_docs_security_review; reuse/dedup=/root/pdf_attr_engineering_review; test-delta=/root/pdf_attr_quality_review + +## Reviewed Change + +Scope: + +- Imported and SHA-256 bound eight user-supplied Workstream reference files as + immutable archival planning inputs with an adjacent precedence marker. +- Marked only imported reference PDFs as Git binary content so diff and CI + tooling cannot attempt UTF-8 decoding; added deterministic attribute proof. +- Adopted WS-AUTH-001 as the target authorization source while preserving the + external Flow authentication boundary and canonical `/api/v1` namespace. +- Added intent, discovery, decisions, risks, plan, source manifest, status, + chunk map, and 16 bounded implementation chunk contracts. +- Defined fail-closed issuer verification, legacy actor classification, + canonical actor/link migration, local grants, permission evaluation, + authority evidence, revocation, bootstrap safety, and product cutovers. +- Preserved intermediate-release intake operability and revision obligations, + assigned every temporary compatibility consumer and deletion owner, and + prohibited test/CI/dependency bypasses. +- Paused `WS-POL-002-03` and left every implementation chunk inactive. +- Recorded D1-D3 as user-approved L0 direction and D4-D10 as an explicit human + approval gate before `WS-AUTH-001-01` activation. + +## Reviewer Results + +| Reviewer | Result | Blocking findings | Notes | +|---|---:|---|---| +| senior engineering | PASS AFTER FIXES | None | Confirmed policy ownership, L0/L1 boundary, bootstrap locking, metrics ownership, and worker registration. | +| QA/test | PASS AFTER FIXES | None | Confirmed fresh-database compatibility, grant cutover, contributor reads, revision proof, and complete cleanup sequencing. | +| security/auth | PASS AFTER FIXES | None | Confirmed bootstrap serialization, append-only evidence, introspection/JWKS transport, privacy, and denial proof. | +| product/ops | PASS AFTER FIXES | None | Confirmed contributor discovery, Operator/Audit reads, and durable needs-revision replacement behavior. | +| architecture | PASS AFTER FIXES | None | Confirmed service/repository boundaries, intermediate operability, feature persistence ownership, and atomic compatibility removal. | +| docs | PASS AFTER FIXES | None | Confirmed archival/canonical labeling and active-document reconciliation ownership. | +| CI integrity | PASS | None | No gate weakening; migrations, dependency approvals, scanners, commands, and post-review closure are explicit. | +| reuse/dedup | PASS AFTER FIXES | None | Existing verifier, audit, repositories, AsyncSession, and Celery registry are reused; no stranded compatibility code remains. | +| test delta | PASS | None | No tests changed; the plan forbids deletion, skip/xfail, assertion weakening, or authorization-fixture bypass. | + +## Valid Findings Addressed + +- Added `AuthorityControl(id=1) FOR UPDATE` to one-time bootstrap and exact + different-target concurrency proof. +- Made audit evidence append-only, denial evidence owner-local, and + idempotency/rate controls feature-owned behind repositories/services. +- Specified safe HTTPS, redirect, response-bound, logging, and client-separation + rules for JWKS and token introspection. +- Corrected `ResourceContext` composition to application services/resource + loaders rather than persistence repositories. +- Distinguished human-approved L0 direction from pending D4-D10 choices and L1 + implementation chunks. +- Preserved fresh-database intake through a bounded workflow-eligibility + compatibility path until exact-project grants replace it; chunk 14 removes + the final consumer, activation surface, adapter, and allowlist atomically. +- Added contributor candidate lookup and exact-project contributor project + reads with pre-filtered pagination and minimal disclosure. +- Added Operator/Audit task, submission, checker, and evidence projections with + read-only, redaction, concealment, and mutation-denial proof. +- Kept revoked needs-revision work in a durable revision obligation and required + replacement-context, supersession, high/medium replay, rollback, and retry + proof at submission time. +- Reused the existing Celery include list for durable reconciliation. +- Added adjacent archival input labeling and complete active-doc/scanner + ownership for the baseline adoption chunk. +- Corrected the PR-discovered PDF diff failure with a narrow Git-native binary + attribute, added it to the planning contract, and proved all five PDFs resolve + `diff`, `merge`, and `text` to `unset` without changing archival bytes. + +## Commands Run + +```bash +python3 scripts/check_stale_workstream_wording.py +python3 scripts/check_markdown_links.py +python3 scripts/check_loop_memory_state.py +python3 scripts/test_agent_gates.py +sha256sum -c docs/reference_specs/SHA256SUMS +git check-attr diff merge text -- docs/reference_specs/*.pdf | awk '$3 != "unset" { exit 1 }' +git diff --check +``` + +Results: + +- Stale wording: passed. +- Markdown links: passed for 34 changed Markdown files before evidence creation. +- Loop memory: passed. +- Agent gate regression suite: 26 passed. +- All eight imported-file hashes: passed. +- Reference PDF binary attribute assertion: passed for all five PDFs. +- `git diff --check`: passed. + +## Remaining Risks + +- D4-D10 still require explicit durable human approval before implementation. +- Production issuer/JWKS/introspection values and production legacy actor + classifications remain future implementation/live-proof inputs. +- Each L1 chunk must prove its own migration, concurrency, authorization, + compatibility-shrinkage, and full regression requirements; this planning + evidence is not implementation proof. + +## Stop Condition + +No implementation chunk is active. Do not activate `WS-AUTH-001-01` without +explicit human approval of D4-D10 and a separate implementation start signal. +Do not resume `WS-POL-002-03` while authorization remains the priority. diff --git a/.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/CHUNK_MAP.md b/.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/CHUNK_MAP.md index 52811d7b1..a32854f8c 100644 --- a/.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/CHUNK_MAP.md +++ b/.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/CHUNK_MAP.md @@ -12,7 +12,7 @@ reviewed, merged by explicit human approval, and followed by a memory update. |---|---|---:|---| | `WS-POL-002-01` | Post-Submit Compiler Contract | L1 | Merged | | `WS-POL-002-02` | Post-Submit Derivation Agent And Resumable Setup Integration | L1 | Merged | -| `WS-POL-002-03` | Server-Owned Policy Approval And Visibility APIs | L1 | Proposed | +| `WS-POL-002-03` | Server-Owned Policy Approval And Visibility APIs | L1 | Paused behind WS-AUTH-001 and explicit resume | | `WS-POL-002-04` | Locked Runtime Execution And Routing Hardening | L1 | Proposed | | `WS-POL-002-05` | Terminal Benchmark Post-Submit Live API Proof | L1 | Proposed | @@ -31,3 +31,6 @@ WS-POL-002-01 After each implementation chunk is reviewed, externally checked, and merged by explicit human approval, perform the memory update before starting the next chunk. + +The initiative is currently paused after chunk 02 by explicit human priority +for `WS-AUTH-001`. Do not start chunk 03 automatically. diff --git a/.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/STATUS.md b/.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/STATUS.md index 3ed82bd60..a41200134 100644 --- a/.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/STATUS.md +++ b/.agent-loop/initiatives/WS-POL-002-post-submit-checker-foundation/STATUS.md @@ -5,6 +5,11 @@ Planning completed and merged through PR #85 as `3fc1a688743f13476d6092078d40792592823d27`. +The initiative is paused after chunk 02 by the user's 2026-07-11 decision to +prioritize `WS-AUTH-001`. No later chunk may start until the relevant +authorization foundation exists and the user explicitly resumes this +initiative. + `WS-POL-002-01` merged through PR #87 as `ed52c21` on 2026-07-09. It implemented the version-stamped trusted post-submit compiler contract, default-checker snapshot validation, canonical policy hashing, and tests around @@ -35,7 +40,7 @@ None. | `WS-POL-002-PLAN` | Merged | `codex/ws-pol-002-post-submit-checker-planning` | #85 | Defines intent, discovery, design, risks, decisions, and implementation chunks. | | `WS-POL-002-01` | Merged | `codex/ws-pol-002-01-post-submit-compiler` | #87 | Post-Submit Compiler Contract; merged as `ed52c21`. | | `WS-POL-002-02` | Merged | `codex/ws-pol-002-02-post-submit-derivation` | #88 | Post-submit derivation agent and resumable setup integration; merged as `32af6a7`. | -| `WS-POL-002-03` | Proposed | - | - | Server-owned approval and setup visibility APIs for compiled post-submit policies. | +| `WS-POL-002-03` | Paused | - | - | Server-owned approval and setup visibility APIs; paused behind WS-AUTH-001 and explicit resume. | | `WS-POL-002-04` | Proposed | - | - | Runtime hardening for locked post-submit policy execution and routing. | | `WS-POL-002-05` | Proposed | - | - | Terminal Benchmark-style live API proof and report. | @@ -43,4 +48,4 @@ None. | Blocker | Owner | Next action | |---|---|---| -| none | none | none | +| `WS-AUTH-001` priority | Authorization foundation must precede new setup approval APIs | Complete relevant auth cutover, then require explicit user resume | diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..41b620229 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +docs/reference_specs/*.pdf binary diff --git a/docs/reference_specs/README.md b/docs/reference_specs/README.md new file mode 100644 index 000000000..65baaef70 --- /dev/null +++ b/docs/reference_specs/README.md @@ -0,0 +1,13 @@ +# Workstream Reference Specifications + +The eight WS-ARCH/WS-AUTH/WS-CON/WS-IMP/WS-REV files in this directory are +byte-immutable archival inputs supplied on 2026-07-11. Their hashes are recorded +in `SHA256SUMS` and `SOURCE_MANIFEST.md` under the WS-AUTH-001 initiative. + +These inputs are not the repository's reconciled runtime contract. In +particular, Workstream retains the canonical `/api/v1` namespace even where an +archival input uses `/v1`. WS-AUTH-001 takes precedence over the current +token-role authorization bootstrap, subject to the human approval gates in the +initiative plan. Chunk `WS-AUTH-001-01` will publish the reconciled canonical +authorization text at `docs/spec_authorization_service.md` and update active +repository documentation without editing the eight archived files. diff --git a/docs/reference_specs/SHA256SUMS b/docs/reference_specs/SHA256SUMS new file mode 100644 index 000000000..eacd4b185 --- /dev/null +++ b/docs/reference_specs/SHA256SUMS @@ -0,0 +1,8 @@ +547176dcedf41895ed896d5262a6ab8c6c029d71dac50bd17ed9da65f692b9b3 docs/reference_specs/WS-ARCH-001-workstream-v0.1-architecture-baseline.pdf +464a7afb032e950b83e8fdb8c04fafe0b9ebddf549c4475dca684946740869a9 docs/reference_specs/WS-AUTH-001-actor-profile-role-and-authorization-service-specification.md +69670ed59006b29cb7f60d12101cedf250c82a3488497f07d6c27e140aef1d2e docs/reference_specs/WS-AUTH-001-actor-profile-role-and-authorization-service-specification.pdf +34c4337f27e42a5b0ed5e153fe8ccd492ecede202c2764506a930d109aef66c1 docs/reference_specs/WS-CON-001-contribution-record-and-compensation-boundary-specification.pdf +e2116bce55fda1cce46a93e64bedcb47133d3898c1d4a51863385803e9dac210 docs/reference_specs/WS-IMP-001-workstream-v0.1-coding-agent-implementation-specification.md +12f094e49c5c80f117e42d0f7f962b843f34508ab58d7f1d8def5f50fef532ed docs/reference_specs/WS-IMP-001-workstream-v0.1-coding-agent-implementation-specification.pdf +87d2c4c5b2158bd11ed76985f8abdc3303be7dcdf7de470bae6b3bcf92b56fe4 docs/reference_specs/WS-REV-001-review-lifecycle-specification.md +d4ab94f2db6ec47dacc6a9063934e481d7089db5bfe142d724aa0b025bc00b52 docs/reference_specs/WS-REV-001-review-lifecycle-specification.pdf diff --git a/docs/reference_specs/WS-ARCH-001-workstream-v0.1-architecture-baseline.pdf b/docs/reference_specs/WS-ARCH-001-workstream-v0.1-architecture-baseline.pdf new file mode 100644 index 000000000..c8bfa71d0 Binary files /dev/null and b/docs/reference_specs/WS-ARCH-001-workstream-v0.1-architecture-baseline.pdf differ diff --git a/docs/reference_specs/WS-AUTH-001-actor-profile-role-and-authorization-service-specification.md b/docs/reference_specs/WS-AUTH-001-actor-profile-role-and-authorization-service-specification.md new file mode 100644 index 000000000..3eb3c2fba --- /dev/null +++ b/docs/reference_specs/WS-AUTH-001-actor-profile-role-and-authorization-service-specification.md @@ -0,0 +1,1933 @@ +# WS-AUTH-001: Workstream Actor Profile, Role, and Authorization Service Specification + +**Status:** Locked for implementation handoff +**Architecture maturity:** Design complete; not yet runtime-proven +**Version:** 0.1 +**Date:** 2026-07-11 +**Owner:** Workstream Engineering +**Scope:** Identity Issuer token verification, Workstream ActorProfile provisioning, identity linking, contributor-domain membership, admin-role grants, project-role grants, permission evaluation, resource guards, revocation, bootstrap, audit, APIs, database constraints, concurrency, conformance, and live proof +**Depends on:** Identity Issuer v0.1 issuer-owned token and JWKS boundary; existing Workstream FastAPI application and PostgreSQL record database +**Precedes:** `WS-REV-001` review and revision lifecycle and `WS-CON-001` contribution/compensation implementation +**Current proof boundary:** Identity/product ownership is architecturally defined, but the complete ActorProfile and authorization service specified here is not yet runtime-proven + +--- + +## 1. Purpose + +This specification defines the Workstream authority spine that MUST exist before the review lifecycle is implemented. + +It establishes: + +- how an Identity Issuer token is verified; +- how one issuer subject resolves to exactly one local Workstream ActorProfile; +- how a first-time human caller is provisioned into the Contributor domain; +- why Contributor membership does not grant project work authority; +- how project-scoped submitter and reviewer privileges are granted; +- how administrative roles are granted independently of contributor privileges; +- the five initial Workstream administrator roles; +- the exact permission catalog for those roles; +- how roles, project scope, resource ownership, resource state, and explicit guards combine into one authorization decision; +- how suspension, deactivation, role revocation, and identity-link revocation take effect; +- how bootstrap authority is established without a hidden database edit; +- which APIs, constraints, audit events, tests, and live proof are required. + +The objective is to ensure that every later Workstream feature calls one established authorization service instead of implementing identity or permissions inside individual routers. + +--- + +## 2. Normative Relationship to Other Specifications + +This specification becomes the authority source for ActorProfile, identity links, admin roles, contributor project roles, and authorization evaluation. + +The following precedence applies: + +1. `WS-AUTH-001` controls identity resolution, ActorProfile state, `AdminRoleGrant`, `ProjectRoleGrant`, role scope, permission evaluation, and authority revocation. +2. `WS-REV-001` controls review queue routing, leases, review decisions, revision chains, and review-specific resource guards. +3. `WS-CON-001` controls contribution creation, compensation policies, awards, adapters, and finance-specific resource behavior. +4. `WS-ARCH-001` controls the overall Workstream system boundary, container/component direction, evidence boundary, deployment, and operational architecture. + +Where `WS-REV-001` currently defines `ProjectRoleGrant`, this specification supersedes that object only for creation authority, scope, persistence, and authorization behavior. The review specification continues to control how submitter/reviewer grants are consumed by task and review operations. + +No implementation of `WS-REV-001` may begin until the definition of done in this specification has been met. + +--- + +## 3. Normative Language + +The words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, and MAY are normative. + +- MUST and SHALL describe mandatory v0.1 behavior. +- MUST NOT and SHALL NOT describe prohibited behavior. +- SHOULD describes a strong implementation recommendation that may be changed only without changing the contract. +- MAY describes an optional implementation detail. + +--- + +## 4. Locked v0.1 Decisions + +The coding agent is not permitted to reinterpret the following decisions. + +1. Identity Issuer owns portable identity, final token signing, and the public product-trusted JWKS. +2. Workstream owns ActorProfiles, roles, permissions, project grants, resource guards, and authorization decisions. +3. Identity Issuer tokens do not contain authoritative Workstream roles. +4. The stable external identity anchor is `(issuer, subject)`. +5. One `(issuer, subject)` maps to exactly one Workstream ActorProfile. +6. One Workstream ActorProfile has exactly one active ActorIdentityLink in v0.1. +7. First-time valid human callers are automatically provisioned as active human ActorProfiles. +8. Every active human ActorProfile belongs to the Contributor domain by default. +9. Contributor-domain membership is not task, submission, or review authority. +10. Project work requires an active `ProjectRoleGrant` for that exact project. +11. Project contributor roles are `submitter`, `reviewer`, or `both`. +12. Project roles are explicitly granted by an authorized Project Manager in v0.1. +13. Skills and reputation may inform a ProjectRoleGrant but do not automatically create one. +14. Administrative authority is represented by separate `AdminRoleGrant` records. +15. An ActorProfile may hold multiple AdminRoleGrants. +16. An ActorProfile may be both an administrator and a project contributor. +17. Administrative authority never automatically grants submitter or reviewer authority. +18. An administrator who submits or reviews work must hold the same active ProjectRoleGrant required of any contributor. +19. The initial administrator roles are `access_administrator`, `operator`, `project_manager`, `finance_authority`, and `audit_authority`. +20. Roles grant permission candidates; resource scope and resource guards still decide the command. +21. Authorization is deny-by-default. +22. An explicit resource guard overrides role-derived permission. +23. Role and grant revocation take effect from Workstream state without waiting for a new Identity Issuer token. +24. Authorization decisions are not cached across requests in v0.1. +25. Service subjects are never automatically provisioned. +26. Unknown service subjects must be pre-provisioned by an Access Administrator. +27. Agent and Space subjects are outside the v0.1 Workstream access contract and are denied. +28. No user can grant themselves an AdminRoleGrant or ProjectRoleGrant. +29. The last active Access Administrator cannot be revoked, suspended, or deactivated. +30. No authorization record is deleted. Revocation and deactivation preserve history. + +--- + +## 5. Authority Model + +```text +Identity Issuer + issuer + subject + signed Workstream token + | + v +ActorIdentityLink (one external identity anchor) + | + v +ActorProfile (one local Workstream actor) + | + +-- Human actor => Contributor domain by default + | | + | +-- ProjectRoleGrant(project A, submitter) + | +-- ProjectRoleGrant(project B, reviewer) + | +-- ProjectRoleGrant(project C, both) + | + +-- Zero or more AdminRoleGrants + +-- access_administrator + +-- operator + +-- project_manager + +-- finance_authority + +-- audit_authority +``` + +The same ActorProfile is used everywhere in Workstream. The system MUST NOT create a contributor profile and a second administrator profile for the same issuer subject. + +--- + +## 6. Core Object Model + +### 6.1 ActorProfile + +`ActorProfile` is Workstream's local identity for a human or service actor. + +```yaml +ActorProfile: + id: uuid + actor_kind: enum [human, service] + status: enum [active, suspended, deactivated] + provisioning_method: enum [automatic_first_access, manual_service_provisioning] + + display_name: string | null + contact_email: string | null + + created_by: actor_id | system_actor_id + created_at: timestamp + updated_at: timestamp + last_seen_at: timestamp | null + + suspended_by: actor_id | null + suspended_at: timestamp | null + suspension_reason: string | null + + deactivated_by: actor_id | null + deactivated_at: timestamp | null + deactivation_reason: string | null +``` + +#### ActorProfile invariants + +- `actor_kind` is immutable. +- `provisioning_method` is immutable. +- A first-time human token creates `actor_kind = human` and `provisioning_method = automatic_first_access`. +- A service actor is created only by an Access Administrator and uses `manual_service_provisioning`. +- `display_name` and `contact_email` are profile/display data and MUST NOT affect authorization. +- `suspended` is reversible. +- `deactivated` is terminal in v0.1. +- ActorProfile rows are never deleted. +- Historical submissions, reviews, contributions, awards, audit events, and grants remain linked after suspension or deactivation. +- Every active human ActorProfile is a Contributor-domain member by definition. Contributor membership is derived from `actor_kind = human`; it is not a separate mutable role grant. +- An ActorProfile is considered part of the Admin domain only when it has at least one effective active AdminRoleGrant. + +### 6.2 ActorIdentityLink + +`ActorIdentityLink` connects the stable Identity Issuer anchor to one ActorProfile. + +```yaml +ActorIdentityLink: + id: uuid + actor_profile_id: uuid + issuer: string + subject: string + subject_kind: enum [human, service] + status: enum [active, revoked] + + linked_by: actor_id | system_actor_id + linked_at: timestamp + last_verified_at: timestamp + + revoked_by: actor_id | null + revoked_at: timestamp | null + revoked_reason: string | null + + reactivated_by: actor_id | null + reactivated_at: timestamp | null + reactivation_reason: string | null +``` + +#### ActorIdentityLink invariants + +- `issuer` is stored in canonical normalized URL form. +- `subject` is opaque and case-sensitive. +- `UNIQUE(issuer, subject)` is mandatory. +- `UNIQUE(actor_profile_id)` is mandatory in v0.1. +- The link `subject_kind` must match ActorProfile `actor_kind`. +- Human first access creates the ActorProfile and ActorIdentityLink in one transaction. +- Workstream does not merge Identity Issuer subjects. +- Adding multiple human subjects to one ActorProfile is out of scope for v0.1. +- Revocation prevents the linked subject from using the ActorProfile. +- Reactivation restores the same link; it does not create another ActorProfile. +- Revoked links are never deleted or replaced with a new row for the same `(issuer, subject)`. + +### 6.3 AdminRoleGrant + +`AdminRoleGrant` represents additive Workstream administrative authority. + +```yaml +AdminRoleGrant: + id: uuid + actor_profile_id: uuid + role: enum [ + access_administrator, + operator, + project_manager, + finance_authority, + audit_authority + ] + + scope_type: enum [system, project] + scope_project_id: uuid | null + status: enum [active, revoked] + + granted_by: actor_id | system_actor_id + granted_by_admin_role_grant_id: uuid | null + granted_at: timestamp + grant_reason: string + + revoked_by: actor_id | null + revoked_at: timestamp | null + revoked_reason: string | null +``` + +#### AdminRoleGrant scope compatibility + +| Role | Allowed scopes | +|---|---| +| `access_administrator` | `system` only | +| `operator` | `system` only | +| `project_manager` | `system` or `project` | +| `finance_authority` | `system` or `project` | +| `audit_authority` | `system` or `project` | + +#### AdminRoleGrant invariants + +- `scope_project_id` is null if and only if `scope_type = system`. +- A project-scoped grant applies to exactly one project. +- A system-scoped grant applies to all resources allowed by that role. +- Role grants are additive; effective permissions are the union of active grants whose scopes cover the resource. +- Explicit resource guards and ActorProfile state override the union. +- An actor cannot grant an AdminRoleGrant to themselves. +- Only an effective Access Administrator may issue or revoke an AdminRoleGrant. +- An Access Administrator may not grant a role outside the scope compatibility table. +- An actor cannot revoke their own AdminRoleGrant. +- The final effective active `access_administrator` grant cannot be revoked. +- Changing role or scope requires a new grant and revocation of the old grant. The existing row is not overwritten. +- Revoked grants remain permanent audit history. + +### 6.4 ProjectRoleQualificationSnapshot + +The qualification snapshot records the evidence visible when a Project Manager makes a manual contributor-role decision. + +```yaml +ProjectRoleQualificationSnapshot: + id: uuid + project_id: uuid + contributor_id: uuid + + skills_snapshot: object + reputation_snapshot: object + prior_project_work_refs: list[uuid] + external_expertise_refs: list[string] + + captured_by: actor_id + captured_at: timestamp +``` + +#### Qualification snapshot invariants + +- The snapshot is immutable. +- Missing skills or reputation are recorded explicitly as unavailable. +- Missing reputation does not block a manual v0.1 grant. +- The snapshot is evidence for the grant decision, not an authorization decision by itself. +- Snapshot payloads MUST exclude secrets and unnecessary personal information. + +### 6.5 ProjectRoleGrant + +`ProjectRoleGrant` is the only contributor-side project authority in v0.1. + +```yaml +ProjectRoleGrant: + id: uuid + project_id: uuid + contributor_id: uuid + role: enum [submitter, reviewer, both] + status: enum [active, revoked] + + grant_method: enum [manual, automated] + automation_policy_ref: uuid | null + qualification_snapshot_ref: uuid + + granted_by: actor_id + granted_by_admin_role_grant_id: uuid + granted_at: timestamp + grant_reason: string + + revoked_by: actor_id | null + revoked_at: timestamp | null + revoked_reason: string | null +``` + +#### ProjectRoleGrant invariants + +- The contributor must be an active human ActorProfile. +- The grant is valid for one project only. +- A Project Manager whose effective scope covers the project may issue or revoke it. +- The issuing Project Manager cannot grant a ProjectRoleGrant to themselves. +- `grant_method = manual` is the only creation path enabled in v0.1. +- `grant_method = automated` is schema-reserved and MUST NOT be emitted by v0.1 code. +- `automation_policy_ref` is null for a manual grant. +- `qualification_snapshot_ref` is mandatory and must match the same project and contributor. +- At most one active ProjectRoleGrant exists for a contributor in a project. +- Changing `submitter`, `reviewer`, or `both` requires revoking the active grant and creating a new grant in one transaction. +- A submitter action requires `role IN (submitter, both)`. +- A reviewer action requires `role IN (reviewer, both)`. +- AdminRoleGrant does not satisfy either requirement. +- Revocation does not delete or invalidate historical work completed while the grant was active. + +### 6.6 AuthorizationContext + +`AuthorizationContext` is an in-memory request object. It is not a canonical database record. + +```yaml +AuthorizationContext: + request_id: uuid + correlation_id: uuid + token_issuer: string + token_subject: string + token_id: string + token_scopes: list[string] + + actor_profile_id: uuid + actor_kind: enum [human, service] + actor_status: enum [active, suspended, deactivated] + + active_admin_role_grants: list[AdminRoleGrantSummary] + active_project_role_grants: list[ProjectRoleGrantSummary] +``` + +The context may be reused inside one request only. It MUST NOT be cached for authorization of a later request. + +### 6.7 AuthorizationDecision + +`AuthorizationDecision` is the result returned by the authorization service. + +```yaml +AuthorizationDecision: + allowed: boolean + permission: string + actor_profile_id: uuid + resource_type: string + resource_id: uuid | null + project_id: uuid | null + matched_grant_ids: list[uuid] + denial_code: string | null + evaluated_at: timestamp +``` + +The full decision is written as an audit fact only for sensitive operations and configured denial classes. Normal request logs may contain the decision code and identifiers but MUST NOT contain token material. + +### 6.8 AuthorityControl + +`AuthorityControl` is a singleton PostgreSQL row used to serialize bootstrap and final-Access-Administrator safety checks. + +```yaml +AuthorityControl: + id: integer # fixed value 1 + version: integer + updated_at: timestamp +``` + +The bootstrap operation and every AdminRoleGrant/ActorProfile transition that could reduce the effective Access Administrator count MUST lock this row with `FOR UPDATE` before counting effective Access Administrators and committing the transition. + +--- + +## 7. Identity Issuer Token Contract + +### 7.1 Trusted issuer boundary + +Workstream trusts only final tokens signed by the Identity Issuer-owned keys. + +It MUST NOT trust: + +- Better Auth JWT/JWKS output; +- a client-generated token; +- a token signed by Workstream; +- a token forwarded from another product without the Workstream audience; +- identity claims supplied in request JSON or headers outside the bearer token. + +### 7.2 Required claims + +A Workstream token MUST contain: + +```yaml +iss: canonical Identity Issuer URL +sub: opaque portable subject +aud: contains workstream +exp: expiry timestamp +iat: issued-at timestamp +jti: unique token identifier +subject_kind: enum [human, service, agent, space] +scope: space-delimited string or equivalent list +``` + +If the Identity Issuer exposes the subject classification under another approved canonical claim name, TokenVerifier may normalize that verified claim to `subject_kind` inside `VerifiedIssuerToken`. Workstream MUST NOT infer a missing classification from email, subject prefix, request route, or client input, and MUST NOT default an unknown subject to human. + +Required deployment defaults: + +```text +WORKSTREAM_TOKEN_AUDIENCE=workstream +WORKSTREAM_REQUIRED_HUMAN_SCOPE=workstream:access +WORKSTREAM_REQUIRED_SERVICE_SCOPE=workstream:service +``` + +Coarse token scope only permits the request class to reach Workstream. It never replaces local authorization. + +### 7.3 Supported subject kinds + +| `subject_kind` | v0.1 behavior | +|---|---| +| `human` | Auto-provision on first valid access | +| `service` | Resolve only if pre-provisioned by Access Administrator | +| `agent` | Deny with `unsupported_subject_kind` | +| `space` | Deny with `unsupported_subject_kind` | + +Delegated agent access is a future additive specification. The coding agent MUST NOT treat an agent token as a human contributor. + +### 7.4 Verification algorithm + +The TokenVerifier MUST: + +1. parse the bearer token without trusting unverified claims; +2. select only configured Identity Issuer algorithms; +3. resolve `kid` against the issuer-owned JWKS; +4. verify signature; +5. validate exact canonical `iss`; +6. validate Workstream audience; +7. validate `exp`, `iat`, and `nbf` when present using configured clock skew; +8. require non-empty `sub`, `jti`, and `subject_kind`; +9. validate the required coarse scope for human or service access; +10. apply the configured introspection/revocation rule when required; +11. return verified claims to ActorResolver. + +Unknown `kid` triggers one controlled JWKS refresh before failure. It MUST NOT disable signature verification. + +### 7.5 JWKS caching + +- JWKS may be cached according to issuer cache headers and configured maximum age. +- Key overlap must allow safe issuer rotation. +- Authorization roles and grants MUST NOT be stored in the JWKS cache. +- JWKS unavailability with no valid cached key returns `identity_verification_unavailable`. +- JWKS failure MUST NOT cause Workstream to accept an unverifiable token. + +--- + +## 8. First Human Access and Automatic Provisioning + +### 8.1 Preconditions + +Automatic provisioning occurs only when: + +- the token is fully verified; +- `aud` includes Workstream; +- required human scope is present; +- `subject_kind = human`; +- `(issuer, subject)` has no existing ActorIdentityLink. + +### 8.2 Provisioning transaction + +The first access transaction MUST: + +1. normalize `issuer`; +2. attempt to load and lock an existing ActorIdentityLink; +3. create an active human ActorProfile if no link exists; +4. create the active ActorIdentityLink; +5. set `provisioning_method = automatic_first_access`; +6. copy only permitted display claims into `display_name` and `contact_email`; +7. record `ActorProfileProvisioned` and `ActorIdentityLinked` audit events; +8. commit; +9. return the ActorProfile as a Contributor-domain actor with no AdminRoleGrant and no ProjectRoleGrant. + +### 8.3 Concurrent first access + +If two first requests arrive for the same `(issuer, subject)`: + +- exactly one ActorIdentityLink may be inserted; +- exactly one ActorProfile may remain associated with the link; +- the losing transaction must resolve the unique conflict by loading the committed profile; +- no orphan ActorProfile may remain. + +The implementation SHOULD insert or reserve the identity link and profile in a transaction pattern that prevents orphan profiles. A cleanup job is not a substitute for transaction correctness. + +### 8.4 First-access response + +```json +{ + "actor_profile_id": "uuid", + "actor_kind": "human", + "status": "active", + "domains": ["contributor"], + "admin_roles": [], + "project_role_grants": [] +} +``` + +The response MUST NOT imply that the actor can claim a task or review. + +--- + +## 9. Service Actor Provisioning + +Service actors are used for authenticated external adapters and approved Workstream service integrations. + +### 9.1 Creation + +An effective Access Administrator MUST create the service ActorProfile and ActorIdentityLink before the first service request. + +Required request data: + +```json +{ + "issuer": "https://identity.flowresearch.tech", + "subject": "sub_service_123", + "display_name": "Project compensation adapter", + "grant_reason": "Approved project fulfillment integration" +} +``` + +### 9.2 Service authority + +- A service ActorProfile does not belong to the Contributor domain. +- A service ActorProfile cannot receive a ProjectRoleGrant. +- A service ActorProfile cannot receive an AdminRoleGrant in v0.1. +- Service authority is provided by explicit adapter binding or service-resource binding defined by the consuming specification. +- The authorization service must validate both the active service ActorProfile and the consuming binding. + +### 9.3 Unknown service subject + +A valid service token with no pre-provisioned link returns `403 service_actor_not_provisioned` and creates no ActorProfile. + +--- + +## 10. Administrative Roles + +### 10.1 Access Administrator + +Purpose: manage Workstream actor and role authority. + +May: + +- read ActorProfile status and identity-link metadata; +- suspend eligible actors; +- reactivate suspended actors; +- deactivate eligible actors; +- revoke or reactivate identity links; +- provision service actors; +- issue and revoke AdminRoleGrants; +- read the permission catalog and role-grant audit history. + +May not by this role alone: + +- manage project guides, tasks, submissions, reviews, compensation, or awards; +- issue ProjectRoleGrants; +- submit or review work; +- view artifact content; +- grant themselves another role. + +### 10.2 Operator + +Purpose: operate and recover Workstream runtime behavior. + +May: + +- read system and project operational status; +- inspect task/review queue state across projects; +- run approved timer sweeps, reconciliation, outbox retry, and projection rebuild operations; +- force-release a review lease through the review specification's audited override; +- inspect operational audit events needed for recovery. + +May not by this role alone: + +- create or change AdminRoleGrants or ProjectRoleGrants; +- approve project policy; +- record a review decision; +- publish compensation policy; +- alter immutable records; +- mark an award fulfilled. + +### 10.3 Project Manager + +Purpose: own Workstream project configuration and project contributor authority. + +May within effective scope: + +- create projects when system-scoped; +- read and update project configuration; +- create and approve guide/effective policy versions; +- create, release, pause, or close tasks through project policy; +- configure review/revision policy; +- create qualification snapshots; +- issue and revoke ProjectRoleGrants; +- inspect project task and review queues; +- read project review and contribution history required for management. + +May not by this role alone: + +- grant AdminRoleGrants; +- grant themselves a ProjectRoleGrant; +- submit or review work; +- publish compensation policy; +- run system-wide recovery outside project-manager operations; +- alter immutable submissions, reviews, or contributions. + +### 10.4 Finance Authority + +Purpose: manage project compensation configuration and observe fulfillment. + +May within effective scope: + +- read project identity and contribution facts needed for compensation; +- create, edit draft, publish, and retire compensation policy versions; +- create, suspend, resume, and retire project adapter bindings; +- read CompensationAwards and fulfillment projections/receipts; +- initiate approved redelivery/reconciliation through the compensation specification. + +May not by this role alone: + +- create or alter Review decisions or ContributionRecords; +- mark fulfillment successful without an authenticated adapter receipt; +- manage ProjectRoleGrants or AdminRoleGrants; +- submit or review work; +- read unrelated project finance data. + +### 10.5 Audit Authority + +Purpose: read immutable and operational evidence without mutation. + +May within effective scope: + +- read audit events; +- read role/grant history; +- read task, submission, checker, review, contribution, award, and fulfillment chains; +- export authorized audit evidence; +- view minimal actor metadata required to attribute actions. + +May not: + +- execute any product mutation; +- reveal secrets, bearer tokens, artifact bytes beyond separately authorized evidence access, or unnecessary personal information; +- grant roles; +- run recovery; +- record review or fulfillment outcomes. + +--- + +## 11. Permission Catalog + +Permission identifiers are stable internal/public contract strings. + +```text +actor.profile.read_self +actor.profile.update_self +actor.profile.read_any +actor.profile.suspend +actor.profile.reactivate +actor.profile.deactivate +actor.identity_link.read +actor.identity_link.revoke +actor.identity_link.reactivate +actor.service.provision + +admin_role.read +admin_role.grant +admin_role.revoke + +project.create +project.read +project.update +project.archive +project.guide.manage +project.effective_policy.manage +project.task.manage +project.review_policy.manage +project.role_grant.read +project.role_grant.manage + +task.queue.read +task.claim +submission.create +submission.read_own +submission.read_for_review + +review.queue.read +review.queue.inspect +review.claim +review.release +review.decline_preference +review.decision +review.lease.force_release +review.chain.read + +contribution.read_self +contribution.read_project + +compensation.policy.manage +compensation.adapter_binding.manage +compensation.award.read +compensation.delivery.reconcile + +operations.status.read +operations.timer.run +operations.reconcile.run +operations.outbox.retry +operations.projection.rebuild + +audit.read +audit.export +``` + +Adding a permission identifier requires a new specification or an approved additive update. The coding agent MUST NOT invent permissions inside routers. + +--- + +## 12. Permission Assignment Matrix + +Legend: `S` = system scope only, `C` = covered system/project scope, `-` = not granted by this admin role. + +| Permission group | Access Administrator | Operator | Project Manager | Finance Authority | Audit Authority | +|---|---:|---:|---:|---:|---:| +| Actor self permissions | inherited human behavior | inherited human behavior | inherited human behavior | inherited human behavior | inherited human behavior | +| Actor/profile administration | S | - | - | - | read-minimal C | +| Identity-link administration | S | - | - | - | read C | +| Service-actor provisioning | S | - | - | - | read C | +| Admin-role administration | S | - | - | - | read C | +| Project create | - | - | system-scoped grant only | - | - | +| Project read | - | S | C | C | C | +| Project update/archive | - | - | C | - | - | +| Guide/effective-policy management | - | - | C | - | read C | +| Task management | - | operational override only | C | - | read C | +| Review-policy management | - | - | C | - | read C | +| ProjectRoleGrant administration | - | - | C | - | read C | +| Review queue inspection | - | S | C | - | read C | +| Force-release review lease | - | S | - | - | - | +| Review decision | requires ProjectRoleGrant | requires ProjectRoleGrant | requires ProjectRoleGrant | requires ProjectRoleGrant | requires ProjectRoleGrant | +| Contribution project read | - | operational C | C | C | C | +| Compensation-policy management | - | - | - | C | read C | +| Adapter-binding management | - | - | - | C | read C | +| Award/receipt read | - | operational C | read C | C | C | +| Reconciliation/runtime operations | - | S | - | compensation-only C | - | +| Audit read/export | authority audit only S | operational audit S | project audit C | finance audit C | C | + +This matrix grants permission candidates. Every request still passes the authorization algorithm and resource guards. + +--- + +## 13. Contributor Permission Matrix + +### 13.1 Default human Contributor domain + +An active human ActorProfile without any project grant may: + +- use `actor.profile.read_self`; +- use `actor.profile.update_self` for permitted display fields; +- use `contribution.read_self` subject to record-level disclosure policy; +- accept project invitations or await an admin grant through future UI flow. + +It may not claim tasks, create submissions, read reviewer queues, or review work. + +### 13.2 Submitter grant + +An active `ProjectRoleGrant(submitter|both)` supplies candidate permissions for the same project: + +```text +project.read +task.queue.read +task.claim +submission.create +submission.read_own +review.chain.read for the actor's own submitted task chain +``` + +TaskAssignment state, task ban, task availability, policy state, and other submitter resource guards still apply. + +### 13.3 Reviewer grant + +An active `ProjectRoleGrant(reviewer|both)` supplies candidate permissions for the same project: + +```text +project.read +review.queue.read +review.claim +review.release +review.decline_preference +review.decision +submission.read_for_review +review.chain.read +``` + +Self-review, active-lease capacity, preferred-reviewer routing, lease state, and decision-state guards still apply. + +### 13.4 Both grant + +`both` is the union of submitter and reviewer project permissions. It does not bypass self-review or any other resource guard. + +--- + +## 14. Scope Resolution + +### 14.1 System scope + +A system-scoped AdminRoleGrant covers all projects only for permissions assigned to that role. + +System scope is not superuser authority. For example, a system-scoped Finance Authority can manage compensation across projects but cannot record reviews. + +### 14.2 Project scope + +A project-scoped AdminRoleGrant covers only resources whose canonical `project_id` equals `scope_project_id`. + +The authorization service MUST derive project ownership from canonical database relationships. It MUST NOT trust `project_id` supplied in a request body when the resource ID resolves to another project. + +### 14.3 Cross-project relationships + +If any related object belongs to a different project, authorization fails with `resource_project_mismatch`. Service validation does not replace database foreign-key ownership constraints. + +--- + +## 15. Authorization Algorithm + +Every protected command MUST call: + +```text +authorize(actor_context, permission, resource_context) +``` + +The authorization service MUST evaluate in this order: + +1. Token was verified by TokenVerifier. +2. ActorIdentityLink exists and is active. +3. ActorProfile exists and is active. +4. Token `subject_kind` matches ActorProfile `actor_kind`. +5. Required coarse token scope is present. +6. Permission is a registered permission identifier. +7. Load active AdminRoleGrants whose scopes cover the resource. +8. Load the relevant active ProjectRoleGrant when the permission is contributor-side. +9. Determine whether at least one effective grant supplies the permission. +10. Resolve the resource's canonical project and ownership chain. +11. Apply global and resource-specific guards. +12. Apply resource-state preconditions. +13. Return allow or a stable denial code. + +Pseudo-code: + +```python +def authorize(ctx, permission, resource): + require_active_identity_link(ctx) + require_active_actor(ctx) + require_registered_permission(permission) + + candidates = permissions_from_effective_admin_grants(ctx, resource) + candidates |= permissions_from_project_role_grant(ctx, resource) + candidates |= default_human_self_permissions(ctx, resource) + + if permission not in candidates: + deny("permission_not_granted") + + require_canonical_project_match(resource) + apply_global_guards(ctx, permission, resource) + apply_resource_guards(ctx, permission, resource) + apply_state_preconditions(permission, resource) + + allow() +``` + +Routers and workers MUST NOT reproduce this algorithm independently. + +--- + +## 16. Global Resource Guards + +The following guards apply even when a role supplies the requested permission. + +1. Suspended ActorProfile cannot execute business mutations. +2. Deactivated ActorProfile cannot execute any business command. +3. Revoked ActorIdentityLink cannot authenticate to the linked ActorProfile. +4. Service actors cannot receive human contributor permissions. +5. Human actors cannot use a service-only adapter binding. +6. An actor cannot issue a grant to themselves. +7. An actor cannot revoke their own AdminRoleGrant. +8. The final Access Administrator cannot be suspended, deactivated, or have the final grant revoked. +9. A reviewer cannot review their own submission. +10. A task-banned contributor cannot reclaim or resubmit that task. +11. A role whose scope does not cover the canonical project is ineffective. +12. A read operation must not reveal unauthorized project existence through counts or error differences. +13. Immutable records have no update/delete authorization path. + +Consuming specifications may add narrower guards. They may not remove these guards. + +--- + +## 17. Actor State Semantics + +### 17.1 Active + +An active actor may exercise effective permissions and grants. + +### 17.2 Suspended + +Suspension is a reversible security/operational control. + +Effects: + +- bearer token verification may succeed; +- ActorResolver returns the suspended ActorProfile; +- all business mutations are denied with `actor_suspended`; +- `/v1/actors/me` may return minimal status and support information; +- active AdminRoleGrants and ProjectRoleGrants remain recorded but ineffective; +- active review leases and other exclusive work claims are invalidated through `AuthorityInvalidationRequested` and consuming lifecycle reconciliation; +- historical records remain unchanged. + +Reactivation restores still-active, non-revoked grants. It does not restore grants revoked separately during suspension. + +### 17.3 Deactivated + +Deactivation is terminal in v0.1. + +Effects: + +- all permissions and grants become permanently ineffective; +- all business APIs return `actor_deactivated` after identity resolution; +- identity link and grant history remain; +- consuming lifecycles reconcile active claims; +- the ActorProfile cannot be reactivated; +- a new ActorProfile MUST NOT be created for the same `(issuer, subject)`. + +### 17.4 State-transition rules + +```text +active --suspend--> suspended --reactivate--> active +active --deactivate--> deactivated +suspended --deactivate--> deactivated +deactivated --X--> no transition +``` + +An Access Administrator cannot suspend or deactivate themselves. Another Access Administrator must execute the action. + +--- + +## 18. Role and Identity Revocation + +### 18.1 AdminRoleGrant revocation + +Revocation: + +- locks the target grant; +- verifies the actor is an effective Access Administrator; +- enforces no-self-revoke and last-access-admin guards; +- sets status/revocation fields; +- appends audit and `AuthorityInvalidationRequested` events; +- commits atomically. + +The permission disappears on the next request because role decisions are not cached across requests. + +### 18.2 ProjectRoleGrant revocation + +Revocation: + +- verifies an effective Project Manager scope for the project; +- prevents self-revocation through the admin operation when grant ownership equals acting actor; +- locks the active grant; +- sets status/revocation fields; +- appends audit and authority-invalidation events; +- invokes or schedules consuming lifecycle reconciliation. + +For review leases, `WS-REV-001` grant-revocation behavior controls the lease transition. + +### 18.3 Identity-link revocation + +An Access Administrator may revoke an identity link only with a reason. + +Effects are immediate for future requests. Existing request transactions that already completed authorization may finish; no new request may build a valid AuthorizationContext from the revoked link. + +### 18.4 Identity-link reactivation + +Reactivation: + +- requires an effective Access Administrator; +- requires the linked ActorProfile not be deactivated; +- uses the same ActorIdentityLink row; +- records reason and audit event; +- does not modify roles or project grants. + +--- + +## 19. Bootstrap Access Administrator + +### 19.1 Purpose + +The first Access Administrator cannot be created through an endpoint that already requires an Access Administrator. + +### 19.2 Bootstrap sequence + +1. The initial human signs in normally and calls `/v1/actors/me`. +2. Workstream creates the human ActorProfile and ActorIdentityLink. +3. An authorized deployment operator runs the Workstream-local bootstrap management operation with the exact ActorProfile ID. +4. The operation verifies that no active Access Administrator exists. +5. It creates one system-scoped `access_administrator` AdminRoleGrant. +6. `granted_by` references the fixed Workstream system bootstrap actor. +7. It appends `InitialAccessAdministratorBootstrapped` audit evidence. +8. It refuses every later invocation once an active Access Administrator exists. + +### 19.3 Bootstrap prohibitions + +- No public bootstrap HTTP endpoint exists. +- No shared bootstrap bearer secret exists. +- No manual SQL insert is part of the supported process. +- The operation cannot bootstrap an unknown ActorProfile. +- The operation cannot bootstrap a service actor. +- Additional Access Administrators are created through the normal grant API. + +--- + +## 20. Core Operations + +### 20.1 Read/update own profile + +An active human may read their ActorProfile and update only permitted display fields. + +The actor cannot change: + +- actor kind; +- status; +- issuer or subject; +- roles or grants; +- provisioning method; +- audit fields. + +### 20.2 Suspend actor + +The transaction MUST: + +1. authorize `actor.profile.suspend`; +2. lock the target ActorProfile; +3. require status `active`; +4. prohibit self-suspension; +5. enforce final Access Administrator safety; +6. set suspension fields; +7. append audit and invalidation events; +8. commit. + +### 20.3 Reactivate actor + +The transaction MUST: + +1. authorize `actor.profile.reactivate`; +2. lock the ActorProfile; +3. require status `suspended`; +4. set status active and reactivation audit fields/event; +5. preserve separately revoked grants; +6. commit. + +### 20.4 Deactivate actor + +The transaction MUST: + +1. authorize `actor.profile.deactivate`; +2. lock the ActorProfile; +3. require status active or suspended; +4. prohibit self-deactivation; +5. enforce final Access Administrator safety; +6. set terminal deactivation fields; +7. append audit and invalidation events; +8. commit. + +### 20.5 Issue AdminRoleGrant + +The transaction MUST: + +1. authorize `admin_role.grant`; +2. lock the target ActorProfile; +3. require an active human target; +4. prohibit self-grant; +5. validate role/scope compatibility; +6. validate the referenced project when project-scoped; +7. verify no identical active grant exists; +8. insert the immutable grant; +9. append audit event; +10. commit. + +### 20.6 Issue ProjectRoleGrant + +The transaction MUST: + +1. authorize `project.role_grant.manage` for the exact project; +2. lock the target ActorProfile and project active-grant selector; +3. require an active human target; +4. prohibit self-grant; +5. create the immutable qualification snapshot; +6. revoke an existing active grant only when the request explicitly replaces it; +7. create the new manual ProjectRoleGrant; +8. append audit and invalidation events where replacement occurred; +9. commit. + +--- + +## 21. API Contract + +All endpoints use the independent `/v1` API namespace. This does not change the product release from Workstream v0.1. + +### 21.1 Current actor + +```http +GET /v1/actors/me +PATCH /v1/actors/me +GET /v1/actors/me/authorization-context?project_id={project_id} +``` + +PATCH request: + +```json +{ + "display_name": "Abiola Adeshina", + "contact_email": "optional@example.com" +} +``` + +Authorization-context response: + +```json +{ + "actor_profile_id": "uuid", + "status": "active", + "domains": ["contributor", "admin"], + "admin_role_grants": [], + "project_role_grant": null, + "capabilities": [] +} +``` + +Capabilities are computed server-side from a registered allowlist. Clients cannot submit an arbitrary permission to be evaluated. + +### 21.2 Actor administration + +```http +GET /v1/admin/actors +GET /v1/admin/actors/{actor_profile_id} +POST /v1/admin/actors/{actor_profile_id}/suspend +POST /v1/admin/actors/{actor_profile_id}/reactivate +POST /v1/admin/actors/{actor_profile_id}/deactivate +``` + +State-change request: + +```json +{ + "reason": "Required administrative reason" +} +``` + +### 21.3 Identity-link administration + +```http +GET /v1/admin/actors/{actor_profile_id}/identity-link +POST /v1/admin/identity-links/{identity_link_id}/revoke +POST /v1/admin/identity-links/{identity_link_id}/reactivate +``` + +No endpoint adds a second human identity link in v0.1. + +### 21.4 Service actors + +```http +POST /v1/admin/service-actors +GET /v1/admin/service-actors +GET /v1/admin/service-actors/{actor_profile_id} +``` + +### 21.5 Admin-role grants + +```http +POST /v1/admin-role-grants +GET /v1/admin-role-grants +GET /v1/actors/{actor_profile_id}/admin-role-grants +POST /v1/admin-role-grants/{grant_id}/revoke +``` + +Create request: + +```json +{ + "actor_profile_id": "uuid", + "role": "project_manager", + "scope_type": "project", + "scope_project_id": "uuid", + "reason": "Assigned to manage the project" +} +``` + +### 21.6 Project-role grants + +```http +POST /v1/projects/{project_id}/role-grants +GET /v1/projects/{project_id}/role-grants +GET /v1/projects/{project_id}/role-grants/{grant_id} +POST /v1/projects/{project_id}/role-grants/{grant_id}/revoke +``` + +Create request: + +```json +{ + "contributor_id": "uuid", + "role": "reviewer", + "grant_method": "manual", + "grant_reason": "Qualified project reviewer", + "qualification": { + "skills_snapshot": {}, + "reputation_snapshot": {"status": "unavailable"}, + "prior_project_work_refs": [], + "external_expertise_refs": [] + }, + "replace_active_grant": false +} +``` + +### 21.7 Permission catalog + +```http +GET /v1/authorization/permissions +GET /v1/authorization/admin-role-definitions +``` + +These endpoints return registered definitions. They do not expose or accept arbitrary dynamic policy code. + +--- + +## 22. Error Contract + +Errors MUST use the Workstream structured error envelope. + +| HTTP | Code | Condition | +|---:|---|---| +| 400 | `invalid_request` | Malformed or incompatible request fields | +| 401 | `missing_token` | No bearer token | +| 401 | `invalid_token` | Signature, issuer, audience, time, or token format invalid | +| 401 | `invalid_token_claims` | Required verified claim missing or invalid | +| 401 | `identity_verification_unavailable` | Token cannot be safely verified | +| 403 | `required_scope_missing` | Coarse Workstream token scope missing | +| 403 | `unsupported_subject_kind` | Agent or Space token in v0.1 | +| 403 | `service_actor_not_provisioned` | Unknown service subject | +| 403 | `identity_link_revoked` | External identity link revoked | +| 403 | `actor_suspended` | Actor is suspended | +| 403 | `actor_deactivated` | Actor is deactivated | +| 403 | `permission_not_granted` | No effective role/grant supplies the permission | +| 403 | `scope_not_authorized` | Admin role does not cover the project | +| 403 | `self_grant_forbidden` | Actor attempted to grant themselves authority | +| 403 | `self_role_revoke_forbidden` | Actor attempted to revoke own admin grant | +| 403 | `resource_guard_denied` | Global or consuming resource guard failed | +| 404 | `actor_not_found` | Authorized admin lookup target absent | +| 404 | `grant_not_found` | Authorized lookup target absent | +| 404 | `resource_not_found` | Resource absent or concealed by authorization policy | +| 409 | `actor_already_suspended` | Suspension replay conflicts with current state | +| 409 | `actor_not_suspended` | Reactivation target is not suspended | +| 409 | `actor_deactivated_terminal` | Transition attempted from deactivated state | +| 409 | `last_access_administrator` | Operation would remove final Access Administrator | +| 409 | `admin_role_grant_exists` | Identical effective grant already active | +| 409 | `project_role_grant_exists` | Active project grant exists and replacement not requested | +| 409 | `identity_link_conflict` | Issuer subject already maps to another ActorProfile | +| 409 | `resource_project_mismatch` | Related resources belong to different projects | +| 409 | `idempotency_mismatch` | Idempotency key reused with different request | +| 422 | `invalid_role_scope` | Role cannot use requested scope | +| 422 | `invalid_project_role` | Project contributor role invalid | +| 422 | `qualification_snapshot_invalid` | Required snapshot shape/ownership invalid | + +Unauthorized list and read endpoints MUST not reveal hidden records through total counts or distinct error messages. + +--- + +## 23. Database Constraints and Indexes + +Core invariants MUST be enforced in PostgreSQL. + +### 23.1 Required constraints + +```text +ActorIdentityLink: + UNIQUE(issuer, subject) + UNIQUE(actor_profile_id) + +AdminRoleGrant: + partial UNIQUE(actor_profile_id, role) + WHERE status = 'active' AND scope_type = 'system' + partial UNIQUE(actor_profile_id, role, scope_project_id) + WHERE status = 'active' AND scope_type = 'project' + +ProjectRoleGrant: + partial UNIQUE(project_id, contributor_id) + WHERE status = 'active' + +ProjectRoleQualificationSnapshot: + immutable after insert + +AuthorityControl: + PRIMARY KEY(id) + CHECK(id = 1) +``` + +Required check constraints: + +```text +ActorProfile: + actor_kind IN ('human', 'service') + status IN ('active', 'suspended', 'deactivated') + automatic_first_access => actor_kind = 'human' + manual_service_provisioning => actor_kind = 'service' + +ActorIdentityLink: + status IN ('active', 'revoked') + subject_kind IN ('human', 'service') + +AdminRoleGrant: + system scope => scope_project_id IS NULL + project scope => scope_project_id IS NOT NULL + access_administrator/operator => system scope + +ProjectRoleGrant: + role IN ('submitter', 'reviewer', 'both') + status IN ('active', 'revoked') + manual => automation_policy_ref IS NULL +``` + +Foreign keys MUST prevent project/snapshot/contributor mismatches. Composite foreign keys MUST be used where necessary. + +### 23.2 Required indexes + +```text +ActorProfile(status, actor_kind) +ActorProfile(last_seen_at) +ActorIdentityLink(issuer, subject, status) +AdminRoleGrant(actor_profile_id, status) +AdminRoleGrant(role, scope_type, scope_project_id, status) +ProjectRoleGrant(project_id, contributor_id, status) +ProjectRoleGrant(contributor_id, status) +ProjectRoleQualificationSnapshot(project_id, contributor_id, captured_at) +AuditEvent(actor_profile_id, occurred_at) +AuditEvent(project_id, occurred_at) +``` + +All timestamps are stored in UTC. Authorization expiry/time comparisons use database time. + +--- + +## 24. Transaction and Concurrency Requirements + +### 24.1 First-access race + +Two concurrent requests for one new `(issuer, subject)` result in one ActorProfile and one ActorIdentityLink. + +### 24.2 Duplicate AdminRoleGrant race + +Two concurrent identical grant requests result in one active grant. An exact idempotent replay returns the existing grant; another request returns `admin_role_grant_exists`. + +### 24.3 Duplicate ProjectRoleGrant race + +Two concurrent project-grant requests for one contributor/project result in one active grant. Replacement requires locking the active grant selector and an explicit replacement request. + +### 24.4 Last Access Administrator race + +Concurrent operations that would revoke, suspend, or deactivate Access Administrators MUST lock a common authority-guard row or equivalent database-protected selector. At least one effective active Access Administrator must remain after commit. + +Counting without a shared lock is insufficient. + +### 24.5 Grant versus authorization + +Authorization loads effective grants inside the command transaction for sensitive mutations. + +The outcome may be: + +- authorization locks/observes the active grant before revocation commits and the command completes; or +- revocation commits first and the command is denied. + +A router-level role check performed before the transaction MUST be revalidated inside the application transaction for sensitive mutations. + +### 24.6 Suspension versus active request + +A request already authorized and committed before suspension remains valid history. Suspension prevents later authorization. Long-running workers MUST re-resolve current ActorProfile and grant state before committing an actor-attributed mutation. + +### 24.7 Idempotency + +The following mutations require an idempotency key: + +- service actor creation; +- AdminRoleGrant creation/revocation; +- ProjectRoleGrant creation/revocation; +- actor suspend/reactivate/deactivate; +- identity-link revoke/reactivate. + +Same key and same canonical request returns the existing result. Same key and different request returns `idempotency_mismatch`. + +--- + +## 25. Audit Events + +All events are append-only. + +### Identity and profile + +- `ActorProfileProvisioned` +- `ServiceActorProvisioned` +- `ActorIdentityLinked` +- `ActorIdentityLinkRevoked` +- `ActorIdentityLinkReactivated` +- `ActorProfileSuspended` +- `ActorProfileReactivated` +- `ActorProfileDeactivated` + +### Admin authority + +- `InitialAccessAdministratorBootstrapped` +- `AdminRoleGrantIssued` +- `AdminRoleGrantRevoked` +- `AdminRoleGrantIssueDenied` +- `LastAccessAdministratorOperationDenied` + +### Project contributor authority + +- `ProjectRoleQualificationSnapshotCaptured` +- `ProjectRoleGrantIssued` +- `ProjectRoleGrantReplaced` +- `ProjectRoleGrantRevoked` + +### Authorization + +- `SensitiveAuthorizationAllowed` +- `SensitiveAuthorizationDenied` +- `AuthorityInvalidationRequested` + +Every event includes: + +- event ID; +- event version; +- occurred-at database timestamp; +- acting ActorProfile or system actor; +- target ActorProfile; +- effective grant ID used by the acting actor; +- project ID where applicable; +- target grant/link ID; +- reason; +- correlation ID; +- idempotency key where applicable; +- before and after state for mutable records. + +Tokens, raw JWTs, secrets, and full identity-provider payloads MUST NOT be stored in audit events. + +--- + +## 26. Authorization Service Interface + +The application layer MUST expose one internal interface equivalent to: + +```python +class AuthorizationService: + async def build_context( + self, + verified_token: VerifiedIssuerToken, + request_id: UUID, + correlation_id: UUID, + ) -> AuthorizationContext: ... + + async def authorize( + self, + context: AuthorizationContext, + permission: Permission, + resource: ResourceContext, + *, + uow: UnitOfWork, + ) -> AuthorizationDecision: ... + + async def require( + self, + context: AuthorizationContext, + permission: Permission, + resource: ResourceContext, + *, + uow: UnitOfWork, + ) -> AuthorizationDecision: ... +``` + +`require` raises the stable authorization error for a denied decision. + +`Permission` must be a registered enum/value object, not an unchecked free-form string from a client. + +`ResourceContext` includes the canonical resource type, ID, resolved project ID, owner/creator actor ID where required, and current lifecycle state. + +--- + +## 27. FastAPI Integration + +### 27.1 Dependencies + +Implement reusable dependencies equivalent to: + +```text +get_verified_issuer_token +get_or_provision_actor_context +require_active_actor +require_permission(permission, resource_loader) +``` + +### 27.2 Router rules + +Routers MUST: + +- validate the request contract; +- resolve the AuthorizationContext; +- invoke one application command/query; +- return the stable response/error envelope. + +Routers MUST NOT: + +- query role tables directly; +- perform permission unions; +- infer project ownership from request JSON; +- implement self-review or task-ban checks locally; +- forward the human token to another service. + +### 27.3 Worker rules + +A worker executing an actor-attributed command MUST load the current ActorProfile and grant state before committing. The actor context serialized into a job is evidence of the request, not current authority. + +System-owned recovery workers use a fixed Workstream system actor and explicit system permission, not a fabricated human administrator. + +--- + +## 28. Security Requirements + +1. Deny by default. +2. Trust only configured Identity Issuer algorithms, issuer, audience, and JWKS. +3. Never accept Workstream role claims from the token as canonical. +4. Never use email, display name, skill, or reputation as a direct authorization key. +5. Never auto-provision service, agent, or Space subjects. +6. Never let an actor grant themselves authority. +7. Never permit loss of the final Access Administrator. +8. Never expose token/JWKS material in application logs. +9. Never cache authorization decisions across requests. +10. Revalidate authority inside sensitive mutation transactions. +11. Use constant, registered permission identifiers. +12. Enforce project ownership in both authorization service and database relationships. +13. Rate-limit first-access provisioning and admin mutation endpoints through the existing API control layer. +14. Protect bootstrap operation as a local deployment operation. +15. Encrypt production connections and managed storage. +16. Record reasons for every suspension, deactivation, role grant, and revocation. + +--- + +## 29. Privacy and Disclosure + +- Actor search/list results expose only fields required for the caller's role. +- Access Administrators may view identity-link metadata but not raw tokens or provider credentials. +- Audit Authority receives minimal actor attribution, not unrestricted profile data. +- Project Managers may search contributors needed for their project grant workflow but may not enumerate unrelated private project activity. +- Finance Authority receives contribution and award fields required for compensation, not unrelated identity/profile data. +- Unauthorized project records are omitted before counts and pagination cursors are calculated. +- Profile contact email is never an authorization key and is excluded from events unless explicitly required by a notification adapter. + +--- + +## 30. Observability + +### 30.1 Logs and traces + +Include when applicable: + +- correlation ID; +- request ID; +- ActorProfile ID; +- actor kind/status; +- permission identifier; +- project/resource identifiers; +- matched grant IDs; +- decision allow/deny and denial code; +- token issuer and hashed/token-safe subject reference. + +Do not include bearer tokens, raw JWT claims, secrets, or unnecessary personal data. + +### 30.2 Metrics + +Required metrics: + +```text +actor_first_access_total{result} +actor_first_access_conflict_total +actor_status_total{status,kind} +identity_link_total{status,kind} +token_verification_total{result} +jwks_refresh_total{result} +authorization_decision_total{permission,result,denial_code} +admin_role_grant_total{role,scope,status} +project_role_grant_total{role,status} +authority_invalidation_total{reason} +bootstrap_attempt_total{result} +``` + +### 30.3 Alerts + +Alert on: + +- sustained token verification failure; +- JWKS refresh failure without valid cache; +- repeated bootstrap attempts after initialization; +- repeated last-access-admin operation attempts; +- unusual admin-role grant/revoke rate; +- authority invalidation reconciliation backlog; +- actor provisioning unique-conflict rate above expected concurrent first access. + +--- + +## 31. Conformance Tests + +### 31.1 Token tests + +- Valid issuer token with Workstream audience succeeds. +- Wrong issuer fails. +- Wrong audience fails. +- Unknown algorithm fails. +- Invalid signature fails. +- Expired/not-yet-valid token fails. +- Missing `sub`, `jti`, or `subject_kind` fails. +- Missing coarse scope fails. +- Unknown `kid` triggers one refresh then fails safely. +- Better Auth token is not accepted as final product token. + +### 31.2 First-access tests + +- New human token creates one ActorProfile and one ActorIdentityLink. +- Created human is Contributor-domain only. +- Created human has no AdminRoleGrant or ProjectRoleGrant. +- Repeated access returns the same ActorProfile. +- Concurrent first access creates no duplicate or orphan. +- Unknown service token creates nothing and is denied. +- Agent/Space tokens create nothing and are denied. + +### 31.3 Actor-state tests + +- Access Administrator suspends another actor with reason. +- Suspended actor cannot mutate business state. +- Reactivation restores non-revoked grants. +- Deactivation is terminal. +- Historical records remain linked. +- Access Administrator cannot suspend/deactivate self. +- Final Access Administrator cannot be suspended/deactivated. + +### 31.4 AdminRoleGrant tests + +- Only Access Administrator can issue/revoke. +- Self-grant is denied. +- Self-revoke is denied. +- Role/scope compatibility is enforced. +- Duplicate active grant is prevented by database constraint. +- Project-scoped role cannot act in another project. +- System-scoped role receives only its own permission group. +- Revocation takes effect without a new issuer token. + +### 31.5 ProjectRoleGrant tests + +- Only Project Manager covering the project can grant/revoke. +- Project Manager cannot grant themselves contributor authority. +- Snapshot is mandatory and immutable. +- Missing reputation may be recorded unavailable. +- Submitter grant cannot review. +- Reviewer grant cannot claim submission tasks. +- Both grant supplies union but not self-review bypass. +- Admin role alone cannot submit or review. +- Duplicate active project grant is prevented. +- Replacement creates a new grant and revokes old history. +- Revocation is visible on next request. + +### 31.6 Permission matrix tests + +For every permission identifier, create table-driven tests proving: + +- each allowed role/scope combination; +- each denied role combination; +- project-scope boundary; +- system scope does not create superuser authority; +- explicit resource guard overrides an allowed permission candidate; +- unauthorized list counts do not leak hidden records. + +### 31.7 Bootstrap tests + +- Bootstrap succeeds only for an existing active human ActorProfile. +- Bootstrap succeeds only when zero active Access Administrators exist. +- Bootstrap creates system-scoped Access Administrator. +- Later bootstrap attempt fails and is audited. +- Service actor cannot be bootstrapped. +- No public bootstrap endpoint exists. + +### 31.8 Concurrency tests + +- Concurrent human first access leaves one profile/link. +- Concurrent identical AdminRoleGrant leaves one active grant. +- Concurrent ProjectRoleGrant leaves one active grant. +- Concurrent final-admin suspension/revocation attempts leave at least one effective Access Administrator. +- Grant revocation versus sensitive command produces one serializable authority outcome. + +### 31.9 Service actor tests + +- Access Administrator can provision a service actor. +- Service actor has no Contributor domain. +- Service actor cannot receive AdminRoleGrant or ProjectRoleGrant. +- Active service binding is still required by consuming adapter endpoint. +- Suspended service actor cannot call callback/integration endpoint. + +--- + +## 32. Live API Drill + +The live drill MUST use supported Workstream APIs and the bootstrap operation. Direct database changes invalidate the drill. + +### 32.1 Drill sequence + +1. Start Workstream with no ActorProfiles or Access Administrators. +2. Human A presents a valid issuer token and calls `/v1/actors/me`. +3. Prove Human A has one active ActorProfile, Contributor domain, and no roles/grants. +4. Run the one-time bootstrap operation for Human A. +5. Prove Human A now has system-scoped `access_administrator` only. +6. Human B signs in and is provisioned as a default Contributor. +7. Human A grants Human B system-scoped `project_manager`. +8. Human B creates or manages Project P. +9. Human C signs in and is provisioned as a default Contributor. +10. Human B captures a qualification snapshot and grants Human C `submitter` for Project P. +11. Prove Human C can access submitter capability for Project P but cannot access review queue capability. +12. Human D signs in. +13. Human B grants Human D `reviewer` for Project P. +14. Prove Human D can access reviewer capability for Project P but cannot claim submitter work. +15. Prove Human B cannot review by Project Manager role alone. +16. Human A grants Finance Authority to Human E for Project P. +17. Prove Human E can manage compensation-policy capability but cannot manage ProjectRoleGrant or review. +18. Human A grants Audit Authority to Human F for Project P. +19. Prove Human F can read authorized audit views but no mutation. +20. Revoke Human D's reviewer grant and prove the same issuer token no longer authorizes review capability. +21. Suspend Human C and prove all business mutations are denied. +22. Reactivate Human C and prove the non-revoked submitter grant is effective again. +23. Attempt self-grant, self-admin-role revoke, cross-project access, and final Access Administrator removal; prove all are denied and audited. +24. Provision one service actor and prove an unknown service token is denied while the provisioned service actor resolves. +25. Export the complete audit chain and database constraint evidence. + +### 32.2 Drill evidence + +The report MUST include: + +- request and correlation IDs; +- relevant response bodies and error codes; +- ActorProfile/IdentityLink identifiers; +- role/grant identifiers and scope; +- database constraint checks; +- audit events; +- proof that no duplicate ActorProfile was created; +- proof that revocation used the same unexpired issuer token; +- proof that admin roles did not substitute for ProjectRoleGrant; +- proof that no direct database edit occurred except the supported bootstrap operation. + +--- + +## 33. Implementation Delivery Order + +### Phase 1: Token verification and request context + +- issuer/JWKS configuration; +- VerifiedIssuerToken contract; +- token verification dependency; +- safe JWKS cache/refresh; +- structured authentication errors. + +Exit: token conformance tests pass. + +### Phase 2: ActorProfile and identity link + +- migrations and constraints; +- first-human-access transaction; +- service subject denial; +- actor resolver; +- self-profile APIs. + +Exit: first-access and concurrency tests pass. + +### Phase 3: Bootstrap and AdminRoleGrant + +- system bootstrap actor; +- one-time bootstrap operation; +- AdminRoleGrant schema; +- five role definitions and scope compatibility; +- grant/revoke APIs; +- last-access-admin concurrency guard. + +Exit: bootstrap and admin-role conformance tests pass. + +### Phase 4: Actor state and identity-link administration + +- suspend/reactivate/deactivate; +- link revoke/reactivate; +- service actor provisioning; +- authority invalidation events. + +Exit: actor-state and service-actor tests pass. + +### Phase 5: ProjectRoleGrant + +- qualification snapshot; +- project grant schema/constraints; +- Project Manager authority; +- create/replace/revoke APIs; +- project permission candidates. + +Exit: project-grant and concurrency tests pass. + +### Phase 6: Authorization service + +- permission registry; +- role matrices; +- scope resolver; +- ResourceContext loaders; +- global resource guards; +- FastAPI permission dependencies; +- worker revalidation. + +Exit: table-driven permission-matrix tests pass. + +### Phase 7: Audit, observability, and live drill + +- all audit events; +- metrics/traces; +- denied-decision evidence; +- live API drill; +- coding-agent evidence pack. + +Exit: definition of done passes and `WS-REV-001` may begin. + +--- + +## 34. Definition of Done + +This specification is complete only when: + +- Workstream verifies only final issuer-owned tokens and JWKS. +- Every valid first-time human subject creates exactly one active ActorProfile and identity link. +- Default human actors are Contributor-domain members with no project or admin authority. +- Unknown service, agent, and Space subjects are not auto-provisioned. +- Service actor provisioning is controlled and audited. +- The five AdminRoleGrants and scope rules are implemented. +- ProjectRoleGrant is project-scoped, manual, snapshot-backed, and revocable. +- Admin roles never substitute for submitter/reviewer ProjectRoleGrant. +- Permission catalog and role matrices are centralized and table-tested. +- Authorization resolves canonical resource project/ownership and applies resource guards. +- Role revocation takes effect with the same still-valid issuer token. +- Actor suspension/deactivation invalidates authority without deleting history. +- Final Access Administrator safety is database/concurrency proven. +- All required constraints and indexes exist in PostgreSQL. +- Every sensitive authority change is auditable. +- The live API drill completes with no unsupported database edit. +- Existing Workstream intake tests remain green. +- The implementation evidence pack distinguishes implemented, tested, and live-proven behavior. + +--- + +## 35. Explicitly Out of Scope + +- Automated ProjectRoleGrant issuance from skills or reputation thresholds. +- Reputation scoring or reputation-policy administration. +- Delegated agent access to Workstream. +- Space subjects and organization membership. +- Multiple Identity Issuer subjects linked to one Workstream ActorProfile. +- Subject merge or split. +- Dynamic administrator roles created through the API. +- User-authored permission policies. +- Attribute-policy language or general policy engine. +- Adjudication above human review. +- Payment provider, points ledger, wallet, or settlement implementation. +- Direct human access to Flow Node. + +These may be additive future specifications. They MUST NOT be inferred into v0.1 code. + +--- + +## 36. Coding-Agent Handoff Rules + +The coding agent MUST: + +1. read this specification completely before modifying code; +2. inspect existing FastAPI auth, actor, role, project, audit, and migration structures; +3. extend existing canonical objects instead of creating parallel models; +4. preserve the existing intake lifecycle and tests; +5. implement phases in the stated order; +6. use PostgreSQL for constraint and concurrency proof; +7. centralize all permission evaluation in AuthorizationService; +8. stop and report a normative conflict rather than inventing a role or permission; +9. produce the live drill and implementation evidence pack; +10. begin `WS-REV-001` only after this specification's definition of done is satisfied. + +The coding agent MUST NOT: + +- add a second ActorProfile for administrators; +- add a single mutable `role` column to ActorProfile; +- accept role claims from the Identity Issuer token as product authority; +- auto-provision service, agent, or Space actors; +- grant project authority from skills or reputation automatically; +- allow self-grant or loss of the final Access Administrator; +- implement permissions separately in routers; +- cache role decisions across requests; +- remove authority history; +- introduce unrelated products or infrastructure. diff --git a/docs/reference_specs/WS-AUTH-001-actor-profile-role-and-authorization-service-specification.pdf b/docs/reference_specs/WS-AUTH-001-actor-profile-role-and-authorization-service-specification.pdf new file mode 100644 index 000000000..8b3298705 Binary files /dev/null and b/docs/reference_specs/WS-AUTH-001-actor-profile-role-and-authorization-service-specification.pdf differ diff --git a/docs/reference_specs/WS-CON-001-contribution-record-and-compensation-boundary-specification.pdf b/docs/reference_specs/WS-CON-001-contribution-record-and-compensation-boundary-specification.pdf new file mode 100644 index 000000000..39f9b88a4 Binary files /dev/null and b/docs/reference_specs/WS-CON-001-contribution-record-and-compensation-boundary-specification.pdf differ diff --git a/docs/reference_specs/WS-IMP-001-workstream-v0.1-coding-agent-implementation-specification.md b/docs/reference_specs/WS-IMP-001-workstream-v0.1-coding-agent-implementation-specification.md new file mode 100644 index 000000000..19b5892fb --- /dev/null +++ b/docs/reference_specs/WS-IMP-001-workstream-v0.1-coding-agent-implementation-specification.md @@ -0,0 +1,1186 @@ +# WS-IMP-001: Workstream v0.1 Coding-Agent Implementation Specification + +**Status:** Locked for implementation handoff +**Architecture maturity:** Implementation contract derived from approved design candidates; runtime proof remains required +**Version:** 0.1 +**Date:** 2026-07-11 +**Owner:** Workstream Engineering +**Scope:** Python/FastAPI implementation of the Workstream v0.1 architecture baseline, review and revision lifecycle, contribution records, compensation boundary, identity integration, evidence ports, asynchronous jobs, tests and proof artifacts +**Normative parents:** `WS-ARCH-001`, `WS-REV-001`, `WS-CON-001` +**Existing proof:** `WS-POL-001-16` proves the intake spine through durable checker completion and `review_pending` +**Delivery rule:** The coding agent MUST implement phases in order and MUST NOT reinterpret locked lifecycle decisions + +--- + +## 1. Purpose + +This document is the deterministic execution contract for the coding agent implementing Workstream v0.1. + +It converts the architecture and lifecycle documents into: + +- a fixed technology and module direction; +- a source-precedence order; +- mandatory repository-integration rules; +- migration and persistence requirements; +- application-service and transaction boundaries; +- API, worker, identity and adapter requirements; +- an ordered implementation plan; +- conformance, concurrency, recovery and live-drill gates; +- explicit prohibited deviations; +- a definition of done based on evidence, not code presence. + +This document does not authorize the coding agent to redesign the product. Where a normative parent contains more detail, that parent remains controlling within its scope. + +--- + +## 2. Normative Precedence + +The coding agent MUST resolve requirements in this order: + +1. `WS-CON-001` controls the contribution, compensation-policy, award, outbox-fulfillment and callback boundary. +2. `WS-REV-001` controls reviewer authority, queue routing, leases, immutable review/revision chains and reject behavior. +3. `WS-ARCH-001` controls system boundaries, C4 components, technology, external ports, security, deployment and operational architecture. +4. Existing runtime-proven intake behavior controls implementation compatibility through `review_pending`. +5. Existing repository conventions control names and file placement only when they do not conflict with items 1-4. + +If two requirements appear inconsistent, the coding agent MUST: + +1. preserve the current proven intake path; +2. apply the narrower normative specification to its own scope; +3. document the apparent conflict in the implementation report; +4. stop before inventing a new business rule. + +The coding agent MUST NOT choose a convenient behavior merely because the source code already contains a placeholder or incomplete implementation. + +--- + +## 3. Locked Technology Decisions + +The following choices are final for Workstream v0.1. + +| Concern | Required implementation | +|---|---| +| Language | Python only for lifecycle and application code | +| Web API | FastAPI with Pydantic request/response contracts | +| Persistence | PostgreSQL with SQLAlchemy and repository/unit-of-work boundaries matching the existing codebase | +| Schema migrations | The migration framework already present in the repository; Alembic when the current stack uses Alembic | +| Background work | Celery workers | +| Broker | Redis, used only for transient delivery/coordination | +| Checker execution | Existing isolated checker runner/process boundary | +| Canonical state | PostgreSQL | +| External delivery | Transactional outbox with at-least-once delivery and idempotent business effects | +| Production evidence | `FlowNodeAdapter` behind `ArtifactStorePort` and `SemanticIndexPort` | +| Development/CI evidence | `LocalStorageAdapter` or in-memory test adapter behind the same ports | +| Identity | Identity Issuer token/JWKS boundary plus local Workstream actor and role resolution | +| Testing | Existing Python test runner, with PostgreSQL integration tests for locking/constraint behavior | + +The coding agent MUST NOT introduce Rust, PyO3, a second API framework, another canonical database, Kafka, a new workflow engine, a provider-specific payment SDK in the domain, or new microservices for this implementation. + +--- + +## 4. Mandatory Repository Reconnaissance + +Before changing code, the coding agent MUST produce a short repository map in its implementation notes. + +It MUST identify: + +1. the FastAPI application entry point; +2. versioned router registration; +3. Pydantic request and response packages; +4. SQLAlchemy models and repository pattern; +5. unit-of-work or transaction helpers; +6. migration directory and current migration head; +7. Celery application, queues and task registration; +8. checker runner integration; +9. authentication/token dependencies; +10. authorization helpers; +11. structured error envelope; +12. audit-event mechanism; +13. outbox or event infrastructure already present; +14. existing project, task, assignment, submission, checker and actor models; +15. test fixtures for PostgreSQL, Redis and authenticated actors. + +Repository reconnaissance is read-only. The coding agent MUST NOT begin by creating a parallel `app`, `core`, `domain`, `api_v2` or standalone service tree. + +If the repository is not available, the coding agent MUST stop and report `source_repository_missing`. It MUST NOT generate a speculative replacement application. + +--- + +## 5. Integration Rules + +### 5.1 Preserve the proven intake spine + +The existing path through `review_pending` is an integration boundary, not a rewrite invitation. + +The coding agent MUST preserve: + +- ProjectGuideSnapshot creation; +- sufficiency and policy-derivation jobs; +- manager approval of the exact effective policy; +- task locked-policy context; +- failed pre-submit behavior with no submission side effect; +- submission finalization; +- durable CheckerRun creation and result persistence; +- successful transition to `review_pending`. + +The new implementation begins by attaching the locked review lifecycle to the successful checker-admission transaction or its existing durable continuation point. + +### 5.2 No duplicate domain models + +When an existing object represents the same business identity, the coding agent MUST extend or migrate it rather than create a second competing model. + +Examples: + +- extend the existing assignment relationship to support frozen compensation reference and task-scoped ban; +- map the existing finalized submission to `SubmissionVersion` if the identity and immutability contract can be preserved; +- reuse the existing audit/outbox framework when it meets the normative contract; +- do not create `NewTask`, `ReviewTaskV2`, `PaymentRecord` or a second contributor table. + +### 5.3 Thin transport layer + +FastAPI routers MUST: + +- validate input; +- resolve identity and authority through dependencies; +- invoke one application command or query; +- translate the result into the stable response/error contract. + +Routers MUST NOT contain state-machine transitions, SQL queries, queue-ordering logic, compensation evaluation or direct external calls. + +### 5.4 Domain isolation + +Review, contribution and compensation domain modules MUST NOT import: + +- FastAPI request objects; +- Celery task objects; +- Redis clients; +- Flow Node HTTP clients; +- compensation-provider clients; +- UI types. + +External behavior enters through ports implemented in infrastructure modules. + +--- + +## 6. Logical Module Map + +The coding agent MUST map the following logical components into the existing package structure. Names may follow repository conventions, but ownership MUST remain recognizable. + +```text +workstream +├── api +│ ├── dependencies # token, actor, role and request context +│ ├── review_routes +│ ├── compensation_policy_routes +│ ├── contribution_routes +│ ├── compensation_award_routes +│ └── integration_callback_routes +│ +├── application +│ ├── review_commands +│ ├── review_queries +│ ├── contribution_commands +│ ├── compensation_policy_commands +│ ├── compensation_queries +│ ├── artifact_commands +│ └── reconciliation_commands +│ +├── domain +│ ├── authority +│ ├── review +│ ├── contribution +│ ├── compensation +│ ├── artifacts +│ └── events +│ +├── persistence +│ ├── models +│ ├── repositories +│ └── unit_of_work +│ +├── ports +│ ├── artifact_store +│ ├── semantic_index +│ └── compensation_fulfillment +│ +├── adapters +│ ├── flow_node +│ ├── local_storage +│ └── compensation_http +│ +├── workers +│ ├── review_sweeps +│ ├── checker_jobs +│ ├── outbox_dispatch +│ ├── reconciliation +│ └── projection_rebuild +│ +└── tests + ├── unit + ├── integration + ├── concurrency + ├── contract + └── live_drill +``` + +This is a responsibility map, not authorization to move stable modules unnecessarily. + +--- + +## 7. Delivery Phases + +The coding agent MUST implement and verify phases in the order below. It MUST keep the repository testable after every phase. + +| Phase | Required result | Exit gate | +|---:|---|---| +| 0 | Reconnaissance, mapping and migration plan | Repository map and no unresolved identity duplication | +| 1 | Shared identity, actor, authority and transaction foundations | Identity/authorization tests pass without breaking intake | +| 2 | Review/revision schema and persistence | Migrations, constraints and repository tests pass on PostgreSQL | +| 3 | Review queue, claims, leases, decisions and recovery | WS-REV-001 conformance, concurrency and live drill pass | +| 4 | Contribution and compensation-policy schema/persistence | WS-CON-001 persistence and policy tests pass | +| 5 | Atomic review-to-contribution/award transaction | Creation-matrix, replay and race tests pass | +| 6 | Outbox delivery and fulfillment callback | Adapter contract, callback and reconciliation tests pass | +| 7 | Flow Node ports and production adapter | Evidence ingest/verify/pin/search/recovery tests pass | +| 8 | Read APIs, operations UI support and observability | Authorized read-model and operational metrics tests pass | +| 9 | Full internal-loop drill and release evidence | End-to-end proof pack accepted | + +No phase is complete because code compiles. The stated exit gate is mandatory. + +--- + +## 8. Phase 1 - Identity, Actor and Authority Foundation + +### 8.1 Token verification + +Implement one FastAPI authentication dependency that: + +1. extracts the bearer token; +2. validates the configured issuer; +3. validates the Workstream audience; +4. validates signature through cached issuer JWKS; +5. validates `exp`, `nbf` and other required temporal claims using configured clock skew; +6. obtains the issuer subject; +7. resolves `(issuer, subject)` to one active local Workstream actor; +8. attaches actor and correlation context to the request. + +Failure codes MUST distinguish invalid token, unknown actor and inactive actor without revealing other users. + +The implementation MUST support safe JWKS key overlap and refresh. A stale cached key may be used only according to configured issuer policy; the coding agent MUST NOT silently bypass signature verification during issuer unavailability. + +### 8.2 Local role resolution + +The local role store MUST load: + +- active admin roles; +- active project scopes for those roles where applicable; +- active `ProjectRoleGrant` records; +- task assignment status for task-scoped commands. + +The token MUST NOT be treated as authoritative for `submitter`, `reviewer`, `project_manager`, `operator`, `finance_authority` or `reputation_authority`. + +### 8.3 Authorization service + +Implement command-specific guards for at least: + +- project administration; +- role-grant creation and revocation; +- submitter task claim/finalization; +- reviewer queue read; +- review claim/release/decline/decision; +- compensation-policy administration; +- adapter-binding administration; +- contribution and award reads; +- fulfillment callback service actor. + +Every guard MUST receive the local actor and exact resource/project identifiers. Generic `is_admin` checks are insufficient for resource-scoped commands. + +### 8.4 Service principals + +Flow Node and compensation-adapter integration MUST use configured service actors/credentials distinct from human request identity. + +A human bearer token MUST never be forwarded to an external system. + +--- + +## 9. Phase 2 - Review and Revision Persistence + +Implement the complete `WS-REV-001` object model, including: + +- `ProjectRoleGrant` and qualification snapshot reference; +- `SubmissionVersion` and prior-version chain; +- `SubmissionFindingResponse`; +- `ReviewQueueEntry`; +- `ReviewLease` including permanent completed/expired/released attempts; +- `Review` and prior-review chain; +- `ReviewFinding`; +- `FindingResolution`; +- `TaskAssignment` task-scoped ban extension; +- project review-policy fields. + +### 9.1 Required review constraints + +The migration MUST enforce: + +```text +UNIQUE ReviewQueueEntry(submission_version_id) +UNIQUE Review(submission_version_id) +UNIQUE SubmissionVersion(task_id, version_number) +UNIQUE FindingResolution(finding_id, submission_version_id) + +PARTIAL UNIQUE ReviewLease(review_queue_entry_id) WHERE status = 'active' +PARTIAL UNIQUE ReviewLease(reviewer_id) WHERE status = 'active' +``` + +The migration MUST also add the exact indexes and compatible-state check constraints required by `WS-REV-001` section 17. + +### 9.2 Migration rules + +Migrations MUST be forward-only and safe for the existing data set. + +For every new non-null field on existing rows, the coding agent MUST provide an explicit deterministic backfill derived from authoritative records. It MUST NOT use a random policy version, current time as historical business time, a placeholder actor, or a fabricated review decision. + +If existing data cannot satisfy a required invariant, the migration MUST stop with a precise report and a separate audited data-remediation plan. + +### 9.3 Enum and state compatibility + +Database enums or check constraints MUST represent the exact v0.1 values from `WS-REV-001`. The coding agent MUST NOT add `in_progress`, `approved`, `escalated`, `adjudication_pending` or other undocumented review decisions/states. + +--- + +## 10. Phase 3 - Review Queue, Claim and Decision Services + +### 10.1 Checker admission to queue creation + +When a durable checker admits a finalized SubmissionVersion: + +1. transition the task to `review_pending` according to the existing intake state machine; +2. create exactly one ReviewQueueEntry for the SubmissionVersion; +3. create it in the routing mode and timestamps required by `WS-REV-001`; +4. append the queue-created audit event; +5. commit all effects atomically or through the already-proven durable boundary without allowing an orphaned admitted version. + +An exact retry MUST return or observe the existing queue entry. + +### 10.2 Reviewer queue query + +Implement the reviewer view as three distinct sections: + +```json +{ + "active_lease": null, + "preferred_backlog": [], + "open_queue": [] +} +``` + +Ordering MUST follow `WS-REV-001`: + +- preferred backlog: the normative preferred order; +- open queue: FIFO using the normative queue-age field and stable ID tie-breaker; +- expired preference becomes open without resetting first queue age; +- expired or released lease returns to open queue with the normative requeue timestamp behavior. + +Queue totals MUST not reveal unauthorized project work. + +### 10.3 Claim transaction + +The claim application service MUST execute one PostgreSQL transaction: + +1. obtain database time; +2. lock the selected ReviewQueueEntry; +3. perform lazy preference/lease recovery required for that entry; +4. verify the entry remains pending and visible to the claimant; +5. verify active reviewer/both ProjectRoleGrant for the entry project; +6. verify the reviewer is not the submission creator; +7. verify the reviewer has no active global lease in v0.1; +8. verify preferred-routing rules; +9. freeze the reviewer compensation-policy version required by `WS-CON-001`; +10. insert one active ReviewLease; +11. update queue routing state; +12. append audit event; +13. commit and return the lease. + +Database partial uniqueness is the final race guard. A uniqueness conflict MUST map to the stable conflict code, not a 500 error. + +### 10.4 Release and decline + +Manual release MUST: + +- authorize only the active leased reviewer or a separately specified operator override; +- close the lease as released; +- return the entry to open routing; +- clear preferred stickiness according to `WS-REV-001`; +- record `ReviewerReleasedTask` rather than `ReviewerLeaseExpired`; +- commit in one transaction. + +Declining a preferred entry before claim MUST implement the exact preferred-routing transition and authority rule from `WS-REV-001`. + +### 10.5 Decision transaction + +The decision endpoint MUST use an idempotency key and one database transaction. + +The service MUST: + +1. lock the ReviewLease and ReviewQueueEntry; +2. use database time to verify the lease is still active and unexpired; +3. recheck the reviewer's active project grant; +4. lock the SubmissionVersion, task and TaskAssignment required by the decision; +5. validate the decision payload and finding rules; +6. insert exactly one immutable Review; +7. insert immutable findings and prior-finding resolutions; +8. close the queue entry and consume the lease; +9. apply accept, needs_revision or reject task effects; +10. execute the atomic contribution/award work after Phase 5 is installed; +11. append audit and outbox records; +12. commit and return the complete committed result. + +No decision path may update a prior SubmissionVersion, Review or ReviewFinding. + +### 10.6 Timer workers + +Implement named Celery jobs for: + +- preference expiry; +- lease expiry; +- orphan queue/lease reconciliation. + +Workers MUST claim batches with PostgreSQL locking suitable for multiple worker replicas, normally `FOR UPDATE SKIP LOCKED` over deterministic indexed candidates. + +Sweep and request-time lazy recovery MUST invoke the same application transition service. + +Redis time MUST NOT decide expiry. Use PostgreSQL/database time. + +--- + +## 11. Review API Requirements + +Implement the normative capability set: + +```http +POST /v1/projects/{project_id}/role-grants +GET /v1/projects/{project_id}/role-grants +POST /v1/projects/{project_id}/role-grants/{grant_id}/revoke + +GET /v1/reviews/queue/mine +GET /v1/projects/{project_id}/reviews/queue + +POST /v1/reviews/queue/{queue_entry_id}/claim +POST /v1/reviews/leases/{lease_id}/release +POST /v1/reviews/queue/{queue_entry_id}/decline-preference +POST /v1/reviews/leases/{lease_id}/decision + +GET /v1/tasks/{task_id}/review-chain +GET /v1/submission-versions/{version_id}/review +``` + +Endpoint names may be aligned with an existing consistent router convention only if capability boundaries, payloads, responses and error semantics remain identical and the change is documented. + +No delete or edit endpoint may exist for SubmissionVersion, Review, ReviewFinding or FindingResolution. + +--- + +## 12. Phase 4 - Contribution and Compensation Persistence + +Implement the complete `WS-CON-001` object model: + +- `ContributionRecord`; +- `CompensationPolicy`; +- `CompensationPolicyVersion`; +- `CompensationRule`; +- `CompensationAwardDefinition`; +- frozen compensation references on TaskAssignment and ReviewLease; +- `CompensationAward`; +- `ProjectCompensationAdapterBinding`; +- `CompensationFulfillmentReceipt`; +- `CompensationStatusProjection`; +- required outbox event data. + +### 12.1 Required compensation constraints + +The database MUST enforce all `WS-CON-001` section 20 constraints, including: + +```text +UNIQUE ContributionRecord(source_review_id, contribution_type) +UNIQUE CompensationPolicyVersion(compensation_policy_id, version_number) +PARTIAL UNIQUE CompensationPolicy(project_id) WHERE status = 'active' +UNIQUE CompensationRule(compensation_policy_version_id, contribution_type) +UNIQUE CompensationAwardDefinition(compensation_policy_version_id, contribution_type, instrument_type) +UNIQUE CompensationAward(contribution_record_id, instrument_type) +PARTIAL UNIQUE ProjectCompensationAdapterBinding(project_id, instrument_type) WHERE status = 'active' +UNIQUE CompensationFulfillmentReceipt(adapter_binding_id, external_event_id) +PARTIAL UNIQUE CompensationFulfillmentReceipt(compensation_award_id) WHERE reported_status = 'fulfilled' +PRIMARY KEY CompensationStatusProjection(compensation_award_id) +``` + +Quantity checks, enum checks, project-chain foreign keys, frozen-policy non-null fields and all normative indexes are mandatory. + +All monetary/point quantities MUST use exact decimal storage. Python `float` MUST NOT be used for policy or award quantities. + +### 12.2 Compensation policy service + +Implement immutable version behavior: + +- creating a policy creates an empty version 1 draft; +- creating the next version copies the current published version into a new draft; +- PUT replaces the complete draft rule set; +- publish validates the entire draft and requires `expected_current_version_id`; +- published and retired versions are immutable; +- one project has at most one active policy; +- a project may intentionally publish an explicit unpaid rule; +- assignment and review lease freeze one committed active policy version. + +Publishing a new policy MUST NOT change existing TaskAssignment, ReviewLease, ContributionRecord or CompensationAward terms. + +--- + +## 13. Phase 5 - Atomic Review-to-Contribution and Award Operation + +The contribution operation is part of the same transaction that records the Review. + +### 13.1 Creation matrix + +| Review decision | Reviewer ContributionRecord | Submitter ContributionRecord | +|---|---:|---:| +| `accept` | Create | Create | +| `needs_revision` | Create | Do not create | +| `reject` | Create | Do not create | + +The reviewer contribution is `completed_review` and is created for every valid recorded decision regardless of outcome. + +The submitter contribution is `accepted_submission` and is created only for `accept`. + +### 13.2 Deterministic award evaluation + +For each new ContributionRecord: + +1. load the frozen CompensationPolicyVersion reference from the originating TaskAssignment or ReviewLease; +2. select the exact rule for the contribution type; +3. if the rule is unpaid, create no CompensationAward and record the policy reference on the contribution as required; +4. if compensated, create one CompensationAward for every award definition; +5. copy immutable instrument, unit, quantity, policy version and adapter binding references into the award; +6. append one `CompensationAwardCreated` event per award; +7. append one `CompensationFulfillmentRequested` event per fulfillable award; +8. append `ContributionRecorded` for each contribution; +9. commit all records with the Review. + +Decision outcome MUST NOT change the reviewer's award unless the published policy itself contains a normative contribution-type distinction. v0.1 uses `completed_review`, not separate accepted/rejected review contribution types. + +### 13.3 Replay behavior + +An exact replay of a committed decision MUST return: + +- the existing Review; +- the existing reviewer ContributionRecord; +- the existing submitter ContributionRecord when accepted; +- the existing CompensationAwards; +- no new outbox events. + +The decision idempotency record MUST protect the entire combined result. The same key with a different payload returns `409 idempotency_mismatch`. + +--- + +## 14. Compensation and Contribution APIs + +Implement the normative API groups from `WS-CON-001` section 18. + +### 14.1 Compensation policies + +```http +POST /v1/projects/{project_id}/compensation-policies +POST /v1/projects/{project_id}/compensation-policies/{policy_id}/versions +PUT /v1/projects/{project_id}/compensation-policy-versions/{version_id}/draft +POST /v1/projects/{project_id}/compensation-policy-versions/{version_id}/publish +POST /v1/projects/{project_id}/compensation-policy-versions/{version_id}/retire +GET /v1/projects/{project_id}/compensation-policies +GET /v1/projects/{project_id}/compensation-policy-versions/{version_id} +``` + +### 14.2 Adapter bindings + +```http +POST /v1/projects/{project_id}/compensation-adapter-bindings +POST /v1/projects/{project_id}/compensation-adapter-bindings/{binding_id}/suspend +POST /v1/projects/{project_id}/compensation-adapter-bindings/{binding_id}/resume +POST /v1/projects/{project_id}/compensation-adapter-bindings/{binding_id}/retire +GET /v1/projects/{project_id}/compensation-adapter-bindings +``` + +### 14.3 Contribution reads + +```http +GET /v1/contributions/{contribution_record_id} +GET /v1/contributors/me/contributions +GET /v1/contributors/{contributor_id}/contributions +GET /v1/projects/{project_id}/contributions +GET /v1/projects/{project_id}/tasks/{task_id}/contributions +``` + +Ordering MUST be: + +```text +recorded_at DESC, id DESC +``` + +No create, update or delete ContributionRecord endpoint may exist. + +### 14.4 Award reads and fulfillment callback + +```http +GET /v1/compensation-awards/{award_id} +GET /v1/projects/{project_id}/compensation-awards +GET /v1/contributors/{contributor_id}/compensation-awards +POST /v1/integrations/compensation/fulfillment-reports +``` + +The callback is the only external mutation endpoint in this boundary. It may create a receipt and update the replaceable status projection. It may not change a contribution, award, policy, assignment, lease, Review, task or contributor. + +--- + +## 15. Phase 6 - Transactional Outbox and Fulfillment Adapter + +### 15.1 Outbox persistence + +Every outbox record MUST contain: + +- immutable `event_id`; +- `event_type`; +- independent `event_version`; +- aggregate type and ID; +- project ID where applicable; +- occurred-at timestamp; +- correlation ID; +- stable idempotency key; +- schema-valid non-sensitive payload; +- delivery state, attempt count and next-attempt time. + +`event_version: 1` is an event-schema version and does not mean Workstream v1.0. + +### 15.2 Dispatcher + +The Celery dispatcher MUST: + +1. select due outbox rows in deterministic batches using PostgreSQL locking suitable for multiple replicas; +2. resolve the active adapter binding referenced by the immutable award; +3. deliver the normative payload with event ID and idempotency key unchanged across retries; +4. record each attempt outcome; +5. mark durable acknowledgement when received; +6. back off transient failure; +7. expose exhausted/dead-letter work to operations; +8. never create another award or replacement event because delivery failed. + +### 15.3 Callback authentication + +The callback MUST authenticate the adapter service actor and verify: + +- active binding; +- project match; +- instrument match; +- award identity; +- external event identity; +- request authenticity/replay controls; +- allowed terminal transition. + +Credentials and secrets MUST come from runtime secret configuration. `route_key` is routing metadata and MUST NOT contain a secret. + +### 15.4 Callback state rules + +Implement exactly: + +- new valid receipt: `201`; +- exact idempotent replay: `200` and existing receipt; +- malformed payload: `400`; +- invalid service identity: `401`; +- binding/project/instrument mismatch: `403`; +- unknown award: `404`; +- idempotency or contradictory-terminal conflict: `409`; +- invalid fulfillment semantics: `422`. + +When failed and fulfilled callbacks race, fulfilled is the monotonic final state. A late acknowledgement MUST never regress fulfilled state. + +--- + +## 16. Phase 7 - Flow Node Evidence Ports + +### 16.1 Ports + +Define domain-facing protocols for: + +```python +class ArtifactStorePort(Protocol): + def request_ingest(...): ... + def get_receipt(...): ... + def verify(...): ... + def pin(...): ... + def retrieve(...): ... + +class SemanticIndexPort(Protocol): + def index(...): ... + def search(...): ... +``` + +The actual signatures MUST use repository DTO conventions and async/sync style consistently. The ports MUST not expose a provider HTTP response object to the domain. + +### 16.2 ArtifactBinding + +Persist a Workstream `ArtifactBinding` containing at least: + +- Workstream source reference; +- artifact/content ID; +- manifest ID; +- expected and verified digest; +- verification state; +- pin state; +- provider/provenance reference; +- last reconciliation timestamp; +- immutable receipt references where required. + +Required bindings MUST be verified and pinned before the dependent lock/check/review gate. + +### 16.3 FlowNodeAdapter + +The production adapter MUST: + +- use service authentication; +- use stable idempotency keys; +- validate returned identifiers and digest evidence; +- translate network/provider errors into typed infrastructure errors; +- persist/reconcile receipts through the application service; +- never update task/review state directly. + +### 16.4 LocalStorageAdapter + +The development/CI adapter MUST implement the same port semantics needed by tests. + +Application startup MUST fail if `LocalStorageAdapter` is selected in the production environment. + +### 16.5 Search authorization + +Flow Node search results are candidates. Before returning any result, Workstream MUST re-authorize the requesting actor for the referenced project/resource and remove unauthorized candidates without revealing their count or metadata. + +Flow Node unavailability MUST become evidence-unavailable/retry state. It MUST NOT become checker failure attributed to the contributor or a human reject decision. + +--- + +## 17. Transaction and Locking Rules + +### 17.1 Unit of work + +Every mutation application service MUST use one explicit unit of work. + +The unit of work MUST include: + +- locked reads required for the invariant; +- canonical inserts/updates; +- audit facts; +- caused outbox events; +- idempotency record where applicable. + +External network calls MUST occur after commit through workers/outbox unless a normative read-only verification call is explicitly required. No external call may be allowed to hold a review-decision or policy-activation database transaction open. + +### 17.2 Database time + +Lease and preference expiry MUST use PostgreSQL time inside the deciding transaction. Web-server and worker clocks are observability inputs only. + +### 17.3 Row locking + +Use targeted `SELECT ... FOR UPDATE` or the SQLAlchemy equivalent for: + +- ReviewQueueEntry claim; +- ReviewLease decision/release/expiry; +- task and assignment terminal decision effects; +- active compensation-policy selector during publish/freeze; +- CompensationAward/status during callback; +- idempotency record creation/resolution. + +Use `SKIP LOCKED` only for background batch claiming. Do not use it to make a user claim silently skip the requested queue entry. + +### 17.4 Isolation and constraints + +The default transaction isolation may remain the repository standard when row locks and constraints make the operation correct. The coding agent MUST not raise global isolation to serializable as a substitute for required row locks and uniqueness. + +Every database `IntegrityError` caused by an expected race MUST be translated to the normative idempotent result or stable conflict. Unexpected integrity errors MUST be logged with correlation context and returned as the existing safe internal error. + +--- + +## 18. Error Contract + +All APIs MUST use the existing structured error envelope. If the repository lacks one, implement: + +```json +{ + "error": { + "code": "reviewer_capacity_reached", + "message": "The reviewer already has an active review lease.", + "details": {}, + "correlation_id": "uuid", + "retryable": false + } +} +``` + +Machine codes from `WS-REV-001` and `WS-CON-001` are stable public contracts. + +The coding agent MUST NOT return raw SQL, stack traces, token claims, provider responses or secret configuration. + +Expected uniqueness races MUST not surface as `500`. + +--- + +## 19. Audit Requirements + +Audit facts MUST be appended for every authority or lifecycle action required by the normative specifications. + +Each fact MUST include: + +- event type; +- occurred-at database timestamp; +- actor ID and actor kind; +- effective local role/grant reference where applicable; +- project ID; +- exact resource IDs; +- correlation ID; +- idempotency key where applicable; +- before/after state names for mutable operational records; +- reason for admin override/revocation; +- immutable policy/version references in force. + +Application logs are not a replacement for audit facts. + +The coding agent MUST preserve distinct audit meanings for voluntary reviewer release and lease timeout. + +--- + +## 20. Worker and Retry Configuration + +Create named queues or routing keys using existing Celery conventions for: + +- project setup/checkers; +- review timers; +- outbox delivery; +- evidence reconciliation; +- compensation reconciliation; +- projection rebuild. + +Every task MUST: + +- accept a stable record/job identifier rather than a full mutable object snapshot; +- reload current PostgreSQL state; +- be safe under duplicate delivery; +- record durable retry/attempt state where required; +- acknowledge after durable outcome; +- emit structured correlation context. + +Retry policy MUST classify errors: + +- validation/authority/domain conflicts are terminal and are not retried; +- network timeout, connection and provider 5xx are retryable; +- provider 4xx is terminal unless the adapter contract explicitly marks it retryable; +- unknown exception follows bounded retry then operational dead-letter handling. + +The exact intervals are deployment configuration, but the algorithm and maximum-attempt handling MUST be tested. + +--- + +## 21. Read Models and Pagination + +Read services MAY compose canonical records with replaceable projections. They MUST label projection state and MUST not use it as sole mutation authority. + +Cursor pagination MUST use stable sort keys. + +Contribution history uses: + +```text +recorded_at DESC, id DESC +``` + +Queue views use the normative routing timestamp plus ID tie-breaker. + +Authorization filtering occurs before totals and cursors are exposed. Unauthorized records MUST not be inferable through counts, empty-page behavior or error differences. + +Projection rebuild commands MUST be idempotent and must derive state from canonical rows and receipts. + +--- + +## 22. Security Requirements + +The implementation MUST include: + +- deny-by-default authorization; +- local role/grant revocation checks at command time; +- self-review prohibition; +- task-scoped banned-assignment check; +- separate human, worker and adapter principals; +- TLS for all production network boundaries; +- runtime secret storage; +- checker resource and credential isolation; +- sensitive-data exclusion from events and logs; +- parameterized SQL/ORM access; +- request body and artifact-reference validation; +- callback replay protection; +- rate limiting at ingress or the existing API-control layer for sensitive endpoints. + +No provider secret, token or artifact byte payload may be stored in an outbox event. + +--- + +## 23. Observability Requirements + +All API and worker paths MUST propagate or create a correlation ID. + +Structured logs and traces MUST include applicable identifiers: + +- `project_id`; +- `task_id`; +- `submission_version_id`; +- `review_queue_entry_id`; +- `review_lease_id`; +- `review_id`; +- `contribution_record_id`; +- `compensation_award_id`; +- `outbox_event_id`. + +Metrics MUST cover at least: + +- API latency/error by route and error code; +- queue size and oldest age by preferred/open view; +- active lease count and expiry/release rates; +- claim conflict and reviewer-capacity conflicts; +- checker backlog, duration and infrastructure failure; +- outbox oldest age, attempts, success/failure and exhausted count; +- Flow Node ingest/verify/pin latency and unavailable bindings; +- reviews missing expected contribution records; +- awards by delivery/fulfillment state and pending age; +- contradictory callback attempts. + +Alerts MUST be based on deployment-configured thresholds and have an operator runbook reference. + +--- + +## 24. Test Strategy + +The coding agent MUST add tests at four levels. + +### 24.1 Unit tests + +Unit-test pure rules: + +- reviewer eligibility; +- routing-mode selection; +- preference and lease transitions; +- review payload/finding validation; +- decision effect matrix; +- compensation policy validation; +- award evaluation with exact decimals; +- callback monotonic state reduction; +- typed adapter error mapping. + +Unit tests MUST not mock away database behavior that is itself the requirement. + +### 24.2 PostgreSQL integration tests + +Use real PostgreSQL for: + +- partial unique indexes; +- composite/project-chain foreign keys; +- check constraints; +- row locks; +- database time; +- `SKIP LOCKED` workers; +- transaction rollback; +- migration upgrade from the prior head. + +SQLite MUST NOT be used as the only test database for these features. + +### 24.3 Concurrency tests + +Run true concurrent transactions proving: + +1. two reviewers claim one entry: exactly one succeeds; +2. one reviewer claims two entries: exactly one active lease exists; +3. decision races expiry: one normative terminal outcome exists; +4. duplicate decision: one Review and one contribution/award set exists; +5. two policy versions publish concurrently: one committed active selector is frozen by new work; +6. duplicate outbox dispatch: one adapter business effect under the stable key; +7. failed and fulfilled callback race: final state is fulfilled; +8. two fulfilled callbacks: one unique fulfilled receipt unless exact replay. + +Tests MUST assert database row counts and immutable values, not only HTTP status. + +### 24.4 Contract tests + +Add JSON-schema or equivalent contract tests for: + +- review API payloads and error codes; +- review-chain reads; +- contribution reads; +- compensation policy draft/publish; +- `ContributionRecorded`; +- `CompensationAwardCreated`; +- `CompensationFulfillmentRequested`; +- fulfillment callback; +- Flow Node adapter request/receipt mapping. + +### 24.5 Security tests + +Test: + +- invalid issuer, audience, signature and expired token; +- unknown/inactive local actor; +- revoked role/grant; +- admin without contributor reviewer grant; +- reviewer self-review attempt; +- banned submitter task reclaim; +- unauthorized project queue/count leakage; +- human token forwarding prohibition; +- unauthorized adapter callback; +- callback replay and mismatch; +- LocalStorageAdapter rejected in production. + +--- + +## 25. Required Live Drills + +### 25.1 Review drill + +The drill MUST use supported APIs and workers to prove: + +1. first submission enters open FIFO; +2. authorized reviewer claims one lease; +3. second claim is blocked by capacity; +4. `needs_revision` creates immutable review/findings and reviewer contribution; +5. new SubmissionVersion explicitly responds to findings; +6. prior reviewer receives preferred backlog; +7. preference expiry opens the same entry without losing original FIFO age; +8. a different reviewer may claim after expiry; +9. accept creates reviewer and submitter contributions and awards; +10. replay creates nothing new; +11. reject in a separate task closes the task and bans that contributor-task assignment; +12. lease timeout returns work to open FIFO with distinct audit evidence. + +### 25.2 Compensation drill + +The drill MUST prove: + +1. finance authority publishes a project policy with money and/or project points; +2. assignment and lease freeze the exact active policy version; +3. later policy publication does not alter frozen work; +4. review decision atomically creates contribution, awards and outbox events; +5. dispatcher redelivery retains one business effect; +6. adapter reports fulfilled; +7. exact callback replay returns the existing receipt; +8. contradictory callback is rejected; +9. contribution history is globally addressable and correctly project-filtered; +10. no internal payment request, wallet balance or point ledger record is created. + +### 25.3 Evidence drill + +The drill MUST prove: + +1. submission artifact intent produces an outbox request; +2. Flow Node returns CID, manifest and verification/pin receipt; +3. required unverified or unpinned artifact blocks the dependent gate; +4. verified/pinned artifact admits checker/review; +5. digest mismatch is an evidence failure, not contributor reject; +6. provider unavailability pauses and later reconciles; +7. semantic search results are filtered by Workstream authorization. + +### 25.4 Full internal-loop drill + +Run one task from project guide through fulfilled adapter receipt without direct database edits. + +Capture request/response IDs, canonical database snapshots, worker logs, audit events, outbox attempts and final immutable chain. + +--- + +## 26. Implementation Evidence Pack + +The coding agent MUST produce: + +1. repository map and changed-file summary; +2. migration list and schema-diff summary; +3. generated OpenAPI diff; +4. test command list and pass/fail counts; +5. concurrency-test evidence; +6. review, compensation, evidence and full-loop drill reports; +7. example immutable review chain; +8. example contribution/award/receipt chain; +9. outbox replay evidence; +10. authorization-denial evidence; +11. remaining known limitations that are explicitly out of v0.1 scope; +12. rollback and deployment notes. + +The evidence pack MUST state separately: + +- what was implemented; +- what was tested; +- what was live-drilled; +- what remains designed only. + +--- + +## 27. Prohibited Deviations + +The coding agent MUST NOT: + +1. introduce Rust or PyO3 for the v0.1 lifecycle; +2. create a second Workstream application or parallel database model; +3. add unrelated products or platform components; +4. move Workstream roles into Identity Issuer tokens; +5. make reputation automatically grant reviewer authority in v0.1; +6. allow admin role alone to review work; +7. allow self-review; +8. allow more than one active global reviewer lease in v0.1; +9. collapse preferred backlog and open FIFO into an unordered queue; +10. overwrite or delete submission, review, finding, contribution, award or receipt history; +11. add an adjudication layer; +12. reopen rejected tasks to another contributor; +13. pay the submitter for `needs_revision` or `reject` through a submitter ContributionRecord; +14. withhold reviewer contribution because the outcome is reject or needs_revision; +15. implement payment requests, provider attempts, wallets, balances or point ledgers inside Workstream; +16. call compensation or Flow Node providers inside the review-decision transaction; +17. treat Redis or a projection as canonical state; +18. use local filesystem as production artifact authority; +19. treat Flow Node failure as contributor failure or reject; +20. add undocumented enum values, states, endpoints or side effects; +21. weaken database constraints because service validation exists; +22. declare success without the required PostgreSQL races and live drills. + +--- + +## 28. Definition of Done + +Implementation is complete only when all statements below are true. + +- Existing intake tests and the WS-POL-001-16 path remain green. +- Identity Issuer verification and local Workstream authorization are tested. +- All `WS-REV-001` schema, API, concurrency, timer, recovery, audit and conformance requirements pass. +- All `WS-CON-001` schema, policy, creation-matrix, award, outbox, callback, read and conformance requirements pass. +- FlowNodeAdapter satisfies evidence port, verification, pinning, search and recovery tests. +- Every canonical mutation commits with its audit facts and caused outbox events. +- Exact replays return existing business results and create no duplicate facts. +- PostgreSQL constraints prevent invalid duplicate or cross-project chains. +- Operations can observe queue age, lease expiry, checker backlog, outbox failure, evidence unavailability and award fulfillment. +- Full internal-loop drill completes through adapter receipt with no manual database edit. +- Documentation and OpenAPI match deployed behavior. +- The implementation evidence pack distinguishes runtime proof from design. + +--- + +## 29. Coding-Agent Start Instruction + +The implementation agent MUST begin with this sequence: + +```text +1. Read WS-ARCH-001, WS-REV-001 and WS-CON-001 completely. +2. Inspect the repository and produce the mandatory map from section 4. +3. Run the existing test suite and WS-POL-001-16 drill before changes. +4. Identify existing objects that will be extended; do not create duplicates. +5. Propose the ordered migration files and application-service mapping. +6. Implement Phase 1 only. +7. Run its exit gate and report evidence. +8. Continue phase by phase; do not skip a failed gate. +9. Stop and report a normative conflict instead of inventing a business rule. +10. Finish only after the full evidence pack and internal-loop drill exist. +``` + +The coding agent has authority to implement this specification inside the supplied Workstream repository. It does not have authority to change product scope, lifecycle decisions or external-system ownership. diff --git a/docs/reference_specs/WS-IMP-001-workstream-v0.1-coding-agent-implementation-specification.pdf b/docs/reference_specs/WS-IMP-001-workstream-v0.1-coding-agent-implementation-specification.pdf new file mode 100644 index 000000000..0ed9677f8 Binary files /dev/null and b/docs/reference_specs/WS-IMP-001-workstream-v0.1-coding-agent-implementation-specification.pdf differ diff --git a/docs/reference_specs/WS-REV-001-review-lifecycle-specification.md b/docs/reference_specs/WS-REV-001-review-lifecycle-specification.md new file mode 100644 index 000000000..425601a81 --- /dev/null +++ b/docs/reference_specs/WS-REV-001-review-lifecycle-specification.md @@ -0,0 +1,1706 @@ +# WS-REV-001: Workstream Review and Revision Lifecycle Specification + +**Status:** Locked for implementation handoff +**Architecture maturity:** Design complete; not yet runtime-proven +**Version:** 1.0 +**Date:** 2026-07-10 +**Owner:** Flow Research / Workstream Engineering +**Scope:** Reviewer authority, routing, preferred-reviewer backlog, open FIFO queue, claim leases, immutable reviews, revision replay, and terminal reject behaviour +**Depends on:** Task locked-policy context, submission finalization, immutable submission artifacts, and durable post-submission checker runs +**Current proof boundary:** The existing live API drill proves the intake spine through `review_pending`; it does not prove the lifecycle specified here +**Precedes:** Contribution record creation, payment status, reputation events, and lessons learned + +--- + +## 1. Purpose + +This specification defines what Workstream does after a finalized submission passes the durable post-submission checker gate and becomes ready for human review. + +It establishes: + +- who may submit and review work; +- how reviewer authority is granted; +- how first submissions enter an open review pool; +- how revisions return to the previous reviewer as a preferred backlog; +- how preferred work falls back into an open FIFO pool; +- how a reviewer claims exactly one review at a time through a lease; +- how expired, released, or revoked leases return work safely to the queue; +- how submission versions, reviews, findings, and finding resolutions remain immutable; +- how `accept`, `needs_revision`, and `reject` affect the task and submitter assignment; +- which events must be auditable now so future reputation policy can use them later. + +The design is informed by real high-volume contribution and review operations in which the global queue may be empty while individual reviewers still have revision backlogs routed to them. + +--- + +## 2. Implementation Status and Proof Boundary + +The coding agent MUST preserve the difference between proven behaviour and locked design. + +| Lifecycle area | Status | +|---|---| +| Guide source snapshot, sufficiency, and policy derivation | Proven | +| Manager approval of an exact submission policy | Proven | +| Effective policy and deterministic checker compilation | Proven | +| Task policy-context locking | Proven | +| Contributor claim, start, pre-submit, and submission finalization | Proven | +| Durable post-submission checker run | Proven | +| Transition to `review_pending` | Proven | +| Project reviewer grants | Locked by this specification | +| Preferred-reviewer backlog and open FIFO pool | Locked by this specification | +| Claim leases and capacity enforcement | Locked by this specification | +| Immutable review and revision chain | Locked by this specification | +| Reject and submitter-task blocking | Locked by this specification | +| Contribution record | Next specification | +| Payment and reputation policy | Deferred | + +The implementation begins at this precondition: + +```text +Submission finalized + -> SubmissionVersion created + -> task enters evaluation_pending + -> durable checker completes + -> routing_recommendation = allow_review + -> task enters review_pending + -> ReviewQueueEntry is created +``` + +A checker failure is not a human `Review` and MUST NOT be stored as `accept`, `needs_revision`, or `reject`. Automated evaluation remains upstream of this specification. + +--- + +## 3. Locked v1 Decisions + +The following decisions are part of this contract and are not left to coding-agent interpretation. + +1. Workstream has two major actor authority domains: Admin and Contributor. +2. Contributor project roles are `submitter`, `reviewer`, or `both`. +3. Admin roles such as Project Manager, Operator, Finance Authority, and Reputation Authority are internal Workstream roles. +4. Admin authority does not automatically grant reviewer authority. +5. An Admin who reviews work MUST also hold an active contributor-side reviewer grant for that project. +6. Skills and reputation inform reviewer-grant decisions but do not automatically grant reviewer authority in v1. +7. External reviewers use the same contributor and role-grant model after identity onboarding. +8. A contributor cannot review their own submission in v1. +9. A reviewer may hold exactly one active review lease globally in v1. +10. First submission versions enter the open FIFO review pool. +11. Revision submission versions return to the reviewer who issued the prior `needs_revision` decision for a time-limited preferred window. +12. A preferred-reviewer backlog and the open FIFO pool are distinct routing views over the same queue-entry model. +13. Preference time and review-lease time are separate timers. +14. When preference expires without a claim, the entry becomes open while preserving its original queue age. +15. When a claimed lease expires or is manually released, reviewer stickiness is cleared and the entry returns to the open pool. +16. Queue routing records are mutable; submission versions, reviews, findings, finding resolutions, and completed lease attempts are permanent. +17. `accept`, `needs_revision`, and `reject` are the only human review decisions. +18. `reject` is terminal for the task in v1. +19. Reject blocks the submitter from that task only; it does not revoke their project membership or project role grant. +20. Contribution, payment, and reputation work begins only after `accept` and is not implemented by this specification. + +--- + +## 4. Actor and Authority Model + +### 4.1 Major actor domains + +```text +Workstream Actor +├── Admin +│ ├── Project Manager +│ ├── Operator +│ ├── Finance Authority +│ ├── Reputation Authority +│ └── Other explicitly granted admin roles +│ +└── Contributor + ├── Submitter + ├── Reviewer + └── Both +``` + +These are authority domains, not necessarily mutually exclusive human identities. The same Identity Issuer subject may have an Admin profile and a Contributor profile only when each authority is explicitly granted. + +### 4.2 Identity boundary + +Identity Issuer establishes the external identity anchor. Workstream resolves that identity to a local actor and authorizes Workstream resources. + +```text +Identity Issuer token + -> Workstream verifies issuer, subject, audience, validity, and scopes + -> Workstream resolves local actor + -> Workstream reads current admin/contributor grants + -> Workstream authorizes the exact project, task, queue entry, lease, or review action +``` + +A valid identity token never proves that the actor is a submitter, reviewer, Project Manager, or Operator. + +### 4.3 Admin authority relevant to this specification + +| Action | Minimum Workstream admin authority | +|---|---| +| Issue or revoke a contributor project role grant | Project Manager or Operator | +| View all project review queues and reviewer backlogs | Project Manager or Operator | +| Clear a preferred-reviewer route | Project Manager or Operator | +| Force-release an active review lease | Operator; Project Manager if project policy permits | +| Close a queue entry administratively | Operator | +| Correct routing metadata through a compensating event | Operator | + +Finance and Reputation Authorities receive no review-routing privilege solely from those roles. + +### 4.4 Contributor authority + +| Contributor grant | May claim submission tasks | May claim review work | +|---|---:|---:| +| `submitter` | Yes | No | +| `reviewer` | No | Yes | +| `both` | Yes | Yes, except own submissions | + +--- + +## 5. Core Object Model + +### 5.1 ProjectRoleGrant + +`ProjectRoleGrant` is the source of contributor authority within one project. + +```yaml +ProjectRoleGrant: + id: uuid + project_id: uuid + contributor_id: uuid + role: enum [submitter, reviewer, both] + status: enum [active, revoked] + + grant_method: enum [manual, automated] + automation_policy_ref: uuid | null + + qualification_snapshot_ref: uuid + + granted_by: actor_id + granted_at: timestamp + revoked_by: actor_id | null + revoked_at: timestamp | null + revoked_reason: string | null +``` + +#### Qualification snapshot + +`qualification_snapshot_ref` points to an immutable snapshot created when the grant is issued. It records what the Admin could evaluate at grant time without requiring the future reputation engine to exist. + +```yaml +ProjectRoleQualificationSnapshot: + id: uuid + project_id: uuid + contributor_id: uuid + skills_snapshot: object + reputation_snapshot: object + prior_project_work_refs: list[uuid] + external_expertise_refs: list[string] + captured_at: timestamp + captured_by: actor_id +``` + +An unavailable reputation value is recorded explicitly as unavailable; it does not prevent a manual grant. + +#### ProjectRoleGrant invariants + +- A submitter claim requires an active grant with `role IN (submitter, both)` for the same project. +- A review claim requires an active grant with `role IN (reviewer, both)` for the same project. +- A review decision re-checks the same active grant at decision time. +- Grants are project-scoped. +- `grant_method = manual` is the only creation path enabled in v1. +- `grant_method = automated` is schema-reserved but MUST NOT be emitted by v1 code. +- Claim-time authority treats manual and future automated grants identically. +- Grant revocation never deletes historical reviews or lease attempts. + +--- + +### 5.2 SubmissionVersion + +`SubmissionVersion` is the immutable work version being evaluated. + +```yaml +SubmissionVersion: + id: uuid + task_id: uuid + version_number: int + submission_id: uuid + artifact_hash: string + prior_submission_version_id: uuid | null + created_by: contributor_id + created_at: timestamp +``` + +#### SubmissionVersion invariants + +- Immutable from creation. +- `version_number` is strictly increasing per task and starts at 1. +- `prior_submission_version_id` is null only for version 1. +- Every later version points to the immediately preceding version for the same task. +- The creator is the submitter whose task assignment is being evaluated. +- The creator cannot claim the corresponding review queue entry. +- At most one canonical `Review` may exist for a submission version. +- A version whose queue entry closes without `review_recorded` may have no Review; therefore the database invariant is at most one, not unconditionally exactly one. + +--- + +### 5.3 SubmissionFindingResponse + +When a contributor creates a revision version, the submission MUST respond to every unresolved blocking finding from the prior review. + +```yaml +SubmissionFindingResponse: + id: uuid + submission_version_id: uuid + finding_id: uuid + response: string + evidence_refs: list[string] + created_at: timestamp +``` + +#### SubmissionFindingResponse invariants + +- Immutable from creation. +- Required for every unresolved blocking finding when `version_number > 1`. +- `finding_id` must belong to the immediately prior review chain for the same task. +- It records the submitter's claim about how the finding was addressed; it does not mark the finding resolved. +- Only a later reviewer may record the canonical finding resolution. + +--- + +### 5.4 ReviewQueueEntry + +`ReviewQueueEntry` is mutable routing infrastructure for exactly one submission version. + +```yaml +ReviewQueueEntry: + id: uuid + project_id: uuid + task_id: uuid + submission_version_id: uuid + + queue_state: enum [pending, leased, closed] + routing_mode: enum [preferred, open] + routing_reason: enum [first_submission, revision_return, admin_assignment] + + first_queued_at: timestamp + available_since: timestamp + + preferred_reviewer_id: uuid | null + preference_expires_at: timestamp | null + + active_lease_id: uuid | null + + closed_at: timestamp | null + closed_reason: enum [review_recorded, task_closed, admin_cancelled] | null +``` + +#### ReviewQueueEntry creation + +For a first submission version: + +```yaml +queue_state: pending +routing_mode: open +routing_reason: first_submission +preferred_reviewer_id: null +preference_expires_at: null +first_queued_at: now +available_since: now +``` + +For a revision after `needs_revision`: + +```yaml +queue_state: pending +routing_mode: preferred +routing_reason: revision_return +preferred_reviewer_id: prior_review.reviewer_id +preference_expires_at: now + project.review_preference_window +first_queued_at: now +available_since: now +``` + +For direct Admin routing to an eligible external or internal reviewer: + +```yaml +queue_state: pending +routing_mode: preferred +routing_reason: admin_assignment +preferred_reviewer_id: selected_reviewer_id +preference_expires_at: now + project.review_preference_window +first_queued_at: now +available_since: now +``` + +Admin assignment is still time-limited so work cannot remain hidden indefinitely in one reviewer backlog. + +#### ReviewQueueEntry invariants + +- Exactly one queue entry per submission version. +- `routing_mode = preferred` requires both a preferred reviewer and a future preference expiry. +- `routing_mode = open` requires preferred-reviewer fields to be null. +- `queue_state = leased` requires a valid `active_lease_id`. +- `queue_state IN (pending, closed)` requires `active_lease_id = null`. +- `queue_state = closed` requires `closed_at` and `closed_reason`. +- Closed entries cannot return to pending. +- Queue entries are never hard-deleted while audit retention applies. + +--- + +### 5.5 ReviewLease + +Every claim creates a separate `ReviewLease`. Lease attempts are not overwritten because they are future reviewer-reputation evidence. + +```yaml +ReviewLease: + id: uuid + review_queue_entry_id: uuid + reviewer_id: contributor_id + status: enum [active, consumed, released, expired, revoked] + + claimed_at: timestamp + expires_at: timestamp + closed_at: timestamp | null + close_reason: enum [review_recorded, manual_release, lease_expired, grant_revoked, admin_override] | null +``` + +#### ReviewLease invariants + +- A reviewer may have at most one active lease globally in v1. +- A queue entry may have at most one active lease. +- A lease may be created only while the queue entry is pending. +- A lease may be consumed only through successful canonical Review creation. +- Terminal lease statuses never change. +- Lease timestamps use server/database time, not client time. +- Expired or revoked leases cannot authorize a Review. + +--- + +### 5.6 Review + +`Review` is the immutable canonical human decision against one submission version. + +```yaml +Review: + id: uuid + submission_version_id: uuid + review_queue_entry_id: uuid + review_lease_id: uuid + + reviewer_id: contributor_id + decision: enum [accept, needs_revision, reject] + decision_reason: string | null + + prior_review_id: uuid | null + reviewed_at: timestamp + lease_duration_actual: interval +``` + +#### Review invariants + +- Immutable from creation. +- Unique constraint on `submission_version_id`. +- Reviewer must own the referenced active lease. +- Lease must belong to the referenced queue entry. +- Queue entry must belong to the referenced submission version. +- Reviewer grant must still be active at decision time. +- Reviewer cannot be the submission creator. +- Version 1 review has `prior_review_id = null`. +- Review of version N points to the Review of version N-1. +- `accept` and `reject` are terminal for the submission/review chain. +- `needs_revision` permits a later submission version if the submitter continues. +- `decision_reason` is required for `needs_revision` and `reject`. + +The review and submission histories are two synchronized predecessor chains, not doubly linked lists. No forward pointer is required. + +--- + +### 5.7 ReviewFinding + +Findings are immutable records attached to a Review. + +```yaml +ReviewFinding: + id: uuid + review_id: uuid + description: string + severity: enum [blocking, advisory] + evidence_refs: list[string] + created_at: timestamp +``` + +#### Decision and finding rules + +- `accept`: findings are optional; any finding present must be advisory. +- `needs_revision`: findings are required and at least one finding must be blocking. +- `reject`: `decision_reason` is required; findings are optional but recommended. +- A finding is never edited to record its later resolution. + +--- + +### 5.8 FindingResolution + +`FindingResolution` records the later reviewer's verdict about a prior finding without mutating that finding. + +```yaml +FindingResolution: + id: uuid + finding_id: uuid + submission_version_id: uuid + resolution_status: enum [resolved, unresolved, not_applicable] + rationale: string + recorded_by_review_id: uuid + recorded_at: timestamp +``` + +#### FindingResolution invariants + +- Immutable from creation. +- Created only as part of reviewing a later submission version. +- Exactly one resolution per `finding_id` and reviewed `submission_version_id`. +- Every prior unresolved blocking finding must receive a resolution record in the next Review transaction. +- `accept` is invalid while any prior blocking finding is recorded as unresolved. +- A later `needs_revision` may leave prior findings unresolved and add new findings. + +--- + +### 5.9 TaskAssignment extension + +`TaskAssignment` is extended to preserve reject semantics. + +```yaml +TaskAssignment: + id: uuid + task_id: uuid + contributor_id: uuid + status: enum [active, completed, blocked] + + blocked_at: timestamp | null + blocked_reason_review_id: uuid | null +``` + +#### TaskAssignment invariants + +- `blocked` is scoped to one contributor-task relationship. +- A blocked contributor can never reclaim the same task. +- Blocking does not revoke a project role grant. +- Blocking does not affect other task assignments. +- `blocked_reason_review_id` points to the terminal reject Review. + +--- + +## 6. Project Review Policy Fields + +The project policy context locked to the task MUST include: + +```yaml +ProjectReviewPolicy: + version: string + review_preference_window: interval + review_lease_duration: interval + max_active_review_leases_per_reviewer: int # fixed to 1 in v1 + self_review_allowed: bool # fixed to false in v1 + reject_policy: enum [close_task] # only v1 value +``` + +These values are task-locked. A later project-policy change does not silently alter an already queued submission version. + +--- + +## 7. Queue Views and Ordering + +### 7.1 Preferred-reviewer backlog + +A preferred backlog contains pending entries where: + +```text +routing_mode = preferred +AND preferred_reviewer_id = current_reviewer +AND preference_expires_at > now +``` + +This backlog explains why an individual reviewer may still have review work while the open project queue is empty. + +Preferred entries are ordered by `first_queued_at ASC`. + +### 7.2 Open FIFO pool + +The open pool contains pending entries where: + +```text +routing_mode = open +``` + +Only entries for which the current actor is eligible are returned. + +Open entries are ordered by: + +```text +first_queued_at ASC, id ASC +``` + +The ID tie-breaker makes ordering deterministic when timestamps match. + +### 7.3 Reviewer next-work ordering + +When Workstream selects the next claimable item for a reviewer: + +1. Eligible preferred-backlog entries for that reviewer, oldest first. +2. Eligible open-pool entries, oldest first. + +The server MUST NOT return another claimable entry if the reviewer already holds an active lease. + +### 7.4 FIFO fairness on requeue + +`first_queued_at` is immutable. Lease expiry and release update `available_since` but do not reset queue age. + +```text +first_queued_at = original queue entry time +available_since = time entry most recently became claimable +``` + +This prevents a contributor from losing queue priority because a reviewer claimed and stalled. + +--- + +## 8. Reviewer Eligibility + +A reviewer is eligible to claim a queue entry only if all conditions are true: + +1. Identity token is valid for Workstream. +2. Local actor resolves to a Contributor profile. +3. Contributor has an active `ProjectRoleGrant(reviewer|both)` for the queue entry's project. +4. Contributor is not the creator of the submission version. +5. Contributor has no active review lease anywhere in Workstream. +6. Contributor is not suspended or otherwise restricted by current Workstream policy. +7. Queue entry is pending. +8. If preference is active, contributor is the preferred reviewer. +9. No canonical Review already exists for the submission version. + +Eligibility is checked at claim time and again at decision time. + +--- + +## 9. State Machines + +### 9.1 ReviewQueueEntry state machine + +```text +pending + -> leased authorized atomic claim + -> closed task/admin closure before review + +leased + -> pending active lease released, expired, or revoked + -> closed canonical Review recorded + +closed + -> terminal +``` + +Preference expiry does not change `queue_state`; it changes only routing: + +```text +pending + preferred + -> pending + open +``` + +### 9.2 ReviewLease state machine + +```text +active + -> consumed canonical Review recorded + -> released reviewer voluntarily releases + -> expired lease duration passes + -> revoked reviewer grant revoked or admin force-release + +consumed | released | expired | revoked + -> terminal +``` + +### 9.3 Task review states + +The queue state does not need to make the Task oscillate between pending and under-review. Queue and lease records provide that operational detail. + +```text +evaluation_pending + -> review_pending durable checker allows human review + +review_pending + -> needs_revision Review decision + -> accepted Review decision + -> closed Review decision = reject + +needs_revision + -> in_progress contributor continues revision work + -> closed contributor withdraws or admin closes under separate policy +``` + +When reject closes the task: + +```yaml +Task.status: closed +Task.closed_reason: review_rejected +``` + +--- + +## 10. Timer Semantics + +### 10.1 Preference timer + +The preference timer starts when a revision queue entry is created. + +Purpose: + +- preserve reviewer continuity; +- keep the reviewer familiar with the findings and prior evidence; +- avoid forcing a new reviewer to reconstruct the chain unnecessarily. + +It does not reserve reviewer capacity and does not create a lease. + +On preference expiry: + +```text +routing_mode -> open +preferred_reviewer_id -> null +preference_expires_at -> null +first_queued_at -> unchanged +available_since -> now +audit ReviewerPreferenceExpired +``` + +### 10.2 Lease timer + +The lease timer starts only when an eligible reviewer successfully claims the queue entry. + +Purpose: + +- enforce one-review-at-a-time capacity; +- prevent indefinite work hoarding; +- allow automatic recovery when a reviewer stops working. + +On lease expiry: + +```text +ReviewLease.status -> expired +ReviewLease.closed_at -> now +ReviewLease.close_reason -> lease_expired + +ReviewQueueEntry.queue_state -> pending +ReviewQueueEntry.routing_mode -> open +ReviewQueueEntry.active_lease_id -> null +ReviewQueueEntry preferred fields -> null +ReviewQueueEntry.first_queued_at -> unchanged +ReviewQueueEntry.available_since -> now + +audit ReviewerLeaseExpired +``` + +Lease expiry burns reviewer stickiness. The expired reviewer may later reclaim the entry from the open pool if still eligible, but receives no renewed preference. + +--- + +## 11. Core Operations + +### 11.1 Create queue entry after checker admission + +Preconditions: + +- task is `evaluation_pending`; +- durable checker run is completed; +- routing recommendation is `allow_review`; +- submission version has no queue entry. + +Atomic effect: + +```text +create ReviewQueueEntry +task.status -> review_pending +record ReviewQueueEntryCreated +record TaskStatusChanged(evaluation_pending -> review_pending) +``` + +Idempotency is required. Reprocessing the same checker completion MUST return or preserve the existing queue entry. + +### 11.2 Claim review + +Claim is an atomic compare-and-set operation. + +Preconditions: + +- actor passes all reviewer eligibility checks; +- queue entry is pending; +- no active lease exists for the queue entry; +- reviewer has no active lease; +- preferred-routing rules permit this reviewer. + +Atomic effect: + +```text +create ReviewLease(status=active) +ReviewQueueEntry.queue_state -> leased +ReviewQueueEntry.active_lease_id -> new lease ID +record ReviewerClaimedTask +``` + +The implementation MAY use `SELECT ... FOR UPDATE`, an atomic conditional `UPDATE`, or another database-safe compare-and-set strategy. It MUST NOT perform an unprotected read followed by a write. + +### 11.3 Release claimed review + +Only the active leased reviewer or authorized Admin may release. + +Reviewer release effect: + +```text +lease -> released/manual_release +queue entry -> pending/open +stickiness -> cleared +available_since -> now +first_queued_at -> unchanged +record ReviewerReleasedTask +``` + +Admin force-release effect: + +```text +lease -> revoked/admin_override +queue entry -> pending/open +stickiness -> cleared +record ReviewerLeaseForceReleased +record AdminOverrideApplied(reason required) +``` + +### 11.4 Decline preferred review before claim + +The preferred reviewer may decline an unclaimed preferred entry without first creating a lease. + +Effect: + +```text +routing_mode -> open +preferred fields -> null +first_queued_at -> unchanged +available_since -> now +record ReviewerDeclinedPreference +``` + +This prevents work from waiting for the full preference window when the previous reviewer already knows they cannot continue. + +### 11.5 Record review decision + +Decision creation is one database transaction. + +Preconditions: + +- reviewer owns the active lease; +- lease is not expired according to server/database time; +- reviewer grant remains active; +- reviewer is not the submission creator; +- queue entry is leased by the referenced lease; +- no Review exists for the submission version; +- decision/finding validation passes; +- required prior finding resolutions are supplied for a revision. + +Transaction effect: + +```text +create Review +create ReviewFinding records +create FindingResolution records +ReviewLease.status -> consumed +ReviewLease.close_reason -> review_recorded +ReviewQueueEntry.queue_state -> closed +ReviewQueueEntry.closed_reason -> review_recorded +ReviewQueueEntry.active_lease_id -> null +apply decision-specific Task and TaskAssignment changes +write all audit events +commit +``` + +No partial Review may survive if any decision-specific state change fails. + +--- + +## 12. Decision Contracts + +### 12.1 Accept + +Validation: + +- no unresolved prior blocking finding; +- any new findings are advisory only; +- decision reason optional. + +Effect: + +```text +Review(decision=accept) created +queue entry closed +lease consumed +Task.status -> accepted +TaskAssignment.status -> completed +emit ReviewAccepted +emit ContributionRecordRequested +``` + +`ContributionRecordRequested` is an integration event. The review implementation MUST NOT create payment or reputation records. + +### 12.2 Needs revision + +Validation: + +- decision reason required; +- at least one finding required; +- at least one finding is blocking. + +Effect: + +```text +Review(decision=needs_revision) created +findings created +queue entry closed +lease consumed +Task.status -> needs_revision +TaskAssignment remains active +emit ReviewNeedsRevision +``` + +If the contributor continues: + +```text +Task.status -> in_progress +create SubmissionVersion(N+1) +create required SubmissionFindingResponse records +run existing finalization and checker spine +if allowed -> create preferred ReviewQueueEntry for prior reviewer +``` + +### 12.3 Reject + +Validation: + +- decision reason required. + +Atomic effect: + +```text +Review(decision=reject) created +queue entry closed +lease consumed + +TaskAssignment.status -> blocked +TaskAssignment.blocked_at -> now +TaskAssignment.blocked_reason_review_id -> Review.id + +Task.status -> closed +Task.closed_reason -> review_rejected + +emit ReviewRejected +emit TaskAssignmentBlocked +emit TaskClosed +``` + +Reject is terminal in v1: + +- no new submission version may be created; +- the blocked submitter cannot reclaim the task; +- the contributor's project role grant remains unchanged; +- other project tasks remain unaffected. + +Future `reopen_to_pool` behaviour is not implemented here. + +--- + +## 13. Revision Routing + +### 13.1 Same reviewer claims within preference window + +```text +R1 reviews v1 -> needs_revision +Submitter creates v2 +v2 passes durable checker +queue entry v2 -> preferred to R1 +R1 claims before preference expiry +R1 records Review(v2) +Review(v2).prior_review_id = Review(v1).id +``` + +### 13.2 Different reviewer after preference expiry + +```text +R1 reviews v1 -> needs_revision +Submitter creates v2 +v2 enters R1 preferred backlog +R1 does not claim before preference expiry +entry becomes open with original queue age preserved +R2 claims +R2 receives complete prior chain +R2 records Review(v2) +Review(v2).prior_review_id = Review(v1).id +``` + +The prior chain is independent of reviewer identity. + +### 13.3 Preferred reviewer becomes ineligible + +Preference MUST be cleared immediately when Workstream detects that the preferred reviewer: + +- lost or had their project reviewer grant revoked; +- became the submitter due to corrected attribution; +- was suspended; +- became otherwise disallowed by locked project policy. + +Effect: + +```text +entry remains pending +routing_mode -> open +preferred fields -> null +available_since -> now +first_queued_at -> unchanged +record ReviewerPreferenceInvalidated +``` + +### 13.4 Preferred reviewer has another active lease + +The entry remains in that reviewer's preferred backlog until: + +- they complete/release their active lease and claim it; +- they decline preference; +- the preference window expires; +- an Admin clears the preference. + +Preferred routing does not bypass the one-active-lease capacity limit. + +--- + +## 14. Grant Revocation During an Active Lease + +Grant revocation and lease recovery MUST be coordinated. + +When a reviewer grant is revoked: + +1. The grant becomes revoked immediately. +2. Any active lease held under that grant becomes `revoked`. +3. The queue entry returns to `pending/open`. +4. Preferred routing is cleared. +5. The reviewer can no longer record a canonical decision. +6. Historical lease and audit records remain. + +The grant-revocation transaction SHOULD perform this recovery immediately. A background reconciliation worker MUST also detect and repair any missed active lease. + +--- + +## 15. API Contract + +Endpoint names may be adapted to existing router conventions, but capability boundaries and error semantics are locked. + +### 15.1 Contributor-role grants + +```http +POST /v1/projects/{project_id}/role-grants +GET /v1/projects/{project_id}/role-grants +POST /v1/projects/{project_id}/role-grants/{grant_id}/revoke +``` + +Create request: + +```json +{ + "contributor_id": "uuid", + "role": "reviewer", + "qualification_snapshot_ref": "uuid", + "grant_method": "manual" +} +``` + +### 15.2 Reviewer queue + +```http +GET /v1/reviews/queue/mine +GET /v1/projects/{project_id}/reviews/queue +``` + +Reviewer response separates routing views: + +```json +{ + "active_lease": null, + "preferred_backlog": [], + "open_queue": [] +} +``` + +### 15.3 Claim + +```http +POST /v1/reviews/queue/{queue_entry_id}/claim +``` + +Response: + +```json +{ + "queue_entry_id": "uuid", + "lease_id": "uuid", + "lease_status": "active", + "claimed_at": "timestamp", + "expires_at": "timestamp" +} +``` + +### 15.4 Release or decline + +```http +POST /v1/reviews/leases/{lease_id}/release +POST /v1/reviews/queue/{queue_entry_id}/decline-preference +``` + +### 15.5 Record decision + +```http +POST /v1/reviews/leases/{lease_id}/decision +``` + +Needs-revision request example: + +```json +{ + "decision": "needs_revision", + "decision_reason": "Two blocking requirements remain unresolved.", + "findings": [ + { + "description": "Required verifier evidence is missing.", + "severity": "blocking", + "evidence_refs": ["cid-or-artifact-ref"] + } + ], + "prior_finding_resolutions": [] +} +``` + +Revision review example: + +```json +{ + "decision": "accept", + "decision_reason": null, + "findings": [], + "prior_finding_resolutions": [ + { + "finding_id": "uuid", + "resolution_status": "resolved", + "rationale": "Verifier evidence is present and valid." + } + ] +} +``` + +### 15.6 Read immutable chain + +```http +GET /v1/tasks/{task_id}/review-chain +GET /v1/submission-versions/{version_id}/review +``` + +The task chain response includes versions, reviews, findings, finding responses, resolutions, and relevant lease summaries in chronological order. + +--- + +## 16. Error Contract + +| HTTP | Code | Condition | +|---:|---|---| +| 403 | `unauthorized_actor` | Missing required Admin or Contributor authority | +| 403 | `reviewer_grant_required` | No active reviewer/both grant for project | +| 403 | `self_review_forbidden` | Reviewer is the submission creator | +| 403 | `lease_not_owned` | Decision/release actor does not own active lease | +| 409 | `reviewer_preference_active` | Another reviewer attempts claim during active preference window | +| 409 | `reviewer_capacity_reached` | Reviewer already holds an active lease | +| 409 | `queue_entry_not_pending` | Claim attempted on leased or closed entry | +| 409 | `queue_entry_not_leased` | Decision attempted when entry is not leased | +| 409 | `lease_expired` | Decision attempted after lease expiry | +| 409 | `review_already_recorded` | Review already exists for submission version | +| 409 | `task_assignment_blocked` | Submitter attempts to reclaim rejected task | +| 409 | `preference_not_owned` | Actor tries to decline someone else's preference | +| 422 | `decision_reason_required` | Missing reason for needs_revision or reject | +| 422 | `findings_required` | needs_revision contains no findings | +| 422 | `blocking_finding_required` | needs_revision has no blocking finding | +| 422 | `blocking_finding_on_accept` | accept includes a new blocking finding | +| 422 | `finding_resolution_required` | Prior blocking finding was not evaluated | +| 422 | `unresolved_finding_on_accept` | accept attempted with unresolved prior blocking finding | + +Errors MUST use the existing structured Workstream error envelope. + +--- + +## 17. Database Constraints and Indexes + +The implementation MUST enforce core invariants in the database, not only in service code. + +Required constraints: + +```text +UNIQUE ReviewQueueEntry(submission_version_id) +UNIQUE Review(submission_version_id) +UNIQUE SubmissionVersion(task_id, version_number) +UNIQUE FindingResolution(finding_id, submission_version_id) +``` + +Required partial uniqueness: + +```text +UNIQUE ReviewLease(review_queue_entry_id) WHERE status = 'active' +UNIQUE ReviewLease(reviewer_id) WHERE status = 'active' +``` + +Required indexes: + +```text +ReviewQueueEntry(project_id, queue_state, routing_mode, first_queued_at) +ReviewQueueEntry(preferred_reviewer_id, queue_state, preference_expires_at) +ReviewLease(status, expires_at) +ProjectRoleGrant(project_id, contributor_id, status, role) +SubmissionVersion(task_id, version_number) +Review(prior_review_id) +ReviewFinding(review_id) +FindingResolution(finding_id, submission_version_id) +TaskAssignment(task_id, contributor_id, status) +``` + +Check constraints SHOULD enforce state-field compatibility for queue entries and leases. + +--- + +## 18. Concurrency and Transaction Requirements + +### 18.1 Claim race + +If two reviewers claim the same entry concurrently, exactly one succeeds. The other receives `409 queue_entry_not_pending` or an equivalent conflict. + +### 18.2 Capacity race + +If one reviewer attempts to claim two entries concurrently, exactly one active lease may be created. The other receives `409 reviewer_capacity_reached`. + +### 18.3 Decision versus expiry race + +The server evaluates lease validity using one transaction and database time. + +- If decision commit locks the active lease before the expiry transition, and the database time is not past `expires_at`, the decision may succeed. +- If expiry wins or the database time is past `expires_at`, decision fails with `lease_expired`. + +### 18.4 Decision idempotency + +The decision endpoint MUST require or derive an idempotency key. Repeating the same request returns the existing Review. Reusing the same key with a different payload returns an idempotency mismatch. + +### 18.5 Reject atomicity + +Review creation, queue closure, lease consumption, assignment blocking, task closure, and audit writes happen in one transaction. + +### 18.6 Needs-revision atomicity + +Review, findings, prior finding resolutions, queue closure, lease consumption, task transition, and audit writes happen in one transaction. + +--- + +## 19. Background Processing and Recovery + +### 19.1 Preference-expiry sweep + +Periodically finds: + +```text +queue_state = pending +routing_mode = preferred +preference_expires_at < now +``` + +and opens the entries idempotently. + +### 19.2 Lease-expiry sweep + +Periodically finds: + +```text +ReviewLease.status = active +expires_at < now +no canonical Review exists +``` + +and returns each entry to the open pool idempotently. + +### 19.3 Lazy recovery + +Queue list, claim, and decision operations SHOULD also detect stale preference/lease state so correctness does not depend exclusively on the sweep schedule. + +### 19.4 Orphan reconciliation + +A reconciliation job detects: + +- leased queue entry without an active lease; +- active lease whose queue entry does not reference it; +- consumed lease without a Review; +- closed `review_recorded` queue entry without a Review; +- revoked grant with an active lease; +- Review whose task/submission chain does not match. + +It MUST alert operators and use explicit compensating events rather than silent history edits. + +--- + +## 20. Audit Events + +The following event types are required. + +### Authority + +- `ProjectRoleGrantIssued` +- `ProjectRoleGrantRevoked` +- `ProjectRoleQualificationSnapshotCreated` + +### Routing + +- `ReviewQueueEntryCreated` +- `ReviewRoutedToPreferredReviewer` +- `ReviewerPreferenceExpired` +- `ReviewerPreferenceInvalidated` +- `ReviewerDeclinedPreference` +- `ReviewQueueEntryOpened` +- `ReviewQueueEntryClosed` + +### Lease + +- `ReviewerClaimedTask` +- `ReviewerReleasedTask` +- `ReviewerLeaseExpired` +- `ReviewerLeaseRevoked` +- `ReviewerLeaseConsumed` +- `ReviewerLeaseForceReleased` + +### Decision and revision + +- `ReviewRecorded` +- `ReviewAccepted` +- `ReviewNeedsRevision` +- `ReviewRejected` +- `ReviewFindingCreated` +- `FindingResolutionRecorded` +- `SubmissionFindingResponseCreated` + +### Task and assignment + +- `TaskAssignmentCompleted` +- `TaskAssignmentBlocked` +- `TaskClosed` +- `AdminOverrideApplied` + +Every event records: + +```yaml +event_id: uuid +event_type: string +occurred_at: timestamp +actor_id: actor_id | system_actor_id +project_id: uuid +task_id: uuid | null +submission_version_id: uuid | null +review_queue_entry_id: uuid | null +review_lease_id: uuid | null +review_id: uuid | null +reason: string | null +policy_version: string +correlation_id: string +``` + +Lease-expiry and voluntary-release events remain separate because future reputation policy may treat them differently. + +--- + +## 21. Notifications and Operational Views + +Notification delivery is an adapter concern, but the lifecycle emits events for: + +- preferred review routed to reviewer; +- preference nearing expiry; +- lease nearing expiry; +- lease expired; +- review needs revision; +- review accepted; +- review rejected; +- reviewer grant revoked while work is leased. + +### Reviewer view + +Must show: + +- current active lease and remaining time; +- preferred backlog count and oldest age; +- open eligible queue count; +- prior submission/review chain for claimed work; +- unresolved findings and submitter responses. + +### Admin view + +Must show: + +- open FIFO depth per project; +- preferred backlog per reviewer; +- oldest preferred entry age; +- active lease count; +- leases approaching expiry; +- expired and voluntarily released lease counts; +- entries reopened because reviewer preference became invalid; +- review turnaround distributions. + +The system MUST NOT report the queue as empty when preferred reviewer backlogs still contain pending work. + +--- + +## 22. Observability + +Required metrics include: + +```text +review_queue_open_depth{project_id} +review_queue_preferred_depth{project_id} +review_queue_oldest_open_age_seconds{project_id} +review_preferred_oldest_age_seconds{project_id} +review_active_lease_count +review_lease_expired_total{project_id} +review_lease_released_total{project_id} +review_claim_conflict_total{reason} +review_decision_total{decision, project_id} +review_turnaround_seconds{project_id, decision} +review_revision_count{project_id} +review_preference_fallback_total{project_id} +``` + +Per-reviewer metrics should be available in authorized operational views but should not create uncontrolled high-cardinality public metric labels. + +Structured logs MUST exclude tokens, credentials, raw private artifact content, and sensitive source paths. + +--- + +## 23. Security Requirements + +- Review authority is checked from current Workstream state, not token role claims. +- No self-review in v1. +- Admin privileges do not bypass reviewer qualification. +- Reviewer decision requires an owned, active, unexpired lease. +- Grant revocation invalidates decision authority immediately. +- Queue and chain reads are project-authorized. +- Reviewer can read only entries they may claim, entries preferred to them, their active lease, and permitted historical context. +- Admin override requires a reason and dedicated audit event. +- Immutable records are corrected only through new compensating records/events. +- All evidence references shown during review must use the task's locked guide, policy, checker, and artifact context. + +--- + +## 24. End-to-End Reference Sequences + +### 24.1 First submission accepted + +```text +1. Finalized submission creates SubmissionVersion(v1). +2. Durable checker completes with allow_review. +3. Open ReviewQueueEntry(v1) is created. +4. Eligible Reviewer R1 claims atomically. +5. ReviewLease L1 becomes active. +6. R1 records accept before L1 expires. +7. Review(v1) is created. +8. L1 becomes consumed. +9. Queue entry closes with review_recorded. +10. Task becomes accepted. +11. Submitter TaskAssignment becomes completed. +12. ContributionRecordRequested is emitted. +``` + +### 24.2 Needs revision and same reviewer returns + +```text +1. R1 reviews v1 as needs_revision with blocking findings. +2. Task becomes needs_revision. +3. Submitter creates v2 plus responses to every blocking finding. +4. v2 passes the durable checker. +5. ReviewQueueEntry(v2) is preferred to R1. +6. R1 sees v2 in their preferred backlog. +7. R1 claims within the preference window. +8. R1 records finding resolutions and a new decision. +9. Review(v2).prior_review_id points to Review(v1). +``` + +### 24.3 Needs revision and another reviewer takes over + +```text +1. v2 is preferred to R1. +2. R1 does not claim before preference expiry. +3. Preference clears; first_queued_at remains unchanged. +4. v2 becomes visible in the open eligible FIFO pool. +5. R2 claims. +6. R2 receives the complete v1/v2 submission and review chain. +7. R2 records the next Review and finding resolutions. +``` + +### 24.4 Claimed review expires + +```text +1. R1 claims queue entry Q1 and receives L1. +2. L1 expires without Review creation. +3. L1 becomes expired. +4. Q1 returns to pending/open. +5. Q1 preserves first_queued_at and updates available_since. +6. ReviewerLeaseExpired is recorded. +7. R1 has no renewed preference but may later claim from the open pool. +``` + +### 24.5 Reject + +```text +1. R1 holds active lease L1. +2. R1 records reject with a required reason. +3. Review is created and L1 consumed. +4. Queue entry closes. +5. Submitter TaskAssignment becomes blocked. +6. Task closes with review_rejected. +7. No new submission version may be created in v1. +``` + +--- + +## 25. Conformance Tests + +### 25.1 Authority tests + +- Submitter-only grant cannot claim review work. +- Reviewer-only grant cannot claim submission task work. +- `both` may perform both functions but cannot self-review. +- Reviewer grant on Project A cannot claim Project B review work. +- Admin without contributor reviewer grant cannot review. +- External reviewer behaves identically after local contributor onboarding and grant. +- Revoked grant cannot claim. +- Grant revoked during lease prevents decision. + +### 25.2 Queue-routing tests + +- First submission enters open pool. +- Revision enters prior reviewer's preferred backlog. +- Non-preferred reviewer receives `reviewer_preference_active` during the window. +- Preferred reviewer may claim during the window. +- Preference expiry opens the entry. +- Preference expiry preserves `first_queued_at`. +- Preferred reviewer decline opens the entry immediately. +- Admin assignment creates time-limited preferred routing. +- General queue may be empty while preferred backlog is non-empty. +- Reviewer next-work ordering chooses preferred backlog before open pool. + +### 25.3 Lease tests + +- Two concurrent reviewers cannot claim the same entry. +- One reviewer cannot hold two active leases. +- Reviewer release returns entry to pending/open. +- Lease expiry returns entry to pending/open. +- Lease expiry clears stickiness. +- Expired reviewer may later claim from open pool if eligible. +- Expired lease cannot record decision. +- Release and expiry emit distinct audit events. +- Decision and expiry race resolves deterministically using database time. + +### 25.4 Review tests + +- A second Review cannot be stored for the same submission version. +- Review references the correct queue entry and lease. +- `needs_revision` without findings fails. +- `needs_revision` without a blocking finding fails. +- `accept` with a new blocking finding fails. +- `reject` without a decision reason fails. +- Decision endpoint is idempotent. +- Same idempotency key with different payload fails. +- Review cannot be edited after creation. + +### 25.5 Revision tests + +- Version numbers increase monotonically. +- v2 points to v1. +- Review(v2) points to Review(v1) regardless of reviewer identity. +- Revised submission requires a response to each unresolved blocking finding. +- Original finding remains unchanged. +- Resolution is stored as a new FindingResolution. +- Accept fails if a prior blocking finding remains unresolved. +- Complete history is traversable from latest version backward. + +### 25.6 Reject tests + +- Reject closes the queue entry. +- Reject consumes the lease. +- Reject blocks the submitter's TaskAssignment. +- Blocked submitter cannot reclaim the task. +- ProjectRoleGrant remains active. +- Other task assignments remain unaffected. +- Task closes with `review_rejected`. +- No later submission version may be created. + +### 25.7 Recovery tests + +- Preference sweep is idempotent. +- Lease sweep is idempotent. +- Lazy access repairs stale expired preference/lease state. +- Reconciliation detects leased entry without active lease. +- Reconciliation detects consumed lease without Review. +- Reconciliation never silently deletes or rewrites immutable history. + +--- + +## 26. Implementation Delivery Order + +### Phase 1: Schema and invariants + +- Project role qualification snapshot +- ProjectRoleGrant updates +- SubmissionVersion constraints +- SubmissionFindingResponse +- ReviewQueueEntry +- ReviewLease +- Review +- ReviewFinding +- FindingResolution +- TaskAssignment extension +- Required unique and partial indexes + +Exit condition: migrations and model-level constraint tests pass. + +### Phase 2: Authority and queue creation + +- Admin grant/revoke endpoints +- reviewer eligibility service +- checker-admission to queue-entry creation +- preferred/open routing fields +- reviewer/admin queue reads + +Exit condition: authority and routing conformance tests pass. + +### Phase 3: Claims and timers + +- atomic claim +- global one-active-lease enforcement +- reviewer release +- preferred decline +- preference-expiry sweep +- lease-expiry sweep +- lazy recovery + +Exit condition: claim concurrency and timer tests pass. + +### Phase 4: Immutable decisions + +- decision validation +- immutable Review and ReviewFinding writes +- FindingResolution writes +- accept and needs-revision transitions +- decision idempotency + +Exit condition: review and revision tests pass. + +### Phase 5: Reject and operational hardening + +- assignment blocking +- terminal task closure +- grant-revocation lease recovery +- Admin force-release +- reconciliation worker +- metrics, dashboards, and notifications + +Exit condition: reject, recovery, security, and observability tests pass. + +### Phase 6: Live API drill + +Run one HTTP-visible reference flow covering: + +```text +first submission +-> preferred/open routing +-> claim +-> needs_revision +-> revised submission +-> preferred reviewer expiry or takeover +-> accept +``` + +Run a separate terminal reject flow and one lease-expiry recovery flow. + +Exit condition: privacy-safe evidence report and internal trust bundle are approved. + +--- + +## 27. Definition of Done + +The review lifecycle is complete only when: + +- reviewer authority is based on explicit current project grants; +- self-review is impossible in v1; +- one reviewer cannot hold multiple active leases; +- preferred reviewer backlogs and the open queue are separately visible; +- first submissions route open and revisions route back to the prior reviewer; +- preference and lease timers operate independently; +- FIFO age is preserved when reviewer delay causes requeue; +- every lease attempt remains auditable; +- Review, ReviewFinding, FindingResolution, and SubmissionVersion are immutable; +- revision replays every unresolved blocking finding; +- accept, needs_revision, and reject transitions are atomic; +- reject blocks only the submitter-task assignment and closes the task in v1; +- runtime state and the HTTP-visible audit trail agree; +- conformance tests and live drills prove normal, failure, timeout, and concurrency paths. + +--- + +## 28. Explicitly Out of Scope + +- Automated reviewer-grant issuance from reputation thresholds. +- Reputation scoring formulas. +- Skills/reputation-based automated queue ranking. +- Multiple simultaneous review leases per reviewer. +- Self-review policy. +- Reviewer bidding or marketplace allocation. +- ContributionRecord implementation. +- Payment-status implementation. +- ReputationEvent implementation. +- Reject reassignment through `reopen_to_pool`. +- Public or cross-organization reviewer federation. +- Changes to Identity Issuer internal architecture. +- Changes to Flow Node artifact-storage internals. + +--- + +## 29. Future Additive Directions + +The following may be added without changing the v1 authority and immutable-chain model: + +- automated `ProjectRoleGrant` issuance from a versioned policy engine; +- reviewer capacity greater than one under project policy; +- routing scores using skill, reputation, turnaround, and conflict data; +- separate submitter and reviewer reputation dimensions; +- project-level reject policy `reopen_to_pool`; +- reviewer cooldown after lease expiry; +- review escalation and adjudication; +- cross-organization expert-reviewer onboarding; +- evaluation-sprint batching if Workstream later needs sprint-level ownership and reporting. + +None of these future directions may silently rewrite existing grants, queue events, leases, submissions, reviews, findings, or resolutions. + +--- + +## 30. Coding-Agent Handoff Rules + +The coding agent MUST: + +1. Treat this specification as the review-lifecycle authority. +2. Inspect existing Workstream status enums, models, services, repositories, routers, audit patterns, and error envelopes before editing. +3. Preserve the proven intake spine and extend it only after `review_pending`. +4. Reuse existing actor-resolution and authorization patterns. +5. Implement database constraints before relying on service checks. +6. Keep routers thin; lifecycle rules belong in services/domain guards. +7. Use database transactions for claims and decisions. +8. Use the existing async worker boundary for sweeps and reconciliation. +9. Add focused unit, service, API, concurrency, authorization, and failure-mode tests. +10. Keep contribution, payment, and reputation implementation outside this change. +11. Produce evidence showing exactly what changed, what passed, and what remains unimplemented. +12. Stop and request architecture confirmation if existing code requires changing a locked decision in this document. diff --git a/docs/reference_specs/WS-REV-001-review-lifecycle-specification.pdf b/docs/reference_specs/WS-REV-001-review-lifecycle-specification.pdf new file mode 100644 index 000000000..8cbb6c9a4 Binary files /dev/null and b/docs/reference_specs/WS-REV-001-review-lifecycle-specification.pdf differ