@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.
- package/AGENTS.md +4 -1
- package/BASELINE.md +1 -0
- package/CHANGELOG.md +514 -0
- package/DELIVERY.md +10 -2
- package/README.md +43 -4
- package/VERIFY.md +47 -0
- package/docs/acp-backend-rail.md +96 -17
- package/docs/setup-clean-host.md +11 -1
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +39 -40
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +2 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-declaration.js +609 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +95 -56
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +46 -273
- package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
- package/mcp/entwurf-bridge/src/index.ts +41 -41
- package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
- package/package.json +9 -8
- package/pi-extensions/entwurf-control.ts +91 -52
- package/pi-extensions/lib/acp/acp-client.ts +3 -3
- package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
- package/pi-extensions/lib/acp/backend.ts +4 -4
- package/pi-extensions/lib/acp/event-mapper.ts +5 -5
- package/pi-extensions/lib/acp/tool-surface.ts +2 -1
- package/pi-extensions/lib/codex-declaration.js +612 -0
- package/pi-extensions/lib/codex-fresh-preflight.ts +107 -51
- package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
- package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
- package/pi-extensions/lib/entwurf-facts.ts +14 -1
- package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
- package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
- package/pi-extensions/lib/entwurf-self-address.ts +33 -0
- package/pi-extensions/lib/entwurf-v2-contract.ts +2 -2
- package/pi-extensions/lib/entwurf-v2-lock.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-runner.ts +1 -1
- package/pi-extensions/lib/fresh-call-composition.ts +493 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
- package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
- package/pi-extensions/lib/herdr-placement.ts +276 -0
- package/pi-extensions/lib/mux-fresh-call.ts +95 -301
- package/run.sh +588 -204
- package/scripts/check-acp-usage-accounting.ts +9 -9
- package/scripts/check-codex-birth-hook.ts +187 -0
- package/scripts/check-entwurf-control-rpc.ts +138 -0
- package/scripts/check-entwurf-fact-provider.ts +4 -2
- package/scripts/check-entwurf-peers-surface.ts +3 -1
- package/scripts/check-entwurf-self-address.ts +68 -0
- package/scripts/check-fresh-call-dispatch.ts +301 -0
- package/scripts/check-gate-qualification.ts +17 -4
- package/scripts/check-herdr-activation.ts +1293 -0
- package/scripts/check-herdr-fresh-call.ts +1250 -0
- package/scripts/check-herdr-placement.ts +383 -0
- package/scripts/check-herdr-plugin-build.ts +842 -0
- package/scripts/check-herdr-plugin-profile.ts +371 -0
- package/scripts/check-herdr-plugin.ts +523 -0
- package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
- package/scripts/check-herdr-sandbox.ts +456 -0
- package/scripts/check-herdr-supply.ts +184 -0
- package/scripts/check-mux-launcher-fence.ts +138 -0
- package/scripts/check-peer-facts.ts +331 -0
- package/scripts/check-pi-launch.ts +358 -0
- package/scripts/check-release-gate-outcomes.ts +19 -0
- package/scripts/check-typing-call-fence.ts +296 -0
- package/scripts/codex-birth-doctor.sh +159 -16
- package/scripts/codex-birth-install.sh +178 -72
- package/scripts/codex-birth-uninstall.sh +162 -7
- package/scripts/fixtures/herdr-supply.json +29 -0
- package/scripts/herdr-activation.mjs +536 -0
- package/scripts/herdr-plugin-activate.mjs +270 -0
- package/scripts/herdr-plugin-deactivate.mjs +193 -0
- package/scripts/herdr-runtime.mjs +1203 -0
- package/scripts/install-herdr-ci.sh +96 -0
- package/scripts/inventory-verification-surface.ts +86 -10
- package/scripts/lib/codex-fresh-live-protocol.ts +2 -2
- package/scripts/lib/tree-digest.ts +97 -0
- package/scripts/meta-bridge-install.sh +19 -2
- package/scripts/meta-bridge-state.py +167 -11
- package/scripts/mutants/codex-caller-seat.json +6 -8
- package/scripts/mutants/codex-native.json +195 -29
- package/scripts/mutants/control-socket-disconnect.json +57 -0
- package/scripts/mutants/fresh-call-dispatch.json +154 -0
- package/scripts/mutants/herdr-activation.json +305 -0
- package/scripts/mutants/herdr-fresh-call.json +448 -0
- package/scripts/mutants/herdr-placement.json +161 -0
- package/scripts/mutants/herdr-plugin-build.json +160 -0
- package/scripts/mutants/herdr-plugin-profile.json +202 -0
- package/scripts/mutants/herdr-plugin.json +133 -0
- package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
- package/scripts/mutants/herdr-supply.json +133 -0
- package/scripts/mutants/meta-hook-session-switch.json +2 -2
- package/scripts/mutants/mux-fresh-call.json +104 -17
- package/scripts/mutants/mux-launcher-fence.json +13 -0
- package/scripts/mutants/omp-fresh.json +4 -4
- package/scripts/mutants/peer-facts.json +98 -0
- package/scripts/mutants/pi-launch.json +113 -0
- package/scripts/mutants/resume-args.json +6 -6
- package/scripts/mutants/self-address.json +33 -0
- package/scripts/mutants/typing-call-fence.json +17 -0
- package/scripts/mutants/v2-surface.json +2 -2
- package/scripts/mutants/v2-visible-resume.json +17 -17
- package/scripts/peer-facts.ts +120 -0
- package/scripts/register-pi-package.py +8 -0
- package/scripts/register-pi-provider.py +88 -12
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-codex-birth.sh +281 -12
- package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
- package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
- package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
- package/scripts/smoke-herdr-raw-install-live.sh +849 -0
- package/scripts/tsconfig.json +5 -0
- package/scripts/check-entwurf-facts.ts +0 -385
- package/scripts/check-entwurf-resume-args.ts +0 -122
- package/scripts/check-entwurf-v2-decider.ts +0 -657
- package/scripts/check-entwurf-v2-lock.ts +0 -371
- package/scripts/check-entwurf-v2-mailbox.ts +0 -261
- package/scripts/check-entwurf-v2-matrix.ts +0 -401
- package/scripts/check-entwurf-v2-native-push.ts +0 -221
- package/scripts/check-entwurf-v2-release.ts +0 -151
- package/scripts/check-entwurf-v2-runner.ts +0 -448
- package/scripts/check-entwurf-v2-send-fallback.ts +0 -350
- package/scripts/check-entwurf-v2-send.ts +0 -458
- package/scripts/check-entwurf-v2-visible-resume.ts +0 -445
- package/scripts/check-socket-discovery.ts +0 -401
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check-typing-call-fence — the boundary nobody has broken yet, kept by NAME.
|
|
3
|
+
*
|
|
4
|
+
* THE RULE: this product composes ARGUMENTS and LETTERS, never keystrokes. It is written in two
|
|
5
|
+
* places already — `docs/herdr-launch-rail.md` §3 ("키 입력(`agent prompt`): 터미널 입력이다") and
|
|
6
|
+
* `pi-extensions/lib/herdr-fresh-call.ts:28` — and until now nothing but prose held it.
|
|
7
|
+
*
|
|
8
|
+
* WHY A GATE FOR A RULE NOBODY BROKE `[외부 관측 2026-09-17 19:35]`. An outside reader measured the
|
|
9
|
+
* tree and found the law perfectly kept and nothing keeping it: zero typing calls across
|
|
10
|
+
* `pi-extensions/`, and zero checks and zero mutants that would notice one arriving. Gates here grow
|
|
11
|
+
* on top of incidents, and this boundary was named and refused at DESIGN time, so it never produced
|
|
12
|
+
* the failure that would have produced a test. A rule kept since day one is the most defenceless
|
|
13
|
+
* kind: the green that covers it means only "nobody has broken it yet".
|
|
14
|
+
*
|
|
15
|
+
* WHAT A TYPING CALL IS. Anything that reaches a sibling by writing into the place a HUMAN types:
|
|
16
|
+
* tmux `send-keys` / `paste-buffer` / `load-buffer`, and herdr's PTY verbs — `agent.prompt`,
|
|
17
|
+
* `agent.send_keys`, `pane.send_text`, `pane.input.set`. `[측정 2026-09-18, herdr @ 7505c08]` those
|
|
18
|
+
* herdr verbs are not an API delivery with an acknowledgement: `src/app/api/agents.rs:195` hands the
|
|
19
|
+
* text to `encode_api_submission_parts`, `src/app/api_helpers.rs:25-32` wraps it in bracketed paste,
|
|
20
|
+
* and the bytes go to the child pane's PTY followed 300 ms later by an encoded Enter. What is
|
|
21
|
+
* acknowledged is that input was written — herdr's own help says it "does not track turns". That is
|
|
22
|
+
* exactly what Hard Rule 16 keeps out of delivery evidence.
|
|
23
|
+
*
|
|
24
|
+
* SCOPE. The production surfaces `pi-extensions/` and `mcp/`, tracked files only (so the gitignored
|
|
25
|
+
* `mcp/entwurf-bridge/dist/` build output is not scanned twice as its own source). This gate lives
|
|
26
|
+
* in `scripts/`, which is NOT in scope — a gate that scanned itself could only be made green by
|
|
27
|
+
* deleting the names it exists to forbid.
|
|
28
|
+
*
|
|
29
|
+
* COMMENTS ARE EXEMPT, AND THAT IS LOAD-BEARING. The sentence stating the rule contains the very
|
|
30
|
+
* names the rule forbids. A scanner that read prose would be red on the rule itself, and the
|
|
31
|
+
* cheapest way to green would be deleting the sentence — the gate would eat its own law. So every
|
|
32
|
+
* file is reduced to CODE (string literals kept, comments blanked) before the names are looked for.
|
|
33
|
+
*
|
|
34
|
+
* TYPEFENCE-PRODUCTION-CLEAN no production module under pi-extensions/ or mcp/ carries a typing
|
|
35
|
+
* call in code. A module that acquires one is refused by name.
|
|
36
|
+
*
|
|
37
|
+
* The three assertions before it are this gate's own oracle: the detector is proven to SEE each
|
|
38
|
+
* forbidden name in planted code, proven NOT to see it in prose, and the scanned inventory is
|
|
39
|
+
* measured rather than assumed — without those, "zero hits" and "looked at nothing" read the same.
|
|
40
|
+
*
|
|
41
|
+
* WHAT THIS GATE DOES NOT CATCH, NAMED RATHER THAN IMPLIED (sol D3, 2026-09-18). It reads SPELLINGS.
|
|
42
|
+
* A typing call assembled at runtime — `["send", "keys"].join("-")`, a template with the verb in a
|
|
43
|
+
* variable, an RPC method built from parts — passes it, and no wording of this scanner would change
|
|
44
|
+
* that without becoming a dataflow analysis.
|
|
45
|
+
*
|
|
46
|
+
* That limit is accepted rather than closed, and the reason is what this gate is FOR. It is a
|
|
47
|
+
* tripwire for the slip: a module that reaches for the vendor's typing verb because the argv route
|
|
48
|
+
* looked harder that afternoon, written the ordinary way, spelled out. It is not a defence against
|
|
49
|
+
* somebody deliberately hiding one, and a gate that claimed to be would be the more dangerous
|
|
50
|
+
* object — the claim above therefore says CARRIES a typing call spelled out, not "composes" one.
|
|
51
|
+
* The defence against deliberate evasion is review, and the rule it enforces is in
|
|
52
|
+
* `docs/herdr-launch-rail.md` §3 where a human reads it.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
import assert from "node:assert/strict";
|
|
56
|
+
import { execFileSync } from "node:child_process";
|
|
57
|
+
import * as fs from "node:fs";
|
|
58
|
+
import * as path from "node:path";
|
|
59
|
+
import { fileURLToPath } from "node:url";
|
|
60
|
+
|
|
61
|
+
const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
62
|
+
|
|
63
|
+
let passed = 0;
|
|
64
|
+
function ok(label: string, cond: boolean, detail = ""): void {
|
|
65
|
+
assert.ok(cond, detail ? `${label}\n${detail}` : label);
|
|
66
|
+
console.log(` ok ${label}`);
|
|
67
|
+
passed++;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Each forbidden name, with the surface it belongs to. A pattern matches CODE only. */
|
|
71
|
+
const TYPING_CALLS: readonly { readonly name: string; readonly pattern: RegExp; readonly surface: string }[] = [
|
|
72
|
+
{ name: "send-keys", pattern: /send-keys/, surface: "tmux / herdr CLI" },
|
|
73
|
+
{ name: "send_keys", pattern: /send_keys/, surface: "herdr agent.send_keys" },
|
|
74
|
+
{ name: "paste-buffer", pattern: /paste-buffer/, surface: "tmux" },
|
|
75
|
+
{ name: "load-buffer", pattern: /load-buffer/, surface: "tmux (feeds paste-buffer)" },
|
|
76
|
+
{ name: "send-text / send_text", pattern: /send[-_]text/, surface: "herdr pane.send_text" },
|
|
77
|
+
{ name: '"agent.prompt"', pattern: /["'`]agent\.prompt["'`]/, surface: "herdr JSON-RPC method" },
|
|
78
|
+
{ name: '"pane.input.set"', pattern: /["'`]pane\.input\.set["'`]/, surface: "herdr JSON-RPC method" },
|
|
79
|
+
{ name: '"agent","prompt"', pattern: /["'`]agent["'`]\s*,\s*["'`]prompt["'`]/, surface: "herdr CLI argv" },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
type Flavor = "c" | "shell";
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Blank every comment, keep every code byte — including string literals, because a typing call
|
|
86
|
+
* spelled as a string IS the call. Offsets are preserved so a hit still reports its own line.
|
|
87
|
+
*/
|
|
88
|
+
function codeOnly(src: string, flavor: Flavor): string {
|
|
89
|
+
const out = src.split("");
|
|
90
|
+
let state: "code" | "line" | "block" | "sq" | "dq" | "tpl" = "code";
|
|
91
|
+
let i = 0;
|
|
92
|
+
while (i < src.length) {
|
|
93
|
+
const c = src[i];
|
|
94
|
+
const n = src[i + 1];
|
|
95
|
+
if (state === "code") {
|
|
96
|
+
if (flavor === "c" && c === "/" && n === "/") {
|
|
97
|
+
state = "line";
|
|
98
|
+
out[i] = " ";
|
|
99
|
+
out[i + 1] = " ";
|
|
100
|
+
i += 2;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (flavor === "c" && c === "/" && n === "*") {
|
|
104
|
+
state = "block";
|
|
105
|
+
out[i] = " ";
|
|
106
|
+
out[i + 1] = " ";
|
|
107
|
+
i += 2;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// POSIX: `#` opens a comment only at the start of a word, so `${VAR#prefix}` is code.
|
|
111
|
+
if (flavor === "shell" && c === "#" && (i === 0 || /\s/.test(src[i - 1]))) {
|
|
112
|
+
state = "line";
|
|
113
|
+
out[i] = " ";
|
|
114
|
+
i += 1;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (c === "'") state = "sq";
|
|
118
|
+
else if (c === '"') state = "dq";
|
|
119
|
+
else if (c === "`" && flavor === "c") state = "tpl";
|
|
120
|
+
i += 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (state === "line") {
|
|
124
|
+
if (c === "\n") state = "code";
|
|
125
|
+
else out[i] = " ";
|
|
126
|
+
i += 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (state === "block") {
|
|
130
|
+
if (c === "*" && n === "/") {
|
|
131
|
+
out[i] = " ";
|
|
132
|
+
out[i + 1] = " ";
|
|
133
|
+
state = "code";
|
|
134
|
+
i += 2;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (c !== "\n") out[i] = " ";
|
|
138
|
+
i += 1;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// inside a string literal: bytes are kept, escapes consume the next byte
|
|
142
|
+
if (c === "\\") {
|
|
143
|
+
i += 2;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if ((state === "sq" && c === "'") || (state === "dq" && c === '"') || (state === "tpl" && c === "`")) {
|
|
147
|
+
state = "code";
|
|
148
|
+
}
|
|
149
|
+
i += 1;
|
|
150
|
+
}
|
|
151
|
+
return out.join("");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type Hit = { readonly file: string; readonly line: number; readonly name: string; readonly text: string };
|
|
155
|
+
|
|
156
|
+
function scan(rel: string, src: string, flavor: Flavor): Hit[] {
|
|
157
|
+
const code = codeOnly(src, flavor);
|
|
158
|
+
const rawLines = src.split("\n");
|
|
159
|
+
const hits: Hit[] = [];
|
|
160
|
+
code.split("\n").forEach((codeLine, idx) => {
|
|
161
|
+
for (const call of TYPING_CALLS) {
|
|
162
|
+
if (call.pattern.test(codeLine)) {
|
|
163
|
+
hits.push({ file: rel, line: idx + 1, name: call.name, text: rawLines[idx].trim() });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
return hits;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const flavorOf = (rel: string): Flavor => (rel.endsWith(".sh") ? "shell" : "c");
|
|
171
|
+
|
|
172
|
+
// ── 1. the detector sees a planted typing call in code ──────────────────────
|
|
173
|
+
{
|
|
174
|
+
const plantedTs = [
|
|
175
|
+
'const argv = ["send-keys", "-t", pane, text];',
|
|
176
|
+
'await run(["paste-buffer", "-t", pane]);',
|
|
177
|
+
'await run(["load-buffer", "-b", buf]);',
|
|
178
|
+
'const m = "agent.send_keys";',
|
|
179
|
+
'const t = "pane.send_text";',
|
|
180
|
+
'const p = "agent.prompt";',
|
|
181
|
+
'const i = "pane.input.set";',
|
|
182
|
+
'const cli = ["agent", "prompt", target, text];',
|
|
183
|
+
].join("\n");
|
|
184
|
+
const found = new Set(scan("fixture.ts", plantedTs, "c").map((h) => h.name));
|
|
185
|
+
ok(
|
|
186
|
+
`the detector names all ${TYPING_CALLS.length} typing calls planted in code (a green scan means it looked, not that it is blind)`,
|
|
187
|
+
found.size === TYPING_CALLS.length,
|
|
188
|
+
`missed: ${TYPING_CALLS.map((c) => c.name)
|
|
189
|
+
.filter((n) => !found.has(n))
|
|
190
|
+
.join(", ")}`,
|
|
191
|
+
);
|
|
192
|
+
ok(
|
|
193
|
+
"the shell flavor sees a typing call too — a launcher script is production surface",
|
|
194
|
+
scan("fixture.sh", 'tmux send-keys -t "$PANE" "$TEXT"\n', "shell").length === 1,
|
|
195
|
+
);
|
|
196
|
+
ok(
|
|
197
|
+
"a `${VAR#prefix}` expansion does not blind the shell scan for the rest of its line",
|
|
198
|
+
scan("fixture.sh", 'name="${PANE#%}"; tmux send-keys -t "$name" x\n', "shell").length === 1,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── 2. prose is exempt — the rule's own sentence must survive its own gate ──
|
|
203
|
+
{
|
|
204
|
+
const prose = [
|
|
205
|
+
"// keystrokes (`agent prompt`) are terminal input, which this product does not use to launch.",
|
|
206
|
+
"/* tmux send-keys and paste-buffer are the shape this fence refuses. */",
|
|
207
|
+
'const real = "fine";',
|
|
208
|
+
].join("\n");
|
|
209
|
+
ok(
|
|
210
|
+
"a typing call NAMED in a comment is not a violation — otherwise the cheapest green would be deleting the sentence that states the rule",
|
|
211
|
+
scan("fixture.ts", prose, "c").length === 0,
|
|
212
|
+
);
|
|
213
|
+
ok(
|
|
214
|
+
"the same exemption in shell comments",
|
|
215
|
+
scan("fixture.sh", "# tmux send-keys is refused here\nexec node x.js\n", "shell").length === 0,
|
|
216
|
+
);
|
|
217
|
+
// The live sentence, read from the rail itself rather than quoted here: if it is ever reworded
|
|
218
|
+
// the exemption must be re-proved against what the file ACTUALLY says. Only the sentence is
|
|
219
|
+
// scanned — whether the module as a whole is clean is the LAW's claim below, not this one's.
|
|
220
|
+
const railLine = fs
|
|
221
|
+
.readFileSync(path.join(REPO, "pi-extensions/lib/herdr-fresh-call.ts"), "utf8")
|
|
222
|
+
.split("\n")
|
|
223
|
+
.find((l) => l.includes("keystrokes (`agent prompt`)"));
|
|
224
|
+
ok(
|
|
225
|
+
"the launch rail's own written rule names `agent prompt` and is exempt as the prose it is",
|
|
226
|
+
railLine !== undefined && scan("rail-rule", railLine, "c").length === 0,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── 3. the scanned inventory is MEASURED, never assumed ─────────────────────
|
|
231
|
+
//
|
|
232
|
+
// SCOPE grew on 2026-09-18 (sol D3 / glm #4). `plugins/herdr/` and the four `scripts/herdr-*.mjs`
|
|
233
|
+
// runtime scripts are the code that actually drives the herdr CLI, and they sat OUTSIDE the law
|
|
234
|
+
// that exists because of that CLI. They are clean today — this gate is what keeps them so.
|
|
235
|
+
//
|
|
236
|
+
// AND THE LISTING IS NOT `--cached` ALONE. A brand-new production module is untracked until it is
|
|
237
|
+
// staged, so a fence that read only the index would look at everything except the file most likely
|
|
238
|
+
// to be carrying the mistake. `--others --exclude-standard` adds exactly the untracked files git
|
|
239
|
+
// would offer to add, which leaves the gitignored build output (`mcp/entwurf-bridge/dist/`) out
|
|
240
|
+
// for the reason it always was: it is this source, compiled, and scanning it twice proves nothing.
|
|
241
|
+
const SCOPE = ["pi-extensions", "mcp", "plugins/herdr"] as const;
|
|
242
|
+
const files = [
|
|
243
|
+
...execFileSync(
|
|
244
|
+
"git",
|
|
245
|
+
["ls-files", "--cached", "--others", "--exclude-standard", "--", ...SCOPE.map((s) => `${s}/*`)],
|
|
246
|
+
{
|
|
247
|
+
cwd: REPO,
|
|
248
|
+
encoding: "utf8",
|
|
249
|
+
},
|
|
250
|
+
)
|
|
251
|
+
.split("\n")
|
|
252
|
+
.filter(Boolean),
|
|
253
|
+
// The herdr runtime scripts live in `scripts/`, which is NOT in scope as a whole — this gate
|
|
254
|
+
// is in there, and a gate that scanned itself could only be made green by deleting the names
|
|
255
|
+
// it exists to forbid. So they are named, one glob, by what they are.
|
|
256
|
+
...execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "--", "scripts/herdr-*.mjs"], {
|
|
257
|
+
cwd: REPO,
|
|
258
|
+
encoding: "utf8",
|
|
259
|
+
})
|
|
260
|
+
.split("\n")
|
|
261
|
+
.filter(Boolean),
|
|
262
|
+
]
|
|
263
|
+
.filter((f) => /\.(ts|js|mjs|cjs|sh)$/.test(f))
|
|
264
|
+
.filter((f) => fs.existsSync(path.join(REPO, f)));
|
|
265
|
+
|
|
266
|
+
{
|
|
267
|
+
const required = [
|
|
268
|
+
"pi-extensions/lib/herdr-fresh-call.ts",
|
|
269
|
+
"pi-extensions/lib/mux-fresh-call.ts",
|
|
270
|
+
"pi-extensions/lib/fresh-call-composition.ts",
|
|
271
|
+
"pi-extensions/entwurf-control.ts",
|
|
272
|
+
"mcp/entwurf-bridge/src/index.ts",
|
|
273
|
+
// The two surfaces that drive the vendor CLI this law is about — named, so widening the
|
|
274
|
+
// scope cannot quietly narrow again.
|
|
275
|
+
"plugins/herdr/lib/build.mjs",
|
|
276
|
+
"scripts/herdr-runtime.mjs",
|
|
277
|
+
];
|
|
278
|
+
const missing = required.filter((r) => !files.includes(r));
|
|
279
|
+
ok(
|
|
280
|
+
`scope is non-vacuous: ${files.length} tracked production sources under ${SCOPE.join(" + ")}, both launch rails and the MCP bridge among them`,
|
|
281
|
+
files.length >= 50 && missing.length === 0,
|
|
282
|
+
missing.length ? `not scanned: ${missing.join(", ")}` : `only ${files.length} files matched`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ── 4. THE LAW ──────────────────────────────────────────────────────────────
|
|
287
|
+
{
|
|
288
|
+
const hits = files.flatMap((rel) => scan(rel, fs.readFileSync(path.join(REPO, rel), "utf8"), flavorOf(rel)));
|
|
289
|
+
ok(
|
|
290
|
+
`[QK:TYPEFENCE-PRODUCTION-CLEAN] none of the ${files.length} production sources CARRIES a typing call spelled out — this rail builds arguments and letters, and a module that acquires one of these names in code is refused by name`,
|
|
291
|
+
hits.length === 0,
|
|
292
|
+
hits.map((h) => `${h.file}:${h.line} carries ${h.name} — ${h.text}`).join("\n"),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
console.log(`\ncheck-typing-call-fence: ${passed} assertions passed over ${files.length} production sources`);
|
|
@@ -7,18 +7,32 @@
|
|
|
7
7
|
# RUNTIME is there a node the launcher can exec, and a Codex CLI at all?
|
|
8
8
|
# UNIT are the declaration, launcher and closure present, ours by digest, and
|
|
9
9
|
# owned/permissioned so that nobody else could have written them?
|
|
10
|
+
# FOREIGN what ELSE declares a SessionStart hook in that file — reported, never judged.
|
|
10
11
|
# TRUST has the VENDOR recorded the operator's one-time decision for this exact
|
|
11
12
|
# declaration identity?
|
|
12
13
|
#
|
|
14
|
+
# WHAT THE UNIT AXIS CERTIFIES ABOUT hooks.json, AND WHAT IT STOPPED CERTIFYING (#117). Not the
|
|
15
|
+
# file. entwurf owns ONE `SessionStart` group in a file it SHARES — Herdr's official Codex
|
|
16
|
+
# integration appends its own, and the vendor runs both, because `[source]`
|
|
17
|
+
# hooks/src/engine/discovery.rs:664-665 keys trust by `<path>:<event>:<group_idx>:<handler_idx>`.
|
|
18
|
+
# So this doctor selects entwurf's group by its launcher command, checks its SHAPE and its
|
|
19
|
+
# NORMALIZED digest (key order and indentation blind — measured: Herdr re-serializes the whole
|
|
20
|
+
# document), and reports every other group as FOREIGN without ever certifying or editing it.
|
|
21
|
+
#
|
|
13
22
|
# WHAT THE TRUST AXIS IS, EXACTLY. It reads `$CODEX_HOME/config.toml` and looks for the
|
|
14
|
-
# vendor's own receipt at
|
|
15
|
-
# be a `trusted_hash` of the shape `sha256:<64 hex>`. That is ALL it claims: a vendor trust
|
|
23
|
+
# vendor's own receipt at `<hooks.json>:session_start:<our group>:<our handler>`, whose value
|
|
24
|
+
# must be a `trusted_hash` of the shape `sha256:<64 hex>`. That is ALL it claims: a vendor trust
|
|
16
25
|
# receipt is present for the declaration identity this unit publishes. It does NOT compute
|
|
17
26
|
# what that hash should be, does not compare it to anything of ours, and therefore never
|
|
18
27
|
# says the approval is cryptographically valid — the vendor hashes a normalized identity of
|
|
19
28
|
# its own (`[source]` hooks/src/engine/discovery.rs:775-792) and recomputing it here would
|
|
20
29
|
# be entwurf asserting authority over somebody else's security decision.
|
|
21
30
|
#
|
|
31
|
+
# AND IT IS READ AT THE INDEX OUR DECLARATION WAS JUST MEASURED AT, never at a constant. The old
|
|
32
|
+
# spelling `…:session_start:0:0` was true only while entwurf was alone in the file; with a
|
|
33
|
+
# neighbour at index 0 it reads THEIR receipt and reports it as ours — a green for a hook the
|
|
34
|
+
# vendor has never been asked to run.
|
|
35
|
+
#
|
|
22
36
|
# WHAT THIS DOCTOR NEVER DOES. It does not write, pre-seed or repair `[hooks.state]`, and it
|
|
23
37
|
# never launches Codex. A missing receipt is not something to fix from here: the operator
|
|
24
38
|
# opens a visible Codex, answers the vendor's prompt once, and comes back.
|
|
@@ -49,7 +63,13 @@ HELPER_DIR="$UNIT_ROOT/helper"
|
|
|
49
63
|
LAUNCHER_NAME="codex-birth-launch.sh"
|
|
50
64
|
LAUNCHER="$HELPER_DIR/$LAUNCHER_NAME"
|
|
51
65
|
STATE_FILE="$UNIT_ROOT/install-state.json"
|
|
66
|
+
REPO_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
67
|
+
DECLARATION_LIB="$REPO_DIR/pi-extensions/lib/codex-declaration.js"
|
|
52
68
|
ME="$(id -u)"
|
|
69
|
+
# Filled by the UNIT axis and read by the TRUST axis: the position our declaration was actually
|
|
70
|
+
# found at. Empty means the UNIT axis could not locate it, and TRUST says so rather than guessing.
|
|
71
|
+
DECL_GROUP_INDEX=""
|
|
72
|
+
DECL_HANDLER_INDEX=""
|
|
53
73
|
|
|
54
74
|
RED=0
|
|
55
75
|
section() { printf '\n── %s %s\n' "$1" "─────────────────────────────────────────"; }
|
|
@@ -105,9 +125,25 @@ judge_parent() { # $1 = dir, $2 = label
|
|
|
105
125
|
section "UNIT (the bytes this unit publishes, and whether they are still ours)"
|
|
106
126
|
judge_parent "$PACKAGE_STATE_ROOT" "the package state root"
|
|
107
127
|
judge_parent "$UNIT_ROOT" "the unit root"
|
|
128
|
+
FOREIGN_LINES=""
|
|
129
|
+
FOREIGN_SCANNED=0
|
|
130
|
+
ORPHAN_DECLARATION=0
|
|
131
|
+
if [ ! -e "$STATE_FILE" ] && [ ! -L "$STATE_FILE" ] && [ -f "$HOOKS_FILE" ] && [ -n "$NODE_BIN" ] && [ -f "$DECLARATION_LIB" ]; then
|
|
132
|
+
# A hooks.json with NO entwurf state is the ordinary shape on a host where only somebody else
|
|
133
|
+
# declares hooks — it is not evidence of anything of ours. What IS evidence is a group
|
|
134
|
+
# commanding OUR launcher with no receipt behind it, so that is the only thing asked about.
|
|
135
|
+
"$NODE_BIN" -e '
|
|
136
|
+
const fs = require("node:fs");
|
|
137
|
+
const [, lib, hooksFile, launcher] = process.argv;
|
|
138
|
+
import(lib).then((m) => {
|
|
139
|
+
const sel = m.selectEntwurfDeclaration(JSON.parse(fs.readFileSync(hooksFile, "utf8")), launcher);
|
|
140
|
+
process.exit(sel.ok || sel.code === "declaration-duplicated" ? 0 : 1);
|
|
141
|
+
}).catch(() => process.exit(1));
|
|
142
|
+
' "$DECLARATION_LIB" "$HOOKS_FILE" "$LAUNCHER" >/dev/null 2>&1 && ORPHAN_DECLARATION=1
|
|
143
|
+
fi
|
|
108
144
|
if [ ! -e "$STATE_FILE" ] && [ ! -L "$STATE_FILE" ]; then
|
|
109
|
-
if [
|
|
110
|
-
bad "no ownership state at $STATE_FILE, yet
|
|
145
|
+
if [ "$ORPHAN_DECLARATION" = "1" ] || [ -e "$HELPER_DIR" ]; then
|
|
146
|
+
bad "no ownership state at $STATE_FILE, yet an entwurf declaration or helper closure is present — those bytes are not provably ours and the inverse will refuse them. Inspect them."
|
|
111
147
|
else
|
|
112
148
|
# NOT an informational note. "Nothing is installed" is the most complete way for this
|
|
113
149
|
# unit to be broken: no declaration, no closure, no record will ever be minted — and a
|
|
@@ -133,15 +169,21 @@ else
|
|
|
133
169
|
INVENTORY="$("$NODE_BIN" -e '
|
|
134
170
|
const fs = require("node:fs");
|
|
135
171
|
const path = require("node:path");
|
|
136
|
-
const
|
|
172
|
+
const [, stateFile, hooksFile, helperDir, lib, launcher] = process.argv;
|
|
173
|
+
// An async main, not a top-level body: `node -e` compiles its input as a script, where a
|
|
174
|
+
// top-level `return` is a SyntaxError, and the declaration leaf is an ESM `import()`.
|
|
175
|
+
void (async function main() {
|
|
176
|
+
const s = JSON.parse(fs.readFileSync(stateFile, "utf8"));
|
|
137
177
|
const out = [];
|
|
138
178
|
const hex = (v) => typeof v === "string" && /^[0-9a-f]{64}$/.test(v);
|
|
139
|
-
if (s.schema
|
|
179
|
+
if (s.schema === "codex-birth-install-state/v1") {
|
|
180
|
+
out.push("BAD\tthe ownership state is codex-birth-install-state/v1, which recorded a digest of the WHOLE hooks.json. This unit now owns ONE declaration inside a file it shares, so that receipt claims bytes it does not own. Repair: ./run.sh install-codex-birth (it supersedes the receipt, removes nothing, and rewrites no foreign byte)");
|
|
181
|
+
} else if (s.schema !== "codex-birth-install-state/v2") {
|
|
182
|
+
out.push("BAD\tforeign state schema " + JSON.stringify(s.schema));
|
|
183
|
+
}
|
|
140
184
|
if (s.status !== "installed") out.push("BAD\tthe state status is " + JSON.stringify(s.status) + ", so a previous install did not finish publishing. Repair: ./run.sh install-codex-birth");
|
|
141
|
-
if (s.hooksFile !==
|
|
142
|
-
if (s.helperDir !==
|
|
143
|
-
if (!hex(s.hooksSha256)) out.push("BAD\thooksSha256 is not a digest");
|
|
144
|
-
else out.push("WANT\t" + s.hooksSha256 + "\t" + s.hooksFile + "\tthe declaration");
|
|
185
|
+
if (s.hooksFile !== hooksFile) out.push("BAD\tthe state is bound to " + s.hooksFile + ", not the fixed path " + hooksFile);
|
|
186
|
+
if (s.helperDir !== helperDir) out.push("BAD\tthe state is bound to helper dir " + s.helperDir + ", not the fixed path " + helperDir);
|
|
145
187
|
const expected = [
|
|
146
188
|
"codex-birth-launch.sh",
|
|
147
189
|
"meta-bridge-hook-codex.ts",
|
|
@@ -168,13 +210,81 @@ else
|
|
|
168
210
|
out.push(["WANT", f.sha256, path.join(s.helperDir, f.path), "helper member " + f.path, f.mode ?? ""].join("\t"));
|
|
169
211
|
}
|
|
170
212
|
out.push("INFO\tunitVersion=" + (s.unitVersion ?? "?") + " nodeBin=" + (s.nodeBin ?? "?"));
|
|
171
|
-
|
|
172
|
-
|
|
213
|
+
|
|
214
|
+
// ── the DECLARATION, judged as one group inside a file this unit shares ──
|
|
215
|
+
const decl = s.declaration;
|
|
216
|
+
const declOk = decl !== null && typeof decl === "object" && !Array.isArray(decl) && decl.event === "SessionStart" && typeof decl.command === "string" && hex(decl.sha256);
|
|
217
|
+
if (!declOk && s.schema === "codex-birth-install-state/v2") out.push("BAD\tthe state records no usable declaration receipt (event/command/sha256)");
|
|
218
|
+
const emit = () => process.stdout.write(out.join("\n") + "\n");
|
|
219
|
+
if (!declOk) return emit();
|
|
220
|
+
const m = await import(lib);
|
|
221
|
+
// OWNERSHIP BEFORE CONTENT (sol B3, 2026-09-18). This doctor used to read the CONTENT of the
|
|
222
|
+
// shared file without ever asking who owned it, while the fresh-call preflight refused a foreign
|
|
223
|
+
// uid or a group-writable mode outright — so a host could be GREEN here and refuse every
|
|
224
|
+
// launch as `codex-birth-unit-missing`. One predicate now answers for all four surfaces, and
|
|
225
|
+
// it answers first: a digest measured in a file somebody else can rewrite certifies nothing.
|
|
226
|
+
const owned = m.classifyOwnedPath(m.statOwnedPath(fs, hooksFile), process.getuid());
|
|
227
|
+
if (owned === "missing") {
|
|
228
|
+
out.push("BAD\tthe declaration file is MISSING: " + hooksFile + " — no Codex thread on this host can become a citizen. Repair: ./run.sh install-codex-birth");
|
|
229
|
+
return emit();
|
|
230
|
+
}
|
|
231
|
+
if (owned !== "ok") {
|
|
232
|
+
out.push("BAD\tthe declaration file " + m.OWNED_PATH_REFUSAL[owned] + " (" + hooksFile + ") — every Codex fresh call refuses this host for the same reason, so a green here would contradict the launcher");
|
|
233
|
+
return emit();
|
|
234
|
+
}
|
|
235
|
+
let text;
|
|
236
|
+
try {
|
|
237
|
+
text = fs.readFileSync(hooksFile, "utf8");
|
|
238
|
+
} catch (err) {
|
|
239
|
+
out.push("BAD\tthe declaration file could not be read (" + hooksFile + "): " + err.message);
|
|
240
|
+
return emit();
|
|
241
|
+
}
|
|
242
|
+
let sel;
|
|
243
|
+
try {
|
|
244
|
+
sel = m.selectEntwurfDeclaration(JSON.parse(text), launcher);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
out.push("BAD\t" + hooksFile + " is not readable JSON (" + err.message + ")");
|
|
247
|
+
return emit();
|
|
248
|
+
}
|
|
249
|
+
// The SCAN marker is emitted whether or not there are neighbours, because "none" and "never
|
|
250
|
+
// looked" are different facts and only one of them claims anything about a file we read.
|
|
251
|
+
for (const group of sel.foreign) {
|
|
252
|
+
out.push("FOREIGN\tSessionStart group " + group.index + ": " + (group.commands.join(" | ") || "(no handler)"));
|
|
253
|
+
}
|
|
254
|
+
out.push("FOREIGNSCAN\t" + sel.foreign.length);
|
|
255
|
+
if (!sel.ok) {
|
|
256
|
+
out.push("BAD\tentwurf\u2019s own declaration is not certifiable in " + hooksFile + " — " + sel.code + ": " + sel.detail + ". Repair: ./run.sh install-codex-birth");
|
|
257
|
+
return emit();
|
|
258
|
+
}
|
|
259
|
+
if (sel.digest !== decl.sha256) {
|
|
260
|
+
out.push("BAD\tentwurf\u2019s own declaration was EDITED after install (" + hooksFile + " group " + sel.groupIndex + ": live normalized " + sel.digest + ", recorded " + decl.sha256 + ") — the inverse will refuse to remove it");
|
|
261
|
+
return emit();
|
|
262
|
+
}
|
|
263
|
+
if (decl.command !== m.entwurfDeclarationCommand(launcher)) {
|
|
264
|
+
out.push("BAD\tthe recorded declaration command " + JSON.stringify(decl.command) + " is not the one these fixed paths produce");
|
|
265
|
+
return emit();
|
|
266
|
+
}
|
|
267
|
+
out.push("DECL\t" + sel.groupIndex + "\t" + sel.handlerIndex + "\t" + sel.digest + "\t" + sel.foreign.length);
|
|
268
|
+
return emit();
|
|
269
|
+
})().catch((err) => {
|
|
270
|
+
process.stderr.write(err && err.message ? err.message : String(err));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
});
|
|
273
|
+
' "$STATE_FILE" "$HOOKS_FILE" "$HELPER_DIR" "$DECLARATION_LIB" "$LAUNCHER" 2>&1)" || { bad "the ownership state is unreadable or malformed: $INVENTORY"; INVENTORY=""; }
|
|
173
274
|
|
|
174
275
|
while IFS="$(printf '\t')" read -r kind a b c d; do
|
|
175
276
|
case "$kind" in
|
|
176
277
|
BAD) bad "$a" ;;
|
|
177
278
|
INFO) info "$a" ;;
|
|
279
|
+
FOREIGN) FOREIGN_LINES="${FOREIGN_LINES}${a}
|
|
280
|
+
" ;;
|
|
281
|
+
FOREIGNSCAN) FOREIGN_SCANNED=1 ;;
|
|
282
|
+
DECL)
|
|
283
|
+
DECL_GROUP_INDEX="$a"; DECL_HANDLER_INDEX="$b"
|
|
284
|
+
ok "entwurf's declaration is certified at $HOOKS_FILE group $a handler $b (normalized sha256 $c)"
|
|
285
|
+
info "certified by NORMALIZED digest, not by file bytes: this unit owns one SessionStart group,"
|
|
286
|
+
info "and a neighbour re-serializing that document changes every byte and nothing of ours."
|
|
287
|
+
;;
|
|
178
288
|
WANT)
|
|
179
289
|
if [ -L "$b" ]; then bad "$c is a SYMLINK ($b) — this unit publishes no links."
|
|
180
290
|
elif [ ! -e "$b" ]; then bad "$c is MISSING: $b"
|
|
@@ -197,11 +307,37 @@ $INVENTORY
|
|
|
197
307
|
EOF
|
|
198
308
|
fi
|
|
199
309
|
|
|
310
|
+
section "FOREIGN (what else declares a SessionStart hook in that file)"
|
|
311
|
+
# REPORTED, NEVER JUDGED. Another integration's declaration is not this unit's to certify, to
|
|
312
|
+
# repair or to remove, and its state says nothing about whether entwurf's own hook will run —
|
|
313
|
+
# `[source]` discovery.rs:664-665 trusts per declaration, so neighbours are independent. This
|
|
314
|
+
# section exists so an operator can SEE them, which is the difference between coexisting and
|
|
315
|
+
# not knowing.
|
|
316
|
+
if [ "$FOREIGN_SCANNED" = "0" ]; then
|
|
317
|
+
# NOT "none". The UNIT axis stopped before it read that document — an absent or unreadable
|
|
318
|
+
# ownership state, a missing file — and reporting "none" here would be this doctor asserting
|
|
319
|
+
# something about the operator's file that it never looked at.
|
|
320
|
+
info "NOT READ — the UNIT axis stopped before it could look at $HOOKS_FILE, so this doctor"
|
|
321
|
+
info "knows nothing about what else declares a SessionStart hook there. Repair the UNIT axis first."
|
|
322
|
+
elif [ -n "$FOREIGN_LINES" ]; then
|
|
323
|
+
printf '%s' "$FOREIGN_LINES" | while IFS= read -r line; do
|
|
324
|
+
[ -n "$line" ] && info "$line"
|
|
325
|
+
done
|
|
326
|
+
info "present-but-foreign: not certified by this unit, never overwritten, never absorbed, and"
|
|
327
|
+
info "no input to any verdict above. Their own trust receipts are their owners' business."
|
|
328
|
+
else
|
|
329
|
+
info "none — entwurf is the only SessionStart declaration in $HOOKS_FILE"
|
|
330
|
+
fi
|
|
331
|
+
|
|
200
332
|
TRUST_REPAIR="Repair: open a visible plain Codex (no flags), answer the vendor's prompt with
|
|
201
333
|
'Trust all and continue', send one first turn, then re-run this doctor."
|
|
202
334
|
|
|
203
335
|
section "TRUST (the vendor's receipt for this exact declaration)"
|
|
204
|
-
if [ "$UNIT_ONLY"
|
|
336
|
+
if [ -z "$DECL_GROUP_INDEX" ] && [ "$UNIT_ONLY" != "1" ]; then
|
|
337
|
+
bad "the UNIT axis could not locate entwurf's declaration, so there is no index to read a vendor
|
|
338
|
+
receipt at. A receipt read at a guessed position would be somebody else's approval.
|
|
339
|
+
Repair the UNIT axis first: ./run.sh install-codex-birth"
|
|
340
|
+
elif [ "$UNIT_ONLY" = "1" ]; then
|
|
205
341
|
info "--unit-only: the vendor receipt was NOT read. This is a skipped axis, not a green one."
|
|
206
342
|
elif [ ! -e "$CODEX_HOME/config.toml" ]; then
|
|
207
343
|
bad "$CODEX_HOME/config.toml does not exist, so the vendor has recorded no trust decision for $HOOKS_FILE.
|
|
@@ -212,7 +348,11 @@ else
|
|
|
212
348
|
# The key the vendor writes is `<declaration path>:<event>:<group>:<handler>` — measured on
|
|
213
349
|
# this host after one 'Trust all'. We look for exactly ours: a receipt for a DIFFERENT
|
|
214
350
|
# declaration is not this unit's approval, however valid it is for whoever owns it.
|
|
215
|
-
|
|
351
|
+
#
|
|
352
|
+
# THE INDEX COMES FROM THE UNIT AXIS, which just measured where our declaration actually sits.
|
|
353
|
+
# Spelling it `0:0` was right only while entwurf was alone in the file: with a neighbour at
|
|
354
|
+
# index 0, that constant reads THEIR receipt and reports it as ours.
|
|
355
|
+
TRUST_KEY="$HOOKS_FILE:session_start:$DECL_GROUP_INDEX:$DECL_HANDLER_INDEX"
|
|
216
356
|
# python3 + tomllib, the same reader the two config atoms already use — node has no TOML
|
|
217
357
|
# parser and inventing one here would be a second opinion about the vendor's own file.
|
|
218
358
|
TRUST_OUT="$(python3 -c '
|
|
@@ -242,8 +382,11 @@ sys.stdout.write("PRESENT\t" + digest)
|
|
|
242
382
|
case "$TRUST_KIND" in
|
|
243
383
|
PRESENT)
|
|
244
384
|
ok "a vendor trust receipt is present for $TRUST_KEY ($TRUST_DETAIL)"
|
|
245
|
-
info "this reports that the vendor RECORDED a decision for this declaration identity
|
|
246
|
-
info "
|
|
385
|
+
info "this reports that the vendor RECORDED a decision for this declaration identity AT THE INDEX"
|
|
386
|
+
info "entwurf's declaration was just measured at — not a cryptographic validation. This doctor"
|
|
387
|
+
info "deliberately does NOT recompute what that hash should be: the vendor hashes a normalized"
|
|
388
|
+
info "identity of its own (discovery.rs:775-792), and reimplementing it here would make entwurf a"
|
|
389
|
+
info "second opinion about somebody else's security decision, drifting with every vendor release."
|
|
247
390
|
;;
|
|
248
391
|
ABSENT)
|
|
249
392
|
bad "no vendor trust receipt for this declaration — $TRUST_DETAIL. Until the operator answers the
|