package capture import ( "context" "time" "github.com/mathiasbq/hyperguild/ingestion/internal/classification" ) // Ref is the read-after-write handle returned by a brain write/update — // the #45 contract. ContentHash lets the caller confirm what landed // without a re-query; for an Update, Superseded is true. type Ref struct { ID string Path string ContentHash string Superseded bool } // StoredNote is a brain note fetched by Get: the read-after-write // confirmation primitive (a direct fetch, never a semantic query). type StoredNote struct { ID string Path string ContentHash string Frontmatter map[string]string Body string } // Note is the brain-write payload. It carries both the wing/hall taxonomy // and the legacy type/domain fields so a single BrainStore serves both // capture insights and the existing MCP brain_write surface. Reason is // the supersede rationale, used only by Update. type Note struct { Content string Filename string Wing string Hall string Type string Domain string Reason string } // BrainStore is the brain persistence port — the shared implementation of // the #45 write/update/get verbs that both the MCP handlers and capture // call, so there is one implementation, not two. The read-after-write + // staleness discipline lives behind this interface so no caller carries // the rule. type BrainStore interface { Write(ctx context.Context, n Note) (Ref, error) Update(ctx context.Context, slug string, n Note) (Ref, error) Get(ctx context.Context, id string) (StoredNote, error) } // IssueRef identifies a ticket touched by the tracker. type IssueRef struct { Repo string Number int URL string } // IssueTracker is the Gitea ticket port. The implementation (#52) always // scopes to owner "mathias"; the port deliberately omits owner. type IssueTracker interface { CreateIssue(ctx context.Context, repo, title, body string) (IssueRef, error) // CloseIssue closes an issue, optionally posting a closing comment // first (empty comment ⇒ close only). CloseIssue(ctx context.Context, repo string, number int, comment string) (IssueRef, error) CommentIssue(ctx context.Context, repo string, number int, body string) (IssueRef, error) } // ClassificationPolicy derives a target's sensitivity (model C). The // "stricter wins" combination of declared vs derived is use-case policy // and lives in the service, so the port stays minimal. Satisfied by // classification.Config (#50). type ClassificationPolicy interface { Derive(target classification.Target) classification.Level } // AuditEntry is the request-level audit record (I5): who/what captured // what, when, via which principal. SecurityEvents carries anomalies such // as a caller under-declaring sensitivity relative to the target floor. type AuditEntry struct { Timestamp time.Time Principal string Actor string Harness string SessionRef string EffectiveClassification string Items []string SecurityEvents []string } // AuditOutcome is how a capture's audit record was (or will be) persisted. type AuditOutcome int const ( // AuditCentral means the record goes to the central sink (loki). AuditCentral AuditOutcome = iota // AuditBuffered means the central sink was unreachable and the record // is written to a durable local buffer for later reconciliation // (internal/public tier only). AuditBuffered ) // AuditSink is the two-phase, classification-aware audit port (I5, §4.4). // // Reserve runs BEFORE any write and decides whether the capture can be // audited at its effective classification: it returns the outcome to use, // or an error to refuse the capture before anything is written // (confidential + central sink down → refuse; the all-tiers floor when // nothing can record → refuse). Record runs AFTER the writes and persists // the final entry per the reserved outcome. // // Splitting reserve from record is what lets "confidential + sink-down → // refuse before any write" be literally true while the record itself // (which lists what landed) is necessarily written afterwards. type AuditSink interface { Reserve(ctx context.Context, level classification.Level) (AuditOutcome, error) Record(ctx context.Context, e AuditEntry, outcome AuditOutcome) error }