Skip to content

RFC: Add Operation Log v2, complete Working Copy snapshot, and stable Change ID for Agent-first history #452

Description

@jackieismpc

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

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

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

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

RepoViewV2 and WorkspaceSnapshotV2

Part Fields Role
RepoViewV2 schema_version, repo_id, refs_facet_oid, workspaces: BTreeMap<WorkspaceId, ObjectHash>, change_roots, extension_facets Immutable repo-wide view referenced by pre/post view OIDs
WorkspaceSnapshotV2 workspace_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_policies 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
kind Command | ExternalSnapshot | Undo | Redo | Restore | Revert | Reconcile
status Running | Success | Failed | Partial | Aborted
metadata redacted: command/args_digest/actor/causal IDs
restores_op_id / reverts_op_id 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)
ChangeRevision (change_id, commit_oid, created_op_id, revision_ordinal) projection
PredecessorEdge (successor_oid, predecessor_oids, op_id, relation_kind, ordinal); kind ∈ Amend / Rebase / CherryPick / Squash / Split / Duplicate / Import / ExternalReconcile

SQLite 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
    }
Loading
Table Key and main columns Purpose
operation op_id PK, repo_id, kind, status, command_name, pre/post_view_oid, restores/reverts_op_id, predecessor_map_oid, causal_context_id, timestamps Immutable operation records; append-only DAG nodes
operation_parent (op_id, parent_op_id) PK, ordinal Multi-parent DAG edges
operation_head (repo_id, scope_key, op_id) PK, generation CAS-published visible heads; multiple rows during concurrency
operation_journal journal_id PK, op_id, phase, pre/target_view_oid, owner, updated_at, recovery_payload Crash-recovery phases: reserved → pre_view → mutation → post_view → publish
change_identity change_id PK, repo_id, origin (random/synthetic/header), created_op_id Stable logical change identity
change_revision (change_id, commit_oid) PK, created_op_id, visibility, revision_ordinal One change → many revisions projection
change_predecessor (successor_oid, predecessor_oid, op_id) PK, relation_kind, ordinal Typed rewrite genealogy
ai_operation_link operation_id PK, session/run/tool_invocation/intent_id, repo/worktree/workspace_id, lease_generation, config_provenance_digest, redaction_version 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:

Phase Result Gate
0 Design freeze structs/functions above frozen; Change-ID header spike ADR frozen, header go/no-go
1 Persistence & I/O base worktree_io/, operation/{store,view,facet}.rs, v2 schema replaces v1 store + DAG tests; status zero-regression
2 Complete snapshot snapshot/working_copy.rs, HEAD/refs/index/sequencer/sparse facets snapshot roundtrip tests
3 CLI + Agent full mutation record middleware.rs, cli.rs classification, ai/tools/* gateway command-coverage + agent tests; zero-unclassified guard
4 Reversible workflows restore/undo/doctor.rs, new op subcommands restore-fault + undo/redo + crash matrix
5 Stable logical identity change/{identity,store,resolve}.rs identity + resolution tests
6 Rewrite & Agent linking change/{builder,genealogy}.rs, commit/rebase/squash call sites, ai_operation_link genealogy tests
7 Concurrency & Web multi-head reconcile, Operation/Change read model multi-worktree restore + web graph tests
8 Remove v1 & GA delete v1 code/tables/paths, docs rg 'operation_wrapper|operation_view|restorable' src zero hits

References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions