@czap/core 0.1.0
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/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/addressed-digest.d.ts +15 -0
- package/dist/addressed-digest.d.ts.map +1 -0
- package/dist/addressed-digest.js +35 -0
- package/dist/addressed-digest.js.map +1 -0
- package/dist/animation.d.ts +46 -0
- package/dist/animation.d.ts.map +1 -0
- package/dist/animation.js +70 -0
- package/dist/animation.js.map +1 -0
- package/dist/assembly.d.ts +25 -0
- package/dist/assembly.d.ts.map +1 -0
- package/dist/assembly.js +58 -0
- package/dist/assembly.js.map +1 -0
- package/dist/av-bridge.d.ts +74 -0
- package/dist/av-bridge.d.ts.map +1 -0
- package/dist/av-bridge.js +107 -0
- package/dist/av-bridge.js.map +1 -0
- package/dist/av-renderer.d.ts +56 -0
- package/dist/av-renderer.d.ts.map +1 -0
- package/dist/av-renderer.js +65 -0
- package/dist/av-renderer.js.map +1 -0
- package/dist/blend.d.ts +61 -0
- package/dist/blend.d.ts.map +1 -0
- package/dist/blend.js +100 -0
- package/dist/blend.js.map +1 -0
- package/dist/boundary.d.ts +154 -0
- package/dist/boundary.d.ts.map +1 -0
- package/dist/boundary.js +269 -0
- package/dist/boundary.js.map +1 -0
- package/dist/brands.d.ts +63 -0
- package/dist/brands.d.ts.map +1 -0
- package/dist/brands.js +31 -0
- package/dist/brands.js.map +1 -0
- package/dist/caps.d.ts +49 -0
- package/dist/caps.d.ts.map +1 -0
- package/dist/caps.js +73 -0
- package/dist/caps.js.map +1 -0
- package/dist/capsule.d.ts +77 -0
- package/dist/capsule.d.ts.map +1 -0
- package/dist/capsule.js +18 -0
- package/dist/capsule.js.map +1 -0
- package/dist/capsules/boundary-evaluate.d.ts +28 -0
- package/dist/capsules/boundary-evaluate.d.ts.map +1 -0
- package/dist/capsules/boundary-evaluate.js +117 -0
- package/dist/capsules/boundary-evaluate.js.map +1 -0
- package/dist/capsules/canonical-cbor.d.ts +13 -0
- package/dist/capsules/canonical-cbor.d.ts.map +1 -0
- package/dist/capsules/canonical-cbor.js +60 -0
- package/dist/capsules/canonical-cbor.js.map +1 -0
- package/dist/capsules/token-buffer.d.ts +24 -0
- package/dist/capsules/token-buffer.d.ts.map +1 -0
- package/dist/capsules/token-buffer.js +53 -0
- package/dist/capsules/token-buffer.js.map +1 -0
- package/dist/capture.d.ts +40 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +10 -0
- package/dist/capture.js.map +1 -0
- package/dist/cbor.d.ts +33 -0
- package/dist/cbor.d.ts.map +1 -0
- package/dist/cbor.js +179 -0
- package/dist/cbor.js.map +1 -0
- package/dist/cell.d.ts +53 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +83 -0
- package/dist/cell.js.map +1 -0
- package/dist/codec.d.ts +30 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +25 -0
- package/dist/codec.js.map +1 -0
- package/dist/component.d.ts +52 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/component.js +44 -0
- package/dist/component.js.map +1 -0
- package/dist/composable.d.ts +76 -0
- package/dist/composable.d.ts.map +1 -0
- package/dist/composable.js +221 -0
- package/dist/composable.js.map +1 -0
- package/dist/compositor-pool.d.ts +74 -0
- package/dist/compositor-pool.d.ts.map +1 -0
- package/dist/compositor-pool.js +119 -0
- package/dist/compositor-pool.js.map +1 -0
- package/dist/compositor.d.ts +90 -0
- package/dist/compositor.d.ts.map +1 -0
- package/dist/compositor.js +278 -0
- package/dist/compositor.js.map +1 -0
- package/dist/config.d.ts +72 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +97 -0
- package/dist/config.js.map +1 -0
- package/dist/dag.d.ts +251 -0
- package/dist/dag.d.ts.map +1 -0
- package/dist/dag.js +450 -0
- package/dist/dag.js.map +1 -0
- package/dist/defaults.d.ts +45 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +45 -0
- package/dist/defaults.js.map +1 -0
- package/dist/derived.d.ts +34 -0
- package/dist/derived.d.ts.map +1 -0
- package/dist/derived.js +101 -0
- package/dist/derived.js.map +1 -0
- package/dist/diagnostics.d.ts +77 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +122 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/dirty.d.ts +55 -0
- package/dist/dirty.d.ts.map +1 -0
- package/dist/dirty.js +80 -0
- package/dist/dirty.js.map +1 -0
- package/dist/easing.d.ts +55 -0
- package/dist/easing.d.ts.map +1 -0
- package/dist/easing.js +291 -0
- package/dist/easing.js.map +1 -0
- package/dist/ecs.d.ts +105 -0
- package/dist/ecs.d.ts.map +1 -0
- package/dist/ecs.js +245 -0
- package/dist/ecs.js.map +1 -0
- package/dist/fnv.d.ts +14 -0
- package/dist/fnv.d.ts.map +1 -0
- package/dist/fnv.js +28 -0
- package/dist/fnv.js.map +1 -0
- package/dist/frame-budget.d.ts +73 -0
- package/dist/frame-budget.d.ts.map +1 -0
- package/dist/frame-budget.js +114 -0
- package/dist/frame-budget.js.map +1 -0
- package/dist/gen-frame.d.ts +102 -0
- package/dist/gen-frame.d.ts.map +1 -0
- package/dist/gen-frame.js +121 -0
- package/dist/gen-frame.js.map +1 -0
- package/dist/harness/arbitrary-from-schema.d.ts +28 -0
- package/dist/harness/arbitrary-from-schema.d.ts.map +1 -0
- package/dist/harness/arbitrary-from-schema.js +262 -0
- package/dist/harness/arbitrary-from-schema.js.map +1 -0
- package/dist/harness/cached-projection.d.ts +19 -0
- package/dist/harness/cached-projection.d.ts.map +1 -0
- package/dist/harness/cached-projection.js +39 -0
- package/dist/harness/cached-projection.js.map +1 -0
- package/dist/harness/index.d.ts +16 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +15 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/harness/policy-gate.d.ts +18 -0
- package/dist/harness/policy-gate.d.ts.map +1 -0
- package/dist/harness/policy-gate.js +46 -0
- package/dist/harness/policy-gate.js.map +1 -0
- package/dist/harness/pure-transform.d.ts +42 -0
- package/dist/harness/pure-transform.d.ts.map +1 -0
- package/dist/harness/pure-transform.js +76 -0
- package/dist/harness/pure-transform.js.map +1 -0
- package/dist/harness/receipted-mutation.d.ts +23 -0
- package/dist/harness/receipted-mutation.d.ts.map +1 -0
- package/dist/harness/receipted-mutation.js +52 -0
- package/dist/harness/receipted-mutation.js.map +1 -0
- package/dist/harness/scene-composition.d.ts +19 -0
- package/dist/harness/scene-composition.d.ts.map +1 -0
- package/dist/harness/scene-composition.js +47 -0
- package/dist/harness/scene-composition.js.map +1 -0
- package/dist/harness/site-adapter.d.ts +18 -0
- package/dist/harness/site-adapter.d.ts.map +1 -0
- package/dist/harness/site-adapter.js +38 -0
- package/dist/harness/site-adapter.js.map +1 -0
- package/dist/harness/state-machine.d.ts +19 -0
- package/dist/harness/state-machine.d.ts.map +1 -0
- package/dist/harness/state-machine.js +44 -0
- package/dist/harness/state-machine.js.map +1 -0
- package/dist/hlc.d.ts +99 -0
- package/dist/hlc.d.ts.map +1 -0
- package/dist/hlc.js +219 -0
- package/dist/hlc.js.map +1 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +137 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolate.d.ts +14 -0
- package/dist/interpolate.d.ts.map +1 -0
- package/dist/interpolate.js +31 -0
- package/dist/interpolate.js.map +1 -0
- package/dist/live-cell.d.ts +46 -0
- package/dist/live-cell.d.ts.map +1 -0
- package/dist/live-cell.js +154 -0
- package/dist/live-cell.js.map +1 -0
- package/dist/op.d.ts +58 -0
- package/dist/op.d.ts.map +1 -0
- package/dist/op.js +171 -0
- package/dist/op.js.map +1 -0
- package/dist/plan.d.ts +195 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +211 -0
- package/dist/plan.js.map +1 -0
- package/dist/protocol.d.ts +33 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +10 -0
- package/dist/protocol.js.map +1 -0
- package/dist/quantizer-types.d.ts +28 -0
- package/dist/quantizer-types.d.ts.map +1 -0
- package/dist/quantizer-types.js +9 -0
- package/dist/quantizer-types.js.map +1 -0
- package/dist/receipt.d.ts +294 -0
- package/dist/receipt.d.ts.map +1 -0
- package/dist/receipt.js +352 -0
- package/dist/receipt.js.map +1 -0
- package/dist/runtime-coordinator.d.ts +75 -0
- package/dist/runtime-coordinator.d.ts.map +1 -0
- package/dist/runtime-coordinator.js +149 -0
- package/dist/runtime-coordinator.js.map +1 -0
- package/dist/scheduler.d.ts +58 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +109 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/ship-capsule.d.ts +54 -0
- package/dist/ship-capsule.d.ts.map +1 -0
- package/dist/ship-capsule.js +142 -0
- package/dist/ship-capsule.js.map +1 -0
- package/dist/ship-manifest.d.ts +45 -0
- package/dist/ship-manifest.d.ts.map +1 -0
- package/dist/ship-manifest.js +175 -0
- package/dist/ship-manifest.js.map +1 -0
- package/dist/signal.d.ts +149 -0
- package/dist/signal.d.ts.map +1 -0
- package/dist/signal.js +277 -0
- package/dist/signal.js.map +1 -0
- package/dist/speculative.d.ts +67 -0
- package/dist/speculative.d.ts.map +1 -0
- package/dist/speculative.js +139 -0
- package/dist/speculative.js.map +1 -0
- package/dist/store.d.ts +39 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +42 -0
- package/dist/store.js.map +1 -0
- package/dist/style.d.ts +119 -0
- package/dist/style.d.ts.map +1 -0
- package/dist/style.js +168 -0
- package/dist/style.js.map +1 -0
- package/dist/testing.d.ts +14 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +14 -0
- package/dist/testing.js.map +1 -0
- package/dist/theme.d.ts +78 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +109 -0
- package/dist/theme.js.map +1 -0
- package/dist/timeline.d.ts +45 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +101 -0
- package/dist/timeline.js.map +1 -0
- package/dist/token-buffer.d.ts +43 -0
- package/dist/token-buffer.d.ts.map +1 -0
- package/dist/token-buffer.js +112 -0
- package/dist/token-buffer.js.map +1 -0
- package/dist/token.d.ts +107 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +143 -0
- package/dist/token.js.map +1 -0
- package/dist/tuple.d.ts +16 -0
- package/dist/tuple.d.ts.map +1 -0
- package/dist/tuple.js +16 -0
- package/dist/tuple.js.map +1 -0
- package/dist/type-utils.d.ts +41 -0
- package/dist/type-utils.d.ts.map +1 -0
- package/dist/type-utils.js +10 -0
- package/dist/type-utils.js.map +1 -0
- package/dist/typed-ref.d.ts +50 -0
- package/dist/typed-ref.d.ts.map +1 -0
- package/dist/typed-ref.js +59 -0
- package/dist/typed-ref.js.map +1 -0
- package/dist/ui-quality.d.ts +50 -0
- package/dist/ui-quality.d.ts.map +1 -0
- package/dist/ui-quality.js +64 -0
- package/dist/ui-quality.js.map +1 -0
- package/dist/validation-error.d.ts +25 -0
- package/dist/validation-error.d.ts.map +1 -0
- package/dist/validation-error.js +32 -0
- package/dist/validation-error.js.map +1 -0
- package/dist/vector-clock.d.ts +46 -0
- package/dist/vector-clock.d.ts.map +1 -0
- package/dist/vector-clock.js +91 -0
- package/dist/vector-clock.js.map +1 -0
- package/dist/video.d.ts +62 -0
- package/dist/video.d.ts.map +1 -0
- package/dist/video.js +59 -0
- package/dist/video.js.map +1 -0
- package/dist/wasm-dispatch.d.ts +52 -0
- package/dist/wasm-dispatch.d.ts.map +1 -0
- package/dist/wasm-dispatch.js +204 -0
- package/dist/wasm-dispatch.js.map +1 -0
- package/dist/wasm-fallback.d.ts +19 -0
- package/dist/wasm-fallback.d.ts.map +1 -0
- package/dist/wasm-fallback.js +93 -0
- package/dist/wasm-fallback.js.map +1 -0
- package/dist/wire.d.ts +49 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +201 -0
- package/dist/wire.js.map +1 -0
- package/dist/zap.d.ts +42 -0
- package/dist/zap.d.ts.map +1 -0
- package/dist/zap.js +172 -0
- package/dist/zap.js.map +1 -0
- package/package.json +71 -0
- package/src/addressed-digest.ts +48 -0
- package/src/animation.ts +103 -0
- package/src/assembly.ts +76 -0
- package/src/av-bridge.ts +161 -0
- package/src/av-renderer.ts +118 -0
- package/src/blend.ts +135 -0
- package/src/boundary.ts +363 -0
- package/src/brands.ts +86 -0
- package/src/caps.ts +100 -0
- package/src/capsule.ts +95 -0
- package/src/capsules/boundary-evaluate.ts +128 -0
- package/src/capsules/canonical-cbor.ts +60 -0
- package/src/capsules/token-buffer.ts +57 -0
- package/src/capture.ts +48 -0
- package/src/cbor.ts +199 -0
- package/src/cell.ts +130 -0
- package/src/codec.ts +39 -0
- package/src/component.ts +102 -0
- package/src/composable.ts +328 -0
- package/src/compositor-pool.ts +162 -0
- package/src/compositor.ts +387 -0
- package/src/config.ts +157 -0
- package/src/dag.ts +527 -0
- package/src/defaults.ts +60 -0
- package/src/derived.ts +164 -0
- package/src/diagnostics.ts +186 -0
- package/src/dirty.ts +101 -0
- package/src/easing.ts +334 -0
- package/src/ecs.ts +382 -0
- package/src/fnv.ts +31 -0
- package/src/frame-budget.ts +149 -0
- package/src/gen-frame.ts +229 -0
- package/src/harness/arbitrary-from-schema.ts +270 -0
- package/src/harness/cached-projection.ts +46 -0
- package/src/harness/index.ts +16 -0
- package/src/harness/policy-gate.ts +51 -0
- package/src/harness/pure-transform.ts +121 -0
- package/src/harness/receipted-mutation.ts +59 -0
- package/src/harness/scene-composition.ts +54 -0
- package/src/harness/site-adapter.ts +43 -0
- package/src/harness/state-machine.ts +49 -0
- package/src/hlc.ts +238 -0
- package/src/index.ts +274 -0
- package/src/interpolate.ts +37 -0
- package/src/live-cell.ts +199 -0
- package/src/op.ts +233 -0
- package/src/plan.ts +317 -0
- package/src/protocol.ts +49 -0
- package/src/quantizer-types.ts +29 -0
- package/src/receipt.ts +444 -0
- package/src/runtime-coordinator.ts +230 -0
- package/src/scheduler.ts +161 -0
- package/src/ship-capsule.ts +191 -0
- package/src/signal.ts +345 -0
- package/src/speculative.ts +186 -0
- package/src/store.ts +77 -0
- package/src/style.ts +249 -0
- package/src/testing.ts +14 -0
- package/src/theme.ts +153 -0
- package/src/timeline.ts +146 -0
- package/src/token-buffer.ts +151 -0
- package/src/token.ts +197 -0
- package/src/tuple.ts +19 -0
- package/src/type-utils.ts +48 -0
- package/src/typed-ref.ts +79 -0
- package/src/ui-quality.ts +105 -0
- package/src/validation-error.ts +34 -0
- package/src/vector-clock.ts +111 -0
- package/src/video.ts +106 -0
- package/src/wasm-dispatch.ts +300 -0
- package/src/wasm-fallback.ts +102 -0
- package/src/wire.ts +274 -0
- package/src/zap.ts +241 -0
package/dist/dag.d.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAG -- receipt DAG merge and canonical linearization.
|
|
3
|
+
*
|
|
4
|
+
* Salvaged from `@kit/core`.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { ReceiptEnvelope } from './receipt.js';
|
|
9
|
+
/** Single vertex in a {@link ReceiptDAG}: an envelope plus its parent and child hashes. */
|
|
10
|
+
export interface DAGNode {
|
|
11
|
+
readonly envelope: ReceiptEnvelope;
|
|
12
|
+
readonly parents: ReadonlyArray<string>;
|
|
13
|
+
readonly children: ReadonlyArray<string>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Immutable snapshot of the receipt DAG: the set of known nodes, the current
|
|
17
|
+
* head(s), and the genesis anchor if any.
|
|
18
|
+
*/
|
|
19
|
+
export interface ReceiptDAG {
|
|
20
|
+
readonly nodes: ReadonlyMap<string, DAGNode>;
|
|
21
|
+
readonly heads: ReadonlyArray<string>;
|
|
22
|
+
readonly genesis: string | null;
|
|
23
|
+
}
|
|
24
|
+
/** Result of a DAG merge: the updated graph, the hashes that were newly added, and whether a fork was observed. */
|
|
25
|
+
export interface MergeResult {
|
|
26
|
+
readonly dag: ReceiptDAG;
|
|
27
|
+
readonly added: ReadonlyArray<string>;
|
|
28
|
+
readonly forked: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Detail record describing a single-writer fork-rule violation. */
|
|
31
|
+
export interface ForkViolation {
|
|
32
|
+
readonly actor: string;
|
|
33
|
+
readonly prevHash: string;
|
|
34
|
+
readonly existing: string;
|
|
35
|
+
readonly attempted: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create an empty receipt DAG with no nodes or heads.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const dag = DAG.empty();
|
|
43
|
+
* // dag.nodes.size === 0
|
|
44
|
+
* // dag.heads.length === 0
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare const empty: () => ReceiptDAG;
|
|
48
|
+
/**
|
|
49
|
+
* Ingest a single receipt envelope into the DAG.
|
|
50
|
+
*
|
|
51
|
+
* Adds the envelope as a node, wires parent/child edges, and recalculates
|
|
52
|
+
* head nodes. Idempotent -- returns the same DAG if the hash already exists.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* let dag = DAG.empty();
|
|
57
|
+
* dag = DAG.ingest(dag, envelope);
|
|
58
|
+
* // dag.nodes.size === 1
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare const ingest: (dag: ReceiptDAG, envelope: ReceiptEnvelope) => ReceiptDAG;
|
|
62
|
+
/**
|
|
63
|
+
* Ingest multiple receipt envelopes into the DAG in order.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* const dag = DAG.ingestAll(DAG.empty(), [envelope1, envelope2]);
|
|
68
|
+
* // dag.nodes.size === 2
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare const ingestAll: (dag: ReceiptDAG, envelopes: ReadonlyArray<ReceiptEnvelope>) => ReceiptDAG;
|
|
72
|
+
/**
|
|
73
|
+
* Build a DAG from an array of receipt envelopes.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const dag = DAG.fromReceipts(envelopes);
|
|
78
|
+
* // dag.nodes.size === envelopes.length
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare const fromReceipts: (envelopes: ReadonlyArray<ReceiptEnvelope>) => ReceiptDAG;
|
|
82
|
+
/**
|
|
83
|
+
* Check whether ingesting an envelope would violate the anti-fork rule.
|
|
84
|
+
*
|
|
85
|
+
* The anti-fork rule prevents a single actor from creating two children
|
|
86
|
+
* of the same parent node. Returns a ForkViolation descriptor or null.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const violation = DAG.checkForkRule(dag, envelope);
|
|
91
|
+
* if (violation) {
|
|
92
|
+
* console.error(`Fork by actor ${violation.actor}`);
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare const checkForkRule: (dag: ReceiptDAG, envelope: ReceiptEnvelope) => ForkViolation | null;
|
|
97
|
+
/**
|
|
98
|
+
* Produce a deterministic topological ordering of all envelopes in the DAG.
|
|
99
|
+
*
|
|
100
|
+
* Kahn's algorithm with stable ordering: sortedInsert maintains tiebreak order in the
|
|
101
|
+
* ready queue, guaranteeing deterministic topological sort across replicas.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* const dag = DAG.fromReceipts(envelopes);
|
|
106
|
+
* const ordered = DAG.linearize(dag);
|
|
107
|
+
* // ordered is a deterministic total order of all envelopes
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare const linearize: (dag: ReceiptDAG) => ReadonlyArray<ReceiptEnvelope>;
|
|
111
|
+
/**
|
|
112
|
+
* Linearize the DAG and return only envelopes after a given hash.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const newEntries = DAG.linearizeFrom(dag, lastSeenHash);
|
|
117
|
+
* // newEntries contains only envelopes after lastSeenHash
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export declare const linearizeFrom: (dag: ReceiptDAG, afterHash: string) => ReadonlyArray<ReceiptEnvelope>;
|
|
121
|
+
/**
|
|
122
|
+
* Get all head (childless) envelopes in the DAG.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const heads = DAG.getHeads(dag);
|
|
127
|
+
* // heads.length > 0 for non-empty DAGs
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export declare const getHeads: (dag: ReceiptDAG) => ReadonlyArray<ReceiptEnvelope>;
|
|
131
|
+
/**
|
|
132
|
+
* Get the single canonical head of the DAG via deterministic tiebreaking.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* const head = DAG.canonicalHead(dag);
|
|
137
|
+
* // head is the deterministically chosen head envelope, or null if empty
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare const canonicalHead: (dag: ReceiptDAG) => ReceiptEnvelope | null;
|
|
141
|
+
/**
|
|
142
|
+
* Check whether the DAG has multiple heads (i.e., is in a forked state).
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* if (DAG.isFork(dag)) {
|
|
147
|
+
* console.log('DAG has diverged, needs merge');
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export declare const isFork: (dag: ReceiptDAG) => boolean;
|
|
152
|
+
/**
|
|
153
|
+
* Get all ancestor hashes of a given node (transitive parents).
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* const anc = DAG.ancestors(dag, headHash);
|
|
158
|
+
* // anc contains all hashes reachable by following parent edges
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export declare const ancestors: (dag: ReceiptDAG, hash: string) => ReadonlyArray<string>;
|
|
162
|
+
/**
|
|
163
|
+
* Check whether node `a` is an ancestor of node `b` in the DAG.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* const yes = DAG.isAncestor(dag, genesisHash, headHash);
|
|
168
|
+
* // yes === true (genesis is ancestor of everything)
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export declare const isAncestor: (dag: ReceiptDAG, a: string, b: string) => boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Find the latest common ancestor of two nodes in the DAG.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* const lca = DAG.commonAncestor(dag, hashA, hashB);
|
|
178
|
+
* // lca is the hash of the most recent shared ancestor, or null
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export declare const commonAncestor: (dag: ReceiptDAG, a: string, b: string) => string | null;
|
|
182
|
+
/**
|
|
183
|
+
* Return the number of nodes in the DAG.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* const n = DAG.size(dag);
|
|
188
|
+
* // n === dag.nodes.size
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export declare const size: (dag: ReceiptDAG) => number;
|
|
192
|
+
/**
|
|
193
|
+
* Merge remote envelopes into a local DAG, enforcing the anti-fork rule.
|
|
194
|
+
*
|
|
195
|
+
* Returns the updated DAG, list of newly added hashes, and whether the
|
|
196
|
+
* result is forked. Throws on anti-fork violations.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* const result = DAG.merge(localDag, remoteEnvelopes);
|
|
201
|
+
* // result.dag -- updated DAG
|
|
202
|
+
* // result.added -- newly ingested hashes
|
|
203
|
+
* // result.forked -- true if DAG has multiple heads
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export declare const merge: (local: ReceiptDAG, remote: ReadonlyArray<ReceiptEnvelope>) => MergeResult;
|
|
207
|
+
/**
|
|
208
|
+
* DAG namespace -- receipt DAG merge and canonical linearization.
|
|
209
|
+
*
|
|
210
|
+
* Build, query, and merge directed acyclic graphs of receipt envelopes.
|
|
211
|
+
* Supports deterministic linearization, fork detection, ancestor queries,
|
|
212
|
+
* and anti-fork rule enforcement.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* import { DAG } from '@czap/core';
|
|
217
|
+
*
|
|
218
|
+
* const dag = DAG.fromReceipts(envelopes);
|
|
219
|
+
* const ordered = DAG.linearize(dag);
|
|
220
|
+
* const forked = DAG.isFork(dag);
|
|
221
|
+
* const result = DAG.merge(dag, remoteEnvelopes);
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
export declare const DAG: {
|
|
225
|
+
empty: () => ReceiptDAG;
|
|
226
|
+
ingest: (dag: ReceiptDAG, envelope: ReceiptEnvelope) => ReceiptDAG;
|
|
227
|
+
ingestAll: (dag: ReceiptDAG, envelopes: ReadonlyArray<ReceiptEnvelope>) => ReceiptDAG;
|
|
228
|
+
fromReceipts: (envelopes: ReadonlyArray<ReceiptEnvelope>) => ReceiptDAG;
|
|
229
|
+
checkForkRule: (dag: ReceiptDAG, envelope: ReceiptEnvelope) => ForkViolation | null;
|
|
230
|
+
linearize: (dag: ReceiptDAG) => ReadonlyArray<ReceiptEnvelope>;
|
|
231
|
+
linearizeFrom: (dag: ReceiptDAG, afterHash: string) => ReadonlyArray<ReceiptEnvelope>;
|
|
232
|
+
getHeads: (dag: ReceiptDAG) => ReadonlyArray<ReceiptEnvelope>;
|
|
233
|
+
canonicalHead: (dag: ReceiptDAG) => ReceiptEnvelope | null;
|
|
234
|
+
isFork: (dag: ReceiptDAG) => boolean;
|
|
235
|
+
ancestors: (dag: ReceiptDAG, hash: string) => ReadonlyArray<string>;
|
|
236
|
+
isAncestor: (dag: ReceiptDAG, a: string, b: string) => boolean;
|
|
237
|
+
commonAncestor: (dag: ReceiptDAG, a: string, b: string) => string | null;
|
|
238
|
+
size: (dag: ReceiptDAG) => number;
|
|
239
|
+
merge: (local: ReceiptDAG, remote: ReadonlyArray<ReceiptEnvelope>) => MergeResult;
|
|
240
|
+
};
|
|
241
|
+
export declare namespace DAG {
|
|
242
|
+
/** Alias for {@link DAGNode}. */
|
|
243
|
+
type Node = DAGNode;
|
|
244
|
+
/** Alias for {@link ReceiptDAG}. */
|
|
245
|
+
type Graph = ReceiptDAG;
|
|
246
|
+
/** Alias for {@link MergeResult}. */
|
|
247
|
+
type Merge = MergeResult;
|
|
248
|
+
/** Alias for {@link ForkViolation}. */
|
|
249
|
+
type Fork = ForkViolation;
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=dag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dag.d.ts","sourceRoot":"","sources":["../src/dag.ts"],"names":[],"mappings":"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"}
|
package/dist/dag.js
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAG -- receipt DAG merge and canonical linearization.
|
|
3
|
+
*
|
|
4
|
+
* Salvaged from `@kit/core`.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { compare as hlcCompare } from './hlc.js';
|
|
9
|
+
import { GENESIS } from './receipt.js';
|
|
10
|
+
const parentsOf = (envelope) => {
|
|
11
|
+
if (Array.isArray(envelope.previous)) {
|
|
12
|
+
return envelope.previous.filter((p) => p !== GENESIS);
|
|
13
|
+
}
|
|
14
|
+
return envelope.previous === GENESIS ? [] : [envelope.previous];
|
|
15
|
+
};
|
|
16
|
+
const actorOf = (envelope) => envelope.subject.id;
|
|
17
|
+
/**
|
|
18
|
+
* Create an empty receipt DAG with no nodes or heads.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const dag = DAG.empty();
|
|
23
|
+
* // dag.nodes.size === 0
|
|
24
|
+
* // dag.heads.length === 0
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const empty = () => ({
|
|
28
|
+
nodes: new Map(),
|
|
29
|
+
heads: [],
|
|
30
|
+
genesis: null,
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Ingest a single receipt envelope into the DAG.
|
|
34
|
+
*
|
|
35
|
+
* Adds the envelope as a node, wires parent/child edges, and recalculates
|
|
36
|
+
* head nodes. Idempotent -- returns the same DAG if the hash already exists.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* let dag = DAG.empty();
|
|
41
|
+
* dag = DAG.ingest(dag, envelope);
|
|
42
|
+
* // dag.nodes.size === 1
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export const ingest = (dag, envelope) => {
|
|
46
|
+
const hash = envelope.hash;
|
|
47
|
+
if (dag.nodes.has(hash))
|
|
48
|
+
return dag;
|
|
49
|
+
const parents = parentsOf(envelope);
|
|
50
|
+
const newNode = { envelope, parents, children: [] };
|
|
51
|
+
const newNodes = new Map(dag.nodes);
|
|
52
|
+
newNodes.set(hash, newNode);
|
|
53
|
+
for (const parentHash of parents) {
|
|
54
|
+
const parentNode = newNodes.get(parentHash);
|
|
55
|
+
if (parentNode) {
|
|
56
|
+
newNodes.set(parentHash, { ...parentNode, children: [...parentNode.children, hash] });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const [existingHash, existingNode] of dag.nodes) {
|
|
60
|
+
if (existingNode.parents.includes(hash)) {
|
|
61
|
+
const updatedNewNode = newNodes.get(hash);
|
|
62
|
+
newNodes.set(hash, { ...updatedNewNode, children: [...updatedNewNode.children, existingHash] });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const heads = [];
|
|
66
|
+
for (const [h, node] of newNodes) {
|
|
67
|
+
if (node.children.length === 0)
|
|
68
|
+
heads.push(h);
|
|
69
|
+
}
|
|
70
|
+
const isGenesisNode = envelope.previous === GENESIS ||
|
|
71
|
+
(Array.isArray(envelope.previous) && envelope.previous.includes(GENESIS));
|
|
72
|
+
const genesis = isGenesisNode ? hash : dag.genesis;
|
|
73
|
+
return { nodes: newNodes, heads, genesis };
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Ingest multiple receipt envelopes into the DAG in order.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* const dag = DAG.ingestAll(DAG.empty(), [envelope1, envelope2]);
|
|
81
|
+
* // dag.nodes.size === 2
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const ingestAll = (dag, envelopes) => envelopes.reduce((d, e) => ingest(d, e), dag);
|
|
85
|
+
/**
|
|
86
|
+
* Build a DAG from an array of receipt envelopes.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const dag = DAG.fromReceipts(envelopes);
|
|
91
|
+
* // dag.nodes.size === envelopes.length
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export const fromReceipts = (envelopes) => ingestAll(empty(), envelopes);
|
|
95
|
+
/**
|
|
96
|
+
* Check whether ingesting an envelope would violate the anti-fork rule.
|
|
97
|
+
*
|
|
98
|
+
* The anti-fork rule prevents a single actor from creating two children
|
|
99
|
+
* of the same parent node. Returns a ForkViolation descriptor or null.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* const violation = DAG.checkForkRule(dag, envelope);
|
|
104
|
+
* if (violation) {
|
|
105
|
+
* console.error(`Fork by actor ${violation.actor}`);
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export const checkForkRule = (dag, envelope) => {
|
|
110
|
+
if (Array.isArray(envelope.previous))
|
|
111
|
+
return null;
|
|
112
|
+
const prevHash = envelope.previous;
|
|
113
|
+
const actor = actorOf(envelope);
|
|
114
|
+
const attemptedHash = envelope.hash;
|
|
115
|
+
if (prevHash === GENESIS)
|
|
116
|
+
return null;
|
|
117
|
+
const parentNode = dag.nodes.get(prevHash);
|
|
118
|
+
if (!parentNode)
|
|
119
|
+
return null;
|
|
120
|
+
for (const childHash of parentNode.children) {
|
|
121
|
+
const childNode = dag.nodes.get(childHash);
|
|
122
|
+
if (childNode && actorOf(childNode.envelope) === actor && childHash !== attemptedHash) {
|
|
123
|
+
return { actor, prevHash, existing: childHash, attempted: attemptedHash };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
};
|
|
128
|
+
// Total ordering: HLC first (causal), then actor ID (deterministic across nodes),
|
|
129
|
+
// then hash (content-based last resort). Ensures identical linearization on every replica.
|
|
130
|
+
const tiebreak = (a, b) => {
|
|
131
|
+
const hlcCmp = hlcCompare(a.timestamp, b.timestamp);
|
|
132
|
+
if (hlcCmp !== 0)
|
|
133
|
+
return hlcCmp;
|
|
134
|
+
const actorA = actorOf(a);
|
|
135
|
+
const actorB = actorOf(b);
|
|
136
|
+
if (actorA < actorB)
|
|
137
|
+
return -1;
|
|
138
|
+
if (actorA > actorB)
|
|
139
|
+
return 1;
|
|
140
|
+
return +(a.hash > b.hash) - +(a.hash < b.hash);
|
|
141
|
+
};
|
|
142
|
+
const sortedInsert = (arr, item, cmp) => {
|
|
143
|
+
let lo = 0;
|
|
144
|
+
let hi = arr.length;
|
|
145
|
+
while (lo < hi) {
|
|
146
|
+
const mid = (lo + hi) >>> 1;
|
|
147
|
+
if (cmp(arr[mid], item) <= 0)
|
|
148
|
+
lo = mid + 1;
|
|
149
|
+
else
|
|
150
|
+
hi = mid;
|
|
151
|
+
}
|
|
152
|
+
arr.splice(lo, 0, item);
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Produce a deterministic topological ordering of all envelopes in the DAG.
|
|
156
|
+
*
|
|
157
|
+
* Kahn's algorithm with stable ordering: sortedInsert maintains tiebreak order in the
|
|
158
|
+
* ready queue, guaranteeing deterministic topological sort across replicas.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* const dag = DAG.fromReceipts(envelopes);
|
|
163
|
+
* const ordered = DAG.linearize(dag);
|
|
164
|
+
* // ordered is a deterministic total order of all envelopes
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export const linearize = (dag) => {
|
|
168
|
+
if (dag.nodes.size === 0)
|
|
169
|
+
return [];
|
|
170
|
+
const inDegree = new Map();
|
|
171
|
+
for (const [hash, node] of dag.nodes) {
|
|
172
|
+
let degree = 0;
|
|
173
|
+
for (const parentHash of node.parents) {
|
|
174
|
+
if (dag.nodes.has(parentHash))
|
|
175
|
+
degree++;
|
|
176
|
+
}
|
|
177
|
+
inDegree.set(hash, degree);
|
|
178
|
+
}
|
|
179
|
+
const ready = [];
|
|
180
|
+
for (const [hash, degree] of inDegree) {
|
|
181
|
+
if (degree === 0) {
|
|
182
|
+
const node = dag.nodes.get(hash);
|
|
183
|
+
sortedInsert(ready, node.envelope, tiebreak);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const result = [];
|
|
187
|
+
while (ready.length > 0) {
|
|
188
|
+
const envelope = ready.shift();
|
|
189
|
+
result.push(envelope);
|
|
190
|
+
const node = dag.nodes.get(envelope.hash);
|
|
191
|
+
for (const childHash of node.children) {
|
|
192
|
+
const childDegree = inDegree.get(childHash);
|
|
193
|
+
if (childDegree !== undefined) {
|
|
194
|
+
const newDegree = childDegree - 1;
|
|
195
|
+
inDegree.set(childHash, newDegree);
|
|
196
|
+
if (newDegree === 0) {
|
|
197
|
+
const childNode = dag.nodes.get(childHash);
|
|
198
|
+
sortedInsert(ready, childNode.envelope, tiebreak);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Linearize the DAG and return only envelopes after a given hash.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* const newEntries = DAG.linearizeFrom(dag, lastSeenHash);
|
|
211
|
+
* // newEntries contains only envelopes after lastSeenHash
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export const linearizeFrom = (dag, afterHash) => {
|
|
215
|
+
const full = linearize(dag);
|
|
216
|
+
const idx = full.findIndex((e) => e.hash === afterHash);
|
|
217
|
+
if (idx === -1)
|
|
218
|
+
return full;
|
|
219
|
+
return full.slice(idx + 1);
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Get all head (childless) envelopes in the DAG.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* const heads = DAG.getHeads(dag);
|
|
227
|
+
* // heads.length > 0 for non-empty DAGs
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
export const getHeads = (dag) => {
|
|
231
|
+
const result = [];
|
|
232
|
+
for (const hash of dag.heads) {
|
|
233
|
+
const node = dag.nodes.get(hash);
|
|
234
|
+
if (node)
|
|
235
|
+
result.push(node.envelope);
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Get the single canonical head of the DAG via deterministic tiebreaking.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* const head = DAG.canonicalHead(dag);
|
|
245
|
+
* // head is the deterministically chosen head envelope, or null if empty
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
export const canonicalHead = (dag) => {
|
|
249
|
+
const heads = getHeads(dag);
|
|
250
|
+
if (heads.length === 0)
|
|
251
|
+
return null;
|
|
252
|
+
if (heads.length === 1)
|
|
253
|
+
return heads[0];
|
|
254
|
+
const sorted = [...heads].sort(tiebreak);
|
|
255
|
+
return sorted[0];
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Check whether the DAG has multiple heads (i.e., is in a forked state).
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* if (DAG.isFork(dag)) {
|
|
263
|
+
* console.log('DAG has diverged, needs merge');
|
|
264
|
+
* }
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
export const isFork = (dag) => dag.heads.length > 1;
|
|
268
|
+
/**
|
|
269
|
+
* Get all ancestor hashes of a given node (transitive parents).
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```ts
|
|
273
|
+
* const anc = DAG.ancestors(dag, headHash);
|
|
274
|
+
* // anc contains all hashes reachable by following parent edges
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
export const ancestors = (dag, hash) => {
|
|
278
|
+
const visited = new Set();
|
|
279
|
+
const stack = [];
|
|
280
|
+
const node = dag.nodes.get(hash);
|
|
281
|
+
if (!node)
|
|
282
|
+
return [];
|
|
283
|
+
for (const parentHash of node.parents) {
|
|
284
|
+
if (dag.nodes.has(parentHash))
|
|
285
|
+
stack.push(parentHash);
|
|
286
|
+
}
|
|
287
|
+
while (stack.length > 0) {
|
|
288
|
+
const current = stack.pop();
|
|
289
|
+
if (visited.has(current))
|
|
290
|
+
continue;
|
|
291
|
+
visited.add(current);
|
|
292
|
+
const currentNode = dag.nodes.get(current);
|
|
293
|
+
for (const parentHash of currentNode.parents) {
|
|
294
|
+
if (dag.nodes.has(parentHash) && !visited.has(parentHash))
|
|
295
|
+
stack.push(parentHash);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return Array.from(visited);
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* Check whether node `a` is an ancestor of node `b` in the DAG.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* const yes = DAG.isAncestor(dag, genesisHash, headHash);
|
|
306
|
+
* // yes === true (genesis is ancestor of everything)
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
export const isAncestor = (dag, a, b) => {
|
|
310
|
+
if (a === b)
|
|
311
|
+
return false;
|
|
312
|
+
const visited = new Set();
|
|
313
|
+
const queue = [];
|
|
314
|
+
const nodeB = dag.nodes.get(b);
|
|
315
|
+
if (!nodeB)
|
|
316
|
+
return false;
|
|
317
|
+
for (const parentHash of nodeB.parents) {
|
|
318
|
+
if (dag.nodes.has(parentHash))
|
|
319
|
+
queue.push(parentHash);
|
|
320
|
+
}
|
|
321
|
+
while (queue.length > 0) {
|
|
322
|
+
const current = queue.shift();
|
|
323
|
+
if (current === a)
|
|
324
|
+
return true;
|
|
325
|
+
if (visited.has(current))
|
|
326
|
+
continue;
|
|
327
|
+
visited.add(current);
|
|
328
|
+
const currentNode = dag.nodes.get(current);
|
|
329
|
+
for (const parentHash of currentNode.parents) {
|
|
330
|
+
if (dag.nodes.has(parentHash) && !visited.has(parentHash))
|
|
331
|
+
queue.push(parentHash);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return false;
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* Find the latest common ancestor of two nodes in the DAG.
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* const lca = DAG.commonAncestor(dag, hashA, hashB);
|
|
342
|
+
* // lca is the hash of the most recent shared ancestor, or null
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
export const commonAncestor = (dag, a, b) => {
|
|
346
|
+
if (a === b)
|
|
347
|
+
return a;
|
|
348
|
+
const ancestorsOfA = new Set(ancestors(dag, a));
|
|
349
|
+
ancestorsOfA.add(a);
|
|
350
|
+
const ancestorsOfB = new Set(ancestors(dag, b));
|
|
351
|
+
ancestorsOfB.add(b);
|
|
352
|
+
const common = [];
|
|
353
|
+
for (const hash of ancestorsOfA) {
|
|
354
|
+
if (ancestorsOfB.has(hash))
|
|
355
|
+
common.push(hash);
|
|
356
|
+
}
|
|
357
|
+
if (common.length === 0)
|
|
358
|
+
return null;
|
|
359
|
+
const linearized = linearize(dag);
|
|
360
|
+
const linearOrder = new Map();
|
|
361
|
+
for (let i = 0; i < linearized.length; i++) {
|
|
362
|
+
linearOrder.set(linearized[i].hash, i);
|
|
363
|
+
}
|
|
364
|
+
let bestHash = null;
|
|
365
|
+
let bestOrder = -1;
|
|
366
|
+
for (const hash of common) {
|
|
367
|
+
const order = linearOrder.get(hash);
|
|
368
|
+
if (order !== undefined && order > bestOrder) {
|
|
369
|
+
bestOrder = order;
|
|
370
|
+
bestHash = hash;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return bestHash;
|
|
374
|
+
};
|
|
375
|
+
/**
|
|
376
|
+
* Return the number of nodes in the DAG.
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```ts
|
|
380
|
+
* const n = DAG.size(dag);
|
|
381
|
+
* // n === dag.nodes.size
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
export const size = (dag) => dag.nodes.size;
|
|
385
|
+
/**
|
|
386
|
+
* Merge remote envelopes into a local DAG, enforcing the anti-fork rule.
|
|
387
|
+
*
|
|
388
|
+
* Returns the updated DAG, list of newly added hashes, and whether the
|
|
389
|
+
* result is forked. Throws on anti-fork violations.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* const result = DAG.merge(localDag, remoteEnvelopes);
|
|
394
|
+
* // result.dag -- updated DAG
|
|
395
|
+
* // result.added -- newly ingested hashes
|
|
396
|
+
* // result.forked -- true if DAG has multiple heads
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
export const merge = (local, remote) => {
|
|
400
|
+
const added = [];
|
|
401
|
+
let current = local;
|
|
402
|
+
for (const envelope of remote) {
|
|
403
|
+
if (current.nodes.has(envelope.hash))
|
|
404
|
+
continue;
|
|
405
|
+
const violation = checkForkRule(current, envelope);
|
|
406
|
+
if (violation !== null) {
|
|
407
|
+
throw new Error(`Anti-fork violation: actor "${violation.actor}" attempted to fork from ` +
|
|
408
|
+
`prev-hash "${violation.prevHash}". Existing child: "${violation.existing}", ` +
|
|
409
|
+
`attempted: "${violation.attempted}" (each actor must have a single causal chain — use merge receipts to join branches).`);
|
|
410
|
+
}
|
|
411
|
+
current = ingest(current, envelope);
|
|
412
|
+
added.push(envelope.hash);
|
|
413
|
+
}
|
|
414
|
+
return { dag: current, added, forked: isFork(current) };
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* DAG namespace -- receipt DAG merge and canonical linearization.
|
|
418
|
+
*
|
|
419
|
+
* Build, query, and merge directed acyclic graphs of receipt envelopes.
|
|
420
|
+
* Supports deterministic linearization, fork detection, ancestor queries,
|
|
421
|
+
* and anti-fork rule enforcement.
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* ```ts
|
|
425
|
+
* import { DAG } from '@czap/core';
|
|
426
|
+
*
|
|
427
|
+
* const dag = DAG.fromReceipts(envelopes);
|
|
428
|
+
* const ordered = DAG.linearize(dag);
|
|
429
|
+
* const forked = DAG.isFork(dag);
|
|
430
|
+
* const result = DAG.merge(dag, remoteEnvelopes);
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
export const DAG = {
|
|
434
|
+
empty,
|
|
435
|
+
ingest,
|
|
436
|
+
ingestAll,
|
|
437
|
+
fromReceipts,
|
|
438
|
+
checkForkRule,
|
|
439
|
+
linearize,
|
|
440
|
+
linearizeFrom,
|
|
441
|
+
getHeads,
|
|
442
|
+
canonicalHead,
|
|
443
|
+
isFork,
|
|
444
|
+
ancestors,
|
|
445
|
+
isAncestor,
|
|
446
|
+
commonAncestor,
|
|
447
|
+
size,
|
|
448
|
+
merge,
|
|
449
|
+
};
|
|
450
|
+
//# sourceMappingURL=dag.js.map
|