@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
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import { orsetElements, orsetContains } from '../crdt/ORSet.js';
|
|
17
17
|
import { decodeEdgeKey, decodePropKey, isEdgePropKey } from './KeyCodec.js';
|
|
18
18
|
|
|
19
|
+
/** @typedef {import('./JoinReducer.js').WarpStateV5} WarpStateV5 */
|
|
20
|
+
|
|
19
21
|
/**
|
|
20
22
|
* Tests whether a string matches a glob-style pattern.
|
|
21
23
|
*
|
|
@@ -38,7 +40,7 @@ function matchGlob(pattern, str) {
|
|
|
38
40
|
/**
|
|
39
41
|
* Computes the set of property keys visible under an observer config.
|
|
40
42
|
*
|
|
41
|
-
* @param {Map<string,
|
|
43
|
+
* @param {Map<string, unknown>} allNodeProps - Map of propKey -> placeholder
|
|
42
44
|
* @param {string[]|undefined} expose - Whitelist of property keys
|
|
43
45
|
* @param {string[]|undefined} redact - Blacklist of property keys
|
|
44
46
|
* @returns {Set<string>} Visible property keys
|
|
@@ -63,7 +65,7 @@ function visiblePropKeys(allNodeProps, expose, redact) {
|
|
|
63
65
|
/**
|
|
64
66
|
* Collects node property keys from state for a given node.
|
|
65
67
|
*
|
|
66
|
-
* @param {
|
|
68
|
+
* @param {WarpStateV5} state - WarpStateV5 materialized state
|
|
67
69
|
* @param {string} nodeId - The node ID
|
|
68
70
|
* @returns {Map<string, boolean>} Map of propKey -> true
|
|
69
71
|
*/
|
|
@@ -111,7 +113,7 @@ function countMissing(source, targetSet) {
|
|
|
111
113
|
/**
|
|
112
114
|
* Computes edge loss between two observer node sets.
|
|
113
115
|
*
|
|
114
|
-
* @param {
|
|
116
|
+
* @param {WarpStateV5} state
|
|
115
117
|
* @param {Set<string>} nodesASet - Nodes visible to A
|
|
116
118
|
* @param {Set<string>} nodesBSet - Nodes visible to B
|
|
117
119
|
* @returns {number} edgeLoss fraction
|
|
@@ -156,7 +158,7 @@ function countNodePropLoss(nodeProps, { configA, configB, nodeInB }) {
|
|
|
156
158
|
/**
|
|
157
159
|
* Computes property loss across all A-visible nodes.
|
|
158
160
|
*
|
|
159
|
-
* @param {
|
|
161
|
+
* @param {WarpStateV5} state - WarpStateV5
|
|
160
162
|
* @param {{ nodesA: string[], nodesBSet: Set<string>, configA: {expose?: string[], redact?: string[]}, configB: {expose?: string[], redact?: string[]} }} opts
|
|
161
163
|
* @returns {number} propLoss fraction
|
|
162
164
|
*/
|
|
@@ -193,7 +195,7 @@ function computePropLoss(state, { nodesA, nodesBSet, configA, configB }) {
|
|
|
193
195
|
* @param {string} configB.match - Glob pattern for visible nodes
|
|
194
196
|
* @param {string[]} [configB.expose] - Property keys to include
|
|
195
197
|
* @param {string[]} [configB.redact] - Property keys to exclude
|
|
196
|
-
* @param {
|
|
198
|
+
* @param {WarpStateV5} state - WarpStateV5 materialized state
|
|
197
199
|
* @returns {{ cost: number, breakdown: { nodeLoss: number, edgeLoss: number, propLoss: number } }}
|
|
198
200
|
*/
|
|
199
201
|
export function computeTranslationCost(configA, configB, state) {
|
|
@@ -27,7 +27,7 @@ import { detectMessageKind, decodePatchMessage } from './WarpMessageCodec.js';
|
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Validates that a SHA parameter is a non-empty string.
|
|
30
|
-
* @param {
|
|
30
|
+
* @param {unknown} sha - The SHA to validate
|
|
31
31
|
* @param {string} paramName - Parameter name for error messages
|
|
32
32
|
* @throws {WormholeError} If SHA is invalid
|
|
33
33
|
* @private
|
|
@@ -321,7 +321,7 @@ export function deserializeWormhole(json) {
|
|
|
321
321
|
});
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
const /** @type {Record<string,
|
|
324
|
+
const /** @type {Record<string, unknown>} */ typedJson = /** @type {Record<string, unknown>} */ (json);
|
|
325
325
|
const requiredFields = ['fromSha', 'toSha', 'writerId', 'patchCount', 'payload'];
|
|
326
326
|
for (const field of requiredFields) {
|
|
327
327
|
if (typedJson[field] === undefined) {
|
|
@@ -332,6 +332,15 @@ export function deserializeWormhole(json) {
|
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
for (const field of ['fromSha', 'toSha', 'writerId']) {
|
|
336
|
+
if (typeof typedJson[field] !== 'string') {
|
|
337
|
+
throw new WormholeError(`Invalid wormhole JSON: '${field}' must be a string`, {
|
|
338
|
+
code: 'E_INVALID_WORMHOLE_JSON',
|
|
339
|
+
context: { [field]: typedJson[field] },
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
335
344
|
if (typeof typedJson.patchCount !== 'number' || typedJson.patchCount < 0) {
|
|
336
345
|
throw new WormholeError('Invalid wormhole JSON: patchCount must be a non-negative number', {
|
|
337
346
|
code: 'E_INVALID_WORMHOLE_JSON',
|
|
@@ -340,11 +349,11 @@ export function deserializeWormhole(json) {
|
|
|
340
349
|
}
|
|
341
350
|
|
|
342
351
|
return {
|
|
343
|
-
fromSha: typedJson.fromSha,
|
|
344
|
-
toSha: typedJson.toSha,
|
|
345
|
-
writerId: typedJson.writerId,
|
|
346
|
-
patchCount: typedJson.patchCount,
|
|
347
|
-
payload: ProvenancePayload.fromJSON(typedJson.payload),
|
|
352
|
+
fromSha: /** @type {string} */ (typedJson.fromSha),
|
|
353
|
+
toSha: /** @type {string} */ (typedJson.toSha),
|
|
354
|
+
writerId: /** @type {string} */ (typedJson.writerId),
|
|
355
|
+
patchCount: /** @type {number} */ (typedJson.patchCount),
|
|
356
|
+
payload: ProvenancePayload.fromJSON(/** @type {import('./ProvenancePayload.js').PatchEntry[]} */ (typedJson.payload)),
|
|
348
357
|
};
|
|
349
358
|
}
|
|
350
359
|
|
|
@@ -14,7 +14,7 @@ import { recordIdPayload, signaturePayload } from './canonical.js';
|
|
|
14
14
|
/**
|
|
15
15
|
* Computes the record ID (SHA-256 hex digest) for a trust record.
|
|
16
16
|
*
|
|
17
|
-
* @param {Record<string,
|
|
17
|
+
* @param {Record<string, unknown>} record - Full trust record
|
|
18
18
|
* @returns {string} 64-character lowercase hex string
|
|
19
19
|
*/
|
|
20
20
|
export function computeRecordId(record) {
|
|
@@ -24,7 +24,7 @@ export function computeRecordId(record) {
|
|
|
24
24
|
/**
|
|
25
25
|
* Computes the signature payload as a Buffer (UTF-8 bytes).
|
|
26
26
|
*
|
|
27
|
-
* @param {Record<string,
|
|
27
|
+
* @param {Record<string, unknown>} record - Full trust record (signature will be stripped)
|
|
28
28
|
* @returns {Buffer} UTF-8 encoded bytes of the domain-separated canonical string
|
|
29
29
|
*/
|
|
30
30
|
export function computeSignaturePayload(record) {
|
|
@@ -34,7 +34,7 @@ export function computeSignaturePayload(record) {
|
|
|
34
34
|
/**
|
|
35
35
|
* Verifies that a record's recordId matches its content.
|
|
36
36
|
*
|
|
37
|
-
* @param {Record<string,
|
|
37
|
+
* @param {Record<string, unknown>} record - Trust record with `recordId` field
|
|
38
38
|
* @returns {boolean} true if recordId matches computed value
|
|
39
39
|
*/
|
|
40
40
|
export function verifyRecordId(record) {
|
|
@@ -16,6 +16,21 @@ import { deriveTrustVerdict } from './verdict.js';
|
|
|
16
16
|
* @typedef {import('./TrustStateBuilder.js').TrustState} TrustState
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} TrustAssessment
|
|
21
|
+
* @property {number} trustSchemaVersion
|
|
22
|
+
* @property {string} mode
|
|
23
|
+
* @property {string} trustVerdict
|
|
24
|
+
* @property {Object} trust
|
|
25
|
+
* @property {'configured'|'pinned'|'error'|'not_configured'} trust.status
|
|
26
|
+
* @property {string} trust.source
|
|
27
|
+
* @property {string|null} trust.sourceDetail
|
|
28
|
+
* @property {string[]} trust.evaluatedWriters
|
|
29
|
+
* @property {string[]} trust.untrustedWriters
|
|
30
|
+
* @property {ReadonlyArray<{writerId: string, trusted: boolean, reasonCode: string, reason: string}>} trust.explanations
|
|
31
|
+
* @property {Record<string, number> & {recordsScanned: number, activeKeys: number, revokedKeys: number, activeBindings: number, revokedBindings: number}} trust.evidenceSummary
|
|
32
|
+
*/
|
|
33
|
+
|
|
19
34
|
/**
|
|
20
35
|
* Evaluates trust status for a set of writers against the current trust state.
|
|
21
36
|
*
|
|
@@ -25,8 +40,8 @@ import { deriveTrustVerdict } from './verdict.js';
|
|
|
25
40
|
*
|
|
26
41
|
* @param {string[]} writerIds - Writer IDs to evaluate
|
|
27
42
|
* @param {TrustState} trustState - Built trust state from TrustStateBuilder
|
|
28
|
-
* @param {Record<string,
|
|
29
|
-
* @returns {
|
|
43
|
+
* @param {Record<string, unknown>} policy - Trust policy configuration
|
|
44
|
+
* @returns {TrustAssessment} Frozen TrustAssessment object
|
|
30
45
|
*/
|
|
31
46
|
export function evaluateWriters(writerIds, trustState, policy) {
|
|
32
47
|
const policyResult = TrustPolicySchema.safeParse(policy);
|
|
@@ -130,7 +145,7 @@ function evaluateSingleWriter(writerId, trustState) {
|
|
|
130
145
|
*
|
|
131
146
|
* @param {string[]} writerIds
|
|
132
147
|
* @param {string} reasonCode
|
|
133
|
-
* @returns {
|
|
148
|
+
* @returns {TrustAssessment}
|
|
134
149
|
*/
|
|
135
150
|
function buildErrorAssessment(writerIds, reasonCode) {
|
|
136
151
|
const sortedWriters = [...writerIds].sort();
|
|
@@ -22,8 +22,8 @@ import TrustError from '../errors/TrustError.js';
|
|
|
22
22
|
export class TrustRecordService {
|
|
23
23
|
/**
|
|
24
24
|
* @param {Object} options
|
|
25
|
-
* @param {
|
|
26
|
-
* @param {
|
|
25
|
+
* @param {import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default & import('../../ports/RefPort.js').default} options.persistence - GraphPersistencePort adapter
|
|
26
|
+
* @param {import('../../ports/CodecPort.js').default} options.codec - CodecPort adapter (CBOR)
|
|
27
27
|
*/
|
|
28
28
|
constructor({ persistence, codec }) {
|
|
29
29
|
this._persistence = persistence;
|
|
@@ -46,7 +46,7 @@ export class TrustRecordService {
|
|
|
46
46
|
* evaluation when the full key set is available.
|
|
47
47
|
*
|
|
48
48
|
* @param {string} graphName
|
|
49
|
-
* @param {Record<string,
|
|
49
|
+
* @param {Record<string, unknown>} record - Complete signed trust record
|
|
50
50
|
* @param {AppendOptions} [options]
|
|
51
51
|
* @returns {Promise<{commitSha: string, ref: string}>}
|
|
52
52
|
*/
|
|
@@ -95,7 +95,7 @@ export class TrustRecordService {
|
|
|
95
95
|
* @param {string} graphName
|
|
96
96
|
* @param {Object} [options]
|
|
97
97
|
* @param {string} [options.tip] - Override tip commit (for pinned reads)
|
|
98
|
-
* @returns {Promise<Array<Record<string,
|
|
98
|
+
* @returns {Promise<Array<Record<string, unknown>>>}
|
|
99
99
|
*/
|
|
100
100
|
async readRecords(graphName, options = {}) {
|
|
101
101
|
const ref = buildTrustRecordRef(graphName);
|
|
@@ -117,13 +117,16 @@ export class TrustRecordService {
|
|
|
117
117
|
|
|
118
118
|
while (current) {
|
|
119
119
|
const info = await this._persistence.getNodeInfo(current);
|
|
120
|
-
const
|
|
121
|
-
await this._persistence.
|
|
122
|
-
(await this._persistence.readTreeOids(
|
|
123
|
-
await this._persistence.getCommitTree(current),
|
|
124
|
-
))['record.cbor'],
|
|
125
|
-
),
|
|
120
|
+
const entries = await this._persistence.readTreeOids(
|
|
121
|
+
await this._persistence.getCommitTree(current),
|
|
126
122
|
);
|
|
123
|
+
const blobOid = entries['record.cbor'];
|
|
124
|
+
if (!blobOid) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
const record = /** @type {Record<string, unknown>} */ (this._codec.decode(
|
|
128
|
+
await this._persistence.readBlob(blobOid),
|
|
129
|
+
));
|
|
127
130
|
|
|
128
131
|
records.unshift(record);
|
|
129
132
|
|
|
@@ -145,7 +148,7 @@ export class TrustRecordService {
|
|
|
145
148
|
* - Each record passes schema validation
|
|
146
149
|
* - First record has prev=null
|
|
147
150
|
*
|
|
148
|
-
* @param {Array<Record<string,
|
|
151
|
+
* @param {Array<Record<string, unknown>>} records - Records in chain order (oldest first)
|
|
149
152
|
* @returns {{valid: boolean, errors: Array<{index: number, error: string}>}}
|
|
150
153
|
*/
|
|
151
154
|
verifyChain(records) {
|
|
@@ -177,7 +180,7 @@ export class TrustRecordService {
|
|
|
177
180
|
// Prev-link check
|
|
178
181
|
if (i === 0) {
|
|
179
182
|
if (record.prev !== null) {
|
|
180
|
-
errors.push({ index: i, error: `Genesis record must have prev=null, got ${record.prev}` });
|
|
183
|
+
errors.push({ index: i, error: `Genesis record must have prev=null, got ${JSON.stringify(record.prev)}` });
|
|
181
184
|
}
|
|
182
185
|
} else {
|
|
183
186
|
const expectedPrev = records[i - 1].recordId;
|
|
@@ -200,12 +203,13 @@ export class TrustRecordService {
|
|
|
200
203
|
* cryptographic verification — that requires the issuer's public key
|
|
201
204
|
* from the trust state, which is resolved during evaluation.
|
|
202
205
|
*
|
|
203
|
-
* @param {Record<string,
|
|
206
|
+
* @param {Record<string, unknown>} record
|
|
204
207
|
* @throws {TrustError} if signature envelope is missing or malformed
|
|
205
208
|
* @private
|
|
206
209
|
*/
|
|
207
210
|
_verifySignatureEnvelope(record) {
|
|
208
|
-
|
|
211
|
+
const sig = /** @type {Record<string, unknown>|undefined} */ (record.signature);
|
|
212
|
+
if (!sig || !sig.sig || !sig.alg) {
|
|
209
213
|
throw new TrustError(
|
|
210
214
|
'Trust record missing or malformed signature',
|
|
211
215
|
{ code: 'E_TRUST_SIGNATURE_MISSING' },
|
|
@@ -237,14 +241,14 @@ export class TrustRecordService {
|
|
|
237
241
|
return { tipSha, recordId: null };
|
|
238
242
|
}
|
|
239
243
|
|
|
240
|
-
const record = this._codec.decode(await this._persistence.readBlob(blobOid));
|
|
241
|
-
return { tipSha, recordId: record.recordId ?? null };
|
|
244
|
+
const record = /** @type {Record<string, unknown>} */ (this._codec.decode(await this._persistence.readBlob(blobOid)));
|
|
245
|
+
return { tipSha, recordId: /** @type {string|null} */ (record.recordId) ?? null };
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
/**
|
|
245
249
|
* Persists a trust record as a Git commit.
|
|
246
250
|
* @param {string} ref
|
|
247
|
-
* @param {Record<string,
|
|
251
|
+
* @param {Record<string, unknown>} record
|
|
248
252
|
* @param {string|null} parentSha - Resolved tip SHA (null for genesis)
|
|
249
253
|
* @returns {Promise<string>} commit SHA
|
|
250
254
|
* @private
|
|
@@ -252,16 +256,19 @@ export class TrustRecordService {
|
|
|
252
256
|
async _persistRecord(ref, record, parentSha) {
|
|
253
257
|
// Encode record as CBOR blob
|
|
254
258
|
const encoded = this._codec.encode(record);
|
|
255
|
-
|
|
259
|
+
// Buffer.from() ensures Uint8Array from codec is accepted by writeBlob
|
|
260
|
+
const blobOid = await this._persistence.writeBlob(Buffer.from(encoded));
|
|
256
261
|
|
|
257
|
-
// Create tree with single entry
|
|
258
|
-
const treeOid = await this._persistence.writeTree(
|
|
262
|
+
// Create tree with single entry (mktree format)
|
|
263
|
+
const treeOid = await this._persistence.writeTree([`100644 blob ${blobOid}\trecord.cbor`]);
|
|
259
264
|
|
|
260
265
|
const parents = parentSha ? [parentSha] : [];
|
|
261
|
-
const
|
|
266
|
+
const rType = typeof record.recordType === 'string' ? record.recordType : '';
|
|
267
|
+
const rId = typeof record.recordId === 'string' ? record.recordId.slice(0, 12) : '';
|
|
268
|
+
const message = `trust: ${rType} ${rId}`;
|
|
262
269
|
|
|
263
|
-
const commitSha = await this._persistence.
|
|
264
|
-
|
|
270
|
+
const commitSha = await this._persistence.commitNodeWithTree({
|
|
271
|
+
treeOid,
|
|
265
272
|
parents,
|
|
266
273
|
message,
|
|
267
274
|
});
|
|
@@ -12,6 +12,19 @@
|
|
|
12
12
|
|
|
13
13
|
import { TrustRecordSchema } from './schemas.js';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} TrustRecord
|
|
17
|
+
* @property {string} recordType - Record type (KEY_ADD, KEY_REVOKE, WRITER_BIND_ADD, WRITER_BIND_REVOKE)
|
|
18
|
+
* @property {string} recordId - Content-addressed record identifier
|
|
19
|
+
* @property {Record<string, string>} subject - Subject fields (keyId, publicKey, writerId, reasonCode vary by type)
|
|
20
|
+
* @property {string} issuedAt - ISO timestamp
|
|
21
|
+
* @property {number} schemaVersion
|
|
22
|
+
* @property {string} issuerKeyId
|
|
23
|
+
* @property {string|null} prev
|
|
24
|
+
* @property {{alg: string, sig: string}} signature
|
|
25
|
+
* @property {Record<string, unknown>} [meta]
|
|
26
|
+
*/
|
|
27
|
+
|
|
15
28
|
/**
|
|
16
29
|
* @typedef {Object} TrustState
|
|
17
30
|
* @property {Map<string, {publicKey: string, addedAt: string}>} activeKeys - keyId → key info
|
|
@@ -30,7 +43,7 @@ import { TrustRecordSchema } from './schemas.js';
|
|
|
30
43
|
* - Binding validity: WRITER_BIND_ADD requires the referenced key to be active
|
|
31
44
|
* - Schema validation: each record is validated against TrustRecordSchema
|
|
32
45
|
*
|
|
33
|
-
* @param {Array<Record<string,
|
|
46
|
+
* @param {Array<Record<string, unknown>>} records - Trust records in chain order
|
|
34
47
|
* @returns {TrustState} Frozen trust state
|
|
35
48
|
*/
|
|
36
49
|
export function buildState(records) {
|
|
@@ -49,13 +62,13 @@ export function buildState(records) {
|
|
|
49
62
|
const parsed = TrustRecordSchema.safeParse(record);
|
|
50
63
|
if (!parsed.success) {
|
|
51
64
|
errors.push({
|
|
52
|
-
recordId: record.recordId
|
|
65
|
+
recordId: typeof record.recordId === 'string' ? record.recordId : '(unknown)',
|
|
53
66
|
error: `Schema validation failed: ${parsed.error.message}`,
|
|
54
67
|
});
|
|
55
68
|
continue;
|
|
56
69
|
}
|
|
57
70
|
|
|
58
|
-
const rec = parsed.data;
|
|
71
|
+
const rec = /** @type {TrustRecord} */ (parsed.data);
|
|
59
72
|
processRecord(rec, activeKeys, revokedKeys, writerBindings, revokedBindings, errors);
|
|
60
73
|
}
|
|
61
74
|
|
|
@@ -63,7 +76,7 @@ export function buildState(records) {
|
|
|
63
76
|
}
|
|
64
77
|
|
|
65
78
|
/**
|
|
66
|
-
* @param {
|
|
79
|
+
* @param {TrustRecord} rec
|
|
67
80
|
* @param {Map<string, {publicKey: string, addedAt: string}>} activeKeys
|
|
68
81
|
* @param {Map<string, {publicKey: string, revokedAt: string, reasonCode: string}>} revokedKeys
|
|
69
82
|
* @param {Map<string, {keyId: string, boundAt: string}>} writerBindings
|
|
@@ -90,7 +103,7 @@ function processRecord(rec, activeKeys, revokedKeys, writerBindings, revokedBind
|
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
/**
|
|
93
|
-
* @param {
|
|
106
|
+
* @param {TrustRecord} rec
|
|
94
107
|
* @param {Map<string, {publicKey: string, addedAt: string}>} activeKeys
|
|
95
108
|
* @param {Map<string, {publicKey: string, revokedAt: string, reasonCode: string}>} revokedKeys
|
|
96
109
|
* @param {Array<{recordId: string, error: string}>} errors
|
|
@@ -118,7 +131,7 @@ function handleKeyAdd(rec, activeKeys, revokedKeys, errors) {
|
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
/**
|
|
121
|
-
* @param {
|
|
134
|
+
* @param {TrustRecord} rec
|
|
122
135
|
* @param {Map<string, {publicKey: string, addedAt: string}>} activeKeys
|
|
123
136
|
* @param {Map<string, {publicKey: string, revokedAt: string, reasonCode: string}>} revokedKeys
|
|
124
137
|
* @param {Array<{recordId: string, error: string}>} errors
|
|
@@ -152,7 +165,7 @@ function handleKeyRevoke(rec, activeKeys, revokedKeys, errors) {
|
|
|
152
165
|
}
|
|
153
166
|
|
|
154
167
|
/**
|
|
155
|
-
* @param {
|
|
168
|
+
* @param {TrustRecord} rec
|
|
156
169
|
* @param {Map<string, {publicKey: string, addedAt: string}>} activeKeys
|
|
157
170
|
* @param {Map<string, {publicKey: string, revokedAt: string, reasonCode: string}>} revokedKeys
|
|
158
171
|
* @param {Map<string, {keyId: string, boundAt: string}>} writerBindings
|
|
@@ -182,7 +195,7 @@ function handleBindAdd(rec, activeKeys, revokedKeys, writerBindings, errors) {
|
|
|
182
195
|
}
|
|
183
196
|
|
|
184
197
|
/**
|
|
185
|
-
* @param {
|
|
198
|
+
* @param {TrustRecord} rec
|
|
186
199
|
* @param {Map<string, {keyId: string, boundAt: string}>} writerBindings
|
|
187
200
|
* @param {Map<string, {keyId: string, revokedAt: string, reasonCode: string}>} revokedBindings
|
|
188
201
|
* @param {Array<{recordId: string, error: string}>} errors
|
|
@@ -24,8 +24,8 @@ export const TRUST_SIGN_DOMAIN = 'git-warp:trust-sign:v1\0';
|
|
|
24
24
|
* Returns the record payload used for recordId computation.
|
|
25
25
|
* Strips `recordId` and `signature` — these are derived, not inputs.
|
|
26
26
|
*
|
|
27
|
-
* @param {Record<string,
|
|
28
|
-
* @returns {Record<string,
|
|
27
|
+
* @param {Record<string, unknown>} record
|
|
28
|
+
* @returns {Record<string, unknown>}
|
|
29
29
|
*/
|
|
30
30
|
export function unsignedRecordForId(record) {
|
|
31
31
|
const out = { ...record };
|
|
@@ -38,8 +38,8 @@ export function unsignedRecordForId(record) {
|
|
|
38
38
|
* Returns the record payload used for signature computation.
|
|
39
39
|
* Strips `signature` only — `recordId` is included in signed payload.
|
|
40
40
|
*
|
|
41
|
-
* @param {Record<string,
|
|
42
|
-
* @returns {Record<string,
|
|
41
|
+
* @param {Record<string, unknown>} record
|
|
42
|
+
* @returns {Record<string, unknown>}
|
|
43
43
|
*/
|
|
44
44
|
export function unsignedRecordForSignature(record) {
|
|
45
45
|
const out = { ...record };
|
|
@@ -50,7 +50,7 @@ export function unsignedRecordForSignature(record) {
|
|
|
50
50
|
/**
|
|
51
51
|
* Computes the canonical string for recordId hashing.
|
|
52
52
|
*
|
|
53
|
-
* @param {Record<string,
|
|
53
|
+
* @param {Record<string, unknown>} record - Full record (recordId and signature will be stripped)
|
|
54
54
|
* @returns {string} Domain-separated canonical JSON string
|
|
55
55
|
*/
|
|
56
56
|
export function recordIdPayload(record) {
|
|
@@ -60,7 +60,7 @@ export function recordIdPayload(record) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Computes the canonical string for signature verification.
|
|
62
62
|
*
|
|
63
|
-
* @param {Record<string,
|
|
63
|
+
* @param {Record<string, unknown>} record - Full record (signature will be stripped)
|
|
64
64
|
* @returns {string} Domain-separated canonical JSON string
|
|
65
65
|
*/
|
|
66
66
|
export function signaturePayload(record) {
|
|
@@ -67,7 +67,7 @@ function validateOp(op, index) {
|
|
|
67
67
|
throw new Error(`ops[${index}] must be an object`);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const entry = /** @type {Record<string,
|
|
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) {
|
|
@@ -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
|
}
|
|
@@ -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);
|