@git-stunts/git-warp 10.8.0 → 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 (136) hide show
  1. package/README.md +53 -32
  2. package/SECURITY.md +64 -0
  3. package/bin/cli/commands/check.js +168 -0
  4. package/bin/cli/commands/doctor/checks.js +422 -0
  5. package/bin/cli/commands/doctor/codes.js +46 -0
  6. package/bin/cli/commands/doctor/index.js +239 -0
  7. package/bin/cli/commands/doctor/types.js +89 -0
  8. package/bin/cli/commands/history.js +80 -0
  9. package/bin/cli/commands/info.js +139 -0
  10. package/bin/cli/commands/install-hooks.js +128 -0
  11. package/bin/cli/commands/materialize.js +99 -0
  12. package/bin/cli/commands/patch.js +142 -0
  13. package/bin/cli/commands/path.js +88 -0
  14. package/bin/cli/commands/query.js +235 -0
  15. package/bin/cli/commands/registry.js +32 -0
  16. package/bin/cli/commands/seek.js +598 -0
  17. package/bin/cli/commands/tree.js +230 -0
  18. package/bin/cli/commands/trust.js +154 -0
  19. package/bin/cli/commands/verify-audit.js +114 -0
  20. package/bin/cli/commands/view.js +46 -0
  21. package/bin/cli/infrastructure.js +350 -0
  22. package/bin/cli/schemas.js +177 -0
  23. package/bin/cli/shared.js +244 -0
  24. package/bin/cli/types.js +96 -0
  25. package/bin/presenters/index.js +41 -9
  26. package/bin/presenters/json.js +14 -12
  27. package/bin/presenters/text.js +286 -28
  28. package/bin/warp-graph.js +5 -2346
  29. package/index.d.ts +111 -21
  30. package/index.js +2 -0
  31. package/package.json +10 -8
  32. package/src/domain/WarpGraph.js +109 -3252
  33. package/src/domain/crdt/ORSet.js +8 -8
  34. package/src/domain/errors/EmptyMessageError.js +2 -2
  35. package/src/domain/errors/ForkError.js +1 -1
  36. package/src/domain/errors/IndexError.js +1 -1
  37. package/src/domain/errors/OperationAbortedError.js +1 -1
  38. package/src/domain/errors/QueryError.js +3 -3
  39. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  40. package/src/domain/errors/ShardCorruptionError.js +2 -2
  41. package/src/domain/errors/ShardLoadError.js +2 -2
  42. package/src/domain/errors/ShardValidationError.js +4 -4
  43. package/src/domain/errors/StorageError.js +2 -2
  44. package/src/domain/errors/SyncError.js +1 -1
  45. package/src/domain/errors/TraversalError.js +1 -1
  46. package/src/domain/errors/TrustError.js +29 -0
  47. package/src/domain/errors/WarpError.js +2 -2
  48. package/src/domain/errors/WormholeError.js +1 -1
  49. package/src/domain/errors/index.js +1 -0
  50. package/src/domain/services/AuditMessageCodec.js +137 -0
  51. package/src/domain/services/AuditReceiptService.js +471 -0
  52. package/src/domain/services/AuditVerifierService.js +707 -0
  53. package/src/domain/services/BitmapIndexBuilder.js +3 -3
  54. package/src/domain/services/BitmapIndexReader.js +28 -19
  55. package/src/domain/services/BoundaryTransitionRecord.js +18 -17
  56. package/src/domain/services/CheckpointSerializerV5.js +17 -16
  57. package/src/domain/services/CheckpointService.js +2 -2
  58. package/src/domain/services/CommitDagTraversalService.js +13 -13
  59. package/src/domain/services/DagPathFinding.js +7 -7
  60. package/src/domain/services/DagTopology.js +1 -1
  61. package/src/domain/services/DagTraversal.js +1 -1
  62. package/src/domain/services/HealthCheckService.js +1 -1
  63. package/src/domain/services/HookInstaller.js +1 -1
  64. package/src/domain/services/HttpSyncServer.js +120 -55
  65. package/src/domain/services/IndexRebuildService.js +7 -7
  66. package/src/domain/services/IndexStalenessChecker.js +4 -3
  67. package/src/domain/services/JoinReducer.js +11 -11
  68. package/src/domain/services/LogicalTraversal.js +1 -1
  69. package/src/domain/services/MessageCodecInternal.js +4 -1
  70. package/src/domain/services/MessageSchemaDetector.js +2 -2
  71. package/src/domain/services/MigrationService.js +1 -1
  72. package/src/domain/services/ObserverView.js +8 -8
  73. package/src/domain/services/PatchBuilderV2.js +42 -26
  74. package/src/domain/services/ProvenanceIndex.js +1 -1
  75. package/src/domain/services/ProvenancePayload.js +1 -1
  76. package/src/domain/services/QueryBuilder.js +3 -3
  77. package/src/domain/services/StateDiff.js +14 -11
  78. package/src/domain/services/StateSerializerV5.js +2 -2
  79. package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
  80. package/src/domain/services/SyncAuthService.js +71 -4
  81. package/src/domain/services/SyncProtocol.js +25 -11
  82. package/src/domain/services/TemporalQuery.js +9 -6
  83. package/src/domain/services/TranslationCost.js +7 -5
  84. package/src/domain/services/WarpMessageCodec.js +4 -1
  85. package/src/domain/services/WormholeService.js +16 -7
  86. package/src/domain/trust/TrustCanonical.js +42 -0
  87. package/src/domain/trust/TrustCrypto.js +111 -0
  88. package/src/domain/trust/TrustEvaluator.js +195 -0
  89. package/src/domain/trust/TrustRecordService.js +281 -0
  90. package/src/domain/trust/TrustStateBuilder.js +222 -0
  91. package/src/domain/trust/canonical.js +68 -0
  92. package/src/domain/trust/reasonCodes.js +64 -0
  93. package/src/domain/trust/schemas.js +160 -0
  94. package/src/domain/trust/verdict.js +42 -0
  95. package/src/domain/types/TickReceipt.js +1 -1
  96. package/src/domain/types/WarpErrors.js +45 -0
  97. package/src/domain/types/WarpOptions.js +29 -0
  98. package/src/domain/types/WarpPersistence.js +41 -0
  99. package/src/domain/types/WarpTypes.js +2 -2
  100. package/src/domain/types/WarpTypesV2.js +2 -2
  101. package/src/domain/types/git-cas.d.ts +20 -0
  102. package/src/domain/utils/MinHeap.js +6 -5
  103. package/src/domain/utils/RefLayout.js +59 -0
  104. package/src/domain/utils/canonicalStringify.js +5 -4
  105. package/src/domain/utils/roaring.js +31 -5
  106. package/src/domain/warp/PatchSession.js +26 -17
  107. package/src/domain/warp/Writer.js +18 -3
  108. package/src/domain/warp/_internal.js +26 -0
  109. package/src/domain/warp/_wire.js +58 -0
  110. package/src/domain/warp/_wiredMethods.d.ts +254 -0
  111. package/src/domain/warp/checkpoint.methods.js +401 -0
  112. package/src/domain/warp/fork.methods.js +323 -0
  113. package/src/domain/warp/materialize.methods.js +238 -0
  114. package/src/domain/warp/materializeAdvanced.methods.js +350 -0
  115. package/src/domain/warp/patch.methods.js +554 -0
  116. package/src/domain/warp/provenance.methods.js +286 -0
  117. package/src/domain/warp/query.methods.js +280 -0
  118. package/src/domain/warp/subscribe.methods.js +272 -0
  119. package/src/domain/warp/sync.methods.js +554 -0
  120. package/src/globals.d.ts +64 -0
  121. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
  122. package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
  123. package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
  124. package/src/infrastructure/adapters/GitGraphAdapter.js +79 -11
  125. package/src/infrastructure/adapters/InMemoryGraphAdapter.js +36 -0
  126. package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
  127. package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
  128. package/src/ports/CommitPort.js +10 -0
  129. package/src/ports/RefPort.js +17 -0
  130. package/src/visualization/layouts/converters.js +2 -2
  131. package/src/visualization/layouts/elkAdapter.js +1 -1
  132. package/src/visualization/layouts/elkLayout.js +10 -7
  133. package/src/visualization/layouts/index.js +1 -1
  134. package/src/visualization/renderers/ascii/seek.js +16 -6
  135. package/src/visualization/renderers/svg/index.js +1 -1
  136. package/src/hooks/post-merge.sh +0 -60
@@ -91,7 +91,7 @@ import { vvContains } from './VersionVector.js';
91
91
  * An element is present if it has at least one non-tombstoned dot.
92
92
  *
93
93
  * @typedef {Object} ORSet
94
- * @property {Map<*, Set<string>>} entries - element -> dots that added it
94
+ * @property {Map<string, Set<string>>} entries - element -> dots that added it
95
95
  * @property {Set<string>} tombstones - global tombstones
96
96
  */
97
97
 
@@ -112,7 +112,7 @@ export function createORSet() {
112
112
  * Mutates the set.
113
113
  *
114
114
  * @param {ORSet} set - The ORSet to mutate
115
- * @param {*} element - The element to add
115
+ * @param {string} element - The element to add
116
116
  * @param {import('./Dot.js').Dot} dot - The dot representing this add operation
117
117
  */
118
118
  export function orsetAdd(set, element, dot) {
@@ -145,7 +145,7 @@ export function orsetRemove(set, observedDots) {
145
145
  * An element is present if it has at least one non-tombstoned dot.
146
146
  *
147
147
  * @param {ORSet} set - The ORSet to check
148
- * @param {*} element - The element to check
148
+ * @param {string} element - The element to check
149
149
  * @returns {boolean}
150
150
  */
151
151
  export function orsetContains(set, element) {
@@ -168,7 +168,7 @@ export function orsetContains(set, element) {
168
168
  * Only returns elements that have at least one non-tombstoned dot.
169
169
  *
170
170
  * @param {ORSet} set - The ORSet
171
- * @returns {Array<*>} Array of present elements
171
+ * @returns {string[]} Array of present elements
172
172
  */
173
173
  export function orsetElements(set) {
174
174
  const result = [];
@@ -186,7 +186,7 @@ export function orsetElements(set) {
186
186
  * Returns the non-tombstoned dots for an element.
187
187
  *
188
188
  * @param {ORSet} set - The ORSet
189
- * @param {*} element - The element
189
+ * @param {string} element - The element
190
190
  * @returns {Set<string>} Set of encoded dots that are not tombstoned
191
191
  */
192
192
  export function orsetGetDots(set, element) {
@@ -311,11 +311,11 @@ export function orsetCompact(set, includedVV) {
311
311
  * Tombstones are sorted.
312
312
  *
313
313
  * @param {ORSet} set
314
- * @returns {{entries: Array<[*, string[]]>, tombstones: string[]}}
314
+ * @returns {{entries: Array<[string, string[]]>, tombstones: string[]}}
315
315
  */
316
316
  export function orsetSerialize(set) {
317
317
  // Serialize entries: convert Map to array of [element, sortedDots]
318
- /** @type {Array<[any, string[]]>} */
318
+ /** @type {Array<[string, string[]]>} */
319
319
  const entriesArray = [];
320
320
  for (const [element, dots] of set.entries) {
321
321
  const sortedDots = [...dots].sort((a, b) => {
@@ -349,7 +349,7 @@ export function orsetSerialize(set) {
349
349
  /**
350
350
  * Deserializes a plain object back to an ORSet.
351
351
  *
352
- * @param {{entries?: Array<[*, string[]]>, tombstones?: string[]}} obj
352
+ * @param {{entries?: Array<[string, string[]]>, tombstones?: string[]}} obj
353
353
  * @returns {ORSet}
354
354
  */
355
355
  export function orsetDeserialize(obj) {
@@ -12,7 +12,7 @@ import IndexError from './IndexError.js';
12
12
  * @property {string} name - The error name ('EmptyMessageError')
13
13
  * @property {string} code - Error code ('EMPTY_MESSAGE')
14
14
  * @property {string} operation - The operation that failed due to empty message
15
- * @property {Record<string, *>} context - Serializable context object for debugging
15
+ * @property {Record<string, unknown>} context - Serializable context object for debugging
16
16
  *
17
17
  * @example
18
18
  * if (!message || message.trim() === '') {
@@ -27,7 +27,7 @@ export default class EmptyMessageError extends IndexError {
27
27
  * Creates a new EmptyMessageError.
28
28
  *
29
29
  * @param {string} message - Human-readable error message
30
- * @param {{ operation?: string, context?: Record<string, *> }} [options={}] - Error options
30
+ * @param {{ operation?: string, context?: Record<string, unknown> }} [options={}] - Error options
31
31
  */
32
32
  constructor(message, options = {}) {
33
33
  const context = {
@@ -26,7 +26,7 @@ import WarpError from './WarpError.js';
26
26
  export default class ForkError extends WarpError {
27
27
  /**
28
28
  * @param {string} message
29
- * @param {{ code?: string, context?: Object }} [options={}]
29
+ * @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
30
30
  */
31
31
  constructor(message, options = {}) {
32
32
  super(message, 'FORK_ERROR', options);
@@ -19,7 +19,7 @@ import WarpError from './WarpError.js';
19
19
  export default class IndexError extends WarpError {
20
20
  /**
21
21
  * @param {string} message
22
- * @param {{ code?: string, context?: Object }} [options={}]
22
+ * @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
23
23
  */
24
24
  constructor(message, options = {}) {
25
25
  super(message, 'INDEX_ERROR', options);
@@ -15,7 +15,7 @@ import WarpError from './WarpError.js';
15
15
  export default class OperationAbortedError extends WarpError {
16
16
  /**
17
17
  * @param {string} operation
18
- * @param {{ code?: string, context?: Object, reason?: string }} [options={}]
18
+ * @param {{ code?: string, context?: Record<string, unknown>, reason?: string }} [options={}]
19
19
  */
20
20
  constructor(operation, options = {}) {
21
21
  const reason = options.reason || 'Operation was aborted';
@@ -11,8 +11,8 @@ import WarpError from './WarpError.js';
11
11
  *
12
12
  * | Code | Description |
13
13
  * |------|-------------|
14
- * | `E_NO_STATE` | No cached state available; call `materialize()` first |
15
- * | `E_STALE_STATE` | Cached state is outdated; call `materialize()` to refresh |
14
+ * | `E_NO_STATE` | No materialized state available; call `materialize()` or use `autoMaterialize: true` |
15
+ * | `E_STALE_STATE` | State is stale; call `materialize()` to refresh |
16
16
  * | `E_QUERY_MATCH_TYPE` | Invalid type passed to `match()` (expected string) |
17
17
  * | `E_QUERY_WHERE_TYPE` | Invalid type passed to `where()` (expected function or object) |
18
18
  * | `E_QUERY_WHERE_VALUE` | Non-primitive value in where() object shorthand |
@@ -35,7 +35,7 @@ import WarpError from './WarpError.js';
35
35
  export default class QueryError extends WarpError {
36
36
  /**
37
37
  * @param {string} message
38
- * @param {{ code?: string, context?: Object }} [options={}]
38
+ * @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
39
39
  */
40
40
  constructor(message, options = {}) {
41
41
  super(message, 'QUERY_ERROR', options);
@@ -13,7 +13,7 @@ import WarpError from './WarpError.js';
13
13
  export default class SchemaUnsupportedError extends WarpError {
14
14
  /**
15
15
  * @param {string} message
16
- * @param {{ code?: string, context?: Object }} [options={}]
16
+ * @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
17
17
  */
18
18
  constructor(message, options = {}) {
19
19
  super(message, 'E_SCHEMA_UNSUPPORTED', options);
@@ -14,7 +14,7 @@ import IndexError from './IndexError.js';
14
14
  * @property {string} shardPath - Path to the corrupted shard file
15
15
  * @property {string} oid - Object ID associated with the shard
16
16
  * @property {string} reason - Reason for corruption (e.g., 'invalid_checksum', 'invalid_version', 'parse_error')
17
- * @property {Record<string, *>} context - Serializable context object for debugging
17
+ * @property {Record<string, unknown>} context - Serializable context object for debugging
18
18
  *
19
19
  * @example
20
20
  * if (!validateChecksum(data)) {
@@ -30,7 +30,7 @@ export default class ShardCorruptionError extends IndexError {
30
30
  * Creates a new ShardCorruptionError.
31
31
  *
32
32
  * @param {string} message - Human-readable error message
33
- * @param {{ shardPath?: string, oid?: string, reason?: string, context?: Record<string, *> }} [options={}] - Error options
33
+ * @param {{ shardPath?: string, oid?: string, reason?: string, context?: Record<string, unknown> }} [options={}] - Error options
34
34
  */
35
35
  constructor(message, options = {}) {
36
36
  const context = {
@@ -14,7 +14,7 @@ import IndexError from './IndexError.js';
14
14
  * @property {string} shardPath - Path to the shard file that failed to load
15
15
  * @property {string} oid - Object ID associated with the shard
16
16
  * @property {Error} cause - The original error that caused the load failure
17
- * @property {Record<string, *>} context - Serializable context object for debugging
17
+ * @property {Record<string, unknown>} context - Serializable context object for debugging
18
18
  *
19
19
  * @example
20
20
  * try {
@@ -32,7 +32,7 @@ export default class ShardLoadError extends IndexError {
32
32
  * Creates a new ShardLoadError.
33
33
  *
34
34
  * @param {string} message - Human-readable error message
35
- * @param {{ shardPath?: string, oid?: string, cause?: Error, context?: Record<string, *> }} [options={}] - Error options
35
+ * @param {{ shardPath?: string, oid?: string, cause?: Error, context?: Record<string, unknown> }} [options={}] - Error options
36
36
  */
37
37
  constructor(message, options = {}) {
38
38
  const context = {
@@ -12,10 +12,10 @@ import IndexError from './IndexError.js';
12
12
  * @property {string} name - The error name ('ShardValidationError')
13
13
  * @property {string} code - Error code ('SHARD_VALIDATION_ERROR')
14
14
  * @property {string} shardPath - Path to the shard file that failed validation
15
- * @property {*} expected - The expected value for the field
16
- * @property {*} actual - The actual value found in the shard
15
+ * @property {unknown} expected - The expected value for the field
16
+ * @property {unknown} actual - The actual value found in the shard
17
17
  * @property {string} field - The field that failed validation (e.g., 'checksum', 'version')
18
- * @property {Record<string, *>} context - Serializable context object for debugging
18
+ * @property {Record<string, unknown>} context - Serializable context object for debugging
19
19
  *
20
20
  * @example
21
21
  * if (shard.version !== EXPECTED_VERSION) {
@@ -32,7 +32,7 @@ export default class ShardValidationError extends IndexError {
32
32
  * Creates a new ShardValidationError.
33
33
  *
34
34
  * @param {string} message - Human-readable error message
35
- * @param {{ shardPath?: string, expected?: *, actual?: *, field?: string, context?: Record<string, *> }} [options={}] - Error options
35
+ * @param {{ shardPath?: string, expected?: unknown, actual?: unknown, field?: string, context?: Record<string, unknown> }} [options={}] - Error options
36
36
  */
37
37
  constructor(message, options = {}) {
38
38
  const context = {
@@ -14,7 +14,7 @@ import IndexError from './IndexError.js';
14
14
  * @property {string} operation - The operation that failed ('read' or 'write')
15
15
  * @property {string} oid - Object ID associated with the operation
16
16
  * @property {Error} cause - The original error that caused the failure
17
- * @property {Record<string, *>} context - Serializable context object for debugging
17
+ * @property {Record<string, unknown>} context - Serializable context object for debugging
18
18
  *
19
19
  * @example
20
20
  * try {
@@ -32,7 +32,7 @@ export default class StorageError extends IndexError {
32
32
  * Creates a new StorageError.
33
33
  *
34
34
  * @param {string} message - Human-readable error message
35
- * @param {{ operation?: string, oid?: string, cause?: Error, context?: Record<string, *> }} [options={}] - Error options
35
+ * @param {{ operation?: string, oid?: string, cause?: Error, context?: Record<string, unknown> }} [options={}] - Error options
36
36
  */
37
37
  constructor(message, options = {}) {
38
38
  const context = {
@@ -26,7 +26,7 @@ import WarpError from './WarpError.js';
26
26
  export default class SyncError extends WarpError {
27
27
  /**
28
28
  * @param {string} message
29
- * @param {{ code?: string, context?: Object }} [options={}]
29
+ * @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
30
30
  */
31
31
  constructor(message, options = {}) {
32
32
  super(message, 'SYNC_ERROR', options);
@@ -19,7 +19,7 @@ import WarpError from './WarpError.js';
19
19
  export default class TraversalError extends WarpError {
20
20
  /**
21
21
  * @param {string} message
22
- * @param {{ code?: string, context?: Object }} [options={}]
22
+ * @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
23
23
  */
24
24
  constructor(message, options = {}) {
25
25
  super(message, 'TRAVERSAL_ERROR', options);
@@ -0,0 +1,29 @@
1
+ import WarpError from './WarpError.js';
2
+
3
+ /**
4
+ * Error class for trust operations.
5
+ *
6
+ * ## Error Codes
7
+ *
8
+ * | Code | Description |
9
+ * |------|-------------|
10
+ * | `E_TRUST_UNSUPPORTED_ALGORITHM` | Algorithm is not `ed25519` |
11
+ * | `E_TRUST_INVALID_KEY` | Public key is malformed (wrong length or bad base64) |
12
+ * | `TRUST_ERROR` | Generic/default trust error |
13
+ *
14
+ * @class TrustError
15
+ * @extends WarpError
16
+ *
17
+ * @property {string} name - Always 'TrustError' for instanceof checks
18
+ * @property {string} code - Machine-readable error code for programmatic handling
19
+ * @property {Object} context - Serializable context object with error details
20
+ */
21
+ export default class TrustError extends WarpError {
22
+ /**
23
+ * @param {string} message
24
+ * @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
25
+ */
26
+ constructor(message, options = {}) {
27
+ super(message, 'TRUST_ERROR', options);
28
+ }
29
+ }
@@ -10,13 +10,13 @@
10
10
  *
11
11
  * @property {string} name - Error name (set from constructor.name)
12
12
  * @property {string} code - Machine-readable error code
13
- * @property {Record<string, *>} context - Serializable context for debugging
13
+ * @property {Record<string, unknown>} context - Serializable context for debugging
14
14
  */
15
15
  export default class WarpError extends Error {
16
16
  /**
17
17
  * @param {string} message - Human-readable error message
18
18
  * @param {string} defaultCode - Default error code if not overridden by options
19
- * @param {{ code?: string, context?: Record<string, *> }} [options={}] - Error options
19
+ * @param {{ code?: string, context?: Record<string, unknown> }} [options={}] - Error options
20
20
  */
21
21
  constructor(message, defaultCode, options = {}) {
22
22
  super(message);
@@ -24,7 +24,7 @@ import WarpError from './WarpError.js';
24
24
  export default class WormholeError extends WarpError {
25
25
  /**
26
26
  * @param {string} message
27
- * @param {{ code?: string, context?: Object }} [options={}]
27
+ * @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
28
28
  */
29
29
  constructor(message, options = {}) {
30
30
  super(message, 'WORMHOLE_ERROR', options);
@@ -17,5 +17,6 @@ export { default as ShardValidationError } from './ShardValidationError.js';
17
17
  export { default as StorageError } from './StorageError.js';
18
18
  export { default as SchemaUnsupportedError } from './SchemaUnsupportedError.js';
19
19
  export { default as TraversalError } from './TraversalError.js';
20
+ export { default as TrustError } from './TrustError.js';
20
21
  export { default as WriterError } from './WriterError.js';
21
22
  export { default as WormholeError } from './WormholeError.js';
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Audit message encoding and decoding for WARP audit commit messages.
3
+ *
4
+ * Handles the 'audit' message type which records the outcome of materializing
5
+ * a data commit. See {@link module:domain/services/WarpMessageCodec} for the
6
+ * facade that re-exports all codec functions.
7
+ *
8
+ * @module domain/services/AuditMessageCodec
9
+ */
10
+
11
+ import { validateGraphName, validateWriterId } from '../utils/RefLayout.js';
12
+ import {
13
+ getCodec,
14
+ MESSAGE_TITLES,
15
+ TRAILER_KEYS,
16
+ validateOid,
17
+ validateSha256,
18
+ } from './MessageCodecInternal.js';
19
+
20
+ // -----------------------------------------------------------------------------
21
+ // Encoder
22
+ // -----------------------------------------------------------------------------
23
+
24
+ /**
25
+ * Encodes an audit commit message with trailers.
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
32
+ * @returns {string} The encoded commit message
33
+ * @throws {Error} If any validation fails
34
+ */
35
+ export function encodeAuditMessage({ graph, writer, dataCommit, opsDigest }) {
36
+ validateGraphName(graph);
37
+ validateWriterId(writer);
38
+ validateOid(dataCommit, 'dataCommit');
39
+ validateSha256(opsDigest, 'opsDigest');
40
+
41
+ const codec = getCodec();
42
+ return codec.encode({
43
+ title: MESSAGE_TITLES.audit,
44
+ trailers: {
45
+ [TRAILER_KEYS.dataCommit]: dataCommit,
46
+ [TRAILER_KEYS.graph]: graph,
47
+ [TRAILER_KEYS.kind]: 'audit',
48
+ [TRAILER_KEYS.opsDigest]: opsDigest,
49
+ [TRAILER_KEYS.schema]: '1',
50
+ [TRAILER_KEYS.writer]: writer,
51
+ },
52
+ });
53
+ }
54
+
55
+ // -----------------------------------------------------------------------------
56
+ // Decoder
57
+ // -----------------------------------------------------------------------------
58
+
59
+ /**
60
+ * Decodes an audit commit message.
61
+ *
62
+ * @param {string} message - The raw commit message
63
+ * @returns {{ kind: 'audit', graph: string, writer: string, dataCommit: string, opsDigest: string, schema: number }}
64
+ * @throws {Error} If the message is not a valid audit message
65
+ */
66
+ export function decodeAuditMessage(message) {
67
+ const codec = getCodec();
68
+ const decoded = codec.decode(message);
69
+ const { trailers } = decoded;
70
+
71
+ // Check for duplicate trailers (strict decode)
72
+ const keys = Object.keys(trailers);
73
+ const seen = new Set();
74
+ for (const key of keys) {
75
+ if (seen.has(key)) {
76
+ throw new Error(`Duplicate trailer rejected: ${key}`);
77
+ }
78
+ seen.add(key);
79
+ }
80
+
81
+ // Validate kind discriminator
82
+ const kind = trailers[TRAILER_KEYS.kind];
83
+ if (kind !== 'audit') {
84
+ throw new Error(`Invalid audit message: eg-kind must be 'audit', got '${kind}'`);
85
+ }
86
+
87
+ // Extract and validate required fields
88
+ const graph = trailers[TRAILER_KEYS.graph];
89
+ if (!graph) {
90
+ throw new Error('Invalid audit message: missing required trailer eg-graph');
91
+ }
92
+ validateGraphName(graph);
93
+
94
+ const writer = trailers[TRAILER_KEYS.writer];
95
+ if (!writer) {
96
+ throw new Error('Invalid audit message: missing required trailer eg-writer');
97
+ }
98
+ validateWriterId(writer);
99
+
100
+ const dataCommit = trailers[TRAILER_KEYS.dataCommit];
101
+ if (!dataCommit) {
102
+ throw new Error('Invalid audit message: missing required trailer eg-data-commit');
103
+ }
104
+ validateOid(dataCommit, 'dataCommit');
105
+
106
+ const opsDigest = trailers[TRAILER_KEYS.opsDigest];
107
+ if (!opsDigest) {
108
+ throw new Error('Invalid audit message: missing required trailer eg-ops-digest');
109
+ }
110
+ validateSha256(opsDigest, 'opsDigest');
111
+
112
+ const schemaStr = trailers[TRAILER_KEYS.schema];
113
+ if (!schemaStr) {
114
+ throw new Error('Invalid audit message: missing required trailer eg-schema');
115
+ }
116
+ if (!/^\d+$/.test(schemaStr)) {
117
+ throw new Error(
118
+ `Invalid audit message: eg-schema must be a positive integer, got '${schemaStr}'`,
119
+ );
120
+ }
121
+ const schema = Number(schemaStr);
122
+ if (!Number.isInteger(schema) || schema < 1) {
123
+ throw new Error(`Invalid audit message: eg-schema must be a positive integer, got '${schemaStr}'`);
124
+ }
125
+ if (schema > 1) {
126
+ throw new Error(`Unsupported audit schema version: ${schema}`);
127
+ }
128
+
129
+ return {
130
+ kind: 'audit',
131
+ graph,
132
+ writer,
133
+ dataCommit,
134
+ opsDigest,
135
+ schema,
136
+ };
137
+ }