@junghanacs/entwurf 0.15.1 → 0.16.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/CHANGELOG.md +354 -0
- package/DELIVERY.md +3 -2
- package/README.md +68 -88
- package/VERIFY.md +4 -1
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +1 -1
- package/docs/external-mcp-host.md +147 -9
- package/docs/setup-clean-host.md +125 -6
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +65 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +19 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +17 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +172 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +152 -17
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +271 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +988 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-receive-omp.js +574 -0
- package/mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js +84 -0
- package/mcp/entwurf-bridge/src/index.ts +65 -19
- package/mcp/entwurf-bridge/tsconfig.build.json +19 -3
- package/mcp/tsconfig.json +10 -0
- package/package.json +19 -9
- package/pi/entwurf-capabilities.json +1 -0
- package/pi/meta-bridge-omp/entwurf-meta-omp/package.json +7 -0
- package/pi/omp-receive/entwurf-receive-omp/package.json +7 -0
- package/pi-extensions/entwurf-control.ts +12 -9
- package/pi-extensions/lib/acp/backend-adapter.ts +19 -9
- package/pi-extensions/lib/acp/backend.ts +125 -7
- package/pi-extensions/lib/acp/claude-acp-launch.js +100 -0
- package/pi-extensions/lib/entwurf-self-address.ts +18 -7
- package/pi-extensions/lib/meta-sender-identity.ts +1 -1
- package/pi-extensions/lib/meta-session.ts +219 -5
- package/pi-extensions/lib/mux-fresh-call.ts +171 -17
- package/pi-extensions/lib/omp-fresh-preflight.ts +275 -0
- package/pi-extensions/meta-bridge-omp.ts +1244 -0
- package/pi-extensions/meta-bridge-receive-omp.ts +666 -0
- package/run.sh +399 -31
- package/scripts/check-acp-launch-namespace.ts +127 -0
- package/scripts/check-acp-prompt-lifecycle.ts +145 -2
- package/scripts/check-copilot-birth-hook.ts +28 -1
- package/scripts/check-entwurf-self-address.ts +31 -0
- package/scripts/check-gate-qualification.ts +7 -3
- package/scripts/check-harness-admission-parity.ts +143 -0
- package/scripts/check-meta-doctor-oracle.sh +20 -0
- package/scripts/check-omp-birth-hook.ts +1049 -0
- package/scripts/check-omp-fresh-preflight.ts +208 -0
- package/scripts/check-omp-receive-arm.ts +516 -0
- package/scripts/check-setup-qualification.sh +40 -2
- package/scripts/copilot-bridge-oracle.sh +14 -6
- package/scripts/fake-copilot-vendor.sh +4 -2
- package/scripts/inventory-verification-surface.ts +1 -1
- package/scripts/meta-bridge-hook-log.sh +9 -1
- package/scripts/mutants/acp-launch-namespace.json +34 -0
- package/scripts/mutants/acp-prompt-lifecycle.json +67 -2
- package/scripts/mutants/copilot-birth.json +10 -10
- package/scripts/mutants/mux-fresh-call.json +22 -22
- package/scripts/mutants/omp-birth.json +173 -0
- package/scripts/mutants/omp-fresh.json +300 -0
- package/scripts/mutants/omp-receive.json +135 -0
- package/scripts/mutants/pack-install.json +2 -2
- package/scripts/mutants/self-address.json +34 -0
- package/scripts/mutants/setup-verdict.json +35 -0
- package/scripts/omp-bridge-doctor.sh +315 -0
- package/scripts/omp-bridge-install.sh +221 -0
- package/scripts/omp-bridge-oracle.sh +154 -0
- package/scripts/omp-bridge-uninstall.sh +57 -0
- package/scripts/omp-config-xdev.py +310 -0
- package/scripts/omp-config-xdev.sh +76 -0
- package/scripts/omp-mcp-bridge.sh +320 -0
- package/scripts/omp-mcp-config.py +392 -0
- package/scripts/omp-receive-doctor.sh +246 -0
- package/scripts/omp-receive-facts.ts +106 -0
- package/scripts/omp-receive-install.sh +228 -0
- package/scripts/omp-receive-uninstall.sh +60 -0
- package/scripts/omp-tool-surface.py +400 -0
- package/scripts/raw-acp-child-exit-measure/README.md +285 -0
- package/scripts/raw-acp-child-exit-measure/acp-turn-population.py +89 -0
- package/scripts/raw-acp-child-exit-measure/reaper-correlation.py +47 -0
- package/scripts/raw-omp-measure/README.md +420 -0
- package/scripts/raw-omp-measure/probe-extension.ts +76 -0
- package/scripts/raw-omp-measure/probe-receive-surface.ts +250 -0
- package/scripts/raw-omp-measure/source-audit.md +414 -0
- package/scripts/smoke-omp-bridge-state.sh +221 -0
- package/scripts/smoke-omp-fresh-live.ts +497 -0
- package/scripts/smoke-omp-mcp-state.sh +327 -0
- package/scripts/smoke-omp-receive-live.ts +340 -0
- package/scripts/smoke-omp-receive-state.sh +196 -0
- package/scripts/smoke-setup-verdict.sh +48 -3
- package/scripts/tsconfig.json +2 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# omp-bridge-doctor.sh — the fail-loud surface for the OMP BIRTH extension (#87).
|
|
3
|
+
#
|
|
4
|
+
# TWO AXES, REPORTED SEPARATELY, EITHER RED IS RED (AGENTS.md Hard Rule 13):
|
|
5
|
+
# runtime — is there a unit omp will actually import, does it carry the CURRENT
|
|
6
|
+
# writer, and what did it do the last time it ran?
|
|
7
|
+
# ownership — does entwurf own that unit, by a state whose shape and bindings hold?
|
|
8
|
+
# Runtime coverage does not prove ownership, and broken ownership does not erase a
|
|
9
|
+
# visibly working configuration.
|
|
10
|
+
#
|
|
11
|
+
# THE PLACEMENT LAYER, AND WHY THIS DOCTOR CAN READ IT AT ALL. omp offers two vendor
|
|
12
|
+
# surfaces for declaring an extension (ledger M2): a native root scan of
|
|
13
|
+
# `<agent-dir>/extensions` (`discovery/builtin.ts:483` → `discovery/helpers.ts:625-712`)
|
|
14
|
+
# and an `extensions:` array in a settings file. This unit uses the FIRST, so the whole
|
|
15
|
+
# installed artifact is one directory entwurf created and can remove exactly — there is
|
|
16
|
+
# no operator-owned SSOT to preimage, merge into, or accidentally clobber, and this
|
|
17
|
+
# doctor's runtime axis is a structural read rather than a diff against somebody else's
|
|
18
|
+
# config. (`~/.omp/agent/config.yml` carries the operator's model roles, status line and
|
|
19
|
+
# plan settings; nothing here writes to it. The MCP hand is the opposite case and does
|
|
20
|
+
# write a vendor config file, with the preimage/inverse discipline that comes with it —
|
|
21
|
+
# see doctor-omp-mcp.)
|
|
22
|
+
#
|
|
23
|
+
# WHAT IT DELIBERATELY DOES NOT CLAIM. A green runtime axis is not a live citizen: only
|
|
24
|
+
# a real omp TUI session minting a record is that (`adding-a-harness.md` step 3(b)). An
|
|
25
|
+
# installed unit with zero omp records is reported as NOT-YET, never as red.
|
|
26
|
+
set -uo pipefail
|
|
27
|
+
|
|
28
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
REPO="$(cd "$HERE/.." && pwd)"
|
|
30
|
+
UNIT="entwurf-meta-omp"
|
|
31
|
+
SRC="$REPO/pi/meta-bridge-omp"
|
|
32
|
+
ASM="${ENTWURF_OMP_ASM:-${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/meta-bridge-omp/.assembled}"
|
|
33
|
+
STATE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/omp-bridge"
|
|
34
|
+
STATE_FILE="$STATE_DIR/install-state.json"
|
|
35
|
+
|
|
36
|
+
fail=0
|
|
37
|
+
own_fail=0
|
|
38
|
+
ok() { echo " ok $*"; }
|
|
39
|
+
note() { echo " note $*"; }
|
|
40
|
+
bad() { echo " FAIL $*"; fail=1; }
|
|
41
|
+
own_bad() { echo " FAIL $*"; own_fail=1; }
|
|
42
|
+
|
|
43
|
+
# shellcheck source=scripts/omp-bridge-oracle.sh
|
|
44
|
+
. "$HERE/omp-bridge-oracle.sh"
|
|
45
|
+
|
|
46
|
+
echo "[omp birth extension — runtime axis]"
|
|
47
|
+
|
|
48
|
+
if ! command -v omp >/dev/null 2>&1; then
|
|
49
|
+
note "the 'omp' CLI is not on PATH — this host has no omp to be a citizen of (entwurf never installs one)"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
AGENT_DIR="$(omp_agent_dir)"
|
|
53
|
+
if [ -z "$AGENT_DIR" ]; then
|
|
54
|
+
bad "the omp agent directory this host reads is AMBIGUOUS (see the reason above) — no verdict can be given about a directory we refuse to guess"
|
|
55
|
+
AGENT_DIR=""
|
|
56
|
+
UNIT_DIR=""
|
|
57
|
+
else
|
|
58
|
+
UNIT_DIR="$AGENT_DIR/extensions/$UNIT"
|
|
59
|
+
ok "effective omp agent dir: $AGENT_DIR (unit path: $UNIT_DIR)"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
if [ -n "$UNIT_DIR" ]; then
|
|
63
|
+
if [ -L "$UNIT_DIR" ]; then
|
|
64
|
+
bad "$UNIT_DIR is a SYMLINK — this installer never creates one, so what omp imports there is not ours"
|
|
65
|
+
elif [ ! -d "$UNIT_DIR" ]; then
|
|
66
|
+
if [ -f "$STATE_FILE" ]; then
|
|
67
|
+
bad "ownership state exists but the unit is GONE: $UNIT_DIR — no omp session can be born. Re-run ./run.sh install-omp-bridge"
|
|
68
|
+
else
|
|
69
|
+
note "no unit installed at $UNIT_DIR and no ownership state — this host has not run install-omp-bridge (zero state, not a fault)"
|
|
70
|
+
fi
|
|
71
|
+
elif omp_assembly_valid "$(dirname "$UNIT_DIR")" "$UNIT" 2>/tmp/omp-doctor-oracle.$$; then
|
|
72
|
+
ENTRY_NAME="index.ts"; [ -f "$UNIT_DIR/index.ts" ] || ENTRY_NAME="index.js"
|
|
73
|
+
ok "installed unit is structurally complete (entry $ENTRY_NAME + lib + capability registry + type:module)"
|
|
74
|
+
ok "omp discovers it by its own native rule: <agent-dir>/extensions/$UNIT/$ENTRY_NAME (discovery/helpers.ts:700-710)"
|
|
75
|
+
# WRITER PARITY. The unit carries a COPY of the shared V3 writer and of the capability
|
|
76
|
+
# registry, exactly like the Claude and Copilot units. A stale copy is the failure mode
|
|
77
|
+
# `adding-a-harness.md` step 2(c) names: the deployed reader refuses records the new
|
|
78
|
+
# backend list admits, including its own. Same judgement, same prescription.
|
|
79
|
+
LIB_EXT="ts"; [ "$ENTRY_NAME" = "index.js" ] && LIB_EXT="js"
|
|
80
|
+
case "$REPO" in
|
|
81
|
+
*/node_modules/@junghanacs/entwurf) SRC_LIB="$REPO/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"; SRC_ENTRY="$REPO/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js" ;;
|
|
82
|
+
*) SRC_LIB="$REPO/pi-extensions/lib/meta-session.ts"; SRC_ENTRY="$REPO/pi-extensions/meta-bridge-omp.ts" ;;
|
|
83
|
+
esac
|
|
84
|
+
for pair in "lib/meta-session.$LIB_EXT|$SRC_LIB|writer" "$ENTRY_NAME|$SRC_ENTRY|birth entry" "entwurf-capabilities.json|$REPO/pi/entwurf-capabilities.json|capability registry"; do
|
|
85
|
+
rel="${pair%%|*}"; rest="${pair#*|}"; src="${rest%%|*}"; label="${rest##*|}"
|
|
86
|
+
if [ ! -f "$src" ]; then
|
|
87
|
+
note "$label source is absent in this checkout ($src) — parity not measurable here"
|
|
88
|
+
continue
|
|
89
|
+
fi
|
|
90
|
+
INST_SHA="$(sha256sum "$UNIT_DIR/$rel" 2>/dev/null | cut -c1-12)"
|
|
91
|
+
SRC_SHA="$(sha256sum "$src" 2>/dev/null | cut -c1-12)"
|
|
92
|
+
if [ -n "$INST_SHA" ] && [ "$INST_SHA" = "$SRC_SHA" ]; then
|
|
93
|
+
ok "installed $label matches source ($INST_SHA)"
|
|
94
|
+
else
|
|
95
|
+
bad "installed $label is STALE: installed=${INST_SHA:-missing} vs source=$SRC_SHA ($UNIT_DIR/$rel). The live omp extension is running old bytes — run ./run.sh install-omp-bridge, then restart any open omp session"
|
|
96
|
+
fi
|
|
97
|
+
done
|
|
98
|
+
else
|
|
99
|
+
bad "installed unit at $UNIT_DIR fails the structural oracle: $(cat /tmp/omp-doctor-oracle.$$ 2>/dev/null)"
|
|
100
|
+
fi
|
|
101
|
+
rm -f /tmp/omp-doctor-oracle.$$
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# ── WHERE THE GARDEN ROOTS ARE, under the SAME policy production uses (#87 B1) ──
|
|
105
|
+
# NOT `PI_CODING_AGENT_DIR ?? ~/.pi/agent`. For backend omp that variable is the VENDOR's
|
|
106
|
+
# agent dir — `setProfile` exports it for every named profile (`oh-my-pi` v18.0.0
|
|
107
|
+
# `utils/src/dirs.ts:452-473`) — so reading it here would point this doctor at a different
|
|
108
|
+
# store than the one the extension and the bridge child actually write to. The OMP policy
|
|
109
|
+
# is: the four `ENTWURF_META_*` overrides, else `<HOME>/.pi/agent/<surface>`, never PI.
|
|
110
|
+
#
|
|
111
|
+
# We do not re-implement that policy in bash. We reach it the same way the OMP bridge child
|
|
112
|
+
# does — drop the foreign variable, then let entwurf's own default resolver answer — and we
|
|
113
|
+
# ask the OWNER for the answer: `meta-facts` (#65) is the read-only projection of the
|
|
114
|
+
# CERTIFIED store, so it reports both the store directory it resolved and the records that
|
|
115
|
+
# passed certification. One call, two facts, no second parser.
|
|
116
|
+
#
|
|
117
|
+
# PI presence itself is NOT a fault and is never reported as one: under `omp --profile work`
|
|
118
|
+
# the vendor sets it deliberately. It is simply not a garden-root input.
|
|
119
|
+
# PREFLIGHT THE SAME GRAMMAR THE POLICY ENFORCES, BEFORE ASKING ANYTHING (#87 A2). The
|
|
120
|
+
# overrides are absolute or `~`/`~/…` only. A relative value would resolve against each
|
|
121
|
+
# process's own working directory, and this doctor does NOT share one with the extension —
|
|
122
|
+
# `run_ts` cd's to the repository. Asking `meta-facts` first would therefore answer from an
|
|
123
|
+
# unrelated empty directory and print NOT-YET (or PASS) about a store that is not the one
|
|
124
|
+
# the extension writes to. A refused environment is RUNTIME RED here, never laundered.
|
|
125
|
+
ROOT_POLICY_REFUSAL=""
|
|
126
|
+
for var in ENTWURF_META_SESSIONS_DIR ENTWURF_META_MAILBOX_DIR ENTWURF_META_SENDERS_DIR ENTWURF_META_RECEIVERS_DIR; do
|
|
127
|
+
val="${!var:-}"
|
|
128
|
+
[ -n "$val" ] || continue
|
|
129
|
+
case "$val" in
|
|
130
|
+
/*|"~"|"~/"*) ;;
|
|
131
|
+
*) ROOT_POLICY_REFUSAL="${ROOT_POLICY_REFUSAL:+$ROOT_POLICY_REFUSAL, }$var=$val" ;;
|
|
132
|
+
esac
|
|
133
|
+
done
|
|
134
|
+
|
|
135
|
+
FACTS_RC=0
|
|
136
|
+
FACTS_JSON=""
|
|
137
|
+
STORE=""
|
|
138
|
+
OMP_RECORDS=0
|
|
139
|
+
STORE_DEFECTS=0
|
|
140
|
+
if [ -n "$ROOT_POLICY_REFUSAL" ]; then
|
|
141
|
+
bad "the omp meta-root policy REFUSES this environment: $ROOT_POLICY_REFUSAL — a garden root must be absolute or ~-rooted. The extension receives the same refusal and mints nothing; no store can be named here, so this is RED rather than a NOT-YET verdict about some other directory"
|
|
142
|
+
else
|
|
143
|
+
FACTS_JSON="$(env -u PI_CODING_AGENT_DIR "$REPO/run.sh" meta-facts 2>&1)" || FACTS_RC=$?
|
|
144
|
+
fi
|
|
145
|
+
if [ -z "$ROOT_POLICY_REFUSAL" ] && [ "$FACTS_RC" -eq 0 ]; then
|
|
146
|
+
FACTS_LINE="$(printf '%s' "$FACTS_JSON" | python3 -c '
|
|
147
|
+
import json, sys
|
|
148
|
+
facts = json.load(sys.stdin)
|
|
149
|
+
citizens = facts.get("citizens") or []
|
|
150
|
+
omp = [c for c in citizens if c.get("backend") == "omp"]
|
|
151
|
+
print(facts.get("storeDir", ""), len(omp), len(facts.get("defects") or []))
|
|
152
|
+
' 2>/dev/null)" || FACTS_LINE=""
|
|
153
|
+
if [ -n "$FACTS_LINE" ]; then
|
|
154
|
+
STORE="${FACTS_LINE%% *}"
|
|
155
|
+
REST="${FACTS_LINE#* }"
|
|
156
|
+
OMP_RECORDS="${REST%% *}"
|
|
157
|
+
STORE_DEFECTS="${REST##* }"
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
if [ -n "$ROOT_POLICY_REFUSAL" ]; then
|
|
162
|
+
AGENT=""
|
|
163
|
+
HOOK_LOG=""
|
|
164
|
+
elif [ -z "$STORE" ]; then
|
|
165
|
+
bad "the certified record store could not be read (meta-facts rc=$FACTS_RC) — no honest statement about omp citizens can be made on this host:"
|
|
166
|
+
printf '%s\n' "$FACTS_JSON" | tail -3 | sed 's/^/ /'
|
|
167
|
+
AGENT=""
|
|
168
|
+
HOOK_LOG=""
|
|
169
|
+
else
|
|
170
|
+
AGENT="$(dirname "$STORE")"
|
|
171
|
+
HOOK_LOG="$AGENT/meta-bridge-hook.log"
|
|
172
|
+
ok "omp garden roots resolve to $AGENT (ENTWURF_META_* overrides, else \$HOME/.pi/agent — PI_CODING_AGENT_DIR is vendor-owned here and ignored)"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# ── what the unit DID, read off the shared hook log ──────────────────────────
|
|
176
|
+
# Mint errors and marker errors are judged on SEPARATE axes, and that separation is not
|
|
177
|
+
# stylistic: a failed marker write lands AFTER the successful mint line, so a doctor that
|
|
178
|
+
# folded them together would read "the hook ran and did not mint" about a session whose
|
|
179
|
+
# record is right there (`adding-a-harness.md` step 6, measured on Copilot).
|
|
180
|
+
echo
|
|
181
|
+
echo "[what the extension did — ${HOOK_LOG:-<unresolved>}]"
|
|
182
|
+
if [ -n "$HOOK_LOG" ] && [ -f "$HOOK_LOG" ]; then
|
|
183
|
+
MINT_ERRORS=' ERROR \[omp\] (?!sender-marker-)'
|
|
184
|
+
LAST_ERROR_LINE="$(grep -nP "$MINT_ERRORS" "$HOOK_LOG" 2>/dev/null | tail -1 | cut -d: -f1)"
|
|
185
|
+
LAST_OK_LINE="$(grep -n ' INFO \[omp\] \(create\|attach\) ' "$HOOK_LOG" 2>/dev/null | tail -1 | cut -d: -f1)"
|
|
186
|
+
TOTAL_ERRORS="$(grep -cP "$MINT_ERRORS" "$HOOK_LOG" 2>/dev/null | head -1)"; TOTAL_ERRORS="${TOTAL_ERRORS:-0}"
|
|
187
|
+
if [ -z "$LAST_ERROR_LINE" ]; then
|
|
188
|
+
ok "no omp mint ERROR lines in $HOOK_LOG"
|
|
189
|
+
elif [ -n "$LAST_OK_LINE" ] && [ "$LAST_OK_LINE" -gt "$LAST_ERROR_LINE" ]; then
|
|
190
|
+
note "$TOTAL_ERRORS historical omp mint ERROR line(s), all followed by a successful mint (line $LAST_OK_LINE > $LAST_ERROR_LINE) — recovered, not red"
|
|
191
|
+
else
|
|
192
|
+
bad "the newest omp mint line in $HOOK_LOG is an unrecovered ERROR — the extension RAN and did not mint:"
|
|
193
|
+
grep -P "$MINT_ERRORS" "$HOOK_LOG" | tail -3 | sed 's/^/ /'
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
MARKER_FAILED="$(grep -c ' ERROR \[omp\] sender-marker-failed ' "$HOOK_LOG" 2>/dev/null | head -1)"
|
|
197
|
+
MARKER_REFUSED="$(grep -c ' WARN \[omp\] sender-marker-refused ' "$HOOK_LOG" 2>/dev/null | head -1)"
|
|
198
|
+
MARKER_OK="$(grep -c ' INFO \[omp\] sender marker ' "$HOOK_LOG" 2>/dev/null | head -1)"
|
|
199
|
+
if [ "${MARKER_FAILED:-0}" -gt 0 ]; then
|
|
200
|
+
bad "${MARKER_FAILED} sender-marker WRITE failure(s) — those citizens exist but cannot send under their own garden id:"
|
|
201
|
+
grep ' ERROR \[omp\] sender-marker-failed ' "$HOOK_LOG" | tail -3 | sed 's/^/ /'
|
|
202
|
+
elif [ "${MARKER_REFUSED:-0}" -gt 0 ]; then
|
|
203
|
+
note "${MARKER_REFUSED} sender-marker refusal(s) and ${MARKER_OK:-0} armed — a refusal is fail-closed, not a fault"
|
|
204
|
+
elif [ "${MARKER_OK:-0}" -gt 0 ]; then
|
|
205
|
+
ok "${MARKER_OK} sender marker(s) armed — those citizens send under their own garden id"
|
|
206
|
+
else
|
|
207
|
+
note "no omp sender-marker lines yet (nothing has fired on this host)"
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# The SCOPE FENCE leaves its own receipt, and it is evidence rather than noise: one
|
|
211
|
+
# line per session this unit refused to mint, naming the mode. A host with subagent
|
|
212
|
+
# traffic and zero refusals would mean the fence never ran.
|
|
213
|
+
SCOPE_REFUSED="$(grep -c ' INFO \[omp\] scope-refused ' "$HOOK_LOG" 2>/dev/null | head -1)"
|
|
214
|
+
STATUS_ISSUES="$(grep -c ' WARN \[omp\] status-' "$HOOK_LOG" 2>/dev/null | head -1)"
|
|
215
|
+
note "${SCOPE_REFUSED:-0} non-tui session(s) refused by the §3.5 scope fence — the designed answer for task subagents, rpc/rpc-ui and acp"
|
|
216
|
+
if [ "${STATUS_ISSUES:-0}" -gt 0 ]; then
|
|
217
|
+
note "${STATUS_ISSUES} visible-identity warning(s) — the garden id did not render on the status line for those sessions (statusLine.showHookStatus is default true; check the operator's config.yml)"
|
|
218
|
+
fi
|
|
219
|
+
elif [ -n "$HOOK_LOG" ]; then
|
|
220
|
+
note "no hook log yet at $HOOK_LOG (nothing has fired on this host)"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# ── the records themselves, read through the CERTIFIED surface ───────────────
|
|
224
|
+
# NEVER a text grep (#87 B3). "This host has an omp garden citizen" is a claim on the
|
|
225
|
+
# record-authority axis, and the production writer earns it by certifying the WHOLE active
|
|
226
|
+
# store before writing — regular non-symlink files, live V3 schema, filename↔body
|
|
227
|
+
# agreement, unique nativeSessionId (`meta-session.ts` certifyActiveStore). A grep for the
|
|
228
|
+
# text `"backend": "omp"` matched a file containing nothing else and printed PASS. A doctor
|
|
229
|
+
# that claims admission must not weaken the contract the writer holds, so the count above
|
|
230
|
+
# comes from `meta-facts`' certified citizen list.
|
|
231
|
+
if [ -n "$STORE" ]; then
|
|
232
|
+
if [ "${STORE_DEFECTS:-0}" -gt 0 ]; then
|
|
233
|
+
note "$STORE_DEFECTS uncertifiable entr(ies) in $STORE are excluded from this count — run ./run.sh doctor-meta-bridge, which owns that axis"
|
|
234
|
+
fi
|
|
235
|
+
if [ "${OMP_RECORDS:-0}" -gt 0 ]; then
|
|
236
|
+
ok "$OMP_RECORDS CERTIFIED omp meta-record(s) in $STORE (garden citizen proven on this host)"
|
|
237
|
+
else
|
|
238
|
+
note "NOT-YET: zero certified omp meta-records. An omp TUI session is born when it OPENS (session_start fires after first paint, before the first prompt) — open one and re-run this doctor"
|
|
239
|
+
fi
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# ── §6 identity-carrier contamination, DETECTED (never silently preferred) ───
|
|
243
|
+
# `adding-a-harness.md` step 6 and `docs/external-mcp-host.md`: a complete
|
|
244
|
+
# PI_SESSION_ID + PI_AGENT_ID pair WINS over a native sender marker in the bridge's
|
|
245
|
+
# authoritative-self resolution, so an omp session started from a pi citizen's bash — and
|
|
246
|
+
# every internal agent borrowing its MCP manager — would speak under the parent pi garden
|
|
247
|
+
# id. Bundle C's managed fresh launch has no per-window unset form in tmux, so it writes
|
|
248
|
+
# both names EMPTY instead. That is an honest scrub: authoritative readers trim and require
|
|
249
|
+
# truthy values, so an empty or whitespace-only value is nonauthoritative just like absence.
|
|
250
|
+
# This doctor detects only an actual nonblank carrier and reports it on its own axis rather
|
|
251
|
+
# than absorbing it into any other verdict.
|
|
252
|
+
echo
|
|
253
|
+
echo "[inherited pi identity carriers on live omp processes]"
|
|
254
|
+
if [ -d /proc ]; then
|
|
255
|
+
CONTAMINATED=""
|
|
256
|
+
# Candidate set. Production scans every `omp` pid. `ENTWURF_OMP_CARRIER_PIDS` (set, even
|
|
257
|
+
# to empty) narrows it so a hermetic smoke can hand this branch REAL processes it launched
|
|
258
|
+
# and read their real `/proc/<pid>/environ`. It narrows candidates ONLY — the nonblank
|
|
259
|
+
# predicate below is the same production code either way.
|
|
260
|
+
if [ -n "${ENTWURF_OMP_CARRIER_PIDS+x}" ]; then
|
|
261
|
+
CARRIER_CANDIDATES="$ENTWURF_OMP_CARRIER_PIDS"
|
|
262
|
+
else
|
|
263
|
+
CARRIER_CANDIDATES="$(pgrep -x omp 2>/dev/null || true)"
|
|
264
|
+
fi
|
|
265
|
+
for pid in $CARRIER_CANDIDATES; do
|
|
266
|
+
case "$pid" in *[!0-9]*) continue ;; esac
|
|
267
|
+
ENVIRON="/proc/$pid/environ"
|
|
268
|
+
[ -r "$ENVIRON" ] || continue
|
|
269
|
+
if tr '\0' '\n' < "$ENVIRON" 2>/dev/null | grep -qE '^(PI_SESSION_ID|PI_AGENT_ID)=.*[^[:space:]]'; then
|
|
270
|
+
CONTAMINATED="$CONTAMINATED $pid"
|
|
271
|
+
fi
|
|
272
|
+
done
|
|
273
|
+
if [ -n "$CONTAMINATED" ]; then
|
|
274
|
+
bad "live omp process(es)$CONTAMINATED carry a non-empty PI_SESSION_ID/PI_AGENT_ID inherited from a pi citizen's shell. Their MCP children would speak under the PARENT pi garden id, not their own — close them and relaunch omp from a shell without those variables"
|
|
275
|
+
else
|
|
276
|
+
ok "no live omp process carries a non-empty PI_SESSION_ID/PI_AGENT_ID (empty carrier values are the managed tmux scrub and are nonauthoritative; omp mints neither itself — the danger is pure inheritance passthrough, ledger M6)"
|
|
277
|
+
fi
|
|
278
|
+
else
|
|
279
|
+
note "/proc is unavailable, so live omp environments could not be read on this platform"
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
# ── ownership axis ───────────────────────────────────────────────────────────
|
|
283
|
+
echo
|
|
284
|
+
echo "[ownership axis]"
|
|
285
|
+
if [ -L "$STATE_FILE" ]; then
|
|
286
|
+
own_bad "ownership state $STATE_FILE is a SYMLINK — refusing to trust it"
|
|
287
|
+
elif [ -f "$STATE_FILE" ]; then
|
|
288
|
+
if STATE_FACTS="$(omp_state_read "$STATE_FILE" "${UNIT_DIR:-$STATE_FILE}" "$ASM" 2>&1)"; then
|
|
289
|
+
ok "ownership state is well-formed and bound to this installation (version ${STATE_FACTS%% *}, entry ${STATE_FACTS##* })"
|
|
290
|
+
if [ -d "$ASM/$UNIT" ]; then
|
|
291
|
+
ok "the recorded assembly is present: $ASM/$UNIT"
|
|
292
|
+
else
|
|
293
|
+
own_bad "ownership state names an assembly that is gone: $ASM/$UNIT — the inverse can no longer prove what it placed. Re-run ./run.sh install-omp-bridge"
|
|
294
|
+
fi
|
|
295
|
+
else
|
|
296
|
+
own_bad "ownership state is corrupt or names a different installation: $STATE_FACTS"
|
|
297
|
+
fi
|
|
298
|
+
else
|
|
299
|
+
if [ -n "$UNIT_DIR" ] && [ -d "$UNIT_DIR" ]; then
|
|
300
|
+
own_bad "a unit is installed at $UNIT_DIR but entwurf holds NO ownership state for it — neither the installer nor the inverse will touch it (#87 B2: a shape is not a proof of ownership). Inspect it; if it is a stale copy of ours, remove it by hand and re-run ./run.sh install-omp-bridge"
|
|
301
|
+
else
|
|
302
|
+
note "no ownership state and no installed unit — zero state, which is a SKIP rather than a fault"
|
|
303
|
+
fi
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
echo
|
|
307
|
+
if [ "$fail" -ne 0 ] || [ "$own_fail" -ne 0 ]; then
|
|
308
|
+
[ "$fail" -ne 0 ] && echo "[omp-bridge-doctor] runtime axis: FAIL" || echo "[omp-bridge-doctor] runtime axis: PASS"
|
|
309
|
+
[ "$own_fail" -ne 0 ] && echo "[omp-bridge-doctor] ownership axis: FAIL" || echo "[omp-bridge-doctor] ownership axis: PASS"
|
|
310
|
+
echo "[omp-bridge-doctor] FAIL"
|
|
311
|
+
exit 1
|
|
312
|
+
fi
|
|
313
|
+
echo "[omp-bridge-doctor] runtime axis: PASS"
|
|
314
|
+
echo "[omp-bridge-doctor] ownership axis: PASS"
|
|
315
|
+
echo "[omp-bridge-doctor] PASS"
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# omp-bridge-install.sh — operator-grade global installer for the OMP BIRTH extension
|
|
3
|
+
# (#87). The omp sibling of copilot-bridge-install.sh, deliberately NOT a mode of it.
|
|
4
|
+
#
|
|
5
|
+
# WHAT THIS INSTALLS. ONE extension directory whose managed job is to mint the visible
|
|
6
|
+
# omp TUI host as a garden citizen, name it as this host's sender, and show its garden id
|
|
7
|
+
# on omp's own status line. The MCP hand is a separate surface (`install-omp-mcp`), and
|
|
8
|
+
# receive does not exist yet — three facts, three installers, three failure modes.
|
|
9
|
+
#
|
|
10
|
+
# WHY THERE IS NO LAUNCHER AND NO MANIFEST BAKE. `[source]` an omp "hook" is an
|
|
11
|
+
# in-process EXTENSION: `--hook` aliases `--extension`, a module default-exports
|
|
12
|
+
# `(pi) => void`, and there is no spawned hook command and no stdin envelope (oh-my-pi
|
|
13
|
+
# v18.0.0, `docs/hooks.md`; `extensibility/extensions/types.ts:1592`). The Claude and
|
|
14
|
+
# Copilot installers bake a node path and an entry path into a launcher because their
|
|
15
|
+
# payload is a separate process; here the vendor imports the module itself, so the whole
|
|
16
|
+
# install is: assemble a self-contained unit, then place it where omp discovers it.
|
|
17
|
+
#
|
|
18
|
+
# WHERE IT PLACES IT, AND WHY THERE. `<agent-dir>/extensions/<unit>/index.{ts,js}` is one
|
|
19
|
+
# of omp's three native discovery rules (`discovery/builtin.ts:483` →
|
|
20
|
+
# `discovery/helpers.ts:625-712`), and the only one that needs nothing rewritten per
|
|
21
|
+
# install shape. The agent dir is resolved by the shared oracle, which REFUSES rather
|
|
22
|
+
# than guesses when an inherited `PI_*` knob makes it ambiguous (ledger M6).
|
|
23
|
+
#
|
|
24
|
+
# Platform: Linux only, same fence as the Claude and Copilot installers.
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
REPO="$(cd "$HERE/.." && pwd)"
|
|
29
|
+
UNIT="entwurf-meta-omp"
|
|
30
|
+
SRC="$REPO/pi/meta-bridge-omp"
|
|
31
|
+
# `ENTWURF_OMP_ASM` exists for the gate: check-omp-birth-hook drives this script for real,
|
|
32
|
+
# into a temp dir, with --assemble-only. A gate that re-implemented the assembly would be
|
|
33
|
+
# testing its own copy of the logic instead of the shipped one.
|
|
34
|
+
ASM="${ENTWURF_OMP_ASM:-${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/meta-bridge-omp/.assembled}"
|
|
35
|
+
STATE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/omp-bridge"
|
|
36
|
+
STATE_FILE="$STATE_DIR/install-state.json"
|
|
37
|
+
ASSEMBLE_ONLY=0
|
|
38
|
+
|
|
39
|
+
die() { echo "[omp-bridge-install] $*" >&2; exit 1; }
|
|
40
|
+
|
|
41
|
+
for arg in "$@"; do
|
|
42
|
+
case "$arg" in
|
|
43
|
+
--assemble-only) ASSEMBLE_ONLY=1 ;;
|
|
44
|
+
*) die "unknown argument: $arg (--assemble-only)" ;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
[ "$(uname -s)" = "Linux" ] || die "Linux only; $(uname -s) is not a certified axis for this install."
|
|
49
|
+
command -v python3 >/dev/null 2>&1 || die "python3 is required and is not on PATH."
|
|
50
|
+
|
|
51
|
+
# shellcheck source=scripts/omp-bridge-oracle.sh
|
|
52
|
+
. "$HERE/omp-bridge-oracle.sh"
|
|
53
|
+
|
|
54
|
+
if [ "$ASSEMBLE_ONLY" -eq 1 ]; then
|
|
55
|
+
# A GATE-ONLY path that writes no ownership state, so it must never touch the LIVE
|
|
56
|
+
# assembly: without a state, a rebuilt live assembly would drift from what the state
|
|
57
|
+
# records. Same refusal the Copilot installer carries, for the same reason.
|
|
58
|
+
DEFAULT_ASM="${XDG_DATA_HOME:-$HOME/.local/share}/entwurf/meta-bridge-omp/.assembled"
|
|
59
|
+
[ -n "${ENTWURF_OMP_ASM:-}" ] \
|
|
60
|
+
|| die "--assemble-only requires an explicit ENTWURF_OMP_ASM (a temp dir): this gate-only path writes no ownership state and must not rebuild the live assembly."
|
|
61
|
+
if [ "$(python3 -c 'import os,sys;print(os.path.abspath(sys.argv[1]))' "$ENTWURF_OMP_ASM")" = \
|
|
62
|
+
"$(python3 -c 'import os,sys;print(os.path.abspath(sys.argv[1]))' "$DEFAULT_ASM")" ]; then
|
|
63
|
+
die "--assemble-only refuses the DEFAULT live assembly path ($DEFAULT_ASM): pass a temp ENTWURF_OMP_ASM instead."
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
AGENT_DIR=""
|
|
68
|
+
UNIT_DIR=""
|
|
69
|
+
if [ "$ASSEMBLE_ONLY" -eq 0 ]; then
|
|
70
|
+
command -v omp >/dev/null 2>&1 || die "the 'omp' CLI is not on PATH. entwurf never installs a harness — install omp first, then re-run."
|
|
71
|
+
AGENT_DIR="$(omp_agent_dir)" || die "refusing: the omp agent directory this host reads is ambiguous (see above)."
|
|
72
|
+
UNIT_DIR="$AGENT_DIR/extensions/$UNIT"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
UNIT_VERSION="$(python3 -c "import json,sys;print(json.load(open(sys.argv[1]))['version'])" "$SRC/$UNIT/package.json")" \
|
|
76
|
+
|| die "could not read the shipped unit version from $SRC/$UNIT/package.json"
|
|
77
|
+
case "$UNIT_VERSION" in
|
|
78
|
+
""|*[![:graph:]]*) die "shipped unit version ${UNIT_VERSION:-<empty>} is empty or carries whitespace/control characters — it travels through space-separated fact transports." ;;
|
|
79
|
+
esac
|
|
80
|
+
|
|
81
|
+
# Same install-shape split as the Claude and Copilot installers: an installed package
|
|
82
|
+
# lives below node_modules where Node refuses strip-types on `.ts`, so it ships the
|
|
83
|
+
# tsc-emitted closure; a dev clone ships the `.ts` source. omp itself reads both (its
|
|
84
|
+
# discovery prefers index.ts and falls back to index.js, `discovery/helpers.ts:700-710`).
|
|
85
|
+
case "$REPO" in
|
|
86
|
+
*/node_modules/@junghanacs/entwurf)
|
|
87
|
+
ENTRY_NAME="index.js"
|
|
88
|
+
ENTRY_SRC="$REPO/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js"
|
|
89
|
+
LIB_SRC="$REPO/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
|
|
90
|
+
LIB_EXT="js" ;;
|
|
91
|
+
*)
|
|
92
|
+
ENTRY_NAME="index.ts"
|
|
93
|
+
ENTRY_SRC="$REPO/pi-extensions/meta-bridge-omp.ts"
|
|
94
|
+
LIB_SRC="$REPO/pi-extensions/lib/meta-session.ts"
|
|
95
|
+
LIB_EXT="ts" ;;
|
|
96
|
+
esac
|
|
97
|
+
|
|
98
|
+
[ -f "$ENTRY_SRC" ] || die "birth entry artifact missing: $ENTRY_SRC (run 'pnpm run build-bridge' in a dev clone, or reinstall the package)."
|
|
99
|
+
[ -f "$LIB_SRC" ] || die "entry lib artifact missing: $LIB_SRC (same build-bridge dist closure)."
|
|
100
|
+
|
|
101
|
+
write_state() { # $1 = ownedUnitDir (true|false)
|
|
102
|
+
mkdir -p "$STATE_DIR"
|
|
103
|
+
STATE_FILE_ENV="$STATE_FILE" UNIT_DIR_ENV="$UNIT_DIR" ASM_ENV="$ASM" \
|
|
104
|
+
UNIT_VERSION_ENV="$UNIT_VERSION" ENTRY_NAME_ENV="$ENTRY_NAME" OWNED_ENV="$1" python3 - <<'PY' \
|
|
105
|
+
|| die "could not write the ownership state at $STATE_FILE"
|
|
106
|
+
import datetime, json, os, tempfile
|
|
107
|
+
state_file = os.environ["STATE_FILE_ENV"]
|
|
108
|
+
state = {
|
|
109
|
+
"schemaVersion": 1,
|
|
110
|
+
"unitDir": os.path.abspath(os.environ["UNIT_DIR_ENV"]),
|
|
111
|
+
"assemblyPath": os.path.abspath(os.environ["ASM_ENV"]),
|
|
112
|
+
"unitVersion": os.environ["UNIT_VERSION_ENV"],
|
|
113
|
+
"entryName": os.environ["ENTRY_NAME_ENV"],
|
|
114
|
+
"ownedUnitDir": os.environ["OWNED_ENV"] == "true",
|
|
115
|
+
"installedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
116
|
+
}
|
|
117
|
+
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(state_file), prefix=".install-state.")
|
|
118
|
+
with os.fdopen(fd, "w") as fh:
|
|
119
|
+
json.dump(state, fh, indent=2)
|
|
120
|
+
fh.write("\n")
|
|
121
|
+
os.replace(tmp, state_file)
|
|
122
|
+
PY
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# ── 0. ownership preflight (READ-ONLY) ───────────────────────────────────────
|
|
126
|
+
# Every destructive step below is licensed HERE, and the only licence is ownership STATE.
|
|
127
|
+
# Anything that already exists at our path and cannot be proven ours refuses with zero
|
|
128
|
+
# writes — a structural shape is not a proof of ownership (#87 B2). A SYMLINK at the unit
|
|
129
|
+
# path is always a refusal too: writing through it would mutate somebody else's tree.
|
|
130
|
+
if [ "$ASSEMBLE_ONLY" -eq 0 ]; then
|
|
131
|
+
if [ -L "$STATE_FILE" ]; then
|
|
132
|
+
die "ownership state $STATE_FILE is a symlink — refusing to trust or overwrite it."
|
|
133
|
+
fi
|
|
134
|
+
if [ -L "$UNIT_DIR" ]; then
|
|
135
|
+
die "$UNIT_DIR is a SYMLINK. entwurf never writes through a link into a tree it does not own — remove it by hand if it is stale, then re-run."
|
|
136
|
+
fi
|
|
137
|
+
if [ -f "$STATE_FILE" ]; then
|
|
138
|
+
omp_state_read "$STATE_FILE" "$UNIT_DIR" "$ASM" >/dev/null \
|
|
139
|
+
|| die "ownership state $STATE_FILE is corrupt or names a different installation (see above) — refusing with zero writes."
|
|
140
|
+
echo "[omp-bridge-install] ownership state matches this installation (reinstall/repair)"
|
|
141
|
+
elif [ -e "$UNIT_DIR" ]; then
|
|
142
|
+
# NO SHAPE-BASED ADOPTION (#87 B2). A structural oracle answers "is this a complete
|
|
143
|
+
# unit", never "is this OURS": it compares no bytes, rejects no extra files and
|
|
144
|
+
# establishes no provenance. Adopting on that answer meant moving a stranger's
|
|
145
|
+
# directory aside, publishing over it and DELETING the preimage — and the inverse
|
|
146
|
+
# then `rm -rf`s the path on the same unproven claim. A foreign or hand-made
|
|
147
|
+
# extension with these filenames, or our own unit plus operator files beside it, was
|
|
148
|
+
# destroyed unrecoverably. Ownership state is the only proof of ownership, so a
|
|
149
|
+
# no-state path refuses with zero writes.
|
|
150
|
+
die "$UNIT_DIR already exists and entwurf holds NO ownership state for it. A directory that merely LOOKS like our unit is not proof that it is ours — adopting it would overwrite it and delete the preimage with no inverse. Refusing with zero writes: inspect it, and if it is stale remove it by hand, then re-run."
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# ── 1. assemble a self-contained unit ────────────────────────────────────────
|
|
155
|
+
# ATOMIC: assemble beside the live artifact and swap only once it is complete, so a
|
|
156
|
+
# failure never leaves a half-written extension for omp to import on the next launch.
|
|
157
|
+
STAGE="$ASM.staging.$$"
|
|
158
|
+
rm -rf "$STAGE"
|
|
159
|
+
trap 'rm -rf "$STAGE"' EXIT
|
|
160
|
+
mkdir -p "$STAGE/$UNIT/lib"
|
|
161
|
+
cp "$SRC/$UNIT/package.json" "$STAGE/$UNIT/package.json"
|
|
162
|
+
cp "$ENTRY_SRC" "$STAGE/$UNIT/$ENTRY_NAME"
|
|
163
|
+
cp "$LIB_SRC" "$STAGE/$UNIT/lib/meta-session.$LIB_EXT"
|
|
164
|
+
cp "$REPO/pi-extensions/lib/session-id.js" "$STAGE/$UNIT/lib/session-id.js"
|
|
165
|
+
# The capability registry must travel at the unit ROOT: metaCapabilitiesFilePath()
|
|
166
|
+
# resolves it via `../` from lib/ in the bundle layout. Without it every mint throws.
|
|
167
|
+
cp "$REPO/pi/entwurf-capabilities.json" "$STAGE/$UNIT/entwurf-capabilities.json"
|
|
168
|
+
|
|
169
|
+
omp_assembly_valid "$STAGE" "$UNIT" || die "the freshly staged assembly fails the structural oracle (see above) — refusing to publish it."
|
|
170
|
+
|
|
171
|
+
PREV=""
|
|
172
|
+
if [ -d "$ASM" ]; then
|
|
173
|
+
PREV="$ASM.previous.$$"
|
|
174
|
+
mv "$ASM" "$PREV" || die "could not move the previous assembly aside ($ASM)."
|
|
175
|
+
fi
|
|
176
|
+
mkdir -p "$(dirname "$ASM")"
|
|
177
|
+
if ! mv "$STAGE" "$ASM"; then
|
|
178
|
+
[ -n "$PREV" ] && mv "$PREV" "$ASM"
|
|
179
|
+
die "could not publish the staged assembly into $ASM (previous assembly restored)."
|
|
180
|
+
fi
|
|
181
|
+
trap - EXIT
|
|
182
|
+
[ -n "$PREV" ] && rm -rf "$PREV"
|
|
183
|
+
|
|
184
|
+
echo "[omp-bridge-install] assembled $ASM (birth extension only; the MCP hand is install-omp-mcp)"
|
|
185
|
+
|
|
186
|
+
if [ "$ASSEMBLE_ONLY" -eq 1 ]; then
|
|
187
|
+
echo "[omp-bridge-install] --assemble-only: stopping before the omp agent directory is touched"
|
|
188
|
+
exit 0
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
# ── 2. place it where omp discovers it ───────────────────────────────────────
|
|
192
|
+
# Ownership state BEFORE the vendor-directory mutation: once the complete assembly is
|
|
193
|
+
# published, the state exists so a partial failure below still leaves an inverse
|
|
194
|
+
# authority behind. `ownedUnitDir` flips false→true around the placement.
|
|
195
|
+
write_state false
|
|
196
|
+
echo "[omp-bridge-install] ownership state written: $STATE_FILE"
|
|
197
|
+
|
|
198
|
+
mkdir -p "$(dirname "$UNIT_DIR")"
|
|
199
|
+
PLACE_STAGE="$UNIT_DIR.staging.$$"
|
|
200
|
+
rm -rf "$PLACE_STAGE"
|
|
201
|
+
cp -r "$ASM/$UNIT" "$PLACE_STAGE" || die "could not stage the unit into $(dirname "$UNIT_DIR")."
|
|
202
|
+
PREV_UNIT=""
|
|
203
|
+
if [ -d "$UNIT_DIR" ]; then
|
|
204
|
+
PREV_UNIT="$UNIT_DIR.previous.$$"
|
|
205
|
+
mv "$UNIT_DIR" "$PREV_UNIT" || { rm -rf "$PLACE_STAGE"; die "could not move the previous unit aside ($UNIT_DIR)."; }
|
|
206
|
+
fi
|
|
207
|
+
if ! mv "$PLACE_STAGE" "$UNIT_DIR"; then
|
|
208
|
+
[ -n "$PREV_UNIT" ] && mv "$PREV_UNIT" "$UNIT_DIR"
|
|
209
|
+
rm -rf "$PLACE_STAGE"
|
|
210
|
+
die "could not publish the unit into $UNIT_DIR (previous unit restored)."
|
|
211
|
+
fi
|
|
212
|
+
[ -n "$PREV_UNIT" ] && rm -rf "$PREV_UNIT"
|
|
213
|
+
write_state true
|
|
214
|
+
|
|
215
|
+
omp_assembly_valid "$(dirname "$UNIT_DIR")" "$UNIT" || die "the placed unit fails the structural oracle (see above)."
|
|
216
|
+
|
|
217
|
+
echo "[omp-bridge-install] installed $UNIT_DIR (entry $ENTRY_NAME, version $UNIT_VERSION)"
|
|
218
|
+
echo "[omp-bridge-install] DONE. An omp TUI session becomes a citizen when it OPENS —"
|
|
219
|
+
echo "the birth edge is the extension's session_start, which fires after first paint and"
|
|
220
|
+
echo "before the first prompt; a task subagent of that session is refused by design."
|
|
221
|
+
echo "Verify with: ./run.sh doctor-omp-bridge · inverse: ./run.sh uninstall-omp-bridge"
|