@git-stunts/git-warp 11.2.1 → 11.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +24 -1
  2. package/bin/cli/commands/check.js +2 -2
  3. package/bin/cli/commands/doctor/checks.js +12 -12
  4. package/bin/cli/commands/doctor/index.js +2 -2
  5. package/bin/cli/commands/doctor/types.js +1 -1
  6. package/bin/cli/commands/history.js +12 -5
  7. package/bin/cli/commands/install-hooks.js +5 -5
  8. package/bin/cli/commands/materialize.js +2 -2
  9. package/bin/cli/commands/patch.js +142 -0
  10. package/bin/cli/commands/path.js +4 -4
  11. package/bin/cli/commands/query.js +54 -13
  12. package/bin/cli/commands/registry.js +4 -0
  13. package/bin/cli/commands/seek.js +17 -11
  14. package/bin/cli/commands/tree.js +230 -0
  15. package/bin/cli/commands/trust.js +3 -3
  16. package/bin/cli/commands/verify-audit.js +8 -7
  17. package/bin/cli/commands/view.js +6 -5
  18. package/bin/cli/infrastructure.js +26 -12
  19. package/bin/cli/shared.js +2 -2
  20. package/bin/cli/types.js +19 -8
  21. package/bin/presenters/index.js +35 -9
  22. package/bin/presenters/json.js +14 -12
  23. package/bin/presenters/text.js +155 -33
  24. package/index.d.ts +118 -22
  25. package/index.js +2 -0
  26. package/package.json +5 -3
  27. package/src/domain/WarpGraph.js +4 -1
  28. package/src/domain/crdt/ORSet.js +8 -8
  29. package/src/domain/errors/EmptyMessageError.js +2 -2
  30. package/src/domain/errors/ForkError.js +1 -1
  31. package/src/domain/errors/IndexError.js +1 -1
  32. package/src/domain/errors/OperationAbortedError.js +1 -1
  33. package/src/domain/errors/QueryError.js +1 -1
  34. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  35. package/src/domain/errors/ShardCorruptionError.js +2 -2
  36. package/src/domain/errors/ShardLoadError.js +2 -2
  37. package/src/domain/errors/ShardValidationError.js +4 -4
  38. package/src/domain/errors/StorageError.js +2 -2
  39. package/src/domain/errors/SyncError.js +1 -1
  40. package/src/domain/errors/TraversalError.js +1 -1
  41. package/src/domain/errors/TrustError.js +1 -1
  42. package/src/domain/errors/WarpError.js +2 -2
  43. package/src/domain/errors/WormholeError.js +1 -1
  44. package/src/domain/services/AuditReceiptService.js +6 -6
  45. package/src/domain/services/AuditVerifierService.js +52 -38
  46. package/src/domain/services/BitmapIndexBuilder.js +3 -3
  47. package/src/domain/services/BitmapIndexReader.js +28 -19
  48. package/src/domain/services/BoundaryTransitionRecord.js +18 -17
  49. package/src/domain/services/CheckpointSerializerV5.js +17 -16
  50. package/src/domain/services/CheckpointService.js +22 -3
  51. package/src/domain/services/CommitDagTraversalService.js +13 -13
  52. package/src/domain/services/DagPathFinding.js +7 -7
  53. package/src/domain/services/DagTopology.js +1 -1
  54. package/src/domain/services/DagTraversal.js +1 -1
  55. package/src/domain/services/HealthCheckService.js +1 -1
  56. package/src/domain/services/HookInstaller.js +1 -1
  57. package/src/domain/services/HttpSyncServer.js +92 -41
  58. package/src/domain/services/IndexRebuildService.js +7 -7
  59. package/src/domain/services/IndexStalenessChecker.js +4 -3
  60. package/src/domain/services/JoinReducer.js +26 -11
  61. package/src/domain/services/KeyCodec.js +7 -0
  62. package/src/domain/services/LogicalTraversal.js +1 -1
  63. package/src/domain/services/MessageCodecInternal.js +1 -1
  64. package/src/domain/services/MigrationService.js +1 -1
  65. package/src/domain/services/ObserverView.js +8 -8
  66. package/src/domain/services/PatchBuilderV2.js +96 -30
  67. package/src/domain/services/ProvenanceIndex.js +1 -1
  68. package/src/domain/services/ProvenancePayload.js +1 -1
  69. package/src/domain/services/QueryBuilder.js +3 -3
  70. package/src/domain/services/StateDiff.js +14 -11
  71. package/src/domain/services/StateSerializerV5.js +2 -2
  72. package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
  73. package/src/domain/services/SyncAuthService.js +3 -2
  74. package/src/domain/services/SyncProtocol.js +25 -11
  75. package/src/domain/services/TemporalQuery.js +9 -6
  76. package/src/domain/services/TranslationCost.js +7 -5
  77. package/src/domain/services/WormholeService.js +16 -7
  78. package/src/domain/trust/TrustCanonical.js +3 -3
  79. package/src/domain/trust/TrustEvaluator.js +18 -3
  80. package/src/domain/trust/TrustRecordService.js +30 -23
  81. package/src/domain/trust/TrustStateBuilder.js +21 -8
  82. package/src/domain/trust/canonical.js +6 -6
  83. package/src/domain/types/TickReceipt.js +1 -1
  84. package/src/domain/types/WarpErrors.js +45 -0
  85. package/src/domain/types/WarpOptions.js +29 -0
  86. package/src/domain/types/WarpPersistence.js +41 -0
  87. package/src/domain/types/WarpTypes.js +2 -2
  88. package/src/domain/types/WarpTypesV2.js +2 -2
  89. package/src/domain/utils/MinHeap.js +6 -5
  90. package/src/domain/utils/canonicalStringify.js +5 -4
  91. package/src/domain/utils/roaring.js +31 -5
  92. package/src/domain/warp/PatchSession.js +40 -18
  93. package/src/domain/warp/_wiredMethods.d.ts +199 -45
  94. package/src/domain/warp/checkpoint.methods.js +5 -1
  95. package/src/domain/warp/fork.methods.js +2 -2
  96. package/src/domain/warp/materialize.methods.js +55 -5
  97. package/src/domain/warp/materializeAdvanced.methods.js +15 -4
  98. package/src/domain/warp/patch.methods.js +54 -29
  99. package/src/domain/warp/provenance.methods.js +5 -3
  100. package/src/domain/warp/query.methods.js +89 -6
  101. package/src/domain/warp/sync.methods.js +16 -11
  102. package/src/globals.d.ts +64 -0
  103. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
  104. package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
  105. package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
  106. package/src/infrastructure/adapters/GitGraphAdapter.js +18 -13
  107. package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
  108. package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
  109. package/src/visualization/layouts/converters.js +2 -2
  110. package/src/visualization/layouts/elkAdapter.js +1 -1
  111. package/src/visualization/layouts/elkLayout.js +10 -7
  112. package/src/visualization/layouts/index.js +1 -1
  113. package/src/visualization/renderers/ascii/seek.js +16 -6
  114. package/src/visualization/renderers/svg/index.js +1 -1
@@ -16,6 +16,19 @@
16
16
  * @see docs/specs/AUDIT_RECEIPT.md Section 8
17
17
  */
18
18
 
19
+ /**
20
+ * @typedef {Object} AuditReceipt
21
+ * @property {number} version
22
+ * @property {string} graphName
23
+ * @property {string} writerId
24
+ * @property {string} dataCommit
25
+ * @property {string} opsDigest
26
+ * @property {string} prevAuditCommit
27
+ * @property {number} tickStart
28
+ * @property {number} tickEnd
29
+ * @property {number} timestamp
30
+ */
31
+
19
32
  import { buildAuditPrefix, buildAuditRef } from '../utils/RefLayout.js';
20
33
  import { decodeAuditMessage } from './AuditMessageCodec.js';
21
34
  import { TrustRecordService } from '../trust/TrustRecordService.js';
@@ -67,14 +80,15 @@ function validateOidFormat(value) {
67
80
 
68
81
  /**
69
82
  * Checks whether a receipt object has the expected 9 fields with correct types.
70
- * @param {*} receipt
83
+ * @param {unknown} receipt
71
84
  * @returns {string|null} Error message or null if valid
72
85
  */
73
86
  function validateReceiptSchema(receipt) {
74
87
  if (!receipt || typeof receipt !== 'object') {
75
88
  return 'receipt is not an object';
76
89
  }
77
- const keys = Object.keys(receipt);
90
+ const rec = /** @type {Record<string, unknown>} */ (receipt);
91
+ const keys = Object.keys(rec);
78
92
  if (keys.length !== 9) {
79
93
  return `expected 9 fields, got ${keys.length}`;
80
94
  }
@@ -83,46 +97,46 @@ function validateReceiptSchema(receipt) {
83
97
  'tickEnd', 'tickStart', 'timestamp', 'version', 'writerId',
84
98
  ];
85
99
  for (const k of required) {
86
- if (!(k in receipt)) {
100
+ if (!(k in rec)) {
87
101
  return `missing field: ${k}`;
88
102
  }
89
103
  }
90
- if (receipt.version !== 1) {
91
- return `unsupported version: ${receipt.version}`;
104
+ if (rec.version !== 1) {
105
+ return `unsupported version: ${rec.version}`;
92
106
  }
93
- if (typeof receipt.graphName !== 'string' || receipt.graphName.length === 0) {
107
+ if (typeof rec.graphName !== 'string' || rec.graphName.length === 0) {
94
108
  return 'graphName must be a non-empty string';
95
109
  }
96
- if (typeof receipt.writerId !== 'string' || receipt.writerId.length === 0) {
110
+ if (typeof rec.writerId !== 'string' || rec.writerId.length === 0) {
97
111
  return 'writerId must be a non-empty string';
98
112
  }
99
- if (typeof receipt.dataCommit !== 'string') {
113
+ if (typeof rec.dataCommit !== 'string') {
100
114
  return 'dataCommit must be a string';
101
115
  }
102
- if (typeof receipt.opsDigest !== 'string') {
116
+ if (typeof rec.opsDigest !== 'string') {
103
117
  return 'opsDigest must be a string';
104
118
  }
105
- if (typeof receipt.prevAuditCommit !== 'string') {
119
+ if (typeof rec.prevAuditCommit !== 'string') {
106
120
  return 'prevAuditCommit must be a string';
107
121
  }
108
- if (!Number.isInteger(receipt.tickStart) || receipt.tickStart < 1) {
109
- return `tickStart must be integer >= 1, got ${receipt.tickStart}`;
122
+ if (!Number.isInteger(rec.tickStart) || /** @type {number} */ (rec.tickStart) < 1) {
123
+ return `tickStart must be integer >= 1, got ${rec.tickStart}`;
110
124
  }
111
- if (!Number.isInteger(receipt.tickEnd) || receipt.tickEnd < receipt.tickStart) {
112
- return `tickEnd must be integer >= tickStart, got ${receipt.tickEnd}`;
125
+ if (!Number.isInteger(rec.tickEnd) || /** @type {number} */ (rec.tickEnd) < /** @type {number} */ (rec.tickStart)) {
126
+ return `tickEnd must be integer >= tickStart, got ${rec.tickEnd}`;
113
127
  }
114
- if (receipt.version === 1 && receipt.tickStart !== receipt.tickEnd) {
115
- return `v1 requires tickStart === tickEnd, got ${receipt.tickStart} !== ${receipt.tickEnd}`;
128
+ if (rec.version === 1 && rec.tickStart !== rec.tickEnd) {
129
+ return `v1 requires tickStart === tickEnd, got ${rec.tickStart} !== ${rec.tickEnd}`;
116
130
  }
117
- if (!Number.isInteger(receipt.timestamp) || receipt.timestamp < 0) {
118
- return `timestamp must be non-negative integer, got ${receipt.timestamp}`;
131
+ if (!Number.isInteger(rec.timestamp) || /** @type {number} */ (rec.timestamp) < 0) {
132
+ return `timestamp must be non-negative integer, got ${rec.timestamp}`;
119
133
  }
120
134
  return null;
121
135
  }
122
136
 
123
137
  /**
124
138
  * Validates trailers against the CBOR receipt fields.
125
- * @param {*} receipt
139
+ * @param {AuditReceipt} receipt
126
140
  * @param {{ graph: string, writer: string, dataCommit: string, opsDigest: string, schema: number }} decoded
127
141
  * @returns {string|null} Error message or null if consistent
128
142
  */
@@ -312,7 +326,7 @@ export class AuditVerifierService {
312
326
  */
313
327
  async _walkChain(graphName, writerId, tip, since, result) {
314
328
  let current = tip;
315
- /** @type {Record<string, *>|null} */ let prevReceipt = null;
329
+ /** @type {AuditReceipt|null} */ let prevReceipt = null;
316
330
  /** @type {number|null} */ let chainOidLen = null;
317
331
 
318
332
  while (current) {
@@ -322,8 +336,8 @@ export class AuditVerifierService {
322
336
  let commitInfo;
323
337
  try {
324
338
  commitInfo = await this._persistence.getNodeInfo(current);
325
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): narrow catch type
326
- this._addError(result, 'MISSING_RECEIPT_BLOB', `Cannot read commit ${current}: ${err?.message}`, current);
339
+ } catch (err) {
340
+ this._addError(result, 'MISSING_RECEIPT_BLOB', `Cannot read commit ${current}: ${err instanceof Error ? err.message : String(err)}`, current);
327
341
  return;
328
342
  }
329
343
 
@@ -456,7 +470,7 @@ export class AuditVerifierService {
456
470
  * @param {string} commitSha
457
471
  * @param {{ message: string }} commitInfo
458
472
  * @param {ChainResult} result
459
- * @returns {Promise<{ receipt: *, decodedTrailers: * }|null>}
473
+ * @returns {Promise<{ receipt: AuditReceipt, decodedTrailers: { graph: string, writer: string, dataCommit: string, opsDigest: string, schema: number } }|null>}
460
474
  * @private
461
475
  */
462
476
  async _readReceipt(commitSha, commitInfo, result) {
@@ -464,9 +478,9 @@ export class AuditVerifierService {
464
478
  let treeOid;
465
479
  try {
466
480
  treeOid = await this._persistence.getCommitTree(commitSha);
467
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): narrow catch type
481
+ } catch (err) {
468
482
  this._addError(result, 'MISSING_RECEIPT_BLOB',
469
- `Cannot read tree for ${commitSha}: ${err?.message}`, commitSha);
483
+ `Cannot read tree for ${commitSha}: ${err instanceof Error ? err.message : String(err)}`, commitSha);
470
484
  return null;
471
485
  }
472
486
 
@@ -474,9 +488,9 @@ export class AuditVerifierService {
474
488
  let treeEntries;
475
489
  try {
476
490
  treeEntries = await this._persistence.readTreeOids(treeOid);
477
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): narrow catch type
491
+ } catch (err) {
478
492
  this._addError(result, 'RECEIPT_TREE_INVALID',
479
- `Cannot read tree ${treeOid}: ${err?.message}`, commitSha);
493
+ `Cannot read tree ${treeOid}: ${err instanceof Error ? err.message : String(err)}`, commitSha);
480
494
  return null;
481
495
  }
482
496
 
@@ -493,19 +507,19 @@ export class AuditVerifierService {
493
507
  let blobContent;
494
508
  try {
495
509
  blobContent = await this._persistence.readBlob(blobOid);
496
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): narrow catch type
510
+ } catch (err) {
497
511
  this._addError(result, 'MISSING_RECEIPT_BLOB',
498
- `Cannot read receipt blob ${blobOid}: ${err?.message}`, commitSha);
512
+ `Cannot read receipt blob ${blobOid}: ${err instanceof Error ? err.message : String(err)}`, commitSha);
499
513
  return null;
500
514
  }
501
515
 
502
516
  // Decode CBOR
503
517
  let receipt;
504
518
  try {
505
- receipt = this._codec.decode(blobContent);
506
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): narrow catch type
519
+ receipt = /** @type {AuditReceipt} */ (this._codec.decode(blobContent));
520
+ } catch (err) {
507
521
  this._addError(result, 'CBOR_DECODE_FAILED',
508
- `CBOR decode failed: ${err?.message}`, commitSha);
522
+ `CBOR decode failed: ${err instanceof Error ? err.message : String(err)}`, commitSha);
509
523
  result.status = STATUS_ERROR;
510
524
  return null;
511
525
  }
@@ -514,9 +528,9 @@ export class AuditVerifierService {
514
528
  let decodedTrailers;
515
529
  try {
516
530
  decodedTrailers = decodeAuditMessage(commitInfo.message);
517
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): narrow catch type
531
+ } catch (err) {
518
532
  this._addError(result, 'TRAILER_MISMATCH',
519
- `Trailer decode failed: ${err?.message}`, commitSha);
533
+ `Trailer decode failed: ${err instanceof Error ? err.message : String(err)}`, commitSha);
520
534
  result.status = STATUS_DATA_MISMATCH;
521
535
  return null;
522
536
  }
@@ -526,7 +540,7 @@ export class AuditVerifierService {
526
540
 
527
541
  /**
528
542
  * Validates OID format for dataCommit, prevAuditCommit, and opsDigest.
529
- * @param {*} receipt
543
+ * @param {AuditReceipt} receipt
530
544
  * @param {ChainResult} result
531
545
  * @param {string} commitSha
532
546
  * @returns {boolean} true if valid
@@ -556,8 +570,8 @@ export class AuditVerifierService {
556
570
 
557
571
  /**
558
572
  * Validates chain linking between current and previous (newer) receipt.
559
- * @param {*} currentReceipt - The older receipt being validated
560
- * @param {*} prevReceipt - The newer receipt (closer to tip)
573
+ * @param {AuditReceipt} currentReceipt - The older receipt being validated
574
+ * @param {AuditReceipt} prevReceipt - The newer receipt (closer to tip)
561
575
  * @param {string} commitSha
562
576
  * @param {ChainResult} result
563
577
  * @returns {boolean} true if valid
@@ -645,7 +659,7 @@ export class AuditVerifierService {
645
659
  * @param {Object} [options]
646
660
  * @param {string} [options.pin] - Pinned trust chain commit SHA
647
661
  * @param {string} [options.mode] - Policy mode ('warn' or 'enforce')
648
- * @returns {Promise<Record<string, *>>}
662
+ * @returns {Promise<import('../trust/TrustEvaluator.js').TrustAssessment>}
649
663
  */
650
664
  async evaluateTrust(graphName, options = {}) {
651
665
  const recordService = new TrustRecordService({
@@ -103,7 +103,7 @@ export default class BitmapIndexBuilder {
103
103
  this.shaToId = new Map();
104
104
  /** @type {string[]} */
105
105
  this.idToSha = [];
106
- /** @type {Map<string, any>} */
106
+ /** @type {Map<string, import('../utils/roaring.js').RoaringBitmapSubset>} */
107
107
  this.bitmaps = new Map();
108
108
  }
109
109
 
@@ -178,7 +178,7 @@ export default class BitmapIndexBuilder {
178
178
  bitmapShards[type][prefix] = {};
179
179
  }
180
180
  // Encode bitmap as base64 for JSON storage
181
- bitmapShards[type][prefix][sha] = bitmap.serialize(true).toString('base64');
181
+ bitmapShards[type][prefix][sha] = Buffer.from(bitmap.serialize(true)).toString('base64');
182
182
  }
183
183
 
184
184
  for (const type of ['fwd', 'rev']) {
@@ -224,6 +224,6 @@ export default class BitmapIndexBuilder {
224
224
  const RoaringBitmap32 = ensureRoaringBitmap32();
225
225
  this.bitmaps.set(key, new RoaringBitmap32());
226
226
  }
227
- this.bitmaps.get(key).add(id);
227
+ /** @type {import('../utils/roaring.js').RoaringBitmapSubset} */ (this.bitmaps.get(key)).add(id);
228
228
  }
229
229
  }
@@ -6,6 +6,7 @@ import { getRoaringBitmap32 } from '../utils/roaring.js';
6
6
  import { canonicalStringify } from '../utils/canonicalStringify.js';
7
7
 
8
8
  /** @typedef {import('../../ports/IndexStoragePort.js').default} IndexStoragePort */
9
+ /** @typedef {import('../types/WarpPersistence.js').IndexStorage} IndexStorage */
9
10
  /** @typedef {import('../../ports/LoggerPort.js').default} LoggerPort */
10
11
  /** @typedef {import('../../ports/CryptoPort.js').default} CryptoPort */
11
12
 
@@ -89,11 +90,11 @@ export default class BitmapIndexReader {
89
90
  * When exceeded, least recently used shards are evicted to free memory.
90
91
  * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort instance for checksum verification.
91
92
  */
92
- constructor({ storage, strict = false, logger = nullLogger, maxCachedShards = DEFAULT_MAX_CACHED_SHARDS, crypto } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
93
+ constructor({ storage, strict = false, logger = nullLogger, maxCachedShards = DEFAULT_MAX_CACHED_SHARDS, crypto } = /** @type {{ storage: IndexStoragePort, strict?: boolean, logger?: LoggerPort, maxCachedShards?: number, crypto?: CryptoPort }} */ ({})) {
93
94
  if (!storage) {
94
95
  throw new Error('BitmapIndexReader requires a storage adapter');
95
96
  }
96
- this.storage = storage;
97
+ this.storage = /** @type {IndexStorage} */ (storage);
97
98
  this.strict = strict;
98
99
  this.logger = logger;
99
100
  this.maxCachedShards = maxCachedShards;
@@ -144,7 +145,8 @@ export default class BitmapIndexReader {
144
145
  async lookupId(sha) {
145
146
  const prefix = sha.substring(0, 2);
146
147
  const path = `meta_${prefix}.json`;
147
- const idMap = await this._getOrLoadShard(path, 'json');
148
+ // Meta shards always map SHA→numeric ID (built by BitmapIndexBuilder)
149
+ const idMap = /** @type {Record<string, number>} */ (await this._getOrLoadShard(path, 'json'));
148
150
  return idMap[sha];
149
151
  }
150
152
 
@@ -176,7 +178,8 @@ export default class BitmapIndexReader {
176
178
  async _getEdges(sha, type) {
177
179
  const prefix = sha.substring(0, 2);
178
180
  const shardPath = `shards_${type}_${prefix}.json`;
179
- const shard = await this._getOrLoadShard(shardPath, 'json');
181
+ // Bitmap shards always map SHA→base64-encoded bitmap data
182
+ const shard = /** @type {Record<string, string>} */ (await this._getOrLoadShard(shardPath, 'json'));
180
183
 
181
184
  const encoded = shard[sha];
182
185
  if (!encoded) {
@@ -195,7 +198,7 @@ export default class BitmapIndexReader {
195
198
  shardPath,
196
199
  oid: this.shardOids.get(shardPath),
197
200
  reason: 'bitmap_deserialize_error',
198
- context: { originalError: /** @type {any} */ (err).message }, // TODO(ts-cleanup): type error
201
+ context: { originalError: err instanceof Error ? err.message : String(err) },
199
202
  });
200
203
  this._handleShardError(corruptionError, {
201
204
  path: shardPath,
@@ -224,7 +227,8 @@ export default class BitmapIndexReader {
224
227
 
225
228
  for (const [path] of this.shardOids) {
226
229
  if (path.startsWith('meta_') && path.endsWith('.json')) {
227
- const shard = await this._getOrLoadShard(path, 'json');
230
+ // Meta shards always map SHA→numeric ID (built by BitmapIndexBuilder)
231
+ const shard = /** @type {Record<string, number>} */ (await this._getOrLoadShard(path, 'json'));
228
232
  for (const [sha, id] of Object.entries(shard)) {
229
233
  this._idToShaCache[id] = sha;
230
234
  }
@@ -247,10 +251,10 @@ export default class BitmapIndexReader {
247
251
  /**
248
252
  * Validates a shard envelope for version and checksum integrity.
249
253
  *
250
- * @param {{ data?: any, version?: number, checksum?: string }} envelope - The shard envelope to validate
254
+ * @param {{ data?: Record<string, string | number>, version?: number, checksum?: string }} envelope - The shard envelope to validate
251
255
  * @param {string} path - Shard path (for error context)
252
256
  * @param {string} oid - Object ID (for error context)
253
- * @returns {Promise<any>} The validated data from the envelope
257
+ * @returns {Promise<Record<string, string | number>>} The validated data from the envelope
254
258
  * @throws {ShardCorruptionError} If envelope format is invalid
255
259
  * @throws {ShardValidationError} If version or checksum validation fails
256
260
  * @private
@@ -299,7 +303,7 @@ export default class BitmapIndexReader {
299
303
  * @param {string} context.path - Shard path
300
304
  * @param {string} context.oid - Object ID
301
305
  * @param {string} context.format - 'json' or 'bitmap'
302
- * @returns {any} Empty shard (non-strict mode only)
306
+ * @returns {Record<string, string | number> | import('../utils/roaring.js').RoaringBitmapSubset} Empty shard (non-strict mode only)
303
307
  * @throws {ShardCorruptionError|ShardValidationError} In strict mode
304
308
  * @private
305
309
  */
@@ -307,17 +311,21 @@ export default class BitmapIndexReader {
307
311
  if (this.strict) {
308
312
  throw err;
309
313
  }
310
- /** @type {any} */ // TODO(ts-cleanup): type lazy singleton
311
- const errAny = err;
314
+ /** @type {string|undefined} */
315
+ const field = err instanceof ShardValidationError ? err.field : undefined;
316
+ /** @type {unknown} */
317
+ const expected = err instanceof ShardValidationError ? err.expected : undefined;
318
+ /** @type {unknown} */
319
+ const actual = err instanceof ShardValidationError ? err.actual : undefined;
312
320
  this.logger.warn('Shard validation warning', {
313
321
  operation: 'loadShard',
314
322
  shardPath: path,
315
323
  oid,
316
324
  error: err.message,
317
325
  code: err.code,
318
- field: errAny.field,
319
- expected: errAny.expected,
320
- actual: errAny.actual,
326
+ field,
327
+ expected,
328
+ actual,
321
329
  });
322
330
  const emptyShard = format === 'json' ? {} : new (getRoaringBitmap32())();
323
331
  this.loadedShards.set(path, emptyShard);
@@ -329,7 +337,7 @@ export default class BitmapIndexReader {
329
337
  * @param {Buffer} buffer - Raw shard buffer
330
338
  * @param {string} path - Shard path (for error context)
331
339
  * @param {string} oid - Object ID (for error context)
332
- * @returns {Promise<Object>} The validated data from the shard
340
+ * @returns {Promise<Record<string, string | number>>} The validated data from the shard
333
341
  * @throws {ShardCorruptionError} If parsing fails or format is invalid
334
342
  * @throws {ShardValidationError} If version or checksum validation fails
335
343
  * @private
@@ -349,7 +357,7 @@ export default class BitmapIndexReader {
349
357
  */
350
358
  async _loadShardBuffer(path, oid) {
351
359
  try {
352
- return await /** @type {any} */ (this.storage).readBlob(oid); // TODO(ts-cleanup): narrow port type
360
+ return await this.storage.readBlob(oid);
353
361
  } catch (cause) {
354
362
  throw new ShardLoadError('Failed to load shard from storage', {
355
363
  shardPath: path,
@@ -382,15 +390,16 @@ export default class BitmapIndexReader {
382
390
  /**
383
391
  * Attempts to handle a shard error based on its type.
384
392
  * Returns handled result for validation/corruption errors, null otherwise.
385
- * @param {any} err - The error to handle
393
+ * @param {unknown} err - The error to handle
386
394
  * @param {Object} context - Error context
387
395
  * @param {string} context.path - Shard path
388
396
  * @param {string} context.oid - Object ID
389
397
  * @param {string} context.format - 'json' or 'bitmap'
390
- * @returns {any} Handled result or null if error should be re-thrown
398
+ * @returns {Record<string, string | number> | import('../utils/roaring.js').RoaringBitmapSubset | null} Handled result or null if error should be re-thrown
391
399
  * @private
392
400
  */
393
401
  _tryHandleShardError(err, context) {
402
+ if (!(err instanceof Error)) { return null; }
394
403
  const wrappedErr = this._wrapParseError(err, context.path, context.oid);
395
404
  const isHandleable = wrappedErr instanceof ShardCorruptionError ||
396
405
  wrappedErr instanceof ShardValidationError;
@@ -406,7 +415,7 @@ export default class BitmapIndexReader {
406
415
  *
407
416
  * @param {string} path - Shard path
408
417
  * @param {string} format - 'json' or 'bitmap'
409
- * @returns {Promise<any>}
418
+ * @returns {Promise<Record<string, string | number> | import('../utils/roaring.js').RoaringBitmapSubset>}
410
419
  * @throws {ShardLoadError} When storage.readBlob fails
411
420
  * @throws {ShardCorruptionError} When shard format is invalid (strict mode only)
412
421
  * @throws {ShardValidationError} When version or checksum validation fails (strict mode only)
@@ -82,7 +82,7 @@ const BTR_VERSION = 1;
82
82
  * @param {string} fields.h_in - Hash of input state
83
83
  * @param {string} fields.h_out - Hash of output state
84
84
  * @param {Uint8Array} fields.U_0 - Serialized initial state
85
- * @param {Array<*>} fields.P - Serialized provenance payload
85
+ * @param {Array<unknown>} fields.P - Serialized provenance payload
86
86
  * @param {string} fields.t - ISO timestamp
87
87
  * @param {string|Uint8Array} key - HMAC key
88
88
  * @param {{ crypto: import('../../ports/CryptoPort.js').default, codec?: import('../../ports/CodecPort.js').default }} deps - Dependencies
@@ -111,7 +111,7 @@ async function computeHmac(fields, key, { crypto, codec }) {
111
111
  * @property {string} h_in - Hash of input state (hex SHA-256)
112
112
  * @property {string} h_out - Hash of output state (hex SHA-256)
113
113
  * @property {Uint8Array} U_0 - Serialized initial state (CBOR)
114
- * @property {Array<*>} P - Serialized provenance payload
114
+ * @property {Array<unknown>} P - Serialized provenance payload
115
115
  * @property {string} t - ISO 8601 timestamp
116
116
  * @property {string} kappa - Authentication tag (hex HMAC-SHA256)
117
117
  */
@@ -189,7 +189,7 @@ const REQUIRED_FIELDS = ['version', 'h_in', 'h_out', 'U_0', 'P', 't', 'kappa'];
189
189
  /**
190
190
  * Validates BTR structure and returns failure reason if invalid.
191
191
  *
192
- * @param {*} btr - The BTR object to validate
192
+ * @param {unknown} btr - The BTR object to validate
193
193
  * @returns {string|null} Error message if invalid, null if valid
194
194
  * @private
195
195
  */
@@ -197,13 +197,14 @@ function validateBTRStructure(btr) {
197
197
  if (!btr || typeof btr !== 'object') {
198
198
  return 'BTR must be an object';
199
199
  }
200
+ const rec = /** @type {Record<string, unknown>} */ (btr);
200
201
  for (const field of REQUIRED_FIELDS) {
201
- if (!(field in btr)) {
202
+ if (!(field in rec)) {
202
203
  return `Missing required field: ${field}`;
203
204
  }
204
205
  }
205
- if (btr.version !== BTR_VERSION) {
206
- return `Unsupported BTR version: ${btr.version} (expected ${BTR_VERSION})`;
206
+ if (rec.version !== BTR_VERSION) {
207
+ return `Unsupported BTR version: ${rec.version} (expected ${BTR_VERSION})`;
207
208
  }
208
209
  return null;
209
210
  }
@@ -250,7 +251,7 @@ async function verifyHmac(btr, key, { crypto, codec }) {
250
251
  * @returns {Promise<string|null>} Error message if replay mismatch, null if valid
251
252
  * @private
252
253
  */
253
- async function verifyReplayHash(btr, { crypto, codec } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
254
+ async function verifyReplayHash(btr, { crypto, codec } = {}) {
254
255
  try {
255
256
  const result = await replayBTR(btr, { crypto, codec });
256
257
  if (result.h_out !== btr.h_out) {
@@ -258,7 +259,7 @@ async function verifyReplayHash(btr, { crypto, codec } = /** @type {*} */ ({}))
258
259
  }
259
260
  return null;
260
261
  } catch (err) {
261
- return `Replay failed: ${/** @type {any} */ (err).message}`; // TODO(ts-cleanup): type error
262
+ return `Replay failed: ${err instanceof Error ? err.message : String(err)}`;
262
263
  }
263
264
  }
264
265
 
@@ -280,7 +281,7 @@ async function verifyReplayHash(btr, { crypto, codec } = /** @type {*} */ ({}))
280
281
  * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
281
282
  * @returns {Promise<VerificationResult>} Verification result with valid flag and optional reason
282
283
  */
283
- export async function verifyBTR(btr, key, options = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
284
+ export async function verifyBTR(btr, key, options = {}) {
284
285
  const { crypto, codec } = options;
285
286
 
286
287
  const structureError = validateBTRStructure(btr);
@@ -323,13 +324,13 @@ export async function verifyBTR(btr, key, options = /** @type {*} */ ({})) { //
323
324
  * The final state and its hash
324
325
  * @throws {Error} If replay fails
325
326
  */
326
- export async function replayBTR(btr, { crypto, codec } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
327
+ export async function replayBTR(btr, { crypto, codec } = {}) {
327
328
  // Deserialize initial state from U_0
328
329
  // Note: U_0 is the full serialized state (via serializeFullStateV5)
329
330
  const initialState = deserializeInitialState(btr.U_0, { codec });
330
331
 
331
332
  // Reconstruct payload
332
- const payload = ProvenancePayload.fromJSON(btr.P);
333
+ const payload = ProvenancePayload.fromJSON(/** @type {import('./ProvenancePayload.js').PatchEntry[]} */ (btr.P));
333
334
 
334
335
  // Replay
335
336
  const finalState = payload.replay(initialState);
@@ -355,7 +356,7 @@ export async function replayBTR(btr, { crypto, codec } = /** @type {*} */ ({}))
355
356
  * @returns {import('./JoinReducer.js').WarpStateV5} The deserialized state
356
357
  * @private
357
358
  */
358
- function deserializeInitialState(U_0, { codec } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
359
+ function deserializeInitialState(U_0, { codec } = {}) {
359
360
  return deserializeFullStateV5(U_0, { codec });
360
361
  }
361
362
 
@@ -370,7 +371,7 @@ function deserializeInitialState(U_0, { codec } = /** @type {*} */ ({})) { // TO
370
371
  * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
371
372
  * @returns {Uint8Array} CBOR-encoded BTR
372
373
  */
373
- export function serializeBTR(btr, { codec } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
374
+ export function serializeBTR(btr, { codec } = {}) {
374
375
  const c = codec || defaultCodec;
375
376
  return c.encode({
376
377
  version: btr.version,
@@ -392,9 +393,9 @@ export function serializeBTR(btr, { codec } = /** @type {*} */ ({})) { // TODO(t
392
393
  * @returns {BTR} The deserialized BTR
393
394
  * @throws {Error} If the bytes are not valid CBOR or missing required fields
394
395
  */
395
- export function deserializeBTR(bytes, { codec } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
396
+ export function deserializeBTR(bytes, { codec } = {}) {
396
397
  const c = codec || defaultCodec;
397
- const obj = /** @type {Record<string, *>} */ (c.decode(bytes));
398
+ const obj = /** @type {Record<string, unknown>} */ (c.decode(bytes));
398
399
 
399
400
  // Validate structure (reuse module-level constant for consistency with validateBTRStructure)
400
401
  for (const field of REQUIRED_FIELDS) {
@@ -403,7 +404,7 @@ export function deserializeBTR(bytes, { codec } = /** @type {*} */ ({})) { // TO
403
404
  }
404
405
  }
405
406
 
406
- return {
407
+ return /** @type {BTR} */ ({
407
408
  version: obj.version,
408
409
  h_in: obj.h_in,
409
410
  h_out: obj.h_out,
@@ -411,7 +412,7 @@ export function deserializeBTR(bytes, { codec } = /** @type {*} */ ({})) { // TO
411
412
  P: obj.P,
412
413
  t: obj.t,
413
414
  kappa: obj.kappa,
414
- };
415
+ });
415
416
  }
416
417
 
417
418
  /**
@@ -39,7 +39,7 @@ import { createEmptyStateV5 } from './JoinReducer.js';
39
39
  * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
40
40
  * @returns {Buffer|Uint8Array} CBOR-encoded full state
41
41
  */
42
- export function serializeFullStateV5(state, { codec } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
42
+ export function serializeFullStateV5(state, { codec } = {}) {
43
43
  const c = codec || defaultCodec;
44
44
  // Serialize ORSets using existing serialization
45
45
  const nodeAliveObj = orsetSerialize(state.nodeAlive);
@@ -90,14 +90,14 @@ export function serializeFullStateV5(state, { codec } = /** @type {*} */ ({})) {
90
90
  * @returns {import('./JoinReducer.js').WarpStateV5}
91
91
  */
92
92
  // eslint-disable-next-line complexity
93
- export function deserializeFullStateV5(buffer, { codec: codecOpt } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
93
+ export function deserializeFullStateV5(buffer, { codec: codecOpt } = {}) {
94
94
  const codec = codecOpt || defaultCodec;
95
95
  // Handle null/undefined buffer before attempting decode
96
96
  if (buffer === null || buffer === undefined) {
97
97
  return createEmptyStateV5();
98
98
  }
99
99
 
100
- const obj = /** @type {Record<string, *>} */ (codec.decode(buffer));
100
+ const obj = /** @type {Record<string, unknown>} */ (codec.decode(buffer));
101
101
 
102
102
  // Handle null/undefined decoded result: return empty state
103
103
  if (obj === null || obj === undefined) {
@@ -107,16 +107,17 @@ export function deserializeFullStateV5(buffer, { codec: codecOpt } = /** @type {
107
107
  // Handle version mismatch: throw with diagnostic info
108
108
  // Accept both 'full-v5' and missing version (for backward compatibility with pre-versioned data)
109
109
  if (obj.version !== undefined && obj.version !== 'full-v5') {
110
+ const ver = /** @type {string} */ (obj.version);
110
111
  throw new Error(
111
- `Unsupported full state version: expected 'full-v5', got '${obj.version}'`
112
+ `Unsupported full state version: expected 'full-v5', got '${ver}'`
112
113
  );
113
114
  }
114
115
 
115
116
  return {
116
117
  nodeAlive: orsetDeserialize(obj.nodeAlive || {}),
117
118
  edgeAlive: orsetDeserialize(obj.edgeAlive || {}),
118
- prop: deserializeProps(obj.prop),
119
- observedFrontier: vvDeserialize(obj.observedFrontier || {}),
119
+ prop: deserializeProps(/** @type {[string, unknown][]} */ (obj.prop)),
120
+ observedFrontier: vvDeserialize(/** @type {{[x: string]: number}} */ (obj.observedFrontier || {})),
120
121
  edgeBirthEvent: /** @type {Map<string, import('../utils/EventId.js').EventId>} */ (deserializeEdgeBirthEvent(obj)),
121
122
  };
122
123
  }
@@ -172,7 +173,7 @@ export function computeAppliedVV(state) {
172
173
  * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
173
174
  * @returns {Buffer|Uint8Array} CBOR-encoded version vector
174
175
  */
175
- export function serializeAppliedVV(vv, { codec } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
176
+ export function serializeAppliedVV(vv, { codec } = {}) {
176
177
  const c = codec || defaultCodec;
177
178
  const obj = vvSerialize(vv);
178
179
  return c.encode(obj);
@@ -186,7 +187,7 @@ export function serializeAppliedVV(vv, { codec } = /** @type {*} */ ({})) { // T
186
187
  * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
187
188
  * @returns {Map<string, number>} Version vector
188
189
  */
189
- export function deserializeAppliedVV(buffer, { codec } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
190
+ export function deserializeAppliedVV(buffer, { codec } = {}) {
190
191
  const c = codec || defaultCodec;
191
192
  const obj = /** @type {{ [x: string]: number }} */ (c.decode(buffer));
192
193
  return vvDeserialize(obj);
@@ -198,14 +199,14 @@ export function deserializeAppliedVV(buffer, { codec } = /** @type {*} */ ({}))
198
199
 
199
200
  /**
200
201
  * Deserializes the props array from checkpoint format.
201
- * @param {Array<*>} propArray - Array of [key, registerObj] pairs
202
- * @returns {Map<string, import('../crdt/LWW.js').LWWRegister<*>>}
202
+ * @param {Array<[string, unknown]>} propArray - Array of [key, registerObj] pairs
203
+ * @returns {Map<string, import('../crdt/LWW.js').LWWRegister<unknown>>}
203
204
  */
204
205
  function deserializeProps(propArray) {
205
206
  const prop = new Map();
206
207
  if (propArray && Array.isArray(propArray)) {
207
208
  for (const [key, registerObj] of propArray) {
208
- prop.set(key, deserializeLWWRegister(registerObj));
209
+ prop.set(key, deserializeLWWRegister(/** @type {{ eventId: { lamport: number, writerId: string, patchSha: string, opIndex: number }, value: unknown } | null} */ (registerObj)));
209
210
  }
210
211
  }
211
212
  return prop;
@@ -213,7 +214,7 @@ function deserializeProps(propArray) {
213
214
 
214
215
  /**
215
216
  * Deserializes edge birth event data, supporting both legacy and current formats.
216
- * @param {Record<string, *>} obj - The decoded checkpoint object
217
+ * @param {Record<string, unknown>} obj - The decoded checkpoint object
217
218
  * @returns {Map<string, import('../utils/EventId.js').EventId>}
218
219
  */
219
220
  function deserializeEdgeBirthEvent(obj) {
@@ -240,8 +241,8 @@ function deserializeEdgeBirthEvent(obj) {
240
241
  * Serializes an LWW register for CBOR encoding.
241
242
  * EventId is serialized as a plain object with sorted keys.
242
243
  *
243
- * @param {import('../crdt/LWW.js').LWWRegister<*>} register
244
- * @returns {{ eventId: { lamport: number, opIndex: number, patchSha: string, writerId: string }, value: * } | null}
244
+ * @param {import('../crdt/LWW.js').LWWRegister<unknown>} register
245
+ * @returns {{ eventId: { lamport: number, opIndex: number, patchSha: string, writerId: string }, value: unknown } | null}
245
246
  */
246
247
  function serializeLWWRegister(register) {
247
248
  if (!register) {
@@ -262,8 +263,8 @@ function serializeLWWRegister(register) {
262
263
  /**
263
264
  * Deserializes an LWW register from CBOR.
264
265
  *
265
- * @param {{ eventId: { lamport: number, writerId: string, patchSha: string, opIndex: number }, value: * } | null} obj
266
- * @returns {import('../crdt/LWW.js').LWWRegister<*> | null}
266
+ * @param {{ eventId: { lamport: number, writerId: string, patchSha: string, opIndex: number }, value: unknown } | null} obj
267
+ * @returns {import('../crdt/LWW.js').LWWRegister<unknown> | null}
267
268
  */
268
269
  function deserializeLWWRegister(obj) {
269
270
  if (!obj) {