11.7.4 Session close-out over `/api/v1` — record one item as integrated, and bulk-close a merged branch with per-item outcomes
The two write endpoints that close the work loop, both gated on the integration scope — which exists for exactly these two operations and names them in its own definition.
What to build
- Record one item as INTEGRATED on a session branch — adapts
workItemsService.markIntegrated. It moves the item toin_reviewAND stampssession_branchin one transaction; the endpoint must not decompose that into two calls, because a crash between them leaves an item in review with no lineage. OptionalimplementationHarnessrides along as provenance. - Bulk-close a merged session branch — adapts
workItemsService.completeSession. Every item recorded on the branch goesdone,session_branchis cleared, and the response carries per-item outcomes, including the partial case where some items closed and others did not. The client reports what came back; it never re-derives an outcome from a count.
Both return shapes come from 11.7.3's schema module. Path and encoding per 11.7.1 Q1 — note a session branch is a git ref and can contain /, so whatever that decision pinned about carrying it is load-bearing here.
Partial success is a real outcome, not an error
complete_session over a branch with a dozen items can legitimately close nine and skip three. That is not a 500 and not a 200-with-everything-fine: the per-item result IS the payload, and the status code choice has to accommodate it. Whatever 11.7.1 pinned, the endpoint must never collapse a partial result into a single boolean.
Scope BOUNDARY
Ends at these two endpoints. It does NOT ship expansion or the plan endpoints (11.7.5 / 11.7.6) or the activity read (11.7.7). It does NOT change workItemsService — both methods ship today, transactional, and this card adapts them unmodified. It does NOT change the status lifecycle, the workflow, or what session_branch means. It does NOT touch the MCP tools, which keep working.
Acceptance criteria
- Both endpoints exist, are declared in the operation registry, appear in the emitted spec, and are gated on
integration— the scope its MCP counterpart carries, asserted againstlib/mcp/scopes.tsrather than a copy. - Marking an item integrated moves it to
in_reviewand stampssession_branchatomically — asserted by a failure injected between the two effects leaving neither applied. - A bulk close returns per-item outcomes, and a deliberately mixed branch (some closable, some not) returns both kinds in one response rather than failing the request or reporting uniform success.
implementationHarnessis recorded when supplied and absent when not.- A token holding
work_items:writebut NOTintegrationis refused 403 on both — asserted, because these two operations are the entire reason that scope exists and a bleed would make it decorative. - Cross-tenant keys return the product's 404-not-403.
- Every domain error either method raises has a deliberate row in the v1 status map, proven by a test that drives it.
- Both payloads match their MCP counterparts field for field.
- No Prisma call, no transaction opened at the route, no import from
lib/mcp/tools/. - The per-file coverage floor (≥90%) holds on every new file.
Context refs
- 11.7.3 — the schema module both responses come from.
- 11.7.1 — Q1's paths and the session-branch encoding decision.
lib/services/workItemsService.ts—markIntegratedandcompleteSession, both transactional, both unchanged by this card.lib/mcp/tools/markIntegrated.ts·completeSession.ts— the argument shapes and the transactional contract to mirror. Read, not imported.lib/mcp/scopes.ts— theintegrationscope, whose doc comment names these two operations.tests/mcp/integration-state.test.ts— the shipped behaviour both endpoints must agree with.- Story: 11.7.