Documentation
¶
Overview ¶
Background expiry sweep, wired in serve. Mirrors posture.Run: an immediate pass at boot plus an hourly tick.
Spec: api-compliance-exceptions v1.0.0 (C-06).
Package exception implements compliance exception governance: operator-approved rule waivers with a request -> approve/reject -> revoke/expire lifecycle.
DB-backed (scan plan decision 2026-06-13): an exception is approval-workflow data with a lifecycle, queries, and an audit trail, not a signed static policy file.
OVERLAY MODEL: an exception NEVER mutates host_rule_state. A failing rule with an active exception stays 'fail' in the raw scan results (Kensa's verdict is authoritative); the exception is a governance annotation the lens/UI reads to mark a failure as accepted risk.
Separation of duties: the requester cannot approve their own request (enforced here, on top of the distinct exception:request vs exception:approve RBAC permissions).
Spec: api-compliance-exceptions v1.0.0.
Index ¶
- Constants
- Variables
- type EmitFunc
- type Exception
- type Notifier
- type Service
- func (s *Service) ActiveCountForHost(ctx context.Context, hostID uuid.UUID) (int, error)
- func (s *Service) ActiveRuleIDsForHost(ctx context.Context, hostID uuid.UUID) (map[string]bool, error)
- func (s *Service) Approve(ctx context.Context, id, reviewedBy uuid.UUID, note string) (Exception, error)
- func (s *Service) ExpireSweep(ctx context.Context) (int, error)
- func (s *Service) ExpiringSoonSweep(ctx context.Context) (int, error)
- func (s *Service) ListFleet(ctx context.Context, status Status, limit int) ([]Exception, error)
- func (s *Service) ListForHost(ctx context.Context, hostID uuid.UUID, includeHistory bool) ([]Exception, error)
- func (s *Service) Reject(ctx context.Context, id, reviewedBy uuid.UUID, note string) (Exception, error)
- func (s *Service) Request(ctx context.Context, hostID uuid.UUID, ruleID, reason string, ...) (Exception, error)
- func (s *Service) Revoke(ctx context.Context, id, reviewedBy uuid.UUID, note string) (Exception, error)
- func (s *Service) Run(ctx context.Context, interval time.Duration) *cron.Scheduler
- func (s *Service) WithNotifier(n Notifier) *Service
- type Status
Constants ¶
const ExpiringSoonWindow = 72 * time.Hour
ExpiringSoonWindow is how far ahead the expiring-soon sweep looks: approvers are warned this long before an approved exception lapses.
const ExpirySweepInterval = time.Hour
ExpirySweepInterval is the production cadence of the expiry sweep. Hourly is fine: an exception only needs to flip to expired soon after its expires_at, and the count/annotation queries already guard on expires_at so suppression stops at the deadline regardless.
Variables ¶
var ( // ErrNotFound is returned when an exception id does not exist. ErrNotFound = errors.New("exception: not found") // ErrDuplicateOpen is returned when a requested/approved exception // already exists for the same host+rule (partial-unique violation). ErrDuplicateOpen = errors.New("exception: an open exception already exists for this host and rule") // ErrWrongState is returned when a transition does not apply to the // current status (e.g. approving a rejected exception). ErrWrongState = errors.New("exception: action not valid for the current state") // ErrSelfReview is returned when the reviewer is the requester: // separation of duties forbids approving your own request. ErrSelfReview = errors.New("exception: requester cannot review their own request") // ErrInvalidInput is returned for empty rule_id/reason etc. ErrInvalidInput = errors.New("exception: invalid input") // ErrExpired is returned when Approve is called on a requested row whose // expires_at has already passed: the requested waiver end is in the past, // so approving it would create an immediately-dead exception. The // requester must submit a fresh request with a future expiry. ErrExpired = errors.New("exception: the request's expiry has already passed") )
Functions ¶
This section is empty.
Types ¶
type Exception ¶
type Exception struct {
ID uuid.UUID
HostID uuid.UUID
// HostName is populated by the list queries (ListForHost,
// ListFleet) via a join; the single-row lifecycle ops leave it
// empty (the UI re-fetches the list after a mutation).
HostName string
RuleID string
Reason string
Status Status
RequestedBy uuid.UUID
ReviewedBy *uuid.UUID
ReviewNote string
ExpiresAt *time.Time
RequestedAt time.Time
ReviewedAt *time.Time
}
Exception is one compliance_exceptions row.
type Notifier ¶
type Notifier interface {
ExceptionRequested(ctx context.Context, exceptionID, hostID uuid.UUID, ruleID string) error
ExceptionDecided(ctx context.Context, exceptionID, requestedBy uuid.UUID, ruleID string, approved bool) error
ExceptionExpiringSoon(ctx context.Context, exceptionID, hostID uuid.UUID, ruleID string) error
ExceptionExpired(ctx context.Context, exceptionID, hostID uuid.UUID, ruleID string) error
}
Notifier receives exception lifecycle signals for the in-app notification feed. notifyfeed.GovernanceProjector implements it; the service holds the interface so it does not import the feed package. Methods are best-effort: the service logs (never fails the transition on) a notifier error. nil disables in-app exception notifications.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the exception governance service.
func NewService ¶
NewService wires the service. emit is audit.Emit in production.
func (*Service) ActiveCountForHost ¶
ActiveCountForHost counts the host's currently-suppressing exceptions (approved, not past expiry). Backs the host-detail Watchlist Exceptions row.
func (*Service) ActiveRuleIDsForHost ¶
func (s *Service) ActiveRuleIDsForHost(ctx context.Context, hostID uuid.UUID) (map[string]bool, error)
ActiveRuleIDsForHost returns the set of rule ids the host has an active (suppressing) exception for. Backs the Compliance-tab waived-rule annotation - read alongside host_rule_state, never mutating it.
func (*Service) Approve ¶
func (s *Service) Approve(ctx context.Context, id, reviewedBy uuid.UUID, note string) (Exception, error)
Approve transitions a 'requested' exception to 'approved'. The reviewer must differ from the requester (separation of duties). Emits compliance.exception.approved.
func (*Service) ExpireSweep ¶
ExpireSweep flips any OPEN exception (approved OR requested) whose expires_at has passed to 'expired' and emits compliance.exception.expired for each. Returns the count expired. Idempotent: a second run finds nothing.
A requested row is expired too: its expires_at is the requested waiver end, so once that has passed there is nothing left to approve — leaving it pending forever would strand it and block a fresh request for the same host+rule (the open-uniqueness rule, C-02). Approve independently rejects a lapsed request (ErrExpired), so the two guards agree.
func (*Service) ExpiringSoonSweep ¶
ExpiringSoonSweep warns approvers about approved exceptions whose expires_at falls within ExpiringSoonWindow. Read-only (no state change); the projector's quiet fan-out makes repeated hourly sweeps notify each recipient at most once. A no-op without a notifier wired. Returns the count warned.
func (*Service) ListFleet ¶
ListFleet returns fleet-wide exceptions, optionally filtered by status. Empty status returns all. Newest first.
func (*Service) ListForHost ¶
func (s *Service) ListForHost(ctx context.Context, hostID uuid.UUID, includeHistory bool) ([]Exception, error)
ListForHost returns a host's exceptions. When includeHistory is false, only open rows (requested + approved) are returned; true returns every row, newest first.
func (*Service) Reject ¶
func (s *Service) Reject(ctx context.Context, id, reviewedBy uuid.UUID, note string) (Exception, error)
Reject transitions a 'requested' exception to 'rejected'. Like Approve, the reviewer must differ from the requester. Emits compliance.exception.rejected.
func (*Service) Request ¶
func (s *Service) Request(ctx context.Context, hostID uuid.UUID, ruleID, reason string, requestedBy uuid.UUID, expiresAt *time.Time) (Exception, error)
Request submits a new exception (status 'requested'). Returns ErrDuplicateOpen when a requested/approved exception already exists for the same host+rule. Emits compliance.exception.requested.
func (*Service) Revoke ¶
func (s *Service) Revoke(ctx context.Context, id, reviewedBy uuid.UUID, note string) (Exception, error)
Revoke transitions an 'approved' exception to 'revoked' before its expiry. The revoker may be anyone with the permission (revoking is not a self-review concern). Emits compliance.exception.revoked.
func (*Service) WithNotifier ¶
WithNotifier attaches the in-app notification projector and returns the service for chaining at boot. nil leaves in-app notifications disabled.