Help us shape the future of Go! Take the 2026 Go Developer Survey

plans

package
v0.7.2 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package plans is a lock's store of plans awaiting a decision: one file per plan in the lock's volume (0600 in a 0700 directory), surviving restarts. A plan is bound to the peer UID that made it, the requesting agent's token, the policy fingerprint and the plan hash; it runs once.

The store keeps the product's plan as an opaque body: the product recomputes the plan hash from it before running (Check), and runs nothing whose binding moved.

One Store owns a store directory: Open locks it (a second Open fails with ErrLocked until Close), so that a decision nonce is accepted once whatever the number of consoles on a volume. A Store is safe for concurrent use.

Index

Constants

View Source
const (
	// MaxPlans bounds the live (not final) plans of a store.
	MaxPlans = 1000
	// MaxBody bounds a plan's body.
	MaxBody = 1 << 20
	// Retention keeps a final plan this long before Prune removes it.
	Retention = 24 * time.Hour
)

Defaults.

Variables

View Source
var (
	// ErrNoSuchPlan: unknown, pruned or expired plan id.
	ErrNoSuchPlan = errors.New("plans: no such plan")
	// ErrWrongPeer: the plan was made by another peer UID.
	ErrWrongPeer = errors.New("plans: plan made by another peer")
	// ErrWrongRequester: the plan was made by another agent (token).
	ErrWrongRequester = errors.New("plans: plan made by another agent")
	// ErrStale: the plan hash or policy fingerprint moved since planning.
	ErrStale = errors.New("plans: plan is stale")
	// ErrUsed: the plan already ran, or is in a final state.
	ErrUsed = errors.New("plans: plan already used")
	// ErrReplay: the decision nonce was consumed before.
	ErrReplay = errors.New("plans: decision nonce already consumed")
	// ErrState: the transition is not allowed from the plan's state.
	ErrState = errors.New("plans: transition not allowed")
	// ErrFull: the store holds MaxPlans live plans.
	ErrFull = errors.New("plans: too many plans")
	// ErrUnsafe: the store directory or a file in it has unsafe
	// permissions; nothing is loaded.
	ErrUnsafe = errors.New("plans: unsafe permissions")
	// ErrLocked: another Store (another console on the same volume) has
	// the directory open.
	ErrLocked = errors.New("plans: the store is open in another process")
)
View Source
var ErrNotExpired = errors.New("plans: plan has not expired")

ErrNotExpired: Renew of a plan that is not Expired.

View Source
var ErrRenewed = errors.New("plans: plan already renewed")

ErrRenewed: the expired plan was renewed already; Renew returns the renewal.

Functions

func HashToken

func HashToken(token string) string

HashToken is the stored form of an agent token: hex SHA-256. The token itself is never written.

Types

type Binding

type Binding struct {
	UID int `json:"uid"`
	// Agent is the requesting agent's label (display only).
	Agent string `json:"agent"`
	// TokenHash is HashToken of the agent's token ("" without one).
	TokenHash string `json:"token_hash"`
	PolicyFP  string `json:"policy_fp"`
	PlanHash  string `json:"plan_hash"`
	// Inputs are the digests of the plan hash's inputs by name
	// (ticket.InputDigests), when the product gives them: a renewal shows
	// which of them moved (ticket.DiffInputs).
	Inputs map[string]string `json:"inputs,omitempty"`
}

Binding is what a plan is bound to.

type Caller

type Caller struct {
	UID int
	// Token is the agent's lea token (LEA_AGENT_TOKEN), "" without lea.
	Token string
	// contains filtered or unexported fields
}

Caller is who presents a plan id.

func OwnerOf added in v0.5.0

func OwnerOf(p *Plan) Caller

OwnerOf is the caller a plan is bound to, for the lock itself acting on the plan for its agent (a renewal the human asked for on the hub). It is a capability of code holding the store, which can act on any plan anyway: never build a caller for a client with it (a product builds those from its clients' credentials).

type Option

type Option func(*Store)

Option configures Open.

func WithClock

func WithClock(now func() time.Time) Option

WithClock sets the store's clock (tests).

type Plan

type Plan struct {
	ID      string  `json:"id"`
	State   State   `json:"state"`
	Binding Binding `json:"binding"`
	// Ticket is the hub ticket id, once pending, and SignedTicket the
	// ticket as the lock signed and pushed it: a decision is matched
	// against it (ticket.Decision.Match), never against one rebuilt.
	Ticket       string          `json:"ticket,omitempty"`
	SignedTicket json.RawMessage `json:"signed_ticket,omitempty"`
	// Nonce is the decision nonce consumed when the plan began executing.
	Nonce string `json:"nonce,omitempty"`
	// Reason says why the plan reached its final state (denial reason,
	// failure, what went stale).
	Reason string `json:"reason,omitempty"`
	// Untold: the plan's ticket was withdrawn here (WithdrawAll, a start
	// without the hub) and the hub has not been told yet; the next start
	// in hub mode tells it (hubclient.Client.Sync), then MarkTold.
	Untold  bool       `json:"untold,omitempty"`
	Created time.Time  `json:"created"`
	Updated time.Time  `json:"updated"`
	Expires *time.Time `json:"expires,omitempty"`
	// Body is the product's plan, opaque to the store.
	Body json.RawMessage `json:"body"`
	// Clear is the clear screen of the plan's ticket: it stays in the
	// lock's volume and leaves only sealed (a reveal, a device).
	Clear *ticket.Screen `json:"clear,omitempty"`
	// ApprovedAt is when an approval that aged before the run was given
	// (an Expired plan whose decision came too late to be used).
	ApprovedAt *time.Time `json:"approved_at,omitempty"`
	// Renews is the expired plan this one asks again for; RenewedBy, on
	// an expired plan, the plan that renews it.
	Renews    string `json:"renews,omitempty"`
	RenewedBy string `json:"renewed_by,omitempty"`
	// RenewChecked: a renewal whose binding the lock computed again (its
	// Inputs and hashes are the lock's current view).
	RenewChecked bool `json:"renew_checked,omitempty"`
}

Plan is one stored plan.

func (*Plan) LockTicket

func (p *Plan) LockTicket(pub ed25519.PublicKey) (*ticket.Ticket, error)

LockTicket decodes the signed ticket of a pending plan, verified under the lock's own key pub (the store is the lock's, but a file is only trusted once its signature checks).

type State

type State string

State of a plan.

const (
	// Planned: made, never run.
	Planned State = "planned"
	// Pending: run asked, a ticket awaits the human.
	Pending State = "pending"
	// Executing: a decision was accepted and consumed; the plan is running.
	Executing State = "executing"
	Executed  State = "executed"
	Failed    State = "failed"
	// Stale: the binding moved (plan hash, policy) before it ran.
	Stale     State = "stale"
	Denied    State = "denied"
	Withdrawn State = "withdrawn"
	Expired   State = "expired"
)

func (State) Final

func (s State) Final() bool

Final reports whether s is a final state: nothing leaves it.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is a directory of plans.

func Open

func Open(dir string, opts ...Option) (*Store, error)

Open opens or creates the store in dir (created 0700). It refuses a directory, plan or nonce file readable or writable by group or others, and loads every plan.

func (*Store) Begin

func (s *Store) Begin(id string, c Caller, nonce string) error

Begin starts executing plan id under the decision nonce: single use. The nonce is recorded durably before the plan changes state; a nonce seen before is ErrReplay, a plan not Planned or Pending is ErrUsed. Without a hub (standalone), nonce is "".

func (*Store) ByTicket

func (s *Store) ByTicket(ticketID string, c Caller) (*Plan, error)

ByTicket returns the plan waiting on ticket id, for caller c.

func (*Store) Check

func (s *Store) Check(id string, c Caller, policyFP, planHash string) error

Check compares a plan's binding with what the product recomputed just before running it: a different policy fingerprint or plan hash marks the plan Stale and returns ErrStale.

func (*Store) Close

func (s *Store) Close() error

Close releases the store directory for another Open. The Store must not be used after it.

func (*Store) Create

func (s *Store) Create(b Binding, body json.RawMessage, ttl time.Duration) (*Plan, error)

Create stores a new Planned plan bound to b, with the product's body, expiring after ttl unless run (0: never).

func (*Store) End

func (s *Store) End(id string, c Caller, st State, reason string) error

End moves a plan that has not begun to a final state: Denied, Withdrawn, Stale or Expired, with reason. It is how the lock records a decision other than an approval, or a ticket withdrawn.

func (*Store) ExpireApproval added in v0.5.0

func (s *Store) ExpireApproval(id string, c Caller, approvedAt time.Time, reason string) error

ExpireApproval ends a pending plan Expired because its approval, given at approvedAt, aged before the run: a renewal says so.

func (*Store) ExpiredByTicket added in v0.5.0

func (s *Store) ExpiredByTicket(ticketID string, c Caller) (*Plan, error)

ExpiredByTicket returns the expired plan of ticket ticketID, for caller c (its binding: same peer UID, same agent token): what a renewal starts from. A plan of the ticket in any other state is ErrNotExpired.

func (*Store) FindPending

func (s *Store) FindPending(c Caller, planHash string) (*Plan, bool)

FindPending returns the pending plan of caller c (peer UID and agent token) with planHash, if any: a second run of the same plan reuses its ticket instead of opening a new one.

func (*Store) Finish

func (s *Store) Finish(id string, c Caller, ok bool, reason string) error

Finish ends an executing plan: Executed, or Failed with reason.

func (*Store) Get

func (s *Store) Get(id string, c Caller) (*Plan, error)

Get returns plan id as caller c may see it: same peer UID, same agent token. An expired plan is ErrNoSuchPlan.

func (*Store) MarkPending

func (s *Store) MarkPending(id string, c Caller, t *ticket.Ticket) error

MarkPending records that plan id waits on t, the ticket the lock signed for it, until t.Expires (nil: no expiry). t must carry the plan's hash and policy fingerprint.

func (*Store) MarkPendingClear added in v0.5.0

func (s *Store) MarkPendingClear(id string, c Caller, t *ticket.Ticket, clear *ticket.Screen) error

MarkPendingClear is MarkPending keeping clear, the clear screen of the ticket, in the plan (Plan.Clear) for a later reveal.

func (*Store) MarkTold added in v0.4.0

func (s *Store) MarkTold(id string) error

MarkTold records that the hub was told plan id's ticket is withdrawn.

func (*Store) Pending

func (s *Store) Pending() []*Plan

Pending lists the pending plans, oldest first.

func (*Store) PlanByID added in v0.6.0

func (s *Store) PlanByID(id string) (*Plan, error)

PlanByID returns plan id whatever its caller and state: for the lock itself (walking a renewal chain), never for a client.

func (*Store) Prune

func (s *Store) Prune() (int, error)

Prune expires plans whose time is up and removes final plans older than Retention. Their nonces stay recorded. It returns how many it removed.

func (*Store) Renew added in v0.5.0

func (s *Store) Renew(id string, policyFP, planHash string, inputs map[string]string, checked bool) (*Plan, error)

Renew makes a new Planned plan asking again for expired plan id: the same body, binding and clear screen, under the policy fingerprint, plan hash and inputs the lock computed now. The expired plan records it (RenewedBy); a plan already renewed returns its renewal with ErrRenewed.

func (*Store) RenewalOf added in v0.5.0

func (s *Store) RenewalOf(id string) (*Plan, error)

RenewalOf returns the plan that renews expired plan id.

func (*Store) TicketPlan added in v0.5.0

func (s *Store) TicketPlan(ticketID string) (*Plan, error)

TicketPlan returns the plan of ticket ticketID whatever its caller: for the lock itself (a request of the hub about one of its tickets), never for a client.

func (*Store) Untold added in v0.4.0

func (s *Store) Untold() []*Plan

Untold lists the withdrawn plans whose ticket the hub was not told about, oldest first.

func (*Store) WithdrawAll

func (s *Store) WithdrawAll(reason string) ([]*Plan, error)

WithdrawAll ends every pending plan as Withdrawn, as a lock starting without its hub does, and returns them, oldest first. They are marked Untold: the hub still shows their ticket until a start in hub mode withdraws it there (Untold, MarkTold).

Jump to

Keyboard shortcuts

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