AI Initiative logoAI Initiative
Reading room / Manthan

G-010 — Define Storage And CT Recovery

G-010 — Define Storage And CT Recovery. Covers 1. Job of this gate; 2. Local ledger schema; 2.1 Tables; 2.2 Schema versioning.

Platform design / acceptance contract
This is the retained source text, not a new validation of its claims. Numerical results and comparative claims require the accompanying source review before public reuse.

Read the connected case study or article →

driver-claude · 2026-05-06

Provenance, authorship and editorial notes

G-010 — Define Storage And CT Recovery

Id

manthan-manthan-platform-gates-g-010-storage-ct-recovery

Project

Manthan

Source path

manthan/manthan-platform/gates/G-010-storage-ct-recovery.md

Source sha256

ab8c2ef974df367ae9ff48e7b2dbba6c17325bd93d0ea7159ab719be93dccf1b

Local copy sha256

ab8c2ef974df367ae9ff48e7b2dbba6c17325bd93d0ea7159ab719be93dccf1b

Repo head

3a94084024a3434cef331fecac91c2c3b43f0313

Date

2026-05-06

Date basis

Explicit document metadata

Author

driver-claude

Author basis

Author

Last source commit

8b75d1b0157c20a0a463f0e31008ef9f73099beb

Last source commit date

2026-06-08T07:40:14Z

Document status

Platform design / acceptance contract

Summary

G-010 — Define Storage And CT Recovery. Covers 1. Job of this gate; 2. Local ledger schema; 2.1 Tables; 2.2 Schema versioning.

Topics
  • From protocol to platform
  • 1. Job of this gate
  • 2. Local ledger schema
  • 2.1 Tables
Theme

From protocol to platform

Priority

deep

Related ids
  • manthan-manthan-platform-gates-g-001-mvp-workflow
Evidence concerns
  • Recovered source text is preserved verbatim. Assertions, comparisons, commands and results belong to its original context; they were not re-executed or externally re-verified during this corpus review.
Word count

1476

Bytes

12532

Source relative

manthan-platform/gates/G-010-storage-ct-recovery.md

Remote mirror

manthan/manthan-platform/gates/G-010-storage-ct-recovery.md

Full text preserved

True

G-010 — Define Storage And CT Recovery

Status: drafted → reviewed → accepted → closed.
Author: driver-claude
Date: 2026-05-06


1. Job of this gate

Original platform §3.1 said only "an embedded database (e.g., SQLite or LevelDB)". Per audits/manthan-platform-review.md §7-F12 and §8-F-N: storage schema, CT submit idempotency, crash recovery, backup, and ledger-corruption quarantine are unspecified. Without these, a Manthan node that crashes mid-CT-submit double-submits envelopes; a corrupted ledger silently propagates bad state; backup/restore is undefined.

G-010 specifies:

2. Local ledger schema

Storage backend: SQLite (chosen for portability, single-file, well-understood crash semantics with WAL).

2.1 Tables

CREATE TABLE envelopes (
  id              BLOB PRIMARY KEY,         -- sha256 hash, 32 bytes
  trace           BLOB NOT NULL,
  parent          BLOB,                     -- nullable for genesis
  from_sigil      TEXT NOT NULL,
  to_sigil        TEXT,
  ts              INTEGER NOT NULL,         -- Unix seconds
  state           TEXT NOT NULL CHECK (state IN
                  ('seen','verified','queued_for_ct',
                   'submitted','inclusion_proven','gossiped','quarantined')),
  body_cbor       BLOB NOT NULL,            -- canonical CBOR body
  signatures_json TEXT NOT NULL,            -- JSON array of signature objects
  attestations_cbor BLOB,                   -- optional, for typed-artifact lookups
  payload_cbor    BLOB,
  received_at     INTEGER NOT NULL,         -- when this node first saw it
  state_ts        INTEGER NOT NULL          -- last state-transition timestamp
);

CREATE INDEX idx_envelopes_trace ON envelopes(trace);
CREATE INDEX idx_envelopes_from ON envelopes(from_sigil);
CREATE INDEX idx_envelopes_state ON envelopes(state);
CREATE INDEX idx_envelopes_ts ON envelopes(ts);

CREATE TABLE ct_submissions (
  envelope_id     BLOB PRIMARY KEY REFERENCES envelopes(id),
  log_id          BLOB NOT NULL,
  sct_json        TEXT NOT NULL,            -- the SCT received from log
  submitted_at    INTEGER NOT NULL,
  sip_json        TEXT,                     -- the SIP, when received
  inclusion_proven_at INTEGER,
  retry_count     INTEGER NOT NULL DEFAULT 0,
  last_retry_at   INTEGER,
  last_error      TEXT
);

CREATE INDEX idx_ct_submissions_log ON ct_submissions(log_id);
CREATE INDEX idx_ct_submissions_unfinished
  ON ct_submissions(log_id, inclusion_proven_at)
  WHERE inclusion_proven_at IS NULL;

CREATE TABLE peers (
  sigil           TEXT PRIMARY KEY,
  endpoint        TEXT NOT NULL,
  last_seen_at    INTEGER NOT NULL,
  charter_url     TEXT,
  charter_signed_at INTEGER,                -- freshness tracker
  jurisdiction    TEXT,                     -- declared per peer's Charter
  ownership_cluster TEXT,
  trust_score     REAL                      -- locally-computed Patina (G-003)
);

CREATE TABLE sths_observed (
  log_id          BLOB NOT NULL,
  tree_size       INTEGER NOT NULL,
  root_hash       BLOB NOT NULL,
  signed_sth_cbor BLOB NOT NULL,
  observed_at     INTEGER NOT NULL,
  observed_via    TEXT NOT NULL,            -- 'direct'|'gossip-dns'|'gossip-p2p'|'cross-pub'
  PRIMARY KEY (log_id, tree_size)
);

CREATE INDEX idx_sths_log ON sths_observed(log_id, tree_size DESC);

CREATE TABLE config (
  key             TEXT PRIMARY KEY,
  value           BLOB NOT NULL
);
-- canonical keys: 'operator_key_seed_hex', 'last_known_good_state', 'schema_version'

2.2 Schema versioning

config.schema_version is set on first init. Migrations are forward-only; a node refuses to start against a higher schema_version than its code knows about.

2.3 WAL mode + sync rules

PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;       -- production deployments may set FULL
PRAGMA foreign_keys = ON;

WAL mode + synchronous=NORMAL is the SQLite-recommended balance for write-heavy workloads with acceptable durability on power-loss.

3. Envelope state machine

Per gemini's tracker §G-010, envelopes flow through:

   ┌──────┐        ┌──────────┐        ┌──────────────────┐
   │ seen │───────▶│ verified │───────▶│ queued_for_ct    │
   └──────┘        └──────────┘        └────────┬─────────┘
                                                 │
                                                 ▼
                                       ┌──────────────────┐
                                       │   submitted      │ (SCT received)
                                       └────────┬─────────┘
                                                 │
                                                 ▼
                                       ┌──────────────────┐
                                       │ inclusion_proven │ (SIP verified)
                                       └────────┬─────────┘
                                                 │
                                                 ▼
                                       ┌──────────────────┐
                                       │    gossiped      │
                                       └──────────────────┘

Anywhere in the flow: detected anomaly → `quarantined`

3.1 State transition rules

FromToTrigger
(none)seenenvelope arrives via gossip or local emit; pre-verify
seenverifiedfull verifyEnvelope() passes; signatures + trace + nonces all OK
seenquarantinedverification fails for non-transient reason
verifiedqueued_for_ctenvelope class requires CT logging (per [EXT §1.3] + G-007 policy)
verifiedgossipedenvelope class doesn't require CT (e.g., optional research_finding)
queued_for_ctsubmittedlog returned 200 + valid SCT
queued_for_ctquarantinedlog returned 4xx with non-retriable reason (e.g., envelope_malformed)
submittedinclusion_provenSIP retrieved + audit-path verifies against an STH
submittedquarantinedMMD elapsed without SIP delivery
inclusion_provengossipedlocal node fans out to peer set per G-006
anyquarantinedmanual quarantine command

3.2 Idempotency

Every transition is idempotent: re-running the trigger on an envelope already in the target state is a no-op. Specifically:

This makes crash recovery (next §) straightforward.

4. Crash recovery

4.1 On startup

1. Open SQLite (WAL ensures consistent state).
2. Verify integrity:
   PRAGMA quick_check;
   If failed → fail-fast, log corruption, refuse to operate, exit non-zero.
3. SELECT envelope_id FROM ct_submissions WHERE inclusion_proven_at IS NULL
   AND submitted_at IS NOT NULL;
   For each: GET /sip/<envelope_id> from the log.
     - If 200: store SIP, transition to inclusion_proven.
     - If 202: SIP still pending; leave in submitted; retry later.
     - If 404 + tree_size > submitted_tree_size + MMD-window: log non-conformance, transition to quarantined.
4. SELECT envelope_id FROM envelopes WHERE state = 'queued_for_ct';
   For each: re-attempt POST /submit with idempotency key = envelope_id.
   Log returns the existing SCT if already submitted; we transition to submitted.
5. SELECT envelope_id FROM envelopes WHERE state = 'verified' AND not-yet-CT-decided;
   For each: re-evaluate "needs CT" per envelope class; transition appropriately.
6. Resume normal operation.

This handles the three crash windows:

4.2 The "double-submit" question

A naive HTTP POST /submit followed by crash mid-response would normally cause double-submit. Manthan's CT submit MUST be idempotent at the log layer (the log returns the same SCT for the same envelope_id submitted twice). Per lib/herald-ct-log/SCHEMA.md §9 (T-101), this is normative for HERALD CT logs.

If the deployed log doesn't honour idempotency: the Manthan node's pre-POST check (§3.2) prevents the duplicate submit by consulting local ledger first.

5. Backup and restore

5.1 Backup format

A "Manthan backup bundle" is:

manthan-backup-<sigil>-<timestamp>.tar.gz
├── manifest.json                 # sigil, version, schema_version, hash of each file
├── ledger.sqlite                 # SQLite file (consistent snapshot via .backup API)
├── operator.key.json             # encrypted under backup-passphrase
├── charter.md                    # current Charter
├── lots.md                       # current LOTS (if running a CT log)
└── peers.json                    # exported peer list (de-trust-scored)

5.2 Backup cadence

5.3 Restore semantics

Restoring a backup to a fresh node:
1. Validate manifest hashes.
2. Restore SQLite, decrypt operator key.
3. CT reconciliation: query the CT log for any envelopes the backup says are submitted but not inclusion_proven. Update accordingly.
4. Peer rediscovery: peers from the backup's peers.json are stale; re-validate Charter freshness before re-trusting.
5. STH catch-up: if the backup's last STH is older than the log's current STH by > MMD, fetch consistency-proof and update.

A restored node is operational within ~5 minutes of restore start (assuming network access to the CT log).

6. Ledger corruption quarantine

If PRAGMA quick_check returns non-OK at any point:
1. Immediately transition to read-only mode; log the corruption, mark last_known_good_state as the previous startup's checkpoint.
2. Do not write new envelopes; do not gossip; do not submit to CT.
3. Surface a SEV-1 incident per Charter §8 incident-disclosure policy.
4. Operator action required: restore from latest backup, or repair using .recover if the corruption is mild.
5. A corrupt-ledger node MUST NOT rejoin gossip until its next-startup PRAGMA quick_check passes against a clean restored ledger.

This is more conservative than "best-effort recover and continue" — Manthan errs toward integrity over availability.

7. Acceptance criteria

8. What G-010 does NOT cover

9. Sign-off


— G-010 closed, 2026-05-06.

Continue exploring