cesr-ts 0.4.0 → 0.6.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/esm/src/adapters/async-iterable.js +8 -0
- package/esm/src/adapters/effection.js +6 -0
- package/esm/src/annotate/annotator.js +12 -0
- package/esm/src/annotate/cli.js +12 -0
- package/esm/src/annotate/comments.js +5 -1
- package/esm/src/annotate/denot.js +6 -0
- package/esm/src/annotate/render.js +15 -3
- package/esm/src/core/bytes.js +9 -0
- package/esm/src/core/errors.js +3 -0
- package/esm/src/core/parser-engine.js +1 -1
- package/esm/src/core/parser-frame-parser.js +22 -18
- package/esm/src/core/vocabulary.js +47 -0
- package/esm/src/index.js +5 -0
- package/esm/src/parser/attachment-parser.js +4 -0
- package/esm/src/parser/cold-start.js +6 -0
- package/esm/src/parser/group-dispatch.js +2 -2
- package/esm/src/primitives/aggor.js +374 -30
- package/esm/src/primitives/bexter.js +2 -3
- package/esm/src/primitives/blinder.js +6 -0
- package/esm/src/primitives/byte-like.js +19 -0
- package/esm/src/primitives/cigar.js +17 -3
- package/esm/src/primitives/cipher.js +91 -6
- package/esm/src/primitives/codex.js +39 -6
- package/esm/src/primitives/compactor.js +307 -3
- package/esm/src/primitives/counter.js +6 -2
- package/esm/src/primitives/dater.js +1 -2
- package/esm/src/primitives/decimer.js +7 -11
- package/esm/src/primitives/decrypter.js +123 -7
- package/esm/src/primitives/diger.js +57 -3
- package/esm/src/primitives/disclosure.js +300 -0
- package/esm/src/primitives/encrypter.js +130 -7
- package/esm/src/primitives/hydrate.js +49 -0
- package/esm/src/primitives/ilker.js +2 -3
- package/esm/src/primitives/indexer.js +21 -4
- package/esm/src/primitives/labeler.js +1 -2
- package/esm/src/primitives/mapper.js +612 -35
- package/esm/src/primitives/matter.js +133 -3
- package/esm/src/primitives/mediar.js +6 -0
- package/esm/src/primitives/noncer.js +2 -3
- package/esm/src/primitives/number.js +1 -2
- package/esm/src/primitives/pather.js +102 -5
- package/esm/src/primitives/prefixer.js +1 -2
- package/esm/src/primitives/primitive.js +14 -0
- package/esm/src/primitives/registry.js +2 -0
- package/esm/src/primitives/saider.js +80 -3
- package/esm/src/primitives/salter.js +96 -2
- package/esm/src/primitives/sealed-box.js +41 -0
- package/esm/src/primitives/seqner.js +1 -2
- package/esm/src/primitives/siger.js +58 -3
- package/esm/src/primitives/signer.js +243 -9
- package/esm/src/primitives/streamer.js +28 -0
- package/esm/src/primitives/structing.js +727 -0
- package/esm/src/primitives/structor.js +20 -0
- package/esm/src/primitives/tagger.js +1 -2
- package/esm/src/primitives/texter.js +1 -2
- package/esm/src/primitives/tholder.js +552 -10
- package/esm/src/primitives/traitor.js +2 -3
- package/esm/src/primitives/verfer.js +50 -6
- package/esm/src/primitives/verser.js +16 -6
- package/esm/src/router/router-stub.js +1 -0
- package/esm/src/serder/native.js +1326 -0
- package/esm/src/serder/serder.js +1558 -59
- package/esm/src/serder/serdery.js +92 -7
- package/esm/src/serder/smell.js +26 -1
- package/esm/src/tables/codex-utils.js +2 -0
- package/esm/src/tables/counter-codex.js +2 -0
- package/esm/src/tables/counter-groups.js +6 -0
- package/esm/src/tables/versions.js +4 -0
- package/esm/src/version.js +5 -2
- package/package.json +5 -2
- package/types/src/adapters/async-iterable.d.ts +8 -0
- package/types/src/adapters/async-iterable.d.ts.map +1 -1
- package/types/src/adapters/effection.d.ts +7 -0
- package/types/src/adapters/effection.d.ts.map +1 -1
- package/types/src/annotate/annotator.d.ts +12 -0
- package/types/src/annotate/annotator.d.ts.map +1 -1
- package/types/src/annotate/cli.d.ts +13 -0
- package/types/src/annotate/cli.d.ts.map +1 -1
- package/types/src/annotate/comments.d.ts +4 -0
- package/types/src/annotate/comments.d.ts.map +1 -1
- package/types/src/annotate/denot.d.ts +6 -0
- package/types/src/annotate/denot.d.ts.map +1 -1
- package/types/src/annotate/render.d.ts.map +1 -1
- package/types/src/annotate/types.d.ts +2 -0
- package/types/src/annotate/types.d.ts.map +1 -1
- package/types/src/bench/parser-benchmark.d.ts +3 -0
- package/types/src/bench/parser-benchmark.d.ts.map +1 -1
- package/types/src/core/bytes.d.ts +9 -0
- package/types/src/core/bytes.d.ts.map +1 -1
- package/types/src/core/errors.d.ts +3 -0
- package/types/src/core/errors.d.ts.map +1 -1
- package/types/src/core/parser-engine.d.ts +4 -0
- package/types/src/core/parser-engine.d.ts.map +1 -1
- package/types/src/core/parser-frame-parser.d.ts.map +1 -1
- package/types/src/core/types.d.ts +2 -0
- package/types/src/core/types.d.ts.map +1 -1
- package/types/src/core/vocabulary.d.ts +48 -0
- package/types/src/core/vocabulary.d.ts.map +1 -0
- package/types/src/index.d.ts +5 -0
- package/types/src/index.d.ts.map +1 -1
- package/types/src/parser/attachment-parser.d.ts +4 -0
- package/types/src/parser/attachment-parser.d.ts.map +1 -1
- package/types/src/parser/cold-start.d.ts +6 -0
- package/types/src/parser/cold-start.d.ts.map +1 -1
- package/types/src/parser/group-dispatch.d.ts.map +1 -1
- package/types/src/primitives/aggor.d.ts +100 -13
- package/types/src/primitives/aggor.d.ts.map +1 -1
- package/types/src/primitives/bexter.d.ts.map +1 -1
- package/types/src/primitives/blinder.d.ts +6 -0
- package/types/src/primitives/blinder.d.ts.map +1 -1
- package/types/src/primitives/byte-like.d.ts +7 -0
- package/types/src/primitives/byte-like.d.ts.map +1 -0
- package/types/src/primitives/cigar.d.ts +11 -1
- package/types/src/primitives/cigar.d.ts.map +1 -1
- package/types/src/primitives/cipher.d.ts +73 -2
- package/types/src/primitives/cipher.d.ts.map +1 -1
- package/types/src/primitives/codex.d.ts +34 -4
- package/types/src/primitives/codex.d.ts.map +1 -1
- package/types/src/primitives/compactor.d.ts +94 -2
- package/types/src/primitives/compactor.d.ts.map +1 -1
- package/types/src/primitives/counter.d.ts.map +1 -1
- package/types/src/primitives/dater.d.ts.map +1 -1
- package/types/src/primitives/decimer.d.ts.map +1 -1
- package/types/src/primitives/decrypter.d.ts +64 -3
- package/types/src/primitives/decrypter.d.ts.map +1 -1
- package/types/src/primitives/diger.d.ts +16 -0
- package/types/src/primitives/diger.d.ts.map +1 -1
- package/types/src/primitives/disclosure.d.ts +201 -0
- package/types/src/primitives/disclosure.d.ts.map +1 -0
- package/types/src/primitives/encrypter.d.ts +66 -4
- package/types/src/primitives/encrypter.d.ts.map +1 -1
- package/types/src/primitives/hydrate.d.ts +20 -0
- package/types/src/primitives/hydrate.d.ts.map +1 -0
- package/types/src/primitives/ilker.d.ts.map +1 -1
- package/types/src/primitives/indexer.d.ts.map +1 -1
- package/types/src/primitives/labeler.d.ts.map +1 -1
- package/types/src/primitives/mapper.d.ts +132 -11
- package/types/src/primitives/mapper.d.ts.map +1 -1
- package/types/src/primitives/matter.d.ts +33 -1
- package/types/src/primitives/matter.d.ts.map +1 -1
- package/types/src/primitives/mediar.d.ts +6 -0
- package/types/src/primitives/mediar.d.ts.map +1 -1
- package/types/src/primitives/noncer.d.ts.map +1 -1
- package/types/src/primitives/number.d.ts.map +1 -1
- package/types/src/primitives/pather.d.ts +21 -0
- package/types/src/primitives/pather.d.ts.map +1 -1
- package/types/src/primitives/prefixer.d.ts.map +1 -1
- package/types/src/primitives/primitive.d.ts +26 -0
- package/types/src/primitives/primitive.d.ts.map +1 -1
- package/types/src/primitives/registry.d.ts +3 -0
- package/types/src/primitives/registry.d.ts.map +1 -1
- package/types/src/primitives/saider.d.ts +41 -0
- package/types/src/primitives/saider.d.ts.map +1 -1
- package/types/src/primitives/salter.d.ts +56 -1
- package/types/src/primitives/salter.d.ts.map +1 -1
- package/types/src/primitives/sealed-box.d.ts +29 -0
- package/types/src/primitives/sealed-box.d.ts.map +1 -0
- package/types/src/primitives/seqner.d.ts.map +1 -1
- package/types/src/primitives/siger.d.ts +14 -0
- package/types/src/primitives/siger.d.ts.map +1 -1
- package/types/src/primitives/signer.d.ts +80 -4
- package/types/src/primitives/signer.d.ts.map +1 -1
- package/types/src/primitives/streamer.d.ts +31 -0
- package/types/src/primitives/streamer.d.ts.map +1 -0
- package/types/src/primitives/structing.d.ts +4114 -0
- package/types/src/primitives/structing.d.ts.map +1 -0
- package/types/src/primitives/structor.d.ts +6 -0
- package/types/src/primitives/structor.d.ts.map +1 -1
- package/types/src/primitives/tagger.d.ts.map +1 -1
- package/types/src/primitives/texter.d.ts.map +1 -1
- package/types/src/primitives/tholder.d.ts +150 -5
- package/types/src/primitives/tholder.d.ts.map +1 -1
- package/types/src/primitives/traitor.d.ts.map +1 -1
- package/types/src/primitives/unknown.d.ts +1 -0
- package/types/src/primitives/unknown.d.ts.map +1 -1
- package/types/src/primitives/verfer.d.ts +12 -0
- package/types/src/primitives/verfer.d.ts.map +1 -1
- package/types/src/primitives/verser.d.ts +14 -3
- package/types/src/primitives/verser.d.ts.map +1 -1
- package/types/src/router/router-stub.d.ts +3 -0
- package/types/src/router/router-stub.d.ts.map +1 -1
- package/types/src/serder/native.d.ts +115 -0
- package/types/src/serder/native.d.ts.map +1 -0
- package/types/src/serder/serder.d.ts +187 -9
- package/types/src/serder/serder.d.ts.map +1 -1
- package/types/src/serder/serdery.d.ts +42 -0
- package/types/src/serder/serdery.d.ts.map +1 -1
- package/types/src/serder/smell.d.ts +22 -0
- package/types/src/serder/smell.d.ts.map +1 -1
- package/types/src/tables/codex-utils.d.ts +3 -0
- package/types/src/tables/codex-utils.d.ts.map +1 -1
- package/types/src/tables/counter-codex.d.ts +4 -0
- package/types/src/tables/counter-codex.d.ts.map +1 -1
- package/types/src/tables/counter-groups.d.ts +6 -0
- package/types/src/tables/counter-groups.d.ts.map +1 -1
- package/types/src/tables/counter.tables.generated.d.ts.map +1 -1
- package/types/src/tables/indexer.codex.generated.d.ts.map +1 -1
- package/types/src/tables/matter.codex.generated.d.ts.map +1 -1
- package/types/src/tables/table-types.d.ts +6 -0
- package/types/src/tables/table-types.d.ts.map +1 -1
- package/types/src/tables/versions.d.ts +6 -0
- package/types/src/tables/versions.d.ts.map +1 -1
- package/types/src/version.d.ts +5 -2
- package/types/src/version.d.ts.map +1 -1
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counted-group transport wrappers for CESR tuple/list families.
|
|
3
|
+
*
|
|
4
|
+
* This file ports the transport/enclosure half of KERIpy `Structor`.
|
|
5
|
+
*
|
|
6
|
+
* Boundary rule:
|
|
7
|
+
* - `Structor` owns counted-group framing, grouped serialization, and parser
|
|
8
|
+
* reconstruction of enclosed payload items
|
|
9
|
+
* - `structing.ts` owns semantic fixed-field seal/blind/media records
|
|
10
|
+
* - `disclosure.ts` owns blind/unblind/commit workflow verbs
|
|
11
|
+
*
|
|
12
|
+
* If you are looking for the meaning of fields like `d`, `u`, `td`, or `mt`,
|
|
13
|
+
* this is the wrong file. `Structor` only knows how many items are in a group
|
|
14
|
+
* and how that enclosed group serializes.
|
|
15
|
+
*/
|
|
1
16
|
import { b, codeB64ToB2, encodeB64, t } from "../core/bytes.js";
|
|
2
17
|
import { UnknownCodeError } from "../core/errors.js";
|
|
3
18
|
import { parseAttachmentDispatch } from "../parser/group-dispatch.js";
|
|
@@ -24,6 +39,11 @@ function inferSerialized(group, sourceDomain) {
|
|
|
24
39
|
*
|
|
25
40
|
* KERIpy substance: `Structor` models one counted-group unit where tuple/list
|
|
26
41
|
* members are hydrated CESR primitives and serialization remains deterministic.
|
|
42
|
+
*
|
|
43
|
+
* TypeScript boundary:
|
|
44
|
+
* - subclasses like `Sealer`, `Blinder`, and `Mediar` specialize transport
|
|
45
|
+
* families
|
|
46
|
+
* - semantic fixed-field values remain plain records outside this class
|
|
27
47
|
*/
|
|
28
48
|
export class Structor extends CounterGroup {
|
|
29
49
|
constructor(init) {
|
|
@@ -27,8 +27,7 @@ export function isTaggerCode(code) {
|
|
|
27
27
|
*/
|
|
28
28
|
export class Tagger extends Matter {
|
|
29
29
|
constructor(init) {
|
|
30
|
-
|
|
31
|
-
super(matter);
|
|
30
|
+
super(init);
|
|
32
31
|
if (!isTaggerCode(this.code)) {
|
|
33
32
|
throw new UnknownCodeError(`Expected tag code, got ${this.code}`);
|
|
34
33
|
}
|
|
@@ -13,8 +13,7 @@ function isTexterCode(code) {
|
|
|
13
13
|
*/
|
|
14
14
|
export class Texter extends Matter {
|
|
15
15
|
constructor(init) {
|
|
16
|
-
|
|
17
|
-
super(matter);
|
|
16
|
+
super(init);
|
|
18
17
|
if (!isTexterCode(this.code)) {
|
|
19
18
|
throw new UnknownCodeError(`Expected texter bytes code, got ${this.code}`);
|
|
20
19
|
}
|
|
@@ -1,31 +1,573 @@
|
|
|
1
|
-
import { t } from "../core/bytes.js";
|
|
2
1
|
import { UnknownCodeError } from "../core/errors.js";
|
|
2
|
+
import { Bexter } from "./bexter.js";
|
|
3
|
+
import { LabelDex } from "./codex.js";
|
|
3
4
|
import { THOLDER_NUMERIC_CODES, THOLDER_WEIGHTED_CODES } from "./codex.js";
|
|
4
5
|
import { Matter, parseMatter } from "./matter.js";
|
|
6
|
+
import { NumberPrimitive } from "./number.js";
|
|
7
|
+
/**
|
|
8
|
+
* Numeric tholder codex capacities keyed by the code used for a given raw width.
|
|
9
|
+
*
|
|
10
|
+
* This is the bridge between bigint numeric thresholds and the compact CESR
|
|
11
|
+
* numeric codex family that `Tholder` reuses.
|
|
12
|
+
*/
|
|
13
|
+
const NUMERIC_CAPACITIES = [
|
|
14
|
+
{ code: "M", rawSize: 2 },
|
|
15
|
+
{ code: "0H", rawSize: 4 },
|
|
16
|
+
{ code: "R", rawSize: 5 },
|
|
17
|
+
{ code: "N", rawSize: 8 },
|
|
18
|
+
{ code: "S", rawSize: 11 },
|
|
19
|
+
{ code: "T", rawSize: 14 },
|
|
20
|
+
{ code: "0A", rawSize: 16 },
|
|
21
|
+
{ code: "U", rawSize: 17 },
|
|
22
|
+
];
|
|
23
|
+
/** Largest bigint that can be mirrored into a lossless JS `number`. */
|
|
24
|
+
const MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
|
|
25
|
+
/** Narrow `unknown` into a plain object map rather than an array-like value. */
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Recognize constructor inputs that already describe a `Matter`/`MatterInit`
|
|
31
|
+
* wire representation rather than a semantic threshold expression.
|
|
32
|
+
*/
|
|
33
|
+
function isMatterInitLike(value) {
|
|
34
|
+
return value instanceof Matter || (isRecord(value)
|
|
35
|
+
&& ("raw" in value || "qb64" in value || "qb64b" in value || "qb2" in value
|
|
36
|
+
|| "code" in value));
|
|
37
|
+
}
|
|
38
|
+
/** Euclidean greatest-common-divisor helper for rational normalization. */
|
|
39
|
+
function gcd(a, b) {
|
|
40
|
+
let left = a < 0n ? -a : a;
|
|
41
|
+
let right = b < 0n ? -b : b;
|
|
42
|
+
while (right !== 0n) {
|
|
43
|
+
const next = left % right;
|
|
44
|
+
left = right;
|
|
45
|
+
right = next;
|
|
46
|
+
}
|
|
47
|
+
return left === 0n ? 1n : left;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Reduce a rational to canonical sign/lowest-terms form.
|
|
51
|
+
*
|
|
52
|
+
* Invariant: denominator is always positive on return.
|
|
53
|
+
*/
|
|
54
|
+
function normalizeRational(numerator, denominator) {
|
|
55
|
+
if (denominator === 0n) {
|
|
56
|
+
throw new Error("Invalid threshold weight denominator=0.");
|
|
57
|
+
}
|
|
58
|
+
if (denominator < 0n) {
|
|
59
|
+
numerator = -numerator;
|
|
60
|
+
denominator = -denominator;
|
|
61
|
+
}
|
|
62
|
+
const divisor = gcd(numerator, denominator);
|
|
63
|
+
return {
|
|
64
|
+
numerator: numerator / divisor,
|
|
65
|
+
denominator: denominator / divisor,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Add two exact rationals without passing through floating-point math. */
|
|
69
|
+
function addRationals(left, right) {
|
|
70
|
+
return normalizeRational(left.numerator * right.denominator + right.numerator * left.denominator, left.denominator * right.denominator);
|
|
71
|
+
}
|
|
72
|
+
/** Compare two exact rationals using cross-multiplication. */
|
|
73
|
+
function compareRationals(left, right) {
|
|
74
|
+
const lhs = left.numerator * right.denominator;
|
|
75
|
+
const rhs = right.numerator * left.denominator;
|
|
76
|
+
if (lhs === rhs)
|
|
77
|
+
return 0;
|
|
78
|
+
return lhs > rhs ? 1 : -1;
|
|
79
|
+
}
|
|
80
|
+
/** True when a rational meets the KERI weighted-threshold satisfaction floor of `1`. */
|
|
81
|
+
function atLeastOne(value) {
|
|
82
|
+
return compareRationals(value, { numerator: 1n, denominator: 1n }) >= 0;
|
|
83
|
+
}
|
|
84
|
+
/** Shared zero rational constant constructor. */
|
|
85
|
+
function rationalZero() {
|
|
86
|
+
return { numerator: 0n, denominator: 1n };
|
|
87
|
+
}
|
|
88
|
+
/** Shared one rational constant constructor. */
|
|
89
|
+
function rationalOne() {
|
|
90
|
+
return { numerator: 1n, denominator: 1n };
|
|
91
|
+
}
|
|
92
|
+
/** Convert a normalized rational back into the SAD-facing textual weight form. */
|
|
93
|
+
function formatWeight(weight) {
|
|
94
|
+
if (weight.denominator === 1n
|
|
95
|
+
|| weight.numerator === 0n
|
|
96
|
+
|| weight.numerator === weight.denominator) {
|
|
97
|
+
return weight.numerator.toString(10);
|
|
98
|
+
}
|
|
99
|
+
return `${weight.numerator.toString(10)}/${weight.denominator.toString(10)}`;
|
|
100
|
+
}
|
|
101
|
+
/** Encode one rational into the compact bext token form used inside weighted limens. */
|
|
102
|
+
function encodeBextWeight(weight) {
|
|
103
|
+
if (weight.denominator === 1n
|
|
104
|
+
|| weight.numerator === 0n
|
|
105
|
+
|| weight.numerator === weight.denominator) {
|
|
106
|
+
return weight.numerator.toString(10);
|
|
107
|
+
}
|
|
108
|
+
return `${weight.numerator.toString(10)}s${weight.denominator.toString(10)}`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Parse and validate one SAD-facing weight token.
|
|
112
|
+
*
|
|
113
|
+
* Boundary contract:
|
|
114
|
+
* - accepts only integer `0`/`1` or fractional `n/d` forms
|
|
115
|
+
* - rejects values outside the inclusive range `[0, 1]`
|
|
116
|
+
* - normalizes equivalent forms such as `2/4` into exact reduced rationals
|
|
117
|
+
*/
|
|
118
|
+
function parseWeight(input) {
|
|
119
|
+
const text = input.trim();
|
|
120
|
+
if (text.length === 0) {
|
|
121
|
+
throw new Error("Empty threshold weight.");
|
|
122
|
+
}
|
|
123
|
+
if (/^-?\d+$/.test(text)) {
|
|
124
|
+
const value = BigInt(text);
|
|
125
|
+
if (value < 0n || value > 1n) {
|
|
126
|
+
throw new Error(`Invalid threshold weight not 0 <= ${text} <= 1.`);
|
|
127
|
+
}
|
|
128
|
+
return { numerator: value, denominator: 1n };
|
|
129
|
+
}
|
|
130
|
+
const match = text.match(/^(-?\d+)\/(\d+)$/);
|
|
131
|
+
if (!match) {
|
|
132
|
+
throw new Error(`Invalid threshold weight ${text}.`);
|
|
133
|
+
}
|
|
134
|
+
const numerator = BigInt(match[1]);
|
|
135
|
+
const denominator = BigInt(match[2]);
|
|
136
|
+
const normalized = normalizeRational(numerator, denominator);
|
|
137
|
+
if (compareRationals(normalized, rationalZero()) < 0
|
|
138
|
+
|| compareRationals(normalized, rationalOne()) > 0) {
|
|
139
|
+
throw new Error(`Invalid threshold weight not 0 <= ${text} <= 1.`);
|
|
140
|
+
}
|
|
141
|
+
return normalized;
|
|
142
|
+
}
|
|
143
|
+
/** Deep-clone the public semantic threshold shape before returning it outward. */
|
|
144
|
+
function cloneThresholdSith(sith) {
|
|
145
|
+
if (typeof sith === "string") {
|
|
146
|
+
return sith;
|
|
147
|
+
}
|
|
148
|
+
return sith.map((clauseOrEntry) => {
|
|
149
|
+
if (typeof clauseOrEntry === "string") {
|
|
150
|
+
return clauseOrEntry;
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(clauseOrEntry)) {
|
|
153
|
+
return clauseOrEntry.map((entry) => typeof entry === "string" ? entry : Object.fromEntries(Object.entries(entry).map(([k, values]) => [k, [...values]])));
|
|
154
|
+
}
|
|
155
|
+
return Object.fromEntries(Object.entries(clauseOrEntry).map(([k, values]) => [k, [...values]]));
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Choose the StrB64/Bexter code family variant for a weighted threshold limen.
|
|
160
|
+
*
|
|
161
|
+
* The selection depends on raw byte length, not bext text length, because
|
|
162
|
+
* StrB64 lead-size semantics are defined over the underlying CESR raw payload.
|
|
163
|
+
*/
|
|
164
|
+
function chooseBexterCode(rawLength) {
|
|
165
|
+
const leadSize = (3 - (rawLength % 3)) % 3;
|
|
166
|
+
return leadSize === 0
|
|
167
|
+
? LabelDex.StrB64_L0
|
|
168
|
+
: leadSize === 1
|
|
169
|
+
? LabelDex.StrB64_L1
|
|
170
|
+
: LabelDex.StrB64_L2;
|
|
171
|
+
}
|
|
172
|
+
/** Resolve the smallest numeric-threshold codex entry that can carry `rawSize` bytes. */
|
|
173
|
+
function chooseNumberCode(rawSize) {
|
|
174
|
+
const entry = NUMERIC_CAPACITIES.find(({ rawSize: max }) => rawSize <= max);
|
|
175
|
+
if (!entry) {
|
|
176
|
+
throw new Error(`Unsupported threshold width=${rawSize}.`);
|
|
177
|
+
}
|
|
178
|
+
return entry.code;
|
|
179
|
+
}
|
|
180
|
+
/** Convert a non-negative bigint threshold into big-endian raw bytes. */
|
|
181
|
+
function bigintToBytes(value) {
|
|
182
|
+
if (value < 0n) {
|
|
183
|
+
throw new Error(`Negative threshold number=${value}.`);
|
|
184
|
+
}
|
|
185
|
+
if (value === 0n) {
|
|
186
|
+
return new Uint8Array([0]);
|
|
187
|
+
}
|
|
188
|
+
const bytes = [];
|
|
189
|
+
let working = value;
|
|
190
|
+
while (working > 0n) {
|
|
191
|
+
bytes.unshift(Number(working & 0xffn));
|
|
192
|
+
working >>= 8n;
|
|
193
|
+
}
|
|
194
|
+
return new Uint8Array(bytes);
|
|
195
|
+
}
|
|
196
|
+
/** Build the `MatterInit` used to serialize a numeric threshold into CESR. */
|
|
197
|
+
function makeNumberMatterInit(value) {
|
|
198
|
+
const raw = bigintToBytes(value);
|
|
199
|
+
const code = chooseNumberCode(raw.length);
|
|
200
|
+
const entry = NUMERIC_CAPACITIES.find(({ code: current }) => current === code);
|
|
201
|
+
if (!entry) {
|
|
202
|
+
throw new Error(`Unsupported threshold number code=${code}.`);
|
|
203
|
+
}
|
|
204
|
+
const padded = new Uint8Array(entry.rawSize);
|
|
205
|
+
padded.set(raw, entry.rawSize - raw.length);
|
|
206
|
+
return { code, raw: padded };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Normalize a public weighted threshold expression into exact arithmetic form.
|
|
210
|
+
*
|
|
211
|
+
* Responsibilities:
|
|
212
|
+
* - validate clause/group shape and KERIpy-style invariants
|
|
213
|
+
* - compute the internal rational AST used by `satisfy(indices)`
|
|
214
|
+
* - return a canonical semantic clone suitable for `sith` exposure
|
|
215
|
+
*/
|
|
216
|
+
function normalizeWeightedInput(value) {
|
|
217
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
218
|
+
throw new Error("Empty weighted threshold expression.");
|
|
219
|
+
}
|
|
220
|
+
const wrapSingleClause = value.some((entry) => !Array.isArray(entry));
|
|
221
|
+
const sourceClauses = wrapSingleClause
|
|
222
|
+
? [value]
|
|
223
|
+
: value;
|
|
224
|
+
const clauses = [];
|
|
225
|
+
for (const clause of sourceClauses) {
|
|
226
|
+
if (!Array.isArray(clause) || clause.length === 0) {
|
|
227
|
+
throw new Error("Empty weighted threshold clause.");
|
|
228
|
+
}
|
|
229
|
+
let topWeight = rationalZero();
|
|
230
|
+
const normalizedClause = [];
|
|
231
|
+
for (const entry of clause) {
|
|
232
|
+
if (typeof entry === "string") {
|
|
233
|
+
const weight = parseWeight(entry);
|
|
234
|
+
normalizedClause.push(weight);
|
|
235
|
+
topWeight = addRationals(topWeight, weight);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!isRecord(entry)) {
|
|
239
|
+
throw new Error("Invalid weighted threshold clause entry.");
|
|
240
|
+
}
|
|
241
|
+
const keys = Object.keys(entry);
|
|
242
|
+
if (keys.length !== 1) {
|
|
243
|
+
throw new Error("Nested weighted threshold groups must have exactly one key.");
|
|
244
|
+
}
|
|
245
|
+
const groupKey = keys[0];
|
|
246
|
+
const nested = entry[groupKey];
|
|
247
|
+
if (!Array.isArray(nested) || nested.length === 0) {
|
|
248
|
+
throw new Error("Nested weighted threshold groups must contain member weights.");
|
|
249
|
+
}
|
|
250
|
+
const groupWeight = parseWeight(groupKey);
|
|
251
|
+
const members = nested.map((member) => {
|
|
252
|
+
if (typeof member !== "string") {
|
|
253
|
+
throw new Error("Nested threshold member weights must be strings.");
|
|
254
|
+
}
|
|
255
|
+
return parseWeight(member);
|
|
256
|
+
});
|
|
257
|
+
let memberSum = rationalZero();
|
|
258
|
+
for (const member of members) {
|
|
259
|
+
memberSum = addRationals(memberSum, member);
|
|
260
|
+
}
|
|
261
|
+
if (!atLeastOne(memberSum)) {
|
|
262
|
+
throw new Error("All nested weighted threshold sums must be at least 1.");
|
|
263
|
+
}
|
|
264
|
+
normalizedClause.push({ weight: groupWeight, members });
|
|
265
|
+
topWeight = addRationals(topWeight, groupWeight);
|
|
266
|
+
}
|
|
267
|
+
if (!atLeastOne(topWeight)) {
|
|
268
|
+
throw new Error("All weighted threshold clause sums must be at least 1.");
|
|
269
|
+
}
|
|
270
|
+
clauses.push(normalizedClause);
|
|
271
|
+
}
|
|
272
|
+
const semanticClauses = clauses.map((clause) => clause.map((entry) => "numerator" in entry
|
|
273
|
+
? formatWeight(entry)
|
|
274
|
+
: { [formatWeight(entry.weight)]: entry.members.map(formatWeight) }));
|
|
275
|
+
return {
|
|
276
|
+
clauses,
|
|
277
|
+
semantic: semanticClauses.length === 1
|
|
278
|
+
? semanticClauses[0]
|
|
279
|
+
: semanticClauses,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/** Parse a JSON threshold literal supplied through string-only config or CLI seams. */
|
|
283
|
+
function parseWeightedJson(text) {
|
|
284
|
+
const parsed = JSON.parse(text);
|
|
285
|
+
if (!Array.isArray(parsed)) {
|
|
286
|
+
throw new Error("Weighted threshold JSON must decode to an array.");
|
|
287
|
+
}
|
|
288
|
+
return parsed;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Normalize any semantic threshold input into one constructor-ready bundle.
|
|
292
|
+
*
|
|
293
|
+
* This is the central semantic funnel for `{ sith }`/numeric/weighted inputs:
|
|
294
|
+
* callers receive CESR serialization material plus the precomputed state needed
|
|
295
|
+
* for `weighted`, `size`, `num`, `thold`, and `sith`.
|
|
296
|
+
*/
|
|
297
|
+
function normalizeThresholdInput(value) {
|
|
298
|
+
if (typeof value === "number") {
|
|
299
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
300
|
+
throw new Error(`Invalid numeric threshold=${value}.`);
|
|
301
|
+
}
|
|
302
|
+
return normalizeThresholdInput(BigInt(value));
|
|
303
|
+
}
|
|
304
|
+
if (typeof value === "bigint") {
|
|
305
|
+
const matterInit = makeNumberMatterInit(value);
|
|
306
|
+
const size = value > MAX_SAFE_BIGINT
|
|
307
|
+
? Number.MAX_SAFE_INTEGER
|
|
308
|
+
: Number(value);
|
|
309
|
+
return {
|
|
310
|
+
matterInit,
|
|
311
|
+
weighted: false,
|
|
312
|
+
numeric: value,
|
|
313
|
+
size,
|
|
314
|
+
thold: value,
|
|
315
|
+
semantic: value.toString(16),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (typeof value === "string") {
|
|
319
|
+
const trimmed = value.trim();
|
|
320
|
+
if (trimmed.length === 0) {
|
|
321
|
+
throw new Error("Empty threshold expression.");
|
|
322
|
+
}
|
|
323
|
+
if (trimmed.startsWith("[")) {
|
|
324
|
+
return normalizeThresholdInput(parseWeightedJson(trimmed));
|
|
325
|
+
}
|
|
326
|
+
if (!/^[0-9a-f]+$/i.test(trimmed)) {
|
|
327
|
+
throw new Error(`Invalid numeric threshold ${trimmed}.`);
|
|
328
|
+
}
|
|
329
|
+
return normalizeThresholdInput(BigInt(`0x${trimmed}`));
|
|
330
|
+
}
|
|
331
|
+
const weighted = normalizeWeightedInput(value);
|
|
332
|
+
const bext = weighted.clauses.map((clause) => clause.map((entry) => "numerator" in entry
|
|
333
|
+
? encodeBextWeight(entry)
|
|
334
|
+
: `${encodeBextWeight(entry.weight)}k${entry.members.map(encodeBextWeight).join("v")}`).join("c")).join("a");
|
|
335
|
+
const raw = Bexter.rawify(bext);
|
|
336
|
+
const code = chooseBexterCode(raw.length);
|
|
337
|
+
return {
|
|
338
|
+
matterInit: { code, raw },
|
|
339
|
+
weighted: true,
|
|
340
|
+
numeric: null,
|
|
341
|
+
size: weighted.clauses.reduce((count, clause) => count
|
|
342
|
+
+ clause.reduce((clauseCount, entry) => clauseCount + ("numerator" in entry ? 1 : entry.members.length), 0), 0),
|
|
343
|
+
thold: weighted.clauses,
|
|
344
|
+
semantic: weighted.semantic,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Decode an already-qualified CESR threshold primitive back into semantic form.
|
|
349
|
+
*
|
|
350
|
+
* Numeric and weighted codex families intentionally converge here so reload,
|
|
351
|
+
* parser hydration, and `limen` construction all share one interpretation path.
|
|
352
|
+
*/
|
|
353
|
+
function decodeThresholdMatter(matter) {
|
|
354
|
+
if (THOLDER_NUMERIC_CODES.has(matter.code)) {
|
|
355
|
+
const number = new NumberPrimitive(matter);
|
|
356
|
+
const value = number.num;
|
|
357
|
+
return {
|
|
358
|
+
weighted: false,
|
|
359
|
+
numeric: value,
|
|
360
|
+
size: value > MAX_SAFE_BIGINT ? Number.MAX_SAFE_INTEGER : Number(value),
|
|
361
|
+
thold: value,
|
|
362
|
+
semantic: value.toString(16),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
if (THOLDER_WEIGHTED_CODES.has(matter.code)) {
|
|
366
|
+
const bexter = new Bexter(matter);
|
|
367
|
+
const clauses = bexter.bext.replaceAll("s", "/").split("a").map((clause) => clause.split("c").map((entry) => {
|
|
368
|
+
const at = entry.indexOf("k");
|
|
369
|
+
if (at < 0) {
|
|
370
|
+
return entry;
|
|
371
|
+
}
|
|
372
|
+
const weight = entry.slice(0, at);
|
|
373
|
+
const members = entry.slice(at + 1).split("v");
|
|
374
|
+
return { [weight]: members };
|
|
375
|
+
}));
|
|
376
|
+
const normalized = normalizeWeightedInput(clauses.length === 1 ? clauses[0] : clauses);
|
|
377
|
+
return {
|
|
378
|
+
weighted: true,
|
|
379
|
+
numeric: null,
|
|
380
|
+
size: normalized.clauses.reduce((count, clause) => count
|
|
381
|
+
+ clause.reduce((clauseCount, entry) => clauseCount + ("numerator" in entry ? 1 : entry.members.length), 0), 0),
|
|
382
|
+
thold: normalized.clauses,
|
|
383
|
+
semantic: normalized.semantic,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
throw new UnknownCodeError(`Expected threshold code, got ${matter.code}`);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Accept the full constructor surface supported by `Tholder`.
|
|
390
|
+
*
|
|
391
|
+
* Order matters here:
|
|
392
|
+
* 1. explicit `Matter`/`MatterInit`
|
|
393
|
+
* 2. explicit `limen`
|
|
394
|
+
* 3. semantic `sith` or direct numeric/weighted input
|
|
395
|
+
*/
|
|
396
|
+
function normalizeConstructorInput(init) {
|
|
397
|
+
if (isMatterInitLike(init)) {
|
|
398
|
+
const matter = init instanceof Matter ? init : new Matter(init);
|
|
399
|
+
return { matterInit: init, ...decodeThresholdMatter(matter) };
|
|
400
|
+
}
|
|
401
|
+
if (isRecord(init) && "limen" in init && init.limen !== undefined) {
|
|
402
|
+
const matterInit = typeof init.limen === "string"
|
|
403
|
+
? { qb64: init.limen }
|
|
404
|
+
: { qb64b: init.limen };
|
|
405
|
+
const matter = new Matter(matterInit);
|
|
406
|
+
return { matterInit, ...decodeThresholdMatter(matter) };
|
|
407
|
+
}
|
|
408
|
+
const semantic = isRecord(init) && "sith" in init ? init.sith : init;
|
|
409
|
+
return normalizeThresholdInput(semantic);
|
|
410
|
+
}
|
|
5
411
|
/**
|
|
6
412
|
* Threshold expression primitive.
|
|
7
413
|
*
|
|
8
|
-
* KERIpy substance:
|
|
9
|
-
*
|
|
414
|
+
* KERIpy substance:
|
|
415
|
+
* - `Tholder` supports both numeric thresholds and weighted threshold
|
|
416
|
+
* expressions encoded as StrB64 payloads
|
|
417
|
+
* - `satisfy(indices)` is the semantic contract upper layers rely on when
|
|
418
|
+
* validating current and prior-next threshold satisfaction
|
|
419
|
+
* - `limen` reuses the CESR `Number` family for numeric thresholds and the
|
|
420
|
+
* `Bexter` bext encoding for weighted thresholds
|
|
10
421
|
*/
|
|
11
422
|
export class Tholder extends Matter {
|
|
423
|
+
/**
|
|
424
|
+
* Construct from wire material (`Matter`, `MatterInit`, `limen`) or semantic
|
|
425
|
+
* threshold input (`sith`, bigint, number, hex string, weighted arrays).
|
|
426
|
+
*/
|
|
12
427
|
constructor(init) {
|
|
13
|
-
const
|
|
14
|
-
super(
|
|
428
|
+
const normalized = normalizeConstructorInput(init);
|
|
429
|
+
super(normalized.matterInit);
|
|
430
|
+
Object.defineProperty(this, "_weighted", {
|
|
431
|
+
enumerable: true,
|
|
432
|
+
configurable: true,
|
|
433
|
+
writable: true,
|
|
434
|
+
value: void 0
|
|
435
|
+
});
|
|
436
|
+
Object.defineProperty(this, "_num", {
|
|
437
|
+
enumerable: true,
|
|
438
|
+
configurable: true,
|
|
439
|
+
writable: true,
|
|
440
|
+
value: void 0
|
|
441
|
+
});
|
|
442
|
+
Object.defineProperty(this, "_size", {
|
|
443
|
+
enumerable: true,
|
|
444
|
+
configurable: true,
|
|
445
|
+
writable: true,
|
|
446
|
+
value: void 0
|
|
447
|
+
});
|
|
448
|
+
Object.defineProperty(this, "_thold", {
|
|
449
|
+
enumerable: true,
|
|
450
|
+
configurable: true,
|
|
451
|
+
writable: true,
|
|
452
|
+
value: void 0
|
|
453
|
+
});
|
|
454
|
+
Object.defineProperty(this, "_sith", {
|
|
455
|
+
enumerable: true,
|
|
456
|
+
configurable: true,
|
|
457
|
+
writable: true,
|
|
458
|
+
value: void 0
|
|
459
|
+
});
|
|
15
460
|
if (!THOLDER_NUMERIC_CODES.has(this.code)
|
|
16
461
|
&& !THOLDER_WEIGHTED_CODES.has(this.code)) {
|
|
17
462
|
throw new UnknownCodeError(`Expected threshold code, got ${this.code}`);
|
|
18
463
|
}
|
|
464
|
+
this._weighted = normalized.weighted;
|
|
465
|
+
this._num = normalized.numeric;
|
|
466
|
+
this._size = normalized.size;
|
|
467
|
+
this._thold = normalized.thold;
|
|
468
|
+
this._sith = cloneThresholdSith(normalized.semantic);
|
|
19
469
|
}
|
|
470
|
+
/** True when this instance represents a weighted threshold rather than a numeric count. */
|
|
471
|
+
get weighted() {
|
|
472
|
+
return this._weighted;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Number of signer slots consumed by this threshold expression.
|
|
476
|
+
*
|
|
477
|
+
* For nested groups this counts nested members, because those are the real
|
|
478
|
+
* contiguous signer slots upper layers index into.
|
|
479
|
+
*/
|
|
480
|
+
get size() {
|
|
481
|
+
return this._size;
|
|
482
|
+
}
|
|
483
|
+
/** Internal normalized threshold semantics exposed for maintainer debugging and tests. */
|
|
484
|
+
get thold() {
|
|
485
|
+
return this._weighted
|
|
486
|
+
? this._thold.map((clause) => clause.map((entry) => "numerator" in entry ? { ...entry } : {
|
|
487
|
+
weight: { ...entry.weight },
|
|
488
|
+
members: entry.members.map((member) => ({ ...member })),
|
|
489
|
+
}))
|
|
490
|
+
: this._thold;
|
|
491
|
+
}
|
|
492
|
+
/** Numeric threshold value when `weighted === false`, otherwise `null`. */
|
|
493
|
+
get num() {
|
|
494
|
+
return this._num;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Qualified CESR threshold representation used in event bodies and state.
|
|
498
|
+
*
|
|
499
|
+
* Numeric thresholds are encoded through the numeric matter family; weighted
|
|
500
|
+
* thresholds are encoded through the compact bext/limen form KERIpy uses.
|
|
501
|
+
*/
|
|
502
|
+
get limen() {
|
|
503
|
+
return this.qb64;
|
|
504
|
+
}
|
|
505
|
+
/** Public semantic threshold expression cloned back into SAD-facing shape. */
|
|
20
506
|
get sith() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
507
|
+
return cloneThresholdSith(this._sith);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Test whether the provided signer indices satisfy this threshold.
|
|
511
|
+
*
|
|
512
|
+
* Maintainer model:
|
|
513
|
+
* - numeric thresholds are pure cardinality checks after deduplication is
|
|
514
|
+
* handled by the caller or irrelevant to the count
|
|
515
|
+
* - weighted thresholds project indices onto consecutive signer slots from
|
|
516
|
+
* left to right across clauses and nested-group members
|
|
517
|
+
* - each clause must accumulate at least weight `1`
|
|
518
|
+
*/
|
|
519
|
+
satisfy(indices) {
|
|
520
|
+
if (!this._weighted) {
|
|
521
|
+
return this._num !== null
|
|
522
|
+
&& this._num > 0n
|
|
523
|
+
&& BigInt(indices.length) >= this._num;
|
|
24
524
|
}
|
|
25
|
-
|
|
525
|
+
if (indices.length === 0) {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
const ordered = [...new Set(indices)].sort((a, b) => a - b);
|
|
529
|
+
const sats = new Array(this._size).fill(false);
|
|
530
|
+
for (const index of ordered) {
|
|
531
|
+
if (index >= 0 && index < sats.length) {
|
|
532
|
+
sats[index] = true;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
let offset = 0;
|
|
536
|
+
for (const clause of this._thold) {
|
|
537
|
+
let clauseWeight = rationalZero();
|
|
538
|
+
for (const entry of clause) {
|
|
539
|
+
if ("numerator" in entry) {
|
|
540
|
+
if (sats[offset]) {
|
|
541
|
+
clauseWeight = addRationals(clauseWeight, entry);
|
|
542
|
+
}
|
|
543
|
+
offset += 1;
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
let memberWeight = rationalZero();
|
|
547
|
+
for (const member of entry.members) {
|
|
548
|
+
if (sats[offset]) {
|
|
549
|
+
memberWeight = addRationals(memberWeight, member);
|
|
550
|
+
}
|
|
551
|
+
offset += 1;
|
|
552
|
+
}
|
|
553
|
+
if (atLeastOne(memberWeight)) {
|
|
554
|
+
clauseWeight = addRationals(clauseWeight, entry.weight);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (!atLeastOne(clauseWeight)) {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return true;
|
|
26
562
|
}
|
|
27
563
|
}
|
|
28
|
-
/**
|
|
564
|
+
/**
|
|
565
|
+
* Parse and hydrate `Tholder` from txt/qb2 bytes.
|
|
566
|
+
*
|
|
567
|
+
* Boundary contract: stream parsing decides whether the next token is text or
|
|
568
|
+
* binary CESR, while `Tholder` owns threshold-codex validation and semantic
|
|
569
|
+
* hydration after the underlying `Matter` is parsed.
|
|
570
|
+
*/
|
|
29
571
|
export function parseTholder(input, cold) {
|
|
30
572
|
return new Tholder(parseMatter(input, cold));
|
|
31
573
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { UnknownCodeError } from "../core/errors.js";
|
|
2
2
|
import { TRAIT_TAGS } from "./codex.js";
|
|
3
|
-
import {
|
|
3
|
+
import { parseMatter } from "./matter.js";
|
|
4
4
|
import { Tagger } from "./tagger.js";
|
|
5
5
|
/**
|
|
6
6
|
* Configuration-trait primitive for key-event trait tags.
|
|
@@ -10,8 +10,7 @@ import { Tagger } from "./tagger.js";
|
|
|
10
10
|
*/
|
|
11
11
|
export class Traitor extends Tagger {
|
|
12
12
|
constructor(init) {
|
|
13
|
-
|
|
14
|
-
super(matter);
|
|
13
|
+
super(init);
|
|
15
14
|
if (!TRAIT_TAGS.has(this.tag)) {
|
|
16
15
|
throw new UnknownCodeError(`Invalid trait tag for Traitor: ${this.tag}`);
|
|
17
16
|
}
|