You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
RFC: Add Operation Log v2, complete Working Copy snapshot, and stable Change ID for Agent-first history
Motivation
Libra is an AI version control tool. It already has Git-compatible object storage, Git commands, linked worktrees, an AI object model, and partial Operation records, but it still cannot answer three questions:
What repository state did a single CLI operation actually change?
Can uncommitted files, the index, branch moves, or an in-progress rebase be undone in one uniform way?
After a commit is amended or rewritten, how can the same logical unit of work still be identified?
This issue proposes a v2 Operation system that borrows three paradigms from Jujutsu (jj):
a full-command, append-only Operation Log with concurrent op-heads CAS and reconcile;
complete Working Copy snapshots that enable crash-safe undo/redo/restore;
stable Change IDs with typed predecessor genealogy, so Agent/Intent causal links survive rewrite.
It should help Libra (and Agents driving Libra) answer:
Which command changed which refs, index files, or working-copy state, and in what order?
Can the last operation — including uncommitted changes, index state, branch moves, or rebase intermediates — be safely undone?
Where is the logical work for a given Intent across amended/rebased revisions?
Design
Scope assumptions
Libra CLI is the only supported mutation entry: every mutating command produces exactly one Operation. Human operations and Agent operations use the same entry point and the same Operation semantics (no separate Agent path).
No permission model: CLI, Agent, and automation callers are assumed to have the permissions needed to run the operation. RestorePolicy is therefore limited to data-consistency semantics: AutoRestore, Rebuild, NeverRestore. Data consistency protection (ref lock, CAS, worktree lease, conflict detection, destructive-op confirmation) remains.
Development phase replaces v1 directly: no v1 compatibility layer. v1 operation code (operation_wrapper.rs, v1 tables/models, v1 command paths) is deleted once the corresponding v2 phase passes its verification gates; there is no long-term dual-write or adapter.
External modifications (editors, plain Git) are recognized at the next Libra CLI entry as an ExternalSnapshot operation, not attributed to a command.
Three capabilities
Append-only Operation DAG. Every persistent CLI mutation writes an immutable Operation with parent op IDs, pre/post RepoViewV2 OIDs, command classification, and redacted causal IDs. Repository heads are published with CAS; concurrent operations from multiple CLI processes/worktrees produce multiple heads that are later converged by a multi-parent Reconcile operation. Undo creates a new Operation instead of deleting history.
Complete Working Copy snapshot. Each workspace keeps a WorkspaceSnapshotV2 manifest (not a user-visible commit): HEAD/refs, semantic index tree + byte-exact raw index, tracked/untracked tree, sparse/sequencer facets, and worktree generation. Every CLI entry snapshots the disk state first; if disk differs from the last post-view, a pure external-snapshot Operation is published before the command runs.
Stable Change ID and genealogy. New logical changes get a random 128-bit ChangeId ([u8;16]). Legacy Git commits without a change ID get a deterministic synthetic ID derived from the commit OID. Amend/rebase/squash/split/duplicate inherit or split the Change ID and record typed PredecessorEdges in the producing Operation, so Intent/Task/Run links survive rewrite.
Unified mutation flow
graph TD
A["CLI dispatch / Agent gateway"] --> B["classify_command: exhaustive Commands + tool specs"]
B -->|Unknown| X["fail closed, refuse"]
B --> C["run_with_operation: pin RequestScope + acquire worktree lease"]
C --> D["load workspace state pointer"]
D --> E{"disk == last post-view?"}
E -->|no| F["capture_workspace_snapshot -> external snapshot op"]
E -->|yes| G["write running op + journal reservation"]
F --> G
G --> H["run business closure (mutation)"]
H --> I["capture post-view + predecessor map, validate object closure"]
I --> J["CAS publish op head + atomically advance workspace pointer"]
J --> K["return OperationResult; failures leave a diagnosable operation"]
Loading
Record and storage model
Immutable payloads are content-addressed in the existing ClientStorage (Git ODB): each manifest is a versioned canonical serialization (sorted map keys, schema validation before hashing). SQLite stores only coordination state and rebuildable projections.
Complete per-workspace snapshot; raw_index_blob_oid preserves intent-to-add / skip-worktree / stat bits that Git trees cannot express
StateFacet / RestorePolicy
Every restorable state owner implements StateFacet (name, schema_version, restore_policy, capture, validate, restore, diff, roots). RestorePolicy is one of AutoRestore (HEAD/refs/index/files/sequencer/sparse), Rebuild (derived projections), NeverRestore (ephemeral runtime state such as leases). Any unregistered mutable state makes the operation fail closed (never fully_restorable).
OperationV2
Field
Meaning
op_id
UUIDv7, time-ordered (not content identity)
parent_op_ids
multi-parent DAG
pre_view_oid / post_view_oid
RepoViewV2 manifest OIDs before/after the mutation
explicit undo semantics (not inferred from view diff)
predecessor_map_oid
typed PredecessorEdge list for genealogy
ChangeId and genealogy
Field
Meaning
ChangeId([u8; 16])
128-bit opaque logical identity; random via CSPRNG; legacy commits use first 16 bytes of SHA-256("libra-change-id-v1\0" || object_format || commit_oid_bytes)
v2 replaces the v1 schema (operation / operation_parent / operation_view*) during development; old data is migrated by a one-shot import script if audit retention is needed.
Causal link between Operations and AI objects; redacted IDs only
Implementation path
The implementation is phased; each phase must pass its verification tests (including existing status/CLI regression and fail-closed guards) before the next phase starts:
RFC: Add Operation Log v2, complete Working Copy snapshot, and stable Change ID for Agent-first history
Motivation
Libra is an AI version control tool. It already has Git-compatible object storage, Git commands, linked worktrees, an AI object model, and partial Operation records, but it still cannot answer three questions:
This issue proposes a v2 Operation system that borrows three paradigms from Jujutsu (jj):
It should help Libra (and Agents driving Libra) answer:
Design
Scope assumptions
RestorePolicyis therefore limited to data-consistency semantics:AutoRestore,Rebuild,NeverRestore. Data consistency protection (ref lock, CAS, worktree lease, conflict detection, destructive-op confirmation) remains.operation_wrapper.rs, v1 tables/models, v1 command paths) is deleted once the corresponding v2 phase passes its verification gates; there is no long-term dual-write or adapter.ExternalSnapshotoperation, not attributed to a command.Three capabilities
Append-only Operation DAG. Every persistent CLI mutation writes an immutable Operation with parent op IDs, pre/post
RepoViewV2OIDs, command classification, and redacted causal IDs. Repository heads are published with CAS; concurrent operations from multiple CLI processes/worktrees produce multiple heads that are later converged by a multi-parentReconcileoperation. Undo creates a new Operation instead of deleting history.Complete Working Copy snapshot. Each workspace keeps a
WorkspaceSnapshotV2manifest (not a user-visible commit): HEAD/refs, semantic index tree + byte-exact raw index, tracked/untracked tree, sparse/sequencer facets, and worktree generation. Every CLI entry snapshots the disk state first; if disk differs from the last post-view, a pure external-snapshot Operation is published before the command runs.Stable Change ID and genealogy. New logical changes get a random 128-bit
ChangeId([u8;16]). Legacy Git commits without a change ID get a deterministic synthetic ID derived from the commit OID. Amend/rebase/squash/split/duplicate inherit or split the Change ID and record typedPredecessorEdges in the producing Operation, so Intent/Task/Run links survive rewrite.Unified mutation flow
graph TD A["CLI dispatch / Agent gateway"] --> B["classify_command: exhaustive Commands + tool specs"] B -->|Unknown| X["fail closed, refuse"] B --> C["run_with_operation: pin RequestScope + acquire worktree lease"] C --> D["load workspace state pointer"] D --> E{"disk == last post-view?"} E -->|no| F["capture_workspace_snapshot -> external snapshot op"] E -->|yes| G["write running op + journal reservation"] F --> G G --> H["run business closure (mutation)"] H --> I["capture post-view + predecessor map, validate object closure"] I --> J["CAS publish op head + atomically advance workspace pointer"] J --> K["return OperationResult; failures leave a diagnosable operation"]Record and storage model
Immutable payloads are content-addressed in the existing
ClientStorage(Git ODB): each manifest is a versioned canonical serialization (sorted map keys, schema validation before hashing). SQLite stores only coordination state and rebuildable projections.RepoViewV2andWorkspaceSnapshotV2RepoViewV2schema_version,repo_id,refs_facet_oid,workspaces: BTreeMap<WorkspaceId, ObjectHash>,change_roots,extension_facetsWorkspaceSnapshotV2workspace_id,head,index_tree_oid,raw_index_blob_oid,working_copy_tree_oid,untracked_manifest_oid,sparse_facet_oid,sequencer_facet_oid,worktree_generation,capture_policy,completeness,facet_restore_policiesraw_index_blob_oidpreserves intent-to-add / skip-worktree / stat bits that Git trees cannot expressStateFacet/RestorePolicyEvery restorable state owner implements
StateFacet(name,schema_version,restore_policy,capture,validate,restore,diff,roots).RestorePolicyis one ofAutoRestore(HEAD/refs/index/files/sequencer/sparse),Rebuild(derived projections),NeverRestore(ephemeral runtime state such as leases). Any unregistered mutable state makes the operation fail closed (neverfully_restorable).OperationV2op_idparent_op_idspre_view_oid/post_view_oidRepoViewV2manifest OIDs before/after the mutationkindCommand | ExternalSnapshot | Undo | Redo | Restore | Revert | ReconcilestatusRunning | Success | Failed | Partial | Abortedmetadatarestores_op_id/reverts_op_idpredecessor_map_oidPredecessorEdgelist for genealogyChangeIdand genealogyChangeId([u8; 16])SHA-256("libra-change-id-v1\0" || object_format || commit_oid_bytes)ChangeRevision(change_id, commit_oid, created_op_id, revision_ordinal)projectionPredecessorEdge(successor_oid, predecessor_oids, op_id, relation_kind, ordinal); kind ∈ Amend / Rebase / CherryPick / Squash / Split / Duplicate / Import / ExternalReconcileSQLite organization
v2 replaces the v1 schema (
operation/operation_parent/operation_view*) during development; old data is migrated by a one-shot import script if audit retention is needed.erDiagram OPERATION ||--o{ OPERATION_PARENT : parents OPERATION ||--o{ OPERATION_HEAD : heads OPERATION ||--o{ OPERATION_JOURNAL : journal OPERATION ||--o| AI_OPERATION_LINK : causal CHANGE_IDENTITY ||--o{ CHANGE_REVISION : revisions CHANGE_REVISION ||--o{ CHANGE_PREDECESSOR : evolves OPERATION { string op_id PK string repo_id int format_version string kind string status string command_name string pre_view_oid string post_view_oid string restores_op_id string reverts_op_id string predecessor_map_oid string causal_context_id int start_ts int end_ts } OPERATION_PARENT { string op_id string parent_op_id int ordinal } OPERATION_HEAD { string repo_id string scope_key string op_id int generation } OPERATION_JOURNAL { string journal_id PK string op_id string phase string pre_view_oid string target_view_oid string owner int updated_at string recovery_payload } CHANGE_IDENTITY { string change_id PK string repo_id string origin string created_op_id int created_at } CHANGE_REVISION { string change_id string commit_oid string created_op_id string visibility int revision_ordinal } CHANGE_PREDECESSOR { string successor_oid string predecessor_oid string op_id string relation_kind int ordinal } AI_OPERATION_LINK { string operation_id PK string session_id string run_id string tool_invocation_id string intent_id string repo_id string worktree_id string workspace_id int lease_generation string config_provenance_digest string redaction_version }operationop_idPK,repo_id,kind,status,command_name,pre/post_view_oid,restores/reverts_op_id,predecessor_map_oid,causal_context_id, timestampsoperation_parent(op_id, parent_op_id)PK,ordinaloperation_head(repo_id, scope_key, op_id)PK,generationoperation_journaljournal_idPK,op_id,phase,pre/target_view_oid,owner,updated_at,recovery_payloadchange_identitychange_idPK,repo_id,origin(random/synthetic/header),created_op_idchange_revision(change_id, commit_oid)PK,created_op_id,visibility,revision_ordinalchange_predecessor(successor_oid, predecessor_oid, op_id)PK,relation_kind,ordinalai_operation_linkoperation_idPK,session/run/tool_invocation/intent_id,repo/worktree/workspace_id,lease_generation,config_provenance_digest,redaction_versionImplementation path
The implementation is phased; each phase must pass its verification tests (including existing
status/CLI regression and fail-closed guards) before the next phase starts:worktree_io/,operation/{store,view,facet}.rs, v2 schema replaces v1snapshot/working_copy.rs, HEAD/refs/index/sequencer/sparse facetsmiddleware.rs,cli.rsclassification,ai/tools/*gatewayrestore/undo/doctor.rs, newopsubcommandschange/{identity,store,resolve}.rschange/{builder,genealogy}.rs, commit/rebase/squash call sites,ai_operation_linkrg 'operation_wrapper|operation_view|restorable' srczero hitsReferences
docs/development/operation-log-working-copy-change-id.md(branchdocs/jj-enhancement)src/internal/operation_wrapper.rs,src/command/op.rssrc/internal/worktree_scope.rs,src/command/status_io_worker.rs