feat(capture): CaptureService use-case + ports + entities (#51)
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>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
// Package capture is the Clean-Architecture use-case for the uniform
|
||||
// capture capability (issue #49/#51): persist a finished session's
|
||||
// valuable output — insights → brain, action items → Gitea tickets,
|
||||
// optional summary → ai-sessions — with one invocation, identical core
|
||||
// behaviour across every harness.
|
||||
//
|
||||
// This package is pure orchestration. It depends only on ports
|
||||
// (interfaces) and plain entities — no HTTP, no live Gitea, no embedding
|
||||
// or audit I/O. The real adapters are wired in #52 (Gitea tracker), #53
|
||||
// (REST + I1 origin gate), and #54/#55 (audit path + relay). The I1
|
||||
// sovereignty refusal and the classification-aware audit degradation are
|
||||
// deliberately NOT here — those need the server-derived principal origin
|
||||
// (#53) and the loki/buffer machinery (#54). What lives here is everything
|
||||
// testable against fakes: validation, effective-classification resolution
|
||||
// (stricter wins), best-effort orchestration, and the partial receipt.
|
||||
package capture
|
||||
|
||||
// CaptureContext is the per-session metadata accompanying a capture.
|
||||
//
|
||||
// Classification is the caller-declared sensitivity (model C, spec §4.1):
|
||||
// the server independently derives the target's classification and gates
|
||||
// on the stricter of the two. Principal is server-derived from the
|
||||
// authenticated identity (#53 populates it); it is never caller-asserted.
|
||||
// Harness is descriptive telemetry only — never a gate input.
|
||||
type CaptureContext struct {
|
||||
Harness string
|
||||
SessionRef string
|
||||
Fidelity string
|
||||
Actor string
|
||||
Classification string // caller-declared level token ("" = unspecified)
|
||||
Principal string // server-derived (auth); audit identity
|
||||
}
|
||||
|
||||
// Insight is one piece of session knowledge bound for the brain. A
|
||||
// non-empty SupersedeSlug routes to Update (revise in place); otherwise
|
||||
// Write (create).
|
||||
type Insight struct {
|
||||
Text string
|
||||
Wing string
|
||||
Hall string
|
||||
SupersedeSlug string
|
||||
}
|
||||
|
||||
// Ticket is one action item bound for a Gitea repo. Owner is always the
|
||||
// operator (set by the tracker adapter), never carried here.
|
||||
type Ticket struct {
|
||||
Repo string
|
||||
Action string // create | close | comment
|
||||
Number int // required for close/comment
|
||||
Title string // required for create
|
||||
Body string
|
||||
}
|
||||
|
||||
// Summary is an optional session summary bound for ai-sessions.
|
||||
type Summary struct {
|
||||
Title string
|
||||
Body string
|
||||
ReposTouched []string
|
||||
}
|
||||
|
||||
// CaptureInput is the whole capture request.
|
||||
type CaptureInput struct {
|
||||
Context CaptureContext
|
||||
Insights []Insight
|
||||
Tickets []Ticket
|
||||
Summary *Summary
|
||||
DryRun bool
|
||||
}
|
||||
|
||||
// InsightResult is the per-insight outcome in the receipt.
|
||||
type InsightResult struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
ContentHash string `json:"content_hash,omitempty"`
|
||||
Superseded bool `json:"superseded"`
|
||||
OK bool `json:"ok"`
|
||||
}
|
||||
|
||||
// TicketResult is the per-ticket outcome in the receipt.
|
||||
type TicketResult struct {
|
||||
Repo string `json:"repo"`
|
||||
Number int `json:"number,omitempty"`
|
||||
Action string `json:"action"`
|
||||
URL string `json:"url,omitempty"`
|
||||
OK bool `json:"ok"`
|
||||
}
|
||||
|
||||
// SummaryResult is the summary outcome in the receipt.
|
||||
type SummaryResult struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
OK bool `json:"ok"`
|
||||
}
|
||||
|
||||
// ItemError pins a failure to a specific request item for the partial
|
||||
// receipt. Item is a stable locator like "insight[1]" or "ticket[0]".
|
||||
type ItemError struct {
|
||||
Item string `json:"item"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// CaptureReceipt is the structured, partial-aware result. Per-item ok
|
||||
// flags plus a flat Errors list make partial success explicit; the
|
||||
// caller never has to infer what landed.
|
||||
type CaptureReceipt struct {
|
||||
Insights []InsightResult `json:"insights"`
|
||||
Tickets []TicketResult `json:"tickets"`
|
||||
Summary *SummaryResult `json:"summary,omitempty"`
|
||||
Errors []ItemError `json:"errors"`
|
||||
EffectiveClassification string `json:"effective_classification,omitempty"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
}
|
||||
Reference in New Issue
Block a user