@junghanacs/entwurf 0.21.0 → 0.23.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 +3 -1
- package/CHANGELOG.md +658 -0
- package/DELIVERY.md +167 -29
- package/README.md +104 -14
- package/VERIFY.md +73 -11
- package/docs/external-mcp-host.md +16 -6
- package/docs/setup-clean-host.md +73 -22
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +54 -31
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-caller-seat.js +174 -0
- 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 +289 -57
- 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-runner.js +3 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +8 -4
- 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 +171 -263
- package/mcp/entwurf-bridge/dist/scripts/codex-socket-path.js +30 -0
- package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
- package/mcp/entwurf-bridge/src/index.ts +65 -31
- package/mcp/entwurf-bridge/tsconfig.build.json +10 -0
- package/package.json +4 -4
- package/pi-extensions/entwurf-control.ts +91 -52
- package/pi-extensions/lib/codex-caller-seat.ts +204 -0
- package/pi-extensions/lib/codex-declaration.js +612 -0
- package/pi-extensions/lib/codex-fresh-preflight.ts +325 -52
- 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-runner.ts +3 -2
- package/pi-extensions/lib/entwurf-v2-send.ts +16 -11
- 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 +257 -304
- package/run.sh +522 -10
- package/scripts/check-codex-app-server-launch.ts +445 -0
- 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-facts.ts +12 -6
- package/scripts/check-entwurf-peers-surface.ts +3 -1
- package/scripts/check-entwurf-self-address.ts +68 -0
- package/scripts/check-entwurf-v2-production.ts +42 -1
- package/scripts/check-entwurf-v2-send.ts +26 -7
- package/scripts/check-fresh-call-dispatch.ts +301 -0
- package/scripts/check-gate-qualification.ts +19 -5
- 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-launch-tmux.ts +331 -35
- package/scripts/check-mux-launcher-fence.ts +138 -0
- package/scripts/check-peer-facts.ts +331 -0
- package/scripts/check-release-gate-outcomes.ts +12 -0
- package/scripts/check-typing-call-fence.ts +296 -0
- package/scripts/codex-app-server-launch.sh +275 -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/codex-socket-path.ts +33 -0
- package/scripts/codex-terminal-title-config.py +500 -0
- package/scripts/codex_toml_io.py +121 -0
- 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/lib/codex-fresh-live-protocol.ts +13 -5
- package/scripts/lib/codex-fresh-source-receipts.ts +29 -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-app-server-launch.json +157 -0
- package/scripts/mutants/codex-caller-seat.json +334 -0
- 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 +184 -25
- 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/self-address.json +33 -0
- package/scripts/mutants/typing-call-fence.json +17 -0
- package/scripts/mutants/v2-surface.json +22 -0
- 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-codex-birth.sh +281 -12
- package/scripts/smoke-codex-config-state.sh +192 -3
- package/scripts/smoke-codex-fresh-live.ts +277 -37
- package/scripts/smoke-entwurf-chain-live.ts +50 -0
- package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
- package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
- package/scripts/smoke-setup-verdict.sh +13 -11
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# codex-app-server-launch.sh — the managed Codex app-server launch,
|
|
3
|
+
# `entwurf codex-app-server` (#95, the operator-UX half).
|
|
4
|
+
#
|
|
5
|
+
# WHY THIS EXISTS. Every Codex rail in this repo — native-push delivery, visible fresh,
|
|
6
|
+
# the loaded-thread probe — needs one thing the operator has to start themselves:
|
|
7
|
+
#
|
|
8
|
+
# codex app-server --listen "unix://$CODEX_HOME/app-server-control/app-server-control.sock"
|
|
9
|
+
#
|
|
10
|
+
# That string is correct and nobody types it. It appeared in three documents and in the
|
|
11
|
+
# text of a refusal, which means the first time most operators meet it is AFTER something
|
|
12
|
+
# has already failed. This leaf owns the SPELLING of that command and nothing else.
|
|
13
|
+
#
|
|
14
|
+
# WHAT IT IS NOT — and this is the whole boundary. It is not a supervisor, a daemon, a
|
|
15
|
+
# restarter, a health loop or a pid file. It does not fork. It `exec`s in the caller's own
|
|
16
|
+
# terminal, so the server's cwd, tty, pid and exit status are the vendor's, and Ctrl-C is
|
|
17
|
+
# the operator's. Entwurf still does not own the app-server's lifecycle (AGENTS Hard Rule
|
|
18
|
+
# 16, NEXT CARRIED step 2): a missing app-server still REJECTS at fresh-call preflight
|
|
19
|
+
# rather than being started behind anyone's back. What changed is only that the refusal can
|
|
20
|
+
# now name a command a person can actually type.
|
|
21
|
+
#
|
|
22
|
+
# THE SOCKET PATH IS SPELLED ONCE, SOMEWHERE ELSE. `resolveCodexDefaultSocketPath`
|
|
23
|
+
# (pi-extensions/lib/native-push/codex-ws-client.ts) is where the product computes this path,
|
|
24
|
+
# and it is the leaf every OTHER Codex surface reads. A bash leaf cannot import it, so this one
|
|
25
|
+
# ASKS — `run.sh codex-socket-path`, through `run_ts`. It was written the other way first, with
|
|
26
|
+
# a bash transcription and a gate comparing the two over an environment matrix; they agreed on
|
|
27
|
+
# every ASCII input and diverged on a BOM-only CODEX_HOME and on path normalization. See
|
|
28
|
+
# scripts/codex-socket-path.ts for that measurement. A transcription can only be tested on the
|
|
29
|
+
# inputs somebody thought of, which is why there is no longer one to test.
|
|
30
|
+
#
|
|
31
|
+
# THE TMUX LINE IS A FACT, NOT A REFUSAL. The MCP bridge is a CHILD of this server and
|
|
32
|
+
# inherits its `TMUX`, so the tmux server this process sits in is the one caller-seat
|
|
33
|
+
# lookups will read (docs/mux-launch-rail.md §Codex). Running outside tmux is a legitimate
|
|
34
|
+
# operator choice with a consequence, so it is reported as a consequence. Refusing it here
|
|
35
|
+
# would make this leaf an authority over the operator's terminal, which it is not.
|
|
36
|
+
set -euo pipefail
|
|
37
|
+
|
|
38
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
39
|
+
REPO_DIR="$(cd "$HERE/.." && pwd)"
|
|
40
|
+
|
|
41
|
+
# The vendor command this leaf manages. A CONSTANT, deliberately not overridable: an env
|
|
42
|
+
# seam here would be a production switch for "which binary is the Codex CLI", and anything
|
|
43
|
+
# that can redirect an exec is an authority, not a test convenience. The gate proves the
|
|
44
|
+
# real contract instead — it puts its fake vendor on a sandbox PATH under this exact name.
|
|
45
|
+
VENDOR_CMD="codex"
|
|
46
|
+
|
|
47
|
+
fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
|
|
48
|
+
note() { printf '[entwurf] %s\n' "$*" >&2; }
|
|
49
|
+
|
|
50
|
+
# --- recursion fence -------------------------------------------------------
|
|
51
|
+
# A `codex` earlier on PATH that itself calls `entwurf codex-app-server` would spin
|
|
52
|
+
# forever, and the symptom would be a hung terminal rather than an error. The sentinel is
|
|
53
|
+
# invocation-local, so seeing it already set means we are inside our own exec chain.
|
|
54
|
+
if [ -n "${ENTWURF_CODEX_APP_SERVER_ACTIVE:-}" ]; then
|
|
55
|
+
fail "recursive managed launch detected (ENTWURF_CODEX_APP_SERVER_ACTIVE is already set).
|
|
56
|
+
Something on PATH named '$VENDOR_CMD' resolves back to this launcher. Fix PATH so
|
|
57
|
+
'$VENDOR_CMD' is the OpenAI Codex CLI, or run the vendor binary by its full path."
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# --- the socket path: ASKED, never re-derived -------------------------------
|
|
61
|
+
# This leaf does no path arithmetic at all, and that is the correction rather than the style.
|
|
62
|
+
# Its first version mirrored `resolveCodexHome` in bash — `${CODEX_HOME:-$HOME/.codex}` plus a
|
|
63
|
+
# POSIX `[:space:]` trim — with a gate comparing the two spellings over four ASCII-normal
|
|
64
|
+
# inputs they happened to agree on. `[측정 2026-09-16, independent review]` they disagree
|
|
65
|
+
# elsewhere: with `CODEX_HOME=$'\ufeff'` the TS leaf trims (JS trim strips U+FEFF) and falls
|
|
66
|
+
# back to `$HOME/.codex`, while the bash trim kept the byte and produced
|
|
67
|
+
# `<BOM>/app-server-control/app-server-control.sock`; `path.join` likewise normalizes a
|
|
68
|
+
# trailing slash or a `..` segment where the bash concatenation did not. Each of those starts a
|
|
69
|
+
# server at an address delivery and preflight never look at.
|
|
70
|
+
#
|
|
71
|
+
# So the second spelling is gone instead of widened — a transcription can only ever be tested
|
|
72
|
+
# on the inputs somebody thought of. `run.sh codex-socket-path` prints what
|
|
73
|
+
# `resolveCodexDefaultSocketPath` computes for THIS environment, through `run_ts` (compiled twin
|
|
74
|
+
# when installed, strip-types in a clone). An empty or failed answer is a hard refusal: there is
|
|
75
|
+
# no fallback spelling left to guess with, and that is the point.
|
|
76
|
+
if ! SOCK="$(bash "$REPO_DIR/run.sh" codex-socket-path)"; then
|
|
77
|
+
fail "could not resolve the Codex control-socket path.
|
|
78
|
+
'$REPO_DIR/run.sh codex-socket-path' failed; that leaf is the only spelling of this address,
|
|
79
|
+
and this launcher will not invent a second one. Its output above says why."
|
|
80
|
+
fi
|
|
81
|
+
[ -n "$SOCK" ] \
|
|
82
|
+
|| fail "the Codex control-socket resolver returned an empty path — neither CODEX_HOME nor
|
|
83
|
+
HOME names a usable Codex home. Set CODEX_HOME."
|
|
84
|
+
|
|
85
|
+
# Asking one authority for the address does not make the ANSWER safe to act on, and this is the
|
|
86
|
+
# one place that acts on it: `mkdir -p` creates a directory at it and the vendor binds it. The
|
|
87
|
+
# resolver faithfully returns whatever `CODEX_HOME` holds — it neither requires an absolute path
|
|
88
|
+
# nor rejects control characters, because the surfaces that merely READ it (preflight's lstat,
|
|
89
|
+
# the delivery probe) fail closed on their own. A RELATIVE address is a different file for every
|
|
90
|
+
# process that resolves it, and the bridge that will look for this socket is the app-server's MCP
|
|
91
|
+
# child with its own cwd — so the one thing a launch cannot do is bind a path whose meaning
|
|
92
|
+
# depends on where it was typed. Control characters are refused with it: a newline in an address
|
|
93
|
+
# is not a path this repo will create on an operator's behalf. Both refuse BEFORE any write.
|
|
94
|
+
case "$SOCK" in
|
|
95
|
+
/*) ;;
|
|
96
|
+
*) fail "codex-app-server-socket-path-not-absolute: the resolver returned
|
|
97
|
+
'$SOCK'
|
|
98
|
+
A relative control-socket address resolves to a different file for every process that reads it,
|
|
99
|
+
and the bridge that looks for this socket runs with its own working directory. Set CODEX_HOME to
|
|
100
|
+
an absolute path." ;;
|
|
101
|
+
esac
|
|
102
|
+
case "$SOCK" in
|
|
103
|
+
*[[:cntrl:]]*) fail "codex-app-server-socket-path-untrusted: the resolved control-socket address
|
|
104
|
+
carries a control character (CODEX_HOME almost certainly does). Refusing to create or bind it." ;;
|
|
105
|
+
esac
|
|
106
|
+
|
|
107
|
+
# --- operator argv: forwarded, never reinterpreted -------------------------
|
|
108
|
+
# Everything the operator passes is appended after the injected pair and crosses
|
|
109
|
+
# byte-identical. The single exception is a second `--listen`: injecting ours beside theirs
|
|
110
|
+
# would hand the vendor two listen addresses and let one of them win silently, and a silent
|
|
111
|
+
# winner is exactly the false success this repo refuses. `--opt=value` and `--opt value` are
|
|
112
|
+
# the same option, so the head is what is compared.
|
|
113
|
+
for tok in "$@"; do
|
|
114
|
+
if [ "${tok%%=*}" = "--listen" ]; then
|
|
115
|
+
fail "codex-app-server-listen-override: this verb exists to spell ONE --listen address
|
|
116
|
+
(unix://$SOCK), and you passed your own. Run the vendor directly if you mean a
|
|
117
|
+
different address: $VENDOR_CMD app-server --listen <your-address>"
|
|
118
|
+
fi
|
|
119
|
+
done
|
|
120
|
+
|
|
121
|
+
# --- resolve the vendor executable -----------------------------------------
|
|
122
|
+
# `type -P` returns the PATH hit only — never a shell function, alias or builtin — so what
|
|
123
|
+
# is exec'd is a real external file. `exec command codex` is deliberately NOT used: it would
|
|
124
|
+
# re-enter shell lookup at exec time and could pick up something other than the file
|
|
125
|
+
# validated here.
|
|
126
|
+
codex_bin="$(type -P "$VENDOR_CMD" 2>/dev/null || true)"
|
|
127
|
+
[ -n "$codex_bin" ] \
|
|
128
|
+
|| fail "no '$VENDOR_CMD' executable found on PATH.
|
|
129
|
+
The managed launch runs the OpenAI Codex CLI; install it, or put it on PATH."
|
|
130
|
+
[ -f "$codex_bin" ] && [ -x "$codex_bin" ] \
|
|
131
|
+
|| fail "'$codex_bin' is not an executable regular file — refusing to exec it."
|
|
132
|
+
|
|
133
|
+
# Self-exec fence, the second half of the recursion guard: resolve symlinks and refuse
|
|
134
|
+
# anything that is one of our own entrypoints even if the sentinel was stripped. POSIX walk,
|
|
135
|
+
# same shape as scripts/copilot-launch.sh — BSD readlink has no -f.
|
|
136
|
+
resolve_path() {
|
|
137
|
+
local SOURCE="$1" DIR TARGET
|
|
138
|
+
while [ -L "$SOURCE" ]; do
|
|
139
|
+
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
140
|
+
TARGET="$(readlink "$SOURCE")"
|
|
141
|
+
case "$TARGET" in
|
|
142
|
+
/*) SOURCE="$TARGET" ;;
|
|
143
|
+
*) SOURCE="$DIR/$TARGET" ;;
|
|
144
|
+
esac
|
|
145
|
+
done
|
|
146
|
+
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
147
|
+
printf '%s/%s\n' "$DIR" "$(basename "$SOURCE")"
|
|
148
|
+
}
|
|
149
|
+
resolved_bin="$(resolve_path "$codex_bin")"
|
|
150
|
+
for own in "$HERE/codex-app-server-launch.sh" "$REPO_DIR/run.sh"; do
|
|
151
|
+
own_resolved="$(resolve_path "$own")"
|
|
152
|
+
[ "$resolved_bin" = "$own_resolved" ] \
|
|
153
|
+
&& fail "'$VENDOR_CMD' on PATH resolves to entwurf's own '$own_resolved' — that is a launch loop, not the vendor CLI."
|
|
154
|
+
done
|
|
155
|
+
|
|
156
|
+
# --- is somebody already listening there? ----------------------------------
|
|
157
|
+
# Three outcomes, and they are NOT the same repair. A live socket means a second server
|
|
158
|
+
# would either lose the bind race or silently replace the endpoint every citizen's record
|
|
159
|
+
# already points at — refuse and name the holder. An indeterminate path (symlink, not a
|
|
160
|
+
# socket, owned by another uid) is the classification `checkCodexSocketFile` already uses in
|
|
161
|
+
# the product, and a launcher that guessed past it would be clobbering something it cannot
|
|
162
|
+
# identify. A dead socket file is the ordinary leftover of a Ctrl-C, and the vendor replaces
|
|
163
|
+
# it — that one is a fact line, not a refusal.
|
|
164
|
+
probe="$(python3 - "$SOCK" <<'PY'
|
|
165
|
+
import os, socket, stat, sys
|
|
166
|
+
|
|
167
|
+
path = sys.argv[1]
|
|
168
|
+
try:
|
|
169
|
+
st = os.lstat(path)
|
|
170
|
+
except FileNotFoundError:
|
|
171
|
+
print("absent")
|
|
172
|
+
raise SystemExit(0)
|
|
173
|
+
except OSError as exc:
|
|
174
|
+
print(f"indeterminate\t{exc.strerror or exc}")
|
|
175
|
+
raise SystemExit(0)
|
|
176
|
+
|
|
177
|
+
if stat.S_ISLNK(st.st_mode):
|
|
178
|
+
print("indeterminate\tthe path is a symlink")
|
|
179
|
+
raise SystemExit(0)
|
|
180
|
+
if not stat.S_ISSOCK(st.st_mode):
|
|
181
|
+
print("indeterminate\tthe path exists and is not a socket")
|
|
182
|
+
raise SystemExit(0)
|
|
183
|
+
if st.st_uid != os.getuid():
|
|
184
|
+
print(f"indeterminate\tthe socket is owned by uid {st.st_uid}, not {os.getuid()}")
|
|
185
|
+
raise SystemExit(0)
|
|
186
|
+
|
|
187
|
+
probe = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
188
|
+
probe.settimeout(2)
|
|
189
|
+
try:
|
|
190
|
+
probe.connect(path)
|
|
191
|
+
except (ConnectionRefusedError, FileNotFoundError):
|
|
192
|
+
print("stale")
|
|
193
|
+
except OSError as exc:
|
|
194
|
+
print(f"indeterminate\t{exc.strerror or exc}")
|
|
195
|
+
else:
|
|
196
|
+
print("live")
|
|
197
|
+
finally:
|
|
198
|
+
probe.close()
|
|
199
|
+
PY
|
|
200
|
+
)" || fail "could not classify the control socket at $SOCK (python3 is required by this launcher)."
|
|
201
|
+
|
|
202
|
+
probe_status="${probe%% *}"
|
|
203
|
+
probe_reason="${probe#* }"
|
|
204
|
+
case "$probe_status" in
|
|
205
|
+
live)
|
|
206
|
+
# Show what the host actually says, and say so when it says nothing. The owner is read
|
|
207
|
+
# out of /proc rather than inferred from the socket: a socket file names no pid, so the
|
|
208
|
+
# only honest answer when no cmdline matches is that no match was found.
|
|
209
|
+
owner=""
|
|
210
|
+
if [ -d /proc ]; then
|
|
211
|
+
for cl in /proc/[0-9]*/cmdline; do
|
|
212
|
+
[ -r "$cl" ] || continue
|
|
213
|
+
if tr '\0' ' ' < "$cl" 2>/dev/null | grep -qF "$SOCK"; then
|
|
214
|
+
owner="${owner} pid $(basename "$(dirname "$cl")"): $(tr '\0' ' ' < "$cl")
|
|
215
|
+
"
|
|
216
|
+
fi
|
|
217
|
+
done
|
|
218
|
+
fi
|
|
219
|
+
[ -n "$owner" ] || owner=" (no process on this host spells that socket in its cmdline — read, not inferred)
|
|
220
|
+
"
|
|
221
|
+
fail "codex-app-server-already-listening: a live app-server is answering on
|
|
222
|
+
unix://$SOCK
|
|
223
|
+
Entwurf never replaces one. What /proc reports about it:
|
|
224
|
+
$owner Use that server, or stop it first."
|
|
225
|
+
;;
|
|
226
|
+
indeterminate)
|
|
227
|
+
fail "codex-app-server-socket-indeterminate: $probe_reason
|
|
228
|
+
Path: $SOCK
|
|
229
|
+
This is the same classification the delivery rail's socket check uses, and a launcher that
|
|
230
|
+
guessed past it would be clobbering something it cannot identify. Inspect that path."
|
|
231
|
+
;;
|
|
232
|
+
stale)
|
|
233
|
+
note "a dead control socket is already at $SOCK (nothing is listening); the vendor replaces it."
|
|
234
|
+
;;
|
|
235
|
+
absent)
|
|
236
|
+
: # nothing is there; the ordinary first launch
|
|
237
|
+
;;
|
|
238
|
+
*)
|
|
239
|
+
# Hard Rule 15. Every branch above is a decision about whether this launch would
|
|
240
|
+
# CLOBBER a running server, so an unrecognised classification is the one case where
|
|
241
|
+
# proceeding is unsafe — and a probe that exits 0 while printing something nobody wrote
|
|
242
|
+
# would otherwise fall straight through to the exec.
|
|
243
|
+
fail "codex-app-server-socket-probe-unrecognised: the socket classifier answered
|
|
244
|
+
'$probe_status', which is not one of live/stale/indeterminate/absent. Refusing to launch on a
|
|
245
|
+
reading nothing understands."
|
|
246
|
+
;;
|
|
247
|
+
esac
|
|
248
|
+
|
|
249
|
+
# --- the control directory the address lives in ----------------------------
|
|
250
|
+
# The same `mkdir -p` the three documents told the operator to run by hand. It is inside
|
|
251
|
+
# their own CODEX_HOME, and without it a first launch fails on a directory rather than on
|
|
252
|
+
# anything meaningful.
|
|
253
|
+
mkdir -p "$(dirname "$SOCK")" \
|
|
254
|
+
|| fail "could not create $(dirname "$SOCK") — the control socket has nowhere to live."
|
|
255
|
+
|
|
256
|
+
# --- the tmux fact, stated once --------------------------------------------
|
|
257
|
+
note "this app-server's tmux seat: ${TMUX:-(none — not inside tmux)}"
|
|
258
|
+
note "the bridge is this server's MCP child and inherits that TMUX, so caller-seat lookups read THAT tmux server's panes."
|
|
259
|
+
note "entwurf neither supervises nor restarts this process — Ctrl-C is yours."
|
|
260
|
+
|
|
261
|
+
export ENTWURF_CODEX_APP_SERVER_ACTIVE=1
|
|
262
|
+
|
|
263
|
+
# --- foreign identity carriers, removed before exec ------------------------
|
|
264
|
+
# The MCP bridge trusts a COMPLETE `PI_SESSION_ID` + `PI_AGENT_ID` pair ahead of a native
|
|
265
|
+
# sender marker when it resolves who is speaking. Inside the pi process that planted them
|
|
266
|
+
# from record birth that is correct. Here they would be somebody else's identity inherited by
|
|
267
|
+
# the app-server and by EVERY bridge child it spawns — start the server from a pi citizen's
|
|
268
|
+
# bash and each Codex thread's MCP child could speak under the parent pi garden id.
|
|
269
|
+
#
|
|
270
|
+
# Both go, together: clearing one only changes the wording of a later failure while leaving a
|
|
271
|
+
# carrier for a partial reader. Nothing is substituted — a Codex thread's identity comes from
|
|
272
|
+
# its own trusted birth hook, which is where the record authority already lives.
|
|
273
|
+
unset PI_SESSION_ID PI_AGENT_ID
|
|
274
|
+
|
|
275
|
+
exec "$codex_bin" app-server --listen "unix://$SOCK" "$@"
|
|
@@ -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
|