Skip to content

Commit 526f0a2

Browse files
committed
fix(mcode-island): correct README drift and lock PermissionRequest decision
Two follow-up changes in response to the hetaoBackend review on PR #21 ("Request changes"): 1. README.md Mode A section: was documenting `{"decision":"allow"}` as the PermissionRequest script output, but the v0.3.0 script emits `{"decision":"ask"}` (the observer opt-in value added by PR #20 commit 28aa5f4). The v0.2.1 -> v0.3.0 transition flipped the decision but the README was not updated. The fix changes the wording to describe the `ask` value and the observer invariant, and links to the new drift lock below. 2. scripts/smoke.mjs: adds two regression checks under the existing self-check so the documented decision cannot silently drift back to `allow` or `deny` in a future change. - 5b. Reads permission-request.ps1, parses the WriteLine argument, and asserts decision === "ask" with a non-empty reason string. Exits 1 on FAIL. Verified locally: a mutation that flips "ask" -> "allow" produces `1 fail` with the message "decision is "allow", expected "ask" (observer opt-in, per PR #20)". - 5c. Reads README.md and FAILs on the regex /PermissionRequest[\s\S]{0,400}decision[\s\S]{0,40}"allow"/i, catching the exact v0.2.1 wording that was in the previously-merged docstring. Smoke is now 42 pass / 7 warn (the same 7 forward events from PR #20) / 0 fail. The two new checks are PASS by default and only trip on actual drift. Out of scope: no change to the Hook scripts themselves, no change to the portable spec (PR #20), no change to the test event payload fixtures used by the e2e smoke (which is a separate PowerShell script in the local dev tree, not the PR). Refs: MiniMax-Code-Plugins PR #21 review at 2026-08-26T01:14:52Z "PermissionRequest returns {\"decision\":\"allow\"} ... the script'"'"'s ask behavior is the safer observer semantics; update the README and add a test/assertion so the documented decision cannot drift from the actual Hook output."
1 parent 7f15cf1 commit 526f0a2

2 files changed

Lines changed: 61 additions & 2 deletions

File tree

plugins/antianqi/mcode-island/README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,18 @@ the runtime spawns a script from this plugin for every matching event:
5252

5353
The agent does not need to remember to push state — the runtime fires the
5454
right script at the right time. `PermissionRequest` is the only
55-
decision-bearing event here; the script returns `{"decision":"allow"}` so the
56-
runtime's fail-closed default does not auto-deny. The widget just shows
55+
decision-bearing event here; the script returns `{"decision":"ask"}` so the
56+
plugin remains a pure observer (it does not auto-allow or auto-deny).
57+
The runtime's fail-closed default is bypassed only because the script
58+
opts the Hook into the "ask the user" path, so the TUI prompt still
59+
appears and the user can approve or deny. The widget just shows
5760
`waiting` so the user knows to act.
5861

62+
> **Drift lock**: `scripts/smoke.mjs` reads `permission-request.ps1`
63+
> directly and asserts the `decision` field is exactly `ask`. A
64+
> future change that flips the value back to `allow` or `deny` will
65+
> fail the smoke before the PR can be submitted.
66+
5967
Until the registry validator accepts the namespace, the `io.minimax.mcode/`
6068
directory is dormant and the plugin falls through to Mode B.
6169

plugins/antianqi/mcode-island/scripts/smoke.mjs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,57 @@ const main = async () => {
270270
out('PASS', '_lib.ps1: shared helper present');
271271
}
272272

273+
// 5b. Drift lock: permission-request.ps1 must emit `{"decision":"ask"}`,
274+
// not `allow` or `deny`. The 0.2.4 Runtime default for PermissionRequest
275+
// is fail-closed; an observer Hook that returns `allow` or `deny`
276+
// would silently change the user-facing permission flow. The portable
277+
// spec (PR #20) added `ask` exactly so observers can opt into
278+
// "ask the user" without becoming the permission owner. This lock
279+
// prevents a future change from regressing that invariant.
280+
const permReqPath = join(PLUGIN_ROOT, 'io.minimax.mcode', 'hooks', 'scripts', 'permission-request.ps1');
281+
if (!(await exists(permReqPath))) {
282+
out('FAIL', 'permission-request.ps1 missing (drift lock skipped)');
283+
} else {
284+
const permReq = await readFile(permReqPath, 'utf8');
285+
const decisionMatch = permReq.match(/WriteLine\(\s*'([^']*\{[^']*\})'\s*\)/);
286+
if (!decisionMatch) {
287+
out('FAIL', 'permission-request.ps1: cannot locate WriteLine decision JSON');
288+
} else {
289+
const decisionJson = decisionMatch[1];
290+
let parsed;
291+
try { parsed = JSON.parse(decisionJson); }
292+
catch (e) {
293+
out('FAIL', `permission-request.ps1: decision JSON is not valid JSON: ${e.message}`);
294+
}
295+
if (parsed) {
296+
if (parsed.decision !== 'ask') {
297+
out('FAIL', `permission-request.ps1: decision is "${parsed.decision}", expected "ask" (observer opt-in, per PR #20). Returning "allow" or "deny" from an observer Hook silently changes the user-facing permission flow.`);
298+
} else {
299+
out('PASS', `permission-request.ps1: decision is locked to "ask" (observer opt-in)`);
300+
}
301+
if (!parsed.reason || typeof parsed.reason !== 'string') {
302+
out('FAIL', 'permission-request.ps1: missing or non-string `reason` field');
303+
} else {
304+
out('PASS', 'permission-request.ps1: reason field present');
305+
}
306+
}
307+
}
308+
}
309+
310+
// 5c. Drift lock: README must not say `{"decision":"allow"}` for
311+
// PermissionRequest. The v0.2.1 baseline docstring is the most
312+
// common place this regresses, since the script changed from
313+
// `allow` to `ask` between v0.2.1 and v0.3.0.
314+
const readmePath = join(PLUGIN_ROOT, 'README.md');
315+
if (await exists(readmePath)) {
316+
const readme = await readFile(readmePath, 'utf8');
317+
if (/PermissionRequest[\s\S]{0,400}decision[\s\S]{0,40}"allow"/i.test(readme)) {
318+
out('FAIL', 'README.md: contains "decision":"allow" near PermissionRequest (the v0.3.0 spec uses "ask")');
319+
} else {
320+
out('PASS', 'README.md: no stale "decision":"allow" near PermissionRequest');
321+
}
322+
}
323+
273324
// 6. cross-platform: scan all .ps1 files for hardcoded paths
274325
console.log('-'.repeat(60));
275326
console.log('cross-platform scan:');

0 commit comments

Comments
 (0)