purplecoverage

package
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package purplecoverage is the control plane that closes the purple loop (#426): it joins the offensive half of the ledger (an emulation.Run's per-technique coverage records — what each technique executed and EXPECTED to be detected, #421) with the defensive half (the detections that ACTUALLY fired on the same asset in the run window, #422/#423) and resolves a per-technique coverage verdict through the pure domain. Coverage is measured from the two independent halves, never claimed.

The join and the verdict order (out_of_reach → unknown → covered → gap) live in the domain; this package supplies the two halves, persists the result tenant-scoped, and audits the computation. It is the same honest deferral as the rest of the blue-team pillar: the compute + store are real; the scheduler that triggers a run and streams its window is wired at the composition root later.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Result

type Result struct {
	Coverage []purplecoverage.Coverage
	Bonus    []string
	Gaps     []purplecoverage.WorkItem
}

Result is one computation's honest outcome: the stored per-technique coverage, the bonus detections (fired but expected by no technique — reported, never hidden or counted as coverage), and the work items (one per gap).

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service computes and serves purple coverage.

func NewService

func NewService(store ports.PurpleCoverageStore, detections ports.DetectionRecordStore, audit ports.AuditLogger, clock ports.Clock) (*Service, error)

NewService validates its dependencies. All are required: without the detection ledger there is no defensive half to join against, and without the audit log a coverage computation would not be attributable.

func (*Service) Compute

func (s *Service) Compute(ctx context.Context, run emulation.Run, window Window) (Result, error)

Compute joins an emulation run with the detections that fired on its asset in the window, resolves a verdict per technique, persists the coverage tenant-scoped, and audits the computation. The run's tenant/engagement must match the authenticated tenant on the context (fail closed) so a run cannot be scored into another tenant's ledger.

Every technique in the run is emulatable by construction (it was in the catalogue and produced a record); out_of_reach is a domain verdict reserved for techniques the platform cannot emulate, which a run does not carry — so a run resolves only to covered/gap/unknown, and a non-executed technique is unknown, never a gap.

func (*Service) Regressions

func (s *Service) Regressions(ctx context.Context, prevRun, currRun shared.ID) ([]purplecoverage.Regression, error)

Regressions compares two runs' coverage and returns the techniques that went from covered to uncovered — a detection regression. Both runs are loaded tenant-scoped.

func (*Service) Trend

func (s *Service) Trend(ctx context.Context, engagementID shared.ID) ([]purplecoverage.Coverage, error)

Trend returns an engagement's coverage across runs, oldest first, so a defender can see coverage improve (or regress) over time. Tenant-scoped through the store.

func (*Service) WorkItems

func (s *Service) WorkItems(ctx context.Context, engagementID, runID shared.ID) ([]purplecoverage.WorkItem, error)

WorkItems returns one actionable item per gap in a run (the missing detection a human must write). It reads the stored coverage so it reflects what was measured, not a recomputation.

The run is bound to engagementID: ListByRun is only tenant-scoped, so a run id belonging to a DIFFERENT engagement in the same tenant would otherwise be readable through an engagement the caller is authorized for. Any record whose EngagementID does not match is dropped, so a mismatched run resolves to no work items rather than leaking another engagement's gaps.

type Window

type Window struct {
	From time.Time
	To   time.Time
}

Window is the observation window a run's detections are joined over. It is explicit because an emulation.Run carries no timestamps: the caller (the scheduler that ran the emulation) owns when the run started and ended, and only detections observed inside [From,To] on the run's asset can count as coverage for that run. A detection outside the window is a different event, not this run's coverage.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL