Files
hyperguild/specs/capture-implementation-report.md
mathiasandClaude Sonnet 5 1cea2c9f78
CI / Lint / Test / Vet (push) Successful in 29s
CI / Mirror to GitHub (push) Successful in 4s
feat(capture): remove summary->ai-sessions write path
ai-sessions#13: capture's `summary` field wrote a frontmatter shape
(title/harness/fidelity/captured_at/repos_touched) that ai-sessions'
own extract/audit pipeline can't parse -- silently invisible to that
repo's own audit tooling, and bypassing its redaction + Stage2
completeness gates by construction. Only 5 files ever landed this way
over 3 weeks; the summary capability's whole value (speed) fights
ai-sessions' whole value (redacted, audited, complete), so kill it
rather than build a second parse branch.

capture now persists insights -> brain and action items -> Gitea
tickets only. Removes Summary/SummaryResult/SummaryWriter and all
wiring (service, REST body, MCP tool schema); close-session updated to
stop assembling a summary payload. specs/capture-*.md kept as historical
record with a superseded note -- the feature shipped and is documented,
just no longer current behaviour.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WPdSHbp9Utb2hPFm9wDG59
2026-07-27 23:17:56 +02:00

124 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Capture capability — implementation report (as-built)
**Status:** Shipped 2026-06-23, tagged `v0.11.0`. Epic hyperguild #49 (sub-issues #50#55) closed.
**Superseded 2026-07-27:** the `summary``ai-sessions` write path documented below was removed.
It wrote a frontmatter shape (`title`/`harness`/`fidelity`/`captured_at`/`repos_touched`) that
`ai-sessions`' own extract/audit pipeline couldn't parse — invisible to that repo's own audit
tooling and bypassing its redaction/completeness gates (`ai-sessions#13`). `capture` now persists
insights → brain and action items → Gitea tickets only. This document is kept as the historical
as-built record of what shipped in #49; treat every `summary`/ai-sessions reference below as
retired, not current behaviour.
**Spec:** `specs/capture-bdd-spec.md` (the design contract this implements).
**Governed by:** `infra/docs/architecture/01-invariants.md` (I1I5) + the I2 acceptance ledger entry in `infra/docs/security-baseline.md`.
This document records what was actually built, where it lives, how it maps to the spec, and what was deferred — for onboarding and future audit. It does not restate the design rationale (see the spec and the linked brain entries).
---
## 1. Outcome
One uniform capture capability — insights → brain, action items → Gitea tickets, optional summary → ai-sessions — reachable identically from every harness:
- **In-process / direct-REST harnesses** (Claude Code CLI, Agentsquad, claude.ai Code, headless): `POST /capture` on the brain server.
- **MCP-native harnesses** (claude.ai Chat/Cowork/Design, Crush, Pi, LLM Council): the `capture` MCP tool, reached over the existing `/mcp` OAuth connector.
Both doors call the **same** `CaptureService`; only the transport and credential assembly differ. The persistence behaviour (validation, classification, I1 gate, orchestration, I5 audit, partial receipt) is written once.
---
## 2. Architecture (as-built)
```
POST /capture (REST) capture MCP tool
capturehttp.Handler mcp.Server.brainCapture
\ /
\ (auth → principal → /
\ origin; decode) /
v v
capture.CaptureService (use-case, pure)
┌───────────────┬───────────────┬──────────────┬───────────────┐
BrainStore IssueTracker SummaryWriter ClassificationPolicy AuditSink
brainstore. gitea.Client (nil today) classification.Config audit.Degrading
Store (REST) Sink / SlogSink
│ │
api.WriteNote/UpdateNote/ReadNote (#45) LokiCentral + FileBuffer
+ wing index + auto-tunnel + graph re-index + NtfyNotifier + Reconcile
```
- **`internal/capture/`** — the use-case + ports + entities. Pure; no I/O. Owns validation (fail-closed), effective-classification resolution (stricter wins), the **I1 sovereignty gate**, best-effort orchestration, the **two-phase I5 audit** (Reserve before writes / Record after), and the partial-aware receipt.
- **`internal/brainstore/`** — concrete `BrainStore` wrapping the #45 `api` primitives + wiki upkeep (wing `_index`, auto-tunnel, graph re-index). The MCP `brain_write`/`brain_update`/`brain_get` handlers were re-pointed at it: one implementation, not two.
- **`internal/classification/`** — `public < internal < confidential` taxonomy + per-wing/repo tags from an optional `classification.yaml`; fail-safe to confidential.
- **`internal/gitea/`** — `IssueTracker` over the Gitea REST API; owner forced to `mathias`; token only in the Authorization header.
- **`internal/capturehttp/`** — the REST adapter + the shared `Authenticate` / `DecodeRequest` / `OriginResolver` (also used by the MCP tool).
- **`internal/audit/`** — `SlogSink` (default) and the `DegradingSink` (loki + durable `FileBuffer` + `NtfyNotifier` + `Reconcile`).
- **`internal/mcp/`** — the `capture` relay tool + principal threading (re-derives the caller's principal from the Bearer header the chassis middleware discards).
---
## 3. Sub-issue → PR map
| Sub | Issue | PR(s) | Delivered |
|-----|-------|-------|-----------|
| 49a | #50 | #56 | classification taxonomy + per-wing/repo tags (fail-safe to confidential) |
| 49b | #51 | #57 | `CaptureService` use-case + ports + entities; `BrainStore` extraction (MCP re-pointed) |
| 49c | #52 | #58 | Gitea `IssueTracker` (owner forced mathias; token never logged) |
| 49d | #53 | #59 | `POST /capture` REST + OAuth2 + I1 sovereignty gate (server-derived origin) |
| 49e | #54 | #60 | I5 audit path + classification-aware degradation (loki + buffer + reconcile) |
| 49f | #55 | #61, infra #151 (ledger), #152 (deploy) | MCP `capture` relay tool + I2 ledger + I3 deploy |
Predecessor: #45 (`brain_update`/`brain_get` verbs, PR #46) — the read-after-write contract capture reuses.
---
## 4. Invariant compliance
| Inv | How satisfied |
|-----|---------------|
| **I1** sovereign containment | Effective classification = stricter(caller-declared, target-derived #50). Origin is **server-derived from the authenticated principal**, never `context.harness`. Confidential + us-nexus origin → refused before any write; refusal audited. Asserted-vs-derived mismatch → security event. |
| **I2** deliberate acceptance | The relay's cross-harness reach is recorded in `infra/docs/security-baseline.md` with six containment properties + Revisit-if, **merged before relay code shipped** (infra #151). |
| **I3** GitOps reconcilability | Env + `gitea-api-token` ExternalSecret under `infra/k3s/apps/supervisor/`, Flux-reconciled; image bumped by CD. No untracked runtime. |
| **I5** auditability | Every capture emits a request-level audit record. Classification-aware degradation: confidential + sink-down → hard-refuse; internal/public + sink-down → durable local buffer + ntfy + reconcile-on-recovery; floor → refuse if nothing can record. |
---
## 5. Operational reference (env)
Set on the `ingestion` deployment (`infra/k3s/apps/supervisor/ingestion-deployment.yaml`):
| Env | Purpose | Notes |
|-----|---------|-------|
| `BRAIN_GITEA_URL` / `BRAIN_GITEA_TOKEN` | enables the `IssueTracker` → gates `/capture` + the MCP tool | token from 1P `DMABE_GITEA_API_TOKEN` via ESO; unset ⇒ capture disabled |
| `BRAIN_LOKI_URL` | activates the `DegradingSink` | unset ⇒ `SlogSink` (audit to stdout → alloy → loki; no refuse/buffer semantics) |
| `BRAIN_NTFY_URL` / `BRAIN_NTFY_TOKEN` | degraded-state alerts | optional |
| `BRAIN_CAPTURE_SOVEREIGN_PRINCIPALS` | JWT subjects treated as sovereign-soil | comma-separated; static-token caller is always sovereign; unknown JWT ⇒ us-nexus (fail safe) |
| `BRAIN_AUDIT_RECONCILE_INTERVAL` | buffer→loki replay tick | default 60s |
The audit buffer lives at `<brain>/.audit-buffer/capture.jsonl` on the brain hostPath (nodeSelector-pinned to koala) — durable across restart without a separate PV.
---
## 6. Tests
67 test functions across the six packages. Coverage maps to the spec's Gherkin: happy path, supersede-not-duplicate, fail-closed validation, partial-failure receipt, dry-run, stricter-classification-wins, I1 confidential-via-us-nexus-refused / via-sovereign-allowed / asserted-label-ignored / caller-cannot-forge-origin, I5 confidential-refuse / internal-buffer / floor-refuse / reconcile / buffer-survives-restart, and the MCP relay tool (forwards, preserves principal, unauth rejected). `task check` green.
---
## 7. Deferred (not in this epic)
Tracked here so they aren't lost; file as issues when picked up:
- **SKILL veneer** — the `close-session` SKILL becomes the claude.ai trigger/harvest layer that calls capture.
- **Per-harness token provisioning** for Crush / Pi / LLM Council (claude.ai is done via the existing `/mcp` connector).
- **Harvest adapters** — transcript-parse vs chat-memory-reconstruct vs agent-runlog, each assembling capture args at its own fidelity.
- **`SummaryWriter` impl** — ai-sessions summary persistence (the port + path logic exist; the concrete writer is nil today, so a request with a summary fails that one item).
---
## 8. Brain learnings
- `wiki/hyperguild/decisions/capture-classification-taxonomy`
- `wiki/hyperguild/decisions/gate-on-server-derived-signals-fail-safe`
- `wiki/hyperguild/decisions/two-phase-reserve-record-audit-gate`
- `wiki/hyperguild/failures/mcp-bearer-middleware-discards-principal`
- `wiki/hyperguild/facts/brain-mcp-embeddings-out-of-band-sync` (from #45, the predecessor)