@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
@@ -16,6 +16,8 @@
16
16
  import { orsetElements, orsetContains } from '../crdt/ORSet.js';
17
17
  import { decodeEdgeKey, decodePropKey, isEdgePropKey } from './KeyCodec.js';
18
18
 
19
+ /** @typedef {import('./JoinReducer.js').WarpStateV5} WarpStateV5 */
20
+
19
21
  /**
20
22
  * Tests whether a string matches a glob-style pattern.
21
23
  *
@@ -38,7 +40,7 @@ function matchGlob(pattern, str) {
38
40
  /**
39
41
  * Computes the set of property keys visible under an observer config.
40
42
  *
41
- * @param {Map<string, *>} allNodeProps - Map of propKey -> placeholder
43
+ * @param {Map<string, unknown>} allNodeProps - Map of propKey -> placeholder
42
44
  * @param {string[]|undefined} expose - Whitelist of property keys
43
45
  * @param {string[]|undefined} redact - Blacklist of property keys
44
46
  * @returns {Set<string>} Visible property keys
@@ -63,7 +65,7 @@ function visiblePropKeys(allNodeProps, expose, redact) {
63
65
  /**
64
66
  * Collects node property keys from state for a given node.
65
67
  *
66
- * @param {*} state - WarpStateV5 materialized state
68
+ * @param {WarpStateV5} state - WarpStateV5 materialized state
67
69
  * @param {string} nodeId - The node ID
68
70
  * @returns {Map<string, boolean>} Map of propKey -> true
69
71
  */
@@ -111,7 +113,7 @@ function countMissing(source, targetSet) {
111
113
  /**
112
114
  * Computes edge loss between two observer node sets.
113
115
  *
114
- * @param {*} state - WarpStateV5
116
+ * @param {WarpStateV5} state
115
117
  * @param {Set<string>} nodesASet - Nodes visible to A
116
118
  * @param {Set<string>} nodesBSet - Nodes visible to B
117
119
  * @returns {number} edgeLoss fraction
@@ -156,7 +158,7 @@ function countNodePropLoss(nodeProps, { configA, configB, nodeInB }) {
156
158
  /**
157
159
  * Computes property loss across all A-visible nodes.
158
160
  *
159
- * @param {*} state - WarpStateV5
161
+ * @param {WarpStateV5} state - WarpStateV5
160
162
  * @param {{ nodesA: string[], nodesBSet: Set<string>, configA: {expose?: string[], redact?: string[]}, configB: {expose?: string[], redact?: string[]} }} opts
161
163
  * @returns {number} propLoss fraction
162
164
  */
@@ -193,7 +195,7 @@ function computePropLoss(state, { nodesA, nodesBSet, configA, configB }) {
193
195
  * @param {string} configB.match - Glob pattern for visible nodes
194
196
  * @param {string[]} [configB.expose] - Property keys to include
195
197
  * @param {string[]} [configB.redact] - Property keys to exclude
196
- * @param {*} state - WarpStateV5 materialized state
198
+ * @param {WarpStateV5} state - WarpStateV5 materialized state
197
199
  * @returns {{ cost: number, breakdown: { nodeLoss: number, edgeLoss: number, propLoss: number } }}
198
200
  */
199
201
  export function computeTranslationCost(configA, configB, state) {
@@ -27,7 +27,7 @@ import { detectMessageKind, decodePatchMessage } from './WarpMessageCodec.js';
27
27
 
28
28
  /**
29
29
  * Validates that a SHA parameter is a non-empty string.
30
- * @param {*} sha - The SHA to validate
30
+ * @param {unknown} sha - The SHA to validate
31
31
  * @param {string} paramName - Parameter name for error messages
32
32
  * @throws {WormholeError} If SHA is invalid
33
33
  * @private
@@ -321,7 +321,7 @@ export function deserializeWormhole(json) {
321
321
  });
322
322
  }
323
323
 
324
- const /** @type {Record<string, *>} */ typedJson = /** @type {Record<string, *>} */ (json);
324
+ const /** @type {Record<string, unknown>} */ typedJson = /** @type {Record<string, unknown>} */ (json);
325
325
  const requiredFields = ['fromSha', 'toSha', 'writerId', 'patchCount', 'payload'];
326
326
  for (const field of requiredFields) {
327
327
  if (typedJson[field] === undefined) {
@@ -332,6 +332,15 @@ export function deserializeWormhole(json) {
332
332
  }
333
333
  }
334
334
 
335
+ for (const field of ['fromSha', 'toSha', 'writerId']) {
336
+ if (typeof typedJson[field] !== 'string') {
337
+ throw new WormholeError(`Invalid wormhole JSON: '${field}' must be a string`, {
338
+ code: 'E_INVALID_WORMHOLE_JSON',
339
+ context: { [field]: typedJson[field] },
340
+ });
341
+ }
342
+ }
343
+
335
344
  if (typeof typedJson.patchCount !== 'number' || typedJson.patchCount < 0) {
336
345
  throw new WormholeError('Invalid wormhole JSON: patchCount must be a non-negative number', {
337
346
  code: 'E_INVALID_WORMHOLE_JSON',
@@ -340,11 +349,11 @@ export function deserializeWormhole(json) {
340
349
  }
341
350
 
342
351
  return {
343
- fromSha: typedJson.fromSha,
344
- toSha: typedJson.toSha,
345
- writerId: typedJson.writerId,
346
- patchCount: typedJson.patchCount,
347
- payload: ProvenancePayload.fromJSON(typedJson.payload),
352
+ fromSha: /** @type {string} */ (typedJson.fromSha),
353
+ toSha: /** @type {string} */ (typedJson.toSha),
354
+ writerId: /** @type {string} */ (typedJson.writerId),
355
+ patchCount: /** @type {number} */ (typedJson.patchCount),
356
+ payload: ProvenancePayload.fromJSON(/** @type {import('./ProvenancePayload.js').PatchEntry[]} */ (typedJson.payload)),
348
357
  };
349
358
  }
350
359
 
@@ -14,7 +14,7 @@ import { recordIdPayload, signaturePayload } from './canonical.js';
14
14
  /**
15
15
  * Computes the record ID (SHA-256 hex digest) for a trust record.
16
16
  *
17
- * @param {Record<string, *>} record - Full trust record
17
+ * @param {Record<string, unknown>} record - Full trust record
18
18
  * @returns {string} 64-character lowercase hex string
19
19
  */
20
20
  export function computeRecordId(record) {
@@ -24,7 +24,7 @@ export function computeRecordId(record) {
24
24
  /**
25
25
  * Computes the signature payload as a Buffer (UTF-8 bytes).
26
26
  *
27
- * @param {Record<string, *>} record - Full trust record (signature will be stripped)
27
+ * @param {Record<string, unknown>} record - Full trust record (signature will be stripped)
28
28
  * @returns {Buffer} UTF-8 encoded bytes of the domain-separated canonical string
29
29
  */
30
30
  export function computeSignaturePayload(record) {
@@ -34,7 +34,7 @@ export function computeSignaturePayload(record) {
34
34
  /**
35
35
  * Verifies that a record's recordId matches its content.
36
36
  *
37
- * @param {Record<string, *>} record - Trust record with `recordId` field
37
+ * @param {Record<string, unknown>} record - Trust record with `recordId` field
38
38
  * @returns {boolean} true if recordId matches computed value
39
39
  */
40
40
  export function verifyRecordId(record) {
@@ -16,6 +16,21 @@ import { deriveTrustVerdict } from './verdict.js';
16
16
  * @typedef {import('./TrustStateBuilder.js').TrustState} TrustState
17
17
  */
18
18
 
19
+ /**
20
+ * @typedef {Object} TrustAssessment
21
+ * @property {number} trustSchemaVersion
22
+ * @property {string} mode
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
32
+ */
33
+
19
34
  /**
20
35
  * Evaluates trust status for a set of writers against the current trust state.
21
36
  *
@@ -25,8 +40,8 @@ import { deriveTrustVerdict } from './verdict.js';
25
40
  *
26
41
  * @param {string[]} writerIds - Writer IDs to evaluate
27
42
  * @param {TrustState} trustState - Built trust state from TrustStateBuilder
28
- * @param {Record<string, *>} policy - Trust policy configuration
29
- * @returns {Record<string, *>} Frozen TrustAssessment object
43
+ * @param {Record<string, unknown>} policy - Trust policy configuration
44
+ * @returns {TrustAssessment} Frozen TrustAssessment object
30
45
  */
31
46
  export function evaluateWriters(writerIds, trustState, policy) {
32
47
  const policyResult = TrustPolicySchema.safeParse(policy);
@@ -130,7 +145,7 @@ function evaluateSingleWriter(writerId, trustState) {
130
145
  *
131
146
  * @param {string[]} writerIds
132
147
  * @param {string} reasonCode
133
- * @returns {Record<string, *>}
148
+ * @returns {TrustAssessment}
134
149
  */
135
150
  function buildErrorAssessment(writerIds, reasonCode) {
136
151
  const sortedWriters = [...writerIds].sort();
@@ -22,8 +22,8 @@ import TrustError from '../errors/TrustError.js';
22
22
  export class TrustRecordService {
23
23
  /**
24
24
  * @param {Object} options
25
- * @param {*} options.persistence - GraphPersistencePort adapter
26
- * @param {*} options.codec - CodecPort adapter (CBOR)
25
+ * @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
26
+ * @param {import('../../ports/CodecPort.js').default} options.codec - CodecPort adapter (CBOR)
27
27
  */
28
28
  constructor({ persistence, codec }) {
29
29
  this._persistence = persistence;
@@ -46,7 +46,7 @@ export class TrustRecordService {
46
46
  * evaluation when the full key set is available.
47
47
  *
48
48
  * @param {string} graphName
49
- * @param {Record<string, *>} record - Complete signed trust record
49
+ * @param {Record<string, unknown>} record - Complete signed trust record
50
50
  * @param {AppendOptions} [options]
51
51
  * @returns {Promise<{commitSha: string, ref: string}>}
52
52
  */
@@ -95,7 +95,7 @@ export class TrustRecordService {
95
95
  * @param {string} graphName
96
96
  * @param {Object} [options]
97
97
  * @param {string} [options.tip] - Override tip commit (for pinned reads)
98
- * @returns {Promise<Array<Record<string, *>>>}
98
+ * @returns {Promise<Array<Record<string, unknown>>>}
99
99
  */
100
100
  async readRecords(graphName, options = {}) {
101
101
  const ref = buildTrustRecordRef(graphName);
@@ -117,13 +117,16 @@ export class TrustRecordService {
117
117
 
118
118
  while (current) {
119
119
  const info = await this._persistence.getNodeInfo(current);
120
- const record = this._codec.decode(
121
- await this._persistence.readBlob(
122
- (await this._persistence.readTreeOids(
123
- await this._persistence.getCommitTree(current),
124
- ))['record.cbor'],
125
- ),
120
+ const entries = await this._persistence.readTreeOids(
121
+ await this._persistence.getCommitTree(current),
126
122
  );
123
+ const blobOid = entries['record.cbor'];
124
+ if (!blobOid) {
125
+ break;
126
+ }
127
+ const record = /** @type {Record<string, unknown>} */ (this._codec.decode(
128
+ await this._persistence.readBlob(blobOid),
129
+ ));
127
130
 
128
131
  records.unshift(record);
129
132
 
@@ -145,7 +148,7 @@ export class TrustRecordService {
145
148
  * - Each record passes schema validation
146
149
  * - First record has prev=null
147
150
  *
148
- * @param {Array<Record<string, *>>} records - Records in chain order (oldest first)
151
+ * @param {Array<Record<string, unknown>>} records - Records in chain order (oldest first)
149
152
  * @returns {{valid: boolean, errors: Array<{index: number, error: string}>}}
150
153
  */
151
154
  verifyChain(records) {
@@ -177,7 +180,7 @@ export class TrustRecordService {
177
180
  // Prev-link check
178
181
  if (i === 0) {
179
182
  if (record.prev !== null) {
180
- errors.push({ index: i, error: `Genesis record must have prev=null, got ${record.prev}` });
183
+ errors.push({ index: i, error: `Genesis record must have prev=null, got ${JSON.stringify(record.prev)}` });
181
184
  }
182
185
  } else {
183
186
  const expectedPrev = records[i - 1].recordId;
@@ -200,12 +203,13 @@ export class TrustRecordService {
200
203
  * cryptographic verification — that requires the issuer's public key
201
204
  * from the trust state, which is resolved during evaluation.
202
205
  *
203
- * @param {Record<string, *>} record
206
+ * @param {Record<string, unknown>} record
204
207
  * @throws {TrustError} if signature envelope is missing or malformed
205
208
  * @private
206
209
  */
207
210
  _verifySignatureEnvelope(record) {
208
- if (!record.signature || !record.signature.sig || !record.signature.alg) {
211
+ const sig = /** @type {Record<string, unknown>|undefined} */ (record.signature);
212
+ if (!sig || !sig.sig || !sig.alg) {
209
213
  throw new TrustError(
210
214
  'Trust record missing or malformed signature',
211
215
  { code: 'E_TRUST_SIGNATURE_MISSING' },
@@ -237,14 +241,14 @@ export class TrustRecordService {
237
241
  return { tipSha, recordId: null };
238
242
  }
239
243
 
240
- const record = this._codec.decode(await this._persistence.readBlob(blobOid));
241
- return { tipSha, recordId: record.recordId ?? null };
244
+ const record = /** @type {Record<string, unknown>} */ (this._codec.decode(await this._persistence.readBlob(blobOid)));
245
+ return { tipSha, recordId: /** @type {string|null} */ (record.recordId) ?? null };
242
246
  }
243
247
 
244
248
  /**
245
249
  * Persists a trust record as a Git commit.
246
250
  * @param {string} ref
247
- * @param {Record<string, *>} record
251
+ * @param {Record<string, unknown>} record
248
252
  * @param {string|null} parentSha - Resolved tip SHA (null for genesis)
249
253
  * @returns {Promise<string>} commit SHA
250
254
  * @private
@@ -252,16 +256,19 @@ export class TrustRecordService {
252
256
  async _persistRecord(ref, record, parentSha) {
253
257
  // Encode record as CBOR blob
254
258
  const encoded = this._codec.encode(record);
255
- const blobOid = await this._persistence.writeBlob(encoded);
259
+ // Buffer.from() ensures Uint8Array from codec is accepted by writeBlob
260
+ const blobOid = await this._persistence.writeBlob(Buffer.from(encoded));
256
261
 
257
- // Create tree with single entry
258
- const treeOid = await this._persistence.writeTree({ 'record.cbor': blobOid });
262
+ // Create tree with single entry (mktree format)
263
+ const treeOid = await this._persistence.writeTree([`100644 blob ${blobOid}\trecord.cbor`]);
259
264
 
260
265
  const parents = parentSha ? [parentSha] : [];
261
- const message = `trust: ${record.recordType} ${record.recordId.slice(0, 12)}`;
266
+ const rType = typeof record.recordType === 'string' ? record.recordType : '';
267
+ const rId = typeof record.recordId === 'string' ? record.recordId.slice(0, 12) : '';
268
+ const message = `trust: ${rType} ${rId}`;
262
269
 
263
- const commitSha = await this._persistence.createCommit({
264
- tree: treeOid,
270
+ const commitSha = await this._persistence.commitNodeWithTree({
271
+ treeOid,
265
272
  parents,
266
273
  message,
267
274
  });
@@ -12,6 +12,19 @@
12
12
 
13
13
  import { TrustRecordSchema } from './schemas.js';
14
14
 
15
+ /**
16
+ * @typedef {Object} TrustRecord
17
+ * @property {string} recordType - Record type (KEY_ADD, KEY_REVOKE, WRITER_BIND_ADD, WRITER_BIND_REVOKE)
18
+ * @property {string} recordId - Content-addressed record identifier
19
+ * @property {Record<string, string>} subject - Subject fields (keyId, publicKey, writerId, reasonCode vary by type)
20
+ * @property {string} issuedAt - ISO timestamp
21
+ * @property {number} schemaVersion
22
+ * @property {string} issuerKeyId
23
+ * @property {string|null} prev
24
+ * @property {{alg: string, sig: string}} signature
25
+ * @property {Record<string, unknown>} [meta]
26
+ */
27
+
15
28
  /**
16
29
  * @typedef {Object} TrustState
17
30
  * @property {Map<string, {publicKey: string, addedAt: string}>} activeKeys - keyId → key info
@@ -30,7 +43,7 @@ import { TrustRecordSchema } from './schemas.js';
30
43
  * - Binding validity: WRITER_BIND_ADD requires the referenced key to be active
31
44
  * - Schema validation: each record is validated against TrustRecordSchema
32
45
  *
33
- * @param {Array<Record<string, *>>} records - Trust records in chain order
46
+ * @param {Array<Record<string, unknown>>} records - Trust records in chain order
34
47
  * @returns {TrustState} Frozen trust state
35
48
  */
36
49
  export function buildState(records) {
@@ -49,13 +62,13 @@ export function buildState(records) {
49
62
  const parsed = TrustRecordSchema.safeParse(record);
50
63
  if (!parsed.success) {
51
64
  errors.push({
52
- recordId: record.recordId ?? '(unknown)',
65
+ recordId: typeof record.recordId === 'string' ? record.recordId : '(unknown)',
53
66
  error: `Schema validation failed: ${parsed.error.message}`,
54
67
  });
55
68
  continue;
56
69
  }
57
70
 
58
- const rec = parsed.data;
71
+ const rec = /** @type {TrustRecord} */ (parsed.data);
59
72
  processRecord(rec, activeKeys, revokedKeys, writerBindings, revokedBindings, errors);
60
73
  }
61
74
 
@@ -63,7 +76,7 @@ export function buildState(records) {
63
76
  }
64
77
 
65
78
  /**
66
- * @param {*} rec
79
+ * @param {TrustRecord} rec
67
80
  * @param {Map<string, {publicKey: string, addedAt: string}>} activeKeys
68
81
  * @param {Map<string, {publicKey: string, revokedAt: string, reasonCode: string}>} revokedKeys
69
82
  * @param {Map<string, {keyId: string, boundAt: string}>} writerBindings
@@ -90,7 +103,7 @@ function processRecord(rec, activeKeys, revokedKeys, writerBindings, revokedBind
90
103
  }
91
104
 
92
105
  /**
93
- * @param {*} rec
106
+ * @param {TrustRecord} rec
94
107
  * @param {Map<string, {publicKey: string, addedAt: string}>} activeKeys
95
108
  * @param {Map<string, {publicKey: string, revokedAt: string, reasonCode: string}>} revokedKeys
96
109
  * @param {Array<{recordId: string, error: string}>} errors
@@ -118,7 +131,7 @@ function handleKeyAdd(rec, activeKeys, revokedKeys, errors) {
118
131
  }
119
132
 
120
133
  /**
121
- * @param {*} rec
134
+ * @param {TrustRecord} rec
122
135
  * @param {Map<string, {publicKey: string, addedAt: string}>} activeKeys
123
136
  * @param {Map<string, {publicKey: string, revokedAt: string, reasonCode: string}>} revokedKeys
124
137
  * @param {Array<{recordId: string, error: string}>} errors
@@ -152,7 +165,7 @@ function handleKeyRevoke(rec, activeKeys, revokedKeys, errors) {
152
165
  }
153
166
 
154
167
  /**
155
- * @param {*} rec
168
+ * @param {TrustRecord} rec
156
169
  * @param {Map<string, {publicKey: string, addedAt: string}>} activeKeys
157
170
  * @param {Map<string, {publicKey: string, revokedAt: string, reasonCode: string}>} revokedKeys
158
171
  * @param {Map<string, {keyId: string, boundAt: string}>} writerBindings
@@ -182,7 +195,7 @@ function handleBindAdd(rec, activeKeys, revokedKeys, writerBindings, errors) {
182
195
  }
183
196
 
184
197
  /**
185
- * @param {*} rec
198
+ * @param {TrustRecord} rec
186
199
  * @param {Map<string, {keyId: string, boundAt: string}>} writerBindings
187
200
  * @param {Map<string, {keyId: string, revokedAt: string, reasonCode: string}>} revokedBindings
188
201
  * @param {Array<{recordId: string, error: string}>} errors
@@ -24,8 +24,8 @@ export const TRUST_SIGN_DOMAIN = 'git-warp:trust-sign:v1\0';
24
24
  * Returns the record payload used for recordId computation.
25
25
  * Strips `recordId` and `signature` — these are derived, not inputs.
26
26
  *
27
- * @param {Record<string, *>} record
28
- * @returns {Record<string, *>}
27
+ * @param {Record<string, unknown>} record
28
+ * @returns {Record<string, unknown>}
29
29
  */
30
30
  export function unsignedRecordForId(record) {
31
31
  const out = { ...record };
@@ -38,8 +38,8 @@ export function unsignedRecordForId(record) {
38
38
  * Returns the record payload used for signature computation.
39
39
  * Strips `signature` only — `recordId` is included in signed payload.
40
40
  *
41
- * @param {Record<string, *>} record
42
- * @returns {Record<string, *>}
41
+ * @param {Record<string, unknown>} record
42
+ * @returns {Record<string, unknown>}
43
43
  */
44
44
  export function unsignedRecordForSignature(record) {
45
45
  const out = { ...record };
@@ -50,7 +50,7 @@ export function unsignedRecordForSignature(record) {
50
50
  /**
51
51
  * Computes the canonical string for recordId hashing.
52
52
  *
53
- * @param {Record<string, *>} record - Full record (recordId and signature will be stripped)
53
+ * @param {Record<string, unknown>} record - Full record (recordId and signature will be stripped)
54
54
  * @returns {string} Domain-separated canonical JSON string
55
55
  */
56
56
  export function recordIdPayload(record) {
@@ -60,7 +60,7 @@ export function recordIdPayload(record) {
60
60
  /**
61
61
  * Computes the canonical string for signature verification.
62
62
  *
63
- * @param {Record<string, *>} record - Full record (signature will be stripped)
63
+ * @param {Record<string, unknown>} record - Full record (signature will be stripped)
64
64
  * @returns {string} Domain-separated canonical JSON string
65
65
  */
66
66
  export function signaturePayload(record) {
@@ -67,7 +67,7 @@ function validateOp(op, index) {
67
67
  throw new Error(`ops[${index}] must be an object`);
68
68
  }
69
69
 
70
- const entry = /** @type {Record<string, *>} */ (op);
70
+ const entry = /** @type {Record<string, unknown>} */ (op);
71
71
  validateOpType(entry.op, index);
72
72
  validateOpTarget(entry.target, index);
73
73
  validateOpResult(entry.result, index);
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Error narrowing utilities for catch clauses.
3
+ *
4
+ * TypeScript catch variables are `unknown`. These helpers provide
5
+ * type-safe narrowing without wildcard casts.
6
+ *
7
+ * @module domain/types/WarpErrors
8
+ */
9
+
10
+ /**
11
+ * Narrows an unknown value to an Error instance.
12
+ * @param {unknown} err
13
+ * @returns {err is Error}
14
+ */
15
+ export function isError(err) {
16
+ return err instanceof Error;
17
+ }
18
+
19
+ /**
20
+ * Checks if an unknown value has a string `code` property.
21
+ * @param {unknown} err
22
+ * @returns {err is {code: string, message?: string, name?: string}}
23
+ */
24
+ export function hasErrorCode(err) {
25
+ return (
26
+ typeof err === 'object' &&
27
+ err !== null &&
28
+ 'code' in err &&
29
+ typeof (/** @type {Record<string, unknown>} */ (err)).code === 'string'
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Narrows an unknown value to an object with a string `message` property.
35
+ * @param {unknown} err
36
+ * @returns {err is {message: string}}
37
+ */
38
+ export function hasMessage(err) {
39
+ return (
40
+ typeof err === 'object' &&
41
+ err !== null &&
42
+ 'message' in err &&
43
+ typeof (/** @type {Record<string, unknown>} */ (err)).message === 'string'
44
+ );
45
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared options types for warp methods.
3
+ * @module domain/types/WarpOptions
4
+ */
5
+
6
+ /**
7
+ * @typedef {Object} ServeOptions
8
+ * @property {number} port
9
+ * @property {string} [host='127.0.0.1']
10
+ * @property {string} [path='/sync']
11
+ * @property {number} [maxRequestBytes=4194304]
12
+ * @property {import('../../ports/HttpServerPort.js').default} httpPort
13
+ * @property {{keys: Record<string, string>, mode?: 'enforce'|'log-only', crypto?: import('../../ports/CryptoPort.js').default, logger?: import('../../ports/LoggerPort.js').default, wallClockMs?: () => number}} [auth]
14
+ * @property {string[]} [allowedWriters]
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} MaterializeOptions
19
+ * @property {boolean} [receipts]
20
+ * @property {number|null} [ceiling]
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} PatchCommitEvent
25
+ * @property {unknown} [patch]
26
+ * @property {string} sha
27
+ */
28
+
29
+ export {};
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Role-specific persistence port types.
3
+ *
4
+ * Instead of casting to `any` when accessing persistence methods,
5
+ * use these narrow types to document which port methods are actually needed.
6
+ *
7
+ * NOTE: CommitPort, BlobPort, TreePort, and RefPort each contain both
8
+ * read and write methods. True read/write separation would require
9
+ * splitting each port, which is deferred. For now, the role-named
10
+ * aliases below are identical — they exist to document *intent* at
11
+ * each call site, not to enforce access restrictions.
12
+ *
13
+ * @module domain/types/WarpPersistence
14
+ */
15
+
16
+ /**
17
+ * Full persistence port — commit + blob + tree + ref + config.
18
+ * @typedef {import('../../ports/GraphPersistencePort.js').default} WarpPersistence
19
+ */
20
+
21
+ /**
22
+ * Commit + blob + tree + ref (no config).
23
+ * Used by sync readers, checkpoint creators, patch writers, and
24
+ * materialize paths. Identical to CheckpointPersistence by design
25
+ * (see module-level note).
26
+ * @typedef {import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default & import('../../ports/RefPort.js').default} CorePersistence
27
+ */
28
+
29
+ /**
30
+ * Ref-only persistence — ref reads, writes, CAS, listing.
31
+ * @typedef {import('../../ports/RefPort.js').default} RefPersistence
32
+ */
33
+
34
+ /**
35
+ * Index storage — blob reads/writes, tree reads/writes, ref reads/writes.
36
+ * Matches the dynamically-composed IndexStoragePort interface.
37
+ * @typedef {import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default & import('../../ports/RefPort.js').default} IndexStorage
38
+ */
39
+
40
+ // Export nothing at runtime — types only
41
+ export {};
@@ -35,7 +35,7 @@
35
35
  * Inline value reference - value stored directly in the operation
36
36
  * @typedef {Object} ValueRefInline
37
37
  * @property {'inline'} type - Discriminator for inline values
38
- * @property {*} value - The actual value (any JSON-serializable type)
38
+ * @property {unknown} value - The actual value (any JSON-serializable type)
39
39
  */
40
40
 
41
41
  /**
@@ -70,7 +70,7 @@
70
70
 
71
71
  /**
72
72
  * Creates an inline value reference
73
- * @param {*} value - The value to store inline
73
+ * @param {unknown} value - The value to store inline
74
74
  * @returns {ValueRefInline} Inline value reference
75
75
  */
76
76
  export function createInlineValue(value) {
@@ -80,7 +80,7 @@
80
80
  * @property {'PropSet'} type - Operation type discriminator
81
81
  * @property {NodeId} node - Node ID to set property on
82
82
  * @property {string} key - Property key
83
- * @property {*} value - Property value (any JSON-serializable type)
83
+ * @property {unknown} value - Property value (any JSON-serializable type)
84
84
  */
85
85
 
86
86
  /**
@@ -156,7 +156,7 @@ export function createEdgeRemoveV2(from, to, label, observedDots) {
156
156
  * Creates a PropSet operation (no dot - uses EventId)
157
157
  * @param {NodeId} node - Node ID to set property on
158
158
  * @param {string} key - Property key
159
- * @param {*} value - Property value (any JSON-serializable type)
159
+ * @param {unknown} value - Property value (any JSON-serializable type)
160
160
  * @returns {OpV2PropSet} PropSet operation
161
161
  */
162
162
  export function createPropSetV2(node, key, value) {
@@ -3,20 +3,21 @@
3
3
  * Items with lowest priority are extracted first.
4
4
  *
5
5
  * @class MinHeap
6
+ * @template T
6
7
  */
7
8
  class MinHeap {
8
9
  /**
9
10
  * Creates an empty MinHeap.
10
11
  */
11
12
  constructor() {
12
- /** @type {Array<{item: *, priority: number}>} */
13
+ /** @type {Array<{item: T, priority: number}>} */
13
14
  this.heap = [];
14
15
  }
15
16
 
16
17
  /**
17
18
  * Insert an item with given priority.
18
19
  *
19
- * @param {*} item - The item to insert
20
+ * @param {T} item - The item to insert
20
21
  * @param {number} priority - Priority value (lower = higher priority)
21
22
  * @returns {void}
22
23
  */
@@ -28,14 +29,14 @@ class MinHeap {
28
29
  /**
29
30
  * Extract and return the item with minimum priority.
30
31
  *
31
- * @returns {*} The item with lowest priority, or undefined if empty
32
+ * @returns {T | undefined} The item with lowest priority, or undefined if empty
32
33
  */
33
34
  extractMin() {
34
35
  if (this.heap.length === 0) { return undefined; }
35
- if (this.heap.length === 1) { return /** @type {{item: *, priority: number}} */ (this.heap.pop()).item; }
36
+ if (this.heap.length === 1) { return /** @type {{item: T, priority: number}} */ (this.heap.pop()).item; }
36
37
 
37
38
  const min = this.heap[0];
38
- this.heap[0] = /** @type {{item: *, priority: number}} */ (this.heap.pop());
39
+ this.heap[0] = /** @type {{item: T, priority: number}} */ (this.heap.pop());
39
40
  this._bubbleDown(0);
40
41
  return min.item;
41
42
  }
@@ -7,7 +7,7 @@
7
7
  * - Array elements that are undefined/function/symbol become "null"
8
8
  * - Object properties with undefined/function/symbol values are omitted
9
9
  *
10
- * @param {*} value - Any JSON-serializable value
10
+ * @param {unknown} value - Any JSON-serializable value
11
11
  * @returns {string} Canonical JSON string with sorted keys
12
12
  */
13
13
  export function canonicalStringify(value) {
@@ -28,14 +28,15 @@ export function canonicalStringify(value) {
28
28
  return `[${elements.join(',')}]`;
29
29
  }
30
30
  if (typeof value === 'object') {
31
+ const obj = /** @type {Record<string, unknown>} */ (value);
31
32
  // Filter out keys with undefined/function/symbol values, then sort
32
- const keys = Object.keys(value)
33
+ const keys = Object.keys(obj)
33
34
  .filter(k => {
34
- const v = value[k];
35
+ const v = obj[k];
35
36
  return v !== undefined && typeof v !== 'function' && typeof v !== 'symbol';
36
37
  })
37
38
  .sort();
38
- const pairs = keys.map(k => `${JSON.stringify(k)}:${canonicalStringify(value[k])}`);
39
+ const pairs = keys.map(k => `${JSON.stringify(k)}:${canonicalStringify(obj[k])}`);
39
40
  return `{${pairs.join(',')}}`;
40
41
  }
41
42
  return JSON.stringify(value);