@git-stunts/git-warp 12.2.1 → 12.4.1

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 (121) hide show
  1. package/README.md +5 -5
  2. package/bin/cli/commands/info.js +1 -5
  3. package/bin/cli/infrastructure.js +6 -9
  4. package/bin/cli/shared.js +8 -0
  5. package/bin/presenters/text.js +10 -3
  6. package/bin/warp-graph.js +6 -6
  7. package/package.json +1 -1
  8. package/src/domain/WarpGraph.js +5 -35
  9. package/src/domain/crdt/ORSet.js +3 -0
  10. package/src/domain/crdt/VersionVector.js +1 -1
  11. package/src/domain/entities/GraphNode.js +1 -6
  12. package/src/domain/errors/ForkError.js +1 -1
  13. package/src/domain/errors/IndexError.js +1 -1
  14. package/src/domain/errors/OperationAbortedError.js +1 -1
  15. package/src/domain/errors/PatchError.js +1 -1
  16. package/src/domain/errors/PersistenceError.js +45 -0
  17. package/src/domain/errors/QueryError.js +1 -1
  18. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  19. package/src/domain/errors/SyncError.js +1 -1
  20. package/src/domain/errors/TraversalError.js +1 -1
  21. package/src/domain/errors/TrustError.js +1 -1
  22. package/src/domain/errors/WormholeError.js +1 -1
  23. package/src/domain/errors/index.js +1 -0
  24. package/src/domain/services/AdjacencyNeighborProvider.js +1 -4
  25. package/src/domain/services/AnchorMessageCodec.js +1 -3
  26. package/src/domain/services/AuditMessageCodec.js +1 -5
  27. package/src/domain/services/AuditReceiptService.js +4 -18
  28. package/src/domain/services/AuditVerifierService.js +3 -7
  29. package/src/domain/services/BitmapIndexBuilder.js +6 -12
  30. package/src/domain/services/BitmapIndexReader.js +7 -20
  31. package/src/domain/services/BitmapNeighborProvider.js +1 -3
  32. package/src/domain/services/BoundaryTransitionRecord.js +7 -23
  33. package/src/domain/services/CheckpointMessageCodec.js +6 -6
  34. package/src/domain/services/CheckpointSerializerV5.js +8 -12
  35. package/src/domain/services/CheckpointService.js +28 -40
  36. package/src/domain/services/CommitDagTraversalService.js +1 -3
  37. package/src/domain/services/DagPathFinding.js +9 -59
  38. package/src/domain/services/DagTopology.js +4 -16
  39. package/src/domain/services/DagTraversal.js +7 -31
  40. package/src/domain/services/Frontier.js +4 -6
  41. package/src/domain/services/GitLogParser.js +1 -2
  42. package/src/domain/services/GraphTraversal.js +14 -114
  43. package/src/domain/services/HealthCheckService.js +3 -9
  44. package/src/domain/services/HookInstaller.js +2 -8
  45. package/src/domain/services/HttpSyncServer.js +24 -25
  46. package/src/domain/services/IncrementalIndexUpdater.js +4 -6
  47. package/src/domain/services/IndexRebuildService.js +6 -52
  48. package/src/domain/services/IndexStalenessChecker.js +2 -3
  49. package/src/domain/services/JoinReducer.js +200 -100
  50. package/src/domain/services/KeyCodec.js +48 -0
  51. package/src/domain/services/LogicalBitmapIndexBuilder.js +1 -2
  52. package/src/domain/services/LogicalIndexBuildService.js +2 -6
  53. package/src/domain/services/LogicalIndexReader.js +1 -2
  54. package/src/domain/services/LogicalTraversal.js +13 -64
  55. package/src/domain/services/MaterializedViewService.js +5 -19
  56. package/src/domain/services/MessageSchemaDetector.js +35 -5
  57. package/src/domain/services/MigrationService.js +1 -4
  58. package/src/domain/services/ObserverView.js +1 -7
  59. package/src/domain/services/OpNormalizer.js +79 -0
  60. package/src/domain/services/PatchBuilderV2.js +67 -38
  61. package/src/domain/services/PatchMessageCodec.js +1 -6
  62. package/src/domain/services/PropertyIndexBuilder.js +1 -2
  63. package/src/domain/services/PropertyIndexReader.js +1 -4
  64. package/src/domain/services/ProvenanceIndex.js +5 -7
  65. package/src/domain/services/ProvenancePayload.js +1 -1
  66. package/src/domain/services/QueryBuilder.js +3 -16
  67. package/src/domain/services/StateDiff.js +3 -9
  68. package/src/domain/services/StateSerializerV5.js +10 -10
  69. package/src/domain/services/StreamingBitmapIndexBuilder.js +13 -41
  70. package/src/domain/services/SyncAuthService.js +8 -32
  71. package/src/domain/services/SyncController.js +5 -25
  72. package/src/domain/services/SyncProtocol.js +10 -13
  73. package/src/domain/services/SyncTrustGate.js +4 -9
  74. package/src/domain/services/TemporalQuery.js +9 -27
  75. package/src/domain/services/TranslationCost.js +2 -8
  76. package/src/domain/services/WarpMessageCodec.js +2 -0
  77. package/src/domain/services/WarpStateIndexBuilder.js +2 -4
  78. package/src/domain/services/WormholeService.js +9 -25
  79. package/src/domain/trust/TrustCrypto.js +9 -10
  80. package/src/domain/trust/TrustEvaluator.js +1 -8
  81. package/src/domain/trust/TrustRecordService.js +5 -10
  82. package/src/domain/types/TickReceipt.js +9 -11
  83. package/src/domain/types/WarpTypes.js +1 -5
  84. package/src/domain/types/WarpTypesV2.js +78 -13
  85. package/src/domain/utils/CachedValue.js +1 -4
  86. package/src/domain/utils/MinHeap.js +3 -3
  87. package/src/domain/utils/RefLayout.js +26 -0
  88. package/src/domain/utils/WriterId.js +2 -7
  89. package/src/domain/utils/canonicalCbor.js +1 -1
  90. package/src/domain/utils/defaultClock.js +1 -0
  91. package/src/domain/utils/defaultCodec.js +1 -1
  92. package/src/domain/utils/parseCursorBlob.js +4 -4
  93. package/src/domain/warp/PatchSession.js +3 -8
  94. package/src/domain/warp/Writer.js +9 -12
  95. package/src/domain/warp/_wire.js +2 -2
  96. package/src/domain/warp/_wiredMethods.d.ts +5 -7
  97. package/src/domain/warp/checkpoint.methods.js +1 -1
  98. package/src/domain/warp/fork.methods.js +2 -6
  99. package/src/domain/warp/materializeAdvanced.methods.js +3 -3
  100. package/src/domain/warp/patch.methods.js +8 -8
  101. package/src/domain/warp/provenance.methods.js +5 -5
  102. package/src/domain/warp/query.methods.js +9 -18
  103. package/src/domain/warp/subscribe.methods.js +2 -8
  104. package/src/globals.d.ts +7 -0
  105. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -18
  106. package/src/infrastructure/adapters/ConsoleLogger.js +2 -9
  107. package/src/infrastructure/adapters/DenoHttpAdapter.js +15 -15
  108. package/src/infrastructure/adapters/GitGraphAdapter.js +234 -58
  109. package/src/infrastructure/adapters/InMemoryGraphAdapter.js +9 -2
  110. package/src/infrastructure/adapters/NodeHttpAdapter.js +14 -14
  111. package/src/infrastructure/adapters/WebCryptoAdapter.js +1 -2
  112. package/src/ports/BlobPort.js +2 -2
  113. package/src/ports/HttpServerPort.js +24 -2
  114. package/src/ports/RefPort.js +2 -1
  115. package/src/visualization/renderers/ascii/box.js +1 -1
  116. package/src/visualization/renderers/ascii/check.js +1 -5
  117. package/src/visualization/renderers/ascii/history.js +1 -6
  118. package/src/visualization/renderers/ascii/path.js +4 -22
  119. package/src/visualization/renderers/ascii/progress.js +1 -4
  120. package/src/visualization/renderers/ascii/seek.js +1 -5
  121. package/src/visualization/renderers/ascii/table.js +1 -3
package/README.md CHANGED
@@ -8,12 +8,12 @@
8
8
  <img src="docs/images/hero.gif" alt="git-warp CLI demo" width="600">
9
9
  </p>
10
10
 
11
- ## What's New in v12.2.1
11
+ ## What's New in v12.4.1
12
12
 
13
- - **M12 SCALPEL complete** — 42-item STANK audit fully resolved: 15 bug fixes, 20+ JSDoc/documentation improvements, and 6 refactors across CRDT core, services, sync, and CLI.
14
- - **Sync correctness hardened** — `join()` state install, `applySyncResponse` cache coherence, unknown-op rejection (fail-closed), and divergence pre-check all fixed.
15
- - **Incremental index improvements** — stale label ID collision fix, re-add edge restoration via adjacency cache, and bitmap churn reduction for node removal.
16
- - **canonicalStringify shared-reference fix** — cycle detection now correctly allows valid DAG structures (shared non-circular references).
13
+ - **JSDoc total coverage** — eliminated all unsafe `{Object}`, `{Function}`, `{*}` type patterns across 135 files (190+ sites), replacing them with precise inline typed shapes.
14
+ - **Zero tsc errors** — fixed tsconfig split-config includes and type divergences; 0 errors across all three tsconfig targets.
15
+ - **JSR dry-run fix** — worked around a deno_ast 0.52.0 panic caused by overlapping text-change entries for duplicate import specifiers.
16
+ - **`check-dts-surface.js` regex fix** — default-export parsing now correctly captures identifiers instead of keywords for `export default class/function` patterns.
17
17
 
18
18
  See the [full changelog](CHANGELOG.md) for details.
19
19
 
@@ -17,11 +17,7 @@ import { createPersistence, listGraphNames, readActiveCursor, readCheckpointDate
17
17
  * Collects metadata about a single graph (writer count, refs, patches, checkpoint).
18
18
  * @param {Persistence} persistence
19
19
  * @param {string} graphName
20
- * @param {Object} [options]
21
- * @param {boolean} [options.includeWriterIds=false]
22
- * @param {boolean} [options.includeRefs=false]
23
- * @param {boolean} [options.includeWriterPatches=false]
24
- * @param {boolean} [options.includeCheckpointDate=false]
20
+ * @param {{ includeWriterIds?: boolean, includeRefs?: boolean, includeWriterPatches?: boolean, includeCheckpointDate?: boolean }} [options]
25
21
  * @returns {Promise<GraphInfoResult>}
26
22
  */
27
23
  async function getGraphInfo(persistence, graphName, {
@@ -127,10 +127,7 @@ Tree options:
127
127
  export class CliError extends Error {
128
128
  /**
129
129
  * @param {string} message - Human-readable error message
130
- * @param {Object} [options]
131
- * @param {string} [options.code='E_CLI'] - Machine-readable error code
132
- * @param {number} [options.exitCode=3] - Process exit code
133
- * @param {Error} [options.cause] - Underlying cause
130
+ * @param {{ code?: string, exitCode?: number, cause?: Error }} [options]
134
131
  */
135
132
  constructor(message, { code = 'E_CLI', exitCode = EXIT_CODES.INTERNAL, cause } = {}) {
136
133
  super(message);
@@ -337,12 +334,12 @@ export function parseArgs(argv) {
337
334
  /**
338
335
  * Parses command-level args using node:util.parseArgs + Zod validation.
339
336
  *
337
+ * @template T
340
338
  * @param {string[]} args - Command-specific args (after command name)
341
- * @param {Object} config - parseArgs options config
342
- * @param {import('zod').ZodType} schema - Zod schema to validate/transform parsed values
343
- * @param {Object} [opts]
344
- * @param {boolean} [opts.allowPositionals=false] - Whether to allow positional arguments
345
- * @returns {{values: *, positionals: string[]}}
339
+ * @param {Record<string, {type: string, short?: string, default?: unknown, multiple?: boolean}>} config - parseArgs options config
340
+ * @param {import('zod').ZodType<T, import('zod').ZodTypeDef, unknown>} schema - Zod schema to validate/transform parsed values
341
+ * @param {{ allowPositionals?: boolean }} [opts]
342
+ * @returns {{values: T, positionals: string[]}}
346
343
  */
347
344
  export function parseCommandArgs(args, config, schema, { allowPositionals = false } = {}) {
348
345
  /** @type {{ values: Record<string, string|boolean|string[]|boolean[]|undefined>, positionals: string[] }} */
package/bin/cli/shared.js CHANGED
@@ -177,6 +177,10 @@ export async function readCheckpointDate(persistence, checkpointSha) {
177
177
  return info.date || null;
178
178
  }
179
179
 
180
+ /**
181
+ * Create a HookInstaller wired with real filesystem dependencies.
182
+ * @returns {import('../../src/domain/services/HookInstaller.js').HookInstaller}
183
+ */
180
184
  export function createHookInstaller() {
181
185
  const __filename = new URL(import.meta.url).pathname;
182
186
  const __dirname = path.dirname(__filename);
@@ -211,6 +215,10 @@ export function execGitConfigValue(repoPath, key) {
211
215
  }
212
216
  }
213
217
 
218
+ /**
219
+ * Check whether stderr is a TTY (interactive terminal).
220
+ * @returns {boolean}
221
+ */
214
222
  export function isInteractive() {
215
223
  return Boolean(process.stderr.isTTY);
216
224
  }
@@ -291,7 +291,8 @@ function formatOpSummaryPlain(summary) {
291
291
  const order = [
292
292
  ['NodeAdd', '+', 'node'],
293
293
  ['EdgeAdd', '+', 'edge'],
294
- ['PropSet', '~', 'prop'],
294
+ ['prop', '~', 'prop'], // coalesced PropSet + NodePropSet
295
+ ['EdgePropSet', '~', 'eprop'],
295
296
  ['NodeTombstone', '-', 'node'],
296
297
  ['EdgeTombstone', '-', 'edge'],
297
298
  ['BlobValue', '+', 'blob'],
@@ -299,7 +300,10 @@ function formatOpSummaryPlain(summary) {
299
300
 
300
301
  const parts = [];
301
302
  for (const [opType, symbol, label] of order) {
302
- const n = summary?.[opType];
303
+ // Coalesce PropSet + NodePropSet into one bucket
304
+ const n = opType === 'prop'
305
+ ? (summary?.PropSet || 0) + (summary?.NodePropSet || 0) || undefined
306
+ : summary?.[opType];
303
307
  if (typeof n === 'number' && Number.isFinite(n) && n > 0) {
304
308
  parts.push(`${symbol}${n}${label}`);
305
309
  }
@@ -612,9 +616,12 @@ function formatPatchOp(op) {
612
616
  if (op.type === 'EdgeTombstone') {
613
617
  return ` - edge ${op.from} -[${op.label}]-> ${op.to}`;
614
618
  }
615
- if (op.type === 'PropSet') {
619
+ if (op.type === 'PropSet' || op.type === 'NodePropSet') {
616
620
  return ` ~ ${op.node}.${op.key} = ${JSON.stringify(op.value)}`;
617
621
  }
622
+ if (op.type === 'EdgePropSet') {
623
+ return ` ~ edge(${op.from} -[${op.label}]-> ${op.to}).${op.key} = ${JSON.stringify(op.value)}`;
624
+ }
618
625
  if (op.type === 'BlobValue') {
619
626
  return ` + blob ${op.node}`;
620
627
  }
package/bin/warp-graph.js CHANGED
@@ -55,20 +55,20 @@ async function main() {
55
55
  throw usageError(`--view is not supported for '${command}'. Supported commands: ${VIEW_SUPPORTED_COMMANDS.join(', ')}`);
56
56
  }
57
57
 
58
- const result = await /** @type {Function} */ (handler)({
58
+ const result = await /** @type {(opts: {command: string, args: string[], options: Record<string, unknown>}) => Promise<unknown>} */ (handler)({
59
59
  command,
60
60
  args: commandArgs,
61
61
  options,
62
62
  });
63
63
 
64
- /** @type {{payload: *, exitCode: number}} */
65
- const normalized = result && typeof result === 'object' && 'payload' in result
66
- ? result
64
+ /** @type {{payload: unknown, exitCode: number}} */
65
+ const normalized = result && typeof result === 'object' && 'payload' in /** @type {Record<string, unknown>} */ (result)
66
+ ? /** @type {{payload: unknown, exitCode: number}} */ (result)
67
67
  : { payload: result, exitCode: EXIT_CODES.OK };
68
68
 
69
69
  if (normalized.payload !== undefined) {
70
70
  const format = options.ndjson ? 'ndjson' : options.json ? 'json' : 'text';
71
- present(normalized.payload, { format, command, view: options.view });
71
+ present(/** @type {Record<string, unknown>} */ (normalized.payload), { format, command, view: /** @type {string | null | boolean} */ (options.view ?? null) });
72
72
  }
73
73
  // Use process.exit() to avoid waiting for fire-and-forget I/O (e.g. seek cache writes).
74
74
  process.exit(normalized.exitCode ?? EXIT_CODES.OK);
@@ -78,7 +78,7 @@ main().catch((error) => {
78
78
  const exitCode = error instanceof CliError ? error.exitCode : EXIT_CODES.INTERNAL;
79
79
  const code = error instanceof CliError ? error.code : 'E_INTERNAL';
80
80
  const message = error instanceof Error ? error.message : 'Unknown error';
81
- /** @type {{error: {code: string, message: string, cause?: *}}} */
81
+ /** @type {{error: {code: string, message: string, cause?: unknown}}} */
82
82
  const payload = { error: { code, message } };
83
83
 
84
84
  if (error && error.cause) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@git-stunts/git-warp",
3
- "version": "12.2.1",
3
+ "version": "12.4.1",
4
4
  "description": "Deterministic WARP graph over Git: graph-native storage, traversal, and tooling.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -50,21 +50,7 @@ const DEFAULT_ADJACENCY_CACHE_SIZE = 3;
50
50
  export default class WarpGraph {
51
51
  /**
52
52
  * @private
53
- * @param {Object} options
54
- * @param {import('../ports/GraphPersistencePort.js').default} options.persistence - Git adapter
55
- * @param {string} options.graphName - Graph namespace
56
- * @param {string} options.writerId - This writer's ID
57
- * @param {Object} [options.gcPolicy] - GC policy configuration (overrides defaults)
58
- * @param {number} [options.adjacencyCacheSize] - Max materialized adjacency cache entries
59
- * @param {{every: number}} [options.checkpointPolicy] - Auto-checkpoint policy; creates a checkpoint every N patches
60
- * @param {boolean} [options.autoMaterialize=true] - If true, query methods auto-materialize instead of throwing
61
- * @param {'reject'|'cascade'|'warn'} [options.onDeleteWithData='warn'] - Policy when deleting a node that still has edges or properties
62
- * @param {import('../ports/LoggerPort.js').default} [options.logger] - Logger for structured logging
63
- * @param {import('../ports/ClockPort.js').default} [options.clock] - Clock for timing instrumentation (defaults to performance-based clock)
64
- * @param {import('../ports/CryptoPort.js').default} [options.crypto] - Crypto adapter for hashing
65
- * @param {import('../ports/CodecPort.js').default} [options.codec] - Codec for CBOR serialization (defaults to domain-local codec)
66
- * @param {import('../ports/SeekCachePort.js').default} [options.seekCache] - Persistent cache for seek materialization (optional)
67
- * @param {boolean} [options.audit=false] - If true, creates audit receipts for each data commit
53
+ * @param {{ persistence: import('../ports/GraphPersistencePort.js').default, graphName: string, writerId: string, gcPolicy?: Record<string, unknown>, adjacencyCacheSize?: number, checkpointPolicy?: {every: number}, autoMaterialize?: boolean, onDeleteWithData?: 'reject'|'cascade'|'warn', logger?: import('../ports/LoggerPort.js').default, clock?: import('../ports/ClockPort.js').default, crypto?: import('../ports/CryptoPort.js').default, codec?: import('../ports/CodecPort.js').default, seekCache?: import('../ports/SeekCachePort.js').default, audit?: boolean }} options
68
54
  */
69
55
  constructor({ persistence, graphName, writerId, gcPolicy = {}, adjacencyCacheSize = DEFAULT_ADJACENCY_CACHE_SIZE, checkpointPolicy, autoMaterialize = true, onDeleteWithData = 'warn', logger, clock, crypto, codec, seekCache, audit = false }) {
70
56
  /** @type {FullPersistence} */
@@ -85,7 +71,7 @@ export default class WarpGraph {
85
71
  /** @type {boolean} */
86
72
  this._stateDirty = false;
87
73
 
88
- /** @type {Object} */
74
+ /** @type {import('./services/GCPolicy.js').GCPolicy} */
89
75
  this._gcPolicy = { ...DEFAULT_GC_POLICY, ...gcPolicy };
90
76
 
91
77
  /** @type {number} */
@@ -224,9 +210,7 @@ export default class WarpGraph {
224
210
  * Logs a timing message for a completed or failed operation.
225
211
  * @param {string} op - Operation name (e.g. 'materialize')
226
212
  * @param {number} t0 - Start timestamp from this._clock.now()
227
- * @param {Object} [opts] - Options
228
- * @param {string} [opts.metrics] - Extra metrics string to append in parentheses
229
- * @param {Error} [opts.error] - If set, logs a failure message instead
213
+ * @param {{ metrics?: string, error?: Error }} [opts] - Options
230
214
  */
231
215
  _logTiming(op, t0, { metrics, error } = {}) {
232
216
  if (!this._logger) {
@@ -259,21 +243,7 @@ export default class WarpGraph {
259
243
  /**
260
244
  * Opens a multi-writer graph.
261
245
  *
262
- * @param {Object} options
263
- * @param {import('../ports/GraphPersistencePort.js').default} options.persistence - Git adapter
264
- * @param {string} options.graphName - Graph namespace
265
- * @param {string} options.writerId - This writer's ID
266
- * @param {Object} [options.gcPolicy] - GC policy configuration (overrides defaults)
267
- * @param {number} [options.adjacencyCacheSize] - Max materialized adjacency cache entries
268
- * @param {{every: number}} [options.checkpointPolicy] - Auto-checkpoint policy; creates a checkpoint every N patches
269
- * @param {boolean} [options.autoMaterialize] - If true, query methods auto-materialize instead of throwing
270
- * @param {'reject'|'cascade'|'warn'} [options.onDeleteWithData] - Policy when deleting a node that still has edges or properties (default: 'warn')
271
- * @param {import('../ports/LoggerPort.js').default} [options.logger] - Logger for structured logging
272
- * @param {import('../ports/ClockPort.js').default} [options.clock] - Clock for timing instrumentation (defaults to performance-based clock)
273
- * @param {import('../ports/CryptoPort.js').default} [options.crypto] - Crypto adapter for hashing
274
- * @param {import('../ports/CodecPort.js').default} [options.codec] - Codec for CBOR serialization (defaults to domain-local codec)
275
- * @param {import('../ports/SeekCachePort.js').default} [options.seekCache] - Persistent cache for seek materialization (optional)
276
- * @param {boolean} [options.audit=false] - If true, creates audit receipts for each data commit
246
+ * @param {{ persistence: import('../ports/GraphPersistencePort.js').default, graphName: string, writerId: string, gcPolicy?: Record<string, unknown>, adjacencyCacheSize?: number, checkpointPolicy?: {every: number}, autoMaterialize?: boolean, onDeleteWithData?: 'reject'|'cascade'|'warn', logger?: import('../ports/LoggerPort.js').default, clock?: import('../ports/ClockPort.js').default, crypto?: import('../ports/CryptoPort.js').default, codec?: import('../ports/CodecPort.js').default, seekCache?: import('../ports/SeekCachePort.js').default, audit?: boolean }} options
277
247
  * @returns {Promise<WarpGraph>} The opened graph instance
278
248
  * @throws {Error} If graphName, writerId, checkpointPolicy, or onDeleteWithData is invalid
279
249
  *
@@ -377,7 +347,7 @@ export default class WarpGraph {
377
347
  /**
378
348
  * Gets the current GC policy.
379
349
  *
380
- * @returns {Object} The GC policy configuration
350
+ * @returns {import('./services/GCPolicy.js').GCPolicy} The GC policy configuration
381
351
  */
382
352
  get gcPolicy() {
383
353
  return { ...this._gcPolicy };
@@ -116,6 +116,9 @@ export function createORSet() {
116
116
  * @param {import('./Dot.js').Dot} dot - The dot representing this add operation
117
117
  */
118
118
  export function orsetAdd(set, element, dot) {
119
+ if (!dot || typeof dot.writerId !== 'string' || !Number.isInteger(dot.counter)) {
120
+ throw new Error(`orsetAdd: invalid dot -- expected {writerId: string, counter: integer}, got ${JSON.stringify(dot)}`);
121
+ }
119
122
  const encoded = encodeDot(dot);
120
123
 
121
124
  let dots = set.entries.get(element);
@@ -155,7 +155,7 @@ export function vvContains(vv, dot) {
155
155
  * Keys are sorted for deterministic serialization.
156
156
  *
157
157
  * @param {VersionVector} vv
158
- * @returns {Object<string, number>}
158
+ * @returns {Record<string, number>}
159
159
  */
160
160
  export function vvSerialize(vv) {
161
161
  /** @type {Record<string, number>} */
@@ -38,12 +38,7 @@ export default class GraphNode {
38
38
  /**
39
39
  * Creates a new immutable GraphNode.
40
40
  *
41
- * @param {Object} data - Node data
42
- * @param {string} data.sha - The commit SHA (40 hex characters). Required.
43
- * @param {string} data.message - The commit message/payload. Required.
44
- * @param {string} [data.author] - The commit author name. Optional.
45
- * @param {string} [data.date] - The commit date string. Optional.
46
- * @param {string[]} [data.parents=[]] - Array of parent commit SHAs. Defaults to empty array.
41
+ * @param {{ sha: string, message: string, author?: string, date?: string, parents?: string[] }} data - Node data
47
42
  * @throws {Error} If sha is missing or not a string
48
43
  * @throws {Error} If message is missing or not a string
49
44
  * @throws {Error} If parents is not an array
@@ -21,7 +21,7 @@ import WarpError from './WarpError.js';
21
21
  *
22
22
  * @property {string} name - Always 'ForkError' for instanceof checks
23
23
  * @property {string} code - Machine-readable error code for programmatic handling
24
- * @property {Object} context - Serializable context object with error details
24
+ * @property {Record<string, unknown>} context - Serializable context object with error details
25
25
  */
26
26
  export default class ForkError extends WarpError {
27
27
  /**
@@ -8,7 +8,7 @@ import WarpError from './WarpError.js';
8
8
  *
9
9
  * @property {string} name - The error name ('IndexError')
10
10
  * @property {string} code - Error code for programmatic handling (default: 'INDEX_ERROR')
11
- * @property {Object} context - Serializable context object for debugging
11
+ * @property {Record<string, unknown>} context - Serializable context object for debugging
12
12
  *
13
13
  * @example
14
14
  * throw new IndexError('Failed to process index', {
@@ -10,7 +10,7 @@ import WarpError from './WarpError.js';
10
10
  * @property {string} code - Error code for programmatic handling (default: 'OPERATION_ABORTED')
11
11
  * @property {string} operation - The name of the operation that was aborted
12
12
  * @property {string} reason - The reason the operation was aborted
13
- * @property {Object} context - Serializable context object for debugging
13
+ * @property {Record<string, unknown>} context - Serializable context object for debugging
14
14
  */
15
15
  export default class OperationAbortedError extends WarpError {
16
16
  /**
@@ -14,7 +14,7 @@ import WarpError from './WarpError.js';
14
14
  *
15
15
  * @property {string} name - Always 'PatchError' for instanceof checks
16
16
  * @property {string} code - Machine-readable error code for programmatic handling
17
- * @property {Object} context - Serializable context object with error details
17
+ * @property {Record<string, unknown>} context - Serializable context object with error details
18
18
  */
19
19
  export default class PatchError extends WarpError {
20
20
  /**
@@ -0,0 +1,45 @@
1
+ import WarpError from './WarpError.js';
2
+
3
+ /**
4
+ * Typed error codes for persistence adapter boundary failures.
5
+ *
6
+ * Replaces generic `Error` throws with machine-readable codes so callers
7
+ * can branch on `err.code` instead of brittle `err.message.includes()`.
8
+ *
9
+ * ## Error Codes
10
+ *
11
+ * | Code | Description |
12
+ * |------|-------------|
13
+ * | `E_MISSING_OBJECT` | Stored object (commit, blob, tree) does not exist |
14
+ * | `E_REF_NOT_FOUND` | Ref does not resolve to any object |
15
+ * | `E_REF_IO` | Ref update/delete failed (lock contention, permission, etc.) |
16
+ *
17
+ * @class PersistenceError
18
+ * @extends WarpError
19
+ *
20
+ * @property {string} name - Always 'PersistenceError' for instanceof checks
21
+ * @property {string} code - Machine-readable error code for programmatic handling
22
+ * @property {Record<string, unknown>} context - Serializable context object with error details
23
+ */
24
+ export default class PersistenceError extends WarpError {
25
+ /** Stored object (commit, blob, tree) does not exist. */
26
+ static E_MISSING_OBJECT = 'E_MISSING_OBJECT';
27
+
28
+ /** Ref does not resolve to any object. */
29
+ static E_REF_NOT_FOUND = 'E_REF_NOT_FOUND';
30
+
31
+ /** Ref update/delete failed (lock contention, permission, etc.). */
32
+ static E_REF_IO = 'E_REF_IO';
33
+
34
+ /**
35
+ * @param {string} message - Human-readable error message
36
+ * @param {string} code - One of the E_* constants
37
+ * @param {{ cause?: Error, context?: Record<string, unknown> }} [options={}]
38
+ */
39
+ constructor(message, code, options = {}) {
40
+ super(message, code, { context: options.context });
41
+ if (options.cause) {
42
+ this.cause = options.cause;
43
+ }
44
+ }
45
+ }
@@ -30,7 +30,7 @@ import WarpError from './WarpError.js';
30
30
  *
31
31
  * @property {string} name - Always 'QueryError' for instanceof checks
32
32
  * @property {string} code - Machine-readable error code for programmatic handling
33
- * @property {Object} context - Serializable context object with error details
33
+ * @property {Record<string, unknown>} context - Serializable context object with error details
34
34
  */
35
35
  export default class QueryError extends WarpError {
36
36
  /**
@@ -8,7 +8,7 @@ import WarpError from './WarpError.js';
8
8
  *
9
9
  * @property {string} name - The error name ('SchemaUnsupportedError')
10
10
  * @property {string} code - Error code ('E_SCHEMA_UNSUPPORTED')
11
- * @property {Object} context - Serializable context object for debugging
11
+ * @property {Record<string, unknown>} context - Serializable context object for debugging
12
12
  */
13
13
  export default class SchemaUnsupportedError extends WarpError {
14
14
  /**
@@ -22,7 +22,7 @@ import WarpError from './WarpError.js';
22
22
  *
23
23
  * @property {string} name - Always 'SyncError' for instanceof checks
24
24
  * @property {string} code - Machine-readable error code for programmatic handling
25
- * @property {Object} context - Serializable context object with error details
25
+ * @property {Record<string, unknown>} context - Serializable context object with error details
26
26
  */
27
27
  export default class SyncError extends WarpError {
28
28
  /**
@@ -8,7 +8,7 @@ import WarpError from './WarpError.js';
8
8
  *
9
9
  * @property {string} name - The error name ('TraversalError')
10
10
  * @property {string} code - Error code for programmatic handling (default: 'TRAVERSAL_ERROR')
11
- * @property {Object} context - Serializable context object for debugging
11
+ * @property {Record<string, unknown>} context - Serializable context object for debugging
12
12
  *
13
13
  * @example
14
14
  * throw new TraversalError('Node not found in index', {
@@ -18,7 +18,7 @@ import WarpError from './WarpError.js';
18
18
  *
19
19
  * @property {string} name - Always 'TrustError' for instanceof checks
20
20
  * @property {string} code - Machine-readable error code for programmatic handling
21
- * @property {Object} context - Serializable context object with error details
21
+ * @property {Record<string, unknown>} context - Serializable context object with error details
22
22
  */
23
23
  export default class TrustError extends WarpError {
24
24
  /**
@@ -19,7 +19,7 @@ import WarpError from './WarpError.js';
19
19
  *
20
20
  * @property {string} name - Always 'WormholeError' for instanceof checks
21
21
  * @property {string} code - Machine-readable error code for programmatic handling
22
- * @property {Object} context - Serializable context object with error details
22
+ * @property {Record<string, unknown>} context - Serializable context object with error details
23
23
  */
24
24
  export default class WormholeError extends WarpError {
25
25
  /**
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  export { default as EmptyMessageError } from './EmptyMessageError.js';
8
+ export { default as PersistenceError } from './PersistenceError.js';
8
9
  export { default as WarpError } from './WarpError.js';
9
10
  export { default as ForkError } from './ForkError.js';
10
11
  export { default as IndexError } from './IndexError.js';
@@ -86,10 +86,7 @@ function mergeSorted(a, b) {
86
86
 
87
87
  export default class AdjacencyNeighborProvider extends NeighborProviderPort {
88
88
  /**
89
- * @param {Object} params
90
- * @param {Map<string, Array<{neighborId: string, label: string}>>} params.outgoing
91
- * @param {Map<string, Array<{neighborId: string, label: string}>>} params.incoming
92
- * @param {Set<string>} params.aliveNodes - Set of alive nodeIds for hasNode()
89
+ * @param {{ outgoing: Map<string, Array<{neighborId: string, label: string}>>, incoming: Map<string, Array<{neighborId: string, label: string}>>, aliveNodes: Set<string> }} params
93
90
  */
94
91
  constructor({ outgoing, incoming, aliveNodes }) {
95
92
  super();
@@ -23,9 +23,7 @@ import {
23
23
  /**
24
24
  * Encodes an anchor commit message.
25
25
  *
26
- * @param {Object} options - The anchor message options
27
- * @param {string} options.graph - The graph name
28
- * @param {number} [options.schema=2] - The schema version (defaults to 2 for new messages)
26
+ * @param {{ graph: string, schema?: number }} options - The anchor message options
29
27
  * @returns {string} The encoded commit message
30
28
  * @throws {Error} If any validation fails
31
29
  *
@@ -24,11 +24,7 @@ import {
24
24
  /**
25
25
  * Encodes an audit commit message with trailers.
26
26
  *
27
- * @param {Object} options
28
- * @param {string} options.graph - The graph name
29
- * @param {string} options.writer - The writer ID
30
- * @param {string} options.dataCommit - The OID of the data commit being audited
31
- * @param {string} options.opsDigest - SHA-256 hex digest of the canonical ops JSON
27
+ * @param {{ graph: string, writer: string, dataCommit: string, opsDigest: string }} options
32
28
  * @returns {string} The encoded commit message
33
29
  * @throws {Error} If any validation fails
34
30
  */
@@ -90,16 +90,7 @@ const OID_HEX_PATTERN = /^[0-9a-f]{40}([0-9a-f]{24})?$/;
90
90
  /**
91
91
  * Validates and builds a frozen receipt record with keys in sorted order.
92
92
  *
93
- * @param {Object} fields
94
- * @param {number} fields.version
95
- * @param {string} fields.graphName
96
- * @param {string} fields.writerId
97
- * @param {string} fields.dataCommit
98
- * @param {number} fields.tickStart
99
- * @param {number} fields.tickEnd
100
- * @param {string} fields.opsDigest
101
- * @param {string} fields.prevAuditCommit
102
- * @param {number} fields.timestamp
93
+ * @param {{ version: number, graphName: string, writerId: string, dataCommit: string, tickStart: number, tickEnd: number, opsDigest: string, prevAuditCommit: string, timestamp: number }} fields
103
94
  * @returns {Readonly<Record<string, unknown>>}
104
95
  * @throws {Error} If any field is invalid
105
96
  */
@@ -206,13 +197,7 @@ export function buildReceiptRecord(fields) {
206
197
  */
207
198
  export class AuditReceiptService {
208
199
  /**
209
- * @param {Object} options
210
- * @param {import('../../ports/RefPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default & import('../../ports/CommitPort.js').default} options.persistence
211
- * @param {string} options.graphName
212
- * @param {string} options.writerId
213
- * @param {import('../../ports/CodecPort.js').default} options.codec
214
- * @param {import('../../ports/CryptoPort.js').default} options.crypto
215
- * @param {import('../../ports/LoggerPort.js').default} [options.logger]
200
+ * @param {{ persistence: import('../../ports/RefPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default & import('../../ports/CommitPort.js').default, graphName: string, writerId: string, codec: import('../../ports/CodecPort.js').default, crypto: import('../../ports/CryptoPort.js').default, logger?: import('../../ports/LoggerPort.js').default }} options
216
201
  */
217
202
  constructor({ persistence, graphName, writerId, codec, crypto, logger }) {
218
203
  this._persistence = persistence;
@@ -339,7 +324,8 @@ export class AuditReceiptService {
339
324
  // Compute opsDigest
340
325
  const opsDigest = await computeOpsDigest(ops, this._crypto);
341
326
 
342
- // Timestamp
327
+ // Wall-clock timestamp for audit receipt (not a perf timer)
328
+ // eslint-disable-next-line no-restricted-syntax
343
329
  const timestamp = Date.now();
344
330
 
345
331
  // Determine prevAuditCommit
@@ -211,10 +211,7 @@ function validateTrailerConsistency(receipt, decoded) {
211
211
 
212
212
  export class AuditVerifierService {
213
213
  /**
214
- * @param {Object} options
215
- * @param {import('../../ports/CommitPort.js').default & import('../../ports/RefPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default} options.persistence
216
- * @param {import('../../ports/CodecPort.js').default} options.codec
217
- * @param {import('../../ports/LoggerPort.js').default} [options.logger]
214
+ * @param {{ persistence: import('../../ports/CommitPort.js').default & import('../../ports/RefPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default, codec: import('../../ports/CodecPort.js').default, logger?: import('../../ports/LoggerPort.js').default }} options
218
215
  */
219
216
  constructor({ persistence, codec, logger }) {
220
217
  this._persistence = persistence;
@@ -257,6 +254,7 @@ export class AuditVerifierService {
257
254
 
258
255
  return {
259
256
  graph: graphName,
257
+ // eslint-disable-next-line no-restricted-syntax -- wall-clock timestamp for audit report
260
258
  verifiedAt: new Date().toISOString(),
261
259
  summary: { total: chains.length, valid, partial, invalid },
262
260
  chains,
@@ -657,9 +655,7 @@ export class AuditVerifierService {
657
655
  * and returns a TrustAssessment.
658
656
  *
659
657
  * @param {string} graphName
660
- * @param {Object} [options]
661
- * @param {string} [options.pin] - Pinned trust chain commit SHA
662
- * @param {string} [options.mode] - Policy mode ('warn' or 'enforce')
658
+ * @param {{ pin?: string, mode?: string }} [options]
663
659
  * @returns {Promise<import('../trust/TrustEvaluator.js').TrustAssessment>}
664
660
  */
665
661
  async evaluateTrust(graphName, options = {}) {
@@ -12,7 +12,7 @@ export { SHARD_VERSION };
12
12
  * Uses canonical JSON stringification for deterministic output
13
13
  * across different JavaScript engines.
14
14
  *
15
- * @param {Object} data - The data object to checksum
15
+ * @param {Record<string, unknown>} data - The data object to checksum
16
16
  * @param {import('../../ports/CryptoPort.js').default} crypto - CryptoPort instance
17
17
  * @returns {Promise<string>} Hex-encoded SHA-256 hash
18
18
  */
@@ -42,9 +42,9 @@ const ensureRoaringBitmap32 = () => {
42
42
 
43
43
  /**
44
44
  * Wraps data in a version/checksum envelope.
45
- * @param {Object} data - The data to wrap
45
+ * @param {Record<string, unknown>} data - The data to wrap
46
46
  * @param {import('../../ports/CryptoPort.js').default} crypto - CryptoPort instance
47
- * @returns {Promise<Object>} Envelope with version, checksum, and data
47
+ * @returns {Promise<{version: number, checksum: string, data: Record<string, unknown>}>} Envelope with version, checksum, and data
48
48
  */
49
49
  const wrapShard = async (data, crypto) => ({
50
50
  version: SHARD_VERSION,
@@ -95,9 +95,7 @@ export default class BitmapIndexBuilder {
95
95
  * - Forward edge bitmaps (parent → children)
96
96
  * - Reverse edge bitmaps (child → parents)
97
97
  *
98
- * @param {Object} [options] - Configuration options
99
- * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort instance for hashing
100
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
98
+ * @param {{ crypto?: import('../../ports/CryptoPort.js').default, codec?: import('../../ports/CodecPort.js').default }} [options] - Configuration options
101
99
  */
102
100
  constructor({ crypto, codec } = {}) {
103
101
  /** @type {import('../../ports/CryptoPort.js').default} */
@@ -149,8 +147,7 @@ export default class BitmapIndexBuilder {
149
147
  *
150
148
  * Each shard is wrapped in a version/checksum envelope for integrity verification.
151
149
  *
152
- * @param {Object} [options] - Serialization options
153
- * @param {Map<string, string>} [options.frontier] - Writer→tip SHA map to include in the tree
150
+ * @param {{ frontier?: Map<string, string> }} [options] - Serialization options
154
151
  * @returns {Promise<Record<string, Buffer>>} Map of path → serialized content
155
152
  */
156
153
  async serialize({ frontier } = {}) {
@@ -217,10 +214,7 @@ export default class BitmapIndexBuilder {
217
214
 
218
215
  /**
219
216
  * Adds an ID to a node's bitmap.
220
- * @param {Object} opts - Options
221
- * @param {string} opts.sha - The SHA to use as key
222
- * @param {number} opts.id - The ID to add to the bitmap
223
- * @param {string} opts.type - 'fwd' or 'rev'
217
+ * @param {{ sha: string, id: number, type: string }} opts - Options
224
218
  * @private
225
219
  */
226
220
  _addToBitmap({ sha, id, type }) {