@junghanacs/entwurf 0.15.0 → 0.16.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 +1 -0
- package/CHANGELOG.md +299 -0
- package/DELIVERY.md +4 -3
- package/README.md +99 -67
- package/VERIFY.md +13 -7
- package/docs/acp-backend-rail.md +1 -1
- package/docs/external-mcp-host.md +147 -9
- package/docs/setup-clean-host.md +221 -16
- 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/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 +14 -4
- 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/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 +1041 -174
- package/scripts/agy-bridge.sh +4 -2
- package/scripts/check-copilot-birth-hook.ts +700 -23
- package/scripts/check-entwurf-self-address.ts +31 -0
- package/scripts/check-fresh-cut-gate.sh +12 -4
- package/scripts/check-gate-qualification.ts +8 -2
- 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 +181 -0
- package/scripts/check-omp-receive-arm.ts +516 -0
- package/scripts/check-setup-qualification.sh +126 -0
- package/scripts/copilot-bridge-doctor.sh +104 -71
- package/scripts/copilot-bridge-install.sh +180 -20
- package/scripts/copilot-bridge-oracle.sh +223 -0
- package/scripts/copilot-bridge-uninstall.sh +137 -0
- package/scripts/dev-bin.sh +50 -9
- package/scripts/fake-copilot-vendor.sh +67 -0
- package/scripts/inventory-verification-surface.ts +1 -1
- package/scripts/meta-bridge-hook-log.sh +9 -1
- package/scripts/mutants/copilot-birth.json +96 -5
- 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/pi-package-ownership.json +92 -0
- package/scripts/mutants/self-address.json +34 -0
- package/scripts/mutants/setup-verdict.json +148 -0
- package/scripts/mutants/source-install.json +32 -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-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 +349 -0
- package/scripts/pi_settings_io.py +22 -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/register-pi-package.py +525 -7
- package/scripts/register-pi-provider.py +162 -12
- package/scripts/smoke-agy-install-state.sh +86 -30
- package/scripts/smoke-agy-statusline-state.sh +9 -6
- 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 +291 -0
- package/scripts/smoke-user-scope-citizen.sh +388 -1
- package/scripts/tsconfig.json +2 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"lane": "setup-verdict",
|
|
4
|
+
"mutants": [
|
|
5
|
+
{
|
|
6
|
+
"claim": "SETUP-FALSE-GREEN",
|
|
7
|
+
"title": "aggregate setup prints its computed NON-GREEN summary but exits 0 again, resurrecting the retired whole-host false success in the only surface an operator reads",
|
|
8
|
+
"subject": "run.sh",
|
|
9
|
+
"find": [
|
|
10
|
+
" echo \"DONE: entwurf setup — result: NON-GREEN (FAIL:$s_fails). Valid components above remain installed; repair the named ones and re-run setup.\"\n return 1"
|
|
11
|
+
],
|
|
12
|
+
"replace": [
|
|
13
|
+
" echo \"DONE: entwurf setup — result: NON-GREEN (FAIL:$s_fails). Valid components above remain installed; repair the named ones and re-run setup.\"\n return 0"
|
|
14
|
+
],
|
|
15
|
+
"gate": ["bash", "scripts/check-setup-qualification.sh"],
|
|
16
|
+
"timeoutSeconds": 120,
|
|
17
|
+
"signature": "[QK:SETUP-FALSE-GREEN]",
|
|
18
|
+
"signatureSource": "scripts/check-setup-qualification.sh"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"claim": "SETUP-PI-FLOOR-SKIP",
|
|
22
|
+
"title": "a resolvable pi below the supported floor is relabeled zero-state SKIP, so a stale-harness host reads green instead of being told its detected integration is incomplete",
|
|
23
|
+
"subject": "run.sh",
|
|
24
|
+
"find": [
|
|
25
|
+
" setup_result pi FAIL \"detected pi ${pi_ver:-<unreadable version>} is outside the supported range $pi_range — Pi wiring not written; align pi, then re-run setup\""
|
|
26
|
+
],
|
|
27
|
+
"replace": [
|
|
28
|
+
" setup_result pi SKIP \"detected pi ${pi_ver:-<unreadable version>} is outside the supported range $pi_range — Pi wiring not written; align pi, then re-run setup\""
|
|
29
|
+
],
|
|
30
|
+
"gate": ["bash", "scripts/check-setup-qualification.sh"],
|
|
31
|
+
"timeoutSeconds": 120,
|
|
32
|
+
"signature": "[QK:SETUP-PI-FLOOR-SKIP]",
|
|
33
|
+
"signatureSource": "scripts/check-setup-qualification.sh"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"claim": "SETUP-INSTALLED-NO-BOOTSTRAP",
|
|
37
|
+
"title": "installed-package setup re-enters the source pnpm bootstrap inside node_modules, so the mode-first contract collapses and a neutral npm consumer's setup dies or mutates the installed tree",
|
|
38
|
+
"subject": "run.sh",
|
|
39
|
+
"find": [
|
|
40
|
+
" if [ \"$mode\" = \"source\" ]; then\n # Source bootstrap is the ONLY install-mode difference (#86): the pinned"
|
|
41
|
+
],
|
|
42
|
+
"replace": [
|
|
43
|
+
" if [ -n \"$mode\" ]; then\n # Source bootstrap is the ONLY install-mode difference (#86): the pinned"
|
|
44
|
+
],
|
|
45
|
+
"gate": ["bash", "scripts/check-setup-qualification.sh"],
|
|
46
|
+
"timeoutSeconds": 120,
|
|
47
|
+
"signature": "[QK:SETUP-INSTALLED-NO-BOOTSTRAP]",
|
|
48
|
+
"signatureSource": "scripts/check-setup-qualification.sh"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"claim": "SETUP-CREDENTIAL-FREE",
|
|
52
|
+
"title": "a credential-store write (auth.json backup) resurrects inside aggregate setup, re-violating the corrected installation rule that setup never reads, copies, aliases or backs up harness credentials",
|
|
53
|
+
"subject": "run.sh",
|
|
54
|
+
"find": [" SETUP_RESULTS=()\n\n if [ \"$mode\" = \"source\" ]; then"],
|
|
55
|
+
"replace": [
|
|
56
|
+
" SETUP_RESULTS=()\n local _auth=\"${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/auth.json\"\n if [ -f \"$_auth\" ]; then cp \"$_auth\" \"$_auth.bak\"; fi\n\n if [ \"$mode\" = \"source\" ]; then"
|
|
57
|
+
],
|
|
58
|
+
"gate": ["bash", "scripts/check-setup-qualification.sh"],
|
|
59
|
+
"timeoutSeconds": 120,
|
|
60
|
+
"signature": "[QK:SETUP-CREDENTIAL-FREE]",
|
|
61
|
+
"signatureSource": "scripts/check-setup-qualification.sh"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"claim": "DEV-BIN-ATTEMPTS-ALL",
|
|
65
|
+
"title": "the no-arg dev-bin expose loop aborts at the first foreign refusal again, so units after it are never attempted while the caller's summary implies they ran",
|
|
66
|
+
"subject": "scripts/dev-bin.sh",
|
|
67
|
+
"find": [
|
|
68
|
+
" rc=0; expose_one \"$b\" || rc=$?\n if [ \"$rc\" -eq 0 ]; then exposed=$((exposed + 1)); else refused=\"$refused $b\"; fi"
|
|
69
|
+
],
|
|
70
|
+
"replace": [
|
|
71
|
+
" rc=0; expose_one \"$b\" || rc=$?\n if [ \"$rc\" -eq 0 ]; then exposed=$((exposed + 1)); else warn \"[dev-bin] REFUSE aborts the loop\"; exit 3; fi"
|
|
72
|
+
],
|
|
73
|
+
"gate": ["bash", "run.sh", "smoke-agy-install-state"],
|
|
74
|
+
"timeoutSeconds": 600,
|
|
75
|
+
"signature": "[QK:DEV-BIN-ATTEMPTS-ALL]",
|
|
76
|
+
"signatureSource": "scripts/smoke-agy-install-state.sh"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"claim": "SETUP-INSTALLED-NO-PNPM",
|
|
80
|
+
"title": "installed setup regains an unconditional pnpm requirement, so a neutral npm consumer without pnpm is refused by an accidental Missing-command instead of composing — the mode decision stops being independent of the source toolchain",
|
|
81
|
+
"subject": "run.sh",
|
|
82
|
+
"find": [
|
|
83
|
+
" if [ \"$mode\" = \"source\" ]; then\n # Source-only dev fixtures: the pinned repo dependencies are build/test\n # fixtures, never a product promise that consumers receive them (#86 A4).\n require_cmd pnpm\n fi"
|
|
84
|
+
],
|
|
85
|
+
"replace": [" require_cmd pnpm"],
|
|
86
|
+
"gate": ["bash", "scripts/check-setup-qualification.sh"],
|
|
87
|
+
"timeoutSeconds": 120,
|
|
88
|
+
"signature": "[QK:SETUP-INSTALLED-NO-PNPM]",
|
|
89
|
+
"signatureSource": "scripts/check-setup-qualification.sh"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"claim": "SETUP-COPILOT-ABSENT-SKIP",
|
|
93
|
+
"title": "the copilot presence gate collapses to always-detected, so an absent-copilot host loses its zero-state SKIP and setup composes (and writes) Copilot units nobody asked for",
|
|
94
|
+
"subject": "run.sh",
|
|
95
|
+
"find": [
|
|
96
|
+
" if ! command -v \"${COPILOT_BIN:-copilot}\" >/dev/null 2>&1; then\n setup_result copilot SKIP \"copilot not on PATH — zero Copilot wiring written\""
|
|
97
|
+
],
|
|
98
|
+
"replace": [
|
|
99
|
+
" if false; then\n setup_result copilot SKIP \"copilot not on PATH — zero Copilot wiring written\""
|
|
100
|
+
],
|
|
101
|
+
"gate": ["bash", "scripts/check-setup-qualification.sh"],
|
|
102
|
+
"timeoutSeconds": 120,
|
|
103
|
+
"signature": "[QK:SETUP-COPILOT-ABSENT-SKIP]",
|
|
104
|
+
"signatureSource": "scripts/check-setup-qualification.sh"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"claim": "SETUP-COPILOT-INDEPENDENT",
|
|
108
|
+
"title": "a failed Copilot birth aborts the whole composition, so the MCP/receiver/footer units are silently never attempted while the operator reads only the first failure",
|
|
109
|
+
"subject": "run.sh",
|
|
110
|
+
"find": [
|
|
111
|
+
" setup_result copilot-birth FAIL \"detected copilot, but the birth plugin install did not complete (see above) — repair, then re-run setup\"\n fi"
|
|
112
|
+
],
|
|
113
|
+
"replace": [
|
|
114
|
+
" setup_result copilot-birth FAIL \"detected copilot, but the birth plugin install did not complete (see above) — repair, then re-run setup\"\n return 1\n fi"
|
|
115
|
+
],
|
|
116
|
+
"gate": ["bash", "scripts/check-setup-qualification.sh"],
|
|
117
|
+
"timeoutSeconds": 120,
|
|
118
|
+
"signature": "[QK:SETUP-COPILOT-INDEPENDENT]",
|
|
119
|
+
"signatureSource": "scripts/check-setup-qualification.sh"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"claim": "SETUP-COPILOT-COSMETIC-PASS",
|
|
123
|
+
"title": "the birth unit's failure capture is swallowed, so a refused vendor preflight reads as copilot-birth PASS and a broken detected integration reports a green composition",
|
|
124
|
+
"subject": "run.sh",
|
|
125
|
+
"find": [" copilot_rc=0; (cd \"$REPO_DIR\" && bash scripts/copilot-bridge-install.sh) || copilot_rc=$?"],
|
|
126
|
+
"replace": [" copilot_rc=0; (cd \"$REPO_DIR\" && bash scripts/copilot-bridge-install.sh) || true"],
|
|
127
|
+
"gate": ["bash", "scripts/check-setup-qualification.sh"],
|
|
128
|
+
"timeoutSeconds": 120,
|
|
129
|
+
"signature": "[QK:SETUP-COPILOT-COSMETIC-PASS]",
|
|
130
|
+
"signatureSource": "scripts/check-setup-qualification.sh"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"claim": "SETUP-AGY-COSMETIC-RETURN",
|
|
134
|
+
"title": "wire_agy_bridge's failed-install path returns 0 again, restoring the WARN-then-success mechanism that let a detected-but-refused agy integration read as a green setup",
|
|
135
|
+
"subject": "run.sh",
|
|
136
|
+
"find": [
|
|
137
|
+
" echo \"[setup] Bridge NOT wired; verify with ./run.sh doctor-agy-bridge. setup continues.\" >&2\n ;;\n esac\n return 1\n}"
|
|
138
|
+
],
|
|
139
|
+
"replace": [
|
|
140
|
+
" echo \"[setup] Bridge NOT wired; verify with ./run.sh doctor-agy-bridge. setup continues.\" >&2\n ;;\n esac\n return 0\n}"
|
|
141
|
+
],
|
|
142
|
+
"gate": ["bash", "run.sh", "smoke-agy-install-state"],
|
|
143
|
+
"timeoutSeconds": 600,
|
|
144
|
+
"signature": "[QK:SETUP-AGY-COSMETIC-RETURN]",
|
|
145
|
+
"signatureSource": "scripts/smoke-agy-install-state.sh"
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"lane": "source-install",
|
|
4
|
+
"mutants": [
|
|
5
|
+
{
|
|
6
|
+
"claim": "SOURCE-OPERATOR-BIN",
|
|
7
|
+
"title": "source setup again omits the entwurf operator command, so Copilot fresh rejects runtime-unresolved unless an unrelated global package happens to mask the defect",
|
|
8
|
+
"subject": "scripts/dev-bin.sh",
|
|
9
|
+
"find": [
|
|
10
|
+
"MANAGED_BINS=\"entwurf entwurf-bridge entwurf-agy-statusline entwurf-agy-imprint entwurf-copilot-statusline\""
|
|
11
|
+
],
|
|
12
|
+
"replace": [
|
|
13
|
+
"MANAGED_BINS=\"entwurf-bridge entwurf-agy-statusline entwurf-agy-imprint entwurf-copilot-statusline\""
|
|
14
|
+
],
|
|
15
|
+
"gate": ["bash", "run.sh", "smoke-agy-install-state"],
|
|
16
|
+
"timeoutSeconds": 300,
|
|
17
|
+
"signature": "[QK:SOURCE-OPERATOR-BIN]",
|
|
18
|
+
"signatureSource": "scripts/smoke-agy-install-state.sh"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"claim": "SOURCE-OPERATOR-FAILS-LOUD",
|
|
22
|
+
"title": "source setup reports green after the managed operator command is foreign, shadowed, or outside PATH, recreating the runtime-unresolved incident behind a cosmetic success",
|
|
23
|
+
"subject": "run.sh",
|
|
24
|
+
"find": [" if ! bash \"$REPO_DIR/scripts/dev-bin.sh\" verify entwurf; then\n return 1\n fi"],
|
|
25
|
+
"replace": [" if ! bash \"$REPO_DIR/scripts/dev-bin.sh\" verify entwurf; then\n return 0\n fi"],
|
|
26
|
+
"gate": ["bash", "run.sh", "smoke-agy-install-state"],
|
|
27
|
+
"timeoutSeconds": 300,
|
|
28
|
+
"signature": "[QK:SOURCE-OPERATOR-FAILS-LOUD]",
|
|
29
|
+
"signatureSource": "scripts/smoke-agy-install-state.sh"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -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"
|