diff --git a/.claude/PROJECT.md b/.claude/PROJECT.md new file mode 100644 index 00000000..af9a4290 --- /dev/null +++ b/.claude/PROJECT.md @@ -0,0 +1,33 @@ +# BOJ Server - Claude Code Instructions + +This repository contains the BOJ (Battle of the Judges) server application. + +## Project Structure + +``` +boj-server/ +├── .claude/ # AI assistant instructions +├── .git/ # Version control +├── .gitignore # Git ignore rules +├── .editorconfig # Editor configuration +└── ... # Server files +``` + +## Build Commands + +Refer to project-specific documentation. + +## Coding Conventions + +- Follow hyperpolymath standards +- All code must have SPDX license headers +- Use approved languages only (see CLAUDE.md) +- Document all non-obvious decisions + +## Security + +- No hardcoded secrets +- All secrets through environment variables or secret management +- SHA-pinned dependencies where applicable +- HTTPS only, no HTTP URLs +- No MD5/SHA1 for security purposes diff --git a/cartridges/typed-wasm-mcp/README.adoc b/cartridges/typed-wasm-mcp/README.adoc index 297ed7f4..05df7e03 100644 --- a/cartridges/typed-wasm-mcp/README.adoc +++ b/cartridges/typed-wasm-mcp/README.adoc @@ -9,19 +9,32 @@ Jonathan D.A. Jewell == Overview -Typed WASM module verifier and compiler. Validates WebAssembly type safety and compiles with type-level guarantees. +AffineScript typed-wasm gateway. Compiles `.affine` sources to Wasm with the +typed-wasm Level 7/10 ownership contract, runs the intra-module verifier +(`Tw_verify`), and runs the cross-module Level 10 boundary verifier +(`Tw_interface`) between two compiled modules. -== Tools (3) +Inputs are always `.affine` source paths — there is no path that ingests +arbitrary `.wasm`, because the verifier is meaningful only against a Wasm +module that the AffineScript compiler emitted with an +`affinescript.ownership` custom section. + +== Tools (4) [cols="2,4"] |=== | Tool | Description -| `typed_wasm_validate_module` | Validate a WASM module file for structural correctness and type safety. -| `typed_wasm_check_types` | Run the typed-WASM type checker on a module. -| `typed_wasm_compile_module` | Compile a source file to WASM with typed-WASM guarantees. +| `typed_wasm_validate_module` | Compile a `.affine` source and run the intra-module Level 7/10 verifier against the emitted Wasm. Returns `valid=true` when every `[own]` param is consumed exactly once on every path and every `[mut]` param is exclusively held. +| `typed_wasm_check_types` | Run the AffineScript type checker on a `.affine` source. Reports type errors only — no Wasm emission, no ownership verification. +| `typed_wasm_compile_module` | Compile a `.affine` source to `.wasm`. The emitted module carries the `[affinescript.ownership]` custom section that downstream verifiers consume. Optional `output_path`; defaults to the source path with `.wasm` extension. +| `typed_wasm_verify_boundary` | Cross-module Level 10 boundary check. Compiles a callee/caller pair and verifies that the caller's local functions invoke each Linear-param import exactly once per execution path. |=== +The gateway shells out to the `affinescript` binary on `PATH` (override via +the `AFFINESCRIPT_BIN` env var). Boundary verification depends on the +cross-module import emission landed in affinescript on 2026-05-03. + == Architecture Three-layer stack per the BoJ cartridge standard: diff --git a/cartridges/typed-wasm-mcp/cartridge.json b/cartridges/typed-wasm-mcp/cartridge.json index bef26ec2..f4ff6936 100644 --- a/cartridges/typed-wasm-mcp/cartridge.json +++ b/cartridges/typed-wasm-mcp/cartridge.json @@ -3,8 +3,8 @@ "spdx": "PMPL-1.0-or-later", "copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)", "name": "typed-wasm-mcp", - "version": "0.1.0", - "description": "Typed WASM module verifier and compiler. Validates WebAssembly type safety and compiles with type-level guarantees.", + "version": "0.2.0", + "description": "AffineScript typed-wasm gateway. Compiles .affine source to Wasm with the typed-wasm Level 7/10 ownership contract, runs the intra-module verifier, and runs the cross-module boundary verifier between two compiled modules.", "domain": "Compiler", "tier": "Ayo", "protocols": [ @@ -23,13 +23,13 @@ "tools": [ { "name": "typed_wasm_validate_module", - "description": "Validate a WASM module file for structural correctness and type safety.", + "description": "Compile a .affine source and run the intra-module typed-wasm Level 7/10 verifier (Tw_verify) against the emitted Wasm. Returns valid=true when every [own] param is consumed exactly once on every path and every [mut] param is exclusively held; otherwise valid=false with a per-violation report.", "inputSchema": { "type": "object", "properties": { "module_path": { "type": "string", - "description": "Absolute path to the .wasm file" + "description": "Path to the .affine source file" } }, "required": [ @@ -39,13 +39,13 @@ }, { "name": "typed_wasm_check_types", - "description": "Run the typed-WASM type checker on a module.", + "description": "Run the AffineScript type checker on a .affine source. Reports type errors without emitting Wasm or running the ownership verifier.", "inputSchema": { "type": "object", "properties": { "module_path": { "type": "string", - "description": "Absolute path to the .wasm or source file" + "description": "Path to the .affine source file" } }, "required": [ @@ -55,23 +55,44 @@ }, { "name": "typed_wasm_compile_module", - "description": "Compile a source file to WASM with typed-WASM guarantees.", + "description": "Compile a .affine source to Wasm. The emitted module carries the [affinescript.ownership] custom section that downstream typed-wasm verifiers consume.", "inputSchema": { "type": "object", "properties": { "module_path": { "type": "string", - "description": "Absolute path to the source file" + "description": "Path to the .affine source file" }, - "target": { + "output_path": { "type": "string", - "description": "Compilation target: wasm32 (default) or wasm64" + "description": "Optional path for the .wasm output. Defaults to ." } }, "required": [ "module_path" ] } + }, + { + "name": "typed_wasm_verify_boundary", + "description": "Run the cross-module typed-wasm Level 10 boundary verifier on a callee/caller pair. Compiles both .affine sources, extracts the callee's ownership-annotated export interface, and checks that every local function in the caller invokes each Linear-param import exactly once per execution path. Returns clean=true on success; otherwise clean=false with the boundary violations.", + "inputSchema": { + "type": "object", + "properties": { + "callee_path": { + "type": "string", + "description": "Path to the .affine source for the exporter (whose pub fns define the contract)" + }, + "caller_path": { + "type": "string", + "description": "Path to the .affine source for the importer (whose function bodies are checked against the callee's contract)" + } + }, + "required": [ + "callee_path", + "caller_path" + ] + } } ], "ffi": { diff --git a/cartridges/typed-wasm-mcp/mod.js b/cartridges/typed-wasm-mcp/mod.js index ed2edc85..dfce3f19 100644 --- a/cartridges/typed-wasm-mcp/mod.js +++ b/cartridges/typed-wasm-mcp/mod.js @@ -1,39 +1,146 @@ // SPDX-License-Identifier: PMPL-1.0-or-later // Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) // -// typed-wasm-mcp/mod.js -- typed-wasm-mcp gateway. Delegates to the `typed-wasm` CLI binary. +// typed-wasm-mcp/mod.js — MCP gateway. Delegates to the `affinescript` CLI, +// which since 2026-05-03 carries the full typed-wasm Level 7/10 verifier +// (intra-module ownership) and the Level 10 cross-module boundary verifier. +// +// Tools: +// typed_wasm_validate_module → affinescript verify FILE.affine +// typed_wasm_check_types → affinescript check FILE.affine +// typed_wasm_compile_module → affinescript compile FILE.affine -o OUT.wasm +// +// All three accept .affine source paths. The verifier runs over the freshly +// emitted Wasm so there is no separate .wasm-input path: shipping unchecked +// .wasm and asking us to validate it would defeat the typed-wasm contract. +// +// Pass an explicit `output_path` to typed_wasm_compile_module; otherwise the +// .wasm is written next to the source. + +const AFFINESCRIPT_BIN = Deno.env.get("AFFINESCRIPT_BIN") ?? "affinescript"; + +function dirOf(path) { + const i = path.lastIndexOf("/"); + return i > 0 ? path.slice(0, i) : "."; +} + +// `cwd` is set to the source's directory because affinescript's Module_loader +// resolves `use Foo.Bar` against the current working directory, not the source +// file's location. +async function runAffinescript(args, cwd) { + try { + const cmd = new Deno.Command(AFFINESCRIPT_BIN, { + args, + cwd, + stdout: "piped", + stderr: "piped", + }); + const out = await cmd.output(); + const stdout = new TextDecoder().decode(out.stdout); + const stderr = new TextDecoder().decode(out.stderr); + return { code: out.code, stdout, stderr }; + } catch (e) { + return { code: -1, stdout: "", stderr: `failed to spawn ${AFFINESCRIPT_BIN}: ${e.message}` }; + } +} + +function requireString(args, key) { + const v = args?.[key]; + if (typeof v !== "string" || v.length === 0) { + return { error: `${key} (string) is required` }; + } + return { value: v }; +} + +function deriveWasmPath(srcPath) { + return srcPath.endsWith(".affine") + ? srcPath.slice(0, -".affine".length) + ".wasm" + : srcPath + ".wasm"; +} export async function handleTool(toolName, args) { switch (toolName) { case "typed_wasm_validate_module": { - const { module_path } = args ?? {}; - if (!module_path) return { status: 400, data: { error: "module_path is required" } }; - const cmd = new Deno.Command("typed-wasm", { args: ["validate-module", String(module_path)], stdout: "piped", stderr: "piped" }); - const out = await cmd.output(); - if (!out.success) return { status: 500, data: { success: false, error: new TextDecoder().decode(out.stderr) } }; - const stdout = new TextDecoder().decode(out.stdout); - try { return { status: 200, data: JSON.parse(stdout) }; } catch { return { status: 200, data: { success: true, output: stdout } }; } + // Compiles + runs the intra-module Level 7/10 verifier (Tw_verify). + // "Validation" = the emitted Wasm satisfies the typed-wasm ownership + // contract declared in the source. + const r = requireString(args, "module_path"); + if (r.error) return { status: 400, data: { error: r.error } }; + const out = await runAffinescript(["verify", r.value], dirOf(r.value)); + const valid = out.code === 0; + return { + status: valid ? 200 : 200, + data: { + valid, + report: out.stdout.trim(), + diagnostics: out.stderr.trim() || undefined, + }, + }; } case "typed_wasm_check_types": { - const { module_path } = args ?? {}; - if (!module_path) return { status: 400, data: { error: "module_path is required" } }; - const cmd = new Deno.Command("typed-wasm", { args: ["check-types", String(module_path)], stdout: "piped", stderr: "piped" }); - const out = await cmd.output(); - if (!out.success) return { status: 500, data: { success: false, error: new TextDecoder().decode(out.stderr) } }; - const stdout = new TextDecoder().decode(out.stdout); - try { return { status: 200, data: JSON.parse(stdout) }; } catch { return { status: 200, data: { success: true, output: stdout } }; } + // Runs only the type checker (no Wasm emission). + const r = requireString(args, "module_path"); + if (r.error) return { status: 400, data: { error: r.error } }; + const out = await runAffinescript(["check", r.value], dirOf(r.value)); + const ok = out.code === 0; + return { + status: 200, + data: { + ok, + report: out.stdout.trim(), + errors: ok ? [] : (out.stderr.trim() ? [out.stderr.trim()] : []), + }, + }; } case "typed_wasm_compile_module": { - const { module_path, target } = args ?? {}; - if (!module_path) return { status: 400, data: { error: "module_path is required" } }; - const cmd = new Deno.Command("typed-wasm", { args: ["compile-module", String(module_path)], stdout: "piped", stderr: "piped" }); - const out = await cmd.output(); - if (!out.success) return { status: 500, data: { success: false, error: new TextDecoder().decode(out.stderr) } }; - const stdout = new TextDecoder().decode(out.stdout); - try { return { status: 200, data: JSON.parse(stdout) }; } catch { return { status: 200, data: { success: true, output: stdout } }; } + // Compiles .affine → .wasm. The emitted module carries the + // [affinescript.ownership] custom section consumed by typed-wasm + // intra- and cross-module verifiers. + const r = requireString(args, "module_path"); + if (r.error) return { status: 400, data: { error: r.error } }; + const outputPath = (typeof args?.output_path === "string" && args.output_path) + ? args.output_path + : deriveWasmPath(r.value); + const out = await runAffinescript(["compile", r.value, "-o", outputPath], dirOf(r.value)); + const success = out.code === 0; + return { + status: success ? 200 : 200, + data: { + success, + output_path: success ? outputPath : undefined, + report: out.stdout.trim(), + diagnostics: out.stderr.trim() || undefined, + }, + }; + } + + case "typed_wasm_verify_boundary": { + // Cross-module Level 10 boundary check. Compiles both files, then + // verifies that the caller's local funcs respect the ownership + // contract declared by the callee's Wasm exports. + const callee = requireString(args, "callee_path"); + if (callee.error) return { status: 400, data: { error: callee.error } }; + const caller = requireString(args, "caller_path"); + if (caller.error) return { status: 400, data: { error: caller.error } }; + // Verify-boundary needs to find both files' imports — run from the + // caller's directory (most likely the place where Callee lives too). + const out = await runAffinescript( + ["verify-boundary", callee.value, caller.value], + dirOf(caller.value), + ); + const clean = out.code === 0; + return { + status: 200, + data: { + clean, + report: out.stdout.trim(), + diagnostics: out.stderr.trim() || undefined, + }, + }; } + default: return { status: 404, data: { error: `Unknown tool: ${toolName}` } }; }