de65991807
Teaches the OpenAI-format lane (Groq/OpenAI/Grok/Gemini/Ollama) to offer tools,
execute them, and loop — the capability that until now existed only on the
Anthropic wire. The tool-execution, consent, bridge and run-progress machinery is
reused unchanged; only the wire dialect is new.
Two pre-existing defects were found while proving it, and are fixed here because
both silently break chat:
1. PROVIDER WIRING NEVER CONNECTED. The launcher exports SOUL_LLM_PROVIDER /
SOUL_LLM_BASE_URL and puts the provider key in ANTHROPIC_API_KEY + SOUL_API_KEY;
the engine's provider fork read only NEURON_LLM_0_*, which nothing sets in a
customer build. So use_openai was ALWAYS false: every non-Anthropic user's turns
went to api.anthropic.com carrying, say, a Groq key, and came back
"llm unavailable". Proven side-by-side against the pinned round-9 brain
(sha256 15cf7d1b…): identical env, shipped brain = "llm unavailable" both chat
modes with ZERO calls to the configured endpoint; this build = a real answer,
with the probe logging POST /v1/chat/completions and Bearer <provider key>.
Fixed brain-side only (env fallbacks) — no app or launcher change needed.
2. TRUNCATION SPLITS UTF-8 CHARACTERS. The session preload cuts recalled memory at
fixed BYTE lengths (continuity snippet 350; session_preload_bullets per bullet).
A cut landing inside a multi-byte character leaves a dangling lead byte in the
SYSTEM PROMPT, making the whole request body invalid UTF-8 — providers reject it
and the user sees an unexplained failure. Captured from a real body: 18,710 bytes,
decode fails at 18,248 on 'e2', a box-drawing rule (U+2500 = E2 94 80) sliced in
half. Trigger is ordinary content — em dash, curly quote, accented name, emoji,
table border — and it gets MORE likely as memory grows. Shared code: this hit the
Anthropic wire too. Fixed with utf8_safe_slice() applied at BOTH cut sites.
WHAT IS IN THE PORT
- llm_base_url / llm_wire_format / agentic_api_key: fall back to the launcher's own
SOUL_LLM_* names; anthropic deliberately still returns "" so its native path is
untouched (endpoint configurability remains neuron#62).
- openai_tools_json(): Anthropic tool schema -> OpenAI function schema; entries with
no input_schema (Anthropic's server-side web_search) are skipped — they cannot
execute on this wire.
- agentic_tools_no_web(): the standard set minus that server tool.
- openai_agentic_loop(): forked rather than parameterised, so agentic_loop — which
carries every round-7/8/9 fix — is provably untouched. Same envelopes, same state
keys, same consent policy (ask_all / escalate / builtin / always-allow), same
client-bridge contract, same run-progress ledger, same 12-iteration cap.
- ADR-0005 mirrored on this wire: parallel_tool_calls:false is sent explicitly, and
if a provider ignores it we honour the FIRST call and echo only that one, so the
conversation we send is never self-contradictory. The drop is logged loudly.
- The assistant turn echoes the provider's own content bytes (json_get_raw), so a
JSON null stays null and nothing is lost to a decode/re-encode round trip.
- Tool results are embedded already-escaped (dispatch_tool json_safe's them);
truncation trims a dangling escape so a cut can't invalidate the body.
- bridge_save() gains a "wire" scalar and agentic_resume branches on it, so a
suspended turn resumes on the wire it suspended on. Legacy blobs (no field) resume
as anthropic. The field is read from the blob's SCALAR HEAD only — an unbounded
first-match scan would run on into messages_raw, which is model-controlled, and
that is exactly the round-9 resume defect. Pinned by a test.
- Three fork sites: handle_chat_agentic, handle_dharma_room_turn_agentic,
agentic_resume. Tool assembly is computed once per lane at both entry points
(it makes an HTTP call to the connector bridge; it was being paid for twice).
TOOLING THAT DID NOT EXIST
- tests/run-el-test.sh — engine tests were never runnable: elc is a compiler, it
emits C and exits. This emits the test to C, compiles soul.c with main renamed
away, links the rest + the repo-pinned runtime, and runs it. It also COMPUTES THE
VERDICT, because every counted test file's "N passed, M failed" summary is a
permanent 0/0 — the counters increment inside if BLOCKS, which El scoping
discards (9 files; real fix filed as neuron#116). Proven to discriminate with a
deliberately-broken assertion.
- tests/gate-openai/ — deterministic OpenAI-dialect provider stub + scenarios +
driver + hostile modes, and a strict request validator that rejects any
Anthropic-shaped field so dialect leakage fails loudly.
VERIFICATION (rungs named)
- E2E-VERIFIED against a LIVE provider (Anthropic's OpenAI-compatible endpoint,
confirmed live): real answer; a tool call whose out-of-root path was DENIED by the
guard, after which the model refused to claim success ("I won't tell you I did it,
because I didn't"); then a valid path -> file physically on disk with exact content,
honest reply, ledger with per-round entries + {done:true}.
- Deterministic lane gate: 11/12 in both consent configurations (bridge + local);
hostile providers produce no hang and no fabricated answer; the 12-iteration cap
trips with its honest message. The one FAIL is oa-tools-off and is NOT this port —
see "Known, not fixed here".
- ANTHROPIC LANE UNCHANGED: gate9 32/32 on this build and on the pinned round-9
brain; request bytes differ only within the noise band that two runs of the
UNMODIFIED brain also produce (proven with a baseline-vs-baseline control), and
the preload sections — the shared code touched here — are byte-identical.
The rig discriminates: the round-8 brain scores 24/32 on it.
- verify-soul-contract.sh: PASS (27/27 routes, no hard-deletes).
- Unit: test_bridge_serialization 36/36 (incl. 8 new wire/field-order assertions),
test_utf8_slice 18/18, test_agentic_tools 18 PASS / 0 FAIL / 3 documented skips.
KNOWN, NOT FIXED HERE (deliberate)
- Tools:Off on an OpenAI provider still fails: the non-agentic path goes through the
el-runtime provider chain, which appends /v1/chat/completions to a base URL that
already ends in /v1 -> /v1/v1/... 404. Runtime/plain-chat territory, untouched
mid-beta. Note openai_chat_complete() has zero callers — that lane is served
entirely by the runtime chain.
- The 12-iteration cap does not bound a chain of BRIDGED tools (iteration is
per-invocation and resume starts fresh). Parity with the Anthropic lane.
- run_progress resets on each resume, so a client rendering cumulative steps across a
consent pause sees earlier legs vanish. Parity with the Anthropic lane.
- verify-soul-contract.sh needs bash >= 4; under macOS's stock bash 3.2 it dies
instantly with a FALSE red ("local: -n: invalid option").
- Groq-specific live E2E not run: no Groq key exists on this machine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
363 lines
18 KiB
EmacsLisp
363 lines
18 KiB
EmacsLisp
// ── test_bridge_serialization.el ──────────────────────────────────────────────
|
|
//
|
|
// Tests for PR #20 fix/bridge-save-serialization:
|
|
// - bridge_save raw JSON serialization (BLOCKER 1 & 2 regression guards)
|
|
// - agentic_resume error-path handling
|
|
// - Legacy fallback: old string-escaped fields still readable
|
|
// - Corrupt/missing bridge state error envelope
|
|
// - Empty messages/tools_json guard in bridge_save
|
|
//
|
|
// What CANNOT be tested here without a live Anthropic API:
|
|
// - agentic_resume golden-path (calls agentic_loop which hits the API)
|
|
// - Full save/resume round-trip with a real tool_result
|
|
//
|
|
// To run:
|
|
// elc chat.el && ./soul --test tests/test_bridge_serialization.el
|
|
//
|
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
import "../chat.el"
|
|
|
|
// ── Test harness ──────────────────────────────────────────────────────────────
|
|
|
|
let pass_count: Int = 0
|
|
let fail_count: Int = 0
|
|
|
|
fn assert_eq(label: String, got: String, expected: String) -> Void {
|
|
if str_eq(got, expected) {
|
|
let pass_count = pass_count + 1
|
|
println(" PASS: " + label)
|
|
} else {
|
|
let fail_count = fail_count + 1
|
|
println(" FAIL: " + label)
|
|
println(" got: " + got)
|
|
println(" expected: " + expected)
|
|
}
|
|
}
|
|
|
|
fn assert_true(label: String, cond: Bool) -> Void {
|
|
if cond {
|
|
let pass_count = pass_count + 1
|
|
println(" PASS: " + label)
|
|
} else {
|
|
let fail_count = fail_count + 1
|
|
println(" FAIL: " + label)
|
|
}
|
|
}
|
|
|
|
fn assert_false(label: String, cond: Bool) -> Void {
|
|
assert_true(label, !cond)
|
|
}
|
|
|
|
fn assert_contains(label: String, haystack: String, needle: String) -> Void {
|
|
if str_contains(haystack, needle) {
|
|
let pass_count = pass_count + 1
|
|
println(" PASS: " + label)
|
|
} else {
|
|
let fail_count = fail_count + 1
|
|
println(" FAIL: " + label)
|
|
println(" missing '" + needle + "' in: " + haystack)
|
|
}
|
|
}
|
|
|
|
fn assert_not_contains(label: String, haystack: String, needle: String) -> Void {
|
|
if str_contains(haystack, needle) {
|
|
let fail_count = fail_count + 1
|
|
println(" FAIL: " + label)
|
|
println(" unexpected '" + needle + "' found in: " + haystack)
|
|
} else {
|
|
let pass_count = pass_count + 1
|
|
println(" PASS: " + label)
|
|
}
|
|
}
|
|
|
|
fn assert_not_empty(label: String, s: String) -> Void {
|
|
if str_eq(s, "") {
|
|
let fail_count = fail_count + 1
|
|
println(" FAIL: " + label + " (got empty string)")
|
|
} else {
|
|
let pass_count = pass_count + 1
|
|
println(" PASS: " + label)
|
|
}
|
|
}
|
|
|
|
// ── Section 1: bridge_save — empty messages guard ─────────────────────────────
|
|
//
|
|
// BLOCKER 2 regression guard: bridge_save must refuse to write a blob when
|
|
// messages or tools_json is empty, as the resulting JSON would be syntactically
|
|
// invalid (bare colon with no value).
|
|
|
|
println("")
|
|
println("1. bridge_save — empty messages guard")
|
|
|
|
let sid1: String = "test-session-empty-messages"
|
|
state_set("mcp_bridge:" + sid1, "")
|
|
|
|
let save1_ok: Bool = bridge_save(sid1, "claude-sonnet-4-5", "sys", "[]", "", "", "call-1", "anthropic")
|
|
assert_false("empty messages -> bridge_save returns false", save1_ok)
|
|
|
|
let saved1: String = state_get("mcp_bridge:" + sid1)
|
|
assert_eq("empty messages -> no blob written to state", saved1, "")
|
|
|
|
// ── Section 2: bridge_save — empty tools_json guard ───────────────────────────
|
|
|
|
println("")
|
|
println("2. bridge_save — empty tools_json guard")
|
|
|
|
let sid2: String = "test-session-empty-tools"
|
|
state_set("mcp_bridge:" + sid2, "")
|
|
|
|
let save2_ok: Bool = bridge_save(sid2, "claude-sonnet-4-5", "sys", "", "[{\"role\":\"user\",\"content\":\"hi\"}]", "", "call-2", "anthropic")
|
|
assert_false("empty tools_json -> bridge_save returns false", save2_ok)
|
|
|
|
let saved2: String = state_get("mcp_bridge:" + sid2)
|
|
assert_eq("empty tools_json -> no blob written to state", saved2, "")
|
|
|
|
// ── Section 3: bridge_save — golden path writes raw JSON fields ───────────────
|
|
//
|
|
// Verifies that messages_raw and tools_raw are stored as inline JSON (not
|
|
// string-escaped) so that json_get_raw retrieves them without corruption.
|
|
|
|
println("")
|
|
println("3. bridge_save — golden path writes messages_raw and tools_raw as raw JSON")
|
|
|
|
let sid3: String = "test-session-golden"
|
|
state_set("mcp_bridge:" + sid3, "")
|
|
|
|
let msgs3: String = "[{\"role\":\"user\",\"content\":\"hello\"}]"
|
|
let tools3: String = "[{\"name\":\"read_file\"}]"
|
|
let save3_ok: Bool = bridge_save(sid3, "claude-sonnet-4-5", "You are a helper.", tools3, msgs3, "read_file", "toolu_abc", "anthropic")
|
|
assert_true("valid args -> bridge_save returns true", save3_ok)
|
|
|
|
let blob3: String = state_get("mcp_bridge:" + sid3)
|
|
assert_not_empty("valid args -> blob written to state", blob3)
|
|
|
|
// messages_raw should be stored as a raw JSON array (not a quoted string)
|
|
// so json_get_raw on the blob returns the array directly
|
|
let raw_msgs3: String = json_get_raw(blob3, "messages_raw")
|
|
assert_contains("messages_raw field present in blob", blob3, "messages_raw")
|
|
assert_eq("messages_raw round-trips without corruption", raw_msgs3, msgs3)
|
|
|
|
let raw_tools3: String = json_get_raw(blob3, "tools_raw")
|
|
assert_eq("tools_raw round-trips without corruption", raw_tools3, tools3)
|
|
|
|
// Scalar fields should still be present as normal string-escaped JSON fields
|
|
let model3: String = json_get(blob3, "model")
|
|
assert_eq("model field preserved in blob", model3, "claude-sonnet-4-5")
|
|
|
|
let tool_use_id3: String = json_get(blob3, "tool_use_id")
|
|
assert_eq("tool_use_id field preserved in blob", tool_use_id3, "toolu_abc")
|
|
|
|
// Verify the blob does NOT contain old-style double-escaped fields
|
|
assert_not_contains("no legacy 'messages' string field in new-format blob", blob3, "\"messages\":\"")
|
|
assert_not_contains("no legacy 'tools_json' string field in new-format blob", blob3, "\"tools_json\":\"")
|
|
|
|
// ── Section 4: agentic_resume — unknown session_id returns error envelope ──────
|
|
|
|
println("")
|
|
println("4. agentic_resume — unknown session_id (empty state)")
|
|
|
|
let sid4: String = "test-session-unknown-xyzzy"
|
|
state_set("mcp_bridge:" + sid4, "")
|
|
|
|
let resume4: String = agentic_resume(sid4, "toolu_xyz", "some result")
|
|
assert_contains("unknown session_id -> error field present", resume4, "\"error\"")
|
|
assert_contains("unknown session_id -> reply field present", resume4, "\"reply\"")
|
|
assert_contains("unknown session_id -> 'unknown session_id' message", resume4, "unknown session_id")
|
|
let reply4: String = json_get(resume4, "reply")
|
|
assert_eq("unknown session_id -> reply is empty string", reply4, "")
|
|
|
|
// ── Section 5: agentic_resume — syntactically invalid JSON in state ───────────
|
|
|
|
println("")
|
|
println("5. agentic_resume — syntactically invalid JSON blob in state")
|
|
|
|
let sid5: String = "test-session-corrupt-json"
|
|
// Write a non-JSON value that state_get would return as-is
|
|
state_set("mcp_bridge:" + sid5, "NOT_JSON_AT_ALL")
|
|
|
|
let resume5: String = agentic_resume(sid5, "toolu_xyz", "some result")
|
|
// The function may take multiple paths here; in all cases it must not crash and
|
|
// must return a JSON envelope with at least an error or empty reply field.
|
|
// When json_get_raw returns "" on unparseable input, the guard catches it.
|
|
assert_contains("corrupt JSON blob -> resume returns JSON", resume5, "\"reply\"")
|
|
|
|
// ── Section 6: agentic_resume — blob with no messages produces error envelope ─
|
|
|
|
println("")
|
|
println("6. agentic_resume — blob missing messages_raw and messages fields")
|
|
|
|
let sid6: String = "test-session-no-messages"
|
|
// Blob with only model/safe_sys — no messages or tools
|
|
state_set("mcp_bridge:" + sid6, "{\"model\":\"claude-sonnet-4-5\",\"safe_sys\":\"sys\",\"tool_use_id\":\"toolu_abc\"}")
|
|
|
|
let resume6: String = agentic_resume(sid6, "toolu_abc", "result")
|
|
assert_contains("missing messages -> error field present", resume6, "\"error\"")
|
|
assert_contains("missing messages -> error mentions corrupt state", resume6, "corrupt bridge state")
|
|
let reply6: String = json_get(resume6, "reply")
|
|
assert_eq("missing messages -> reply is empty string", reply6, "")
|
|
|
|
// ── Section 7: Legacy fallback — old-format blob (string-escaped fields) ──────
|
|
//
|
|
// BLOCKER 1 regression guard: sessions saved before the fix used 'messages'
|
|
// and 'tools_json' as string-escaped fields. The fallback path in agentic_resume
|
|
// must read them correctly. We verify the fallback resolves the correct values
|
|
// before the function reaches the api call (which we cannot make in tests).
|
|
//
|
|
// We test the fallback by writing a legacy blob and verifying that
|
|
// agentic_resume does NOT return the "corrupt bridge state" error
|
|
// (which would mean the fallback is broken), instead it gets past the guard
|
|
// and then fails on the API call (outside our test scope).
|
|
//
|
|
// NOTE: We cannot confirm a successful API-dependent round-trip in this test;
|
|
// the goal is only to confirm the state-reading fallback path resolves values.
|
|
|
|
println("")
|
|
println("7. Legacy fallback — old-format blob with string-escaped 'messages' field")
|
|
|
|
let sid7: String = "test-session-legacy-format"
|
|
// Simulate an old-format blob: messages and tools_json as json_safe-escaped strings.
|
|
// json_safe escapes " to \" so the stored value is a JSON string containing the array.
|
|
let legacy_msgs: String = "[{\"role\":\"user\",\"content\":\"legacy hello\"}]"
|
|
let legacy_tools: String = "[{\"name\":\"read_file\"}]"
|
|
// Build the blob the OLD way: string-escaped
|
|
let safe_msgs: String = json_safe(legacy_msgs)
|
|
let safe_tools: String = json_safe(legacy_tools)
|
|
let legacy_blob: String = "{\"model\":\"claude-sonnet-4-5\",\"safe_sys\":\"sys\",\"messages\":\"" + safe_msgs + "\",\"tools_json\":\"" + safe_tools + "\",\"tool_use_id\":\"toolu_legacy\"}"
|
|
state_set("mcp_bridge:" + sid7, legacy_blob)
|
|
|
|
let resume7: String = agentic_resume(sid7, "toolu_legacy", "legacy result")
|
|
// The fallback should successfully read the fields and NOT return "corrupt bridge state"
|
|
assert_not_contains("legacy blob -> no 'corrupt bridge state' error (fallback working)", resume7, "corrupt bridge state")
|
|
// It will fail on API call in test env, but should get past the state-reading guard
|
|
// Accept "unknown session_id" NOT happening - the blob was found, just API fails
|
|
|
|
// ── Section 8: bridge_save with tool_use_id containing special chars ──────────
|
|
|
|
println("")
|
|
println("8. bridge_save — tool_use_id with JSON-special characters is escaped")
|
|
|
|
let sid8: String = "test-session-special-chars"
|
|
state_set("mcp_bridge:" + sid8, "")
|
|
|
|
let special_id: String = "toolu_test\"quoted\""
|
|
let msgs8: String = "[{\"role\":\"user\",\"content\":\"hi\"}]"
|
|
let tools8: String = "[{\"name\":\"read_file\"}]"
|
|
let save8_ok: Bool = bridge_save(sid8, "claude-sonnet-4-5", "sys", tools8, msgs8, "", special_id, "anthropic")
|
|
assert_true("special chars in tool_use_id -> bridge_save returns true", save8_ok)
|
|
|
|
let blob8: String = state_get("mcp_bridge:" + sid8)
|
|
// The blob must be parseable (json_get succeeds on it)
|
|
let retrieved_id: String = json_get(blob8, "tool_use_id")
|
|
assert_eq("tool_use_id with quotes round-trips via json_safe", retrieved_id, special_id)
|
|
|
|
// ── Section 9: the "wire" field (OpenAI-tools port, 2026-08-06) ───────────────
|
|
//
|
|
// A suspended turn must resume on the SAME wire format it suspended on: an OpenAI-lane
|
|
// bridge answered with an Anthropic-shaped tool_result (or vice versa) is a dead run.
|
|
// bridge_save therefore stamps the blob with "wire", and agentic_resume branches on it.
|
|
//
|
|
// §9c is the important one. json_get is a first-substring-match scanner, so any key that
|
|
// appears inside the UNESCAPED conversation embedded in messages_raw can be matched
|
|
// instead of the blob's own field — that exact class of bug produced the round-9 resume
|
|
// failure (json_get(blob,"tool_use_id") matching a web_search_tool_result's id inside the
|
|
// replayed conversation). "wire" is written as a json_safe'd SCALAR ahead of both raw
|
|
// fields precisely so a decoy in model-controlled bytes can never win. This test plants
|
|
// that decoy on purpose. If someone later moves the field after messages_raw, this fails.
|
|
|
|
println("")
|
|
println("9. bridge_save — wire tagging and its field-order guarantee")
|
|
|
|
// 9a. an OpenAI-lane suspension round-trips as "openai"
|
|
let sid9: String = "test-session-wire-openai"
|
|
state_set("mcp_bridge:" + sid9, "")
|
|
let msgs9: String = "[{\"role\":\"user\",\"content\":\"hi\"}]"
|
|
let tools9: String = "[{\"name\":\"read_file\"}]"
|
|
let save9_ok: Bool = bridge_save(sid9, "llama-3.3-70b-versatile", "sys", tools9, msgs9, "", "call_abc", "openai")
|
|
assert_true("openai wire -> bridge_save returns true", save9_ok)
|
|
let blob9: String = state_get("mcp_bridge:" + sid9)
|
|
assert_eq("wire round-trips as openai", json_get(blob9, "wire"), "openai")
|
|
|
|
// 9b. an Anthropic-lane suspension round-trips as "anthropic"
|
|
let sid9b: String = "test-session-wire-anthropic"
|
|
state_set("mcp_bridge:" + sid9b, "")
|
|
let save9b_ok: Bool = bridge_save(sid9b, "claude-sonnet-4-5", "sys", tools9, msgs9, "", "toolu_abc", "anthropic")
|
|
assert_true("anthropic wire -> bridge_save returns true", save9b_ok)
|
|
let blob9b: String = state_get("mcp_bridge:" + sid9b)
|
|
assert_eq("wire round-trips as anthropic", json_get(blob9b, "wire"), "anthropic")
|
|
|
|
// 9c. FIELD-ORDER GUARD: a decoy "wire" inside the conversation must NOT be matched.
|
|
let sid9c: String = "test-session-wire-decoy"
|
|
state_set("mcp_bridge:" + sid9c, "")
|
|
let msgs9c: String = "[{\"role\":\"user\",\"content\":\"please save this literal text: \\\"wire\\\":\\\"anthropic\\\" end\"}]"
|
|
let save9c_ok: Bool = bridge_save(sid9c, "llama-3.3-70b-versatile", "sys", tools9, msgs9c, "", "call_decoy", "openai")
|
|
assert_true("decoy conversation -> bridge_save returns true", save9c_ok)
|
|
let blob9c: String = state_get("mcp_bridge:" + sid9c)
|
|
assert_eq("blob's own wire wins over a decoy planted in messages_raw", json_get(blob9c, "wire"), "openai")
|
|
|
|
// 9d. LEGACY blob (written before the port) has no wire field: json_get yields "",
|
|
// which agentic_resume treats as the Anthropic path — old suspensions still resume.
|
|
let sid9d: String = "test-session-wire-legacy"
|
|
let legacy_blob: String = "{\"model\":\"claude-sonnet-4-5\",\"safe_sys\":\"sys\",\"tools_log\":\"\""
|
|
+ ",\"tool_use_id\":\"toolu_legacy\",\"tools_raw\":[{\"name\":\"read_file\"}]"
|
|
+ ",\"messages_raw\":[{\"role\":\"user\",\"content\":\"hi\"}]}"
|
|
state_set("mcp_bridge:" + sid9d, legacy_blob)
|
|
let blob9d: String = state_get("mcp_bridge:" + sid9d)
|
|
assert_eq("legacy blob has no wire field -> empty (resumes as anthropic)", json_get(blob9d, "wire"), "")
|
|
assert_eq("legacy blob still reads its tool_use_id", json_get(blob9d, "tool_use_id"), "toolu_legacy")
|
|
|
|
// 9e. THE HARDER DECOY: a LEGACY blob (no wire field of its own) that carries the bytes
|
|
// of a wire tag deeper inside, where an unbounded first-match scan would find it and
|
|
// misroute the resume onto the wrong loop — the round-9 defect class exactly.
|
|
//
|
|
// WHAT IS AND IS NOT REACHABLE (measured here, not assumed — an earlier version of this
|
|
// test asserted the wrong thing and was corrected by running it):
|
|
// * NOT reachable from ordinary conversation TEXT. Any quote a user or model writes is
|
|
// backslash-escaped when it is serialized into the blob, so prose containing
|
|
// "wire":"openai" is stored as \"wire\":\"openai\" and does not match a scan for the
|
|
// unescaped key. §9f pins that.
|
|
// * REACHABLE from STRUCTURAL keys, which are embedded raw. Conversation and tool
|
|
// objects keep real quotes — that is precisely how round 9's scan found a
|
|
// web_search_tool_result's tool_use_id. A connector-supplied tool schema or a future
|
|
// message field literally named "wire" would be found the same way.
|
|
// The bound removes the whole class rather than reasoning about which keys exist today.
|
|
let sid9e: String = "test-session-wire-legacy-decoy"
|
|
let decoy_blob: String = "{\"model\":\"claude-sonnet-4-5\",\"safe_sys\":\"sys\",\"tools_log\":\"\""
|
|
+ ",\"tool_use_id\":\"toolu_legacy\""
|
|
+ ",\"tools_raw\":[{\"name\":\"read_file\",\"wire\":\"openai\"}]"
|
|
+ ",\"messages_raw\":[{\"role\":\"user\",\"content\":\"hi\"}]}"
|
|
state_set("mcp_bridge:" + sid9e, decoy_blob)
|
|
let blob9e: String = state_get("mcp_bridge:" + sid9e)
|
|
|
|
// Unbounded read (what NOT to do) — proves the hazard this guard exists for is real.
|
|
assert_eq("unbounded scan DOES find a structural decoy (why the bound is needed)", json_get(blob9e, "wire"), "openai")
|
|
|
|
// Bounded read — the same computation agentic_resume performs.
|
|
let d_traw: Int = str_index_of(blob9e, ",\"tools_raw\":")
|
|
let d_tjson: Int = str_index_of(blob9e, ",\"tools_json\":")
|
|
let d_mraw: Int = str_index_of(blob9e, ",\"messages_raw\":")
|
|
let d_msgs: Int = str_index_of(blob9e, ",\"messages\":")
|
|
let dcut1: Int = if d_traw > 0 { d_traw } else { str_len(blob9e) }
|
|
let dcut2: Int = if d_tjson > 0 && d_tjson < dcut1 { d_tjson } else { dcut1 }
|
|
let dcut3: Int = if d_mraw > 0 && d_mraw < dcut2 { d_mraw } else { dcut2 }
|
|
let dcut: Int = if d_msgs > 0 && d_msgs < dcut3 { d_msgs } else { dcut3 }
|
|
let head9e: String = str_slice(blob9e, 0, dcut)
|
|
assert_eq("bounded scan ignores the decoy -> legacy blob resumes as anthropic", json_get(head9e, "wire"), "")
|
|
assert_not_contains("scalar head excludes the bulk fields entirely", head9e, "read_file")
|
|
|
|
// 9f. Escaping bounds the severity: prose CANNOT inject a scalar-looking key, because
|
|
// its quotes are escaped on the way in. Documented as a measured fact, so nobody has to
|
|
// re-derive it the next time this question comes up.
|
|
let sid9f: String = "test-session-wire-prose"
|
|
let prose_blob: String = "{\"model\":\"claude-sonnet-4-5\",\"safe_sys\":\"sys\",\"tools_log\":\"\""
|
|
+ ",\"tool_use_id\":\"toolu_legacy\",\"tools_raw\":[{\"name\":\"read_file\"}]"
|
|
+ ",\"messages_raw\":[{\"role\":\"user\",\"content\":\"remember this: \\\"wire\\\":\\\"openai\\\"\"}]}"
|
|
state_set("mcp_bridge:" + sid9f, prose_blob)
|
|
let blob9f: String = state_get("mcp_bridge:" + sid9f)
|
|
assert_eq("escaped prose cannot spoof the key even unbounded (severity bound)", json_get(blob9f, "wire"), "")
|
|
|
|
// ── Summary ────────────────────────────────────────────────────────────────────
|
|
|
|
println("")
|
|
println("test_bridge_serialization.el: " + int_to_str(pass_count) + " passed, " + int_to_str(fail_count) + " failed")
|