@optimystic/db-core 0.14.1 → 0.16.2
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/dist/src/blocks/block-store.d.ts +18 -1
- package/dist/src/blocks/block-store.d.ts.map +1 -1
- package/dist/src/blocks/helpers.d.ts +2 -2
- package/dist/src/blocks/helpers.d.ts.map +1 -1
- package/dist/src/blocks/helpers.js +2 -2
- package/dist/src/blocks/helpers.js.map +1 -1
- package/dist/src/btree/btree.d.ts +27 -6
- package/dist/src/btree/btree.d.ts.map +1 -1
- package/dist/src/btree/btree.js +104 -61
- package/dist/src/btree/btree.js.map +1 -1
- package/dist/src/chain/chain.d.ts.map +1 -1
- package/dist/src/chain/chain.js +11 -1
- package/dist/src/chain/chain.js.map +1 -1
- package/dist/src/cluster/index.d.ts +1 -0
- package/dist/src/cluster/index.d.ts.map +1 -1
- package/dist/src/cluster/index.js +1 -0
- package/dist/src/cluster/index.js.map +1 -1
- package/dist/src/cluster/membership.d.ts +49 -0
- package/dist/src/cluster/membership.d.ts.map +1 -0
- package/dist/src/cluster/membership.js +75 -0
- package/dist/src/cluster/membership.js.map +1 -0
- package/dist/src/cluster/structs.d.ts +50 -1
- package/dist/src/cluster/structs.d.ts.map +1 -1
- package/dist/src/cluster/structs.js +9 -1
- package/dist/src/cluster/structs.js.map +1 -1
- package/dist/src/cohort-topic/addressing.d.ts +8 -2
- package/dist/src/cohort-topic/addressing.d.ts.map +1 -1
- package/dist/src/cohort-topic/addressing.js +11 -3
- package/dist/src/cohort-topic/addressing.js.map +1 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts +13 -7
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js +17 -6
- package/dist/src/cohort-topic/antidos/bootstrap-evidence.js.map +1 -1
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts +17 -0
- package/dist/src/cohort-topic/antidos/replay-guard.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/replay-guard.js +33 -0
- package/dist/src/cohort-topic/antidos/replay-guard.js.map +1 -1
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts +7 -0
- package/dist/src/cohort-topic/antidos/topic-budget.d.ts.map +1 -1
- package/dist/src/cohort-topic/antidos/topic-budget.js +8 -2
- package/dist/src/cohort-topic/antidos/topic-budget.js.map +1 -1
- package/dist/src/cohort-topic/coldstart.d.ts +34 -14
- package/dist/src/cohort-topic/coldstart.d.ts.map +1 -1
- package/dist/src/cohort-topic/coldstart.js +25 -11
- package/dist/src/cohort-topic/coldstart.js.map +1 -1
- package/dist/src/cohort-topic/gossip/bus.d.ts +12 -1
- package/dist/src/cohort-topic/gossip/bus.d.ts.map +1 -1
- package/dist/src/cohort-topic/gossip/bus.js +27 -1
- package/dist/src/cohort-topic/gossip/bus.js.map +1 -1
- package/dist/src/cohort-topic/gossip/view.d.ts +8 -0
- package/dist/src/cohort-topic/gossip/view.d.ts.map +1 -1
- package/dist/src/cohort-topic/gossip/view.js +17 -1
- package/dist/src/cohort-topic/gossip/view.js.map +1 -1
- package/dist/src/cohort-topic/member-engine.d.ts +10 -3
- package/dist/src/cohort-topic/member-engine.d.ts.map +1 -1
- package/dist/src/cohort-topic/member-engine.js +29 -14
- package/dist/src/cohort-topic/member-engine.js.map +1 -1
- package/dist/src/cohort-topic/membership/publisher.d.ts +14 -7
- package/dist/src/cohort-topic/membership/publisher.d.ts.map +1 -1
- package/dist/src/cohort-topic/membership/publisher.js +15 -27
- package/dist/src/cohort-topic/membership/publisher.js.map +1 -1
- package/dist/src/cohort-topic/membership/verifier.d.ts +35 -0
- package/dist/src/cohort-topic/membership/verifier.d.ts.map +1 -1
- package/dist/src/cohort-topic/membership/verifier.js +88 -3
- package/dist/src/cohort-topic/membership/verifier.js.map +1 -1
- package/dist/src/cohort-topic/promotion.d.ts +7 -0
- package/dist/src/cohort-topic/promotion.d.ts.map +1 -1
- package/dist/src/cohort-topic/promotion.js +16 -2
- package/dist/src/cohort-topic/promotion.js.map +1 -1
- package/dist/src/cohort-topic/registration/renewal.d.ts +18 -0
- package/dist/src/cohort-topic/registration/renewal.d.ts.map +1 -1
- package/dist/src/cohort-topic/registration/renewal.js +54 -0
- package/dist/src/cohort-topic/registration/renewal.js.map +1 -1
- package/dist/src/cohort-topic/registration/types.d.ts +12 -0
- package/dist/src/cohort-topic/registration/types.d.ts.map +1 -1
- package/dist/src/cohort-topic/registration/types.js +14 -0
- package/dist/src/cohort-topic/registration/types.js.map +1 -1
- package/dist/src/cohort-topic/service.d.ts +4 -3
- package/dist/src/cohort-topic/service.d.ts.map +1 -1
- package/dist/src/cohort-topic/service.js +38 -12
- package/dist/src/cohort-topic/service.js.map +1 -1
- package/dist/src/cohort-topic/sig/payloads.d.ts +23 -7
- package/dist/src/cohort-topic/sig/payloads.d.ts.map +1 -1
- package/dist/src/cohort-topic/sig/payloads.js +22 -6
- package/dist/src/cohort-topic/sig/payloads.js.map +1 -1
- package/dist/src/cohort-topic/traffic.d.ts +6 -0
- package/dist/src/cohort-topic/traffic.d.ts.map +1 -1
- package/dist/src/cohort-topic/traffic.js +16 -1
- package/dist/src/cohort-topic/traffic.js.map +1 -1
- package/dist/src/cohort-topic/walk.d.ts +22 -7
- package/dist/src/cohort-topic/walk.d.ts.map +1 -1
- package/dist/src/cohort-topic/walk.js +78 -18
- package/dist/src/cohort-topic/walk.js.map +1 -1
- package/dist/src/cohort-topic/wire/codec.d.ts +3 -1
- package/dist/src/cohort-topic/wire/codec.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/codec.js +7 -1
- package/dist/src/cohort-topic/wire/codec.js.map +1 -1
- package/dist/src/cohort-topic/wire/index.d.ts +1 -1
- package/dist/src/cohort-topic/wire/index.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/index.js +1 -1
- package/dist/src/cohort-topic/wire/index.js.map +1 -1
- package/dist/src/cohort-topic/wire/payloads.d.ts +5 -4
- package/dist/src/cohort-topic/wire/payloads.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/payloads.js +14 -5
- package/dist/src/cohort-topic/wire/payloads.js.map +1 -1
- package/dist/src/cohort-topic/wire/primitives.d.ts +65 -0
- package/dist/src/cohort-topic/wire/primitives.d.ts.map +1 -0
- package/dist/src/cohort-topic/wire/primitives.js +173 -0
- package/dist/src/cohort-topic/wire/primitives.js.map +1 -0
- package/dist/src/cohort-topic/wire/types.d.ts +132 -5
- package/dist/src/cohort-topic/wire/types.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/validate.d.ts +15 -5
- package/dist/src/cohort-topic/wire/validate.d.ts.map +1 -1
- package/dist/src/cohort-topic/wire/validate.js +146 -123
- package/dist/src/cohort-topic/wire/validate.js.map +1 -1
- package/dist/src/collection/collection.d.ts +23 -12
- package/dist/src/collection/collection.d.ts.map +1 -1
- package/dist/src/collection/collection.js +111 -35
- package/dist/src/collection/collection.js.map +1 -1
- package/dist/src/collection/struct.d.ts +37 -2
- package/dist/src/collection/struct.d.ts.map +1 -1
- package/dist/src/collection/struct.js +16 -1
- package/dist/src/collection/struct.js.map +1 -1
- package/dist/src/collections/tree/tree.d.ts +18 -1
- package/dist/src/collections/tree/tree.d.ts.map +1 -1
- package/dist/src/collections/tree/tree.js +36 -6
- package/dist/src/collections/tree/tree.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/log/log.d.ts +26 -3
- package/dist/src/log/log.d.ts.map +1 -1
- package/dist/src/log/log.js +34 -7
- package/dist/src/log/log.js.map +1 -1
- package/dist/src/log/struct.d.ts +5 -4
- package/dist/src/log/struct.d.ts.map +1 -1
- package/dist/src/log/struct.js.map +1 -1
- package/dist/src/matchmaking/wire.d.ts +2 -1
- package/dist/src/matchmaking/wire.d.ts.map +1 -1
- package/dist/src/matchmaking/wire.js +13 -92
- package/dist/src/matchmaking/wire.js.map +1 -1
- package/dist/src/network/i-key-network.d.ts +9 -0
- package/dist/src/network/i-key-network.d.ts.map +1 -1
- package/dist/src/network/i-repo.d.ts +8 -0
- package/dist/src/network/i-repo.d.ts.map +1 -1
- package/dist/src/network/struct.d.ts +10 -0
- package/dist/src/network/struct.d.ts.map +1 -1
- package/dist/src/reactivity/push-state.d.ts.map +1 -1
- package/dist/src/reactivity/push-state.js +4 -33
- package/dist/src/reactivity/push-state.js.map +1 -1
- package/dist/src/reactivity/wire-validate.d.ts +7 -22
- package/dist/src/reactivity/wire-validate.d.ts.map +1 -1
- package/dist/src/reactivity/wire-validate.js +7 -73
- package/dist/src/reactivity/wire-validate.js.map +1 -1
- package/dist/src/reactivity/wire.js +3 -3
- package/dist/src/reactivity/wire.js.map +1 -1
- package/dist/src/testing/async-wait.d.ts +23 -0
- package/dist/src/testing/async-wait.d.ts.map +1 -0
- package/dist/src/testing/async-wait.js +42 -0
- package/dist/src/testing/async-wait.js.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +3 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/test-transactor.d.ts +44 -0
- package/dist/src/testing/test-transactor.d.ts.map +1 -0
- package/dist/src/testing/test-transactor.js +423 -0
- package/dist/src/testing/test-transactor.js.map +1 -0
- package/dist/src/transaction/actions-engine.d.ts +6 -3
- package/dist/src/transaction/actions-engine.d.ts.map +1 -1
- package/dist/src/transaction/actions-engine.js +8 -7
- package/dist/src/transaction/actions-engine.js.map +1 -1
- package/dist/src/transaction/coordinator.d.ts +43 -22
- package/dist/src/transaction/coordinator.d.ts.map +1 -1
- package/dist/src/transaction/coordinator.js +443 -178
- package/dist/src/transaction/coordinator.js.map +1 -1
- package/dist/src/transaction/errors.d.ts +82 -0
- package/dist/src/transaction/errors.d.ts.map +1 -0
- package/dist/src/transaction/errors.js +93 -0
- package/dist/src/transaction/errors.js.map +1 -0
- package/dist/src/transaction/index.d.ts +6 -3
- package/dist/src/transaction/index.d.ts.map +1 -1
- package/dist/src/transaction/index.js +4 -2
- package/dist/src/transaction/index.js.map +1 -1
- package/dist/src/transaction/operations-hash.d.ts +119 -0
- package/dist/src/transaction/operations-hash.d.ts.map +1 -0
- package/dist/src/transaction/operations-hash.js +180 -0
- package/dist/src/transaction/operations-hash.js.map +1 -0
- package/dist/src/transaction/read-dependency-collector.d.ts +43 -0
- package/dist/src/transaction/read-dependency-collector.d.ts.map +1 -0
- package/dist/src/transaction/read-dependency-collector.js +69 -0
- package/dist/src/transaction/read-dependency-collector.js.map +1 -0
- package/dist/src/transaction/session.d.ts +12 -3
- package/dist/src/transaction/session.d.ts.map +1 -1
- package/dist/src/transaction/session.js +35 -9
- package/dist/src/transaction/session.js.map +1 -1
- package/dist/src/transaction/transaction.d.ts +162 -1
- package/dist/src/transaction/transaction.d.ts.map +1 -1
- package/dist/src/transaction/transaction.js +85 -3
- package/dist/src/transaction/transaction.js.map +1 -1
- package/dist/src/transaction/validator.d.ts +3 -11
- package/dist/src/transaction/validator.d.ts.map +1 -1
- package/dist/src/transaction/validator.js +69 -28
- package/dist/src/transaction/validator.js.map +1 -1
- package/dist/src/transactor/network-transactor.d.ts +35 -0
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +146 -40
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/dist/src/transactor/transactor-source.d.ts +19 -5
- package/dist/src/transactor/transactor-source.d.ts.map +1 -1
- package/dist/src/transactor/transactor-source.js +43 -11
- package/dist/src/transactor/transactor-source.js.map +1 -1
- package/dist/src/transform/atomic-proxy.d.ts +35 -5
- package/dist/src/transform/atomic-proxy.d.ts.map +1 -1
- package/dist/src/transform/atomic-proxy.js +39 -7
- package/dist/src/transform/atomic-proxy.js.map +1 -1
- package/dist/src/transform/cache-source.d.ts +36 -5
- package/dist/src/transform/cache-source.d.ts.map +1 -1
- package/dist/src/transform/cache-source.js +84 -8
- package/dist/src/transform/cache-source.js.map +1 -1
- package/dist/src/transform/helpers.d.ts.map +1 -1
- package/dist/src/transform/helpers.js +8 -2
- package/dist/src/transform/helpers.js.map +1 -1
- package/dist/src/transform/tracker.d.ts +17 -2
- package/dist/src/transform/tracker.d.ts.map +1 -1
- package/dist/src/transform/tracker.js +66 -11
- package/dist/src/transform/tracker.js.map +1 -1
- package/dist/src/utility/backoff.d.ts +48 -0
- package/dist/src/utility/backoff.d.ts.map +1 -0
- package/dist/src/utility/backoff.js +60 -0
- package/dist/src/utility/backoff.js.map +1 -0
- package/dist/src/utility/pending.d.ts +1 -0
- package/dist/src/utility/pending.d.ts.map +1 -1
- package/dist/src/utility/pending.js +3 -1
- package/dist/src/utility/pending.js.map +1 -1
- package/package.json +4 -4
- package/src/blocks/block-store.ts +19 -1
- package/src/blocks/helpers.ts +3 -3
- package/src/btree/btree.ts +108 -63
- package/src/chain/chain.ts +11 -1
- package/src/cluster/index.ts +1 -0
- package/src/cluster/membership.ts +85 -0
- package/src/cluster/structs.ts +51 -1
- package/src/cohort-topic/addressing.ts +12 -4
- package/src/cohort-topic/antidos/bootstrap-evidence.ts +21 -9
- package/src/cohort-topic/antidos/replay-guard.ts +37 -0
- package/src/cohort-topic/antidos/topic-budget.ts +17 -2
- package/src/cohort-topic/coldstart.ts +48 -15
- package/src/cohort-topic/gossip/bus.ts +39 -2
- package/src/cohort-topic/gossip/view.ts +26 -1
- package/src/cohort-topic/member-engine.ts +36 -15
- package/src/cohort-topic/membership/publisher.ts +25 -33
- package/src/cohort-topic/membership/verifier.ts +119 -3
- package/src/cohort-topic/promotion.ts +23 -2
- package/src/cohort-topic/registration/renewal.ts +73 -1
- package/src/cohort-topic/registration/types.ts +15 -0
- package/src/cohort-topic/service.ts +41 -15
- package/src/cohort-topic/sig/payloads.ts +29 -9
- package/src/cohort-topic/traffic.ts +23 -1
- package/src/cohort-topic/walk.ts +95 -24
- package/src/cohort-topic/wire/codec.ts +12 -0
- package/src/cohort-topic/wire/index.ts +2 -0
- package/src/cohort-topic/wire/payloads.ts +14 -5
- package/src/cohort-topic/wire/primitives.ts +188 -0
- package/src/cohort-topic/wire/types.ts +136 -4
- package/src/cohort-topic/wire/validate.ts +168 -134
- package/src/collection/collection.ts +123 -40
- package/src/collection/struct.ts +44 -2
- package/src/collections/tree/tree.ts +42 -5
- package/src/index.ts +1 -0
- package/src/log/log.ts +37 -9
- package/src/log/struct.ts +5 -4
- package/src/matchmaking/wire.ts +25 -102
- package/src/network/i-key-network.ts +10 -0
- package/src/network/i-repo.ts +8 -0
- package/src/network/struct.ts +10 -0
- package/src/reactivity/push-state.ts +4 -37
- package/src/reactivity/wire-validate.ts +7 -81
- package/src/reactivity/wire.ts +3 -3
- package/src/testing/async-wait.ts +65 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/test-transactor.ts +468 -0
- package/src/transaction/actions-engine.ts +8 -6
- package/src/transaction/coordinator.ts +498 -229
- package/src/transaction/errors.ts +91 -0
- package/src/transaction/index.ts +23 -3
- package/src/transaction/operations-hash.ts +196 -0
- package/src/transaction/read-dependency-collector.ts +78 -0
- package/src/transaction/session.ts +37 -9
- package/src/transaction/transaction.ts +211 -4
- package/src/transaction/validator.ts +73 -48
- package/src/transactor/network-transactor.ts +164 -49
- package/src/transactor/transactor-source.ts +47 -12
- package/src/transform/atomic-proxy.ts +92 -49
- package/src/transform/cache-source.ts +87 -9
- package/src/transform/helpers.ts +8 -2
- package/src/transform/tracker.ts +69 -11
- package/src/utility/backoff.ts +95 -0
- package/src/utility/pending.ts +3 -1
- package/src/transaction/context.ts +0 -103
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohort-topic wire substrate — shared structural-validation primitives.
|
|
3
|
+
*
|
|
4
|
+
* The generic per-field checks every wire codec across the substrate (cohort-topic, matchmaking,
|
|
5
|
+
* reactivity) uses to narrow an already-parsed `unknown` (the output of `JSON.parse` on a decoded
|
|
6
|
+
* frame) into a validated V1 shape. Each helper throws {@link CohortWireError} on a structural defect:
|
|
7
|
+
* missing required field, wrong `v`, out-of-enum discriminant, a byte field that does not decode as
|
|
8
|
+
* base64url, or an out-of-range numeric.
|
|
9
|
+
*
|
|
10
|
+
* These are the *only* generic primitives — domain-specific narrowing (the `validate*V1` functions, the
|
|
11
|
+
* cohort-topic `tier` / `treeTier` semantics, matchmaking utf8 coders, reactivity notification shapes)
|
|
12
|
+
* lives in the modules that own those message shapes. Keeping the primitives in one place means a
|
|
13
|
+
* hardening tweak to a check (e.g. the fixed-length byte-width guard in {@link b64urlFixedLen}) lands
|
|
14
|
+
* once and every consumer inherits it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { b64urlToBytes } from "./codec.js";
|
|
18
|
+
|
|
19
|
+
/** Thrown for any malformed, oversized, or structurally invalid cohort-topic frame. */
|
|
20
|
+
export class CohortWireError extends Error {
|
|
21
|
+
constructor(message: string) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "CohortWireError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Throw a {@link CohortWireError} with `message`. */
|
|
28
|
+
export function failWire(message: string): never {
|
|
29
|
+
throw new CohortWireError(message);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Set `obj[key]` only when `value` is defined — keeps absent optionals off the decoded object. */
|
|
33
|
+
export function assignDefined<T extends object, K extends keyof T>(obj: T, key: K, value: T[K] | undefined): void {
|
|
34
|
+
if (value !== undefined) {
|
|
35
|
+
obj[key] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Narrow `value` to a plain object (not null, not an array), or throw. */
|
|
40
|
+
export function asObject(value: unknown, what: string): Record<string, unknown> {
|
|
41
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
42
|
+
failWire(`${what}: expected an object`);
|
|
43
|
+
}
|
|
44
|
+
return value as Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Require `obj.v === 1`. */
|
|
48
|
+
export function requireV1(obj: Record<string, unknown>, what: string): void {
|
|
49
|
+
if (obj["v"] !== 1) {
|
|
50
|
+
failWire(`${what}: expected v === 1, got ${JSON.stringify(obj["v"])}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Require `obj[key]` to be a string; returns it. */
|
|
55
|
+
export function reqString(obj: Record<string, unknown>, key: string, what: string): string {
|
|
56
|
+
const value = obj[key];
|
|
57
|
+
if (typeof value !== "string") {
|
|
58
|
+
failWire(`${what}: field "${key}" must be a string`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Return `obj[key]` when it is a string, `undefined` when absent; throw on any other type. */
|
|
64
|
+
export function optString(obj: Record<string, unknown>, key: string, what: string): string | undefined {
|
|
65
|
+
const value = obj[key];
|
|
66
|
+
if (value === undefined) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
if (typeof value !== "string") {
|
|
70
|
+
failWire(`${what}: field "${key}" must be a string when present`);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Require `obj[key]` to be a finite number; returns it. */
|
|
76
|
+
export function reqFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number {
|
|
77
|
+
const value = obj[key];
|
|
78
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
79
|
+
failWire(`${what}: field "${key}" must be a finite number`);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Return `obj[key]` when it is a finite number, `undefined` when absent; throw on any other type. */
|
|
85
|
+
export function optFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number | undefined {
|
|
86
|
+
const value = obj[key];
|
|
87
|
+
if (value === undefined) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
91
|
+
failWire(`${what}: field "${key}" must be a finite number when present`);
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Require `obj[key]` to be a boolean; returns it. */
|
|
97
|
+
export function reqBool(obj: Record<string, unknown>, key: string, what: string): boolean {
|
|
98
|
+
const value = obj[key];
|
|
99
|
+
if (typeof value !== "boolean") {
|
|
100
|
+
failWire(`${what}: field "${key}" must be a boolean`);
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Return `obj[key]` when it is a boolean, `undefined` when absent; throw on any other type. */
|
|
106
|
+
export function optBool(obj: Record<string, unknown>, key: string, what: string): boolean | undefined {
|
|
107
|
+
const value = obj[key];
|
|
108
|
+
if (value === undefined) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
if (typeof value !== "boolean") {
|
|
112
|
+
failWire(`${what}: field "${key}" must be a boolean when present`);
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Require `obj[key]` to be an integer `>= min` (and `<= max` when supplied); returns it. Reads and
|
|
119
|
+
* finite-checks the field itself (via {@link reqFiniteNumber}) so callers pass the object + key rather
|
|
120
|
+
* than a pre-extracted value — this composes with the other `req*` helpers.
|
|
121
|
+
*/
|
|
122
|
+
export function reqIntInRange(obj: Record<string, unknown>, key: string, what: string, min: number, max?: number): number {
|
|
123
|
+
const value = reqFiniteNumber(obj, key, what);
|
|
124
|
+
if (!Number.isInteger(value) || value < min || (max !== undefined && value > max)) {
|
|
125
|
+
const bound = max === undefined ? `>= ${min}` : `in ${min}..${max}`;
|
|
126
|
+
failWire(`${what}: field "${key}" must be an integer ${bound}, got ${value}`);
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Require `obj[key]` to be an array of strings; returns it. */
|
|
132
|
+
export function reqStringArray(obj: Record<string, unknown>, key: string, what: string): string[] {
|
|
133
|
+
const value = obj[key];
|
|
134
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
135
|
+
failWire(`${what}: field "${key}" must be an array of strings`);
|
|
136
|
+
}
|
|
137
|
+
return value as string[];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Return `obj[key]` when it is an array of strings, `undefined` when absent; throw on any other type. */
|
|
141
|
+
export function optStringArray(obj: Record<string, unknown>, key: string, what: string): string[] | undefined {
|
|
142
|
+
if (obj[key] === undefined) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
return reqStringArray(obj, key, what);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Require `obj[key]` to be one of `allowed`; returns it narrowed to the enum type. */
|
|
149
|
+
export function reqEnum<T extends string>(obj: Record<string, unknown>, key: string, allowed: readonly T[], what: string): T {
|
|
150
|
+
const value = obj[key];
|
|
151
|
+
if (typeof value !== "string" || !(allowed as readonly string[]).includes(value)) {
|
|
152
|
+
failWire(`${what}: field "${key}" must be one of ${allowed.join(" | ")}`);
|
|
153
|
+
}
|
|
154
|
+
return value as T;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Assert a base64url string decodes cleanly; returns it unchanged. Used for variable-width fields —
|
|
159
|
+
* peer ids (multihash-encoded, not 32 raw bytes), signatures, and opaque application payloads.
|
|
160
|
+
*
|
|
161
|
+
* NOTE: no max-length bound here. A hostile peer can still bloat one of these variable-width fields
|
|
162
|
+
* (e.g. `participantCoord`, a signature) into a large map key in the store / rate limiter / replay
|
|
163
|
+
* guard. Their widths aren't pinned by the spec, so a ceiling would be a chosen policy value rather
|
|
164
|
+
* than a decode of the format. If a bloated one is ever seen as a map key in practice, add a
|
|
165
|
+
* `b64urlMaxLen` ceiling here. (Fixed-width hash-derived fields go through `b64urlFixedLen` instead.)
|
|
166
|
+
*/
|
|
167
|
+
export function b64urlField(value: string, key: string, what: string): string {
|
|
168
|
+
try {
|
|
169
|
+
b64urlToBytes(value);
|
|
170
|
+
} catch {
|
|
171
|
+
failWire(`${what}: field "${key}" is not valid base64url`);
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Assert a base64url string decodes cleanly to exactly `len` bytes; returns it unchanged. */
|
|
177
|
+
export function b64urlFixedLen(value: string, key: string, len: number, what: string): string {
|
|
178
|
+
let bytes: Uint8Array;
|
|
179
|
+
try {
|
|
180
|
+
bytes = b64urlToBytes(value);
|
|
181
|
+
} catch {
|
|
182
|
+
return failWire(`${what}: field "${key}" is not valid base64url`);
|
|
183
|
+
}
|
|
184
|
+
if (bytes.length !== len) {
|
|
185
|
+
failWire(`${what}: field "${key}" must decode to ${len} bytes, got ${bytes.length}`);
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
@@ -28,6 +28,20 @@ export interface RegisterV1 {
|
|
|
28
28
|
ttl: number;
|
|
29
29
|
/** True on a root cold-start request. */
|
|
30
30
|
bootstrap?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Follow-on cold-start request: set on the **dedicated re-issue** the participant sends after a
|
|
33
|
+
* `Promoted` redirect target answered {@link RegisterResult} `no_state` (the redirect points at a
|
|
34
|
+
* tier-`(d+1)` child cohort that does not exist yet). It tells that cold child "I am here because
|
|
35
|
+
* your parent redirected me — instantiate", which drives the cold-start gate
|
|
36
|
+
* (`shouldInstantiate({ followOn, … })`) exactly as `bootstrap` drives it at the root. Constraints:
|
|
37
|
+
* always `treeTier >= 1` (a follow-on is a deeper-than-root growth point, never the root); mutually
|
|
38
|
+
* exclusive with {@link bootstrap} and {@link probe} (the walk sets at most one). Because a wire flag is
|
|
39
|
+
* participant-forgeable, safety does NOT come from its provenance: a `followOn: true` register is gated
|
|
40
|
+
* by the **same** `bootstrapEvidence` policy a `bootstrap: true` cold-root register passes (§Anti-DoS),
|
|
41
|
+
* so it carries the identical proof in {@link bootstrapEvidence} and pays the identical anti-abuse cost.
|
|
42
|
+
* Covered by `signature` (appended to `registerSigningPayload`) so a MITM cannot strip or flip it.
|
|
43
|
+
*/
|
|
44
|
+
followOn?: boolean;
|
|
31
45
|
/**
|
|
32
46
|
* Read-only lookup probe: classify + return the cohort snapshot without admitting (no record, no
|
|
33
47
|
* arrival, no promotion trigger, no topic-budget touch, and **never** a cold-start instantiation /
|
|
@@ -42,9 +56,11 @@ export interface RegisterV1 {
|
|
|
42
56
|
appPayload?: string;
|
|
43
57
|
/**
|
|
44
58
|
* Cold-start bootstrap-evidence envelope, base64url (a `BootstrapEvidenceEnvelopeV1` — see
|
|
45
|
-
* `../antidos/bootstrap-evidence-envelope.js`). Present
|
|
46
|
-
*
|
|
47
|
-
*
|
|
59
|
+
* `../antidos/bootstrap-evidence-envelope.js`). Present on a `bootstrap: true` root register **or** a
|
|
60
|
+
* `followOn: true` deeper-tier re-issue — both are cold-start requests gated by the identical evidence
|
|
61
|
+
* policy (§Anti-DoS), so both carry this proof; carries the tier-dependent proof a cold cohort demands
|
|
62
|
+
* (proof-of-work / reputation endorsement / signed parent reference — §Anti-DoS). This is a
|
|
63
|
+
* **dedicated** field, NOT `appPayload`: the cohort
|
|
48
64
|
* copies `appPayload` verbatim into the registration's `appState` and replicates it cluster-wide,
|
|
49
65
|
* whereas the bootstrap evidence is parsed-and-checked by the substrate, **covered by `signature`**
|
|
50
66
|
* (so a MITM cannot strip or swap it), and never stored as appState. Empty string is treated as
|
|
@@ -143,6 +159,53 @@ export interface RenewReplyV1 {
|
|
|
143
159
|
cohortEpoch?: string;
|
|
144
160
|
}
|
|
145
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Child cohort → parent cohort link: "I am the tier-`d` cohort at {@link childCohortCoord}; record me as
|
|
164
|
+
* your child." A freshly cold-started deeper forwarder sends this to its tier-`(d − 1)` parent (routed the
|
|
165
|
+
* same way a participant register is, over `routeAndAct` to the parent coord) so the parent authenticates,
|
|
166
|
+
* records the child, and acks — replacing the interim plain participant-register the child used to send.
|
|
167
|
+
*/
|
|
168
|
+
export interface ChildLinkV1 {
|
|
169
|
+
v: 1;
|
|
170
|
+
/** Topic id, 32 bytes, base64url. */
|
|
171
|
+
topicId: string;
|
|
172
|
+
/**
|
|
173
|
+
* The child cohort's served coord `coord_d(childParticipantCoord, topicId)`, 32 bytes base64url — the
|
|
174
|
+
* coord the parent verifies the threshold signature against (it looks up the child cohort's
|
|
175
|
+
* {@link MembershipCertV1} at this coord).
|
|
176
|
+
*/
|
|
177
|
+
childCohortCoord: string;
|
|
178
|
+
/**
|
|
179
|
+
* A representative participant coord in the child's prefix-shard (the seed the child engine was
|
|
180
|
+
* instantiated at). Lets the parent *deterministically bind the parent-child relationship*: it recomputes
|
|
181
|
+
* `coord_childTier(childParticipantCoord, topicId) == childCohortCoord` AND
|
|
182
|
+
* `coord_(childTier-1)(childParticipantCoord, topicId) == this parent's served coord`. Any participant
|
|
183
|
+
* sharing the child's `d·log₂F`-bit prefix yields the same pair, so it is a representative, not an
|
|
184
|
+
* identity. 32 bytes base64url.
|
|
185
|
+
*/
|
|
186
|
+
childParticipantCoord: string;
|
|
187
|
+
/** Child tree tier `d` (always ≥ 1 — the root has no parent to link to). Parent serves `d − 1`. */
|
|
188
|
+
childTier: number;
|
|
189
|
+
/** Op capacity tier T0..T3 (stamped so a real parent validates the frame's tier). */
|
|
190
|
+
tier: number;
|
|
191
|
+
/** Unix ms; the parent's per-child freshness/ordering key (strictly-newer wins). */
|
|
192
|
+
effectiveAt: number;
|
|
193
|
+
/** Child cohort threshold signature over `childLinkSigningPayload`, base64url. Empty in the key-less interim. */
|
|
194
|
+
thresholdSig: string;
|
|
195
|
+
/** Signing members' PeerIds, `>= minSigs`, base64url. Empty in the key-less interim. */
|
|
196
|
+
signers: string[];
|
|
197
|
+
/** Child cohort epoch, 32 bytes base64url (the epoch the threshold sig was collected under). */
|
|
198
|
+
cohortEpoch: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Parent → child ack. `linked` flips the child `awaiting_parent → serving`; `rejected` keeps it awaiting. */
|
|
202
|
+
export interface ChildLinkReplyV1 {
|
|
203
|
+
v: 1;
|
|
204
|
+
result: "linked" | "rejected";
|
|
205
|
+
/** Human-readable, optional. */
|
|
206
|
+
reason?: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
146
209
|
/** Threshold-signed promotion notice. */
|
|
147
210
|
export interface PromotionNoticeV1 {
|
|
148
211
|
v: 1;
|
|
@@ -150,6 +213,15 @@ export interface PromotionNoticeV1 {
|
|
|
150
213
|
fromTier: number;
|
|
151
214
|
/** Typically `fromTier + 1`. */
|
|
152
215
|
toTier: number;
|
|
216
|
+
/**
|
|
217
|
+
* The served coord `coord_d(participantCoord, topicId)` the deciding cohort sits at, 32 bytes, base64url
|
|
218
|
+
* — the same value as that cohort's {@link MembershipCertV1.cohortCoord}, which the threshold signature
|
|
219
|
+
* verifies against. The receiver routes the notice to the local engine for exactly this coord rather than
|
|
220
|
+
* scanning for the first engine matching `(topic, tier)`, so a node serving several sibling cohorts for
|
|
221
|
+
* one `(topic, tier)` (possible at `d ≥ 1`, where each cohort has its own served coord) applies the notice
|
|
222
|
+
* to the cohort that actually produced it. Covered by the threshold signature — rewriting it breaks verification.
|
|
223
|
+
*/
|
|
224
|
+
cohortCoord: string;
|
|
153
225
|
/** Unix ms. */
|
|
154
226
|
effectiveAt: number;
|
|
155
227
|
/** Cohort threshold signature, base64url. */
|
|
@@ -166,6 +238,13 @@ export interface DemotionNoticeV1 {
|
|
|
166
238
|
tier: number;
|
|
167
239
|
/** 32 bytes, base64url. */
|
|
168
240
|
parentCohortCoord: string;
|
|
241
|
+
/**
|
|
242
|
+
* The served coord `coord_d(participantCoord, topicId)` the deciding cohort sits at, 32 bytes, base64url —
|
|
243
|
+
* the same served-coord concept as {@link PromotionNoticeV1.cohortCoord}. Distinct from
|
|
244
|
+
* {@link parentCohortCoord} (the tier-`(d − 1)` parent this demotion hands off to): `cohortCoord` names the
|
|
245
|
+
* *demoting* cohort and is what the receiver routes + verifies by. Covered by the threshold signature.
|
|
246
|
+
*/
|
|
247
|
+
cohortCoord: string;
|
|
169
248
|
effectiveAt: number;
|
|
170
249
|
thresholdSig: string;
|
|
171
250
|
signers: string[];
|
|
@@ -203,6 +282,33 @@ export interface GossipRecordRefV1 {
|
|
|
203
282
|
topicId: string;
|
|
204
283
|
/** Participant peer id, base64url. */
|
|
205
284
|
participantId: string;
|
|
285
|
+
/**
|
|
286
|
+
* Unix ms of the evicted record's most-recent ping (mirrors {@link GossipRecordV1.lastPing}). The
|
|
287
|
+
* receiver's freshness guard for the delete: a held record newer than this stamp is a re-registration
|
|
288
|
+
* that arrived after the eviction was produced, so the stale eviction is ignored rather than deleting
|
|
289
|
+
* the fresh record. Required — every real eviction has this on hand from the held record.
|
|
290
|
+
*/
|
|
291
|
+
lastPing: number;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* A child-cohort link/unlink advertised in cohort gossip, for cross-member convergence of the child set.
|
|
296
|
+
*
|
|
297
|
+
* FRET routes a {@link ChildLinkV1} to a **single** parent member, so only that member's child registry
|
|
298
|
+
* records the child; siblings would read `childCohortCount == 0`. Gossiping the child **set** (a converged
|
|
299
|
+
* union) closes that gap: a `childLinks` ref replicates a recorded child to every parent member, and a
|
|
300
|
+
* `childUnlinks` ref replicates a released (demoted) child. Merge is **last-writer-wins by
|
|
301
|
+
* {@link effectiveAt}** per `(topicId, childCohortCoord)` — a link and a later unlink converge regardless of
|
|
302
|
+
* arrival order, and re-delivery is idempotent. The child set is keyed by child coord, not the parent's
|
|
303
|
+
* epoch, so a parent membership rotation never drops it.
|
|
304
|
+
*/
|
|
305
|
+
export interface ChildLinkRefV1 {
|
|
306
|
+
/** Topic id, 32 bytes, base64url. */
|
|
307
|
+
topicId: string;
|
|
308
|
+
/** The child cohort's served coord, 32 bytes, base64url. */
|
|
309
|
+
childCohortCoord: string;
|
|
310
|
+
/** Unix ms — the link/unlink effectiveAt; last-writer-wins per `(topicId, childCohortCoord)`. */
|
|
311
|
+
effectiveAt: number;
|
|
206
312
|
}
|
|
207
313
|
|
|
208
314
|
/** Intra-cohort gossip: willingness vector, load barometer, exact per-topic summaries, record deltas. */
|
|
@@ -217,6 +323,14 @@ export interface CohortGossipV1 {
|
|
|
217
323
|
*/
|
|
218
324
|
coord: string;
|
|
219
325
|
cohortEpoch: string;
|
|
326
|
+
/**
|
|
327
|
+
* The tree tier `d` the originating cohort sits at (the coord encodes the tier-shard, but a coord is a
|
|
328
|
+
* hash and cannot be inverted to recover `d`). Carried so a cold sibling that instantiates its engine
|
|
329
|
+
* off a co-member's frame (§Cold-start instantiation) adopts the right tier. Always well-defined: the
|
|
330
|
+
* only members that originate a frame already know their `treeTier`, and every member of a coord shares
|
|
331
|
+
* one `treeTier` by construction. Covered by {@link signature} so it cannot be spoofed.
|
|
332
|
+
*/
|
|
333
|
+
treeTier: number;
|
|
220
334
|
/** 4 bits T0..T3, hex. */
|
|
221
335
|
willingnessBits: string;
|
|
222
336
|
/** 4 entries, 0..7 per tier. */
|
|
@@ -232,6 +346,19 @@ export interface CohortGossipV1 {
|
|
|
232
346
|
records?: GossipRecordV1[];
|
|
233
347
|
/** Registrations this member evicted (stale), so all members converge on the active set. */
|
|
234
348
|
evicted?: GossipRecordRefV1[];
|
|
349
|
+
/**
|
|
350
|
+
* Child cohorts this member freshly recorded (links), replicated so every parent member converges on the
|
|
351
|
+
* same child set. Absent when this gossip carries no child-link changes. Merge is last-writer-wins by
|
|
352
|
+
* {@link ChildLinkRefV1.effectiveAt} per `(topicId, childCohortCoord)`; a link sets the child linked.
|
|
353
|
+
*/
|
|
354
|
+
childLinks?: ChildLinkRefV1[];
|
|
355
|
+
/**
|
|
356
|
+
* Child cohorts this member released (a demoted child unrecorded), replicated so every parent member drops
|
|
357
|
+
* it and can shrink in turn. Absent when this gossip carries no child-unlink changes. Same last-writer-wins
|
|
358
|
+
* merge as {@link childLinks}; an unlink sets the child unlinked. A link@t1 then unlink@t2 (t2 > t1)
|
|
359
|
+
* converges to unlinked regardless of arrival order.
|
|
360
|
+
*/
|
|
361
|
+
childUnlinks?: ChildLinkRefV1[];
|
|
235
362
|
timestamp: number;
|
|
236
363
|
signature: string;
|
|
237
364
|
}
|
|
@@ -257,8 +384,11 @@ export interface CohortTopicSummary {
|
|
|
257
384
|
* - `rotation` — an epoch hand-off: the endorser attests it was a member of the cohort at the **prior**
|
|
258
385
|
* epoch carried as `cohortEpoch` (the predecessor cohort threshold-signs the successor cert's payload),
|
|
259
386
|
* so the gate checks *prior*-epoch membership rather than current. See `cohort-topic-trust-anchor-rotation-production`.
|
|
387
|
+
* - `childlink` — a child cohort signs a {@link ChildLinkV1} over its **own** served coord at its current
|
|
388
|
+
* epoch (identical endorsement shape to `promotion`/`demotion`: share the current cohort + epoch around
|
|
389
|
+
* `coord`); the parent verifies it against the child cohort's cert before recording the child.
|
|
260
390
|
*/
|
|
261
|
-
export type SignKind = "membership" | "promotion" | "demotion" | "rotation";
|
|
391
|
+
export type SignKind = "membership" | "promotion" | "demotion" | "rotation" | "childlink";
|
|
262
392
|
|
|
263
393
|
/**
|
|
264
394
|
* Intra-cohort sign request (`/optimystic/cohort-topic/1.0.0/sign`). A member assembling a `k − x`
|
|
@@ -334,6 +464,8 @@ export type CohortMessageV1 =
|
|
|
334
464
|
| RegisterReplyV1
|
|
335
465
|
| RenewV1
|
|
336
466
|
| RenewReplyV1
|
|
467
|
+
| ChildLinkV1
|
|
468
|
+
| ChildLinkReplyV1
|
|
337
469
|
| PromotionNoticeV1
|
|
338
470
|
| DemotionNoticeV1
|
|
339
471
|
| CohortGossipV1
|