Ctrl K
ComunidadGuías…

docs/session-handshake.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Session Handshake (Crux Daemon)

Crux Daemon supports the VaultCrux Session Handshake v1 protocol — one endpoint
and one MCP tool that together give an agent a receipted plan describing
what it's allowed to do. This document is the operator-facing summary —
where state lives on disk, what the wire formats look like, and how to
verify a session plan or invocation receipt outside the daemon. The
canonical protocol invariants are encoded in the `crux-session` crate
([crates/crux-session/src/lib.rs](../crates/crux-session/src/lib.rs)).

## What Crux Daemon Gives You

- **One MCP tool:** `cuecrux_session`. Registered at the head of the
  `tools/list` catalogue; collapses per-service discovery into one call.
- **One HTTP endpoint:** `POST /session` (unversioned path; local-daemon specific,
  matches master-plan §5.1).
- **One verification endpoint:** `POST /invocation/verify`. Decodes a
  hex-encoded invocation receipt and returns the governance verdict.
- **Durable session state** under `$CORECRUXD_DATA_DIR`:
  - `.install-uuid` — per-install random UUID; hashed into the principal.
  - `sessions/{session_id}.json` — one sealed plan per session.
  - `session-events.jsonl` — append-only sealed-event log (one JSON line
    per `SessionPlanSealed` or `InvocationReceipted` event).

Plans on Crux Daemon run in `"local"` receipt mode: BLAKE3 hash over canonical
CBOR, no ed25519 signature. Local mode covers integrity; signed plans
are a hosted-only thing because the local-daemon threat model trusts the local
machine.

## Opening a session

```http
POST /session HTTP/1.1
Host: localhost:14800
Content-Type: application/json
Accept: application/json

{
  "client_id": "my-agent",
  "client_version": "1.2.3",
  "accepts": ["application/json"],
  "intent": "document_ingest",
  "hints": { "prefer_bulk": false }
}
```

Response:

```http
HTTP/1.1 200 OK
Content-Type: application/json
X-CueCrux-Session-Id: 0102030405060708090a0b0c0d0e0f10
X-CueCrux-Plan-Hash: b8e1...
Cache-Control: no-store

{ ...SessionPlan... }
```

The response body is the canonical JSON form of the `SessionPlan`; the
`X-CueCrux-Plan-Hash` header mirrors `receipt.hash`. All subsequent calls
that should chain to this session include `X-CueCrux-Session-Id` in their
headers (or the equivalent in MCP metadata).

## What's in a SessionPlan

Fields an agent should care about:

| Field | Meaning |
|---|---|
| `session_id` | Opaque 16-byte ULID; used as the bearer for subsequent calls. |
| `capability_graph` | Array of `{cap, prefer, shape, min_tier, cost_class, impl_path}`. Everything the passport is entitled to invoke this session. |
| `capability_graph_hash` | BLAKE3 over the canonical-CBOR of `capability_graph`. Exposed so MemoryCrux + audit tools can index by surface. |
| `channels` | `{bulk, mcp}` — where to route calls. `bulk` is null on Crux Daemon until Layer 2 ships. |
| `receipt.hash` | BLAKE3 over canonical-CBOR of the plan with `hash`/`signature`/`signer_kid` zeroed. |
| `receipt.mode` | `"local"` on Crux Daemon. `"verified"` on hosted, with an `ed25519` signature over the hash. |
| `intent_hint` | Echoes back the `intent` field from the request, if supplied. |

The Crux Daemon capability graph is shaped by:

1. **Affinity** — the passport has `["*"]` so every catalogue entry
   passes the affinity filter.
2. **Tier** — Crux Daemon runs at tier `"local"`. Catalogue entries with
   `min_tier: "free"` or above are filtered out. Baseline free
   capabilities (`session_context`, `journal_append`, etc.) are in;
   `retrieve` / `proof_document` / `audit_replay` are hosted-only.

## Invocation receipts

Every call made under an active session should produce an
`InvocationReceipt` — a 200-byte receipt chained to the plan via
`parent_plan_receipt_hash`.

To verify one:

```http
POST /invocation/verify HTTP/1.1
Host: localhost:14800
Content-Type: application/json

{
  "invocation_id":            "<16 bytes hex>",
  "session_id":               "<16 bytes hex>",
  "parent_plan_receipt_hash": "<32 bytes hex>",
  "capability":               "retrieve",
  "channel":                  "bulk",
  "invoked_at":               1745000001000,
  "completed_at":             1745000001100,
  "input_hash":               "<32 bytes hex>",
  "output_hash":              "<32 bytes hex>",
  "outcome":                  "ok",
  "receipt_hash":             "<32 bytes hex>"
}
```

Response:

```json
{
  "verified": true,
  "integrity_ok": true,
  "capability_ok": true,
  "channel_ok": true,
  "governance_faults": [],
  "parent_plan_found": true,
  "parent_plan_principal_id": "ce:a4f3b1c2:tester"
}
```

The endpoint returns `200` even when the verdict flags governance faults
(wrong capability, wrong channel). Master-plan §8.2 — faults are
evidence, not reasons to drop the receipt. The caller decides what
enforcement to apply.

## On-disk format

Crux Daemon is designed so that an operator can `jq` through state without
spinning up a database:

```bash
# Every session you've ever opened:
ls "$CORECRUXD_DATA_DIR/sessions/"

# Sealed events in write order (1 JSON line each):
cat "$CORECRUXD_DATA_DIR/session-events.jsonl" | jq -r .event_type | sort | uniq -c
```

Session files are rewritten atomically (temp-file + rename) on every
close/revoke; the event log is append-only and fsync'd per write.

## Verifying a receipt offline

The `crux-session` crate's CBOR + BLAKE3 primitives are pure; a verifier
can be built with zero I/O. Sketch:

```rust
use crux_session::{plan_receipt_hash, verify_invocation_receipt, SessionPlan};

let plan = SessionPlan::from_canonical_cbor(&plan_cbor_bytes)?;

// Plan-level integrity:
assert_eq!(plan_receipt_hash(&plan), plan.receipt.hash);

// Invocation-level chain:
let verdict = verify_invocation_receipt(&receipt, &plan);
assert!(verdict.verified_overall());
```

The TypeScript mirror at `@cuecrux-shared/session` produces byte-identical
canonical CBOR, so the same receipt verifies in either runtime.

## Feature flags

Crux Daemon has no plan-level feature flags at M6 — everything is always on. On
hosted the session feature is gated behind `FEATURE_SESSION_HANDSHAKE`
per tenant; Crux Daemon ships with it enabled by default.

## What's missing (follow-ups)

- **Layer 2 bulk channel.** Crux Daemon surfaces `channels.bulk: null`;
  the HTTP/2 + CBOR bulk transport is a separate plan.
- **Automatic MCP interceptor.** Invocation receipts are minted by an
  agent that chooses to chain them; there is no built-in trap-door that
  silently mints one for every tool call. See the M4 deferred-to-follow-up
  note in the ExecPlan.
- **Hosted import migration.** The import event type and the
  `imported_principal_map` table are pre-positioned for Phase 8; the
  actual upload pipeline is not yet wired. The persisted `ce:` principal
  prefix remains as a wire-compatibility identifier for existing local
  receipts.