report

package
v0.72.0 Latest Latest
Warning

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

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

Documentation

Overview

Package report implements `sigcomply report`: the read-only, auditor-facing snapshot tool. It walks a vault's {framework}/{period_id}/run_*/ tree and produces deterministic views of the data — no collection, no evaluation, no cloud calls.

Six views ship today:

  • latest: per-policy roll-up using the latest run that produced a result for each policy. Answers "what was the state at period close?"
  • exceptions: every waiver/NA in effect during the period, pulled from each run manifest's exceptions_applied table.
  • integrity: per-run signature + file-hash verification.
  • scope: the declared estate and how it fared, plus every control the run did not evaluate. Answers "did this run look at everything?"
  • coverage: per control, whether it is verified by inspecting infrastructure or merely by a document being on file, and whether that evidence exists this period. Answers "what is behind the green?"
  • soa: the Statement of Applicability — per catalog control, whether it is applicable, why, and whether it is implemented. Answers the first thing an ISO 27001 Stage 1 auditor asks for.

Each view is independently structured and independently formattable (text, json, csv). PDF is deferred to v1.x — the formatter layer returns a clear error rather than emit a half-rendered document.

Determinism is a hard requirement: the same vault state must produce byte-identical output across invocations, modulo any "generated_at" timestamp the CLI command stamps outside the snapshot's content. Snapshot views sort every list by a stable key (policy_id, run path, etc.) before serializing.

Index

Constants

This section is empty.

Variables

View Source
var ErrUnknownView = fmt.Errorf("report: unknown view")

ErrUnknownView is returned by Build when an unknown view string is passed. Sentinel so callers can distinguish "configuration error" from "vault read error".

Functions

func FormatCSV

func FormatCSV(w io.Writer, snap *Snapshot) error

FormatCSV writes a CSV rendering of snap to w. One row per policy for the latest view, one row per exception for the exceptions view, one row per run for the integrity view. Designed to play well with auditor spreadsheets.

The header row is the first record. CSV escaping is the standard encoding/csv behavior (RFC 4180): fields containing commas, quotes, or newlines are quoted.

func FormatJSON

func FormatJSON(w io.Writer, snap *Snapshot) error

FormatJSON writes a structured JSON rendering of snap to w. The output is indented 2-space for readability and is fully deterministic — Snapshot's content is already sorted by Build, and json.Encoder emits map keys in lexicographic order for any embedded maps.

No "generated_at" field is emitted from here; the CLI command stamps run-time metadata outside the snapshot body so tests can assert byte-identical content across invocations.

func FormatText

func FormatText(w io.Writer, snap *Snapshot) error

FormatText writes a human-readable text rendering of snap to w. Output is deterministic: no "generated_at" timestamp is emitted from here. The CLI command can prefix a header if it wants.

Format choices:

  • latest: aligned columns via tabwriter (policy_id control status severity last_evaluated exception)
  • exceptions: aligned columns (policy_id scope state approved_by approved_at expires_at reason)
  • integrity: aligned columns (run_path status files_verified/total details)

Types

type CoverageRow added in v0.70.0

type CoverageRow struct {
	ControlID string
	// Assurance is "automated", "manual" or "none" — the strongest check
	// behind this control. Where a run recorded its own evidence mode,
	// that wins over the framework's declaration, so a project override
	// is reflected rather than papered over.
	Assurance string
	// AutomatedPolicies and ManualPolicies give the mix the single
	// Assurance label hides: one automated check plus four documents is
	// "automated", and a reader deciding where to spend the next month
	// deserves to see the four.
	AutomatedPolicies int
	ManualPolicies    int
	// Evaluated is how many of this control's policies produced a result
	// in this period; Policies is how many exist.
	Evaluated int
	Policies  int
	// ManagementSystem marks a row as a management-system requirement
	// rather than a selectable catalog control.
	ManagementSystem bool
	// Status is the roll-up over this period's results — the worst of
	// them, or "not evaluated" when there are none.
	Status string
	// Overridden marks a control whose evidence mode the project changed.
	Overridden bool
	// Note explains a row that needs it: why nothing ran, or what an
	// error said.
	Note string
}

CoverageRow is one control and what stands behind it.

type CoverageView added in v0.70.0

type CoverageView struct {
	// Controls/Automated/Manual/Uncovered describe the framework's
	// selectable control catalog and do not vary by period.
	Controls  int
	Automated int
	Manual    int
	Uncovered int

	// ManagementSystem counts the framework's management-system
	// requirements — ISO 27001's clauses 4-10 — and
	// ManagementSystemOnFile how many have their document for this
	// period. They are counted apart from the catalog because blending
	// them flatters the headline exactly as the gap closes: adding
	// sixteen clause requirements that nobody has uploaded yet would
	// otherwise turn "93 of 93 covered" into "109 of 109 covered".
	ManagementSystem       int
	ManagementSystemOnFile int

	// Evaluated counts controls with at least one policy result in this
	// period. NotEvaluated is the rest — see the type doc for why that
	// is routine rather than alarming for a supra-period cadence.
	Evaluated    int
	NotEvaluated int

	// ManualOnFile and ManualMissing split the manual-assurance controls
	// by whether their document actually exists for this period.
	ManualOnFile  int
	ManualMissing int

	// Overridden counts controls a project reconfigured away from the
	// framework's declared evidence mode — nearly always an automated
	// check downgraded to a document upload.
	Overridden int

	// Rows is one entry per declared control, sorted by control ID.
	Rows []CoverageRow
}

CoverageView answers the question a compliance score cannot: what is actually behind the green?

The score is a pass rate over the policies that ran, and a policy satisfied by a document sitting in a folder counts exactly as much as one that inspected live infrastructure. That arithmetic is defensible; as a headline it is not, because the two are not the same claim. This view separates them.

It is framework-scoped, not run-scoped, and deliberately so. Cadences are independent of the audit period: a framework whose manual policies are annual produces no result at all for those controls in three quarters out of four. A view built only from what the period folder contains would show those controls as neither passing nor failing but simply absent — reproducing, in a new place, the exact blind spot it exists to close. So every declared control gets a row, and the row says what happened to it in this period, including "nothing".

type ExceptionEntry

type ExceptionEntry struct {
	PolicyID       string
	State          string
	Scope          string // resource_id, resource_pattern, or "policy"
	ApprovedBy     string
	ApprovedAt     string
	ExpiresAt      string
	Reason         string
	FirstSeenRunID string
	LastSeenRunID  string
}

ExceptionEntry is one row of the exceptions register. Fields mirror core.AppliedException — duplicated here so the report's data model is self-contained and not coupled to the manifest schema.

type ExceptionsView

type ExceptionsView struct {
	Exceptions []ExceptionEntry
}

ExceptionsView is the centralized register of every waiver/NA in effect during the period, deduplicated across runs by policy_id + resource_id + resource_pattern. The order of runs is preserved so readers can trace which run first/last applied a given exception.

type Input

type Input struct {
	Vault     core.Vault
	Framework string
	PeriodID  string
	View      View

	// Controls and Policies are the framework's catalog, resolved by the
	// command layer and passed in so this package never imports
	// internal/frameworks — Build stays a reader of vault bytes plus
	// whatever its caller hands it. Required by the coverage view, which
	// must name controls that produced no result at all; ignored by
	// every other view.
	Controls []core.Control
	Policies []core.Policy

	// ControlConfigs carries the project's per-control applicability
	// decisions, keyed by control ID — the half of a Statement of
	// Applicability that lives in .sigcomply.yaml rather than in the
	// vault or the framework. Only the soa view reads it.
	//
	// This is the package's one import of internal/spec, and a
	// deliberate exception to its "pure reader of vault bytes"
	// property: an applicability decision is authored, not observed, so
	// there is nowhere in the vault to read it from. spec is a
	// dependency-free leaf, so the coupling is narrow and acyclic —
	// unlike internal/frameworks, which this package still must not
	// import.
	ControlConfigs map[string]spec.ControlConfig
}

Input is the parameter bundle for Build. Vault is read-only; Build never writes.

type IntegrityRow

type IntegrityRow struct {
	RunPath           string
	RunID             string
	CompletedAt       time.Time
	SignatureValid    bool
	FilesVerified     int
	FilesTotal        int
	FirstMismatchPath string
	Error             string // populated when SignatureValid is false or a file is missing
}

IntegrityRow records the outcome of integrity verification for one run folder.

func (*IntegrityRow) Status

func (r *IntegrityRow) Status() string

Status returns "pass" if the run's signature verifies and every file_hashes entry matched its recomputed SHA-256; "fail" otherwise. Centralizing the boolean → label mapping lets every formatter render the same word.

type IntegrityView

type IntegrityView struct {
	Runs []IntegrityRow
}

IntegrityView is the per-run signature + file-hash verification table. Rows are sorted by run path (which sorts by timestamp in ISO 8601 basic form) so the output is reproducible.

type LatestPolicy

type LatestPolicy struct {
	PolicyID      string
	ControlID     string
	Status        string
	Severity      string
	Category      string
	LastEvaluated time.Time
	RunID         string
	// ExceptionID, when non-empty, points at the policy_id of the
	// exception that suppressed this result (waived/na). The free-CLI
	// design uses the policy_id as the exception's primary key — the
	// exceptions view is the place to look up details.
	ExceptionID string
	// Reason is the one-line explanation for a fail or error, the same
	// projection `check` prints inline. Empty for a plain pass. Without
	// it this view showed a bare "error" with the diagnostic stranded in
	// the vault's result.json, which is the least useful place for it.
	// Full detail still lives there.
	Reason string
}

LatestPolicy is one row of the latest-wins view.

type LatestView

type LatestView struct {
	Policies []LatestPolicy
}

LatestView is the latest-wins per-policy roll-up for the period. One row per policy that appears in any run in the period folder; for policies that appear in multiple runs, the row reflects the latest run (by manifest.completed_at) that produced a result for that policy.

type ScopeSource added in v0.67.0

type ScopeSource struct {
	SourceID string
	State    string
}

ScopeSource is one declared source and how it fared.

type ScopeView added in v0.67.0

type ScopeView struct {
	// Declared reports whether the run carried an estate declaration.
	// False for runs written before scope existed, and for projects that
	// have not opted in.
	Declared bool
	// Status is the run-level verdict: complete, incomplete, or empty
	// when nothing was declared.
	Status string
	// DeclaredBy/DeclaredAt are the operator's audit trail.
	DeclaredBy string
	DeclaredAt string
	// RunID is the run this verdict came from — the latest in the period.
	RunID string

	// Sources is one row per declared source, sorted by ID.
	Sources []ScopeSource
	// Skipped is one row per unevaluated control, sorted by policy ID.
	Skipped []SkippedPolicy
}

ScopeView answers "what was this run supposed to cover, and did it?"

Two independent things live here because they answer the same auditor question from opposite ends. Sources is the estate the operator declared and how each declared entry actually fared — present only for projects that opted in by declaring one. Skipped is every control the run did not evaluate, with the reason, which is available for every project whether or not an estate was declared.

The second matters on its own: a skipped control leaves the compliance-score denominator entirely, so a run can go green while quietly evaluating nothing.

type SkippedPolicy added in v0.67.0

type SkippedPolicy struct {
	PolicyID string
	Status   string
	Reason   string
}

SkippedPolicy is one control the run did not evaluate.

type Snapshot

type Snapshot struct {
	View      View
	Framework string
	PeriodID  string

	Latest     *LatestView     `json:",omitempty"`
	Exceptions *ExceptionsView `json:",omitempty"`
	Integrity  *IntegrityView  `json:",omitempty"`
	Scope      *ScopeView      `json:",omitempty"`
	Coverage   *CoverageView   `json:",omitempty"`
	SoA        *SoAView        `json:",omitempty"`
}

Snapshot is the top-level result of Build. Exactly one of the per-view sub-structs is populated based on the requested view; the other fields are zero-valued.

GeneratedAt is excluded from the formatted body — it lives on the header that the CLI command stamps separately so tests can assert byte-identical content across invocations.

func Build

func Build(ctx context.Context, in *Input) (*Snapshot, error)

Build walks the vault for the requested {framework}/{period_id} subtree and returns a Snapshot for the chosen view.

Build is strictly read-only: it lists run folders, parses each manifest, and (for the latest view) reads per-policy result.json files. It never writes, never opens network connections, and never requires OIDC.

type SoARow added in v0.72.0

type SoARow struct {
	ControlID string
	Name      string

	// Applicable is false only where the project declared the control
	// not_applicable.
	Applicable bool

	// Justification is the organization's reason for including the
	// control, or — for an excluded one — its reason for leaving it out.
	// JustificationDerived says SigComply wrote it rather than the
	// operator.
	Justification        string
	JustificationDerived bool

	// Status is "implemented", "partially implemented", "not
	// implemented", "not evaluated" or "excluded". It is derived from
	// this period's results and never from the catalog: a control whose
	// checks did not run is reported as unevaluated, not as passing.
	Status string

	// Assurance is "automated", "manual" or "none" — the strongest check
	// behind the control. An implemented control evidenced only by a
	// document on file is a weaker claim than one where infrastructure
	// was inspected, and the SoA should not flatten the two.
	Assurance string

	// Evaluated is how many of this control's policies produced a result
	// in this period; Policies names them all.
	Evaluated int
	Policies  []string

	// ApprovedBy carries the project config's approver for the
	// applicability decision, where one is recorded.
	ApprovedBy string
}

SoARow is one control's entry in the Statement of Applicability.

type SoAView added in v0.72.0

type SoAView struct {
	// Controls is the number of catalog controls — the rows below.
	// ManagementSystem is what was deliberately left out.
	Controls         int
	ManagementSystem int

	// Applicable and Excluded split the catalog by the project's
	// applicability decisions.
	Applicable int
	Excluded   int

	// Implemented, Partial, NotImplemented and NotEvaluated split the
	// applicable controls by what this period's results showed. They sum
	// to Applicable.
	Implemented    int
	Partial        int
	NotImplemented int
	NotEvaluated   int

	// Derived counts applicable controls whose inclusion justification
	// SigComply wrote rather than the organization. Surfaced because the
	// difference matters to a reader: a derived justification is
	// accurate but says nothing about why this organization kept the
	// control.
	Derived int

	// Rows is one entry per catalog control, sorted by control ID.
	Rows []SoARow

	// Note names what the table omits and what its statuses do not mean.
	Note string
}

SoAView is a Statement of Applicability: the document ISO/IEC 27001:2022 6.1.3 d requires, and the first thing a Stage 1 auditor asks for.

It answers four questions per control — is it necessary, why, is it implemented, and if it was left out, why — by joining three things SigComply already held separately: the framework's control catalog, the project's applicability decisions, and the period's results.

Management-system requirements are counted but never listed. A Statement of Applicability is about the control catalog an organization selects from; ISO's clauses 4-10 are not selectable, and putting them in a table of include/exclude decisions would invite an exclusion that the standard does not permit and the planner refuses.

type View

type View string

View names the requested snapshot kind. Surfaced as the --view flag on the CLI.

const (
	ViewLatest     View = "latest"
	ViewExceptions View = "exceptions"
	ViewIntegrity  View = "integrity"
	ViewScope      View = "scope"
	ViewCoverage   View = "coverage"
	ViewSoA        View = "soa"
)

Supported views. See package doc for the auditor question each answers.

Jump to

Keyboard shortcuts

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