fix(gate): coerce stringified object params; block model edits to gate code
Some models (GLM 5.2) send object params (reviewer_confirm's artifact, write_schema_linking's schema_linking) as JSON-encoded strings. These failed TypeBox validation before execute(), making the model retry in an unbounded loop (observed ~2100s hang). jsonObjectOrSelf() coerces them back to objects in prepareReviewerArguments and write_schema_linking. Also close an anti-bypass gap: pi's write/edit tools were unrestricted on the gate code, so a looping model actually patched tht-gate.js. GATE_CODE_FILES now blocks any write under .pi/extensions. Requires a pi restart to take effect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
// #2 robustness: some models send object params (Type.Object / Type.Any) as
|
||||
// stringified JSON, which fails validation before execute() and makes the model
|
||||
// loop. prepareReviewerArguments must coerce them back; the anti-bypass hook must
|
||||
// block the model from editing the gate code itself.
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const {
|
||||
jsonObjectOrSelf,
|
||||
prepareReviewerArguments,
|
||||
GATE_CODE_FILES,
|
||||
} = require("../../tht-gate.js");
|
||||
|
||||
test("jsonObjectOrSelf parses a stringified object/array, leaves plain strings intact", () => {
|
||||
assert.deepEqual(jsonObjectOrSelf('{"a":1}'), { a: 1 });
|
||||
assert.deepEqual(jsonObjectOrSelf('[1,2]'), [1, 2]);
|
||||
assert.equal(jsonObjectOrSelf("SELECT 1"), "SELECT 1"); // not JSON -> unchanged
|
||||
assert.equal(jsonObjectOrSelf('"just a string"'), '"just a string"'); // JSON string primitive -> unchanged
|
||||
const obj = { kind: "text" };
|
||||
assert.equal(jsonObjectOrSelf(obj), obj); // already an object -> same reference
|
||||
});
|
||||
|
||||
test("prepareReviewerArguments coerces a stringified artifact into an object", () => {
|
||||
const out = prepareReviewerArguments({
|
||||
session: "s",
|
||||
artifact: JSON.stringify({ kind: "text", data: { recap: "x" }, version: 1 }),
|
||||
});
|
||||
assert.equal(typeof out.artifact, "object");
|
||||
assert.equal(out.artifact.kind, "text");
|
||||
assert.equal(out.artifact.data.recap, "x");
|
||||
});
|
||||
|
||||
test("prepareReviewerArguments leaves an object artifact and parses stringified options", () => {
|
||||
const art = { kind: "text", data: {} };
|
||||
const out = prepareReviewerArguments({
|
||||
artifact: art,
|
||||
options: JSON.stringify([{ id: "a", label: "A" }]),
|
||||
});
|
||||
assert.deepEqual(out.artifact, art);
|
||||
assert.ok(Array.isArray(out.options));
|
||||
assert.equal(out.options[0].id, "a");
|
||||
});
|
||||
|
||||
test("GATE_CODE_FILES blocks writes to gate extensions, not session artifacts", () => {
|
||||
assert.ok(GATE_CODE_FILES.test("harness/.pi/extensions/tht-gate.js"));
|
||||
assert.ok(GATE_CODE_FILES.test(".pi/extensions/reserved-labels.mjs"));
|
||||
assert.equal(GATE_CODE_FILES.test("sessions/s1/schema_linking.json"), false);
|
||||
assert.equal(GATE_CODE_FILES.test("sessions/s1/sql_final.sql"), false);
|
||||
});
|
||||
Reference in New Issue
Block a user