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 (*Repo) Delete ¶ added in v0.2.0
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) Save ¶
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:
- write the new contents to a sibling temp file,
- fsync the temp file so the bytes are durably on disk,
- rename → meta.json (atomic on POSIX),
- 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.