@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.
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +103 -0
- package/README.md +57 -119
- package/VERIFY.md +25 -12
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +1 -1
- package/docs/setup-clean-host.md +37 -16
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +1 -1
- package/package.json +10 -10
- package/pi/pi-durable/bootstrap.mjs +111 -11
- package/pi/pi-durable/carrier/runtime.js +11 -14
- package/pi/pi-durable/carrier/tui.js +76 -11
- package/pi/pi-durable/overlay/runtime-contacts.patch +199 -7
- package/pi/pi-durable/overlay/upstream-pin.json +15 -15
- package/pi-extensions/meta-bridge-pi-durable.ts +4 -2
- package/run.sh +83 -53
- package/scripts/check-gate-qualification.ts +60 -3
- package/scripts/check-release-gate-outcomes.ts +22 -18
- package/scripts/emit-pi-durable-carrier.ts +61 -21
- package/scripts/mutants/control-send-receipt.json +1 -1
- package/scripts/mutants/gate-qualification.json +24 -0
- package/scripts/mutants/pack-install.json +3 -3
- package/scripts/mutants/pi-durable.json +178 -11
- package/scripts/mutants/pi-floor.json +6 -6
- package/scripts/mutants/release-gate.json +5 -3
|
@@ -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..
|
|
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
|
-
@@ -
|
|
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 +
|
|
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 +
|
|
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 +
|
|
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 +
|
|
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
|
-
@@ -
|
|
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
|
|
6
|
-
"commit": "
|
|
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": "
|
|
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":
|
|
31
|
+
"patched": true,
|
|
32
32
|
"blob": "397b276cbeb1c90e1fbc96c3cc59e6a25f92dad7",
|
|
33
|
-
"sha256": "
|
|
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
|
|
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
|
|
95
|
-
"@earendil-works/pi-agent-core": "1.0
|
|
96
|
-
"@earendil-works/pi-ai": "1.0
|
|
97
|
-
"@earendil-works/pi-codemode": "1.0
|
|
98
|
-
"@earendil-works/pi-coding-agent": "1.0
|
|
99
|
-
"@earendil-works/pi-durable": "1.0
|
|
100
|
-
"@earendil-works/pi-mcp": "1.0
|
|
101
|
-
"@earendil-works/pi-telemetry": "1.0
|
|
102
|
-
"@earendil-works/pi-tui": "1.0
|
|
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` (
|
|
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
|
-
//
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
3834
|
-
'@earendil-works+pi-ai@1.
|
|
3835
|
-
'@earendil-works+pi-ai@1.
|
|
3836
|
-
'@earendil-works+pi-ai@1.
|
|
3837
|
-
'@earendil-works+pi-ai@1.0
|
|
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
|
|
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
|
|
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
|
|
3849
|
+
'@earendil-works+chord@1.1.0' \
|
|
3850
3850
|
'@earendil-works+chord@0.87.1' \
|
|
3851
|
-
'@earendil-works+pi-ai@1.0
|
|
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
|
|
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
|
|
3877
|
-
#
|
|
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
|
|
4028
|
-
# refusal (runtime.js
|
|
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
|
|
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.
|
|
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 (
|
|
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
|
|
4076
|
-
fail "[QK:PACK-INSTALL-HOST-RANGE-SEPARATE] [check-pack-install] a real pi
|
|
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
|
|
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
|
-
|
|
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
|
|
4446
|
-
"@earendil-works/pi-coding-agent@1.0
|
|
4447
|
-
"@earendil-works/pi-tui@1.0
|
|
4448
|
-
"@earendil-works/pi-agent-core@1.0
|
|
4449
|
-
"@earendil-works/pi-client@1.0
|
|
4450
|
-
"@earendil-works/pi-protocol@1.0
|
|
4451
|
-
"@earendil-works/pi-telemetry@1.0
|
|
4452
|
-
"@earendil-works/chord@1.0
|
|
4453
|
-
"@earendil-works/pi-codemode@1.0
|
|
4454
|
-
"@earendil-works/pi-mcp@1.0
|
|
4455
|
-
"@earendil-works/pi-durable@1.0
|
|
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
|
|
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
|
|
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
|
|
6806
|
-
#
|
|
6807
|
-
#
|
|
6808
|
-
#
|
|
6809
|
-
#
|
|
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
|
-
|
|
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
|