@git-stunts/git-warp 12.3.0 → 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 (114) hide show
  1. package/README.md +5 -6
  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/warp-graph.js +6 -6
  6. package/package.json +1 -1
  7. package/src/domain/WarpGraph.js +5 -35
  8. package/src/domain/crdt/VersionVector.js +1 -1
  9. package/src/domain/entities/GraphNode.js +1 -6
  10. package/src/domain/errors/ForkError.js +1 -1
  11. package/src/domain/errors/IndexError.js +1 -1
  12. package/src/domain/errors/OperationAbortedError.js +1 -1
  13. package/src/domain/errors/PatchError.js +1 -1
  14. package/src/domain/errors/PersistenceError.js +45 -0
  15. package/src/domain/errors/QueryError.js +1 -1
  16. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  17. package/src/domain/errors/SyncError.js +1 -1
  18. package/src/domain/errors/TraversalError.js +1 -1
  19. package/src/domain/errors/TrustError.js +1 -1
  20. package/src/domain/errors/WormholeError.js +1 -1
  21. package/src/domain/errors/index.js +1 -0
  22. package/src/domain/services/AdjacencyNeighborProvider.js +1 -4
  23. package/src/domain/services/AnchorMessageCodec.js +1 -3
  24. package/src/domain/services/AuditMessageCodec.js +1 -5
  25. package/src/domain/services/AuditReceiptService.js +2 -17
  26. package/src/domain/services/AuditVerifierService.js +2 -7
  27. package/src/domain/services/BitmapIndexBuilder.js +6 -12
  28. package/src/domain/services/BitmapIndexReader.js +7 -20
  29. package/src/domain/services/BitmapNeighborProvider.js +1 -3
  30. package/src/domain/services/BoundaryTransitionRecord.js +6 -23
  31. package/src/domain/services/CheckpointMessageCodec.js +1 -6
  32. package/src/domain/services/CheckpointSerializerV5.js +8 -12
  33. package/src/domain/services/CheckpointService.js +9 -39
  34. package/src/domain/services/CommitDagTraversalService.js +1 -3
  35. package/src/domain/services/DagPathFinding.js +9 -59
  36. package/src/domain/services/DagTopology.js +4 -16
  37. package/src/domain/services/DagTraversal.js +7 -31
  38. package/src/domain/services/Frontier.js +4 -6
  39. package/src/domain/services/GitLogParser.js +1 -2
  40. package/src/domain/services/GraphTraversal.js +14 -114
  41. package/src/domain/services/HealthCheckService.js +3 -9
  42. package/src/domain/services/HookInstaller.js +2 -8
  43. package/src/domain/services/HttpSyncServer.js +24 -25
  44. package/src/domain/services/IncrementalIndexUpdater.js +4 -6
  45. package/src/domain/services/IndexRebuildService.js +6 -52
  46. package/src/domain/services/IndexStalenessChecker.js +2 -3
  47. package/src/domain/services/JoinReducer.js +39 -65
  48. package/src/domain/services/LogicalBitmapIndexBuilder.js +1 -2
  49. package/src/domain/services/LogicalIndexBuildService.js +2 -6
  50. package/src/domain/services/LogicalIndexReader.js +1 -2
  51. package/src/domain/services/LogicalTraversal.js +13 -64
  52. package/src/domain/services/MaterializedViewService.js +4 -18
  53. package/src/domain/services/MigrationService.js +1 -4
  54. package/src/domain/services/ObserverView.js +1 -7
  55. package/src/domain/services/PatchBuilderV2.js +6 -18
  56. package/src/domain/services/PatchMessageCodec.js +1 -6
  57. package/src/domain/services/PropertyIndexBuilder.js +1 -2
  58. package/src/domain/services/PropertyIndexReader.js +1 -4
  59. package/src/domain/services/ProvenanceIndex.js +5 -7
  60. package/src/domain/services/ProvenancePayload.js +1 -1
  61. package/src/domain/services/QueryBuilder.js +3 -16
  62. package/src/domain/services/StateDiff.js +3 -9
  63. package/src/domain/services/StateSerializerV5.js +10 -10
  64. package/src/domain/services/StreamingBitmapIndexBuilder.js +13 -41
  65. package/src/domain/services/SyncAuthService.js +5 -32
  66. package/src/domain/services/SyncController.js +5 -25
  67. package/src/domain/services/SyncProtocol.js +4 -8
  68. package/src/domain/services/SyncTrustGate.js +4 -9
  69. package/src/domain/services/TemporalQuery.js +9 -27
  70. package/src/domain/services/TranslationCost.js +2 -8
  71. package/src/domain/services/WarpStateIndexBuilder.js +2 -4
  72. package/src/domain/services/WormholeService.js +9 -25
  73. package/src/domain/trust/TrustCrypto.js +1 -5
  74. package/src/domain/trust/TrustEvaluator.js +1 -8
  75. package/src/domain/trust/TrustRecordService.js +5 -10
  76. package/src/domain/types/TickReceipt.js +3 -7
  77. package/src/domain/types/WarpTypes.js +1 -5
  78. package/src/domain/types/WarpTypesV2.js +1 -8
  79. package/src/domain/utils/CachedValue.js +1 -4
  80. package/src/domain/utils/MinHeap.js +3 -3
  81. package/src/domain/utils/RefLayout.js +26 -0
  82. package/src/domain/utils/WriterId.js +2 -7
  83. package/src/domain/utils/canonicalCbor.js +1 -1
  84. package/src/domain/utils/defaultCodec.js +1 -1
  85. package/src/domain/utils/parseCursorBlob.js +4 -4
  86. package/src/domain/warp/PatchSession.js +3 -8
  87. package/src/domain/warp/Writer.js +3 -12
  88. package/src/domain/warp/_wire.js +2 -2
  89. package/src/domain/warp/_wiredMethods.d.ts +5 -7
  90. package/src/domain/warp/checkpoint.methods.js +1 -1
  91. package/src/domain/warp/fork.methods.js +1 -5
  92. package/src/domain/warp/materializeAdvanced.methods.js +3 -3
  93. package/src/domain/warp/patch.methods.js +6 -8
  94. package/src/domain/warp/provenance.methods.js +5 -5
  95. package/src/domain/warp/query.methods.js +9 -18
  96. package/src/domain/warp/subscribe.methods.js +2 -8
  97. package/src/globals.d.ts +7 -0
  98. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -18
  99. package/src/infrastructure/adapters/ConsoleLogger.js +2 -9
  100. package/src/infrastructure/adapters/DenoHttpAdapter.js +15 -15
  101. package/src/infrastructure/adapters/GitGraphAdapter.js +234 -58
  102. package/src/infrastructure/adapters/InMemoryGraphAdapter.js +9 -2
  103. package/src/infrastructure/adapters/NodeHttpAdapter.js +14 -14
  104. package/src/infrastructure/adapters/WebCryptoAdapter.js +1 -2
  105. package/src/ports/BlobPort.js +2 -2
  106. package/src/ports/HttpServerPort.js +24 -2
  107. package/src/ports/RefPort.js +2 -1
  108. package/src/visualization/renderers/ascii/box.js +1 -1
  109. package/src/visualization/renderers/ascii/check.js +1 -5
  110. package/src/visualization/renderers/ascii/history.js +1 -6
  111. package/src/visualization/renderers/ascii/path.js +4 -22
  112. package/src/visualization/renderers/ascii/progress.js +1 -4
  113. package/src/visualization/renderers/ascii/seek.js +1 -5
  114. package/src/visualization/renderers/ascii/table.js +1 -3
@@ -61,13 +61,8 @@ async function verifyShaExists(persistence, sha, paramName) {
61
61
 
62
62
  /**
63
63
  * Processes a single commit in the wormhole chain.
64
- * @param {Object} opts - Options
65
- * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default} opts.persistence - Git persistence adapter
66
- * @param {string} opts.sha - The commit SHA
67
- * @param {string} opts.graphName - Expected graph name
68
- * @param {string|null} opts.expectedWriter - Expected writer ID (null for first commit)
69
- * @param {import('../../ports/CodecPort.js').default} [opts.codec] - Codec for deserialization
70
- * @returns {Promise<{patch: Object, sha: string, writerId: string, parentSha: string|null}>}
64
+ * @param {{ persistence: import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default, sha: string, graphName: string, expectedWriter: string|null, codec?: import('../../ports/CodecPort.js').default }} opts - Options
65
+ * @returns {Promise<{patch: import('../types/WarpTypesV2.js').PatchV2, sha: string, writerId: string, parentSha: string|null}>}
71
66
  * @throws {WormholeError} On validation errors
72
67
  * @private
73
68
  */
@@ -101,7 +96,7 @@ async function processCommit({ persistence, sha, graphName, expectedWriter, code
101
96
  }
102
97
 
103
98
  const patchBuffer = await persistence.readBlob(patchMeta.patchOid);
104
- const patch = /** @type {Object} */ (codec.decode(patchBuffer));
99
+ const patch = /** @type {import('../types/WarpTypesV2.js').PatchV2} */ (codec.decode(patchBuffer));
105
100
 
106
101
  return {
107
102
  patch,
@@ -135,12 +130,7 @@ async function processCommit({ persistence, sha, graphName, expectedWriter, code
135
130
  * must be an ancestor of `toSha` in the writer's patch chain. Both endpoints
136
131
  * are inclusive in the wormhole.
137
132
  *
138
- * @param {Object} options - Wormhole creation options
139
- * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default} options.persistence - Git persistence adapter
140
- * @param {string} options.graphName - Name of the graph
141
- * @param {string} options.fromSha - SHA of the first (oldest) patch commit
142
- * @param {string} options.toSha - SHA of the last (newest) patch commit
143
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
133
+ * @param {{ persistence: import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default, graphName: string, fromSha: string, toSha: string, codec?: import('../../ports/CodecPort.js').default }} options - Wormhole creation options
144
134
  * @returns {Promise<WormholeEdge>} The created wormhole
145
135
  * @throws {WormholeError} If fromSha or toSha doesn't exist (E_WORMHOLE_SHA_NOT_FOUND)
146
136
  * @throws {WormholeError} If fromSha is not an ancestor of toSha (E_WORMHOLE_INVALID_RANGE)
@@ -171,13 +161,8 @@ export async function createWormhole({ persistence, graphName, fromSha, toSha, c
171
161
  * Walks the parent chain from toSha towards fromSha, collecting and
172
162
  * validating each commit along the way.
173
163
  *
174
- * @param {Object} options
175
- * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default} options.persistence - Git persistence adapter
176
- * @param {string} options.graphName - Expected graph name
177
- * @param {string} options.fromSha - SHA of the first (oldest) patch commit
178
- * @param {string} options.toSha - SHA of the last (newest) patch commit
179
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
180
- * @returns {Promise<Array<{patch: Object, sha: string, writerId: string}>>} Patches in newest-first order
164
+ * @param {{ persistence: import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default, graphName: string, fromSha: string, toSha: string, codec?: import('../../ports/CodecPort.js').default }} options
165
+ * @returns {Promise<Array<{patch: import('../types/WarpTypesV2.js').PatchV2, sha: string, writerId: string}>>} Patches in newest-first order
181
166
  * @throws {WormholeError} If fromSha is not an ancestor of toSha or range is empty
182
167
  * @private
183
168
  */
@@ -232,8 +217,7 @@ async function collectPatchRange({ persistence, graphName, fromSha, toSha, codec
232
217
  *
233
218
  * @param {WormholeEdge} first - The earlier (older) wormhole
234
219
  * @param {WormholeEdge} second - The later (newer) wormhole
235
- * @param {Object} [options] - Composition options
236
- * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default} [options.persistence] - Git persistence adapter (for validation)
220
+ * @param {{ persistence?: import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default }} [options] - Composition options
237
221
  * @returns {Promise<WormholeEdge>} The composed wormhole
238
222
  * @throws {WormholeError} If wormholes are from different writers (E_WORMHOLE_MULTI_WRITER)
239
223
  * @throws {WormholeError} If wormholes are not consecutive (E_WORMHOLE_INVALID_RANGE)
@@ -294,7 +278,7 @@ export function replayWormhole(wormhole, initialState) {
294
278
  * Serializes a wormhole to a JSON-serializable object.
295
279
  *
296
280
  * @param {WormholeEdge} wormhole - The wormhole to serialize
297
- * @returns {Object} JSON-serializable representation
281
+ * @returns {Record<string, unknown>} JSON-serializable representation
298
282
  */
299
283
  export function serializeWormhole(wormhole) {
300
284
  return {
@@ -309,7 +293,7 @@ export function serializeWormhole(wormhole) {
309
293
  /**
310
294
  * Deserializes a wormhole from a JSON object.
311
295
  *
312
- * @param {Object} json - The JSON object to deserialize
296
+ * @param {Record<string, unknown>} json - The JSON object to deserialize
313
297
  * @returns {WormholeEdge} The deserialized wormhole
314
298
  * @throws {WormholeError} If the JSON structure is invalid
315
299
  */
@@ -63,11 +63,7 @@ function decodePublicKey(base64) {
63
63
  /**
64
64
  * Verifies an Ed25519 signature against a payload.
65
65
  *
66
- * @param {Object} params
67
- * @param {string} params.algorithm - Must be 'ed25519'
68
- * @param {string} params.publicKeyBase64 - Base64-encoded 32-byte public key
69
- * @param {string} params.signatureBase64 - Base64-encoded signature
70
- * @param {Buffer} params.payload - Bytes to verify
66
+ * @param {{ algorithm: string, publicKeyBase64: string, signatureBase64: string, payload: Buffer }} params
71
67
  * @returns {boolean} true if signature is valid
72
68
  * @throws {TrustError} E_TRUST_UNSUPPORTED_ALGORITHM for non-ed25519
73
69
  * @throws {TrustError} E_TRUST_INVALID_KEY for malformed public key
@@ -21,14 +21,7 @@ import { deriveTrustVerdict } from './verdict.js';
21
21
  * @property {number} trustSchemaVersion
22
22
  * @property {string} mode
23
23
  * @property {string} trustVerdict
24
- * @property {Object} trust
25
- * @property {'configured'|'pinned'|'error'|'not_configured'} trust.status
26
- * @property {string} trust.source
27
- * @property {string|null} trust.sourceDetail
28
- * @property {string[]} trust.evaluatedWriters
29
- * @property {string[]} trust.untrustedWriters
30
- * @property {ReadonlyArray<{writerId: string, trusted: boolean, reasonCode: string, reason: string}>} trust.explanations
31
- * @property {Record<string, number> & {recordsScanned: number, activeKeys: number, revokedKeys: number, activeBindings: number, revokedBindings: number}} trust.evidenceSummary
24
+ * @property {{ status: 'configured'|'pinned'|'error'|'not_configured', source: string, sourceDetail: string|null, evaluatedWriters: string[], untrustedWriters: string[], explanations: ReadonlyArray<{writerId: string, trusted: boolean, reasonCode: string, reason: string}>, evidenceSummary: Record<string, number> & {recordsScanned: number, activeKeys: number, revokedKeys: number, activeBindings: number, revokedBindings: number} }} trust
32
25
  */
33
26
 
34
27
  /**
@@ -12,6 +12,7 @@
12
12
  import { buildTrustRecordRef } from '../utils/RefLayout.js';
13
13
  import { TrustRecordSchema } from './schemas.js';
14
14
  import { verifyRecordId } from './TrustCanonical.js';
15
+ import PersistenceError from '../errors/PersistenceError.js';
15
16
  import TrustError from '../errors/TrustError.js';
16
17
 
17
18
  /**
@@ -32,9 +33,7 @@ const MAX_CAS_ATTEMPTS = 3;
32
33
 
33
34
  export class TrustRecordService {
34
35
  /**
35
- * @param {Object} options
36
- * @param {import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default & import('../../ports/RefPort.js').default} options.persistence - GraphPersistencePort adapter
37
- * @param {import('../../ports/CodecPort.js').default} options.codec - CodecPort adapter (CBOR)
36
+ * @param {{ persistence: import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default & import('../../ports/RefPort.js').default, codec: import('../../ports/CodecPort.js').default }} options
38
37
  */
39
38
  constructor({ persistence, codec }) {
40
39
  this._persistence = persistence;
@@ -104,8 +103,7 @@ export class TrustRecordService {
104
103
  * Reads all trust records from the chain, oldest first.
105
104
  *
106
105
  * @param {string} graphName
107
- * @param {Object} [options]
108
- * @param {string} [options.tip] - Override tip commit (for pinned reads)
106
+ * @param {{ tip?: string }} [options]
109
107
  * @returns {Promise<ReadRecordsResult>}
110
108
  */
111
109
  async readRecords(graphName, options = {}) {
@@ -118,7 +116,7 @@ export class TrustRecordService {
118
116
  tip = await this._persistence.readRef(ref);
119
117
  } catch (err) {
120
118
  // Distinguish "ref not found" from operational error (J15)
121
- if (err instanceof Error && (err.message?.includes('not found') || err.message?.includes('does not exist'))) {
119
+ if (err instanceof PersistenceError && err.code === PersistenceError.E_REF_NOT_FOUND) {
122
120
  return { ok: true, records: [] };
123
121
  }
124
122
  return {
@@ -234,10 +232,7 @@ export class TrustRecordService {
234
232
  *
235
233
  * @param {string} graphName
236
234
  * @param {Record<string, unknown>} record - Complete signed trust record
237
- * @param {Object} [options]
238
- * @param {number} [options.maxRetries=3] - Maximum rebuild-and-retry attempts
239
- * @param {((record: Record<string, unknown>) => Promise<Record<string, unknown>>)|null} [options.resign] - Function to re-sign a rebuilt record (null for unsigned)
240
- * @param {boolean} [options.skipSignatureVerify=false] - Skip signature verification
235
+ * @param {{ maxRetries?: number, resign?: ((record: Record<string, unknown>) => Promise<Record<string, unknown>>)|null, skipSignatureVerify?: boolean }} [options]
241
236
  * @returns {Promise<{commitSha: string, ref: string, attempts: number}>}
242
237
  * @throws {TrustError} E_TRUST_CAS_EXHAUSTED if all retries fail
243
238
  */
@@ -175,11 +175,7 @@ function validateOpResult(value, i) {
175
175
  /**
176
176
  * Creates an immutable TickReceipt.
177
177
  *
178
- * @param {Object} params
179
- * @param {string} params.patchSha - SHA of the patch commit
180
- * @param {string} params.writer - Writer ID
181
- * @param {number} params.lamport - Lamport timestamp (non-negative integer)
182
- * @param {OpOutcome[]} params.ops - Per-operation outcome records
178
+ * @param {{ patchSha: string, writer: string, lamport: number, ops: OpOutcome[] }} params
183
179
  * @returns {Readonly<TickReceipt>} Frozen tick receipt
184
180
  * @throws {Error} If any parameter is invalid
185
181
  */
@@ -279,9 +275,9 @@ export function canonicalJson(receipt) {
279
275
  */
280
276
  function sortedReplacer(_key, value) {
281
277
  if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
282
- /** @type {{ [x: string]: * }} */
278
+ /** @type {{ [x: string]: unknown }} */
283
279
  const sorted = {};
284
- const obj = /** @type {{ [x: string]: * }} */ (value);
280
+ const obj = /** @type {{ [x: string]: unknown }} */ (value);
285
281
  for (const k of Object.keys(obj).sort()) {
286
282
  sorted[k] = obj[k];
287
283
  }
@@ -197,11 +197,7 @@ export function createPropSet(node, key, value) {
197
197
 
198
198
  /**
199
199
  * Creates an EventId
200
- * @param {Object} options - EventId options
201
- * @param {number} options.lamport - Lamport timestamp
202
- * @param {string} options.writerId - Writer ID
203
- * @param {string} options.patchSha - Patch SHA
204
- * @param {number} options.opIndex - Operation index within patch
200
+ * @param {{ lamport: number, writerId: string, patchSha: string, opIndex: number }} options - EventId options
205
201
  * @returns {EventId} EventId object
206
202
  */
207
203
  export function createEventId({ lamport, writerId, patchSha, opIndex }) {
@@ -241,14 +241,7 @@ export function createEdgePropSetV2(from, to, label, key, value) {
241
241
 
242
242
  /**
243
243
  * Creates a PatchV2
244
- * @param {Object} options - Patch options
245
- * @param {2|3} [options.schema=2] - Schema version (2 for node-only, 3 for edge properties)
246
- * @param {string} options.writer - Writer ID
247
- * @param {number} options.lamport - Lamport timestamp
248
- * @param {VersionVector} options.context - Writer's observed frontier
249
- * @param {OpV2[]} options.ops - Array of operations
250
- * @param {string[]} [options.reads] - Node/edge IDs read by this patch (for provenance tracking)
251
- * @param {string[]} [options.writes] - Node/edge IDs written by this patch (for provenance tracking)
244
+ * @param {{ schema?: 2|3, writer: string, lamport: number, context: VersionVector, ops: OpV2[], reads?: string[], writes?: string[] }} options - Patch options
252
245
  * @returns {PatchV2} PatchV2 object
253
246
  */
254
247
  export function createPatchV2({ schema = 2, writer, lamport, context, ops, reads, writes }) {
@@ -28,10 +28,7 @@ class CachedValue {
28
28
  /**
29
29
  * Creates a CachedValue instance.
30
30
  *
31
- * @param {Object} options
32
- * @param {import('../../ports/ClockPort.js').default} options.clock - Clock port for timing
33
- * @param {number} options.ttlMs - Time-to-live in milliseconds
34
- * @param {() => T | Promise<T>} options.compute - Function to compute the value when cache is stale
31
+ * @param {{ clock: import('../../ports/ClockPort.js').default, ttlMs: number, compute: () => T | Promise<T> }} options
35
32
  * @throws {Error} If ttlMs is not a positive number
36
33
  */
37
34
  constructor({ clock, ttlMs, compute }) {
@@ -9,9 +9,9 @@ class MinHeap {
9
9
  /**
10
10
  * Creates an empty MinHeap.
11
11
  *
12
- * @param {Object} [options] - Configuration options
13
- * @param {((a: T, b: T) => number)} [options.tieBreaker] - Comparator invoked when two
14
- * entries have equal priority. Negative return = a wins (comes out first).
12
+ * @param {{ tieBreaker?: (a: T, b: T) => number }} [options] - Configuration options.
13
+ * `tieBreaker`: comparator invoked when two entries have equal priority.
14
+ * Negative return = a wins (comes out first).
15
15
  * When omitted, equal-priority extraction order is unspecified (heap-natural).
16
16
  */
17
17
  constructor(options) {
@@ -45,6 +45,23 @@ const WRITER_ID_PATTERN = /^[A-Za-z0-9._-]+$/;
45
45
  */
46
46
  const PATH_TRAVERSAL_PATTERN = /\.\./;
47
47
 
48
+ /**
49
+ * Ref-layout keywords that must not appear as any `/`-delimited segment
50
+ * of a graph name. Using one of these would create an ambiguous ref path
51
+ * (e.g. `refs/warp/writers/writers/alice`).
52
+ *
53
+ * @type {Set<string>}
54
+ */
55
+ export const RESERVED_GRAPH_NAME_SEGMENTS = new Set([
56
+ 'writers',
57
+ 'checkpoints',
58
+ 'coverage',
59
+ 'cursor',
60
+ 'audit',
61
+ 'trust',
62
+ 'seek-cache',
63
+ ]);
64
+
48
65
  // -----------------------------------------------------------------------------
49
66
  // Validators
50
67
  // -----------------------------------------------------------------------------
@@ -94,6 +111,15 @@ export function validateGraphName(name) {
94
111
  if (name.includes('\0')) {
95
112
  throw new Error(`Invalid graph name: contains null byte: ${name}`);
96
113
  }
114
+
115
+ const segments = name.split('/');
116
+ for (const seg of segments) {
117
+ if (RESERVED_GRAPH_NAME_SEGMENTS.has(seg)) {
118
+ throw new Error(
119
+ `Invalid graph name: segment '${seg}' is a reserved ref-layout keyword: ${name}`
120
+ );
121
+ }
122
+ }
97
123
  }
98
124
 
99
125
  /**
@@ -116,8 +116,7 @@ function crockfordBase32(bytes) {
116
116
  * Uses 128 bits of entropy (16 bytes) encoded as Crockford Base32.
117
117
  * The result is prefixed with `w_` for a total length of 28 characters.
118
118
  *
119
- * @param {Object} [options]
120
- * @param {(n: number) => Uint8Array} [options.randomBytes] - Custom RNG for testing
119
+ * @param {{ randomBytes?: (n: number) => Uint8Array }} [options] - Options with optional custom RNG for testing
121
120
  * @returns {string} A canonical writer ID (e.g., 'w_0123456789abcdefghjkmnpqrs')
122
121
  * @throws {WriterIdError} If RNG is unavailable or returns wrong shape
123
122
  *
@@ -148,11 +147,7 @@ export function generateWriterId({ randomBytes } = {}) {
148
147
  * 2. Load from git config key `warp.writerId.<graphName>`
149
148
  * 3. If missing or invalid, generate new canonical ID, persist, and return
150
149
  *
151
- * @param {Object} args
152
- * @param {string} args.graphName - The graph name
153
- * @param {string|undefined} args.explicitWriterId - Optional explicit writer ID
154
- * @param {(key: string) => Promise<string|null>} args.configGet - Function to read git config
155
- * @param {(key: string, value: string) => Promise<void>} args.configSet - Function to write git config
150
+ * @param {{ graphName: string, explicitWriterId: string|null|undefined, configGet: (key: string) => Promise<string|null>, configSet: (key: string, value: string) => Promise<void> }} args
156
151
  * @returns {Promise<string>} The resolved writer ID
157
152
  * @throws {WriterIdError} If config operations fail
158
153
  *
@@ -28,7 +28,7 @@ export function encodeCanonicalCbor(value) {
28
28
  /**
29
29
  * Decodes CBOR bytes to a value.
30
30
  *
31
- * @param {Buffer|Uint8Array} buffer - CBOR bytes
31
+ * @param {Uint8Array} buffer - CBOR bytes
32
32
  * @returns {unknown} Decoded value
33
33
  */
34
34
  export function decodeCanonicalCbor(buffer) {
@@ -34,7 +34,7 @@ function sortKeys(value) {
34
34
  }
35
35
  return sorted;
36
36
  }
37
- if (typeof value === 'object' && (/** @type {Object} */ (value).constructor === Object || /** @type {Object} */ (value).constructor === undefined)) {
37
+ if (typeof value === 'object' && (/** @type {Record<string, unknown>} */ (value).constructor === Object || /** @type {Record<string, unknown>} */ (value).constructor === undefined)) {
38
38
  /** @type {Record<string, unknown>} */
39
39
  const sorted = {};
40
40
  for (const key of Object.keys(value).sort()) {
@@ -5,13 +5,13 @@
5
5
  */
6
6
 
7
7
  /**
8
- * Parses and validates a cursor blob (Buffer) into a cursor object.
8
+ * Parses and validates a cursor blob (Uint8Array) into a cursor object.
9
9
  *
10
10
  * The blob must contain UTF-8-encoded JSON representing a plain object with at
11
11
  * minimum a finite numeric `tick` field. Any additional fields (e.g. `mode`,
12
12
  * `name`) are preserved in the returned object.
13
13
  *
14
- * @param {Buffer} buf - Raw blob contents (UTF-8 encoded JSON)
14
+ * @param {Uint8Array} buf - Raw blob contents (UTF-8 encoded JSON)
15
15
  * @param {string} label - Human-readable label used in error messages
16
16
  * (e.g. `"active cursor"`, `"saved cursor 'foo'"`)
17
17
  * @returns {{ tick: number, mode?: string, [key: string]: unknown }}
@@ -23,13 +23,13 @@
23
23
  * Infinity
24
24
  *
25
25
  * @example
26
- * const buf = Buffer.from('{"tick":5,"mode":"lamport"}', 'utf8');
26
+ * const buf = new TextEncoder().encode('{"tick":5,"mode":"lamport"}');
27
27
  * const cursor = parseCursorBlob(buf, 'active cursor');
28
28
  * // => { tick: 5, mode: 'lamport' }
29
29
  *
30
30
  * @example
31
31
  * // Throws: "Corrupted active cursor: blob is not valid JSON"
32
- * parseCursorBlob(Buffer.from('not json', 'utf8'), 'active cursor');
32
+ * parseCursorBlob(new TextEncoder().encode('not json'), 'active cursor');
33
33
  */
34
34
  export function parseCursorBlob(buf, label) {
35
35
  let obj;
@@ -19,12 +19,7 @@ export class PatchSession {
19
19
  /**
20
20
  * Creates a new PatchSession.
21
21
  *
22
- * @param {Object} options
23
- * @param {import('../services/PatchBuilderV2.js').PatchBuilderV2} options.builder - Internal builder
24
- * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default} options.persistence - Git adapter
25
- * @param {string} options.graphName - Graph namespace
26
- * @param {string} options.writerId - Writer ID
27
- * @param {string|null} options.expectedOldHead - Expected parent SHA for CAS
22
+ * @param {{ builder: import('../services/PatchBuilderV2.js').PatchBuilderV2, persistence: import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default, graphName: string, writerId: string, expectedOldHead: string|null }} options
28
23
  */
29
24
  constructor({ builder, persistence, graphName, writerId, expectedOldHead }) {
30
25
  /** @type {import('../services/PatchBuilderV2.js').PatchBuilderV2} */
@@ -152,7 +147,7 @@ export class PatchSession {
152
147
  * Attaches content to a node.
153
148
  *
154
149
  * @param {string} nodeId - The node ID to attach content to
155
- * @param {Buffer|string} content - The content to attach
150
+ * @param {Uint8Array|string} content - The content to attach
156
151
  * @returns {Promise<this>} This session for chaining
157
152
  * @throws {WriterError} SESSION_COMMITTED if already committed
158
153
  */
@@ -168,7 +163,7 @@ export class PatchSession {
168
163
  * @param {string} from - Source node ID
169
164
  * @param {string} to - Target node ID
170
165
  * @param {string} label - Edge label/type
171
- * @param {Buffer|string} content - The content to attach
166
+ * @param {Uint8Array|string} content - The content to attach
172
167
  * @returns {Promise<this>} This session for chaining
173
168
  * @throws {WriterError} SESSION_COMMITTED if already committed
174
169
  */
@@ -36,16 +36,7 @@ export class Writer {
36
36
  /**
37
37
  * Creates a new Writer instance.
38
38
  *
39
- * @param {Object} options
40
- * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default & import('../../ports/CommitPort.js').default} options.persistence - Git adapter
41
- * @param {string} options.graphName - Graph namespace
42
- * @param {string} options.writerId - This writer's ID
43
- * @param {import('../crdt/VersionVector.js').VersionVector} options.versionVector - Current version vector
44
- * @param {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} options.getCurrentState - Async function returning the current materialized V5 state
45
- * @param {(result: {patch: Object, sha: string}) => void | Promise<void>} [options.onCommitSuccess] - Callback invoked after successful commit with { patch, sha }
46
- * @param {'reject'|'cascade'|'warn'} [options.onDeleteWithData='warn'] - Policy when deleting a node with attached data
47
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for CBOR serialization (defaults to domain-local codec)
48
- * @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger port
39
+ * @param {{ persistence: import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default & import('../../ports/CommitPort.js').default, graphName: string, writerId: string, versionVector: import('../crdt/VersionVector.js').VersionVector, getCurrentState: () => import('../services/JoinReducer.js').WarpStateV5 | null, onCommitSuccess?: (result: {patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}) => void | Promise<void>, onDeleteWithData?: 'reject'|'cascade'|'warn', codec?: import('../../ports/CodecPort.js').default, logger?: import('../../ports/LoggerPort.js').default }} options
49
40
  */
50
41
  constructor({ persistence, graphName, writerId, versionVector, getCurrentState, onCommitSuccess, onDeleteWithData = 'warn', codec, logger }) {
51
42
  validateWriterId(writerId);
@@ -62,10 +53,10 @@ export class Writer {
62
53
  /** @type {import('../crdt/VersionVector.js').VersionVector} */
63
54
  this._versionVector = versionVector;
64
55
 
65
- /** @type {Function} */
56
+ /** @type {() => import('../services/JoinReducer.js').WarpStateV5 | null} */
66
57
  this._getCurrentState = getCurrentState;
67
58
 
68
- /** @type {Function|undefined} */
59
+ /** @type {((result: {patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}) => void | Promise<void>)|undefined} */
69
60
  this._onCommitSuccess = onCommitSuccess;
70
61
 
71
62
  /** @type {'reject'|'cascade'|'warn'} */
@@ -14,8 +14,8 @@
14
14
  * become method names on the prototype. Duplicates across modules are
15
15
  * detected eagerly and throw at import time (not at call time).
16
16
  *
17
- * @param {Function} Class - The class constructor whose prototype to extend
18
- * @param {Array<Record<string, Function>>} methodModules - Array of method module namespace objects
17
+ * @param {{ prototype: object, name: string }} Class - The class constructor whose prototype to extend
18
+ * @param {Array<Record<string, unknown>>} methodModules - Array of method module namespace objects
19
19
  * @throws {Error} If a method name appears in more than one module
20
20
  */
21
21
  export function wireWarpMethods(Class, methodModules) {
@@ -179,13 +179,11 @@ declare module '../WarpGraph.js' {
179
179
  // ── provenance.methods.js ─────────────────────────────────────────────
180
180
  patchesFor(entityId: string): Promise<string[]>;
181
181
  materializeSlice(nodeId: string, options?: { receipts?: boolean }): Promise<{ state: WarpStateV5; patchCount: number; receipts?: TickReceipt[] }>;
182
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- internal method; `any` avoids breaking provenance.methods.js callers
183
- _computeBackwardCone(nodeId: string): Promise<Map<string, any>>;
184
- loadPatchBySha(sha: string): Promise<{ patch: PatchV2; sha: string }>;
185
- _loadPatchBySha(sha: string): Promise<{ patch: PatchV2; sha: string }>;
182
+ _computeBackwardCone(nodeId: string): Promise<Map<string, PatchV2>>;
183
+ loadPatchBySha(sha: string): Promise<PatchV2>;
184
+ _loadPatchBySha(sha: string): Promise<PatchV2>;
186
185
  _loadPatchesBySha(shas: string[]): Promise<Array<{ patch: PatchV2; sha: string }>>;
187
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- internal method; `any` avoids breaking provenance.methods.js callers
188
- _sortPatchesCausally(patches: Array<{ patch: any; sha: string }>): Array<{ patch: any; sha: string }>;
186
+ _sortPatchesCausally(patches: Array<{ patch: PatchV2; sha: string }>): Array<{ patch: PatchV2; sha: string }>;
189
187
 
190
188
  // ── fork.methods.js ───────────────────────────────────────────────────
191
189
  fork(options: { from: string; at: string; forkName?: string; forkWriterId?: string }): Promise<WarpGraph>;
@@ -251,7 +249,7 @@ declare module '../WarpGraph.js' {
251
249
  _buildView(state: WarpStateV5, stateHash: string, diff?: import('../types/PatchDiff.js').PatchDiff): void;
252
250
  _setMaterializedState(state: WarpStateV5, optionsOrDiff?: import('../types/PatchDiff.js').PatchDiff | { diff?: import('../types/PatchDiff.js').PatchDiff | null }): Promise<{ state: WarpStateV5; stateHash: string; adjacency: unknown }>;
253
251
  _materializeWithCeiling(ceiling: number, collectReceipts: boolean, t0: number): Promise<WarpStateV5 | { state: WarpStateV5; receipts: TickReceipt[] }>;
254
- _persistSeekCacheEntry(cacheKey: string, buf: Buffer, state: WarpStateV5): Promise<void>;
252
+ _persistSeekCacheEntry(cacheKey: string, buf: Uint8Array, state: WarpStateV5): Promise<void>;
255
253
  _restoreIndexFromCache(indexTreeOid: string): Promise<void>;
256
254
  materializeAt(checkpointSha: string): Promise<WarpStateV5>;
257
255
  verifyIndex(options?: { seed?: number; sampleRate?: number }): { passed: number; failed: number; errors: Array<{ nodeId: string; direction: string; expected: string[]; actual: string[] }> };
@@ -375,7 +375,7 @@ export function _maybeRunGC(state) {
375
375
  * **Requires a cached state.**
376
376
  *
377
377
  * @this {import('../WarpGraph.js').default}
378
- * @returns {{ran: boolean, result: Object|null, reasons: string[]}} GC result
378
+ * @returns {{ran: boolean, result: import('../services/GCPolicy.js').GCExecuteResult|null, reasons: string[]}} GC result
379
379
  *
380
380
  * @example
381
381
  * await graph.materialize();
@@ -31,11 +31,7 @@ import { createWormhole as createWormholeImpl } from '../services/WormholeServic
31
31
  * - History up to the fork point is shared (content-addressed dedup)
32
32
  *
33
33
  * @this {import('../WarpGraph.js').default}
34
- * @param {Object} options - Fork configuration
35
- * @param {string} options.from - Writer ID whose chain to fork from
36
- * @param {string} options.at - Patch SHA to fork at (must be in the writer's chain)
37
- * @param {string} [options.forkName] - Name for the forked graph. Defaults to `<graphName>-fork-<timestamp>`
38
- * @param {string} [options.forkWriterId] - Writer ID for the fork. Defaults to a new canonical ID.
34
+ * @param {{ from: string, at: string, forkName?: string, forkWriterId?: string }} options - Fork configuration
39
35
  * @returns {Promise<import('../WarpGraph.js').default>} A new WarpGraph instance for the fork
40
36
  * @throws {ForkError} If `from` writer does not exist (code: `E_FORK_WRITER_NOT_FOUND`)
41
37
  * @throws {ForkError} If `at` SHA does not exist (code: `E_FORK_PATCH_NOT_FOUND`)
@@ -351,7 +351,7 @@ export async function _materializeWithCeiling(ceiling, collectReceipts, t0) {
351
351
  cacheKey = buildSeekCacheKey(ceiling, frontier);
352
352
  }
353
353
  const buf = serializeFullStateV5(state, { codec: this._codec });
354
- this._persistSeekCacheEntry(cacheKey, /** @type {Buffer} */ (buf), state)
354
+ this._persistSeekCacheEntry(cacheKey, buf, state)
355
355
  .catch(() => {});
356
356
  }
357
357
 
@@ -377,7 +377,7 @@ export async function _materializeWithCeiling(ceiling, collectReceipts, t0) {
377
377
  *
378
378
  * @this {import('../WarpGraph.js').default}
379
379
  * @param {string} cacheKey - Seek cache key
380
- * @param {Buffer} buf - Serialized WarpStateV5 buffer
380
+ * @param {Uint8Array} buf - Serialized WarpStateV5 buffer
381
381
  * @param {import('../services/JoinReducer.js').WarpStateV5} state
382
382
  * @returns {Promise<void>}
383
383
  * @private
@@ -473,7 +473,7 @@ export async function materializeAt(checkpointSha) {
473
473
 
474
474
  const patchMeta = decodePatchMessage(message);
475
475
  const patchBuffer = await this._persistence.readBlob(patchMeta.patchOid);
476
- const patch = this._codec.decode(patchBuffer);
476
+ const patch = /** @type {import('../types/WarpTypesV2.js').PatchV2} */ (this._codec.decode(patchBuffer));
477
477
 
478
478
  patches.push({ patch, sha: currentSha });
479
479
 
@@ -287,9 +287,9 @@ export async function writer(writerId) {
287
287
  graphName: this._graphName,
288
288
  writerId: resolvedWriterId,
289
289
  versionVector: this._versionVector,
290
- getCurrentState: /** @type {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} */ (/** @type {unknown} */ (() => this._cachedState)),
290
+ getCurrentState: () => this._cachedState,
291
291
  onDeleteWithData: this._onDeleteWithData,
292
- onCommitSuccess: /** @type {(result: {patch: Object, sha: string}) => void} */ (/** @type {unknown} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ opts) => this._onPatchCommitted(resolvedWriterId, opts))),
292
+ onCommitSuccess: /** @type {(result: {patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}) => void} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ opts) => this._onPatchCommitted(resolvedWriterId, opts)),
293
293
  codec: this._codec,
294
294
  logger: this._logger || undefined,
295
295
  });
@@ -304,9 +304,7 @@ export async function writer(writerId) {
304
304
  *
305
305
  * @deprecated Use `writer()` to resolve a stable ID from git config, or `writer(id)` with an explicit ID.
306
306
  * @this {import('../WarpGraph.js').default}
307
- * @param {Object} [opts]
308
- * @param {'config'|'none'} [opts.persist='none'] - Whether to persist the new ID to git config
309
- * @param {string} [opts.alias] - Optional alias for config key (used with persist:'config')
307
+ * @param {{ persist?: 'config'|'none', alias?: string }} [opts]
310
308
  * @returns {Promise<Writer>} A Writer instance with new canonical ID
311
309
  * @throws {Error} If config operations fail (when persist:'config')
312
310
  *
@@ -346,9 +344,9 @@ export async function createWriter(opts = {}) {
346
344
  graphName: this._graphName,
347
345
  writerId: freshWriterId,
348
346
  versionVector: this._versionVector,
349
- getCurrentState: /** @type {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} */ (/** @type {unknown} */ (() => this._cachedState)),
347
+ getCurrentState: () => this._cachedState,
350
348
  onDeleteWithData: this._onDeleteWithData,
351
- onCommitSuccess: /** @type {(result: {patch: Object, sha: string}) => void} */ (/** @type {unknown} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ commitOpts) => this._onPatchCommitted(freshWriterId, commitOpts))),
349
+ onCommitSuccess: /** @type {(result: {patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}) => void} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ commitOpts) => this._onPatchCommitted(freshWriterId, commitOpts)),
352
350
  codec: this._codec,
353
351
  logger: this._logger || undefined,
354
352
  });
@@ -484,7 +482,7 @@ export async function discoverTicks() {
484
482
  *
485
483
  * @this {import('../WarpGraph.js').default}
486
484
  * @param {import('../services/JoinReducer.js').WarpStateV5} otherState - The state to merge in
487
- * @returns {{state: import('../services/JoinReducer.js').WarpStateV5, receipt: Object}} Merged state and receipt
485
+ * @returns {{state: import('../services/JoinReducer.js').WarpStateV5, receipt: {nodesAdded: number, nodesRemoved: number, edgesAdded: number, edgesRemoved: number, propsChanged: number, frontierMerged: boolean}}} Merged state and receipt
488
486
  * @throws {QueryError} If no cached state exists (code: `E_NO_STATE`)
489
487
  * @throws {Error} If otherState is invalid
490
488
  */
@@ -152,7 +152,7 @@ export async function materializeSlice(nodeId, options) {
152
152
  *
153
153
  * @this {import('../WarpGraph.js').default}
154
154
  * @param {string} nodeId - The target node ID
155
- * @returns {Promise<Map<string, Object>>} Map of patch SHA to loaded patch object
155
+ * @returns {Promise<Map<string, import('../types/WarpTypesV2.js').PatchV2>>} Map of patch SHA to loaded patch object
156
156
  */
157
157
  export async function _computeBackwardCone(nodeId) {
158
158
  if (!this._provenanceIndex) {
@@ -209,7 +209,7 @@ export async function _computeBackwardCone(nodeId) {
209
209
  *
210
210
  * @this {import('../WarpGraph.js').default}
211
211
  * @param {string} sha - The patch commit SHA
212
- * @returns {Promise<Object>} The decoded patch object
212
+ * @returns {Promise<import('../types/WarpTypesV2.js').PatchV2>} The decoded patch object
213
213
  * @throws {Error} If the commit is not a patch or loading fails
214
214
  */
215
215
  export async function loadPatchBySha(sha) {
@@ -221,7 +221,7 @@ export async function loadPatchBySha(sha) {
221
221
  *
222
222
  * @this {import('../WarpGraph.js').default}
223
223
  * @param {string} sha - The patch commit SHA
224
- * @returns {Promise<Object>} The decoded patch object
224
+ * @returns {Promise<import('../types/WarpTypesV2.js').PatchV2>} The decoded patch object
225
225
  * @throws {Error} If the commit is not a patch or loading fails
226
226
  */
227
227
  export async function _loadPatchBySha(sha) {
@@ -234,7 +234,7 @@ export async function _loadPatchBySha(sha) {
234
234
 
235
235
  const patchMeta = decodePatchMessage(nodeInfo.message);
236
236
  const patchBuffer = await this._persistence.readBlob(patchMeta.patchOid);
237
- return /** @type {Object} */ (this._codec.decode(patchBuffer));
237
+ return /** @type {import('../types/WarpTypesV2.js').PatchV2} */ (this._codec.decode(patchBuffer));
238
238
  }
239
239
 
240
240
  /**
@@ -242,7 +242,7 @@ export async function _loadPatchBySha(sha) {
242
242
  *
243
243
  * @this {import('../WarpGraph.js').default}
244
244
  * @param {string[]} shas - Array of patch commit SHAs
245
- * @returns {Promise<Array<{patch: Object, sha: string}>>} Array of patch entries
245
+ * @returns {Promise<Array<{patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}>>} Array of patch entries
246
246
  * @throws {Error} If any SHA is not a patch or loading fails
247
247
  */
248
248
  export async function _loadPatchesBySha(shas) {