Status: Accepted Date: 2026-10-01
Issue #739 (API-4) lists eight read surfaces. Three are shipped on main
(GET /api/v1/runs, GET /api/v1/runs/{run_id},
GET /api/v1/runs/{run_id}/attempts); /evidence and /decisions are
blocked on the content store owned by issue #740. That leaves
GET /api/v1/work-units, GET /api/v1/work-units/{id} and
GET /api/v1/projects/{project_id}.
Reading the code rather than the issue text shows what the Product Spine actually persists:
{id, version, digest, source_revision}
(schemas/idkmesh-product-spine-run-v0.1.schema.json). No component stores
a WorkUnit body.project_id is a bare string on the run. No project record or store
exists; project-manifest.schema.json is a repository file contract, not a
served resource.The decision is therefore not whether to serve these surfaces but what they may truthfully claim. Issue #739 requires stable identities, exact source-revision/digest binding, deterministic ordering, bounded filters, cursor pagination, no hidden winner selection, explicit 404 behavior and no mutation authority. ADR-0016 requires one API shape with separate authority and forbids a read surface from acquiring write or selection authority.
work-units and projects are read models derived on demand from stored
run projections. They introduce no new store, no new write path and no field
that is not a pure function of stored run references.
id:
id;run_count: number of stored runs referencing this id;revisions: the distinct {version, digest, source_revision} references
seen for this id, each with its own run_count, ordered ascending by
(version, digest, source_revision).
No WorkUnit body is returned, because none is stored; digest is the
binding a caller uses to fetch or verify a body elsewhere.project_id:
project_id;run_count;runs_by_state: every canonical run state present as a key, zero-filled,
so the shape is identical for every project;work_unit_count: number of distinct WorkUnit ids referenced.
Per-project WorkUnits are reached with GET /api/v1/work-units?project_id=,
not embedded, so the response stays bounded.GET /api/v1/work-units/{id} takes the entire
path remainder as the literal id, because WorkUnit ids share the run-id
grammar and may contain /. v0.1 reserves no sub-resource suffix for
work-units; adding one requires a new ADR that extends ADR-0019’s
path-only resolution rule. GET /api/v1/projects/{project_id} follows the
same literal-remainder rule.GET /api/v1/work-units is ordered by id ascending,
keyset-paginated with an opaque self-issued cursor
(product-spine-work-unit-list-cursor-v1), bounded by the existing
limit range, with one declared filter, project_id. Any other query
parameter fails with the conventions’ explicit unknown-parameter error.
There is no GET /api/v1/projects list in this ADR; it is not in #739’s
target surfaces and is deferred.product_spine_store_not_configured behavior as /runs. Unknown ids return
work_unit_not_found / project_not_found (404). The endpoints are GET
only and add no authority, matching ADR-0016.idkmesh-work-unit-resource-v0.1, idkmesh-project-resource-v0.1) and
reuses the existing idkmesh-list-v0.1 envelope. ADR-0020’s
backward-compatibility gate applies from first commit.json_extract, as list_runs
already does for project_id. Acceptable at local development-store scale;
a dedicated index or table is a revisit condition, not a v0.1 requirement./runs.Rejected. It needs authority and lifecycle decisions (who may create a project, how WorkUnit bodies are validated and versioned) that no issue or ADR has settled, and it would block a read slice whose data already exists.
Rejected. Issue #739’s acceptance requires a client to work without reading repository files, and a body fetched from a working tree is not bound to the stored digest.
Rejected. “Latest” is an implicit selection, which #739 and ADR-0016 forbid, and it hides runs that used earlier revisions.
Rejected. It is unbounded for a busy project; the filtered list endpoints already give a bounded, paginated route to the same data.
idkmesh/connector_store.py owns the derivation queries,
idkmesh/product_spine_run_store.py the typed service methods and cursor,
idkmesh/control_tower_ui.py routing, and
docs/specifications/CONTROL_TOWER_LOCAL_API_V0_1.md the endpoint contract.Revisit this ADR if:
Any revision must preserve ADR-0016’s authority invariant and #739’s “no hidden winner selection” requirement.