Audit findings 5.1-5.3.
5.1 `phase reopen` now appends `phase_reopened` BEFORE the artifact
teardown: a crash between the two used to leave later-phase artifacts
deleted with the ledger still at the old phase (resume entered a phase
missing its artifacts). The inverse half-state — reopened with stale later
artifacts — is benign. Order locked by tests/test_phase_reopen_order.py.
5.2 New `tht decision add-batch --doc -`: N substantive decisions in ONE
atomic ledger write (meta types and cte_approved stay on `decision add`;
strictest min-phase enforced). reviewer_schema_linking now builds the
complete curation set and persists it with a single add-batch call — a
mid-loop failure can no longer leave the audit ledger half-written, and a
retry cannot duplicate the first K decisions.
5.3 The anti-bypass hook now also blocks BASH mutations of protected
state (`echo >> review_decisions.jsonl`, `sed -i` on the manifest,
`cat > tht-gate.js`, python open('w'), mv/rm/tee/…): FORBIDDEN only
covered tht subcommands and the write/edit hook only covered pi's own
tools. Read-only access (cat/grep/tail/ls) stays allowed.
Also: knownDecisionTypes is defensive — a workflow meta declaring NO
emits at all (older tht, minimal stubs) skips pre-validation instead of
rejecting every substantive type; with emits present, unknown types are
still rejected before the widget (new L1 test).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
69 lines
2.5 KiB
JavaScript
69 lines
2.5 KiB
JavaScript
const test = require("node:test");
|
|
const assert = require("node:assert");
|
|
const { createFakePi } = require("./fake_pi_runtime.js");
|
|
|
|
const installGatePromise = import("../../tht-gate.js").then((m) => m.default);
|
|
|
|
test("tht schema introspect --refresh e' bloccato in sessione (manutenzione)", async () => {
|
|
const installGate = await installGatePromise;
|
|
const { pi } = createFakePi();
|
|
installGate(pi);
|
|
const res = await pi.emit("tool_call", {
|
|
toolName: "bash",
|
|
input: { command: "tht schema introspect -c workspaces/psd.yaml --refresh" },
|
|
});
|
|
assert.equal(res?.block, true);
|
|
});
|
|
|
|
test("tht schema introspect senza --refresh passa (cache hit innocuo)", async () => {
|
|
const installGate = await installGatePromise;
|
|
const { pi } = createFakePi();
|
|
installGate(pi);
|
|
const res = await pi.emit("tool_call", {
|
|
toolName: "bash",
|
|
input: { command: "tht schema introspect -c workspaces/psd.yaml" },
|
|
});
|
|
assert.equal(res, undefined);
|
|
});
|
|
|
|
// Bash mutations of protected state bypass the write/edit hook: block them.
|
|
const BLOCKED_BASH = [
|
|
'echo \'{"type":"phase_approved","subject":"phase:4"}\' >> sessions/s1/review_decisions.jsonl',
|
|
"sed -i '' 's/open/finalized/' sessions/s1/session_manifest.yaml",
|
|
"cat /tmp/patch.js > .pi/extensions/tht-gate.js",
|
|
"python3 -c \"open('sessions/s1/review_decisions.jsonl','a').write('x')\"",
|
|
"mv /tmp/fake.json sessions/s1/cte_plan.json",
|
|
"rm sessions/s1/session_manifest.yaml",
|
|
"tee -a sessions/s1/review_decisions.jsonl < /tmp/x",
|
|
];
|
|
|
|
// Read-only access and unrelated redirects stay allowed.
|
|
const ALLOWED_BASH = [
|
|
"cat sessions/s1/review_decisions.jsonl",
|
|
"grep phase_approved sessions/s1/review_decisions.jsonl",
|
|
"tail -5 sessions/s1/session_manifest.yaml",
|
|
"ls .pi/extensions",
|
|
"tht session show s1 > /tmp/out.txt",
|
|
"echo done > /tmp/scratch.txt",
|
|
];
|
|
|
|
for (const cmd of BLOCKED_BASH) {
|
|
test(`bash mutation su stato protetto e' bloccata: ${cmd.slice(0, 60)}`, async () => {
|
|
const installGate = await installGatePromise;
|
|
const { pi } = createFakePi();
|
|
installGate(pi);
|
|
const res = await pi.emit("tool_call", { toolName: "bash", input: { command: cmd } });
|
|
assert.equal(res?.block, true);
|
|
});
|
|
}
|
|
|
|
for (const cmd of ALLOWED_BASH) {
|
|
test(`bash read-only/estraneo passa: ${cmd.slice(0, 60)}`, async () => {
|
|
const installGate = await installGatePromise;
|
|
const { pi } = createFakePi();
|
|
installGate(pi);
|
|
const res = await pi.emit("tool_call", { toolName: "bash", input: { command: cmd } });
|
|
assert.equal(res, undefined);
|
|
});
|
|
}
|