diff --git a/.env.devin-bridge.example b/.env.devin-bridge.example
new file mode 100644
index 0000000000..fb02ecd66b
--- /dev/null
+++ b/.env.devin-bridge.example
@@ -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
diff --git a/.gitignore b/.gitignore
index 045656a5ff..ec10295b10 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/docker/devin-bridge/Dockerfile b/docker/devin-bridge/Dockerfile
new file mode 100644
index 0000000000..3fd2a30dba
--- /dev/null
+++ b/docker/devin-bridge/Dockerfile
@@ -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"]
diff --git a/docker/devin-bridge/compose.yml b/docker/devin-bridge/compose.yml
new file mode 100644
index 0000000000..36b164b684
--- /dev/null
+++ b/docker/devin-bridge/compose.yml
@@ -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: {}
diff --git a/docker/devin-bridge/mock-devin.mjs b/docker/devin-bridge/mock-devin.mjs
new file mode 100755
index 0000000000..28919969ed
--- /dev/null
+++ b/docker/devin-bridge/mock-devin.mjs
@@ -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: '{"name":"Read","arguments":{"file_path":"/workspace/math.js"}}',
+ },
+ },
+ },
+ });
+ 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
+ ? `${JSON.stringify(action)}`
+ : "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" } });
+ }
+});
diff --git a/docker/devin-bridge/network-guard/proxy.mjs b/docker/devin-bridge/network-guard/proxy.mjs
new file mode 100644
index 0000000000..88f7409ea5
--- /dev/null
+++ b/docker/devin-bridge/network-guard/proxy.mjs
@@ -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);
diff --git a/docker/devin-bridge/run-claude-e2e.sh b/docker/devin-bridge/run-claude-e2e.sh
new file mode 100755
index 0000000000..726aff33a2
--- /dev/null
+++ b/docker/devin-bridge/run-claude-e2e.sh
@@ -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
diff --git a/docker/devin-bridge/run-claude-live-e2e.sh b/docker/devin-bridge/run-claude-live-e2e.sh
new file mode 100644
index 0000000000..27a2c853f5
--- /dev/null
+++ b/docker/devin-bridge/run-claude-live-e2e.sh
@@ -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'
diff --git a/docker/devin-bridge/run-contract.mjs b/docker/devin-bridge/run-contract.mjs
new file mode 100644
index 0000000000..b78cb2d510
--- /dev/null
+++ b/docker/devin-bridge/run-contract.mjs
@@ -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");
diff --git a/scripts/devin-bridge/build b/scripts/devin-bridge/build
new file mode 100755
index 0000000000..abb309d453
--- /dev/null
+++ b/scripts/devin-bridge/build
@@ -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
diff --git a/scripts/devin-bridge/clean b/scripts/devin-bridge/clean
new file mode 100755
index 0000000000..1fc195aadb
--- /dev/null
+++ b/scripts/devin-bridge/clean
@@ -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
diff --git a/scripts/devin-bridge/common b/scripts/devin-bridge/common
new file mode 100755
index 0000000000..67c452ee5b
--- /dev/null
+++ b/scripts/devin-bridge/common
@@ -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" "$@"
+}
diff --git a/scripts/devin-bridge/launch b/scripts/devin-bridge/launch
index 7e8fab8eda..7f6b5c2475 100755
--- a/scripts/devin-bridge/launch
+++ b/scripts/devin-bridge/launch
@@ -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
diff --git a/scripts/devin-bridge/login-devin b/scripts/devin-bridge/login-devin
new file mode 100755
index 0000000000..30a9387d30
--- /dev/null
+++ b/scripts/devin-bridge/login-devin
@@ -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"
diff --git a/scripts/devin-bridge/select-live-model.mjs b/scripts/devin-bridge/select-live-model.mjs
new file mode 100644
index 0000000000..145b8fd513
--- /dev/null
+++ b/scripts/devin-bridge/select-live-model.mjs
@@ -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);
diff --git a/scripts/devin-bridge/test-contract b/scripts/devin-bridge/test-contract
new file mode 100755
index 0000000000..c1749f66e5
--- /dev/null
+++ b/scripts/devin-bridge/test-contract
@@ -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'
diff --git a/scripts/devin-bridge/test-e2e-mock b/scripts/devin-bridge/test-e2e-mock
new file mode 100755
index 0000000000..14d2e9d8b8
--- /dev/null
+++ b/scripts/devin-bridge/test-e2e-mock
@@ -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'
diff --git a/scripts/devin-bridge/test-live-devin b/scripts/devin-bridge/test-live-devin
new file mode 100755
index 0000000000..e5734306b5
--- /dev/null
+++ b/scripts/devin-bridge/test-live-devin
@@ -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"
diff --git a/scripts/devin-bridge/test-unit b/scripts/devin-bridge/test-unit
index e938810d7f..5e684724cf 100755
--- a/scripts/devin-bridge/test-unit
+++ b/scripts/devin-bridge/test-unit
@@ -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
-
diff --git a/scripts/devin-bridge/verify-anthropic-isolation b/scripts/devin-bridge/verify-anthropic-isolation
index bdaf38d971..97931bcaf9 100755
--- a/scripts/devin-bridge/verify-anthropic-isolation
+++ b/scripts/devin-bridge/verify-anthropic-isolation
@@ -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'
diff --git a/tests/fixtures/devin-bridge/e2e-workspace/.claude/commands/bridge-check.md b/tests/fixtures/devin-bridge/e2e-workspace/.claude/commands/bridge-check.md
new file mode 100644
index 0000000000..d474a5642e
--- /dev/null
+++ b/tests/fixtures/devin-bridge/e2e-workspace/.claude/commands/bridge-check.md
@@ -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.
diff --git a/tests/fixtures/devin-bridge/e2e-workspace/.claude/hooks/log-tool.mjs b/tests/fixtures/devin-bridge/e2e-workspace/.claude/hooks/log-tool.mjs
new file mode 100644
index 0000000000..5bf44abd6e
--- /dev/null
+++ b/tests/fixtures/devin-bridge/e2e-workspace/.claude/hooks/log-tool.mjs
@@ -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`);
diff --git a/tests/fixtures/devin-bridge/e2e-workspace/.claude/settings.json b/tests/fixtures/devin-bridge/e2e-workspace/.claude/settings.json
new file mode 100644
index 0000000000..a27b989a5e
--- /dev/null
+++ b/tests/fixtures/devin-bridge/e2e-workspace/.claude/settings.json
@@ -0,0 +1,16 @@
+{
+ "hooks": {
+ "PreToolUse": [
+ {
+ "matcher": "Skill|Read|Edit|Bash",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "node .claude/hooks/log-tool.mjs",
+ "timeout": 10
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/tests/fixtures/devin-bridge/e2e-workspace/.claude/skills/bridge-proof/SKILL.md b/tests/fixtures/devin-bridge/e2e-workspace/.claude/skills/bridge-proof/SKILL.md
new file mode 100644
index 0000000000..292a032b92
--- /dev/null
+++ b/tests/fixtures/devin-bridge/e2e-workspace/.claude/skills/bridge-proof/SKILL.md
@@ -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.
diff --git a/tests/fixtures/devin-bridge/e2e-workspace/CLAUDE.md b/tests/fixtures/devin-bridge/e2e-workspace/CLAUDE.md
new file mode 100644
index 0000000000..306607468f
--- /dev/null
+++ b/tests/fixtures/devin-bridge/e2e-workspace/CLAUDE.md
@@ -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.
diff --git a/tests/fixtures/devin-bridge/e2e-workspace/math.js b/tests/fixtures/devin-bridge/e2e-workspace/math.js
new file mode 100644
index 0000000000..29b385181d
--- /dev/null
+++ b/tests/fixtures/devin-bridge/e2e-workspace/math.js
@@ -0,0 +1,3 @@
+export function add(a, b) {
+ return a - b;
+}
diff --git a/tests/fixtures/devin-bridge/e2e-workspace/math.test.js b/tests/fixtures/devin-bridge/e2e-workspace/math.test.js
new file mode 100644
index 0000000000..bc07a818bc
--- /dev/null
+++ b/tests/fixtures/devin-bridge/e2e-workspace/math.test.js
@@ -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);
+});
diff --git a/tests/fixtures/devin-bridge/e2e-workspace/package.json b/tests/fixtures/devin-bridge/e2e-workspace/package.json
new file mode 100644
index 0000000000..9c983c74eb
--- /dev/null
+++ b/tests/fixtures/devin-bridge/e2e-workspace/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "devin-bridge-e2e-fixture",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "test": "node --test math.test.js"
+ }
+}