hostpreflight

package
v0.42.0 Latest Latest
Warning

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

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

Documentation

Overview

Package hostpreflight observes a target host and admits or refuses a local Apply before it mutates anything.

The package answers one question: can this device run what this kit declares it needs? It only reports what it measured — an unobservable fact is stated as unknown, never assumed to pass. Requirements come from the kit's CUE authority, never from constants here, so a kit cannot silently change what it demands of a device.

Index

Constants

View Source
const SchemaVersion = "stackkit.host-preflight/v1"

SchemaVersion identifies the machine-readable preflight report.

Variables

View Source
var ErrResolutionNotExecutable = errors.New("resolution cannot be carried out here")

ErrResolutionNotExecutable is returned for a resolution this host or this process may not carry out. It is a refusal, not a failure: nothing ran.

Functions

func Executable

func Executable(resolution Resolution) (bool, string)

Executable reports whether ApplyResolution could run this resolution now, and why not when it could not. It performs no probing and changes nothing, so a caller can list honest options before asking for consent.

func ValidPolicy

func ValidPolicy(value string) bool

ValidPolicy reports whether value names a supported policy.

Types

type Baseline

type Baseline struct {
	SchemaVersion    string          `json:"schemaVersion"`
	Revision         string          `json:"revision"`
	PlanHash         string          `json:"planHash,omitempty"`
	NodeRef          string          `json:"nodeRef,omitempty"`
	ObservedAt       time.Time       `json:"observedAt"`
	ExpiresAt        time.Time       `json:"expiresAt"`
	BootID           string          `json:"bootId,omitempty"`
	NetworkNamespace string          `json:"networkNamespace,omitempty"`
	Scopes           []BaselineScope `json:"scopes"`
}

Baseline is scoped evidence, never host-wide absence or permission to adopt. Its own version lets consumers distinguish older reports without this evidence.

type BaselineScope

type BaselineScope struct {
	Kind     string   `json:"kind"`
	Coverage string   `json:"coverage"`
	Source   string   `json:"source"`
	Records  []string `json:"records"`
}

type Check

type Check struct {
	ID           string   `json:"id"`
	Status       Status   `json:"status"`
	Summary      string   `json:"summary"`
	FailureClass string   `json:"failureClass,omitempty"`
	Remediation  []string `json:"remediation,omitempty"`
}

Check is one performed admission check.

func FindCheck

func FindCheck(report Report, id string) *Check

FindCheck returns the named check from a report.

type DiskFact

type DiskFact struct {
	Path     string  `json:"path"`
	FreeGB   float64 `json:"freeGb"`
	Observed bool    `json:"observed"`
}

DiskFact is free space at one path that Apply will write to.

type DockerFacts

type DockerFacts struct {
	BinaryPresent        bool   `json:"binaryPresent"`
	DaemonReachable      bool   `json:"daemonReachable"`
	PermissionDenied     bool   `json:"permissionDenied"`
	ServerVersion        string `json:"serverVersion,omitempty"`
	ComposePluginVersion string `json:"composePluginVersion,omitempty"`
	StorageDriver        string `json:"storageDriver,omitempty"`
	CgroupDriver         string `json:"cgroupDriver,omitempty"`
	CgroupVersion        string `json:"cgroupVersion,omitempty"`
	MemoryLimitSupported bool   `json:"memoryLimitSupported"`
	SwapLimitSupported   bool   `json:"swapLimitSupported"`
	Rootless             bool   `json:"rootless"`
	RootDir              string `json:"rootDir,omitempty"`
	Diagnostic           string `json:"diagnostic,omitempty"`
}

DockerFacts is what one `docker info` and one `docker compose version` observation proves about the container runtime.

type Facts

type Facts struct {
	ObservedAt     time.Time                       `json:"observedAt"`
	OS             string                          `json:"os"`
	Distribution   string                          `json:"distribution,omitempty"`
	OSVersion      string                          `json:"osVersion,omitempty"`
	Architecture   string                          `json:"architecture"`
	KernelRelease  string                          `json:"kernelRelease,omitempty"`
	Virtualization string                          `json:"virtualization,omitempty"`
	CPUCores       int                             `json:"cpuCores"`
	CPUBaseline    *bool                           `json:"cpuBaselineX8664V2,omitempty"`
	InitSystem     hostconformance.InitSystemFacts `json:"initSystem"`
	Memory         MemoryFacts                     `json:"memory"`
	CgroupVersion  string                          `json:"cgroupVersion,omitempty"`
	MemoryCgroup   *bool                           `json:"memoryCgroupController,omitempty"`
	NamespacesOK   *bool                           `json:"namespacesAvailable,omitempty"`
	ClockSynced    *bool                           `json:"clockSynchronized,omitempty"`
	Docker         DockerFacts                     `json:"docker"`
	Disks          []DiskFact                      `json:"disks,omitempty"`
	Ports          []PortFact                      `json:"ports,omitempty"`
	Baseline       *Baseline                       `json:"baseline,omitempty"`
}

Facts is everything the probe measured about the host.

func Observe

func Observe(ctx context.Context, request ObserveRequest) Facts

Observe measures the host. It never returns an error for an unobservable fact: the fact is recorded as unobserved and the evaluation decides what that means under the active policy.

type FileChange

type FileChange struct {
	Path string `json:"path"`
	Mode uint32 `json:"mode"`

	Merge   map[string]any `json:"merge,omitempty"`
	Content string         `json:"content,omitempty"`
	Append  string         `json:"append,omitempty"`

	// AppendUnlessPresent keeps an append idempotent: the line is added only
	// when the file does not already mention this substring.
	AppendUnlessPresent string `json:"appendUnlessPresent,omitempty"`

	Backup bool `json:"backup"`
}

FileChange is one declared file edit. Merge, Content and Append are exclusive: a merge folds keys into an existing JSON document without disturbing the rest, content writes a self-contained drop-in, and append adds a line to a file that must otherwise stay as it is.

type ListenerRequirement

type ListenerRequirement struct {
	ID          string `json:"id"`
	NodeRef     string `json:"nodeRef"`
	Transport   string `json:"transport"`
	BindAddress string `json:"bindAddress"`
	Port        int    `json:"port"`
}

ListenerRequirement is a projection of a verified compiler listener. Container target ports, URLs and observed sockets never become requirements.

func ListenersFromPlan

func ListenersFromPlan(plan resolvedplan.ResolvedPlan, nodeRef string) ([]ListenerRequirement, error)

type MemoryFacts

type MemoryFacts struct {
	TotalGB     float64 `json:"totalGb"`
	AvailableGB float64 `json:"availableGb"`
	SwapGB      float64 `json:"swapGb"`
	Observed    bool    `json:"observed"`
}

MemoryFacts reports host memory in gibibytes. Available and Swap are read separately from Total because a host with enough installed memory can still be unable to start a workload.

type Mode

type Mode string

Mode says whether a resolution can be carried out or only described.

const (
	// ModeHint is advice. Something outside StackKits' authority has to change:
	// a credential, a hypervisor setting, a decision only the owner can make.
	ModeHint Mode = "hint"
	// ModeApply can be carried out on this host.
	ModeApply Mode = "apply"
)

type ObserveRequest

type ObserveRequest struct {
	WorkspacePath           string
	PriorRuntimeComposePath string // Verified signed upgrade checkpoint only.
	RequiredPorts           []int  // Legacy diagnostic callers only; Apply uses compiler listeners.
	RequiredListeners       []ListenerRequirement
	PlanHash                string
	NodeRef                 string
}

ObserveRequest names the paths and ports this Apply will actually use, so the probe measures the host the rollout touches rather than a generic machine.

type Policy

type Policy string

Policy decides how a report is turned into an admission decision.

const (
	// PolicyWarn admits the host unless a check is blocking. It is the default:
	// an unverifiable fact must not stop a rollout that would otherwise work.
	PolicyWarn Policy = "warn"
	// PolicyStrict additionally refuses on warnings and unknown facts.
	PolicyStrict Policy = "strict"
	// PolicySkip performs no observation and admits the host.
	PolicySkip Policy = "skip"
)

type PortFact

type PortFact struct {
	ListenerID            string `json:"listenerId,omitempty"`
	Transport             string `json:"transport,omitempty"`
	BindAddress           string `json:"bindAddress,omitempty"`
	Namespace             string `json:"namespace,omitempty"`
	Observed              bool   `json:"observed"`
	Port                  int    `json:"port"`
	InUse                 bool   `json:"inUse"`
	OwnedByCurrentRuntime bool   `json:"ownedByCurrentRuntime,omitempty"`
	Detail                string `json:"detail,omitempty"`
}

PortFact records whether a port Apply must publish is already bound.

func ObserveListeners

func ObserveListeners(ctx context.Context, workspace string, listeners []ListenerRequirement) []PortFact

ObserveListeners measures only the exact host bindings being changed. A bind probe is immediately closed, not a reservation. Apply must still handle a foreign process winning the race afterwards without stopping that process.

type Record

type Record struct {
	ID         string       `json:"id"`
	Title      string       `json:"title"`
	AppliesTo  string       `json:"appliesTo"`
	Status     string       `json:"status"`
	StartedAt  time.Time    `json:"startedAt"`
	FinishedAt time.Time    `json:"finishedAt"`
	Steps      []StepRecord `json:"steps,omitempty"`
	Before     *Check       `json:"before,omitempty"`
	After      *Check       `json:"after,omitempty"`
	Reason     string       `json:"reason,omitempty"`
}

Record is the account of one remediation: what was declared, what ran, and what the host looked like before and after.

The before/after pair is the point. A fix that reports success while the condition it targeted is unchanged is the same lie as a green checkmark over a failed rollout, so the check is re-measured rather than assumed.

func ApplyResolution

func ApplyResolution(ctx context.Context, resolution Resolution) (Record, error)

ApplyResolution carries out one resolution and returns the account of it.

Files are written before commands so a service restart observes the configuration it is being restarted for. A failed step stops the rest: the remaining steps assume the earlier ones took effect, and running them anyway is how a partially changed host becomes an unexplainable one.

type Report

type Report struct {
	SchemaVersion string `json:"schemaVersion"`
	Policy        Policy `json:"policy"`
	// Status is the worst measured check; Admitted is the policy decision made
	// from it. They stay separate so a strict refusal still shows what it
	// refused on, and a warn-policy pass still shows what was degraded.
	Status       Status       `json:"status"`
	Admitted     bool         `json:"admitted"`
	KitSlug      string       `json:"kitSlug,omitempty"`
	Requirements Requirements `json:"requirements"`
	Facts        Facts        `json:"facts"`
	Checks       []Check      `json:"checks"`
}

Report is the machine-readable preflight result.

func Evaluate

func Evaluate(facts Facts, requirements Requirements, kitSlug string, policy Policy) Report

Evaluate turns measured facts into an admission decision under one policy.

A check that could not be measured reports unknown rather than pass, so a probe that silently failed can never admit a host it did not verify.

func EvaluateListenerAdmission

func EvaluateListenerAdmission(ctx context.Context, request ObserveRequest, kitSlug string) Report

EvaluateListenerAdmission is the mandatory collision check when optional resource diagnostics are skipped. It cannot turn missing binding evidence into permission to mutate the host.

func (Report) BlockedChecks

func (r Report) BlockedChecks() []Check

BlockedChecks returns the checks that caused a refusal, in report order.

func (Report) Blocking

func (r Report) Blocking() bool

Blocking reports whether this report refuses the host under its policy.

type Requirements

type Requirements struct {
	MinCPUCores          int     `json:"minCpuCores"`
	MinRAMGB             int     `json:"minRamGB"`
	MinStorageGB         int     `json:"minStorageGB"`
	RecommendedCPUCores  int     `json:"recommendedCpuCores"`
	RecommendedRAMGB     int     `json:"recommendedRamGB"`
	RecommendedStorageGB int     `json:"recommendedStorageGB"`
	HeadroomFactor       float64 `json:"headroomFactor"`
}

Requirements is the kit-declared host floor, projected from the CUE KitDefinition. Zero values mean the kit declared nothing and the matching check reports unknown rather than inventing a threshold.

func RequirementsFromDefinition

func RequirementsFromDefinition(definition map[string]any) Requirements

RequirementsFromDefinition projects the kit-declared host floor out of a decoded foundation.#KitDefinition. A kit that declares nothing yields a zero Requirements, and the resource checks then report unknown instead of inventing a threshold.

func RequirementsFromDefinitionForTier

func RequirementsFromDefinitionForTier(definition map[string]any, tier string) Requirements

RequirementsFromDefinitionForTier uses computeTierGraphs[tier].hostRequirements when the kit declared that graph, otherwise the kit-level hostRequirements.

func (Requirements) Declared

func (r Requirements) Declared() bool

Declared reports whether the kit supplied a usable floor.

type Resolution

type Resolution struct {
	ID        string `json:"id"`
	Title     string `json:"title"`
	AppliesTo string `json:"appliesTo"`
	Mode      Mode   `json:"mode"`
	Summary   string `json:"summary"`

	// Files are applied before Commands, so a service restart observes the
	// configuration it is being restarted for.
	Files    []FileChange `json:"files,omitempty"`
	Commands [][]string   `json:"commands,omitempty"`

	// Guidance is what to tell the operator: the whole content of a hint, and
	// the caveats of an apply.
	Guidance []string `json:"guidance,omitempty"`

	RequiresRoot   bool `json:"requiresRoot"`
	RequiresReboot bool `json:"requiresReboot"`

	// Reversible means this host can be put back the way it was: a backup is
	// kept, or the change is a drop-in that can simply be removed.
	Reversible bool `json:"reversible"`

	// AutoInstallerEligible marks a resolution the installer may carry out in
	// its unattended mode. It requires Reversible, no reboot and no credential:
	// a fix nobody is watching must be one nobody regrets.
	AutoInstallerEligible bool `json:"autoInstallerEligible"`
}

Resolution is one fix, bound to the check that justifies it.

func ResolutionByID

func ResolutionByID(id string) (Resolution, bool)

ResolutionByID returns one catalog entry.

func Resolutions

func Resolutions() []Resolution

Resolutions is the closed catalog, ordered by the check they answer.

func ResolutionsForReport

func ResolutionsForReport(report Report) []Resolution

ResolutionsForReport returns the resolutions that answer what this report actually found. A passing check needs no fix, so only warnings, blocks and unknowns bring one forward.

type Status

type Status string

Status is the closed outcome vocabulary for a single check and for a report.

const (
	// StatusPass means the check was performed and satisfied.
	StatusPass Status = "pass"
	// StatusWarning means the check was performed and the host is usable but
	// degraded. Apply proceeds.
	StatusWarning Status = "warning"
	// StatusBlocked means the check was performed and Apply must not mutate
	// this host.
	StatusBlocked Status = "blocked"
	// StatusUnknown means the fact could not be observed. It never silently
	// passes: strict policy treats it as blocking.
	StatusUnknown Status = "unknown"
	// StatusSkipped means the check does not apply to this host.
	StatusSkipped Status = "skipped"
)

type StepRecord

type StepRecord struct {
	Kind   string   `json:"kind"`
	Target string   `json:"target"`
	Argv   []string `json:"argv,omitempty"`
	Status string   `json:"status"`
	Detail string   `json:"detail,omitempty"`
}

StepRecord is what one declared change actually did.

Jump to

Keyboard shortcuts

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