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) 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.