plans

package
v0.13.1 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 17 Imported by: 1

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