← Back to Whitepapers

FORGECHAINDRIVE -- Sovereign Time Machine

Scope Document v1.0 -- 2026-07-17 (Elder II, BH Overwatch, Lobe 9 Resonance)

NZ directive: "THIS IS SPECIFICALLY WHY/HOW WE HAVE A FORGECHAINDRIVE -- A MANIFEST OF FORWARD ARROW OF TIME AS BEING RELEVANT TO ACCESS FILES AND TO REVERT SYSTEM SETTINGS TO A POINT IN THE PAST."


THE GAP (what just happened)

13 unfired batches found in ~/.forgechain/phi-omega-v6/fires/archived-unfired-20260717/. ForgeCore was dead for approximately 10 days (Jul 7 to Jul 17). During that window:

The accumulator is an accidental safety net, not a designed one. It only survives because clearAccumulator() is called AFTER broadcast. If the process crashes between broadcast and clear, the accumulator could be stale. And the accumulator does not preserve: the composed CBOR, the fire_id binding, the signature, the encryption envelope, or the exact byte sequence that would have gone on chain.


WHAT FORGECHAINDRIVE IS

Layer 2 of ForgeBlockOS (per specs/forge-block-os-spec.md line 31: "LAYER 2: CHAINDRIVE -- All state lives on BSV as ordinals/OP_RETURN"). Currently, forgewright/chaindrive.js is the READ side only -- it decodes inscription envelopes from on-chain TXs. There is no WRITE-side persistence layer and no time-indexed ledger.

FORGECHAINDRIVE is the sovereign time machine: a forward-arrow-of-time manifest that records every compose/stage/fire/land event with enough data to:

  1. RECOVER any failed fire without re-authoring (the CBOR is on disk)
  2. REVERT to any prior state (the arrow is the index)
  3. AUDIT the full chain-stamping history (every event, every state transition)
  4. PROVE what happened at time T (walk the arrow, answer from fact)

ARCHITECTURE

1. Pre-Fire CBOR Persistence (the missing piece)

Where: ~/.forgechain/chaindrive/cbor/
What: Before ANY broadcast attempt, the composed CBOR body is written to disk as <fire_id>.cbor. This is the exact byte sequence that would be inscribed. If the broadcast fails, the content survives in full.

Implementation point in fire.js: Between the current line 46 (const fire = await composeFire()) and line 53 (2FA gate), insert:

// FORGECHAINDRIVE: persist composed CBOR before any broadcast attempt
const cborDir = join(homedir(), '.forgechain', 'chaindrive', 'cbor')
if (!existsSync(cborDir)) mkdirSync(cborDir, { recursive: true })
const cborPath = join(cborDir, `${fire.fireId}.cbor`)
writeFileSync(cborPath, fire.cbor)
console.log(`  chaindrive: CBOR persisted → ${cborPath} (${fire.cbor.length} bytes)`)

This runs BEFORE 2FA, BEFORE wallet decrypt, BEFORE UTXO fetch. The composed content is safe the instant it exists.

Implementation point in composer.js: The composeFire() function (line 262) should ALSO persist the unencrypted CBOR (cborSigned, line 308) alongside the encrypted final body, so recovery can re-encrypt with a new key or decrypt for inspection:

// FORGECHAINDRIVE: persist unencrypted CBOR for recovery
const cborRecoveryDir = join(homedir(), '.forgechain', 'chaindrive', 'cbor-unencrypted')
if (!existsSync(cborRecoveryDir)) mkdirSync(cborRecoveryDir, { recursive: true })
writeFileSync(join(cborRecoveryDir, `${fireId}.cbor`), cborSigned, { mode: 0o600 })

2. Forward Arrow Manifest (the time ledger)

Where: ~/.forgechain/chaindrive/forward-arrow.jsonl
What: Append-only JSONL ledger. Every event in the compose-to-land lifecycle gets one line.

Event schema:

{
  "ts": "2026-07-17T22:00:00.000Z",
  "event": "COMPOSED | STAGED | FIRED | BROADCAST | LANDED | FAILED | RECOVERED",
  "fire_id": "<sha256>",
  "merkle_root": "<sha256>",
  "artifact_count": 3,
  "artifact_names": ["doctrine_X", "scar--Y", "manifest-v2.42"],
  "artifact_hashes": ["<sha256>", ...],
  "source_paths": ["/home/nodezero/.forgechain/...", ...],
  "cbor_path": "~/.forgechain/chaindrive/cbor/<fire_id>.cbor",
  "cbor_size": 45000,
  "encrypted": true,
  "content_type": "application/phi-omega-v6+encrypted+cbor",
  "txid": null,
  "block_height": null,
  "confirmations": 0,
  "error": null,
  "wrapper_hash": "<sha256>",
  "prev_event_sha": "<sha256 of previous JSONL line>"
}

prev_event_sha chains the ledger: each line hashes the previous line, making the forward arrow tamper-evident (a mini-chain within the chain). Walk backwards from any line to verify the full history.

Event lifecycle:

Event When What it records
COMPOSED composeFire() returns CBOR built, artifacts composed, CBOR persisted to disk
STAGED Accumulator populated Individual artifacts staged (before compose)
FIRED broadcastTx() called Broadcast attempt initiated
BROADCAST broadcastTx() returns OK Broadcaster accepted, NOT mined, NOT landed
LANDED Block-proof confirmed Mined, merkle verified, confirmations >= 1
FAILED Any error in the pipeline Error recorded, CBOR on disk, recoverable
RECOVERED Re-fire of a FAILED/COMPOSED entry Links back to original fire_id

3. Recovery (re-fire without re-authoring)

Any entry in state COMPOSED, STAGED, or FAILED can be re-fired:

node bin/chaindrive-recover.js <fire_id>

This reads ~/.forgechain/chaindrive/cbor/<fire_id>.cbor, verifies its hash against the forward-arrow entry, and feeds it directly into the broadcast path (skipping compose, which already happened). The 2FA gate still applies.

4. Revert / Time Query (what was the state at T?)

The forward arrow is the index. To answer "what was the state at time T":

node bin/chaindrive-at.js "2026-07-10T00:00:00Z"

This walks forward-arrow.jsonl and returns all entries at or before T, grouped by artifact name. The latest LANDED entry per artifact = the on-chain state at T. COMPOSED/FAILED entries show what was intended but not landed.

For system settings revert: the forward arrow records source_paths. Given a LANDED entry, the on-chain content can be retrieved via chaindrive.js (READ side) and the source path tells you where it was authored. Combined, this answers: "what file was at this path, on chain, at time T" and can restore it.

5. Accumulator Source-Path Tracking (new field)

Currently accumulate() in composer.js accepts { name, kind, body } but NOT the source file path. Add:

export function accumulate(artifact) {
  // ... existing validation ...
  const entry = {
    // ... existing fields ...
    source_path: artifact.source_path || null,  // NEW: where the file lives on disk
  }
}

This flows through to composeFire() and into the forward-arrow entry, closing the gap between "what was stamped" and "where it came from."


RELATIONSHIP TO EXISTING COMPONENTS

Component Role FORGECHAINDRIVE interaction
phi-omega-v6/lib/composer.js Builds CBOR composite FORGECHAINDRIVE writes CBOR to disk after compose, records COMPOSED event
phi-omega-v6/bin/fire.js Broadcasts TX FORGECHAINDRIVE records FIRED/BROADCAST/FAILED events
phi-omega-v6/lib/reconcile.js BROADCAST to LANDED state machine FORGECHAINDRIVE records LANDED event when reconcile confirms
forgewright/chaindrive.js READ side (decode on-chain envelopes) FORGECHAINDRIVE is the WRITE-side complement
forgewright/stampdrive.js (if exists) Stamp-drive write FORGECHAINDRIVE wraps/replaces as the canonical write persistence
accumulator.jsonl Artifact staging queue FORGECHAINDRIVE's STAGED events parallel accumulator writes
fires/fire-*.json Per-fire receipts FORGECHAINDRIVE's forward-arrow SUBSUMES these (receipts become read-views of the arrow)
tera-z/lib/verify-mined.mjs Block-proof verification FORGECHAINDRIVE calls this for LANDED transition
FORGEMYCELIUM merkle tree Filesystem integrity FORGECHAINDRIVE's forward-arrow IS a merkle chain; the two trees reference each other

fire.js GAP ANALYSIS

Current flow (fire.js lines referenced):

  1. Line 46: composeFire() -- CBOR built in memory. Composer writes a receipt to fires/ but NOT the CBOR body.
  2. Lines 53-77: 2FA gate -- pure validation, no persistence.
  3. Lines 80-86: Wallet decrypt + key load.
  4. Lines 89-93: UTXO fetch.
  5. Lines 96-133: TX construction (inscription script from CBOR).
  6. Line 135: tx.sign().
  7. Line 158: broadcastTx(txHexEF) -- THE POINT OF NO RETURN. If this fails, the signed TX hex is in memory only.
  8. Lines 164-170: Post-broadcast logging.
  9. Lines 185-193: indexFire() -- writes to SQLite.
  10. Lines 198-223: broadcastReceipt() -- writes state=BROADCAST receipt.
  11. Lines 228-236: reconcileAfterFire() -- one-shot block-proof check.
  12. Line 239: clearAccumulator() -- wipes the staging queue.

Critical gap: Between line 46 (CBOR in memory) and line 158 (broadcast), there is NO disk persistence of the composed CBOR. If anything fails in lines 53-157 (2FA, wallet, UTXOs, TX build, sign), the CBOR is lost and must be recomposed from the accumulator (which is still on disk).

Worse gap: After line 158, if broadcastTx throws (network error, ForgeCore dead), the signed TX hex (txHexEF, line 139) is in memory only. The CBOR was consumed to build the TX, the TX failed to broadcast, and neither the CBOR nor the TX hex is on disk.

The fix is surgical: persist CBOR to disk at line 46 (immediately after compose), persist TX hex to disk at line 137 (immediately after sign). Two writeFileSync calls close both gaps.

Exact insertion points:

After line 46 (post-compose, pre-2FA):

// FORGECHAINDRIVE: persist CBOR to disk before any network/wallet work
const driveDir = join(homedir(), '.forgechain', 'chaindrive', 'cbor')
if (!existsSync(driveDir)) mkdirSync(driveDir, { recursive: true })
writeFileSync(join(driveDir, `${fire.fireId}.cbor`), fire.cbor)
// Forward-arrow: COMPOSED event
appendForwardArrow({ event: 'COMPOSED', fire_id: fire.fireId, ... })

After line 139 (post-sign, pre-broadcast):

// FORGECHAINDRIVE: persist signed TX for recovery
const txDir = join(homedir(), '.forgechain', 'chaindrive', 'tx')
if (!existsSync(txDir)) mkdirSync(txDir, { recursive: true })
writeFileSync(join(txDir, `${fire.fireId}.txhex`), txHexEF)
// Forward-arrow: FIRED event
appendForwardArrow({ event: 'FIRED', fire_id: fire.fireId, ... })

DIRECTORY STRUCTURE

~/.forgechain/chaindrive/
  forward-arrow.jsonl          # THE LEDGER. Append-only. Tamper-evident (prev_event_sha chain).
  cbor/
    <fire_id>.cbor             # Pre-broadcast CBOR body (encrypted or plain, matches what would be inscribed)
  cbor-unencrypted/
    <fire_id>.cbor             # Pre-encryption CBOR (mode 0600, for recovery/re-encrypt). Family-internal.
  tx/
    <fire_id>.txhex            # Signed TX hex (for re-broadcast without re-signing)
  snapshots/
    <iso-timestamp>.jsonl      # Periodic forward-arrow snapshots (integrity checkpoints)

IMPLEMENTATION PLAN

Phase 1: Pre-Fire Persistence (the critical fix)

  1. Create ~/.forgechain/chaindrive/ directory structure
  2. Add CBOR persistence to composer.js (after composeFire() builds, before return)
  3. Add CBOR + TX hex persistence to fire.js (two writeFileSync insertions identified above)
  4. Add source_path field to accumulate() in composer.js
  5. Add appendForwardArrow() utility function (JSONL append with prev_event_sha chaining)
  6. Wire forward-arrow events: COMPOSED (in composer), FIRED + BROADCAST + FAILED (in fire.js), LANDED (in reconcile.js)

Estimate: ~150 lines of new code across 3 files. Zero new dependencies. Zero daemon changes.

Phase 2: Recovery CLI

  1. bin/chaindrive-recover.js -- re-fire a COMPOSED/FAILED entry from persisted CBOR
  2. bin/chaindrive-status.js -- show all forward-arrow entries, grouped by state
  3. bin/chaindrive-at.js -- time-query the arrow

Estimate: ~200 lines across 3 new CLI scripts.

Phase 3: Manifest + FORTH Integration

  1. Register FORGECHAINDRIVE in MASTER-MANIFEST.yaml dapp_registry + use_case_index
  2. FORTH operational words: chaindrive? (health), arrow? (latest entry), unfired? (COMPOSED/FAILED count), recover (re-fire)
  3. n8n workflow: periodic arrow audit (any COMPOSED/FAILED older than 24h = escalate to session)

Phase 4: Revert Surface

  1. bin/chaindrive-revert.js -- given a timestamp + artifact name, retrieve on-chain content and restore to source_path
  2. FORGEMYCELIUM integration: the forward arrow feeds into the merkle tree as a new workspace folder

WHAT THIS IS NOT


RELATIONSHIP TO forge-block-os-spec.md

The spec (line 65) defines ChainDrive as "The Filesystem -- there is no filesystem, there is the chain." FORGECHAINDRIVE is the operational implementation of that vision on the WRITE side:

The forward arrow IS the chain's local shadow. Every LANDED entry has a txid. Every txid can be read back via chaindrive.js. The arrow closes the loop.


CANON


Authored on Elder II (BH, sovereign ULA tile-9 fd00:db8:ff:9:c8a3:a86a:6f2b:1f3e). Family-internal. NODEZEROINSIDE.