@archstone/cli 0.5.2 → 0.7.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/dist/index.js CHANGED
@@ -23,6 +23,13 @@ archstone apply ${dir}
23
23
  for (const d of res.capabilityDocs) {
24
24
  console.log(` \u2713 ${d.capability.id} [${d.capability.effect}] \u2192 ${d.capability.provider ?? "?"}`);
25
25
  }
26
+ if (res.policyDocs.length > 0) {
27
+ console.log(` policies ${res.policyDocs.length} policy document(s)`);
28
+ for (const p of res.policyDocs) {
29
+ const target = p.metadata.scope === "capability" ? `capability ${p.metadata.capabilityId ?? "?"}` : p.metadata.scope === "provider" ? `provider ${p.metadata.provider ?? "?"}` : "(no scope)";
30
+ console.log(` \u2713 ${p.metadata.id} \u2192 ${target}`);
31
+ }
32
+ }
26
33
  if (res.issues.length > 0) {
27
34
  console.log(`
28
35
  \u2717 ${res.issues.length} shape issue(s):`);
@@ -100,29 +107,74 @@ function runServeHttp(dir, port, token) {
100
107
  }
101
108
  const handler = createHttpHandler(built.registry, { bearerToken: token });
102
109
  const server = createServer((req, res) => {
103
- void handleHttpRequest(handler, req, res);
110
+ handleHttpRequest(handler, req, res).catch((err) => {
111
+ console.error("archstone serve --http: request handling failed \u2014", err);
112
+ endResponseQuietly(res, 500);
113
+ });
104
114
  });
105
115
  server.listen(port, () => {
106
116
  console.error(`archstone: serving MCP over HTTP on http://localhost:${port}/ (bearer-token gated)`);
107
117
  });
108
118
  }
119
+ var MAX_REQUEST_BODY_BYTES = 4 * 1024 * 1024;
120
+ function endResponseQuietly(res, status, opts = {}) {
121
+ try {
122
+ if (res.writableEnded || res.destroyed) return;
123
+ if (!res.headersSent) {
124
+ res.statusCode = status;
125
+ if (opts.closeConnection) res.setHeader("connection", "close");
126
+ }
127
+ res.end();
128
+ } catch {
129
+ }
130
+ }
109
131
  async function handleHttpRequest(handler, req, res) {
132
+ const declared = Number(req.headers["content-length"]);
133
+ if (Number.isFinite(declared) && declared > MAX_REQUEST_BODY_BYTES) {
134
+ endResponseQuietly(res, 413, { closeConnection: true });
135
+ return;
136
+ }
110
137
  const chunks = [];
111
- for await (const chunk of req) chunks.push(chunk);
112
- const headers = new Headers();
113
- for (const [key, value] of Object.entries(req.headers)) {
114
- if (value !== void 0) headers.set(key, Array.isArray(value) ? value.join(", ") : value);
115
- }
116
- const hasBody = req.method !== "GET" && req.method !== "HEAD" && chunks.length > 0;
117
- const request = new Request(`http://${req.headers.host ?? "localhost"}${req.url ?? "/"}`, {
118
- method: req.method ?? "GET",
119
- headers,
120
- body: hasBody ? Buffer.concat(chunks) : void 0
121
- });
122
- const response = await handler(request);
123
- res.statusCode = response.status;
124
- response.headers.forEach((value, key) => res.setHeader(key, value));
125
- res.end(response.body ? Buffer.from(await response.arrayBuffer()) : void 0);
138
+ try {
139
+ let received = 0;
140
+ for await (const chunk of req) {
141
+ const buf = chunk;
142
+ received += buf.length;
143
+ if (received > MAX_REQUEST_BODY_BYTES) {
144
+ endResponseQuietly(res, 413, { closeConnection: true });
145
+ return;
146
+ }
147
+ chunks.push(buf);
148
+ }
149
+ } catch {
150
+ endResponseQuietly(res, 400);
151
+ return;
152
+ }
153
+ let request;
154
+ try {
155
+ const headers = new Headers();
156
+ for (const [key, value] of Object.entries(req.headers)) {
157
+ if (value !== void 0) headers.set(key, Array.isArray(value) ? value.join(", ") : value);
158
+ }
159
+ const hasBody = req.method !== "GET" && req.method !== "HEAD" && chunks.length > 0;
160
+ request = new Request(`http://${req.headers.host ?? "localhost"}${req.url ?? "/"}`, {
161
+ method: req.method ?? "GET",
162
+ headers,
163
+ body: hasBody ? Buffer.concat(chunks) : void 0
164
+ });
165
+ } catch {
166
+ endResponseQuietly(res, 400);
167
+ return;
168
+ }
169
+ try {
170
+ const response = await handler(request);
171
+ res.statusCode = response.status;
172
+ response.headers.forEach((value, key) => res.setHeader(key, value));
173
+ res.end(response.body ? Buffer.from(await response.arrayBuffer()) : void 0);
174
+ } catch (err) {
175
+ console.error("archstone serve --http: request handling failed \u2014", err);
176
+ endResponseQuietly(res, 500);
177
+ }
126
178
  }
127
179
  var HEALTH_ICON = { green: "\u{1F7E2}", yellow: "\u{1F7E1}", red: "\u{1F534}" };
128
180
  async function runVerifyCmd(dir, json) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n// @archstone/cli — `archstone apply` (#1) + `archstone serve` (#7, + `--http` ADD-0008 #29)\n// + `archstone verify` (#18-20) + `archstone build` (ADD-0008 #27)\n//\n// apply: parse → shape-validate (#2) → semantic-validate (#3) → compile IR (#4)\n// → index Registry (#5), and REPORT (human output, exits).\n// serve: build the registry and expose it as an MCP server over stdio (#7),\n// so Claude/Cursor/ChatGPT can discover and invoke the tools. Blocks.\n// serve --http: same registry, served over real Streamable-HTTP instead of stdio —\n// `@archstone/runtime/http`'s createHttpHandler (Web-standard Request/Response,\n// bearer-token gated, shared with @archstone/agent/mcp's mcpHandler(), ADD-0008 D-3)\n// behind a thin Node-http adapter. Blocks.\n// verify: replay each bound capability's golden fixture against the LIVE backend\n// and report a per-binding health status (ADD-18). The only command that\n// makes a network call outside a real MCP invocation — on demand, never\n// scheduled by Archstone itself (wire it into your own CI/cron).\n// build: run the same compile pipeline as `apply`, strip each tool's `contract`\n// (D-8 — the fingerprint/golden-fixture path is meaningless once the fixture\n// file isn't shipping), and write the IR as a standalone JSON artifact —\n// the substrate `@archstone/agent`'s `fromIR()` will consume (RFC-0008).\n\nimport { writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { load } from \"@archstone/schema\";\nimport { validateSemantics, compile, type IR } from \"@archstone/compiler\";\nimport { Registry, buildRegistry, serveStdio, runVerify, type HealthStatus } from \"@archstone/runtime\";\nimport { createHttpHandler } from \"@archstone/runtime/http\";\n\nfunction runApply(dir: string): void {\n const res = load(dir);\n console.log(`\\narchstone apply ${dir}\\n`);\n\n if (res.capabilities) {\n const c = res.capabilities;\n console.log(` company ${c.company.name ?? c.company.id} (${c.company.id})`);\n console.log(` providers ${c.providers.join(\", \")}`);\n console.log(` declared ${c.capabilities.length} capabilities`);\n }\n console.log(` loaded ${res.capabilityDocs.length} capability docs, ${res.bindings.length} bindings`);\n for (const d of res.capabilityDocs) {\n console.log(` ✓ ${d.capability.id} [${d.capability.effect}] → ${d.capability.provider ?? \"?\"}`);\n }\n\n // Shape (schema) issues from #2 — \"valid shapes\" is not \"deployable\".\n if (res.issues.length > 0) {\n console.log(`\\n ✗ ${res.issues.length} shape issue(s):`);\n for (const i of res.issues) console.log(` - ${i.file}: ${i.message}`);\n } else {\n console.log(`\\n ✓ shapes valid`);\n }\n\n // Semantic pass (#3) — cross-file resolution; errors block, warnings inform.\n const diags = validateSemantics(res);\n const errors = diags.filter((d) => d.severity === \"error\");\n const warnings = diags.filter((d) => d.severity === \"warning\");\n console.log(` semantic ${errors.length} error(s), ${warnings.length} warning(s)`);\n for (const d of errors) console.log(` ✗ ${d.message}`);\n for (const d of warnings) console.log(` ⚠ ${d.message}`);\n\n const shapesAndSemanticsOk = res.ok && errors.length === 0;\n\n // Compile to IR (#4) + index into the Registry (#5) — only when valid enough to emit.\n // ADD-30: a tool-name collision (two capability ids sanitizing to the same advertised\n // name) is checked here, before the final `ok`, alongside the semantic errors above —\n // 'apply' must refuse the same manifest 'build'/'serve' would refuse (D-2).\n const registry = shapesAndSemanticsOk ? new Registry(compile(res)) : undefined;\n const collisions = registry?.toolNameCollisions ?? [];\n if (collisions.length > 0) {\n console.log(`\\n ✗ ${collisions.length} tool-name collision(s):`);\n for (const c of collisions) {\n console.log(` - tool name '${c.name}' is ambiguous — capabilities ${c.ids.join(\", \")} all sanitize to it`);\n }\n }\n\n const ok = shapesAndSemanticsOk && collisions.length === 0;\n\n if (ok && registry) {\n const invocable = registry.listCapabilities().filter((t) => t.connector).length;\n console.log(` registry IR v${registry.ir.version} — ${registry.size} capabilities, ${invocable} invocable (bound)`);\n console.log(`\\n → run 'archstone serve ${dir}' to expose ${invocable} tool(s) to an AI agent over MCP`);\n }\n\n console.log(\"\");\n process.exit(ok ? 0 : 1);\n}\n\nfunction runBuild(dir: string, outPath: string | undefined): void {\n const res = load(dir);\n const diags = validateSemantics(res);\n const errors = diags.filter((d) => d.severity === \"error\");\n const ok = res.ok && errors.length === 0;\n\n if (!ok) {\n console.error(`archstone build ${dir}: manifest invalid — run 'archstone apply ${dir}' for details`);\n for (const i of res.issues) console.error(` - ${i.file}: ${i.message}`);\n for (const d of errors) console.error(` - ${d.message}`);\n process.exit(1);\n }\n\n const ir = compile(res);\n\n // ADD-30 R-2: `runBuild` didn't construct a Registry at all, so it could ship a broken\n // artifact whose ambiguous tool name only surfaces later, inside a third party's\n // `fromIR()` call. Refuse to write on a collision — fail at `build` time instead\n // (the same \"ambiguous is a compile-time error, never a guess\" pattern this repo already\n // applies to resource-name resolution, compiler/src/resolve.ts).\n const registry = new Registry(ir);\n if (registry.toolNameCollisions.length > 0) {\n console.error(`archstone build ${dir}: refusing to write artifact — tool-name collision(s):`);\n for (const c of registry.toolNameCollisions) {\n console.error(` - tool name '${c.name}' is ambiguous — capabilities ${c.ids.join(\", \")} all sanitize to it`);\n }\n process.exit(1);\n }\n\n // D-8: the artifact ships with no code alongside it — the fingerprint + golden-fixture\n // path have no meaning without the fixture file / `archstone verify`, so strip `contract`\n // from every tool before writing.\n const stripped: IR = { ...ir, tools: ir.tools.map(({ contract: _contract, ...t }) => t) };\n\n const outFile = resolve(process.cwd(), outPath ?? \"archstone.ir.json\");\n writeFileSync(outFile, `${JSON.stringify(stripped, null, 2)}\\n`);\n console.log(`archstone build ${dir} → ${outFile} (${stripped.tools.length} tool(s))`);\n process.exit(0);\n}\n\nfunction runServeHttp(dir: string, port: number, token: string | undefined): void {\n // Rule #7 / ADD-0008 R-5: fail closed before touching the network — a missing token is a\n // startup error, never a silently-open endpoint. `--token` wins over the env var if both\n // are set; createHttpHandler itself would also throw on empty, but checking here first\n // gives a CLI-appropriate error message instead of an uncaught exception.\n if (!token) {\n console.error(\n \"archstone serve --http: bearer token required — set ARCHSTONE_HTTP_TOKEN or pass --token <value>\",\n );\n process.exit(1);\n }\n\n const built = buildRegistry(dir);\n if (!built.ok || !built.registry) {\n console.error(`archstone: cannot serve '${dir}' — manifest invalid:`);\n for (const i of built.issues) console.error(` - ${i.file}: ${i.message}`);\n for (const d of built.diagnostics.filter((x) => x.severity === \"error\")) console.error(` - ${d.message}`);\n process.exit(1);\n }\n\n const handler = createHttpHandler(built.registry, { bearerToken: token });\n const server = createServer((req, res) => {\n void handleHttpRequest(handler, req, res);\n });\n server.listen(port, () => {\n console.error(`archstone: serving MCP over HTTP on http://localhost:${port}/ (bearer-token gated)`);\n });\n}\n\n// D-3's \"~20-line wrapper\": Node's http.IncomingMessage/ServerResponse <-> Web-standard\n// Request/Response, so createHttpHandler (already Web-standard, shared with\n// @archstone/agent/mcp's mcpHandler()) can serve real Node HTTP traffic without a second\n// transport implementation. CLI-level plumbing only — HTTP itself still lives in\n// providers/rest for business-backend calls; this adapter never touches a backend.\nasync function handleHttpRequest(\n handler: (request: Request) => Promise<Response>,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n const chunks: Buffer[] = [];\n for await (const chunk of req) chunks.push(chunk as Buffer);\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value !== undefined) headers.set(key, Array.isArray(value) ? value.join(\", \") : value);\n }\n const hasBody = req.method !== \"GET\" && req.method !== \"HEAD\" && chunks.length > 0;\n const request = new Request(`http://${req.headers.host ?? \"localhost\"}${req.url ?? \"/\"}`, {\n method: req.method ?? \"GET\",\n headers,\n body: hasBody ? Buffer.concat(chunks) : undefined,\n });\n\n const response = await handler(request);\n res.statusCode = response.status;\n response.headers.forEach((value, key) => res.setHeader(key, value));\n res.end(response.body ? Buffer.from(await response.arrayBuffer()) : undefined);\n}\n\nconst HEALTH_ICON: Record<HealthStatus, string> = { green: \"🟢\", yellow: \"🟡\", red: \"🔴\" };\n\nasync function runVerifyCmd(dir: string, json: boolean): Promise<void> {\n const res = load(dir);\n const diags = validateSemantics(res);\n const errors = diags.filter((d) => d.severity === \"error\");\n const ok = res.ok && errors.length === 0;\n if (!ok) {\n if (json) {\n // ADD-20 D-2: this shape is strictly disjoint from the `{results}` shape below —\n // never add a shared \"envelope\" field (e.g. `ok`) to either.\n console.log(JSON.stringify({ error: \"manifest_invalid\", issues: res.issues, errors }));\n } else {\n console.error(`archstone verify ${dir}: manifest invalid — run 'archstone apply ${dir}' for details`);\n }\n process.exit(2);\n }\n\n const registry = new Registry(compile(res));\n const reports = await runVerify(registry.listCapabilities(), dir, registry.ir.resources);\n\n if (json) {\n // ADD-20 D-2: strictly disjoint from the `{error, issues, errors}` shape above.\n console.log(JSON.stringify({ results: reports }));\n process.exit(reports.some((r) => r.status === \"red\") ? 1 : 0);\n }\n\n console.log(`\\narchstone verify ${dir}\\n`);\n if (reports.length === 0) {\n console.log(\" (no bindings declare a contract: — nothing to verify)\\n\");\n process.exit(0);\n }\n for (const r of reports) {\n console.log(` ${HEALTH_ICON[r.status]} ${r.capabilityId} — ${r.detail}`);\n }\n console.log(\"\");\n process.exit(reports.some((r) => r.status === \"red\") ? 1 : 0);\n}\n\n/** Value of a `--name value` flag pair, plus the index it was found at (-1 if absent) —\n * used both to read the value and to exclude both tokens from the positional args. */\nfunction flagArg(argv: string[], name: string): { value?: string; idx: number } {\n const idx = argv.indexOf(name);\n return { value: idx !== -1 ? argv[idx + 1] : undefined, idx };\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const json = argv.includes(\"--json\");\n const http = argv.includes(\"--http\");\n const out = flagArg(argv, \"--out\");\n const port = flagArg(argv, \"--port\");\n const token = flagArg(argv, \"--token\");\n\n const consumed = new Set<number>();\n for (const f of [out, port, token]) {\n if (f.idx !== -1) {\n consumed.add(f.idx);\n consumed.add(f.idx + 1);\n }\n }\n const positional = argv.filter((a, i) => !consumed.has(i) && a !== \"--json\" && a !== \"--http\");\n const [cmd, dir] = positional;\n\n if (cmd === \"apply\" && dir) {\n runApply(dir);\n return;\n }\n if (cmd === \"serve\" && dir && http) {\n // Bearer token: --token wins over ARCHSTONE_HTTP_TOKEN if both are set (Rule #7 —\n // required, never defaults open).\n runServeHttp(dir, Number(port.value ?? 8787), token.value ?? process.env.ARCHSTONE_HTTP_TOKEN);\n return; // blocks on the HTTP server\n }\n if (cmd === \"serve\" && dir) {\n await serveStdio(dir); // blocks on the stdio transport\n return;\n }\n if (cmd === \"verify\" && dir) {\n await runVerifyCmd(dir, json);\n return;\n }\n if (cmd === \"build\" && dir) {\n runBuild(dir, out.value);\n return;\n }\n\n console.error(\n \"usage: archstone <apply|serve|verify|build> <manifest-dir> [--json] [--out path]\\n\" +\n \" archstone serve --http <manifest-dir> [--port <n>] [--token <value>]\\n\" +\n \" bearer token: --token <value>, or the ARCHSTONE_HTTP_TOKEN env var (required — never serves open)\",\n );\n process.exit(2);\n}\n\nmain();\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n// @archstone/cli — `archstone apply` (#1) + `archstone serve` (#7, + `--http` ADD-0008 #29)\n// + `archstone verify` (#18-20) + `archstone build` (ADD-0008 #27)\n//\n// apply: parse → shape-validate (#2) → semantic-validate (#3) → compile IR (#4)\n// → index Registry (#5), and REPORT (human output, exits).\n// serve: build the registry and expose it as an MCP server over stdio (#7),\n// so Claude/Cursor/ChatGPT can discover and invoke the tools. Blocks.\n// serve --http: same registry, served over real Streamable-HTTP instead of stdio —\n// `@archstone/runtime/http`'s createHttpHandler (Web-standard Request/Response,\n// bearer-token gated, shared with @archstone/agent/mcp's mcpHandler(), ADD-0008 D-3)\n// behind a thin Node-http adapter. Blocks.\n// verify: replay each bound capability's golden fixture against the LIVE backend\n// and report a per-binding health status (ADD-18). The only command that\n// makes a network call outside a real MCP invocation — on demand, never\n// scheduled by Archstone itself (wire it into your own CI/cron).\n// build: run the same compile pipeline as `apply`, strip each tool's `contract`\n// (D-8 — the fingerprint/golden-fixture path is meaningless once the fixture\n// file isn't shipping), and write the IR as a standalone JSON artifact —\n// the substrate `@archstone/agent`'s `fromIR()` will consume (RFC-0008).\n\nimport { writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { load } from \"@archstone/schema\";\nimport { validateSemantics, compile, type IR } from \"@archstone/compiler\";\nimport { Registry, buildRegistry, serveStdio, runVerify, type HealthStatus } from \"@archstone/runtime\";\nimport { createHttpHandler } from \"@archstone/runtime/http\";\n\nfunction runApply(dir: string): void {\n const res = load(dir);\n console.log(`\\narchstone apply ${dir}\\n`);\n\n if (res.capabilities) {\n const c = res.capabilities;\n console.log(` company ${c.company.name ?? c.company.id} (${c.company.id})`);\n console.log(` providers ${c.providers.join(\", \")}`);\n console.log(` declared ${c.capabilities.length} capabilities`);\n }\n console.log(` loaded ${res.capabilityDocs.length} capability docs, ${res.bindings.length} bindings`);\n for (const d of res.capabilityDocs) {\n console.log(` ✓ ${d.capability.id} [${d.capability.effect}] → ${d.capability.provider ?? \"?\"}`);\n }\n // #43: a policy the author believes is enforced must never be invisible here — the whole\n // point of the semantic pass's scope diagnostics is that \"attached to nothing\" is loud.\n if (res.policyDocs.length > 0) {\n console.log(` policies ${res.policyDocs.length} policy document(s)`);\n for (const p of res.policyDocs) {\n const target =\n p.metadata.scope === \"capability\"\n ? `capability ${p.metadata.capabilityId ?? \"?\"}`\n : p.metadata.scope === \"provider\"\n ? `provider ${p.metadata.provider ?? \"?\"}`\n : \"(no scope)\";\n console.log(` ✓ ${p.metadata.id} → ${target}`);\n }\n }\n\n // Shape (schema) issues from #2 — \"valid shapes\" is not \"deployable\".\n if (res.issues.length > 0) {\n console.log(`\\n ✗ ${res.issues.length} shape issue(s):`);\n for (const i of res.issues) console.log(` - ${i.file}: ${i.message}`);\n } else {\n console.log(`\\n ✓ shapes valid`);\n }\n\n // Semantic pass (#3) — cross-file resolution; errors block, warnings inform.\n const diags = validateSemantics(res);\n const errors = diags.filter((d) => d.severity === \"error\");\n const warnings = diags.filter((d) => d.severity === \"warning\");\n console.log(` semantic ${errors.length} error(s), ${warnings.length} warning(s)`);\n for (const d of errors) console.log(` ✗ ${d.message}`);\n for (const d of warnings) console.log(` ⚠ ${d.message}`);\n\n const shapesAndSemanticsOk = res.ok && errors.length === 0;\n\n // Compile to IR (#4) + index into the Registry (#5) — only when valid enough to emit.\n // ADD-30: a tool-name collision (two capability ids sanitizing to the same advertised\n // name) is checked here, before the final `ok`, alongside the semantic errors above —\n // 'apply' must refuse the same manifest 'build'/'serve' would refuse (D-2).\n const registry = shapesAndSemanticsOk ? new Registry(compile(res)) : undefined;\n const collisions = registry?.toolNameCollisions ?? [];\n if (collisions.length > 0) {\n console.log(`\\n ✗ ${collisions.length} tool-name collision(s):`);\n for (const c of collisions) {\n console.log(` - tool name '${c.name}' is ambiguous — capabilities ${c.ids.join(\", \")} all sanitize to it`);\n }\n }\n\n const ok = shapesAndSemanticsOk && collisions.length === 0;\n\n if (ok && registry) {\n const invocable = registry.listCapabilities().filter((t) => t.connector).length;\n console.log(` registry IR v${registry.ir.version} — ${registry.size} capabilities, ${invocable} invocable (bound)`);\n console.log(`\\n → run 'archstone serve ${dir}' to expose ${invocable} tool(s) to an AI agent over MCP`);\n }\n\n console.log(\"\");\n process.exit(ok ? 0 : 1);\n}\n\nfunction runBuild(dir: string, outPath: string | undefined): void {\n const res = load(dir);\n const diags = validateSemantics(res);\n const errors = diags.filter((d) => d.severity === \"error\");\n const ok = res.ok && errors.length === 0;\n\n if (!ok) {\n console.error(`archstone build ${dir}: manifest invalid — run 'archstone apply ${dir}' for details`);\n for (const i of res.issues) console.error(` - ${i.file}: ${i.message}`);\n for (const d of errors) console.error(` - ${d.message}`);\n process.exit(1);\n }\n\n const ir = compile(res);\n\n // ADD-30 R-2: `runBuild` didn't construct a Registry at all, so it could ship a broken\n // artifact whose ambiguous tool name only surfaces later, inside a third party's\n // `fromIR()` call. Refuse to write on a collision — fail at `build` time instead\n // (the same \"ambiguous is a compile-time error, never a guess\" pattern this repo already\n // applies to resource-name resolution, compiler/src/resolve.ts).\n const registry = new Registry(ir);\n if (registry.toolNameCollisions.length > 0) {\n console.error(`archstone build ${dir}: refusing to write artifact — tool-name collision(s):`);\n for (const c of registry.toolNameCollisions) {\n console.error(` - tool name '${c.name}' is ambiguous — capabilities ${c.ids.join(\", \")} all sanitize to it`);\n }\n process.exit(1);\n }\n\n // THE STRIP RULE, stated as a principle rather than a list (ADD-43 D-9), so the next field\n // added to `IRTool` is classified deliberately instead of by whichever example was copied:\n //\n // strip what the INVOCATION PATH cannot use.\n //\n // `contract` qualifies (ADD-0008 D-8): it is verify-time-only and carries an fs path that is\n // meaningless once the golden fixture is not shipping alongside the artifact.\n //\n // `policyRules` (#43) is the exact opposite and MUST survive: it is invocation-path data, read\n // by the evaluator on every `execute()` call. Stripping it would ship an unpoliced embedded\n // SDK beside a policed MCP surface — the precise cross-path drift #43 exists to prevent, and\n // silent, because `fromIR` validates only `version` and treats the rest as opaque.\n const stripped: IR = { ...ir, tools: ir.tools.map(({ contract: _contract, ...t }) => t) };\n\n const outFile = resolve(process.cwd(), outPath ?? \"archstone.ir.json\");\n writeFileSync(outFile, `${JSON.stringify(stripped, null, 2)}\\n`);\n console.log(`archstone build ${dir} → ${outFile} (${stripped.tools.length} tool(s))`);\n process.exit(0);\n}\n\nfunction runServeHttp(dir: string, port: number, token: string | undefined): void {\n // Rule #7 / ADD-0008 R-5: fail closed before touching the network — a missing token is a\n // startup error, never a silently-open endpoint. `--token` wins over the env var if both\n // are set; createHttpHandler itself would also throw on empty, but checking here first\n // gives a CLI-appropriate error message instead of an uncaught exception.\n if (!token) {\n console.error(\n \"archstone serve --http: bearer token required — set ARCHSTONE_HTTP_TOKEN or pass --token <value>\",\n );\n process.exit(1);\n }\n\n const built = buildRegistry(dir);\n if (!built.ok || !built.registry) {\n console.error(`archstone: cannot serve '${dir}' — manifest invalid:`);\n for (const i of built.issues) console.error(` - ${i.file}: ${i.message}`);\n for (const d of built.diagnostics.filter((x) => x.severity === \"error\")) console.error(` - ${d.message}`);\n process.exit(1);\n }\n\n const handler = createHttpHandler(built.registry, { bearerToken: token });\n const server = createServer((req, res) => {\n // #49 belt-and-braces: this used to be `void handleHttpRequest(...)`. Fire-and-forget\n // means nothing is attached to the returned promise, so ANY rejection escaping the\n // function became an unhandled rejection — fatal under Node's default\n // `--unhandled-rejections=throw`, killing the server on one aborted client connection.\n // handleHttpRequest now contains its own failures, but this `.catch` is the seam that\n // makes the fix independent of that catch staying exhaustive: a future throw added\n // outside its `try` cannot resurrect the process-death bug.\n handleHttpRequest(handler, req, res).catch((err: unknown) => {\n console.error(\"archstone serve --http: request handling failed —\", err);\n endResponseQuietly(res, 500);\n });\n });\n server.listen(port, () => {\n console.error(`archstone: serving MCP over HTTP on http://localhost:${port}/ (bearer-token gated)`);\n });\n}\n\n/**\n * Largest request body `archstone serve --http` will buffer, in bytes (#50).\n *\n * 4 MiB is not chosen by feel: it is the limit the MCP SDK itself applies to an MCP message\n * arriving over HTTP (`MAXIMUM_MESSAGE_SIZE = '4mb'` in the SDK's own Node SSE transport,\n * enforced via `raw-body`). Same protocol, same message class, same SDK version this package\n * already depends on — so the ceiling matches what an MCP client can reasonably expect to send\n * anywhere else in the ecosystem, rather than inventing an Archstone-specific number. The\n * Web-standard transport used here never reads the socket itself (this adapter hands it an\n * already-built `Request`), which is precisely why the SDK's limit does not apply on this path\n * and has to be reapplied here.\n *\n * For scale: an MCP `tools/call` body carries a capability's declared inputs as JSON. 4 MiB is\n * orders of magnitude above any manifest in `examples/`.\n */\nconst MAX_REQUEST_BODY_BYTES = 4 * 1024 * 1024;\n\n/**\n * Terminate a response without ever throwing (#49). Every exit path out of the adapter goes\n * through here, including the ones reached after the client is already gone: on an aborted\n * connection the socket is destroyed, and a naive `res.end()` there is at best pointless and\n * at worst a second error thrown out of an error path. Ending is still attempted whenever the\n * socket survives — a truncated body on a keep-alive connection has a live socket that would\n * otherwise hang until the client's own timeout.\n */\nfunction endResponseQuietly(\n res: ServerResponse,\n status: number,\n opts: { closeConnection?: boolean } = {},\n): void {\n try {\n if (res.writableEnded || res.destroyed) return;\n if (!res.headersSent) {\n res.statusCode = status;\n // #50: on a refused oversized body the connection must not be reused. The client is\n // mid-upload and the rest of its bytes are still in flight, so a keep-alive socket\n // would leave that remainder to be misparsed as the next request. `Connection: close`\n // lets Node flush the response first and then close — destroying the socket here\n // instead would race the 413 and the client would see nothing.\n if (opts.closeConnection) res.setHeader(\"connection\", \"close\");\n }\n res.end();\n } catch {\n // The socket went away between the checks above and the write. Nothing is left to\n // terminate and there is no one to tell — swallowing here is the whole point.\n }\n}\n\n// D-3's \"~20-line wrapper\": Node's http.IncomingMessage/ServerResponse <-> Web-standard\n// Request/Response, so createHttpHandler (already Web-standard, shared with\n// @archstone/agent/mcp's mcpHandler()) can serve real Node HTTP traffic without a second\n// transport implementation. CLI-level plumbing only — HTTP itself still lives in\n// providers/rest for business-backend calls; this adapter never touches a backend.\n//\n// #49 (P0, unauthenticated remote DoS): this function must never reject and must always\n// reach a terminal `res.end()`. It is invoked from a Node `request` listener, where an\n// escaping rejection is an unhandled rejection and therefore a fatal uncaught exception —\n// one client that declares a Content-Length and disconnects mid-body used to kill the\n// process, before any handler and therefore before any credential check ran.\nasync function handleHttpRequest(\n handler: (request: Request) => Promise<Response>,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n // #50: the body is buffered BEFORE authentication (the bearer check lives inside\n // createHttpHandler, reached only once the Request is built), so an unauthenticated client\n // controls how much memory this allocates. Measured server-side: the body is held ~4x over\n // simultaneously — the chunk array, `Buffer.concat`'s copy, and undici's own copies inside\n // `new Request` — so a 256 MiB body peaked at 1,081 MiB RSS, essentially all of it in\n // `external`/`arrayBuffers`. Being external is what makes it nasty: `--max-old-space-size`\n // does not bound it, and the terminal symptom is an uncatchable OOM abort.\n //\n // A declared Content-Length over the cap is refused before a single byte is read; the\n // running total is then enforced during streaming as well, because Content-Length can lie\n // and chunked encoding omits it entirely. Like every other client fault in this adapter the\n // 413 is NOT logged — an unauthenticated caller must not be able to drive log volume (#49\n // BF-1).\n const declared = Number(req.headers[\"content-length\"]);\n if (Number.isFinite(declared) && declared > MAX_REQUEST_BODY_BYTES) {\n endResponseQuietly(res, 413, { closeConnection: true });\n return;\n }\n\n const chunks: Buffer[] = [];\n try {\n let received = 0;\n for await (const chunk of req) {\n const buf = chunk as Buffer;\n received += buf.length;\n if (received > MAX_REQUEST_BODY_BYTES) {\n // Returning from inside `for await` calls the iterator's `return()`, which tears the\n // request stream down — so the remaining bytes are never buffered, and the client is\n // not left streaming into a socket nobody drains.\n endResponseQuietly(res, 413, { closeConnection: true });\n return;\n }\n chunks.push(buf);\n }\n } catch {\n // The client went away mid-body (ECONNRESET / aborted), or delivered fewer bytes than\n // its declared Content-Length. On a public endpoint this is routine traffic — a closed\n // laptop, a cancelled fetch, a load-balancer health probe — NOT a server fault, so it is\n // deliberately not logged: turning an aborted-request flood into a log flood just trades\n // one denial of service for another.\n //\n // 400 is the deliberate status, not 500: the request was never completed, and nothing on\n // the server failed. In practice nobody reads it — this catch is reached only once the\n // socket is already dead. (Node does NOT surface a short body while the connection is\n // still open: it waits for the declared bytes until `server.requestTimeout`, 300 s by\n // default, and answers that itself.) The end is still attempted rather than skipped\n // because this code cannot tell from here whether `res` is writable — `req` erroring\n // does not by itself prove the response side is gone — and `endResponseQuietly` makes\n // the attempt free when it is.\n endResponseQuietly(res, 400);\n return;\n }\n\n // Translating the raw request into a Web `Request` is still CLIENT input handling, and it\n // runs BEFORE authentication (the bearer check lives inside createHttpHandler, reached only\n // at `handler(request)` below). `req.headers.host` and `req.url` are attacker-controlled and\n // a malformed value throws here — a bad `Host` was in fact a second unauthenticated kill\n // vector before #49's containment landed. So this gets its own client-fault arm, on exactly\n // the argument the body-read catch above makes: answering 500 and logging a stack trace per\n // request would hand an unauthenticated caller ~13x log amplification and trade the crash\n // for a disk-fill DoS. RFC 9112 §3.2 also makes 400 the required answer to an invalid Host.\n //\n // Classification is positional, not by error sniffing: what failed decides the class, so it\n // cannot drift when undici changes an error's shape between Node versions.\n let request: Request;\n try {\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value !== undefined) headers.set(key, Array.isArray(value) ? value.join(\", \") : value);\n }\n const hasBody = req.method !== \"GET\" && req.method !== \"HEAD\" && chunks.length > 0;\n request = new Request(`http://${req.headers.host ?? \"localhost\"}${req.url ?? \"/\"}`, {\n method: req.method ?? \"GET\",\n headers,\n body: hasBody ? Buffer.concat(chunks) : undefined,\n });\n } catch {\n endResponseQuietly(res, 400);\n return;\n }\n\n try {\n const response = await handler(request);\n res.statusCode = response.status;\n response.headers.forEach((value, key) => res.setHeader(key, value));\n res.end(response.body ? Buffer.from(await response.arrayBuffer()) : undefined);\n } catch (err) {\n // A genuine server-side failure: the handler rejected, or serialising its Response threw.\n // Unlike a malformed or abandoned request this IS worth surfacing, so it is logged — and\n // answered with a 500 rather than left to hang the caller. Nothing attacker-controlled\n // reaches this arm without first passing through the handler, so it cannot be used as a\n // log-amplification primitive the way the pre-auth construction path above could.\n console.error(\"archstone serve --http: request handling failed —\", err);\n endResponseQuietly(res, 500);\n }\n}\n\nconst HEALTH_ICON: Record<HealthStatus, string> = { green: \"🟢\", yellow: \"🟡\", red: \"🔴\" };\n\nasync function runVerifyCmd(dir: string, json: boolean): Promise<void> {\n const res = load(dir);\n const diags = validateSemantics(res);\n const errors = diags.filter((d) => d.severity === \"error\");\n const ok = res.ok && errors.length === 0;\n if (!ok) {\n if (json) {\n // ADD-20 D-2: this shape is strictly disjoint from the `{results}` shape below —\n // never add a shared \"envelope\" field (e.g. `ok`) to either.\n console.log(JSON.stringify({ error: \"manifest_invalid\", issues: res.issues, errors }));\n } else {\n console.error(`archstone verify ${dir}: manifest invalid — run 'archstone apply ${dir}' for details`);\n }\n process.exit(2);\n }\n\n const registry = new Registry(compile(res));\n const reports = await runVerify(registry.listCapabilities(), dir, registry.ir.resources);\n\n if (json) {\n // ADD-20 D-2: strictly disjoint from the `{error, issues, errors}` shape above.\n console.log(JSON.stringify({ results: reports }));\n process.exit(reports.some((r) => r.status === \"red\") ? 1 : 0);\n }\n\n console.log(`\\narchstone verify ${dir}\\n`);\n if (reports.length === 0) {\n console.log(\" (no bindings declare a contract: — nothing to verify)\\n\");\n process.exit(0);\n }\n for (const r of reports) {\n console.log(` ${HEALTH_ICON[r.status]} ${r.capabilityId} — ${r.detail}`);\n }\n console.log(\"\");\n process.exit(reports.some((r) => r.status === \"red\") ? 1 : 0);\n}\n\n/** Value of a `--name value` flag pair, plus the index it was found at (-1 if absent) —\n * used both to read the value and to exclude both tokens from the positional args. */\nfunction flagArg(argv: string[], name: string): { value?: string; idx: number } {\n const idx = argv.indexOf(name);\n return { value: idx !== -1 ? argv[idx + 1] : undefined, idx };\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const json = argv.includes(\"--json\");\n const http = argv.includes(\"--http\");\n const out = flagArg(argv, \"--out\");\n const port = flagArg(argv, \"--port\");\n const token = flagArg(argv, \"--token\");\n\n const consumed = new Set<number>();\n for (const f of [out, port, token]) {\n if (f.idx !== -1) {\n consumed.add(f.idx);\n consumed.add(f.idx + 1);\n }\n }\n const positional = argv.filter((a, i) => !consumed.has(i) && a !== \"--json\" && a !== \"--http\");\n const [cmd, dir] = positional;\n\n if (cmd === \"apply\" && dir) {\n runApply(dir);\n return;\n }\n if (cmd === \"serve\" && dir && http) {\n // Bearer token: --token wins over ARCHSTONE_HTTP_TOKEN if both are set (Rule #7 —\n // required, never defaults open).\n runServeHttp(dir, Number(port.value ?? 8787), token.value ?? process.env.ARCHSTONE_HTTP_TOKEN);\n return; // blocks on the HTTP server\n }\n if (cmd === \"serve\" && dir) {\n await serveStdio(dir); // blocks on the stdio transport\n return;\n }\n if (cmd === \"verify\" && dir) {\n await runVerifyCmd(dir, json);\n return;\n }\n if (cmd === \"build\" && dir) {\n runBuild(dir, out.value);\n return;\n }\n\n console.error(\n \"usage: archstone <apply|serve|verify|build> <manifest-dir> [--json] [--out path]\\n\" +\n \" archstone serve --http <manifest-dir> [--port <n>] [--token <value>]\\n\" +\n \" bearer token: --token <value>, or the ARCHSTONE_HTTP_TOKEN env var (required — never serves open)\",\n );\n process.exit(2);\n}\n\nmain();\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archstone/cli",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "CLI (#1): archstone apply — wires the pipeline (parse -> validate -> IR -> runtime).",
@@ -22,9 +22,9 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@archstone/compiler": "0.5.2",
26
- "@archstone/schema": "0.5.2",
27
- "@archstone/runtime": "0.5.2"
25
+ "@archstone/compiler": "0.7.0",
26
+ "@archstone/runtime": "0.7.0",
27
+ "@archstone/schema": "0.7.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "tsup": "^8.5.1"