@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.
- package/README.md +53 -32
- package/SECURITY.md +64 -0
- package/bin/cli/commands/check.js +168 -0
- package/bin/cli/commands/doctor/checks.js +422 -0
- package/bin/cli/commands/doctor/codes.js +46 -0
- package/bin/cli/commands/doctor/index.js +239 -0
- package/bin/cli/commands/doctor/types.js +89 -0
- package/bin/cli/commands/history.js +80 -0
- package/bin/cli/commands/info.js +139 -0
- package/bin/cli/commands/install-hooks.js +128 -0
- package/bin/cli/commands/materialize.js +99 -0
- package/bin/cli/commands/patch.js +142 -0
- package/bin/cli/commands/path.js +88 -0
- package/bin/cli/commands/query.js +235 -0
- package/bin/cli/commands/registry.js +32 -0
- package/bin/cli/commands/seek.js +598 -0
- package/bin/cli/commands/tree.js +230 -0
- package/bin/cli/commands/trust.js +154 -0
- package/bin/cli/commands/verify-audit.js +114 -0
- package/bin/cli/commands/view.js +46 -0
- package/bin/cli/infrastructure.js +350 -0
- package/bin/cli/schemas.js +177 -0
- package/bin/cli/shared.js +244 -0
- package/bin/cli/types.js +96 -0
- package/bin/presenters/index.js +41 -9
- package/bin/presenters/json.js +14 -12
- package/bin/presenters/text.js +286 -28
- package/bin/warp-graph.js +5 -2346
- package/index.d.ts +111 -21
- package/index.js +2 -0
- package/package.json +10 -8
- package/src/domain/WarpGraph.js +109 -3252
- 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 +3 -3
- 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 +29 -0
- package/src/domain/errors/WarpError.js +2 -2
- package/src/domain/errors/WormholeError.js +1 -1
- package/src/domain/errors/index.js +1 -0
- package/src/domain/services/AuditMessageCodec.js +137 -0
- package/src/domain/services/AuditReceiptService.js +471 -0
- package/src/domain/services/AuditVerifierService.js +707 -0
- 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 +120 -55
- 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 +4 -1
- package/src/domain/services/MessageSchemaDetector.js +2 -2
- 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 +71 -4
- 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/WarpMessageCodec.js +4 -1
- package/src/domain/services/WormholeService.js +16 -7
- package/src/domain/trust/TrustCanonical.js +42 -0
- package/src/domain/trust/TrustCrypto.js +111 -0
- package/src/domain/trust/TrustEvaluator.js +195 -0
- package/src/domain/trust/TrustRecordService.js +281 -0
- package/src/domain/trust/TrustStateBuilder.js +222 -0
- package/src/domain/trust/canonical.js +68 -0
- package/src/domain/trust/reasonCodes.js +64 -0
- package/src/domain/trust/schemas.js +160 -0
- package/src/domain/trust/verdict.js +42 -0
- 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/types/git-cas.d.ts +20 -0
- package/src/domain/utils/MinHeap.js +6 -5
- package/src/domain/utils/RefLayout.js +59 -0
- package/src/domain/utils/canonicalStringify.js +5 -4
- package/src/domain/utils/roaring.js +31 -5
- package/src/domain/warp/PatchSession.js +26 -17
- package/src/domain/warp/Writer.js +18 -3
- package/src/domain/warp/_internal.js +26 -0
- package/src/domain/warp/_wire.js +58 -0
- package/src/domain/warp/_wiredMethods.d.ts +254 -0
- package/src/domain/warp/checkpoint.methods.js +401 -0
- package/src/domain/warp/fork.methods.js +323 -0
- package/src/domain/warp/materialize.methods.js +238 -0
- package/src/domain/warp/materializeAdvanced.methods.js +350 -0
- package/src/domain/warp/patch.methods.js +554 -0
- package/src/domain/warp/provenance.methods.js +286 -0
- package/src/domain/warp/query.methods.js +280 -0
- package/src/domain/warp/subscribe.methods.js +272 -0
- package/src/domain/warp/sync.methods.js +554 -0
- 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 +79 -11
- package/src/infrastructure/adapters/InMemoryGraphAdapter.js +36 -0
- package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
- package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
- package/src/ports/CommitPort.js +10 -0
- package/src/ports/RefPort.js +17 -0
- 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/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,
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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:
|
|
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 {
|
|
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 {
|
|
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:
|
|
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:
|
|
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 {
|
|
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(
|
|
33
|
+
const keys = Object.keys(obj)
|
|
33
34
|
.filter(k => {
|
|
34
|
-
const v =
|
|
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(
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|