Skip to content

Commit 29966ca

Browse files
hyperpolymathclaude
andcommitted
feat(typed-wasm-mcp): expand cartridge (mod.js, manifest, README)
WIP snapshot: cartridge.json manifest, mod.js handler logic, README.adoc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9a47266 commit 29966ca

4 files changed

Lines changed: 211 additions & 37 deletions

File tree

.claude/PROJECT.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# BOJ Server - Claude Code Instructions
2+
3+
This repository contains the BOJ (Battle of the Judges) server application.
4+
5+
## Project Structure
6+
7+
```
8+
boj-server/
9+
├── .claude/ # AI assistant instructions
10+
├── .git/ # Version control
11+
├── .gitignore # Git ignore rules
12+
├── .editorconfig # Editor configuration
13+
└── ... # Server files
14+
```
15+
16+
## Build Commands
17+
18+
Refer to project-specific documentation.
19+
20+
## Coding Conventions
21+
22+
- Follow hyperpolymath standards
23+
- All code must have SPDX license headers
24+
- Use approved languages only (see CLAUDE.md)
25+
- Document all non-obvious decisions
26+
27+
## Security
28+
29+
- No hardcoded secrets
30+
- All secrets through environment variables or secret management
31+
- SHA-pinned dependencies where applicable
32+
- HTTPS only, no HTTP URLs
33+
- No MD5/SHA1 for security purposes

cartridges/typed-wasm-mcp/README.adoc

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,32 @@ Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
99

1010
== Overview
1111

12-
Typed WASM module verifier and compiler. Validates WebAssembly type safety and compiles with type-level guarantees.
12+
AffineScript typed-wasm gateway. Compiles `.affine` sources to Wasm with the
13+
typed-wasm Level 7/10 ownership contract, runs the intra-module verifier
14+
(`Tw_verify`), and runs the cross-module Level 10 boundary verifier
15+
(`Tw_interface`) between two compiled modules.
1316

14-
== Tools (3)
17+
Inputs are always `.affine` source paths — there is no path that ingests
18+
arbitrary `.wasm`, because the verifier is meaningful only against a Wasm
19+
module that the AffineScript compiler emitted with an
20+
`affinescript.ownership` custom section.
21+
22+
== Tools (4)
1523

1624
[cols="2,4"]
1725
|===
1826
| Tool | Description
1927

20-
| `typed_wasm_validate_module` | Validate a WASM module file for structural correctness and type safety.
21-
| `typed_wasm_check_types` | Run the typed-WASM type checker on a module.
22-
| `typed_wasm_compile_module` | Compile a source file to WASM with typed-WASM guarantees.
28+
| `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.
29+
| `typed_wasm_check_types` | Run the AffineScript type checker on a `.affine` source. Reports type errors only — no Wasm emission, no ownership verification.
30+
| `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.
31+
| `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.
2332
|===
2433

34+
The gateway shells out to the `affinescript` binary on `PATH` (override via
35+
the `AFFINESCRIPT_BIN` env var). Boundary verification depends on the
36+
cross-module import emission landed in affinescript on 2026-05-03.
37+
2538
== Architecture
2639

2740
Three-layer stack per the BoJ cartridge standard:

cartridges/typed-wasm-mcp/cartridge.json

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"spdx": "PMPL-1.0-or-later",
44
"copyright": "Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath)",
55
"name": "typed-wasm-mcp",
6-
"version": "0.1.0",
7-
"description": "Typed WASM module verifier and compiler. Validates WebAssembly type safety and compiles with type-level guarantees.",
6+
"version": "0.2.0",
7+
"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.",
88
"domain": "Compiler",
99
"tier": "Ayo",
1010
"protocols": [
@@ -23,13 +23,13 @@
2323
"tools": [
2424
{
2525
"name": "typed_wasm_validate_module",
26-
"description": "Validate a WASM module file for structural correctness and type safety.",
26+
"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.",
2727
"inputSchema": {
2828
"type": "object",
2929
"properties": {
3030
"module_path": {
3131
"type": "string",
32-
"description": "Absolute path to the .wasm file"
32+
"description": "Path to the .affine source file"
3333
}
3434
},
3535
"required": [
@@ -39,13 +39,13 @@
3939
},
4040
{
4141
"name": "typed_wasm_check_types",
42-
"description": "Run the typed-WASM type checker on a module.",
42+
"description": "Run the AffineScript type checker on a .affine source. Reports type errors without emitting Wasm or running the ownership verifier.",
4343
"inputSchema": {
4444
"type": "object",
4545
"properties": {
4646
"module_path": {
4747
"type": "string",
48-
"description": "Absolute path to the .wasm or source file"
48+
"description": "Path to the .affine source file"
4949
}
5050
},
5151
"required": [
@@ -55,23 +55,44 @@
5555
},
5656
{
5757
"name": "typed_wasm_compile_module",
58-
"description": "Compile a source file to WASM with typed-WASM guarantees.",
58+
"description": "Compile a .affine source to Wasm. The emitted module carries the [affinescript.ownership] custom section that downstream typed-wasm verifiers consume.",
5959
"inputSchema": {
6060
"type": "object",
6161
"properties": {
6262
"module_path": {
6363
"type": "string",
64-
"description": "Absolute path to the source file"
64+
"description": "Path to the .affine source file"
6565
},
66-
"target": {
66+
"output_path": {
6767
"type": "string",
68-
"description": "Compilation target: wasm32 (default) or wasm64"
68+
"description": "Optional path for the .wasm output. Defaults to <module_path with .wasm extension>."
6969
}
7070
},
7171
"required": [
7272
"module_path"
7373
]
7474
}
75+
},
76+
{
77+
"name": "typed_wasm_verify_boundary",
78+
"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.",
79+
"inputSchema": {
80+
"type": "object",
81+
"properties": {
82+
"callee_path": {
83+
"type": "string",
84+
"description": "Path to the .affine source for the exporter (whose pub fns define the contract)"
85+
},
86+
"caller_path": {
87+
"type": "string",
88+
"description": "Path to the .affine source for the importer (whose function bodies are checked against the callee's contract)"
89+
}
90+
},
91+
"required": [
92+
"callee_path",
93+
"caller_path"
94+
]
95+
}
7596
}
7697
],
7798
"ffi": {

cartridges/typed-wasm-mcp/mod.js

Lines changed: 129 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,146 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
22
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33
//
4-
// typed-wasm-mcp/mod.js -- typed-wasm-mcp gateway. Delegates to the `typed-wasm` CLI binary.
4+
// typed-wasm-mcp/mod.js — MCP gateway. Delegates to the `affinescript` CLI,
5+
// which since 2026-05-03 carries the full typed-wasm Level 7/10 verifier
6+
// (intra-module ownership) and the Level 10 cross-module boundary verifier.
7+
//
8+
// Tools:
9+
// typed_wasm_validate_module → affinescript verify FILE.affine
10+
// typed_wasm_check_types → affinescript check FILE.affine
11+
// typed_wasm_compile_module → affinescript compile FILE.affine -o OUT.wasm
12+
//
13+
// All three accept .affine source paths. The verifier runs over the freshly
14+
// emitted Wasm so there is no separate .wasm-input path: shipping unchecked
15+
// .wasm and asking us to validate it would defeat the typed-wasm contract.
16+
//
17+
// Pass an explicit `output_path` to typed_wasm_compile_module; otherwise the
18+
// .wasm is written next to the source.
19+
20+
const AFFINESCRIPT_BIN = Deno.env.get("AFFINESCRIPT_BIN") ?? "affinescript";
21+
22+
function dirOf(path) {
23+
const i = path.lastIndexOf("/");
24+
return i > 0 ? path.slice(0, i) : ".";
25+
}
26+
27+
// `cwd` is set to the source's directory because affinescript's Module_loader
28+
// resolves `use Foo.Bar` against the current working directory, not the source
29+
// file's location.
30+
async function runAffinescript(args, cwd) {
31+
try {
32+
const cmd = new Deno.Command(AFFINESCRIPT_BIN, {
33+
args,
34+
cwd,
35+
stdout: "piped",
36+
stderr: "piped",
37+
});
38+
const out = await cmd.output();
39+
const stdout = new TextDecoder().decode(out.stdout);
40+
const stderr = new TextDecoder().decode(out.stderr);
41+
return { code: out.code, stdout, stderr };
42+
} catch (e) {
43+
return { code: -1, stdout: "", stderr: `failed to spawn ${AFFINESCRIPT_BIN}: ${e.message}` };
44+
}
45+
}
46+
47+
function requireString(args, key) {
48+
const v = args?.[key];
49+
if (typeof v !== "string" || v.length === 0) {
50+
return { error: `${key} (string) is required` };
51+
}
52+
return { value: v };
53+
}
54+
55+
function deriveWasmPath(srcPath) {
56+
return srcPath.endsWith(".affine")
57+
? srcPath.slice(0, -".affine".length) + ".wasm"
58+
: srcPath + ".wasm";
59+
}
560

661
export async function handleTool(toolName, args) {
762
switch (toolName) {
863
case "typed_wasm_validate_module": {
9-
const { module_path } = args ?? {};
10-
if (!module_path) return { status: 400, data: { error: "module_path is required" } };
11-
const cmd = new Deno.Command("typed-wasm", { args: ["validate-module", String(module_path)], stdout: "piped", stderr: "piped" });
12-
const out = await cmd.output();
13-
if (!out.success) return { status: 500, data: { success: false, error: new TextDecoder().decode(out.stderr) } };
14-
const stdout = new TextDecoder().decode(out.stdout);
15-
try { return { status: 200, data: JSON.parse(stdout) }; } catch { return { status: 200, data: { success: true, output: stdout } }; }
64+
// Compiles + runs the intra-module Level 7/10 verifier (Tw_verify).
65+
// "Validation" = the emitted Wasm satisfies the typed-wasm ownership
66+
// contract declared in the source.
67+
const r = requireString(args, "module_path");
68+
if (r.error) return { status: 400, data: { error: r.error } };
69+
const out = await runAffinescript(["verify", r.value], dirOf(r.value));
70+
const valid = out.code === 0;
71+
return {
72+
status: valid ? 200 : 200,
73+
data: {
74+
valid,
75+
report: out.stdout.trim(),
76+
diagnostics: out.stderr.trim() || undefined,
77+
},
78+
};
1679
}
1780

1881
case "typed_wasm_check_types": {
19-
const { module_path } = args ?? {};
20-
if (!module_path) return { status: 400, data: { error: "module_path is required" } };
21-
const cmd = new Deno.Command("typed-wasm", { args: ["check-types", String(module_path)], stdout: "piped", stderr: "piped" });
22-
const out = await cmd.output();
23-
if (!out.success) return { status: 500, data: { success: false, error: new TextDecoder().decode(out.stderr) } };
24-
const stdout = new TextDecoder().decode(out.stdout);
25-
try { return { status: 200, data: JSON.parse(stdout) }; } catch { return { status: 200, data: { success: true, output: stdout } }; }
82+
// Runs only the type checker (no Wasm emission).
83+
const r = requireString(args, "module_path");
84+
if (r.error) return { status: 400, data: { error: r.error } };
85+
const out = await runAffinescript(["check", r.value], dirOf(r.value));
86+
const ok = out.code === 0;
87+
return {
88+
status: 200,
89+
data: {
90+
ok,
91+
report: out.stdout.trim(),
92+
errors: ok ? [] : (out.stderr.trim() ? [out.stderr.trim()] : []),
93+
},
94+
};
2695
}
2796

2897
case "typed_wasm_compile_module": {
29-
const { module_path, target } = args ?? {};
30-
if (!module_path) return { status: 400, data: { error: "module_path is required" } };
31-
const cmd = new Deno.Command("typed-wasm", { args: ["compile-module", String(module_path)], stdout: "piped", stderr: "piped" });
32-
const out = await cmd.output();
33-
if (!out.success) return { status: 500, data: { success: false, error: new TextDecoder().decode(out.stderr) } };
34-
const stdout = new TextDecoder().decode(out.stdout);
35-
try { return { status: 200, data: JSON.parse(stdout) }; } catch { return { status: 200, data: { success: true, output: stdout } }; }
98+
// Compiles .affine → .wasm. The emitted module carries the
99+
// [affinescript.ownership] custom section consumed by typed-wasm
100+
// intra- and cross-module verifiers.
101+
const r = requireString(args, "module_path");
102+
if (r.error) return { status: 400, data: { error: r.error } };
103+
const outputPath = (typeof args?.output_path === "string" && args.output_path)
104+
? args.output_path
105+
: deriveWasmPath(r.value);
106+
const out = await runAffinescript(["compile", r.value, "-o", outputPath], dirOf(r.value));
107+
const success = out.code === 0;
108+
return {
109+
status: success ? 200 : 200,
110+
data: {
111+
success,
112+
output_path: success ? outputPath : undefined,
113+
report: out.stdout.trim(),
114+
diagnostics: out.stderr.trim() || undefined,
115+
},
116+
};
117+
}
118+
119+
case "typed_wasm_verify_boundary": {
120+
// Cross-module Level 10 boundary check. Compiles both files, then
121+
// verifies that the caller's local funcs respect the ownership
122+
// contract declared by the callee's Wasm exports.
123+
const callee = requireString(args, "callee_path");
124+
if (callee.error) return { status: 400, data: { error: callee.error } };
125+
const caller = requireString(args, "caller_path");
126+
if (caller.error) return { status: 400, data: { error: caller.error } };
127+
// Verify-boundary needs to find both files' imports — run from the
128+
// caller's directory (most likely the place where Callee lives too).
129+
const out = await runAffinescript(
130+
["verify-boundary", callee.value, caller.value],
131+
dirOf(caller.value),
132+
);
133+
const clean = out.code === 0;
134+
return {
135+
status: 200,
136+
data: {
137+
clean,
138+
report: out.stdout.trim(),
139+
diagnostics: out.stderr.trim() || undefined,
140+
},
141+
};
36142
}
143+
37144
default:
38145
return { status: 404, data: { error: `Unknown tool: ${toolName}` } };
39146
}

0 commit comments

Comments
 (0)