feat: CaptureService use-case + BrainStore extraction (#51, capture 49b) #57

Merged
mathias merged 2 commits from feat/capture-service into main 2026-06-22 21:31:47 +00:00
Owner

Implements #51 (capture 49b) — the Clean-Architecture spine of the capture capability.

Stacked on #56 (#50). Base is feat/capture-classification so this diff is #51-only. It reuses classification.Level/Target (shared domain type — avoids a duplicate, drift-prone enum), which is the one soft dependency on #50. Merge #56 first, then this retargets cleanly to main.

What landed

ingestion/internal/capture/ — pure use-case, no I/O:

  • Ports: BrainStore (#45 Write/Update/Get), IssueTracker, SummaryWriter, ClassificationPolicy, AuditSink. Entities: Insight, Ticket, Summary, CaptureContext, CaptureInput, CaptureReceipt.
  • CaptureService.Capture: validate-before-write (fail-closed) → resolve effective classification (stricter of declared vs target-derived) → orchestrate insights (write/supersede) → tickets → summary, best-effort with per-item outcome → emit a request-level audit record of exactly what landed → return a partial-aware receipt. dry_run short-circuits after validation, writes nothing (not even audit).

ingestion/internal/brainstore/ — the BrainStore extraction:

  • One concrete Store implementing capture.BrainStore, composing the api file primitives (#45 read-after-write) with the wiki upkeep a write needs (wing _index rebuild, cross-wing auto-tunnel, graph re-index).
  • MCP brain_write/brain_update/brain_get re-pointed at it — one implementation, not two (the DRY payoff #51 is named for). The {id, path, content_hash} response contract and all existing MCP tests are preserved unmodified.

Scope boundaries (deliberate)

  • I1 origin sovereignty gate → #53 — needs the server-derived principal origin; not here.
  • Classification-aware audit degradation/refusal → #54 — the AuditSink port exists; the confidential-fails-closed / internal-degrades policy layers on around this core.
  • Effective folded into the service as classification.Stricter (port stays minimal Derive) — "stricter wins" is use-case policy, and it keeps classification.Config a drop-in ClassificationPolicy.
  • Unknown-repo pre-validation is structural-only here (repo non-empty + valid action + required fields); true repo-existence validation lands with the real tracker (#52). A bad repo surfaces as a per-item ticket error (partial receipt), not a silent write.

Acceptance criteria

  • capture/ package: ports, entities, CaptureService
  • BrainStore concrete impl (Write/Update/Get); MCP handlers re-pointed — no duplicate logic
  • #45 read-after-write + (out-of-band) staleness discipline preserved inside the store
  • Validate-before-write: malformed input → error, nothing written
  • Best-effort orchestration + partial receipt with per-item ok/error; no rollback
  • dry_run returns would-be receipt, writes nothing
  • Effective-classification via injected ClassificationPolicy (stricter wins)
  • Unit tests vs fake ports: write+supersede, partial-failure, dry-run, validation-reject, stricter-wins (+ caller-raising-honoured, summary path/fidelity)
  • Existing MCP brain_update/brain_get/brain_write tests pass after re-pointing
  • task check green (lint 0 issues, vet clean, -race green)

BDD scenarios satisfied (against fakes)

supersede-not-duplicate · fail-closed validation · partial-failure receipt · dry-run · stricter-classification-wins · caller-raising-honoured · summary fidelity stamping.

Not auto-merged — review + merge from claude.ai.

Closes #51

🤖 Generated with Claude Code

Implements #51 (capture 49b) — the Clean-Architecture spine of the capture capability. > **Stacked on #56 (#50).** Base is `feat/capture-classification` so this diff is #51-only. It reuses `classification.Level`/`Target` (shared domain type — avoids a duplicate, drift-prone enum), which is the one soft dependency on #50. **Merge #56 first**, then this retargets cleanly to `main`. ## What landed **`ingestion/internal/capture/`** — pure use-case, no I/O: - **Ports:** `BrainStore` (#45 Write/Update/Get), `IssueTracker`, `SummaryWriter`, `ClassificationPolicy`, `AuditSink`. **Entities:** `Insight`, `Ticket`, `Summary`, `CaptureContext`, `CaptureInput`, `CaptureReceipt`. - **`CaptureService.Capture`:** validate-before-write (fail-closed) → resolve effective classification (stricter of declared vs target-derived) → orchestrate insights (write/supersede) → tickets → summary, best-effort with per-item outcome → emit a request-level audit record of exactly what landed → return a partial-aware receipt. `dry_run` short-circuits after validation, writes nothing (not even audit). **`ingestion/internal/brainstore/`** — the BrainStore extraction: - One concrete `Store` implementing `capture.BrainStore`, composing the `api` file primitives (#45 read-after-write) with the wiki upkeep a write needs (wing `_index` rebuild, cross-wing auto-tunnel, graph re-index). - **MCP `brain_write`/`brain_update`/`brain_get` re-pointed at it** — one implementation, not two (the DRY payoff #51 is named for). The `{id, path, content_hash}` response contract and all existing MCP tests are preserved unmodified. ## Scope boundaries (deliberate) - **I1 origin sovereignty gate → #53** — needs the server-derived principal origin; not here. - **Classification-aware audit degradation/refusal → #54** — the `AuditSink` port exists; the confidential-fails-closed / internal-degrades policy layers on around this core. - **`Effective` folded into the service** as `classification.Stricter` (port stays minimal `Derive`) — "stricter wins" is use-case policy, and it keeps `classification.Config` a drop-in `ClassificationPolicy`. - **Unknown-repo pre-validation** is structural-only here (repo non-empty + valid action + required fields); true repo-existence validation lands with the real tracker (#52). A bad repo surfaces as a per-item ticket error (partial receipt), not a silent write. ## Acceptance criteria - [x] `capture/` package: ports, entities, `CaptureService` - [x] `BrainStore` concrete impl (Write/Update/Get); **MCP handlers re-pointed — no duplicate logic** - [x] #45 read-after-write + (out-of-band) staleness discipline preserved inside the store - [x] Validate-before-write: malformed input → error, nothing written - [x] Best-effort orchestration + partial receipt with per-item `ok`/`error`; no rollback - [x] `dry_run` returns would-be receipt, writes nothing - [x] Effective-classification via injected `ClassificationPolicy` (stricter wins) - [x] Unit tests vs fake ports: write+supersede, partial-failure, dry-run, validation-reject, stricter-wins (+ caller-raising-honoured, summary path/fidelity) - [x] Existing MCP `brain_update`/`brain_get`/`brain_write` tests pass after re-pointing - [x] `task check` green (lint 0 issues, vet clean, `-race` green) ## BDD scenarios satisfied (against fakes) supersede-not-duplicate · fail-closed validation · partial-failure receipt · dry-run · stricter-classification-wins · caller-raising-honoured · summary fidelity stamping. Not auto-merged — review + merge from claude.ai. Closes #51 🤖 Generated with [Claude Code](https://claude.com/claude-code)
mathias changed target branch from feat/capture-classification to main 2026-06-22 21:31:38 +00:00
mathias added 2 commits 2026-06-22 21:31:38 +00:00
The Clean-Architecture core of the capture capability (#49b). Pure
orchestration over ports — no HTTP, no live Gitea, no audit I/O — fully
unit-tested against fakes before any adapter exists.

- Ports: BrainStore (#45 write/update/get), IssueTracker, SummaryWriter,
  ClassificationPolicy (satisfied by #50's classification.Config),
  AuditSink. Entities: Insight, Ticket, Summary, CaptureContext,
  CaptureInput, CaptureReceipt.
- CaptureService.Capture: validate-before-write (fail-closed), resolve
  effective classification (stricter of declared vs target-derived;
  under-declaration logged as a security event), orchestrate insights
  (write/supersede) → tickets → summary best-effort, emit a request-level
  audit record of exactly what landed, return a partial-aware receipt.
- dry_run short-circuits after validation, writes nothing (not even audit).

Out of scope here, layered on later: the I1 origin sovereignty gate (#53,
needs the server-derived principal) and the classification-aware audit
degradation/refusal (#54). "Effective" is folded into the service as
classification.Stricter rather than a port method — the stricter-wins
rule is use-case policy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extracts the #45 write/update/get logic + the wiki upkeep that must
accompany a write (wing _index rebuild, cross-wing auto-tunnel, graph
re-index) into a single concrete brainstore.Store implementing
capture.BrainStore. The MCP brain_write/brain_update/brain_get handlers
are re-pointed at it, so there is one implementation, not two — the DRY
payoff #51 is named for. capture and MCP now share the exact same brain
write path and read-after-write contract.

The Server gains a *brainstore.Store, constructed in NewServer and given
the graph store in WithGraph. Embedding refresh stays out-of-band
(mtime-driven vectorstore.Sync), unchanged. Existing MCP brain_update/
brain_get/brain_write tests pass unmodified — behaviour and the
{id, path, content_hash} response contract are preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mathias merged commit 07fca9ee73 into main 2026-06-22 21:31:47 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/hyperguild#57