@git-stunts/git-warp 11.2.1 → 11.3.3

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 (111) hide show
  1. package/bin/cli/commands/check.js +2 -2
  2. package/bin/cli/commands/doctor/checks.js +12 -12
  3. package/bin/cli/commands/doctor/index.js +2 -2
  4. package/bin/cli/commands/doctor/types.js +1 -1
  5. package/bin/cli/commands/history.js +12 -5
  6. package/bin/cli/commands/install-hooks.js +5 -5
  7. package/bin/cli/commands/materialize.js +2 -2
  8. package/bin/cli/commands/patch.js +142 -0
  9. package/bin/cli/commands/path.js +4 -4
  10. package/bin/cli/commands/query.js +54 -13
  11. package/bin/cli/commands/registry.js +4 -0
  12. package/bin/cli/commands/seek.js +17 -11
  13. package/bin/cli/commands/tree.js +230 -0
  14. package/bin/cli/commands/trust.js +3 -3
  15. package/bin/cli/commands/verify-audit.js +8 -7
  16. package/bin/cli/commands/view.js +6 -5
  17. package/bin/cli/infrastructure.js +26 -12
  18. package/bin/cli/shared.js +2 -2
  19. package/bin/cli/types.js +19 -8
  20. package/bin/presenters/index.js +35 -9
  21. package/bin/presenters/json.js +14 -12
  22. package/bin/presenters/text.js +155 -33
  23. package/index.d.ts +82 -22
  24. package/package.json +3 -2
  25. package/src/domain/WarpGraph.js +4 -1
  26. package/src/domain/crdt/ORSet.js +8 -8
  27. package/src/domain/errors/EmptyMessageError.js +2 -2
  28. package/src/domain/errors/ForkError.js +1 -1
  29. package/src/domain/errors/IndexError.js +1 -1
  30. package/src/domain/errors/OperationAbortedError.js +1 -1
  31. package/src/domain/errors/QueryError.js +1 -1
  32. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  33. package/src/domain/errors/ShardCorruptionError.js +2 -2
  34. package/src/domain/errors/ShardLoadError.js +2 -2
  35. package/src/domain/errors/ShardValidationError.js +4 -4
  36. package/src/domain/errors/StorageError.js +2 -2
  37. package/src/domain/errors/SyncError.js +1 -1
  38. package/src/domain/errors/TraversalError.js +1 -1
  39. package/src/domain/errors/TrustError.js +1 -1
  40. package/src/domain/errors/WarpError.js +2 -2
  41. package/src/domain/errors/WormholeError.js +1 -1
  42. package/src/domain/services/AuditReceiptService.js +6 -6
  43. package/src/domain/services/AuditVerifierService.js +52 -38
  44. package/src/domain/services/BitmapIndexBuilder.js +3 -3
  45. package/src/domain/services/BitmapIndexReader.js +28 -19
  46. package/src/domain/services/BoundaryTransitionRecord.js +18 -17
  47. package/src/domain/services/CheckpointSerializerV5.js +17 -16
  48. package/src/domain/services/CheckpointService.js +2 -2
  49. package/src/domain/services/CommitDagTraversalService.js +13 -13
  50. package/src/domain/services/DagPathFinding.js +7 -7
  51. package/src/domain/services/DagTopology.js +1 -1
  52. package/src/domain/services/DagTraversal.js +1 -1
  53. package/src/domain/services/HealthCheckService.js +1 -1
  54. package/src/domain/services/HookInstaller.js +1 -1
  55. package/src/domain/services/HttpSyncServer.js +92 -41
  56. package/src/domain/services/IndexRebuildService.js +7 -7
  57. package/src/domain/services/IndexStalenessChecker.js +4 -3
  58. package/src/domain/services/JoinReducer.js +11 -11
  59. package/src/domain/services/LogicalTraversal.js +1 -1
  60. package/src/domain/services/MessageCodecInternal.js +1 -1
  61. package/src/domain/services/MigrationService.js +1 -1
  62. package/src/domain/services/ObserverView.js +8 -8
  63. package/src/domain/services/PatchBuilderV2.js +42 -26
  64. package/src/domain/services/ProvenanceIndex.js +1 -1
  65. package/src/domain/services/ProvenancePayload.js +1 -1
  66. package/src/domain/services/QueryBuilder.js +3 -3
  67. package/src/domain/services/StateDiff.js +14 -11
  68. package/src/domain/services/StateSerializerV5.js +2 -2
  69. package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
  70. package/src/domain/services/SyncAuthService.js +3 -2
  71. package/src/domain/services/SyncProtocol.js +25 -11
  72. package/src/domain/services/TemporalQuery.js +9 -6
  73. package/src/domain/services/TranslationCost.js +7 -5
  74. package/src/domain/services/WormholeService.js +16 -7
  75. package/src/domain/trust/TrustCanonical.js +3 -3
  76. package/src/domain/trust/TrustEvaluator.js +18 -3
  77. package/src/domain/trust/TrustRecordService.js +30 -23
  78. package/src/domain/trust/TrustStateBuilder.js +21 -8
  79. package/src/domain/trust/canonical.js +6 -6
  80. package/src/domain/types/TickReceipt.js +1 -1
  81. package/src/domain/types/WarpErrors.js +45 -0
  82. package/src/domain/types/WarpOptions.js +29 -0
  83. package/src/domain/types/WarpPersistence.js +41 -0
  84. package/src/domain/types/WarpTypes.js +2 -2
  85. package/src/domain/types/WarpTypesV2.js +2 -2
  86. package/src/domain/utils/MinHeap.js +6 -5
  87. package/src/domain/utils/canonicalStringify.js +5 -4
  88. package/src/domain/utils/roaring.js +31 -5
  89. package/src/domain/warp/PatchSession.js +9 -18
  90. package/src/domain/warp/_wiredMethods.d.ts +199 -45
  91. package/src/domain/warp/checkpoint.methods.js +5 -1
  92. package/src/domain/warp/fork.methods.js +2 -2
  93. package/src/domain/warp/materialize.methods.js +55 -5
  94. package/src/domain/warp/materializeAdvanced.methods.js +15 -4
  95. package/src/domain/warp/patch.methods.js +54 -29
  96. package/src/domain/warp/provenance.methods.js +5 -3
  97. package/src/domain/warp/query.methods.js +6 -5
  98. package/src/domain/warp/sync.methods.js +16 -11
  99. package/src/globals.d.ts +64 -0
  100. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
  101. package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
  102. package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
  103. package/src/infrastructure/adapters/GitGraphAdapter.js +14 -12
  104. package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
  105. package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
  106. package/src/visualization/layouts/converters.js +2 -2
  107. package/src/visualization/layouts/elkAdapter.js +1 -1
  108. package/src/visualization/layouts/elkLayout.js +10 -7
  109. package/src/visualization/layouts/index.js +1 -1
  110. package/src/visualization/renderers/ascii/seek.js +16 -6
  111. package/src/visualization/renderers/svg/index.js +1 -1
@@ -24,7 +24,7 @@ import {
24
24
  createPatchV2,
25
25
  } from '../types/WarpTypesV2.js';
26
26
  import { encodeEdgeKey, EDGE_PROP_PREFIX } from './KeyCodec.js';
27
- import { encodePatchMessage, decodePatchMessage } from './WarpMessageCodec.js';
27
+ import { encodePatchMessage, decodePatchMessage, detectMessageKind } from './WarpMessageCodec.js';
28
28
  import { buildWriterRef } from '../utils/RefLayout.js';
29
29
  import WriterError from '../errors/WriterError.js';
30
30
 
@@ -86,7 +86,7 @@ export class PatchBuilderV2 {
86
86
  */
87
87
  constructor({ persistence, graphName, writerId, lamport, versionVector, getCurrentState, expectedParentSha = null, onCommitSuccess = null, onDeleteWithData = 'warn', codec, logger }) {
88
88
  /** @type {import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default} */
89
- this._persistence = /** @type {*} */ (persistence); // TODO(ts-cleanup): narrow port type
89
+ this._persistence = /** @type {import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default} */ (persistence);
90
90
 
91
91
  /** @type {string} */
92
92
  this._graphName = graphName;
@@ -346,7 +346,7 @@ export class PatchBuilderV2 {
346
346
  *
347
347
  * @param {string} nodeId - The node ID to set the property on
348
348
  * @param {string} key - Property key (should not contain null bytes)
349
- * @param {*} value - Property value. Must be JSON-serializable (strings,
349
+ * @param {unknown} value - Property value. Must be JSON-serializable (strings,
350
350
  * numbers, booleans, arrays, plain objects, or null). Use `null` to
351
351
  * effectively delete a property (LWW semantics).
352
352
  * @returns {PatchBuilderV2} This builder instance for method chaining
@@ -389,7 +389,7 @@ export class PatchBuilderV2 {
389
389
  * @param {string} to - Target node ID (edge destination)
390
390
  * @param {string} label - Edge label/type identifying which edge to modify
391
391
  * @param {string} key - Property key (should not contain null bytes)
392
- * @param {*} value - Property value. Must be JSON-serializable (strings,
392
+ * @param {unknown} value - Property value. Must be JSON-serializable (strings,
393
393
  * numbers, booleans, arrays, plain objects, or null). Use `null` to
394
394
  * effectively delete a property (LWW semantics).
395
395
  * @returns {PatchBuilderV2} This builder instance for method chaining
@@ -454,7 +454,7 @@ export class PatchBuilderV2 {
454
454
  schema,
455
455
  writer: this._writerId,
456
456
  lamport: this._lamport,
457
- context: /** @type {*} */ (this._vv), // TODO(ts-cleanup): narrow port type
457
+ context: vvSerialize(this._vv),
458
458
  ops: this._ops,
459
459
  reads: [...this._reads].sort(),
460
460
  writes: [...this._writes].sort(),
@@ -468,11 +468,12 @@ export class PatchBuilderV2 {
468
468
  * 1. Validates the patch is non-empty
469
469
  * 2. Checks for concurrent modifications (compare-and-swap on writer ref)
470
470
  * 3. Calculates the next lamport timestamp from the parent commit
471
- * 4. Encodes the patch as CBOR and writes it as a Git blob
472
- * 5. Creates a Git tree containing the patch blob
473
- * 6. Creates a commit with proper trailers linking to the parent
474
- * 7. Updates the writer ref to point to the new commit
475
- * 8. Invokes the success callback if provided (for eager re-materialization)
471
+ * 4. Builds the PatchV2 structure with the resolved lamport
472
+ * 5. Encodes the patch as CBOR and writes it as a Git blob
473
+ * 6. Creates a Git tree containing the patch blob
474
+ * 7. Creates a commit with proper trailers linking to the parent
475
+ * 8. Updates the writer ref to point to the new commit
476
+ * 9. Invokes the success callback if provided (for eager re-materialization)
476
477
  *
477
478
  * The commit is written to the writer's patch chain at:
478
479
  * `refs/warp/<graphName>/writers/<writerId>`
@@ -524,19 +525,39 @@ export class PatchBuilderV2 {
524
525
  throw err;
525
526
  }
526
527
 
527
- // 3. Calculate lamport and parent from current ref state
528
- let lamport = 1;
528
+ // 3. Calculate lamport and parent from current ref state.
529
+ // Start from this._lamport (set by _nextLamport() in createPatch()), which already
530
+ // incorporates the globally-observed max Lamport tick via _maxObservedLamport.
531
+ // This ensures a first-time writer whose own chain is empty still commits at a tick
532
+ // above any previously-observed writer, winning LWW tiebreakers correctly.
533
+ let lamport = this._lamport;
529
534
  let parentCommit = null;
530
535
 
531
536
  if (currentRefSha) {
532
- // Read the current patch commit to get its lamport timestamp
533
- const commitMessage = await this._persistence.showNode(currentRefSha);
534
- const patchInfo = decodePatchMessage(commitMessage);
535
- lamport = patchInfo.lamport + 1;
536
537
  parentCommit = currentRefSha;
538
+ // Read the current patch commit to get its lamport timestamp and take the max,
539
+ // so the chain stays monotonic even if the ref advanced since createPatch().
540
+ const commitMessage = await this._persistence.showNode(currentRefSha);
541
+ const kind = detectMessageKind(commitMessage);
542
+
543
+ if (kind === 'patch') {
544
+ let patchInfo;
545
+ try {
546
+ patchInfo = decodePatchMessage(commitMessage);
547
+ } catch (err) {
548
+ throw new Error(
549
+ `Failed to parse lamport from writer ref ${writerRef}: ` +
550
+ `commit ${currentRefSha} has invalid patch message format`,
551
+ { cause: err }
552
+ );
553
+ }
554
+ lamport = Math.max(this._lamport, patchInfo.lamport + 1);
555
+ }
556
+ // Non-patch ref (checkpoint, etc.): keep lamport from this._lamport
557
+ // (already incorporates _maxObservedLamport), matching _nextLamport() behavior.
537
558
  }
538
559
 
539
- // 3. Build PatchV2 structure with correct lamport
560
+ // 4. Build PatchV2 structure with correct lamport
540
561
  // Note: Dots were assigned using constructor lamport, but commit lamport may differ.
541
562
  // For now, we use the calculated lamport for the patch metadata.
542
563
  // The dots themselves are independent of patch lamport (they use VV counters).
@@ -552,10 +573,8 @@ export class PatchBuilderV2 {
552
573
  writes: [...this._writes].sort(),
553
574
  });
554
575
 
555
- // 4. Encode patch as CBOR
576
+ // 5. Encode patch as CBOR and write as a Git blob
556
577
  const patchCbor = this._codec.encode(patch);
557
-
558
- // 5. Write patch.cbor blob
559
578
  const patchBlobOid = await this._persistence.writeBlob(/** @type {Buffer} */ (patchCbor));
560
579
 
561
580
  // 6. Create tree with the blob
@@ -563,7 +582,7 @@ export class PatchBuilderV2 {
563
582
  const treeEntry = `100644 blob ${patchBlobOid}\tpatch.cbor`;
564
583
  const treeOid = await this._persistence.writeTree([treeEntry]);
565
584
 
566
- // 7. Create patch commit message with trailers (schema:2)
585
+ // 7. Create commit with proper trailers linking to the parent
567
586
  const commitMessage = encodePatchMessage({
568
587
  graph: this._graphName,
569
588
  writer: this._writerId,
@@ -571,8 +590,6 @@ export class PatchBuilderV2 {
571
590
  patchOid: patchBlobOid,
572
591
  schema,
573
592
  });
574
-
575
- // 8. Create commit with tree, linking to previous patch as parent if exists
576
593
  const parents = parentCommit ? [parentCommit] : [];
577
594
  const newCommitSha = await this._persistence.commitNodeWithTree({
578
595
  treeOid,
@@ -580,10 +597,10 @@ export class PatchBuilderV2 {
580
597
  message: commitMessage,
581
598
  });
582
599
 
583
- // 9. Update writer ref to point to new commit
600
+ // 8. Update writer ref to point to new commit
584
601
  await this._persistence.updateRef(writerRef, newCommitSha);
585
602
 
586
- // 10. Notify success callback (updates graph's version vector + eager re-materialize)
603
+ // 9. Notify success callback (updates graph's version vector + eager re-materialize)
587
604
  if (this._onCommitSuccess) {
588
605
  try {
589
606
  await this._onCommitSuccess({ patch, sha: newCommitSha });
@@ -593,7 +610,6 @@ export class PatchBuilderV2 {
593
610
  }
594
611
  }
595
612
 
596
- // 11. Return the new commit SHA
597
613
  return newCommitSha;
598
614
  }
599
615
 
@@ -277,7 +277,7 @@ class ProvenanceIndex {
277
277
  static deserialize(buffer, { codec } = {}) {
278
278
  const c = codec || defaultCodec;
279
279
  /** @type {{ version?: number, entries?: Array<[string, string[]]> }} */
280
- const obj = /** @type {any} */ (c.decode(buffer)); // TODO(ts-cleanup): narrow port type
280
+ const obj = /** @type {{ version?: number, entries?: Array<[string, string[]]> }} */ (c.decode(buffer));
281
281
 
282
282
  if (obj.version !== 1) {
283
283
  throw new Error(`Unsupported ProvenanceIndex version: ${obj.version}`);
@@ -172,7 +172,7 @@ class ProvenancePayload {
172
172
  // Use JoinReducer's reduceV5 for deterministic materialization.
173
173
  // Note: reduceV5 returns { state, receipts } when options.receipts is truthy,
174
174
  // but returns bare WarpStateV5 when no options passed (as here).
175
- return /** @type {import('./JoinReducer.js').WarpStateV5} */ (reduceV5(/** @type {*} */ (this.#patches), initialState)); // TODO(ts-cleanup): type patch array
175
+ return /** @type {import('./JoinReducer.js').WarpStateV5} */ (reduceV5(/** @type {Parameters<typeof reduceV5>[0]} */ ([...this.#patches]), initialState));
176
176
  }
177
177
 
178
178
  /**
@@ -675,7 +675,7 @@ export default class QueryBuilder {
675
675
  * @throws {QueryError} If an unknown select field is specified (code: E_QUERY_SELECT_FIELD)
676
676
  */
677
677
  async run() {
678
- const materialized = await /** @type {any} */ (this._graph)._materializeGraph(); // TODO(ts-cleanup): narrow port type
678
+ const materialized = await /** @type {{ _materializeGraph: () => Promise<{adjacency: AdjacencyMaps, stateHash: string}> }} */ (this._graph)._materializeGraph();
679
679
  const { adjacency, stateHash } = materialized;
680
680
  const allNodes = sortIds(await this._graph.getNodes());
681
681
 
@@ -805,11 +805,11 @@ export default class QueryBuilder {
805
805
  for (const nodeId of workingSet) {
806
806
  const propsMap = (await this._graph.getNodeProps(nodeId)) || new Map();
807
807
  for (const { segments, values } of propsByAgg.values()) {
808
- /** @type {*} */ // TODO(ts-cleanup): type deep property traversal
808
+ /** @type {unknown} */
809
809
  let value = propsMap.get(segments[0]);
810
810
  for (let i = 1; i < segments.length; i++) {
811
811
  if (value && typeof value === 'object') {
812
- value = value[segments[i]];
812
+ value = /** @type {Record<string, unknown>} */ (value)[segments[i]];
813
813
  } else {
814
814
  value = undefined;
815
815
  break;
@@ -24,8 +24,8 @@ import { decodeEdgeKey, decodePropKey, isEdgePropKey } from './KeyCodec.js';
24
24
  * @property {string} key - Encoded property key
25
25
  * @property {string} nodeId - Node ID (for node props)
26
26
  * @property {string} propKey - Property name
27
- * @property {*} oldValue - Previous value (undefined if new)
28
- * @property {*} newValue - New value
27
+ * @property {unknown} oldValue - Previous value (undefined if new)
28
+ * @property {unknown} newValue - New value
29
29
  */
30
30
 
31
31
  /**
@@ -33,7 +33,7 @@ import { decodeEdgeKey, decodePropKey, isEdgePropKey } from './KeyCodec.js';
33
33
  * @property {string} key - Encoded property key
34
34
  * @property {string} nodeId - Node ID (for node props)
35
35
  * @property {string} propKey - Property name
36
- * @property {*} oldValue - Previous value
36
+ * @property {unknown} oldValue - Previous value
37
37
  */
38
38
 
39
39
  /**
@@ -86,8 +86,8 @@ function compareProps(a, b) {
86
86
 
87
87
  /**
88
88
  * Checks if two arrays are deeply equal.
89
- * @param {Array<*>} a
90
- * @param {Array<*>} b
89
+ * @param {Array<unknown>} a
90
+ * @param {Array<unknown>} b
91
91
  * @returns {boolean}
92
92
  */
93
93
  function arraysEqual(a, b) {
@@ -104,8 +104,8 @@ function arraysEqual(a, b) {
104
104
 
105
105
  /**
106
106
  * Checks if two objects are deeply equal.
107
- * @param {Record<string, *>} a
108
- * @param {Record<string, *>} b
107
+ * @param {Record<string, unknown>} a
108
+ * @param {Record<string, unknown>} b
109
109
  * @returns {boolean}
110
110
  */
111
111
  function objectsEqual(a, b) {
@@ -127,8 +127,8 @@ function objectsEqual(a, b) {
127
127
 
128
128
  /**
129
129
  * Checks if two values are deeply equal (for property comparison).
130
- * @param {*} a
131
- * @param {*} b
130
+ * @param {unknown} a
131
+ * @param {unknown} b
132
132
  * @returns {boolean}
133
133
  */
134
134
  function deepEqual(a, b) {
@@ -148,9 +148,12 @@ function deepEqual(a, b) {
148
148
  return false;
149
149
  }
150
150
  if (Array.isArray(a)) {
151
- return arraysEqual(a, b);
151
+ return arraysEqual(a, /** @type {unknown[]} */ (b));
152
152
  }
153
- return objectsEqual(a, b);
153
+ return objectsEqual(
154
+ /** @type {Record<string, unknown>} */ (a),
155
+ /** @type {Record<string, unknown>} */ (b),
156
+ );
154
157
  }
155
158
 
156
159
  /**
@@ -139,11 +139,11 @@ export async function computeStateHashV5(state, { crypto, codec } = /** @type {{
139
139
  * @param {Buffer} buffer
140
140
  * @param {Object} [options]
141
141
  * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
142
- * @returns {{nodes: string[], edges: Array<{from: string, to: string, label: string}>, props: Array<{node: string, key: string, value: *}>}}
142
+ * @returns {{nodes: string[], edges: Array<{from: string, to: string, label: string}>, props: Array<{node: string, key: string, value: unknown}>}}
143
143
  */
144
144
  export function deserializeStateV5(buffer, { codec } = {}) {
145
145
  const c = codec || defaultCodec;
146
- return /** @type {{nodes: string[], edges: Array<{from: string, to: string, label: string}>, props: Array<{node: string, key: string, value: *}>}} */ (c.decode(buffer));
146
+ return /** @type {{nodes: string[], edges: Array<{from: string, to: string, label: string}>, props: Array<{node: string, key: string, value: unknown}>}} */ (c.decode(buffer));
147
147
  }
148
148
 
149
149
  // ============================================================================
@@ -8,6 +8,8 @@ import { getRoaringBitmap32 } from '../utils/roaring.js';
8
8
  import { canonicalStringify } from '../utils/canonicalStringify.js';
9
9
  import { SHARD_VERSION } from '../utils/shardVersion.js';
10
10
 
11
+ /** @typedef {import('../types/WarpPersistence.js').IndexStorage} IndexStorage */
12
+
11
13
  // Re-export for backwards compatibility
12
14
  export { SHARD_VERSION };
13
15
 
@@ -81,7 +83,7 @@ export default class StreamingBitmapIndexBuilder {
81
83
  * Creates a new StreamingBitmapIndexBuilder instance.
82
84
  *
83
85
  * @param {Object} options - Configuration options
84
- * @param {Object} options.storage - Storage adapter implementing IndexStoragePort.
86
+ * @param {import('../../ports/IndexStoragePort.js').default} options.storage - Storage adapter implementing IndexStoragePort.
85
87
  * Required methods: writeBlob, writeTree, readBlob
86
88
  * @param {number} [options.maxMemoryBytes=52428800] - Maximum bitmap memory before flush (default 50MB).
87
89
  * Note: SHA→ID mappings are not counted against this limit as they must remain in memory.
@@ -106,8 +108,8 @@ export default class StreamingBitmapIndexBuilder {
106
108
  /** @type {import('../../ports/CodecPort.js').default} */
107
109
  this._codec = codec || defaultCodec;
108
110
 
109
- /** @type {Object} */
110
- this.storage = storage;
111
+ /** @type {IndexStorage} */
112
+ this.storage = /** @type {IndexStorage} */ (storage);
111
113
 
112
114
  /** @type {number} */
113
115
  this.maxMemoryBytes = maxMemoryBytes;
@@ -124,7 +126,7 @@ export default class StreamingBitmapIndexBuilder {
124
126
  /** @type {string[]} ID → SHA reverse mapping (kept in memory) */
125
127
  this.idToSha = [];
126
128
 
127
- /** @type {Map<string, any>} Current in-memory bitmaps */
129
+ /** @type {Map<string, import('../utils/roaring.js').RoaringBitmapSubset>} Current in-memory bitmaps */
128
130
  this.bitmaps = new Map();
129
131
 
130
132
  /** @type {number} Estimated bytes used by current bitmaps */
@@ -139,8 +141,8 @@ export default class StreamingBitmapIndexBuilder {
139
141
  /** @type {number} Number of flush operations performed */
140
142
  this.flushCount = 0;
141
143
 
142
- /** @type {any} Cached Roaring bitmap constructor */ // TODO(ts-cleanup): type lazy singleton
143
- this._RoaringBitmap32 = getRoaringBitmap32(); // TODO(ts-cleanup): type lazy singleton
144
+ /** @type {typeof import('roaring').RoaringBitmap32} Cached Roaring bitmap constructor */
145
+ this._RoaringBitmap32 = getRoaringBitmap32();
144
146
  }
145
147
 
146
148
  /**
@@ -206,7 +208,7 @@ export default class StreamingBitmapIndexBuilder {
206
208
  if (!bitmapShards[type][prefix]) {
207
209
  bitmapShards[type][prefix] = {};
208
210
  }
209
- bitmapShards[type][prefix][sha] = bitmap.serialize(true).toString('base64');
211
+ bitmapShards[type][prefix][sha] = Buffer.from(bitmap.serialize(true)).toString('base64');
210
212
  }
211
213
  return bitmapShards;
212
214
  }
@@ -238,7 +240,7 @@ export default class StreamingBitmapIndexBuilder {
238
240
  data: shardData,
239
241
  };
240
242
  const buffer = Buffer.from(JSON.stringify(envelope));
241
- const oid = await /** @type {any} */ (this.storage).writeBlob(buffer); // TODO(ts-cleanup): narrow port type
243
+ const oid = await this.storage.writeBlob(buffer);
242
244
  if (!this.flushedChunks.has(path)) {
243
245
  this.flushedChunks.set(path, []);
244
246
  }
@@ -348,7 +350,7 @@ export default class StreamingBitmapIndexBuilder {
348
350
  data: map,
349
351
  };
350
352
  const buffer = Buffer.from(JSON.stringify(envelope));
351
- const oid = await /** @type {any} */ (this.storage).writeBlob(buffer); // TODO(ts-cleanup): narrow port type
353
+ const oid = await this.storage.writeBlob(buffer);
352
354
  return `100644 blob ${oid}\t${path}`;
353
355
  })
354
356
  );
@@ -410,8 +412,8 @@ export default class StreamingBitmapIndexBuilder {
410
412
  * @param {Object} [options] - Finalization options
411
413
  * @param {AbortSignal} [options.signal] - Optional AbortSignal for cancellation.
412
414
  * If aborted, throws an error with code 'ABORT_ERR'.
413
- * @param {Map<string, number>} [options.frontier] - Optional version vector frontier
414
- * (writerId → clock) for staleness detection. If provided, frontier.cbor and
415
+ * @param {Map<string, string>} [options.frontier] - Optional writer frontier
416
+ * (writerId → tip SHA) for staleness detection. If provided, frontier.cbor and
415
417
  * frontier.json files are included in the tree.
416
418
  * @returns {Promise<string>} OID of the created Git tree containing the complete index
417
419
  * @throws {Error} If the operation is aborted via signal
@@ -440,19 +442,19 @@ export default class StreamingBitmapIndexBuilder {
440
442
 
441
443
  // Store frontier metadata for staleness detection
442
444
  if (frontier) {
443
- /** @type {Record<string, number|undefined>} */
445
+ /** @type {Record<string, string|undefined>} */
444
446
  const sorted = {};
445
447
  for (const key of Array.from(frontier.keys()).sort()) {
446
448
  sorted[key] = frontier.get(key);
447
449
  }
448
450
  const envelope = { version: 1, writerCount: frontier.size, frontier: sorted };
449
- const cborOid = await /** @type {any} */ (this.storage).writeBlob(Buffer.from(/** @type {any} */ (this._codec).encode(envelope))); // TODO(ts-cleanup): narrow port type
451
+ const cborOid = await this.storage.writeBlob(Buffer.from(this._codec.encode(envelope)));
450
452
  flatEntries.push(`100644 blob ${cborOid}\tfrontier.cbor`);
451
- const jsonOid = await /** @type {any} */ (this.storage).writeBlob(Buffer.from(canonicalStringify(envelope))); // TODO(ts-cleanup): narrow port type
453
+ const jsonOid = await this.storage.writeBlob(Buffer.from(canonicalStringify(envelope)));
452
454
  flatEntries.push(`100644 blob ${jsonOid}\tfrontier.json`);
453
455
  }
454
456
 
455
- const treeOid = await /** @type {any} */ (this.storage).writeTree(flatEntries); // TODO(ts-cleanup): narrow port type
457
+ const treeOid = await this.storage.writeTree(flatEntries);
456
458
 
457
459
  this.logger.debug('Index finalized', {
458
460
  operation: 'finalize',
@@ -539,7 +541,7 @@ export default class StreamingBitmapIndexBuilder {
539
541
  this.estimatedBitmapBytes += BITMAP_BASE_OVERHEAD;
540
542
  }
541
543
 
542
- const bitmap = this.bitmaps.get(key);
544
+ const bitmap = /** @type {import('../utils/roaring.js').RoaringBitmapSubset} */ (this.bitmaps.get(key));
543
545
  const sizeBefore = bitmap.size;
544
546
  bitmap.add(id);
545
547
  const sizeAfter = bitmap.size;
@@ -569,7 +571,7 @@ export default class StreamingBitmapIndexBuilder {
569
571
  * @private
570
572
  */
571
573
  async _loadAndValidateChunk(oid) {
572
- const buffer = await /** @type {any} */ (this.storage).readBlob(oid); // TODO(ts-cleanup): narrow port type
574
+ const buffer = await this.storage.readBlob(oid);
573
575
  let envelope;
574
576
  try {
575
577
  envelope = JSON.parse(buffer.toString('utf-8'));
@@ -577,7 +579,7 @@ export default class StreamingBitmapIndexBuilder {
577
579
  throw new ShardCorruptionError('Failed to parse shard JSON', {
578
580
  oid,
579
581
  reason: 'invalid_format',
580
- context: { originalError: /** @type {any} */ (err).message }, // TODO(ts-cleanup): type error
582
+ context: { originalError: err instanceof Error ? err.message : String(err) },
581
583
  });
582
584
  }
583
585
 
@@ -614,7 +616,7 @@ export default class StreamingBitmapIndexBuilder {
614
616
  * it using `orInPlace` to combine edge sets.
615
617
  *
616
618
  * @param {Object} opts - Options object
617
- * @param {Record<string, any>} opts.merged - Object mapping SHA to
619
+ * @param {Record<string, import('../utils/roaring.js').RoaringBitmapSubset>} opts.merged - Object mapping SHA to
618
620
  * RoaringBitmap32 instances (mutated in place)
619
621
  * @param {string} opts.sha - The SHA key for this bitmap (40-character hex string)
620
622
  * @param {string} opts.base64Bitmap - Base64-encoded serialized RoaringBitmap32 data
@@ -631,7 +633,7 @@ export default class StreamingBitmapIndexBuilder {
631
633
  throw new ShardCorruptionError('Failed to deserialize bitmap', {
632
634
  oid,
633
635
  reason: 'invalid_bitmap',
634
- context: { originalError: /** @type {any} */ (err).message }, // TODO(ts-cleanup): type error
636
+ context: { originalError: err instanceof Error ? err.message : String(err) },
635
637
  });
636
638
  }
637
639
 
@@ -675,7 +677,7 @@ export default class StreamingBitmapIndexBuilder {
675
677
  */
676
678
  async _mergeChunks(oids, { signal } = {}) {
677
679
  // Load all chunks and merge bitmaps by SHA
678
- /** @type {Record<string, any>} */
680
+ /** @type {Record<string, import('../utils/roaring.js').RoaringBitmapSubset>} */
679
681
  const merged = {};
680
682
 
681
683
  for (const oid of oids) {
@@ -691,7 +693,7 @@ export default class StreamingBitmapIndexBuilder {
691
693
  /** @type {Record<string, string>} */
692
694
  const result = {};
693
695
  for (const [sha, bitmap] of Object.entries(merged)) {
694
- result[sha] = bitmap.serialize(true).toString('base64');
696
+ result[sha] = Buffer.from(bitmap.serialize(true)).toString('base64');
695
697
  }
696
698
 
697
699
  // Wrap merged result in envelope with version and checksum
@@ -707,9 +709,9 @@ export default class StreamingBitmapIndexBuilder {
707
709
  } catch (err) {
708
710
  throw new ShardCorruptionError('Failed to serialize merged shard', {
709
711
  reason: 'serialization_error',
710
- context: { originalError: /** @type {any} */ (err).message }, // TODO(ts-cleanup): type error
712
+ context: { originalError: err instanceof Error ? err.message : String(err) },
711
713
  });
712
714
  }
713
- return /** @type {any} */ (this.storage).writeBlob(serialized); // TODO(ts-cleanup): narrow port type
715
+ return await this.storage.writeBlob(serialized);
714
716
  }
715
717
  }
@@ -144,6 +144,7 @@ function _checkHeaderFormats(timestamp, nonce, signature) {
144
144
 
145
145
  /**
146
146
  * @param {Record<string, string>|undefined} keys
147
+ * @returns {asserts keys is Record<string, string>}
147
148
  */
148
149
  function _validateKeys(keys) {
149
150
  if (!keys || typeof keys !== 'object' || Object.keys(keys).length === 0) {
@@ -180,7 +181,7 @@ export default class SyncAuthService {
180
181
  * @param {() => number} [options.wallClockMs] - Wall clock function
181
182
  * @param {string[]} [options.allowedWriters] - Optional whitelist of allowed writer IDs. If set, sync requests with unlisted writers are rejected with 403.
182
183
  */
183
- constructor({ keys, mode = 'enforce', nonceCapacity, maxClockSkewMs, crypto, logger, wallClockMs, allowedWriters } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
184
+ constructor({ keys, mode = 'enforce', nonceCapacity, maxClockSkewMs, crypto, logger, wallClockMs, allowedWriters } = /** @type {{ keys: Record<string, string> }} */ ({})) {
184
185
  _validateKeys(keys);
185
186
  this._keys = keys;
186
187
  this._mode = mode;
@@ -433,7 +434,7 @@ export default class SyncAuthService {
433
434
  /**
434
435
  * Records an auth failure and returns the result.
435
436
  * @param {string} message
436
- * @param {Record<string, *>} context
437
+ * @param {Record<string, unknown>} context
437
438
  * @param {{ ok: false, reason: string, status: number }} result
438
439
  * @returns {{ ok: false, reason: string, status: number }}
439
440
  * @private
@@ -42,6 +42,16 @@ import { join, cloneStateV5 } from './JoinReducer.js';
42
42
  import { cloneFrontier, updateFrontier } from './Frontier.js';
43
43
  import { vvDeserialize } from '../crdt/VersionVector.js';
44
44
 
45
+ /**
46
+ * A decoded patch object after CBOR deserialization.
47
+ * @typedef {Object} DecodedPatch
48
+ * @property {Object | Map<string, number>} [context] - VersionVector (Map after normalization, plain object before)
49
+ * @property {import('../types/WarpTypesV2.js').OpV2[]} ops - Ordered array of operations
50
+ * @property {string} [writer] - Writer ID
51
+ * @property {number} [lamport] - Lamport timestamp
52
+ * @property {number} [schema] - Schema version
53
+ */
54
+
45
55
  // -----------------------------------------------------------------------------
46
56
  // Patch Loading
47
57
  // -----------------------------------------------------------------------------
@@ -56,9 +66,9 @@ import { vvDeserialize } from '../crdt/VersionVector.js';
56
66
  * **Mutation**: This function mutates the input patch object for efficiency.
57
67
  * The original object reference is returned.
58
68
  *
59
- * @param {{ context?: Object | Map<any, any>, ops: any[] }} patch - The raw decoded patch from CBOR.
69
+ * @param {DecodedPatch} patch - The raw decoded patch from CBOR.
60
70
  * If context is present as a plain object, it will be converted to a Map.
61
- * @returns {{ context?: Object | Map<any, any>, ops: any[] }} The same patch object with context converted to Map
71
+ * @returns {DecodedPatch} The same patch object with context converted to Map
62
72
  * @private
63
73
  */
64
74
  function normalizePatch(patch) {
@@ -88,7 +98,7 @@ function normalizePatch(patch) {
88
98
  * @param {string} sha - The 40-character commit SHA to load the patch from
89
99
  * @param {Object} [options]
90
100
  * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
91
- * @returns {Promise<{ context?: Object | Map<any, any>, ops: any[] }>} The decoded and normalized patch object containing:
101
+ * @returns {Promise<DecodedPatch>} The decoded and normalized patch object containing:
92
102
  * - `ops`: Array of patch operations
93
103
  * - `context`: VersionVector (Map) of causal dependencies
94
104
  * - `writerId`: The writer who created this patch
@@ -99,7 +109,7 @@ function normalizePatch(patch) {
99
109
  * @throws {Error} If the patch blob cannot be CBOR-decoded (corrupted data)
100
110
  * @private
101
111
  */
102
- async function loadPatchFromCommit(persistence, sha, { codec: codecOpt } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
112
+ async function loadPatchFromCommit(persistence, sha, { codec: codecOpt } = /** @type {{ codec?: import('../../ports/CodecPort.js').default }} */ ({})) {
103
113
  const codec = codecOpt || defaultCodec;
104
114
  // Read commit message to extract patch OID
105
115
  const message = await persistence.showNode(sha);
@@ -107,7 +117,7 @@ async function loadPatchFromCommit(persistence, sha, { codec: codecOpt } = /** @
107
117
 
108
118
  // Read and decode the patch blob
109
119
  const patchBuffer = await persistence.readBlob(decoded.patchOid);
110
- const patch = /** @type {{ context?: Object | Map<any, any>, ops: any[] }} */ (codec.decode(patchBuffer));
120
+ const patch = /** @type {DecodedPatch} */ (codec.decode(patchBuffer));
111
121
 
112
122
  // Normalize the patch (convert context from object to Map)
113
123
  return normalizePatch(patch);
@@ -134,7 +144,9 @@ async function loadPatchFromCommit(persistence, sha, { codec: codecOpt } = /** @
134
144
  * @param {string|null} fromSha - Start SHA (exclusive). Pass null to load ALL patches
135
145
  * for this writer from the beginning of their chain.
136
146
  * @param {string} toSha - End SHA (inclusive). This is typically the writer's current tip.
137
- * @returns {Promise<Array<{patch: Object, sha: string}>>} Array of patch objects in
147
+ * @param {Object} [options]
148
+ * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
149
+ * @returns {Promise<Array<{patch: DecodedPatch, sha: string}>>} Array of patch objects in
138
150
  * chronological order (oldest first). Each entry contains:
139
151
  * - `patch`: The decoded patch object
140
152
  * - `sha`: The commit SHA this patch came from
@@ -152,7 +164,7 @@ async function loadPatchFromCommit(persistence, sha, { codec: codecOpt } = /** @
152
164
  * // Load ALL patches for a new writer
153
165
  * const patches = await loadPatchRange(persistence, 'events', 'new-writer', null, tipSha);
154
166
  */
155
- export async function loadPatchRange(persistence, graphName, writerId, fromSha, toSha, { codec } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
167
+ export async function loadPatchRange(persistence, graphName, writerId, fromSha, toSha, { codec } = /** @type {{ codec?: import('../../ports/CodecPort.js').default }} */ ({})) {
156
168
  const patches = [];
157
169
  let cur = toSha;
158
170
 
@@ -298,7 +310,7 @@ export function computeSyncDelta(localFrontier, remoteFrontier) {
298
310
  * @property {'sync-response'} type - Message type discriminator for protocol parsing
299
311
  * @property {Object.<string, string>} frontier - Responder's frontier as a plain object.
300
312
  * Keys are writer IDs, values are SHAs.
301
- * @property {Array<{writerId: string, sha: string, patch: Object}>} patches - Patches
313
+ * @property {Array<{writerId: string, sha: string, patch: DecodedPatch}>} patches - Patches
302
314
  * the requester needs, in chronological order per writer. Contains:
303
315
  * - `writerId`: The writer who created this patch
304
316
  * - `sha`: The commit SHA this patch came from (for frontier updates)
@@ -361,6 +373,8 @@ export function createSyncRequest(frontier) {
361
373
  * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default} persistence - Git persistence
362
374
  * layer for loading patches (uses CommitPort + BlobPort methods)
363
375
  * @param {string} graphName - Graph name for error messages and logging
376
+ * @param {Object} [options]
377
+ * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
364
378
  * @returns {Promise<SyncResponse>} Response containing local frontier and patches.
365
379
  * Patches are ordered chronologically within each writer.
366
380
  * @throws {Error} If patch loading fails for reasons other than divergence
@@ -374,7 +388,7 @@ export function createSyncRequest(frontier) {
374
388
  * res.json(response);
375
389
  * });
376
390
  */
377
- export async function processSyncRequest(request, localFrontier, persistence, graphName, { codec } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
391
+ export async function processSyncRequest(request, localFrontier, persistence, graphName, { codec } = /** @type {{ codec?: import('../../ports/CodecPort.js').default }} */ ({})) {
378
392
  // Convert incoming frontier from object to Map
379
393
  const remoteFrontier = new Map(Object.entries(request.frontier));
380
394
 
@@ -401,7 +415,7 @@ export async function processSyncRequest(request, localFrontier, persistence, gr
401
415
  } catch (err) {
402
416
  // If we detect divergence, skip this writer
403
417
  // The requester may need to handle this separately
404
- if (/** @type {any} */ (err).code === 'E_SYNC_DIVERGENCE' || /** @type {any} */ (err).message?.includes('Divergence detected')) { // TODO(ts-cleanup): type error
418
+ if ((err instanceof Error && 'code' in err && /** @type {{ code: string }} */ (err).code === 'E_SYNC_DIVERGENCE') || (err instanceof Error && err.message?.includes('Divergence detected'))) {
405
419
  continue;
406
420
  }
407
421
  throw err;
@@ -491,7 +505,7 @@ export function applySyncResponse(response, state, frontier) {
491
505
  // will prevent silent data loss until the reader is upgraded.
492
506
  assertOpsCompatible(normalizedPatch.ops, SCHEMA_V3);
493
507
  // Apply patch to state
494
- join(newState, /** @type {*} */ (normalizedPatch), sha); // TODO(ts-cleanup): type patch array
508
+ join(newState, /** @type {Parameters<typeof join>[1]} */ (normalizedPatch), sha);
495
509
  applied++;
496
510
  }
497
511
 
@@ -36,13 +36,16 @@ import { orsetContains } from '../crdt/ORSet.js';
36
36
  * InlineValue objects `{ type: 'inline', value: ... }` are unwrapped
37
37
  * to their inner value. All other values pass through unchanged.
38
38
  *
39
- * @param {*} value - Property value (potentially InlineValue-wrapped)
40
- * @returns {*} The unwrapped value
39
+ * @param {unknown} value - Property value (potentially InlineValue-wrapped)
40
+ * @returns {unknown} The unwrapped value
41
41
  * @private
42
42
  */
43
43
  function unwrapValue(value) {
44
- if (value && typeof value === 'object' && value.type === 'inline') {
45
- return value.value;
44
+ if (value && typeof value === 'object' && 'type' in value) {
45
+ const rec = /** @type {Record<string, unknown>} */ (value);
46
+ if (rec.type === 'inline') {
47
+ return rec.value;
48
+ }
46
49
  }
47
50
  return value;
48
51
  }
@@ -60,11 +63,11 @@ function unwrapValue(value) {
60
63
  *
61
64
  * @param {import('./JoinReducer.js').WarpStateV5} state - Current state
62
65
  * @param {string} nodeId - Node ID to extract
63
- * @returns {{ id: string, exists: boolean, props: Record<string, *> }}
66
+ * @returns {{ id: string, exists: boolean, props: Record<string, unknown> }}
64
67
  */
65
68
  function extractNodeSnapshot(state, nodeId) {
66
69
  const exists = orsetContains(state.nodeAlive, nodeId);
67
- /** @type {Record<string, *>} */
70
+ /** @type {Record<string, unknown>} */
68
71
  const props = {};
69
72
 
70
73
  if (exists) {