report

package
v0.8.4 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package report implements the Reports library: point-in-time, immutable, Ed25519-signed compliance artifacts. It generates four kinds (executive summary, framework attestation, exception register, remediation activity), each computed once from data that already exists and stored as a frozen JSON document; the row is never recomputed. Each kind renders to downloadable faces (PDF / CSV / OSCAL SAR / JSON).

DEFERRED: the Templates gallery and retention sweeps (see the spec excludes). Recurring generation + email delivery lives in internal/reportschedule.

Spec: api-reports.

Index

Constants

View Source
const (
	FaceJSON = "json"
	FacePDF  = "pdf"
	FaceCSV  = "csv"
)

Face identifiers.

View Source
const FaceOSCALSAR = "oscal_sar"

FaceOSCALSAR is the fleet OSCAL assessment-results face (attestation-only).

View Source
const RenderJobType = "report.render"

RenderJobType is the job_type for an async report-face render.

Variables

View Source
var ErrGroupScopeUnavailable = errors.New("report: group scope unavailable")

ErrGroupScopeUnavailable is returned by Generate when a group scope is requested but no group resolver was wired (WithGroups). In production the resolver is always wired, so this is a programmer/config error.

View Source
var ErrInvalidFace = errors.New("report: invalid face")

ErrInvalidFace is returned by Export for an unknown face (or a face that does not apply to the report's kind). Handlers map it to 400.

View Source
var ErrInvalidKind = errors.New("report: invalid kind")

ErrInvalidKind is returned by Generate for an unknown report kind. Handlers map it to 400.

View Source
var ErrNotFound = errors.New("report: not found")

ErrNotFound is returned by Get when no report has the given id. Handlers map it to 404.

Functions

func ExportFilename

func ExportFilename(rep Report, face string) string

ExportFilename builds a download filename for a report face, e.g. "openwatch-executive-all-hosts-2026-06-21.pdf" or "openwatch-attestation-production-cis-2026-06-21.csv".

func VerifySignature

func VerifySignature(pub ed25519.PublicKey, contentSHA256 string, sig []byte) bool

VerifySignature checks an Ed25519 signature over a content address with the given public key. Offline-verifiable: callers reconstruct the payload from the published content_sha256.

Types

type AttestationContent

type AttestationContent struct {
	// Framework is the lens (a framework_refs key), or "" for all.
	Framework string `json:"framework"`
	// HostsTotal is the active in-scope host count; HostsAttested is how
	// many of those have a completed scan to attest from.
	HostsTotal    int `json:"hosts_total"`
	HostsAttested int `json:"hosts_attested"`
	// Attested lists, per attested host, the scan the attestation is over.
	Attested []AttestedHost `json:"attested"`
	// Rollup is the headline compliance aggregate, FROZEN into the signed
	// snapshot at generation time (computed once from the frozen scans'
	// immutable scan_results, framework-lensed). Because it is part of the
	// content it is signed + tamper-evident, and the in-app view, the PDF
	// cover, and the signature all read the same numbers (P1: one snapshot,
	// identical across every face). It is bounded (aggregates + a small
	// top-N), never the per-(host, rule) rows.
	Rollup AttestationRollup `json:"rollup"`
}

AttestationContent is the frozen snapshot for a Framework Attestation: it captures WHICH scan attests each in-scope host (point-in-time, since scan_results are immutable), not the bulk rows themselves - the CSV / OSCAL faces reconstruct those from the referenced scans on demand.

type AttestationRollup

type AttestationRollup struct {
	// LegacyCompliancePct is DECODE ONLY. It is the pooled whole-percent
	// number artifacts carried before 2026-09-03: passing over every rule
	// outcome in the attested set, so a host with 700 rules outvoted one with
	// 50, and a whole percent could never equal the one-decimal fleet score.
	//
	// No constructor sets it. It exists so a legacy artifact still decodes
	// and still renders the number it was signed with. omitempty is what
	// keeps it out of every artifact generated from now on.
	LegacyCompliancePct *int `json:"compliance_pct,omitempty"`

	// ScorePct is the equal-host mean of the attested hosts' scores, to one
	// decimal, null when no attested host produced a verdict.
	//
	// Null and 0.0 are different facts. Zero means every evaluated rule
	// failed; null means nothing was evaluated, and collapsing the second
	// into the first is bugs/OW-023 and OW-024.
	ScorePct *float64 `json:"score_pct"`
	// TotalChecks is every (host, rule) outcome counted; the rest are the
	// per-status splits.
	TotalChecks int `json:"total_checks"`
	Passing     int `json:"passing"`
	Failing     int `json:"failing"`
	Skipped     int `json:"skipped"`
	Errored     int `json:"errored"`
	// TopFailing lists the rules failing on the most hosts (capped).
	TopFailing []TopFailingRule `json:"top_failing"`

	// Provenance is the frozen envelope. Nil on a legacy artifact, which is
	// exactly how a legacy artifact is identified: a missing artifact_class
	// is the legacy shape and nothing backfills one.
	Provenance *ScoreProvenance `json:"provenance,omitempty"`
}

AttestationRollup is the bounded compliance aggregate stored on an attestation snapshot: pass/fail/total counts, fleet compliance percent, and a sampled top-failing list, over the frozen scans (framework-lensed).

type AttestedHost

type AttestedHost struct {
	HostID    uuid.UUID `json:"host_id"`
	ScanID    uuid.UUID `json:"scan_id"`
	ScannedAt time.Time `json:"scanned_at"`
}

AttestedHost ties an in-scope host to the completed scan that attests it (its latest as of generation time) and when that scan finished.

type CorpusEntry added in v0.8.0

type CorpusEntry struct {
	// Version is null for a curated corpus, which has a digest and no version.
	Version            *string `json:"corpus_version"`
	Digest             string  `json:"corpus_digest"`
	ContributorsScored int     `json:"contributors_scored"`
}

CorpusEntry is one corpus that measured part of a frozen score.

type Coverage

type Coverage struct {
	HostsTotal       int `json:"hosts_total"`
	HostsFresh       int `json:"hosts_fresh"`
	HostsStale       int `json:"hosts_stale"`
	HostsUnreachable int `json:"hosts_unreachable"`
}

Coverage is the staleness disclosure behind every report: of the in-scope active hosts, how many have fresh compliance data versus stale-or-never-scanned, and how many are currently unreachable. It is the basis of the coverage caveat - the honesty that lets a reader trust or discount the headline numbers. hosts_fresh + hosts_stale == hosts_total; hosts_unreachable is an independent reachability count (a host can be both stale and unreachable).

type EngineEntry added in v0.8.0

type EngineEntry struct {
	EngineVersion      string `json:"engine_version"`
	ContributorsScored int    `json:"contributors_scored"`
}

EngineEntry is one engine version behind a frozen score.

type ErrAmbiguousScore added in v0.8.0

type ErrAmbiguousScore struct{ Kind string }

ErrAmbiguousScore reports an artifact carrying both score fields.

A current artifact must expose ONE authoritative number. Carrying the pooled legacy percent beside the equal-host mean would publish two plausible scores over the same population and leave every reader to pick, which is the ambiguity the removal of passing_fraction was meant to end.

func (ErrAmbiguousScore) Error added in v0.8.0

func (e ErrAmbiguousScore) Error() string

type ErrInvalidProvenance added in v0.8.0

type ErrInvalidProvenance struct{ Reason string }

ErrInvalidProvenance reports provenance that is present but unusable.

A present-but-empty envelope is not a legacy artifact. Treating it as one let "provenance": {} and "provenance": null read as "signed before the formula changed", which is a claim about history that the bytes do not support. Absence means legacy; anything else present must be valid.

func (ErrInvalidProvenance) Error added in v0.8.0

func (e ErrInvalidProvenance) Error() string

type ExceptionContent

type ExceptionContent struct {
	Summary    ExceptionSummary `json:"summary"`
	Exceptions []ExceptionRow   `json:"exceptions"`

	// Provenance is the read-model envelope. It carries no score and no
	// contributor count, because this artifact aggregates no scan set.
	Provenance *ReadModelProvenance `json:"provenance,omitempty"`
}

ExceptionContent is the frozen snapshot for an Exception Register: a point-in-time summary of compliance waivers plus the register rows. The CSV face writes one row per exception; the PDF face renders the bounded summary (counts + expiring-soon).

type ExceptionRow

type ExceptionRow struct {
	HostName    string     `json:"host_name"`
	RuleID      string     `json:"rule_id"`
	Status      string     `json:"status"`
	Reason      string     `json:"reason"`
	RequestedBy string     `json:"requested_by"`
	RequestedAt time.Time  `json:"requested_at"`
	ReviewedBy  string     `json:"reviewed_by"`
	ReviewedAt  *time.Time `json:"reviewed_at"`
	ExpiresAt   *time.Time `json:"expires_at"`
	Active      bool       `json:"active"`
}

ExceptionRow is one waiver in the register. Requester/reviewer are resolved to usernames (or "" when unresolved) so the register reads without a second lookup. Active is true for an approved, unexpired waiver.

type ExceptionSummary

type ExceptionSummary struct {
	Total        int `json:"total"`
	Active       int `json:"active"`
	Requested    int `json:"requested"`
	Approved     int `json:"approved"`
	Rejected     int `json:"rejected"`
	Revoked      int `json:"revoked"`
	Expired      int `json:"expired"`
	ExpiringSoon int `json:"expiring_soon"`
}

ExceptionSummary is the bounded rollup of the in-scope waivers by state. Active counts approved waivers not past their expiry; ExpiringSoon is the subset of Active whose expiry falls within the next 30 days.

type ExecutiveContent

type ExecutiveContent struct {
	// LegacyCompliancePct is DECODE ONLY, the pooled whole-percent number
	// artifacts carried before 2026-09-03. See AttestationRollup for why it
	// is kept and why no constructor sets it.
	LegacyCompliancePct *int `json:"compliance_pct,omitempty"`

	// ScorePct is the equal-host mean of the in-scope hosts' scores, to one
	// decimal, null when no host produced a verdict.
	ScorePct *float64 `json:"score_pct"`
	// HostCount is the number of active (non-deleted) hosts.
	HostCount int `json:"host_count"`
	// PassingRules / FailingRules are host_rule_state rows by status.
	PassingRules int `json:"passing_rules"`
	FailingRules int `json:"failing_rules"`
	// CriticalIssues is the count of failing rows with critical severity.
	CriticalIssues int `json:"critical_issues"`
	// TopFailingRules lists the rules failing on the most hosts.
	TopFailingRules []TopFailingRule `json:"top_failing_rules"`
	// Coverage describes how much of the in-scope fleet the numbers
	// actually reflect (fresh vs stale/never-scanned, and unreachable).
	Coverage Coverage `json:"coverage"`

	// Provenance is the frozen envelope; nil on a legacy artifact.
	Provenance *ScoreProvenance `json:"provenance,omitempty"`
}

ExecutiveContent is the JSON posture document stored for an executive summary report. It is computed once at generation time from host_rule_state and frozen.

type FrameworkCount

type FrameworkCount struct {
	Framework string `json:"framework"`
	RuleCount int    `json:"rule_count"`
}

FrameworkCount is one entry in the fleet framework catalog: a framework_refs key present somewhere in the fleet and the number of distinct rules mapped to it. Backs the report scope picker's framework lens.

type GenerateRequest

type GenerateRequest struct {
	// Kind selects the report kind; "" defaults to executive. attestation
	// produces the Framework Attestation (CSV/OSCAL bulk faces).
	Kind Kind
	// GroupID scopes the report to one group's member hosts.
	GroupID *uuid.UUID
	// Framework scopes the report to one framework lens.
	Framework string
	// PeriodDays is the look-back window for time-windowed kinds
	// (remediation): the report covers requests in the last PeriodDays. 0
	// defaults to defaultPeriodDays; ignored by point-in-time kinds.
	PeriodDays int
}

GenerateRequest is the (all-optional) input to Generate. An empty request generates the all-hosts, all-frameworks executive summary — the pre-A1 behavior.

type GroupScoper

type GroupScoper interface {
	ScopeGroup(ctx context.Context, groupID uuid.UUID) (name string, hostIDs []uuid.UUID, err error)

	// ScopeGroupIn resolves the same thing inside a caller-supplied
	// transaction, so a signed artifact's scope and its content come from one
	// snapshot. Resolving membership on the pool first let a host join or
	// leave in between, and nothing in the artifact would say so.
	ScopeGroupIn(ctx context.Context, q db.Queryer, groupID uuid.UUID) (name string, hostIDs []uuid.UUID, err error)
}

GroupScoper resolves a group id to its display name and member host ids, so the report service can scope a fleet computation to one group without depending on the group package's types. internal/group's Service satisfies it via ScopeGroup.

type Kind

type Kind string

Kind is the report flavor. The MVP only generates KindExecutive.

const (
	// KindExecutive is the Fleet Compliance Executive Summary.
	KindExecutive Kind = "executive"
	// KindAttestation is the Framework Attestation: the auditor/GRC bulk
	// evidence path. Its snapshot freezes the latest completed scan per
	// in-scope host; its bulk faces (CSV now, OSCAL SAR next) reconstruct
	// per-(host, rule) outcomes from those immutable scan_results.
	KindAttestation Kind = "attestation"
	// KindException is the Exception Register: a Compliance/GRC point-in-time
	// read-model of compliance waivers (compliance_exceptions) - who waived
	// which rule on which host, the justification, the approver, and the
	// expiry. Faces: a CSV register + a bounded PDF summary (counts by
	// status + active + expiring-soon).
	KindException Kind = "exception"
	// KindRemediation is the Remediation Activity report: an Operations
	// read-model of remediation execute/rollback requests over a time
	// window (remediation_requests filtered on requested_at). Faces: a CSV
	// activity log + a bounded PDF summary (counts by outcome).
	KindRemediation Kind = "remediation"
)

type ReadModelProvenance added in v0.8.0

type ReadModelProvenance struct {
	ArtifactClass        string `json:"artifact_class"`
	CorpusIdentityStatus string `json:"corpus_identity_status"`

	// Kept as explicit nulls to honor decision 06's always-present rule.
	// Omitting them would narrow that decision and would need a superseding
	// record.
	CorpusVersion *string `json:"corpus_version"`
	CorpusDigest  *string `json:"corpus_digest"`
}

ReadModelProvenance is the envelope on an artifact that aggregates no scan set.

compliance_exceptions carries host_id and rule_id and no scan id, so contributor counts have no referent here. Attributing a corpus could only mean the corpus installed at generation time, which decision 08 forbids. The word is not_applicable rather than unavailable because corpus identity does not APPLY to a read model rather than being missing from it.

func NewReadModelProvenance added in v0.8.0

func NewReadModelProvenance() *ReadModelProvenance

NewReadModelProvenance builds the read-model envelope.

type RemediationActRow

type RemediationActRow struct {
	HostName    string     `json:"host_name"`
	RuleID      string     `json:"rule_id"`
	Status      string     `json:"status"`
	Mechanism   string     `json:"mechanism"`
	RequestedBy string     `json:"requested_by"`
	RequestedAt time.Time  `json:"requested_at"`
	ReviewedBy  string     `json:"reviewed_by"`
	ReviewedAt  *time.Time `json:"reviewed_at"`
}

RemediationActRow is one remediation request in the activity log. Requester/reviewer are resolved to usernames.

type RemediationContent

type RemediationContent struct {
	// PeriodFrom/PeriodTo bound the window (by requested_at). PeriodTo is
	// the generation instant; PeriodFrom is PeriodTo minus the period.
	PeriodFrom time.Time           `json:"period_from"`
	PeriodTo   time.Time           `json:"period_to"`
	Summary    RemediationSummary  `json:"summary"`
	Activities []RemediationActRow `json:"activities"`

	// Provenance is the read-model envelope; see ExceptionContent.
	Provenance *ReadModelProvenance `json:"provenance,omitempty"`
}

RemediationContent is the frozen snapshot for a Remediation Activity report: the period it covers, a summary of requests by outcome, and the activity rows. The CSV face writes one row per request; the PDF face renders the bounded summary.

type RemediationSummary

type RemediationSummary struct {
	Total      int `json:"total"`
	Executed   int `json:"executed"`
	RolledBack int `json:"rolled_back"`
	Failed     int `json:"failed"`
	Rejected   int `json:"rejected"`
	Pending    int `json:"pending"`
}

RemediationSummary is the bounded rollup of in-window requests by outcome.

type RenderPayload

type RenderPayload struct {
	SnapshotID uuid.UUID `json:"snapshot_id"`
}

RenderPayload is the job payload: which snapshot to render faces for.

type RenderProcessor

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

RenderProcessor renders a report's faces for a claimed report.render job and publishes ReportReady. It is registered on the in-process worker via WithReportProcessor; its ProcessJob signature matches the worker's other processors.

func NewRenderProcessor

func NewRenderProcessor(svc *Service, bus *eventbus.Bus) *RenderProcessor

NewRenderProcessor builds the processor over a report Service (for Export) and an event bus (to publish ReportReady). A nil bus renders the faces but publishes nothing.

func (*RenderProcessor) ProcessJob

func (p *RenderProcessor) ProcessJob(ctx context.Context, j *queue.Job)

ProcessJob renders every face that applies to the report's kind (warming the cache and flipping each 'pending' row to 'ready' via Export's upsert) and publishes a ReportReady event. A render error fails the job so it can be retried; faces already rendered are idempotent (deterministic bytes).

type Report

type Report struct {
	ID          uuid.UUID
	Title       string
	Kind        Kind
	ScopeLabel  string
	Scope       Scope
	DataAsOf    time.Time
	GeneratedBy string
	Format      string
	Content     json.RawMessage
	// ContentSHA256 is the snapshot's content address: the hex SHA-256 of
	// the canonical (marshaled) Content. Identical content yields an
	// identical hash; it is the stable identity the signature signs over.
	ContentSHA256 string
	// Signature is the Ed25519 signature over the content address (nil
	// when the snapshot was generated without a signer). SigningKeyID is
	// the fingerprint of the key that produced it.
	Signature    []byte
	SigningKeyID string
	CreatedAt    time.Time
}

Report is one row of the report_snapshots table. Content holds the rendered JSON posture document (see ExecutiveContent for the executive shape).

type Scope

type Scope struct {
	// GroupID, when set, scopes the report to that group's member hosts.
	GroupID *uuid.UUID `json:"group_id,omitempty"`
	// GroupName is the group's display name at generation time, frozen
	// onto the report so the label survives a later group rename/delete.
	GroupName string `json:"group_name,omitempty"`
	// Framework, when non-empty, scopes the report to rules whose
	// framework_refs contain this key (same lens as the fleet rollup).
	Framework string `json:"framework,omitempty"`
}

Scope is the structured slice of the fleet a report summarizes: an optional group and/or framework lens. The zero value (no group, no framework) is the all-hosts, all-frameworks scope. It is stored as the reports.scope JSONB column and echoed on the API so a caller can see (and reproduce) exactly what a report covers.

type ScoreProvenance added in v0.8.0

type ScoreProvenance struct {
	ArtifactClass string `json:"artifact_class"`

	FormulaVersion    *int    `json:"formula_version"`
	AggregationMethod *string `json:"aggregation_method"`
	Lens              *string `json:"lens"`

	EngineIdentityStatus       string        `json:"engine_identity_status"`
	Engines                    []EngineEntry `json:"engines"`
	HostsWithoutEngineIdentity *int          `json:"hosts_without_engine_identity"`
	EngineVersion              *string       `json:"engine_version"`

	CorpusIdentityStatus       string        `json:"corpus_identity_status"`
	Corpora                    []CorpusEntry `json:"corpora"`
	HostsWithoutCorpusIdentity *int          `json:"hosts_without_corpus_identity"`
	CorpusVersion              *string       `json:"corpus_version"`
	CorpusDigest               *string       `json:"corpus_digest"`

	// Participation counts. A mean over 2 of 200 hosts and a mean over 200 of
	// 200 are different claims that a percentage alone cannot tell apart, and
	// on a signed artifact the reader cannot go and look.
	HostsTotal        int `json:"hosts_total"`
	HostsScored       int `json:"hosts_scored"`
	HostsWithoutScore int `json:"hosts_without_score"`
}

ScoreProvenance is the frozen envelope on a score-bearing artifact.

Every field is present. A value that is unknown is null, never absent and never an empty string: bugs/OW-009 is the case where an empty non-nil slice serialized as "" and became indistinguishable from a real value.

func NewScoreProvenance added in v0.8.0

func NewScoreProvenance(env compliance.Envelope, agg compliance.Aggregate) *ScoreProvenance

NewScoreProvenance converts a validated envelope plus its participation counts into the frozen wire shape.

It takes a compliance.Envelope rather than the raw parts, so the validation that refuses an envelope whose counts do not reconcile runs before anything is signed. An artifact showing two count sets that do not add up is the same defect as a mean displayed beside pooled totals it cannot be derived from.

type Service

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

Service owns the reports library: generating an executive summary from current posture, and listing/fetching stored reports.

func NewService

func NewService(pool *pgxpool.Pool) *Service

func (*Service) Export

func (s *Service) Export(ctx context.Context, id uuid.UUID, face string) ([]byte, string, error)

Export returns a rendered face of the report (its bytes + media type), or ErrNotFound for an unknown id / ErrInvalidFace for a face that does not apply to the report's kind. The JSON face is the canonical content (any kind); the PDF face is executive-only; the CSV face is attestation-only. Rendered faces are cached in report_faces.

func (*Service) Frameworks

func (s *Service) Frameworks(ctx context.Context) ([]FrameworkCount, error)

Frameworks returns the distinct framework_refs keys present anywhere in the fleet, each with the count of distinct rules mapped to it, most-populated first. Backs the report scope picker's framework lens.

Scoped to current corpora (internal/corpus), so the picker offers only frameworks some host is still scanned against. A framework that exists on retired rows alone would otherwise stay on the menu and produce a report whose every rule is frozen.

func (*Service) Generate

func (s *Service) Generate(ctx context.Context, generatedBy string, req GenerateRequest) (Report, error)

Generate computes the Fleet Compliance Executive Summary from current posture (host_rule_state pass/fail counts + critical, the active host count, and the top failing rules) and inserts an immutable report row. The optional req scopes the summary to a group's member hosts and/or a framework lens; an empty req covers all hosts and all frameworks (the pre-A1 behavior). generatedBy is the actor recorded on the artifact (an email or "scheduler"). The returned Report carries the stored JSON content and the resolved scope.

func (*Service) Get

func (s *Service) Get(ctx context.Context, id uuid.UUID) (Report, error)

Get returns one report by id, or ErrNotFound.

func (*Service) List

func (s *Service) List(ctx context.Context) ([]Report, error)

List returns every report, newest first.

func (*Service) Signer

func (s *Service) Signer() *Signer

Signer exposes the wired signer (for the signing-key endpoint), or nil.

func (*Service) WithAsyncRender

func (s *Service) WithAsyncRender() *Service

WithAsyncRender enables async rendering of attestation bulk faces: Generate then marks the faces 'pending' and enqueues a report.render job (a RenderProcessor on the in-process worker renders them and publishes ReportReady). Enable it only when a worker is running to drain the queue; without it Generate stays synchronous and faces render lazily on first download.

func (*Service) WithGroups

func (s *Service) WithGroups(g GroupScoper) *Service

WithGroups wires the group resolver used for group-scoped reports. Returns the receiver for chaining at construction time.

func (*Service) WithSigner

func (s *Service) WithSigner(signer *Signer) *Service

WithSigner wires the Ed25519 signer that signs new snapshots over their content address. Without it, snapshots are generated unsigned (signature + signing_key_id stay null).

type Signer

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

Signer signs report snapshots with an Ed25519 key.

func NewSigner

func NewSigner(keyFile string) (*Signer, error)

NewSigner builds a Signer. When keyFile is non-empty it loads a 32-byte raw Ed25519 seed from that path (mode 0600 expected); when empty it generates an ephemeral key for development (Ephemeral() reports true so the caller can warn). The key id is a stable fingerprint of the public key.

func (*Signer) Ephemeral

func (s *Signer) Ephemeral() bool

Ephemeral reports whether the signer is a per-boot development key (no durable key was configured).

func (*Signer) KeyID

func (s *Signer) KeyID() string

KeyID returns the signer's key id.

func (*Signer) PublicKey

func (s *Signer) PublicKey() ed25519.PublicKey

PublicKey returns the verifying key.

func (*Signer) Sign

func (s *Signer) Sign(contentSHA256 string) (sig []byte, keyID string)

Sign returns the Ed25519 signature over the snapshot's content address and the key id that produced it.

type TopFailingRule

type TopFailingRule struct {
	RuleID           string `json:"rule_id"`
	FailingHostCount int    `json:"failing_host_count"`
}

TopFailingRule is one entry in the executive summary's top-failing list: a rule id and how many hosts it fails on.

Jump to

Keyboard shortcuts

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