Files
turnstone/sdk/typescript/tests
Patrick Buckley edf52016ac refactor(core): lift list + saved verb bodies across both kinds (Stage 2 verb lift)
New ``make_list_handler(cfg)`` and ``make_saved_handler(cfg)``
factories in ``turnstone/core/session_routes.py`` replace four
pre-lift bodies (interactive ``list_workstreams`` +
``list_saved_workstreams``; coord ``coordinator_list`` +
``coordinator_saved``). Same factory + capability-flag pattern as
the merged cancel / open / events / create lifts.

Four new ``SessionEndpointConfig`` fields:

- ``list_resolve_titles: ListResolveTitles | None`` — bulk lookup
  ``(ws_ids) -> {ws_id: title-or-None}``. Interactive wires
  ``get_workstream_display_names`` (new bulk helper added on the
  storage layer + memory.py); the lifted body resolves every active
  row in ONE ``SELECT ... WHERE ws_id IN (...)`` instead of the
  pre-lift N+1 (one SELECT per row).
- ``list_kind: WorkstreamKind | None`` — explicit kind classifier
  for the saved-list storage filter. Replaces the initial draft's
  ``audit_action_prefix == "coordinator"`` string compare which
  would have silently leaked INTERACTIVE rows for any future kind
  whose audit prefix didn't match. Required when a kind mounts
  list/saved; misconfig surfaces as a 500 with a clear log line.
- ``saved_state_filter: str | None`` — coord wires ``"closed"``;
  interactive wires ``None``.
- ``saved_loaded_lookup: SavedLoadedLookup | None`` — coord-only
  defence-in-depth filter that excludes ws_ids in the warm pool.

Behaviour changes (all observable in CHANGELOG):

- **Active-list row shape converges on always-include** ``{ws_id,
  name, state, kind, parent_ws_id, user_id}``. Interactive renames
  ``id`` → ``ws_id``; both kinds populate every field (coord adds
  kind + parent_ws_id; interactive adds user_id).
- **Top-level response key converges on ``"workstreams"``** on
  both endpoints. Coord ``coordinators`` key removed — coord is a
  1.5.0aN-only surface (never shipped stable) so the convergence
  has no compat shim; SDK / frontend consumers swap once.
- **Storage + manager-lock work moved off the event loop on
  interactive**. ``list_workstreams_with_history`` runs through
  ``asyncio.to_thread`` on both kinds (matches coord's pre-existing
  perf-2 pattern from the saved-coordinators review); ``mgr.list_all``
  + per-row work also offloaded.
- **N+1 storage round-trips on /v1/api/workstreams eliminated**.
  Pre-lift interactive resolved the alias for every active row in a
  separate SELECT (up to 50 round-trips per dashboard refresh on a
  saturated node). Lifted body issues one bulk SELECT.

Pydantic schemas: ``WorkstreamInfo.id`` renamed → ``ws_id``,
``WorkstreamInfo.user_id`` field added. ``CoordinatorInfo`` and
``CoordinatorListResponse`` removed (folded into the unified
``WorkstreamInfo`` / ``ListWorkstreamsResponse``). OpenAPI spec
snapshots regenerated. TS SDK types updated (``WorkstreamInfo``
interface gains ws_id + the always-include fields); TS test
mock + assertion updated to match.

``GET /v1/api/dashboard`` is intentionally NOT in this PR's scope
and still returns rows keyed on ``id``. Tracked as a separate
cleanup PR (tombstone-note added at the dashboard handler).

/review pipeline run; the four Major findings + one Minor + six
nits all addressed in the same commit:

- M1: TS SDK ``WorkstreamInfo`` interface stale (id: string) →
  renamed + fields added.
- M2: TS SDK test masked the type-mismatch with stale mock → updated.
- M3: N+1 alias resolution on active list → bulk
  ``get_workstream_display_names`` helper + ``list_resolve_titles``
  bulk cfg hook.
- M4: Missing interactive parity regression test for unified row
  shape → mirror of coord's added in test_server_authz.py.
- Mi1: ``audit_action_prefix`` string-compare deriving kind →
  explicit ``cfg.list_kind: WorkstreamKind`` field.
- Six nits: redundant inner asyncio import, forward-ref quotes on
  Awaitable, duplicated frontend comments, dashboard ``id`` field
  has no tombstone-note, empty-coord_mgr short-circuit on
  ``saved_loaded_lookup``.

4512 tests passing; ruff + mypy clean.
2026-04-26 13:11:07 -07:00
..