magenta-canon 0.1.12 → 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 +6 -3
- package/bin/magenta-canon.mjs +11 -2
- package/dist/MANIFEST.json +32 -0
- package/dist/package.json +3 -0
- package/dist/scripts/demo-control-plane.js +171 -0
- package/dist/scripts/intake-cli.js +303 -0
- package/dist/scripts/magenta-mirror.js +387 -0
- package/dist/scripts/magenta-verify.js +194 -0
- package/dist/scripts/mcp-gateway.js +304 -0
- package/dist/server/agent-policy.js +86 -0
- package/dist/server/agent-record.js +72 -0
- package/dist/server/crypto.js +135 -0
- package/dist/server/execution-receipts.js +79 -0
- package/dist/server/intake/categories.js +61 -0
- package/dist/server/intake/engine.js +330 -0
- package/dist/server/intake/gateway-intake.js +105 -0
- package/dist/server/intake/index.js +42 -0
- package/{server/intake/intake-witness.ts → dist/server/intake/intake-witness.js} +73 -69
- package/dist/server/intake/model-witness.js +144 -0
- package/dist/server/intake/persistence.js +181 -0
- package/dist/server/intake/queue.js +160 -0
- package/dist/server/intake/rationale.js +65 -0
- package/dist/server/intake/types.js +17 -0
- package/dist/server/intake/witness.js +192 -0
- package/dist/server/persistence.js +153 -0
- package/dist/server/storage.js +1279 -0
- package/dist/server/transparency-log.js +201 -0
- package/dist/server/trust-bootstrap.js +149 -0
- package/{server/witness.ts → dist/server/witness.js} +68 -64
- package/{shared/canonical.ts → dist/shared/canonical.js} +193 -221
- package/dist/shared/certificates.js +174 -0
- package/dist/shared/schema.js +1955 -0
- package/dist/shared/terminating-kernel.js +79 -0
- package/docs/MCP_GATEWAY.md +1 -1
- package/docs/NPM_PACKAGING.md +41 -10
- package/docs/SECURITY_MODEL.md +50 -0
- package/package.json +27 -32
- package/scripts/demo.mjs +31 -10
- package/scripts/mcp-demo-drive.mjs +8 -3
- package/scripts/mirror-feed.mjs +14 -12
- package/scripts/intake-cli.ts +0 -343
- package/scripts/magenta-mirror.ts +0 -424
- package/scripts/magenta-verify.ts +0 -235
- package/scripts/mcp-gateway.ts +0 -380
- package/scripts/uci-inspector.cjs +0 -624
- package/scripts/uci-inspector.js +0 -35
- package/scripts/uci-witness.cjs +0 -102
- package/scripts/uci-witness.js +0 -28
- package/server/agent-auth.ts +0 -126
- package/server/agent-policy.ts +0 -97
- package/server/agent-record.ts +0 -96
- package/server/authority-containment.ts +0 -582
- package/server/authority-topology.ts +0 -826
- package/server/behavioral-vector.ts +0 -575
- package/server/canon-self-audit/checks/01-spine-body.ts +0 -165
- package/server/canon-self-audit/checks/02-deps-coherence.ts +0 -164
- package/server/canon-self-audit/checks/03-headers-posture.ts +0 -133
- package/server/canon-self-audit/checks/04-mutation-absence.ts +0 -87
- package/server/canon-self-audit/checks/05-language-discipline.ts +0 -182
- package/server/canon-self-audit/checks/06-validator-health.ts +0 -132
- package/server/canon-self-audit/index.ts +0 -29
- package/server/canon-self-audit/loopback.ts +0 -53
- package/server/canon-self-audit/provenance.ts +0 -73
- package/server/canon-self-audit/runner.ts +0 -119
- package/server/canon-self-audit/types.ts +0 -236
- package/server/canon-spine-validator.selftest.ts +0 -281
- package/server/canon-spine-validator.ts +0 -446
- package/server/conformance.ts +0 -317
- package/server/corrigibility.ts +0 -603
- package/server/crypto.ts +0 -133
- package/server/db.ts +0 -42
- package/server/economic-trust.ts +0 -511
- package/server/execution-gate.ts +0 -553
- package/server/execution-receipts.ts +0 -97
- package/server/index.ts +0 -369
- package/server/ingestion-generators.ts +0 -140
- package/server/intake/categories.ts +0 -63
- package/server/intake/engine.ts +0 -410
- package/server/intake/gateway-intake.ts +0 -163
- package/server/intake/index.ts +0 -29
- package/server/intake/ledger.ts +0 -42
- package/server/intake/model-witness.ts +0 -208
- package/server/intake/persistence.ts +0 -174
- package/server/intake/queue.ts +0 -244
- package/server/intake/rationale.ts +0 -74
- package/server/intake/types.ts +0 -183
- package/server/intake/witness.ts +0 -284
- package/server/opus-bridge.ts +0 -132
- package/server/origin-proof.ts +0 -245
- package/server/persistence.ts +0 -130
- package/server/precedent-memory.ts +0 -705
- package/server/proposal-containment.ts +0 -747
- package/server/routes.ts +0 -2931
- package/server/sovereign-auth.ts +0 -380
- package/server/ssr-templates.ts +0 -1292
- package/server/static.ts +0 -36
- package/server/storage.ts +0 -2293
- package/server/transparency-log.ts +0 -218
- package/server/trust-bootstrap.ts +0 -197
- package/server/types/semver.d.ts +0 -7
- package/server/ucik-normalize.ts +0 -40
- package/server/verification-harness.ts +0 -107
- package/server/verification.ts +0 -150
- package/server/vite.ts +0 -58
- package/shared/certificates.ts +0 -233
- package/shared/schema.ts +0 -2824
- package/shared/terminating-kernel.ts +0 -97
- package/tsconfig.json +0 -23
package/README.md
CHANGED
|
@@ -141,9 +141,12 @@ record and shows verification **fails**. It is local/dev only — an ephemeral p
|
|
|
141
141
|
and a fresh universe in a temp dir, cleaned up on exit.
|
|
142
142
|
|
|
143
143
|
<details>
|
|
144
|
-
<summary>Prefer the manual steps
|
|
144
|
+
<summary>Prefer the manual steps? (repo checkout)</summary>
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
|
+
# These manual steps run from a REPO CHECKOUT (they boot the full hosted
|
|
148
|
+
# control plane, which does not ship in the npm package). From an npm install,
|
|
149
|
+
# just run `npx magenta-canon demo` — it is the same proof loop, self-contained.
|
|
147
150
|
# 1. boot the control plane (the witness + evidence surface) and bootstrap trust
|
|
148
151
|
INTERNAL_API_KEY=operator-secret-xyz MAGENTA_STATE_FILE=/tmp/magenta-state.json \
|
|
149
152
|
PORT=5000 npx tsx server/index.ts &
|
|
@@ -158,7 +161,7 @@ DOWNSTREAM_LOG=/tmp/downstream-calls.log \
|
|
|
158
161
|
|
|
159
162
|
# 3. publish evidence and verify it independently
|
|
160
163
|
curl -s :5000/api/trust/evidence > bundle.json
|
|
161
|
-
npx
|
|
164
|
+
npx magenta-canon verify bundle.json # → RESULT: VERIFIED
|
|
162
165
|
```
|
|
163
166
|
</details>
|
|
164
167
|
|
|
@@ -219,7 +222,7 @@ REFUSED_CALL isError=true :: Blocked by Magenta capability gate: exceeds delega
|
|
|
219
222
|
{"name":"refund","args":{"amount_cents":8900,"order_id":"4471"}} ← only the allowed call
|
|
220
223
|
# the blocked $250 refund is ABSENT — it never reached the tool.
|
|
221
224
|
|
|
222
|
-
$ npx
|
|
225
|
+
$ npx magenta-canon verify bundle.json
|
|
223
226
|
[PASS] STH signature verifies
|
|
224
227
|
[PASS] receipt chain intact
|
|
225
228
|
[PASS] recomputed Merkle root == signed STH root
|
package/bin/magenta-canon.mjs
CHANGED
|
@@ -66,12 +66,21 @@ function run(cmd, args, opts = {}) {
|
|
|
66
66
|
child.on("error", (e) => { console.error(`magenta-canon: failed to start: ${e.message}`); process.exit(1); });
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
// Run one of the package's
|
|
69
|
+
// Run one of the package's entry scripts. The published package carries the
|
|
70
|
+
// PRECOMPILED runtime (dist/**/*.js, plain CommonJS) and executes it directly
|
|
71
|
+
// with Node — no tsx, no esbuild, no install scripts in the consumer graph.
|
|
72
|
+
// A repo checkout (no dist/ built) falls back to running the TypeScript source
|
|
73
|
+
// via tsx, which is a devDependency there.
|
|
70
74
|
function runTs(relScript, args, opts = {}) {
|
|
75
|
+
const compiled = path.join(ROOT, "dist", relScript.replace(/\.ts$/, ".js"));
|
|
76
|
+
if (existsSync(compiled)) {
|
|
77
|
+
run(process.execPath, [compiled, ...args], opts);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
71
80
|
const cli = tsxCli();
|
|
72
81
|
const script = path.join(ROOT, relScript);
|
|
73
82
|
if (!existsSync(script)) { console.error(`magenta-canon: missing ${relScript} in package`); process.exit(1); }
|
|
74
|
-
if (!cli) { console.error("magenta-canon: could not locate 'tsx' (
|
|
83
|
+
if (!cli) { console.error("magenta-canon: could not locate 'tsx' (dev fallback for repo checkouts). Run `npm install` first."); process.exit(1); }
|
|
75
84
|
run(process.execPath, [cli, script, ...args], opts);
|
|
76
85
|
}
|
|
77
86
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"package.json": "8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
|
|
3
|
+
"scripts/demo-control-plane.js": "3357c8de8e3fe32e73e0be99a3bb02179dff3ee041854d4ee6f8d135e4d8dcbf",
|
|
4
|
+
"scripts/intake-cli.js": "189014db22d6fccb034ed1a93ec11efe8905be820bc468366c68bb2cabaf8a97",
|
|
5
|
+
"scripts/magenta-mirror.js": "cf701065f7a6e20f7f44a9b815396aeb5fe031c3f003bcd793b8014ea8801b85",
|
|
6
|
+
"scripts/magenta-verify.js": "3c41d176a435ae8f53dd49c2418f63be5ad82de7e26fbe9f436a6545f521d331",
|
|
7
|
+
"scripts/mcp-gateway.js": "335923004b73511fe42ea0fc6f2e567afe8018dc95464a79027e4c2537e30de1",
|
|
8
|
+
"server/agent-policy.js": "20dd9150f8244c33aaf14ecdf3a09f471210960d246cda9ab8d5abe0e8433518",
|
|
9
|
+
"server/agent-record.js": "790bbfa2a10601c2bdcd98873e6e41babdf96d1df7c7ca34f19791de4c8b860f",
|
|
10
|
+
"server/crypto.js": "ebbcb2929e7b3651e4ec04c9fbf3dcb656e3ae0d30bf8864f3642f72f3be2f39",
|
|
11
|
+
"server/execution-receipts.js": "171a506df7d1e99f3370de9a41c1a4f0b21b38d1f02edc78d9c44e68c93dbee4",
|
|
12
|
+
"server/intake/categories.js": "e9860eee0e2e0fe96c7ade165e5e068fae0874de2c7d39ff796efc91e713013b",
|
|
13
|
+
"server/intake/engine.js": "abb8feacd925520cbf01acc2e932d9ebb037a3d016b053b7b10deaf8f545a605",
|
|
14
|
+
"server/intake/gateway-intake.js": "40514fa489b031c29725e9b837e83998217bbc1ae84cf9259154a16b1faae2e8",
|
|
15
|
+
"server/intake/index.js": "fd287181a5deb2a2141b479851b4e8fc21aefea39a22baecebdef6a7450af752",
|
|
16
|
+
"server/intake/intake-witness.js": "f974ca4637e03536ed84e1ea65d249b628cb52b322ddee344d42dcf4bba4ed00",
|
|
17
|
+
"server/intake/model-witness.js": "a80563a28b31a041fa9b61ed7b2d13dbca6f8464656e0f2abc2c174a22580d65",
|
|
18
|
+
"server/intake/persistence.js": "49b73cbac3f36f19a3d4f0f4dca88da90e0c281c6aaeae00a022093e339c4792",
|
|
19
|
+
"server/intake/queue.js": "f5e4f6a9a425e23ad113b08e35739355ff979d906e05bb95a48e6082757bbcfd",
|
|
20
|
+
"server/intake/rationale.js": "3ea64a1430828ae5a06b41f4e93d06cdd667850d6a2ff9a1e266f98bdfc6c404",
|
|
21
|
+
"server/intake/types.js": "014891c6c8360648d6954b3185ae92d56be625484bf2f0d0f71dac271a59e23b",
|
|
22
|
+
"server/intake/witness.js": "588ad166f4870d65a6ed334c70aaa2deb1d6d96ec97d889902a9eda7528bd34f",
|
|
23
|
+
"server/persistence.js": "2e6b1d0b1c74babbd581780b028c2593256c5ec96c2d60f4c25159e3f54e4e3f",
|
|
24
|
+
"server/storage.js": "4f1660e9542dcb157148f561efd757d8aa452730c3a09064d3b1314feb16e8c9",
|
|
25
|
+
"server/transparency-log.js": "42d0d9fd3de9c60389ca882b546d9784e6ef0003895898dd5958d763d54f2f6b",
|
|
26
|
+
"server/trust-bootstrap.js": "57a53a3ee9cd630ada2867e8b087a34b069ba26f86a696d3ead74888dd7e8d5f",
|
|
27
|
+
"server/witness.js": "e94384a35a4cfce39990e580eb5b572ef9a2d4c4d9bad1a2ed9b5382aba64b1b",
|
|
28
|
+
"shared/canonical.js": "476cee4b5c5702e8a2a198e79c2639cf8ff84000ffb92f68a04433ed2a2f5484",
|
|
29
|
+
"shared/certificates.js": "7844e71a38e1b1324586c7d054b2f5c15222b86446318d1e6dea9bab504a4a73",
|
|
30
|
+
"shared/schema.js": "f89190990e3826e44c722d5e8f5b39fd59b4d3fb9ec047229930a17604e4646c",
|
|
31
|
+
"shared/terminating-kernel.js": "8e2aa68d47d6780e4a70822482e3e410924e9d1af843f301a487da0af37ed047"
|
|
32
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* MAGENTA DEMO CONTROL PLANE — minimal, local-only proof harness.
|
|
5
|
+
*
|
|
6
|
+
* This is NOT the production server. It is a deliberately tiny node:http surface
|
|
7
|
+
* that exposes ONLY the four endpoints `scripts/demo.mjs` needs to prove the
|
|
8
|
+
* wedge end-to-end:
|
|
9
|
+
*
|
|
10
|
+
* GET /api/health readiness
|
|
11
|
+
* POST /internal/founder/ceremony bootstrap the trust root (INTERNAL_API_KEY)
|
|
12
|
+
* POST /internal/agent/action gate + witness one agent action (INTERNAL_API_KEY)
|
|
13
|
+
* GET /api/trust/evidence export the signed evidence bundle
|
|
14
|
+
*
|
|
15
|
+
* It reuses the EXACT same core trust modules the production routes use
|
|
16
|
+
* (`witnessLog`, `storage`, `recordAgentAction`, `bootstrapRootAuthority`) — the
|
|
17
|
+
* gate/witness/receipt/evidence semantics are identical; only the HTTP shell is
|
|
18
|
+
* different. This lets the lean npm package keep the one-command `demo` without
|
|
19
|
+
* shipping the hosted Express/Passport/PostgreSQL control plane (server/routes.ts,
|
|
20
|
+
* server/index.ts). The production hosted plane remains intact in the repo as its
|
|
21
|
+
* own artifact; this harness does not replace it.
|
|
22
|
+
*
|
|
23
|
+
* Hard constraints (it is a demo, treated like one):
|
|
24
|
+
* - binds 127.0.0.1 ONLY (never a public interface);
|
|
25
|
+
* - persists nothing unless MAGENTA_STATE_FILE is explicitly set (the demo sets
|
|
26
|
+
* it to an ephemeral temp file);
|
|
27
|
+
* - INTERNAL_API_KEY-gated /internal/* routes, fail closed (503) when unset;
|
|
28
|
+
* - no Express, no Passport, no database driver, no sessions, no founder admin,
|
|
29
|
+
* no production routes — only the proof path.
|
|
30
|
+
*/
|
|
31
|
+
const node_http_1 = require("node:http");
|
|
32
|
+
const node_crypto_1 = require("node:crypto");
|
|
33
|
+
const storage_1 = require("../server/storage");
|
|
34
|
+
const witness_1 = require("../server/witness");
|
|
35
|
+
const agent_record_1 = require("../server/agent-record");
|
|
36
|
+
const trust_bootstrap_1 = require("../server/trust-bootstrap");
|
|
37
|
+
const PORT = Number(process.env.PORT ?? 0);
|
|
38
|
+
const HOST = "127.0.0.1";
|
|
39
|
+
const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY;
|
|
40
|
+
function sendJson(res, status, body) {
|
|
41
|
+
const text = JSON.stringify(body);
|
|
42
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
43
|
+
res.end(text);
|
|
44
|
+
}
|
|
45
|
+
function readBody(req) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
let raw = "";
|
|
48
|
+
req.on("data", (c) => {
|
|
49
|
+
raw += c;
|
|
50
|
+
if (raw.length > 1000000)
|
|
51
|
+
reject(new Error("request body too large"));
|
|
52
|
+
});
|
|
53
|
+
req.on("end", () => {
|
|
54
|
+
if (!raw.trim())
|
|
55
|
+
return resolve({});
|
|
56
|
+
try {
|
|
57
|
+
resolve(JSON.parse(raw));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
reject(new Error("invalid JSON body"));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
req.on("error", reject);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// Constant-time INTERNAL_API_KEY check, same posture as the production route.
|
|
67
|
+
function internalAuthOk(req, res) {
|
|
68
|
+
if (!INTERNAL_API_KEY) {
|
|
69
|
+
sendJson(res, 503, { error: "internal_auth_unconfigured", message: "INTERNAL_API_KEY is not set" });
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const provided = req.headers["x-internal-key"];
|
|
73
|
+
const ok = typeof provided === "string" &&
|
|
74
|
+
(0, node_crypto_1.timingSafeEqual)((0, node_crypto_1.createHash)("sha256").update(provided).digest(), (0, node_crypto_1.createHash)("sha256").update(INTERNAL_API_KEY).digest());
|
|
75
|
+
if (!ok) {
|
|
76
|
+
sendJson(res, 401, { error: "unauthorized", message: "Valid X-Internal-Key header required" });
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
const server = (0, node_http_1.createServer)(async (req, res) => {
|
|
82
|
+
try {
|
|
83
|
+
const url = (req.url ?? "").split("?")[0];
|
|
84
|
+
const method = req.method ?? "GET";
|
|
85
|
+
// ── GET /api/health ──────────────────────────────────────────────────────
|
|
86
|
+
if (method === "GET" && url === "/api/health") {
|
|
87
|
+
return sendJson(res, 200, { status: "healthy", plane: "demo-control-plane" });
|
|
88
|
+
}
|
|
89
|
+
// ── POST /internal/founder/ceremony ──────────────────────────────────────
|
|
90
|
+
// recordAgentAction only requires the ROOT authority (it signs receipts with
|
|
91
|
+
// the root key), so the minimal ceremony bootstraps the root if absent.
|
|
92
|
+
if (method === "POST" && url === "/internal/founder/ceremony") {
|
|
93
|
+
if (!internalAuthOk(req, res))
|
|
94
|
+
return;
|
|
95
|
+
let rootCert = await storage_1.storage.getRootCertificate();
|
|
96
|
+
if (!rootCert) {
|
|
97
|
+
await (0, trust_bootstrap_1.bootstrapRootAuthority)();
|
|
98
|
+
rootCert = await storage_1.storage.getRootCertificate();
|
|
99
|
+
if (!rootCert)
|
|
100
|
+
return sendJson(res, 500, { error: "ceremony_failed", message: "root bootstrap did not persist" });
|
|
101
|
+
}
|
|
102
|
+
return sendJson(res, 200, {
|
|
103
|
+
status: "FOUNDER_CEREMONY_COMPLETE",
|
|
104
|
+
message: "Demo trust root established (minimal local proof harness).",
|
|
105
|
+
root_public_key: rootCert.subject_pubkey,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// ── POST /internal/agent/action ──────────────────────────────────────────
|
|
109
|
+
// Same shared core the MCP gateway uses; response shape mirrors the
|
|
110
|
+
// production route so the gateway driver consumes it unchanged.
|
|
111
|
+
if (method === "POST" && url === "/internal/agent/action") {
|
|
112
|
+
if (!internalAuthOk(req, res))
|
|
113
|
+
return;
|
|
114
|
+
let body;
|
|
115
|
+
try {
|
|
116
|
+
body = await readBody(req);
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
return sendJson(res, 400, { error: "bad_request", message: e.message });
|
|
120
|
+
}
|
|
121
|
+
if (!body?.agent_id || !body?.authorized_by || !Array.isArray(body?.granted_capabilities) || body.granted_capabilities.length === 0 || !body?.action) {
|
|
122
|
+
return sendJson(res, 400, { error: "bad_request", message: "provide {agent_id, authorized_by, granted_capabilities[], action, params?}" });
|
|
123
|
+
}
|
|
124
|
+
let recorded;
|
|
125
|
+
try {
|
|
126
|
+
recorded = await (0, agent_record_1.recordAgentAction)({
|
|
127
|
+
action: body.action,
|
|
128
|
+
params: body.params ?? {},
|
|
129
|
+
grantedCapabilities: body.granted_capabilities,
|
|
130
|
+
agentId: body.agent_id,
|
|
131
|
+
authorizedBy: body.authorized_by,
|
|
132
|
+
intent: body.intent ?? "",
|
|
133
|
+
model: body.model ?? "none(structured-input)",
|
|
134
|
+
session: body.session,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
if (e instanceof agent_record_1.TrustNotBootstrappedError) {
|
|
139
|
+
return sendJson(res, 409, { error: "trust_not_bootstrapped", message: e.message });
|
|
140
|
+
}
|
|
141
|
+
throw e;
|
|
142
|
+
}
|
|
143
|
+
const { decision, receipt, index } = recorded;
|
|
144
|
+
return sendJson(res, 200, {
|
|
145
|
+
recorded: true,
|
|
146
|
+
allowed: decision.allowed,
|
|
147
|
+
decision,
|
|
148
|
+
receipt: { action: receipt.action, receipt_hash: receipt.receipt_hash, timestamp: receipt.timestamp },
|
|
149
|
+
witness: { index, latest_sth: witness_1.witnessLog.latestSTH() ?? null },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// ── GET /api/trust/evidence ──────────────────────────────────────────────
|
|
153
|
+
if (method === "GET" && url === "/api/trust/evidence") {
|
|
154
|
+
const receipts = [...(await storage_1.storage.getExecutionReceipts())].reverse(); // creation order
|
|
155
|
+
return sendJson(res, 200, {
|
|
156
|
+
witness_pubkey: witness_1.witnessLog.witnessPublicKey,
|
|
157
|
+
sth: witness_1.witnessLog.latestSTH() ?? null,
|
|
158
|
+
receipts,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
sendJson(res, 404, { error: "not_found", message: `${method} ${url} is not served by the demo control plane` });
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
sendJson(res, 500, { error: "internal_error", message: e instanceof Error ? e.message : String(e) });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
server.listen(PORT, HOST, () => {
|
|
168
|
+
const addr = server.address();
|
|
169
|
+
const port = typeof addr === "object" && addr ? addr.port : PORT;
|
|
170
|
+
console.log(`demo-control-plane listening on http://${HOST}:${port}`);
|
|
171
|
+
});
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Intake Discipline CLI — exercise classification-as-governed-promotion.
|
|
5
|
+
*
|
|
6
|
+
* npx tsx scripts/intake-cli.ts kernel # print + verify the terminating kernel
|
|
7
|
+
* npx tsx scripts/intake-cli.ts demo # worked promote / quarantine / fail-closed
|
|
8
|
+
* npx tsx scripts/intake-cli.ts classify <in.json> # run one classification from a JSON spec
|
|
9
|
+
*
|
|
10
|
+
* The CLI never authorizes runtime/autonomy. It only classifies intake and
|
|
11
|
+
* prints witnessed promote-or-quarantine receipts. Action remains fail-closed:
|
|
12
|
+
* a "promote" here means "admitted to the trusted pipeline", not "executed".
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
const node_fs_1 = require("node:fs");
|
|
39
|
+
const index_1 = require("../server/intake/index");
|
|
40
|
+
const C = {
|
|
41
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
42
|
+
green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m", magenta: "\x1b[35m", cyan: "\x1b[36m",
|
|
43
|
+
};
|
|
44
|
+
const tty = process.stdout.isTTY;
|
|
45
|
+
const c = (code, s) => (tty ? code + s + C.reset : s);
|
|
46
|
+
function printKernel() {
|
|
47
|
+
console.log(c(C.bold, "Terminating Kernel ") + c(C.dim, `(${index_1.TERMINATING_KERNEL.version})`));
|
|
48
|
+
for (const a of index_1.TERMINATING_KERNEL.axioms) {
|
|
49
|
+
console.log(` ${c(C.cyan, a.id)} ${c(C.dim, `[${a.kind}]`)} ${a.law}`);
|
|
50
|
+
}
|
|
51
|
+
const ok = (0, index_1.verifyKernelSeal)();
|
|
52
|
+
console.log("");
|
|
53
|
+
console.log(` seal: ${c(C.dim, index_1.KERNEL_SEAL)}`);
|
|
54
|
+
console.log(` sealed & intact: ${ok ? c(C.green, "yes") : c(C.red, "NO — kernel has been edited without re-sealing")}`);
|
|
55
|
+
}
|
|
56
|
+
function decorrelatedWitnesses(independentVerdict) {
|
|
57
|
+
return [
|
|
58
|
+
index_1.provenanceWitness,
|
|
59
|
+
index_1.structuralRationaleWitness,
|
|
60
|
+
(0, index_1.makeDeterministicReclassifier)("reclass.cli", () => independentVerdict),
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
function runOne(ledger, spec) {
|
|
64
|
+
const raw = ledger.submitRaw(spec.content, spec.source);
|
|
65
|
+
const claim = {
|
|
66
|
+
raw_id: raw.raw_id,
|
|
67
|
+
raw_hash: raw.raw_hash,
|
|
68
|
+
proposed_category: spec.proposed_category,
|
|
69
|
+
classifier_identity: spec.classifier_identity ?? "cli:operator",
|
|
70
|
+
rationale: [{ cites: "raw_hash", value: raw.raw_hash }],
|
|
71
|
+
allowed_downstream_uses: spec.allowed_downstream_uses ?? ["display"],
|
|
72
|
+
confidence: spec.confidence ?? 0.9,
|
|
73
|
+
};
|
|
74
|
+
const witnesses = decorrelatedWitnesses(spec.independent_verdict ?? spec.proposed_category);
|
|
75
|
+
if (spec.authority) {
|
|
76
|
+
// High-authority promotions require agreement across >= 2 mechanism CLASSES;
|
|
77
|
+
// add a rule-class witness alongside the deterministic set.
|
|
78
|
+
witnesses.push((0, index_1.makeRuleWitness)("cli-policy", (_c, r) => ({
|
|
79
|
+
verdict: spec.independent_verdict ?? spec.proposed_category,
|
|
80
|
+
reason: "cli policy table match",
|
|
81
|
+
citations: [{ cites: "raw_hash", value: r.raw_hash }],
|
|
82
|
+
})));
|
|
83
|
+
}
|
|
84
|
+
const outcome = ledger.classify(claim, witnesses, spec.authority);
|
|
85
|
+
const r = outcome.receipt;
|
|
86
|
+
const tag = outcome.decision === "promote"
|
|
87
|
+
? c(C.green, "● PROMOTE")
|
|
88
|
+
: c(C.yellow, "▲ QUARANTINE");
|
|
89
|
+
console.log("");
|
|
90
|
+
console.log(`${tag} ${c(C.bold, spec.proposed_category)} ${c(C.dim, raw.raw_id)}`);
|
|
91
|
+
console.log(` source ${r.source_identity} @ ${r.source_location}`);
|
|
92
|
+
console.log(` raw_hash ${c(C.dim, r.raw_hash)}`);
|
|
93
|
+
console.log(` classifier ${r.classifier_identity} (confidence ${String(r.confidence)})`);
|
|
94
|
+
console.log(` witnesses ${r.witnesses.map((w) => `${c(C.dim, w.mechanism_class)}/${w.mechanism}:${w.availability !== "available" ? c(C.yellow, w.availability) : w.agree ? c(C.green, "agree") : c(C.red, w.verdict)}`).join(" ")}`);
|
|
95
|
+
console.log(` reconcile ${r.reconciliation.status} (${r.reconciliation.against})`);
|
|
96
|
+
if (r.decision_reasons.length > 0) {
|
|
97
|
+
console.log(c(C.yellow, " held because:"));
|
|
98
|
+
for (const reason of r.decision_reasons)
|
|
99
|
+
console.log(c(C.yellow, ` - ${reason}`));
|
|
100
|
+
}
|
|
101
|
+
console.log(` receipt_hash ${c(C.magenta, r.receipt_hash)}`);
|
|
102
|
+
}
|
|
103
|
+
function demo() {
|
|
104
|
+
console.log(c(C.bold, "Intake Discipline — worked example\n"));
|
|
105
|
+
printKernel();
|
|
106
|
+
const ledger = new index_1.IntakeLedger({ anchor: () => { } });
|
|
107
|
+
console.log("\n" + c(C.dim, "1) clean low-authority classification → PROMOTE"));
|
|
108
|
+
runOne(ledger, {
|
|
109
|
+
content: { observed: "agent read a file" },
|
|
110
|
+
source: { identity: "fs:watcher", location: "/repo/README.md" },
|
|
111
|
+
proposed_category: "observation",
|
|
112
|
+
});
|
|
113
|
+
console.log("\n" + c(C.dim, "2) independent reclassifier disagrees → QUARANTINE (retained, not discarded)"));
|
|
114
|
+
runOne(ledger, {
|
|
115
|
+
content: { text: "the refund is approved" },
|
|
116
|
+
source: { identity: "model:opus", location: "chat" },
|
|
117
|
+
proposed_category: "approval",
|
|
118
|
+
independent_verdict: "claim", // the rule sees a mere claim, not an approval
|
|
119
|
+
authority: { capabilities: ["promote:approval"], grantedBy: "human:cj" },
|
|
120
|
+
});
|
|
121
|
+
console.log("\n" + c(C.dim, "3) high-authority category with NO governed authority → QUARANTINE (fail-closed)"));
|
|
122
|
+
runOne(ledger, {
|
|
123
|
+
content: { do: "refund $5000" },
|
|
124
|
+
source: { identity: "agent:autopilot", location: "tool:stripe" },
|
|
125
|
+
proposed_category: "execution",
|
|
126
|
+
});
|
|
127
|
+
console.log("\n" + c(C.dim, "4) same act WITH a governed authority grant → PROMOTE"));
|
|
128
|
+
runOne(ledger, {
|
|
129
|
+
content: { do: "refund $50" },
|
|
130
|
+
source: { identity: "agent:autopilot", location: "tool:stripe" },
|
|
131
|
+
proposed_category: "execution",
|
|
132
|
+
authority: { capabilities: ["promote:execution"], grantedBy: "human:cj" },
|
|
133
|
+
});
|
|
134
|
+
console.log("\n" + c(C.dim, "5) an UNAVAILABLE model witness (no runtime injected) → QUARANTINE (held, not crashed)"));
|
|
135
|
+
{
|
|
136
|
+
const raw = ledger.submitRaw({ text: "needs a model opinion" }, { identity: "feed", location: "intake" });
|
|
137
|
+
const claim = {
|
|
138
|
+
raw_id: raw.raw_id,
|
|
139
|
+
raw_hash: raw.raw_hash,
|
|
140
|
+
proposed_category: "evidence",
|
|
141
|
+
classifier_identity: "cli:operator",
|
|
142
|
+
rationale: [{ cites: "raw_hash", value: raw.raw_hash }],
|
|
143
|
+
allowed_downstream_uses: ["display"],
|
|
144
|
+
confidence: 0.6,
|
|
145
|
+
};
|
|
146
|
+
const outcome = ledger.classify(claim, [
|
|
147
|
+
index_1.provenanceWitness,
|
|
148
|
+
index_1.structuralRationaleWitness,
|
|
149
|
+
(0, index_1.createModelWitnessAdapter)("opus"), // no decide → unavailable → blocks
|
|
150
|
+
]);
|
|
151
|
+
const r = outcome.receipt;
|
|
152
|
+
console.log(`${c(C.yellow, "▲ QUARANTINE")} evidence ${c(C.dim, raw.raw_id)}`);
|
|
153
|
+
console.log(` witnesses ${r.witnesses.map((w) => `${c(C.dim, w.mechanism_class)}:${w.availability !== "available" ? c(C.yellow, w.availability) : c(C.green, "agree")}`).join(" ")}`);
|
|
154
|
+
for (const reason of r.decision_reasons)
|
|
155
|
+
console.log(c(C.yellow, ` - ${reason}`));
|
|
156
|
+
}
|
|
157
|
+
const s = ledger.status();
|
|
158
|
+
console.log("\n" + c(C.bold, "ledger ") + `promoted=${s.promoted} quarantined=${s.quarantined} receipts=${s.receipts}`);
|
|
159
|
+
console.log(c(C.dim, `chain head ${s.head}`));
|
|
160
|
+
}
|
|
161
|
+
function queueDemo() {
|
|
162
|
+
console.log(c(C.bold, "Quarantine Escalation Queue — worked example\n"));
|
|
163
|
+
const ledger = new index_1.IntakeLedger({ anchor: () => { } });
|
|
164
|
+
const queue = new index_1.QuarantineQueue();
|
|
165
|
+
// 1) a model-unavailable quarantine lands in the queue
|
|
166
|
+
const raw = ledger.submitRaw({ text: "ambiguous directive" }, { identity: "feed", location: "intake" });
|
|
167
|
+
const claim = {
|
|
168
|
+
raw_id: raw.raw_id,
|
|
169
|
+
raw_hash: raw.raw_hash,
|
|
170
|
+
proposed_category: "claim",
|
|
171
|
+
classifier_identity: "cli:operator",
|
|
172
|
+
rationale: [{ cites: "raw_hash", value: raw.raw_hash }],
|
|
173
|
+
allowed_downstream_uses: ["display"],
|
|
174
|
+
confidence: 0.5,
|
|
175
|
+
};
|
|
176
|
+
const held = ledger.classify(claim, [index_1.provenanceWitness, index_1.structuralRationaleWitness, (0, index_1.createModelWitnessAdapter)("opus")]);
|
|
177
|
+
if (held.decision !== "quarantine")
|
|
178
|
+
throw new Error("expected quarantine");
|
|
179
|
+
const item = queue.ingest(held);
|
|
180
|
+
console.log(c(C.dim, "1) model-unavailable quarantine ingested as a queue item"));
|
|
181
|
+
console.log(` ${c(C.cyan, item.item_id)} status=${item.status} reasons=${item.quarantine_reasons.length} original receipt ${c(C.dim, item.quarantine_receipt_hash.slice(0, 16))}…`);
|
|
182
|
+
// 2) a human reviews it; their decision is ONE witness; the ceremony decides
|
|
183
|
+
console.log(c(C.dim, "\n2) human review supplies a HumanDecision → ceremony re-runs"));
|
|
184
|
+
const { item: reviewed, outcome } = queue.review(ledger, item.item_id, {
|
|
185
|
+
decision: { reviewer: "cj", verdict: "claim", reason: "read it; it is an assertion, not an instruction" },
|
|
186
|
+
witnesses: [index_1.provenanceWitness, index_1.structuralRationaleWitness],
|
|
187
|
+
});
|
|
188
|
+
console.log(` ${c(C.cyan, reviewed.item_id)} status=${c(reviewed.status === "resolved_promoted" ? C.green : C.yellow, reviewed.status)}`);
|
|
189
|
+
console.log(` original receipt ${c(C.dim, reviewed.quarantine_receipt_hash.slice(0, 16))}… preserved`);
|
|
190
|
+
console.log(` new receipt ${c(C.magenta, reviewed.resolution.new_receipt_hash.slice(0, 16))}… (${outcome.decision})`);
|
|
191
|
+
// 3) duplicate processing fails closed
|
|
192
|
+
console.log(c(C.dim, "\n3) duplicate review attempt fails closed"));
|
|
193
|
+
try {
|
|
194
|
+
queue.review(ledger, item.item_id, {
|
|
195
|
+
decision: { reviewer: "cj", verdict: "claim", reason: "again" },
|
|
196
|
+
witnesses: [index_1.provenanceWitness, index_1.structuralRationaleWitness],
|
|
197
|
+
});
|
|
198
|
+
console.log(c(C.red, " UNEXPECTED: duplicate review succeeded"));
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
console.log(` ${c(C.green, "refused:")} ${err.message}`);
|
|
202
|
+
}
|
|
203
|
+
const qs = queue.status();
|
|
204
|
+
console.log("\n" + c(C.bold, "queue ") + JSON.stringify(qs.byStatus) + c(C.dim, ` ledger receipts=${ledger.status().receipts}`));
|
|
205
|
+
}
|
|
206
|
+
function modelDemo() {
|
|
207
|
+
console.log(c(C.bold, "Model Witness Runtime Adapter — worked example\n"));
|
|
208
|
+
const ledger = new index_1.IntakeLedger({ anchor: () => { } });
|
|
209
|
+
const run = (label, witness, category = "observation", authority) => {
|
|
210
|
+
const raw = ledger.submitRaw({ text: label }, { identity: "feed", location: "intake" });
|
|
211
|
+
const claim = {
|
|
212
|
+
raw_id: raw.raw_id, raw_hash: raw.raw_hash, proposed_category: category,
|
|
213
|
+
classifier_identity: "cli:operator",
|
|
214
|
+
rationale: [{ cites: "raw_hash", value: raw.raw_hash }],
|
|
215
|
+
allowed_downstream_uses: ["display"], confidence: 0.8,
|
|
216
|
+
};
|
|
217
|
+
const outcome = ledger.classify(claim, [index_1.provenanceWitness, index_1.structuralRationaleWitness, witness], authority);
|
|
218
|
+
const m = outcome.receipt.witnesses.find((w) => w.mechanism_class === "model");
|
|
219
|
+
const tag = outcome.decision === "promote" ? c(C.green, "● PROMOTE ") : c(C.yellow, "▲ QUARANTINE");
|
|
220
|
+
console.log(`${tag} ${label}`);
|
|
221
|
+
console.log(` model witness: ${m.availability !== "available" ? c(C.yellow, m.availability) : m.agree ? c(C.green, "agree") : c(C.red, String(m.verdict))} — ${m.reason.slice(0, 100)}`);
|
|
222
|
+
if (outcome.decision === "quarantine") {
|
|
223
|
+
for (const r of outcome.receipt.decision_reasons.slice(0, 2))
|
|
224
|
+
console.log(c(C.dim, ` - ${r.slice(0, 110)}`));
|
|
225
|
+
}
|
|
226
|
+
console.log("");
|
|
227
|
+
};
|
|
228
|
+
run("1) no runtime injected → unavailable, held", (0, index_1.createModelRuntimeWitness)("opus"));
|
|
229
|
+
run("2) configured fake runtime, valid structured decision → usable vote, promotes", (0, index_1.createModelRuntimeWitness)("opus", (0, index_1.makeFakeModelRuntime)("fake:ok", () => "observation", { confidence: 0.85 })));
|
|
230
|
+
run("3) prose-only output → inadmissible, held", (0, index_1.createModelRuntimeWitness)("opus", (0, index_1.makeFakeModelRuntime)("fake:prose", () => "observation", { returnProse: true })));
|
|
231
|
+
run("4) runtime throws → contained, held", (0, index_1.createModelRuntimeWitness)("opus", (0, index_1.makeFakeModelRuntime)("fake:boom", () => "observation", { throwError: true })));
|
|
232
|
+
run("5) model agrees on EXECUTION but no governed authority → held (model is never authority)", (0, index_1.createModelRuntimeWitness)("opus", (0, index_1.makeFakeModelRuntime)("fake:ok", () => "execution")), "execution");
|
|
233
|
+
const s = ledger.status();
|
|
234
|
+
console.log(c(C.bold, "ledger ") + `promoted=${s.promoted} quarantined=${s.quarantined} receipts=${s.receipts}`);
|
|
235
|
+
}
|
|
236
|
+
async function gatewayDemo() {
|
|
237
|
+
const { McpGate } = await Promise.resolve().then(() => __importStar(require("./mcp-gateway")));
|
|
238
|
+
console.log(c(C.bold, "Gateway Intake Integration — worked example (enforce mode)\n"));
|
|
239
|
+
console.log(c(C.dim, "Intake sits in FRONT of the capability gate. A tool call reaches downstream"));
|
|
240
|
+
console.log(c(C.dim, "ONLY IF intake promotes AND the gate allows.\n"));
|
|
241
|
+
const ledger = new index_1.IntakeLedger({ anchor: () => { } });
|
|
242
|
+
const queue = new index_1.QuarantineQueue();
|
|
243
|
+
const checker = (0, index_1.makeGatewayIntakeChecker)({
|
|
244
|
+
ledger, queue,
|
|
245
|
+
baseWitnesses: [index_1.provenanceWitness, index_1.structuralRationaleWitness],
|
|
246
|
+
policy: index_1.conservativeGatewayPolicy,
|
|
247
|
+
});
|
|
248
|
+
const config = { agent: { agentId: "a1", authorizedBy: "op" }, grantedCapabilities: ["agent.mcp.call"] };
|
|
249
|
+
let downstreamHits = 0;
|
|
250
|
+
const forward = async (msg) => { downstreamHits++; return { jsonrpc: "2.0", id: (msg.id ?? null), result: { content: [{ type: "text", text: "DOWNSTREAM REACHED" }] } }; };
|
|
251
|
+
const gateFor = (allowed) => new McpGate(config, async () => ({ allowed, reason: allowed ? "ok" : "ceiling exceeded", receiptHash: "exec" }), forward, { mode: "enforce", check: checker });
|
|
252
|
+
const call = (name, args) => ({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name, arguments: args } });
|
|
253
|
+
const show = async (label, gateAllowed, args) => {
|
|
254
|
+
const before = downstreamHits;
|
|
255
|
+
const out = await gateFor(gateAllowed).handle(call("refund", args));
|
|
256
|
+
const reached = downstreamHits > before;
|
|
257
|
+
const tag = reached ? c(C.green, "→ DOWNSTREAM REACHED") : c(C.red, "✗ blocked, downstream NOT reached");
|
|
258
|
+
console.log(`${c(C.bold, label)}`);
|
|
259
|
+
console.log(` intake: ${out.intakeQuarantined ? c(C.yellow, "QUARANTINE") : c(C.green, "promote")} | gate: ${gateAllowed ? c(C.green, "allow") : c(C.red, "refuse")} ⇒ ${tag}\n`);
|
|
260
|
+
};
|
|
261
|
+
await show("1) well-formed args, gate allows", true, { amount: 50 });
|
|
262
|
+
await show("2) well-formed args, gate REFUSES (intake promote can't override the gate)", false, { amount: 9999 });
|
|
263
|
+
await show("3) prototype-pollution args, gate would allow (intake quarantines first)", true, { amount: 50, ["__proto__"]: { x: 1 } });
|
|
264
|
+
console.log(c(C.bold, "result ") + `downstream reached ${downstreamHits}/3 times (only case 1)`);
|
|
265
|
+
console.log(c(C.dim, `escalation queue: ${queue.pending().length} item(s) pending review`));
|
|
266
|
+
}
|
|
267
|
+
function classifyFromFile(path) {
|
|
268
|
+
const spec = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
269
|
+
if (!(0, index_1.isIntakeCategory)(spec.proposed_category)) {
|
|
270
|
+
console.error(`invalid proposed_category: ${spec.proposed_category}`);
|
|
271
|
+
process.exit(2);
|
|
272
|
+
}
|
|
273
|
+
const ledger = new index_1.IntakeLedger({ anchor: () => { } });
|
|
274
|
+
runOne(ledger, spec);
|
|
275
|
+
}
|
|
276
|
+
const [sub, arg] = process.argv.slice(2);
|
|
277
|
+
switch (sub) {
|
|
278
|
+
case "kernel":
|
|
279
|
+
printKernel();
|
|
280
|
+
break;
|
|
281
|
+
case "demo":
|
|
282
|
+
demo();
|
|
283
|
+
break;
|
|
284
|
+
case "queue-demo":
|
|
285
|
+
queueDemo();
|
|
286
|
+
break;
|
|
287
|
+
case "model-demo":
|
|
288
|
+
modelDemo();
|
|
289
|
+
break;
|
|
290
|
+
case "gateway-demo":
|
|
291
|
+
gatewayDemo();
|
|
292
|
+
break;
|
|
293
|
+
case "classify":
|
|
294
|
+
if (!arg) {
|
|
295
|
+
console.error("usage: intake-cli classify <input.json>");
|
|
296
|
+
process.exit(2);
|
|
297
|
+
}
|
|
298
|
+
classifyFromFile(arg);
|
|
299
|
+
break;
|
|
300
|
+
default:
|
|
301
|
+
console.log("usage: intake-cli <kernel|demo|queue-demo|model-demo|gateway-demo|classify <input.json>>");
|
|
302
|
+
process.exit(sub ? 2 : 0);
|
|
303
|
+
}
|