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
- func Register(c Collector)
- type BuildInfo
- type BuildOptions
- type BuildResult
- type CollectInput
- type CollectionError
- type CollectionStats
- type Collector
- type CollectorMeta
- type DebugLevel
- type GeneratedBy
- type IntegrityInfo
- type NodeInfo
- type RedactionInfo
- type RedactionPreview
- type RedactionPreviewSection
- type RedactionReport
- type RedactionReportCounts
- type RedactionReportSection
- type Result
- type Runner
- type RuntimeInfo
- type ScopeInfo
- type SectionEntry
- type SectionSink
- type SectionStatus
- type SupportBundleManifest
Constants ¶
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).
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 ¶
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 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.