Files
hyperguild/specs/capture-implementation-report.md
mathiasandClaude Opus 4.8 06e21c019e
CI / Lint / Test / Vet (push) Successful in 12s
CI / Mirror to GitHub (push) Successful in 4s
docs(capture): as-built implementation report for the #49 epic
Records what shipped (v0.11.0): architecture, sub-issue→PR map, I1–I5
compliance, the operational env reference, test coverage, and the
deferred follow-ups. Companion to specs/capture-bdd-spec.md (the design
contract) for onboarding + future audit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 07:41:24 +02:00

8.1 KiB
Raw Permalink Blame History

Capture capability — implementation report (as-built)

Status: Shipped 2026-06-23, tagged v0.11.0. Epic hyperguild #49 (sub-issues #50#55) closed. 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)