@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.
Files changed (90) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +299 -0
  3. package/DELIVERY.md +4 -3
  4. package/README.md +99 -67
  5. package/VERIFY.md +13 -7
  6. package/docs/acp-backend-rail.md +1 -1
  7. package/docs/external-mcp-host.md +147 -9
  8. package/docs/setup-clean-host.md +221 -16
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +65 -20
  10. package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +17 -6
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +1 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +172 -5
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +152 -17
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +271 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +988 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-receive-omp.js +574 -0
  18. package/mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js +84 -0
  19. package/mcp/entwurf-bridge/src/index.ts +65 -19
  20. package/mcp/entwurf-bridge/tsconfig.build.json +19 -3
  21. package/mcp/tsconfig.json +10 -0
  22. package/package.json +14 -4
  23. package/pi/entwurf-capabilities.json +1 -0
  24. package/pi/meta-bridge-omp/entwurf-meta-omp/package.json +7 -0
  25. package/pi/omp-receive/entwurf-receive-omp/package.json +7 -0
  26. package/pi-extensions/entwurf-control.ts +12 -9
  27. package/pi-extensions/lib/entwurf-self-address.ts +18 -7
  28. package/pi-extensions/lib/meta-sender-identity.ts +1 -1
  29. package/pi-extensions/lib/meta-session.ts +219 -5
  30. package/pi-extensions/lib/mux-fresh-call.ts +171 -17
  31. package/pi-extensions/lib/omp-fresh-preflight.ts +275 -0
  32. package/pi-extensions/meta-bridge-omp.ts +1244 -0
  33. package/pi-extensions/meta-bridge-receive-omp.ts +666 -0
  34. package/run.sh +1041 -174
  35. package/scripts/agy-bridge.sh +4 -2
  36. package/scripts/check-copilot-birth-hook.ts +700 -23
  37. package/scripts/check-entwurf-self-address.ts +31 -0
  38. package/scripts/check-fresh-cut-gate.sh +12 -4
  39. package/scripts/check-gate-qualification.ts +8 -2
  40. package/scripts/check-harness-admission-parity.ts +143 -0
  41. package/scripts/check-meta-doctor-oracle.sh +20 -0
  42. package/scripts/check-omp-birth-hook.ts +1049 -0
  43. package/scripts/check-omp-fresh-preflight.ts +181 -0
  44. package/scripts/check-omp-receive-arm.ts +516 -0
  45. package/scripts/check-setup-qualification.sh +126 -0
  46. package/scripts/copilot-bridge-doctor.sh +104 -71
  47. package/scripts/copilot-bridge-install.sh +180 -20
  48. package/scripts/copilot-bridge-oracle.sh +223 -0
  49. package/scripts/copilot-bridge-uninstall.sh +137 -0
  50. package/scripts/dev-bin.sh +50 -9
  51. package/scripts/fake-copilot-vendor.sh +67 -0
  52. package/scripts/inventory-verification-surface.ts +1 -1
  53. package/scripts/meta-bridge-hook-log.sh +9 -1
  54. package/scripts/mutants/copilot-birth.json +96 -5
  55. package/scripts/mutants/mux-fresh-call.json +22 -22
  56. package/scripts/mutants/omp-birth.json +173 -0
  57. package/scripts/mutants/omp-fresh.json +300 -0
  58. package/scripts/mutants/omp-receive.json +135 -0
  59. package/scripts/mutants/pi-package-ownership.json +92 -0
  60. package/scripts/mutants/self-address.json +34 -0
  61. package/scripts/mutants/setup-verdict.json +148 -0
  62. package/scripts/mutants/source-install.json +32 -0
  63. package/scripts/omp-bridge-doctor.sh +315 -0
  64. package/scripts/omp-bridge-install.sh +221 -0
  65. package/scripts/omp-bridge-oracle.sh +154 -0
  66. package/scripts/omp-bridge-uninstall.sh +57 -0
  67. package/scripts/omp-mcp-bridge.sh +320 -0
  68. package/scripts/omp-mcp-config.py +392 -0
  69. package/scripts/omp-receive-doctor.sh +246 -0
  70. package/scripts/omp-receive-facts.ts +106 -0
  71. package/scripts/omp-receive-install.sh +228 -0
  72. package/scripts/omp-receive-uninstall.sh +60 -0
  73. package/scripts/omp-tool-surface.py +349 -0
  74. package/scripts/pi_settings_io.py +22 -0
  75. package/scripts/raw-omp-measure/README.md +420 -0
  76. package/scripts/raw-omp-measure/probe-extension.ts +76 -0
  77. package/scripts/raw-omp-measure/probe-receive-surface.ts +250 -0
  78. package/scripts/raw-omp-measure/source-audit.md +414 -0
  79. package/scripts/register-pi-package.py +525 -7
  80. package/scripts/register-pi-provider.py +162 -12
  81. package/scripts/smoke-agy-install-state.sh +86 -30
  82. package/scripts/smoke-agy-statusline-state.sh +9 -6
  83. package/scripts/smoke-omp-bridge-state.sh +221 -0
  84. package/scripts/smoke-omp-fresh-live.ts +497 -0
  85. package/scripts/smoke-omp-mcp-state.sh +327 -0
  86. package/scripts/smoke-omp-receive-live.ts +340 -0
  87. package/scripts/smoke-omp-receive-state.sh +196 -0
  88. package/scripts/smoke-setup-verdict.sh +291 -0
  89. package/scripts/smoke-user-scope-citizen.sh +388 -1
  90. 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"