@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,300 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "omp-fresh",
4
+ "mutants": [
5
+ {
6
+ "claim": "ADMISSION-FRESH-PARITY",
7
+ "title": "a post-#82 harness admitted as a full D6 citizen while entwurf_fresh_call cannot open it — the exact state the Bundle A+B candidate shipped in, where every gate was green and only prose said the backend was unsupported. This is the defect the whole gate exists for: without it, an `unsupported` note reads as a partial-release permit",
8
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
9
+ "find": ["export const FRESH_CALL_BACKENDS = [\"pi\", \"claude-code\", \"copilot\", \"omp\"] as const;"],
10
+ "replace": ["export const FRESH_CALL_BACKENDS = [\"pi\", \"claude-code\", \"copilot\"] as const;"],
11
+ "gate": ["bash", "run.sh", "check-harness-admission-parity"],
12
+ "timeoutSeconds": 300,
13
+ "signature": "[QK:ADMISSION-FRESH-PARITY]",
14
+ "signatureSource": "scripts/check-harness-admission-parity.ts"
15
+ },
16
+ {
17
+ "claim": "ADMISSION-LEGACY-DOCUMENTED",
18
+ "title": "a pre-#82 exception that only the gate believes in — the matrix sentence removed while the gate literal stays, so an operator reading DELIVERY.md finds a shipped citizen with no statement that it was never evaluated under step 9. The exclusion half of check-release-gate-outcomes cell 7, replanted on the admission axis",
19
+ "subject": "DELIVERY.md",
20
+ "find": [
21
+ " Admitted before the #82 step 9 contract and not re-evaluated under it, so it is legacy citizen evidence, not a step-9 supported harness: `entwurf_fresh_call` cannot open an agy sibling, and nothing here should be read as claiming visible lifecycle parity."
22
+ ],
23
+ "replace": [""],
24
+ "gate": ["bash", "run.sh", "check-harness-admission-parity"],
25
+ "timeoutSeconds": 300,
26
+ "signature": "[QK:ADMISSION-LEGACY-DOCUMENTED]",
27
+ "signatureSource": "scripts/check-harness-admission-parity.ts"
28
+ },
29
+ {
30
+ "claim": "FRESHCALL-OMP-EXPLICIT-POLICY",
31
+ "title": "the permission width dropped from the omp argv. NOTHING OBSERVABLE FAILS: omp 18.0.0's schema default for tools.approvalMode is ALREADY yolo, so the sibling still boots and a LIVE acceptance still passes — the argv has simply started depending on a vendor default and on whatever the operator's config happens to say. This is the drift step 9 clause 2 forbids, and it is invisible to every test that only watches behaviour",
32
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
33
+ "find": [
34
+ "\t\t\treturn [`--${OMP_BOOTSTRAP_FLAG}`, composition.bootstrapPayload, \"--model\", model, \"--approval-mode\", \"yolo\"];"
35
+ ],
36
+ "replace": ["\t\t\treturn [`--${OMP_BOOTSTRAP_FLAG}`, composition.bootstrapPayload, \"--model\", model];"],
37
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
38
+ "timeoutSeconds": 300,
39
+ "signature": "[QK:FRESHCALL-OMP-EXPLICIT-POLICY]",
40
+ "signatureSource": "test/mux-fresh-call.test.ts"
41
+ },
42
+ {
43
+ "claim": "FRESHCALL-OMP-CALLBACK-TOOL-DIALECT",
44
+ "title": "the callback tool name spelled the way a reader expects instead of the way omp mints it — `entwurf_v2` keeps its digit, which omp's sanitizer charset [a-z_] eats. The sibling is told to call a tool that does not exist in its session, so the whole first turn is spent and no address ever comes back. A launch receipt still prints",
45
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
46
+ "find": ["\tomp: \"mcp__entwurf_bridge_entwurf_v\","],
47
+ "replace": ["\tomp: \"mcp__entwurf_bridge_entwurf_v2\","],
48
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
49
+ "timeoutSeconds": 300,
50
+ "signature": "[QK:FRESHCALL-OMP-CALLBACK-TOOL-DIALECT]",
51
+ "signatureSource": "test/mux-fresh-call.test.ts"
52
+ },
53
+ {
54
+ "claim": "FRESHCALL-OMP-XDEV-FAIL-CLOSED",
55
+ "title": "the callback-callable axis inverted from `must prove false` to `refuse only on explicit true`. The vendor default IS true, so a host that never set tools.xdev preflights green and the fresh call opens a window whose model cannot see the callback tool's schema at all — a sibling that can never name itself, with a successful launch receipt in the caller's hand",
56
+ "subject": "pi-extensions/lib/omp-fresh-preflight.ts",
57
+ "find": [
58
+ "\tif (readOmpConfigFlag(agentDir, \"tools\", \"xdev\") !== false) return \"omp-callback-tool-uncallable\";"
59
+ ],
60
+ "replace": [
61
+ "\tif (readOmpConfigFlag(agentDir, \"tools\", \"xdev\") === true) return \"omp-callback-tool-uncallable\";"
62
+ ],
63
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
64
+ "timeoutSeconds": 300,
65
+ "signature": "[QK:FRESHCALL-OMP-XDEV-FAIL-CLOSED]",
66
+ "signatureSource": "test/mux-fresh-call.test.ts"
67
+ },
68
+ {
69
+ "claim": "FRESHCALL-OMP-AGENT-DIR-REFUSES-PI-KNOB",
70
+ "title": "an inherited PI_CODING_AGENT_DIR taken as a lookup instead of a refusal. omp is a pi fork sharing that name, so the preflight would check units under entwurf's own pi sandbox and report green about a directory no live omp reads — the ledger M6 collision, resolved by guessing",
71
+ "subject": "pi-extensions/lib/omp-fresh-preflight.ts",
72
+ "find": [
73
+ "\tif (typeof env.PI_CODING_AGENT_DIR === \"string\" && env.PI_CODING_AGENT_DIR.length > 0) return null;"
74
+ ],
75
+ "replace": [
76
+ "\tif (typeof env.PI_CODING_AGENT_DIR === \"string\" && env.PI_CODING_AGENT_DIR.length > 0)\n\t\treturn path.resolve(env.PI_CODING_AGENT_DIR);"
77
+ ],
78
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
79
+ "timeoutSeconds": 300,
80
+ "signature": "[QK:FRESHCALL-OMP-AGENT-DIR-REFUSES-PI-KNOB]",
81
+ "signatureSource": "test/mux-fresh-call.test.ts"
82
+ },
83
+ {
84
+ "claim": "OMP-PREFLIGHT-XDEV-ORACLE",
85
+ "title": "the reproduced config reader drifting from the shipped python leaf — a key matched at ANY depth inside the section, so `tools.nested.xdev: false` reads as `tools.xdev: false`. This is the real defect the agreement gate caught on its first run: a fail-OPEN misread that preflights green off a config which never set the flag",
86
+ "subject": "pi-extensions/lib/omp-fresh-preflight.ts",
87
+ "find": ["\t\tif (indent !== childIndent) continue; // a grandchild, or a mis-indented line: not our key"],
88
+ "replace": ["\t\tif (false && indent !== childIndent) continue;"],
89
+ "gate": ["bash", "run.sh", "check-omp-fresh-preflight"],
90
+ "timeoutSeconds": 300,
91
+ "signature": "[QK:OMP-PREFLIGHT-XDEV-ORACLE]",
92
+ "signatureSource": "scripts/check-omp-fresh-preflight.ts"
93
+ },
94
+ {
95
+ "claim": "OMP-PREFLIGHT-AGENT-DIR-ORACLE",
96
+ "title": "the reproduced agent-dir resolver diverging from the installer's own shell oracle on the profile axis — OMP_PROFILE honoured without its name grammar, so a profile the shell refuses resolves here and the two halves of the omp lane address different stores while both look correct",
97
+ "subject": "pi-extensions/lib/omp-fresh-preflight.ts",
98
+ "find": ["\t\tif (!PROFILE_NAME_RE.test(ompProfile)) return null;"],
99
+ "replace": ["\t\tif (false && !PROFILE_NAME_RE.test(ompProfile)) return null;"],
100
+ "gate": ["bash", "run.sh", "check-omp-fresh-preflight"],
101
+ "timeoutSeconds": 300,
102
+ "signature": "[QK:OMP-PREFLIGHT-AGENT-DIR-ORACLE]",
103
+ "signatureSource": "scripts/check-omp-fresh-preflight.ts"
104
+ },
105
+ {
106
+ "claim": "FRESHCALL-IDENTITY-SCRUB",
107
+ "title": "the inherited pi identity carrier left unscrubbed at the launch seam. A tmux server started from a shell that exported PI_SESSION_ID hands that value to every window it opens (measured), and the sibling's own MCP child reads the stale pair as its authoritative identity — so a fresh citizen calls home as someone else, confidently",
108
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
109
+ "find": ["\t\t...SCRUBBED_INHERITED_ENV.flatMap((assignment) => [\"-e\", assignment]),"],
110
+ "replace": [""],
111
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
112
+ "timeoutSeconds": 300,
113
+ "signature": "[QK:FRESHCALL-IDENTITY-SCRUB]",
114
+ "signatureSource": "test/mux-fresh-call.test.ts"
115
+ },
116
+ {
117
+ "claim": "FRESHCALL-BACKEND-SET-SURFACE-PARITY-SKILL",
118
+ "title": "a backend added to the composition and both schema surfaces but not to the operator skill — the third public surface step 9 names, and the one that had no gate at all. The skill is what an operator reads to decide what can be opened, so a stale list makes the backend unreachable in practice exactly the way a missing enum value makes it unreachable in schema",
119
+ "subject": ".claude/skills/entwurf-dev/SKILL.md",
120
+ "find": ["backend는 정확히 `pi | claude-code | copilot | omp`이고"],
121
+ "replace": ["backend는 정확히 `pi | claude-code | copilot`이고"],
122
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
123
+ "timeoutSeconds": 300,
124
+ "signature": "[QK:FRESHCALL-BACKEND-SET-SURFACE-PARITY-SKILL]",
125
+ "signatureSource": "test/fresh-call-surfaces.contract.test.ts"
126
+ },
127
+ {
128
+ "claim": "FRESHCALL-OMP-ARGV-BOOTSTRAP-FLAG",
129
+ "title": "the positional first-turn prompt put back on the omp argv beside the bootstrap flag — the exact shape the first Bundle C candidate shipped. `[LIVE 2026-08-30]` it opens a window whose turn starts ~830ms BEFORE the callback tool exists, and the sibling answers the literal text `ACK` with zero tool calls. It parses, it launches, and the caller times out at 240s: the failure is invisible to anything that only checks that argv is well-formed",
130
+ "subject": "pi-extensions/lib/mux-fresh-call.ts",
131
+ "find": [
132
+ "\t\t\treturn [`--${OMP_BOOTSTRAP_FLAG}`, composition.bootstrapPayload, \"--model\", model, \"--approval-mode\", \"yolo\"];"
133
+ ],
134
+ "replace": [
135
+ "\t\t\treturn [composition.prompt, `--${OMP_BOOTSTRAP_FLAG}`, composition.bootstrapPayload, \"--model\", model, \"--approval-mode\", \"yolo\"];"
136
+ ],
137
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
138
+ "timeoutSeconds": 300,
139
+ "signature": "[QK:FRESHCALL-OMP-ARGV-BOOTSTRAP-FLAG]",
140
+ "signatureSource": "test/mux-fresh-call.test.ts"
141
+ },
142
+ {
143
+ "claim": "OMP-BOOTSTRAP-FLAG-AGREES",
144
+ "title": "the installed unit renamed its registered flag while the launcher keeps sending the old one. The unit still loads, still mints, still shows the garden id — and every fresh call at omp silently becomes a manual session that sits there with no task, because `getFlag` answers only for flags THIS extension registered. Two halves that ship in different directories and cannot import each other is exactly where a constant drifts",
145
+ "subject": "pi-extensions/meta-bridge-omp.ts",
146
+ "find": ["export const OMP_BOOTSTRAP_FLAG = \"entwurf-bootstrap\";"],
147
+ "replace": ["export const OMP_BOOTSTRAP_FLAG = \"entwurf-fresh-bootstrap\";"],
148
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
149
+ "timeoutSeconds": 300,
150
+ "signature": "[QK:OMP-BOOTSTRAP-FLAG-AGREES]",
151
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
152
+ },
153
+ {
154
+ "claim": "OMP-BOOTSTRAP-READY-NEEDS-BOTH",
155
+ "title": "readiness decided from the tool REGISTRY alone, dropping the enabled-tools half. A tool can be registered and not enabled, and a prompt can only call an enabled one — so this sends the callback-only prompt into a window where the model still cannot make the call, which is the same 830ms race the positional prompt lost, moved one layer inward",
156
+ "subject": "pi-extensions/meta-bridge-omp.ts",
157
+ "find": ["\treturn registered && active.includes(OMP_BOOTSTRAP_CALLBACK_TOOL);"],
158
+ "replace": ["\treturn registered;"],
159
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
160
+ "timeoutSeconds": 300,
161
+ "signature": "[QK:OMP-BOOTSTRAP-READY-NEEDS-BOTH]",
162
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
163
+ },
164
+ {
165
+ "claim": "OMP-BOOTSTRAP-CALLBACK-ONLY",
166
+ "title": "the task folded back into the stage-one prompt, so the sibling is asked to call back AND to work in one message. This is the prompt the failed LIVE actually ran, minus the missing tool: a competing goal beside the callback is what the two-stage split exists to remove, and nothing about argv or readiness would notice it coming back",
167
+ "subject": "pi-extensions/meta-bridge-omp.ts",
168
+ "find": ["\t\t\tsend(buildOmpCallbackOnlyPrompt({ target: payload.target, nonce: payload.nonce }));"],
169
+ "replace": [
170
+ "\t\t\tsend(`${buildOmpCallbackOnlyPrompt({ target: payload.target, nonce: payload.nonce })}\\n\\n${payload.task}`);"
171
+ ],
172
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
173
+ "timeoutSeconds": 300,
174
+ "signature": "[QK:OMP-BOOTSTRAP-CALLBACK-ONLY]",
175
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
176
+ },
177
+ {
178
+ "claim": "OMP-BOOTSTRAP-ISERROR-EXACT-FALSE",
179
+ "title": "the success predicate loosened from `isError === false` to `isError === true` being the only failure, so a result whose isError is ABSENT or non-boolean releases the caller's task. The happy path is unchanged and every LIVE run still passes; what changes is that a malformed or partial result now counts as a callback that succeeded",
180
+ "subject": "pi-extensions/meta-bridge-omp.ts",
181
+ "find": ["\t\t\tif (event?.isError !== false) {"],
182
+ "replace": ["\t\t\tif (event?.isError === true) {"],
183
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
184
+ "timeoutSeconds": 300,
185
+ "signature": "[QK:OMP-BOOTSTRAP-ISERROR-EXACT-FALSE]",
186
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
187
+ },
188
+ {
189
+ "claim": "OMP-BOOTSTRAP-NO-ARBITRARY-CARRIER",
190
+ "title": "the decoder's unknown-key refusal removed, so the one flag a caller can put arbitrary bytes into starts silently ignoring whatever it does not recognise. Every valid payload still decodes identically, so no behavioural test moves — but `what the launcher meant` and `what the unit acts on` have stopped being the same closed set, which is the property that keeps this from becoming a general carrier",
191
+ "subject": "pi-extensions/meta-bridge-omp.ts",
192
+ "find": ["\t\tif (!BOOTSTRAP_KEYS.has(key)) return { ok: false, reason: \"payload-unknown-key\" };"],
193
+ "replace": [
194
+ "\t\tif (!BOOTSTRAP_KEYS.has(key) && key.startsWith(\"__\")) return { ok: false, reason: \"payload-unknown-key\" };"
195
+ ],
196
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
197
+ "timeoutSeconds": 300,
198
+ "signature": "[QK:OMP-BOOTSTRAP-NO-ARBITRARY-CARRIER]",
199
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
200
+ },
201
+ {
202
+ "claim": "OMP-BOOTSTRAP-RESULT-ARMS-ONLY",
203
+ "title": "the task sent from inside the tool_result handler again, instead of at the turn_end boundary. `[LIVE 2026-08-30]` this is the exact shape that failed: the hook log showed armed/ready/callback-observed/released, the release predicate was provably correct, and the task still never appeared in the session — because that handler runs while the callback turn is streaming. Every static contract about correlation and readiness stays green while the product delivers nothing",
204
+ "subject": "pi-extensions/meta-bridge-omp.ts",
205
+ "find": [
206
+ "\t\t\tlog(\"INFO\", `bootstrap-released nonce=${payload.nonce} toolCallId=${callId}: task armed for the next turn_end`);"
207
+ ],
208
+ "replace": [
209
+ "\t\t\tlog(\"INFO\", `bootstrap-released nonce=${payload.nonce} toolCallId=${callId}: task armed for the next turn_end`);\n\t\t\tphase = \"task-sent\";\n\t\t\tsend(payload.task);"
210
+ ],
211
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
212
+ "timeoutSeconds": 300,
213
+ "signature": "[QK:OMP-BOOTSTRAP-RESULT-ARMS-ONLY]",
214
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
215
+ },
216
+ {
217
+ "claim": "OMP-BOOTSTRAP-NO-EXPLICIT-DELIVERY",
218
+ "title": "an explicit delivery option put back on the send. `[source]` omitted `deliverAs` starts a turn when idle and steers while streaming; an EXPLICIT one queues without starting a turn in either state (`oh-my-pi v18.0.0 session/agent-session.ts:6511-6513`). `[LIVE 2026-08-30]` the queued form was measured to never become a message at all, three seconds before the Bundle B doorbell's omitted-option send landed and started a turn in the SAME session. Nothing about argv, readiness or correlation notices this",
219
+ "subject": "pi-extensions/meta-bridge-omp.ts",
220
+ "find": ["\t\t\tpi.sendUserMessage(content);"],
221
+ "replace": ["\t\t\tpi.sendUserMessage(content, { deliverAs: \"followUp\" });"],
222
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
223
+ "timeoutSeconds": 300,
224
+ "signature": "[QK:OMP-BOOTSTRAP-NO-EXPLICIT-DELIVERY]",
225
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
226
+ },
227
+ {
228
+ "claim": "OMP-BOOTSTRAP-ONE-TASK",
229
+ "title": "the turn_end guard widened to admit an already-spent bootstrap, so every later turn of the session re-delivers the caller's task. The first turn is identical and a LIVE acceptance that only checks `the task arrived` still passes; what changes is that a sibling working for ten turns receives its opening instruction ten times",
230
+ "subject": "pi-extensions/meta-bridge-omp.ts",
231
+ "find": ["\t\t\tif (phase !== \"released\") return;"],
232
+ "replace": ["\t\t\tif (phase !== \"released\" && phase !== \"task-sent\") return;"],
233
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
234
+ "timeoutSeconds": 300,
235
+ "signature": "[QK:OMP-BOOTSTRAP-ONE-TASK]",
236
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
237
+ },
238
+ {
239
+ "claim": "OMP-BOOTSTRAP-RELEASED-IS-INVALIDATABLE",
240
+ "title": "`released` made un-invalidatable, so a bootstrap whose session is replaced by `/new`, a fork or a resume BETWEEN the callback result and the next turn_end still fires its task — into a session the caller never opened and cannot address. The window between those two events is small and the failure is invisible until the operator's own next session receives someone else's instruction",
241
+ "subject": "pi-extensions/meta-bridge-omp.ts",
242
+ "find": [
243
+ "\t\t\t// `released` is invalidatable ON PURPOSE: between the callback result and the next\n\t\t\t// turn_end the session can be replaced by `/new`, a fork or a resume, and the\n\t\t\t// caller's task must die with the session it was addressed to.\n\t\t\tif (isFinal()) return;"
244
+ ],
245
+ "replace": ["\t\t\tif (phase === \"released\" || isFinal()) return;"],
246
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
247
+ "timeoutSeconds": 300,
248
+ "signature": "[QK:OMP-BOOTSTRAP-RELEASED-IS-INVALIDATABLE]",
249
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
250
+ },
251
+ {
252
+ "claim": "OMP-BOOTSTRAP-SESSION-FENCE",
253
+ "title": "the native session id dropped from the event fence, leaving only `mode === \"tui\"`. Tool and turn events carry no session id of their own, so after a `/new` the previous caller's bootstrap would start accepting the REPLACEMENT session's tool results and deliver its task there. The happy path — one launch, one session, no switch — is completely unaffected",
254
+ "subject": "pi-extensions/meta-bridge-omp.ts",
255
+ "find": ["\treturn typeof id === \"string\" && id === boundSessionId;"],
256
+ "replace": ["\tvoid boundSessionId;\n\treturn typeof id === \"string\";"],
257
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
258
+ "timeoutSeconds": 300,
259
+ "signature": "[QK:OMP-BOOTSTRAP-SESSION-FENCE]",
260
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
261
+ },
262
+ {
263
+ "claim": "OMP-BOOTSTRAP-SAME-ID-SWITCH-INVALIDATES",
264
+ "title": "the native-id comparison put back INSIDE the one function that owns the epoch rule, exactly as the frozen Bundle C candidate had it. The vendor emits session_switch(reason:\"resume\") on the same-file reload path WITHOUT changing the native id and replaces the transcript right after (agent-session.ts:7986-7988 computes switchingToDifferentSession, :8071-8078 emits regardless), so an id-conditional rule lets a waiting, callback-sent or released bootstrap survive a real epoch change wearing the old id. Note what does NOT catch this: on the wired onBirthEdge path the pre-envelope call passes no id at all and still ends the epoch, so the defence in depth added for O-D1r masks the mutation there — it is the direct-drive cell, which reaches the rule with a real id the way a healthy edge does, that kills it",
265
+ "subject": "pi-extensions/meta-bridge-omp.ts",
266
+ "find": ["\tif (!bootstrapConsumed || activeBootstrap === null) return;\n"],
267
+ "replace": [
268
+ "\tif (!bootstrapConsumed || activeBootstrap === null) return;\n\tif (nativeSessionId !== null && activeBootstrap.sessionId === nativeSessionId) return;\n"
269
+ ],
270
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
271
+ "timeoutSeconds": 300,
272
+ "signature": "[QK:OMP-BOOTSTRAP-SAME-ID-SWITCH-INVALIDATES]",
273
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
274
+ },
275
+ {
276
+ "claim": "OMP-BOOTSTRAP-REFUSES-UNCANCELLABLE-TIMER",
277
+ "title": "a silent global-timer fallback restored under the creator-owned seam. A vendor build with no ctx.setTimeout/ctx.clearTimer would arm anyway on a raw setTimeout that belongs to no session registry and that clearTimer cannot reach — a 100ms poll running inside the operator's TUI for the life of the process, surviving the disposal of the session that scheduled it. It is invisible to every behavioural test on a COMPLETE vendor context, because on v18.0.0 the fallback is never taken",
278
+ "subject": "pi-extensions/meta-bridge-omp.ts",
279
+ "find": ["\tconst timers = creatorOwnedTimers(ctx);\n"],
280
+ "replace": [
281
+ "\tconst timers: OmpBootstrapTimers | null =\n\t\tcreatorOwnedTimers(ctx) ??\n\t\t({\n\t\t\tset: (fn: () => void, ms: number) => setTimeout(fn, ms),\n\t\t\tclear: (handle: unknown) => {\n\t\t\t\tclearTimeout(handle as ReturnType<typeof setTimeout>);\n\t\t\t},\n\t\t\tnow: () => Date.now(),\n\t\t} satisfies OmpBootstrapTimers);\n"
282
+ ],
283
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
284
+ "timeoutSeconds": 300,
285
+ "signature": "[QK:OMP-BOOTSTRAP-REFUSES-UNCANCELLABLE-TIMER]",
286
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
287
+ },
288
+ {
289
+ "claim": "OMP-BOOTSTRAP-EPOCH-ENDS-BEFORE-ENVELOPE",
290
+ "title": "the epoch-ending call moved back behind the envelope, which is the pre-O-D1r shape. onBirthEdge has two early exits before it ever reaches startOmpBootstrap — a refused envelope and a throwing upsert — so ending the epoch only from inside startOmpBootstrap makes the rule conditional on the LATER edge succeeding. A same-id session_switch that takes either exit then leaves a released bootstrap alive, and it still passes both fences in ompBootstrapCtxAccepts, so the previous caller's task lands at the next turn_end of a session the caller never opened. Every healthy edge is unaffected, which is why a review found this by reading the bail paths rather than by running anything",
291
+ "subject": "pi-extensions/meta-bridge-omp.ts",
292
+ "find": ["\tendBootstrapEpochOnLaterEdge(edge, null);\n\n\tconst envelope = readBirthEnvelope(ctx);"],
293
+ "replace": ["\tconst envelope = readBirthEnvelope(ctx);"],
294
+ "gate": ["bash", "run.sh", "check-mux-fresh-call"],
295
+ "timeoutSeconds": 300,
296
+ "signature": "[QK:OMP-BOOTSTRAP-EPOCH-ENDS-BEFORE-ENVELOPE]",
297
+ "signatureSource": "test/omp-fresh-bootstrap.contract.test.ts"
298
+ }
299
+ ]
300
+ }
@@ -0,0 +1,135 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "omp-receive",
4
+ "mutants": [
5
+ {
6
+ "claim": "OMP-RECEIVE-SCOPE-FENCE",
7
+ "title": "a receiver that arms on any mode publishes the HOST citizen's doorbell from inside a task subagent — two watchers on one mailbox, and a marker whose owner is not the session it names",
8
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
9
+ "find": ["\t\tif (ctx.mode !== HOST_MODE) {"],
10
+ "replace": ["\t\tif (false && ctx.mode !== HOST_MODE) {"],
11
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
12
+ "timeoutSeconds": 300,
13
+ "signature": "[QK:OMP-RECEIVE-SCOPE-FENCE]",
14
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
15
+ },
16
+ {
17
+ "claim": "OMP-RECEIVE-NEW-RETIRES-OLD",
18
+ "title": "skipping the unarm on a birth edge leaves the PREVIOUS citizen armed after `/new` — same pid, same start key, so the marker reads live forever and dispatch enqueues into a mailbox nobody watches. The one false-deliverability cell the start-key guard structurally cannot catch",
19
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
20
+ "find": ["\t\tthis.unarm(`edge=${edge}`);"],
21
+ "replace": ["\t\tif (!this.armed) this.unarm(`edge=${edge}`);"],
22
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
23
+ "timeoutSeconds": 300,
24
+ "signature": "[QK:OMP-RECEIVE-NEW-RETIRES-OLD]",
25
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
26
+ },
27
+ {
28
+ "claim": "OMP-RECEIVE-NO-WATCH-NO-MARKER",
29
+ "title": "writing the marker BEFORE the watch advertises a doorbell nobody is listening at — the fail-closed rule inverted, and invisible until a reply silently rots",
30
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
31
+ "find": ["\t\t\twatcher = fs.watch(signal, () => this.ring(\"signal\"));"],
32
+ "replace": [
33
+ "\t\t\twriteMetaReceiverMarker({\n\t\t\t\tgardenId,\n\t\t\t\tbackend: BACKEND,\n\t\t\t\tnativeSessionId,\n\t\t\t\townerPid: this.deps.pid,\n\t\t\t\townerKind: \"omp-host\",\n\t\t\t\tarmProvenance: \"session-start\",\n\t\t\t\treceiversDir: bundle.receiversDir,\n\t\t\t});\n\t\t\twatcher = fs.watch(signal, () => this.ring(\"signal\"));"
34
+ ],
35
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
36
+ "timeoutSeconds": 300,
37
+ "signature": "[QK:OMP-RECEIVE-NO-WATCH-NO-MARKER]",
38
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
39
+ },
40
+ {
41
+ "claim": "OMP-RECEIVE-ANNOUNCE-NEVER-PUSHES",
42
+ "title": "carrying the mailbox body into the doorbell turns every message into a prompt injection — the body is untrusted by construction, and the announce-only contract is the only thing keeping it out of the model's turn",
43
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
44
+ "find": [
45
+ "\t\t\t\t\t`[entwurf inbox] ${unread} unread mailbox ${plural} available for garden ${armed.gardenId}. ` +"
46
+ ],
47
+ "replace": [
48
+ "\t\t\t\t\t`[entwurf inbox] ${unread} unread mailbox ${plural} for garden ${armed.gardenId}: ${fs.readFileSync(path.join(armed.mailbox, `${fresh[0]}.delivered`), \"utf8\")} ` +"
49
+ ],
50
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
51
+ "timeoutSeconds": 300,
52
+ "signature": "[QK:OMP-RECEIVE-ANNOUNCE-NEVER-PUSHES]",
53
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
54
+ },
55
+ {
56
+ "claim": "OMP-RECEIVE-NO-UNCANCELLABLE-TIMER",
57
+ "title": "starting the arm retry on a build with no `ctx.clearTimer` leaves a 500ms poll running inside the operator's TUI for the life of the session, with nothing able to stop it — the exact silent no-op measured when `clearInterval` was called through `?.`",
58
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
59
+ "find": ["\t\tif (typeof ctx.setInterval !== \"function\" || typeof ctx.clearTimer !== \"function\") {"],
60
+ "replace": ["\t\tif (typeof ctx.setInterval !== \"function\") {"],
61
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
62
+ "timeoutSeconds": 300,
63
+ "signature": "[QK:OMP-RECEIVE-NO-UNCANCELLABLE-TIMER]",
64
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
65
+ },
66
+ {
67
+ "claim": "OMP-RECEIVE-REFUSES-WITHOUT-WAKE",
68
+ "title": "arming without the vendor's send surface advertises a citizen as addressable while nothing can ever wake it — a marker that promises a wake this process cannot perform",
69
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
70
+ "find": ["\t\tif (!this.canRing()) {"],
71
+ "replace": ["\t\tif (false) {"],
72
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
73
+ "timeoutSeconds": 300,
74
+ "signature": "[QK:OMP-RECEIVE-REFUSES-WITHOUT-WAKE]",
75
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
76
+ },
77
+ {
78
+ "claim": "OMP-RECEIVE-RETRY-ARMS",
79
+ "title": "an arm that never retries leaves every session where this unit loaded before birth permanently unaddressable — and directory-name collation decides which of the two runs first, so the defect is invisible on the host that happened to sort favourably",
80
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
81
+ "find": ["\t\tthis.startRetry(edge, ctx);"],
82
+ "replace": ["\t\tif (false) this.startRetry(edge, ctx);"],
83
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
84
+ "timeoutSeconds": 300,
85
+ "signature": "[QK:OMP-RECEIVE-RETRY-ARMS]",
86
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
87
+ },
88
+ {
89
+ "claim": "OMP-RECEIVE-BIND-LEAKS-NO-LISTENERS",
90
+ "title": "registering the exit guard at BIND time adds one process listener per session — subagents included — so a host that spawns enough of them prints MaxListenersExceededWarning into the operator's TUI",
91
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
92
+ "find": ["\tconst receiver = new OmpReceiver(pi, deps);"],
93
+ "replace": [
94
+ "\tconst receiver = new OmpReceiver(pi, deps);\n\tprocess.on(\"exit\", () => receiver.unarm(\"process-exit\"));"
95
+ ],
96
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
97
+ "timeoutSeconds": 300,
98
+ "signature": "[QK:OMP-RECEIVE-BIND-LEAKS-NO-LISTENERS]",
99
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
100
+ },
101
+ {
102
+ "claim": "OMP-RECEIVE-UNARM-IS-IDENTITY-GUARDED",
103
+ "title": "an unguarded teardown deletes a replacement receiver's marker for the same citizen — the live doorbell disappears because a dying instance tidied up after someone else",
104
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
105
+ "find": ["\t\t\tif (mine && mine.ownerPid === this.deps.pid && mine.gardenId === armed.gardenId) {"],
106
+ "replace": ["\t\t\tif (mine) {"],
107
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
108
+ "timeoutSeconds": 300,
109
+ "signature": "[QK:OMP-RECEIVE-UNARM-IS-IDENTITY-GUARDED]",
110
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
111
+ },
112
+ {
113
+ "claim": "OMP-RECEIVE-ID-DRIFT-REFUSED",
114
+ "title": "trusting a STALE sender marker arms the doorbell off an id the vendor has already moved past — the record check does not cover it, because the record can agree with the vendor while the marker does not",
115
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
116
+ "find": ["\tif (marker.nativeSessionId !== nativeSessionId) {"],
117
+ "replace": ["\tif (false) {"],
118
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
119
+ "timeoutSeconds": 300,
120
+ "signature": "[QK:OMP-RECEIVE-ID-DRIFT-REFUSED]",
121
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
122
+ },
123
+ {
124
+ "claim": "OMP-RECEIVE-EDGE-CANCELS-OLD-RETRY",
125
+ "title": "skipping the stored cancel closure on a birth edge and nulling only the handle ledger leaves the FIRST context's 500ms poll running in the operator's TUI with nothing left able to stop it, and starts a second beside it — one orphan per birth edge that arrives while birth is still slow. The uncancellable-timer failure the arm path refuses to create, reintroduced on the cancel path",
126
+ "subject": "pi-extensions/meta-bridge-receive-omp.ts",
127
+ "find": ["\t\tthis.cancelRetry();\n\t\tthis.unarm(`edge=${edge}`);"],
128
+ "replace": ["\t\tthis.retryHandle = null;\n\t\tthis.retryCancel = null;\n\t\tthis.unarm(`edge=${edge}`);"],
129
+ "gate": ["bash", "run.sh", "check-omp-receive-arm"],
130
+ "timeoutSeconds": 300,
131
+ "signature": "[QK:OMP-RECEIVE-EDGE-CANCELS-OLD-RETRY]",
132
+ "signatureSource": "scripts/check-omp-receive-arm.ts"
133
+ }
134
+ ]
135
+ }
@@ -0,0 +1,92 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "pi-package-ownership",
4
+ "mutants": [
5
+ {
6
+ "claim": "SILENT-TAKEOVER",
7
+ "title": "a normal user-scope install silently replaces another LIVE root's registration, resurrecting the last-writer-wins takeover the #86 C2 ownership contract retires (only the operator-explicit takeover-user-scope may move the shared entry)",
8
+ "subject": "scripts/register-pi-package.py",
9
+ "find": [
10
+ " if os.path.isdir(owner):\n _refuse(6, \"register-pi-package: the user-scope entwurf registration is owned by another LIVE root: \"\n f\"{owner}. Normal install/setup never replaces another owner — zero settings bytes written. \"\n f\"Run './run.sh takeover-user-scope' from {repo_dir} to explicitly move the shared entry.\")"
11
+ ],
12
+ "replace": [
13
+ " if os.path.isdir(owner):\n return register_user(settings_path, repo_dir_arg, state_path, True)"
14
+ ],
15
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
16
+ "timeoutSeconds": 300,
17
+ "signature": "[QK:SILENT-TAKEOVER]",
18
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
19
+ },
20
+ {
21
+ "claim": "FOREIGN-INVERSE-REMOVE",
22
+ "title": "an old root's inverse removes the CURRENT live owner's user-scope registration, so one stale checkout's remove-user-scope deletes the shared entry every other project depends on",
23
+ "subject": "scripts/register-pi-package.py",
24
+ "find": [
25
+ " if os.path.isdir(owner):\n _refuse(6, \"register-pi-package: the user-scope registration is owned by another LIVE root: \"\n f\"{owner}. This root's inverse must not remove someone else's registration — zero settings \"\n \"bytes written. Run remove from that root, or take the entry over first.\")"
26
+ ],
27
+ "replace": [
28
+ " if os.path.isdir(owner):\n n, kept = remove(settings_path, owner)\n state_path.unlink()\n print(f\"remove: removed {n} entries (foreign owner ignored)\")\n return 0"
29
+ ],
30
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
31
+ "timeoutSeconds": 300,
32
+ "signature": "[QK:FOREIGN-INVERSE-REMOVE]",
33
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
34
+ },
35
+ {
36
+ "claim": "BROAD-INSTALL-COLLAPSE",
37
+ "title": "a same-owner user-scope install routes through the broad project-scope register() collapse, so a second entwurf-shaped root in the GLOBAL packages[] is silently deleted as collateral of an ordinary install — the exact transition B reproduced against 096a5cd (#86 C2 corrective amendment, blocker 1)",
38
+ "subject": "scripts/register-pi-package.py",
39
+ "find": [
40
+ " _require_user_owner_entry(settings_path, owner)\n if preflight:\n print(\"preflight: install ok (this root owns one unambiguous registration)\")\n return 0\n print(f\"install: entwurf package already registered (no-op) -> {repo_dir}\")\n return 0"
41
+ ],
42
+ "replace": [
43
+ " if preflight:\n print(\"preflight: install ok (this root owns one unambiguous registration)\")\n return 0\n register(settings_path, repo_dir_arg)\n print(f\"install: entwurf package already registered (no-op) -> {repo_dir}\")\n return 0"
44
+ ],
45
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
46
+ "timeoutSeconds": 300,
47
+ "signature": "[QK:BROAD-INSTALL-COLLAPSE]",
48
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
49
+ },
50
+ {
51
+ "claim": "BROAD-DOCTOR-GREEN",
52
+ "title": "doctor-pi-package judges the recorded owner through the broad entwurf-shape matcher, so a store whose exact owner entry is GONE but which still holds some other entwurf-shaped root reads owned/exit-0 while the inverse refuses the very same state — contradictory verdicts on one store (#86 C2 corrective amendment, blocker 2)",
53
+ "subject": "scripts/register-pi-package.py",
54
+ "find": [
55
+ " owner_exact, owner_extra = _classify_user_owner_entries(settings_path, owner)\n if len(owner_exact) != 1 or owner_extra:\n print(f\"doctor-pi-package: mismatch — owner state records {owner} but the settings hold \"\n f\"{len(owner_exact)} exact entr{'y' if len(owner_exact) == 1 else 'ies'} and \"\n f\"{len(owner_extra)} additional entwurf-shaped entr{'y' if len(owner_extra) == 1 else 'ies'} \"\n \"(expected exactly one exact entry and no other entwurf-shaped root)\")\n return 1"
56
+ ],
57
+ "replace": [
58
+ " owner_matches = _entwurf_matches(packages, owner, settings_dir)\n if not owner_matches:\n print(f\"doctor-pi-package: mismatch — owner state records {owner} but no packages[] entry names it\")\n return 1"
59
+ ],
60
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
61
+ "timeoutSeconds": 300,
62
+ "signature": "[QK:BROAD-DOCTOR-GREEN]",
63
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
64
+ },
65
+ {
66
+ "claim": "PROVIDER-ROOT-TYPE-FAIL-OPEN",
67
+ "title": "the typed installerRoot classifier reports a wrong-TYPE value (number, empty string, bool, object, array) as LEGACY instead of CORRUPT, so provider install adopts and rebinds an unattributed state, remove proceeds on it, and the doctor coupling stays silent — the fall-through B reproduced against 096a5cd (#86 C2 corrective amendment, blocker 3)",
68
+ "subject": "scripts/pi_settings_io.py",
69
+ "find": [" if isinstance(root, str) and root:\n return \"owner\", root\n return \"corrupt\", None"],
70
+ "replace": [
71
+ " if isinstance(root, str) and root:\n return \"owner\", root\n return \"legacy\", None"
72
+ ],
73
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
74
+ "timeoutSeconds": 300,
75
+ "signature": "[QK:PROVIDER-ROOT-TYPE-FAIL-OPEN]",
76
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
77
+ },
78
+ {
79
+ "claim": "MISSING-OWNER-AUTO-TAKEOVER",
80
+ "title": "a normal install auto-claims a MISSING owner's registration, turning every moved or deleted checkout into a silent ownership transfer instead of the named missing-owner verdict plus explicit takeover/orphan path",
81
+ "subject": "scripts/register-pi-package.py",
82
+ "find": [
83
+ " _refuse(6, \"register-pi-package: the user-scope entwurf registration is owned by a MISSING root: \"\n f\"{owner} (doctor verdict: missing-owner). Normal install/setup still refuses — zero settings \"\n \"bytes written. Use './run.sh takeover-user-scope' to claim it explicitly, or \"\n \"'./run.sh remove-user-scope' for the aligned orphan cleanup.\")"
84
+ ],
85
+ "replace": [" return register_user(settings_path, repo_dir_arg, state_path, True)"],
86
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
87
+ "timeoutSeconds": 300,
88
+ "signature": "[QK:MISSING-OWNER-AUTO-TAKEOVER]",
89
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
90
+ }
91
+ ]
92
+ }
@@ -54,6 +54,40 @@
54
54
  "timeoutSeconds": 60,
55
55
  "signature": "[QK:SELFADDR-RAIL-FACT-LEAK]",
56
56
  "signatureSource": "scripts/check-entwurf-self-address.ts"
57
+ },
58
+ {
59
+ "claim": "SELFADDR-NO-FALLBACK-SELF-FETCH",
60
+ "title": "buildTrustedMetaSenderEnvelope falls every non-native-push backend back to self-fetch (omp renders a mailboxPath nothing drains)",
61
+ "subject": "mcp/entwurf-bridge/src/index.ts",
62
+ "find": [
63
+ "\tconst metaDeliveryDomain: MetaDeliveryDomain = nativePushSupported(identity.backend)",
64
+ "\t\t? \"native-push\"",
65
+ "\t\t: resolveMailboxWakeModeCapability(identity)",
66
+ "\t\t\t? \"self-fetch\"",
67
+ "\t\t\t: \"none\";"
68
+ ],
69
+ "replace": [
70
+ "\tconst metaDeliveryDomain: MetaDeliveryDomain = nativePushSupported(identity.backend) ? \"native-push\" : \"self-fetch\";"
71
+ ],
72
+ "gate": ["bash", "run.sh", "check-entwurf-self-address"],
73
+ "timeoutSeconds": 60,
74
+ "signature": "[QK:SELFADDR-NO-FALLBACK-SELF-FETCH]",
75
+ "signatureSource": "scripts/check-entwurf-self-address.ts"
76
+ },
77
+ {
78
+ "claim": "SELFADDR-MAILBOX-WAKE-SEAM",
79
+ "title": "self-fetch derivation uses a today-correct backend-name list instead of resolveMailboxWakeModeCapability — drifts when the registry admits another self-fetch backend",
80
+ "subject": "mcp/entwurf-bridge/src/index.ts",
81
+ "find": ["\t\t: resolveMailboxWakeModeCapability(identity)", "\t\t\t? \"self-fetch\"", "\t\t\t: \"none\";"],
82
+ "replace": [
83
+ "\t\t: identity.backend === \"claude-code\" || identity.backend === \"copilot\"",
84
+ "\t\t\t? \"self-fetch\"",
85
+ "\t\t\t: \"none\";"
86
+ ],
87
+ "gate": ["bash", "run.sh", "check-entwurf-self-address"],
88
+ "timeoutSeconds": 60,
89
+ "signature": "[QK:SELFADDR-MAILBOX-WAKE-SEAM]",
90
+ "signatureSource": "scripts/check-entwurf-self-address.ts"
57
91
  }
58
92
  ]
59
93
  }