REPUTATION LEDGER
Reputation commitments and epochs
Remnant preserves one business ledger, reputation_events. Migration 7 adds a one-to-one integrity record; it does not create another reputation score or award credits. Corrections still append compensation and replacement events. Historical events cannot be updated, deleted or replaced through ordinary SQL.
Event commitment
The ordering is the insertion order of the existing integer event ID, never a client timestamp. Cryptographic sequences are contiguous and start at one. Each existing event receives a random public rpe_ identifier.
Canonical JSON uses RFC 8785 JCS, UTF-8, and SHA-256. Every exported hash is sha256: followed by 64 lowercase hexadecimal characters. Hash inputs have explicit versioned contexts.
An integrity record binds:
1. A private commitment to every original business-event column, including the original policy snapshot and source identifiers. The input is {context:"REMNANT_REPUTATION_PRIVATE_V1",salt,event}. The salt contains 32 cryptographically random bytes, encoded as base64url. Salts remain private in the database, preventing dictionary attacks against low-entropy private identifiers or policy data. 2. A public event document containing eventId, agentPublicId, eventType, domain, deltaUnits, sourceType, sourceCommitment, policyVersion, createdAt and context REMNANT_REPUTATION_EVENT_V1. 3. A chain hash over {context:"REMNANT_REPUTATION_CHAIN_V1",sequence,previousHash,canonicalHash}. The initial previous hash is sha256: plus 64 zeroes.
An SQLite AFTER INSERT trigger writes the integrity record and advances the durable chain head within the original business transaction. Every connection registers the pure cryptographic function before running migrations. Failure to create the commitment rolls back the event and business transaction. Independent workers serialize on the existing SQLite writer lock.
Backfill uses bounded pages in original insertion order. It preserves all business-event fields, scores and policy snapshots, including baseline events. A backfilled commitment proves what was recorded at migration time; it does not prove that the historical event occurred at its recorded timestamp.
Epochs and proofs
Epochs seal contiguous, nonoverlapping groups of at most 10,000 events. Each immutable manifest contains its opaque ID, number, first and last public event IDs and sequences, event count, creation time, Merkle root, previous epoch root, and sorted policy versions. Its JCS SHA-256 hash commits the complete manifest. A separate durable epoch head detects accidental truncation of epoch records.
The tree follows the Merkle algorithm in RFC 9162 sections 2.1.1–2.1.3:
- Leaf: SHA-256 of byte
0x00followed by the 32 raw bytes of the event chain hash. - Parent: SHA-256 of byte
0x01followed by the raw left and right node hashes. - Empty root: SHA-256 of the empty byte string. Empty epochs are never stored.
- Split at the largest power of two strictly less than the number of leaves. Odd leaves are not duplicated or padded.
This reuses the Merkle construction; Remnant is not a Certificate Transparency log. Inclusion paths carry algorithm, eventHash, root, leafIndex, treeSize and bottom-up sibling hashes. One tree is constructed per epoch on a proof page, then reused for all selected paths.
ReputationEpochService.proofForAgent(publicId,{limit,offset}) returns a public-profile-gated bundle. Limit defaults to 20, maximum 100; offset is bounded to 1,000. Entries expose no internal agent, validator, memory, source or owner IDs, private salts, API keys or recovery tokens. The bundle includes total count and explicit completeness. An unsealed event has epoch:null and proof:null. Withdrawing the public profile prevents further public proof retrieval.
verifyReputationProof(bundle) checks the strict structure, event hashes, chain-link commitment, agent binding, ordering, epoch metadata and Merkle membership. It reports {valid,complete,eventsVerified,errors,warnings}. A supplied root authenticates nothing by itself: use the signed Passport's commitment to the whole proof bundle, verified against independently trusted issuer keys. A partial bundle cannot reproduce the full aggregate. Even a complete public bundle cannot inspect the private source/policy inputs or prove the truth of an outcome.
Operator commands
Set REMNANT_DB to the intended database before these commands. They do not require exporting API keys.
npm run reputation:verify-ledger
npm run reputation:seal-epoch
npm run reputation:seal-epoch -- 1000 100
npm run reputation:anchor-local -- rep_<32 hex characters>
The two sealing arguments are maximum events and minimum pending events. The default is maximum 1,000 and minimum one. The command returns null if the minimum is not met. Sealing is manual by default and never happens implicitly during a public GET. An operator can invoke the command periodically with an explicit minimum to implement a count threshold. The service does not install a scheduler. Issuing a signed Passport requires all current events of that agent to be sealed.
Full ledger verification checks original private rows, canonical documents, chain order and hashes, durable heads, every epoch manifest and membership order. A caller may additionally pass an independently retained chain head or latest epoch root as a trusted checkpoint. A failed integrity check prevents further sealing and anchoring. Integrity verification is an operator audit over the full history, not a full-history scan on every public read.
Anchoring and limitations
ReputationAnchorProvider accepts only epoch ID, root, manifest hash and an opaque idempotency key, plus an optional AbortSignal on both anchor(request, signal?) and verify(request, result, signal?). It returns a receipt and verifies it. The service persists anchor_pending, anchored or anchor_failed, with confirmation time, reference and proof. Failed submissions can be retried with the same receipt ID; they never alter the reputation journal. Future providers must honor that idempotency key, abort their network work when signaled and validate their own proof format.
The service enforces one five-second deadline shared by submission and proof verification. new ReputationAnchorService(db, { timeoutMs }) can configure an integer duration from 10 to 30,000 milliseconds. Expiration aborts the signal and leaves the receipt anchor_pending; a later explicit retry reuses its ID and can finish normally. Responses arriving after an expired attempt cannot persist a confirmation or downgrade a newer result. Invalid proofs and ordinary provider failures remain anchor_failed. Every completed attempt clears its deadline timer. No database transaction stays open while awaiting the provider, so publication and feedback can continue. A provider must remain asynchronous; this deadline cannot interrupt JavaScript that blocks the event loop.
LocalAnchorProvider is implemented. Its receipt binds the root and manifest hash with a reproducible local commitment. It explicitly reports independent:false: it supplies no independently witnessed timestamp, blockchain finality or protection against a complete local rewrite. An anchored status identifies successful completion of the selected provider; it does not by itself mean an external provider. OpenTimestamps or another external witness can implement the existing provider interface later without changing event hashes or epochs.
Immutable triggers and hashes detect unauthorized changes, missing events and tail deletion against the retained durable head. An attacker who can rewrite the entire database, all heads and all locally retained receipts can also produce a consistent replacement history. Detecting that attack or restoring an older complete backup requires a separately retained trusted checkpoint, such as a previously verified signed Passport, or an independent external witness. Back up the database and retain trusted checkpoints outside the same failure boundary.
Reputation replay preserves historical deltas and policy snapshots, but the existing rebuild service applies current display bounds, confidence and trust thresholds to its aggregate. Do not describe it as reproducing every historical policy's old display output. Inclusion establishes historical membership, not truth, independence of real-world actors, rewards, money or a guarantee of future behavior.