@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.
- package/bin/cli/commands/check.js +2 -2
- package/bin/cli/commands/doctor/checks.js +12 -12
- package/bin/cli/commands/doctor/index.js +2 -2
- package/bin/cli/commands/doctor/types.js +1 -1
- package/bin/cli/commands/history.js +12 -5
- package/bin/cli/commands/install-hooks.js +5 -5
- package/bin/cli/commands/materialize.js +2 -2
- package/bin/cli/commands/patch.js +142 -0
- package/bin/cli/commands/path.js +4 -4
- package/bin/cli/commands/query.js +54 -13
- package/bin/cli/commands/registry.js +4 -0
- package/bin/cli/commands/seek.js +17 -11
- package/bin/cli/commands/tree.js +230 -0
- package/bin/cli/commands/trust.js +3 -3
- package/bin/cli/commands/verify-audit.js +8 -7
- package/bin/cli/commands/view.js +6 -5
- package/bin/cli/infrastructure.js +26 -12
- package/bin/cli/shared.js +2 -2
- package/bin/cli/types.js +19 -8
- package/bin/presenters/index.js +35 -9
- package/bin/presenters/json.js +14 -12
- package/bin/presenters/text.js +155 -33
- package/index.d.ts +82 -22
- package/package.json +3 -2
- package/src/domain/WarpGraph.js +4 -1
- package/src/domain/crdt/ORSet.js +8 -8
- package/src/domain/errors/EmptyMessageError.js +2 -2
- package/src/domain/errors/ForkError.js +1 -1
- package/src/domain/errors/IndexError.js +1 -1
- package/src/domain/errors/OperationAbortedError.js +1 -1
- package/src/domain/errors/QueryError.js +1 -1
- package/src/domain/errors/SchemaUnsupportedError.js +1 -1
- package/src/domain/errors/ShardCorruptionError.js +2 -2
- package/src/domain/errors/ShardLoadError.js +2 -2
- package/src/domain/errors/ShardValidationError.js +4 -4
- package/src/domain/errors/StorageError.js +2 -2
- package/src/domain/errors/SyncError.js +1 -1
- package/src/domain/errors/TraversalError.js +1 -1
- package/src/domain/errors/TrustError.js +1 -1
- package/src/domain/errors/WarpError.js +2 -2
- package/src/domain/errors/WormholeError.js +1 -1
- package/src/domain/services/AuditReceiptService.js +6 -6
- package/src/domain/services/AuditVerifierService.js +52 -38
- package/src/domain/services/BitmapIndexBuilder.js +3 -3
- package/src/domain/services/BitmapIndexReader.js +28 -19
- package/src/domain/services/BoundaryTransitionRecord.js +18 -17
- package/src/domain/services/CheckpointSerializerV5.js +17 -16
- package/src/domain/services/CheckpointService.js +2 -2
- package/src/domain/services/CommitDagTraversalService.js +13 -13
- package/src/domain/services/DagPathFinding.js +7 -7
- package/src/domain/services/DagTopology.js +1 -1
- package/src/domain/services/DagTraversal.js +1 -1
- package/src/domain/services/HealthCheckService.js +1 -1
- package/src/domain/services/HookInstaller.js +1 -1
- package/src/domain/services/HttpSyncServer.js +92 -41
- package/src/domain/services/IndexRebuildService.js +7 -7
- package/src/domain/services/IndexStalenessChecker.js +4 -3
- package/src/domain/services/JoinReducer.js +11 -11
- package/src/domain/services/LogicalTraversal.js +1 -1
- package/src/domain/services/MessageCodecInternal.js +1 -1
- package/src/domain/services/MigrationService.js +1 -1
- package/src/domain/services/ObserverView.js +8 -8
- package/src/domain/services/PatchBuilderV2.js +42 -26
- package/src/domain/services/ProvenanceIndex.js +1 -1
- package/src/domain/services/ProvenancePayload.js +1 -1
- package/src/domain/services/QueryBuilder.js +3 -3
- package/src/domain/services/StateDiff.js +14 -11
- package/src/domain/services/StateSerializerV5.js +2 -2
- package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
- package/src/domain/services/SyncAuthService.js +3 -2
- package/src/domain/services/SyncProtocol.js +25 -11
- package/src/domain/services/TemporalQuery.js +9 -6
- package/src/domain/services/TranslationCost.js +7 -5
- package/src/domain/services/WormholeService.js +16 -7
- package/src/domain/trust/TrustCanonical.js +3 -3
- package/src/domain/trust/TrustEvaluator.js +18 -3
- package/src/domain/trust/TrustRecordService.js +30 -23
- package/src/domain/trust/TrustStateBuilder.js +21 -8
- package/src/domain/trust/canonical.js +6 -6
- package/src/domain/types/TickReceipt.js +1 -1
- package/src/domain/types/WarpErrors.js +45 -0
- package/src/domain/types/WarpOptions.js +29 -0
- package/src/domain/types/WarpPersistence.js +41 -0
- package/src/domain/types/WarpTypes.js +2 -2
- package/src/domain/types/WarpTypesV2.js +2 -2
- package/src/domain/utils/MinHeap.js +6 -5
- package/src/domain/utils/canonicalStringify.js +5 -4
- package/src/domain/utils/roaring.js +31 -5
- package/src/domain/warp/PatchSession.js +9 -18
- package/src/domain/warp/_wiredMethods.d.ts +199 -45
- package/src/domain/warp/checkpoint.methods.js +5 -1
- package/src/domain/warp/fork.methods.js +2 -2
- package/src/domain/warp/materialize.methods.js +55 -5
- package/src/domain/warp/materializeAdvanced.methods.js +15 -4
- package/src/domain/warp/patch.methods.js +54 -29
- package/src/domain/warp/provenance.methods.js +5 -3
- package/src/domain/warp/query.methods.js +6 -5
- package/src/domain/warp/sync.methods.js +16 -11
- package/src/globals.d.ts +64 -0
- package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
- package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
- package/src/infrastructure/adapters/GitGraphAdapter.js +14 -12
- package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
- package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
- package/src/visualization/layouts/converters.js +2 -2
- package/src/visualization/layouts/elkAdapter.js +1 -1
- package/src/visualization/layouts/elkLayout.js +10 -7
- package/src/visualization/layouts/index.js +1 -1
- package/src/visualization/renderers/ascii/seek.js +16 -6
- package/src/visualization/renderers/svg/index.js +1 -1
package/src/domain/crdt/ORSet.js
CHANGED
|
@@ -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
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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<[
|
|
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<[
|
|
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<[
|
|
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,
|
|
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,
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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';
|
|
@@ -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?:
|
|
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?:
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 {
|
|
16
|
-
* @property {
|
|
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,
|
|
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?:
|
|
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,
|
|
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,
|
|
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?:
|
|
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?:
|
|
22
|
+
* @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
|
|
23
23
|
*/
|
|
24
24
|
constructor(message, options = {}) {
|
|
25
25
|
super(message, 'TRAVERSAL_ERROR', options);
|
|
@@ -21,7 +21,7 @@ import WarpError from './WarpError.js';
|
|
|
21
21
|
export default class TrustError extends WarpError {
|
|
22
22
|
/**
|
|
23
23
|
* @param {string} message
|
|
24
|
-
* @param {{ code?: string, context?:
|
|
24
|
+
* @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
|
|
25
25
|
*/
|
|
26
26
|
constructor(message, options = {}) {
|
|
27
27
|
super(message, 'TRUST_ERROR', options);
|
|
@@ -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,
|
|
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,
|
|
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?:
|
|
27
|
+
* @param {{ code?: string, context?: Record<string, unknown> }} [options={}]
|
|
28
28
|
*/
|
|
29
29
|
constructor(message, options = {}) {
|
|
30
30
|
super(message, 'WORMHOLE_ERROR', options);
|
|
@@ -290,12 +290,12 @@ export class AuditReceiptService {
|
|
|
290
290
|
|
|
291
291
|
try {
|
|
292
292
|
return await this._commitInner(tickReceipt);
|
|
293
|
-
} catch (
|
|
293
|
+
} catch (err) {
|
|
294
294
|
this._failed++;
|
|
295
295
|
this._logger?.warn('[warp:audit]', {
|
|
296
296
|
code: 'AUDIT_COMMIT_FAILED',
|
|
297
297
|
writerId: this._writerId,
|
|
298
|
-
error: err
|
|
298
|
+
error: err instanceof Error ? err.message : String(err),
|
|
299
299
|
});
|
|
300
300
|
return null;
|
|
301
301
|
}
|
|
@@ -366,11 +366,11 @@ export class AuditReceiptService {
|
|
|
366
366
|
let blobOid;
|
|
367
367
|
try {
|
|
368
368
|
blobOid = await this._persistence.writeBlob(Buffer.from(cborBytes));
|
|
369
|
-
} catch (
|
|
369
|
+
} catch (err) {
|
|
370
370
|
this._logger?.warn('[warp:audit]', {
|
|
371
371
|
code: 'AUDIT_WRITE_BLOB_FAILED',
|
|
372
372
|
writerId: this._writerId,
|
|
373
|
-
error: err
|
|
373
|
+
error: err instanceof Error ? err.message : String(err),
|
|
374
374
|
});
|
|
375
375
|
throw err;
|
|
376
376
|
}
|
|
@@ -381,11 +381,11 @@ export class AuditReceiptService {
|
|
|
381
381
|
treeOid = await this._persistence.writeTree([
|
|
382
382
|
`100644 blob ${blobOid}\treceipt.cbor`,
|
|
383
383
|
]);
|
|
384
|
-
} catch (
|
|
384
|
+
} catch (err) {
|
|
385
385
|
this._logger?.warn('[warp:audit]', {
|
|
386
386
|
code: 'AUDIT_WRITE_TREE_FAILED',
|
|
387
387
|
writerId: this._writerId,
|
|
388
|
-
error: err
|
|
388
|
+
error: err instanceof Error ? err.message : String(err),
|
|
389
389
|
});
|
|
390
390
|
throw err;
|
|
391
391
|
}
|
|
@@ -16,6 +16,19 @@
|
|
|
16
16
|
* @see docs/specs/AUDIT_RECEIPT.md Section 8
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} AuditReceipt
|
|
21
|
+
* @property {number} version
|
|
22
|
+
* @property {string} graphName
|
|
23
|
+
* @property {string} writerId
|
|
24
|
+
* @property {string} dataCommit
|
|
25
|
+
* @property {string} opsDigest
|
|
26
|
+
* @property {string} prevAuditCommit
|
|
27
|
+
* @property {number} tickStart
|
|
28
|
+
* @property {number} tickEnd
|
|
29
|
+
* @property {number} timestamp
|
|
30
|
+
*/
|
|
31
|
+
|
|
19
32
|
import { buildAuditPrefix, buildAuditRef } from '../utils/RefLayout.js';
|
|
20
33
|
import { decodeAuditMessage } from './AuditMessageCodec.js';
|
|
21
34
|
import { TrustRecordService } from '../trust/TrustRecordService.js';
|
|
@@ -67,14 +80,15 @@ function validateOidFormat(value) {
|
|
|
67
80
|
|
|
68
81
|
/**
|
|
69
82
|
* Checks whether a receipt object has the expected 9 fields with correct types.
|
|
70
|
-
* @param {
|
|
83
|
+
* @param {unknown} receipt
|
|
71
84
|
* @returns {string|null} Error message or null if valid
|
|
72
85
|
*/
|
|
73
86
|
function validateReceiptSchema(receipt) {
|
|
74
87
|
if (!receipt || typeof receipt !== 'object') {
|
|
75
88
|
return 'receipt is not an object';
|
|
76
89
|
}
|
|
77
|
-
const
|
|
90
|
+
const rec = /** @type {Record<string, unknown>} */ (receipt);
|
|
91
|
+
const keys = Object.keys(rec);
|
|
78
92
|
if (keys.length !== 9) {
|
|
79
93
|
return `expected 9 fields, got ${keys.length}`;
|
|
80
94
|
}
|
|
@@ -83,46 +97,46 @@ function validateReceiptSchema(receipt) {
|
|
|
83
97
|
'tickEnd', 'tickStart', 'timestamp', 'version', 'writerId',
|
|
84
98
|
];
|
|
85
99
|
for (const k of required) {
|
|
86
|
-
if (!(k in
|
|
100
|
+
if (!(k in rec)) {
|
|
87
101
|
return `missing field: ${k}`;
|
|
88
102
|
}
|
|
89
103
|
}
|
|
90
|
-
if (
|
|
91
|
-
return `unsupported version: ${
|
|
104
|
+
if (rec.version !== 1) {
|
|
105
|
+
return `unsupported version: ${rec.version}`;
|
|
92
106
|
}
|
|
93
|
-
if (typeof
|
|
107
|
+
if (typeof rec.graphName !== 'string' || rec.graphName.length === 0) {
|
|
94
108
|
return 'graphName must be a non-empty string';
|
|
95
109
|
}
|
|
96
|
-
if (typeof
|
|
110
|
+
if (typeof rec.writerId !== 'string' || rec.writerId.length === 0) {
|
|
97
111
|
return 'writerId must be a non-empty string';
|
|
98
112
|
}
|
|
99
|
-
if (typeof
|
|
113
|
+
if (typeof rec.dataCommit !== 'string') {
|
|
100
114
|
return 'dataCommit must be a string';
|
|
101
115
|
}
|
|
102
|
-
if (typeof
|
|
116
|
+
if (typeof rec.opsDigest !== 'string') {
|
|
103
117
|
return 'opsDigest must be a string';
|
|
104
118
|
}
|
|
105
|
-
if (typeof
|
|
119
|
+
if (typeof rec.prevAuditCommit !== 'string') {
|
|
106
120
|
return 'prevAuditCommit must be a string';
|
|
107
121
|
}
|
|
108
|
-
if (!Number.isInteger(
|
|
109
|
-
return `tickStart must be integer >= 1, got ${
|
|
122
|
+
if (!Number.isInteger(rec.tickStart) || /** @type {number} */ (rec.tickStart) < 1) {
|
|
123
|
+
return `tickStart must be integer >= 1, got ${rec.tickStart}`;
|
|
110
124
|
}
|
|
111
|
-
if (!Number.isInteger(
|
|
112
|
-
return `tickEnd must be integer >= tickStart, got ${
|
|
125
|
+
if (!Number.isInteger(rec.tickEnd) || /** @type {number} */ (rec.tickEnd) < /** @type {number} */ (rec.tickStart)) {
|
|
126
|
+
return `tickEnd must be integer >= tickStart, got ${rec.tickEnd}`;
|
|
113
127
|
}
|
|
114
|
-
if (
|
|
115
|
-
return `v1 requires tickStart === tickEnd, got ${
|
|
128
|
+
if (rec.version === 1 && rec.tickStart !== rec.tickEnd) {
|
|
129
|
+
return `v1 requires tickStart === tickEnd, got ${rec.tickStart} !== ${rec.tickEnd}`;
|
|
116
130
|
}
|
|
117
|
-
if (!Number.isInteger(
|
|
118
|
-
return `timestamp must be non-negative integer, got ${
|
|
131
|
+
if (!Number.isInteger(rec.timestamp) || /** @type {number} */ (rec.timestamp) < 0) {
|
|
132
|
+
return `timestamp must be non-negative integer, got ${rec.timestamp}`;
|
|
119
133
|
}
|
|
120
134
|
return null;
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
/**
|
|
124
138
|
* Validates trailers against the CBOR receipt fields.
|
|
125
|
-
* @param {
|
|
139
|
+
* @param {AuditReceipt} receipt
|
|
126
140
|
* @param {{ graph: string, writer: string, dataCommit: string, opsDigest: string, schema: number }} decoded
|
|
127
141
|
* @returns {string|null} Error message or null if consistent
|
|
128
142
|
*/
|
|
@@ -312,7 +326,7 @@ export class AuditVerifierService {
|
|
|
312
326
|
*/
|
|
313
327
|
async _walkChain(graphName, writerId, tip, since, result) {
|
|
314
328
|
let current = tip;
|
|
315
|
-
/** @type {
|
|
329
|
+
/** @type {AuditReceipt|null} */ let prevReceipt = null;
|
|
316
330
|
/** @type {number|null} */ let chainOidLen = null;
|
|
317
331
|
|
|
318
332
|
while (current) {
|
|
@@ -322,8 +336,8 @@ export class AuditVerifierService {
|
|
|
322
336
|
let commitInfo;
|
|
323
337
|
try {
|
|
324
338
|
commitInfo = await this._persistence.getNodeInfo(current);
|
|
325
|
-
} catch (
|
|
326
|
-
this._addError(result, 'MISSING_RECEIPT_BLOB', `Cannot read commit ${current}: ${err
|
|
339
|
+
} catch (err) {
|
|
340
|
+
this._addError(result, 'MISSING_RECEIPT_BLOB', `Cannot read commit ${current}: ${err instanceof Error ? err.message : String(err)}`, current);
|
|
327
341
|
return;
|
|
328
342
|
}
|
|
329
343
|
|
|
@@ -456,7 +470,7 @@ export class AuditVerifierService {
|
|
|
456
470
|
* @param {string} commitSha
|
|
457
471
|
* @param {{ message: string }} commitInfo
|
|
458
472
|
* @param {ChainResult} result
|
|
459
|
-
* @returns {Promise<{ receipt:
|
|
473
|
+
* @returns {Promise<{ receipt: AuditReceipt, decodedTrailers: { graph: string, writer: string, dataCommit: string, opsDigest: string, schema: number } }|null>}
|
|
460
474
|
* @private
|
|
461
475
|
*/
|
|
462
476
|
async _readReceipt(commitSha, commitInfo, result) {
|
|
@@ -464,9 +478,9 @@ export class AuditVerifierService {
|
|
|
464
478
|
let treeOid;
|
|
465
479
|
try {
|
|
466
480
|
treeOid = await this._persistence.getCommitTree(commitSha);
|
|
467
|
-
} catch (
|
|
481
|
+
} catch (err) {
|
|
468
482
|
this._addError(result, 'MISSING_RECEIPT_BLOB',
|
|
469
|
-
`Cannot read tree for ${commitSha}: ${err
|
|
483
|
+
`Cannot read tree for ${commitSha}: ${err instanceof Error ? err.message : String(err)}`, commitSha);
|
|
470
484
|
return null;
|
|
471
485
|
}
|
|
472
486
|
|
|
@@ -474,9 +488,9 @@ export class AuditVerifierService {
|
|
|
474
488
|
let treeEntries;
|
|
475
489
|
try {
|
|
476
490
|
treeEntries = await this._persistence.readTreeOids(treeOid);
|
|
477
|
-
} catch (
|
|
491
|
+
} catch (err) {
|
|
478
492
|
this._addError(result, 'RECEIPT_TREE_INVALID',
|
|
479
|
-
`Cannot read tree ${treeOid}: ${err
|
|
493
|
+
`Cannot read tree ${treeOid}: ${err instanceof Error ? err.message : String(err)}`, commitSha);
|
|
480
494
|
return null;
|
|
481
495
|
}
|
|
482
496
|
|
|
@@ -493,19 +507,19 @@ export class AuditVerifierService {
|
|
|
493
507
|
let blobContent;
|
|
494
508
|
try {
|
|
495
509
|
blobContent = await this._persistence.readBlob(blobOid);
|
|
496
|
-
} catch (
|
|
510
|
+
} catch (err) {
|
|
497
511
|
this._addError(result, 'MISSING_RECEIPT_BLOB',
|
|
498
|
-
`Cannot read receipt blob ${blobOid}: ${err
|
|
512
|
+
`Cannot read receipt blob ${blobOid}: ${err instanceof Error ? err.message : String(err)}`, commitSha);
|
|
499
513
|
return null;
|
|
500
514
|
}
|
|
501
515
|
|
|
502
516
|
// Decode CBOR
|
|
503
517
|
let receipt;
|
|
504
518
|
try {
|
|
505
|
-
receipt = this._codec.decode(blobContent);
|
|
506
|
-
} catch (
|
|
519
|
+
receipt = /** @type {AuditReceipt} */ (this._codec.decode(blobContent));
|
|
520
|
+
} catch (err) {
|
|
507
521
|
this._addError(result, 'CBOR_DECODE_FAILED',
|
|
508
|
-
`CBOR decode failed: ${err
|
|
522
|
+
`CBOR decode failed: ${err instanceof Error ? err.message : String(err)}`, commitSha);
|
|
509
523
|
result.status = STATUS_ERROR;
|
|
510
524
|
return null;
|
|
511
525
|
}
|
|
@@ -514,9 +528,9 @@ export class AuditVerifierService {
|
|
|
514
528
|
let decodedTrailers;
|
|
515
529
|
try {
|
|
516
530
|
decodedTrailers = decodeAuditMessage(commitInfo.message);
|
|
517
|
-
} catch (
|
|
531
|
+
} catch (err) {
|
|
518
532
|
this._addError(result, 'TRAILER_MISMATCH',
|
|
519
|
-
`Trailer decode failed: ${err
|
|
533
|
+
`Trailer decode failed: ${err instanceof Error ? err.message : String(err)}`, commitSha);
|
|
520
534
|
result.status = STATUS_DATA_MISMATCH;
|
|
521
535
|
return null;
|
|
522
536
|
}
|
|
@@ -526,7 +540,7 @@ export class AuditVerifierService {
|
|
|
526
540
|
|
|
527
541
|
/**
|
|
528
542
|
* Validates OID format for dataCommit, prevAuditCommit, and opsDigest.
|
|
529
|
-
* @param {
|
|
543
|
+
* @param {AuditReceipt} receipt
|
|
530
544
|
* @param {ChainResult} result
|
|
531
545
|
* @param {string} commitSha
|
|
532
546
|
* @returns {boolean} true if valid
|
|
@@ -556,8 +570,8 @@ export class AuditVerifierService {
|
|
|
556
570
|
|
|
557
571
|
/**
|
|
558
572
|
* Validates chain linking between current and previous (newer) receipt.
|
|
559
|
-
* @param {
|
|
560
|
-
* @param {
|
|
573
|
+
* @param {AuditReceipt} currentReceipt - The older receipt being validated
|
|
574
|
+
* @param {AuditReceipt} prevReceipt - The newer receipt (closer to tip)
|
|
561
575
|
* @param {string} commitSha
|
|
562
576
|
* @param {ChainResult} result
|
|
563
577
|
* @returns {boolean} true if valid
|
|
@@ -645,7 +659,7 @@ export class AuditVerifierService {
|
|
|
645
659
|
* @param {Object} [options]
|
|
646
660
|
* @param {string} [options.pin] - Pinned trust chain commit SHA
|
|
647
661
|
* @param {string} [options.mode] - Policy mode ('warn' or 'enforce')
|
|
648
|
-
* @returns {Promise<
|
|
662
|
+
* @returns {Promise<import('../trust/TrustEvaluator.js').TrustAssessment>}
|
|
649
663
|
*/
|
|
650
664
|
async evaluateTrust(graphName, options = {}) {
|
|
651
665
|
const recordService = new TrustRecordService({
|
|
@@ -103,7 +103,7 @@ export default class BitmapIndexBuilder {
|
|
|
103
103
|
this.shaToId = new Map();
|
|
104
104
|
/** @type {string[]} */
|
|
105
105
|
this.idToSha = [];
|
|
106
|
-
/** @type {Map<string,
|
|
106
|
+
/** @type {Map<string, import('../utils/roaring.js').RoaringBitmapSubset>} */
|
|
107
107
|
this.bitmaps = new Map();
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -178,7 +178,7 @@ export default class BitmapIndexBuilder {
|
|
|
178
178
|
bitmapShards[type][prefix] = {};
|
|
179
179
|
}
|
|
180
180
|
// Encode bitmap as base64 for JSON storage
|
|
181
|
-
bitmapShards[type][prefix][sha] = bitmap.serialize(true).toString('base64');
|
|
181
|
+
bitmapShards[type][prefix][sha] = Buffer.from(bitmap.serialize(true)).toString('base64');
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
for (const type of ['fwd', 'rev']) {
|
|
@@ -224,6 +224,6 @@ export default class BitmapIndexBuilder {
|
|
|
224
224
|
const RoaringBitmap32 = ensureRoaringBitmap32();
|
|
225
225
|
this.bitmaps.set(key, new RoaringBitmap32());
|
|
226
226
|
}
|
|
227
|
-
this.bitmaps.get(key).add(id);
|
|
227
|
+
/** @type {import('../utils/roaring.js').RoaringBitmapSubset} */ (this.bitmaps.get(key)).add(id);
|
|
228
228
|
}
|
|
229
229
|
}
|