@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,543 @@
1
+ /**
2
+ * smoke-herdr-plugin-build-live — the #116 M3-b3 journey a real Herdr user performs, with a REAL
3
+ * `herdr plugin install` driving our REAL `[[build]]` and a REAL `npm pack` of the product git spec.
4
+ *
5
+ * OUT of `pnpm check`. Needs `LIVE=1`, a `herdr` binary, `git`, `npm` — and the NETWORK, because a
6
+ * git-spec pack builds the bridge through `prepare`, which installs devDependencies from the
7
+ * registry. That is the axis `check-herdr-plugin-build` cannot have and why it does not claim it.
8
+ *
9
+ * WHAT MAKES IT HERMETIC ANYWAY. Two substitutions, and only two. HOME/XDG are a mkdtemp sandbox,
10
+ * so Herdr's registry, its managed checkout, our runtime, our ledger and both harnesses' wiring all
11
+ * land inside it. And the FIXED product remote — `https://github.com/junghan0611/entwurf.git`, which
12
+ * neither this smoke nor any caller can change — is redirected through git's own `insteadOf`, in the
13
+ * sandbox `.gitconfig`, to a bare clone of a CANDIDATE SNAPSHOT built inside the sandbox (see the
14
+ * block below: a bare clone of the worktree would carry only the last commit, which is how this
15
+ * fixture first passed while testing the wrong bytes). So the argv under test is byte-for-byte the
16
+ * product argv; what differs is where git looks, which is the one substitution that keeps an
17
+ * unpushed candidate testable. This repo's HEAD, its porcelain status and the operator's own herdr
18
+ * registry are all read before and after and must be identical.
19
+ *
20
+ * WHAT IS REAL VENDOR EVIDENCE HERE. If `claude` is on PATH, the Claude activation runs the vendor
21
+ * CLI for real against the sandbox HOME — which is the named LIVE boundary the deterministic gates
22
+ * refuse to claim: whether Claude Code accepts an ABSOLUTE executable as an MCP command. A host
23
+ * without `claude` reports that cell as a named skip inside an otherwise green run, never a pass.
24
+ *
25
+ * THE FOUR CELLS, AND THEY ARE NOT ALL THE SAME KIND OF EVIDENCE:
26
+ *
27
+ * | cell | what drives it | what it is evidence of |
28
+ * |---|---|---|
29
+ * | 1. remote-commit AVAILABLE | real `herdr plugin install` | the Herdr journey: one install, runtime at the stable root keyed by the commit Herdr resolved, Pi wired at the absolute bridge, ledger `active` |
30
+ * | 2. REINSTALL widening | real `herdr plugin install` | the Herdr journey: Pi reconciled JSON-identical, Claude added as exactly ONE counted owner entry (real vendor CLI), OpenCode given zero Entwurf state |
31
+ * | 3. remote-commit UNAVAILABLE | the shipped runtime leaf, called directly with the FIXED product argv | acquisition-leaf evidence only — a remote that cannot serve a commit. Routing it through Herdr would add a second explanation for one red, so it does not, and it does not call itself a journey |
32
+ * | 4. POST-BUILD GAP | real `herdr plugin install` | the Herdr journey: a build that mutates the manifest makes HERDR's own commit fail after our runner already succeeded, so Herdr keeps the previous registration while the runtime and the rebound activation REMAIN, and the next normal install re-binds to HEAD. Nothing here calls that atomic |
33
+ */
34
+
35
+ import assert from "node:assert/strict";
36
+ import { spawnSync } from "node:child_process";
37
+ import fs from "node:fs";
38
+ import os from "node:os";
39
+ import path from "node:path";
40
+ import { skipLive } from "./lib/live-skip.ts";
41
+ import { treeDigest } from "./lib/tree-digest.ts";
42
+
43
+ const LABEL = "smoke-herdr-plugin-build-live";
44
+ const REPO = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
45
+ const PACKAGE = "@junghanacs/entwurf";
46
+ const PRODUCT_REMOTE = "https://github.com/junghan0611/entwurf.git";
47
+ const PLUGIN_SPEC = "junghan0611/entwurf/plugins/herdr";
48
+ const PLUGIN_ID = "junghan0611.entwurf";
49
+
50
+ let passed = 0;
51
+ let skipped = 0;
52
+ function ok(label: string, cond: boolean): void {
53
+ assert.ok(cond, label);
54
+ console.log(` ok ${label}`);
55
+ passed++;
56
+ }
57
+ function skip(label: string, why: string): void {
58
+ console.log(` skip ${label} — ${why}`);
59
+ skipped++;
60
+ }
61
+
62
+ function run(bin: string, argv: string[], env: NodeJS.ProcessEnv, cwd = REPO) {
63
+ return spawnSync(bin, argv, { encoding: "utf8", env, cwd, timeout: 900_000 });
64
+ }
65
+
66
+ if (process.env.LIVE !== "1") {
67
+ skipLive(
68
+ LABEL,
69
+ "LIVE=1 not set — this smoke runs a real `herdr plugin install` and a real npm pack over the network",
70
+ );
71
+ }
72
+ for (const bin of ["herdr", "git", "npm"]) {
73
+ const probe = spawnSync(bin, ["--version"], { encoding: "utf8" });
74
+ if (probe.error || probe.status !== 0) skipLive(LABEL, `\`${bin}\` is not usable on this host`);
75
+ }
76
+
77
+ const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-hpbl-"));
78
+ const HOME = path.join(ROOT, "home");
79
+ const MIRROR = path.join(ROOT, "entwurf.git");
80
+ for (const d of ["config", "data", "state", "cache", ".pi", ".claude", ".config/opencode"]) {
81
+ fs.mkdirSync(path.join(HOME, d), { recursive: true });
82
+ }
83
+ const env: NodeJS.ProcessEnv = {
84
+ PATH: process.env.PATH,
85
+ TERM: "dumb",
86
+ HOME,
87
+ XDG_CONFIG_HOME: path.join(HOME, "config"),
88
+ XDG_DATA_HOME: path.join(HOME, "data"),
89
+ XDG_STATE_HOME: path.join(HOME, "state"),
90
+ XDG_CACHE_HOME: path.join(HOME, "cache"),
91
+ PI_CODING_AGENT_DIR: path.join(HOME, ".pi", "agent"),
92
+ CLAUDE_CONFIG_DIR: path.join(HOME, ".claude"),
93
+ GIT_CONFIG_GLOBAL: path.join(HOME, ".gitconfig"),
94
+ };
95
+ fs.mkdirSync(env.PI_CODING_AGENT_DIR as string, { recursive: true });
96
+ fs.writeFileSync(path.join(env.PI_CODING_AGENT_DIR as string, "settings.json"), '{"theme":"dark"}\n');
97
+
98
+ // ── COMMITS RUN UNDER THE OPERATOR'S OWN GIT CONFIG, HOOKS INCLUDED ────────────────────────
99
+ //
100
+ // The sandbox `GIT_CONFIG_GLOBAL` above is what keeps herdr's clones and our `insteadOf` out of the
101
+ // operator's config — but handing it to a `git commit` HIDES their global
102
+ // `core.hooksPath`, which is the safety rail that scans staged diffs for identity terms and
103
+ // secrets. A fixture that commits without it has bypassed that rail by omission, which is worse
104
+ // than bypassing it on purpose: nothing says so. So the two commits below run under the process's
105
+ // NORMAL environment — no sandbox config, no `--no-verify`, no `AGENT_ALLOW_UNSAFE_COMMIT`, and no
106
+ // argv or env that sets, clears or redirects `core.hooksPath`. The only `-c` overrides are the
107
+ // repo-local author identity, because a temp clone has no reason to inherit one.
108
+ const COMMIT_ENV: NodeJS.ProcessEnv = { ...process.env };
109
+ const configuredHooksPath = (
110
+ spawnSync("git", ["config", "--global", "--get", "core.hooksPath"], { encoding: "utf8", env: COMMIT_ENV }).stdout ??
111
+ ""
112
+ ).trim();
113
+
114
+ /**
115
+ * One ordinary commit, with its hook invocation OBSERVED rather than steered: `GIT_TRACE2_EVENT`
116
+ * writes git's own child-process events to a sandbox file, and the assertion reads that file for a
117
+ * `pre-commit` child under the configured hooks path. Trace is read-only — it cannot change which
118
+ * hook git chooses — so this proves the rail ran instead of asserting that we did not disable it.
119
+ */
120
+ function commitInClone(dir: string, message: string, tag: string): { hookTrace: string; traceFile: string } {
121
+ const traceFile = path.join(ROOT, `git-trace-${tag}.json`);
122
+ const committed = spawnSync(
123
+ "git",
124
+ [
125
+ "-C",
126
+ dir,
127
+ "-c",
128
+ "user.email=candidate@sandbox.local",
129
+ "-c",
130
+ "user.name=candidate snapshot",
131
+ "commit",
132
+ "--quiet",
133
+ "-a",
134
+ "-m",
135
+ message,
136
+ ],
137
+ { encoding: "utf8", env: { ...COMMIT_ENV, GIT_TRACE2_EVENT: traceFile } },
138
+ );
139
+ assert.equal(committed.status, 0, `${tag} commit failed: ${committed.stderr || committed.stdout}`);
140
+ const trace = fs.existsSync(traceFile) ? fs.readFileSync(traceFile, "utf8") : "";
141
+ const hookLines = trace.split("\n").filter((line) => line.includes('"pre-commit"') && line.includes('"child_start"'));
142
+ if (configuredHooksPath === "") {
143
+ console.log(` info ${tag}: no global core.hooksPath on this host — portable behaviour, no hook receipt to read`);
144
+ return { hookTrace: "no-configured-hook-path", traceFile };
145
+ }
146
+ // EXACTLY ONE. `> 0` would also pass a commit that ran the rail twice, and two invocations mean
147
+ // one of them is reading a state the other already changed — the count is part of the claim, so
148
+ // it is asserted rather than reported.
149
+ const underConfigured = hookLines.filter((line) => line.includes(configuredHooksPath));
150
+ assert.equal(
151
+ underConfigured.length,
152
+ 1,
153
+ `${tag}: expected EXACTLY ONE pre-commit child under the configured hook path ${configuredHooksPath}, observed ` +
154
+ `${underConfigured.length} (pre-commit child_start lines in ${traceFile}: ${hookLines.length})`,
155
+ );
156
+ return {
157
+ hookTrace: `exactly ${underConfigured.length} pre-commit child under ${configuredHooksPath}`,
158
+ traceFile,
159
+ };
160
+ }
161
+
162
+ /** A clone that a commit has just finished with: nothing staged, nothing modified, nothing untracked. */
163
+ function assertExactlyClean(dir: string, tag: string): void {
164
+ const porcelain = (
165
+ spawnSync("git", ["-C", dir, "status", "--porcelain"], { encoding: "utf8", env }).stdout ?? ""
166
+ ).trim();
167
+ const diff = spawnSync("git", ["-C", dir, "diff", "--binary", "HEAD"], { encoding: "utf8", env }).stdout ?? "";
168
+ assert.equal(
169
+ porcelain,
170
+ "",
171
+ `${tag} is not clean after its commit — a hook or the commit itself left changes: ${porcelain}`,
172
+ );
173
+ assert.equal(
174
+ diff.length,
175
+ 0,
176
+ `${tag} still differs from its own HEAD after committing (${diff.length} bytes of diff)`,
177
+ );
178
+ }
179
+
180
+ // ── THE CANDIDATE HAS TO BE IN THE MIRROR, AND A BARE CLONE DOES NOT PUT IT THERE ──────────
181
+ //
182
+ // Measured on this smoke's first run: `git clone --bare <repo>` copies COMMITS, so a frozen
183
+ // uncommitted candidate — 22 modified files and 5 new ones sitting in the operator's index and
184
+ // worktree — never reached Herdr at all. `herdr plugin install` reported status 0 while installing
185
+ // the PREVIOUS commit, whose manifest has no `[[build]]`, and every candidate assertion below was
186
+ // absent rather than wrong. A fixture that quietly tests the wrong bytes is worse than a red.
187
+ //
188
+ // So: clone the operator repo into the sandbox, replay its `git diff --binary HEAD` (the exact
189
+ // index+worktree bytes) into that clone, and commit it there with an ORDINARY commit under the
190
+ // sandbox `GIT_CONFIG_GLOBAL` — no `--no-verify`, no hook path games. The bare MIRROR is then a
191
+ // clone of THAT snapshot. Post-commit (an empty diff) the same code runs with no snapshot commit at
192
+ // all, which is the shape this will have once the candidate lands.
193
+ //
194
+ // UNTRACKED FILES ARE A HARD FAILURE, not a silent omission: `git diff HEAD` cannot see them, so a
195
+ // candidate byte living only in an untracked file would be dropped exactly the way the whole
196
+ // candidate just was.
197
+ const operatorStatusBefore = (run("git", ["status", "--porcelain"], env).stdout ?? "").trim();
198
+ const operatorHeadBefore = (run("git", ["rev-parse", "HEAD"], env).stdout ?? "").trim();
199
+ const untracked = operatorStatusBefore.split("\n").filter((line) => line.startsWith("??"));
200
+ assert.equal(
201
+ untracked.length,
202
+ 0,
203
+ `the candidate carries untracked files, which \`git diff HEAD\` cannot capture — stage them (git add -N) ` +
204
+ `so this fixture tests the candidate and not the last commit: ${untracked.join(", ")}`,
205
+ );
206
+ const SNAPSHOT = path.join(ROOT, "snapshot");
207
+ assert.equal(run("git", ["clone", "--quiet", REPO, SNAPSHOT], env).status, 0, "candidate clone failed");
208
+ const candidatePatch = run("git", ["diff", "--binary", "HEAD"], env).stdout ?? "";
209
+ let snapshotKind = "operator HEAD (no uncommitted candidate)";
210
+ let snapshotHook = "no snapshot commit was needed";
211
+ if (candidatePatch.trim().length > 0) {
212
+ const patchFile = path.join(ROOT, "candidate.patch");
213
+ fs.writeFileSync(patchFile, candidatePatch);
214
+ const applied = run("git", ["-C", SNAPSHOT, "apply", "--index", patchFile], env);
215
+ assert.equal(applied.status, 0, `the candidate patch did not apply to the snapshot clone: ${applied.stderr}`);
216
+ snapshotHook = commitInClone(SNAPSHOT, "candidate snapshot under test", "snapshot").hookTrace;
217
+ // LOAD-BEARING, before the mirror is made from it: if a hook rewrote a file, or the patch left
218
+ // anything behind, the mirror would carry bytes the candidate does not have.
219
+ assertExactlyClean(SNAPSHOT, "the candidate snapshot clone");
220
+ snapshotKind = "snapshot commit of the frozen candidate";
221
+ }
222
+
223
+ // The ONE substitution: the fixed product remote resolves to a bare clone of that snapshot.
224
+ const mirrorClone = run("git", ["clone", "--bare", "--quiet", SNAPSHOT, MIRROR], env);
225
+ assert.equal(mirrorClone.status, 0, `bare clone failed: ${mirrorClone.stderr}`);
226
+ fs.writeFileSync(
227
+ path.join(HOME, ".gitconfig"),
228
+ [`[url "file://${MIRROR}"]`, `\tinsteadOf = ${PRODUCT_REMOTE}`, "[safe]", "\tdirectory = *", ""].join("\n"),
229
+ );
230
+ // HEAD/BRANCH come from what the MIRROR actually serves, never from the operator repo: the whole
231
+ // failure above was the gap between those two.
232
+ const HEAD = (run("git", ["-C", SNAPSHOT, "rev-parse", "HEAD"], env).stdout ?? "").trim();
233
+ const BRANCH = (run("git", ["-C", SNAPSHOT, "rev-parse", "--abbrev-ref", "HEAD"], env).stdout ?? "").trim();
234
+ const mirrorCommit = (run("git", ["--git-dir", MIRROR, "rev-parse", BRANCH], env).stdout ?? "").trim();
235
+ assert.equal(mirrorCommit, HEAD, `the mirror serves ${mirrorCommit} for ${BRANCH}, not the candidate ${HEAD}`);
236
+ console.log(
237
+ ` info candidate under test: ${HEAD.slice(0, 12)} on ${BRANCH} — ${snapshotKind}; safety hooks: ${snapshotHook}`,
238
+ );
239
+
240
+ const operatorBefore = spawnSync("herdr", ["plugin", "list"], { encoding: "utf8" }).stdout ?? "";
241
+
242
+ const activeDir = path.join(env.XDG_DATA_HOME as string, "entwurf", "herdr-plugin", "runtime", "active");
243
+ const journalPath = path.join(env.XDG_DATA_HOME as string, "entwurf", "herdr-plugin", "journal.json");
244
+ const ledgerPath = path.join(env.XDG_STATE_HOME as string, "entwurf", "herdr-plugin", "activation.json");
245
+ const piSettings = path.join(env.PI_CODING_AGENT_DIR as string, "settings.json");
246
+ const opencodeDir = path.join(HOME, ".config", "opencode");
247
+ fs.writeFileSync(path.join(opencodeDir, "config.json"), '{"theme":"tokyonight"}\n');
248
+ const opencodeBefore = fs.readFileSync(path.join(opencodeDir, "config.json"), "utf8");
249
+
250
+ function readJson(file: string): Record<string, unknown> {
251
+ return JSON.parse(fs.readFileSync(file, "utf8")) as Record<string, unknown>;
252
+ }
253
+
254
+ function integrationInstall(atom: string) {
255
+ return run("herdr", ["integration", "install", atom], env);
256
+ }
257
+ function pluginInstall(ref: string) {
258
+ return run("herdr", ["plugin", "install", PLUGIN_SPEC, "--ref", ref, "--yes"], env);
259
+ }
260
+ function managedCheckout(): string | null {
261
+ const base = path.join(env.XDG_CONFIG_HOME as string, "herdr", "plugins", "github");
262
+ if (!fs.existsSync(base)) return null;
263
+ const entry = fs.readdirSync(base).find((e) => e.startsWith(PLUGIN_ID));
264
+ return entry ? path.join(base, entry) : null;
265
+ }
266
+
267
+ try {
268
+ // ── 1. the remote HAS the commit: one install, {pi} activated ──────────────
269
+ const piIntegration = integrationInstall("pi");
270
+ if (piIntegration.status !== 0) {
271
+ skipLive(
272
+ LABEL,
273
+ `herdr could not admit the pi integration in a sandbox HOME (${(piIntegration.stderr || piIntegration.stdout || "").trim().slice(0, 200)}) — without a selected atom there is no journey to run`,
274
+ );
275
+ }
276
+ const first = pluginInstall(BRANCH);
277
+ const journal = fs.existsSync(journalPath) ? readJson(journalPath) : null;
278
+ const ledger = fs.existsSync(ledgerPath) ? readJson(ledgerPath) : null;
279
+ const identity = (journal?.artifactIdentity ?? {}) as Record<string, string>;
280
+ const checkout = managedCheckout();
281
+ // The managed checkout's git root is asked for rather than guessed: the manifest root is a
282
+ // SUBDIRECTORY of the clone, and a hardcoded `<dir>/checkout` would silently answer nothing.
283
+ const checkoutRoot =
284
+ checkout === null ? "" : (run("git", ["-C", checkout, "rev-parse", "--show-toplevel"], env).stdout ?? "").trim();
285
+ const checkoutHead =
286
+ checkoutRoot === "" ? "" : (run("git", ["-C", checkoutRoot, "rev-parse", "HEAD"], env).stdout ?? "").trim();
287
+ const shallow =
288
+ checkoutRoot === ""
289
+ ? "no-checkout"
290
+ : (run("git", ["-C", checkoutRoot, "rev-parse", "--is-shallow-repository"], env).stdout ?? "").trim();
291
+ const piAfterFirst = readJson(piSettings);
292
+ const provider = (piAfterFirst.entwurfProvider ?? {}) as Record<string, unknown>;
293
+ const providerText = JSON.stringify(provider);
294
+ ok(
295
+ `[${LABEL}] REMOTE-COMMIT AVAILABLE: a real \`herdr plugin install\` drove this plugin's own \`[[build]]\`, which ` +
296
+ "packed the FIXED product git spec at the commit Herdr resolved, installed it at the stable runtime root, and " +
297
+ "activated exactly the integrated set — the whole journey a Herdr user gets for one command, with no Entwurf " +
298
+ "clone and no npm release anywhere in it. THREE commits have to be the same one here — what the mirror " +
299
+ "served, what Herdr checked out, and what the runtime records — because this fixture's first failure was " +
300
+ `exactly the gap between them (install=${first.status} mirror=${mirrorCommit.slice(0, 12)} checkout=${checkoutHead.slice(0, 12)} runtime=${identity.commit?.slice(0, 12)} kind=${identity.kind} package=${identity.packageName}@${identity.packageVersion} ledger=${JSON.stringify(ledger?.activatedBackends)} phase=${ledger?.phase} shallow=${shallow} provider-absolute=${providerText.includes(activeDir)})`,
301
+ first.status === 0 &&
302
+ identity.kind === "herdr-checkout" &&
303
+ identity.commit === HEAD &&
304
+ checkoutHead === HEAD &&
305
+ mirrorCommit === HEAD &&
306
+ identity.repository === "junghan0611/entwurf" &&
307
+ identity.packageName === PACKAGE &&
308
+ (ledger?.phase as string) === "active" &&
309
+ JSON.stringify(ledger?.activatedBackends) === JSON.stringify(["pi"]) &&
310
+ (ledger?.runtimeRoot as string) === activeDir &&
311
+ providerText.includes(activeDir),
312
+ );
313
+
314
+ // ── 2. the reinstall widens {pi} → {pi, claude-code}, OpenCode untouched ────
315
+ const claudeOnPath = spawnSync("claude", ["--version"], { encoding: "utf8" }).status === 0;
316
+ const claudeIntegration = integrationInstall("claude");
317
+ const opencodeIntegration = integrationInstall("opencode");
318
+ const piBeforeSecond = JSON.stringify(readJson(piSettings));
319
+ const second = pluginInstall(BRANCH);
320
+ const ledger2 = fs.existsSync(ledgerPath) ? readJson(ledgerPath) : null;
321
+ const piAfterSecond = JSON.stringify(readJson(piSettings));
322
+ const opencodeUntouched = fs.readFileSync(path.join(opencodeDir, "config.json"), "utf8") === opencodeBefore;
323
+ const opencodeEntwurfState = fs
324
+ .readdirSync(opencodeDir)
325
+ .filter((e) => e.toLowerCase().includes("entwurf"))
326
+ .concat(JSON.stringify(readJson(path.join(opencodeDir, "config.json"))).includes("entwurf") ? ["config"] : []);
327
+ if (!claudeOnPath) {
328
+ skip(
329
+ `[${LABEL}] REINSTALL WIDENING`,
330
+ "no `claude` on PATH, so the vendor half of this cell cannot run; the Pi/OpenCode halves are proven by " +
331
+ "check-herdr-activation and a partial pass here would read as vendor evidence",
332
+ );
333
+ } else {
334
+ // EXACTLY ONE OWNER ENTRY, counted. A substring search would pass on a config that carried the
335
+ // command twice, or carried it under a key nobody reads — neither is "wired exactly once".
336
+ const claudeConfig = path.join(HOME, ".claude.json");
337
+ const claudeCfg = fs.existsSync(claudeConfig) ? readJson(claudeConfig) : {};
338
+ const servers = (claudeCfg.mcpServers ?? {}) as Record<string, { command?: string }>;
339
+ const bridgeKeys = Object.keys(servers).filter((k) => k.includes("entwurf"));
340
+ const bridgeCommand = bridgeKeys.length === 1 ? (servers[bridgeKeys[0] as string]?.command ?? "") : "";
341
+ ok(
342
+ `[${LABEL}] REINSTALL WIDENING: with Claude Code and OpenCode now integrated, the same install command ` +
343
+ "reconciles Pi with JSON-IDENTICAL settings and adds Claude as EXACTLY ONE counted owner entry whose " +
344
+ "command is the absolute bridge under the stable runtime — accepted by the REAL vendor CLI, which is the " +
345
+ "LIVE boundary no deterministic gate may claim. The entry is counted, not searched for: a substring would " +
346
+ "pass on a config carrying the command twice, or under a key nobody reads. OpenCode, present the whole " +
347
+ `time, is given zero Entwurf state (claude-integration=${claudeIntegration.status} opencode-integration=${opencodeIntegration.status} install=${second.status} ledger=${JSON.stringify(ledger2?.activatedBackends)} pi-identical=${piBeforeSecond === piAfterSecond} entwurf-mcp-keys=${JSON.stringify(bridgeKeys)} command-under-runtime=${bridgeCommand.startsWith(activeDir)} opencode-untouched=${opencodeUntouched} opencode-entwurf=${JSON.stringify(opencodeEntwurfState)})`,
348
+ second.status === 0 &&
349
+ JSON.stringify(ledger2?.activatedBackends) === JSON.stringify(["pi", "claude-code"]) &&
350
+ (ledger2?.phase as string) === "active" &&
351
+ piBeforeSecond === piAfterSecond &&
352
+ bridgeKeys.length === 1 &&
353
+ path.isAbsolute(bridgeCommand) &&
354
+ bridgeCommand.startsWith(activeDir) &&
355
+ opencodeUntouched &&
356
+ opencodeEntwurfState.length === 0,
357
+ );
358
+ }
359
+
360
+ // ── 3. ACQUISITION LEAF: the remote does NOT have the commit ───────────────
361
+ //
362
+ // This cell is NOT the Herdr journey. It calls the shipped runtime leaf directly with the FIXED
363
+ // product argv, because the failure it measures is the acquisition's — a remote that cannot serve
364
+ // a commit — and routing it through `herdr plugin install` would add nothing but a second
365
+ // explanation for the same red. Cells 1, 2 and 4 are the real-Herdr journey; this one is
366
+ // acquisition-leaf LIVE evidence, and that distinction is the point of naming it here.
367
+ {
368
+ const runtimeBefore = treeDigest(activeDir);
369
+ const ledgerBefore = fs.readFileSync(ledgerPath, "utf8");
370
+ const piBefore = JSON.stringify(readJson(piSettings));
371
+ const journalBefore = readJson(journalPath) as {
372
+ artifactIdentity: Record<string, string>;
373
+ previousRuntime: Record<string, string> | null;
374
+ };
375
+ const absentCommit = "9".repeat(40);
376
+ const probe = run(
377
+ "node",
378
+ [
379
+ "--input-type=module",
380
+ "-e",
381
+ [
382
+ `const m = await import(${JSON.stringify(path.join(REPO, "scripts", "herdr-runtime.mjs"))});`,
383
+ "try {",
384
+ " m.bootstrapRuntime({ env: process.env, lock: { source: 'herdr-checkout', repository: 'junghan0611/entwurf' },",
385
+ ` checkoutRoot: ${JSON.stringify(REPO)}, resolveCommit: () => ${JSON.stringify(absentCommit)} });`,
386
+ " console.log('NO-REFUSAL');",
387
+ "} catch (err) { console.log(err.code); }",
388
+ ].join("\n"),
389
+ ],
390
+ env,
391
+ );
392
+ const verdict = (probe.stdout || "").trim().split("\n").pop();
393
+ // The HONEST oracle. The journal is NOT byte-identical and must not be claimed as such: the
394
+ // transaction deliberately writes a certified `installing` entry BEFORE acquiring, because that
395
+ // entry is the retry authority and the only thing that makes the staging tree and the cache
396
+ // ours to reclaim. What must be unchanged is everything the failure had no business touching —
397
+ // the active runtime, the activation ledger, the harness wiring — while the journal is left in
398
+ // the recoverable state its own contract describes, and the normal source still installs.
399
+ //
400
+ // MEASURE THE FAILURE BEFORE THE RETRY. The first run of this cell asserted the post-failure
401
+ // facts AFTER running the retry install, so the retry's own (correct) work — a fresh runtime
402
+ // tree and a re-created cache — read as "the failure changed things". Order is the oracle here.
403
+ const afterFailure = {
404
+ runtime: treeDigest(activeDir),
405
+ ledger: fs.readFileSync(ledgerPath, "utf8"),
406
+ pi: JSON.stringify(readJson(piSettings)),
407
+ staging: fs.existsSync(path.join(path.dirname(activeDir), "staging")),
408
+ cache: fs.existsSync(path.join(env.XDG_CACHE_HOME as string, "entwurf", "herdr-plugin", "npm")),
409
+ journal: readJson(journalPath) as {
410
+ phase: string;
411
+ artifactIdentity: Record<string, string>;
412
+ previousRuntime: Record<string, string> | null;
413
+ },
414
+ };
415
+ const journalAfter = afterFailure.journal;
416
+ const retry = pluginInstall(BRANCH);
417
+ const journalRetried = readJson(journalPath) as { phase: string; artifactIdentity: Record<string, string> };
418
+ ok(
419
+ `[${LABEL}] REMOTE-COMMIT UNAVAILABLE (acquisition leaf, not the Herdr journey): the FIXED product argv at a ` +
420
+ "commit the remote does not carry is refused by name and substitutes NOTHING. The active runtime, the " +
421
+ "activation ledger and the Pi wiring are byte-identical; the journal is left as a CERTIFIED `installing` " +
422
+ "entry naming the requested commit with the previous runtime's identity preserved — that is the retry " +
423
+ "authority, not damage — the staging tree and cache are gone, and a normal install afterwards is green. " +
424
+ "This is the branch where a fallback to a registry version would be most tempting and would silently serve " +
425
+ `an artifact nobody asked for (verdict=${verdict} runtime-identical=${runtimeBefore === afterFailure.runtime} ledger-identical=${ledgerBefore === afterFailure.ledger} pi-identical=${piBefore === afterFailure.pi} journal=${journalAfter.phase}/${journalAfter.artifactIdentity.commit?.slice(0, 8)}/prev=${journalAfter.previousRuntime?.commit?.slice(0, 8)} staging=${afterFailure.staging} cache=${afterFailure.cache} retry=${retry.status}/${journalRetried.phase}/${journalRetried.artifactIdentity.commit?.slice(0, 8)})`,
426
+ verdict === "runtime-checkout-source-unavailable" &&
427
+ runtimeBefore === afterFailure.runtime &&
428
+ ledgerBefore === afterFailure.ledger &&
429
+ piBefore === afterFailure.pi &&
430
+ journalAfter.phase === "installing" &&
431
+ journalAfter.artifactIdentity.commit === absentCommit &&
432
+ journalAfter.previousRuntime?.commit === journalBefore.artifactIdentity.commit &&
433
+ !afterFailure.staging &&
434
+ !afterFailure.cache &&
435
+ retry.status === 0 &&
436
+ journalRetried.phase === "runtime-ready" &&
437
+ journalRetried.artifactIdentity.commit === HEAD,
438
+ );
439
+ }
440
+
441
+ // ── 4. Herdr's own commit fails AFTER our runner succeeded ─────────────────
442
+ {
443
+ const scratch = path.join(ROOT, "scratch");
444
+ assert.equal(run("git", ["clone", "--quiet", MIRROR, scratch], env).status, 0, "scratch clone");
445
+ assert.equal(run("git", ["-C", scratch, "checkout", "--quiet", "-b", "gapprobe", HEAD], env).status, 0, "branch");
446
+ // A build that MUTATES the manifest: herdr reloads it after the build and aborts the install
447
+ // (measured: `ensure_manifest_unchanged_after_build`). Our runner still ran to completion.
448
+ const runnerPath = path.join(scratch, "plugins", "herdr", "lib", "build.mjs");
449
+ // The mutation has to run BEFORE the runner's process exits, so it is INSERTED INTO the entry
450
+ // block rather than appended after it. Measured on the previous two runs: appended top-level
451
+ // code never executed at all (`process.exit(runBuild(...))` ends the process first), and the
452
+ // earlier `# comment` version of it would not have mattered anyway — herdr compares the PARSED
453
+ // manifest by full struct equality (`ensure_manifest_unchanged_after_build`, read at
454
+ // src/cli/plugin.rs), so only a semantic change makes its commit fail. Rewriting the declared
455
+ // plugin version is exactly that.
456
+ const runnerSource = fs.readFileSync(runnerPath, "utf8");
457
+ const entry = "\t\tprocess.exit(runBuild(process.env));";
458
+ assert.ok(runnerSource.includes(entry), "the runner's entry block is not the shape this probe patches");
459
+ fs.writeFileSync(
460
+ runnerPath,
461
+ runnerSource.replace(
462
+ entry,
463
+ [
464
+ "\t\tconst probeCode = runBuild(process.env);",
465
+ "\t\t// gap probe: herdr re-reads the manifest after the build and aborts its own commit.",
466
+ '\t\tconst probeManifest = path.join(PLUGIN_DIR, "herdr-plugin.toml");',
467
+ "\t\tfs.writeFileSync(",
468
+ "\t\t\tprobeManifest,",
469
+ '\t\t\tfs.readFileSync(probeManifest, "utf8").replace(\'version = "0.1.0"\', \'version = "0.1.1"\'),',
470
+ "\t\t);",
471
+ "\t\tprocess.exit(probeCode);",
472
+ ].join("\n"),
473
+ ),
474
+ );
475
+ const gapHook = commitInClone(scratch, "gap probe", "gapprobe").hookTrace;
476
+ assertExactlyClean(scratch, "the gap-probe scratch clone");
477
+ assert.equal(run("git", ["-C", scratch, "push", "--quiet", MIRROR, "gapprobe"], env).status, 0, "push probe");
478
+ const gapCommit = (run("git", ["-C", scratch, "rev-parse", "HEAD"], env).stdout ?? "").trim();
479
+ const registryBefore = run("herdr", ["plugin", "list"], env).stdout ?? "";
480
+ const ledgerBefore = readJson(ledgerPath) as {
481
+ artifactIdentity: Record<string, string>;
482
+ activatedBackends: string[];
483
+ components: { backend: string; state: string }[];
484
+ };
485
+ const gapRun = pluginInstall("gapprobe");
486
+ const registryAfter = run("herdr", ["plugin", "list"], env).stdout ?? "";
487
+ // The gap commit is a DIFFERENT artifact, so the ledger cannot be byte-identical here and
488
+ // claiming it would be false: our runner ran to completion, which means it legally rebound the
489
+ // activation to that commit. What Herdr then failed to do is its own registration.
490
+ const ledgerAfterGap = readJson(ledgerPath) as {
491
+ phase: string;
492
+ artifactIdentity: Record<string, string>;
493
+ activatedBackends: string[];
494
+ components: { backend: string; state: string }[];
495
+ };
496
+ const reconcile = pluginInstall(BRANCH);
497
+ const ledgerAfterReconcile = readJson(ledgerPath) as {
498
+ phase: string;
499
+ artifactIdentity: Record<string, string>;
500
+ activatedBackends: string[];
501
+ components: { backend: string; state: string }[];
502
+ };
503
+ const noDuplicates = (l: { components: { backend: string }[] }) =>
504
+ new Set(l.components.map((c) => c.backend)).size === l.components.length;
505
+ ok(
506
+ `[${LABEL}] POST-BUILD GAP: Herdr commits its checkout and registry only AFTER a build, so a build that makes ` +
507
+ "that commit fail leaves Herdr's PREVIOUS registration standing while what our runner already did REMAINS " +
508
+ "— the runtime is the gap commit and the ledger has legally rebound to it, active and retained, because " +
509
+ "Herdr has no cleanup hook to call. The next normal install re-binds to HEAD's identity and reconciles the " +
510
+ "same components rather than duplicating them. The gap is NAMED, not atomic, and this cell asserts the " +
511
+ `actual transition rather than an unchanged-bytes claim that could never be true (gap-commit-hooks=${gapHook} gap-install=${gapRun.status} registry-unchanged=${registryBefore === registryAfter} before=${ledgerBefore.artifactIdentity.commit?.slice(0, 8)} after-gap=${ledgerAfterGap.phase}/${ledgerAfterGap.artifactIdentity.commit?.slice(0, 8)}=${gapCommit.slice(0, 8)} reconcile=${reconcile.status}/${ledgerAfterReconcile.phase}/${ledgerAfterReconcile.artifactIdentity.commit?.slice(0, 8)}=${HEAD.slice(0, 8)} backends=${JSON.stringify(ledgerAfterReconcile.activatedBackends)} no-dupes=${noDuplicates(ledgerAfterGap)}/${noDuplicates(ledgerAfterReconcile)})`,
512
+ gapRun.status !== 0 &&
513
+ registryBefore === registryAfter &&
514
+ ledgerAfterGap.phase === "active" &&
515
+ ledgerAfterGap.artifactIdentity.commit === gapCommit &&
516
+ noDuplicates(ledgerAfterGap) &&
517
+ reconcile.status === 0 &&
518
+ ledgerAfterReconcile.phase === "active" &&
519
+ ledgerAfterReconcile.artifactIdentity.commit === HEAD &&
520
+ JSON.stringify(ledgerAfterReconcile.activatedBackends) === JSON.stringify(ledgerBefore.activatedBackends) &&
521
+ noDuplicates(ledgerAfterReconcile),
522
+ );
523
+ }
524
+
525
+ // ── the operator's own herdr surface is untouched ──────────────────────────
526
+ const operatorAfter = spawnSync("herdr", ["plugin", "list"], { encoding: "utf8" }).stdout ?? "";
527
+ const operatorStatusAfter = (run("git", ["status", "--porcelain"], env).stdout ?? "").trim();
528
+ const operatorHeadAfter = (run("git", ["rev-parse", "HEAD"], env).stdout ?? "").trim();
529
+ ok(
530
+ `[${LABEL}] the OPERATOR is untouched: their herdr plugin registry is byte-identical, and so are this repo's HEAD ` +
531
+ "and porcelain status — the candidate snapshot is built by READING this repo's diff and committing it inside " +
532
+ "the sandbox, because a fixture that needed the candidate committed for real would be indistinguishable from " +
533
+ `the product asking for it (registry=${operatorBefore === operatorAfter} head=${operatorHeadBefore === operatorHeadAfter} status=${operatorStatusBefore === operatorStatusAfter})`,
534
+ operatorBefore === operatorAfter &&
535
+ operatorHeadBefore === operatorHeadAfter &&
536
+ operatorStatusBefore === operatorStatusAfter,
537
+ );
538
+
539
+ console.log(`\n${LABEL}: ${passed} assertions passed, ${skipped} skipped (sandbox kept at ${ROOT})`);
540
+ } catch (err) {
541
+ console.error(`\n${LABEL}: FAILED — sandbox kept for inspection at ${ROOT}`);
542
+ throw err;
543
+ }