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.
Files changed (204) hide show
  1. package/esm/src/adapters/async-iterable.js +8 -0
  2. package/esm/src/adapters/effection.js +6 -0
  3. package/esm/src/annotate/annotator.js +12 -0
  4. package/esm/src/annotate/cli.js +12 -0
  5. package/esm/src/annotate/comments.js +5 -1
  6. package/esm/src/annotate/denot.js +6 -0
  7. package/esm/src/annotate/render.js +15 -3
  8. package/esm/src/core/bytes.js +9 -0
  9. package/esm/src/core/errors.js +3 -0
  10. package/esm/src/core/parser-engine.js +1 -1
  11. package/esm/src/core/parser-frame-parser.js +22 -18
  12. package/esm/src/core/vocabulary.js +47 -0
  13. package/esm/src/index.js +5 -0
  14. package/esm/src/parser/attachment-parser.js +4 -0
  15. package/esm/src/parser/cold-start.js +6 -0
  16. package/esm/src/parser/group-dispatch.js +2 -2
  17. package/esm/src/primitives/aggor.js +374 -30
  18. package/esm/src/primitives/bexter.js +2 -3
  19. package/esm/src/primitives/blinder.js +6 -0
  20. package/esm/src/primitives/byte-like.js +19 -0
  21. package/esm/src/primitives/cigar.js +17 -3
  22. package/esm/src/primitives/cipher.js +91 -6
  23. package/esm/src/primitives/codex.js +39 -6
  24. package/esm/src/primitives/compactor.js +307 -3
  25. package/esm/src/primitives/counter.js +6 -2
  26. package/esm/src/primitives/dater.js +1 -2
  27. package/esm/src/primitives/decimer.js +7 -11
  28. package/esm/src/primitives/decrypter.js +123 -7
  29. package/esm/src/primitives/diger.js +57 -3
  30. package/esm/src/primitives/disclosure.js +300 -0
  31. package/esm/src/primitives/encrypter.js +130 -7
  32. package/esm/src/primitives/hydrate.js +49 -0
  33. package/esm/src/primitives/ilker.js +2 -3
  34. package/esm/src/primitives/indexer.js +21 -4
  35. package/esm/src/primitives/labeler.js +1 -2
  36. package/esm/src/primitives/mapper.js +612 -35
  37. package/esm/src/primitives/matter.js +133 -3
  38. package/esm/src/primitives/mediar.js +6 -0
  39. package/esm/src/primitives/noncer.js +2 -3
  40. package/esm/src/primitives/number.js +1 -2
  41. package/esm/src/primitives/pather.js +102 -5
  42. package/esm/src/primitives/prefixer.js +1 -2
  43. package/esm/src/primitives/primitive.js +14 -0
  44. package/esm/src/primitives/registry.js +2 -0
  45. package/esm/src/primitives/saider.js +80 -3
  46. package/esm/src/primitives/salter.js +96 -2
  47. package/esm/src/primitives/sealed-box.js +41 -0
  48. package/esm/src/primitives/seqner.js +1 -2
  49. package/esm/src/primitives/siger.js +58 -3
  50. package/esm/src/primitives/signer.js +243 -9
  51. package/esm/src/primitives/streamer.js +28 -0
  52. package/esm/src/primitives/structing.js +727 -0
  53. package/esm/src/primitives/structor.js +20 -0
  54. package/esm/src/primitives/tagger.js +1 -2
  55. package/esm/src/primitives/texter.js +1 -2
  56. package/esm/src/primitives/tholder.js +552 -10
  57. package/esm/src/primitives/traitor.js +2 -3
  58. package/esm/src/primitives/verfer.js +50 -6
  59. package/esm/src/primitives/verser.js +16 -6
  60. package/esm/src/router/router-stub.js +1 -0
  61. package/esm/src/serder/native.js +1326 -0
  62. package/esm/src/serder/serder.js +1558 -59
  63. package/esm/src/serder/serdery.js +92 -7
  64. package/esm/src/serder/smell.js +26 -1
  65. package/esm/src/tables/codex-utils.js +2 -0
  66. package/esm/src/tables/counter-codex.js +2 -0
  67. package/esm/src/tables/counter-groups.js +6 -0
  68. package/esm/src/tables/versions.js +4 -0
  69. package/esm/src/version.js +5 -2
  70. package/package.json +5 -2
  71. package/types/src/adapters/async-iterable.d.ts +8 -0
  72. package/types/src/adapters/async-iterable.d.ts.map +1 -1
  73. package/types/src/adapters/effection.d.ts +7 -0
  74. package/types/src/adapters/effection.d.ts.map +1 -1
  75. package/types/src/annotate/annotator.d.ts +12 -0
  76. package/types/src/annotate/annotator.d.ts.map +1 -1
  77. package/types/src/annotate/cli.d.ts +13 -0
  78. package/types/src/annotate/cli.d.ts.map +1 -1
  79. package/types/src/annotate/comments.d.ts +4 -0
  80. package/types/src/annotate/comments.d.ts.map +1 -1
  81. package/types/src/annotate/denot.d.ts +6 -0
  82. package/types/src/annotate/denot.d.ts.map +1 -1
  83. package/types/src/annotate/render.d.ts.map +1 -1
  84. package/types/src/annotate/types.d.ts +2 -0
  85. package/types/src/annotate/types.d.ts.map +1 -1
  86. package/types/src/bench/parser-benchmark.d.ts +3 -0
  87. package/types/src/bench/parser-benchmark.d.ts.map +1 -1
  88. package/types/src/core/bytes.d.ts +9 -0
  89. package/types/src/core/bytes.d.ts.map +1 -1
  90. package/types/src/core/errors.d.ts +3 -0
  91. package/types/src/core/errors.d.ts.map +1 -1
  92. package/types/src/core/parser-engine.d.ts +4 -0
  93. package/types/src/core/parser-engine.d.ts.map +1 -1
  94. package/types/src/core/parser-frame-parser.d.ts.map +1 -1
  95. package/types/src/core/types.d.ts +2 -0
  96. package/types/src/core/types.d.ts.map +1 -1
  97. package/types/src/core/vocabulary.d.ts +48 -0
  98. package/types/src/core/vocabulary.d.ts.map +1 -0
  99. package/types/src/index.d.ts +5 -0
  100. package/types/src/index.d.ts.map +1 -1
  101. package/types/src/parser/attachment-parser.d.ts +4 -0
  102. package/types/src/parser/attachment-parser.d.ts.map +1 -1
  103. package/types/src/parser/cold-start.d.ts +6 -0
  104. package/types/src/parser/cold-start.d.ts.map +1 -1
  105. package/types/src/parser/group-dispatch.d.ts.map +1 -1
  106. package/types/src/primitives/aggor.d.ts +100 -13
  107. package/types/src/primitives/aggor.d.ts.map +1 -1
  108. package/types/src/primitives/bexter.d.ts.map +1 -1
  109. package/types/src/primitives/blinder.d.ts +6 -0
  110. package/types/src/primitives/blinder.d.ts.map +1 -1
  111. package/types/src/primitives/byte-like.d.ts +7 -0
  112. package/types/src/primitives/byte-like.d.ts.map +1 -0
  113. package/types/src/primitives/cigar.d.ts +11 -1
  114. package/types/src/primitives/cigar.d.ts.map +1 -1
  115. package/types/src/primitives/cipher.d.ts +73 -2
  116. package/types/src/primitives/cipher.d.ts.map +1 -1
  117. package/types/src/primitives/codex.d.ts +34 -4
  118. package/types/src/primitives/codex.d.ts.map +1 -1
  119. package/types/src/primitives/compactor.d.ts +94 -2
  120. package/types/src/primitives/compactor.d.ts.map +1 -1
  121. package/types/src/primitives/counter.d.ts.map +1 -1
  122. package/types/src/primitives/dater.d.ts.map +1 -1
  123. package/types/src/primitives/decimer.d.ts.map +1 -1
  124. package/types/src/primitives/decrypter.d.ts +64 -3
  125. package/types/src/primitives/decrypter.d.ts.map +1 -1
  126. package/types/src/primitives/diger.d.ts +16 -0
  127. package/types/src/primitives/diger.d.ts.map +1 -1
  128. package/types/src/primitives/disclosure.d.ts +201 -0
  129. package/types/src/primitives/disclosure.d.ts.map +1 -0
  130. package/types/src/primitives/encrypter.d.ts +66 -4
  131. package/types/src/primitives/encrypter.d.ts.map +1 -1
  132. package/types/src/primitives/hydrate.d.ts +20 -0
  133. package/types/src/primitives/hydrate.d.ts.map +1 -0
  134. package/types/src/primitives/ilker.d.ts.map +1 -1
  135. package/types/src/primitives/indexer.d.ts.map +1 -1
  136. package/types/src/primitives/labeler.d.ts.map +1 -1
  137. package/types/src/primitives/mapper.d.ts +132 -11
  138. package/types/src/primitives/mapper.d.ts.map +1 -1
  139. package/types/src/primitives/matter.d.ts +33 -1
  140. package/types/src/primitives/matter.d.ts.map +1 -1
  141. package/types/src/primitives/mediar.d.ts +6 -0
  142. package/types/src/primitives/mediar.d.ts.map +1 -1
  143. package/types/src/primitives/noncer.d.ts.map +1 -1
  144. package/types/src/primitives/number.d.ts.map +1 -1
  145. package/types/src/primitives/pather.d.ts +21 -0
  146. package/types/src/primitives/pather.d.ts.map +1 -1
  147. package/types/src/primitives/prefixer.d.ts.map +1 -1
  148. package/types/src/primitives/primitive.d.ts +26 -0
  149. package/types/src/primitives/primitive.d.ts.map +1 -1
  150. package/types/src/primitives/registry.d.ts +3 -0
  151. package/types/src/primitives/registry.d.ts.map +1 -1
  152. package/types/src/primitives/saider.d.ts +41 -0
  153. package/types/src/primitives/saider.d.ts.map +1 -1
  154. package/types/src/primitives/salter.d.ts +56 -1
  155. package/types/src/primitives/salter.d.ts.map +1 -1
  156. package/types/src/primitives/sealed-box.d.ts +29 -0
  157. package/types/src/primitives/sealed-box.d.ts.map +1 -0
  158. package/types/src/primitives/seqner.d.ts.map +1 -1
  159. package/types/src/primitives/siger.d.ts +14 -0
  160. package/types/src/primitives/siger.d.ts.map +1 -1
  161. package/types/src/primitives/signer.d.ts +80 -4
  162. package/types/src/primitives/signer.d.ts.map +1 -1
  163. package/types/src/primitives/streamer.d.ts +31 -0
  164. package/types/src/primitives/streamer.d.ts.map +1 -0
  165. package/types/src/primitives/structing.d.ts +4114 -0
  166. package/types/src/primitives/structing.d.ts.map +1 -0
  167. package/types/src/primitives/structor.d.ts +6 -0
  168. package/types/src/primitives/structor.d.ts.map +1 -1
  169. package/types/src/primitives/tagger.d.ts.map +1 -1
  170. package/types/src/primitives/texter.d.ts.map +1 -1
  171. package/types/src/primitives/tholder.d.ts +150 -5
  172. package/types/src/primitives/tholder.d.ts.map +1 -1
  173. package/types/src/primitives/traitor.d.ts.map +1 -1
  174. package/types/src/primitives/unknown.d.ts +1 -0
  175. package/types/src/primitives/unknown.d.ts.map +1 -1
  176. package/types/src/primitives/verfer.d.ts +12 -0
  177. package/types/src/primitives/verfer.d.ts.map +1 -1
  178. package/types/src/primitives/verser.d.ts +14 -3
  179. package/types/src/primitives/verser.d.ts.map +1 -1
  180. package/types/src/router/router-stub.d.ts +3 -0
  181. package/types/src/router/router-stub.d.ts.map +1 -1
  182. package/types/src/serder/native.d.ts +115 -0
  183. package/types/src/serder/native.d.ts.map +1 -0
  184. package/types/src/serder/serder.d.ts +187 -9
  185. package/types/src/serder/serder.d.ts.map +1 -1
  186. package/types/src/serder/serdery.d.ts +42 -0
  187. package/types/src/serder/serdery.d.ts.map +1 -1
  188. package/types/src/serder/smell.d.ts +22 -0
  189. package/types/src/serder/smell.d.ts.map +1 -1
  190. package/types/src/tables/codex-utils.d.ts +3 -0
  191. package/types/src/tables/codex-utils.d.ts.map +1 -1
  192. package/types/src/tables/counter-codex.d.ts +4 -0
  193. package/types/src/tables/counter-codex.d.ts.map +1 -1
  194. package/types/src/tables/counter-groups.d.ts +6 -0
  195. package/types/src/tables/counter-groups.d.ts.map +1 -1
  196. package/types/src/tables/counter.tables.generated.d.ts.map +1 -1
  197. package/types/src/tables/indexer.codex.generated.d.ts.map +1 -1
  198. package/types/src/tables/matter.codex.generated.d.ts.map +1 -1
  199. package/types/src/tables/table-types.d.ts +6 -0
  200. package/types/src/tables/table-types.d.ts.map +1 -1
  201. package/types/src/tables/versions.d.ts +6 -0
  202. package/types/src/tables/versions.d.ts.map +1 -1
  203. package/types/src/version.d.ts +5 -2
  204. 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
- const matter = init instanceof Matter ? init : new Matter(init);
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
- const matter = init instanceof Matter ? init : new Matter(init);
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: `Tholder` supports both numeric thresholds and weighted
9
- * threshold expressions encoded as StrB64 payloads.
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 matter = init instanceof Matter ? init : new Matter(init);
14
- super(matter);
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
- if (THOLDER_NUMERIC_CODES.has(this.code)) {
22
- return [...this.raw].reduce((acc, b) => (acc << 8n) | BigInt(b), 0n)
23
- .toString(16);
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
- return t(this.raw);
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
- /** Parse and hydrate `Tholder` from txt/qb2 bytes. */
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 { Matter, parseMatter } from "./matter.js";
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
- const matter = init instanceof Matter ? init : new Matter(init);
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
  }