@junghanacs/entwurf 0.22.0 → 0.23.1

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.
Files changed (135) hide show
  1. package/AGENTS.md +4 -1
  2. package/BASELINE.md +1 -0
  3. package/CHANGELOG.md +514 -0
  4. package/DELIVERY.md +10 -2
  5. package/README.md +43 -4
  6. package/VERIFY.md +47 -0
  7. package/docs/acp-backend-rail.md +96 -17
  8. package/docs/setup-clean-host.md +11 -1
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +39 -40
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +2 -1
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-declaration.js +609 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +95 -56
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +2 -2
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +1 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +46 -273
  27. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
  28. package/mcp/entwurf-bridge/src/index.ts +41 -41
  29. package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
  30. package/package.json +9 -8
  31. package/pi-extensions/entwurf-control.ts +91 -52
  32. package/pi-extensions/lib/acp/acp-client.ts +3 -3
  33. package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
  34. package/pi-extensions/lib/acp/backend.ts +4 -4
  35. package/pi-extensions/lib/acp/event-mapper.ts +5 -5
  36. package/pi-extensions/lib/acp/tool-surface.ts +2 -1
  37. package/pi-extensions/lib/codex-declaration.js +612 -0
  38. package/pi-extensions/lib/codex-fresh-preflight.ts +107 -51
  39. package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
  40. package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
  41. package/pi-extensions/lib/entwurf-facts.ts +14 -1
  42. package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
  43. package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
  44. package/pi-extensions/lib/entwurf-self-address.ts +33 -0
  45. package/pi-extensions/lib/entwurf-v2-contract.ts +2 -2
  46. package/pi-extensions/lib/entwurf-v2-lock.ts +1 -1
  47. package/pi-extensions/lib/entwurf-v2-runner.ts +1 -1
  48. package/pi-extensions/lib/fresh-call-composition.ts +493 -0
  49. package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
  50. package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
  51. package/pi-extensions/lib/herdr-placement.ts +276 -0
  52. package/pi-extensions/lib/mux-fresh-call.ts +95 -301
  53. package/run.sh +588 -204
  54. package/scripts/check-acp-usage-accounting.ts +9 -9
  55. package/scripts/check-codex-birth-hook.ts +187 -0
  56. package/scripts/check-entwurf-control-rpc.ts +138 -0
  57. package/scripts/check-entwurf-fact-provider.ts +4 -2
  58. package/scripts/check-entwurf-peers-surface.ts +3 -1
  59. package/scripts/check-entwurf-self-address.ts +68 -0
  60. package/scripts/check-fresh-call-dispatch.ts +301 -0
  61. package/scripts/check-gate-qualification.ts +17 -4
  62. package/scripts/check-herdr-activation.ts +1293 -0
  63. package/scripts/check-herdr-fresh-call.ts +1250 -0
  64. package/scripts/check-herdr-placement.ts +383 -0
  65. package/scripts/check-herdr-plugin-build.ts +842 -0
  66. package/scripts/check-herdr-plugin-profile.ts +371 -0
  67. package/scripts/check-herdr-plugin.ts +523 -0
  68. package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
  69. package/scripts/check-herdr-sandbox.ts +456 -0
  70. package/scripts/check-herdr-supply.ts +184 -0
  71. package/scripts/check-mux-launcher-fence.ts +138 -0
  72. package/scripts/check-peer-facts.ts +331 -0
  73. package/scripts/check-pi-launch.ts +358 -0
  74. package/scripts/check-release-gate-outcomes.ts +19 -0
  75. package/scripts/check-typing-call-fence.ts +296 -0
  76. package/scripts/codex-birth-doctor.sh +159 -16
  77. package/scripts/codex-birth-install.sh +178 -72
  78. package/scripts/codex-birth-uninstall.sh +162 -7
  79. package/scripts/fixtures/herdr-supply.json +29 -0
  80. package/scripts/herdr-activation.mjs +536 -0
  81. package/scripts/herdr-plugin-activate.mjs +270 -0
  82. package/scripts/herdr-plugin-deactivate.mjs +193 -0
  83. package/scripts/herdr-runtime.mjs +1203 -0
  84. package/scripts/install-herdr-ci.sh +96 -0
  85. package/scripts/inventory-verification-surface.ts +86 -10
  86. package/scripts/lib/codex-fresh-live-protocol.ts +2 -2
  87. package/scripts/lib/tree-digest.ts +97 -0
  88. package/scripts/meta-bridge-install.sh +19 -2
  89. package/scripts/meta-bridge-state.py +167 -11
  90. package/scripts/mutants/codex-caller-seat.json +6 -8
  91. package/scripts/mutants/codex-native.json +195 -29
  92. package/scripts/mutants/control-socket-disconnect.json +57 -0
  93. package/scripts/mutants/fresh-call-dispatch.json +154 -0
  94. package/scripts/mutants/herdr-activation.json +305 -0
  95. package/scripts/mutants/herdr-fresh-call.json +448 -0
  96. package/scripts/mutants/herdr-placement.json +161 -0
  97. package/scripts/mutants/herdr-plugin-build.json +160 -0
  98. package/scripts/mutants/herdr-plugin-profile.json +202 -0
  99. package/scripts/mutants/herdr-plugin.json +133 -0
  100. package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
  101. package/scripts/mutants/herdr-supply.json +133 -0
  102. package/scripts/mutants/meta-hook-session-switch.json +2 -2
  103. package/scripts/mutants/mux-fresh-call.json +104 -17
  104. package/scripts/mutants/mux-launcher-fence.json +13 -0
  105. package/scripts/mutants/omp-fresh.json +4 -4
  106. package/scripts/mutants/peer-facts.json +98 -0
  107. package/scripts/mutants/pi-launch.json +113 -0
  108. package/scripts/mutants/resume-args.json +6 -6
  109. package/scripts/mutants/self-address.json +33 -0
  110. package/scripts/mutants/typing-call-fence.json +17 -0
  111. package/scripts/mutants/v2-surface.json +2 -2
  112. package/scripts/mutants/v2-visible-resume.json +17 -17
  113. package/scripts/peer-facts.ts +120 -0
  114. package/scripts/register-pi-package.py +8 -0
  115. package/scripts/register-pi-provider.py +88 -12
  116. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  117. package/scripts/smoke-codex-birth.sh +281 -12
  118. package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
  119. package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
  120. package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
  121. package/scripts/smoke-herdr-raw-install-live.sh +849 -0
  122. package/scripts/tsconfig.json +5 -0
  123. package/scripts/check-entwurf-facts.ts +0 -385
  124. package/scripts/check-entwurf-resume-args.ts +0 -122
  125. package/scripts/check-entwurf-v2-decider.ts +0 -657
  126. package/scripts/check-entwurf-v2-lock.ts +0 -371
  127. package/scripts/check-entwurf-v2-mailbox.ts +0 -261
  128. package/scripts/check-entwurf-v2-matrix.ts +0 -401
  129. package/scripts/check-entwurf-v2-native-push.ts +0 -221
  130. package/scripts/check-entwurf-v2-release.ts +0 -151
  131. package/scripts/check-entwurf-v2-runner.ts +0 -448
  132. package/scripts/check-entwurf-v2-send-fallback.ts +0 -350
  133. package/scripts/check-entwurf-v2-send.ts +0 -458
  134. package/scripts/check-entwurf-v2-visible-resume.ts +0 -445
  135. package/scripts/check-socket-discovery.ts +0 -401
@@ -0,0 +1,842 @@
1
+ /**
2
+ * check-herdr-plugin-build — deterministic gate for the #116 M3-b3 build runner
3
+ * (`plugins/herdr/lib/build.mjs`): the ONE thing Herdr's `[[build]]` calls, and the order in which
4
+ * it composes integration status → profile → runtime → activation.
5
+ *
6
+ * WHAT IS REAL HERE AND WHAT IS A SEAM. There is no fake `herdr` binary in this repo and this gate
7
+ * does not introduce one (same reason `check-mux-placement` has no fake tmux: a stand-in authors the
8
+ * contract before anything is measured). The listing therefore arrives through the runner's OWN
9
+ * `spawn` seam as a STRING — exactly the shape the measured 0.9.0 binary emits — and one cell drives
10
+ * the REAL binary when the host has it, with a named SKIP when it does not.
11
+ *
12
+ * WHERE THE WRITERS ARE PROVEN, AND WHY NOT HERE. The bytes a two-stage `{pi}` → `{pi, claude-code}`
13
+ * reinstall actually produces — Pi reconciled identically, Claude wired exactly once at the absolute
14
+ * command, OpenCode untouched — are `check-herdr-activation`'s `HAC-TWO-STAGE-ADD-ONLY`, which
15
+ * drives the real Pi writers. THIS gate proves the RUNNER: which backends it hands down, how many
16
+ * times, that nothing outside `A` is ever named, and that the verb it calls is the INSTALLED one.
17
+ * Duplicating the writer proof here would mean two places to keep true and one of them would rot.
18
+ *
19
+ * WHAT NEEDS THE NETWORK LIVES ELSEWHERE. A real checkout acquisition packs a git spec, which npm
20
+ * builds by installing devDependencies from the registry. That is not a deterministic surface, so
21
+ * the real `herdr plugin install` → real `npm pack git+https://…#<sha>` journey (with the product
22
+ * argv redirected to a local bare clone through git `insteadOf`, and the remote-commit
23
+ * available/unavailable pair) is `smoke-herdr-plugin-build-live`. Neither gate stands in for the
24
+ * other, and this paragraph is where that boundary is named rather than implied.
25
+ *
26
+ * Each [QK:HPB-*] token appears exactly once, on the assertion that fails for that claim.
27
+ */
28
+
29
+ import assert from "node:assert/strict";
30
+ import { spawnSync } from "node:child_process";
31
+ import { createHash } from "node:crypto";
32
+ import fs from "node:fs";
33
+ import os from "node:os";
34
+ import path from "node:path";
35
+ import { pathToFileURL } from "node:url";
36
+ import { reclaimOnExit } from "./lib/reclaim-on-exit.ts";
37
+ import { treeDigest } from "./lib/tree-digest.ts";
38
+
39
+ let passed = 0;
40
+ function ok(label: string, cond: boolean): void {
41
+ assert.ok(cond, label);
42
+ console.log(` ok ${label}`);
43
+ passed++;
44
+ }
45
+
46
+ const REPO = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
47
+ const PLUGIN_DIR = path.join(REPO, "plugins", "herdr");
48
+ const RUNNER = path.join(PLUGIN_DIR, "lib", "build.mjs");
49
+ const MANIFEST = path.join(PLUGIN_DIR, "herdr-plugin.toml");
50
+ const PACKAGE = "@junghanacs/entwurf";
51
+ const COMPILED_ENTRY = path.join("mcp", "entwurf-bridge", "dist", "mcp", "entwurf-bridge", "src", "index.js");
52
+ const COMMIT_A = "a".repeat(40);
53
+ const COMMIT_B = "b".repeat(40);
54
+
55
+ interface SpawnResult {
56
+ status: number | null;
57
+ stdout: string;
58
+ stderr: string;
59
+ error?: Error;
60
+ }
61
+ interface AcquireArgs {
62
+ identity: Record<string, string>;
63
+ prefix: string;
64
+ cacheDir: string;
65
+ env: NodeJS.ProcessEnv;
66
+ }
67
+ interface Runner {
68
+ PLUGIN_DIR: string;
69
+ certifyActivationPlan: (
70
+ env: NodeJS.ProcessEnv,
71
+ args: { lock: Record<string, string>; checkoutRoot: string; requested: string[]; resolveCommit?: () => string },
72
+ ) => { requested: Record<string, string>; disposition: string };
73
+ CHECKOUT_ROOT: string;
74
+ ACTIVATE_ENTRY: string;
75
+ buildStatusArgv: () => string[];
76
+ readIntegrationListing: (env: NodeJS.ProcessEnv, deps?: Record<string, unknown>) => string;
77
+ resolveActivationEntry: (activeDir: string, packageName: string) => string;
78
+ runBuild: (env: NodeJS.ProcessEnv, deps?: Record<string, unknown>) => number;
79
+ }
80
+ interface Profile {
81
+ activate: string[];
82
+ skip: { backend: string }[];
83
+ observedOtherAtoms: string[];
84
+ }
85
+
86
+ const activation = (await import(pathToFileURL(path.join(REPO, "scripts", "herdr-activation.mjs")).href)) as {
87
+ writeLedger: (layout: { stateRoot: string; ledgerPath: string }, entry: Record<string, unknown>) => void;
88
+ ledgerBody: (args: Record<string, unknown>) => Record<string, unknown>;
89
+ resolveActivationLayout: (env: NodeJS.ProcessEnv) => { stateRoot: string; ledgerPath: string };
90
+ resolveComponentRoots: (env: NodeJS.ProcessEnv) => Record<string, { path: string; source: string }>;
91
+ };
92
+
93
+ const runner = (await import(pathToFileURL(RUNNER).href)) as Runner;
94
+ const { ACTIVATE_ENTRY, buildStatusArgv, readIntegrationListing, resolveActivationEntry, runBuild } = runner;
95
+ const profileLeaf = (await import(pathToFileURL(path.join(PLUGIN_DIR, "lib", "integration-profile.mjs")).href)) as {
96
+ buildActivationProfile: (listing: string) => Profile;
97
+ };
98
+ const progressLeaf = (await import(pathToFileURL(path.join(PLUGIN_DIR, "lib", "build-progress.mjs")).href)) as {
99
+ createProgressReporter: (deps: {
100
+ openTty?: () => number;
101
+ writeTty?: (fd: number, line: string) => void;
102
+ closeTty?: (fd: number) => void;
103
+ mirror?: (line: string) => void;
104
+ }) => { live: boolean; step: (t: string) => void; done: (t: string) => void; close: () => void };
105
+ };
106
+
107
+ /** A listing in the exact prose shape measured on herdr 0.9.0 (#116 `issuecomment-5690462527`). */
108
+ function listing(rows: Record<string, string>): string {
109
+ const all: Record<string, string> = {
110
+ pi: "not installed (/home/u/.pi)",
111
+ claude: "not installed (/home/u/.claude)",
112
+ opencode: "current (v3) (/home/u/.config/opencode)",
113
+ ...rows,
114
+ };
115
+ return `${Object.entries(all)
116
+ .map(([atom, state]) => `${atom}: ${state}`)
117
+ .join("\n")}\n`;
118
+ }
119
+
120
+ function world(tag: string): NodeJS.ProcessEnv {
121
+ const home = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), `entwurf-hpb-${tag}-`)));
122
+ return {
123
+ HOME: home,
124
+ XDG_DATA_HOME: path.join(home, "data"),
125
+ XDG_CACHE_HOME: path.join(home, "cache"),
126
+ XDG_STATE_HOME: path.join(home, "state"),
127
+ PATH: process.env.PATH,
128
+ };
129
+ }
130
+
131
+ function activeDirOf(env: NodeJS.ProcessEnv): string {
132
+ return path.join(env.XDG_DATA_HOME as string, "entwurf", "herdr-plugin", "runtime", "active");
133
+ }
134
+ function journalPathOf(env: NodeJS.ProcessEnv): string {
135
+ return path.join(env.XDG_DATA_HOME as string, "entwurf", "herdr-plugin", "journal.json");
136
+ }
137
+
138
+ /** A named refusal's code, or null when the call did not refuse. */
139
+ function refusalOf(fn: () => unknown): string | null {
140
+ try {
141
+ fn();
142
+ return null;
143
+ } catch (err) {
144
+ return (err as { code?: string }).code ?? `unnamed:${String(err)}`;
145
+ }
146
+ }
147
+
148
+ /** A checkout the npm branch's coherence check can read a name@version from. */
149
+ function fixtureCheckoutRoot(): string {
150
+ const root = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-hpb-checkout-")));
151
+ fs.writeFileSync(path.join(root, "package.json"), `${JSON.stringify({ name: PACKAGE, version: "0.21.0" })}\n`);
152
+ return root;
153
+ }
154
+
155
+ /** Every file under a root, relative and sorted — the zero-write oracle. */
156
+ function tree(root: string): string[] {
157
+ const rows: string[] = [];
158
+ const walk = (dir: string): void => {
159
+ if (!fs.existsSync(dir)) return;
160
+ for (const e of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
161
+ const abs = path.join(dir, e.name);
162
+ if (e.isDirectory()) walk(abs);
163
+ else rows.push(path.relative(root, abs));
164
+ }
165
+ };
166
+ walk(root);
167
+ return rows;
168
+ }
169
+
170
+ /**
171
+ * The fixture ARTIFACT: what an acquisition puts in the staging prefix. `activate` decides what the
172
+ * installed package's activation verb is — absent, incapable (exits 0 on an empty request), or a
173
+ * faithful recorder that refuses an empty request by name and logs the argv it was given.
174
+ */
175
+ function fixtureAcquire(opts: { activate: "absent" | "incapable" | "recorder"; log?: string; fail?: boolean }) {
176
+ return ({ identity, prefix }: AcquireArgs) => {
177
+ const root = path.join(prefix, "node_modules", PACKAGE);
178
+ fs.mkdirSync(path.join(root, path.dirname(COMPILED_ENTRY)), { recursive: true });
179
+ fs.writeFileSync(path.join(root, "package.json"), `${JSON.stringify({ name: PACKAGE, version: "0.21.0" })}\n`);
180
+ fs.writeFileSync(path.join(root, COMPILED_ENTRY), "// compiled entry fixture\n");
181
+ const bin = path.join(prefix, "node_modules", ".bin");
182
+ fs.mkdirSync(bin, { recursive: true });
183
+ for (const name of ["entwurf", "entwurf-bridge", "entwurf-statusline"]) {
184
+ fs.writeFileSync(path.join(bin, name), "#!/bin/sh\nexit 0\n");
185
+ fs.chmodSync(path.join(bin, name), 0o755);
186
+ }
187
+ if (opts.activate !== "absent") {
188
+ const entry = path.join(root, ACTIVATE_ENTRY);
189
+ fs.mkdirSync(path.dirname(entry), { recursive: true });
190
+ const body =
191
+ opts.activate === "incapable"
192
+ ? ["process.exit(0);"]
193
+ : [
194
+ "import fs from 'node:fs';",
195
+ "const argv = process.argv.slice(2);",
196
+ "if (argv.length === 0) {",
197
+ " process.stderr.write('[stub] activation-backend-outside-p: name at least one backend\\n');",
198
+ " process.exit(1);",
199
+ "}",
200
+ `fs.appendFileSync(${JSON.stringify(opts.log ?? "/dev/null")}, argv.join(',') + '\\n');`,
201
+ `process.exit(${opts.fail ? 7 : 0});`,
202
+ ];
203
+ fs.writeFileSync(entry, `${body.join("\n")}\n`);
204
+ }
205
+ return {
206
+ observedDigest: `sha256-${createHash("sha256")
207
+ .update(identity.commit ?? "x")
208
+ .digest("hex")}`,
209
+ packageName: PACKAGE,
210
+ packageVersion: "0.21.0",
211
+ };
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Drive the runner with the listing arriving through its own spawn seam. Every OTHER spawn — the
217
+ * capability probe and the activation call — goes to the real `spawnSync`, because those are the
218
+ * calls this gate is actually about.
219
+ */
220
+ const HERDR_STUB = "herdr-stub";
221
+ function drive(
222
+ env: NodeJS.ProcessEnv,
223
+ text: string | { error: string } | { status: number; stderr: string },
224
+ deps: Record<string, unknown> = {},
225
+ ): { code: number | null; refusal: string | null; out: string; progress: string[] } {
226
+ let out = "";
227
+ // The operator's terminal is a SEAM here, never the real `/dev/tty`: a gate that narrated into
228
+ // whatever terminal happened to be running it would be writing outside its own sandbox.
229
+ const progressLines: string[] = [];
230
+ const progress = {
231
+ live: false,
232
+ step: (text: string) => progressLines.push(text),
233
+ done: (text: string) => progressLines.push(`done: ${text}`),
234
+ note: (text: string) => progressLines.push(`note: ${text}`),
235
+ close: () => {},
236
+ };
237
+ const spawn = (bin: string, argv: string[], opts: unknown): SpawnResult => {
238
+ if (bin === HERDR_STUB) {
239
+ if (typeof text === "string") return { status: 0, stdout: text, stderr: "" };
240
+ if ("error" in text) return { status: null, stdout: "", stderr: "", error: new Error(text.error) };
241
+ return { status: text.status, stdout: "", stderr: text.stderr };
242
+ }
243
+ return spawnSync(bin, argv, opts as Record<string, never>) as unknown as SpawnResult;
244
+ };
245
+ try {
246
+ const code = runBuild(env, {
247
+ herdrBin: HERDR_STUB,
248
+ spawn,
249
+ write: (t: string) => (out += t),
250
+ progress,
251
+ ...deps,
252
+ });
253
+ return { code, refusal: null, out, progress: progressLines };
254
+ } catch (err) {
255
+ return {
256
+ code: null,
257
+ refusal: (err as { code?: string }).code ?? `not-a-HerdrBuildError:${String(err)}`,
258
+ out,
259
+ progress: progressLines,
260
+ };
261
+ }
262
+ }
263
+
264
+ // ── 1. the manifest names exactly one runner, and nothing else grew ────────────
265
+ {
266
+ const manifest = fs.readFileSync(MANIFEST, "utf8");
267
+ const buildSections = (manifest.match(/^\[\[build\]\]$/gm) ?? []).length;
268
+ const commandLines = manifest
269
+ .split("\n")
270
+ .filter((l) => l.startsWith("command ="))
271
+ .map((l) => l.trim());
272
+ const shipped = (JSON.parse(fs.readFileSync(path.join(REPO, "package.json"), "utf8")) as { files: string[] }).files;
273
+ const forbidden = ["[[startup]]", "[[events]]", "[[actions]]", "[[link_handlers]]"].filter((s) =>
274
+ manifest.includes(`\n${s}`),
275
+ );
276
+ ok(
277
+ "[QK:HPB-MANIFEST-ONE-RUNNER] Herdr's `[[build]]` calls exactly ONE command — `node lib/build.mjs`, argv not a " +
278
+ "shell string — and the runner is a CHECKOUT file that the npm package deliberately does not ship, because a " +
279
+ "build only ever runs in a checkout while everything that must outlive Herdr's uninstall lives under " +
280
+ "`scripts/`. The sections this plugin refuses are still absent: a startup hook would volunteer on every " +
281
+ `server start and an event hook is exactly where a watcher would grow (build-sections=${buildSections} commands=${JSON.stringify(commandLines)} forbidden=${JSON.stringify(forbidden)} plugins-shipped=${shipped.some((f) => f.startsWith("plugins"))})`,
282
+ buildSections === 1 &&
283
+ commandLines.includes('command = ["node", "lib/build.mjs"]') &&
284
+ commandLines.length === 2 &&
285
+ forbidden.length === 0 &&
286
+ fs.existsSync(RUNNER) &&
287
+ !shipped.some((f) => f.startsWith("plugins")) &&
288
+ shipped.includes("scripts/"),
289
+ );
290
+ }
291
+
292
+ // ── 2. no herdr, or a herdr that cannot answer, is a named refusal ─────────────
293
+ {
294
+ const missingEnv = world("no-binary");
295
+ const missing = drive(missingEnv, { error: "spawn herdr ENOENT" });
296
+ const failedEnv = world("status-failed");
297
+ const failed = drive(failedEnv, { status: 2, stderr: "usage: herdr integration status [--outdated-only]" });
298
+ const residue = [...tree(missingEnv.HOME as string), ...tree(failedEnv.HOME as string)];
299
+ ok(
300
+ "[QK:HPB-BINARY-REQUIRED] a missing `herdr` and a `herdr integration status` that exits non-zero are two " +
301
+ "different named refusals, and neither invents a listing — status exits 0 in every state it can describe " +
302
+ "(measured on 0.9.0), so a non-zero exit is not a verdict about any harness: it means we do not have the " +
303
+ "listing, and an empty stand-in would read as 'nothing is integrated' and quietly activate nothing while " +
304
+ `reporting success (missing=${missing.refusal} failed=${failed.refusal} argv=${JSON.stringify(buildStatusArgv())} residue=${JSON.stringify(residue)})`,
305
+ missing.refusal === "herdr-build-binary-missing" &&
306
+ failed.refusal === "herdr-build-status-unavailable" &&
307
+ JSON.stringify(buildStatusArgv()) === JSON.stringify(["integration", "status"]) &&
308
+ residue.length === 0,
309
+ );
310
+ }
311
+
312
+ // ── 3. a selected atom Herdr cannot serve fails BEFORE any runtime work ────────
313
+ {
314
+ const cells: Record<string, { refusal: string | null; residue: number }> = {};
315
+ const failRows: Record<string, Record<string, string>> = {
316
+ outdated: { pi: "outdated (legacy < v8) (/home/u/.pi)" },
317
+ "needs-repair": { pi: "needs repair (v8) (/home/u/.pi)" },
318
+ malformed: { pi: "current (v8" },
319
+ duplicate: {},
320
+ };
321
+ for (const [tag, rows] of Object.entries(failRows)) {
322
+ const env = world(`fail-${tag}`);
323
+ const text =
324
+ tag === "duplicate" ? `${listing({ pi: "current (v8) (/home/u/.pi)" })}pi: current (v8) (/x)\n` : listing(rows);
325
+ const r = drive(env, text, { acquire: fixtureAcquire({ activate: "recorder" }), resolveCommit: () => COMMIT_A });
326
+ cells[tag] = { refusal: r.refusal, residue: tree(env.HOME as string).length };
327
+ }
328
+ const verdicts = Object.entries(cells).map(([k, v]) => `${k}=${v.refusal}/${v.residue}`);
329
+ ok(
330
+ "[QK:HPB-SELECTED-FAIL-BEFORE-BOOTSTRAP] `outdated`, `needs repair`, a malformed row and a duplicate " +
331
+ "declaration for a SELECTED atom each fail by name before one byte of runtime work — and `outdated " +
332
+ "(legacy < vN)` is exactly the catch-all an empty or unreadable integration file produces (measured), so " +
333
+ "treating it as absence would install a runtime for a harness whose integration cannot run it. The refusal " +
334
+ `arrives with the runtime address still untouched (${verdicts.join(" ")})`,
335
+ cells.outdated.refusal === "herdr-build-selected-integration-unusable" &&
336
+ cells["needs-repair"].refusal === "herdr-build-selected-integration-unusable" &&
337
+ cells.malformed.refusal === "herdr-status-row-malformed" &&
338
+ cells.duplicate.refusal === "herdr-status-row-duplicate" &&
339
+ Object.values(cells).every((v) => v.residue === 0),
340
+ );
341
+ }
342
+
343
+ // ── 4. nothing integrated is a clean exit 0 that writes nothing ────────────────
344
+ {
345
+ const env = world("empty-a");
346
+ const r = drive(env, listing({}), {
347
+ acquire: fixtureAcquire({ activate: "recorder" }),
348
+ resolveCommit: () => COMMIT_A,
349
+ });
350
+ const profile = profileLeaf.buildActivationProfile(listing({}));
351
+ ok(
352
+ "[QK:HPB-EMPTY-A-ZERO-WRITE] a host where neither Pi nor Claude is integrated gets exit 0 and ZERO writes — no " +
353
+ "runtime, no wiring and above all no removal. A Herdr user in that state asked for nothing; a plugin that " +
354
+ "tore something down, or installed a runtime for nobody, would be acting on an absence. OpenCode is reported " +
355
+ `as observed and never planned (code=${r.code} residue=${JSON.stringify(tree(env.HOME as string))} activate=${JSON.stringify(profile.activate)} observed=${JSON.stringify(profile.observedOtherAtoms)} out=${JSON.stringify(r.out.trim())})`,
356
+ r.code === 0 &&
357
+ tree(env.HOME as string).length === 0 &&
358
+ profile.activate.length === 0 &&
359
+ profile.observedOtherAtoms.includes("opencode") &&
360
+ r.out.includes("nothing to activate") &&
361
+ r.out.includes("opencode"),
362
+ );
363
+ }
364
+
365
+ // ── 5. the activation verb is the INSTALLED one, and it must be capable ────────
366
+ {
367
+ const absentEnv = world("entry-absent");
368
+ const absent = drive(absentEnv, listing({ pi: "current (v8) (/home/u/.pi)" }), {
369
+ acquire: fixtureAcquire({ activate: "absent" }),
370
+ resolveCommit: () => COMMIT_A,
371
+ });
372
+ const incapableEnv = world("entry-incapable");
373
+ const incapable = drive(incapableEnv, listing({ pi: "current (v8) (/home/u/.pi)" }), {
374
+ acquire: fixtureAcquire({ activate: "incapable" }),
375
+ resolveCommit: () => COMMIT_A,
376
+ });
377
+ const okEnv = world("entry-ok");
378
+ const log = path.join(okEnv.HOME as string, "argv.log");
379
+ const good = drive(okEnv, listing({ pi: "current (v8) (/home/u/.pi)" }), {
380
+ acquire: fixtureAcquire({ activate: "recorder", log }),
381
+ resolveCommit: () => COMMIT_A,
382
+ });
383
+ const failEnv = world("entry-fails");
384
+ const failed = drive(failEnv, listing({ pi: "current (v8) (/home/u/.pi)" }), {
385
+ acquire: fixtureAcquire({ activate: "recorder", log: path.join(failEnv.HOME as string, "argv.log"), fail: true }),
386
+ resolveCommit: () => COMMIT_A,
387
+ });
388
+ const resolved = resolveActivationEntry(activeDirOf(okEnv), PACKAGE);
389
+ const source = fs.readFileSync(RUNNER, "utf8");
390
+ const namesCheckoutVerb = /CHECKOUT_ROOT[^\n]*herdr-plugin-activate|PACKAGE_ROOT/.test(source);
391
+ ok(
392
+ "[QK:HPB-INSTALLED-ENTRY-ONLY] the activation verb is resolved UNDER the stable runtime that was just " +
393
+ "installed — never in this checkout, which Herdr deletes on uninstall while calling no cleanup hook, so a " +
394
+ "checkout-side activation would be undoable only by code that is about to vanish and would prove nothing " +
395
+ "about the artifact just placed. An absent entry and one that exits 0 on an empty request are separate named " +
396
+ "refusals: the capability probe's evidence IS the named refusal, and a file that succeeds on an empty " +
397
+ `request is not this verb (absent=${absent.refusal} incapable=${incapable.refusal} ok=${good.code}/logged=${fs.readFileSync(log, "utf8").trim().length > 0} entry-under-runtime=${resolved.startsWith(activeDirOf(okEnv))} component-failure=${failed.refusal} checkout-verb-referenced=${namesCheckoutVerb})`,
398
+ absent.refusal === "herdr-build-activation-entry-missing" &&
399
+ incapable.refusal === "herdr-build-activation-entry-incapable" &&
400
+ good.code === 0 &&
401
+ fs.readFileSync(log, "utf8").trim().length > 0 &&
402
+ resolved === path.join(activeDirOf(okEnv), "node_modules", PACKAGE, ACTIVATE_ENTRY) &&
403
+ failed.refusal === "herdr-build-activation-failed" &&
404
+ !namesCheckoutVerb,
405
+ );
406
+ }
407
+
408
+ // ── 6. the two-stage journey, as the RUNNER composes it ────────────────────────
409
+ {
410
+ const env = world("two-stage");
411
+ const log = path.join(env.HOME as string, "argv.log");
412
+ let acquisitions = 0;
413
+ const counted = (args: AcquireArgs) => {
414
+ acquisitions++;
415
+ return fixtureAcquire({ activate: "recorder", log })(args);
416
+ };
417
+ const stage1 = drive(env, listing({ pi: "current (v8) (/home/u/.pi)" }), {
418
+ acquire: counted,
419
+ resolveCommit: () => COMMIT_A,
420
+ });
421
+ const stage2 = drive(env, listing({ pi: "current (v8) (/home/u/.pi)", claude: "current (v5) (/home/u/.claude)" }), {
422
+ acquire: counted,
423
+ resolveCommit: () => COMMIT_A,
424
+ });
425
+ const stage3 = drive(env, listing({ pi: "current (v8) (/home/u/.pi)", claude: "current (v5) (/home/u/.claude)" }), {
426
+ acquire: counted,
427
+ resolveCommit: () => COMMIT_B,
428
+ });
429
+ const calls = fs.readFileSync(log, "utf8").trim().split("\n");
430
+ const journal = JSON.parse(fs.readFileSync(journalPathOf(env), "utf8")) as {
431
+ artifactIdentity: { commit: string };
432
+ };
433
+ ok(
434
+ "[QK:HPB-SEQUENTIAL-TWO-STAGE] the sequence a real operator performs, as the runner composes it: `{pi}` first, " +
435
+ "then `{pi, claude-code}` after they integrated Claude Code, then a NEW commit of the same package version. " +
436
+ "The unchanged commit acquires nothing while still reconciling the widened set; the new commit reinstalls. " +
437
+ "OpenCode is present in every listing and appears in NO activation argv — the bytes those activations write " +
438
+ "are `check-herdr-activation`'s HAC-TWO-STAGE-ADD-ONLY, which drives the real Pi writers; what this cell owns " +
439
+ `is what the runner hands down (codes=${stage1.code}/${stage2.code}/${stage3.code} acquisitions=${acquisitions} calls=${JSON.stringify(calls)} commit=${journal.artifactIdentity.commit.slice(0, 8)})`,
440
+ stage1.code === 0 &&
441
+ stage2.code === 0 &&
442
+ stage3.code === 0 &&
443
+ acquisitions === 2 &&
444
+ JSON.stringify(calls) === JSON.stringify(["pi", "pi,claude-code", "pi,claude-code"]) &&
445
+ !calls.some((c) => c.includes("opencode")) &&
446
+ journal.artifactIdentity.commit === COMMIT_B,
447
+ );
448
+ }
449
+
450
+ // ── 6a. the authority check happens BEFORE any runtime work ───────────────────
451
+ {
452
+ const IDENTITY = (commit: string) => ({
453
+ kind: "herdr-checkout",
454
+ repository: "junghan0611/entwurf",
455
+ commit,
456
+ packageName: PACKAGE,
457
+ packageVersion: "0.21.0",
458
+ observedDigest: `sha256-${createHash("sha256").update(commit).digest("hex")}`,
459
+ });
460
+ /** A host that already has a runtime at commit A and a ledger that says so. */
461
+ const seeded = (tag: string, backends: string[]): NodeJS.ProcessEnv => {
462
+ const env = world(tag);
463
+ const log = path.join(env.HOME as string, "argv.log");
464
+ drive(env, listing({ pi: "current (v8) (/home/u/.pi)" }), {
465
+ acquire: fixtureAcquire({ activate: "recorder", log }),
466
+ resolveCommit: () => COMMIT_A,
467
+ });
468
+ const states: Record<string, string> = {};
469
+ for (const b of backends) states[b] = "active";
470
+ activation.writeLedger(
471
+ activation.resolveActivationLayout(env),
472
+ activation.ledgerBody({
473
+ phase: "active",
474
+ runtimeRoot: activeDirOf(env),
475
+ artifactIdentity: IDENTITY(COMMIT_A),
476
+ roots: activation.resolveComponentRoots(env),
477
+ states,
478
+ }),
479
+ );
480
+ return env;
481
+ };
482
+ // CONTENT, mode and topology — not a list of names. A same-length in-place edit passes a
483
+ // path/size comparison, and "this refusal changed nothing" is exactly the claim such an edit
484
+ // would falsify.
485
+ const snapshot = (env: NodeJS.ProcessEnv) => ({
486
+ runtime: treeDigest(activeDirOf(env)),
487
+ journal: fs.readFileSync(journalPathOf(env), "utf8"),
488
+ ledger: fs.readFileSync(activation.resolveActivationLayout(env).ledgerPath, "utf8"),
489
+ cache: treeDigest(path.join(env.XDG_CACHE_HOME as string, "entwurf", "herdr-plugin", "npm")),
490
+ activations: fs.readFileSync(path.join(env.HOME as string, "argv.log"), "utf8"),
491
+ });
492
+ const unchanged = (env: NodeJS.ProcessEnv, before: ReturnType<typeof snapshot>) =>
493
+ JSON.stringify(snapshot(env)) === JSON.stringify(before);
494
+
495
+ // THE ORACLE'S OWN PROOF, in the cell whose whole claim rests on it. The first cut of this
496
+ // oracle compared sorted paths and sizes and called that byte-identical; a same-length in-place
497
+ // edit — the shape an in-place rewrite actually has — passed it. So: an identical copy must
498
+ // digest the same, and flipping ONE byte without changing any length must digest differently,
499
+ // along with a mode change and an emptied (rather than removed) directory.
500
+ const oracleRoot = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-hpb-oracle-")));
501
+ fs.mkdirSync(path.join(oracleRoot, "nested", "deep"), { recursive: true });
502
+ fs.writeFileSync(path.join(oracleRoot, "nested", "deep", "a.txt"), "AAAA");
503
+ fs.writeFileSync(path.join(oracleRoot, "nested", "b.sh"), "#!/bin/sh\nexit 0\n");
504
+ fs.chmodSync(path.join(oracleRoot, "nested", "b.sh"), 0o755);
505
+ const oracleBase = treeDigest(oracleRoot);
506
+ const oracleCopy = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-hpb-oracle-copy-")));
507
+ fs.cpSync(oracleRoot, oracleCopy, { recursive: true });
508
+ const sameTree = treeDigest(oracleCopy) === oracleBase;
509
+ fs.writeFileSync(path.join(oracleRoot, "nested", "deep", "a.txt"), "AAAB"); // SAME length
510
+ const sameLengthEditSeen = treeDigest(oracleRoot) !== oracleBase;
511
+ fs.writeFileSync(path.join(oracleRoot, "nested", "deep", "a.txt"), "AAAA");
512
+ const restored = treeDigest(oracleRoot) === oracleBase;
513
+ fs.chmodSync(path.join(oracleRoot, "nested", "b.sh"), 0o644);
514
+ const modeSeen = treeDigest(oracleRoot) !== oracleBase;
515
+ fs.chmodSync(path.join(oracleRoot, "nested", "b.sh"), 0o755);
516
+ fs.rmSync(path.join(oracleRoot, "nested", "deep", "a.txt"));
517
+ const emptiedDirSeen = treeDigest(oracleRoot) !== oracleBase;
518
+ const oracleHonest = sameTree && sameLengthEditSeen && restored && modeSeen && emptiedDirSeen;
519
+
520
+ // (a) a SOURCE switch: the committed lock would now name npm over a checkout-sourced activation.
521
+ // (a) is asked of the SHARED authority directly: the runner reads its lock from the committed
522
+ // file, and this gate does not add a seam that would let any caller choose the source.
523
+ const src = seeded("plan-source", ["pi"]);
524
+ const srcBefore = snapshot(src);
525
+ const srcRefusal = refusalOf(() =>
526
+ runner.certifyActivationPlan(src, {
527
+ lock: { source: "npm", name: PACKAGE, version: "0.21.0", integrity: "sha512-x" },
528
+ checkoutRoot: fixtureCheckoutRoot(),
529
+ requested: ["pi"],
530
+ }),
531
+ );
532
+ const srcUnchanged = unchanged(src, srcBefore);
533
+
534
+ // (b) a new commit whose request DROPS a backend the ledger holds.
535
+ const drop = seeded("plan-drop", ["pi", "claude-code"]);
536
+ const dropBefore = snapshot(drop);
537
+ let dropAcquired = 0;
538
+ const dropDrive = drive(drop, listing({ pi: "current (v8) (/home/u/.pi)" }), {
539
+ acquire: (args: AcquireArgs) => {
540
+ dropAcquired++;
541
+ return fixtureAcquire({ activate: "recorder", log: path.join(drop.HOME as string, "argv.log") })(args);
542
+ },
543
+ resolveCommit: () => COMMIT_B,
544
+ });
545
+
546
+ // (c) the ledger and the runtime already disagree — nobody rebound an earlier replacement.
547
+ const torn = seeded("plan-torn", ["pi"]);
548
+ const tornLedger = activation.resolveActivationLayout(torn).ledgerPath;
549
+ fs.writeFileSync(
550
+ tornLedger,
551
+ `${JSON.stringify({ ...(JSON.parse(fs.readFileSync(tornLedger, "utf8")) as Record<string, unknown>), artifactIdentity: IDENTITY(COMMIT_B) }, null, 2)}\n`,
552
+ );
553
+ const tornBefore = snapshot(torn);
554
+ let tornAcquired = 0;
555
+ const tornDrive = drive(torn, listing({ pi: "current (v8) (/home/u/.pi)" }), {
556
+ acquire: (args: AcquireArgs) => {
557
+ tornAcquired++;
558
+ return fixtureAcquire({ activate: "recorder", log: path.join(torn.HOME as string, "argv.log") })(args);
559
+ },
560
+ resolveCommit: () => COMMIT_A,
561
+ });
562
+
563
+ // (d) the LEGAL case still passes: same source, new commit, request covers the ledger. The
564
+ // authority is asked on the PRE-install state, which is the only state it is ever asked about.
565
+ const legal = seeded("plan-legal", ["pi"]);
566
+ const legalPlan = runner.certifyActivationPlan(legal, {
567
+ lock: { source: "herdr-checkout", repository: "junghan0611/entwurf" },
568
+ checkoutRoot: fixtureCheckoutRoot(),
569
+ requested: ["pi"],
570
+ resolveCommit: () => COMMIT_B,
571
+ });
572
+ const legalDrive = drive(legal, listing({ pi: "current (v8) (/home/u/.pi)" }), {
573
+ acquire: fixtureAcquire({ activate: "recorder", log: path.join(legal.HOME as string, "argv.log") }),
574
+ resolveCommit: () => COMMIT_B,
575
+ });
576
+
577
+ ok(
578
+ "[QK:HPB-PRE-BOOTSTRAP-AUTHORITY] the activation authority is asked BEFORE any runtime work, through the SAME " +
579
+ "shipped functions the installed verb runs: a source switch, a new commit whose request drops an activated " +
580
+ "backend, and a ledger that already disagrees with the runtime standing there are each refused with the " +
581
+ "runtime tree, its journal, our cache, the ledger and the activation log ALL byte-identical and the " +
582
+ "acquisition never called. The first cut bootstrapped first and let the installed verb refuse afterwards, " +
583
+ "which left the stable root holding an artifact no record accounted for while the run reported failure — and " +
584
+ "the harness wiring names that root, not the version, so it resolved to those bytes anyway. " +
585
+ "UNCHANGED here means content + mode + topology, hashed: this cell proves its own oracle, because the first " +
586
+ "cut of it compared paths and sizes and a same-length in-place edit walked straight through " +
587
+ `(oracle-honest=${oracleHonest}/same-tree=${sameTree}/same-length-edit-seen=${sameLengthEditSeen}/mode-seen=${modeSeen}/emptied-dir-seen=${emptiedDirSeen} source=${srcRefusal}/unchanged=${srcUnchanged} dropped=${dropDrive.refusal}/acquired=${dropAcquired}/unchanged=${unchanged(drop, dropBefore)} torn=${tornDrive.refusal}/acquired=${tornAcquired}/unchanged=${unchanged(torn, tornBefore)} legal=${legalDrive.code}/${legalPlan.disposition})`,
588
+ oracleHonest &&
589
+ srcRefusal === "activation-artifact-source-drifted" &&
590
+ srcUnchanged &&
591
+ dropDrive.refusal === "activation-rebind-refused" &&
592
+ dropAcquired === 0 &&
593
+ unchanged(drop, dropBefore) &&
594
+ tornDrive.refusal === "activation-runtime-ledger-mismatch" &&
595
+ tornAcquired === 0 &&
596
+ unchanged(torn, tornBefore) &&
597
+ legalDrive.code === 0 &&
598
+ legalPlan.disposition === "rebind",
599
+ );
600
+ }
601
+
602
+ // ── 6b. what landed must be what the authority admitted ───────────────────────
603
+ {
604
+ const env = world("d1-d2-drift");
605
+ const log = path.join(env.HOME as string, "argv.log");
606
+ const commits = [COMMIT_A, COMMIT_B, COMMIT_B];
607
+ let call = 0;
608
+ const moving = () => commits[Math.min(call++, commits.length - 1)] as string;
609
+ const drifted = drive(env, listing({ pi: "current (v8) (/home/u/.pi)" }), {
610
+ acquire: fixtureAcquire({ activate: "recorder", log }),
611
+ resolveCommit: moving,
612
+ });
613
+ const journal = JSON.parse(fs.readFileSync(journalPathOf(env), "utf8")) as { artifactIdentity: { commit: string } };
614
+ const activated = fs.existsSync(log) ? fs.readFileSync(log, "utf8").trim() : "";
615
+ ok(
616
+ "[QK:HPB-BOOTSTRAP-IDENTITY-EXACT] the artifact that lands must be EXACTLY the one the authority check admitted: " +
617
+ "when the source moves between the judgement and the install, the run refuses by name instead of handing the " +
618
+ "installed activation verb a different artifact than the one this build was cleared to place — the verb would " +
619
+ "then re-judge, and pass or fail on a question nobody asked. The refusal is honest about what did happen: the " +
620
+ `runtime was replaced, and NO activation ran (refusal=${drifted.refusal} landed=${journal.artifactIdentity.commit.slice(0, 8)} admitted=${COMMIT_A.slice(0, 8)} activations=${JSON.stringify(activated)})`,
621
+ drifted.refusal === "herdr-build-artifact-drifted" &&
622
+ journal.artifactIdentity.commit === COMMIT_B &&
623
+ activated === "",
624
+ );
625
+ }
626
+
627
+ // ── 7. the runner claims nothing about Herdr's own commit ──────────────────────
628
+ {
629
+ const env = world("post-build-gap");
630
+ const log = path.join(env.HOME as string, "argv.log");
631
+ const rows = { pi: "current (v8) (/home/u/.pi)" };
632
+ const first = drive(env, listing(rows), {
633
+ acquire: fixtureAcquire({ activate: "recorder", log }),
634
+ resolveCommit: () => COMMIT_A,
635
+ });
636
+ const journalAfter = fs.readFileSync(journalPathOf(env), "utf8");
637
+ const runtimeAfter = tree(activeDirOf(env));
638
+ // Herdr's own commit now FAILS: it reclaims its temp checkout and leaves its registry on the
639
+ // previous entry. Nothing of ours is called — there is no cleanup hook — so the runtime and the
640
+ // activation we performed simply remain. The next successful install must reconcile them.
641
+ const second = drive(env, listing(rows), {
642
+ acquire: fixtureAcquire({ activate: "recorder", log }),
643
+ resolveCommit: () => COMMIT_A,
644
+ });
645
+ const calls = fs.readFileSync(log, "utf8").trim().split("\n");
646
+ const journalPreserved = fs.readFileSync(journalPathOf(env), "utf8") === journalAfter;
647
+ ok(
648
+ "[QK:HPB-POST-BUILD-GAP] Herdr commits its checkout and registry only AFTER a successful build, so this runner " +
649
+ "records `runtime-ready` and says NOTHING about Herdr's registry: when Herdr's own commit then fails, the " +
650
+ "runtime and the activation it performed REMAIN — Herdr has no cleanup hook to call — and the next " +
651
+ "successful install reconciles them rather than duplicating them. This is a NAMED GAP, not an atomic " +
652
+ `transaction, and the receipt above it must not say otherwise (first=${first.code} second=${second.code} journal-preserved=${journalPreserved} runtime=${runtimeAfter.length} calls=${JSON.stringify(calls)} out=${JSON.stringify(second.out.trim().split("\n")[0])})`,
653
+ first.code === 0 &&
654
+ second.code === 0 &&
655
+ journalPreserved &&
656
+ runtimeAfter.length > 0 &&
657
+ JSON.stringify(calls) === JSON.stringify(["pi", "pi"]) &&
658
+ !/herdr-installed|registered|registry/i.test(first.out) &&
659
+ second.out.includes("already exact"),
660
+ );
661
+ }
662
+
663
+ // ── 8. the REAL binary's listing, when this host has one ───────────────────────
664
+ {
665
+ const probe = spawnSync("herdr", ["--version"], { encoding: "utf8" });
666
+ if (probe.error || probe.status !== 0) {
667
+ if (process.env.ENTWURF_REQUIRE_HERDR === "1") {
668
+ assert.fail(
669
+ "[QK:HPB-REAL-STATUS-PARSES] ENTWURF_REQUIRE_HERDR=1 and no usable `herdr` on PATH: absence is optional, " +
670
+ `broken is not (${probe.error ? probe.error.message : `exit ${probe.status}`})`,
671
+ );
672
+ }
673
+ console.log(
674
+ " skip [QK:HPB-REAL-STATUS-PARSES] no `herdr` on PATH — herdr is an optional rail, so this cell reports a " +
675
+ "named SKIP rather than a silent pass (set ENTWURF_REQUIRE_HERDR=1 to make absence red)",
676
+ );
677
+ } else {
678
+ const env = world("real-status");
679
+ const status = spawnSync("herdr", [...buildStatusArgv()], {
680
+ encoding: "utf8",
681
+ env: { ...env, XDG_CONFIG_HOME: path.join(env.HOME as string, "config") },
682
+ });
683
+ const text = readIntegrationListing(
684
+ { ...env, XDG_CONFIG_HOME: path.join(env.HOME as string, "config") },
685
+ { herdrBin: "herdr" },
686
+ );
687
+ const profile = profileLeaf.buildActivationProfile(text);
688
+ const rows = text.trim().split("\n").length;
689
+ ok(
690
+ "[QK:HPB-REAL-STATUS-PARSES] the REAL binary's `integration status` answers with no server running and its " +
691
+ "prose parses through the production profile leaf on this host — that serverless read is the whole reason " +
692
+ "a build can ask Herdr what is integrated before Herdr has committed anything, and the grammar this " +
693
+ `plugin parses is the grammar the shipped binary emits (version=${(probe.stdout || "").trim()} exit=${status.status} rows=${rows} activate=${JSON.stringify(profile.activate)} observed=${JSON.stringify(profile.observedOtherAtoms)})`,
694
+ status.status === 0 && rows >= 2 && Array.isArray(profile.activate),
695
+ );
696
+ }
697
+ }
698
+
699
+ // ── 10. the operator is told what is happening, on a channel herdr does not eat ─
700
+ {
701
+ const env = world("progress-sequence");
702
+ const full = drive(env, listing({ pi: "current (v8) (/home/u/.pi)", claude: "current (v8) (/home/u/.claude)" }), {
703
+ acquire: fixtureAcquire({ activate: "recorder", log: path.join(env.HOME as string, "argv.log") }),
704
+ resolveCommit: () => COMMIT_A,
705
+ });
706
+ const idle = drive(world("progress-idle"), listing({}));
707
+ const longStep = full.progress[2] ?? "";
708
+ ok(
709
+ "[QK:HPB-PROGRESS-NAMED-SEQUENCE] a build narrates the five steps IN ORDER and names the long one — herdr pipes " +
710
+ "both of this process's streams into a buffer it DISCARDS on success (`src/cli/plugin.rs:1328-1373` @ c77af189), " +
711
+ "so an operator who has just answered the install prompt sees nothing at all through a multi-minute `npm pack` " +
712
+ "and reads it as a hang. The sequence is reported, not logged: each step names the work about to start, the " +
713
+ "acquisition step says out loud that silence is expected and which source it is reaching for, and a run with " +
714
+ "nothing to activate takes exactly ONE step and then closes — it must not narrate work it never did. " +
715
+ "Pinning all six positions is also what keeps a usage NOTE out of the sequence: a note is not an outcome, " +
716
+ "and one read as a seventh step says the install is still going " +
717
+ `(full=${JSON.stringify(full.progress)} idle=${JSON.stringify(idle.progress)})`,
718
+ full.code === 0 &&
719
+ full.progress.length >= 6 &&
720
+ full.progress[0].includes("integration status") &&
721
+ full.progress[1].includes("pi, claude-code") &&
722
+ longStep.includes("long step") &&
723
+ longStep.includes(COMMIT_A.slice(0, 8)) &&
724
+ full.progress[3].includes("what landed") &&
725
+ full.progress[4].includes("wiring pi, claude-code") &&
726
+ full.progress[5].startsWith("done: ") &&
727
+ idle.code === 0 &&
728
+ idle.progress.length === 2 &&
729
+ idle.progress[1].startsWith("done: nothing to activate"),
730
+ );
731
+ }
732
+
733
+ // ── 10a. the install says how to USE what it just wired ────────────────────────
734
+ {
735
+ const env = world("progress-usage-note");
736
+ const full = drive(env, listing({ pi: "current (v8) (/home/u/.pi)", claude: "current (v8) (/home/u/.claude)" }), {
737
+ acquire: fixtureAcquire({ activate: "recorder", log: path.join(env.HOME as string, "argv.log") }),
738
+ resolveCommit: () => COMMIT_A,
739
+ });
740
+ const piOnly = drive(world("progress-usage-note-pi"), listing({ pi: "current (v8) (/home/u/.pi)" }), {
741
+ acquire: fixtureAcquire({ activate: "recorder", log: path.join(world("unused").HOME as string, "argv.log") }),
742
+ resolveCommit: () => COMMIT_A,
743
+ });
744
+ const notesOf = (r: { progress: string[] }) => r.progress.filter((line) => line.startsWith("note: "));
745
+ ok(
746
+ "[QK:HPB-INSTALL-USAGE-NOTE] a green install ends by saying how to USE each backend it just wired, one line " +
747
+ "each, and only for the backends it actually activated. `[관측: GLG, 날것 PC, 2026-09-17]` the two wirings " +
748
+ "feel opposite from the operator's chair and neither is guessable: claude-code gets an MCP server so an " +
749
+ "ordinary `claude` has the tools, while pi gets a USER-SCOPE package registration that loads in every pi " +
750
+ "session on the host and is STILL not a citizen until it is started with --entwurf-control. Green install, " +
751
+ "`pi` starts, nothing there — indistinguishable from an install that did nothing. The note names the FLAG " +
752
+ `and not a launcher, because what goes on a host's PATH is the operator's call (both=${JSON.stringify(notesOf(full))} pi-only=${JSON.stringify(notesOf(piOnly))})`,
753
+ notesOf(full).length === 4 &&
754
+ notesOf(full)[0].includes("pi: ") &&
755
+ notesOf(full)[0].includes("--entwurf-control") &&
756
+ notesOf(full)[2].includes("claude-code: ") &&
757
+ notesOf(full)[2].includes("MCP") &&
758
+ notesOf(piOnly).length === 2 &&
759
+ notesOf(piOnly)[0].includes("--entwurf-control") &&
760
+ notesOf(piOnly).every((line) => line.includes("pi: ")),
761
+ );
762
+ // `[관측: GLG, 날것 PC, 2026-09-17]` "일단 정확한 모델명을 모른다" — the note that says a backend is
763
+ // wired is not usable until the operator can name a model for it, and the two harnesses spell
764
+ // them differently enough that guessing fails. One example each, scoped to what was activated.
765
+ ok(
766
+ `[QK:HPB-USAGE-NOTE-NAMES-A-MODEL] each activated backend also gets ONE example model id in its own grammar — provider-qualified for pi, the vendor id for claude-code — and a backend that was not activated gets neither (both=${JSON.stringify(notesOf(full))} pi-only=${JSON.stringify(notesOf(piOnly))})`,
767
+ ((): boolean => {
768
+ // The token INSIDE the backticks, not a substring of the line: `anthropic/claude-sonnet-5`
769
+ // contains `claude-sonnet-5`, so a containment test cannot tell the two grammars apart —
770
+ // which is the whole claim.
771
+ const idIn = (lines: string[], backend: string): string | null => {
772
+ const line = lines.find((l) => l.startsWith(`note: ${backend}: `) && l.includes("model id looks like"));
773
+ return line === undefined ? null : (/`([^`]+)`/.exec(line)?.[1] ?? null);
774
+ };
775
+ const piId = idIn(notesOf(full), "pi");
776
+ const claudeId = idIn(notesOf(full), "claude-code");
777
+ return (
778
+ piId === "openai-codex/gpt-5.6-terra" &&
779
+ piId.includes("/") &&
780
+ claudeId === "claude-sonnet-5" &&
781
+ !claudeId.includes("/") &&
782
+ idIn(notesOf(piOnly), "pi") === "openai-codex/gpt-5.6-terra" &&
783
+ idIn(notesOf(piOnly), "claude-code") === null
784
+ );
785
+ })(),
786
+ );
787
+ }
788
+
789
+ // ── 11. no terminal is an ordinary state, and the trail survives either way ─────
790
+ {
791
+ const tty: string[] = [];
792
+ const mirrored: string[] = [];
793
+ let closed = 0;
794
+ const live = progressLeaf.createProgressReporter({
795
+ openTty: () => 7,
796
+ writeTty: (fd: number, line: string) => tty.push(`${fd}:${line}`),
797
+ closeTty: () => {
798
+ closed += 1;
799
+ },
800
+ mirror: (line: string) => mirrored.push(line),
801
+ });
802
+ live.step("one");
803
+ live.done("two");
804
+ live.close();
805
+ live.close();
806
+
807
+ const blindMirror: string[] = [];
808
+ const blind = progressLeaf.createProgressReporter({
809
+ openTty: () => {
810
+ throw new Error("ENXIO: no controlling terminal");
811
+ },
812
+ writeTty: () => {
813
+ throw new Error("must never be called without a terminal");
814
+ },
815
+ closeTty: () => {
816
+ throw new Error("must never be called without a terminal");
817
+ },
818
+ mirror: (line: string) => blindMirror.push(line),
819
+ });
820
+ blind.step("one");
821
+ blind.close();
822
+
823
+ ok(
824
+ "[QK:HPB-PROGRESS-TTY-OPTIONAL] the progress channel is the operator's terminal when there is one and NOTHING " +
825
+ "when there is not — a CI runner, a pipe or a daemon has no `/dev/tty`, and a build that failed for want of a " +
826
+ "terminal would be the narration breaking the install it exists to explain. Every line is mirrored to stderr " +
827
+ "either way, which costs nothing on success (herdr drops it) and is the trail in front of the error on failure. " +
828
+ `The descriptor is closed once, and closing twice is not an error (tty=${JSON.stringify(tty)} mirrored=${JSON.stringify(mirrored)} closed=${closed} blind-live=${blind.live} blind-mirrored=${JSON.stringify(blindMirror)})`,
829
+ live.live === true &&
830
+ tty.length === 2 &&
831
+ tty[0] === "7:[entwurf 1/5] one\n" &&
832
+ tty[1] === "7:[entwurf done] two\n" &&
833
+ mirrored.length === 2 &&
834
+ mirrored[0] === "[entwurf 1/5] one\n" &&
835
+ closed === 1 &&
836
+ blind.live === false &&
837
+ blindMirror.length === 1 &&
838
+ blindMirror[0] === "[entwurf 1/5] one\n",
839
+ );
840
+ }
841
+
842
+ console.log(`\ncheck-herdr-plugin-build: ${passed} assertions passed`);