spec

package
v0.70.0 Latest Latest
Warning

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

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

Documentation

Overview

Package spec is L0 of the SigComply CLI: parsers and validators for the declarative artifacts that drive the engine — framework specs, policy specs, evidence type schemas, source plugin manifests, the project config (.sigcomply.yaml), and the manual evidence catalog.

L0 owns the on-disk format. Nothing here executes; specs are read, validated, and handed up to L1 (types) and L2 (registries).

See docs/architecture/02-layers.md for the full layer contract.

Index

Constants

View Source
const DefaultLocalVaultPath = "./.sigcomply/vault"

DefaultLocalVaultPath is where a backend-less project stores signed evidence by default. It keeps a first run zero-config: no vault: block is required to get going. Production deployments override with an s3/gcs/azure_blob backend (which must be write-once / versioned for real tamper-resistance — see Invariant #3).

View Source
const ManualSlotName = "_manual"

ManualSlotName is the synthetic slot name the planner creates for manual policies. The collector and evaluator use it as the key in RecordsByPolicy[policyID].

View Source
const RosterKey = "roster"

RosterKey is the subkey under `experimental:` that designates the organization's roster source. Like experimental.scope it lives under the escape hatch so a config written for a newer CLI keeps loading on an older pinned one (docs/architecture/08-project-config.md §Config evolution policy).

View Source
const ScopeKey = "scope"

ScopeKey is the subkey under `experimental:` that carries the estate declaration. It lives under the escape hatch rather than at the top level so a config written for a newer CLI keeps loading on an older pinned one — the loader runs with KnownFields(true), which would hard-fail any brand-new top-level key. See docs/architecture/08-project-config.md §Config evolution policy.

Variables

This section is empty.

Functions

func LoadEvidenceType

func LoadEvidenceType(data []byte) (core.EvidenceType, error)

LoadEvidenceType parses a JSON Schema document declaring an evidence type and returns the L1 core.EvidenceType. See docs/architecture/04-source-plugins.md §Evidence types for the canonical file shape.

The Schema field on the returned value is the original bytes verbatim — record-against-schema validation happens at L4 (M6) using a JSON Schema library; this loader only checks the wrapper fields it needs.

func LoadPolicy

func LoadPolicy(data []byte) (core.Policy, error)

LoadPolicy parses a policy.yaml document and returns the L1 core.Policy with all fields populated.

Out of scope at L0: verifying that `control` exists in the framework's control catalog, that `rule` resolves in the rule registry, and that each `slot.type` exists in the evidence-type registry. Those are cross-spec checks handled by the planner (L3).

func ValidatePassWhen added in v0.68.0

func ValidatePassWhen(p core.Policy) error

ValidatePassWhen checks a policy's pass_when clauses and slot roles against the DSL's semantic rules. The YAML loader calls it for every project-local policy; each built-in framework's tests call it for every Go-built policy, which never pass through the loader.

It checks: every clause slot and every matches_in in_slot (in condition, filter and nested trees) is declared; operators, fields and values are well-formed; matches_in has in_slot and remote_field, a known normalize, and no matches_in inside its where; the matches_in-only keys appear on no other operator; slot roles are valid, at most one slot is the roster, a roster_subject slot needs a roster slot, and role slots accept disjoint evidence types (a record's envelope path carries no slot name, so an overlap would be ambiguous).

Types

type BindingEntry

type BindingEntry struct {
	Source     string         `yaml:"source"`
	SlotParams map[string]any `yaml:"slot_params"`
}

BindingEntry is one element of a slot binding list. It accepts both the string shape ("aws.iam") and the object shape with optional slot_params ({source: aws.iam, slot_params: {...}}).

func (*BindingEntry) UnmarshalYAML

func (b *BindingEntry) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts either a scalar string or a mapping; both are valid shapes in .sigcomply.yaml.

type CIConfig

type CIConfig struct {
	FailOnViolation *bool         `yaml:"fail_on_violation"`
	FailSeverity    core.Severity `yaml:"fail_severity"`
}

CIConfig models CI exit-code behavior knobs.

type CloudConfig

type CloudConfig struct {
	Enabled *bool  `yaml:"enabled"`
	BaseURL string `yaml:"base_url"`
}

CloudConfig models the cloud submission section.

type ControlConfig added in v0.35.2

type ControlConfig struct {
	Applicability string `yaml:"applicability"` // "" | applicable | not_applicable
	Reason        string `yaml:"reason"`
	ApprovedBy    string `yaml:"approved_by"`
}

ControlConfig is a control-level decision — the coarse governance axis authored per control (the unit auditors and the ISO Statement of Applicability think in), not per check. applicability: not_applicable cascades to every policy mapping to this control. Reason/ApprovedBy give the audit trail; Owner/InheritedFrom are reserved for later.

type ControlSpec

type ControlSpec struct {
	ID               string        `yaml:"id"`
	Name             string        `yaml:"name"`
	Description      string        `yaml:"description"`
	Category         string        `yaml:"category"`
	BaselineSeverity core.Severity `yaml:"baseline_severity"`
}

ControlSpec mirrors core.Control for YAML decoding.

type CustomPeriod

type CustomPeriod struct {
	ID    string `yaml:"id"`
	Start string `yaml:"start"`
	End   string `yaml:"end"`
}

CustomPeriod is one entry in a custom fiscal_calendar.periods list.

type ExceptionScope

type ExceptionScope struct {
	ResourceID      string `yaml:"resource_id"`
	ResourcePattern string `yaml:"resource_pattern"`
}

ExceptionScope narrows an exception to specific resources.

type ExtensionsConfig

type ExtensionsConfig struct {
	Path string `yaml:"path"`
}

ExtensionsConfig overrides extension discovery paths.

type FiscalCalendarConfig

type FiscalCalendarConfig struct {
	Type    string         `yaml:"type"`
	Starts  string         `yaml:"starts"`
	Periods []CustomPeriod `yaml:"periods"`
}

FiscalCalendarConfig models the period.fiscal_calendar subsection.

type FrameworkSpec

type FrameworkSpec struct {
	SchemaVersion string          `yaml:"schema_version"`
	IDValue       string          `yaml:"id"`
	VersionValue  string          `yaml:"version"`
	DisplayName   string          `yaml:"display_name"`
	Description   string          `yaml:"description"`
	ControlsList  []ControlSpec   `yaml:"controls"`
	PoliciesList  []PolicyRefSpec `yaml:"policies"`
}

FrameworkSpec is the loaded representation of a framework.yaml file. It satisfies core.Framework so callers may register the value directly with the framework registry.

A framework.yaml carries the framework's identity, the catalog of controls it certifies against, and the list of policy IDs that contribute evaluations. Policy specs themselves live in separate files (one directory per policy, per docs/architecture/03-policy- spec.md) — the framework spec only references them by ID.

func LoadFramework

func LoadFramework(data []byte) (*FrameworkSpec, error)

LoadFramework parses a framework.yaml document. The returned value satisfies core.Framework and may be registered directly.

func (*FrameworkSpec) Controls

func (f *FrameworkSpec) Controls() []core.Control

Controls implements core.Framework.

func (*FrameworkSpec) ID

func (f *FrameworkSpec) ID() string

ID implements core.Framework.

func (*FrameworkSpec) Policies

func (f *FrameworkSpec) Policies() []core.PolicyRef

Policies implements core.Framework.

func (*FrameworkSpec) Version

func (f *FrameworkSpec) Version() string

Version implements core.Framework.

type OutputConfig

type OutputConfig struct {
	Format   string `yaml:"format"`
	JSONPath string `yaml:"json_path"`
	Verbose  bool   `yaml:"verbose"`
}

OutputConfig models output preferences.

type PeriodConfig

type PeriodConfig struct {
	FiscalCalendar FiscalCalendarConfig `yaml:"fiscal_calendar"`
	TimeBasis      string               `yaml:"time_basis"`
}

PeriodConfig models the period section.

type PluginConfigField

type PluginConfigField struct {
	Type        string `yaml:"type"`
	Required    bool   `yaml:"required"`
	Default     any    `yaml:"default"`
	Enum        []any  `yaml:"enum"`
	Description string `yaml:"description"`
}

PluginConfigField describes one knob in the plugin's config_schema. Default is left as `any` because the static type is named by the Type field (`string`, `int`, `bool`, etc.).

type PluginCredentialRequirement

type PluginCredentialRequirement struct {
	Source string `yaml:"source"`
	EnvVar string `yaml:"env_var,omitempty"`
}

PluginCredentialRequirement declares one credential the plugin needs. `Source` names the mechanism (e.g. env_or_default_chain, env, file).

type PluginManifest

type PluginManifest struct {
	SchemaVersion       string                        `yaml:"schema_version"`
	ID                  string                        `yaml:"id"`
	DisplayName         string                        `yaml:"display_name"`
	Version             string                        `yaml:"version"`
	Description         string                        `yaml:"description"`
	Emits               []string                      `yaml:"emits"`
	Singleton           bool                          `yaml:"singleton"`
	ConfigSchema        map[string]PluginConfigField  `yaml:"config_schema"`
	RequiresCredentials []PluginCredentialRequirement `yaml:"requires_credentials"`
}

PluginManifest is the in-memory shape of a plugin's plugin.yaml. It declares the plugin's identity, the evidence types it emits, and the config_schema customers fill in under sources.<id> in .sigcomply.yaml. See docs/architecture/04-source-plugins.md.

There is no L1 core type for plugin manifests — they are metadata the planner (L3) reads to validate project config and build SlotRequest values; nothing flows from a manifest into the evidence or aggregation paths.

func LoadPluginManifest

func LoadPluginManifest(data []byte) (PluginManifest, error)

LoadPluginManifest parses a plugin manifest YAML document. Unknown fields are rejected (typo-safety); refer to the comment-doc on the PluginManifest struct for the field set.

type PolicyConfig added in v0.35.2

type PolicyConfig struct {
	Bindings     map[string][]BindingEntry `yaml:"bindings"`
	Parameters   map[string]any            `yaml:"parameters"`
	Cadence      string                    `yaml:"cadence"`
	EvidenceMode string                    `yaml:"evidence_mode"`
	CatalogEntry string                    `yaml:"catalog_entry"`
	Exceptions   []PolicyException         `yaml:"exceptions"`
}

PolicyConfig is the per-policy configuration object — everything a project can override or declare about one policy, co-located under its ID in the policies: map. Every field is optional; an absent policy entry means "framework defaults, auto-bound sources, no exceptions."

policies:
  soc2.cc6.1.mfa_enforced:
    cadence: hourly
    bindings: { user_directory: [okta, acme.internal_iam] }
    parameters: { exempt_service_accounts: false }
    exceptions:
      - scope: { resource_id: "okta_user:bot@acme.com" }
        state: waived
        reason: "Legacy deploy bot; retired by Q3."
        expires_at: 2026-09-30

New per-policy dimensions (severity, scope, owner, enabled) are added here as new optional fields — additive, no schema bump.

type PolicyException added in v0.35.2

type PolicyException struct {
	Scope      ExceptionScope `yaml:"scope"`
	State      string         `yaml:"state"`
	Reason     string         `yaml:"reason"`
	ApprovedBy string         `yaml:"approved_by"`
	ApprovedAt string         `yaml:"approved_at"`
	ExpiresAt  string         `yaml:"expires_at"`
}

PolicyException is one declarative waiver or N/A entry on a policy. The owning policy is the map key in PolicyConfig — there is no policy field. Multiple entries on one policy support distinct resource scopes.

type PolicyRefSpec

type PolicyRefSpec struct {
	ID string `yaml:"id"`
}

PolicyRefSpec is one entry in the framework's policies list — a reference to a policy by ID. The actual policy.yaml lives elsewhere.

type ProjectConfig

type ProjectConfig struct {
	SchemaVersion string                    `yaml:"schema_version"`
	Framework     string                    `yaml:"framework"`
	Period        PeriodConfig              `yaml:"period"`
	Vault         VaultConfig               `yaml:"vault"`
	Sources       map[string]map[string]any `yaml:"sources"`

	// Policies holds all per-policy configuration as one object per
	// policy ID — bindings, parameter overrides, cadence override,
	// evidence_mode override, and scoped exceptions co-located in a single
	// place. This replaces the former family of parallel policy-ID-keyed
	// maps (bindings/policy_parameters/policy_cadences/policy_overrides/
	// exceptions). The shape is what makes the config extensible without a
	// schema bump: a new per-policy dimension (severity, scope, owner) is a
	// new optional field on PolicyConfig — additive, never a new top-level
	// section. See docs/architecture/08-project-config.md §The binding model.
	Policies map[string]PolicyConfig `yaml:"policies"`

	// Controls holds control-level decisions — the coarse, governance
	// axis that is naturally per-control, not per-check: applicability
	// (the ISO Statement of Applicability), and room for ownership and
	// inheritance. A control marked not_applicable cascades to every
	// policy that maps to it (planner sets status=na). Fine-grained,
	// resource-scoped waivers stay under Policies[id].exceptions.
	Controls map[string]ControlConfig `yaml:"controls"`

	Cloud         CloudConfig      `yaml:"cloud"`
	CIEnvironment map[string]any   `yaml:"ci_environment"`
	Output        OutputConfig     `yaml:"output"`
	CI            CIConfig         `yaml:"ci"`
	Extensions    ExtensionsConfig `yaml:"extensions"`

	// Experimental is the forward-compatibility escape hatch. The loader
	// runs with KnownFields(true) so a typo in a recognized key is a loud
	// error — but that strictness would also make any *new* top-level key
	// break older CLIs that predate it. New, not-yet-stable fields are
	// introduced under `experimental:` first: every CLI version that has
	// this field tolerates (and ignores) experimental subkeys it does not
	// understand, so a newer config never hard-fails an older pinned CLI.
	// A field graduates from `experimental.<name>` to a first-class
	// top-level key in a later release. The loader does not interpret
	// anything in here; individual features opt in by reading their own
	// key. See docs/architecture/08-project-config.md §Config evolution.
	Experimental map[string]any `yaml:"experimental"`

	// ProjectLocalPolicies are PolicyRefs for policies discovered under
	// .sigcomply/policies/*/policy.yaml at bootstrap and registered into
	// the policy registry. The planner unions these with the active
	// framework's own Policies() so a customer-authored YAML policy is
	// actually planned and evaluated. yaml:"-" — populated by the
	// orchestrator's project-local loader, never decoded from the file.
	ProjectLocalPolicies []core.PolicyRef `yaml:"-"`
}

ProjectConfig is the parsed shape of .sigcomply.yaml. See docs/architecture/08-project-config.md for the canonical field reference. Cross-spec resolution (does this binding's source exist? is this parameter declared on the named policy?) is the planner's job (L3 / M4) — this loader only validates what's expressible without the registries.

func LoadProjectConfig

func LoadProjectConfig(data []byte) (ProjectConfig, error)

LoadProjectConfig parses a .sigcomply.yaml document. The file is the customer's source of truth, so all error messages name the field path to make corrections obvious.

func (*ProjectConfig) BindingsFor added in v0.35.2

func (c *ProjectConfig) BindingsFor(policyID string) map[string][]BindingEntry

BindingsFor returns the slot→entries map a project declared for a policy, or nil if the policy has no entry. nil means "auto-bind every configured source whose Emits() intersects the slot" — the planner's substitutability default.

func (*ProjectConfig) CadenceFor added in v0.35.2

func (c *ProjectConfig) CadenceFor(policyID string) string

CadenceFor returns the cadence override for a policy, or "" (use the framework default).

func (*ProjectConfig) EvidenceModeOverrideFor added in v0.35.2

func (c *ProjectConfig) EvidenceModeOverrideFor(policyID string) (mode, catalogEntry string)

EvidenceModeOverrideFor returns the (mode, catalog_entry) override for a policy. mode is "" when the project does not override the framework's evidence_mode for this policy.

func (*ProjectConfig) ExceptionsFor added in v0.35.2

func (c *ProjectConfig) ExceptionsFor(policyID string) []PolicyException

ExceptionsFor returns the scoped waiver/NA entries a project declared for a policy, or nil.

func (*ProjectConfig) ParametersFor added in v0.35.2

func (c *ProjectConfig) ParametersFor(policyID string) map[string]any

ParametersFor returns the parameter overrides a project declared for a policy, or nil.

type RosterConfig added in v0.68.0

type RosterConfig struct {
	// Source is the designated roster source ID. Required when the
	// block is present. Bracketed multi-instance IDs are not supported.
	Source string

	// Aliases maps source ID → lowercased account name → roster email,
	// for accounts whose own email is absent or differs from the
	// roster's. Account names match a record's id or payload.username,
	// case-insensitively.
	Aliases map[string]map[string]string

	// NonHuman maps source ID → lowercased account names that are not
	// people (CI bots, deploy users). Sorted and de-duplicated.
	NonHuman map[string][]string

	// UnknownKeys are subkeys of experimental.roster this CLI does not
	// understand — tolerated, never fatal, but surfaced as warnings.
	UnknownKeys []string
}

RosterConfig designates the one source whose people are the authoritative roster, plus the operator's declarations for accounts that cannot be linked to the roster by email alone.

The roster must come from a system other than the one being checked: the planner never binds the roster source to a policy's accounts slot (a directory cannot vouch for its own accounts).

Nothing in here crosses the aggregation boundary. Aliases carry email addresses and account names — identity data that must never reach the cloud payload (root CLAUDE.md §Privacy invariant).

func LoadRosterConfig added in v0.68.0

func LoadRosterConfig(cfg *ProjectConfig) (*RosterConfig, error)

LoadRosterConfig projects the experimental.roster block out of a loaded project config. It returns (nil, nil) when the block is absent: no roster source is designated, so roster slots stay unbound and the policies that need one are skipped.

Validation here is shape-only. Whether the named sources are configured, and whether the roster source emits what a roster slot accepts, needs the registries and belongs to the planner.

type ScopeConfig added in v0.67.0

type ScopeConfig struct {
	// DeclaredBy and DeclaredAt are the audit trail for the assertion,
	// matching the approved_by/approved_at idiom used by exceptions and
	// control applicability. Both optional.
	DeclaredBy string
	DeclaredAt string

	// RequiredSources lists the source IDs this project asserts are in
	// scope. Sorted on load so the derived report is deterministic
	// (Core Principle #7 — auditors diff runs).
	RequiredSources []string

	// UnknownKeys are subkeys of experimental.scope this CLI does not
	// understand. They are tolerated, never fatal — that tolerance is
	// the whole point of the experimental: hatch — but they are
	// surfaced so a typo is loud without breaking the run.
	UnknownKeys []string
}

ScopeConfig is the operator's declaration of the estate this project asserts coverage over — the authoritative baseline the run is checked against, and the auditor-legible scope object.

Without it the CLI can only evaluate the evidence it happened to collect, against itself: a source the operator forgot to configure leaves its policies with an unbound required slot, which is skipped at evaluation and drops out of the compliance-score denominator. The run then reports a perfect score for an estate it never looked at. The declaration is what turns that silence into a finding.

Semantics are deliberately per-project, not per-organization: one project is one repo is one framework (ARCHITECTURE.md Core Principle #9), so this declares the estate *this project* asserts coverage over. Rolling several projects up into one view is the Cloud dashboard's job.

Nothing in here crosses the aggregation boundary. DeclaredBy is an email address and must never reach the cloud payload (root CLAUDE.md §Privacy invariant); the scope report is a vault-side artifact only.

func LoadScopeConfig added in v0.67.0

func LoadScopeConfig(cfg *ProjectConfig) (*ScopeConfig, error)

LoadScopeConfig projects the experimental.scope block out of a loaded project config. It returns (nil, nil) when the block is absent — the undeclared case, which leaves every existing behavior untouched.

Validation here is shape-only. Whether a declared source ID actually exists is a cross-reference question that needs the registries, and so belongs to the planner (see the layering note on LoadProjectConfig).

type VaultConfig

type VaultConfig struct {
	Backend string
	Config  map[string]any
}

VaultConfig selects an output vault backend and carries its backend-specific settings as an open bag — symmetric with the `sources:` config, where each plugin reads the keys it needs. Only `backend` is interpreted by the spec layer; every other key flows through to the backend's factory untouched. This is what makes a new destination backend additive: it reads its own keys from Config, with no typed struct field and no central validation switch to edit (see docs/architecture/05-vault-layout.md §Adding a backend).

The YAML stays flat for ergonomics — `vault: {backend: s3, bucket: x, region: y}` — via the custom UnmarshalYAML below: `backend` is lifted out and the remaining keys become Config.

func (VaultConfig) Bool added in v0.35.2

func (v VaultConfig) Bool(key string) bool

Bool returns the bool value of a Config key, or false if absent/non-bool.

func (VaultConfig) Str added in v0.35.2

func (v VaultConfig) Str(key string) string

Str returns the string value of a Config key, or "" if absent/non-string.

func (*VaultConfig) UnmarshalYAML added in v0.35.2

func (v *VaultConfig) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts the flat `vault:` mapping, lifting `backend` into its own field and passing all other keys through as Config. Decoding into a map deliberately bypasses the parent decoder's KnownFields strictness for vault keys — the bag is open, exactly like `sources:`.

Jump to

Keyboard shortcuts

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