test: add isolated Claude Devin bridge harness

This commit is contained in:
Lucas Israel
2026-07-27 10:55:16 -03:00
parent aa5856376e
commit b0d4d64e6c
28 changed files with 977 additions and 55 deletions

View File

@@ -0,0 +1,6 @@
ENABLE_LIVE_DEVIN_TESTS=0
DEVIN_BRIDGE_MODEL=devin-cli-agentic/swe-1-7
DEVIN_BRIDGE_SONNET_MODEL=devin-cli-agentic/swe-1-7
DEVIN_BRIDGE_OPUS_MODEL=devin-cli-agentic/swe-1-7
DEVIN_BRIDGE_HAIKU_MODEL=devin-cli-agentic/swe-1-7
DEVIN_BRIDGE_SUBAGENT_MODEL=devin-cli-agentic/swe-1-7

5
.gitignore vendored
View File

@@ -72,6 +72,7 @@ yarn-error.log*
# env files (can opt-in for committing if needed)
.env*
!.env.example
!.env.devin-bridge.example
!.env.homolog.example
# Provider API keys (never commit)
*.api-key
@@ -207,6 +208,8 @@ scripts/i18n/_pending-keys.json
.agents/
.antigravitycli/
.claude/
!tests/fixtures/devin-bridge/e2e-workspace/.claude/
!tests/fixtures/devin-bridge/e2e-workspace/.claude/**
# PR Reviews and local feedback files
pr_reviews*.json
@@ -243,7 +246,7 @@ _artifacts/ # release-green artifacts
# CI/local quality artifacts (eslint-results.json, quality-ratchet.md, etc.)
.artifacts/
.sandbox/
.sandbox/ # isolated Devin bridge workspaces, evidence, and test databases
# Homologation E2E suite (npm run homolog) — real-environment credentials + report output
.env.homolog

View File

@@ -0,0 +1,57 @@
FROM node:26.0.0-bookworm-slim
ARG CLAUDE_CODE_VERSION=2.1.220
ARG DEVIN_CLI_VERSION=3000.2.17
ARG TARGETARCH
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl git bash python3 make g++ tini \
&& rm -rf /var/lib/apt/lists/* \
&& npm install --global "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}"
RUN set -eu; \
case "${TARGETARCH}" in \
amd64) devin_arch=x86_64-unknown-linux; devin_sha=f0e1e9363afc6ee68c4ef87bab4aeb7ff5cc08a5fa838350ef3ceefdbb2a2be2 ;; \
arm64) devin_arch=aarch64-unknown-linux; devin_sha=116dc71ef085a922bc3ff0ea0377d4b26c529a431d58246e36572913e2d25624 ;; \
*) echo "Unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \
esac; \
curl -fsSL "https://static.devin.ai/cli/${DEVIN_CLI_VERSION}/devin-${DEVIN_CLI_VERSION}-${devin_arch}.tar.gz" -o /tmp/devin.tar.gz; \
echo "${devin_sha} /tmp/devin.tar.gz" | sha256sum -c -; \
tar -xzf /tmp/devin.tar.gz -C /tmp; \
install -m 0755 "$(find /tmp -type f -name devin | head -1)" /usr/local/bin/devin; \
rm -rf /tmp/devin.tar.gz /tmp/devin-*
RUN groupadd --gid 10001 bridge \
&& useradd --uid 10001 --gid bridge --create-home --home-dir /home/bridge --shell /bin/bash bridge \
&& mkdir -p /opt/omniroute /workspace \
&& chown -R bridge:bridge /opt/omniroute /workspace
WORKDIR /opt/omniroute
USER bridge
COPY --chown=bridge:bridge package.json package-lock.json .npmrc ./
RUN npm ci --ignore-scripts --no-audit --fund=false
COPY --chown=bridge:bridge . .
RUN npm rebuild better-sqlite3 || true
ENV HOME=/home/bridge \
CLAUDE_CONFIG_DIR=/home/bridge/.claude-devin-isolated \
DEVIN_AGENTIC_HOME=/home/bridge \
DATA_DIR=/home/bridge/.omniroute-isolated \
SQLITE_FILE=/home/bridge/.omniroute-isolated/storage.sqlite \
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 \
DISABLE_TELEMETRY=1 \
DISABLE_ERROR_REPORTING=1 \
DISABLE_AUTOUPDATER=1 \
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 \
NEXT_TELEMETRY_DISABLED=1
RUN mkdir -p /home/bridge/.claude-devin-isolated /home/bridge/.local/share/devin \
/home/bridge/.omniroute-isolated
RUN DATA_DIR=/tmp/omniroute-build-data \
SQLITE_FILE=/tmp/omniroute-build-data/storage.sqlite \
npm run build \
&& rm -rf /tmp/omniroute-build-data
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["bash"]

View File

@@ -0,0 +1,164 @@
name: omniroute-devin-bridge
x-isolated-environment: &isolated-environment
HOME: /home/bridge
CLAUDE_CONFIG_DIR: /home/bridge/.claude-devin-isolated
DEVIN_AGENTIC_HOME: /home/bridge
DATA_DIR: /home/bridge/.omniroute-isolated
SQLITE_FILE: /home/bridge/.omniroute-isolated/storage.sqlite
ANTHROPIC_BASE_URL: http://omniroute:20128
ANTHROPIC_AUTH_TOKEN: sk-local-devin-gateway
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
DISABLE_TELEMETRY: "1"
DISABLE_ERROR_REPORTING: "1"
DISABLE_AUTOUPDATER: "1"
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1"
DEVIN_BRIDGE_MODEL: ${DEVIN_BRIDGE_MODEL:-devin-cli-agentic/swe-1-7}
ANTHROPIC_MODEL: ${DEVIN_BRIDGE_MODEL:-devin-cli-agentic/swe-1-7}
ANTHROPIC_DEFAULT_SONNET_MODEL: ${DEVIN_BRIDGE_SONNET_MODEL:-devin-cli-agentic/swe-1-7}
ANTHROPIC_DEFAULT_OPUS_MODEL: ${DEVIN_BRIDGE_OPUS_MODEL:-devin-cli-agentic/swe-1-7}
ANTHROPIC_DEFAULT_HAIKU_MODEL: ${DEVIN_BRIDGE_HAIKU_MODEL:-devin-cli-agentic/swe-1-7}
CLAUDE_CODE_SUBAGENT_MODEL: ${DEVIN_BRIDGE_SUBAGENT_MODEL:-devin-cli-agentic/swe-1-7}
REQUIRE_API_KEY: "true"
OMNIROUTE_API_KEY: sk-local-devin-gateway
x-runtime: &runtime
image: omniroute-devin-bridge:local
build:
context: ../..
dockerfile: docker/devin-bridge/Dockerfile
args:
CLAUDE_CODE_VERSION: 2.1.220
DEVIN_CLI_VERSION: 3000.2.17
user: "10001:10001"
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,nodev,size=256m
- /opt/omniroute/.source:rw,nosuid,nodev,size=16m,uid=10001,gid=10001
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
environment: *isolated-environment
networks: [bridge-internal]
services:
omniroute:
<<: *runtime
profiles: [offline]
hostname: omniroute
environment:
<<: *isolated-environment
CLI_DEVIN_AGENTIC_BIN: /opt/omniroute/docker/devin-bridge/mock-devin.mjs
DEVIN_BRIDGE_MOCK_LOG: /evidence/mock-acp.jsonl
command: ["npm", "run", "start"]
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://127.0.0.1:20128/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
]
interval: 2s
timeout: 2s
retries: 60
volumes:
- claude-isolated-config:/home/bridge/.claude-devin-isolated
- omniroute-offline-data:/home/bridge/.omniroute-isolated
- ../../.sandbox/evidence:/evidence
- ./mock-devin.mjs:/opt/omniroute/docker/devin-bridge/mock-devin.mjs:ro
claude:
<<: *runtime
profiles: [offline]
depends_on:
omniroute:
condition: service_healthy
working_dir: /workspace
command: ["bash", "/opt/omniroute/docker/devin-bridge/run-claude-e2e.sh"]
volumes:
- claude-isolated-config:/home/bridge/.claude-devin-isolated
- ../../.sandbox/e2e-workspace:/workspace
- ../../.sandbox/evidence:/evidence
- ./run-claude-e2e.sh:/opt/omniroute/docker/devin-bridge/run-claude-e2e.sh:ro
contract:
<<: *runtime
profiles: [offline]
depends_on:
omniroute:
condition: service_healthy
command: ["node", "/opt/omniroute/docker/devin-bridge/run-contract.mjs"]
volumes:
- claude-isolated-config:/home/bridge/.claude-devin-isolated
- ./run-contract.mjs:/opt/omniroute/docker/devin-bridge/run-contract.mjs:ro
network-guard:
image: node:26.0.0-bookworm-slim
profiles: [live-devin]
user: "10001:10001"
read_only: true
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
command: ["node", "/guard/proxy.mjs"]
environment:
GUARD_LISTEN: 0.0.0.0:8080
GUARD_ALLOW_SUFFIXES: .devin.ai,.cognition.ai
GUARD_LOG: /logs/egress.jsonl
volumes:
- ./network-guard:/guard:ro
- ../../.sandbox/evidence:/logs
networks: [bridge-internal, guard-egress]
omniroute-live:
<<: *runtime
profiles: [live-devin]
hostname: omniroute
depends_on: [network-guard]
environment:
<<: *isolated-environment
CLI_DEVIN_AGENTIC_BIN: /usr/local/bin/devin
HTTPS_PROXY: http://network-guard:8080
HTTP_PROXY: http://network-guard:8080
NO_PROXY: localhost,127.0.0.1,omniroute
command: ["npm", "run", "start"]
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://127.0.0.1:20128/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
]
interval: 2s
timeout: 2s
retries: 60
volumes:
- claude-isolated-config:/home/bridge/.claude-devin-isolated
- devin-auth:/home/bridge/.local/share/devin
- omniroute-live-data:/home/bridge/.omniroute-isolated
claude-live:
<<: *runtime
profiles: [live-devin]
depends_on:
omniroute-live:
condition: service_healthy
working_dir: /workspace
command: ["bash", "/opt/omniroute/docker/devin-bridge/run-claude-live-e2e.sh"]
volumes:
- claude-isolated-config:/home/bridge/.claude-devin-isolated
- devin-auth:/home/bridge/.local/share/devin
- ../../.sandbox/live-workspace:/workspace
- ../../.sandbox/evidence:/evidence
- ./run-claude-live-e2e.sh:/opt/omniroute/docker/devin-bridge/run-claude-live-e2e.sh:ro
networks:
bridge-internal:
internal: true
guard-egress: {}
volumes:
claude-isolated-config: {}
devin-auth: {}
omniroute-offline-data: {}
omniroute-live-data: {}

View File

@@ -0,0 +1,184 @@
#!/usr/bin/env node
import fs from "node:fs";
import readline from "node:readline";
if (
process.argv[2] !== "acp" ||
process.argv[3] !== "--agent-type" ||
process.argv[4] !== "summarizer"
) {
process.exit(64);
}
const logFile = process.env.DEVIN_BRIDGE_MOCK_LOG || "/evidence/mock-acp.jsonl";
const rl = readline.createInterface({ input: process.stdin });
const send = (value) => process.stdout.write(`${JSON.stringify(value)}\n`);
const log = (value) => fs.appendFileSync(logFile, `${JSON.stringify(value)}\n`);
const actions = [
{
name: "Skill",
arguments: { skill: "bridge-proof" },
},
{
name: "Bash",
arguments: {
command: "find . -maxdepth 2 -type f -print",
description: "Locate the fixture files",
},
},
{
name: "Read",
arguments: { file_path: "/workspace/math.js" },
},
{
name: "Edit",
arguments: {
file_path: "/workspace/math.js",
old_string: "return a - b;",
new_string: "return a * b;",
},
},
{
name: "Bash",
arguments: { command: "npm test", description: "Run the fixture tests" },
},
{
name: "Edit",
arguments: {
file_path: "/workspace/math.js",
old_string: "return a * b;",
new_string: "return a + b;",
},
},
{
name: "Bash",
arguments: { command: "npm test", description: "Confirm the corrected fixture" },
},
];
rl.on("line", (line) => {
const message = JSON.parse(line);
if (message.method === "initialize") {
if (message.params?.protocolVersion !== 1) {
send({ jsonrpc: "2.0", id: message.id, error: { code: -32602, message: "ACP v1 required" } });
return;
}
send({ jsonrpc: "2.0", id: message.id, result: { protocolVersion: 1 } });
} else if (message.method === "session/new") {
if (message.params?.cwd !== "/home/bridge" || !Array.isArray(message.params?.mcpServers)) {
send({ jsonrpc: "2.0", id: message.id, error: { code: -32602, message: "unsafe session" } });
return;
}
send({ jsonrpc: "2.0", id: message.id, result: { sessionId: "offline" } });
} else if (message.method === "session/prompt") {
const prompt = String(message.params?.prompt?.[0]?.text || "");
if (prompt.includes("CONTRACT_AFTER_TOOL")) {
log({ provider: "devin-cli-agentic", scenario: "after-tool" });
send({
jsonrpc: "2.0",
method: "session/update",
params: {
sessionId: "offline",
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: "contract continued" },
},
},
});
send({ jsonrpc: "2.0", id: message.id, result: { stopReason: "end_turn" } });
return;
}
if (prompt.includes("CONTRACT_EXIT")) {
log({ provider: "devin-cli-agentic", scenario: "exit" });
process.exit(7);
}
if (prompt.includes("CONTRACT_ERROR")) {
log({ provider: "devin-cli-agentic", scenario: "error" });
send({
jsonrpc: "2.0",
id: message.id,
error: { code: -32000, message: "deterministic upstream failure" },
});
return;
}
if (prompt.includes("CONTRACT_TEXT")) {
log({ provider: "devin-cli-agentic", scenario: "text" });
send({
jsonrpc: "2.0",
method: "session/update",
params: {
sessionId: "offline",
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: "contract text" },
},
},
});
send({ jsonrpc: "2.0", id: message.id, result: { stopReason: "end_turn" } });
return;
}
if (prompt.includes("CONTRACT_TOOL")) {
log({ provider: "devin-cli-agentic", scenario: "tool" });
send({
jsonrpc: "2.0",
method: "session/update",
params: {
sessionId: "offline",
update: {
sessionUpdate: "agent_message_chunk",
content: {
type: "text",
text: '<tool>{"name":"Read","arguments":{"file_path":"/workspace/math.js"}}</tool>',
},
},
},
});
send({ jsonrpc: "2.0", id: message.id, result: { stopReason: "end_turn" } });
return;
}
const resultCount = (prompt.match(/\[Tool Result\]/g) || []).length;
if (!prompt.includes("CLAUDE_MD_BRIDGE_ACTIVE") || !prompt.includes("COMMAND_BRIDGE_ACTIVE")) {
send({
jsonrpc: "2.0",
id: message.id,
error: { code: -32602, message: "Claude project context missing" },
});
return;
}
const action = actions[resultCount];
const text = action
? `<tool>${JSON.stringify(action)}</tool>`
: "BRIDGE_E2E_COMPLETE CLAUDE_MD_BRIDGE_ACTIVE SKILL_BRIDGE_ACTIVE COMMAND_BRIDGE_ACTIVE";
if (!action && !prompt.includes("SKILL_BRIDGE_ACTIVE")) {
send({
jsonrpc: "2.0",
id: message.id,
error: { code: -32602, message: "Skill result missing" },
});
return;
}
log({
provider: "devin-cli-agentic",
model: message.params?.model || "swe-1-7",
resultCount,
action: action?.name || "final",
});
const midpoint = Math.max(1, Math.floor(text.length / 2));
for (const chunk of [text.slice(0, midpoint), text.slice(midpoint)]) {
send({
jsonrpc: "2.0",
method: "session/update",
params: {
sessionId: "offline",
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: chunk },
},
},
});
}
send({ jsonrpc: "2.0", id: message.id, result: { stopReason: "end_turn" } });
}
});

View File

@@ -0,0 +1,58 @@
import http from "node:http";
import net from "node:net";
import fs from "node:fs";
const [host, portText] = (process.env.GUARD_LISTEN || "0.0.0.0:8080").split(":");
const port = Number(portText);
const suffixes = (process.env.GUARD_ALLOW_SUFFIXES || "")
.split(",")
.map((value) => value.trim().toLowerCase())
.filter(Boolean);
const logPath = process.env.GUARD_LOG || "/tmp/egress.jsonl";
function allowed(hostname) {
const value = hostname.toLowerCase().replace(/\.$/, "");
return suffixes.some((suffix) => value === suffix.slice(1) || value.endsWith(suffix));
}
function audit(hostname, decision) {
fs.appendFileSync(
logPath,
`${JSON.stringify({ at: new Date().toISOString(), hostname, decision })}\n`
);
}
const server = http.createServer((req, res) => {
const target = new URL(req.url);
if (!allowed(target.hostname)) {
audit(target.hostname, "deny");
res.writeHead(403).end("egress denied\n");
return;
}
audit(target.hostname, "allow");
const upstream = http.request(target, { method: req.method, headers: req.headers }, (reply) => {
res.writeHead(reply.statusCode || 502, reply.headers);
reply.pipe(res);
});
req.pipe(upstream);
upstream.on("error", () => res.writeHead(502).end("upstream error\n"));
});
server.on("connect", (req, client, head) => {
const [hostname, portValue] = req.url.split(":");
if (!allowed(hostname)) {
audit(hostname, "deny");
client.end("HTTP/1.1 403 Forbidden\r\n\r\n");
return;
}
audit(hostname, "allow");
const upstream = net.connect(Number(portValue) || 443, hostname, () => {
client.write("HTTP/1.1 200 Connection Established\r\n\r\n");
if (head.length) upstream.write(head);
upstream.pipe(client);
client.pipe(upstream);
});
upstream.on("error", () => client.end("HTTP/1.1 502 Bad Gateway\r\n\r\n"));
});
server.listen(port, host);

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
unset ANTHROPIC_API_KEY CLAUDE_CODE_OAUTH_TOKEN ANTHROPIC_BEDROCK_BASE_URL ANTHROPIC_VERTEX_BASE_URL
unset CLAUDE_CODE_USE_BEDROCK CLAUDE_CODE_USE_VERTEX CLAUDE_CODE_USE_FOUNDRY
set -o pipefail
check() {
"$@"
printf 'E2E check passed: %s\n' "$*"
}
claude -p --output-format stream-json --verbose --max-turns 12 \
--permission-mode bypassPermissions \
"/bridge-check" | tee /evidence/claude-stream.jsonl
if grep -Eqi 'log[ -]?in|authenticate.*anthropic|claude\.ai' /evidence/claude-stream.jsonl; then
echo "Claude Code requested forbidden authentication" >&2
exit 1
fi
check grep -q 'return a + b;' /workspace/math.js
npm test
check grep -q 'Skill' /workspace/.e2e-hook.log
check grep -q 'Read' /workspace/.e2e-hook.log
check grep -q 'Edit' /workspace/.e2e-hook.log
check grep -q 'Bash' /workspace/.e2e-hook.log
check grep -q 'BRIDGE_E2E_COMPLETE' /evidence/claude-stream.jsonl

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail
unset ANTHROPIC_API_KEY CLAUDE_CODE_OAUTH_TOKEN ANTHROPIC_BEDROCK_BASE_URL ANTHROPIC_VERTEX_BASE_URL
unset CLAUDE_CODE_USE_BEDROCK CLAUDE_CODE_USE_VERTEX CLAUDE_CODE_USE_FOUNDRY
run_scenario() {
local evidence_file="$1"
local prompt="$2"
claude -p --output-format stream-json --verbose --max-turns 12 \
--permission-mode bypassPermissions "$prompt" | tee "$evidence_file"
if grep -Eqi 'log[ -]?in|authenticate.*anthropic|claude\.ai' "$evidence_file"; then
echo "Claude Code requested forbidden authentication" >&2
exit 1
fi
}
run_scenario /evidence/live-analysis.jsonl \
"Read CLAUDE.md, inspect math.js and its test without editing, explain the defect, then end with LIVE_ANALYSIS_COMPLETE."
grep -q LIVE_ANALYSIS_COMPLETE /evidence/live-analysis.jsonl
run_scenario /evidence/live-fix.jsonl \
"Fix the defect in math.js, run npm test, and end with LIVE_FIX_COMPLETE only after the test passes."
grep -q 'return a + b;' /workspace/math.js
npm test
grep -q LIVE_FIX_COMPLETE /evidence/live-fix.jsonl
run_scenario /evidence/live-command.jsonl "/bridge-check"
grep -q BRIDGE_E2E_COMPLETE /evidence/live-command.jsonl
printf 'PASS: three live Devin-backed Claude Code scenarios completed\n'

View File

@@ -0,0 +1,115 @@
#!/usr/bin/env node
import assert from "node:assert/strict";
const endpoint = "http://omniroute:20128/v1/messages";
const headers = {
"anthropic-version": "2023-06-01",
"content-type": "application/json",
"x-api-key": "sk-local-devin-gateway",
};
const model = process.env.DEVIN_BRIDGE_MODEL || "devin-cli-agentic/swe-1-7";
async function request(prompt, extra = {}) {
return fetch(endpoint, {
method: "POST",
headers,
body: JSON.stringify({
model,
max_tokens: 256,
messages: [{ role: "user", content: prompt }],
...extra,
}),
});
}
const textReply = await request("CONTRACT_TEXT");
assert.equal(textReply.status, 200);
assert.match(textReply.headers.get("content-type") || "", /application\/json/);
const textBody = await textReply.json();
assert.equal(textBody.type, "message");
assert.equal(textBody.role, "assistant");
assert.equal(textBody.stop_reason, "end_turn");
assert.deepEqual(textBody.content, [{ type: "text", text: "contract text" }]);
const toolReply = await request("CONTRACT_TOOL", {
stream: true,
tools: [
{
name: "Read",
description: "Read a file",
input_schema: {
type: "object",
properties: { file_path: { type: "string" } },
required: ["file_path"],
additionalProperties: false,
},
},
],
});
assert.equal(toolReply.status, 200);
assert.match(toolReply.headers.get("content-type") || "", /text\/event-stream/);
const toolStream = await toolReply.text();
const eventNames = toolStream
.split("\n")
.filter((line) => line.startsWith("event: "))
.map((line) => line.slice(7));
assert.deepEqual(eventNames, [
"message_start",
"content_block_start",
"content_block_delta",
"content_block_stop",
"message_delta",
"message_stop",
]);
const toolEvents = toolStream
.split("\n")
.filter((line) => line.startsWith("data: "))
.map((line) => JSON.parse(line.slice(6)));
const toolUse = toolEvents.find((event) => event.type === "content_block_start")?.content_block;
assert.equal(toolUse?.type, "tool_use");
assert.equal(toolUse?.name, "Read");
assert.match(toolUse?.id || "", /^tool_devin_/);
const continuationReply = await fetch(endpoint, {
method: "POST",
headers,
body: JSON.stringify({
model,
max_tokens: 256,
tools: [
{
name: "Read",
description: "Read a file",
input_schema: { type: "object", properties: {}, additionalProperties: true },
},
],
messages: [
{ role: "user", content: "CONTRACT_TOOL" },
{ role: "assistant", content: [toolUse] },
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: toolUse.id,
content: "CONTRACT_AFTER_TOOL",
},
],
},
],
}),
});
assert.equal(continuationReply.status, 200);
const continuationBody = await continuationReply.json();
assert.equal(continuationBody.stop_reason, "end_turn");
assert.deepEqual(continuationBody.content, [{ type: "text", text: "contract continued" }]);
for (const marker of ["CONTRACT_ERROR", "CONTRACT_EXIT"]) {
const failedReply = await request(marker);
assert.equal(failedReply.status, 502);
const failedBody = await failedReply.json();
assert.equal(failedBody.error?.type, "server_error");
assert.doesNotMatch(JSON.stringify(failedBody), /stack|anthropic|openai/i);
}
console.log("PASS: Anthropic Messages wire contracts and fail-closed errors passed");

5
scripts/devin-bridge/build Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/common"
bridge_prepare_sandbox
docker compose -f "$BRIDGE_COMPOSE" --profile offline build

12
scripts/devin-bridge/clean Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/common"
if [[ "${1:-}" == "--all" ]]; then
docker compose -f "$BRIDGE_COMPOSE" --profile offline --profile live-devin down \
--remove-orphans --volumes
printf 'Containers, networks, and bridge-owned named volumes were removed.\n'
else
docker compose -f "$BRIDGE_COMPOSE" --profile offline --profile live-devin down \
--remove-orphans
printf 'Containers and networks stopped. Named auth/config volumes were preserved; use --all to remove them.\n'
fi

35
scripts/devin-bridge/common Executable file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail
BRIDGE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BRIDGE_COMPOSE="$BRIDGE_ROOT/docker/devin-bridge/compose.yml"
BRIDGE_SANDBOX="$BRIDGE_ROOT/.sandbox"
bridge_prepare_sandbox() {
mkdir -p "$BRIDGE_SANDBOX/home" "$BRIDGE_SANDBOX/test-data" \
"$BRIDGE_SANDBOX/e2e-workspace" "$BRIDGE_SANDBOX/live-workspace" \
"$BRIDGE_SANDBOX/evidence"
chmod 0777 "$BRIDGE_SANDBOX/e2e-workspace" "$BRIDGE_SANDBOX/live-workspace" \
"$BRIDGE_SANDBOX/evidence"
}
bridge_reset_e2e_fixture() {
bridge_prepare_sandbox
cp -R "$BRIDGE_ROOT/tests/fixtures/devin-bridge/e2e-workspace/." \
"$BRIDGE_SANDBOX/e2e-workspace/"
rm -f "$BRIDGE_SANDBOX/e2e-workspace/.e2e-hook.log" \
"$BRIDGE_SANDBOX/evidence/claude-stream.jsonl" \
"$BRIDGE_SANDBOX/evidence/mock-acp.jsonl"
}
bridge_reset_live_fixture() {
bridge_prepare_sandbox
cp -R "$BRIDGE_ROOT/tests/fixtures/devin-bridge/e2e-workspace/." \
"$BRIDGE_SANDBOX/live-workspace/"
rm -f "$BRIDGE_SANDBOX/live-workspace/.e2e-hook.log" \
"$BRIDGE_SANDBOX/evidence/live-analysis.jsonl" \
"$BRIDGE_SANDBOX/evidence/live-fix.jsonl" \
"$BRIDGE_SANDBOX/evidence/live-command.jsonl" \
"$BRIDGE_SANDBOX/evidence/live-models.json" \
"$BRIDGE_SANDBOX/evidence/egress.jsonl"
}
bridge_test_env() {
bridge_prepare_sandbox
env HOME="$BRIDGE_SANDBOX/home" DATA_DIR="$BRIDGE_SANDBOX/test-data" SQLITE_FILE="$BRIDGE_SANDBOX/test-data/storage.sqlite" DEVIN_AGENTIC_HOME="$BRIDGE_SANDBOX/home" "$@"
}

View File

@@ -1,13 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ "${ENABLE_LIVE_DEVIN_TESTS:-}" != "1" && "${DEVIN_BRIDGE_OFFLINE:-}" != "1" ]]; then
printf 'Set DEVIN_BRIDGE_OFFLINE=1 for mock/offline mode or ENABLE_LIVE_DEVIN_TESTS=1 for live Devin.\n' >&2
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
"$SCRIPT_DIR/verify-anthropic-isolation"
printf 'Bridge launch preflight passed. Start Claude Code only inside the isolated container/profile.\n'
source "$(dirname "$0")/common"
bridge_prepare_sandbox
"$(dirname "$0")/verify-anthropic-isolation"
docker compose -f "$BRIDGE_COMPOSE" --profile offline --profile live-devin down --remove-orphans
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin up -d network-guard
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin run --rm --no-deps omniroute-live \
devin auth status
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin run --rm --no-deps omniroute-live \
devin models list --format json >"$BRIDGE_SANDBOX/evidence/live-models.json"
devin_model="$(node --import tsx/esm "$BRIDGE_ROOT/scripts/devin-bridge/select-live-model.mjs" \
<"$BRIDGE_SANDBOX/evidence/live-models.json")"
export DEVIN_BRIDGE_MODEL="devin-cli-agentic/$devin_model"
export DEVIN_BRIDGE_SONNET_MODEL="${DEVIN_BRIDGE_SONNET_MODEL:-$DEVIN_BRIDGE_MODEL}"
export DEVIN_BRIDGE_OPUS_MODEL="${DEVIN_BRIDGE_OPUS_MODEL:-$DEVIN_BRIDGE_MODEL}"
export DEVIN_BRIDGE_HAIKU_MODEL="${DEVIN_BRIDGE_HAIKU_MODEL:-$DEVIN_BRIDGE_MODEL}"
export DEVIN_BRIDGE_SUBAGENT_MODEL="${DEVIN_BRIDGE_SUBAGENT_MODEL:-$DEVIN_BRIDGE_MODEL}"
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin up -d --wait omniroute-live
exec docker compose -f "$BRIDGE_COMPOSE" --profile live-devin run --rm --no-deps \
claude-live claude

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/common"
[[ "${ENABLE_LIVE_DEVIN_TESTS:-}" == 1 ]] || { echo 'Set ENABLE_LIVE_DEVIN_TESTS=1' >&2; exit 1; }
bridge_prepare_sandbox
docker compose -f "$BRIDGE_COMPOSE" --profile offline --profile live-devin down --remove-orphans
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin up -d network-guard
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin run --rm --no-deps omniroute-live \
devin auth login
exec env ENABLE_LIVE_DEVIN_TESTS=1 "$(dirname "$0")/test-live-devin"

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env node
import fs from "node:fs";
import { DEVIN_MODEL_CATALOG } from "../../open-sse/config/providers/registry/devin/catalog.ts";
const document = JSON.parse(fs.readFileSync(0, "utf8"));
const candidates = [];
function collect(value) {
if (Array.isArray(value)) {
value.forEach(collect);
return;
}
if (!value || typeof value !== "object") return;
for (const [key, nested] of Object.entries(value)) {
if (
typeof nested === "string" &&
["id", "model", "model_id", "slug"].includes(key.toLowerCase()) &&
/^[a-z0-9][a-z0-9._/-]*$/i.test(nested)
) {
candidates.push(nested);
}
collect(nested);
}
}
collect(document);
const unique = [...new Set(candidates)];
const catalogIds = new Set(DEVIN_MODEL_CATALOG.map((entry) => entry.id));
const available = unique.filter((candidate) => catalogIds.has(candidate));
for (const [name, configured] of [
["DEVIN_BRIDGE_SONNET_MODEL", process.env.DEVIN_BRIDGE_SONNET_MODEL],
["DEVIN_BRIDGE_OPUS_MODEL", process.env.DEVIN_BRIDGE_OPUS_MODEL],
["DEVIN_BRIDGE_HAIKU_MODEL", process.env.DEVIN_BRIDGE_HAIKU_MODEL],
["DEVIN_BRIDGE_SUBAGENT_MODEL", process.env.DEVIN_BRIDGE_SUBAGENT_MODEL],
]) {
if (!configured) continue;
const prefix = "devin-cli-agentic/";
const modelId = configured.startsWith(prefix) ? configured.slice(prefix.length) : "";
if (!modelId || !available.includes(modelId)) {
throw new Error(`${name} is not a model returned by Devin and present in OmniRoute`);
}
}
const selected =
available.find((candidate) => candidate === "swe-1-7") ||
available.find((candidate) => /swe|claude|gpt|gemini/i.test(candidate)) ||
available[0];
if (!selected) {
throw new Error("Devin returned no model identifier present in OmniRoute's Devin catalog");
}
process.stdout.write(selected);

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/common"
bridge_prepare_sandbox
rm -f "$BRIDGE_SANDBOX/evidence/mock-acp.jsonl"
"$(dirname "$0")/verify-anthropic-isolation" --static
docker compose -f "$BRIDGE_COMPOSE" --profile offline down --remove-orphans
docker compose -f "$BRIDGE_COMPOSE" --profile offline up --abort-on-container-exit \
--exit-code-from contract contract
node -e '
const fs = require("node:fs");
const rows = fs.readFileSync(process.argv[1], "utf8").trim().split("\n").map(JSON.parse);
if (rows.length !== 5 || rows.some((row) => row.provider !== "devin-cli-agentic")) {
throw new Error("wire contract observed a missing or non-Devin provider");
}
' "$BRIDGE_SANDBOX/evidence/mock-acp.jsonl"
printf 'PASS: bridge wire contract suite completed without provider fallback\n'

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/common"
bridge_reset_e2e_fixture
"$(dirname "$0")/verify-anthropic-isolation" --static
docker compose -f "$BRIDGE_COMPOSE" --profile offline down --remove-orphans
docker compose -f "$BRIDGE_COMPOSE" --profile offline up --abort-on-container-exit \
--exit-code-from claude claude
grep -q '"action":"final"' "$BRIDGE_SANDBOX/evidence/mock-acp.jsonl"
grep -q 'BRIDGE_E2E_COMPLETE' "$BRIDGE_SANDBOX/evidence/claude-stream.jsonl"
printf 'PASS: real Claude Code completed the offline agentic fixture\n'

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/common"
[[ "${ENABLE_LIVE_DEVIN_TESTS:-}" == 1 ]] || { echo 'Set ENABLE_LIVE_DEVIN_TESTS=1' >&2; exit 1; }
bridge_reset_live_fixture
"$(dirname "$0")/verify-anthropic-isolation" --static
docker compose -f "$BRIDGE_COMPOSE" --profile offline --profile live-devin down --remove-orphans
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin up -d network-guard
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin run --rm --no-deps omniroute-live \
devin auth status
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin run --rm --no-deps omniroute-live \
devin models list --format json >"$BRIDGE_SANDBOX/evidence/live-models.json"
devin_model="$(node --import tsx/esm "$BRIDGE_ROOT/scripts/devin-bridge/select-live-model.mjs" \
<"$BRIDGE_SANDBOX/evidence/live-models.json")"
export DEVIN_BRIDGE_MODEL="devin-cli-agentic/$devin_model"
export DEVIN_BRIDGE_SONNET_MODEL="$DEVIN_BRIDGE_MODEL"
export DEVIN_BRIDGE_OPUS_MODEL="$DEVIN_BRIDGE_MODEL"
export DEVIN_BRIDGE_HAIKU_MODEL="$DEVIN_BRIDGE_MODEL"
export DEVIN_BRIDGE_SUBAGENT_MODEL="$DEVIN_BRIDGE_MODEL"
docker compose -f "$BRIDGE_COMPOSE" --profile live-devin up --abort-on-container-exit --exit-code-from claude-live claude-live
node -e '
const fs = require("node:fs");
const path = process.argv[1];
if (!fs.existsSync(path)) process.exit(0);
for (const line of fs.readFileSync(path, "utf8").trim().split("\n").filter(Boolean)) {
const entry = JSON.parse(line);
if (entry.decision === "allow" && !/(^|\.)(devin\.ai|cognition\.ai)$/.test(entry.hostname)) {
throw new Error(`unexpected allowed egress: ${entry.hostname}`);
}
}
' "$BRIDGE_SANDBOX/evidence/egress.jsonl"
printf 'PASS: live model %s was discovered and validated by three scenarios\n' "$devin_model"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
node --import tsx/esm --test \
source "$(dirname "$0")/common"
cd "$BRIDGE_ROOT"
bridge_test_env node --import tsx/esm --test \
tests/unit/executor-devin-cli-agentic-core.test.ts \
tests/unit/executor-devin-cli-agentic-acp.test.ts

View File

@@ -1,43 +1,71 @@
#!/usr/bin/env bash
set -euo pipefail
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
case "${CLAUDE_CONFIG_DIR:-}" in
/home/bridge/.claude-devin-isolated|*/.sandbox/*|*/claude-devin-isolated*)
;;
"")
fail "CLAUDE_CONFIG_DIR is not set"
;;
*)
fail "CLAUDE_CONFIG_DIR must point at an isolated bridge directory"
;;
esac
for key in \
ANTHROPIC_API_KEY \
CLAUDE_CODE_OAUTH_TOKEN \
ANTHROPIC_BEDROCK_BASE_URL \
ANTHROPIC_VERTEX_BASE_URL \
CLAUDE_CODE_USE_BEDROCK \
CLAUDE_CODE_USE_VERTEX \
CLAUDE_CODE_USE_FOUNDRY
do
if [[ -n "${!key:-}" ]]; then
fail "$key must be unset"
fi
source "$(dirname "$0")/common"
fail() { printf 'FAIL: %s\n' "$1" >&2; exit 1; }
bridge_prepare_sandbox
config="$(docker compose -f "$BRIDGE_COMPOSE" --profile offline --profile live-devin config)"
config_json="$(docker compose -f "$BRIDGE_COMPOSE" --profile offline --profile live-devin config --format json)"
for forbidden in "$HOME/.claude" "$HOME/.claude.json" "$HOME/.ssh" "/var/run/docker.sock"; do
[[ "$config" != *"$forbidden"* ]] || fail "forbidden host mount appears in compose: $forbidden"
done
if [[ "${ANTHROPIC_BASE_URL:-}" != "http://omniroute:20128" && "${ANTHROPIC_BASE_URL:-}" != http://127.0.0.1:* && "${ANTHROPIC_BASE_URL:-}" != http://localhost:* ]]; then
fail "ANTHROPIC_BASE_URL must target local OmniRoute"
fi
if [[ "${ANTHROPIC_AUTH_TOKEN:-}" != sk-local-* ]]; then
fail "ANTHROPIC_AUTH_TOKEN must be a local bridge token"
fi
printf 'PASS: Anthropic isolation environment looks local and isolated\n'
grep -q 'user: 10001:10001' <<<"$config" || fail "runtime is not non-root"
grep -q 'read_only: true' <<<"$config" || fail "runtime root filesystem is not read-only"
grep -q 'internal: true' <<<"$config" || fail "internal network is missing"
grep -q 'CLAUDE_CONFIG_DIR: /home/bridge/.claude-devin-isolated' <<<"$config" || fail "isolated Claude config is missing"
node -e '
const fs = require("node:fs");
const config = JSON.parse(fs.readFileSync(0, "utf8"));
const liveNetworks = Object.keys(config.services["omniroute-live"].networks || {}).sort();
if (JSON.stringify(liveNetworks) !== JSON.stringify(["bridge-internal"])) {
throw new Error(`live runtime network escape: ${liveNetworks.join(",")}`);
}
const guardNetworks = Object.keys(config.services["network-guard"].networks || {}).sort();
if (JSON.stringify(guardNetworks) !== JSON.stringify(["bridge-internal", "guard-egress"])) {
throw new Error(`network guard topology mismatch: ${guardNetworks.join(",")}`);
}
if (config.services["network-guard"].environment.GUARD_ALLOW_SUFFIXES !== ".devin.ai,.cognition.ai") {
throw new Error("network guard allowlist mismatch");
}
for (const serviceName of ["omniroute", "claude", "contract", "omniroute-live", "claude-live"]) {
const service = config.services[serviceName];
if (String(service.user) !== "10001:10001" || !service.read_only) {
throw new Error(`${serviceName} is not non-root and read-only`);
}
const configMount = (service.volumes || []).find(
(mount) => mount.target === "/home/bridge/.claude-devin-isolated",
);
if (!configMount || configMount.type !== "volume") {
throw new Error(`${serviceName} does not use the isolated named Claude volume`);
}
for (const key of [
"ANTHROPIC_MODEL",
"ANTHROPIC_DEFAULT_SONNET_MODEL",
"ANTHROPIC_DEFAULT_OPUS_MODEL",
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
"CLAUDE_CODE_SUBAGENT_MODEL",
]) {
if (!String(service.environment[key] || "").startsWith("devin-cli-agentic/")) {
throw new Error(`${serviceName} has a non-Devin model alias in ${key}`);
}
}
}
' <<<"$config_json" || fail "structured compose isolation checks failed"
if [[ "${1:-}" == --static ]]; then printf 'PASS: static bridge isolation checks passed\n'; exit 0; fi
docker compose -f "$BRIDGE_COMPOSE" --profile offline run --rm --no-deps claude bash -ceu '
test "$(id -u)" = 10001
test "$HOME" = /home/bridge
test "$CLAUDE_CONFIG_DIR" = /home/bridge/.claude-devin-isolated
test "$ANTHROPIC_BASE_URL" = http://omniroute:20128
test "$ANTHROPIC_AUTH_TOKEN" = sk-local-devin-gateway
test -z "${ANTHROPIC_API_KEY:-}${CLAUDE_CODE_OAUTH_TOKEN:-}${AWS_ACCESS_KEY_ID:-}${AWS_SECRET_ACCESS_KEY:-}${GOOGLE_APPLICATION_CREDENTIALS:-}${AZURE_OPENAI_API_KEY:-}"
test ! -e /var/run/docker.sock
if touch /bridge-must-remain-read-only 2>/dev/null; then
echo "container root filesystem is writable" >&2; exit 1
fi
for host in api.anthropic.com claude.ai; do
if node -e "require(\"net\").connect(443,process.argv[1]).on(\"connect\",()=>process.exit(0)).on(\"error\",()=>process.exit(1)).setTimeout(1500,()=>process.exit(1))" "$host"; then
echo "unexpected network access to $host" >&2; exit 1
fi
done
'
printf 'PASS: runtime bridge isolation checks passed\n'

View File

@@ -0,0 +1,9 @@
---
description: Exercise the isolated Claude-to-Devin agentic bridge
allowed-tools: Skill, Read, Edit, Bash
---
`COMMAND_BRIDGE_ACTIVE`
Use the bridge-proof skill. Locate and read the implementation, correct it, run its test, diagnose
the intentionally failed first correction, fix it, rerun the test, and report completion.

View File

@@ -0,0 +1,6 @@
import fs from "node:fs";
let input = "";
for await (const chunk of process.stdin) input += chunk;
const event = JSON.parse(input || "{}");
fs.appendFileSync("/workspace/.e2e-hook.log", `${String(event.tool_name || "unknown")}\n`);

View File

@@ -0,0 +1,16 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Skill|Read|Edit|Bash",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/log-tool.mjs",
"timeout": 10
}
]
}
]
}
}

View File

@@ -0,0 +1,9 @@
---
name: bridge-proof
description: This skill should be used when the user invokes the bridge-check command or asks to verify the isolated Devin bridge.
version: 1.0.0
---
`SKILL_BRIDGE_ACTIVE`
Use project-local tools to inspect, edit, and test the fixture. Do not merely narrate actions.

View File

@@ -0,0 +1,7 @@
# Offline bridge fixture
`CLAUDE_MD_BRIDGE_ACTIVE`
Use the `bridge-proof` project skill for the `/bridge-check` task. Inspect the project, repair
`math.js`, and run the tests. A failed first attempt must be diagnosed and corrected. Finish only
after the tests pass.

View File

@@ -0,0 +1,3 @@
export function add(a, b) {
return a - b;
}

View File

@@ -0,0 +1,8 @@
import assert from "node:assert/strict";
import test from "node:test";
import { add } from "./math.js";
test("add returns the sum", () => {
assert.equal(add(2, 3), 5);
});

View File

@@ -0,0 +1,8 @@
{
"name": "devin-bridge-e2e-fixture",
"private": true,
"type": "module",
"scripts": {
"test": "node --test math.test.js"
}
}