# Capture capability — use-case & BDD specification **Status:** Draft for review (pre-build). Spec precedes implementation because `capture` is a privileged cross-harness write path touching brain + Gitea + ai-sessions. **Tracks:** hyperguild #49. **Governed by:** `infra/docs/architecture/01-invariants.md` (I1–I5), the admissibility test in `00-synthesis-model.md`, and the distributed-consolidation shape mandated by `brain/wiki/homelab/decisions/no-centralized-cross-harness-observer-2026-06-17.md`. --- ## 1. Use-case (Clean Architecture form) **Name:** CaptureSession **Actor:** A harness acting on the user's behalf (claude.ai Chat/Cowork/Code/Design, Claude Code CLI, Crush, Pi, LLM Council, Agentsquad executor/reviewer) — or the user directly. **Goal:** Durably persist a finished session's valuable output — insights → brain, action items → Gitea tickets, optional summary → ai-sessions — with one uniform invocation, identical core behaviour across harnesses. **Primary success scenario (essential steps):** 1. Caller assembles capture input (insights, tickets, optional summary) + context (harness, session_ref, fidelity, actor, **data-classification**). 2. System validates the whole request (fail-closed). 3. System checks the **sovereignty gate** (I1): if the session is classified confidential AND the caller's harness is a non-sovereign (us-nexus) surface, the capture is **refused** at this layer. 4. System persists insights (write or supersede), tickets (create/close/comment), summary — each best-effort, recording per-item outcome. 5. System emits an **audit record** (I5) of who/what captured what, when, via which principal. 6. System returns a structured, partial-aware receipt. **Architectural shape:** the *logic* is a shared use-case (`CaptureService`), invoked **per-harness against the caller's own credentials** (distributed consolidation — no high-degree observer node). A central authenticated relay endpoint exists ONLY as a fallback for harnesses that cannot run the use-case in-process (Crush/Pi/headless); the relay holds no standing visibility and retains nothing beyond the I5 audit log. --- ## 2. Invariant obligations (acceptance gates, not nice-to-haves) | Invariant | Obligation on `capture` | |---|---| | **I1 sovereign containment** | A confidential-classified session MUST NOT be captured through a us-nexus harness. Classification is explicit in `context`, enforced here, not assumed. | | **I2 deliberate acceptance** | The *distributed-library* form opens no new acceptance. IF a central relay node is deployed, its cross-harness reach MUST be entered in `infra/docs/security-baseline.md` with Why-accepted / Revisit-if before it ships. | | **I3 GitOps reconcilability** | IF `capture` runs as a deployed service, its manifest lives under `infra/k3s/apps/**` (sovereign source, Flux-reconciled). No untracked runtime. | | **I4 decisions captured** | The distributed-vs-central decision and the intent-named-verb pattern are recorded (ADR + brain). | | **I5 auditability** | Every capture emits a request-level audit record to the alloy/loki substrate: actor/principal, harness, items written, timestamp. | --- ## 3. BDD scenarios (Gherkin) ```gherkin Feature: Capture session value uniformly across harnesses As an operator working across many AI harnesses I want one uniform command to persist insights and file tickets So that valuable session output is never lost and is always auditable Background: Given a brain store, a Gitea issue tracker, and an ai-sessions summary writer And the caller is authenticated with a principal And the session context declares a harness, a fidelity, and a data classification # --- Core happy path --- Scenario: Capture insights and tickets from a non-confidential session Given a session classified as "internal" And the capture input has 2 insights and 1 ticket to create When capture is invoked Then both insights are written to the brain and their ids and content hashes are returned And the ticket is created in the named repo under owner "mathias" And an audit record is emitted naming the principal, harness, and items written And the receipt reports every item as ok # --- I1: sovereignty gate (the load-bearing refusal) --- Scenario: Refuse capture of a confidential session through a us-nexus harness Given a session classified as "confidential" And the calling harness is a us-nexus surface When capture is invoked Then the capture is refused before any write And no insight, ticket, or summary is persisted And the refusal names the sovereignty invariant as the reason Scenario: Allow capture of a confidential session through a sovereign harness Given a session classified as "confidential" And the calling harness is a sovereign-soil surface When capture is invoked Then the capture proceeds and persists normally # --- Supersession + staleness discipline (reuses #45 / #47 resolution) --- Scenario: Supersede a prior insight rather than duplicating it Given an insight whose context names an existing note to supersede When capture is invoked Then the existing note is updated in place, not duplicated And the prior content hash is recorded in the superseding note And read-after-write confirmation uses a direct fetch, never a semantic query # --- Validation: fail-closed --- Scenario: Reject a malformed request before any write Given a capture input with an invalid wing/hall or unknown repo When capture is invoked Then the request is rejected with a validation error And nothing is written to the brain, Gitea, or ai-sessions # --- Partial failure: best-effort + honest receipt --- Scenario: Report partial success when one item fails mid-capture Given a capture input with 2 insights and 1 ticket And the second insight write will fail When capture is invoked Then the first insight and the ticket are persisted And the second insight is reported as failed in the receipt And no rollback is attempted And the audit record reflects exactly what landed # --- Dry run --- Scenario: Preview a capture without writing Given a valid capture input with dry_run true When capture is invoked Then the would-be receipt is returned And nothing is written anywhere # --- I5: auditability is non-optional --- Scenario: Capture is unavailable if the audit sink is unreachable Given the audit substrate cannot be written to When capture is invoked Then the capture is refused And the reason names the auditability invariant # A privileged write path that cannot be audited must not run silently. # --- Summary fidelity (collision rule from the retro work) --- Scenario: A richer-fidelity summary supersedes a thinner one for the same session Given a summary already exists for session_ref X at fidelity "live-capture" And a new summary arrives for session_ref X at fidelity "transcript-parse" When capture is invoked Then the transcript-parse summary supersedes the live-capture one And the live-capture summary is not left as a contradicting duplicate ``` --- ## 4. Open questions for review (deliberately unresolved here) 1. **Classification source.** How does `context.classification` get set, and is it trusted? A harness self-declaring "internal" to bypass the I1 gate is an attack surface. Options: caller declares + server cross-checks against a project/repo allow-list; or classification derived from the target wing/repo rather than caller-asserted. **This is the highest-risk open question** — the whole I1 gate is only as strong as the classification it reads. 2. **Sovereign-harness determination.** Is "is this harness us-nexus?" derived from the authenticated principal/origin (server-side, trustworthy) or from `context.harness` (caller-asserted, spoofable)? Must be server-derived to be load-bearing. 3. **Central relay: build or defer?** The distributed-library form is admissible without a ledger entry. The fallback relay needs the I2 acceptance entry. Decide whether the relay ships in v1 or whether Crush/Pi/headless are deferred until it's ledgered. 4. **Audit-sink-down behaviour.** Spec above says "refuse if unauditable." Confirm that's the desired posture vs. degrade-and-warn — it's a real availability/assurance trade.