Skip to content

Commit b53bba6

Browse files
committed
fix(ci): repair bridge boot gates
1 parent ccd31df commit b53bba6

14 files changed

Lines changed: 101 additions & 23 deletions

File tree

.github/CICD-CHANGES-2026-06-04.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
<!--
2+
SPDX-License-Identifier: MPL-2.0
3+
Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
-->
5+
16
# CI/CD Changes — 2026-06-04
27

38
**Date:** 2026-06-04
@@ -22,7 +27,7 @@ All 18 workflows in this repository have been updated to include `timeout-minute
2227
| Workflow | timeout-minutes | Concurrency Added | Notes |
2328
|----------|-----------------|------------------|-------|
2429
| `abi-drift.yml` | 15 | | ABI manifest + FFI verification |
25-
| `codeql.yml` | 15 || Includes C++ support (has C/C++ headers) |
30+
| `codeql.yml` | 15 || JavaScript/TypeScript CodeQL only; Zig FFI is covered by Zig workflows |
2631
| `container-publish.yml` | 30 | | Container build & push |
2732
| `dogfood-gate.yml` | 5-15 || 6 jobs: a2ml(5), k9(5), empty-lint(15), groove(5), eclexiaiser(5), summary(5) |
2833
| `e2e.yml` | 15 || MCP bridge input fuzz tests |
@@ -54,8 +59,13 @@ All 18 workflows in this repository have been updated to include `timeout-minute
5459

5560
## CodeQL Configuration
5661

57-
**Languages:** `javascript-typescript` + `cpp`
58-
**Reason:** This repository contains C/C++ headers in the FFI layer.
62+
**Languages:** `javascript-typescript`
63+
64+
**Reason:** The FFI implementation is Zig. The tracked C ABI file is a generated
65+
header-only surface (`generated/abi/boj_catalogue.h`), not a C/C++ translation
66+
unit; enabling CodeQL `cpp` for headers alone makes extraction fail before
67+
analysis. Re-add `cpp` only when tracked `.c`, `.cc`, `.cpp`, or `.cxx` sources
68+
exist.
5969

6070
---
6171

.github/workflows/codeql.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ jobs:
3333
include:
3434
- language: javascript-typescript
3535
build-mode: none
36-
- language: cpp
37-
build-mode: none
36+
# C/C++ CodeQL is intentionally not enabled for the generated
37+
# header-only ABI surface. The FFI implementation is Zig and is
38+
# covered by the Zig workflows; re-add cpp only when tracked
39+
# .c/.cc/.cpp/.cxx translation units exist.
3840

3941
steps:
4042
- name: Checkout

.github/workflows/dogfood-gate.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ jobs:
165165
echo "::warning file=${REL_PATH}::Invisible Unicode characters detected (zero-width space, BOM, NBSP, etc.)"
166166
done < /tmp/empty-lint-results.txt
167167
168+
- name: Check shebang placement
169+
run: bash scripts/check-shebang-first.sh
170+
168171
- name: Write summary
169172
run: |
170173
if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then
@@ -374,4 +377,3 @@ jobs:
374377
*Generated by the [Dogfood Gate](https://github.com/hyperpolymath/rsr-template-repo) workflow.*
375378
*Dogfooding is guinea pig fooding — we test our tools on ourselves.*
376379
EOF
377-

cartridges/claude-ai-mcp/src/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
#!/usr/bin/env node
12
// SPDX-License-Identifier: MPL-2.0
23
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3-
#!/usr/bin/env node
44
//
55
// claude-ai-mcp — Anthropic Messages API cartridge for the BoJ
66
//

cartridges/model-router-mcp/src/router.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
#!/usr/bin/env node
12
// SPDX-License-Identifier: MPL-2.0
23
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3-
#!/usr/bin/env node
44
//
55
// Model Router — Intelligent model switching for Claude Code
66
//

mcp-bridge/lib/generate-offline-menu.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
#!/usr/bin/env -S deno run --allow-read
12
// SPDX-License-Identifier: MPL-2.0
23
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3-
#!/usr/bin/env -S deno run --allow-read
44
//
55
// Generate offline-menu.js from the cartridges/ directory structure.
66
//

mcp-bridge/lib/runtime.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// Supports the "Deno > Bun > NPM" hierarchy while maintaining
88
// compatibility with Node-only MCP clients like Glama.
99

10+
import { writeSync as writeFdSync } from "node:fs";
11+
1012
const isDeno = typeof globalThis.Deno !== "undefined";
1113

1214
/** @type {{ get: (name: string) => string|undefined }} */
@@ -32,7 +34,7 @@ export const stdout = {
3234
if (isDeno) {
3335
globalThis.Deno.stdout.writeSync(buf);
3436
} else if (typeof process !== "undefined") {
35-
process.stdout.write(buf);
37+
writeFdSync(process.stdout.fd, buf);
3638
}
3739
}
3840
};
@@ -44,7 +46,7 @@ export const stderr = {
4446
if (isDeno) {
4547
globalThis.Deno.stderr.writeSync(buf);
4648
} else if (typeof process !== "undefined") {
47-
process.stderr.write(buf);
49+
writeFdSync(process.stderr.fd, buf);
4850
}
4951
}
5052
};

mcp-bridge/main.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
#!/usr/bin/env -S deno run --allow-net --allow-env --allow-read
12
// SPDX-License-Identifier: MPL-2.0
23
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
3-
#!/usr/bin/env -S deno run --allow-net --allow-env --allow-read
44
//
55
// BoJ Server — MCP transport bridge (stdio + HTTP per ADR-0013)
66
//
@@ -17,10 +17,8 @@
1717

1818
import { env, stdout } from "./lib/runtime.js";
1919
import { sanitizeErrorMessage } from "./lib/security.js";
20-
import { dispatchMcpMessage } from "./lib/dispatcher.js";
2120
import { info, error as logError } from "./lib/logger.js";
2221
import * as otel from "./lib/otel.js";
23-
import { startHttpTransport } from "./lib/http-transport.js";
2422

2523
const TRANSPORT = (env.get("BOJ_TRANSPORT") ?? "stdio").toLowerCase();
2624

@@ -43,6 +41,15 @@ function sendError(id, code, message) {
4341
send({ jsonrpc: "2.0", id, error: { code, message: sanitizeErrorMessage(message) } });
4442
}
4543

44+
let dispatchMcpMessagePromise;
45+
46+
async function getDispatchMcpMessage() {
47+
if (!dispatchMcpMessagePromise) {
48+
dispatchMcpMessagePromise = import("./lib/dispatcher.js").then((m) => m.dispatchMcpMessage);
49+
}
50+
return dispatchMcpMessagePromise;
51+
}
52+
4653
async function handleStdioLine(line) {
4754
let msg;
4855
try {
@@ -51,6 +58,7 @@ async function handleStdioLine(line) {
5158
sendError(null, -32700, "Parse error");
5259
return;
5360
}
61+
const dispatchMcpMessage = await getDispatchMcpMessage();
5462
const response = await dispatchMcpMessage(msg, { transport: "stdio" });
5563
if (response !== null) send(response);
5664
}
@@ -77,8 +85,16 @@ async function runStdio() {
7785
if (typeof Deno !== "undefined") {
7886
for await (const chunk of Deno.stdin.readable) processChunk(chunk);
7987
} else {
80-
// @ts-ignore: process is global in Node
81-
for await (const chunk of process.stdin) processChunk(chunk);
88+
await new Promise((resolve, reject) => {
89+
// @ts-ignore: process is global in Node/Bun
90+
process.stdin.on("data", processChunk);
91+
// @ts-ignore: process is global in Node/Bun
92+
process.stdin.once("end", resolve);
93+
// @ts-ignore: process is global in Node/Bun
94+
process.stdin.once("error", reject);
95+
// @ts-ignore: process is global in Node/Bun
96+
process.stdin.resume();
97+
});
8298
}
8399
await Promise.allSettled(pendingMessages);
84100
}
@@ -88,6 +104,7 @@ async function runStdio() {
88104
// ===================================================================
89105

90106
async function runHttp() {
107+
const { startHttpTransport } = await import("./lib/http-transport.js");
91108
const handle = await startHttpTransport();
92109
await new Promise((resolve) => {
93110
const stop = async () => {

mcp-bridge/tests/boot_smoke.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MPL-2.0
2-
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
2+
// Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
33
//
44
// BoJ Server — bridge boot smoke (runtime portability gate)
55
//
@@ -58,14 +58,17 @@ let stdout = "";
5858
let stderr = "";
5959
child.stdout.on("data", (d) => (stdout += d));
6060
child.stderr.on("data", (d) => (stderr += d));
61+
child.stdin.on("error", (e) => (stderr += `stdin error: ${e.message}\n`));
6162

6263
const killTimer = setTimeout(() => {
6364
console.error(`FAIL: bridge did not exit within ${TIMEOUT_MS}ms`);
6465
child.kill("SIGKILL");
6566
}, TIMEOUT_MS);
6667

67-
child.stdin.write(requests.map((r) => JSON.stringify(r)).join("\n") + "\n");
68-
child.stdin.end();
68+
const payload = requests.map((r) => JSON.stringify(r)).join("\n") + "\n";
69+
child.once("spawn", () => {
70+
child.stdin.end(payload);
71+
});
6972

7073
child.on("close", (code) => {
7174
clearTimeout(killTimer);

scripts/check-shebang-first.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
4+
#
5+
# Shebangs are only interpreter directives when they are the first line.
6+
# A license header above "#!" makes Node, Deno, Bun, and POSIX shells parse it
7+
# as source text instead, so executable scripts must keep "#!" at line 1.
8+
9+
set -euo pipefail
10+
11+
fail=0
12+
13+
while IFS= read -r -d '' path; do
14+
case "$path" in
15+
*.awk|*.bash|*.cjs|*.exs|*.fish|*.js|*.mjs|*.pl|*.py|*.rb|*.scm|*.sh|*.ts|*.zsh) ;;
16+
*) continue ;;
17+
esac
18+
19+
line_no=0
20+
while IFS= read -r line || [ -n "$line" ]; do
21+
line_no=$((line_no + 1))
22+
case "$line" in
23+
'#!'*)
24+
if [ "$line_no" != "1" ]; then
25+
printf 'ERROR: %s:%s has a shebang after line 1\n' "$path" "$line_no" >&2
26+
fail=1
27+
fi
28+
;;
29+
esac
30+
done < "$path"
31+
done < <(git ls-files -z)
32+
33+
if [ "$fail" -ne 0 ]; then
34+
cat >&2 <<'EOF'
35+
36+
Shebangs must be the first line of executable scripts. Put SPDX and copyright
37+
comments immediately after the shebang.
38+
EOF
39+
exit 1
40+
fi
41+
42+
echo "OK: all tracked shebangs are on line 1"

0 commit comments

Comments
 (0)