Skip to content

Commit d585a59

Browse files
hyperpolymathclaude
andcommitted
feat(adr-0006): migrate 3 cartridges to 5-symbol ABI (Pattern A final)
Pattern A migration (research-mcp, secrets-mcp, ssg-mcp) — closes the Pattern A queue (23 cartridges total: agent-mcp, bsp-mcp, cloud-mcp, codeseeker-mcp, comms-mcp, container-mcp, dap-mcp, database-mcp, feedback-mcp, fleet-mcp, git-mcp, iac-mcp, k8s-mcp, lang-mcp, local-coord-mcp, lsp-mcp, nesy-mcp, observe-mcp, proof-mcp, queues-mcp, research-mcp, secrets-mcp, ssg-mcp). All dispatch via cartridge_shim. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 225191c commit d585a59

6 files changed

Lines changed: 246 additions & 0 deletions

File tree

cartridges/research-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@ pub fn build(b: *std.Build) void {
99
const target = b.standardTargetOptions(.{});
1010
const optimize = b.standardOptimizeOption(.{});
1111

12+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
13+
14+
const shim_mod = b.addModule("cartridge_shim", .{
15+
16+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
17+
18+
.target = target,
19+
20+
.optimize = optimize,
21+
22+
});
23+
1224
const research_mod = b.addModule("research_ffi", .{
1325
.root_source_file = b.path("research_ffi.zig"),
1426
.target = target,
1527
.optimize = optimize,
1628
});
1729

30+
research_mod.addImport("cartridge_shim", shim_mod);
31+
1832
// ── Tests ────────────────────────────────────────────────────────
1933
const research_tests = b.addTest(.{
2034
.root_module = research_mod,

cartridges/research-mcp/ffi/research_ffi.zig

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,37 @@ pub export fn boj_cartridge_version() [*:0]const u8 {
188188
return "0.1.0";
189189
}
190190

191+
// ═══════════════════════════════════════════════════════════════════════
192+
// ADR-0006 dispatch (boj_cartridge_invoke, 5th standard symbol)
193+
// ═══════════════════════════════════════════════════════════════════════
194+
195+
const shim = @import("cartridge_shim");
196+
197+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha — each arm
198+
/// returns a stub JSON body shaped to the tool's intended response.
199+
export fn boj_cartridge_invoke(
200+
tool_name: [*c]const u8,
201+
json_args: [*c]const u8,
202+
out_buf: [*c]u8,
203+
in_out_len: [*c]usize,
204+
) callconv(.c) i32 {
205+
_ = json_args;
206+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
207+
208+
const body: []const u8 = if (shim.toolIs(tool_name, "research_authenticate"))
209+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
210+
else if (shim.toolIs(tool_name, "research_search"))
211+
"{\"result\":{\"matches\":[],\"status\":\"stub\"}}"
212+
else if (shim.toolIs(tool_name, "research_get_paper"))
213+
"{\"result\":{\"metadata\":{},\"status\":\"stub\"}}"
214+
else if (shim.toolIs(tool_name, "research_list_providers"))
215+
"{\"result\":{\"items\":[],\"count\":0,\"status\":\"stub\"}}"
216+
else
217+
return shim.RC_UNKNOWN_TOOL;
218+
219+
return shim.writeResult(out_buf, in_out_len, body);
220+
}
221+
191222
// ═══════════════════════════════════════════════════════════════════════
192223
// Research Provider Operations (all providers share the same API shape)
193224
// Grade D Alpha — stub implementations
@@ -461,3 +492,38 @@ test "research all providers credential storage" {
461492
try std.testing.expectEqual(@as(c_int, 0), research_logout(slot));
462493
}
463494
}
495+
496+
// ═══════════════════════════════════════════════════════════════════════
497+
// ADR-0006 invoke dispatch tests
498+
// ═══════════════════════════════════════════════════════════════════════
499+
500+
test "invoke: each declared tool succeeds" {
501+
var buf: [256]u8 = undefined;
502+
const tools = [_][]const u8{
503+
"research_authenticate",
504+
"research_search",
505+
"research_get_paper",
506+
"research_list_providers",
507+
};
508+
for (tools) |t| {
509+
var len: usize = buf.len;
510+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
511+
try std.testing.expectEqual(@as(i32, 0), rc);
512+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
513+
}
514+
}
515+
516+
test "invoke: unknown tool returns -1" {
517+
var buf: [64]u8 = undefined;
518+
var len: usize = buf.len;
519+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
520+
try std.testing.expectEqual(@as(i32, -1), rc);
521+
}
522+
523+
test "invoke: buffer too small returns -3" {
524+
var buf: [4]u8 = undefined;
525+
var len: usize = buf.len;
526+
const rc = boj_cartridge_invoke("research_authenticate", "{}", &buf, &len);
527+
try std.testing.expectEqual(@as(i32, -3), rc);
528+
try std.testing.expect(len > 4);
529+
}

cartridges/secrets-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@ pub fn build(b: *std.Build) void {
99
const target = b.standardTargetOptions(.{});
1010
const optimize = b.standardOptimizeOption(.{});
1111

12+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
13+
14+
const shim_mod = b.addModule("cartridge_shim", .{
15+
16+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
17+
18+
.target = target,
19+
20+
.optimize = optimize,
21+
22+
});
23+
1224
const sec_mod = b.addModule("secrets_ffi", .{
1325
.root_source_file = b.path("secrets_ffi.zig"),
1426
.target = target,
1527
.optimize = optimize,
1628
});
1729

30+
sec_mod.addImport("cartridge_shim", shim_mod);
31+
1832
// ── Tests ────────────────────────────────────────────────────────
1933
const sec_tests = b.addTest(.{
2034
.root_module = sec_mod,

cartridges/secrets-mcp/ffi/secrets_ffi.zig

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,39 @@ pub export fn boj_cartridge_version() [*:0]const u8 {
202202
return "0.1.0";
203203
}
204204

205+
// ═══════════════════════════════════════════════════════════════════════
206+
// ADR-0006 dispatch (boj_cartridge_invoke, 5th standard symbol)
207+
// ═══════════════════════════════════════════════════════════════════════
208+
209+
const shim = @import("cartridge_shim");
210+
211+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha — each arm
212+
/// returns a stub JSON body shaped to the tool's intended response.
213+
export fn boj_cartridge_invoke(
214+
tool_name: [*c]const u8,
215+
json_args: [*c]const u8,
216+
out_buf: [*c]u8,
217+
in_out_len: [*c]usize,
218+
) callconv(.c) i32 {
219+
_ = json_args;
220+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
221+
222+
const body: []const u8 = if (shim.toolIs(tool_name, "secrets_unseal"))
223+
"{\"result\":{\"status\":\"stub\"}}"
224+
else if (shim.toolIs(tool_name, "secrets_authenticate"))
225+
"{\"result\":{\"status\":\"stub\"}}"
226+
else if (shim.toolIs(tool_name, "secrets_get"))
227+
"{\"result\":{\"status\":\"stub\"}}"
228+
else if (shim.toolIs(tool_name, "secrets_set"))
229+
"{\"result\":{\"status\":\"stub\"}}"
230+
else if (shim.toolIs(tool_name, "secrets_seal"))
231+
"{\"result\":{\"status\":\"stub\"}}"
232+
else
233+
return shim.RC_UNKNOWN_TOOL;
234+
235+
return shim.writeResult(out_buf, in_out_len, body);
236+
}
237+
205238
// ═══════════════════════════════════════════════════════════════════════
206239
// Tests
207240
// ═══════════════════════════════════════════════════════════════════════
@@ -268,3 +301,39 @@ test "state transition validation" {
268301
try std.testing.expectEqual(@as(c_int, 0), sec_can_transition(0, 3)); // sealed -> accessing
269302
try std.testing.expectEqual(@as(c_int, 0), sec_can_transition(3, 0)); // accessing -> sealed
270303
}
304+
305+
// ═══════════════════════════════════════════════════════════════════════
306+
// ADR-0006 invoke dispatch tests
307+
// ═══════════════════════════════════════════════════════════════════════
308+
309+
test "invoke: each declared tool succeeds" {
310+
var buf: [256]u8 = undefined;
311+
const tools = [_][]const u8{
312+
"secrets_unseal",
313+
"secrets_authenticate",
314+
"secrets_get",
315+
"secrets_set",
316+
"secrets_seal",
317+
};
318+
for (tools) |t| {
319+
var len: usize = buf.len;
320+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
321+
try std.testing.expectEqual(@as(i32, 0), rc);
322+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
323+
}
324+
}
325+
326+
test "invoke: unknown tool returns -1" {
327+
var buf: [64]u8 = undefined;
328+
var len: usize = buf.len;
329+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
330+
try std.testing.expectEqual(@as(i32, -1), rc);
331+
}
332+
333+
test "invoke: buffer too small returns -3" {
334+
var buf: [4]u8 = undefined;
335+
var len: usize = buf.len;
336+
const rc = boj_cartridge_invoke("secrets_unseal", "{}", &buf, &len);
337+
try std.testing.expectEqual(@as(i32, -3), rc);
338+
try std.testing.expect(len > 4);
339+
}

cartridges/ssg-mcp/ffi/build.zig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@ pub fn build(b: *std.Build) void {
99
const target = b.standardTargetOptions(.{});
1010
const optimize = b.standardOptimizeOption(.{});
1111

12+
// Shared ADR-0006 invoke-shim module (relative path up to boj-server trunk).
13+
14+
const shim_mod = b.addModule("cartridge_shim", .{
15+
16+
.root_source_file = b.path("../../../ffi/zig/src/cartridge_shim.zig"),
17+
18+
.target = target,
19+
20+
.optimize = optimize,
21+
22+
});
23+
1224
const ssg_mod = b.addModule("ssg_ffi", .{
1325
.root_source_file = b.path("ssg_ffi.zig"),
1426
.target = target,
1527
.optimize = optimize,
1628
});
1729

30+
ssg_mod.addImport("cartridge_shim", shim_mod);
31+
1832
// ── Tests ────────────────────────────────────────────────────────
1933
const ssg_tests = b.addTest(.{
2034
.root_module = ssg_mod,

cartridges/ssg-mcp/ffi/ssg_ffi.zig

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,39 @@ pub export fn boj_cartridge_version() [*:0]const u8 {
213213
return "0.1.0";
214214
}
215215

216+
// ═══════════════════════════════════════════════════════════════════════
217+
// ADR-0006 dispatch (boj_cartridge_invoke, 5th standard symbol)
218+
// ═══════════════════════════════════════════════════════════════════════
219+
220+
const shim = @import("cartridge_shim");
221+
222+
/// Dispatch the cartridge.json MCP tools. Grade D Alpha — each arm
223+
/// returns a stub JSON body shaped to the tool's intended response.
224+
export fn boj_cartridge_invoke(
225+
tool_name: [*c]const u8,
226+
json_args: [*c]const u8,
227+
out_buf: [*c]u8,
228+
in_out_len: [*c]usize,
229+
) callconv(.c) i32 {
230+
_ = json_args;
231+
if (shim.invokeArgsNull(tool_name, out_buf, in_out_len)) return shim.RC_BAD_ARGS;
232+
233+
const body: []const u8 = if (shim.toolIs(tool_name, "ssg_load_content"))
234+
"{\"result\":{\"status\":\"stub\"}}"
235+
else if (shim.toolIs(tool_name, "ssg_build"))
236+
"{\"result\":{\"status\":\"stub\"}}"
237+
else if (shim.toolIs(tool_name, "ssg_preview"))
238+
"{\"result\":{\"status\":\"stub\"}}"
239+
else if (shim.toolIs(tool_name, "ssg_deploy"))
240+
"{\"result\":{\"status\":\"stub\"}}"
241+
else if (shim.toolIs(tool_name, "ssg_clean"))
242+
"{\"result\":{\"status\":\"stub\"}}"
243+
else
244+
return shim.RC_UNKNOWN_TOOL;
245+
246+
return shim.writeResult(out_buf, in_out_len, body);
247+
}
248+
216249
// ═══════════════════════════════════════════════════════════════════════
217250
// Tests
218251
// ═══════════════════════════════════════════════════════════════════════
@@ -288,3 +321,39 @@ test "state transition validation" {
288321
try std.testing.expectEqual(@as(c_int, 0), ssg_can_transition(2, 5)); // built -> deployed (BLOCKED)
289322
try std.testing.expectEqual(@as(c_int, 0), ssg_can_transition(0, 5)); // empty -> deployed
290323
}
324+
325+
// ═══════════════════════════════════════════════════════════════════════
326+
// ADR-0006 invoke dispatch tests
327+
// ═══════════════════════════════════════════════════════════════════════
328+
329+
test "invoke: each declared tool succeeds" {
330+
var buf: [256]u8 = undefined;
331+
const tools = [_][]const u8{
332+
"ssg_load_content",
333+
"ssg_build",
334+
"ssg_preview",
335+
"ssg_deploy",
336+
"ssg_clean",
337+
};
338+
for (tools) |t| {
339+
var len: usize = buf.len;
340+
const rc = boj_cartridge_invoke(t.ptr, "{}", &buf, &len);
341+
try std.testing.expectEqual(@as(i32, 0), rc);
342+
try std.testing.expect(std.mem.indexOf(u8, buf[0..len], "result") != null);
343+
}
344+
}
345+
346+
test "invoke: unknown tool returns -1" {
347+
var buf: [64]u8 = undefined;
348+
var len: usize = buf.len;
349+
const rc = boj_cartridge_invoke("nope", "{}", &buf, &len);
350+
try std.testing.expectEqual(@as(i32, -1), rc);
351+
}
352+
353+
test "invoke: buffer too small returns -3" {
354+
var buf: [4]u8 = undefined;
355+
var len: usize = buf.len;
356+
const rc = boj_cartridge_invoke("ssg_load_content", "{}", &buf, &len);
357+
try std.testing.expectEqual(@as(i32, -3), rc);
358+
try std.testing.expect(len > 4);
359+
}

0 commit comments

Comments
 (0)