Documentation
¶
Overview ¶
Package team implements the team controller — a reconciliation loop that maintains desired replica count for each role within each team, and orchestrates shift operations (rolling session replacement).
Index ¶
- Constants
- Variables
- type Controller
- func (c *Controller) ActiveTicks(sessKey string, window time.Duration, now time.Time) (active, total int, full bool)
- func (c *Controller) ActivityOf(sess api.Session, now time.Time) api.ActiveTicks
- func (c *Controller) AdmissionHold(workspace, team, role string) (string, bool)
- func (c *Controller) ClearRoleHealthForRole(workspace, team, role string) bool
- func (c *Controller) ClearRoleHealthForTeam(workspace, team string)
- func (c *Controller) ClearRoleHealthForWorkspace(workspace string)
- func (c *Controller) ClusterQuietWindow() time.Duration
- func (c *Controller) DeleteTeam(t api.Team) error
- func (c *Controller) InitConvergencePosture() error
- func (c *Controller) InitiateShift(teamKey, role string) error
- func (c *Controller) IsShifting(teamKey string) bool
- func (c *Controller) NoteViewMoved(sess api.Session, view, path, previous, commit string)
- func (c *Controller) PlanConvergence() []RolePlan
- func (c *Controller) ProjectHeldRoleRows(store *api.Store) ([]api.Session, map[string]string)
- func (c *Controller) Prune(keys []string, p api.TeamPruning, check func(*api.Team, []api.Session) string) (deleted []string, draining []api.PruneDraining, skipped []api.PruneSkipped)
- func (c *Controller) PutHandoff(token, text string) (HandoffPut, error)
- func (c *Controller) ReconcileOnce()
- func (c *Controller) RefreshLiveness()
- func (c *Controller) RehydrateRoleHealth() error
- func (c *Controller) RoleHealthSnapshot(workspace, team, role string) (*RoleHealth, bool)
- func (c *Controller) Run(ctx context.Context, interval time.Duration)
- func (c *Controller) SetClusterQuietWindow(d time.Duration)
- func (c *Controller) SetConvergencePosture(teamKey string, posture api.ConvergencePosture) ([]RolePlan, error)
- func (c *Controller) SnapshotRoleHealth() map[string]RoleHealth
- func (c *Controller) TeamLoad() []api.TeamLoad
- func (c *Controller) ViewTreesHeld(sess api.Session, view string) int
- type ExposureSource
- type HandoffPublisher
- type HandoffPut
- type HoldReason
- type RoleAction
- type RoleHealth
- type RolePlan
- type Snapshotter
- type TickRing
Constants ¶
const ( NoticeMaxAge = "max-age" NoticeContextPressure = "context-pressure" // NoticeRetire is a retiring seat's handoff ask (docs/design/work-prune.md // section 6.1). NoticeRetire = "retire" NoticeViewNotice = "view-notice" )
The origins a Notify caller names. The daemon records them as injector=marvel:<origin>.
const MaxHandoffPutBytes = api.MaxHandoffPutBytes
MaxHandoffPutBytes caps the text one handoff.put may carry.
const TickRingWindow = 15 * time.Minute
TickRingWindow is how much evaluation history a session's ring holds (docs/design/get-sessions-output.md section 4.4).
Variables ¶
var ( // record that carries no token hash (a handoff is a write, so it does not // inherit the heartbeat's fail-open for unbound records). ErrHandoffUnauthorized = errors.New("handoff.put: token does not identify a bound session") // ErrHandoffNotRequested: the authenticated session does not hold the // role's pending max-age request. ErrHandoffNotRequested = errors.New("handoff.put: this session holds no pending handoff request") // ErrHandoffNoPath: the role declares no handoff file for marvel to write. ErrHandoffNoPath = errors.New("handoff.put: the role declares no handoff file") // ErrHandoffTooLarge: the text is empty or over MaxHandoffPutBytes. ErrHandoffTooLarge = errors.New("handoff.put: text is empty or too large") // ErrHandoffDir: the handoff directory is not the directory marvel // recorded when it asked, or it or an ancestor is a link, or the target // is not a regular file. ErrHandoffDir = errors.New("handoff.put: handoff directory refused") )
Refusals of a handoff.put. Each is wrapped with the detail a caller or an event needs; callers match with errors.Is.
Functions ¶
This section is empty.
Types ¶
type Controller ¶
type Controller struct {
SocketPath string
// Events receives structured state-transition events. Nil is safe.
Events events.Emitter
// Publisher delivers a handoff-missing escalation to the team's
// supervisor role over the bus. Nil means event-only (handoff-missing-delivery.md).
Publisher HandoffPublisher
// BusGate reports whether the cluster bus can take new sessions and, when
// it cannot, why. Nil means no managed bus, so no gate. A spawn withheld
// by the gate is RoleHold with HoldBus and one bus.unavailable per role
// per transition, the admission-latch shape (aae-orc-xy1dh).
BusGate func() (ready bool, reason string)
// Exposures is where the exposure gate reads what a slot has read. Nil
// means the store. A role with no Writes never reads it (marvel#945).
Exposures ExposureSource
// ShiftTimeout bounds how long a single shift may run before the
// reconciler declares it stuck and aborts it. Zero uses
// defaultShiftTimeout. See aae-orc-qkfl.
ShiftTimeout time.Duration
// Snapshots supplies the measured state a full admission check needs.
// Nil is safe and means count-shaped clauses only, which is all the
// reconciler ever evaluates anyway (R2 in internal/admission: gating
// repair on a monotonic meter would be an outage). It exists for
// InitiateShift, whose cumulative clause needs the daemon's meter, and
// keeps this package free of any usage import.
Snapshots Snapshotter
// Notify delivers a line of text to a session's pane, as `marvel inject`
// does. The max-age trigger uses it to ask a seat for its handoff
// (design D5 step 1), and the view notice uses it to say a view moved. The
// origin names which, so the inject record and any refusal carry the right
// injector. Nil means no delivery: the request is still recorded, and its
// event says the notice did not reach the pane.
Notify func(sess api.Session, text, origin string) error
// SealViewTrees hollows and seals the named superseded trees of a seat's
// view. Nil means nothing is sealed and the trees stay readable until
// teardown (docs/design/readonly-view.md section 6).
SealViewTrees func(sess api.Session, view string, commits []string) error
// CaptureScreen reads a session's pane history, and RetiredDir is where a
// retired seat's last screen is saved (<marvel home>/retired). The daemon
// sets both; with either unset a retirement saves no screen and says so.
CaptureScreen func(sess api.Session) (string, error)
RetiredDir string
// OnPruned, when set, is told the teams a reconcile pass deleted as the
// end of a prune, after c.mu is released, so the daemon can revoke their
// broker users as marvel delete team does.
OnPruned func(teams []string)
// contains filtered or unexported fields
}
Controller reconciles desired team state with actual running sessions.
func NewController ¶
func NewController(store *api.Store, sessMgr *session.Manager) *Controller
NewController creates a team controller.
func (*Controller) ActiveTicks ¶
func (c *Controller) ActiveTicks(sessKey string, window time.Duration, now time.Time) (active, total int, full bool)
ActiveTicks is the counts for one session's ring, or zero and not full for a session the controller has not evaluated.
func (*Controller) ActivityOf ¶
func (c *Controller) ActivityOf(sess api.Session, now time.Time) api.ActiveTicks
ActivityOf is the tick-ring reading for one session, judged under its window: the role's activity_timeout, else the operator's window, else the default (api.QuietWindow). Observable says whether marvel has an activity channel for the seat, and At is the newest tick the ring holds. A session the controller has not evaluated reads no ticks and is not full.
func (*Controller) AdmissionHold ¶
func (c *Controller) AdmissionHold(workspace, team, role string) (string, bool)
AdmissionHold returns a role's latched verdict key, for tests and for operator-facing diagnostics. Returns ("", false) when the role is not held.
func (*Controller) ClearRoleHealthForRole ¶
func (c *Controller) ClearRoleHealthForRole(workspace, team, role string) bool
ClearRoleHealthForRole deletes crash-loop state for a single role — the operator's "this role is fine now" verb, exposed as `marvel reset-health`. It is the single-role sibling of ClearRoleHealthForTeam: it removes one role's accumulated RestartCount and backoff (including a saturation or restart_policy=never freeze) without deleting the whole team, which was the only prior route to clearing a role's restart history. Returns whether a record existed, so the caller can distinguish a reset from a no-op. Success- based decay (see decayHealthyRoles) is the automatic counterpart; this verb is the manual override, and the only sanctioned way to thaw a frozen role short of a team delete. See aae-orc-fv3h.
func (*Controller) ClearRoleHealthForTeam ¶
func (c *Controller) ClearRoleHealthForTeam(workspace, team string)
ClearRoleHealthForTeam deletes crash-loop state for every role under the given (workspace, team). Called from the cascade delete path in daemon.handleDelete so that a subsequent re-apply of the same manifest starts with a fresh RestartCount and BackoffUntil — without this, accumulated state survives workspace/team delete (the map is keyed by name, which the operator is free to reuse) and the reconciler refuses spawns until the prior generation's backoff window elapses. If the prior generation hit MaxRestarts saturation, BackoffUntil would be frozen far in the future and the role would never recover. See ArcavenAE/marvel#29.
func (*Controller) ClearRoleHealthForWorkspace ¶
func (c *Controller) ClearRoleHealthForWorkspace(workspace string)
ClearRoleHealthForWorkspace deletes crash-loop state for every role under every team in the given workspace. Called from the workspace- delete cascade. See ClearRoleHealthForTeam for the rationale.
func (*Controller) ClusterQuietWindow ¶
func (c *Controller) ClusterQuietWindow() time.Duration
ClusterQuietWindow is the operator's watchdog.window as the controller holds it, zero when unset.
func (*Controller) DeleteTeam ¶
func (c *Controller) DeleteTeam(t api.Team) error
DeleteTeam deletes the team at key with its sessions, and forgets its roles' crash-loop state so a later re-apply starts fresh (ArcavenAE/marvel#29). It is marvel delete team, and the delete a prune ends with. The caller revokes the team's broker user after, as both do.
func (*Controller) InitConvergencePosture ¶
func (c *Controller) InitConvergencePosture() error
InitConvergencePosture sets each team's convergence posture from the ground truth left by adoption (and RefreshLiveness's reap): a team with any live session is PostureConverge — a daemon restarted under an operator, its fleet still running, is maintained, crash-loop restarts and top-ups included; a team with none is PostureHold — a fresh or stale start holds at the start line and waits for `marvel converge`.
This is the money-safety guarantee for aae-orc-cxdf. It OVERRIDES whatever posture the store rehydrated, so a stale bolt that persisted PostureConverge cannot cold-spawn a fleet nobody asked for: only surviving panes yield converge. It answers the ticket's central question (a daemon restarted under an operator vs one started fresh) with a fact the daemon can observe — the panes — rather than a guess.
Called once from daemon Start after adoption + RefreshLiveness, before the reconcile loop. tu2e (resume-on-power) will let an operator opt a team out of the hold default. Takes c.mu; must not be called by a caller already holding it.
func (*Controller) InitiateShift ¶
func (c *Controller) InitiateShift(teamKey, role string) error
InitiateShift starts a shift operation for a team. If role is empty, all roles shift (supervisor last). If role is specified, only that role shifts.
func (*Controller) IsShifting ¶
func (c *Controller) IsShifting(teamKey string) bool
IsShifting returns true if the team is currently shifting.
func (*Controller) NoteViewMoved ¶
func (c *Controller) NoteViewMoved(sess api.Session, view, path, previous, commit string)
NoteViewMoved records that a seat's view moved to commit. It replaces a notice not yet delivered, keeping the time of the first undelivered move, so the deferral stays bounded however many refreshes happen. A move after a delivered notice starts a new one and clears the record for the old commit. It is the keeper's hook and takes only the store's lock, never the controller's, so a refresh never waits for a reconcile pass.
func (*Controller) PlanConvergence ¶
func (c *Controller) PlanConvergence() []RolePlan
PlanConvergence returns the steady-state convergence plan for every team not mid-shift — the sessions the next reconcile tick would spawn or scale down, computed without enacting anything. It is the read-only companion to ReconcileOnce's role loop and the inspection seam the dry-run surface (aae-orc-nrk1), the hold posture (aae-orc-rwiw), and the startup report build on. It takes c.mu and must NOT be called by a caller already holding it (the mutex is not reentrant).
Two fidelity limits a consumer must account for; both are properties of this aggregate view, not of any single RolePlan, and neither affects the live reconciler (which never calls this):
- Budgeted multi-role teams are evaluated per role against CURRENT live state, because no plan is applied between roles here. A real tick applies each role before planning the next (that is why admissionVerdict recounts per role), so when several roles share one MaxSessions the summed Spawn across the returned plans can exceed the headroom a real sequence of ticks would grant. A consumer that needs a budget-accurate multi-role preview must simulate sequential application itself.
- A team mid-shift contributes nothing. reconcileTeam defers such a team to reconcileShift, which reconciles roles at shift-aware generations; that path is out of scope for this steady-state view, so a dry-run built on it under-reports repairs that happen during a rotation.
func (*Controller) ProjectHeldRoleRows ¶
ProjectHeldRoleRows joins the two truths marvel keeps about a role — its live session rows (store.sessions) and its crash-loop state (RoleHealth) — into the synthetic rows `get sessions` needs so a held-down role reads as held rather than absent. restartSession deletes a crash-looping session's row before setting the backoff, so a single-replica restart_policy=always role is missing from the session table for the whole (growing) backoff window; the absence is indistinguishable from "never declared" or "deleted".
For each declared role whose live row count is below its replica count AND whose gap RoleHealth explains, it returns Replicas-rowCount synthetic rows carrying the derived crash-loop state. Roles at or above their replica count get nothing — a saturated or restart_policy=never role keeps its terminal SessionFailed row, which is why aae-orc-83m9's "saturated role is absent" premise is refuted here: that row never left. A gap with no RoleHealth also gets nothing — that is a mid-flight spawn the reconciler is about to fill, and inventing a row there would be noise.
This is read-model composition only: the reconciler, its counting, its tests, and RoleHealth persistence are untouched. SnapshotRoleHealth is taken under c.mu and released before the store reads, so c.mu and the store lock are never held together. See aae-orc-prhx, aae-orc-83m9.
func (*Controller) Prune ¶
func (c *Controller) Prune(keys []string, p api.TeamPruning, check func(*api.Team, []api.Session) string) (deleted []string, draining []api.PruneDraining, skipped []api.PruneSkipped)
Prune marks, re-checks and deletes each team of a prune set in one hold of c.mu, so no reconcile pass runs between the mark and the delete (docs/design/work-prune.md section 5). check is the prune's rule set for one team: "" when it may be pruned, else why it is kept or deferred. It runs at the mark, in the store's lock, and again in the store hold that would delete the team's record, with the drain rule after it. A team with no seat left is deleted there, its sessions after the record, exactly those the last check saw. A team whose seats are still leaving stays marked and is returned in draining: the reconcile retires its seats, and finishPrune deletes it once it is empty. A team that fails a check is not deleted, its Pruning is cleared, and it is returned in skipped with the reason. Each list is in key order.
func (*Controller) PutHandoff ¶
func (c *Controller) PutHandoff(token, text string) (HandoffPut, error)
PutHandoff writes the handoff of the session that owns token, and of no other. The write gate has four parts, and a put is refused, with an event naming the reason, unless all hold:
- The token is bound and matches a session that is running, not ending, and was created before the request it holds (the rule the shift path and the store's write-authority lookup already use). The request names no session.
- That session holds the role's pending request (one per role), not escalated.
- The path comes from the role's declared template and the stored session, never from the request.
- No link is followed in the file or its directory, and the directory is the one recorded when marvel asked: checked on the opened handle, so a swap after the check cannot redirect the write.
The file is written off the controller lock (marvel#444: a slow filesystem must not stall every team). The request is checked under the lock before the write and again after it; a request that was dropped, replaced or escalated in between has its file removed again and the put is refused. Same-uid seats are not isolated from each other by marvel; the residuals are stated in the design (section 2). The text is capped, never truncated.
func (*Controller) ReconcileOnce ¶
func (c *Controller) ReconcileOnce()
ReconcileOnce runs one reconciliation pass for all teams. Reaps dead sessions first so the reconciler sees accurate state.
func (*Controller) RefreshLiveness ¶
func (c *Controller) RefreshLiveness()
RefreshLiveness reaps sessions whose panes have vanished so the store's alive-count reflects the panes that actually survived, WITHOUT reconciling desired state (no spawns). The daemon calls it at start after adoption and before InitConvergencePosture: a team whose panes did not survive (a host reboot leaves stale Running records with dead panes) must be counted as the cold team it is, not read as alive and given the converge posture. Takes c.mu; must not be called by a caller already holding it.
func (*Controller) RehydrateRoleHealth ¶
func (c *Controller) RehydrateRoleHealth() error
RehydrateRoleHealth loads persisted crash-loop state into the controller's map. Called at daemon start after Store.OpenBolt has rehydrated the rest of L2, so a role sitting in a backoff window (or frozen at MaxRestarts saturation) stays held back across a daemon restart instead of getting a free respawn on the first reconcile tick. No-op when persistence is disabled. See aae-orc-qdew.
func (*Controller) RoleHealthSnapshot ¶
func (c *Controller) RoleHealthSnapshot(workspace, team, role string) (*RoleHealth, bool)
RoleHealthSnapshot returns the current restart state for a role, useful for tests and for `marvel describe team` observability. Returns (nil, false) if the role has no recorded crash-loop history. The result is a full value copy of RoleHealth (flat ints and timestamps, so a struct copy is a deep copy — go.md rule 12), including HealthySince: an operator needs it to tell a role about to decay from one that just started its window, and copying the whole struct means a future field cannot be silently dropped here the way HealthySince once was.
func (*Controller) Run ¶
func (c *Controller) Run(ctx context.Context, interval time.Duration)
Run starts the reconciliation loop, reconciling every interval. Blocks until ctx is cancelled.
func (*Controller) SetClusterQuietWindow ¶
func (c *Controller) SetClusterQuietWindow(d time.Duration)
SetClusterQuietWindow sets the operator's watchdog.window, zero when unset. The daemon sets it at start.
func (*Controller) SetConvergencePosture ¶
func (c *Controller) SetConvergencePosture(teamKey string, posture api.ConvergencePosture) ([]RolePlan, error)
SetConvergencePosture sets the convergence posture for one team, or for every team when teamKey is empty, persisting it, and returns the steady-state plans the posture now permits so a caller can report what a converge will do. It does NOT reconcile — the caller drives that (the converge RPC reconciles immediately; a future majordomo may batch). This is the control-plane lever behind `marvel converge`; the CLI is one client of it, so the same lever is available to an in-process assessor. Takes c.mu; must not be called by a caller already holding it.
func (*Controller) SnapshotRoleHealth ¶
func (c *Controller) SnapshotRoleHealth() map[string]RoleHealth
SnapshotRoleHealth returns a value copy of every role's crash-loop state, keyed workspace/team/role. RoleHealth is flat (ints and timestamps), so a struct copy is a full deep copy; the result is decoupled from the live map and safe to read without holding c.mu (go.md rule 12).
func (*Controller) TeamLoad ¶
func (c *Controller) TeamLoad() []api.TeamLoad
TeamLoad is the per-team load rows the last evaluation tick computed, a copy the caller may keep. It is empty before the first tick.
func (*Controller) ViewTreesHeld ¶
func (c *Controller) ViewTreesHeld(sess api.Session, view string) int
ViewTreesHeld is how many superseded trees of a seat's view are still readable. The keeper pauses the view's refresh at view.MaxHeldTrees.
type ExposureSource ¶
type ExposureSource = api.ExposureReader
ExposureSource is where the planner reads what a slot has read. The store satisfies it; a test supplies one that fails, to prove the gate fails closed.
type HandoffPublisher ¶
type HandoffPublisher interface {
PublishRoleInbox(ctx context.Context, ws, team, role string, env envelope.Envelope, msgID string) error
}
HandoffPublisher is the controller's only bus capability: it publishes one envelope to a role's inbox. The implementation builds the subject itself from validated tokens and can publish nowhere else, because the principal behind it can publish to ">" (docs/design/handoff-missing-delivery.md, section 4). Nil means no managed bus, which is event-only escalation.
type HandoffPut ¶
type HandoffPut struct {
// Path is the handoff file marvel wrote.
Path string
// Bytes is the size of the file written, marker included.
Bytes int
}
HandoffPut is what a successful handoff.put reports.
type HoldReason ¶
type HoldReason string
HoldReason names why a would-be spawn is withheld, in whole or in part. It is set whenever the plan spawns fewer sessions than (Desired-Actual): on a full hold (Action==RoleHold) and on a partial admission grant (Action==RoleSpawn with Spawn < Desired-Actual).
const ( // HoldNone means nothing is withheld. HoldNone HoldReason = "" // HoldBackoff means the role is inside its crash-loop backoff window (or // frozen at MaxRestarts / restart_policy=never saturation). HoldBackoff HoldReason = "crash_loop_backoff" // HoldAdmission means a team budget refused some or all of the spawn. HoldAdmission HoldReason = "admission" // HoldBus means the cluster's managed bus is not ready: down, restarting // under backoff, or not yet started. Spawning would hand a session a // NATS_URL nothing answers on. HoldBus HoldReason = "bus_unavailable" // HoldExposure means the role writes a function its slot has read, or the // record of what the slot has read could not be consulted (marvel#945). HoldExposure HoldReason = "exposure" // HoldSchedule means the role is on a schedule and its firing record // allows no spawn now: no firing is due yet, the firing settled, a // retry is backing off, or on_failure froze the schedule. Without it a // failed run would be refilled at once, under a restart policy that // does not apply to a scheduled role (ADR-010 Amendment 1). HoldSchedule HoldReason = "schedule" )
type RoleAction ¶
type RoleAction string
RoleAction is the convergence verb planRole chose for a role this tick. It summarizes the RolePlan for a reader — a dry-run print, a startup report; the payload fields (Spawn, Delete, Hold) carry the detail.
const ( // RoleSteady means the alive replica count already matches desired — // nothing to do. RoleSteady RoleAction = "steady" // RoleSpawn means the plan creates Spawn new sessions. On a partial // admission grant, Action is RoleSpawn with Spawn < Desired-Actual and Hold // set to HoldAdmission — so a reader must consult Hold even when the action // is a spawn. RoleSpawn RoleAction = "spawn" // RoleScaleDown means the plan deletes the sessions in Delete. RoleScaleDown RoleAction = "scale_down" // RoleHold means actual is below desired but nothing is spawned this tick — // withheld by crash-loop backoff or a full admission refusal. Hold names // which. RoleHold RoleAction = "hold" )
type RoleHealth ¶
type RoleHealth struct {
RestartCount int
LastRestartAt time.Time
// BackoffUntil is the wall-clock after which the next restart is
// allowed. Sessions whose role is inside the backoff window are
// marked SessionCrashLoopBackOff and left alive (not deleted) so
// operators see the condition and the reconciler doesn't respawn.
BackoffUntil time.Time
// HealthySince stamps when the role began an unbroken healthy streak
// after its most recent charge; the zero value means no active streak
// (never healthy, or a crash/flap cleared it). Once the streak reaches
// restartCountDecayWindow, evaluateHealth resets RestartCount so a role
// that has run clean for a while does not carry a lifetime-sized backoff
// into its next crash. In-memory only (not persisted): a daemon restart
// conservatively restarts the streak rather than granting decay early.
// See decayHealthyRoles and aae-orc-fv3h.
HealthySince time.Time
}
RoleHealth is the per-role crash-loop tracking state.
type RolePlan ¶
type RolePlan struct {
Workspace string
Team string
Role string
Generation int64
// Desired is the role's declared replica count.
Desired int
// Actual is the number of the role's sessions that count as alive now.
Actual int
// Action is the convergence verb — a summary of the fields below.
Action RoleAction
// Spawn is how many new sessions applyRolePlan would create. Zero unless
// Action is RoleSpawn.
Spawn int
// Delete is the exact set of sessions applyRolePlan would remove, in the
// order it removes them (newest first). Non-empty only when Action is
// RoleScaleDown.
Delete []api.Session
// Hold names why the plan spawns fewer than Desired-Actual sessions, when
// it does; HoldNone otherwise.
Hold HoldReason
// HoldDetail is the human-readable reason behind Hold, for a dry-run or
// startup report. It is plan metadata only — never emitted as an event, so
// it changes no reconcile behavior.
HoldDetail string
// Firing is the schedule firing a spawn is stamped with; empty for a
// role with no schedule.
Firing string
// contains filtered or unexported fields
}
RolePlan is the convergence decision for one role at one generation: what the reconciler would change, computed without changing anything. planRole produces it (pure — it reads state but mutates none), and applyRolePlan enacts exactly it. Exposing the decision as an inspectable value is the keystone for the convergence-posture arc (question-convergence-posture, aae-orc-nf0w): the dry-run surface prints it, the hold posture computes it and declines to apply, and the startup report is the plan at boot.
type Snapshotter ¶
Snapshotter supplies the measured state one admission check evaluates. The daemon implements it over its usage accountant.
type TickRing ¶
type TickRing struct {
// contains filtered or unexported fields
}
TickRing is one session's last TickRingWindow of evaluation ticks, in memory. It stores what each tick saw (the time and the session's ContextAt), never a percentage, so the reader applies the quiet window it wants and the one quiet predicate decides. A ring is not safe for concurrent use; the controller guards its rings.