@junghanacs/entwurf 0.19.0 → 0.20.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 +1 -1
- package/BASELINE.md +23 -5
- package/CHANGELOG.md +261 -0
- package/DELIVERY.md +59 -6
- package/README.md +41 -9
- package/VERIFY.md +9 -1
- package/docs/acp-backend-rail.md +41 -16
- package/docs/external-mcp-host.md +10 -5
- package/docs/fresh-cut-policy.md +3 -2
- package/docs/setup-clean-host.md +93 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +3 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +6 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +6 -0
- package/package.json +3 -3
- package/pi-extensions/acp-provider.ts +1 -1
- 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 +3 -3
- package/pi-extensions/lib/acp/event-mapper.ts +4 -4
- package/pi-extensions/lib/acp/models.ts +3 -3
- package/pi-extensions/lib/acp/overlay.ts +6 -2
- package/pi-extensions/lib/acp/tool-surface.ts +6 -0
- package/pi-extensions/lib/meta-session.ts +25 -0
- package/run.sh +104 -33
- package/scripts/agy-bridge.sh +1 -1
- package/scripts/agy-imprint.sh +10 -7
- package/scripts/check-acp-carrier-augment.ts +6 -5
- package/scripts/check-acp-cortex.ts +41 -0
- package/scripts/check-acp-provider-surface.ts +9 -6
- package/scripts/check-acp-usage-accounting.ts +9 -9
- package/scripts/check-copilot-birth-hook.ts +3 -1
- package/scripts/check-copilot-receive-arm.ts +169 -21
- package/scripts/check-copilot-statusline.ts +3 -1
- package/scripts/check-gate-qualification.ts +11 -10
- package/scripts/check-hook-launch-topology.ts +6 -5
- package/scripts/check-install-surface.ts +25 -8
- package/scripts/check-meta-facts.ts +6 -4
- package/scripts/check-meta-hook-session-switch.ts +4 -3
- package/scripts/check-meta-identity-consumers.ts +8 -7
- package/scripts/check-meta-receiver-marker.ts +2 -1
- package/scripts/check-omp-birth-hook.ts +3 -2
- package/scripts/check-probe-bridge-command.ts +3 -1
- package/scripts/check-setup-qualification.sh +21 -0
- package/scripts/copilot-bridge-doctor.sh +15 -5
- package/scripts/copilot-bridge-install.sh +6 -2
- package/scripts/copilot-launch.sh +16 -2
- package/scripts/copilot-receive-bridge.sh +80 -13
- package/scripts/lib/reclaim-on-exit.ts +86 -0
- package/scripts/meta-bridge-doctor.sh +56 -15
- package/scripts/meta-bridge-install.sh +13 -7
- package/scripts/meta-bridge-uninstall.sh +6 -4
- package/scripts/mutants/acp-cortex.json +26 -2
- package/scripts/mutants/copilot-receive.json +26 -0
- package/scripts/mutants/omp-birth.json +23 -6
- package/scripts/mutants/pi-package-ownership.json +26 -0
- package/scripts/mutants/setup-verdict.json +13 -0
- package/scripts/omp-bridge-doctor.sh +110 -41
- package/scripts/omp-bridge-install.sh +6 -2
- package/scripts/omp-receive-install.sh +7 -2
- package/scripts/pi_settings_io.py +3 -1
- package/scripts/raw-async-delivery/README.md +10 -1
- package/scripts/raw-codex-measure/README.md +689 -0
- package/scripts/raw-codex-measure/source-audit.md +243 -0
- package/scripts/raw-macos-measure/README.md +148 -0
- package/scripts/raw-macos-measure/probe.sh +389 -0
- package/scripts/register-pi-package.py +38 -30
- package/scripts/register-pi-provider.py +3 -2
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-meta-async-drift.sh +7 -5
- package/scripts/smoke-meta-install-state.sh +70 -19
- package/scripts/smoke-omp-bridge-state.sh +38 -0
- package/scripts/smoke-setup-verdict.sh +100 -0
- package/scripts/smoke-user-scope-citizen.sh +199 -71
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# raw-macos-measure/probe.sh — Darwin host measurement for the macOS parity lane (#78).
|
|
3
|
+
#
|
|
4
|
+
# MEASUREMENT ONLY. This script writes nothing outside its own temp dir, installs
|
|
5
|
+
# nothing, needs no entwurf installation, and touches no operator config. It is
|
|
6
|
+
# meant to be run ONCE on a borrowed Mac and its whole stdout pasted into
|
|
7
|
+
# scripts/raw-macos-measure/README.md.
|
|
8
|
+
#
|
|
9
|
+
# It is /bin/sh (not bash) on purpose: macOS ships bash 3.2 and one of the things
|
|
10
|
+
# under measurement is what a shell can assume here at all.
|
|
11
|
+
#
|
|
12
|
+
# ./probe.sh # human-readable ledger on stdout
|
|
13
|
+
#
|
|
14
|
+
# Every cell prints its own verdict token so a reader never has to interpret raw
|
|
15
|
+
# tool output: PRESENT / ABSENT / OK / DIFFERS / FAIL / UNKNOWN.
|
|
16
|
+
|
|
17
|
+
set -u
|
|
18
|
+
|
|
19
|
+
TMP=$(mktemp -d "${TMPDIR:-/tmp}/entwurf-macos-probe.XXXXXX") || exit 1
|
|
20
|
+
trap 'rm -rf "$TMP"' EXIT INT TERM
|
|
21
|
+
|
|
22
|
+
hdr() {
|
|
23
|
+
printf '\n== %s ==\n' "$1"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# `have <cmd>` -> PRESENT/ABSENT plus the resolved path.
|
|
27
|
+
have() {
|
|
28
|
+
if p=$(command -v "$1" 2>/dev/null); then
|
|
29
|
+
printf ' %-18s PRESENT %s\n' "$1" "$p"
|
|
30
|
+
return 0
|
|
31
|
+
fi
|
|
32
|
+
printf ' %-18s ABSENT\n' "$1"
|
|
33
|
+
return 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
printf 'entwurf raw-macos-measure probe\n'
|
|
37
|
+
printf 'run at: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ') UTC"
|
|
38
|
+
|
|
39
|
+
hdr 'M1. host identity'
|
|
40
|
+
if command -v sw_vers >/dev/null 2>&1; then
|
|
41
|
+
sw_vers | sed 's/^/ /'
|
|
42
|
+
else
|
|
43
|
+
printf ' sw_vers ABSENT — this is not a Darwin host; the ledger cell is void.\n'
|
|
44
|
+
fi
|
|
45
|
+
printf ' uname -srm: %s\n' "$(uname -srm)"
|
|
46
|
+
printf ' hw.model: %s\n' "$(sysctl -n hw.model 2>/dev/null || echo UNKNOWN)"
|
|
47
|
+
printf ' cpu: %s\n' "$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo UNKNOWN)"
|
|
48
|
+
|
|
49
|
+
hdr 'M2. shell — what a script may assume'
|
|
50
|
+
printf ' /bin/sh -> %s\n' "$(/bin/sh -c 'echo ${BASH_VERSION:-not-bash}' 2>/dev/null)"
|
|
51
|
+
printf ' /bin/bash -> %s\n' "$(/bin/bash -c 'echo ${BASH_VERSION:-unknown}' 2>/dev/null || echo ABSENT)"
|
|
52
|
+
printf ' env bash -> %s (%s)\n' \
|
|
53
|
+
"$(env bash -c 'echo ${BASH_VERSION:-unknown}' 2>/dev/null || echo ABSENT)" \
|
|
54
|
+
"$(command -v bash 2>/dev/null || echo no-bash-on-PATH)"
|
|
55
|
+
# bash 4 features the repo's gates use: `declare -A`, `mapfile`.
|
|
56
|
+
if env bash -c 'declare -A _x' >/dev/null 2>&1; then
|
|
57
|
+
printf ' declare -A (bash4) OK\n'
|
|
58
|
+
else
|
|
59
|
+
printf ' declare -A (bash4) FAIL — associative arrays unavailable to `env bash`\n'
|
|
60
|
+
fi
|
|
61
|
+
if env bash -c 'mapfile -t _y < /dev/null' >/dev/null 2>&1; then
|
|
62
|
+
printf ' mapfile (bash4) OK\n'
|
|
63
|
+
else
|
|
64
|
+
printf ' mapfile (bash4) FAIL\n'
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
hdr 'M3. runtime prerequisites'
|
|
68
|
+
have node && printf ' %-18s %s\n' 'node --version' "$(node --version 2>/dev/null)"
|
|
69
|
+
have npm && printf ' %-18s %s\n' 'npm --version' "$(npm --version 2>/dev/null)"
|
|
70
|
+
have pnpm && printf ' %-18s %s\n' 'pnpm --version' "$(pnpm --version 2>/dev/null)"
|
|
71
|
+
# python3 on macOS may be the CommandLineTools STUB at /usr/bin/python3, which
|
|
72
|
+
# prints nothing useful and pops a GUI installer instead. The shipped operator
|
|
73
|
+
# surface calls python3 in hundreds of places, so a stub is a total install
|
|
74
|
+
# failure, not a degraded cell.
|
|
75
|
+
if have python3; then
|
|
76
|
+
printf ' %-18s %s\n' 'python3 -V' "$(python3 -V 2>&1)"
|
|
77
|
+
if python3 -c 'print("live")' 2>/dev/null | grep -q live; then
|
|
78
|
+
printf ' %-18s REAL (executes code, no install prompt)\n' 'python3 kind'
|
|
79
|
+
else
|
|
80
|
+
printf ' %-18s STUB/BROKEN — the whole install+doctor surface dies here\n' 'python3 kind'
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
have git
|
|
84
|
+
have tmux && printf ' %-18s %s\n' 'tmux -V' "$(tmux -V 2>/dev/null)"
|
|
85
|
+
|
|
86
|
+
hdr 'M4. GNU-vs-BSD tool matrix (the P1 substitution surface)'
|
|
87
|
+
have sha256sum || true
|
|
88
|
+
have shasum || true
|
|
89
|
+
have openssl || true
|
|
90
|
+
have realpath || true
|
|
91
|
+
have timeout || true
|
|
92
|
+
have gtimeout || true
|
|
93
|
+
have gsed || true
|
|
94
|
+
have gstat || true
|
|
95
|
+
printf '\n -- behavioural probes (presence is not compatibility) --\n'
|
|
96
|
+
printf 'abc' >"$TMP/f"
|
|
97
|
+
# stat: GNU `-c %s` vs BSD `-f %z`
|
|
98
|
+
if stat -c %s "$TMP/f" >/dev/null 2>&1; then
|
|
99
|
+
printf ' stat -c %%s OK (GNU form works)\n'
|
|
100
|
+
else
|
|
101
|
+
printf ' stat -c %%s FAIL (GNU form rejected) — BSD `stat -f %%z` = %s\n' \
|
|
102
|
+
"$(stat -f %z "$TMP/f" 2>/dev/null || echo FAIL)"
|
|
103
|
+
fi
|
|
104
|
+
# readlink -f
|
|
105
|
+
ln -s "$TMP/f" "$TMP/link"
|
|
106
|
+
if readlink -f "$TMP/link" >/dev/null 2>&1; then
|
|
107
|
+
printf ' readlink -f OK\n'
|
|
108
|
+
else
|
|
109
|
+
printf ' readlink -f FAIL (no GNU -f)\n'
|
|
110
|
+
fi
|
|
111
|
+
# stat: both fields the repo reads (size AND mtime), because a working `-f %z`
|
|
112
|
+
# does not promise `-f %m`.
|
|
113
|
+
printf ' stat -f %%z (size) %s\n' "$(stat -f %z "$TMP/f" 2>/dev/null || echo FAIL)"
|
|
114
|
+
printf ' stat -f %%m (mtime) %s\n' "$(stat -f %m "$TMP/f" 2>/dev/null || echo FAIL)"
|
|
115
|
+
printf ' stat -c %%Y (mtime) %s\n' "$(stat -c %Y "$TMP/f" 2>/dev/null || echo FAIL)"
|
|
116
|
+
# sha256: which producer, and does the digest text match across producers?
|
|
117
|
+
S_SUM=$(sha256sum "$TMP/f" 2>/dev/null | awk '{print $1}')
|
|
118
|
+
S_SHA=$(shasum -a 256 "$TMP/f" 2>/dev/null | awk '{print $1}')
|
|
119
|
+
S_SSL=$(openssl dgst -sha256 "$TMP/f" 2>/dev/null | awk '{print $NF}')
|
|
120
|
+
printf ' sha256sum %s\n' "${S_SUM:-ABSENT}"
|
|
121
|
+
printf ' shasum -a 256 %s\n' "${S_SHA:-ABSENT}"
|
|
122
|
+
printf ' openssl dgst %s\n' "${S_SSL:-ABSENT}"
|
|
123
|
+
for v in "$S_SUM" "$S_SHA" "$S_SSL"; do
|
|
124
|
+
[ -n "$v" ] && [ "$v" != "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" ] &&
|
|
125
|
+
printf ' DIGEST MISMATCH a producer disagrees with the known sha256 of "abc"\n'
|
|
126
|
+
done
|
|
127
|
+
# NUL-delimited pipelines
|
|
128
|
+
if printf 'a\0b\0' | sort -z >/dev/null 2>&1; then
|
|
129
|
+
printf ' sort -z OK\n'
|
|
130
|
+
else
|
|
131
|
+
printf ' sort -z FAIL (no BSD equivalent flag)\n'
|
|
132
|
+
fi
|
|
133
|
+
if printf 'a\0' | xargs -0 true >/dev/null 2>&1; then
|
|
134
|
+
printf ' xargs -0 OK\n'
|
|
135
|
+
else
|
|
136
|
+
printf ' xargs -0 FAIL\n'
|
|
137
|
+
fi
|
|
138
|
+
if printf 'a\0' | xargs -0r true >/dev/null 2>&1; then
|
|
139
|
+
printf ' xargs -0r OK\n'
|
|
140
|
+
else
|
|
141
|
+
printf ' xargs -0r FAIL (GNU-only -r; BSD xargs already skips empty input)\n'
|
|
142
|
+
fi
|
|
143
|
+
# grep -P
|
|
144
|
+
if printf 'a\n' | grep -P 'a' >/dev/null 2>&1; then
|
|
145
|
+
printf ' grep -P OK\n'
|
|
146
|
+
else
|
|
147
|
+
printf ' grep -P FAIL (no PCRE); grep -E = %s\n' \
|
|
148
|
+
"$(printf 'a\n' | grep -E 'a' >/dev/null 2>&1 && echo OK || echo FAIL)"
|
|
149
|
+
fi
|
|
150
|
+
# date -d
|
|
151
|
+
if date -d '@0' >/dev/null 2>&1; then
|
|
152
|
+
printf ' date -d OK\n'
|
|
153
|
+
else
|
|
154
|
+
printf ' date -d FAIL (BSD date uses -r/-j -f)\n'
|
|
155
|
+
fi
|
|
156
|
+
# mktemp with a template (both accept it, but confirm)
|
|
157
|
+
printf ' mktemp -d %s\n' "$(mktemp -d "${TMPDIR:-/tmp}/probe.XXXXXX" >/dev/null 2>&1 && echo OK || echo FAIL)"
|
|
158
|
+
|
|
159
|
+
hdr 'M5. start-key sources — the identity rail (meta-session.ts:1554)'
|
|
160
|
+
printf ' /proc present? %s\n' "$([ -d /proc ] && echo YES || echo NO)"
|
|
161
|
+
SELF=$$
|
|
162
|
+
printf ' ps -o lstart= self: %s\n' "$(ps -o lstart= -p "$SELF" 2>/dev/null | sed 's/^ *//;s/ *$//')"
|
|
163
|
+
printf ' ps -o command= self: %s\n' "$(ps -o command= -p "$SELF" 2>/dev/null | sed 's/^ *//' | cut -c1-100)"
|
|
164
|
+
# RESOLUTION TEST: two children started back to back. If their lstart strings are
|
|
165
|
+
# equal, the `ps:` scheme's pid-reuse defense is 1-second granular.
|
|
166
|
+
sleep 4 &
|
|
167
|
+
C1=$!
|
|
168
|
+
sleep 4 &
|
|
169
|
+
C2=$!
|
|
170
|
+
L1=$(ps -o lstart= -p "$C1" 2>/dev/null | sed 's/^ *//;s/ *$//')
|
|
171
|
+
L2=$(ps -o lstart= -p "$C2" 2>/dev/null | sed 's/^ *//;s/ *$//')
|
|
172
|
+
printf ' child A (%s): %s\n' "$C1" "$L1"
|
|
173
|
+
printf ' child B (%s): %s\n' "$C2" "$L2"
|
|
174
|
+
if [ "$L1" = "$L2" ]; then
|
|
175
|
+
printf ' RESOLUTION 1-SECOND (two back-to-back starts share one key string)\n'
|
|
176
|
+
else
|
|
177
|
+
printf ' RESOLUTION SUB-SECOND (back-to-back starts differ) — re-run to confirm\n'
|
|
178
|
+
fi
|
|
179
|
+
# WIDENING candidate: would including argv in the key have separated them? Both
|
|
180
|
+
# children run the SAME argv on purpose — that is the WORST case for widening, and
|
|
181
|
+
# the worst case is the one a fail-closed identity rail has to survive.
|
|
182
|
+
W1=$(ps -o lstart=,command= -p "$C1" 2>/dev/null | sed 's/^ *//;s/ *$//')
|
|
183
|
+
W2=$(ps -o lstart=,command= -p "$C2" 2>/dev/null | sed 's/^ *//;s/ *$//')
|
|
184
|
+
printf ' widened key A %s\n' "$W1"
|
|
185
|
+
printf ' widened key B %s\n' "$W2"
|
|
186
|
+
if [ "$W1" = "$W2" ]; then
|
|
187
|
+
printf ' WIDENING INSUFFICIENT ALONE (identical argv collides even widened)\n'
|
|
188
|
+
else
|
|
189
|
+
printf ' WIDENING SEPARATES (argv distinguished two same-second starts)\n'
|
|
190
|
+
fi
|
|
191
|
+
kill "$C1" "$C2" 2>/dev/null
|
|
192
|
+
wait "$C1" 2>/dev/null
|
|
193
|
+
wait "$C2" 2>/dev/null
|
|
194
|
+
# MICROSECOND candidate: libproc proc_pidinfo(PROC_PIDTBSDINFO) via python3 ctypes.
|
|
195
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
196
|
+
python3 - "$SELF" <<'PY' 2>&1 | sed 's/^/ /'
|
|
197
|
+
import ctypes, ctypes.util, sys
|
|
198
|
+
pid = int(sys.argv[1])
|
|
199
|
+
lib = ctypes.util.find_library("proc") or "/usr/lib/libproc.dylib"
|
|
200
|
+
try:
|
|
201
|
+
libproc = ctypes.CDLL(lib)
|
|
202
|
+
except OSError as e:
|
|
203
|
+
print(f"libproc UNAVAILABLE ({e})")
|
|
204
|
+
raise SystemExit(0)
|
|
205
|
+
|
|
206
|
+
PROC_PIDTBSDINFO = 3
|
|
207
|
+
class ProcBsdInfo(ctypes.Structure):
|
|
208
|
+
_fields_ = [
|
|
209
|
+
("pbi_flags", ctypes.c_uint32), ("pbi_status", ctypes.c_uint32),
|
|
210
|
+
("pbi_xstatus", ctypes.c_uint32), ("pbi_pid", ctypes.c_uint32),
|
|
211
|
+
("pbi_ppid", ctypes.c_uint32), ("pbi_uid", ctypes.c_uint32),
|
|
212
|
+
("pbi_gid", ctypes.c_uint32), ("pbi_ruid", ctypes.c_uint32),
|
|
213
|
+
("pbi_rgid", ctypes.c_uint32), ("pbi_svuid", ctypes.c_uint32),
|
|
214
|
+
("pbi_svgid", ctypes.c_uint32), ("rfu_1", ctypes.c_uint32),
|
|
215
|
+
("pbi_comm", ctypes.c_char * 16), ("pbi_name", ctypes.c_char * 32),
|
|
216
|
+
("pbi_nfiles", ctypes.c_uint32), ("pbi_pgid", ctypes.c_uint32),
|
|
217
|
+
("pbi_pjobc", ctypes.c_uint32), ("e_tdev", ctypes.c_uint32),
|
|
218
|
+
("e_tpgid", ctypes.c_uint32), ("pbi_nice", ctypes.c_int32),
|
|
219
|
+
("pbi_start_tvsec", ctypes.c_uint64), ("pbi_start_tvusec", ctypes.c_uint64),
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
info = ProcBsdInfo()
|
|
223
|
+
n = libproc.proc_pidinfo(pid, PROC_PIDTBSDINFO, ctypes.c_uint64(0),
|
|
224
|
+
ctypes.byref(info), ctypes.sizeof(info))
|
|
225
|
+
if n != ctypes.sizeof(info):
|
|
226
|
+
print(f"proc_pidinfo FAIL (returned {n}, expected {ctypes.sizeof(info)})")
|
|
227
|
+
else:
|
|
228
|
+
print(f"proc_pidinfo OK pid={info.pbi_pid} "
|
|
229
|
+
f"start={info.pbi_start_tvsec}.{info.pbi_start_tvusec:06d} "
|
|
230
|
+
f"comm={info.pbi_comm.decode(errors='replace')}")
|
|
231
|
+
print("RESOLUTION MICROSECOND (a sub-second start key IS reachable here)")
|
|
232
|
+
PY
|
|
233
|
+
else
|
|
234
|
+
printf ' proc_pidinfo SKIPPED (no python3)\n'
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
hdr 'M6. process discovery — can a reader see another process at all?'
|
|
238
|
+
# The bridge/receiver doctors narrow candidates through /proc/<pid>/{environ,cmdline}
|
|
239
|
+
# (scripts/omp-bridge-doctor.sh:258-267, scripts/copilot-receive-bridge.sh:264-289).
|
|
240
|
+
# Darwin has neither, so `ps -E` is the only candidate — and WHICH PROCESS IS ASKED
|
|
241
|
+
# decides the answer. Apple-signed, SIP-protected binaries refuse environ reads by
|
|
242
|
+
# construction, so probing `/bin/sleep` would report a false REJECTED.
|
|
243
|
+
#
|
|
244
|
+
# SCOPE, STATED EXACTLY: the node group below is a PROXY for entwurf's own MCP child,
|
|
245
|
+
# which is a node process this probe can start. It is NOT a measurement of the Copilot
|
|
246
|
+
# CLI or of omp: those are different binaries with their own signing and hardened-
|
|
247
|
+
# runtime state, and a proxy result does not transfer to them. The optional group 3
|
|
248
|
+
# probes a REAL copilot/omp process only if the operator already has one running; when
|
|
249
|
+
# it does not, that is reported as NOT MEASURED, never as coverage.
|
|
250
|
+
probe_environ() {
|
|
251
|
+
# $1 = label, $2 = pid, $3 = marker expected in the environment (or "")
|
|
252
|
+
_lbl=$1
|
|
253
|
+
_pid=$2
|
|
254
|
+
_marker=$3
|
|
255
|
+
if ps -E -p "$_pid" >/dev/null 2>&1; then
|
|
256
|
+
_out=$(ps -Eww -p "$_pid" 2>/dev/null | tail -1)
|
|
257
|
+
if [ -n "$_marker" ]; then
|
|
258
|
+
if printf '%s' "$_out" | grep -q "$_marker"; then
|
|
259
|
+
printf ' %-22s VISIBLE — marker found in `ps -Eww` output\n' "$_lbl"
|
|
260
|
+
else
|
|
261
|
+
printf ' %-22s ACCEPTED-BUT-EMPTY — ps -E returned, marker ABSENT\n' "$_lbl"
|
|
262
|
+
fi
|
|
263
|
+
else
|
|
264
|
+
printf ' %-22s ACCEPTED (width %s chars)\n' "$_lbl" \
|
|
265
|
+
"$(printf '%s' "$_out" | wc -c | tr -d ' ')"
|
|
266
|
+
fi
|
|
267
|
+
else
|
|
268
|
+
printf ' %-22s REJECTED by ps -E\n' "$_lbl"
|
|
269
|
+
fi
|
|
270
|
+
}
|
|
271
|
+
# Group 1 — UNRESTRICTED target: a node process we start ourselves, carrying a marker.
|
|
272
|
+
# This is the group that decides whether the doctors' discovery path survives.
|
|
273
|
+
if command -v node >/dev/null 2>&1; then
|
|
274
|
+
ENTWURF_PROBE_MARKER=hello-entwurf \
|
|
275
|
+
node -e 'setTimeout(function(){}, 5000)' >/dev/null 2>&1 &
|
|
276
|
+
N1=$!
|
|
277
|
+
sleep 1
|
|
278
|
+
printf ' node target pid %s\n' "$N1"
|
|
279
|
+
printf ' node argv %s\n' \
|
|
280
|
+
"$(ps -o command= -p "$N1" 2>/dev/null | sed 's/^ *//' | cut -c1-120)"
|
|
281
|
+
probe_environ 'node environ' "$N1" 'ENTWURF_PROBE_MARKER=hello-entwurf'
|
|
282
|
+
printf ' node ppid %s (expect this shell, %s)\n' \
|
|
283
|
+
"$(ps -o ppid= -p "$N1" 2>/dev/null | tr -d ' ')" "$SELF"
|
|
284
|
+
kill "$N1" 2>/dev/null
|
|
285
|
+
wait "$N1" 2>/dev/null
|
|
286
|
+
else
|
|
287
|
+
printf ' node target SKIPPED (no node on PATH) — THIS CELL IS THE IMPORTANT ONE\n'
|
|
288
|
+
fi
|
|
289
|
+
# Group 2 — RESTRICTED control: an Apple-signed system binary. A REJECTED here beside
|
|
290
|
+
# a VISIBLE above is the proof that the refusal is SIP, not a missing capability.
|
|
291
|
+
sleep 5 &
|
|
292
|
+
D1=$!
|
|
293
|
+
printf ' sleep argv %s\n' \
|
|
294
|
+
"$(ps -o command= -p "$D1" 2>/dev/null | sed 's/^ *//' | cut -c1-100)"
|
|
295
|
+
probe_environ '/bin/sleep environ' "$D1" ''
|
|
296
|
+
kill "$D1" 2>/dev/null
|
|
297
|
+
wait "$D1" 2>/dev/null
|
|
298
|
+
# Group 3 — THE REAL TARGETS, when the operator already has one running. Group 1 is a
|
|
299
|
+
# proxy; these are the binaries the doctors actually interrogate. A different signing
|
|
300
|
+
# or hardened-runtime state here than on our own node child is exactly the result that
|
|
301
|
+
# would invalidate the proxy, so an absent process is NOT MEASURED, never coverage.
|
|
302
|
+
for real in copilot omp claude; do
|
|
303
|
+
_rp=$(pgrep -x "$real" 2>/dev/null | head -1)
|
|
304
|
+
if [ -n "${_rp:-}" ]; then
|
|
305
|
+
printf ' %-22s live pid %s\n' "$real (real target)" "$_rp"
|
|
306
|
+
probe_environ "$real environ" "$_rp" ''
|
|
307
|
+
else
|
|
308
|
+
printf ' %-22s NOT MEASURED — no live `%s` process on this host\n' \
|
|
309
|
+
"$real (real target)" "$real"
|
|
310
|
+
fi
|
|
311
|
+
done
|
|
312
|
+
# Full-table scan: the fallback if environ is unreadable is argv-only matching over
|
|
313
|
+
# every process. Measure that it works AND what it costs.
|
|
314
|
+
SCAN_START=$(date +%s)
|
|
315
|
+
SCAN_N=$(ps -A -o pid=,ppid=,command= 2>/dev/null | wc -l | tr -d ' ')
|
|
316
|
+
SCAN_END=$(date +%s)
|
|
317
|
+
printf ' ps -A scan %s rows in ~%ss\n' "$SCAN_N" "$((SCAN_END - SCAN_START))"
|
|
318
|
+
printf ' pgrep present? %s\n' "$(command -v pgrep >/dev/null 2>&1 && echo YES || echo NO)"
|
|
319
|
+
printf ' lsof present? %s\n' "$(command -v lsof >/dev/null 2>&1 && echo YES || echo NO)"
|
|
320
|
+
|
|
321
|
+
hdr 'M7. pid space — how reachable is a same-second pid reuse?'
|
|
322
|
+
printf ' kern.maxproc: %s\n' "$(sysctl -n kern.maxproc 2>/dev/null || echo UNKNOWN)"
|
|
323
|
+
printf ' kern.maxprocperuid: %s\n' "$(sysctl -n kern.maxprocperuid 2>/dev/null || echo UNKNOWN)"
|
|
324
|
+
printf ' current pid: %s\n' "$SELF"
|
|
325
|
+
printf ' (PID_MAX on Darwin is a compile-time 99999; a wrap needs that many spawns)\n'
|
|
326
|
+
|
|
327
|
+
hdr 'M8. filesystem semantics that installers depend on'
|
|
328
|
+
printf ' case-sensitive? '
|
|
329
|
+
touch "$TMP/CaseFile"
|
|
330
|
+
if [ -e "$TMP/casefile" ]; then printf 'NO (case-INSENSITIVE volume)\n'; else printf 'YES\n'; fi
|
|
331
|
+
printf ' HOME: %s\n' "${HOME:-UNSET}"
|
|
332
|
+
printf ' XDG_DATA_HOME: %s\n' "${XDG_DATA_HOME:-unset (entwurf defaults apply)}"
|
|
333
|
+
printf ' symlink support: %s\n' "$([ -L "$TMP/link" ] && echo OK || echo FAIL)"
|
|
334
|
+
printf ' ~/.local/bin on PATH? %s\n' \
|
|
335
|
+
"$(printf '%s' "${PATH:-}" | tr ':' '\n' | grep -qx "$HOME/.local/bin" && echo YES || echo NO)"
|
|
336
|
+
|
|
337
|
+
hdr 'M9. fs.watch rename semantics — the mailbox doorbell'
|
|
338
|
+
# The receiver arms a watch and a sender delivers by writing then renaming into
|
|
339
|
+
# place. Darwin's fs.watch is FSEvents-backed, not inotify: confirm a rename-over
|
|
340
|
+
# actually raises an event for the WATCHED path.
|
|
341
|
+
if command -v node >/dev/null 2>&1; then
|
|
342
|
+
mkdir -p "$TMP/watch"
|
|
343
|
+
printf 'v1\n' >"$TMP/watch/target"
|
|
344
|
+
node -e '
|
|
345
|
+
const fs = require("fs");
|
|
346
|
+
const dir = process.argv[1];
|
|
347
|
+
let fired = [];
|
|
348
|
+
let watchErr = null;
|
|
349
|
+
let w = null;
|
|
350
|
+
try {
|
|
351
|
+
w = fs.watch(dir + "/target", (ev) => fired.push(ev));
|
|
352
|
+
w.on("error", (e) => { watchErr = e; });
|
|
353
|
+
} catch (e) {
|
|
354
|
+
watchErr = e;
|
|
355
|
+
}
|
|
356
|
+
setTimeout(() => {
|
|
357
|
+
try {
|
|
358
|
+
fs.writeFileSync(dir + "/staged", "v2\n");
|
|
359
|
+
fs.renameSync(dir + "/staged", dir + "/target");
|
|
360
|
+
} catch (e) {
|
|
361
|
+
watchErr = watchErr || e;
|
|
362
|
+
}
|
|
363
|
+
}, 200);
|
|
364
|
+
setTimeout(() => {
|
|
365
|
+
try { if (w) w.close(); } catch (e) { /* closing a dead watcher is not the measurement */ }
|
|
366
|
+
// EVERY path prints exactly one verdict token. An error is an honest UNKNOWN, not a
|
|
367
|
+
// missing line: a borrowed host must never hand back a cell that simply is not there.
|
|
368
|
+
if (watchErr) {
|
|
369
|
+
console.log(" fs.watch rename UNKNOWN — watcher errored: " + (watchErr.code || watchErr.message));
|
|
370
|
+
} else if (fired.length) {
|
|
371
|
+
console.log(" fs.watch rename FIRED [" + fired.join(",") + "]");
|
|
372
|
+
} else {
|
|
373
|
+
console.log(" fs.watch rename SILENT — doorbell would not ring");
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const dw = fs.watch(dir, () => {});
|
|
377
|
+
dw.close();
|
|
378
|
+
console.log(" fs.watch on dir SUPPORTED");
|
|
379
|
+
} catch (e) {
|
|
380
|
+
console.log(" fs.watch on dir UNSUPPORTED — " + (e.code || e.message));
|
|
381
|
+
}
|
|
382
|
+
}, 1200);
|
|
383
|
+
' "$TMP/watch" 2>&1 | sed 's/^/ /;s/^ \( *fs\.watch\)/\1/'
|
|
384
|
+
else
|
|
385
|
+
printf ' fs.watch SKIPPED (no node on PATH)\n'
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
printf '\n== probe complete ==\n'
|
|
389
|
+
printf 'Paste this whole output into scripts/raw-macos-measure/README.md under the matching cell.\n'
|
|
@@ -22,29 +22,36 @@ settings-RELATIVE entry that resolves to repo_dir (see below). A look-alike repo
|
|
|
22
22
|
removed. Every non-entwurf package and every other settings key is preserved.
|
|
23
23
|
|
|
24
24
|
CANONICAL IS NOT ONLY THE ABSOLUTE PATH. A packages[] entry is resolved by pi
|
|
25
|
-
against the SETTINGS FILE'S OWN DIRECTORY, so
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
path
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
against the SETTINGS FILE'S OWN DIRECTORY, so a settings-relative entry such as
|
|
26
|
+
`".."` can name the very repo being registered. Comparing entries against the
|
|
27
|
+
resolved absolute path ALONE did not recognize that as entwurf, so `setup` appended
|
|
28
|
+
the absolute path beside it and rewrote a tracked, biome-governed file in a foreign
|
|
29
|
+
style: a dev clone went RED at `pnpm check` step 1, diagnosed as a "formatting"
|
|
30
|
+
error (#53 B). Two rules follow, and they are the same rule read forwards and
|
|
31
|
+
backwards:
|
|
32
32
|
- register: an entry that RESOLVES to repo_dir is already canonical → no-op, and
|
|
33
33
|
when a rewrite is genuinely needed a settings-relative self-reference is kept
|
|
34
34
|
as the survivor, so the portable form is never silently absolutized;
|
|
35
35
|
- remove: register only ever WRITES the absolute form, so a settings-relative
|
|
36
|
-
self-reference cannot be install's own output — it was authored by
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
self-reference cannot be install's own output — it was authored by hand.
|
|
37
|
+
Uninstall is install's inverse, not a settings editor: it leaves that ONE class
|
|
38
|
+
in place and SAYS SO on stdout, rather than deleting source bytes install never
|
|
39
|
+
wrote. The cost is stated rather than hidden: on a settings file whose only
|
|
40
|
+
entwurf entry is relative, `--remove` is a no-op and the package stays
|
|
41
|
+
registered until a human edits it. would_remove() asks the same split, so
|
|
42
|
+
`--dry-run` can never disagree with what `--remove` does.
|
|
43
|
+
This repo's own `.pi/settings.json` USED to be the load-bearing instance of that
|
|
44
|
+
shape. It no longer carries a packages[] entry at all: entwurf's checkout
|
|
45
|
+
self-registers through USER scope only, because a project entry naming "whichever
|
|
46
|
+
checkout I am in" plus a user entry naming the installed one are two package
|
|
47
|
+
identities to pi, and a second checkout then loads a rival copy of pi-extensions
|
|
48
|
+
and wins `--entwurf-control` (#110). The predicate stays because the shape is still
|
|
49
|
+
writable by hand, and because resolving-then-comparing is also what keeps an
|
|
50
|
+
unrelated relative package like `../../repos/gh/andenken` from being read as ours.
|
|
44
51
|
A rewrite also preserves the file's existing indentation instead of forcing 2
|
|
45
52
|
spaces. That is narrower than it sounds — it keeps the indent UNIT, not a
|
|
46
|
-
formatter's line-collapsing decisions — so
|
|
47
|
-
|
|
53
|
+
formatter's line-collapsing decisions — so a byte-identity guarantee always comes
|
|
54
|
+
from the no-op path, never from the writer's style.
|
|
48
55
|
|
|
49
56
|
This wiring (user scope) dropped when `pi install` was removed from setup
|
|
50
57
|
(2026-07-03: `--entwurf-control` unknown in a foreign cwd). Extracting it here
|
|
@@ -129,11 +136,11 @@ def source_of(item: object) -> object:
|
|
|
129
136
|
def is_settings_relative_self(source: str, settings_dir: str, repo_dir: str) -> bool:
|
|
130
137
|
"""True iff this entry is a SETTINGS-RELATIVE path naming repo_dir itself.
|
|
131
138
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
The portable form (`".."` in <repo>/.pi/settings.json) — functionally identical
|
|
140
|
+
to the absolute entry, because pi resolves a relative package source against the
|
|
141
|
+
settings file's own directory. It is also the one shape register never writes,
|
|
142
|
+
which is what lets remove treat it as source rather than as install state. This
|
|
143
|
+
repo stopped shipping it at #110; an operator may still write it by hand.
|
|
137
144
|
|
|
138
145
|
Anything carrying a scheme (npm:/git:/https:) is a spec, not a path; anything
|
|
139
146
|
absolute (`/`, `~`) is not settings-relative. Everything else is resolved and
|
|
@@ -159,8 +166,8 @@ def is_entwurf_source(source: str, repo_dir: str, settings_dir: str | None = Non
|
|
|
159
166
|
- the exact resolved repo dir;
|
|
160
167
|
- an npm install path ending in node_modules/@junghanacs/entwurf;
|
|
161
168
|
- an explicit npm package source for @junghanacs/entwurf;
|
|
162
|
-
- a settings-relative path that RESOLVES to the repo dir (the
|
|
163
|
-
|
|
169
|
+
- a settings-relative path that RESOLVES to the repo dir (the portable
|
|
170
|
+
`".."` form), when the caller supplies settings_dir;
|
|
164
171
|
- a local filesystem path whose final directory is literally "entwurf"
|
|
165
172
|
(dev clone / stale move). Remote URL/git-like strings are NOT treated as
|
|
166
173
|
local paths merely because their last segment is "entwurf".
|
|
@@ -242,7 +249,7 @@ def register(settings_path: Path, repo_dir_arg: str) -> str:
|
|
|
242
249
|
# the canonical absolute string or as a settings-relative path resolving there.
|
|
243
250
|
# Both are the same registration to pi, so both are a no-op: order-insensitive, no
|
|
244
251
|
# rewrite, mtime stable. Recognizing only the absolute form is what made `setup`
|
|
245
|
-
# duplicate
|
|
252
|
+
# duplicate a settings-relative `".."` and restyle the tracked file (#53 B).
|
|
246
253
|
# Both arms require the STRING form, so an object-form entry still collapses to a
|
|
247
254
|
# canonical string exactly as before.
|
|
248
255
|
if len(entwurf_entries) == 1 and isinstance(entwurf_entries[0], str) and (
|
|
@@ -252,8 +259,9 @@ def register(settings_path: Path, repo_dir_arg: str) -> str:
|
|
|
252
259
|
return "noop"
|
|
253
260
|
|
|
254
261
|
# A rewrite keeps the PORTABLE form when the file already had one: absolutizing a
|
|
255
|
-
#
|
|
256
|
-
# same breath. Otherwise the canonical absolute path is what install
|
|
262
|
+
# hand-written `".."` would repair the duplicate and dirty bytes install never
|
|
263
|
+
# wrote in the same breath. Otherwise the canonical absolute path is what install
|
|
264
|
+
# writes.
|
|
257
265
|
survivor = next(
|
|
258
266
|
(
|
|
259
267
|
source_of(e) for e in entwurf_entries
|
|
@@ -277,7 +285,7 @@ def _removable(packages: list, repo_dir: str, settings_dir: str) -> tuple[list,
|
|
|
277
285
|
|
|
278
286
|
PRESERVED = settings-relative self-references, the one shape register CANNOT
|
|
279
287
|
have produced (it always writes the resolved absolute path). Such an entry is
|
|
280
|
-
the
|
|
288
|
+
the operator's own hand edit.
|
|
281
289
|
Deleting it would make uninstall a source editor, which is the defect #53 B is
|
|
282
290
|
about, pointed the other way. main() prints what was kept so the incompleteness
|
|
283
291
|
is loud rather than silent.
|
|
@@ -665,7 +673,7 @@ def remove_user(settings_path: Path, repo_dir_arg: str, state_path: Path, orphan
|
|
|
665
673
|
else:
|
|
666
674
|
print(f"remove: no entwurf packages[] entry naming this root ({settings_path})")
|
|
667
675
|
if preserved_rel:
|
|
668
|
-
print(f"remove: kept {len(preserved_rel)} settings-relative entwurf entr{'y' if len(preserved_rel) == 1 else 'ies'} —
|
|
676
|
+
print(f"remove: kept {len(preserved_rel)} settings-relative entwurf entr{'y' if len(preserved_rel) == 1 else 'ies'} — operator source, not install state")
|
|
669
677
|
if foreign_like:
|
|
670
678
|
print(f"remove: kept {len(foreign_like)} entwurf-shaped entr{'y' if len(foreign_like) == 1 else 'ies'} with no recorded owner "
|
|
671
679
|
"(not provably this root's) — use './run.sh doctor-pi-package' / takeover-user-scope to resolve ownership")
|
|
@@ -829,7 +837,7 @@ def main(argv: list[str]) -> int:
|
|
|
829
837
|
if kept:
|
|
830
838
|
print(
|
|
831
839
|
f"remove: kept {kept} settings-relative entwurf entr{'y' if kept == 1 else 'ies'} "
|
|
832
|
-
f"({settings_path}) — install never writes that form, so it is
|
|
840
|
+
f"({settings_path}) — install never writes that form, so it is operator "
|
|
833
841
|
"source, not install state; edit the file by hand to drop it"
|
|
834
842
|
)
|
|
835
843
|
return 0
|
|
@@ -25,8 +25,9 @@ Scope asymmetry (봉인계약 4·6, REASONED — not the unfounded asymmetry dev
|
|
|
25
25
|
of our key is tidied; siblings kept.
|
|
26
26
|
project <repo>/.pi/settings.json — checkout-LOCAL, disposable, re-creatable, and `run.sh
|
|
27
27
|
remove` already covers it → NO state. install normalizes the command; remove strips
|
|
28
|
-
our-managed shapes (the bare bin AND the legacy repo path). project
|
|
29
|
-
|
|
28
|
+
our-managed shapes (the bare bin AND the legacy repo path). project scope takes NO
|
|
29
|
+
state, deliberately: it is checkout-local, disposable, and `run.sh remove` is its
|
|
30
|
+
whole inverse.
|
|
30
31
|
|
|
31
32
|
USER-SCOPE OWNER BINDING (#86 C2): the user install-state additionally records
|
|
32
33
|
`installerRoot` (the repo/package root that installed the key), so an old root's
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// LIVE=1 ./run.sh smoke-acp-raw-turn-live
|
|
4
4
|
//
|
|
5
5
|
// What this proves (and ONLY this): the pinned Claude ACP adapter
|
|
6
|
-
// (@agentclientprotocol/claude-agent-acp@0.
|
|
6
|
+
// (@agentclientprotocol/claude-agent-acp@0.76.0) spawns, speaks the ACP wire
|
|
7
7
|
// protocol over stdio NDJSON, and returns one real model turn. It is the
|
|
8
8
|
// bytes-flow proof that the S2a dep surface is not just installable but
|
|
9
9
|
// actually drivable — before any provider/overlay/streamSimple code (S2b+).
|
|
@@ -62,11 +62,13 @@ RAW_DIR="$HERE/raw-async-delivery"
|
|
|
62
62
|
# 1.0 -> 1.1) is the real "re-verify the markers + Gotchas + raw/LIVE probes"
|
|
63
63
|
# trigger and DOES scream.
|
|
64
64
|
PIN_CLAUDE_MINOR="2.1"
|
|
65
|
-
# codex 0.
|
|
66
|
-
#
|
|
67
|
-
# 0.
|
|
68
|
-
#
|
|
69
|
-
|
|
65
|
+
# codex 0.153 (2026-09-08): RE-VERIFIED, not merely observed. The archived 0.136.0 raw probe
|
|
66
|
+
# (codex-local-appserver.sh + raw-codex-ws-turn-start.py) was re-run AS SHIPPED on codex-cli
|
|
67
|
+
# 0.153.4 and went green — idle plain `codex` auto-attached to a bare `codex app-server
|
|
68
|
+
# --listen` woke with zero typing and the model replied in the same visible session. Receipts
|
|
69
|
+
# and the six step-1 vendor measurements: scripts/raw-codex-measure/README.md (see raw-codex-
|
|
70
|
+
# measure); coordinates restated in DELIVERY.md §Codex. Codex is still NOT a citizen backend.
|
|
71
|
+
PIN_CODEX_MINOR="0.153"
|
|
70
72
|
# agy 1.1 (2026-07-14): re-verified live — entwurf_self called without a permission prompt,
|
|
71
73
|
# bidirectional native-push reply on the same gid, LIVE=1 smoke-agy-native-push-live 13/13
|
|
72
74
|
# at agy 1.1.0 (evidence recorded in DELIVERY.md §Antigravity).
|