File size: 2,802 Bytes
518343a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#!/usr/bin/env node
import * as path from "node:path";
import {
  appendReceiptJsonl,
  createToolEnvelope,
  emitReceipt,
  readReceiptJsonl,
  verifyChain,
  type ReceiptProtocol,
} from "./index.ts";

interface CliArgs {
  out: string;
  actor: string;
  tool: string;
  protocol: ReceiptProtocol;
  payloadJson: string;
  quorum: string;
  nodes: string[];
  lambdaAxes: string[];
}

function parseArgs(argv: readonly string[]): CliArgs {
  const args = new Map<string, string>();
  for (let i = 0; i < argv.length; i += 1) {
    const token = argv[i];
    if (!token.startsWith("--")) continue;
    const key = token.slice(2);
    const value = argv[i + 1];
    if (!value || value.startsWith("--")) {
      throw new Error(`Missing value for --${key}`);
    }
    args.set(key, value);
    i += 1;
  }

  const out = args.get("out");
  const actor = args.get("actor");
  const tool = args.get("tool");
  const payloadJson = args.get("payload-json");
  if (!out || !actor || !tool || !payloadJson) {
    throw new Error("Required: --out <file> --actor <id> --tool <name> --payload-json <json>");
  }

  return {
    out,
    actor,
    tool,
    payloadJson,
    protocol: (args.get("protocol") ?? "mcp") as ReceiptProtocol,
    quorum: args.get("quorum") ?? "1-of-1",
    nodes: (args.get("nodes") ?? "local-operator").split(",").map((node) => node.trim()).filter(Boolean),
    lambdaAxes: (args.get("lambda-axes") ?? "Λ7").split(",").map((axis) => axis.trim()).filter(Boolean),
  };
}

function main(): void {
  const args = parseArgs(process.argv.slice(2));
  const payload = JSON.parse(args.payloadJson) as unknown;
  const out = path.resolve(args.out);
  const chain = readReceiptJsonl(out);
  const previousReceipt = chain.length > 0 ? chain[chain.length - 1] : null;

  const envelope = createToolEnvelope({
    protocol: args.protocol,
    actor_id: args.actor,
    tool_name: args.tool,
    lambda_axes: args.lambdaAxes,
    payload,
    metadata: {
      source: "a11oy-receipt-substrate-cli",
    },
  });

  const receipt = emitReceipt(envelope, {
    previousReceipt,
    policy: {
      algorithm: "SHA3-256",
      chaining: "hash_chain",
      quorum: args.quorum,
      nodes: args.nodes,
    },
  });

  const nextChain = [...chain, receipt];
  const verification = verifyChain(nextChain, {
    quorum: args.quorum,
    nodes: args.nodes,
  });

  if (!verification.valid) {
    throw new Error(`Refusing to append invalid receipt: ${verification.errors.join("; ")}`);
  }

  appendReceiptJsonl(out, receipt);
  console.log(JSON.stringify({
    ok: true,
    out,
    receipt_id: receipt.receipt_id,
    merkle_root: receipt.merkle_root,
    sequence: receipt.sequence,
  }));
}

try {
  main();
} catch (error) {
  console.error((error as Error).message);
  process.exit(1);
}