fix: pre-dispatch rejection of unloggable args, mutation-proof event copies, proto-safe bindings (Codex round 1)

Three findings from the PR-4 convergence round:

(A) A root-undefined binding argument passed normalization untouched, so
the sub-call DISPATCHED and only then failed the tool/code-dispatch append
(Session.append rejects undefined event data) — a sub-call executed with
no log record, violating the nothing-executes-unlogged contract. And the
tool received the SAME object later handed to the append, so a tool
mutating its args desynced the logged record from what was dispatched (or
re-poisoned the append). jsonNormalizeArgs now rejects undefined up front
with a model-correctable message and returns TWO independent parses of the
canonical JSON text: the tool gets one, the event logs the sibling —
identical by construction, mutation-proof.

(B) The bridge built its bindings record with plain-object assignment, so
a registered tool named __proto__ hit the prototype setter and silently
vanished (the runtime host resolves binding names as own properties). The
record is now null-prototype with defineProperty, mirroring the
worker-side namespace build.

(B) The header-pin sanity assertions ran only inside NON-pinning
scenarios, so a class consisting solely of its pinning scenario (the two
Code Mode classes) would accept a re-recorded pin carrying several headers
or a header-delta. A fixtures meta-test now asserts every pinning fixture
directly.
This commit is contained in:
Tianyi Cui
2026-07-08 13:39:51 +08:00
parent 2cb10cbc63
commit 84088300bc
3 changed files with 92 additions and 21 deletions

View File

@@ -408,6 +408,20 @@ describe('snapshot fixtures', () => {
})
})
it('every pinning fixture carries exactly one request/header and no deltas', async () => {
// The live uniformity guard runs only in NON-pinning scenarios, so a
// class made of just its pinning scenario (the Code Mode classes) would
// otherwise accept a re-recorded pin with several headers or a mid-run
// header-delta — shapes the pin design cannot represent. Assert the
// committed pins directly.
for (const scenario of pinningByClass.values()) {
const fixture = await readFile(join(SNAPSHOTS_DIR, scenario.name, 'session.jsonl'), 'utf8')
const headers = normalizedHeaders(fixture, fixtureContext(fixture))
expect(headers.length, `${scenario.name}: a pinning fixture must carry exactly one request/header`).toBe(1)
expect(headerDeltaCount(fixture), `${scenario.name}: a pinning fixture must carry no request/header-delta`).toBe(0)
}
})
it('committed fixtures carry request-header content ONLY in the pinning scenario', async () => {
// The whole point of the pin: a system-prompt or tool-schema change must
// churn exactly one committed line. A non-pinning fixture that carries the