dag

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package dag maintains the commit-DAG of environment snapshots.

Design:

  • Each Node maps to one read-only btrfs snapshot (an immutable state).
  • A single parent per node is enough for environment rollback, so this is a tree; extend to a real DAG later if merges are ever needed.
  • Metadata is persisted as a single JSON file (meta.json): single writer, zero external dependencies.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Node

type Node struct {
	ID        string            `json:"id"`
	Parent    string            `json:"parent"`   // empty for the root node
	Children  []string          `json:"children"` // child IDs, forming the tree
	Message   string            `json:"message"`  // git-like commit message
	Command   string            `json:"command"`  // action that produced it, e.g. "apt install nginx"
	CreatedAt time.Time         `json:"created_at"`
	Meta      map[string]string `json:"meta,omitempty"`
}

Node is an immutable node in the DAG, i.e. one commit.

type Repo

type Repo struct {
	Nodes map[string]*Node  `json:"nodes"`
	Head  string            `json:"head"`           // node the current working subvolume derives from
	Tags  map[string]string `json:"tags,omitempty"` // human name → node ID
	// contains filtered or unexported fields
}

Repo is the whole commit graph plus the current HEAD and named tags.

CONCURRENCY CONTRACT: Repo is NOT goroutine-safe on its own. All callers (currently the orchestration layer in internal/repo) MUST hold a higher-level mutex — repo.Repo.opMu — across any read or write of Nodes / Tags / Head. This is intentional: the agentenv DAG is small and updates are infrequent, so a single repo-wide lock is simpler than embedding a mutex here and trying to keep the lock-ordering between dag.Repo and the snapshotter consistent. The doc comment exists so a future caller from a new package doesn't assume dag.Repo is safe to share.

func Load

func Load(root string) (*Repo, error)

Load reads root/meta.json, returning an empty repo if it does not exist.

func (*Repo) Add

func (r *Repo) Add(n *Node)

Add attaches a node under its parent and inserts it into the graph.

func (*Repo) Delete added in v0.2.0

func (r *Repo) Delete(id string) (parent string, ok bool)

Delete removes node id from the graph, splicing it out: its children are re-parented to id's parent (so the tree stays connected — deleting a middle node keeps its descendants), and any tags pointing at id are dropped. The caller is responsible for the policy checks (not HEAD, not the only node) and for removing the on-disk snapshot. Returns id's former parent ("" if it was a root) and whether the node existed.

func (*Repo) Get

func (r *Repo) Get(id string) (*Node, bool)

Get returns a node by ID.

func (*Repo) Roots

func (r *Repo) Roots() []*Node

Roots returns all nodes without a parent (normally just one).

func (*Repo) Save

func (r *Repo) Save() error

Save writes meta.json atomically and crash-safely. meta.json is the only source of truth for the commit DAG (every node, parent, tag, HEAD), so a torn or zero-byte file would lose the entire history. Sequence:

  1. write the new contents to a sibling temp file,
  2. fsync the temp file so the bytes are durably on disk,
  3. rename → meta.json (atomic on POSIX),
  4. fsync the parent directory so the rename itself is durable.

Steps 2 and 4 are what plain `os.WriteFile`+`os.Rename` is missing — after a kernel/host crash without them, the rename can land first and the data later, or the new file can be empty.

Jump to

Keyboard shortcuts

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