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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion .ci/behavior-ownership/partition.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@
"group": "shared",
"target": "backend/app/api/deps/post_policy.py"
},
{
"group": "lifecycle",
"target": "backend/app/api/deps/project_catalogue.py"
},
{
"group": "lifecycle",
"target": "backend/app/api/deps/project_guide_reads.py"
Expand Down Expand Up @@ -268,6 +272,10 @@
"group": "shared",
"target": "backend/app/api/routes/post_policy.py"
},
{
"group": "lifecycle",
"target": "backend/app/api/routes/project_catalogue.py"
},
{
"group": "lifecycle",
"target": "backend/app/api/routes/project_guide_reads.py"
Expand Down Expand Up @@ -684,6 +692,10 @@
"group": "auth",
"target": "backend/app/modules/authorization/api/post_policy.py"
},
{
"group": "auth",
"target": "backend/app/modules/authorization/api/project_catalogue.py"
},
{
"group": "auth",
"target": "backend/app/modules/authorization/api/project_guide_compilation.py"
Expand Down Expand Up @@ -872,6 +884,10 @@
"group": "auth",
"target": "backend/app/modules/authorization/domain/prepared_submission_policy.py"
},
{
"group": "auth",
"target": "backend/app/modules/authorization/domain/project_catalogue.py"
},
{
"group": "auth",
"target": "backend/app/modules/authorization/domain/project_create.py"
Expand Down Expand Up @@ -1036,6 +1052,10 @@
"group": "auth",
"target": "backend/app/modules/authorization/prepared_submission_replay.py"
},
{
"group": "auth",
"target": "backend/app/modules/authorization/project_catalogue.py"
},
{
"group": "auth",
"target": "backend/app/modules/authorization/project_role_schemas.py"
Expand Down Expand Up @@ -2349,7 +2369,7 @@
"target": "backend/scripts/validate_test_lane_evidence.py"
}
],
"authority_digest": "bcb6024819d3bbda45e7f8fbfd0dba07ea4a26981348253daca0c5d9d9ec90db",
"authority_digest": "38802d2eaf89cd8ce3ec68743f7c8a4868771d69fb99544400dd16559a269098",
"protected_base_commit": "7676ce4347db0c9694962a9b587a20765e16eac6",
"schema": "workstream.behavior-ownership-partition.v1"
}
11 changes: 10 additions & 1 deletion .commitrail/initiatives/WS-CLI-001/OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
and eligible warning acknowledgement through existing public POSTs;
[WS-CLI-001-41](WS-CLI-001-41.md), explicit manual artifact-policy draft
creation and exact-predecessor hash-guarded successor through public writes;
[WS-CLI-001-46](WS-CLI-001-46.md), a separate minimal system project-reader
catalogue and `project list` command, without widening contributor membership;
[PILOT-13](../../changes/pilot13-assigned-task-guide-documents.md), assigned-task locked-guide listing/download;
[PILOT-02](../../changes/pilot02-bulk-transitions-cli.md), explicit CSV conversion,
source custody, atomic import, batch status and whole-batch screen/release.
Expand All @@ -68,6 +70,12 @@ client gaps and the optional TUI remain separate work. Binary distribution's
workflow is prepared by -44; a protected release run and public-asset audit
remain operational work.

[WS-CLI-001-46](WS-CLI-001-46.md) adds `GET /api/v1/projects` and
`workstream project list`: current system Project Manager, Operator, Finance
and Audit project-reader grants can discover bounded ID/name/status pages.
`me projects` remains exact-grant membership; catalogue reads neither confer
guide/management access nor implement the optional TUI.

The Operator commands `operations task list`, `operations task locked-context`
and `operations task start` use three existing public routes. Their restricted
read projections remain observations; the backend rechecks the distinct
Expand Down Expand Up @@ -330,7 +338,8 @@ CLIs. Keep the package independent of backend and MCP runtime dependencies.
identity, authority or backend policy.
3. **WS-CLI-001-03:** Inspect a known project alongside its current authority,
through the public project read. Preserve administrative/contributor response
boundaries; do not invent a public project-list contract.
boundaries. The separate system-reader catalogue arrives in WS-CLI-001-46;
contributor membership stays exact-grant scoped.
4. **WS-CLI-001-04:** Browse a manager task page and open its exact project/task
detail, passing opaque continuation unchanged without automatic pagination.
5. **WS-CLI-001-05:** Discover one ready-task page and inspect contributor
Expand Down
110 changes: 110 additions & 0 deletions .commitrail/initiatives/WS-CLI-001/WS-CLI-001-46.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# WS-CLI-001-46 — System-covered project catalogue

- Initiative: `WS-CLI-001`
- Durable disposition: `Complete`
- Issue: [#663](https://github.com/Flow-Research/workstream/issues/663)
- Intended merge outcome: A minimal public project catalogue and explicit CLI page read let holders of a current system-wide Project Manager, Operator, Finance Authority, or Audit Authority grant find projects by name without treating exact-project membership as global discovery.

## Intent

`GET /api/v1/actors/me/projects` currently lists only active exact-project grants. `actor.project.list` is an actor-self action; a system grant adds effective roles to listed rows but enumerates no projects (`docs/spec_authorization_service.md`). The separate exact-project `project.read` action permits a system-wide Project Manager, Operator, Finance Authority, or Audit Authority to inspect a known project. Access Administrator has no `project.read` permission. Requiring a UUID obtained out of band makes the intended administrative terminal picker unusable. The human decision for this chunk is to permit those four existing system-wide project-reader roles, but no others, to enumerate only project ID, name and status.

## Bounded change

1. Add one `project.catalogue.list` AUTH action mapped to existing `project.read`, with an exact actor-owned collection context, a fresh active-human/system-scope-only grant check restricted to the four named roles, grant locking through the canonical AUTH repository, and decision/audit provenance. Extend the existing closed audit action/permission CHECK with that exact pair in one narrow migration; it otherwise rejects even denied decision evidence. Deny services, Access Administrator alone, exact-project-only grants and contributor-only grants. Do not modify `actor.project.list`.
2. Extend the PROJECTS-owned summary reader/public port with a separate bounded project-ID keyset page over all projects containing only ID, name and status. ID order is stable enumeration, not creation, commit or business order. Do not materialize every project ID or join guide, task, source, actor or payment rows. Use the existing snapshot-read and signed cursor conventions to bind actor, action, query and limit; the cursor position discloses only the already-public ID. Authorize before cursor parsing on every page. New `GET /api/v1/projects` returns only that page and an opaque `next_cursor`.
3. Add `workstream project list [--limit N] [--cursor TOKEN]`, using the existing typed public client and stable text/`--output json`/structured error conventions. No TUI runtime is part of this PR. Update the canonical authorization spec, CLI README and affected roadmap/initiative navigation for the intended merged outcome.

### Allowed files

- AUTH catalogue, typed context/port, kernel/read dispatch, focused evaluator, repository and their tests under `backend/app/modules/authorization/`, `backend/app/adapters/auth/__init__.py` and `backend/tests/authorization/`.
- One next Alembic revision under `backend/alembic/versions/` solely admitting `project.catalogue.list`/`project.read` to the closed `audit_events` action/permission CHECK, its migration test, the linear-head guard in `backend/alembic/env.py`, the exact revision graph test in `backend/tests/test_alembic.py`, and the checked public-schema fingerprint in `backend/tests/conftest.py` after comparing the actual object change. Reconcile catalogue revision `0061_project_catalogue_read` to current main's `0060_audit_restore_path` as predecessor; no parallel head or replacement of retained facts, audit validator bodies or their pinned safe search paths.
- PROJECTS public summary port/reader and focused tests under `backend/app/modules/projects/` and `backend/tests/contributor_reads/` or `backend/tests/project_reads/`.
- One public route/composition under `backend/app/api/`, router wiring and API-control tests under `backend/tests/`, including the exact production rate-dependency inventory in `backend/tests/test_api_rate_controls.py`.
- `backend/scripts/test_lane_catalogue.py` and its exact-inventory test only to assign the new project API and migration proof to existing hosted lanes; no lane count, runner, limit or gate-behavior change.
- `.ci/behavior-ownership/partition.v1.json`, `backend/scripts/behavior_ownership.py`, and `backend/tests/test_behavior_ownership.py` only to register the five new eligible API/AUTH catalogue modules in the existing exact target-to-group partition. Retain the protected-base commit, all prior assignments, digest/custody validation, and fail-closed rejection of neighboring targets.
- `cli/internal/api/` and `cli/internal/command/` project-list client/command, plus focused CLI integration tests and `cli/README.md`.
- `mcp_server/contracts/authorization_context_get.json` only to reconcile the existing selected OpenAPI `ActionId` schema after this action is added. The existing authorization-context response validator accepts that new enum value; no MCP tool, route or operation is added.
- `docs/spec_authorization_service.md`, the system-catalogue row in `docs/operations_roles_permissions.md`, affected `docs/roadmap_status.md` sections, this record, `.commitrail/initiatives/WS-CLI-001/OVERVIEW.md`, and `.commitrail/INDEX.md` only for the new durable boundary.

### Prohibited changes

No change to contributor/exact-grant `me projects`, existing project detail authorization, role permission definitions, project lifecycle, guide disclosure, task search, reviewer workflow, compensation/payment, login/token storage, TUI behavior, CI gate behavior, or unrelated catalogue operations. No duplicate project resolver, all-project materialization, fallback authority path, compatibility alias, automatic pagination, or merge/release.

## Acceptance criteria

- Each of the four named *system-scoped* roles independently obtains pages of only project ID, name and `draft|active|paused|archived` status, including a project lacking an exact grant. Same-project overlaps do not duplicate rows; page size is 1–100 with stable ID order and a bounded response. The cursor envelope must not expose creation timestamps or any other undisclosed project fact.
- A system Access Administrator alone, exact-project Project Manager/Finance/Audit, Submitter/Reviewer and unrelated actor cannot enumerate; an existing exact project grant remains visible only through unchanged `me projects`. Absent/denied cases never reveal private project facts or substitute an empty success.
- Every page starts from current actor and system grant authority; cursor tampering, cross-actor/limit/action reuse is refused, and revocation committed before a later page removes catalogue access. A concurrent revoke cannot commit until an already-authorized response releases its locked grant; an in-flight response may still arrive after that later commit. AUTH decision and evidence identify the matched system grant.
- The PROJECTS read uses one bounded SQL page of three columns in primary-key order, not a full project-ID set. Demonstrate real PostgreSQL storage and actor/resource isolation, grant revocation/concurrent-session locking, and no duplicate/gap pagination. The primary-key keyset avoids a catalogue-wide sort; production-scale latency is unmeasured and not claimed. A projection/response validation failure must roll back its AUTH decision evidence. The new audit CHECK must accept the exact pair and reject a crossed pair by direct SQL while retaining the valid row.
- CLI text escapes untrusted terminal controls, JSON preserves validated public values, failures retain stable bounded structured stderr and no partial stdout. Existing `workstream me projects`, `project show/access`, and agent CLI output remain unchanged.
- Run focused backend/CLI tests, applicable full suites and public API drill, the selected MCP/OpenAPI contract comparison, stale-wording scan, Markdown-link check, roadmap/INDEX reconciliation, and reviewer tracks on a clean candidate. Hosted backend lanes must select both new modules, and the separate CLI public-API lane must supply the built binary for the native drill; no test may silently skip when that binary is absent.
- The canonical behavior-ownership partition contains each new eligible catalogue module exactly once in its deterministic group, validates against protected `main`, and still rejects an unapproved neighboring target. Do not weaken partition completeness, digest or transition custody to make preflight pass.

## Evidence

Focused proof lives in `backend/tests/project_catalogue/test_public.py`,
`backend/tests/migrations/test_project_catalogue_read.py`, and
`cli/tests/integration/test_project_catalogue_http.py`, and the existing
`cli/tests/integration/test_admin_inspection_public_api.py` journey. It uses separately
stored actors and grants; the fixture's default manager is already system
authorized and is not a negative control. Two independently authorized readers
exercise cross-actor cursor binding. Existing canonical nonhuman/inactive
identity tests cover the shared read gate. The catalogue cases prove exact-only
and Access Administrator denials, revoked system authority, a grant-lock race,
and rollback of invalid projection evidence. The existing CLI public-API journey
now runs the catalogue command with its built binary, system Audit Authority
and two real projects against the live API and PostgreSQL, avoiding a second
database/server setup in the hosted 15-minute job. The CLI HTTP suite checks
process output against hostile responses. The HMAC cursor is
integrity-protected but its Base64 payload is not confidential; it contains
only the already-disclosed project ID as position.

The isolated PostgreSQL 16 public-API module passes as one settled run (11
cases): four distinct system roles, exact-grant, contributor and Access
Administrator denials, signed pagination with action binding and ID-only
payload, revocation, a concurrent grant-lock race, and invalid-projection
rollback. The separate shadow-schema migration regression covers crossed-pair
direct SQL and role search-path cleanup.
The migration chain preserves main's `0060_audit_restore_path` unchanged before
catalogue revision `0061_project_catalogue_read`. Retained upgrades from both
`0059_neutral_guide_runtime` and `0060_audit_restore_path` check audit validator
identity/body custody and safe function-local search paths; the catalogue-only
upgrade must leave the complete already-pinned validator custody unchanged.
The PostgreSQL 16 schema-object comparison confirms `0059 -> 0060` changes only
the two audit helper function definitions and `0060 -> 0061` changes only the
closed `audit_events` action/permission CHECK. Main's reviewed fingerprint and
the earlier catalogue-only fingerprint both reproduce from those exact object
sets; the combined fingerprint is derived from the actual merged schema.
AUTH catalogue and API-control inventories, Go tests/build/module verification,
CLI process tests, Ruff, Markdown links and stale-wording checks pass. The
exact production rate-dependency inventory includes the new catalogue route
under the existing authorization-read limiter; its full route/dependency
equality assertion is retained, not replaced with a catalogue-only assertion.
The existing MCP authorization-context selected fragment was reconciled only for
the added `ActionId`: the actual backend OpenAPI comparison passes all 29
cases, and the MCP non-integration suite passes 599 cases with two existing
skips. The existing MCP response validator now accepts that shared enum value;
no MCP tool, route or operation was added. These
are local source/binary proofs; hosted CI and independent review belong to the
draft PR. The initial real-API 503 from an omitted closed AUTH decision-resource
type was caught by this module and fixed without weakening error normalization.

Hosted preflight exposed the five new eligible catalogue Python modules missing
from the behavior-ownership partition. The exact-target repair retains the
protected-base commit and every prior assignment, adds only those five modules
in the deterministic auth/lifecycle groups, and recalculates the canonical
digest. Local partition validation, the full ownership catalogue validator,
and all 167 ownership tests pass; a neighboring module remains rejected.

## Risk and review routing

- Risk class: `L1` — new cross-project enumeration under administrative authority and a public API/CLI contract. No delivery SLA supplied.
- Focused plan review precedes code. Implementation reviewers: `security`, `architecture`, `qa`, `documentation`, `product_ops` for workspace/role semantics, and `ci_integrity` for the exact hosted-lane ownership change; add `reuse_dedup` if the owner extension creates duplicate selection logic. The lead runs shared checks once, then freezes a clean candidate.
- Human review focus: confirm only the four approved system-wide `project.read` roles enumerate and that ID/name/status is the entire catalogue disclosure; inspect AUTH grant-lock/audit proof, real PostgreSQL negative paths, bounded paging, and unmodified contributor membership semantics.

## Alternatives and remaining risks

Extending `me projects` with a system mode was rejected: exact-grant membership and system-wide catalogue selection have different permission/audit and disclosure contracts; reusing its `active_guide` field would over-disclose to system Finance. `GET /api/v1/projects` is the natural separate read on the existing PROJECTS owner and does not establish a second resolver. Client-side UUID entry remains available but does not solve discovery. A future TUI consumes the public read; it is not delivered here.

Potential risks are grant/actor race handling, cursor substitution, row projection drift, and unmeasured production-scale catalogue latency. The project-ID primary key supplies the page order without an added index; no delivery-time performance guarantee is claimed. The policy decision is resolved by the user; the human merge decision remains separate.
3 changes: 2 additions & 1 deletion backend/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
target_metadata = Base.metadata

_BASELINE_REVISION = "0001_uuid7_v01"
_CURRENT_HEAD_REVISION = "0060_audit_restore_path"
_CURRENT_HEAD_REVISION = "0061_project_catalogue_read"
_RECREATE_GUIDANCE = (
"Workstream v0.1 requires a fresh database; recreate this database before "
"running the 0001_uuid7_v01 migration"
Expand Down Expand Up @@ -113,6 +113,7 @@ def do_run_migrations(connection: Connection) -> None:
("0057_roles_anywhere_custody",),
("0058_compensation_unit_create",),
("0059_neutral_guide_runtime",),
("0060_audit_restore_path",),
(_CURRENT_HEAD_REVISION,),
):
raise RuntimeError(_RECREATE_GUIDANCE)
Expand Down
Loading
Loading