@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
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Trust V1 Zod schemas.
3
+ *
4
+ * Schemas for trust record envelope, per-type subjects, policy config,
5
+ * and assessment output contract. These are the canonical validation
6
+ * boundary — all trust data passes through these schemas.
7
+ *
8
+ * @module domain/trust/schemas
9
+ * @see docs/specs/TRUST_V1_CRYPTO.md Sections 8–10, 14
10
+ */
11
+
12
+ import { z } from 'zod';
13
+
14
+ // ── Primitives ──────────────────────────────────────────────────────────
15
+
16
+ export const KeyIdSchema = z.string().regex(
17
+ /^ed25519:[a-f0-9]{64}$/,
18
+ 'keyId must be "ed25519:" followed by 64 hex chars',
19
+ );
20
+
21
+ export const RecordIdSchema = z.string().regex(
22
+ /^[a-f0-9]{64}$/,
23
+ 'recordId must be 64 lowercase hex chars',
24
+ );
25
+
26
+ export const RecordTypeSchema = z.enum([
27
+ 'KEY_ADD',
28
+ 'KEY_REVOKE',
29
+ 'WRITER_BIND_ADD',
30
+ 'WRITER_BIND_REVOKE',
31
+ ]);
32
+
33
+ // ── Signature ───────────────────────────────────────────────────────────
34
+
35
+ export const TrustSignatureSchema = z.object({
36
+ alg: z.literal('ed25519'),
37
+ sig: z.string().min(1, 'signature must not be empty'),
38
+ });
39
+
40
+ // ── Per-type subject schemas ────────────────────────────────────────────
41
+
42
+ export const KeyAddSubjectSchema = z.object({
43
+ keyId: KeyIdSchema,
44
+ publicKey: z.string().min(1, 'publicKey must not be empty'),
45
+ });
46
+
47
+ export const KeyRevokeSubjectSchema = z.object({
48
+ keyId: KeyIdSchema,
49
+ reasonCode: z.enum(['KEY_COMPROMISE', 'KEY_ROLLOVER', 'OPERATOR_REQUEST']),
50
+ });
51
+
52
+ export const WriterBindAddSubjectSchema = z.object({
53
+ writerId: z.string().trim().min(1),
54
+ keyId: KeyIdSchema,
55
+ });
56
+
57
+ export const WriterBindRevokeSubjectSchema = z.object({
58
+ writerId: z.string().trim().min(1),
59
+ keyId: KeyIdSchema,
60
+ reasonCode: z.enum(['ACCESS_REMOVED', 'ROTATION', 'KEY_REVOKED']),
61
+ });
62
+
63
+ // ── Trust record envelope ───────────────────────────────────────────────
64
+
65
+ export const TrustRecordSchema = z.object({
66
+ schemaVersion: z.literal(1),
67
+ recordType: RecordTypeSchema,
68
+ recordId: RecordIdSchema,
69
+ issuerKeyId: KeyIdSchema,
70
+ issuedAt: z.string().datetime({ offset: false }),
71
+ prev: RecordIdSchema.nullable(),
72
+ subject: z.record(z.unknown()),
73
+ meta: z.record(z.unknown()).optional().default({}),
74
+ signature: TrustSignatureSchema,
75
+ }).superRefine((record, ctx) => {
76
+ /** @param {string} message */
77
+ const addIssue = (message) =>
78
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message });
79
+
80
+ switch (record.recordType) {
81
+ case 'KEY_ADD': {
82
+ const r = KeyAddSubjectSchema.safeParse(record.subject);
83
+ if (!r.success) {
84
+ addIssue(`Invalid KEY_ADD subject: ${r.error.message}`);
85
+ } else {
86
+ record.subject = r.data;
87
+ }
88
+ break;
89
+ }
90
+ case 'KEY_REVOKE': {
91
+ const r = KeyRevokeSubjectSchema.safeParse(record.subject);
92
+ if (!r.success) {
93
+ addIssue(`Invalid KEY_REVOKE subject: ${r.error.message}`);
94
+ } else {
95
+ record.subject = r.data;
96
+ }
97
+ break;
98
+ }
99
+ case 'WRITER_BIND_ADD': {
100
+ const r = WriterBindAddSubjectSchema.safeParse(record.subject);
101
+ if (!r.success) {
102
+ addIssue(`Invalid WRITER_BIND_ADD subject: ${r.error.message}`);
103
+ } else {
104
+ record.subject = r.data;
105
+ }
106
+ break;
107
+ }
108
+ case 'WRITER_BIND_REVOKE': {
109
+ const r = WriterBindRevokeSubjectSchema.safeParse(record.subject);
110
+ if (!r.success) {
111
+ addIssue(`Invalid WRITER_BIND_REVOKE subject: ${r.error.message}`);
112
+ } else {
113
+ record.subject = r.data;
114
+ }
115
+ break;
116
+ }
117
+ default:
118
+ addIssue(`Unsupported recordType: ${/** @type {string} */ (record.recordType)}`);
119
+ }
120
+ });
121
+
122
+ // ── Policy config ───────────────────────────────────────────────────────
123
+
124
+ export const TrustPolicySchema = z.object({
125
+ schemaVersion: z.literal(1),
126
+ mode: z.enum(['warn', 'enforce']),
127
+ writerPolicy: z.literal('all_writers_must_be_trusted'),
128
+ });
129
+
130
+ // ── Assessment output contract ──────────────────────────────────────────
131
+
132
+ export const TrustExplanationSchema = z.object({
133
+ writerId: z.string().min(1),
134
+ trusted: z.boolean(),
135
+ reasonCode: z.string().min(1),
136
+ reason: z.string().min(1),
137
+ });
138
+
139
+ export const EvidenceSummarySchema = z.object({
140
+ recordsScanned: z.number().int().nonnegative(),
141
+ activeKeys: z.number().int().nonnegative(),
142
+ revokedKeys: z.number().int().nonnegative(),
143
+ activeBindings: z.number().int().nonnegative(),
144
+ revokedBindings: z.number().int().nonnegative(),
145
+ });
146
+
147
+ export const TrustAssessmentSchema = z.object({
148
+ trustSchemaVersion: z.literal(1),
149
+ mode: z.literal('signed_evidence_v1'),
150
+ trustVerdict: z.enum(['pass', 'fail', 'not_configured']),
151
+ trust: z.object({
152
+ status: z.enum(['configured', 'pinned', 'error', 'not_configured']),
153
+ source: z.enum(['ref', 'cli_pin', 'env_pin', 'none']),
154
+ sourceDetail: z.string().nullable(),
155
+ evaluatedWriters: z.array(z.string()),
156
+ untrustedWriters: z.array(z.string()),
157
+ explanations: z.array(TrustExplanationSchema),
158
+ evidenceSummary: EvidenceSummarySchema,
159
+ }),
160
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Trust V1 verdict derivation.
3
+ *
4
+ * Deterministic mapping from TrustAssessment to verdict string.
5
+ * This is the single source of truth for verdict logic.
6
+ *
7
+ * @module domain/trust/verdict
8
+ * @see docs/specs/TRUST_V1_CRYPTO.md Section 13
9
+ */
10
+
11
+ /**
12
+ * @typedef {Object} TrustAssessmentV1
13
+ * @property {'not_configured'|'configured'|'pinned'|'error'} status
14
+ * @property {string[]} untrustedWriters
15
+ */
16
+
17
+ /**
18
+ * Derives the trust verdict from a V1 trust assessment.
19
+ *
20
+ * Mapping (evaluated in order):
21
+ * - status 'not_configured' → 'not_configured'
22
+ * - status 'error' → 'fail'
23
+ * - untrustedWriters.length > 0 → 'fail'
24
+ * - otherwise → 'pass'
25
+ *
26
+ * V1 has no 'degraded' verdict — untrusted writers are a hard failure.
27
+ *
28
+ * @param {TrustAssessmentV1} trust
29
+ * @returns {'pass'|'fail'|'not_configured'}
30
+ */
31
+ export function deriveTrustVerdict(trust) {
32
+ if (trust.status === 'not_configured') {
33
+ return 'not_configured';
34
+ }
35
+ if (trust.status === 'error') {
36
+ return 'fail';
37
+ }
38
+ if (Array.isArray(trust.untrustedWriters) && trust.untrustedWriters.length > 0) {
39
+ return 'fail';
40
+ }
41
+ return 'pass';
42
+ }
@@ -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) {
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Type stub for @git-stunts/git-cas.
3
+ *
4
+ * Provides just enough shape for CasSeekCacheAdapter to typecheck.
5
+ */
6
+ declare module '@git-stunts/git-cas' {
7
+ interface CasStore {
8
+ put(key: string, value: Uint8Array): Promise<string>;
9
+ get(key: string): Promise<Uint8Array | null>;
10
+ has(key: string): Promise<boolean>;
11
+ delete(key: string): Promise<boolean>;
12
+ }
13
+
14
+ interface ContentAddressableStore {
15
+ createCbor(opts: { plumbing: unknown }): CasStore;
16
+ }
17
+
18
+ const ContentAddressableStore: ContentAddressableStore;
19
+ export default ContentAddressableStore;
20
+ }
@@ -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
  }
@@ -10,6 +10,8 @@
10
10
  * - refs/warp/<graph>/coverage/head
11
11
  * - refs/warp/<graph>/cursor/active
12
12
  * - refs/warp/<graph>/cursor/saved/<name>
13
+ * - refs/warp/<graph>/audit/<writer_id>
14
+ * - refs/warp/<graph>/trust/records
13
15
  *
14
16
  * @module domain/utils/RefLayout
15
17
  */
@@ -291,6 +293,43 @@ export function buildCursorSavedPrefix(graphName) {
291
293
  return `${REF_PREFIX}/${graphName}/cursor/saved/`;
292
294
  }
293
295
 
296
+ /**
297
+ * Builds the audit ref path for the given graph and writer ID.
298
+ *
299
+ * Audit refs track the latest audit commit for each writer, forming
300
+ * an independent chain of tamper-evident receipts per writer.
301
+ *
302
+ * @param {string} graphName - The name of the graph
303
+ * @param {string} writerId - The writer's unique identifier
304
+ * @returns {string} The full ref path, e.g. `refs/warp/<graphName>/audit/<writerId>`
305
+ * @throws {Error} If graphName or writerId is invalid
306
+ *
307
+ * @example
308
+ * buildAuditRef('events', 'alice');
309
+ * // => 'refs/warp/events/audit/alice'
310
+ */
311
+ export function buildAuditRef(graphName, writerId) {
312
+ validateGraphName(graphName);
313
+ validateWriterId(writerId);
314
+ return `${REF_PREFIX}/${graphName}/audit/${writerId}`;
315
+ }
316
+
317
+ /**
318
+ * Builds the audit ref prefix for listing all audit writers of a graph.
319
+ *
320
+ * @param {string} graphName - The name of the graph
321
+ * @returns {string} The ref prefix, e.g. `refs/warp/<graphName>/audit/`
322
+ * @throws {Error} If graphName is invalid
323
+ *
324
+ * @example
325
+ * buildAuditPrefix('events');
326
+ * // => 'refs/warp/events/audit/'
327
+ */
328
+ export function buildAuditPrefix(graphName) {
329
+ validateGraphName(graphName);
330
+ return `${REF_PREFIX}/${graphName}/audit/`;
331
+ }
332
+
294
333
  /**
295
334
  * Builds the seek cache ref path for the given graph.
296
335
  *
@@ -310,6 +349,26 @@ export function buildSeekCacheRef(graphName) {
310
349
  return `${REF_PREFIX}/${graphName}/seek-cache`;
311
350
  }
312
351
 
352
+ /**
353
+ * Builds the trust record chain ref path for the given graph.
354
+ *
355
+ * The trust record ref points to the tip commit of the trust record
356
+ * chain — an append-only sequence of signed trust records (key adds,
357
+ * key revokes, writer bindings).
358
+ *
359
+ * @param {string} graphName - The name of the graph
360
+ * @returns {string} The full ref path, e.g. `refs/warp/<graphName>/trust/records`
361
+ * @throws {Error} If graphName is invalid
362
+ *
363
+ * @example
364
+ * buildTrustRecordRef('events');
365
+ * // => 'refs/warp/events/trust/records'
366
+ */
367
+ export function buildTrustRecordRef(graphName) {
368
+ validateGraphName(graphName);
369
+ return `${REF_PREFIX}/${graphName}/trust/records`;
370
+ }
371
+
313
372
  // -----------------------------------------------------------------------------
314
373
  // Parsers
315
374
  // -----------------------------------------------------------------------------
@@ -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);
@@ -30,9 +30,35 @@
30
30
  */
31
31
  const NOT_CHECKED = Symbol('NOT_CHECKED');
32
32
 
33
+ /**
34
+ * Shape of the lazily-loaded roaring module after ESM/CJS unwrapping.
35
+ * Uses Function instead of typeof import('roaring').RoaringBitmap32 to avoid
36
+ * duplicate import() references that crash Deno's JSR rewriter (deno_ast
37
+ * overlapping TextChange bug). The single import() reference lives on
38
+ * getRoaringBitmap32()'s @returns tag.
39
+ * @typedef {Object} RoaringModule
40
+ * @property {Function & { isNativelyInstalled?: () => boolean }} RoaringBitmap32
41
+ * @property {{ RoaringBitmap32: Function }} [default]
42
+ * @property {boolean} [isNativelyInstalled]
43
+ */
44
+
45
+ /**
46
+ * Minimum structural contract of a RoaringBitmap32 as used by bitmap index code.
47
+ * Named "Subset" because it only covers methods actually called by index builders/readers.
48
+ * Using import('roaring').RoaringBitmap32 directly fails under checkJs + skipLibCheck
49
+ * because tsc doesn't fully resolve inherited methods from ReadonlyRoaringBitmap32.
50
+ * @typedef {Object} RoaringBitmapSubset
51
+ * @property {number} size
52
+ * @property {function(number): void} add
53
+ * @property {function(number): boolean} has
54
+ * @property {function(Iterable<number>): void} orInPlace
55
+ * @property {function(boolean): Uint8Array} serialize
56
+ * @property {function(): number[]} toArray
57
+ */
58
+
33
59
  /**
34
60
  * Cached reference to the loaded roaring module.
35
- * @type {any} // TODO(ts-cleanup): type lazy singleton
61
+ * @type {RoaringModule | null}
36
62
  * @private
37
63
  */
38
64
  let roaringModule = null;
@@ -51,7 +77,7 @@ let nativeAvailability = NOT_CHECKED;
51
77
  * Uses a top-level-await-friendly pattern with dynamic import.
52
78
  * The module is cached after first load.
53
79
  *
54
- * @returns {any} The roaring module exports
80
+ * @returns {RoaringModule} The roaring module exports
55
81
  * @throws {Error} If the roaring package is not installed or fails to load
56
82
  * @private
57
83
  */
@@ -67,7 +93,7 @@ function loadRoaring() {
67
93
  * This is called automatically via top-level await when the module is imported,
68
94
  * but can also be called manually with a pre-loaded module for testing.
69
95
  *
70
- * @param {Object} [mod] - Pre-loaded roaring module (for testing/DI)
96
+ * @param {RoaringModule} [mod] - Pre-loaded roaring module (for testing/DI)
71
97
  * @returns {Promise<void>}
72
98
  */
73
99
  export async function initRoaring(mod) {
@@ -76,7 +102,7 @@ export async function initRoaring(mod) {
76
102
  return;
77
103
  }
78
104
  if (!roaringModule) {
79
- roaringModule = await import('roaring');
105
+ roaringModule = /** @type {RoaringModule} */ (await import('roaring'));
80
106
  // Handle both ESM default export and CJS module.exports
81
107
  if (roaringModule.default && roaringModule.default.RoaringBitmap32) {
82
108
  roaringModule = roaringModule.default;
@@ -116,7 +142,7 @@ try {
116
142
  * const intersection = RoaringBitmap32.and(a, b); // [2, 3]
117
143
  */
118
144
  export function getRoaringBitmap32() {
119
- return loadRoaring().RoaringBitmap32;
145
+ return /** @type {typeof import('roaring').RoaringBitmap32} */ (loadRoaring().RoaringBitmap32);
120
146
  }
121
147
 
122
148
  /**
@@ -120,7 +120,7 @@ export class PatchSession {
120
120
  *
121
121
  * @param {string} nodeId - The node ID
122
122
  * @param {string} key - Property key
123
- * @param {*} value - Property value (must be JSON-serializable)
123
+ * @param {unknown} value - Property value (must be JSON-serializable)
124
124
  * @returns {this} This session for chaining
125
125
  * @throws {Error} If this session has already been committed
126
126
  */
@@ -130,6 +130,24 @@ export class PatchSession {
130
130
  return this;
131
131
  }
132
132
 
133
+ /**
134
+ * Sets a property on an edge.
135
+ *
136
+ * @param {string} from - Source node ID
137
+ * @param {string} to - Target node ID
138
+ * @param {string} label - Edge label/type
139
+ * @param {string} key - Property key
140
+ * @param {unknown} value - Property value (must be JSON-serializable)
141
+ * @returns {this} This session for chaining
142
+ * @throws {Error} If this session has already been committed
143
+ */
144
+ // eslint-disable-next-line max-params -- direct delegate matching PatchBuilderV2 signature
145
+ setEdgeProperty(from, to, label, key, value) {
146
+ this._ensureNotCommitted();
147
+ this._builder.setEdgeProperty(from, to, label, key, value);
148
+ return this;
149
+ }
150
+
133
151
  /**
134
152
  * Builds the PatchV2 object without committing.
135
153
  *
@@ -176,23 +194,14 @@ export class PatchSession {
176
194
  const sha = await this._builder.commit();
177
195
  this._committed = true;
178
196
  return sha;
179
- } catch (/** @type {any} */ err) { // TODO(ts-cleanup): type error
180
- // Check if it's a concurrent commit error from PatchBuilderV2
181
- if (err.message?.includes('Concurrent commit detected') ||
182
- err.message?.includes('has advanced')) {
183
- throw new WriterError(
184
- 'WRITER_REF_ADVANCED',
185
- err.message,
186
- err
187
- );
197
+ } catch (err) {
198
+ const errMsg = err instanceof Error ? err.message : String(err);
199
+ const cause = err instanceof Error ? err : undefined;
200
+ if (errMsg.includes('Concurrent commit detected') ||
201
+ errMsg.includes('has advanced')) {
202
+ throw new WriterError('WRITER_REF_ADVANCED', errMsg, cause);
188
203
  }
189
-
190
- // Wrap other errors
191
- throw new WriterError(
192
- 'PERSIST_WRITE_FAILED',
193
- `Failed to persist patch: ${err.message}`,
194
- err
195
- );
204
+ throw new WriterError('PERSIST_WRITE_FAILED', `Failed to persist patch: ${errMsg}`, cause);
196
205
  }
197
206
  }
198
207