Skip to content

feat(cave): add conversational control surface for the mutation authority - #6

Closed
CompleteDotTech wants to merge 1 commit into
release/sdk-0.1.0-unlock-41from
feat/conversational-control-42
Closed

feat(cave): add conversational control surface for the mutation authority#6
CompleteDotTech wants to merge 1 commit into
release/sdk-0.1.0-unlock-41from
feat/conversational-control-42

Conversation

@CompleteDotTech

Copy link
Copy Markdown
Owner

Summary

Implements the SDK-owned half of the approved conversational-control design (docs/superpowers/specs/2026-08-28-sdk-conversational-control-design.md) for OpenCoven#42: canonical conversation create, idempotent send with one caller-visible operation UUID, explicit stop, retry of a failed/cancelled turn with a fresh operation UUID plus explicit retryOfTurnId, a typed resumable event stream, and reconciliation helpers — with Cave remaining the sole executor, idempotency authority, replay authority, and canonical owner.

Observable behavior

  • client.createConversation({ operationId, familiarId, projectId? }) validates the 36-character Client v1 UUID (normalized to the lowercase idempotency-key form), refuses unknown fields and non-canonical IDs, and returns the recorded create result with a replayed flag.
  • client.sendConversationMessage(conversationId, { operationId, text }) preserves text byte for byte; client.retryConversationTurn(conversationId, { operationId, retryOfTurnId }) is a typed convenience over the same messages.send operation with a fresh operation UUID — no replacement text, no second producer route.
  • client.getConversationOperation returns the non-content operation record: fixed codes, turn references, event bounds, timestamps only — never prompt, attachment, bearer, or cause content.
  • client.streamConversationOperation(operationId, options) exposes the typed resumable event stream: one total timeoutMs budget across long polls (each poll receives only the remaining budget), duplicate suppression at or below the accepted cursor, contiguous monotonic event IDs, and the same translator for initial and resumed pages. A caller abort closes the current read and the generator; it never calls Stop and never resubmits the send.
  • client.stopConversationOperation sends each explicit Stop exactly once and never retries it after an ambiguous transport completion; calling Stop again explicitly is safe.
  • Every mutation dispatches exactly once. An ambiguous transport completion never causes an automatic replay; after acceptance, every local, transport, and application error exposes the operation UUID via CaveClientError.operationId.
  • reconcile_required (with bounded details.reason) is surfaced as an instruction to reload getConversation() / listConversationMessages() from the first page and replace the local projection — never retried, never fabricated.

Upstream contract gap (tracked, not invented)

The five Client v1 conversation operations (conversations.create, messages.send, operations.read, operations.events, operations.stop) are not declared by the authoritative contract fixture vendored here — pinned Cave producer commit 4adc97b1 (provenance: packages/cave/fixtures/contract-fixture.provenance.json, digest b2694cd1…). Verified through the vendored fixture bytes and pnpm verify:contracts: 13 reviewed operations, all reads/pairing/admin. chat:write / conversations:write pairing scopes, the 36-character idempotency-key limit, and the conflict/reconcile_required error codes are reserved in the contract, but no mutation operation record, event/cursor contract, generated-fixture limits (message byte/page/wait/retention bounds), or canonical request-hash conformance vectors exist upstream yet.

Per the approved design ("an SDK PR must not land first with speculative route paths"):

  • The SDK ships the typed surface, request validation, result/event DTO parsing, the single event translator, operation-ID error propagation, no-auto-replay semantics, and reconciliation helpers — with no route paths. The five CaveTransport methods (createConversation, sendConversationMessage, getConversationOperation, readConversationOperationEvents, stopConversationOperation) are optional and unbound, so calls report unsupported_operation until the contract exists. No arbitrary HTTP paths, private Cave routes, or raw transport escape hatches are introduced.
  • Who owes what: the Cave producer owes the five Client v1 operation records/routes (fixed conversations:write/chat:write scopes, originating-scope operation reads, capability families, protected-operation registration, exact generated-fixture limits, the event/cursor contract, and canonical request-hash conformance vectors) reviewed in contract.ts/operations.ts/docs with a generated fixture — design §16 steps 2–3. Then pnpm sync:contracts imports the exact producer commit here. Chat owes the Tauri commands, explicit write-scope re-pairing, and native evidence (design §15). The private CLI human/JSON/NDJSON streaming renderers are staged for the follow-up SDK PR (design step 5): they cannot execute a mutation against a real authority before the producer contract exists. Real-authority conformance and fault-injection journeys and the separate authority-level security review follow after the producer contract exists (design §16 steps 6–8; [SDK 0.1.0][P0] Prove the packed SDK with cross-repository real-authority conformance OpenCoven/sdk#38's platform-record gap remains open upstream).
  • Contract fixtures were intentionally not refreshed: pnpm sync:contracts imports from a producer checkout that does not yet contain the mutation contract. The pinned fixture at 4adc97b1 (digest b2694cd1…) is unchanged and verified.

Validation

  • corepack pnpm@10.34.0 verify exit 0 on the pushed head: typecheck, 1,279 tests across 60 files plus 6 stress tests, contract/package/release verification, coverage (global 90.42% lines / 85.89% branches), and lint; git diff --check clean on the same head.
  • 64 focused tests added — 46 in tests/cave-conversation-control.spec.ts, 18 in tests/cave-conversation-stream.spec.ts — covering: UUID normalization and malformed-ID refusal without echoing untrusted values; exactly-one-of text/retryOfTurnId; byte-for-byte text preservation; fresh-UUID retry with explicit retryOfTurnId; recorded-result replay with replayed: true; unsupported_operation with the operation ID on old transports; operation ID attached to transport and hostile-response errors; exactly-once dispatch with no automatic replay after ambiguous transport completion; one-shot Stop; reconcile_required reasons; the shared translator (initial/resume parity, duplicate suppression at or below the accepted cursor, in-page and cross-page gap refusal, events after terminal, repeated stopping, ahead-of-record, complete/non-terminal contradictions, empty terminal pages); one shared stream budget with per-poll remaining budgets; caller-abort closing the read without Stop or resend; and hostile envelope/record/payload refusal.
  • api-baselines/cave.d.ts + cave.json regenerated via api:baseline:update; the diff is purely additive (new conversation-control exports; existing signatures unchanged). tests/public-contract.spec.ts's export pin updated to the same reviewed list.

Release implications

Changeset: minor for @opencoven/cave-client (the fixed version group bumps the other public packages per the 0.1.0 unlock group stacked below this PR). API baselines regenerated and reviewed: additive exports only, no signature changes. No release authorization is implied; publication remains locked per OpenCoven#41's execution gates.

Merge order

#1 (security review) → #5 (0.1.0 unlock prep) → this PR; OpenCoven#42 closes on merge only after OpenCoven#41's release execution if the maintainer upholds the recorded dependency graph.

Assignee: @CompleteDotTech
Advances OpenCoven#42 (maintainer close on merge)
Dependency-graph override by user directive (2026-08-30): started despite Blocked by: #41 — stacked on PR #5; merges only after #1#5, and only after OpenCoven#41's release execution if the maintainer upholds the recorded graph.
Stacked on fork PR #5 (release/sdk-0.1.0-unlock-41).

Upstream PR (one click for a maintainer): https://github.com/OpenCoven/sdk/compare/main...CompleteDotTech:sdk:feat/conversational-control-42?expand=1

…rity

Implement the SDK-owned half of the approved conversational-control design
(docs/superpowers/specs/2026-08-28-sdk-conversational-control-design.md):

- typed create/send/retry requests with one caller-visible operation UUID,
  validated against the existing Client v1 36-character UUID contract and
  normalized to the lowercase idempotency key form;
- non-content operation records, create/send result envelopes, and a single
  event translator shared by initial and resumed streams (contiguity,
  duplicate suppression, terminal-sequence refusal, reconcile reasons);
- six CaveClient methods backed by optional CaveTransport methods: create,
  send, retry, read, stream, stop - one-shot dispatch, no automatic replay
  after ambiguous transport completion, operation ID attached to every
  post-acceptance error, abort closes the read without Stop or resend;
- one total stream budget across long polls; duplicate suppression at or
  below the accepted cursor; reconcile_required exposed as a reload
  instruction with its reason.

Upstream-contract gap (stated, not invented): the five Client v1 operations
(conversations.create, messages.send, operations.read, operations.events,
operations.stop) are not declared by the authoritative fixture pinned at Cave
4adc97b1, so no transport binding or route path ships; every call reports
unsupported_operation until the producer contract lands and
pnpm sync:contracts imports it. Private-CLI streaming renderers follow in a
separate PR per the design's PR plan.
@CompleteDotTech

Copy link
Copy Markdown
Owner Author

Recreated upstream as OpenCoven#81: OpenCoven#81. Closing this duplicate fork PR; review continues upstream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant