@mikeargento/bitgraph-player 0.3.0 → 0.5.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/src/cli.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  /**
5
5
  * bitgraph-play <rule.json> <bundle> [--out <file>] [--summary]
6
6
  * bitgraph-play init <file>... [--out <rule.json>]
7
+ * bitgraph-play check <bundle-or-file>... [--json] [--out <file>]
7
8
  *
8
9
  * Evaluate: runs a bitgraph-player/1 rule against a proof bundle
9
10
  * (directory, .tar, .tar.gz, or .tgz) and writes the verdict JSON to
@@ -15,9 +16,15 @@
15
16
  * a placeholder the author must replace — the trust floor has no
16
17
  * default, and the scaffolder must not choose it either.
17
18
  *
19
+ * Check: reads a bundle (a directory or archive, or a list of files such
20
+ * as a proof.json and the file it records) and reports, in the three
21
+ * values, what the bundle establishes about each recording it holds and
22
+ * what bounds it. Human text on stdout by default; --json for the
23
+ * bitgraph-check/1 report. Offline. See check.ts for the vocabulary.
24
+ *
18
25
  * Exit codes: 0 TRUE, 1 FALSE, 2 UNDETERMINED, 3 error (init: 0 or 3).
19
- * Diagnostics go to stderr; stdout carries the verdict (or skeleton)
20
- * bytes only.
26
+ * Diagnostics go to stderr; stdout carries the verdict (or skeleton, or
27
+ * check report) bytes only.
21
28
  *
22
29
  * The process exits by setting process.exitCode and returning, never by
23
30
  * process.exit(): exiting early would truncate output still draining to
@@ -26,9 +33,12 @@
26
33
 
27
34
  import { createHash } from "node:crypto";
28
35
  import { createReadStream, existsSync, writeFileSync } from "node:fs";
36
+ import { readFile, stat } from "node:fs/promises";
29
37
  import { basename } from "node:path";
30
38
  import { pipeline } from "node:stream/promises";
31
- import type { AuditResult } from "@mikeargento/bitgraph-audit";
39
+ import type { AuditResult, BundleEntrySource } from "@mikeargento/bitgraph-audit";
40
+ import { ingestBundle, ingestEntries } from "@mikeargento/bitgraph-audit";
41
+ import { checkIngest, renderCheckText, serializeCheckReport } from "./check.js";
32
42
  import { scaffoldRule } from "./init.js";
33
43
  import type { ScaffoldEntry } from "./init.js";
34
44
  import { play, PlayError } from "./play.js";
@@ -38,8 +48,9 @@ function usage(): number {
38
48
  process.stderr.write(
39
49
  "usage: bitgraph-play <rule.json> <bundle> [--out <file>] [--summary]\n" +
40
50
  " bitgraph-play init <file>... [--out <rule.json>]\n" +
41
- ' "--" ends option parsing; a rule file literally named "init" is\n' +
42
- " evaluated with: bitgraph-play -- init <bundle>\n" +
51
+ " bitgraph-play check <bundle-or-file>... [--json] [--out <file>]\n" +
52
+ ' "--" ends option parsing; a rule file literally named "init" or\n' +
53
+ ' "check" is evaluated with: bitgraph-play -- init <bundle>\n' +
43
54
  " exit codes: 0 TRUE, 1 FALSE, 2 UNDETERMINED, 3 error\n"
44
55
  );
45
56
  return 3;
@@ -79,10 +90,11 @@ interface ParsedArgs {
79
90
  positional: string[];
80
91
  outFile?: string;
81
92
  summary: boolean;
93
+ json: boolean;
82
94
  }
83
95
 
84
96
  function parseArgs(args: string[]): ParsedArgs | undefined {
85
- const parsed: ParsedArgs = { positional: [], summary: false };
97
+ const parsed: ParsedArgs = { positional: [], summary: false, json: false };
86
98
  for (let i = 0; i < args.length; i++) {
87
99
  const arg = args[i] as string;
88
100
  if (arg === "--") {
@@ -95,6 +107,8 @@ function parseArgs(args: string[]): ParsedArgs | undefined {
95
107
  parsed.outFile = next;
96
108
  } else if (arg === "--summary") {
97
109
  parsed.summary = true;
110
+ } else if (arg === "--json") {
111
+ parsed.json = true;
98
112
  } else if (arg.startsWith("-")) {
99
113
  return undefined;
100
114
  } else {
@@ -104,8 +118,58 @@ function parseArgs(args: string[]): ParsedArgs | undefined {
104
118
  return parsed;
105
119
  }
106
120
 
107
- async function runInit(args: ParsedArgs): Promise<number> {
121
+ /**
122
+ * check: one directory or archive path is ingested as a bundle; any other
123
+ * argument list (one or more plain files, or several paths) is ingested
124
+ * in memory as a flat set of entries named by their basenames, so
125
+ * `bitgraph-play check proof.json photo.jpg` works without a folder.
126
+ */
127
+ async function runCheck(args: ParsedArgs): Promise<number> {
108
128
  if (args.summary) return usage();
129
+ const targets = args.positional;
130
+ if (targets.length === 0) return usage();
131
+
132
+ let ingest;
133
+ try {
134
+ const single = targets.length === 1 ? await stat(targets[0] as string) : undefined;
135
+ if (single !== undefined && (single.isDirectory() || looksLikeArchive(targets[0] as string))) {
136
+ ingest = await ingestBundle(targets[0] as string);
137
+ } else {
138
+ const entries: BundleEntrySource[] = [];
139
+ for (const target of targets) {
140
+ const info = await stat(target);
141
+ if (info.isDirectory()) {
142
+ // A directory among several arguments: ingest it as its own
143
+ // bundle would be ambiguous, so refuse plainly.
144
+ process.stderr.write(`error: ${target} is a directory; pass one bundle path, or a list of files\n`);
145
+ return 3;
146
+ }
147
+ entries.push({ path: basename(target), open: () => readFile(target).then((b) => new Uint8Array(b)) });
148
+ }
149
+ ingest = await ingestEntries(entries);
150
+ }
151
+ } catch (err) {
152
+ process.stderr.write(`error: cannot read bundle: ${(err as Error).message}\n`);
153
+ return 3;
154
+ }
155
+
156
+ const report = await checkIngest(ingest);
157
+ const bytes = args.json ? serializeCheckReport(report) : renderCheckText(report);
158
+ if (args.outFile !== undefined) {
159
+ writeFileSync(args.outFile, bytes);
160
+ } else {
161
+ process.stdout.write(bytes);
162
+ }
163
+ return report.result === "TRUE" ? 0 : report.result === "FALSE" ? 1 : 2;
164
+ }
165
+
166
+ function looksLikeArchive(path: string): boolean {
167
+ const lower = path.toLowerCase();
168
+ return lower.endsWith(".tar") || lower.endsWith(".tar.gz") || lower.endsWith(".tgz");
169
+ }
170
+
171
+ async function runInit(args: ParsedArgs): Promise<number> {
172
+ if (args.summary || args.json) return usage();
109
173
  const files = args.positional;
110
174
  if (files.length === 0) return usage();
111
175
  if (args.outFile !== undefined && existsSync(args.outFile)) {
@@ -161,7 +225,7 @@ async function runInit(args: ParsedArgs): Promise<number> {
161
225
  }
162
226
 
163
227
  async function runEvaluate(args: ParsedArgs): Promise<number> {
164
- if (args.positional.length !== 2) return usage();
228
+ if (args.json || args.positional.length !== 2) return usage();
165
229
  const [rulePath, bundlePath] = args.positional as [string, string];
166
230
 
167
231
  let result;
@@ -192,22 +256,24 @@ async function main(): Promise<number> {
192
256
  const argv = process.argv.slice(2);
193
257
  // "--" as the first token forces evaluate mode: parseArgs treats
194
258
  // everything after it as positional, so a rule file literally named
195
- // "init" is reachable as `bitgraph-play -- init <bundle>`.
196
- const isInit = argv[0] === "init";
197
- if (isInit && existsSync("init")) {
259
+ // "init" or "check" is reachable as `bitgraph-play -- init <bundle>`.
260
+ const subcommand = argv[0] === "init" || argv[0] === "check" ? argv[0] : undefined;
261
+ if (subcommand !== undefined && existsSync(subcommand)) {
198
262
  // Both readings are plausible here; a silent pick would hand a
199
263
  // 0.1.1 caller a skeleton with exit 0 where the published contract
200
264
  // returned a verdict. Refuse loudly instead.
201
265
  process.stderr.write(
202
- 'error: a file named "init" exists here, so this command is ambiguous.\n' +
203
- ' to evaluate it as a rule: bitgraph-play -- init <bundle> (or ./init)\n' +
204
- " to scaffold a rule: rename that file or run from another directory\n"
266
+ `error: a file named "${subcommand}" exists here, so this command is ambiguous.\n` +
267
+ ` to evaluate it as a rule: bitgraph-play -- ${subcommand} <bundle> (or ./${subcommand})\n` +
268
+ ` to run the ${subcommand} subcommand: rename that file or run from another directory\n`
205
269
  );
206
270
  return 3;
207
271
  }
208
- const parsed = parseArgs(isInit ? argv.slice(1) : argv);
272
+ const parsed = parseArgs(subcommand !== undefined ? argv.slice(1) : argv);
209
273
  if (parsed === undefined) return usage();
210
- return isInit ? runInit(parsed) : runEvaluate(parsed);
274
+ if (subcommand === "init") return runInit(parsed);
275
+ if (subcommand === "check") return runCheck(parsed);
276
+ return runEvaluate(parsed);
211
277
  }
212
278
 
213
279
  main().then(
package/src/index.ts CHANGED
@@ -44,3 +44,6 @@ export { kleeneAll, kleeneAny, kleeneNot } from "./logic.js";
44
44
  export { evaluate } from "./evaluate.js";
45
45
  export type { Evaluation } from "./evaluate.js";
46
46
  export { buildVerdict, serializeVerdict, playerVersion, PLAYER_VERSION } from "./verdict.js";
47
+
48
+ export { checkIngest, buildCheckReport, renderCheckText, serializeCheckReport, KNOWN_ENCLAVE_MEASUREMENTS } from "./check.js";
49
+ export type { CheckReport, CheckRecording, CheckAnchor, CheckLine, CheckBounds, CheckBound, CheckOptions } from "./check.js";
package/src/verdict.ts CHANGED
@@ -31,7 +31,7 @@ import type { DeclaredEntry, Resolution, Rule, Verdict } from "./types.js";
31
31
  * A unit test asserts this equals package.json's version, so the
32
32
  * constant cannot drift silently across releases.
33
33
  */
34
- export const PLAYER_VERSION = "0.3.0";
34
+ export const PLAYER_VERSION = "0.5.0";
35
35
 
36
36
  /** The player package's own version. */
37
37
  export function playerVersion(): string {