Commit chain

Every change to a scene lands as a commit on a hash-linked chain — the scene's version history across all its jobs. It's what makes permalinks, live diffs, and "what changed since Tuesday" structurally true rather than best-effort. What a job did along the way is recorded separately, in its trace.

A commit is a pointer to a tree

Each commit holds:

  • parentHash — the previous commit, or null for the first.
  • treeHash — a tree object snapshotting the whole scene at this commit.
  • actoruser, agent, system, or provider, with id and name.
  • jobId — the job that produced it (one job can produce many commits).
  • message, timestamp.

A tree is a sorted list of { id, hash } — one entry per asset, and one per child scene. An asset's hash is computed from its canonical content (fields plus its cells' projection config), so identical content means identical hash, and trees that didn't change share hashes structurally.

Diffs are derived, not stored

Commits carry no changes array. To see what changed between two commits, walk both trees and compare hashes per asset. Storing changes would duplicate what the trees already encode — and it would make commits non-canonical: two paths to the same state would hash differently, defeating content-addressing. The trade: producing a diff reads two trees. They're small sorted lists; it's cheap.

Timelines and permalinks are built on it

The derived-diff walk is what the live update stream uses to push only changed cells, how the web client invalidates precisely, and how a permalink reconstructs the scene at any commit. The scene timeline — when assets changed, who did it, from which job — is the same walk rendered for humans. You don't handle commits directly in normal use; they're the substrate.

What's next

  • Assets — what the tree entries hash.
  • Traces — the sibling record: what a job did.
  • Audit — reading both records.
  • Blueprints — the chain's three-way merge, applied.

Updated 2026-07-18