From 7eaaaf3e773ebb2c03dbd8689886739338e6d6bf Mon Sep 17 00:00:00 2001 From: Arjit Jaiswal Date: Mon, 28 Sep 2026 20:00:08 -0400 Subject: [PATCH 1/2] test(policy): exercise the documented normalization command --- .../scripts/model-policy/cli.test.ts | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/plugins/pstack/skills/poteto-mode/scripts/model-policy/cli.test.ts b/plugins/pstack/skills/poteto-mode/scripts/model-policy/cli.test.ts index f2e6f3f..55269ae 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/model-policy/cli.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/model-policy/cli.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it } from "bun:test"; -import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { main } from "./cli.ts"; @@ -807,6 +807,71 @@ swarm workers: claude:haiku@low -> grok:grok-4.7@xhigh ], capture.capture), `${lane}/${attempt}`).toBe(64); } }); + + describe("documented normalize invocation", () => { + const SKILL_ROOT = join(import.meta.dir, "../.."); + + function documentedNormalize(args: string[]): { argv: string[]; cwd: string } { + const dispatch = readFileSync( + join(SKILL_ROOT, "references", "provider-dispatch.md"), + "utf8" + ); + const match = dispatch.match( + /`bun\s+(scripts\/model-policy\/[^\s`]+)\s+normalize\b/ + ); + if (match === null) { + throw new Error( + "provider-dispatch.md does not document a bun normalize invocation" + ); + } + return { argv: [process.execPath, match[1], ...args], cwd: SKILL_ROOT }; + } + + async function runDocumented( + args: string[] + ): Promise<{ code: number; stdout: string; stderr: string }> { + const { argv, cwd } = documentedNormalize(args); + const child = Bun.spawn(argv, { cwd, stdout: "pipe", stderr: "pipe" }); + const [stdout, stderr, code] = await Promise.all([ + new Response(child.stdout).text(), + new Response(child.stderr).text(), + child.exited, + ]); + return { code, stdout, stderr }; + } + + it("spawns the documented normalize executable on a terminal receipt", async () => { + const result = await runDocumented(normalizeArgs(receipt())); + expect(result.code, result.stderr).toBe(0); + expect(JSON.parse(result.stdout)).toMatchObject({ + status: "event", + attempt: "grok:grok-4.7@xhigh", + event: { + attemptIndex: 0, + status: "terminal-failure", + processStarted: true, + receiptPath: join(scratch, "receipt.json"), + }, + }); + expect(result.stderr).toBe(""); + }); + + it("rejects invalid evidence through the documented normalize executable", async () => { + const identity = await runDocumented( + normalizeArgs( + receipt({ provider: "devin", model: "swe-2", effort: "high" }) + ) + ); + expect(identity.code).toBe(65); + expect(identity.stdout).toBe(""); + expect(identity.stderr).toContain("identity mismatch"); + + const malformed = await runDocumented(normalizeArgs("not json")); + expect(malformed.code).toBe(64); + expect(malformed.stdout).toBe(""); + expect(malformed.stderr).toContain("error:"); + }); + }); }); describe("model-policy validate command", () => { From efa07c0beefe37c9f9fd254b704a074ee3d593f7 Mon Sep 17 00:00:00 2001 From: Arjit Jaiswal Date: Mon, 28 Sep 2026 20:00:08 -0400 Subject: [PATCH 2/2] fix(policy): document the executable normalization wrapper --- .agents/skills/verify-open-pstack/features/routing.md | 9 +++++++++ .claude-plugin/marketplace.json | 2 +- CHANGELOG.md | 6 ++++++ UPSTREAM.md | 2 +- plugins/pstack/.claude-plugin/plugin.json | 2 +- plugins/pstack/.codex-plugin/plugin.json | 2 +- .../skills/poteto-mode/references/provider-dispatch.md | 2 +- 7 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.agents/skills/verify-open-pstack/features/routing.md b/.agents/skills/verify-open-pstack/features/routing.md index 43a362e..77c8681 100644 --- a/.agents/skills/verify-open-pstack/features/routing.md +++ b/.agents/skills/verify-open-pstack/features/routing.md @@ -5,6 +5,7 @@ Users inspect how a saved role resolves, while the parent owns execution and any ## Sub-features - `routing-resolve`: read the configured lane chains without dispatch. +- `receipt-normalize`: execute the documented command and check accepted and rejected receipts. - `claude-primary-model`: verify the main Claude assistant model independently of helper usage. - `codex-completion`: require completed final-turn output while preserving protocol success as a replay veto. - `devin-refusal-fallback`: exclude inherited provider-side fallback from an assigned Devin lane. @@ -32,6 +33,14 @@ capture routing-unchanged diff -u "$VERIFY_EVIDENCE/routing-sheet-before.stdout" - **Live dispatch:** run the direct How entry in [workflows](workflows.md). Compare actual native launch metadata or external runner receipt with the resolved descriptor, parent, model, effort, access mode, and saved API-spend fact. Retain the final terminal outcome and output. - **Recovery:** follow the saved fallback-chain scenario in `tests/setup-selected-providers.md`. Require authentic terminal evidence, a visible failure/substitution notice, and any required writer inspection before the exact saved next attempt. A synthetic event passed to the `next` CLI proves a decision only, not a provider failure or parent recovery. +### Documented receipt normalization + +For changes to the documented normalization command, run `bun test model-policy/cli.test.ts -t 'documented normalize'` from the installed candidate's `skills/poteto-mode/scripts` directory. The tests extract the executable from `references/provider-dispatch.md`, then check the resulting JSON event and rejection diagnostics using synthetic receipt inputs. + +From a fresh installed session in each parent, invoke `pstack:poteto-mode` and ask the parent to use the normalization command in its loaded provider-dispatch reference. Supply a preserved real runner receipt and its matching frozen sheet, role, lane, attempt, parent, and access mode. The `--parent` value must match the receipt's originating parent, even when the test session runs in the other app. Keep that distinction in the evidence. Expect a nonempty JSON event with the matching attempt, terminal status, and receipt path. + +Repeat with a copy whose model differs from the frozen assignment. Expect nonzero exit, an identity-mismatch diagnostic, and no policy event. This altered copy is a synthetic negative fixture. Retain both commands, stdout, stderr, exit codes, skill invocation, loaded reference path, and installed-tree comparison. Replaying a saved receipt proves normalization from the parent workflow, not a new provider execution. An empty successful exit does not prove normalization or authorize dispatch. + ### Claude primary-model evidence For a change to Claude output parsing, install the candidate in both parents and invoke setup's availability phase. Use an external Claude descriptor in each parent so both exercise the parser. An exact Haiku ID without a native agent definition can exercise the external route from Claude Code. Preserve the installed tree identity, parent tool transcript, runner receipt, and independent output-file check. diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f254025..308400a 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "pstack", "source": "./plugins/pstack", "description": "if you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence.", - "version": "1.10.0", + "version": "1.10.1", "author": { "name": "Lauren Tan (original)" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index f974050..d9cf7e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ This file records what each version of the Open Pstack package changed. Versions Entries describe package versions. Published release checkpoints have tag links; installation follows `main` unless pinned. Validation belongs to the linked pull requests. Older reports remain available through immutable links. +## 1.10.1 fixes the documented receipt-normalization command + +Cursor baseline: [0.15.5](https://github.com/cursor/plugins/tree/12d587dfb20741cafc376c42c696c5f6e2a64487/pstack). [Issue #99](https://github.com/arjitj2/open-pstack/issues/99). + +Provider-dispatch instructions invoke the executable policy wrapper so normalization returns a receipt event or rejects invalid input. Regression tests execute the documented command and check its output and rejection behavior. + ## 1.10.0 adds opt-in Codex startup routing Cursor baseline: [0.15.5](https://github.com/cursor/plugins/tree/12d587dfb20741cafc376c42c696c5f6e2a64487/pstack). [Issue #88](https://github.com/arjitj2/open-pstack/issues/88). diff --git a/UPSTREAM.md b/UPSTREAM.md index 233f9ba..4e22fe1 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -12,7 +12,7 @@ This page records the current Cursor baseline and the maintainer procedure for r | Path | `pstack/` | | Commit | `12d587dfb20741cafc376c42c696c5f6e2a64487` | | Upstream version | `0.15.5` | -| open-pstack version | `1.10.0` | +| open-pstack version | `1.10.1` | The table records the packaged version on `main` and the Cursor content it incorporates, minus the exclusions below. Detecting or reviewing a newer Cursor commit does not advance this baseline. Cursor's version identifies the imported content. The open-pstack version identifies the cross-harness package, and its numbers are independent of Cursor and Eric's port. diff --git a/plugins/pstack/.claude-plugin/plugin.json b/plugins/pstack/.claude-plugin/plugin.json index 63d6134..b5734c8 100644 --- a/plugins/pstack/.claude-plugin/plugin.json +++ b/plugins/pstack/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "pstack", "displayName": "pstack", - "version": "1.10.0", + "version": "1.10.1", "description": "if you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence. Ported from cursor/plugins/pstack for Claude Code and Codex.", "author": { "name": "Lauren Tan" diff --git a/plugins/pstack/.codex-plugin/plugin.json b/plugins/pstack/.codex-plugin/plugin.json index e50dd5b..4e55b06 100644 --- a/plugins/pstack/.codex-plugin/plugin.json +++ b/plugins/pstack/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pstack", - "version": "1.10.0", + "version": "1.10.1", "description": "if you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence. Codex port of the Claude Code plugin; skills are shared, tool names resolve via skills/poteto-mode/references/codex-tools.md.", "author": { "name": "Lauren Tan" diff --git a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md index 5f85337..5345145 100644 --- a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md +++ b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md @@ -262,7 +262,7 @@ The runner and native tool envelopes classify failures; the helper owns every ro The `on` list is a closed union over exactly those four policy outcomes, with no duplicates and no unknown keys; a second `# fallback:` line anywhere in the sheet (preamble, between rows, or footer) is malformed and rejected. A sheet without the line keeps quota-only behavior — `{"on":["usage-exhausted"]}` — so existing sheets advance only on proven exhaustion. Declaring the line makes the sheet policy-enabled in the same sense as saved access facts and chains: required role rows must be present rather than defaulting, and every configured route needs an access fact. The policy declares which terminal outcomes *may* advance; it never adds an attempt, a provider, a model, an effort, or an `apiSpend` approval beyond what the saved chain already authorizes. Setup may recommend a broader policy on a new sheet and must show its effect in the candidate; it never edits an existing sheet's policy without explicit acceptance. -The receipt boundary is deterministic: `bun scripts/model-policy/cli.ts normalize --sheet --role --parent

--lane --attempt --receipt --mode [--contract ]` reads the actual runner receipt, checks that its parent/provider/model/effort match the frozen sheet's attempt `i` on lane `n`, that its recorded access mode equals the requested `--mode`, its contract equals `--contract` (legacy by default), and its recorded `apiSpend` equals the attempt's saved access fact, checks internal consistency (a not-started receipt can only fail in preflight, a preflight failure cannot be started, postprocess requires a clean exit), and prints the policy `AttemptOutcome` (plus the receipt path as evidence) or exits nonzero. Agents never reimplement the mapping. The total mapping over receipt statuses: +The receipt boundary is deterministic: `bun scripts/model-policy/pstack-model-policy normalize --sheet --role --parent

--lane --attempt --receipt --mode [--contract ]` reads the actual runner receipt, checks that its parent/provider/model/effort match the frozen sheet's attempt `i` on lane `n`, that its recorded access mode equals the requested `--mode`, its contract equals `--contract` (legacy by default), and its recorded `apiSpend` equals the attempt's saved access fact, checks internal consistency (a not-started receipt can only fail in preflight, a preflight failure cannot be started, postprocess requires a clean exit), and prints the policy `AttemptOutcome` (plus the receipt path as evidence) or exits nonzero. Agents never reimplement the mapping. The total mapping over receipt statuses: | Receipt status | Policy outcome | Gate | |---|---|---|