@mikeargento/bitgraph-player 0.1.1 → 0.2.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/README.md CHANGED
@@ -25,6 +25,24 @@ folders the BitGraph Folder writes). Exit codes: `0` TRUE, `1` FALSE,
25
25
  `2` UNDETERMINED, `3` error. `--out file` writes the verdict to a file;
26
26
  `--summary` prints a bundle reconnaissance to stderr.
27
27
 
28
+ ## Start a rule
29
+
30
+ bitgraph-play init po.pdf delivery.jpg approval.pdf --out rule.json
31
+
32
+ `init` hashes the files you name and writes a rule skeleton with the
33
+ cast filled in: one role per file, digests computed, role names from
34
+ filenames. The skeleton does not run as written — `requires.ordering`
35
+ is a placeholder you must replace, because the trust floor is the
36
+ rule's own security policy and has no default, from the scaffolder or
37
+ anywhere else. Choose the floor, say what each digest means, refine the
38
+ claim, then run it.
39
+
40
+ `init` is a reserved first word as of 0.2.0. A rule file literally
41
+ named `init` is still reachable: write it as `./init`, or after `--`,
42
+ which ends option parsing (`bitgraph-play -- init bundle/`). When a
43
+ file named `init` exists in the working directory, the bare spelling is
44
+ refused as ambiguous rather than silently picking a mode.
45
+
28
46
  ## A rule
29
47
 
30
48
  ```json
@@ -79,6 +97,18 @@ agree with it.
79
97
 
80
98
  ## API
81
99
 
100
+ One call runs the whole pipeline — the CLI is built on the same
101
+ function, so embedding Player cannot drift from it:
102
+
103
+ ```ts
104
+ import { play } from "@mikeargento/bitgraph-player";
105
+
106
+ const { verdict, bytes, exitCode } = await play("rule.json", "bundle/");
107
+ ```
108
+
109
+ The pieces are exported individually for callers that already hold an
110
+ `AuditResult` or want to intercept a stage:
111
+
82
112
  ```ts
83
113
  import { runAudit } from "@mikeargento/bitgraph-audit";
84
114
  import {
package/SPEC.md CHANGED
@@ -232,3 +232,8 @@ bundle contents MUST be byte-identical.
232
232
  0 TRUE 1 FALSE 2 UNDETERMINED 3 error
233
233
 
234
234
  Diagnostics go to stderr. Stdout carries verdict bytes only.
235
+
236
+ These requirements govern evaluation invocations. A command-line Player
237
+ MAY offer authoring conveniences (such as rule scaffolding) as distinct
238
+ subcommands; those are outside this specification and MUST NOT change
239
+ the behavior of evaluation invocations.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=init-play.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-play.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/init-play.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,302 @@
1
+ // Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
2
+ /**
3
+ * Tests for the init scaffolder and the play() one-call pipeline.
4
+ *
5
+ * Same discipline as the rest of the suite: synthetic AuditResult
6
+ * fixtures, no ledger writes, no network. The CLI wiring tests spawn the
7
+ * built cli.js against temp files created under os.tmpdir().
8
+ */
9
+ import { strict as assert } from "node:assert";
10
+ import { spawnSync } from "node:child_process";
11
+ import { createHash } from "node:crypto";
12
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import { test } from "node:test";
17
+ import { ORDERING_PLACEHOLDER, roleNameForFile, scaffoldRule } from "../init.js";
18
+ import { play, playAudit, PlayError } from "../play.js";
19
+ import { parseRule, RuleError } from "../rule.js";
20
+ import { serializeVerdict } from "../verdict.js";
21
+ import { digestFor, makeAudit } from "./fixtures.js";
22
+ const CLI_PATH = fileURLToPath(new URL("../cli.js", import.meta.url));
23
+ function hexOf(text) {
24
+ return createHash("sha256").update(text).digest("hex");
25
+ }
26
+ // ---------------------------------------------------------------------------
27
+ // roleNameForFile
28
+ // ---------------------------------------------------------------------------
29
+ test("role names: extension dropped, path stripped, disallowed chars folded", () => {
30
+ assert.equal(roleNameForFile("purchase_order.pdf"), "purchase_order");
31
+ assert.equal(roleNameForFile("/some/dir/delivery note.jpg"), "delivery-note");
32
+ assert.equal(roleNameForFile("My Photo (1).jpg"), "My-Photo-1");
33
+ assert.equal(roleNameForFile("archive.tar.gz"), "archive.tar");
34
+ });
35
+ test("role names: backslash is an ordinary character, never a separator", () => {
36
+ // On POSIX `a\b.jpg` is one legal filename. The CLI hands scaffoldRule a
37
+ // platform-correct basename; this function must not re-split it.
38
+ assert.equal(roleNameForFile(String.raw `a\b.jpg`), "a-b");
39
+ assert.equal(roleNameForFile(String.raw `C:\photos\approval.png`), "C-photos-approval");
40
+ });
41
+ test("role names: grammar guards — never empty, never pure-integer, dotfiles keep their stem", () => {
42
+ assert.equal(roleNameForFile("2024.jpg"), "file-2024");
43
+ assert.equal(roleNameForFile("™️.jpg"), "file");
44
+ assert.equal(roleNameForFile(".gitignore"), "gitignore");
45
+ assert.equal(roleNameForFile("..."), "file");
46
+ });
47
+ // ---------------------------------------------------------------------------
48
+ // scaffoldRule
49
+ // ---------------------------------------------------------------------------
50
+ const ENTRIES = [
51
+ { name: "po.pdf", sha256Hex: hexOf("po") },
52
+ { name: "delivery.jpg", sha256Hex: hexOf("delivery") },
53
+ ];
54
+ test("scaffold does not parse as written, and the only issue is the ordering placeholder", () => {
55
+ const text = scaffoldRule(ENTRIES);
56
+ assert.ok(text.includes(ORDERING_PLACEHOLDER));
57
+ assert.throws(() => parseRule(text), (err) => {
58
+ assert.ok(err instanceof RuleError);
59
+ const ruleErr = err;
60
+ assert.equal(ruleErr.issues.length, 1);
61
+ assert.match(ruleErr.issues[0], /requires\.ordering/);
62
+ return true;
63
+ });
64
+ });
65
+ test("scaffold parses once the floor is chosen; cast in input order; digests normalize", () => {
66
+ const text = scaffoldRule(ENTRIES).replace(ORDERING_PLACEHOLDER, "hash-linked");
67
+ const rule = parseRule(text);
68
+ assert.deepEqual(Object.keys(rule.cast), ["po", "delivery"]);
69
+ assert.equal(rule.cast["po"]?.digest, `sha256:${hexOf("po")}`);
70
+ assert.equal(rule.cast["po"]?.means, "po.pdf");
71
+ assert.equal(rule.requires.ordering, "hash-linked");
72
+ assert.deepEqual(rule.claim, {
73
+ all: [{ exists: "po" }, { exists: "delivery" }],
74
+ });
75
+ });
76
+ test("scaffold is deterministic; id depends on digests, not entry order", () => {
77
+ assert.equal(scaffoldRule(ENTRIES), scaffoldRule(ENTRIES));
78
+ const reversed = scaffoldRule([...ENTRIES].reverse());
79
+ const idOf = (text) => JSON.parse(text).id;
80
+ assert.equal(idOf(scaffoldRule(ENTRIES)), idOf(reversed));
81
+ assert.match(idOf(scaffoldRule(ENTRIES)), /^rule-[0-9a-f]{12}$/);
82
+ });
83
+ test("scaffold: colliding role names get ordinal suffixes in input order", () => {
84
+ const text = scaffoldRule([
85
+ { name: "a/photo.jpg", sha256Hex: hexOf("one") },
86
+ { name: "b/photo.png", sha256Hex: hexOf("two") },
87
+ { name: "c/photo.webp", sha256Hex: hexOf("three") },
88
+ ]).replace(ORDERING_PLACEHOLDER, "hash-linked");
89
+ assert.deepEqual(Object.keys(parseRule(text).cast), ["photo", "photo-2", "photo-3"]);
90
+ });
91
+ test("scaffold: a collision suffix never takes another file's natural name", () => {
92
+ // photo.png must NOT become "photo-2" while the real photo-2.jpg is
93
+ // pushed to "photo-2-2": an author editing the claim by role name would
94
+ // silently bind the wrong digest.
95
+ const text = scaffoldRule([
96
+ { name: "photo.jpg", sha256Hex: hexOf("one") },
97
+ { name: "photo.png", sha256Hex: hexOf("two") },
98
+ { name: "photo-2.jpg", sha256Hex: hexOf("three") },
99
+ ]).replace(ORDERING_PLACEHOLDER, "hash-linked");
100
+ const rule = parseRule(text);
101
+ assert.deepEqual(Object.keys(rule.cast), ["photo", "photo-3", "photo-2"]);
102
+ assert.equal(rule.cast["photo-2"]?.digest, `sha256:${hexOf("three")}`);
103
+ assert.equal(rule.cast["photo-3"]?.digest, `sha256:${hexOf("two")}`);
104
+ });
105
+ test("scaffold: backslash names keep their identity; degenerate names omit means", () => {
106
+ const text = scaffoldRule([
107
+ { name: String.raw `a\b.jpg`, sha256Hex: hexOf("bs") },
108
+ { name: "b.jpg", sha256Hex: hexOf("plain") },
109
+ ]).replace(ORDERING_PLACEHOLDER, "hash-linked");
110
+ const rule = parseRule(text);
111
+ assert.deepEqual(Object.keys(rule.cast), ["a-b", "b"]);
112
+ assert.equal(rule.cast["a-b"]?.means, String.raw `a\b.jpg`);
113
+ assert.equal(rule.cast["b"]?.means, "b.jpg");
114
+ // A trailing-slash or empty name has no basename to assert: no means
115
+ // key at all, never means: "".
116
+ const degenerate = scaffoldRule([
117
+ { name: "photos/", sha256Hex: hexOf("deg") },
118
+ ]).replace(ORDERING_PLACEHOLDER, "hash-linked");
119
+ const degRule = parseRule(degenerate);
120
+ assert.deepEqual(Object.keys(degRule.cast), ["file"]);
121
+ assert.equal(degRule.cast["file"]?.means, undefined);
122
+ });
123
+ test("scaffold: __proto__ as a filename stem becomes an ordinary cast key", () => {
124
+ const text = scaffoldRule([{ name: "__proto__.jpg", sha256Hex: hexOf("p") }]).replace(ORDERING_PLACEHOLDER, "assumption-dependent");
125
+ const rule = parseRule(text);
126
+ assert.deepEqual(Object.keys(rule.cast), ["__proto__"]);
127
+ assert.equal(rule.cast["__proto__"]?.digest, `sha256:${hexOf("p")}`);
128
+ });
129
+ test("scaffold rejects empty input and malformed hex", () => {
130
+ assert.throws(() => scaffoldRule([]));
131
+ assert.throws(() => scaffoldRule([{ name: "a", sha256Hex: "ABC" }]));
132
+ assert.throws(() => scaffoldRule([{ name: "a", sha256Hex: hexOf("x").toUpperCase() }]));
133
+ });
134
+ // ---------------------------------------------------------------------------
135
+ // playAudit / play
136
+ // ---------------------------------------------------------------------------
137
+ const RULE_TEXT = JSON.stringify({
138
+ rule: "bitgraph-player/1",
139
+ id: "play-test",
140
+ cast: {
141
+ first: { digest: digestFor("first") },
142
+ second: { digest: digestFor("second") },
143
+ },
144
+ world: "closed",
145
+ requires: { ordering: "hash-linked" },
146
+ claim: { before: ["first", "second"] },
147
+ then: { label: "ok" },
148
+ });
149
+ function chainAudit() {
150
+ return makeAudit({
151
+ proofs: [
152
+ { name: "p1", digestB64: digestFor("first"), epochId: "e1", counter: "1" },
153
+ { name: "p2", digestB64: digestFor("second"), epochId: "e1", counter: "2", prev: "p1" },
154
+ ],
155
+ partitions: [{ epochId: "e1", members: ["p1", "p2"], components: [["p1", "p2"]] }],
156
+ });
157
+ }
158
+ test("playAudit: verdict, bytes, and exit code agree with the pipeline pieces", () => {
159
+ const sha = hexOf(RULE_TEXT);
160
+ const result = playAudit(parseRule(RULE_TEXT), sha, chainAudit());
161
+ assert.equal(result.verdict.result, "TRUE");
162
+ assert.equal(result.exitCode, 0);
163
+ assert.equal(result.verdict.rule.sha256, sha);
164
+ assert.equal(result.bytes, serializeVerdict(result.verdict));
165
+ assert.equal(result.verdict.then?.label, "ok");
166
+ });
167
+ test("playAudit exit codes: FALSE is 1, UNDETERMINED is 2", () => {
168
+ const falseRule = parseRule(RULE_TEXT.replace('"before"', '"after"'));
169
+ assert.equal(playAudit(falseRule, hexOf("x"), chainAudit()).exitCode, 1);
170
+ const emptyAudit = makeAudit({ proofs: [] });
171
+ const rule = parseRule(RULE_TEXT);
172
+ assert.equal(playAudit(rule, hexOf("x"), emptyAudit).exitCode, 2);
173
+ });
174
+ test("play: missing rule file throws PlayError with stage rule-read", async () => {
175
+ await assert.rejects(() => play("/nonexistent/rule.json", "/nonexistent/bundle"), (err) => {
176
+ assert.ok(err instanceof PlayError);
177
+ assert.equal(err.stage, "rule-read");
178
+ return true;
179
+ });
180
+ });
181
+ test("play: invalid rule file throws RuleError before any bundle work", async () => {
182
+ const dir = mkdtempSync(join(tmpdir(), "player-play-"));
183
+ try {
184
+ const rulePath = join(dir, "rule.json");
185
+ writeFileSync(rulePath, '{"rule": "bitgraph-player/1"}');
186
+ await assert.rejects(() => play(rulePath, "/nonexistent/bundle"), (err) => err instanceof RuleError);
187
+ }
188
+ finally {
189
+ rmSync(dir, { recursive: true, force: true });
190
+ }
191
+ });
192
+ // ---------------------------------------------------------------------------
193
+ // CLI init wiring
194
+ // ---------------------------------------------------------------------------
195
+ test("cli init: hashes files, emits skeleton on stdout, exit 0", () => {
196
+ const dir = mkdtempSync(join(tmpdir(), "player-init-"));
197
+ try {
198
+ const po = join(dir, "po.pdf");
199
+ const delivery = join(dir, "delivery.jpg");
200
+ writeFileSync(po, "po-bytes");
201
+ writeFileSync(delivery, "delivery-bytes");
202
+ const run = spawnSync(process.execPath, [CLI_PATH, "init", po, delivery], {
203
+ encoding: "utf8",
204
+ });
205
+ assert.equal(run.status, 0, run.stderr);
206
+ const skeleton = JSON.parse(run.stdout);
207
+ assert.deepEqual(Object.keys(skeleton.cast), ["po", "delivery"]);
208
+ assert.equal(skeleton.cast["po"]?.digest, `sha256:${hexOf("po-bytes")}`);
209
+ assert.equal(skeleton.requires.ordering, ORDERING_PLACEHOLDER);
210
+ assert.match(run.stderr, /next steps:/);
211
+ }
212
+ finally {
213
+ rmSync(dir, { recursive: true, force: true });
214
+ }
215
+ });
216
+ test("cli init --out: writes the file, refuses to overwrite it, exit 3", () => {
217
+ const dir = mkdtempSync(join(tmpdir(), "player-init-out-"));
218
+ try {
219
+ const input = join(dir, "photo.jpg");
220
+ writeFileSync(input, "photo-bytes");
221
+ const out = join(dir, "rule.json");
222
+ const first = spawnSync(process.execPath, [CLI_PATH, "init", input, "--out", out], {
223
+ encoding: "utf8",
224
+ });
225
+ assert.equal(first.status, 0, first.stderr);
226
+ assert.equal(first.stdout, "");
227
+ const written = JSON.parse(readFileSync(out, "utf8"));
228
+ assert.equal(written.cast["photo"]?.digest, `sha256:${hexOf("photo-bytes")}`);
229
+ const second = spawnSync(process.execPath, [CLI_PATH, "init", input, "--out", out], {
230
+ encoding: "utf8",
231
+ });
232
+ assert.equal(second.status, 3);
233
+ assert.match(second.stderr, /refusing to overwrite/);
234
+ }
235
+ finally {
236
+ rmSync(dir, { recursive: true, force: true });
237
+ }
238
+ });
239
+ test("cli init: unreadable input is an error, exit 3; no files is usage, exit 3", () => {
240
+ const missing = spawnSync(process.execPath, [CLI_PATH, "init", "/nonexistent/file.jpg"], { encoding: "utf8" });
241
+ assert.equal(missing.status, 3);
242
+ assert.match(missing.stderr, /cannot hash/);
243
+ const empty = spawnSync(process.execPath, [CLI_PATH, "init"], { encoding: "utf8" });
244
+ assert.equal(empty.status, 3);
245
+ assert.match(empty.stderr, /usage:/);
246
+ });
247
+ test("cli: a file named init in cwd makes `bitgraph-play init …` a loud error, not a silent mode pick", () => {
248
+ const dir = mkdtempSync(join(tmpdir(), "player-init-shadow-"));
249
+ try {
250
+ writeFileSync(join(dir, "init"), "{}");
251
+ writeFileSync(join(dir, "photo.jpg"), "bytes");
252
+ const run = spawnSync(process.execPath, [CLI_PATH, "init", "photo.jpg"], {
253
+ encoding: "utf8",
254
+ cwd: dir,
255
+ });
256
+ assert.equal(run.status, 3);
257
+ assert.match(run.stderr, /ambiguous/);
258
+ assert.equal(run.stdout, "");
259
+ }
260
+ finally {
261
+ rmSync(dir, { recursive: true, force: true });
262
+ }
263
+ });
264
+ test("cli: -- forces evaluate mode, so a rule file named init is still reachable", () => {
265
+ const dir = mkdtempSync(join(tmpdir(), "player-init-dashdash-"));
266
+ try {
267
+ // An invalid rule proves the point: init-mode would hash it and exit 0;
268
+ // evaluate mode must report an invalid rule with exit 3.
269
+ writeFileSync(join(dir, "init"), "{}");
270
+ const run = spawnSync(process.execPath, [CLI_PATH, "--", "init", dir], {
271
+ encoding: "utf8",
272
+ cwd: dir,
273
+ });
274
+ assert.equal(run.status, 3);
275
+ assert.match(run.stderr, /invalid rule/);
276
+ }
277
+ finally {
278
+ rmSync(dir, { recursive: true, force: true });
279
+ }
280
+ });
281
+ test("PLAYER_VERSION matches package.json (the constant must not drift across releases)", async () => {
282
+ const { PLAYER_VERSION } = await import("../verdict.js");
283
+ const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
284
+ assert.equal(PLAYER_VERSION, pkg.version);
285
+ });
286
+ test("cli evaluate: unchanged contract, invalid rule reports issues on stderr, exit 3", () => {
287
+ const dir = mkdtempSync(join(tmpdir(), "player-eval-"));
288
+ try {
289
+ const rulePath = join(dir, "rule.json");
290
+ writeFileSync(rulePath, "{}");
291
+ const run = spawnSync(process.execPath, [CLI_PATH, rulePath, dir], {
292
+ encoding: "utf8",
293
+ });
294
+ assert.equal(run.status, 3);
295
+ assert.match(run.stderr, /invalid rule/);
296
+ assert.equal(run.stdout, "");
297
+ }
298
+ finally {
299
+ rmSync(dir, { recursive: true, force: true });
300
+ }
301
+ });
302
+ //# sourceMappingURL=init-play.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-play.test.js","sourceRoot":"","sources":["../../src/__tests__/init-play.test.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEtE,SAAS,KAAK,CAAC,IAAY;IACzB,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,IAAI,CAAC,uEAAuE,EAAE,GAAG,EAAE;IACjF,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,oBAAoB,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACtE,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,6BAA6B,CAAC,EAAE,eAAe,CAAC,CAAC;IAC9E,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,kBAAkB,CAAC,EAAE,YAAY,CAAC,CAAC;IAChE,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,gBAAgB,CAAC,EAAE,aAAa,CAAC,CAAC;AACjE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mEAAmE,EAAE,GAAG,EAAE;IAC7E,yEAAyE;IACzE,iEAAiE;IACjE,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAA,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAA,wBAAwB,CAAC,EAAE,mBAAmB,CAAC,CAAC;AACzF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,wFAAwF,EAAE,GAAG,EAAE;IAClG,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,CAAC;IACvD,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;IAChD,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC,CAAC;IACzD,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/C,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,MAAM,OAAO,GAAG;IACd,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE;IAC1C,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE;CACvD,CAAC;AAEF,IAAI,CAAC,oFAAoF,EAAE,GAAG,EAAE;IAC9F,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAC/C,MAAM,CAAC,MAAM,CACX,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,EACrB,CAAC,GAAY,EAAE,EAAE;QACf,MAAM,CAAC,EAAE,CAAC,GAAG,YAAY,SAAS,CAAC,CAAC;QACpC,MAAM,OAAO,GAAG,GAAgB,CAAC;QACjC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAW,EAAE,oBAAoB,CAAC,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC,CACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kFAAkF,EAAE,GAAG,EAAE;IAC5F,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,aAAa,CAAC,CAAC;IAChF,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;IAC7D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC/C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IACpD,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE;QAC3B,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;KAChD,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mEAAmE,EAAE,GAAG,EAAE;IAC7E,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,CAAC,IAAY,EAAU,EAAE,CACnC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC,EAAE,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC;AACnE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oEAAoE,EAAE,GAAG,EAAE;IAC9E,MAAM,IAAI,GAAG,YAAY,CAAC;QACxB,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE;QAChD,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE;QAChD,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE;KACpD,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,aAAa,CAAC,CAAC;IAChD,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;AACvF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sEAAsE,EAAE,GAAG,EAAE;IAChF,oEAAoE;IACpE,wEAAwE;IACxE,kCAAkC;IAClC,MAAM,IAAI,GAAG,YAAY,CAAC;QACxB,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE;QAC9C,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE;QAC9C,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE;KACnD,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,aAAa,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;IAC1E,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACvE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACvE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4EAA4E,EAAE,GAAG,EAAE;IACtF,MAAM,IAAI,GAAG,YAAY,CAAC;QACxB,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAA,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE;QACrD,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE;KAC7C,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,aAAa,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IACvD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAA,SAAS,CAAC,CAAC;IAC3D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAE7C,qEAAqE;IACrE,+BAA+B;IAC/B,MAAM,UAAU,GAAG,YAAY,CAAC;QAC9B,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE;KAC7C,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,aAAa,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IACtD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qEAAqE,EAAE,GAAG,EAAE;IAC/E,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CACnF,oBAAoB,EACpB,sBAAsB,CACvB,CAAC;IACF,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACvE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC1D,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;IACtC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AAC1F,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IAC/B,IAAI,EAAE,mBAAmB;IACzB,EAAE,EAAE,WAAW;IACf,IAAI,EAAE;QACJ,KAAK,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE;QACrC,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE;KACxC;IACD,KAAK,EAAE,QAAQ;IACf,QAAQ,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE;IACrC,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE;IACtC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;CACtB,CAAC,CAAC;AAEH,SAAS,UAAU;IACjB,OAAO,SAAS,CAAC;QACf,MAAM,EAAE;YACN,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;YAC1E,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE;SACxF;QACD,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;KACnF,CAAC,CAAC;AACL,CAAC;AAED,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;IACnF,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC;IAClE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,gBAAgB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7D,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAC/D,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;IACtE,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAEzE,MAAM,UAAU,GAAG,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;IAC/E,MAAM,MAAM,CAAC,OAAO,CAClB,GAAG,EAAE,CAAC,IAAI,CAAC,wBAAwB,EAAE,qBAAqB,CAAC,EAC3D,CAAC,GAAY,EAAE,EAAE;QACf,MAAM,CAAC,EAAE,CAAC,GAAG,YAAY,SAAS,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAE,GAAiB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC;IACd,CAAC,CACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;IACjF,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACxC,aAAa,CAAC,QAAQ,EAAE,+BAA+B,CAAC,CAAC;QACzD,MAAM,MAAM,CAAC,OAAO,CAClB,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,qBAAqB,CAAC,EAC3C,CAAC,GAAY,EAAE,EAAE,CAAC,GAAG,YAAY,SAAS,CAC3C,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,IAAI,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACpE,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAC3C,aAAa,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;QAC9B,aAAa,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;QAE1C,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE;YACxE,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAGrC,CAAC;QACF,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;QACjE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACzE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;QAC/D,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC1C,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kEAAkE,EAAE,GAAG,EAAE;IAC5E,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC5D,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACrC,aAAa,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QAEnC,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE;YACjF,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAEnD,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QAE9E,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE;YAClF,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IACvD,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2EAA2E,EAAE,GAAG,EAAE;IACrF,MAAM,OAAO,GAAG,SAAS,CACvB,OAAO,CAAC,QAAQ,EAChB,CAAC,QAAQ,EAAE,MAAM,EAAE,uBAAuB,CAAC,EAC3C,EAAE,QAAQ,EAAE,MAAM,EAAE,CACrB,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAChC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAE5C,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACpF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iGAAiG,EAAE,GAAG,EAAE;IAC3G,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAC/D,IAAI,CAAC;QACH,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;QACvC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE;YACvE,QAAQ,EAAE,MAAM;YAChB,GAAG,EAAE,GAAG;SACT,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC/B,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,4EAA4E,EAAE,GAAG,EAAE;IACtF,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,uBAAuB,CAAC,CAAC,CAAC;IACjE,IAAI,CAAC;QACH,wEAAwE;QACxE,yDAAyD;QACzD,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;YACrE,QAAQ,EAAE,MAAM;YAChB,GAAG,EAAE,GAAG;SACT,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC3C,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;IACnG,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,GAAG,CAAC,oBAAoB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAC9C,CAAC;IACzB,MAAM,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;AAC5C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iFAAiF,EAAE,GAAG,EAAE;IAC3F,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACxC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE;YACjE,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QACzC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC/B,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC,CAAC"}
package/dist/cli.js CHANGED
@@ -2,28 +2,38 @@
2
2
  // Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
3
3
  /**
4
4
  * bitgraph-play <rule.json> <bundle> [--out <file>] [--summary]
5
+ * bitgraph-play init <file>... [--out <rule.json>]
5
6
  *
6
- * Evaluates a bitgraph-player/1 rule against a proof bundle (directory,
7
- * .tar, .tar.gz, or .tgz) and writes the verdict JSON to stdout (or
8
- * --out). Offline: the audit pipeline and the evaluator make no network
9
- * requests of any kind.
7
+ * Evaluate: runs a bitgraph-player/1 rule against a proof bundle
8
+ * (directory, .tar, .tar.gz, or .tgz) and writes the verdict JSON to
9
+ * stdout (or --out). Offline: the audit pipeline and the evaluator make
10
+ * no network requests of any kind.
10
11
  *
11
- * Exit codes: 0 TRUE, 1 FALSE, 2 UNDETERMINED, 3 error.
12
- * Diagnostics go to stderr; stdout carries verdict bytes only.
12
+ * Init: hashes the given files and emits a rule skeleton with the cast
13
+ * filled in. The skeleton does not run as written: requires.ordering is
14
+ * a placeholder the author must replace — the trust floor has no
15
+ * default, and the scaffolder must not choose it either.
16
+ *
17
+ * Exit codes: 0 TRUE, 1 FALSE, 2 UNDETERMINED, 3 error (init: 0 or 3).
18
+ * Diagnostics go to stderr; stdout carries the verdict (or skeleton)
19
+ * bytes only.
13
20
  *
14
21
  * The process exits by setting process.exitCode and returning, never by
15
- * process.exit(): exiting early would truncate a verdict still draining
16
- * to a pipe.
22
+ * process.exit(): exiting early would truncate output still draining to
23
+ * a pipe.
17
24
  */
18
25
  import { createHash } from "node:crypto";
19
- import { readFileSync, writeFileSync } from "node:fs";
20
- import { runAudit } from "@mikeargento/bitgraph-audit";
21
- import { resolveCast } from "./cast.js";
22
- import { evaluate } from "./evaluate.js";
23
- import { parseRule, RuleError } from "./rule.js";
24
- import { buildVerdict, serializeVerdict } from "./verdict.js";
26
+ import { createReadStream, existsSync, writeFileSync } from "node:fs";
27
+ import { basename } from "node:path";
28
+ import { pipeline } from "node:stream/promises";
29
+ import { scaffoldRule } from "./init.js";
30
+ import { play, PlayError } from "./play.js";
31
+ import { RuleError } from "./rule.js";
25
32
  function usage() {
26
33
  process.stderr.write("usage: bitgraph-play <rule.json> <bundle> [--out <file>] [--summary]\n" +
34
+ " bitgraph-play init <file>... [--out <rule.json>]\n" +
35
+ ' "--" ends option parsing; a rule file literally named "init" is\n' +
36
+ " evaluated with: bitgraph-play -- init <bundle>\n" +
27
37
  " exit codes: 0 TRUE, 1 FALSE, 2 UNDETERMINED, 3 error\n");
28
38
  return 3;
29
39
  }
@@ -42,44 +52,97 @@ function printSummary(audit) {
42
52
  lines.push(`unchained: ${audit.reconstruction.unchainedProofHashes.length}, unpartitioned: ${audit.reconstruction.unpartitionedProofHashes.length}`);
43
53
  process.stderr.write(lines.join("\n") + "\n");
44
54
  }
45
- async function main() {
46
- const args = process.argv.slice(2);
47
- const positional = [];
48
- let outFile;
49
- let summary = false;
55
+ async function sha256HexOfFile(path) {
56
+ const hash = createHash("sha256");
57
+ await pipeline(createReadStream(path), hash);
58
+ return hash.digest("hex");
59
+ }
60
+ function parseArgs(args) {
61
+ const parsed = { positional: [], summary: false };
50
62
  for (let i = 0; i < args.length; i++) {
51
63
  const arg = args[i];
52
- if (arg === "--out") {
64
+ if (arg === "--") {
65
+ // End of options: everything after is positional, dashes and all.
66
+ parsed.positional.push(...args.slice(i + 1));
67
+ break;
68
+ }
69
+ else if (arg === "--out") {
53
70
  const next = args[++i];
54
71
  if (next === undefined || next.startsWith("-"))
55
- return usage();
56
- outFile = next;
72
+ return undefined;
73
+ parsed.outFile = next;
57
74
  }
58
75
  else if (arg === "--summary") {
59
- summary = true;
76
+ parsed.summary = true;
60
77
  }
61
78
  else if (arg.startsWith("-")) {
62
- return usage();
79
+ return undefined;
63
80
  }
64
81
  else {
65
- positional.push(arg);
82
+ parsed.positional.push(arg);
66
83
  }
67
84
  }
68
- if (positional.length !== 2)
85
+ return parsed;
86
+ }
87
+ async function runInit(args) {
88
+ if (args.summary)
69
89
  return usage();
70
- const [rulePath, bundlePath] = positional;
71
- let ruleBytes;
72
- try {
73
- ruleBytes = readFileSync(rulePath);
74
- }
75
- catch (err) {
76
- process.stderr.write(`error: cannot read rule file: ${err.message}\n`);
90
+ const files = args.positional;
91
+ if (files.length === 0)
92
+ return usage();
93
+ if (args.outFile !== undefined && existsSync(args.outFile)) {
94
+ process.stderr.write(`error: refusing to overwrite existing file: ${args.outFile}\n`);
77
95
  return 3;
78
96
  }
79
- const ruleSha256Hex = createHash("sha256").update(ruleBytes).digest("hex");
80
- let rule;
97
+ const entries = [];
98
+ for (const file of files) {
99
+ let sha256Hex;
100
+ try {
101
+ sha256Hex = await sha256HexOfFile(file);
102
+ }
103
+ catch (err) {
104
+ process.stderr.write(`error: cannot hash ${file}: ${err.message}\n`);
105
+ return 3;
106
+ }
107
+ // Platform-correct basename here, at the boundary: scaffoldRule
108
+ // itself splits only on "/" so its output is machine-independent.
109
+ entries.push({ name: basename(file), sha256Hex });
110
+ process.stderr.write(` sha256:${sha256Hex} ${file}\n`);
111
+ }
112
+ const skeleton = scaffoldRule(entries);
113
+ if (args.outFile !== undefined) {
114
+ // Exclusive write: the pre-hash existsSync is only a fast-fail, and
115
+ // hashing is unbounded in duration, so the refusal must hold at the
116
+ // write itself or a file created mid-hash is silently clobbered.
117
+ try {
118
+ writeFileSync(args.outFile, skeleton, { flag: "wx" });
119
+ }
120
+ catch (err) {
121
+ if (err.code === "EEXIST") {
122
+ process.stderr.write(`error: refusing to overwrite existing file: ${args.outFile}\n`);
123
+ return 3;
124
+ }
125
+ throw err;
126
+ }
127
+ process.stderr.write(`wrote ${args.outFile}\n`);
128
+ }
129
+ else {
130
+ process.stdout.write(skeleton);
131
+ }
132
+ process.stderr.write("\nnext steps:\n" +
133
+ ' 1. set requires.ordering — "hash-linked" accepts only hash-link evidence;\n' +
134
+ ' "assumption-dependent" also accepts counter and anchor evidence\n' +
135
+ " 2. say what each digest means; refine the claim (before / after / between / not)\n" +
136
+ " 3. bitgraph-play <rule.json> <bundle>\n");
137
+ return 0;
138
+ }
139
+ async function runEvaluate(args) {
140
+ if (args.positional.length !== 2)
141
+ return usage();
142
+ const [rulePath, bundlePath] = args.positional;
143
+ let result;
81
144
  try {
82
- rule = parseRule(ruleBytes.toString("utf8"));
145
+ result = await play(rulePath, bundlePath);
83
146
  }
84
147
  catch (err) {
85
148
  if (err instanceof RuleError) {
@@ -87,39 +150,43 @@ async function main() {
87
150
  for (const issue of err.issues)
88
151
  process.stderr.write(` - ${issue}\n`);
89
152
  }
153
+ else if (err instanceof PlayError) {
154
+ process.stderr.write(`error: ${err.message}\n`);
155
+ }
90
156
  else {
91
157
  process.stderr.write(`error: ${err.message}\n`);
92
158
  }
93
159
  return 3;
94
160
  }
95
- let audit;
96
- try {
97
- audit = await runAudit(bundlePath);
98
- }
99
- catch (err) {
100
- process.stderr.write(`error: bundle audit failed: ${err.message}\n`);
101
- return 3;
102
- }
103
- const resolutions = resolveCast(rule.cast, audit);
104
- const evaluation = evaluate(rule, resolutions, audit);
105
- const verdict = buildVerdict(rule, ruleSha256Hex, resolutions, evaluation, audit);
106
- const bytes = serializeVerdict(verdict);
107
- if (outFile !== undefined) {
108
- writeFileSync(outFile, bytes);
161
+ if (args.outFile !== undefined) {
162
+ writeFileSync(args.outFile, result.bytes);
109
163
  }
110
164
  else {
111
- process.stdout.write(bytes);
165
+ process.stdout.write(result.bytes);
112
166
  }
113
- if (summary)
114
- printSummary(audit);
115
- switch (evaluation.result) {
116
- case "TRUE":
117
- return 0;
118
- case "FALSE":
119
- return 1;
120
- case "UNDETERMINED":
121
- return 2;
167
+ if (args.summary)
168
+ printSummary(result.audit);
169
+ return result.exitCode;
170
+ }
171
+ async function main() {
172
+ const argv = process.argv.slice(2);
173
+ // "--" as the first token forces evaluate mode: parseArgs treats
174
+ // everything after it as positional, so a rule file literally named
175
+ // "init" is reachable as `bitgraph-play -- init <bundle>`.
176
+ const isInit = argv[0] === "init";
177
+ if (isInit && existsSync("init")) {
178
+ // Both readings are plausible here; a silent pick would hand a
179
+ // 0.1.1 caller a skeleton with exit 0 where the published contract
180
+ // returned a verdict. Refuse loudly instead.
181
+ process.stderr.write('error: a file named "init" exists here, so this command is ambiguous.\n' +
182
+ ' to evaluate it as a rule: bitgraph-play -- init <bundle> (or ./init)\n' +
183
+ " to scaffold a rule: rename that file or run from another directory\n");
184
+ return 3;
122
185
  }
186
+ const parsed = parseArgs(isInit ? argv.slice(1) : argv);
187
+ if (parsed === undefined)
188
+ return usage();
189
+ return isInit ? runInit(parsed) : runEvaluate(parsed);
123
190
  }
124
191
  main().then((code) => {
125
192
  process.exitCode = code;