Documentation
¶
Overview ¶
Package compliance generates audit-based compliance reports for Chronicle.
Engine ¶
Engine is the central type. Construct it with NewEngine, which accepts three separate store interfaces drawn from the same composite backend, plus a *slog.Logger:
engine := compliance.NewEngine(
s, // audit.Store
s, // verify.Store
s, // ReportStore
logger,
)
When using a standard Chronicle backend (Postgres, Bun, SQLite, Memory) the same [store.Store] value satisfies all three interfaces.
Reports verify the hash chain behind their scope when the engine is given the deployment's chain with WithChain, and optionally its checkpoint store and signer with WithCheckpoints. Each report records the result in Report.Verification and what it covered in Report.VerificationScope. See WithVerifyWindow for how much of a long chain one report checks.
Report Types ¶
Four report types are supported, each with its own input struct:
- Engine.SOC2 — SOC2 Type II: access events, authentication, authorisation denials, critical errors; maps to SOC2 Trust Service Criteria CC6/CC7
- Engine.HIPAA — HIPAA: PHI access, user activity, audit controls
- Engine.EUAIAct — EU AI Act: AI system interactions, decisions, outcomes
- Engine.Custom — arbitrary filters, sections, and metadata
All methods return a *Report and persist it to ReportStore automatically.
Report Structure ¶
Report embeds chronicle.Entity (CreatedAt, UpdatedAt) and contains:
- ID — TypeID with "report_" prefix
- Title — human-readable report name
- Type — "soc2", "hipaa", "euaiact", or "custom"
- Period — DateRange (From/To time.Time)
- AppID — application scope
- TenantID — tenant scope (empty for single-tenant)
- Sections — []Section, each with a Title, Events, Stats, and Notes
- Stats — *Stats aggregate (TotalEvents, CriticalEvents, FailedEvents, DeniedEvents)
- Verification — the *verify.Report from generation time, nil when none ran
- VerificationScope — what that verification covered, and why it did not run when it did not
- GeneratedBy — identity of the requestor
- Format — the Format the report was exported in
- Data — raw exported bytes (populated by Engine.Export)
Export ¶
Engine.Export serialises a *Report to an io.Writer in the requested Format:
- FormatJSON — machine-readable JSON
- FormatCSV — spreadsheet-compatible CSV
- FormatMarkdown — human-readable Markdown
- FormatHTML — styled HTML for browser or email
- FormatPDF — defined as a constant but not yet implemented
ReportStore ¶
ReportStore persists generated reports:
- ReportStore.SaveReport — persist a new report
- ReportStore.GetReport — retrieve by id.ID
- ReportStore.ListReports — paginated listing
- ReportStore.DeleteReport — remove a report
Package compliance defines compliance report entities and the report store interface.
Index ¶
- Constants
- func DecodeReportBody(data []byte, r *Report) error
- func EncodeReportBody(r *Report) ([]byte, error)
- type CustomInput
- type CustomSection
- type DateRange
- type EUAIActInput
- type Engine
- func (e *Engine) Custom(ctx context.Context, input *CustomInput) (*Report, error)
- func (e *Engine) EUAIAct(ctx context.Context, input *EUAIActInput) (*Report, error)
- func (e *Engine) Export(_ context.Context, r *Report, format Format, w io.Writer) error
- func (e *Engine) HIPAA(ctx context.Context, input *HIPAAInput) (*Report, error)
- func (e *Engine) SOC2(ctx context.Context, input *SOC2Input) (*Report, error)
- type EngineOption
- type Format
- type HIPAAInput
- type ListOpts
- type Report
- type ReportStore
- type SOC2Input
- type Section
- type Stats
- type StreamSource
- type VerificationScope
- type VerificationStatus
Constants ¶
const DefaultVerifyWindow uint64 = 50_000
DefaultVerifyWindow is how many sequences a report verifies when the engine was not given a window of its own.
VerifyChain holds every event in its range in memory at once, so the window is a memory bound, not a sampling rate. Fifty thousand events is tens of megabytes on a typical event, which a report generator can afford on every run.
const MaxSectionEvents = 1000
MaxSectionEvents bounds the evidence listing embedded in one report section.
A report covering a busy period can match far more events than belong in a single document, so the listing is a sample. Section.MatchedEvents and Section.EventsTruncated say so explicitly; the report's summary Stats are computed separately and are exact.
Variables ¶
This section is empty.
Functions ¶
func DecodeReportBody ¶
DecodeReportBody fills r's Sections, Stats, Verification and VerificationScope from data written by EncodeReportBody.
It also reads the older form, a bare JSON array of sections. A report stored that way never kept its stats or verification, so those stay nil and the report reads as unverified, which is what it is.
func EncodeReportBody ¶
EncodeReportBody serialises the parts of r a backend keeps in its data column. DecodeReportBody reverses it.
Types ¶
type CustomInput ¶
type CustomInput struct {
Title string `json:"title"`
Period DateRange `json:"period"`
AppID string `json:"app_id" optional:"true"`
TenantID string `json:"tenant_id,omitempty" optional:"true"`
GeneratedBy string `json:"generated_by" optional:"true"`
Sections []CustomSection `json:"sections"`
}
CustomInput defines inputs for a custom compliance report.
type CustomSection ¶
type CustomSection struct {
Title string `json:"title"`
Categories []string `json:"categories,omitempty"`
Actions []string `json:"actions,omitempty"`
Severity []string `json:"severity,omitempty"`
Notes string `json:"notes,omitempty"`
}
CustomSection defines a section in a custom compliance report.
type EUAIActInput ¶
type EUAIActInput struct {
Period DateRange `json:"period"`
AppID string `json:"app_id" optional:"true"`
TenantID string `json:"tenant_id,omitempty" optional:"true"`
GeneratedBy string `json:"generated_by" optional:"true"`
}
EUAIActInput defines inputs for EU AI Act transparency report generation.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine generates compliance reports from audit data.
func NewEngine ¶
func NewEngine( auditStore audit.Store, verifyStore verify.Store, reportStore ReportStore, logger log.Logger, opts ...EngineOption, ) *Engine
NewEngine creates a compliance engine.
Reports verify their scope's hash chain only when opts include WithChain. Pass the deployment's own chain there, and WithCheckpoints when it signs checkpoints.
type EngineOption ¶
type EngineOption func(*Engine)
EngineOption configures an Engine.
func WithChain ¶
func WithChain(streams StreamSource, chain *hash.Chain) EngineOption
WithChain lets the engine verify the hash chain behind every report it generates.
chain must be the deployment's own chain, carrying its key provider. A verifier built without it recomputes digests unkeyed, so on an HMAC deployment every event would read as tampered or downgraded. Without this option reports carry no verification and say so.
func WithCheckpoints ¶
func WithCheckpoints(store checkpoint.Store, signer checkpoint.Signer) EngineOption
WithCheckpoints lets report verification check signed checkpoints. Both halves are needed: a checkpoint store without a signer holds rows nothing can authenticate. If either is nil the option does nothing, which matches how the admin API's verifier is built.
func WithVerifyWindow ¶
func WithVerifyWindow(n uint64) EngineOption
WithVerifyWindow sets how many sequences a report verifies at most. Zero keeps DefaultVerifyWindow.
type HIPAAInput ¶
type HIPAAInput struct {
Period DateRange `json:"period"`
AppID string `json:"app_id" optional:"true"`
TenantID string `json:"tenant_id,omitempty" optional:"true"`
GeneratedBy string `json:"generated_by" optional:"true"`
}
HIPAAInput defines inputs for HIPAA audit report generation.
type ListOpts ¶
ListOpts defines pagination and scope options for listing reports.
Scope is applied by the store, before LIMIT/OFFSET. Filtering after pagination returned an empty page whenever another tenant's reports filled the first page.
type Report ¶
type Report struct {
chronicle.Entity
ID id.ID `json:"id"`
Title string `json:"title"`
Type string `json:"type"`
Period DateRange `json:"period"`
AppID string `json:"app_id"`
TenantID string `json:"tenant_id"`
Sections []Section `json:"sections"`
Stats *Stats `json:"stats,omitempty"`
// Verification is the chain verification run when the report was
// generated. It is nil when none ran; VerificationScope says why, and
// nil never means verification passed.
Verification *verify.Report `json:"verification,omitempty"`
// VerificationScope is what Verification covered: the stream, the exact
// sequences, whether the window cut the chain short, and the caveats
// needed to read the result.
VerificationScope *VerificationScope `json:"verification_scope,omitempty"`
GeneratedBy string `json:"generated_by"`
Format Format `json:"format"`
Data []byte `json:"data,omitempty"`
}
Report is a generated compliance report.
type ReportStore ¶
type ReportStore interface {
// SaveReport persists a generated compliance report.
SaveReport(ctx context.Context, r *Report) error
// GetReport returns a report by ID.
GetReport(ctx context.Context, reportID id.ID) (*Report, error)
// ListReports returns reports matching opts, scoped before pagination.
ListReports(ctx context.Context, opts ListOpts) ([]*Report, error)
// DeleteReport removes a report.
DeleteReport(ctx context.Context, reportID id.ID) error
}
ReportStore manages generated compliance reports.
type SOC2Input ¶
type SOC2Input struct {
Period DateRange `json:"period"`
AppID string `json:"app_id" optional:"true"`
TenantID string `json:"tenant_id,omitempty" optional:"true"`
GeneratedBy string `json:"generated_by" optional:"true"`
}
SOC2Input defines inputs for SOC2 Type II report generation.
type Section ¶
type Section struct {
Title string `json:"title"`
// Events is the evidence listing. It is capped at MaxSectionEvents, so it
// may be a sample rather than the full set — check EventsTruncated.
Events []*audit.Event `json:"events,omitempty"`
// MatchedEvents is how many events matched this section's filters,
// regardless of how many are embedded above.
MatchedEvents int64 `json:"matched_events"`
// EventsTruncated reports that Events is a sample of MatchedEvents.
// A report that silently truncated its evidence would read as complete
// when it was not.
EventsTruncated bool `json:"events_truncated"`
Stats *audit.AggregateResult `json:"stats,omitempty"`
Notes string `json:"notes,omitempty"`
}
Section is a part of a compliance report.
type Stats ¶
type Stats struct {
TotalEvents int64 `json:"total_events"`
CriticalEvents int64 `json:"critical_events"`
FailedEvents int64 `json:"failed_events"`
DeniedEvents int64 `json:"denied_events"`
}
Stats holds summary statistics for a report.
type StreamSource ¶
type StreamSource interface {
GetStreamByScope(ctx context.Context, appID, tenantID string) (*stream.Stream, error)
}
StreamSource resolves the hash chain stream for a report's scope. Every standard backend implements it.
type VerificationScope ¶
type VerificationScope struct {
Status VerificationStatus `json:"status"`
// StreamID is the stream verified, the one GetStreamByScope returns for
// the report's AppID and TenantID.
StreamID string `json:"stream_id,omitempty"`
// Scheme and SchemeSince are the stream's digest pin, taken from the
// stream row.
Scheme string `json:"scheme,omitempty"`
SchemeSince uint64 `json:"scheme_since,omitempty"`
// HeadSeq is the stream's head when the report was generated.
HeadSeq uint64 `json:"head_seq"`
// FromSeq and ToSeq are the sequences verification was asked to cover.
// Verification.FirstEvent and LastEvent are the events it actually found
// there.
FromSeq uint64 `json:"from_seq"`
ToSeq uint64 `json:"to_seq"`
// Window is the most sequences one report verifies, and Capped is whether
// it cut this chain short. A capped report verified the newest Window
// sequences up to the head and nothing before them.
Window uint64 `json:"window"`
Capped bool `json:"capped"`
// CheckpointsConfigured is whether the engine had a checkpoint store and
// signer to consult.
CheckpointsConfigured bool `json:"checkpoints_configured"`
// Notes are the caveats a reader needs to interpret the result. Every
// export renders them.
Notes []string `json:"notes,omitempty"`
}
VerificationScope records what a report's chain verification covered.
Report.Verification is the verifier's own result and says nothing about why a range was chosen. This does, so a report never implies more coverage than it checked: the exact sequences asked for, whether the window cut the chain short, and the caveats that apply to reading the result.
type VerificationStatus ¶
type VerificationStatus string
VerificationStatus says whether a report's chain verification ran, and if not, why not.
const ( // VerificationRan means the chain was verified and Report.Verification // holds the result, which may be valid or not. VerificationRan VerificationStatus = "verified" // VerificationNoChain means the report's scope has no hash chain stream, // so there was nothing to verify. It is not a failure: a scope that never // recorded an event has no chain. VerificationNoChain VerificationStatus = "no_chain" // VerificationNotConfigured means the engine was built without a hash // chain (see WithChain), so it could not verify anything. It never falls // back to an unkeyed chain, which would report every keyed event as // tampered. VerificationNotConfigured VerificationStatus = "not_configured" )