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
8.7 KiB
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 (I1–I5) + 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 /captureon the brain server. - MCP-native harnesses (claude.ai Chat/Cowork/Design, Crush, Pi, LLM Council): the
captureMCP tool, reached over the existing/mcpOAuth 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/— concreteBrainStorewrapping the #45apiprimitives + wiki upkeep (wing_index, auto-tunnel, graph re-index). The MCPbrain_write/brain_update/brain_gethandlers were re-pointed at it: one implementation, not two.internal/classification/—public < internal < confidentialtaxonomy + per-wing/repo tags from an optionalclassification.yaml; fail-safe to confidential.internal/gitea/—IssueTrackerover the Gitea REST API; owner forced tomathias; token only in the Authorization header.internal/capturehttp/— the REST adapter + the sharedAuthenticate/DecodeRequest/OriginResolver(also used by the MCP tool).internal/audit/—SlogSink(default) and theDegradingSink(loki + durableFileBuffer+NtfyNotifier+Reconcile).internal/mcp/— thecapturerelay 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-sessionSKILL 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
/mcpconnector). - Harvest adapters — transcript-parse vs chat-memory-reconstruct vs agent-runlog, each assembling capture args at its own fidelity.
SummaryWriterimpl — 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-taxonomywiki/hyperguild/decisions/gate-on-server-derived-signals-fail-safewiki/hyperguild/decisions/two-phase-reserve-record-audit-gatewiki/hyperguild/failures/mcp-bearer-middleware-discards-principalwiki/hyperguild/facts/brain-mcp-embeddings-out-of-band-sync(from #45, the predecessor)