{"_id":"@brightchain/brightledger-metering-log-lib","name":"@brightchain/brightledger-metering-log-lib","dist-tags":{"latest":"0.31.0"},"versions":{"0.31.0":{"name":"@brightchain/brightledger-metering-log-lib","version":"0.31.0","description":"BrightLedger metering log - append-only hash-chained CBOR log with Ed25519 signing and Merkle proofs","main":"src/index.js","types":"src/index.d.ts","repository":{"type":"git","url":"git+https://github.com/Digital-Defiance/BrightChain.git","directory":"brightledger-metering-log-lib"},"homepage":"https://github.com/Digital-Defiance/BrightChain#readme","bugs":{"url":"https://github.com/Digital-Defiance/BrightChain/issues"},"keywords":["brightchain","brightledger","metering","log","cbor","blake3","ed25519","merkle"],"author":{"name":"Digital Defiance"},"license":"MIT","dependencies":{"@brightchain/brightchain-lib":"^0.31.0","@digitaldefiance/ecies-lib":"5.1.6","@noble/curves":"^1.9.0","@noble/hashes":"^1.8.0","cbor-x":"^1.6.0"},"devDependencies":{"@types/node":"^22.0.0","fast-check":"^4.7.0","ts-jest":"^29.0.0"},"scripts":{"build:prod":"npx nx build brightledger-metering-log-lib --configuration=production"},"type":"commonjs","_id":"@brightchain/brightledger-metering-log-lib@0.31.0","gitHead":"96e479427cb59853e7170c050139f67d2ddd8907","_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-dgJDCEfv4NsU9H296rHU85UYijCNCFtdd/xdmUC5NrLnjLeSiceGVaXlPHsAqTeNmN5wuHz2bZO1ixpn2zG9Zg==","shasum":"22de396ef9003f04367051e311246d842d990a48","tarball":"https://registry.npmjs.org/@brightchain/brightledger-metering-log-lib/-/brightledger-metering-log-lib-0.31.0.tgz","fileCount":78,"unpackedSize":287921,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIF9Q1nsxo+3H+ir3QgjFgYdjV6Cy3AmjfOCKaT58FCpqAiEA2ba0I8USiXSB9B1avlnzwWk22eJjCYsaAzLL9X0mPpo="}]},"_npmUser":{"name":"jessica-mulein","email":"jessica@mulein.com"},"directories":{},"maintainers":[{"name":"jessica-mulein","email":"jessica@mulein.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/brightledger-metering-log-lib_0.31.0_1778085626887_0.35667356199704026"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-06T16:40:26.827Z","0.31.0":"2026-05-06T16:40:27.054Z","modified":"2026-05-06T16:40:27.243Z"},"maintainers":[{"name":"jessica-mulein","email":"jessica@mulein.com"}],"description":"BrightLedger metering log - append-only hash-chained CBOR log with Ed25519 signing and Merkle proofs","homepage":"https://github.com/Digital-Defiance/BrightChain#readme","keywords":["brightchain","brightledger","metering","log","cbor","blake3","ed25519","merkle"],"repository":{"type":"git","url":"git+https://github.com/Digital-Defiance/BrightChain.git","directory":"brightledger-metering-log-lib"},"author":{"name":"Digital Defiance"},"bugs":{"url":"https://github.com/Digital-Defiance/BrightChain/issues"},"license":"MIT","readme":"# BrightLedger Metering Log Library\n\nNode-only TypeScript library implementing the metering-log Layer 2 of the\nBrightLedger stack.  It provides a durable, hash-chained, Ed25519-signed\nappend-only log per shard, a Merkle-tree-indexed settlement batcher, a\ndispute/challenge path, and crash-recovery primitives.\n\n---\n\n## Table of Contents\n\n1. [Storage Layout](#storage-layout)\n2. [Signing Cadence](#signing-cadence)\n3. [Settlement Format](#settlement-format)\n4. [Dispute Window](#dispute-window)\n5. [Operator Runbook — Compromise Revoke](#operator-runbook--compromise-revoke)\n6. [Brand Vocabulary](#brand-vocabulary)\n\n---\n\n## Storage Layout\n\n### On-disk directory structure\n\n```\n<shardDir>/\n  writer.lock          — exclusive POSIX lock held by the active writer\n  log.000001.cbor      — first log segment\n  log.000002.cbor      — second segment (created after rotation)\n  ...\n  state.json           — crash-recovery state (written atomically via .bak)\n  state.json.bak       — temp file used during atomic state write\n```\n\n### Record framing\n\nEvery record is stored as a **length-prefixed frame**:\n\n```\n┌──────────────────┬────────────────────────────────────┐\n│  u32-LE (4 B)    │  CBOR-encoded MeteringRecord        │\n│  payload length  │  (variable, ≤ MAX_LOG_FILE_SIZE)    │\n└──────────────────┴────────────────────────────────────┘\n```\n\nThe `LENGTH_PREFIX_SIZE` constant is `4`.  A scan stops (silently) at the first\nincomplete frame — it never raises on a torn write.\n\n### File rotation\n\nLog segments are rotated when the *next* append would push the file past\n`MAX_LOG_FILE_SIZE` (256 MiB).  File sequence numbers are 1-based and\nzero-padded to six digits: `log.000001.cbor`, `log.000002.cbor`, …\n\n### Group-commit fsync\n\n`FlatFileMeteringStorage` batches writes and calls `fdatasync` every\n`groupCommitSize` appends (default `DEFAULT_GROUP_COMMIT_SIZE = 64`).\nThis balances durability against throughput; p99 per-append latency stays\nbelow 5 ms on typical SSD hardware.\n\n### Exclusive writer lock\n\n`open()` creates and `flock(LOCK_EX | LOCK_NB)`-locks `writer.lock`.  A\nsecond writer opening the same directory will throw immediately.  `close()`\nreleases the lock and removes the file.\n\n---\n\n## Signing Cadence\n\nEvery `signingCadence`-th record the shard emits a **signature record**\n(`PROCESS_KEY_SIGN`) that covers the Ed25519 signature of the running BLAKE3\nchain tip.\n\n| Constant | Value | Meaning |\n|---|---|---|\n| `MIN_SIGNING_CADENCE` | 16 | Minimum records between signatures |\n| `DEFAULT_SIGNING_CADENCE` | 64 | Default (records per signature) |\n| `MAX_SIGNING_CADENCE` | 256 | Maximum records between signatures |\n| `MAX_PROCESS_KEY_LIFETIME_MS` | 604 800 000 (7 days) | Maximum cert lifetime |\n\nThe process key cert (`createProcessKeyAction`) encodes an Ed25519 public key,\nan expiry timestamp (Unix-ms), and is signed by an operator root key.  At\nexpiry the shard refuses new appends until the cert is renewed.\n\n### Hash chain\n\nEach record carries a BLAKE3 hash that chains the previous record's hash:\n\n```\ntipHash[n] = BLAKE3( CBOR(record[n]) || tipHash[n-1] )\n```\n\n`GENESIS_HASH` (all-zero 32 bytes) seeds the chain for sequence 0.\n\n---\n\n## Settlement Format\n\n`BatchSettlementAction` is emitted when either the record count exceeds\n`DEFAULT_MAX_RECORDS` (10 000) or `DEFAULT_MAX_AGE_MS` (5 000 ms) since the\nlast settlement.\n\n```typescript\ninterface BatchSettlementAction {\n  type: 'BATCH_SETTLEMENT';\n  shardId: string;\n  fromSeq: bigint;       // inclusive start of the settled range\n  toSeq:   bigint;       // inclusive end of the settled range\n  tipHash: Uint8Array;   // 32-byte BLAKE3 chain tip at toSeq\n  itemsRoot: Uint8Array; // 32-byte Merkle root of all record leaf-hashes in the range\n  memberDeltas: MemberDelta[];  // one entry per (memberId, assetId) pair\n  sigEnvelope: {\n    publicKey: Uint8Array;   // process-key Ed25519 public key (32 B)\n    signature:  Uint8Array;  // Ed25519 sig over canonical settlement bytes (64 B)\n  };\n}\n\ninterface MemberDelta {\n  memberId: Uint8Array;   // 32-byte opaque member identifier\n  assetId:  string;       // e.g. 'joule', 'compute-hour'\n  earned:   bigint;\n  spent:    bigint;\n}\n```\n\n### Merkle tree\n\n`itemsRoot` is the root of a binary BLAKE3 Merkle tree over the leaf hashes of\nall records in the settled range.  Leaf hashes are computed as\n`BLAKE3(0x00 || data)`.  Inclusion proofs (`proveInclusion`) allow any\ndownstream verifier to spot-check any individual record without replaying the\nfull range.\n\n### Settlement size guarantee\n\nWith up to 10 000 records and ≤ 1 000 distinct `(memberId, assetId)` pairs the\nJSON-serialised `memberDeltas` array fits within **256 KiB** (UTF-8 bytes).\n\n---\n\n## Dispute Window\n\nAn off-chain verifier may raise a `DisputeChallenge` against a published\nsettlement.  The following timing constants govern the protocol:\n\n| Constant | Value | Meaning |\n|---|---|---|\n| `DEFAULT_DISPUTE_WINDOW_MS` | 86 400 000 (24 h) | Window after settlement in which a challenge may be raised |\n| `DEFAULT_DISPUTE_RESPONSE_MS` | 21 600 000 (6 h) | Time the operator has to respond to a challenge |\n\n### Dispute lifecycle\n\n```\nSETTLEMENT_CONFIRMED\n  └─[within disputeWindowMs]→ CHALLENGED\n      ├─[operator responds, hashes match]→ CONFIRMED (challenge rejected)\n      ├─[operator responds, mismatch]    → DISPUTED_FRAUD\n      └─[no response within responseMs]  → DISPUTED_NO_RESPONSE\n```\n\n`evaluateDisputeChallenge(challenge, response?, options?)` implements the\nstate-machine and returns a `DisputeResolution`.\n\n`applyDisputeReversal(store, resolution)` applies the reversal of earned and\nspent balances from a fraudulent or unresponded settlement to an\n`IAssetAccountStore`.\n\n---\n\n## Operator Runbook — Compromise Revoke\n\nUse this procedure when a process key's private key is believed to be\ncompromised.\n\n### 1 — Stop the shard writer\n\nStop any process that holds `writer.lock` in the affected shard directory.\n\n### 2 — Issue a revocation action\n\n```typescript\nimport { createProcessKeyRevokeAction } from 'brightledger-metering-log-lib';\n\nconst revokeAction = createProcessKeyRevokeAction({\n  processKeyId: '<hex of compromised public key>',\n  revokedAt:    Date.now(),\n  reason:       'key-compromise',\n  operatorSig:  operatorSignBytes(canonicalRevokeBytes),\n});\n```\n\nAppend the revocation record to the shard log using a replacement process key\nthat has already been certified.\n\n### 3 — Identify the tainted range\n\n```typescript\nimport { MeteringLogShard } from 'brightledger-metering-log-lib';\n\nconst shard = new MeteringLogShard(storage, verifier);\n// reverseRange returns all settlement actions in [fromSeq, toSeq]\n// that were signed by the revoked key\nconst tainted = await verifier.reverseRange(revokedPublicKey, fromSeq, toSeq);\n```\n\n### 4 — Dispute or reverse tainted settlements\n\nFor each tainted `BatchSettlementAction`:\n\n```typescript\nimport { applyDisputeReversal } from 'brightledger-metering-log-lib';\n\n// Build a DisputeResolution manually (DISPUTED_FRAUD or DISPUTED_NO_RESPONSE)\nconst resolution = {\n  status: 'DISPUTED_FRAUD',\n  challenge: { /* ... */ },\n  detail: 'process key compromised — automated reversal',\n};\n\napplyDisputeReversal(assetAccountStore, resolution);\n```\n\n### 5 — Crash recovery after forced shutdown\n\nIf the shard was killed mid-write, run crash recovery before reopening:\n\n```typescript\nimport { recoverShard } from 'brightledger-metering-log-lib';\n\nconst result = await recoverShard(shardDir, shardId);\nconsole.log(`Recovered ${result.recordCount} records. Truncated: ${result.wasTruncated}`);\n// result.state.lastSeq  — last valid sequence number\n// result.state.tipHash  — BLAKE3 chain tip at lastSeq\n```\n\n`recoverShard` scans forward from the start, finds the first incomplete frame,\ntruncates the file at that byte offset, writes an atomic `state.json` (via\n`state.json.bak` + `rename`), and returns a `ShardRecoveryResult`.\n\n---\n\n## Brand Vocabulary\n\nAllowed verbs: `earn`, `spend`, `transfer`, `reserve`, `settle`, `release`,\n`credit`, `debit`, `attest`, `flush`, `seal`.\n\nForbidden terms (must not appear in source or docs):\n`coin`, `holder`, `tokenomics`, `airdrop`, `staking`, `marketCap`.\n","readmeFilename":"README.md","_rev":"1-a286a9a426feca300557031acf481d75"}