@junghanacs/entwurf 0.45.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +3 -2
- package/BASELINE.md +110 -1
- package/CHANGELOG.md +169 -0
- package/CONTRIBUTING.md +1 -1
- package/ENTWURF-CLI.json +1229 -0
- package/ENTWURF.json +63 -15
- package/FAQ.md +4 -4
- package/README.md +124 -188
- package/VERIFY.md +2 -2
- package/docs/external-mcp-host.md +3 -3
- package/docs/setup-clean-host.md +37 -12
- package/mcp/entwurf-bridge/dist/ENTWURF.json +63 -15
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +19 -8
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +483 -18
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +613 -33
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-resume-call.js +497 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +3 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +50 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-location.js +347 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-owner-fence.js +1132 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +18 -11
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +30 -15
- package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +40 -7
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +1 -27
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
- package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +1 -1
- package/mcp/entwurf-bridge/src/index.ts +28 -8
- package/mcp/entwurf-bridge/tsconfig.build.json +7 -1
- package/native/win32-x64/manifest.json +1 -1
- package/package.json +6 -3
- package/pi/pi-durable/bootstrap.mjs +179 -6
- package/pi/pi-durable/carrier/runtime.js +108 -9
- package/pi/pi-durable/carrier/session-name.js +11 -0
- package/pi/pi-durable/carrier/sessions.js +96 -20
- package/pi/pi-durable/overlay/upstream-pin.json +11 -5
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +710 -38
- package/pi-extensions/lib/fresh-call-dispatch.ts +64 -12
- package/pi-extensions/lib/herdr-fresh-call.ts +919 -40
- package/pi-extensions/lib/herdr-resume-call.ts +810 -0
- package/pi-extensions/lib/meta-session.ts +13 -8
- package/pi-extensions/lib/mux-resume-call.ts +77 -4
- package/pi-extensions/lib/pi-durable-fresh-preflight.ts +9 -2
- package/pi-extensions/lib/pi-durable-location.ts +446 -0
- package/pi-extensions/lib/pi-durable-owner-fence.ts +1461 -0
- package/pi-extensions/lib/pi-durable-runtime.ts +4 -0
- package/pi-extensions/lib/resume-launch-identity.ts +31 -12
- package/pi-extensions/meta-bridge-pi-durable.ts +29 -15
- package/run.sh +411 -657
- package/scripts/check-claude-launch.ts +341 -0
- package/scripts/check-entwurf-v2-surface.ts +2 -2
- package/scripts/check-fresh-call-dispatch.ts +70 -9
- package/scripts/check-fresh-cut-gate.sh +23 -9
- package/scripts/check-gate-qualification.ts +18 -13
- package/scripts/check-herdr-activation.ts +301 -23
- package/scripts/check-herdr-fresh-call.ts +161 -25
- package/scripts/check-herdr-plugin-build.ts +173 -1
- package/scripts/check-herdr-plugin.ts +28 -2
- package/scripts/check-herdr-runtime-bootstrap.ts +136 -0
- package/scripts/check-install-surface.ts +1236 -6
- package/scripts/check-meta-doctor-oracle.sh +1 -1
- package/scripts/check-meta-facts.ts +20 -1
- package/scripts/check-mux-resume-call.ts +28 -0
- package/scripts/check-peer-facts.ts +39 -2
- package/scripts/cli-help.mjs +222 -0
- package/scripts/dev-bin.sh +39 -2
- package/scripts/herdr-activation.mjs +28 -6
- package/scripts/herdr-operator-link.mjs +389 -0
- package/scripts/herdr-plugin-activate.mjs +5 -0
- package/scripts/herdr-plugin-deactivate.mjs +18 -5
- package/scripts/herdr-rails.mjs +43 -7
- package/scripts/herdr-runtime.mjs +27 -0
- package/scripts/meta-bridge-fresh-cut.ts +1 -27
- package/scripts/meta-bridge-prune.ts +1 -1
- package/scripts/meta-facts.ts +1 -1
- package/scripts/mutants/agent-guidance.json +4 -4
- package/scripts/mutants/claude-launch.json +142 -0
- package/scripts/mutants/cli-help-source.json +420 -0
- package/scripts/mutants/dev-gate-dispatch.json +109 -0
- package/scripts/mutants/fresh-call-dispatch.json +38 -2
- package/scripts/mutants/fresh-cut.json +15 -0
- package/scripts/mutants/herdr-activation.json +128 -0
- package/scripts/mutants/herdr-fresh-call.json +278 -5
- package/scripts/mutants/herdr-operator-link.json +318 -0
- package/scripts/mutants/herdr-plugin-build.json +55 -0
- package/scripts/mutants/herdr-plugin.json +17 -0
- package/scripts/mutants/herdr-resume-call.json +288 -0
- package/scripts/mutants/herdr-runtime-bootstrap.json +48 -0
- package/scripts/mutants/meta-facts.json +15 -0
- package/scripts/mutants/mux-resume-call.json +63 -2
- package/scripts/mutants/peer-facts.json +15 -0
- package/scripts/mutants/pi-durable.json +979 -1
- package/scripts/mutants/source-install.json +35 -0
- package/scripts/mutants/v2-surface.json +2 -2
- package/scripts/mutants/v2-visible-resume.json +255 -6
- package/scripts/peer-facts.ts +1 -1
- package/scripts/smoke-agy-install-state.sh +66 -0
- package/scripts/smoke-meta-prune.sh +23 -0
- package/scripts/smoke-omp-fresh-live.ts +12 -3
- package/scripts/smoke-omp-receive-live.ts +7 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +12 -2
- package/team.json.example +39 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check-claude-launch — deterministic gate for the Claude Code launch (`entwurf claude`, #157 stage 4).
|
|
3
|
+
* Hermetic: no real Claude Code, no network, no model turn, no write outside its own temp root. Linux
|
|
4
|
+
* only: the fake vendor reads its own environment from /proc/$$/environ (it runs in the Linux check job).
|
|
5
|
+
*
|
|
6
|
+
* SAME ORACLE AS check-pi-launch and check-copilot-launch: the subject is a process REPLACEMENT, so
|
|
7
|
+
* the truth is a FAKE VENDOR — a real executable named `claude` on a sandbox PATH that reports the
|
|
8
|
+
* argv, cwd, environment and pid it was actually handed. Nothing below is asserted by reading the
|
|
9
|
+
* launcher's source, and the launcher is driven through its PUBLIC address (`run.sh claude`),
|
|
10
|
+
* because the dispatcher's own argv handling — the help preflight included — is part of the contract.
|
|
11
|
+
*
|
|
12
|
+
* WHAT THE LAUNCH PROMISES, AND WHAT IT DOES NOT. It is a spelling: the vendor's argv, cwd, exit
|
|
13
|
+
* status and signal are Claude Code's own, and `--help` is Claude Code's help, not an Entwurf page.
|
|
14
|
+
* It adds nothing to the argv. Its only environment changes are the recursion sentinel and the
|
|
15
|
+
* REMOVAL of an inherited PI_SESSION_ID/PI_AGENT_ID pair, which is another citizen's identity. It
|
|
16
|
+
* does not promise a garden citizen: that comes from the meta-bridge plugin and hooks `setup`
|
|
17
|
+
* installed, which this launch neither checks nor supplies, so nothing here models them.
|
|
18
|
+
*
|
|
19
|
+
* WHY TWO RECURSION FENCES WHEN PI HAS ONE. pi's single fence rests on the flag it injects: a PATH
|
|
20
|
+
* `pi` symlinked to run.sh re-enters as the unknown verb `--entwurf-control`. This launch injects
|
|
21
|
+
* nothing, so the same symlink would re-enter with the operator's argv as the VERB. The resolved-path
|
|
22
|
+
* fence refuses that, and the cell measuring it hands the loop an EMPTY argv on purpose: with the
|
|
23
|
+
* fence mutated away, the re-entry prints the root page and exits 0 instead of running any verb.
|
|
24
|
+
*
|
|
25
|
+
* TWO CELLS CARRY NO MUTANT, and the reason is measured coupling rather than a gap. run.sh is
|
|
26
|
+
* `set -euo pipefail`, so dropping `exec` still propagates a non-zero vendor status (set -e exits
|
|
27
|
+
* with it) and turns a vendor death by signal into a status of 128+n. The mutation that breaks the
|
|
28
|
+
* exit or signal claim is therefore the one that breaks the pid claim, and the pid cell is asserted
|
|
29
|
+
* first so that mutant lands on exactly one QK. The exit and signal cells stand on their assertions.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import assert from "node:assert/strict";
|
|
33
|
+
import { spawnSync } from "node:child_process";
|
|
34
|
+
import {
|
|
35
|
+
chmodSync,
|
|
36
|
+
existsSync,
|
|
37
|
+
mkdirSync,
|
|
38
|
+
mkdtempSync,
|
|
39
|
+
readFileSync,
|
|
40
|
+
realpathSync,
|
|
41
|
+
rmSync,
|
|
42
|
+
symlinkSync,
|
|
43
|
+
writeFileSync,
|
|
44
|
+
} from "node:fs";
|
|
45
|
+
import { tmpdir } from "node:os";
|
|
46
|
+
import * as path from "node:path";
|
|
47
|
+
import { fileURLToPath } from "node:url";
|
|
48
|
+
|
|
49
|
+
const REPO = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
50
|
+
const RUN_SH = path.join(REPO, "run.sh");
|
|
51
|
+
const SENTINEL = "ENTWURF_CLAUDE_LAUNCH_ACTIVE";
|
|
52
|
+
/** The variables bash itself maintains across a script and an exec — not the launcher's choice. */
|
|
53
|
+
const BASH_OWNED = new Set(["SHLVL", "_", "PWD", "OLDPWD"]);
|
|
54
|
+
|
|
55
|
+
let passed = 0;
|
|
56
|
+
function ok(label: string, cond: boolean, detail = ""): void {
|
|
57
|
+
assert.ok(cond, detail ? `${label}\n${detail}` : label);
|
|
58
|
+
console.log(` ok ${label}`);
|
|
59
|
+
passed++;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const root = mkdtempSync(path.join(tmpdir(), "entwurf-claude-launch."));
|
|
63
|
+
try {
|
|
64
|
+
const bin = path.join(root, "bin");
|
|
65
|
+
const work = path.join(root, "work");
|
|
66
|
+
mkdirSync(bin, { recursive: true });
|
|
67
|
+
mkdirSync(work, { recursive: true });
|
|
68
|
+
const envDump = path.join(root, "vendor.environ");
|
|
69
|
+
|
|
70
|
+
// The fake vendor, installed under the REAL name `claude`. It reports what exec handed it: its
|
|
71
|
+
// own initial environment is read from the kernel (/proc/$$/environ), not from its shell's view,
|
|
72
|
+
// so bash's own bookkeeping inside the fake is not mistaken for the launcher's.
|
|
73
|
+
const vendor = path.join(bin, "claude");
|
|
74
|
+
writeFileSync(
|
|
75
|
+
vendor,
|
|
76
|
+
`#!/usr/bin/env bash
|
|
77
|
+
echo "PID=$$"
|
|
78
|
+
echo "CWD=$(pwd -P)"
|
|
79
|
+
echo "SENTINEL=[\${${SENTINEL}-<unset>}]"
|
|
80
|
+
cat "/proc/$$/environ" >"$FAKE_CLAUDE_ENV_OUT"
|
|
81
|
+
for a in "$@"; do printf 'ARG<%s>\\n' "$a"; done
|
|
82
|
+
if [ -n "\${FAKE_CLAUDE_SIGNAL:-}" ]; then kill -s "$FAKE_CLAUDE_SIGNAL" "$$"; sleep 5; fi
|
|
83
|
+
exit "\${FAKE_CLAUDE_EXIT:-0}"
|
|
84
|
+
`,
|
|
85
|
+
);
|
|
86
|
+
chmodSync(vendor, 0o755);
|
|
87
|
+
|
|
88
|
+
// TOTAL sandbox, as the sibling launch gates: the launcher writes nothing, but it EXECS a vendor.
|
|
89
|
+
const sandboxEnv = {
|
|
90
|
+
HOME: path.join(root, "home"),
|
|
91
|
+
XDG_DATA_HOME: path.join(root, "xdg", "data"),
|
|
92
|
+
XDG_CONFIG_HOME: path.join(root, "xdg", "config"),
|
|
93
|
+
XDG_STATE_HOME: path.join(root, "xdg", "state"),
|
|
94
|
+
XDG_CACHE_HOME: path.join(root, "xdg", "cache"),
|
|
95
|
+
XDG_RUNTIME_DIR: path.join(root, "xdg", "runtime"),
|
|
96
|
+
PI_CODING_AGENT_DIR: path.join(root, "pi-agent"),
|
|
97
|
+
CLAUDE_CONFIG_DIR: path.join(root, "claude-config"),
|
|
98
|
+
};
|
|
99
|
+
for (const d of Object.values(sandboxEnv)) mkdirSync(d, { recursive: true });
|
|
100
|
+
|
|
101
|
+
// A PATH with no `claude` anywhere on it: every real entry that holds one is dropped, rather than
|
|
102
|
+
// emptying PATH (the launcher still needs bash, dirname and readlink).
|
|
103
|
+
const pathWithoutVendor = (process.env.PATH ?? "")
|
|
104
|
+
.split(":")
|
|
105
|
+
.filter((d) => d !== "" && !existsSync(path.join(d, "claude")))
|
|
106
|
+
.join(":");
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Every bash this gate starts gets a NON-SOCKET stdin. Node's default stdin is a socketpair, and
|
|
110
|
+
* bash reads `-c` with a socket on stdin as a remote shell and sources the system bashrc — measured
|
|
111
|
+
* here on NixOS, where that re-runs set-environment and resets PATH, dropping the sandbox bin
|
|
112
|
+
* (S4CLI-F01-BASHC-REMOTE-SHELL-R1). The sibling gates never see it because they pass the
|
|
113
|
+
* operator's whole environment; this one builds its environment from scratch to compare it.
|
|
114
|
+
*/
|
|
115
|
+
const STDIO: ["ignore", "pipe", "pipe"] = ["ignore", "pipe", "pipe"];
|
|
116
|
+
|
|
117
|
+
/** The caller's whole environment, built explicitly so the vendor's can be compared to it. */
|
|
118
|
+
function callerEnv(pathValue: string, extra: Record<string, string> = {}): Record<string, string> {
|
|
119
|
+
return { ...sandboxEnv, PATH: pathValue, LANG: "C.UTF-8", FAKE_CLAUDE_ENV_OUT: envDump, ...extra };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface Run {
|
|
123
|
+
status: number | null;
|
|
124
|
+
signal: NodeJS.Signals | null;
|
|
125
|
+
out: string;
|
|
126
|
+
args: string[];
|
|
127
|
+
sentinel: string;
|
|
128
|
+
pid: string;
|
|
129
|
+
cwd: string;
|
|
130
|
+
env: Map<string, string> | null;
|
|
131
|
+
}
|
|
132
|
+
function launch(args: string[], env: Record<string, string>, cwd = work): Run {
|
|
133
|
+
rmSync(envDump, { force: true });
|
|
134
|
+
const r = spawnSync("bash", [RUN_SH, "claude", ...args], {
|
|
135
|
+
cwd,
|
|
136
|
+
encoding: "utf8",
|
|
137
|
+
env,
|
|
138
|
+
timeout: 30_000,
|
|
139
|
+
stdio: STDIO,
|
|
140
|
+
});
|
|
141
|
+
const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
142
|
+
const argv: string[] = [];
|
|
143
|
+
for (const line of out.split("\n")) {
|
|
144
|
+
const m = /^ARG<([\s\S]*)>$/.exec(line);
|
|
145
|
+
if (m) argv.push(m[1] as string);
|
|
146
|
+
}
|
|
147
|
+
let vendorEnv: Map<string, string> | null = null;
|
|
148
|
+
if (existsSync(envDump)) {
|
|
149
|
+
vendorEnv = new Map();
|
|
150
|
+
for (const entry of readFileSync(envDump, "utf8").split("\0")) {
|
|
151
|
+
if (entry === "") continue;
|
|
152
|
+
const eq = entry.indexOf("=");
|
|
153
|
+
vendorEnv.set(entry.slice(0, eq), entry.slice(eq + 1));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
status: r.status,
|
|
158
|
+
signal: r.signal,
|
|
159
|
+
out,
|
|
160
|
+
args: argv,
|
|
161
|
+
sentinel: /^SENTINEL=\[(.*)\]$/m.exec(out)?.[1] ?? "<no-launch>",
|
|
162
|
+
pid: /^PID=(.*)$/m.exec(out)?.[1] ?? "",
|
|
163
|
+
cwd: /^CWD=(.*)$/m.exec(out)?.[1] ?? "",
|
|
164
|
+
env: vendorEnv,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const withVendor = `${bin}:${pathWithoutVendor}`;
|
|
168
|
+
|
|
169
|
+
// ── 1. the dispatcher verb must not reach the vendor ────────────────────────
|
|
170
|
+
{
|
|
171
|
+
const r = launch(["--model", "x"], callerEnv(withVendor));
|
|
172
|
+
ok(
|
|
173
|
+
"[QK:CLAUDELAUNCH-VERB-DROPPED] the dispatcher verb never reaches the vendor — without the shift, `claude` arrives as a prompt argument",
|
|
174
|
+
r.status === 0 && r.pid !== "" && !r.args.includes("claude"),
|
|
175
|
+
`status=${r.status} argv=${JSON.stringify(r.args)}\n${r.out}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── 2. operator argv crosses byte-identical, and nothing is added to it ─────
|
|
180
|
+
{
|
|
181
|
+
const operator = ["--model", "a b", "", "-", "--", "한글 인자", " spaced ", "--help"];
|
|
182
|
+
const r = launch(operator, callerEnv(withVendor));
|
|
183
|
+
ok(
|
|
184
|
+
"[QK:CLAUDELAUNCH-ARGV-UNTOUCHED] the vendor's argv IS the operator's, element for element — empty strings, spaces, a bare dash, the terminator, non-ASCII and a trailing --help — with nothing injected",
|
|
185
|
+
r.status === 0 && JSON.stringify(r.args) === JSON.stringify(operator),
|
|
186
|
+
`expected: ${JSON.stringify(operator)}\nactual: ${JSON.stringify(r.args)}`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── 3. the vendor's own help, never an Entwurf page in its place ───────────
|
|
191
|
+
{
|
|
192
|
+
const bad = (["--help", "-h"] as const).flatMap((flag) => {
|
|
193
|
+
const r = launch([flag], callerEnv(withVendor));
|
|
194
|
+
return r.status === 0 && r.pid !== "" && JSON.stringify(r.args) === JSON.stringify([flag])
|
|
195
|
+
? []
|
|
196
|
+
: [`${flag}: status=${r.status} argv=${JSON.stringify(r.args)} out=${JSON.stringify(r.out.slice(0, 160))}`];
|
|
197
|
+
});
|
|
198
|
+
ok(
|
|
199
|
+
"[QK:CLAUDELAUNCH-VENDOR-HELP] `entwurf claude --help` and `-h` reach Claude Code itself — the help preflight lets this verb through instead of answering with the command's description",
|
|
200
|
+
bad.length === 0,
|
|
201
|
+
bad.join("\n"),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── 4. the caller's directory is the vendor's ───────────────────────────────
|
|
206
|
+
{
|
|
207
|
+
const r = launch([], callerEnv(withVendor));
|
|
208
|
+
ok(
|
|
209
|
+
"[QK:CLAUDELAUNCH-CWD-KEPT] the vendor starts in the caller's directory, not the package's — Claude Code keys its project and session to that directory",
|
|
210
|
+
r.status === 0 && r.cwd === realpathSync(work),
|
|
211
|
+
`expected=${realpathSync(work)} actual=${r.cwd}`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── 5–7. the environment: a sentinel added, a foreign identity removed, nothing else ──
|
|
216
|
+
{
|
|
217
|
+
const caller = callerEnv(withVendor, { PI_SESSION_ID: "foreign-garden", PI_AGENT_ID: "pi-agent-fixture" });
|
|
218
|
+
const r = launch([], caller);
|
|
219
|
+
ok(
|
|
220
|
+
"[QK:CLAUDELAUNCH-SENTINEL-EXPORTED] the sentinel is EXPORTED, so a PATH `claude` that shells back to `entwurf claude` sees it on re-entry",
|
|
221
|
+
r.sentinel === "1",
|
|
222
|
+
`sentinel=${r.sentinel}`,
|
|
223
|
+
);
|
|
224
|
+
ok(
|
|
225
|
+
"[QK:CLAUDELAUNCH-CARRIERS-SCRUBBED] an inherited PI_SESSION_ID/PI_AGENT_ID pair is another citizen's identity and BOTH are gone from the vendor's environment — absent, not emptied",
|
|
226
|
+
r.env !== null && !r.env.has("PI_SESSION_ID") && !r.env.has("PI_AGENT_ID"),
|
|
227
|
+
`vendor env keys: ${JSON.stringify(r.env === null ? null : [...r.env.keys()].filter((k) => k.startsWith("PI_")))}`,
|
|
228
|
+
);
|
|
229
|
+
const expected = new Map(Object.entries(caller));
|
|
230
|
+
expected.delete("PI_SESSION_ID");
|
|
231
|
+
expected.delete("PI_AGENT_ID");
|
|
232
|
+
expected.set(SENTINEL, "1");
|
|
233
|
+
const diff: string[] = [];
|
|
234
|
+
const actual = r.env ?? new Map<string, string>();
|
|
235
|
+
for (const [k, v] of expected)
|
|
236
|
+
if (actual.get(k) !== v) diff.push(`expected ${k}=${JSON.stringify(v)}, got ${JSON.stringify(actual.get(k))}`);
|
|
237
|
+
for (const [k, v] of actual)
|
|
238
|
+
if (!expected.has(k) && !BASH_OWNED.has(k)) diff.push(`added ${k}=${JSON.stringify(v)}`);
|
|
239
|
+
ok(
|
|
240
|
+
`[QK:CLAUDELAUNCH-ENV-EXACT] beyond those two changes the vendor's environment IS the caller's (${expected.size} variables; ${[...BASH_OWNED].join(", ")} are bash's own) — no auth, model, permission or settings variable is decided here`,
|
|
241
|
+
r.env !== null && diff.length === 0,
|
|
242
|
+
diff.join("\n"),
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── 8. the vendor's exit status is this command's exit status (no mutant: see header) ──
|
|
247
|
+
{
|
|
248
|
+
const r = launch([], callerEnv(withVendor, { FAKE_CLAUDE_EXIT: "7" }));
|
|
249
|
+
ok(
|
|
250
|
+
"[QK:CLAUDELAUNCH-EXIT-PASSTHROUGH] the vendor's exit status is the launch's exit status",
|
|
251
|
+
r.status === 7,
|
|
252
|
+
`status=${r.status}`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── 9. exec, not fork: the pid survives ─────────────────────────────────────
|
|
257
|
+
{
|
|
258
|
+
const r = spawnSync("bash", ["-c", `echo "SHELL_PID=$$"; exec bash "${RUN_SH}" claude`], {
|
|
259
|
+
cwd: work,
|
|
260
|
+
encoding: "utf8",
|
|
261
|
+
env: callerEnv(withVendor),
|
|
262
|
+
timeout: 30_000,
|
|
263
|
+
stdio: STDIO,
|
|
264
|
+
});
|
|
265
|
+
const shellPid = /^SHELL_PID=(\d+)$/m.exec(r.stdout ?? "")?.[1];
|
|
266
|
+
const vendorPid = /^PID=(\d+)$/m.exec(r.stdout ?? "")?.[1];
|
|
267
|
+
ok(
|
|
268
|
+
"[QK:CLAUDELAUNCH-EXEC-KEEPS-PID] the vendor REPLACES this process — a fork would leave run.sh as a parent that owns nothing, between Claude Code and the terminal",
|
|
269
|
+
Boolean(shellPid) && shellPid === vendorPid,
|
|
270
|
+
`shell=${shellPid} vendor=${vendorPid}`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ── 10. a vendor death by signal is the caller's to see (no mutant: see header) ──
|
|
275
|
+
{
|
|
276
|
+
const r = launch([], callerEnv(withVendor, { FAKE_CLAUDE_SIGNAL: "TERM" }));
|
|
277
|
+
ok(
|
|
278
|
+
"[QK:CLAUDELAUNCH-SIGNAL-PASSTHROUGH] a vendor killed by a signal is reported as that signal, not as a shell's 128+n status",
|
|
279
|
+
r.status === null && r.signal === "SIGTERM",
|
|
280
|
+
`status=${r.status} signal=${r.signal}`,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── 11. an absent vendor is a NAMED refusal, never a quiet success ──────────
|
|
285
|
+
{
|
|
286
|
+
const r = launch([], callerEnv(pathWithoutVendor));
|
|
287
|
+
ok(
|
|
288
|
+
"[QK:CLAUDELAUNCH-VENDOR-ABSENT] with no `claude` on PATH the launch refuses by name and non-zero, and runs nothing",
|
|
289
|
+
r.status === 1 && r.out.includes("no 'claude' executable found on PATH") && r.pid === "",
|
|
290
|
+
`status=${r.status}\n${r.out}`,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ── 12. a PATH hit that is not a regular file is refused before exec ────────
|
|
295
|
+
{
|
|
296
|
+
const fifoBin = path.join(root, "fifobin");
|
|
297
|
+
mkdirSync(fifoBin, { recursive: true });
|
|
298
|
+
const made = spawnSync("mkfifo", [path.join(fifoBin, "claude")], { encoding: "utf8" });
|
|
299
|
+
assert.equal(made.status, 0, `mkfifo failed: ${made.stderr}`);
|
|
300
|
+
chmodSync(path.join(fifoBin, "claude"), 0o755);
|
|
301
|
+
const r = launch([], callerEnv(`${fifoBin}:${pathWithoutVendor}`));
|
|
302
|
+
ok(
|
|
303
|
+
"[QK:CLAUDELAUNCH-NOT-REGULAR] an executable PATH `claude` that is not a regular file (a FIFO) is refused by name, not handed to exec",
|
|
304
|
+
r.status === 1 && r.out.includes("is not an executable regular file") && r.pid === "",
|
|
305
|
+
`status=${r.status}\n${r.out}`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ── 13. the sentinel fence ──────────────────────────────────────────────────
|
|
310
|
+
{
|
|
311
|
+
const r = launch([], callerEnv(withVendor, { [SENTINEL]: "1" }));
|
|
312
|
+
ok(
|
|
313
|
+
"[QK:CLAUDELAUNCH-RECURSION-FENCE] a re-entered launch refuses by name instead of spinning behind a hung terminal",
|
|
314
|
+
r.status === 1 && r.out.includes("recursive managed launch detected") && r.pid === "",
|
|
315
|
+
`status=${r.status}\n${r.out}`,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ── 14. the resolved-path fence: a PATH `claude` that IS run.sh ─────────────
|
|
320
|
+
{
|
|
321
|
+
// Two hops, the first relative, so the walk itself is exercised: loop/claude -> ../hop/claude
|
|
322
|
+
// -> run.sh. The argv is EMPTY on purpose (see header): without this fence the re-entry would
|
|
323
|
+
// print the root page and exit 0, never run a verb.
|
|
324
|
+
const loopBin = path.join(root, "loop");
|
|
325
|
+
const hop = path.join(root, "hop");
|
|
326
|
+
mkdirSync(loopBin, { recursive: true });
|
|
327
|
+
mkdirSync(hop, { recursive: true });
|
|
328
|
+
symlinkSync(RUN_SH, path.join(hop, "claude"));
|
|
329
|
+
symlinkSync(path.join("..", "hop", "claude"), path.join(loopBin, "claude"));
|
|
330
|
+
const r = launch([], callerEnv(`${loopBin}:${pathWithoutVendor}`));
|
|
331
|
+
ok(
|
|
332
|
+
"[QK:CLAUDELAUNCH-SELF-EXEC-FENCE] a PATH `claude` that resolves through symlinks to this run.sh is refused by name — with nothing injected, re-entry would make the operator's argv an entwurf verb",
|
|
333
|
+
r.status === 1 && r.out.includes(`resolves to entwurf's own '${realpathSync(RUN_SH)}'`) && r.pid === "",
|
|
334
|
+
`status=${r.status}\n${r.out.slice(0, 400)}`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
console.log(`\ncheck-claude-launch: ${passed} checks passed`);
|
|
339
|
+
} finally {
|
|
340
|
+
rmSync(root, { recursive: true, force: true });
|
|
341
|
+
}
|
|
@@ -176,7 +176,7 @@ function assertDormantHonesty(tag: string, rawText: string): void {
|
|
|
176
176
|
* be satisfiable by adding a per-row action/routing field, which is why it is asserted on the
|
|
177
177
|
* DESCRIPTION and paired with a negative that forbids the row-level fix. What the description owes
|
|
178
178
|
* a caller is that `dead` is a reported fact carrying no delivery invitation. A separately
|
|
179
|
-
* requested, proven-dormant Pi/Claude lifecycle can reopen the same id without submitting a prompt.
|
|
179
|
+
* requested, proven-dormant Pi/pi-durable/Claude lifecycle can reopen the same id without submitting a prompt.
|
|
180
180
|
*/
|
|
181
181
|
function assertPeersDeadRowHonesty(tag: string, rawBlock: string): void {
|
|
182
182
|
const text = rawBlock;
|
|
@@ -187,7 +187,7 @@ function assertPeersDeadRowHonesty(tag: string, rawBlock: string): void {
|
|
|
187
187
|
/not an invitation to dispatch/.test(text) &&
|
|
188
188
|
/no entwurf_v2 delivery is available while it is dormant/.test(text) &&
|
|
189
189
|
/Only on an explicit operator request, the separate entwurf_resume_call/.test(text) &&
|
|
190
|
-
/proven-dormant Pi or Claude Code citizen under the same id, submitting no prompt/.test(text),
|
|
190
|
+
/proven-dormant Pi, pi-durable or Claude Code citizen under the same id, submitting no prompt/.test(text),
|
|
191
191
|
);
|
|
192
192
|
ok(
|
|
193
193
|
`${tag} — entwurf_peers does not turn delivery dormancy into a ban on every lifecycle verb`,
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
* FCD-NO-RAIL-PARAMETER no caller-facing input can choose a rail
|
|
19
19
|
* FCD-NO-FALLBACK an incomplete herdr context refuses by its herdr reason
|
|
20
20
|
* FCD-HERDR-BACKEND-FIRST a non-pilot backend refuses before any preflight and any mutation
|
|
21
|
+
* FCD-HERDR-PI-DURABLE-LAYOUT inside herdr pi-durable reaches the layout branch, which is told
|
|
22
|
+
* only WHETHER the caller is a Codex citizen and never falls back from
|
|
23
|
+
* its missing record cwd to this process's own (#157 A-L1)
|
|
24
|
+
* FCD-HERDR-NO-THREAD-ID the herdr branch builds its request from an explicit field list in
|
|
25
|
+
* which the Codex thread id appears only as that boolean (#157 A-L1 D5)
|
|
21
26
|
* FCD-CODEX-PREFLIGHT-TMUX the Codex preflight still gates the tmux rail, and only that rail
|
|
22
27
|
* FCD-ONE-NONCE one nonce is minted per call and reaches the selected rail
|
|
23
28
|
* FCD-SURFACE-ROOT-ONLY the one public surface (the bridge; pi reaches it through Pi's
|
|
@@ -28,6 +33,8 @@
|
|
|
28
33
|
* FCD-RECOVERY-VISIBLE a post-split failure shows closed vs orphan-unreclaimed:reason
|
|
29
34
|
* FCD-ACP-MODEL-EXACT a pi `entwurf/<id>` that is not an exact curated ACP id refuses on
|
|
30
35
|
* either rail before anything runs (#128)
|
|
36
|
+
* FCD-RESUME-RAIL-SHARED a same-id visible resume reaches the same exact rail choice through
|
|
37
|
+
* its own entry point, with no fallback in either direction (#157 G1a)
|
|
31
38
|
*/
|
|
32
39
|
|
|
33
40
|
import assert from "node:assert/strict";
|
|
@@ -36,6 +43,7 @@ import * as path from "node:path";
|
|
|
36
43
|
import { fileURLToPath } from "node:url";
|
|
37
44
|
import {
|
|
38
45
|
dispatchFreshCall,
|
|
46
|
+
dispatchResumeLaunch,
|
|
39
47
|
renderDispatchedFreshCall,
|
|
40
48
|
selectFreshCallRail,
|
|
41
49
|
} from "../pi-extensions/lib/fresh-call-dispatch.ts";
|
|
@@ -180,25 +188,45 @@ async function main(): Promise<void> {
|
|
|
180
188
|
!herdrBranch.includes("codexCallerFreshPreflight"),
|
|
181
189
|
);
|
|
182
190
|
|
|
183
|
-
// #
|
|
184
|
-
// pi-durable
|
|
185
|
-
//
|
|
191
|
+
// #157 A-L1 (main 0a6f9b relaying GLG's #157 grant; supersedes the #129 "not in herdr" cell):
|
|
192
|
+
// inside herdr pi-durable reaches the herdr LAYOUT branch, which never runs the herdr CLI. The
|
|
193
|
+
// root hands that branch one boolean — the caller IS a record-backed Codex citizen — and the
|
|
194
|
+
// branch, not this root, owns the pi-durable preflight. Both calls below are refused before any
|
|
195
|
+
// socket is opened (this env names none), so nothing here can reach a herdr server.
|
|
186
196
|
const durable = recordingSpawn();
|
|
187
|
-
const
|
|
188
|
-
|
|
197
|
+
const durableRequest = { ...REQUEST, backend: "pi-durable", model: "loopback/scripted" };
|
|
198
|
+
const durablePlain = await dispatchFreshCall(durableRequest, HERDR_ENV, durable.spawn);
|
|
199
|
+
const durableCodexCaller = await dispatchFreshCall(
|
|
200
|
+
{ ...durableRequest, callerNativeSessionId: "019a0000-0000-7000-8000-000000000000" },
|
|
189
201
|
HERDR_ENV,
|
|
190
202
|
durable.spawn,
|
|
191
203
|
);
|
|
192
204
|
ok(
|
|
193
|
-
"[QK:FCD-HERDR-PI-DURABLE-
|
|
194
|
-
|
|
195
|
-
!
|
|
196
|
-
|
|
205
|
+
"[QK:FCD-HERDR-PI-DURABLE-LAYOUT] inside herdr pi-durable is no longer refused as unsupported: with the caller's own process cwd it reaches the layout branch's socket-path refusal, while a known Codex caller with no record cwd is refused as codex-caller-cwd-unavailable instead of falling back to this process's cwd — the root passes Codex-ness as a boolean, nothing spawns the herdr CLI, no tmux fallback, and the root's herdr branch names no pi-durable preflight",
|
|
206
|
+
durablePlain.rail === "herdr" &&
|
|
207
|
+
!durablePlain.result.ok &&
|
|
208
|
+
durablePlain.result.reason === "herdr-socket-path-missing" &&
|
|
209
|
+
durableCodexCaller.rail === "herdr" &&
|
|
210
|
+
!durableCodexCaller.result.ok &&
|
|
211
|
+
durableCodexCaller.result.reason === "codex-caller-cwd-unavailable" &&
|
|
197
212
|
durable.calls.length === 0 &&
|
|
198
213
|
herdrBranchLocated &&
|
|
199
214
|
!herdrBranch.includes("piDurable"),
|
|
200
215
|
);
|
|
201
216
|
|
|
217
|
+
// #157 A-L1 D5: the Codex thread id is a tmux seat input and STOPS at this root. The herdr branch
|
|
218
|
+
// therefore spells its request field by field — a spread of the whole request would carry the id
|
|
219
|
+
// along even if no herdr code read it today — and names the id once, inside the boolean.
|
|
220
|
+
const threadIdMentions = herdrBranch.split("callerNativeSessionId").length - 1;
|
|
221
|
+
ok(
|
|
222
|
+
"[QK:FCD-HERDR-NO-THREAD-ID] the herdr branch hands its rail an explicit field list, not a spread of the request, and the Codex thread id appears in it exactly once — as `callerIsCodex: request.callerNativeSessionId !== undefined` — so only a boolean and the record cwd cross to herdr while the tmux request stays whole",
|
|
223
|
+
herdrBranchLocated &&
|
|
224
|
+
!/\.\.\.request\b/.test(herdrBranch) &&
|
|
225
|
+
threadIdMentions === 1 &&
|
|
226
|
+
herdrBranch.includes("callerIsCodex: request.callerNativeSessionId !== undefined,") &&
|
|
227
|
+
herdrBranch.includes("callerCwd: request.callerCwd"),
|
|
228
|
+
);
|
|
229
|
+
|
|
202
230
|
// ── the tmux rail keeps its pre-existing ordering ────────────────────────────────────
|
|
203
231
|
// The import line names the preflight too, so the body is what gets counted: the claim is
|
|
204
232
|
// "called once, on one path", not "the word appears once".
|
|
@@ -292,6 +320,8 @@ async function main(): Promise<void> {
|
|
|
292
320
|
backend: "pi",
|
|
293
321
|
requestedKind: "pi",
|
|
294
322
|
model: "m/1",
|
|
323
|
+
cwd: "/repo/dir",
|
|
324
|
+
cwdSource: "caller-process",
|
|
295
325
|
herdrAgentName: "entwurf-abc",
|
|
296
326
|
herdrWorkspaceId: "w7",
|
|
297
327
|
herdrTabId: "w7:t3",
|
|
@@ -354,6 +384,37 @@ async function main(): Promise<void> {
|
|
|
354
384
|
!unknown.text.includes("orphan-unreclaimed"),
|
|
355
385
|
);
|
|
356
386
|
|
|
387
|
+
// ── #157 G1a: a visible resume reaches the SAME rail choice ──────────────────────────
|
|
388
|
+
// The root's second entry point owns only the rail choice for a same-id visible resume. Its tmux
|
|
389
|
+
// half is whatever the surface hands in, so a recording stub stands for it; its herdr half is the
|
|
390
|
+
// real herdr resume launch, driven with an incomplete herdr context so it refuses before any
|
|
391
|
+
// runner exists. No existing cell exercises this entry point: a resume entry that read the marker
|
|
392
|
+
// loosely, or fell back to the surface's tmux mapping, would leave every cell above green.
|
|
393
|
+
{
|
|
394
|
+
const RESUME_INPUT = { backend: "pi", cwd: "/x/project", runtimeArgs: ["--session", "/x/s.jsonl"] } as const;
|
|
395
|
+
const tmuxCalls: unknown[] = [];
|
|
396
|
+
const tmuxLaunch = (input: typeof RESUME_INPUT) => {
|
|
397
|
+
tmuxCalls.push(input);
|
|
398
|
+
return { ok: false as const, reason: "tmux-stub" };
|
|
399
|
+
};
|
|
400
|
+
const resumeSpawn = recordingSpawn();
|
|
401
|
+
const inHerdr = await dispatchResumeLaunch(RESUME_INPUT, tmuxLaunch, { HERDR_ENV: "1" }, resumeSpawn.spawn);
|
|
402
|
+
const tmuxCallsInHerdr = tmuxCalls.length;
|
|
403
|
+
const elsewhere = [{ HERDR_ENV: "0" }, { HERDR_ENV: "true" }, { HERDR_ENV: "" }, { HERDR_ENV: " 1" }, {}].map(
|
|
404
|
+
(env) => dispatchResumeLaunch(RESUME_INPUT, tmuxLaunch, env, resumeSpawn.spawn),
|
|
405
|
+
);
|
|
406
|
+
ok(
|
|
407
|
+
"[QK:FCD-RESUME-RAIL-SHARED] a same-id visible resume reaches the SAME rail choice fresh call does, through its own entry point: exactly `HERDR_ENV=1` goes to the herdr resume launch and, with that herdr context incomplete, refuses by the herdr reason without ever calling the surface's tmux mapping; every other value, and an absent variable, goes to that tmux mapping with the record-derived input unchanged — a second, looser predicate or a fallback either way would reopen a citizen where the operator is not looking",
|
|
408
|
+
!inHerdr.ok &&
|
|
409
|
+
inHerdr.reason === "herdr-context-missing" &&
|
|
410
|
+
tmuxCallsInHerdr === 0 &&
|
|
411
|
+
resumeSpawn.calls.length === 0 &&
|
|
412
|
+
elsewhere.every((r) => !("then" in r) && r.reason === "tmux-stub") &&
|
|
413
|
+
tmuxCalls.length === elsewhere.length &&
|
|
414
|
+
tmuxCalls.every((input) => input === RESUME_INPUT),
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
357
418
|
console.log(`\n[check-fresh-call-dispatch] ${passed} assertions ok`);
|
|
358
419
|
}
|
|
359
420
|
|
|
@@ -63,6 +63,7 @@ export HOME="$SANDBOX/home"
|
|
|
63
63
|
export XDG_DATA_HOME="$SANDBOX/home/.local/share"
|
|
64
64
|
export XDG_STATE_HOME="$SANDBOX/home/.local/state"
|
|
65
65
|
export XDG_CACHE_HOME="$SANDBOX/home/.cache"
|
|
66
|
+
export XDG_CONFIG_HOME="$SANDBOX/home/.config"
|
|
66
67
|
export PI_CODING_AGENT_DIR="$SANDBOX/home/.pi/agent"
|
|
67
68
|
export ENTWURF_META_SESSIONS_DIR="$SANDBOX/store"
|
|
68
69
|
export ENTWURF_META_MAILBOX_DIR="$SANDBOX/mailbox"
|
|
@@ -1460,6 +1461,11 @@ fake_pkg="$SANDBOX/nm/node_modules/@junghanacs/entwurf"
|
|
|
1460
1461
|
mkdir -p "$fake_pkg/scripts"
|
|
1461
1462
|
cp "$REPO/run.sh" "$fake_pkg/run.sh"
|
|
1462
1463
|
cp "$REPO/scripts/meta-bridge-store-doctor.ts" "$fake_pkg/scripts/"
|
|
1464
|
+
# Sourcing run.sh with no argv still walks its dispatch to the root usage, which renders
|
|
1465
|
+
# ENTWURF-CLI.json through scripts/cli-help.mjs (#153). Without those two files that render fails
|
|
1466
|
+
# first, under run.sh's own errexit, and the doctor branch below is never reached.
|
|
1467
|
+
cp "$REPO/ENTWURF-CLI.json" "$fake_pkg/ENTWURF-CLI.json"
|
|
1468
|
+
cp "$REPO/scripts/cli-help.mjs" "$fake_pkg/scripts/"
|
|
1463
1469
|
set +e
|
|
1464
1470
|
out="$(RUNSH="$fake_pkg/run.sh" bash -c 'set --; source "$RUNSH" >/dev/null; preflight_v3_store install' 2>&1)"; rc=$?
|
|
1465
1471
|
set -e
|
|
@@ -1485,19 +1491,27 @@ rm -rf "$SANDBOX/nm"
|
|
|
1485
1491
|
# names — the point is not that failures are nonzero, it is that a caller can BRANCH.
|
|
1486
1492
|
echo "[check-fresh-cut-gate] J. exit contract"
|
|
1487
1493
|
|
|
1488
|
-
# J1/J2 — the two states that need no host at all.
|
|
1494
|
+
# J1/J2 — the two states that need no host at all. Streams are kept apart: the leaf's own
|
|
1495
|
+
# usage line is now only the address of the description (#153), and the contract text itself —
|
|
1496
|
+
# exit numbers included — is the public page `entwurf meta-bridge-fresh-cut --help` prints, which
|
|
1497
|
+
# check-install-surface S9u holds against this verb's ce92dc7 text and FRESH_CUT_EXIT.
|
|
1498
|
+
fc_pointer='meta-bridge-fresh-cut: see `entwurf help meta-bridge-fresh-cut` (from that checkout'"'"'s root: `./run.sh help meta-bridge-fresh-cut`)'
|
|
1489
1499
|
set +e
|
|
1490
|
-
|
|
1500
|
+
node --experimental-strip-types "${FRESH_CUT[@]}" --help >"$SANDBOX/j1.out" 2>"$SANDBOX/j1.err"; rc=$?
|
|
1491
1501
|
set -e
|
|
1492
|
-
if [ "$rc" = 0 ]; then ok "J1 --help exits 0"; else bad "J1 --help exited $rc" "$
|
|
1493
|
-
case "$help_out" in
|
|
1494
|
-
*"EXIT CONTRACT"*) ok "J1b --help PRINTS the contract (an operator can read it where they hit it)" ;;
|
|
1495
|
-
*) bad "J1b --help never printed the exit contract" "$help_out" ;;
|
|
1496
|
-
esac
|
|
1502
|
+
if [ "$rc" = 0 ]; then ok "J1 --help exits 0"; else bad "J1 --help exited $rc" "$(cat "$SANDBOX/j1.err")"; fi
|
|
1497
1503
|
set +e
|
|
1498
|
-
|
|
1504
|
+
node --experimental-strip-types "${FRESH_CUT[@]}" --wat >"$SANDBOX/j2.out" 2>"$SANDBOX/j2.err"; rc2=$?
|
|
1499
1505
|
set -e
|
|
1500
|
-
if [ "$
|
|
1506
|
+
if [ "$rc2" = 2 ]; then ok "J2 an unknown argument is USAGE (exit 2), never a store verdict"; else bad "J2 unknown arg exited $rc2, want 2" "$(cat "$SANDBOX/j2.err")"; fi
|
|
1507
|
+
if [ "$rc" = 0 ] && [ "$rc2" = 2 ] && [ ! -s "$SANDBOX/j1.out" ] && [ ! -s "$SANDBOX/j2.out" ] \
|
|
1508
|
+
&& [ "$(cat "$SANDBOX/j1.err")" = "$fc_pointer" ] && [ "$(wc -l <"$SANDBOX/j1.err")" = 1 ] \
|
|
1509
|
+
&& cmp -s "$SANDBOX/j1.err" "$SANDBOX/j2.err"; then
|
|
1510
|
+
ok "J1b --help (exit 0) and an unknown argument (exit 2) print nothing on stdout and one stderr line pointing at \`entwurf help meta-bridge-fresh-cut\`"
|
|
1511
|
+
else
|
|
1512
|
+
bad "[QK:FRESHCUT-HELP-POINTER] J1b the leaf's usage is not exactly the one pointer line on stderr with an empty stdout" \
|
|
1513
|
+
"help rc=$rc stdout=$(head -c 200 "$SANDBOX/j1.out") stderr=$(head -c 400 "$SANDBOX/j1.err") | wat rc=$rc2 stdout=$(head -c 200 "$SANDBOX/j2.out") stderr=$(head -c 400 "$SANDBOX/j2.err")"
|
|
1514
|
+
fi
|
|
1501
1515
|
|
|
1502
1516
|
# J3 — the success state, distinct from every failure below.
|
|
1503
1517
|
reset_world prevgen
|
|
@@ -3467,6 +3467,9 @@ let manifestCount: number;
|
|
|
3467
3467
|
"compaction-send-guard": 7,
|
|
3468
3468
|
"control-socket-disconnect": 4,
|
|
3469
3469
|
"demo-reproducibility": 12,
|
|
3470
|
+
"dev-gate-dispatch": 9,
|
|
3471
|
+
"cli-help-source": 36,
|
|
3472
|
+
"claude-launch": 12,
|
|
3470
3473
|
"copilot-birth": 19,
|
|
3471
3474
|
"copilot-launch": 14,
|
|
3472
3475
|
"pi-launch": 9,
|
|
@@ -3474,23 +3477,25 @@ let manifestCount: number;
|
|
|
3474
3477
|
"pi-bridge-sender": 4,
|
|
3475
3478
|
"pi-mcp-bridge": 2,
|
|
3476
3479
|
"pi-mcp-register": 3,
|
|
3477
|
-
"pi-durable":
|
|
3480
|
+
"pi-durable": 171,
|
|
3478
3481
|
"copilot-receive": 20,
|
|
3479
3482
|
"entwurf-peers": 1,
|
|
3480
|
-
"fresh-call-dispatch":
|
|
3481
|
-
"fresh-cut":
|
|
3483
|
+
"fresh-call-dispatch": 18,
|
|
3484
|
+
"fresh-cut": 4,
|
|
3482
3485
|
"gate-qualification": 10,
|
|
3483
3486
|
"herdr-placement": 13,
|
|
3484
|
-
"herdr-plugin":
|
|
3487
|
+
"herdr-plugin": 26,
|
|
3485
3488
|
"herdr-plugin-profile": 14,
|
|
3486
|
-
"herdr-activation":
|
|
3487
|
-
"herdr-plugin-build":
|
|
3489
|
+
"herdr-activation": 42,
|
|
3490
|
+
"herdr-plugin-build": 25,
|
|
3488
3491
|
"herdr-dev-checkout": 4,
|
|
3489
|
-
"herdr-
|
|
3490
|
-
"herdr-
|
|
3492
|
+
"herdr-operator-link": 19,
|
|
3493
|
+
"herdr-runtime-bootstrap": 35,
|
|
3494
|
+
"herdr-fresh-call": 61,
|
|
3495
|
+
"herdr-resume-call": 24,
|
|
3491
3496
|
"herdr-supply": 11,
|
|
3492
|
-
"meta-facts":
|
|
3493
|
-
"peer-facts":
|
|
3497
|
+
"meta-facts": 5,
|
|
3498
|
+
"peer-facts": 9,
|
|
3494
3499
|
"meta-hook-session-switch": 25,
|
|
3495
3500
|
"meta-identity": 9,
|
|
3496
3501
|
"meta-retire": 4,
|
|
@@ -3501,7 +3506,7 @@ let manifestCount: number;
|
|
|
3501
3506
|
"pack-install": 9,
|
|
3502
3507
|
"pi-package-ownership": 8,
|
|
3503
3508
|
"qualification-ledger": 42,
|
|
3504
|
-
"mux-resume-call":
|
|
3509
|
+
"mux-resume-call": 20,
|
|
3505
3510
|
"omp-birth": 13,
|
|
3506
3511
|
"omp-fresh": 25,
|
|
3507
3512
|
"omp-receive": 11,
|
|
@@ -3518,10 +3523,10 @@ let manifestCount: number;
|
|
|
3518
3523
|
"resume-launch-identity": 10,
|
|
3519
3524
|
"self-address": 8,
|
|
3520
3525
|
"setup-verdict": 14,
|
|
3521
|
-
"source-install":
|
|
3526
|
+
"source-install": 11,
|
|
3522
3527
|
"typing-call-fence": 1,
|
|
3523
3528
|
"v2-surface": 16,
|
|
3524
|
-
"v2-visible-resume":
|
|
3529
|
+
"v2-visible-resume": 62,
|
|
3525
3530
|
"win32-preview": 19,
|
|
3526
3531
|
};
|
|
3527
3532
|
const laneTally: Record<string, number> = {};
|