@junghanacs/entwurf 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  diff --git a/packages/coding-agent/src/experimental/durable/runtime.ts b/packages/coding-agent/src/experimental/durable/runtime.ts
2
- index fcd6e523..929dbc79 100644
2
+ index fcd6e523..7f00e27f 100644
3
3
  --- a/packages/coding-agent/src/experimental/durable/runtime.ts
4
4
  +++ b/packages/coding-agent/src/experimental/durable/runtime.ts
5
5
  @@ -8,7 +8,9 @@ import {
@@ -12,7 +12,19 @@ index fcd6e523..929dbc79 100644
12
12
  type ModelRef,
13
13
  ROOT_CONVERSATION_ID,
14
14
  type Submission,
15
- @@ -81,6 +83,10 @@ export interface DurableController {
15
+ @@ -56,8 +58,10 @@ export interface DurableView {
16
+ readonly conversations: readonly ConversationSummary[];
17
+ readonly models: readonly ModelSummary[];
18
+ readonly notices: readonly Notice[];
19
+ - /** The live task graph while the task panel is open. */
20
+ + /** The live task graph, observed for the session's lifetime whether or not its panel is shown. */
21
+ readonly tasks?: TaskGraph;
22
+ + /** Whether the task panel is shown; /tasks toggles only this. */
23
+ + readonly tasksPanel: boolean;
24
+ }
25
+
26
+ export interface DurableViewSource {
27
+ @@ -81,6 +85,10 @@ export interface DurableController {
16
28
  export interface OpenDurableOptions {
17
29
  readonly cwd?: string;
18
30
  readonly continueSession?: boolean;
@@ -23,7 +35,7 @@ index fcd6e523..929dbc79 100644
23
35
  }
24
36
 
25
37
  export interface OpenDurableResult {
26
- @@ -88,6 +94,8 @@ export interface OpenDurableResult {
38
+ @@ -88,6 +96,8 @@ export interface OpenDurableResult {
27
39
  readonly controller: DurableController;
28
40
  /** pi's settings, for the TUI's theme and terminal capabilities. */
29
41
  readonly settings: SettingsManager;
@@ -32,7 +44,7 @@ index fcd6e523..929dbc79 100644
32
44
  close(): Promise<void>;
33
45
  }
34
46
 
35
- @@ -122,6 +130,9 @@ function titleOf(entry: EntryRecord | undefined): { title?: string } {
47
+ @@ -122,6 +132,9 @@ function titleOf(entry: EntryRecord | undefined): { title?: string } {
36
48
  }
37
49
 
38
50
  export async function openDurable(options: OpenDurableOptions = {}): Promise<OpenDurableResult> {
@@ -42,7 +54,7 @@ index fcd6e523..929dbc79 100644
42
54
  const location = await selectSession(options.cwd ?? process.cwd(), options.continueSession ?? false);
43
55
  const envs = new ExecutionEnvs(location.cwd);
44
56
  let harness: Harness | undefined;
45
- @@ -132,6 +143,7 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
57
+ @@ -132,6 +145,7 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
46
58
  const settings = createHarnessSettings(settingsManager);
47
59
  const registry = createCodingRegistry(settingsManager, location.cwd);
48
60
  registry.install(Subagent);
@@ -50,7 +62,7 @@ index fcd6e523..929dbc79 100644
50
62
 
51
63
  const pendingReports: unknown[] = [];
52
64
  let report: (error: unknown) => void = (error) => pendingReports.push(error);
53
- @@ -146,7 +158,29 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
65
+ @@ -146,7 +160,29 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
54
66
  },
55
67
  context,
56
68
  );
@@ -81,7 +93,49 @@ index fcd6e523..929dbc79 100644
81
93
  const root = await harness.root(context, {
82
94
  agent: {
83
95
  cwd: location.cwd,
84
- @@ -342,6 +376,7 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
96
+ @@ -179,6 +215,7 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
97
+ conversations: summaries,
98
+ models: models(),
99
+ notices: [],
100
+ + tasksPanel: true,
101
+ };
102
+ const listeners = new Set<() => void>();
103
+ let notifying = false;
104
+ @@ -296,17 +333,8 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
105
+ const thinking: ModelThinkingLevel = agentOf(state.conversation).thinkingLevel ?? "off";
106
+ await current.configure({ model: ref, thinkingLevel: clampThinkingLevel(model, thinking) }, context);
107
+ }),
108
+ - toggleTasks: () =>
109
+ - command(async () => {
110
+ - if (tasks !== undefined) {
111
+ - closeTasks();
112
+ - update({ tasks: undefined });
113
+ - return;
114
+ - }
115
+ - const graph = await opened.taskGraph(context);
116
+ - tasks = graph;
117
+ - unsubscribeTasks = graph.subscribe((value) => update({ tasks: value }));
118
+ - }),
119
+ + // Only the panel: the graph stays observed, so live background work is never out of view.
120
+ + toggleTasks: () => command(async () => update({ tasksPanel: !state.tasksPanel })),
121
+ switchConversation: (id) =>
122
+ command(async () => {
123
+ const next = await opened.conversation(id, context);
124
+ @@ -326,8 +354,11 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
125
+ notice("warning", `Saved model is unavailable: ${saved.provider}/${saved.modelId}`);
126
+ }
127
+ if (initial?.fallbackMessage !== undefined) notice("info", initial.fallbackMessage);
128
+ - // The task panel starts open; /tasks hides it.
129
+ - await controller.toggleTasks();
130
+ + // The task graph is observed until close; its panel starts open and /tasks hides it.
131
+ + const graph = await opened.taskGraph(context);
132
+ + tasks = graph;
133
+ + update({ tasks: graph.value });
134
+ + unsubscribeTasks = graph.subscribe((value) => update({ tasks: value }));
135
+ // Recovered work from an interrupted turn continues now.
136
+ harness.resume();
137
+
138
+ @@ -342,6 +373,7 @@ export async function openDurable(options: OpenDurableOptions = {}): Promise<Ope
85
139
  },
86
140
  controller,
87
141
  settings: settingsManager,
@@ -89,3 +143,141 @@ index fcd6e523..929dbc79 100644
89
143
  close() {
90
144
  closing ??= (async () => {
91
145
  unsubscribe();
146
+ diff --git a/packages/coding-agent/src/experimental/durable/tui.ts b/packages/coding-agent/src/experimental/durable/tui.ts
147
+ index 397b276c..6a8fce8f 100644
148
+ --- a/packages/coding-agent/src/experimental/durable/tui.ts
149
+ +++ b/packages/coding-agent/src/experimental/durable/tui.ts
150
+ @@ -23,12 +23,14 @@ import {
151
+ SelectList,
152
+ type SelectListTheme,
153
+ Spacer,
154
+ + type StackEntry,
155
+ setCapabilityOverrides,
156
+ setKeybindings,
157
+ Text,
158
+ TruncatedText,
159
+ TuiAltScreen,
160
+ VStack,
161
+ + visibleWidth,
162
+ } from "@earendil-works/pi-tui";
163
+ import { getAgentDir } from "../../config.ts";
164
+ import { KeybindingsManager } from "../../core/keybindings.ts";
165
+ @@ -150,10 +152,75 @@ interface Handlers {
166
+ cycleThinking(): void;
167
+ }
168
+
169
+ +/**
170
+ + * The live background-task badge: the dock's first row, never shrunk, so live background work stays on
171
+ + * screen however small the terminal gets. It counts the graph's background tasks whatever their kind and
172
+ + * names the ones that are not running; with none it renders nothing and the dock is upstream's.
173
+ + */
174
+ +export class TaskBadge implements Component {
175
+ + #count = 0;
176
+ + #detail = "";
177
+ +
178
+ + get count(): number {
179
+ + return this.#count;
180
+ + }
181
+ +
182
+ + set(graph: TaskGraph | undefined): void {
183
+ + const nodes = Object.values(graph?.tasks ?? {}).filter((node) => node.background);
184
+ + const tally = (state: string, match: (node: TaskGraphNode) => boolean): string[] => {
185
+ + const n = nodes.filter(match).length;
186
+ + return n === 0 ? [] : [`${n} ${state}`];
187
+ + };
188
+ + this.#count = nodes.length;
189
+ + this.#detail = [
190
+ + ...tally("aborting", (node) => node.abortRequested),
191
+ + ...tally("completing", (node) => !node.abortRequested && node.state.status === "completing"),
192
+ + ...tally("waiting", (node) => !node.abortRequested && node.state.status === "waiting"),
193
+ + ...tally("pending", (node) => !node.abortRequested && node.state.status === "pending"),
194
+ + ].join(", ");
195
+ + }
196
+ +
197
+ + /** The widest form that fits, padded when there is room; narrower terminals drop detail, then words. */
198
+ + render(width: number): string[] {
199
+ + const n = this.#count;
200
+ + if (n === 0) return [];
201
+ + const tasks = `⏳ ${n} ${n === 1 ? "task" : "tasks"}`;
202
+ + const detailed = this.#detail ? [`${tasks} (${this.#detail})`] : [];
203
+ + const forms = [...detailed, tasks, `⏳ ${n}`, `⏳${n}`, `*${n}`, "*+", "*"];
204
+ + for (const form of forms) {
205
+ + if (visibleWidth(form) < width) return [theme.fg("accent", ` ${form}`)];
206
+ + if (visibleWidth(form) === width) return [theme.fg("accent", form)];
207
+ + }
208
+ + return [theme.fg("accent", "*")];
209
+ + }
210
+ +
211
+ + invalidate(): void {}
212
+ +}
213
+ +
214
+ +/**
215
+ + * The screen: the transcript above the dock. The badge is the dock's first row and never shrinks, so the
216
+ + * renderer, which clips from the bottom, drops the editor and the footer before it; a one-row terminal
217
+ + * with live background work shows the badge instead of the transcript.
218
+ + */
219
+ +export function durableLayout(transcript: Component, badge: TaskBadge, dock: readonly StackEntry[]): VStack {
220
+ + return new VStack([
221
+ + {
222
+ + component: transcript,
223
+ + basis: 0,
224
+ + grow: 1,
225
+ + shrink: 1,
226
+ + minSize: 1,
227
+ + visible: (viewport) => badge.count === 0 || viewport.height > 1,
228
+ + },
229
+ + { component: new VStack([{ component: badge, shrink: 0 }, ...dock]), basis: "auto", grow: 0, shrink: 1, minSize: 1 },
230
+ + ]);
231
+ +}
232
+ +
233
+ class DurableTui {
234
+ static readonly #renderers: Record<string, ToolRenderers> = createAllToolRenderers();
235
+ readonly #ui: TuiAltScreen;
236
+ readonly #chat = new Container();
237
+ + readonly #taskBadge = new TaskBadge();
238
+ readonly #tasks = new Container();
239
+ readonly #queue = new Container();
240
+ readonly #notices = new Container();
241
+ @@ -216,15 +283,9 @@ class DurableTui {
242
+ content.addChild(new Spacer(1));
243
+ const transcript = new ScrollView(content, { follow: "end", primary: true, overscroll: "chain" });
244
+ this.#transcript = transcript;
245
+ - const dock = new VStack([
246
+ - { component: this.#tasks, shrink: 1, minSize: 0 },
247
+ - { component: this.#queue, shrink: 1, minSize: 0 },
248
+ - { component: this.#notices, shrink: 1, minSize: 0 },
249
+ - { component: this.#editorContainer, shrink: 1, minSize: 3 },
250
+ - { component: this.#footer, shrink: 1, minSize: 0 },
251
+ - ]);
252
+ for (const component of [
253
+ this.#chat,
254
+ + this.#taskBadge,
255
+ this.#tasks,
256
+ this.#queue,
257
+ this.#notices,
258
+ @@ -234,9 +295,12 @@ class DurableTui {
259
+ this.#ui.addChild(component);
260
+ }
261
+ this.#ui.setLayoutRoot(
262
+ - new VStack([
263
+ - { component: transcript, basis: 0, grow: 1, shrink: 1, minSize: 1 },
264
+ - { component: dock, basis: "auto", grow: 0, shrink: 1, minSize: 1 },
265
+ + durableLayout(transcript, this.#taskBadge, [
266
+ + { component: this.#tasks, shrink: 1, minSize: 0 },
267
+ + { component: this.#queue, shrink: 1, minSize: 0 },
268
+ + { component: this.#notices, shrink: 1, minSize: 0 },
269
+ + { component: this.#editorContainer, shrink: 1, minSize: 3 },
270
+ + { component: this.#footer, shrink: 1, minSize: 0 },
271
+ ]),
272
+ );
273
+ this.#ui.setFocus(this.#editor);
274
+ @@ -299,7 +363,8 @@ class DurableTui {
275
+ );
276
+ }
277
+ }
278
+ - this.#syncTasks(view.tasks);
279
+ + this.#taskBadge.set(view.tasks);
280
+ + this.#syncTasks(view.tasksPanel ? view.tasks : undefined);
281
+ this.#syncQueue((view.conversation.docs["pi.inbox"] ?? { items: [] }) as InboxState);
282
+ this.#syncNotices(view);
283
+ this.#editor.borderColor = theme.getThinkingBorderColor(agentOf(view.conversation).thinkingLevel ?? "off");
@@ -2,8 +2,8 @@
2
2
  "schemaVersion": 2,
3
3
  "comment": "The one upstream commit the pi-durable carrier is emitted from, the patch it applies, the emitted files and the published SDK set they run against. The experimental durable app is source-only upstream (published pi-coding-agent ships no dist/experimental), so Entwurf ships it as a CARRIER: the pinned app files, type-stripped, with the relocation in pi/pi-durable/carrier-relocation.mjs. scripts/emit-pi-durable-carrier.ts is the only writer of the generated fields (blob, sha256, emitter.node) and the only reader of upstream source; an installed package never reads source. Model data rides the published @earendil-works/pi-ai.",
4
4
  "repository": "https://github.com/earendil-works/pi",
5
- "tag": "v1.0.4",
6
- "commit": "7c10bd4337495ee613f2224843ecdf349b80d1df",
5
+ "tag": "v1.1.0",
6
+ "commit": "abe508e1b89912adde45528136c3221eb69acdd7",
7
7
  "patches": ["runtime-contacts.patch"],
8
8
  "carrier": {
9
9
  "comment": "files: the six app files bootstrap.mjs needs (main.ts is replaced by the bootstrap). distTargets: every pi-coding-agent internal the stripped files import, as paths under the published dist/; the emitter refuses a set that differs and the resolver answers no other.",
@@ -23,14 +23,14 @@
23
23
  "source": "packages/coding-agent/src/experimental/durable/runtime.ts",
24
24
  "patched": true,
25
25
  "blob": "fcd6e5239e999ebf17dea0335fb3dc9cd7f7ce45",
26
- "sha256": "ca0ee10dc904a57d46bb33b37fcc4efe48dfa08b2f0d10e79e7bc22136871f0a"
26
+ "sha256": "bfd88844fae61e8bd32d57b5b986f48056c64eebb5466d9826318a0783ac9005"
27
27
  },
28
28
  {
29
29
  "name": "tui.js",
30
30
  "source": "packages/coding-agent/src/experimental/durable/tui.ts",
31
- "patched": false,
31
+ "patched": true,
32
32
  "blob": "397b276cbeb1c90e1fbc96c3cc59e6a25f92dad7",
33
- "sha256": "bef1ea4bd6e8ab150273752a9b94552d8ce672b518ba3d1ed1a209b8405be1b3"
33
+ "sha256": "f61cc68e5d32552a8196785093ae9e2f4a946088fe71582bb05690517db8c5b4"
34
34
  },
35
35
  {
36
36
  "name": "harness-setup.js",
@@ -89,17 +89,17 @@
89
89
  ]
90
90
  },
91
91
  "sdk": {
92
- "comment": "The published library set the carrier runs against, derived from the 1.0.4 declarations. members: the @earendil-works packages the carrier imports and every @earendil-works package those declare. direct: a third-party package a carrier file imports itself (sessions.ts: proper-lockfile, at the version pi-coding-agent 1.0.4 pins). Each is an exact production dependency in package.json; the members' own edges are caret ranges, so the installed physical edges, not semver, are what must equal these versions.",
92
+ "comment": "The published library set the carrier runs against, derived from the 1.1.0 declarations. members: the @earendil-works packages the carrier imports and every @earendil-works package those declare. direct: a third-party package a carrier file imports itself (sessions.ts: proper-lockfile, at the version pi-coding-agent 1.1.0 pins). Each is an exact production dependency in package.json; the members' own edges are caret ranges, so the installed physical edges, not semver, are what must equal these versions.",
93
93
  "members": {
94
- "@earendil-works/chord": "1.0.4",
95
- "@earendil-works/pi-agent-core": "1.0.4",
96
- "@earendil-works/pi-ai": "1.0.4",
97
- "@earendil-works/pi-codemode": "1.0.4",
98
- "@earendil-works/pi-coding-agent": "1.0.4",
99
- "@earendil-works/pi-durable": "1.0.4",
100
- "@earendil-works/pi-mcp": "1.0.4",
101
- "@earendil-works/pi-telemetry": "1.0.4",
102
- "@earendil-works/pi-tui": "1.0.4"
94
+ "@earendil-works/chord": "1.1.0",
95
+ "@earendil-works/pi-agent-core": "1.1.0",
96
+ "@earendil-works/pi-ai": "1.1.0",
97
+ "@earendil-works/pi-codemode": "1.1.0",
98
+ "@earendil-works/pi-coding-agent": "1.1.0",
99
+ "@earendil-works/pi-durable": "1.1.0",
100
+ "@earendil-works/pi-mcp": "1.1.0",
101
+ "@earendil-works/pi-telemetry": "1.1.0",
102
+ "@earendil-works/pi-tui": "1.1.0"
103
103
  },
104
104
  "direct": {
105
105
  "proper-lockfile": "4.1.2"
@@ -16,7 +16,7 @@
16
16
  * The bootstrap that composes the two lives with the overlay (`pi/pi-durable/bootstrap.mjs`), not
17
17
  * here: this module never imports or initializes the durable app runtime and carries no path into
18
18
  * a vendor checkout. That is this module's import boundary, not the package's dependency list: the
19
- * package does declare the exact `@earendil-works/pi-durable` (1.0.4) for its carrier (#130).
19
+ * package does declare the exact `@earendil-works/pi-durable` (per the pin's SDK set) for its carrier (#130).
20
20
  *
21
21
  * WHY BIRTH IS AFTER OPEN, BEHIND A GATE. `[read @cd32f77]` The native session id is chosen
22
22
  * inside `openDurable` (`runtime.ts:125`, `sessions.ts:18-55`), and `openDurable` calls
@@ -276,7 +276,9 @@ export interface PiDurableRoots {
276
276
  // ---------------------------------------------------------------------------
277
277
  // Durable surface, typed NARROWLY and locally — read at pi-durable 1.0.2 @cd32f77 (#129,
278
278
  // `harness/types.ts`; historical coordinates, not re-anchored here). The current supply is the
279
- // 1.0.4 carrier at `7c10bd43`, whose authority is `pi/pi-durable/overlay/upstream-pin.json`.
279
+ // carrier `pi/pi-durable/overlay/upstream-pin.json` names (1.1.0 at `abe508e`). `[read 2026-10-08]`
280
+ // v1.0.4 → v1.1.0 `harness/types.ts` adds `models`, `contextRetentionMs` and a `context()` options
281
+ // bag only; none of the shapes typed below moved.
280
282
  // `defineExtension`/`defineTool` are identity functions (`harness/define.ts:6-17`), so a plain
281
283
  // object is the extension.
282
284
  // ---------------------------------------------------------------------------
package/run.sh CHANGED
@@ -134,7 +134,7 @@ Usage:
134
134
  ./run.sh check-omp-birth-hook # #87 gate: drives the real OMP assembler into a temp dir, imports the ASSEMBLED index.ts into a MOCK omp host and fires session_start/session_switch. tui mints one backend:"omp" record + a sender marker keyed to the host's OWN pid (the one-process join) + the garden id on the status line; print/rpc/json mint NOTHING (hasUI is true on the rpc rows, as in the vendor); switch attaches on the same native id and mints the replacement on a new one; the CERTIFIED receiver reader still finds no marker. Also the four-root joint binding: extension and omp-labeled bridge child resolve the same sessions/mailbox/senders/receivers bundle against test-built literals (never the production resolver as its own oracle), under a poisoned PI_CODING_AGENT_DIR and under four distinct ENTWURF_META_* overrides; the override grammar is absolute-or-~ and both halves plus the doctor refuse anything else by name; and a drifted provenance label selects no root policy at all. Hermetic; no omp, no model turn
135
135
  ./run.sh check-pi-durable-contact # #129 L-identity gate, CHECKOUT-ONLY (an installed package refuses it in run_ts). Opens the REAL durable app from THIS checkout's verified carrier (pi/pi-durable/carrier/ under its resolver, against the checkout's pinned pi SDK set — refused by name first) with the pi-durable contact, births the citizen and runs its tools adapter-direct through a privately emitted branch bridge (ENTWURF_PI_DURABLE_BRIDGE_ENTRY; its registry must match this checkout). Matrix roots {default, explicit} x {fresh, --continue}: one record keyed by the durable session dir, sender marker on the spawned host pid, exactly one bridge child whose /proc PPID is that host, poisoned identity carriers kept from it, its four root carriers bound to the roots written, the receiver ARMED (raw marker owned by the host pid + start key as pi-durable-host, joined to the sender marker; removed on close), self = the record and replyable, peers from an outside bridge, same garden id on reopen; the bundle's compiled owner join must admit pi-durable-host. Reaps only the host/bridges it owns on any failure. Harness-bypassed: no model turn, no ToolTask. Missing inputs → SKIP (97), never a pass
136
136
  ./run.sh check-pi-durable-send # #129 L-send FIRST CELL, CHECKOUT-ONLY. Same input as check-pi-durable-contact. One native durable turn: the app's own controller input → GenerationTask → ToolTask → the contact's entwurf_v2 → the private bridge → a seeded self-fetch target (C2 recipe, owned by a separate idle child). S = a 127.0.0.1 scripted OpenAI-compatible endpoint configured through the app's documented models.json (dummy key, PI_OFFLINE); H = the native ToolTask path; V (a vendor turn) is NOT run. Oracles: the wire (tools offered, v2 schema = the bridge's tools/list, exactly two requests, the tool result + receipt in the second), the raw mailbox (.msg 1, signal, sender = the host record), the native view (one call, its settled result, root current). Not exactly-once, not recovery, not a recipient turn. Missing inputs → SKIP (97)
137
- ./run.sh check-pi-durable-beside # #129 mutant execution coordinate (VERIFY rule 3): run_vitest over the five pi-durable beside tests (adapter, carrier/SDK-set verifier, preflight, owner join, carrier relocation/resolver/emitter) — the pi-durable qualification lane's gate argv. Same files check-tests-beside-behavior already runs.
137
+ ./run.sh check-pi-durable-beside # #129 mutant execution coordinate (VERIFY rule 3): run_vitest over the six pi-durable beside tests (adapter, carrier/SDK-set verifier, preflight, owner join, carrier relocation/resolver/emitter/task footer, native-module file and directory ingress) — the pi-durable qualification lane's gate argv. Same files check-tests-beside-behavior already runs.
138
138
  ./run.sh check-pi-durable-receive # #129 L-receive FIRST CELL, CHECKOUT-ONLY. Same input (the bundle must carry the pi-durable-host owner join). The host driver gives the app NO input; a seeded claude-code sender (marker owned by a separate idle child, handed to a gate-owned sending bridge through ENTWURF_META_SENDER_MARKER) enqueues one message, the host's doorbell admits its notice to the ROOT conversation, and the scripted model drains it with entwurf_inbox_read. S withholds response #1 until the ENQUEUE receipt, the .delivered bytes, the signal, the host-owned receiver marker and the notice are captured. Oracles: receiver() admission, native view, raw archive (.delivered.read + lastReadAt), wire (exactly two requests, drain result = view), and after a green reap a forensic read-only SQLite copy (submission row by request id = reported id, root, input, done, placed entry = the notice). V NOT run. Missing inputs → SKIP (97)
139
139
  ./run.sh check-copilot-receive-arm # #82 RAIL 5 gate: the REAL receiver installer + the REAL extension.mjs forked with a stubbed SDK. Arms only after birth, marker owned by the WATCHER pid, self-fetch dispatch answer, doorbell carries the garden id and NOT the body, id-drift/foreign-parent refusals. Hermetic; no Copilot, no model turn
140
140
  ./run.sh check-copilot-launch # #82 RAIL 7 gate: the MANAGED launch `entwurf copilot`, driven through its public address against a FAKE VENDOR on a sandbox PATH. Receiver precondition refusals, EXTENSIONS token + operator token preservation, injected defaults before the `--` terminator, byte-identical argv, the 11 explicit permission/surface policy overrides that suppress `--yolo`, exec (not fork) pid identity, exit passthrough, recursion refusal. Hermetic; no Copilot, no model turn
@@ -208,7 +208,7 @@ Usage:
208
208
  ./run.sh smoke-entwurf-chain-live # LIVE cross-harness delivery chain: native Claude Code -> pi GPT -> pi ACP Sonnet -> mailbox terminus, proving sender identity/replyable at every hop and a real read receipt at the end. Prerequisites (claude on PATH, pi credentials per backend) report protocol SKIP, never a pass
209
209
  ./run.sh check-release-gate-outcomes # release-gate STEP OUTCOME protocol (P1): one skip exit code shared by the shell + TS halves, classifier never rounds a skip up to a pass, `--cut` refuses a MUST SKIP while a bare diagnostic stays exit 0, no LIVE smoke keeps the old exit-0 skip shape, and both real skip surfaces are INVOKED and observed to propagate the code
210
210
  ./run.sh check-gate-manifests # the HEAD of that qualification, on its own: runner self-test + the committed manifests validated against the origin index + the declared lane inventory. Executes ZERO committed mutants (only the self-test's synthetic fixture mutants, in tmp fixture repos) and never snapshots this repo (~20s), which is why it lives in check:hermetic while the body stays on CI/release-gate — three of the five reds qualification ever produced in CI died right here, before a mutant gate ran (#99 B-3)
211
- ./run.sh check-gate-qualification # kill-proof qualification (the gate-of-gates): runner self-test (classifier truth table + synthetic negatives incl. wrong-reason/hang/control-red/impurity) + committed mutant manifests (scripts/mutants/*.json) run in an isolated snapshot repo under control→mutant→restore→control; the real checkout is never written. Evidence = claim IDs + killed mutant IDs, never assertion counts
211
+ ./run.sh check-gate-qualification # kill-proof qualification (the gate-of-gates): runner self-test (classifier truth table + synthetic negatives incl. wrong-reason/hang/control-red/impurity) + committed mutant manifests (scripts/mutants/*.json) run in an isolated snapshot repo under control→mutant→restore→control; the real checkout is never written. Evidence = claim IDs + killed mutant IDs, never assertion counts. Bare = the whole body (the prepared SHA's CI call); `--group '<argv JSON>' --receipt <abs path outside the repo>` = one affected exact-argv group, a PARTIAL receipt (development's run, #133)
212
212
  ./run.sh check-probe-ordering # §11-7 ordering-probe deterministic gate: raw-client SAMENESS pinned to backend.ts (sequence/args/timeouts/permission policy — the probe may never measure a lookalike), phase attribution incl. set-model, probe-mode fixture wire markers (delay honored, probeRunId REQUIRED, smoke-acp-mcp-live legacy compat), event-log door integrity (reserved keys refused at write; unknown marker name, broken sort axis, or a payload the classifier cannot judge on is MALFORMED, never a quiet event), and the §11-7 paired-verdict truth table (P0/I0 outside the space, phase-qualified D, B promotion ladder, C, A two-delay rule). Offline/deterministic; one product claim (no production prompt cutoff) is replant-qualified via scripts/mutants/probe-ordering.json, while the other assertions are direct [CHECK:*] contracts
213
213
  ./run.sh check-probe-cli-shim # §11-7-c B-name-snapshot PRODUCER gate: the CLI shim driven as a REAL process against fake CLIs (no API, no cost). Proves what a defect would buy — FABRICATED evidence (a malformed init is never reported as an empty name set; the boot report carries the true target path+sha256 the classifier verifies against the roster), a DESTROYED turn (byte transparency across mid-UTF8/CRLF/oversized/unterminated framing, exit-code fidelity, signal re-raise, inbound signal forwarding, stderr passthrough, stdout backpressure), and LEAKED operator state (exact-allowlist env scrub, no argv/env/prompt body in the log). Offline/deterministic; 20 direct [CHECK:*] contracts, deliberately no longer replant-qualified after #70 subtraction
214
214
  ./run.sh smoke-agy-statusline-state # agy ambient garden-id statusLine install/doctor/inverse regression. Offline/deterministic
@@ -1520,8 +1520,8 @@ check_pi_durable_beside() {
1520
1520
  # from re-running every beside test. The six files are one contact: the adapter and its packaged
1521
1521
  # bootstrap, the installed carrier/SDK-set verifier (which also reads the three checkout gates as
1522
1522
  # SOURCE), the fresh preflight, the owner join that admits the durable host, the carrier's
1523
- # relocation grammar, resolver and maintainer emitter (#130), and the bootstrap's explicit
1524
- # --native-module ingress (#130 P2).
1523
+ # relocation grammar, resolver and maintainer emitter (#130) with the carrier's task footer (#134),
1524
+ # and the bootstrap's explicit --native-module ingress (#130 P2) and its --native-module-dir (#134).
1525
1525
  run_vitest pi-extensions/meta-bridge-pi-durable.test.ts pi-extensions/lib/pi-durable-runtime.test.ts pi-extensions/lib/pi-durable-fresh-preflight.test.ts pi-extensions/lib/entwurf-deliverability.owner-join.test.ts pi-extensions/lib/pi-durable-carrier.test.ts pi-extensions/lib/pi-durable-native-module.test.ts
1526
1526
  }
1527
1527
 
@@ -3810,7 +3810,7 @@ check_pack() {
3810
3810
  # which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
3811
3811
  # below so a future suffix change cannot pass vacuously.)
3812
3812
  pack_install_leaked_pi() {
3813
- grep '^@earendil-works+' | grep -Ev '@1\.0\.4(_|$)' || true
3813
+ grep '^@earendil-works+' | grep -Ev '@1\.1\.0(_|$)' || true
3814
3814
  }
3815
3815
 
3816
3816
  # Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
@@ -3830,15 +3830,15 @@ check_pack_pin_matcher() {
3830
3830
  # None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`0.86.0-beta.1`,
3831
3831
  # the prerelease shape that an unanchored match would bless) and an off-pin version.
3832
3832
  matcher_probe=$(printf '%s\n' \
3833
- '@earendil-works+pi-ai@1.0.4' \
3834
- '@earendil-works+pi-ai@1.0.4_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
3835
- '@earendil-works+pi-ai@1.0.4_ws@8.21.3' \
3836
- '@earendil-works+pi-ai@1.0.4_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
3837
- '@earendil-works+pi-ai@1.0.4-beta.1' \
3833
+ '@earendil-works+pi-ai@1.1.0' \
3834
+ '@earendil-works+pi-ai@1.1.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
3835
+ '@earendil-works+pi-ai@1.1.0_ws@8.21.3' \
3836
+ '@earendil-works+pi-ai@1.1.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
3837
+ '@earendil-works+pi-ai@1.1.0-beta.1' \
3838
3838
  '@earendil-works+pi-agent-core@0.87.1' | pack_install_leaked_pi)
3839
- if [ "$matcher_probe" != '@earendil-works+pi-ai@1.0.4-beta.1
3839
+ if [ "$matcher_probe" != '@earendil-works+pi-ai@1.1.0-beta.1
3840
3840
  @earendil-works+pi-agent-core@0.87.1' ]; then
3841
- fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.0.4-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.0.4 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
3841
+ fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.1.0-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.1.0 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
3842
3842
  return 1
3843
3843
  fi
3844
3844
 
@@ -3846,9 +3846,9 @@ check_pack_pin_matcher() {
3846
3846
  # (0.85.0 onward, still true at 0.99.1). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
3847
3847
  # back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
3848
3848
  matcher_probe=$(printf '%s\n' \
3849
- '@earendil-works+chord@1.0.4' \
3849
+ '@earendil-works+chord@1.1.0' \
3850
3850
  '@earendil-works+chord@0.87.1' \
3851
- '@earendil-works+pi-ai@1.0.4' | pack_install_leaked_pi)
3851
+ '@earendil-works+pi-ai@1.1.0' | pack_install_leaked_pi)
3852
3852
  if [ "$matcher_probe" != '@earendil-works+chord@0.87.1' ]; then
3853
3853
  fail "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE] the pin-leak matcher must cover every @earendil-works closure member, not just the pi-* families — an off-pin @earendil-works/chord has to leak (it is a runtime dependency of pi-coding-agent, pi-agent-core, pi-client and pi-protocol — `[측정 2026-09-20]` at 0.86.0, re-measured `[측정 2026-09-22]` at 0.87.0, `[측정 2026-09-23]` at 0.87.1 and `[측정 2026-09-30]` at 0.99.1, where pi-coding-agent adds pi-codemode and pi-mcp beside them) — got: ${matcher_probe:-<nothing leaked>}"
3854
3854
  return 1
@@ -3862,7 +3862,7 @@ check_pack_pin_matcher() {
3862
3862
  # Cells: L1s (Entwurf alone, $3), L1 (project-local beside an aligned host pi-coding-agent at the
3863
3863
  # pinned SDK version), L2 (an isolated `npm -g --prefix`), each through (a) the INSTALLED verifier
3864
3864
  # (exit 0, prints its own carrier-resolver) and (b) an independent consumer oracle; then the L3
3865
- # NEGATIVE (an override binds pi-durable's pi-ai edge one patch behind: the installed verifier must
3865
+ # NEGATIVE (an override binds pi-durable's pi-ai edge to an older published version: the installed verifier must
3866
3866
  # refuse it by name, while a bare import still succeeds); then (c) the public launch on L1s.
3867
3867
  # The oracle observes RESOLUTIONS (a resolve hook), not every filesystem read; it asserts each
3868
3868
  # resolved product file lies inside THIS consumer's node_modules — a sandbox ancestor is not the
@@ -3873,8 +3873,9 @@ pack_install_durable_cells() {
3873
3873
  local tgz="$1" box="$2" l1s="$3" pin_ver oracle_dir cell root control pkg out rc
3874
3874
  local host_specs=() host_old_specs=() spec
3875
3875
  pin_ver=$(cd "$REPO_DIR" && node -p "require('./pi/pi-durable/overlay/upstream-pin.json').sdk.members['@earendil-works/pi-coding-agent']")
3876
- # The HOST fixture needs a coherent generation too: coding-agent@1.0.4 alone
3877
- # has ^1.0.4 SDK edges, which can resolve 1.1.0 (CI37694671506: pi-ai tar 404).
3876
+ # The HOST fixture needs a coherent generation too: a coding-agent root alone has
3877
+ # caret SDK edges, which can float to a newer generation (CI37694671506, measured at
3878
+ # coding-agent@1.0.4 with ^1.0.4 edges resolving 1.1.0: pi-ai tar 404).
3878
3879
  # These are fixture roots from the owning pin, not product overrides or fallback.
3879
3880
  out=$(cd "$REPO_DIR" && node -e 'const p = require("./pi/pi-durable/overlay/upstream-pin.json"); console.log(Object.entries(p.sdk.members).map(([n,v]) => `${n}@${v}`).join("\n"));') || return 1
3880
3881
  mapfile -t host_specs <<<"$out"
@@ -4024,8 +4025,8 @@ ORACLE
4024
4025
  # copied from this checkout into the sandbox — it never ships (test/ is outside the package files).
4025
4026
  # Its initialization appends `init` to the receipt this cell names; the same public launch must then
4026
4027
  # reach the same carrier model refusal, so the installed bootstrap loaded the module before the
4027
- # runtime and the native registry installed its extension (carrier/runtime.js 146) before that
4028
- # refusal (runtime.js 162). A module claiming an extension name the app installs itself is refused by
4028
+ # runtime and the native registry installed its extension (carrier/runtime.js 148) before that
4029
+ # refusal (runtime.js 164). A module claiming an extension name the app installs itself is refused by
4029
4030
  # name before anything opens: no durable session directory, no citizen, in a fresh sandbox home.
4030
4031
  # Native SDK file opens here are ordinary (the module runs in the native process); none is judged.
4031
4032
  local mod_dir="$box/pd-module" mod_home="$box/pd-module-home" mod_env=()
@@ -4047,18 +4048,44 @@ ORACLE
4047
4048
  fi
4048
4049
  echo "[check-pack-install] pi-durable native module: the installed bootstrap initialized one explicit module before the carrier's runtime (then the same model refusal), and refused a reserved extension name before anything opened"
4049
4050
 
4051
+ # (c3) #134: the paired --native-module-dir on the INSTALLED package. One named directory holding the
4052
+ # same synthetic module under a matching name beside a helper and a dotfile that would throw if
4053
+ # opened: the installed bootstrap must read only the top-level *.extension.mjs, initialize it once
4054
+ # (one `init` receipt) and reach the same carrier model refusal. A directory without a matching
4055
+ # module is refused by name before anything opens: no durable session directory, no citizen.
4056
+ local dir_mods="$box/pd-module-dir" dir_empty="$box/pd-module-dir-empty" dir_home="$box/pd-module-dir-home" dir_env=()
4057
+ mkdir -p "$dir_mods" "$dir_empty" "$dir_home/.pi/agent"
4058
+ cp "$REPO_DIR/test/fixtures/pi-durable-native-module/mock.mjs" "$dir_mods/10-mock.extension.mjs"
4059
+ printf 'throw new Error("pack-install: helper opened");\n' > "$dir_mods/helper.mjs"
4060
+ printf 'throw new Error("pack-install: dotfile opened");\n' > "$dir_mods/.hidden.extension.mjs"
4061
+ cp "$dir_mods/helper.mjs" "$dir_empty/index.mjs"
4062
+ rc=0; out=$(cd "$launch_cwd" && env "${launch_env[@]}" PI_OFFLINE=1 MOCK_NATIVE_RECEIPT="$dir_mods/receipt.log" "$l1s/node_modules/.bin/entwurf" pi-durable --provider entwurf-pack-probe --model no-such-model --width task-wide --native-module-dir "$dir_mods" 2>&1 </dev/null) || rc=$?
4063
+ if [ "$rc" -eq 0 ] || [ "$(cat "$dir_mods/receipt.log" 2>/dev/null)" != "init" ] || ! grep -q 'Could not resolve model: Unknown provider "entwurf-pack-probe"' <<<"$out" || ! grep -q "at openDurable (file://.*/pi/pi-durable/carrier/runtime.js" <<<"$out" || grep -q -e "native-module-" -e "pack-install: .* opened" <<<"$out"; then
4064
+ fail "[QK:PACK-INSTALL-DURABLE-NATIVE-MODULE-DIR] [check-pack-install] the installed 'entwurf pi-durable --native-module-dir' did not initialize exactly its one top-level *.extension.mjs and then reach the carrier openDurable's own model refusal (rc=$rc, receipt $(cat "$dir_mods/receipt.log" 2>/dev/null | tr '\n' ' ')):"
4065
+ echo "$out" | tail -15 | sed 's/^/ /' >&2
4066
+ return 1
4067
+ fi
4068
+ mapfile -t dir_env < <(pack_install_consumer_env "$dir_home")
4069
+ rc=0; out=$(cd "$launch_cwd" && env "${dir_env[@]}" PI_OFFLINE=1 "$l1s/node_modules/.bin/entwurf" pi-durable --provider entwurf-pack-probe --model no-such-model --width task-wide --native-module-dir "$dir_empty" 2>&1 </dev/null) || rc=$?
4070
+ if [ "$rc" -eq 0 ] || ! grep -q "native-module-dir-empty: " <<<"$out" || grep -q -e "Unknown provider" -e "pack-install: .* opened" <<<"$out" || [ -e "$dir_home/.pi/agent/experimental" ] || [ -e "$dir_home/.pi/agent/meta-sessions" ]; then
4071
+ fail "[QK:PACK-INSTALL-DURABLE-NATIVE-MODULE-DIR] [check-pack-install] the installed launch did not refuse a directory without a module by name before anything opened (rc=$rc):"
4072
+ echo "$out" | tail -15 | sed 's/^/ /' >&2
4073
+ return 1
4074
+ fi
4075
+ echo "[check-pack-install] pi-durable module directory: the installed bootstrap initialized only the named directory's one top-level *.extension.mjs before the carrier's runtime (then the same model refusal), and refused a directory without a module before anything opened"
4076
+
4050
4077
  # (d) Host vs native are two subjects. The installed package's native SDK set is the pin (above);
4051
4078
  # the ordinary Pi HOST on PATH is judged by its OWN real `pi --version` against the derived range.
4052
- # Real host binaries, no forged version: a sandbox install of the previous pi (1.0.2) must make the
4079
+ # Real host binaries, no forged version: a sandbox install of the previous pi generation must make the
4053
4080
  # installed setup's pi row a named FAIL with ZERO Pi wiring while pi-durable still PASSes, and the
4054
4081
  # aligned host binary (the pinned version, installed above for L1) must be inside the range.
4055
4082
  # The previous host version is a variable, not a quoted `@pkg@x.y.z` spec: check-dep-versions reads
4056
4083
  # those specs as this repo's install pins, and this one is deliberately off the floor.
4057
- local host_home host_proj host_old="$box/pd-host-old" host_env=() host_old_ver="1.0.2"
4084
+ local host_home host_proj host_old="$box/pd-host-old" host_env=() host_old_ver="1.0.4"
4058
4085
  mkdir -p "$host_old"
4059
4086
  for spec in "${host_specs[@]}"; do host_old_specs+=("${spec%@*}@$host_old_ver"); done
4060
4087
  out=$(cd "$host_old" && npm install "${host_old_specs[@]}" --no-audit --no-fund 2>&1) || {
4061
- fail "[check-pack-install] could not install the previous-host pi fixture (1.0.2):"; echo "$out" | tail -10 | sed 's/^/ /' >&2; return 1; }
4088
+ fail "[check-pack-install] could not install the previous-host pi fixture ($host_old_ver):"; echo "$out" | tail -10 | sed 's/^/ /' >&2; return 1; }
4062
4089
  for cell in old aligned; do
4063
4090
  host_home="$box/pd-host-$cell-home"; host_proj="$box/pd-host-$cell-proj"
4064
4091
  mkdir -p "$host_home/.pi/agent" "$host_proj"
@@ -4072,8 +4099,8 @@ ORACLE
4072
4099
  fail "[check-pack-install] host $cell: the native pi-durable row must stay PASS whatever the PATH host is:"; echo "$out" | tail -20 | sed 's/^/ /' >&2; return 1
4073
4100
  fi
4074
4101
  if [ "$cell" = old ]; then
4075
- if [ "$rc" -eq 0 ] || ! grep -q "pi: FAIL — detected pi 1.0.2 is outside the supported range >=$pin_ver <1.1" <<<"$out" || [ -e "$host_proj/.pi" ] || [ -e "$host_home/.pi/agent/settings.json" ]; then
4076
- fail "[QK:PACK-INSTALL-HOST-RANGE-SEPARATE] [check-pack-install] a real pi 1.0.2 host beside the $pin_ver native set must be a named pi FAIL with zero Pi wiring and a non-green setup (rc=$rc):"
4102
+ if [ "$rc" -eq 0 ] || ! grep -q "pi: FAIL — detected pi $host_old_ver is outside the supported range >=$pin_ver <1.2" <<<"$out" || [ -e "$host_proj/.pi" ] || [ -e "$host_home/.pi/agent/settings.json" ]; then
4103
+ fail "[QK:PACK-INSTALL-HOST-RANGE-SEPARATE] [check-pack-install] a real pi $host_old_ver host beside the $pin_ver native set must be a named pi FAIL with zero Pi wiring and a non-green setup (rc=$rc):"
4077
4104
  echo "$out" | tail -20 | sed 's/^/ /' >&2
4078
4105
  return 1
4079
4106
  fi
@@ -4081,7 +4108,7 @@ ORACLE
4081
4108
  fail "[check-pack-install] host aligned: the pinned-version host binary was judged outside the range:"; echo "$out" | tail -20 | sed 's/^/ /' >&2; return 1
4082
4109
  fi
4083
4110
  done
4084
- echo "[check-pack-install] host vs native: a real pi 1.0.2 host is a named pi FAIL with no wiring while pi-durable PASSes; the $pin_ver host binary is inside the range"
4111
+ echo "[check-pack-install] host vs native: a real pi $host_old_ver host is a named pi FAIL with no wiring while pi-durable PASSes; the $pin_ver host binary is inside the range"
4085
4112
  }
4086
4113
 
4087
4114
  # The writable roots of ONE consumer sandbox, shared by every consumer subcall of
@@ -4438,21 +4465,26 @@ _check_pack_install_impl() {
4438
4465
  # library the pi-durable carrier imports, a production dependency of this package since 0.32 (its
4439
4466
  # own deps: chord, pi-ai). All eleven are published at 1.0.4. The tarball now DECLARES the nine-member
4440
4467
  # SDK set itself, so these pins are an aligned host install beside it, not peers it lacks.
4441
- echo "[check-pack-install] pnpm add into $tmp (with the aligned 1.0.x SDK + chord + codemode + mcp + durable + typebox)"
4468
+ # `[측정 2026-10-08]` at 1.1.0 the constellation is unchanged: the nine registry manifests
4469
+ # (.tmp-verify/033-prep/registry-sdk-manifests.json) give pi-coding-agent {chord, pi-agent-core,
4470
+ # pi-ai, pi-codemode, pi-mcp, pi-tui}, pi-ai {pi-telemetry}, pi-agent-core {pi-ai}, pi-durable
4471
+ # {chord, pi-ai}, and `npm view` gives pi-client {chord, pi-protocol}, pi-protocol {chord}. All
4472
+ # eleven are published at 1.1.0.
4473
+ echo "[check-pack-install] pnpm add into $tmp (with the aligned 1.1.x SDK + chord + codemode + mcp + durable + typebox)"
4442
4474
  local install_log
4443
4475
  install_log=$(cd "$tmp" && pnpm add \
4444
4476
  "$tgz_path" \
4445
- "@earendil-works/pi-ai@1.0.4" \
4446
- "@earendil-works/pi-coding-agent@1.0.4" \
4447
- "@earendil-works/pi-tui@1.0.4" \
4448
- "@earendil-works/pi-agent-core@1.0.4" \
4449
- "@earendil-works/pi-client@1.0.4" \
4450
- "@earendil-works/pi-protocol@1.0.4" \
4451
- "@earendil-works/pi-telemetry@1.0.4" \
4452
- "@earendil-works/chord@1.0.4" \
4453
- "@earendil-works/pi-codemode@1.0.4" \
4454
- "@earendil-works/pi-mcp@1.0.4" \
4455
- "@earendil-works/pi-durable@1.0.4" \
4477
+ "@earendil-works/pi-ai@1.1.0" \
4478
+ "@earendil-works/pi-coding-agent@1.1.0" \
4479
+ "@earendil-works/pi-tui@1.1.0" \
4480
+ "@earendil-works/pi-agent-core@1.1.0" \
4481
+ "@earendil-works/pi-client@1.1.0" \
4482
+ "@earendil-works/pi-protocol@1.1.0" \
4483
+ "@earendil-works/pi-telemetry@1.1.0" \
4484
+ "@earendil-works/chord@1.1.0" \
4485
+ "@earendil-works/pi-codemode@1.1.0" \
4486
+ "@earendil-works/pi-mcp@1.1.0" \
4487
+ "@earendil-works/pi-durable@1.1.0" \
4456
4488
  "typebox@latest" \
4457
4489
  --ignore-workspace --ignore-scripts 2>&1) || {
4458
4490
  fail "[check-pack-install] pnpm add failed:"
@@ -4468,11 +4500,11 @@ _check_pack_install_impl() {
4468
4500
  local leaked_pi
4469
4501
  leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
4470
4502
  if [ -n "$leaked_pi" ]; then
4471
- fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.0.4):"
4503
+ fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.1.0):"
4472
4504
  printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
4473
4505
  return 1
4474
4506
  fi
4475
- echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.0.4 (chord and pi-durable included)"
4507
+ echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.1.0 (chord and pi-durable included)"
4476
4508
 
4477
4509
  # Resolve the installed package.json and confirm pi.extensions
4478
4510
  # arrived intact. If pi.extensions is empty or missing, the
@@ -6802,19 +6834,11 @@ release_gate() {
6802
6834
  results+=("FAIL static (pnpm run check:full)"); failc=$((failc + 1))
6803
6835
  fi
6804
6836
 
6805
- # 1b. Discriminating power of that floor. The mutant-EXECUTING body left the
6806
- # default check chains (operator inner-loop cost) — only its head rides along,
6807
- # as check-gate-manifests inside check:hermetic — so release acceptance carries
6808
- # the body explicitly as its own MUST step: a cut must re-prove the gates still
6809
- # kill what they claim to kill.
6810
- section "release-gate step: check-gate-qualification"
6811
- if (cd "$REPO_DIR" && bash "$self" check-gate-qualification); then
6812
- ok "check-gate-qualification: PASS"
6813
- results+=("PASS check-gate-qualification"); pass=$((pass + 1))
6814
- else
6815
- fail "check-gate-qualification: FAIL"
6816
- results+=("FAIL check-gate-qualification"); failc=$((failc + 1))
6817
- fi
6837
+ # 1b. Discriminating power is NOT re-proven here. A cut re-proves that the gates still kill
6838
+ # what they claim exactly once: the qualification BODY step of the exact-SHA CI run at the
6839
+ # prepared SHA, which the release oracle (verify-exact-ci.sh) requires before any candidate
6840
+ # exists (#132 / #133 — a local BODY here repeated that ~100-minute proof on a SHA nobody ships).
6841
+ # Its head still rides check:full as check-gate-manifests.
6818
6842
 
6819
6843
  # 2. (gemini-availability step removed — claude-only floor; gemini CLI is
6820
6844
  # deprecated, so the gate no longer asserts a three-backend claim.)
@@ -7485,7 +7509,13 @@ case "$cmd" in
7485
7509
  # (HEAD + work-surface content hash asserted identical before/after). The runner is negative-controlled
7486
7510
  # first: zero-match/multi-match/survived/wrong-reason/hang(+pgroup grandchild kill)/
7487
7511
  # control-red/impurity all must classify red before any real manifest counts.
7488
- run_ts scripts/check-gate-qualification.ts
7512
+ # Arguments reach the runner's own parser unchanged (#133): `--group '<argv JSON>'
7513
+ # --receipt <abs path>` is the affected PARTIAL a development lane runs, and a malformed,
7514
+ # missing or repeated flag is refused there before any body or snapshot. Dropping them
7515
+ # turned a requested PARTIAL into the whole body. Bare, it stays the whole body — the
7516
+ # prepared SHA's CI call. Never retry bare after a refusal.
7517
+ shift || true
7518
+ run_ts scripts/check-gate-qualification.ts "$@"
7489
7519
  ;;
7490
7520
  check-probe-ordering)
7491
7521
  # §11-7 ordering-probe gate (docs/acp-backend-rail.md §11-7): the raw-client seam is