Documentation
¶
Overview ¶
Package detectledger turns the agent-side detection engine's output (#422) into hash-chained, attributable evidence (#423). It does NOT own a chain: a detection is sealed into the SAME evidence spine as findings and judgments, with kind = "detection", so it is defensible in an audit and joins the correlation graph.
Boundary (enforced here and asserted by test): DETECTIONS are chained; raw telemetry is NOT. This package has no method that seals a detection.Event — only a detection.Detection becomes a chain link. Per-event chaining would collapse throughput and is deliberately impossible through this API.
Index ¶
- type AgentKeyResolver
- type EvidenceChain
- type IngestItem
- type IngestResult
- type Reader
- type Service
- func (s *Service) Expire(ctx context.Context, engagementID shared.ID, actor, reason string) (int, error)
- func (s *Service) Incidents(ctx context.Context, engagementID shared.ID) ([]detection.Incident, error)
- func (s *Service) Ingest(ctx context.Context, batch fleetagent.AgentBatch, items []IngestItem) (IngestResult, error)
- func (s *Service) ListDetections(ctx context.Context, engagementID shared.ID) ([]detection.Record, error)
- func (s *Service) VerifyChain(ctx context.Context, engagementID shared.ID) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AgentKeyResolver ¶
type AgentKeyResolver interface {
AgentPublicKey(ctx context.Context, agentID shared.ID) (ed25519.PublicKey, error)
}
AgentKeyResolver returns the enrolment public key for an agent (#408), used to verify a batch's signature. An agent with no known key cannot have its batches admitted — fail closed.
type EvidenceChain ¶
type EvidenceChain interface {
// Seal appends content under the given kind, bound to the engagement's chain head, and returns the
// new link's id.
Seal(ctx context.Context, engagementID shared.ID, kind string, content []byte, createdBy string) (shared.ID, error)
// Verify checks the engagement's chain and returns a non-nil error (wrapping evidence.ErrChainBroken)
// when it is broken, so a dependent report can be blocked.
//
// IMPORTANT for the composition-root bridge: evidence.Service.Verify returns (Report, error) and
// signals a BROKEN chain via Report.Intact=false with a NIL error (the non-nil error is reserved for
// I/O failures). A bridge must therefore inspect Report.Intact and synthesize an
// evidence.ErrChainBroken-wrapping error itself — returning the raw error would report a tampered
// chain as healthy. This contract exists precisely so that mistake cannot be made silently.
Verify(ctx context.Context, engagementID shared.ID) error
}
EvidenceChain is the narrow slice of the evidence vault this package needs: seal a detection into the chain, and verify the chain. It is a consumer-side interface bridged to *evidence.Service at the composition root (like offensivepolicy's EvidenceSealer), so this package never depends on the concrete vault or the domain Evidence shape.
type IngestItem ¶
IngestItem is one detection in a batch together with the asset it was observed on (#423 requirement 5: a detection joins the asset model).
type IngestResult ¶
type IngestResult struct {
EngagementID shared.ID
SealedRecords []shared.ID
EvidenceIDs []shared.ID
Skipped []shared.ID // already-sealed detections skipped on an idempotent retry
Gap fleetagent.SequenceGap
}
IngestResult reports the outcome of ingesting a batch.
type Reader ¶
type Reader struct {
// contains filtered or unexported fields
}
Reader is the read-only projection of the ledger, for the HTTP read routes. It needs only the record store — no chain, key resolver, or audit — so the read surface can be wired live before the agent batch-ingest transport is. The full Service handles the write (ingest/seal/expire) path.
func NewReader ¶
func NewReader(records ports.DetectionRecordStore) (*Reader, error)
NewReader builds the read-only ledger view.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service ingests agent detection batches into the evidence ledger.
func NewService ¶
func NewService(records ports.DetectionRecordStore, chain EvidenceChain, keys AgentKeyResolver, audit ports.AuditLogger, clock ports.Clock, ids ports.IDGenerator, retention time.Duration) (*Service, error)
NewService validates its dependencies. Every one is required: a ledger that cannot seal, resolve an agent key, persist, or audit is not producing attributable evidence.
func (*Service) Expire ¶
func (s *Service) Expire(ctx context.Context, engagementID shared.ID, actor, reason string) (int, error)
Expire removes projection rows whose retention has elapsed, as an AUDITED action carrying the actor and reason. It never removes chain links (those are permanent) and never runs silently: deleting evidence without a trail is exactly what this project exists to prevent.
func (*Service) Incidents ¶
func (s *Service) Incidents(ctx context.Context, engagementID shared.ID) ([]detection.Incident, error)
Incidents returns the incident-level rollup for an engagement. The rollup is a view: the individual attributable detections remain the ledger underneath.
func (*Service) Ingest ¶
func (s *Service) Ingest(ctx context.Context, batch fleetagent.AgentBatch, items []IngestItem) (IngestResult, error)
Ingest admits one signed, sequenced agent batch: it verifies the signature, detects a sequence gap (reported as a potential loss, never silently accepted), seals each detection into the evidence chain as kind="detection", and persists the projection rows bound to their chain links and asset.
func (*Service) ListDetections ¶
func (s *Service) ListDetections(ctx context.Context, engagementID shared.ID) ([]detection.Record, error)
ListDetections returns the engagement's (non-expired) detection records, tenant-scoped by the store.
func (*Service) VerifyChain ¶
VerifyChain checks the engagement's evidence chain; a broken chain returns an error wrapping evidence.ErrChainBroken so the report that depends on it is blocked, exactly as any chain break is.