support

package
v1.0.126 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package support is the support-bundle engine (SUPPORT-BUNDLE-SPEC.md, COLLECTOR-CONTRACT.md): a plugin registry of small, isolated, budgeted, self-redacting collectors plus a runner that assembles a deterministic, integrity-hashed `csb/1` bundle. Collectors live in their owning package and register here; the engine owns orchestration, redaction hand-off, budgeting, failure isolation, and serialization — never data-gathering logic.

rawgate.go — the raw-collector hard-gate (#788 merge-gate invariant).

Every collector must redact at source via in.Redactor (COLLECTOR-CONTRACT): redaction.Classify WALKS the payload and returns only maps, slices, and primitives — never structs. A struct with exported fields reaching the section sink therefore proves the payload BYPASSED the class model (a "raw" collector): its redact tags — if it even has them — were never evaluated, no SENSITIVE field was masked, no SECRET field was dropped, and the free-form scrubber never ran. That is exactly the defect class this gate exists to stop shipping: a future raw-file collector that dumps an untagged (or tagged-but-unwalked) struct straight into the bundle.

The gate is enforced at runtime in sectionSink.WriteJSON — fail-closed per section (the collector's own error path marks the section failed; the framework never aborts the bundle) — and pinned by rawgate_test.go plus the all-registered-collectors sweep in the package-main wall tests.

Zero-exported-field structs (e.g. time.Time) are tolerated: they carry no per-field class decisions for the walker to make and marshal as opaque scalars.

Index

Constants

View Source
const (
	BundleFormat        = "csb/1"
	CollectorEngineVer  = 1 // internal/support orchestration schema
	RedactionModelVer   = 1 // REDACTION-MODEL taxonomy/registry version
	ManifestName        = "manifest.json"
	CollectionErrorName = "collection-errors.json"
	RedactionReportName = "redaction-report.json"
)

Bundle format identity. Consumers reject an unknown MAJOR (SUPPORT-BUNDLE-SPEC §4).

View Source
const RedactionPreviewName = "redaction-preview.json"

RedactionPreviewName is the SERVER-SIDE-ONLY consent-preview file. It is written next to the manifest in the bundle dir but is NEVER added to the shareable tar and NEVER downloaded — it exists only so the pre-export consent endpoint can show the approver the retained free-form values. The bundled redaction-report.json stays counts-only (P4/P6); this is the sighted-gate companion that closes the "the human backstop is blind to the value it releases" gap without weakening the shareable report.

Variables

This section is empty.

Functions

func Register

func Register(c Collector)

Register adds a collector. Duplicate ID or Path is a wiring bug (fatal at startup, not a runtime condition) — it panics, matching the repo's route/config-surface parity discipline.

Types

type BuildInfo

type BuildInfo struct {
	Commit  string `json:"commit,omitempty"`
	BuiltAt string `json:"built_at,omitempty"`
	Go      string `json:"go"`
}

BuildInfo is the compiled-binary provenance recorded in GeneratedBy.

type BuildOptions

type BuildOptions struct {
	Version       string
	GoVersion     string
	BuildCommit   string
	BuildAt       string
	Runtime       RuntimeInfo
	ClusterID     string
	Level         DebugLevel
	IncidentScope string
	// IncludeCollectors, when non-empty, restricts the bundle to collectors whose
	// ID is present (an incident-scoped collection). Empty/nil = every collector
	// (the "standard" scope). The catalog of scope→IDs lives in the owning package;
	// the engine stays generic. Level gating still applies within the scope.
	IncludeCollectors map[string]bool
	Profile           string
	CaseID            string
	Nonce             string
	Clock             func() time.Time
	Salt              []byte // optional; deterministic masking for tests
}

BuildOptions parameterizes one bundle build. Clock and Nonce are injected so a build over identical frozen state is byte-deterministic (TestBundleDeterministic).

type BuildResult

type BuildResult struct {
	BundleID     string
	Manifest     SupportBundleManifest
	Report       RedactionReport  // counts-only redaction report (also packaged in the tar)
	Preview      RedactionPreview // server-side consent preview; NEVER packaged in the tar
	TarGz        []byte
	BundleSHA256 string
}

BuildResult is a finished bundle: its id, manifest, and the gzipped-tar bytes.

type CollectInput

type CollectInput struct {
	Level    DebugLevel
	Redactor redaction.Redactor
	Runtime  RuntimeInfo
	Clock    func() time.Time
}

CollectInput is handed to every collector. Redactor is the ONLY sanctioned masking path; Clock is injected so collectors never call time.Now directly (determinism, mirroring the autoexclude engine-test discipline).

type CollectionError

type CollectionError struct {
	Collector  string `json:"collector"`
	Phase      string `json:"phase"`       // preflight|execute|redact|assemble
	ErrorClass string `json:"error_class"` // timeout|panic|unavailable|permission|budget|runtime_unsupported
	Message    string `json:"message"`     // redacted
	Fatal      bool   `json:"fatal"`
}

CollectionError is one collection-errors.json entry. fatal is ALWAYS false for a collector; true only for an engine-level abort.

type CollectionStats

type CollectionStats struct {
	EngineStartedAt string `json:"engine_started_at"`
	EngineEndedAt   string `json:"engine_ended_at"`
	TotalCollectors int    `json:"total_collectors"`
	OK              int    `json:"ok"`
	Failed          int    `json:"failed"`
	Skipped         int    `json:"skipped"`
	ErrorCount      int    `json:"error_count"`
}

CollectionStats summarizes one Build run: how many collectors ran, and with what outcome.

type Collector

type Collector interface {
	Meta() CollectorMeta
	// Collect writes exactly one section into sink. It MUST respect ctx,
	// redact at source via in.Redactor, and return a Result (not an error) for
	// "subsystem down". It should not rely on the runner's panic recovery.
	Collect(ctx context.Context, in CollectInput, sink SectionSink) Result
}

Collector produces one section of a CSB.

func Collectors

func Collectors() []Collector

Collectors returns the registered collectors sorted by ID (deterministic ordering — the spec requires collector-ID-ascending section order).

type CollectorMeta

type CollectorMeta struct {
	ID            string // stable snake_case; == section id; unique
	Path          string // section path in the tar (e.g. "sections/diagnostics.json")
	Owner         string // subsystem team
	SchemaVersion int    // per-section schema; bumped on shape change
	Description   string
	Timeout       time.Duration       // hard cap; runner cancels ctx at this
	ByteBudget    int64               // section size cap
	Mandatory     bool                // mandatory collectors gate completeness, never abort
	MinLevel      DebugLevel          // runs only at/above this level (L0 always)
	MaxClass      redaction.DataClass // asserted ceiling; a section exceeding it is dropped
	Sensitivity   redaction.DataClass // declared default class of this section's raw data
}

CollectorMeta is a collector's static contract.

type DebugLevel

type DebugLevel int

DebugLevel gates collectors (L0 always runs; richer levels unlock more).

const (
	L0 DebugLevel = iota // always-on: product, readiness
	L1                   // standard bundle: diagnostics, config, policy, logs…
	L2                   // runtime/host (goroutine, container facts)
	L3                   // heap profile (gated)
	L4                   // maximal
)

DebugLevel values, from always-on to maximal capture depth.

type GeneratedBy

type GeneratedBy struct {
	Product                string    `json:"product"`
	Version                string    `json:"version"`
	Build                  BuildInfo `json:"build"`
	CollectorEngineVersion int       `json:"collector_engine_version"`
}

GeneratedBy identifies the product/build that produced the bundle.

type IntegrityInfo

type IntegrityInfo struct {
	ManifestSHA256 string `json:"manifest_sha256"` // hash of manifest with integrity fields zeroed
	BundleSHA256   string `json:"bundle_sha256"`   // hash of the whole tar
}

IntegrityInfo carries the self-referential tamper-detect hashes checked by validateBundleTar.

type NodeInfo

type NodeInfo struct {
	NodeID    string `json:"node_id"`
	Role      string `json:"role"`
	Runtime   string `json:"runtime"`
	ClusterID string `json:"cluster_id,omitempty"`
}

NodeInfo describes the appliance node the bundle was collected from.

type RedactionInfo

type RedactionInfo struct {
	ModelVersion int    `json:"model_version"`
	Profile      string `json:"profile"`
	FailClosed   bool   `json:"fail_closed"`
}

RedactionInfo records which redaction model/profile governed this bundle.

type RedactionPreview

type RedactionPreview struct {
	ModelVersion int                       `json:"model_version"`
	Sections     []RedactionPreviewSection `json:"sections"`
}

RedactionPreview is redaction-preview.json — a BOUNDED sample of the INTERNAL free-form string values KEPT (post-scrub) in each section, surfaced to the approving admin only. Server-side; not part of the bundle.

type RedactionPreviewSection

type RedactionPreviewSection struct {
	ID               string   `json:"id"`
	RetainedFreeForm []string `json:"retained_freeform"`
}

RedactionPreviewSection is one redaction-preview.json sections[] row: the bounded, deduped sample of retained INTERNAL free-form values for one section.

type RedactionReport

type RedactionReport struct {
	ModelVersion int                      `json:"model_version"`
	Profile      string                   `json:"profile"`
	FailClosed   bool                     `json:"fail_closed"`
	Sections     []RedactionReportSection `json:"sections"`
	Totals       RedactionReportCounts    `json:"totals"`
}

RedactionReport is redaction-report.json — counts only, never values (P4/P6).

type RedactionReportCounts

type RedactionReportCounts struct {
	Masked   int `json:"masked"`
	Dropped  int `json:"dropped"`
	Scrubbed int `json:"scrubbed"`
}

RedactionReportCounts is the redaction-report.json totals row, summed across all sections.

type RedactionReportSection

type RedactionReportSection struct {
	ID       string `json:"id"`
	ClassMax string `json:"class_max"`
	Masked   int    `json:"masked"`
	Dropped  int    `json:"dropped"`
	Scrubbed int    `json:"scrubbed"` // free-form secret shapes redacted in kept strings
}

RedactionReportSection is one redaction-report.json sections[] row — counts-only, never values (P4/P6).

type Result

type Result struct {
	Status    SectionStatus
	ClassMax  redaction.DataClass // highest class actually written (post-redaction)
	Truncated bool
	Note      string // redacted, human-readable
}

Result is what a collector returns. A "subsystem down" is a Status, never an error — the runner keeps going; only the framework can fail a bundle.

type Runner

type Runner struct{}

Runner assembles a bundle from the registered collectors.

func NewRunner

func NewRunner() *Runner

NewRunner returns a runner over the process collector registry.

func (*Runner) Build

func (rn *Runner) Build(ctx context.Context, opts BuildOptions) (*BuildResult, error)

Build runs every eligible collector and assembles a csb/1 bundle. It never returns an error for a collector failure (that becomes a failed section + collection-errors entry); it errors only on an engine-level assembly failure.

type RuntimeInfo

type RuntimeInfo struct {
	Runtime string // compose|k8s|host|unknown
	Role    string // standalone|control-plane|data-plane|ha-standby
	NodeID  string
}

RuntimeInfo describes the appliance's execution context.

type ScopeInfo

type ScopeInfo struct {
	IncidentScope string `json:"incident_scope"`
	DebugLevel    int    `json:"debug_level"`
}

ScopeInfo records the incident scope and capture level a bundle was built with.

type SectionEntry

type SectionEntry struct {
	ID               string        `json:"id"`
	Path             string        `json:"path"`
	Collector        string        `json:"collector"`
	CollectorVersion int           `json:"collector_version"`
	Owner            string        `json:"owner"`
	ClassMax         string        `json:"class_max"`
	SHA256           string        `json:"sha256,omitempty"`
	SizeBytes        int64         `json:"size_bytes"`
	StartedAt        string        `json:"started_at"`
	EndedAt          string        `json:"ended_at"`
	Status           SectionStatus `json:"status"`
	Truncated        bool          `json:"truncated"`
	Note             string        `json:"note,omitempty"`
}

SectionEntry is one manifest.sections[] row — present even for failed/skipped collectors so a reader can tell "not collected" from "collected empty".

type SectionSink

type SectionSink interface {
	// WriteJSON marshals v (already redacted by the collector) deterministically
	// into the section, enforcing the byte budget.
	WriteJSON(v any) error
}

SectionSink receives exactly one section's bytes and enforces its byte budget while computing the integrity hash.

type SectionStatus

type SectionStatus string

SectionStatus is the per-section outcome recorded in the manifest.

const (
	StatusOK          SectionStatus = "ok"          // complete
	StatusPartial     SectionStatus = "partial"     // budget/timeout-truncated but usable
	StatusSkipped     SectionStatus = "skipped"     // level/runtime/feature gated out
	StatusUnavailable SectionStatus = "unavailable" // dependency down (DB, agent)
	StatusFailed      SectionStatus = "failed"      // collector error/panic
)

SectionStatus values a collector run can end in.

type SupportBundleManifest

type SupportBundleManifest struct {
	Format      string          `json:"format"`
	BundleID    string          `json:"bundle_id"`
	CreatedAt   string          `json:"created_at"` // RFC3339 UTC
	GeneratedBy GeneratedBy     `json:"generated_by"`
	Node        NodeInfo        `json:"node"`
	Scope       ScopeInfo       `json:"scope"`
	CaseID      string          `json:"case_id,omitempty"`
	Redaction   RedactionInfo   `json:"redaction"`
	Sections    []SectionEntry  `json:"sections"`
	Integrity   IntegrityInfo   `json:"integrity"`
	Collection  CollectionStats `json:"collection"`
}

SupportBundleManifest is manifest.json — always the FIRST tar entry.

Jump to

Keyboard shortcuts

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