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
2 changes: 1 addition & 1 deletion .commitrail/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ for current product capability.
|---|---|---|
| [WS-DB-002](initiatives/WS-DB-002/OVERVIEW.md) | Complete | Shared UUIDv7 record generation, native-UUID relationships and fresh v0.1 baseline; natural-owner retry custody and aligned CI/local setup |
| [WS-MCP-002](initiatives/WS-MCP-002/OVERVIEW.md) | Planned | Nine tools through WS-MCP-002-03: self-service and administrative reads; 18 tools remain and WS-MCP-002-04 administrative grant mutations are next |
| [WS-CLI-001](initiatives/WS-CLI-001/OVERVIEW.md) | Planned | Public Go CLI self-service, project inspection and manager task browsing delivered through WS-CLI-001-04; further public journeys and binary distribution remain |
| [WS-CLI-001](initiatives/WS-CLI-001/OVERVIEW.md) | Planned | Public Go CLI self-service, project inspection, manager browsing and contributor discovery delivered through WS-CLI-001-05; further public journeys and binary distribution remain |
| [WS-ARCH-001](initiatives/WS-ARCH-001/OVERVIEW.md) | Planned | Source storage, inert AUTH contracts, TASK request reservation, hidden exact AUTH preparation and the REV-04C hidden FinalAcceptance/TASK/CON participant are delivered; TASK-before-CHECKERS reservation/current-read custody and ordered admission INSERTs are delivered by 04E1B-B1; hidden routing handlers are next. True admission remains independent of shared acceptance; false activation still requires exact AUTH receipt custody, database complete-set enforcement, currentness race proof and atomic routing activation. |
| [WS-ART-001](initiatives/WS-ART-001/OVERVIEW.md) | Planned | Exact checker input/output custody and packet foundations are delivered; routing integration, remediation and public intake remain. |
| [WS-AUTH-001](initiatives/WS-AUTH-001/OVERVIEW.md) | Planned | AUTH-19A commitments, TASK request reservation, ARCH-04E2-A hidden strict PREP matching and the REV-04C hidden FinalAcceptance/TASK/CON participant are delivered; the action remains unavailable, with mandatory exact AUTH receipt input, database closure, audit/outbox and scoped activation still required for the automated path. |
Expand Down
11 changes: 8 additions & 3 deletions .commitrail/initiatives/WS-CLI-001/OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
exact-project authorization context; [WS-CLI-001-02](WS-CLI-001-02.md), human
self-profile editing through the public REST API; [WS-CLI-001-03](WS-CLI-001-03.md),
exact-project inspection with server-owned full/minimal disclosure;
[WS-CLI-001-04](WS-CLI-001-04.md), public manager task pagination and detail.
[WS-CLI-001-04](WS-CLI-001-04.md), public manager task pagination and detail;
[WS-CLI-001-05](WS-CLI-001-05.md), contributor ready-task discovery and instructions.

## Current boundary

Expand All @@ -24,6 +25,8 @@ package under `cli/` implements `whoami`, `project access PROJECT_ID`, and
reads only the existing public project's server-selected disclosure shape.
`project tasks PROJECT_ID` and `project task PROJECT_ID TASK_ID` add the public
manager queue/detail journey with server-owned authority on each page.
`task ready PROJECT_ID` and `task show TASK_ID` add the contributor projection
with exact Submitter authority and server-owned assignment visibility.
All have text/JSON
output and built-binary integration proof. Mutations preserve omitted/null
semantics and explicitly report uncertain outcomes without automatic retries.
Expand Down Expand Up @@ -66,11 +69,13 @@ CLIs. Keep the package independent of backend and MCP runtime dependencies.
boundaries; do not invent a public project-list contract.
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. **Later governed-work commands:** Add project setup, contributor task, submission,
5. **WS-CLI-001-05:** Discover one ready-task page and inspect contributor
instructions through public reads, without management metadata or task writes.
6. **Later governed-work commands:** Add project setup, contributor task mutations, submission,
review, revision, and contribution reads/writes only as their actual public
contracts and authority boundaries become available. Split by user journey,
not one PR per endpoint or one giant catalogue PR.
6. **Optional TUI:** Add a focused public queue/evidence view after its API
7. **Optional TUI:** Add a focused public queue/evidence view after its API
workflow is complete. Never require a TUI for agents or scripts.

## Risks and proof
Expand Down
123 changes: 123 additions & 0 deletions .commitrail/initiatives/WS-CLI-001/WS-CLI-001-05.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# WS-CLI-001-05 — Discover contributor work

- Initiative: `WS-CLI-001`
- Durable disposition: `Complete`
- Intended merge outcome: Browse one ready-task page and inspect contributor
instructions through existing public REST reads.

## Intent

The CLI has manager task browsing, not a contributor discovery journey.
`queue_router.py:ready_tasks` exposes the exact-project Submitter queue;
`router.py:get_task` exposes contributor instructions for unassigned ready work
or the caller's own active assignment. AUTH and TASK retain all decisions.

## Bounded change

Allowed: cohesive Go client/command code under `cli/internal/`, built-process
integration tests under `cli/tests/integration/`, CLI/root README, affected
roadmap claims, this record, CLI overview and index. Reuse the existing transport,
UUID identity, strict decoding, pagination bounds and terminal escaping.

Not allowed: backend, MCP, workflows, dependencies, hidden routes, task writes,
claim/start commands, credential persistence, role preflight/filtering, automatic
pages/retries, generic dispatch, a TUI, or additional test/coverage gates.

## Design

`workstream task ready PROJECT_ID [--limit N] [--cursor CURSOR]` calls exactly one
`GET /api/v1/projects/{project_id}/tasks/ready`. `workstream task show TASK_ID`
calls exactly one `GET /api/v1/tasks/{task_id}`. Original supported UUID selectors
are individually escaped and sent unchanged; response identity compares UUID
values. Pagination defaults to 50, bounds 1–100 and passes a supplied 1–512
character UTF-8 cursor unchanged. Workstream binds cursors to action, project
and limit. Do not infer authority or reserve work from a discovery response.

Validate the closed queue envelope and its eight-field contributor summaries,
not management summaries. Validate contributor detail without allowing management
source, actor or assignment fields. Preserve nullable/omitted fields from the
API, exact raw JSON and complete safely escaped text. Reject malformed required
fields, null arrays/members, invalid identities/times, unknown/duplicate fields,
foreign queue items, duplicate task identities and substituted detail identities
before output. A task-only detail selector has no caller-supplied project to
compare; validate its returned project UUID without inventing a preflight.
Existing 12-second deadline and 64 KiB response bound remain.

## Acceptance criteria

- Exact fixed route, unchanged bearer, one request per invocation; invalid local
selectors/limits/cursors produce no network call.
- Full text/raw-JSON parity, queue nulls, detail null/omission, safe continuation
and terminal escaping. Management-only data cannot silently become contributor
output. Malformed/substituted/oversized replies fail with empty stdout.
- Real FastAPI/PostgreSQL proof stores two projects with ready tasks, discovers
two pages and exact detail, omits draft/claimed work, and preserves a caller's
own assigned-detail visibility while another active same-project Submitter
cannot read that assignment. That other actor first succeeds on unassigned
ready detail, so missing authority cannot masquerade as assignment isolation.
- Exact Submitter grants permit reads; absent, Reviewer-only, foreign-project,
revoked and suspended authority deny. Restore a fresh Submitter grant and prove
both reads work before suspension. Cursor project substitution uses a caller
with active Submitter grants on both active projects; action substitution uses
a caller with both Submitter and Manager authority on that project; limit
substitution retains the same valid authority. Each independently reaches
422, separately from authority concealment.
- Reuse canonical guide-activation fixtures to arrange approved upstream inputs;
scripted guide inference/storage are fixture boundaries, not live guide or S3
certification. Activate with real AUTH/PROJECT, create/screen/release/claim work
and issue/revoke grants through public HTTP. No runtime API dependency override
or direct task/assignment row creation. Both CLI reads must appear in OpenAPI.
- Existing tests are retained. Add focused process proof in
`cli/tests/integration/test_contributor_task_http.py`. Extend the existing
self-service API journey after its administrator bootstrap through
`cli/tests/integration/contributor_task_journey.py`; reuse its API process and
configure the local verifier for the activation fixture issuer
`https://identity.flowresearch.tech`. No second bootstrap or order-dependent
standalone API journey. Run Go verify/tidy/vet/build,
Ruff, links/stale scans, Commitrail, workflow guards and the complete hosted CI.

## Evidence

The built-process HTTP checks prove exact contributor fields, wire selectors,
terminal safety, malformed-response refusal and single-request behavior.
Duplicate task rejection covers both identical UUID strings and canonical/compact
spellings of the same identity, independently of the page-size guard; raw-string
duplicate tracking must fail this regression.
The public FastAPI/PostgreSQL journey proves persisted ready work, three pages,
own-assignment visibility, same-project non-owner concealment and independent
authority/cursor controls. It reuses one API/bootstrap and canonical upstream
fixtures; scripted guide inference/storage are not live-provider certification.
Hosted completeness and exact-target reviewer evidence belong in the PR, not
this durable record. Existing CLI proof and the 240-second deadline are retained.

```sh
cd cli
go mod verify
go mod tidy -diff
go vet ./...
go build -trimpath -o /tmp/workstream-cli ./cmd/workstream
cd ../backend
WORKSTREAM_CLI_EXECUTABLE=/tmp/workstream-cli python scripts/run_isolated_tests.py --metadata-json /tmp/workstream-cli-isolation.json --timeout-seconds 240 -- python -m pytest -q ../cli/tests/integration
```

## Risk and review routing

Risk: `L1`, bounded credential transport and tenant/actor disclosure. Plan review
precedes code. Required tracks: security; architecture/documentation; QA/test
delta, in three focused assignments. No CI track: CI is unchanged, and the lead
runs its guards. Human focus: contributor-only disclosure, one-page continuation,
assignment visibility and server-owned live authority.

Reject manager-route reuse: it grants and discloses a different contract. Reject
role inference, automatic paging and hidden setup in the CLI. Separate read-only
discovery from future claim/intake mutations. No new human design decision is
needed; approval and merge remain human-owned.

Remaining boundary: further role-specific public workflows and binary distribution.

## Plan reconciliation

`PLAN-SEC-CLI05-001/002` require positive same-project authority before testing
assignment concealment and independent authorized controls for cursor dimensions.
`PLAN-FIXTURE-CLI05-001` reuses one administrator bootstrap/API journey and the
canonical fixture issuer. Preserve the existing 240-second CI deadline.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ caller-owned human display fields, through the currently public REST API.
the caller's current authority, preserving contributor-minimal disclosure.
`workstream project tasks PROJECT_ID` and `project task PROJECT_ID TASK_ID`
provide a paginated manager list-to-detail journey through public reads.
`workstream task ready PROJECT_ID` and `task show TASK_ID` provide contributor
discovery and instructions, with live Submitter authority and no task claim.
All support human-readable and JSON output, using the caller's Flow token.
The first source package is buildable; further workflow commands and published
binaries remain planned.
Expand Down
46 changes: 45 additions & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

An independent Go client for Workstream's public REST API, for humans and
agents using the terminal. It provides human self-profile reads and editing,
plus exact-project inspection, authority reads and manager task browsing:
plus exact-project inspection, authority reads, manager task browsing and
contributor work discovery:

| Command | Public API |
|---|---|
Expand All @@ -12,6 +13,8 @@ plus exact-project inspection, authority reads and manager task browsing:
| `workstream project show PROJECT_ID` | `GET /api/v1/projects/PROJECT_ID` |
| `workstream project tasks PROJECT_ID` | `GET /api/v1/projects/PROJECT_ID/tasks` |
| `workstream project task PROJECT_ID TASK_ID` | `GET /api/v1/projects/PROJECT_ID/tasks/TASK_ID` |
| `workstream task ready PROJECT_ID` | `GET /api/v1/projects/PROJECT_ID/tasks/ready` |
| `workstream task show TASK_ID` | `GET /api/v1/tasks/TASK_ID` |

Workstream verifies the caller's Flow bearer and owns identity resolution,
authorization and lifecycle decisions. Reading a profile can admit a first-time
Expand Down Expand Up @@ -119,6 +122,38 @@ malformed replies fail with empty stdout. The existing 64 KiB response bound
applies to a whole page: an oversized response fails without partial output;
request a smaller `--limit` if needed.

## Discover work as a contributor

```sh
workstream task ready PROJECT_ID --limit 10 --output json
workstream task ready PROJECT_ID --limit 10 --cursor PREVIOUS_NEXT_CURSOR --output json
workstream task show TASK_ID --output json
```

These are contributor routes, not aliases for manager browsing. The ready queue
requires an active project and its exact active Submitter grant; Manager or
Reviewer authority alone does not permit it. It lists only unassigned ready
tasks. Contributor detail shows unassigned ready work or the caller's own active
assignment under current Submitter authority. It does not expose management
source/actor/assignment metadata, and a different same-project Submitter cannot
read your claimed task. Workstream makes these decisions on every request.

The same one-page limit/cursor bounds, UUID selector encoding, strict response
validation and safe text/raw-JSON output apply. Ready summaries contain task and
project IDs, title, nullable type/difficulty/estimated minutes, skills and creation
time. Detail adds instructions, criteria, status, deadline and update time;
nullable detail fields may be omitted by the API and display as `—`.
`task show` takes only a task selector; Workstream resolves its project and
authorizes that resource. No project preflight or locally inferred permission
is added. Management-only fields in a contributor reply are rejected rather
than silently displayed or ignored.

Discovery is live, not a reservation or a claimability guarantee. A later claim
must revalidate current authority and state. Cursors are action/project/limit
bound and cannot be reused as manager cursors; the CLI never decodes them or
automatically fetches another page. These commands do not claim/start tasks,
upload submissions or complete unfinished acceptance integration.

## Edit your profile

```sh
Expand Down Expand Up @@ -169,6 +204,15 @@ It separates signed-cursor substitution (authorized caller receives 422) from
foreign authority denial, and proves a restored grant permits both reads before
suspension denies them. Hostile HTTP process tests validate complete field
output, one-request pagination, malformed/substituted replies and redirect refusal.
Contributor proof reuses that API process and bootstrap, arranging approved
upstream guide inputs through canonical fixtures. Guide inference and storage
are scripted prerequisites, not live-provider proof; real AUTH activates the
projects. Public task create/screen/release/claim operations supply persisted
ready and assigned work. Reads prove pagination parity, draft/claimed exclusion,
same-project non-owner concealment, independent authorized cursor substitution,
exact Submitter grants, Reviewer-only/foreign/revoked denial and suspension after
restored positive authority. The process fixture verifies the separate contributor
shapes, complete field output and refusal of management-only data.
Local Flow-compatible tokens are test fixtures, not deployed-provider proof.
No coverage percentage or test-count target is used.

Expand Down
93 changes: 93 additions & 0 deletions cli/internal/api/contributor_tasks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package api

import (
"context"
"errors"
"net/http"
"net/url"
)

// ReadyTaskSummary is the public contributor projection, not management facts.
type ReadyTaskSummary struct {
TaskID string `json:"task_id"`
ProjectID string `json:"project_id"`
Title string `json:"title"`
TaskType *string `json:"task_type"`
Difficulty *string `json:"difficulty"`
SkillTags []string `json:"skill_tags"`
EstimatedTimeMinutes *int `json:"estimated_time_minutes"`
CreatedAt string `json:"created_at"`
}

type ReadyTaskPage struct {
ProjectID string
Items []ReadyTaskSummary
NextCursor *string
}

// ContributorTaskDetail excludes management-only source and actor metadata.
type ContributorTaskDetail struct {
TaskSummary
Description string `json:"description"`
AcceptanceCriteria *string `json:"acceptance_criteria"`
RejectionCriteria *string `json:"rejection_criteria"`
}

func (c *Client) ReadyTasks(ctx context.Context, project string, limit int, cursor *string) (Result[ReadyTaskPage], error) {
var result Result[ReadyTaskPage]
path, err := taskProjectPath(project)
if err != nil {
return result, err
}
query, err := taskPageQuery(limit, cursor)
if err != nil {
return result, err
}
raw, err := c.request(ctx, http.MethodGet, path+"/ready", query, nil)
if err != nil {
return result, err
}
page, err := decodeTaskPage(raw, project, limit)
if err != nil {
return result, err
}
value := ReadyTaskPage{ProjectID: page.ProjectID, Items: make([]ReadyTaskSummary, 0, len(page.Items)), NextCursor: page.NextCursor}
seen := make(map[[16]byte]bool)
selected, _ := uuidIdentity(project)
for _, item := range page.Items {
var task ReadyTaskSummary
err := decodeTaskFields(item, &task,
[]string{"task_id", "project_id", "title", "created_at"},
[]string{"task_type", "difficulty", "estimated_time_minutes", "skill_tags"})
id, validID := uuidIdentity(task.TaskID)
returned, validProject := uuidIdentity(task.ProjectID)
if err != nil || !validID || !validProject || returned != selected ||
!validTime(task.CreatedAt) || seen[id] {
return result, &Failure{Code: "invalid_api_response"}
}
seen[id] = true
value.Items = append(value.Items, task)
}
return Result[ReadyTaskPage]{Raw: raw, Value: value}, nil
}

func (c *Client) ContributorTask(ctx context.Context, selector string) (Result[ContributorTaskDetail], error) {
var result Result[ContributorTaskDetail]
selected, valid := uuidIdentity(selector)
if !valid || len(selector) > 100 {
return result, errors.New("TASK_ID must be a UUID")
}
raw, err := c.request(ctx, http.MethodGet, "/api/v1/tasks/"+url.PathEscape(selector), "", nil)
if err != nil {
return result, err
}
var value ContributorTaskDetail
err = decodeTaskFields(raw, &value,
[]string{"task_id", "project_id", "title", "description", "status", "created_at", "updated_at"},
[]string{"skill_tags"})
returned, valid := uuidIdentity(value.TaskID)
if err != nil || !valid || returned != selected || !validTask(value.TaskSummary, value.ProjectID) {
return result, &Failure{Code: "invalid_api_response"}
}
return Result[ContributorTaskDetail]{Raw: raw, Value: value}, nil
}
Loading
Loading