ledger

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package ledger is the authoritative, on-disk record of a run: run.json holds the engine's current State (what resume loads directly — never by replaying internal/sdlc/journal's observability log), and nodes/<id>.json holds each node's own execution history for audit and `sdlc status`. Every write is atomic: a same-directory temp file plus rename, guarded by an exclusive lock on a dedicated lock file (internal/filelock), so a crash mid-write can never leave a reader with a partially-written file, and concurrent writers can never interleave.

Index

Constants

View Source
const (
	RunFileName = "run.json"
)

RunFileName and nodesDirName name the files within a run's directory.

Variables

This section is empty.

Functions

This section is empty.

Types

type Activity

type Activity struct {
	Owner string    `json:"owner,omitempty"`
	Until time.Time `json:"until"`
	Seen  time.Time `json:"seen"`
	Host  bool      `json:"host,omitempty"`
}

type Allowances

type Allowances struct {
	Assignments int     `json:"assignments"`
	Revisions   int     `json:"revisions"`
	Children    int     `json:"children"`
	Steps       int     `json:"steps"`
	Seconds     float64 `json:"seconds"`
	CostUSD     float64 `json:"costUsd"`
}

type AttemptRecord

type AttemptRecord struct {
	Attempt    int             `json:"attempt"`
	StartedAt  string          `json:"startedAt"`
	Effect     json.RawMessage `json:"effect,omitempty"`
	ResolvedAt string          `json:"resolvedAt,omitempty"`
	Event      json.RawMessage `json:"event,omitempty"`
}

AttemptRecord is one execution of a node's effect: the effect asked for and, once resolved, the event that resolved it. Encoded as opaque JSON so this package stays decoupled from engine's concrete Effect/Event types, the same reasoning as internal/sdlc/journal.

type Budget

type Budget struct {
	Version           int                    `json:"version"`
	Original          Allowances             `json:"original"`
	Limits            Allowances             `json:"limits"`
	Usage             Allowances             `json:"usage"`
	Reservations      map[string]Reservation `json:"reservations"`
	Activities        map[string]Activity    `json:"activities"`
	Extensions        []Extension            `json:"extensions,omitempty"`
	Warned            map[string]bool        `json:"warned,omitempty"`
	Checkpoint        time.Time              `json:"checkpoint"`
	TimeEstimated     bool                   `json:"timeEstimated,omitempty"`
	InvocationSeconds float64                `json:"invocationSeconds"`
	WorkflowSteps     map[string]int         `json:"workflowSteps,omitempty"`
}

Budget is the single admission ledger for an entire run tree. It lives in budget.json under the root, separately from run.json so a stale child/driver snapshot cannot overwrite a reservation or an operator's grant.

func NewBudget

func NewBudget(limits Allowances, now time.Time, invocationSeconds float64) Budget

func (Budget) Blockers

func (b Budget) Blockers(kind string) []string

Blockers returns all allowances which would block the requested admission. Completed admitted work does not consult these limits.

func (*Budget) Complete

func (b *Budget) Complete(id string, changed bool, cost float64) error

func (*Budget) Extend

func (b *Budget) Extend(runID, action string, add Allowances, now time.Time) error

func (*Budget) Reserve

func (b *Budget) Reserve(id, runID, kind string) error

func (Budget) ReservedRevisions

func (b Budget) ReservedRevisions() int

func (*Budget) Tick

func (b *Budget) Tick(now time.Time)

Tick counts the union of active intervals. Driver leases that expired since the last checkpoint are charged only through their last observed heartbeat; host assignments are charged through their independent invocation expiry.

func (*Budget) Warnings

func (b *Budget) Warnings() []string

type Candidate

type Candidate struct {
	ID     string `json:"id"`
	Reason string `json:"reason,omitempty"` // empty means eligible
}

type Decision

type Decision struct {
	At         string            `json:"at"`
	RunID      string            `json:"runId"`
	Kind       string            `json:"kind"`
	Stage      string            `json:"stage,omitempty"`
	Invocation string            `json:"invocation,omitempty"`
	Runtime    string            `json:"runtime,omitempty"`
	Trigger    string            `json:"trigger,omitempty"`
	Candidates []Candidate       `json:"candidates,omitempty"`
	Rubrics    map[string]string `json:"rubrics,omitempty"`
	Choice     string            `json:"choice,omitempty"`
	Confidence *float64          `json:"confidence,omitempty"`
	Outcome    string            `json:"outcome,omitempty"`
	Next       string            `json:"next,omitempty"`
	Detail     string            `json:"detail,omitempty"`
}

Decision is recorded evidence about a routing or lifecycle choice. Detail is display text, never a claim to contain Jev's private reasoning.

type Event

type Event struct {
	At               string `json:"at"`
	RunID            string `json:"runId"`
	Stage            string `json:"stage"`
	Outcome          string `json:"outcome,omitempty"`
	Agent            string `json:"agent,omitempty"`
	Runtime          string `json:"runtime,omitempty"`
	Invocation       string `json:"invocation,omitempty"`
	Reason           string `json:"reason,omitempty"`
	Workflow         string `json:"workflow,omitempty"`
	Assignments      int    `json:"assignments"`
	Revisions        int    `json:"revisions"`
	ChildRuns        int    `json:"childRuns"`
	TransitionStage  string `json:"transitionStage,omitempty"`
	TransitionKind   string `json:"transitionKind,omitempty"`
	TransitionAnswer string `json:"transitionAnswer,omitempty"`
	TransitionNext   string `json:"transitionNext,omitempty"`
	ChildRunID       string `json:"childRunId,omitempty"`
}

type Extension

type Extension struct {
	At     time.Time  `json:"at"`
	RunID  string     `json:"runId"`
	Action string     `json:"action"`
	Before Allowances `json:"before"`
	After  Allowances `json:"after"`
}

type InvocationUsage

type InvocationUsage struct {
	Invocation          string   `json:"invocation"`
	Agent               string   `json:"agent"`
	Runtime             string   `json:"runtime"`
	Model               string   `json:"model,omitempty"`
	Role                string   `json:"role"`
	SessionID           string   `json:"sessionId,omitempty"`
	InputTokens         *int64   `json:"inputTokens"`
	OutputTokens        *int64   `json:"outputTokens"`
	ToolCalls           *int64   `json:"toolCalls,omitempty"`
	CacheReadTokens     *int64   `json:"cacheReadTokens,omitempty"`
	CacheCreationTokens *int64   `json:"cacheCreationTokens,omitempty"`
	CostUSD             *float64 `json:"costUsd,omitempty"`
	// ElapsedMS is the invocation's wall-clock time; nil on records written
	// before it was tracked.
	ElapsedMS               *int64 `json:"elapsedMs,omitempty"`
	UsageProvenance         string `json:"usageProvenance,omitempty"`
	StablePrefixBytes       int    `json:"stablePrefixBytes,omitempty"`
	StablePrefixFingerprint string `json:"stablePrefixFingerprint,omitempty"`
}

InvocationUsage uses pointers so an absent count remains unknown. Cache fields and provenance are omitempty so older run.json ledgers that never recorded them stay readable and round-trip without inventing zeros.

type NodeRecord

type NodeRecord struct {
	NodeID   string          `json:"nodeId"`
	Attempts []AttemptRecord `json:"attempts,omitempty"`
}

NodeRecord is one node's execution history: nodes/<id>.json.

type Reservation

type Reservation struct {
	RunID     string    `json:"runId"`
	Revision  bool      `json:"revision,omitempty"`
	Completed bool      `json:"completed,omitempty"`
	CostUSD   float64   `json:"costUsd,omitempty"`
	ExpiresAt time.Time `json:"expiresAt,omitempty"`
}

type ReviewRecovery

type ReviewRecovery struct {
	ToolPolicyFingerprint  string   `json:"toolPolicyFingerprint,omitempty"`
	RuntimeArgsFingerprint string   `json:"runtimeArgsFingerprint,omitempty"`
	Invocation             string   `json:"invocation"`
	Agent                  string   `json:"agent"`
	Binding                string   `json:"binding"`
	Runtime                string   `json:"runtime"`
	Model                  string   `json:"model,omitempty"`
	Revision               string   `json:"revision"`
	Outcome                string   `json:"outcome"`
	Content                string   `json:"content,omitempty"`
	Reason                 string   `json:"reason,omitempty"`
	SessionID              string   `json:"sessionId,omitempty"`
	InputTokens            *int64   `json:"inputTokens,omitempty"`
	OutputTokens           *int64   `json:"outputTokens,omitempty"`
	ToolCalls              *int64   `json:"toolCalls,omitempty"`
	CacheReadTokens        *int64   `json:"cacheReadTokens,omitempty"`
	CacheCreationTokens    *int64   `json:"cacheCreationTokens,omitempty"`
	CostUSD                *float64 `json:"costUsd,omitempty"`
	UsageProvenance        string   `json:"usageProvenance,omitempty"`
	Paths                  []string `json:"paths,omitempty"`
	Truncated              bool     `json:"truncated,omitempty"`
	Applied                bool     `json:"applied,omitempty"`
}

ReviewRecovery is durable evidence for a parsed review that was interrupted by workspace drift or a crash before its result was applied.

type Run

type Run struct {
	BudgetDefaults           *Allowances         `json:"budgetDefaults,omitempty"`
	InvocationSeconds        float64             `json:"invocationSeconds,omitempty"`
	RunID                    string              `json:"runId"`
	SessionStrategy          string              `json:"sessionStrategy,omitempty"`
	RuntimeIntegration       *RuntimeIntegration `json:"runtimeIntegration,omitempty"`
	RequirePlanApproval      bool                `json:"requirePlanApproval,omitempty"`
	ApprovedPlanRevision     string              `json:"approvedPlanRevision,omitempty"`
	ApprovedChecksRevision   string              `json:"approvedChecksRevision,omitempty"`
	ApprovedSubtasksRevision string              `json:"approvedSubtasksRevision,omitempty"`
	// AuthorizedChecksRevision is the exact checks.json digest the operator
	// authorized for this run. --auto alone never sets it.
	AuthorizedChecksRevision string `json:"authorizedChecksRevision,omitempty"`
	PlanFeedback             string `json:"planFeedback,omitempty"`
	// OperatorGuidance is free text the human supplied while the run was
	// paused. It is added to the next agent prompt and cleared once an
	// invocation completes with a real outcome.
	OperatorGuidance string            `json:"operatorGuidance,omitempty"`
	Sessions         map[string]string `json:"sessions,omitempty"`
	// SessionContexts records content-free binding metadata so chooseSession can
	// refuse silent resume when project/workdir or plan/diff revision drifts.
	// Omitted on older run.json files; missing entries keep prior resume behavior
	// until the next successful invocation writes a context.
	SessionContexts map[string]SessionContext `json:"sessionContexts,omitempty"`
	Usage           []InvocationUsage         `json:"usage,omitempty"`
	ReviewRecovery  *ReviewRecovery           `json:"reviewRecovery,omitempty"`
	WorkDir         string                    `json:"workDir,omitempty"`
	AllowRead       []string                  `json:"allowRead,omitempty"`
	TreeUsage       *TreeUsage                `json:"treeUsage,omitempty"`
	// Fanout is the durable supervisor schedule for an approved subtask graph.
	Fanout *adaptive.Schedule `json:"fanout,omitempty"`
	// Integration is the supervisor-owned merge of fan-out subtask patches.
	// Stored with the parent run so users can inspect or delete it together.
	Integration *adaptive.IntegrationRecord `json:"integration,omitempty"`
	// Verification is the supervisor-owned check pass for the current candidate.
	// Receipts are owner-only under the run; diagnostic logs are separately
	// prunable via `sdlc prune --logs-only`.
	Verification       *adaptive.VerificationRecord `json:"verification,omitempty"`
	DelegateBuiltins   bool                         `json:"delegateBuiltins,omitempty"`
	AutoDecisionDone   bool                         `json:"autoDecisionDone,omitempty"`
	AutoDecisionReason string                       `json:"autoDecisionReason,omitempty"`
	AutoChildRunID     string                       `json:"autoChildRunId,omitempty"`
	AutoTarget         string                       `json:"autoTarget,omitempty"`
	ParentRunID        string                       `json:"parentRunId,omitempty"`
	Depth              int                          `json:"depth,omitempty"`
	Workflow           string                       `json:"workflow"`
	SelectionReason    string                       `json:"selectionReason,omitempty"`
	GraphSHA256        string                       `json:"graphSha256"`
	CreatedAt          string                       `json:"createdAt"`
	UpdatedAt          string                       `json:"updatedAt"`
	// Task is the run's task statement, stored intact (run.json is written
	// at 0600, like every ledger file); it is redacted before it ever
	// reaches Jev, but kept whole here for a human or `sdlc status` to read.
	Task      string           `json:"task,omitempty"`
	State     engine.State     `json:"state"`
	Adaptive  *adaptive.State  `json:"adaptive,omitempty"`
	StageFlow *stageflow.State `json:"stageFlow,omitempty"`
}

func NewRun

func NewRun(store *Store, runID, workflow, graphSHA256 string, st engine.State, now time.Time) (Run, error)

NewRun writes a freshly created run.json. now is stamped as both CreatedAt and UpdatedAt.

type RuntimeIntegration

type RuntimeIntegration struct {
	Hooks      bool `json:"hooks"`
	Compaction bool `json:"compaction"`
	MCP        bool `json:"mcp,omitempty"`
}

Run is one run's authoritative record: run.json.

type SessionContext

type SessionContext struct {
	WorkDir      string `json:"workDir,omitempty"`
	PlanRevision string `json:"planRevision,omitempty"`
	DiffRevision string `json:"diffRevision,omitempty"`
}

SessionContext is content-free metadata for a stored runtime session ID.

type Store

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

Store is a run's ledger directory: <root>/<runId>/.

func Open

func Open(root, runID string) *Store

Open returns the Store for runID under root. It does not touch disk. A runID containing a path separator, or equal to "." or "..", makes every subsequent read/write on the returned Store return an error instead of resolving outside root.

func (*Store) AppendAttempt

func (s *Store) AppendAttempt(nodeID string, att AttemptRecord) error

AppendAttempt loads a node's record, appends att, and writes it back. Callers needing this under concurrent writers should serialize their own updates (e.g. one goroutine driving a given run); WriteNode's atomic rename only guarantees a reader never sees a torn file, not read-modify- write isolation across the two calls.

func (*Store) AppendDecision

func (s *Store) AppendDecision(d Decision) error

AppendDecision serializes complete JSON lines. Callers supply already redacted text; this layer also bounds every free text field.

func (*Store) AppendEvent

func (s *Store) AppendEvent(e Event) error

func (*Store) ReadAllNodes

func (s *Store) ReadAllNodes() (map[string]NodeRecord, error)

ReadAllNodes returns every node record in the run's ledger, sorted by node id. A run with no nodes directory yet returns none, not an error.

func (*Store) ReadArtifact

func (s *Store) ReadArtifact(artifact string) ([]byte, error)

ReadArtifact reads a previously stored artifact.

func (*Store) ReadBudget

func (s *Store) ReadBudget() (Budget, error)

func (*Store) ReadDecisions

func (s *Store) ReadDecisions() ([]Decision, error)

func (*Store) ReadEvents

func (s *Store) ReadEvents() ([]Event, error)

func (*Store) ReadNode

func (s *Store) ReadNode(nodeID string) (NodeRecord, error)

ReadNode reads nodes/<nodeID>.json. A missing record returns a zero-value NodeRecord (no attempts yet), not an error: a node the run hasn't reached yet simply has none.

func (*Store) ReadRun

func (s *Store) ReadRun() (Run, error)

ReadRun reads run.json.

func (*Store) ReserveFanoutSlot

func (s *Store) ReserveFanoutSlot(now time.Time, mutate func(run *Run, schedule *adaptive.Schedule) error) error

ReserveFanoutSlot serializes schedule mutation under the run lock so simultaneous drive/host requests cannot oversubscribe or double-launch.

func (*Store) UpdateBudget

func (s *Store) UpdateBudget(initial func() (Budget, error), update func(*Budget) error) error

UpdateBudget holds the root's dedicated budget lock. Lock order is run lock, then budget lock; callbacks must never acquire a run lock. Atomic rename commits reservations, accounting and extension history together.

func (*Store) UpdateFanout

func (s *Store) UpdateFanout(now time.Time, mutate func(run *Run, schedule *adaptive.Schedule) error) error

UpdateFanout serializes an arbitrary schedule update under the run lock.

func (*Store) UpdateIntegration

func (s *Store) UpdateIntegration(now time.Time, mutate func(run *Run, integration *adaptive.IntegrationRecord) error) error

UpdateIntegration serializes supervisor integration state under the run lock.

func (*Store) UpdateState

func (s *Store) UpdateState(st engine.State, now time.Time) (Run, error)

UpdateState loads run.json, applies st, stamps UpdatedAt as now, and writes the result back — the one call a caller needs after every engine.Apply.

func (*Store) UpdateVerification

func (s *Store) UpdateVerification(now time.Time, mutate func(run *Run, verification *adaptive.VerificationRecord) error) error

UpdateVerification serializes supervisor verification receipts under the run lock.

func (*Store) WithRunLock

func (s *Store) WithRunLock(fn func() error) error

WithRunLock serializes read-modify-write operations on one run across processes. Individual file writes remain atomic under their own locks.

func (*Store) WriteArtifact

func (s *Store) WriteArtifact(artifact string, data []byte) error

WriteArtifact atomically stores a seeded or produced artifact's content under the run's artifacts directory.

func (*Store) WriteNode

func (s *Store) WriteNode(rec NodeRecord) error

WriteNode atomically writes rec to nodes/<rec.NodeID>.json.

func (*Store) WriteRun

func (s *Store) WriteRun(r Run) error

WriteRun atomically writes r to run.json.

type TreeUsage

type TreeUsage struct {
	Assignments      int     `json:"assignments"`
	Revisions        int     `json:"revisions"`
	ChildRuns        int     `json:"childRuns"`
	StageSteps       int     `json:"stageSteps"`
	EstimatedCostUSD float64 `json:"estimatedCostUsd,omitempty"`
}

TreeUsage is charged at the root as work is reserved or completed.

Jump to

Keyboard shortcuts

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