@junghanacs/entwurf 0.22.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/AGENTS.md +4 -1
  2. package/BASELINE.md +1 -0
  3. package/CHANGELOG.md +514 -0
  4. package/DELIVERY.md +10 -2
  5. package/README.md +43 -4
  6. package/VERIFY.md +47 -0
  7. package/docs/acp-backend-rail.md +96 -17
  8. package/docs/setup-clean-host.md +11 -1
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +39 -40
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +2 -1
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-declaration.js +609 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +95 -56
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +2 -2
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +1 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +46 -273
  27. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
  28. package/mcp/entwurf-bridge/src/index.ts +41 -41
  29. package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
  30. package/package.json +9 -8
  31. package/pi-extensions/entwurf-control.ts +91 -52
  32. package/pi-extensions/lib/acp/acp-client.ts +3 -3
  33. package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
  34. package/pi-extensions/lib/acp/backend.ts +4 -4
  35. package/pi-extensions/lib/acp/event-mapper.ts +5 -5
  36. package/pi-extensions/lib/acp/tool-surface.ts +2 -1
  37. package/pi-extensions/lib/codex-declaration.js +612 -0
  38. package/pi-extensions/lib/codex-fresh-preflight.ts +107 -51
  39. package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
  40. package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
  41. package/pi-extensions/lib/entwurf-facts.ts +14 -1
  42. package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
  43. package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
  44. package/pi-extensions/lib/entwurf-self-address.ts +33 -0
  45. package/pi-extensions/lib/entwurf-v2-contract.ts +2 -2
  46. package/pi-extensions/lib/entwurf-v2-lock.ts +1 -1
  47. package/pi-extensions/lib/entwurf-v2-runner.ts +1 -1
  48. package/pi-extensions/lib/fresh-call-composition.ts +493 -0
  49. package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
  50. package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
  51. package/pi-extensions/lib/herdr-placement.ts +276 -0
  52. package/pi-extensions/lib/mux-fresh-call.ts +95 -301
  53. package/run.sh +588 -204
  54. package/scripts/check-acp-usage-accounting.ts +9 -9
  55. package/scripts/check-codex-birth-hook.ts +187 -0
  56. package/scripts/check-entwurf-control-rpc.ts +138 -0
  57. package/scripts/check-entwurf-fact-provider.ts +4 -2
  58. package/scripts/check-entwurf-peers-surface.ts +3 -1
  59. package/scripts/check-entwurf-self-address.ts +68 -0
  60. package/scripts/check-fresh-call-dispatch.ts +301 -0
  61. package/scripts/check-gate-qualification.ts +17 -4
  62. package/scripts/check-herdr-activation.ts +1293 -0
  63. package/scripts/check-herdr-fresh-call.ts +1250 -0
  64. package/scripts/check-herdr-placement.ts +383 -0
  65. package/scripts/check-herdr-plugin-build.ts +842 -0
  66. package/scripts/check-herdr-plugin-profile.ts +371 -0
  67. package/scripts/check-herdr-plugin.ts +523 -0
  68. package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
  69. package/scripts/check-herdr-sandbox.ts +456 -0
  70. package/scripts/check-herdr-supply.ts +184 -0
  71. package/scripts/check-mux-launcher-fence.ts +138 -0
  72. package/scripts/check-peer-facts.ts +331 -0
  73. package/scripts/check-pi-launch.ts +358 -0
  74. package/scripts/check-release-gate-outcomes.ts +19 -0
  75. package/scripts/check-typing-call-fence.ts +296 -0
  76. package/scripts/codex-birth-doctor.sh +159 -16
  77. package/scripts/codex-birth-install.sh +178 -72
  78. package/scripts/codex-birth-uninstall.sh +162 -7
  79. package/scripts/fixtures/herdr-supply.json +29 -0
  80. package/scripts/herdr-activation.mjs +536 -0
  81. package/scripts/herdr-plugin-activate.mjs +270 -0
  82. package/scripts/herdr-plugin-deactivate.mjs +193 -0
  83. package/scripts/herdr-runtime.mjs +1203 -0
  84. package/scripts/install-herdr-ci.sh +96 -0
  85. package/scripts/inventory-verification-surface.ts +86 -10
  86. package/scripts/lib/codex-fresh-live-protocol.ts +2 -2
  87. package/scripts/lib/tree-digest.ts +97 -0
  88. package/scripts/meta-bridge-install.sh +19 -2
  89. package/scripts/meta-bridge-state.py +167 -11
  90. package/scripts/mutants/codex-caller-seat.json +6 -8
  91. package/scripts/mutants/codex-native.json +195 -29
  92. package/scripts/mutants/control-socket-disconnect.json +57 -0
  93. package/scripts/mutants/fresh-call-dispatch.json +154 -0
  94. package/scripts/mutants/herdr-activation.json +305 -0
  95. package/scripts/mutants/herdr-fresh-call.json +448 -0
  96. package/scripts/mutants/herdr-placement.json +161 -0
  97. package/scripts/mutants/herdr-plugin-build.json +160 -0
  98. package/scripts/mutants/herdr-plugin-profile.json +202 -0
  99. package/scripts/mutants/herdr-plugin.json +133 -0
  100. package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
  101. package/scripts/mutants/herdr-supply.json +133 -0
  102. package/scripts/mutants/meta-hook-session-switch.json +2 -2
  103. package/scripts/mutants/mux-fresh-call.json +104 -17
  104. package/scripts/mutants/mux-launcher-fence.json +13 -0
  105. package/scripts/mutants/omp-fresh.json +4 -4
  106. package/scripts/mutants/peer-facts.json +98 -0
  107. package/scripts/mutants/pi-launch.json +113 -0
  108. package/scripts/mutants/resume-args.json +6 -6
  109. package/scripts/mutants/self-address.json +33 -0
  110. package/scripts/mutants/typing-call-fence.json +17 -0
  111. package/scripts/mutants/v2-surface.json +2 -2
  112. package/scripts/mutants/v2-visible-resume.json +17 -17
  113. package/scripts/peer-facts.ts +120 -0
  114. package/scripts/register-pi-package.py +8 -0
  115. package/scripts/register-pi-provider.py +88 -12
  116. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  117. package/scripts/smoke-codex-birth.sh +281 -12
  118. package/scripts/smoke-entwurf-v2-matrix-live.ts +1 -1
  119. package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
  120. package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
  121. package/scripts/smoke-herdr-raw-install-live.sh +849 -0
  122. package/scripts/tsconfig.json +5 -0
  123. package/scripts/check-entwurf-facts.ts +0 -385
  124. package/scripts/check-entwurf-resume-args.ts +0 -122
  125. package/scripts/check-entwurf-v2-decider.ts +0 -657
  126. package/scripts/check-entwurf-v2-lock.ts +0 -371
  127. package/scripts/check-entwurf-v2-mailbox.ts +0 -261
  128. package/scripts/check-entwurf-v2-matrix.ts +0 -401
  129. package/scripts/check-entwurf-v2-native-push.ts +0 -221
  130. package/scripts/check-entwurf-v2-release.ts +0 -151
  131. package/scripts/check-entwurf-v2-runner.ts +0 -448
  132. package/scripts/check-entwurf-v2-send-fallback.ts +0 -350
  133. package/scripts/check-entwurf-v2-send.ts +0 -458
  134. package/scripts/check-entwurf-v2-visible-resume.ts +0 -445
  135. package/scripts/check-socket-discovery.ts +0 -401
@@ -0,0 +1,448 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "herdr-fresh-call",
4
+ "mutants": [
5
+ {
6
+ "claim": "HFC-RAIL-IMPORT-FENCE",
7
+ "title": "the herdr rail takes a mux import again — one is all it takes to make either rail undeletable",
8
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
9
+ "find": ["import { createHash } from \"node:crypto\";"],
10
+ "replace": ["import { createHash } from \"node:crypto\";\nimport { runTmux } from \"./mux-placement.ts\";"],
11
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
12
+ "timeoutSeconds": 120,
13
+ "signature": "[QK:HFC-RAIL-IMPORT-FENCE]",
14
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
15
+ },
16
+ {
17
+ "claim": "HFC-PILOT-CLOSED",
18
+ "title": "a backend nobody measured on herdr is admitted to the pilot, so a caller gets a pane running something this rail never proved it can open",
19
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
20
+ "find": ["export const HERDR_FRESH_CALL_BACKENDS = [\"pi\", \"claude-code\"] as const;"],
21
+ "replace": ["export const HERDR_FRESH_CALL_BACKENDS = [\"pi\", \"claude-code\", \"codex\"] as const;"],
22
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
23
+ "timeoutSeconds": 120,
24
+ "signature": "[QK:HFC-PILOT-CLOSED]",
25
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
26
+ },
27
+ {
28
+ "claim": "HFC-TMUX-PLACEMENT-REFUSED",
29
+ "title": "a tmux seat inside herdr is silently ignored instead of refused, so the sibling opens somewhere the caller did not ask for and the call still looks successful",
30
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
31
+ "find": ["\treturn placement === undefined ? null : \"herdr-placement-tmux-rejected\";"],
32
+ "replace": [
33
+ "\treturn placement === undefined || placement.tmuxSession === undefined || placement.tmuxSession.length === 0\n\t\t? null\n\t\t: \"herdr-placement-tmux-rejected\";"
34
+ ],
35
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
36
+ "timeoutSeconds": 120,
37
+ "signature": "[QK:HFC-TMUX-PLACEMENT-REFUSED]",
38
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
39
+ },
40
+ {
41
+ "claim": "HFC-ENCODE-ROUNDTRIP",
42
+ "title": "the encoder normalises the caller's task bytes before quoting them, so the sibling is born with a task nobody wrote",
43
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
44
+ "find": ["\tconst literal = escapeRemainingControlChars(JSON.stringify(task));"],
45
+ "replace": ["\tconst literal = escapeRemainingControlChars(JSON.stringify(task.replace(/\\t/g, \" \")));"],
46
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
47
+ "timeoutSeconds": 120,
48
+ "signature": "[QK:HFC-ENCODE-ROUNDTRIP]",
49
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
50
+ },
51
+ {
52
+ "claim": "HFC-ENCODE-NO-CONTROL",
53
+ "title": "the C1/DEL escape pass is dropped and only JSON.stringify's C0 handling remains — herdr refuses the argv after the pane already exists",
54
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
55
+ "find": ["\tconst literal = escapeRemainingControlChars(JSON.stringify(task));"],
56
+ "replace": ["\tconst literal = JSON.stringify(task);"],
57
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
58
+ "timeoutSeconds": 120,
59
+ "signature": "[QK:HFC-ENCODE-NO-CONTROL]",
60
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
61
+ },
62
+ {
63
+ "claim": "HFC-ENCODE-MAX-TASK",
64
+ "title": "the encoded literal is truncated past a length nobody documented, so a long task silently loses its tail",
65
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
66
+ "find": ["\tconst argv = `${folded} ${HERDR_TASK_LITERAL_INSTRUCTION}${literal}`;"],
67
+ "replace": ["\tconst argv = `${folded} ${HERDR_TASK_LITERAL_INSTRUCTION}${literal.slice(0, 4096)}`;"],
68
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
69
+ "timeoutSeconds": 120,
70
+ "signature": "[QK:HFC-ENCODE-MAX-TASK]",
71
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
72
+ },
73
+ {
74
+ "claim": "HFC-FRAMING-IS-PROSE",
75
+ "title": "the framing is quoted back into a JSON literal behind a decode instruction — the exact shape a safety-tuned sibling refused, restored",
76
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
77
+ "find": ["\tconst folded = framing.filter((line) => line.length > 0).join(\" \");"],
78
+ "replace": [
79
+ "\tconst folded = `Decode the following JSON string literal and follow the decoded instructions exactly as if they were this message: ${JSON.stringify(framing.join(\"\\n\"))}`;"
80
+ ],
81
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
82
+ "timeoutSeconds": 120,
83
+ "signature": "[QK:HFC-FRAMING-IS-PROSE]",
84
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
85
+ },
86
+ {
87
+ "claim": "HFC-FRAMING-CONTROL-REFUSED",
88
+ "title": "a control character in the rail's own framing is escaped into the prose instead of refused, so a sibling reads an escape sequence as text",
89
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
90
+ "find": ["\tconst folded = framing.filter((line) => line.length > 0).join(\" \");"],
91
+ "replace": [
92
+ "\tconst folded = escapeRemainingControlChars(framing.filter((line) => line.length > 0).join(\" \"));"
93
+ ],
94
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
95
+ "timeoutSeconds": 120,
96
+ "signature": "[QK:HFC-FRAMING-CONTROL-REFUSED]",
97
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
98
+ },
99
+ {
100
+ "claim": "HFC-PREMUTATION-ONLY",
101
+ "title": "a pre-mutation input refusal is dropped, so a call that should have refused instead splits a pane in the operator's workspace first",
102
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
103
+ "find": [
104
+ "\t\tconst badCwd = classifyHerdrCwd(cwd);\n\t\tif (badCwd !== null) return { ok: false, reason: badCwd };"
105
+ ],
106
+ "replace": ["\t\tvoid classifyHerdrCwd(cwd);"],
107
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
108
+ "timeoutSeconds": 120,
109
+ "signature": "[QK:HFC-PREMUTATION-ONLY]",
110
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
111
+ },
112
+ {
113
+ "claim": "HFC-CALLER-WORKSPACE",
114
+ "title": "the caller's workspace is dropped and herdr defaults --workspace to whatever is focused, so the sibling opens in a workspace nobody asked for under a green receipt",
115
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
116
+ "find": [
117
+ "\tif (callerPane.workspaceId === undefined) return { ok: false, reason: \"herdr-caller-workspace-missing\" };\n"
118
+ ],
119
+ "replace": [""],
120
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
121
+ "timeoutSeconds": 120,
122
+ "signature": "[QK:HFC-CALLER-WORKSPACE]",
123
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
124
+ },
125
+ {
126
+ "claim": "HFC-CALLER-PANE-BOUND",
127
+ "title": "the caller pane-get answer is no longer bound to the pane we asked about, so a readable reply describing ANOTHER pane places the sibling in a workspace nobody named, under a green receipt",
128
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
129
+ "find": [
130
+ "\tif (callerPane.paneId !== context.context.callerPaneId) return { ok: false, reason: \"herdr-caller-pane-drift\" };\n"
131
+ ],
132
+ "replace": [""],
133
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
134
+ "timeoutSeconds": 120,
135
+ "signature": "[QK:HFC-CALLER-PANE-BOUND]",
136
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
137
+ },
138
+ {
139
+ "claim": "HFC-TAB-ARGV",
140
+ "title": "the new tab steals focus from the operator who was typing when the sibling opened",
141
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
142
+ "find": ["\t\t\"--no-focus\",\n"],
143
+ "replace": [""],
144
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
145
+ "timeoutSeconds": 120,
146
+ "signature": "[QK:HFC-TAB-ARGV]",
147
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
148
+ },
149
+ {
150
+ "claim": "HFC-START-ARGV",
151
+ "title": "the backend argv loses its `--` separator, so herdr reads the sibling's prompt as its own flags",
152
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
153
+ "find": ["\t\tString(HERDR_START_READY_MS),\n\t\t\"--\",\n\t\t...params.backendArgs,"],
154
+ "replace": ["\t\tString(HERDR_START_READY_MS),\n\t\t...params.backendArgs,"],
155
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
156
+ "timeoutSeconds": 120,
157
+ "signature": "[QK:HFC-START-ARGV]",
158
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
159
+ },
160
+ {
161
+ "claim": "HFC-START-READY-BOUND",
162
+ "title": "the start stops telling herdr how long to wait, so herdr falls back to its own 30s default and a cold runtime start comes back as a named timeout over a pane holding a healthy sibling",
163
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
164
+ "find": ["\t\t\"--timeout\",\n\t\tString(HERDR_START_READY_MS),\n"],
165
+ "replace": [""],
166
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
167
+ "timeoutSeconds": 120,
168
+ "signature": "[QK:HFC-START-READY-BOUND]",
169
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
170
+ },
171
+ {
172
+ "claim": "HFC-AGENT-NAME-OPAQUE",
173
+ "title": "the raw nonce becomes the herdr agent name, which herdr's own name rule refuses — and a correlation tag becomes a visible label",
174
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
175
+ "find": ["\treturn `entwurf-${createHash(\"sha256\").update(nonce).digest(\"hex\").slice(0, 20)}`;"],
176
+ "replace": ["\treturn nonce;"],
177
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
178
+ "timeoutSeconds": 120,
179
+ "signature": "[QK:HFC-AGENT-NAME-OPAQUE]",
180
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
181
+ },
182
+ {
183
+ "claim": "HFC-PARSE-STRICT",
184
+ "title": "a pane reply missing its terminal id is half-believed, so the launch carries a coordinate it never received",
185
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
186
+ "find": ["\tif (typeof terminalId !== \"string\" || terminalId.length === 0) return null;"],
187
+ "replace": ["\tif (typeof terminalId !== \"string\") return null;"],
188
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
189
+ "timeoutSeconds": 120,
190
+ "signature": "[QK:HFC-PARSE-STRICT]",
191
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
192
+ },
193
+ {
194
+ "claim": "HFC-TAB-HALVES-AGREE",
195
+ "title": "the tab-create reply's two halves are bound only when they happen to agree in one field, so a contradictory reply is assembled into a green launch whose receipt names a tab the agent did not start in",
196
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
197
+ "find": [
198
+ "\tif (rootPane.tabId !== tabId) return null;\n\tif (rootPane.workspaceId !== workspaceId) return null;"
199
+ ],
200
+ "replace": ["\tif (rootPane.tabId !== undefined && rootPane.tabId !== tabId) return null;"],
201
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
202
+ "timeoutSeconds": 120,
203
+ "signature": "[QK:HFC-TAB-HALVES-AGREE]",
204
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
205
+ },
206
+ {
207
+ "claim": "HFC-PANE-ID-OPAQUE",
208
+ "title": "the pane id is normalised on the way in, so herdr's own `pA` coordinate stops matching the one herdr will answer about",
209
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
210
+ "find": ["\t\tpaneId,\n\t\tterminalId,"],
211
+ "replace": ["\t\tpaneId: paneId.toLowerCase(),\n\t\tterminalId,"],
212
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
213
+ "timeoutSeconds": 120,
214
+ "signature": "[QK:HFC-PANE-ID-OPAQUE]",
215
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
216
+ },
217
+ {
218
+ "claim": "HFC-RECEIPT-NO-ADDRESS",
219
+ "title": "the launch receipt starts carrying an identity field, which is the first step in promoting a herdr view to a garden address",
220
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
221
+ "find": ["\t\t\therdrPaneId: started.pane.paneId,"],
222
+ "replace": ["\t\t\tnativeSessionId: started.pane.terminalId,\n\t\t\therdrPaneId: started.pane.paneId,"],
223
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
224
+ "timeoutSeconds": 120,
225
+ "signature": "[QK:HFC-RECEIPT-NO-ADDRESS]",
226
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
227
+ },
228
+ {
229
+ "claim": "HFC-CLOSE-WITHIN-GENERATION",
230
+ "title": "the close drops its terminal-id proof and trusts a bare pane id — after a herdr restart that id belongs to someone else's view",
231
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
232
+ "find": [
233
+ "\tif (current.terminalId !== created.terminalId) return { close: false, reason: \"terminal-id-mismatch\" };"
234
+ ],
235
+ "replace": ["\tvoid created.terminalId;"],
236
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
237
+ "timeoutSeconds": 120,
238
+ "signature": "[QK:HFC-CLOSE-WITHIN-GENERATION]",
239
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
240
+ },
241
+ {
242
+ "claim": "HFC-ORPHAN-NAMED",
243
+ "title": "two different reclaim failures collapse into one name, so the operator cannot tell a wrong pane from an unreadable one",
244
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
245
+ "find": ["\tif (current.paneId !== created.paneId) return { close: false, reason: \"pane-id-mismatch\" };"],
246
+ "replace": ["\tif (current.paneId !== created.paneId) return { close: false, reason: \"pane-get-failed\" };"],
247
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
248
+ "timeoutSeconds": 120,
249
+ "signature": "[QK:HFC-ORPHAN-NAMED]",
250
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
251
+ },
252
+ {
253
+ "claim": "HFC-INPUT-PARITY",
254
+ "title": "the task cap is dropped on the herdr rail, so the same oversized task is refused on one rail and launched on the other",
255
+ "subject": "pi-extensions/lib/fresh-call-composition.ts",
256
+ "find": ["\tif (task.length > TASK_MAX_CHARS) return { ok: false, reason: \"task-too-long\" };"],
257
+ "replace": [""],
258
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
259
+ "timeoutSeconds": 120,
260
+ "signature": "[QK:HFC-INPUT-PARITY]",
261
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
262
+ },
263
+ {
264
+ "claim": "HFC-CWD-EMPTY-IS-OMITTED",
265
+ "title": "an empty cwd is read as a path again, turning the public verb's documented no-op into an invalid-directory refusal",
266
+ "subject": "pi-extensions/lib/fresh-call-composition.ts",
267
+ "find": ["\tconst cwd = params.cwd === undefined || params.cwd === \"\" ? undefined : params.cwd;"],
268
+ "replace": ["\tconst cwd = params.cwd;"],
269
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
270
+ "timeoutSeconds": 120,
271
+ "signature": "[QK:HFC-CWD-EMPTY-IS-OMITTED]",
272
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
273
+ },
274
+ {
275
+ "claim": "HFC-TAB-OCCUPIED",
276
+ "title": "a brand-new tab whose initial pane already holds an agent is started into anyway, landing beside somebody else's sibling",
277
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
278
+ "find": ["\tif (pane.hasAgentSession) {"],
279
+ "replace": ["\tif (false) {"],
280
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
281
+ "timeoutSeconds": 120,
282
+ "signature": "[QK:HFC-TAB-OCCUPIED]",
283
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
284
+ },
285
+ {
286
+ "claim": "HFC-CREATE-OUTCOME-HONEST",
287
+ "title": "a tab create cut off by OUR OWN timeout is reported as `nothing was created`, sending the operator away from a tab that may be sitting on their screen",
288
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
289
+ "find": ["\t\t\trecovery: code === undefined ? indeterminate() : nothingCreated(),"],
290
+ "replace": ["\t\t\trecovery: nothingCreated(),"],
291
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
292
+ "timeoutSeconds": 120,
293
+ "signature": "[QK:HFC-CREATE-OUTCOME-HONEST]",
294
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
295
+ },
296
+ {
297
+ "claim": "HFC-START-PANE-BINDING",
298
+ "title": "a start reporting another pane, terminal or TAB is accepted as success, so the receipt points at a coordinate that never held the sibling",
299
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
300
+ "find": [
301
+ "\tif (\n\t\tstarted.pane.paneId !== pane.paneId ||\n\t\tstarted.pane.terminalId !== pane.terminalId ||\n\t\t(started.pane.tabId !== undefined && started.pane.tabId !== tab.tabId)\n\t) {"
302
+ ],
303
+ "replace": ["\tif (false) {"],
304
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
305
+ "timeoutSeconds": 120,
306
+ "signature": "[QK:HFC-START-PANE-BINDING]",
307
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
308
+ },
309
+ {
310
+ "claim": "HFC-WITNESS-SETTLED-BY-READ",
311
+ "title": "the settle window stops believing a session that arrives on a later read, so every launch whose start reply was early reports an unavailable witness it did not have to",
312
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
313
+ "find": [
314
+ "\t\t\tif (agent.hasAgentSession) return { ok: true, witness: { state: \"reported\", reads, settleMs: elapsed } };"
315
+ ],
316
+ "replace": ["\t\t\tif (false) return { ok: true, witness: { state: \"reported\", reads, settleMs: elapsed } };"],
317
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
318
+ "timeoutSeconds": 120,
319
+ "signature": "[QK:HFC-WITNESS-SETTLED-BY-READ]",
320
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
321
+ },
322
+ {
323
+ "claim": "HFC-WITNESS-ABSENCE-KEEPS",
324
+ "title": "the settle window expiring becomes a launch FAILURE again, which is the shape that closed a pane holding a live Claude Code child",
325
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
326
+ "find": [
327
+ "\t\tif (elapsed >= settleMs) {\n\t\t\t// The two expiry words are the whole point of `observed`: we may only say the agent is\n\t\t\t// still in our pane if a readable reply said so.\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\twitness: { state: observed ? \"unavailable\" : \"unobserved\", reads, settleMs: elapsed },\n\t\t\t};\n\t\t}"
328
+ ],
329
+ "replace": ["\t\tif (elapsed >= settleMs) {\n\t\t\treturn { ok: false, reads };\n\t\t}"],
330
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
331
+ "timeoutSeconds": 120,
332
+ "signature": "[QK:HFC-WITNESS-ABSENCE-KEEPS]",
333
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
334
+ },
335
+ {
336
+ "claim": "HFC-WITNESS-DRIFT-VANISHED",
337
+ "title": "a readable reply about a different agent stops being a named failure, so a launch that drifted waits out its window and reports a healthy unavailable witness",
338
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
339
+ "find": ["\t\t\tif (drifted) return { ok: false, reads };"],
340
+ "replace": ["\t\t\tif (false) return { ok: false, reads };"],
341
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
342
+ "timeoutSeconds": 120,
343
+ "signature": "[QK:HFC-WITNESS-DRIFT-VANISHED]",
344
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
345
+ },
346
+ {
347
+ "claim": "HFC-CLOSE-REFUSES-OCCUPIED",
348
+ "title": "the reclaim stops reading occupancy and judges an empty pane by the session id alone, so a pane holding an agent nobody has reported yet is closable again",
349
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
350
+ "find": ["\tif (current.agent !== undefined) return { close: false, reason: \"agent-present\" };"],
351
+ "replace": [""],
352
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
353
+ "timeoutSeconds": 120,
354
+ "signature": "[QK:HFC-CLOSE-REFUSES-OCCUPIED]",
355
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
356
+ },
357
+ {
358
+ "claim": "HFC-START-ARGV-FIDELITY",
359
+ "title": "the echoed argv is no longer checked, so a sibling started with a framing we never composed passes as ours",
360
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
361
+ "find": ["\tif (!argvMatchesRequest(started.argv, backend, backendArgs)) {"],
362
+ "replace": ["\tif (false) {"],
363
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
364
+ "timeoutSeconds": 120,
365
+ "signature": "[QK:HFC-START-ARGV-FIDELITY]",
366
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
367
+ },
368
+ {
369
+ "claim": "HERDR-RUNNER-NONBLOCKING",
370
+ "title": "a synchronous wait is reintroduced on the caller thread, so a herdr call in flight holds the event loop and the sibling first callback cannot be read",
371
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
372
+ "find": ["\t\treturn new Promise((resolve) => {"],
373
+ "replace": [
374
+ "\t\treturn new Promise((resolve) => {",
375
+ "\t\t\tAtomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000);"
376
+ ],
377
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
378
+ "timeoutSeconds": 120,
379
+ "signature": "[QK:HERDR-RUNNER-NONBLOCKING]",
380
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
381
+ },
382
+ {
383
+ "claim": "HERDR-RUNNER-TIMEOUT-KILLS",
384
+ "title": "the bound stops waiting without killing the child, so a child keeps its pane after we reported the launch gone",
385
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
386
+ "find": ["\t\t\tconst timer = setTimeout(() => {", "\t\t\t\tchild.kill(\"SIGKILL\");"],
387
+ "replace": ["\t\t\tconst timer = setTimeout(() => {"],
388
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
389
+ "timeoutSeconds": 120,
390
+ "signature": "[QK:HERDR-RUNNER-TIMEOUT-KILLS]",
391
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
392
+ },
393
+ {
394
+ "claim": "HERDR-RUNNER-OUTPUT-BOUNDED",
395
+ "title": "the output cap is no longer enforced, so a runaway child is buffered without limit into the caller memory",
396
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
397
+ "find": ["\t\t\t\tif (stdout.length + stderr.length <= bounds.maxOutputBytes) return;"],
398
+ "replace": ["\t\t\t\treturn;"],
399
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
400
+ "timeoutSeconds": 120,
401
+ "signature": "[QK:HERDR-RUNNER-OUTPUT-BOUNDED]",
402
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
403
+ },
404
+ {
405
+ "claim": "HFC-CWD-HASH-IS-DATA",
406
+ "title": "the herdr rail absorbs the tmux rails format-token refusal, so an ordinary directory whose name contains a hash is rejected on a rail that has no format expansion to protect against",
407
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
408
+ "find": ["\tif (!path.isAbsolute(cwd)) return \"cwd-not-absolute\";"],
409
+ "replace": [
410
+ "\tif (!path.isAbsolute(cwd)) return \"cwd-not-absolute\";\n\tif (cwd.includes(\"#\")) return \"cwd-not-directory\";"
411
+ ],
412
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
413
+ "timeoutSeconds": 120,
414
+ "signature": "[QK:HFC-CWD-HASH-IS-DATA]",
415
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
416
+ },
417
+ {
418
+ "claim": "HFC-WITNESS-UNREAD-SAYS-NOTHING",
419
+ "title": "a launch whose every witness read failed borrows the sentence that says herdr still showed the agent in our pane — the receipt then asserts a current liveness nobody confirmed, and contradicts its own closing paragraph while doing it",
420
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
421
+ "find": [
422
+ "\t\t\treturn `unobserved after ${cost} — no readable answer about this agent ever came back, so this receipt says nothing about whether the sibling is running. Nothing was reclaimed on that silence: an unreadable read is not evidence that a sibling is gone. Its peers placement column will read unobserved, and its garden id still arrives in the callback`;"
423
+ ],
424
+ "replace": [
425
+ "\t\t\treturn `unavailable after ${cost} — herdr DID still show this agent in the pane we opened when we last read it`;"
426
+ ],
427
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
428
+ "timeoutSeconds": 120,
429
+ "signature": "[QK:HFC-WITNESS-UNREAD-SAYS-NOTHING]",
430
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
431
+ },
432
+ {
433
+ "claim": "HFC-WITNESS-BINDING-COMPLETE",
434
+ "title": "a half-bound reply — one that omits tab, name or kind — is counted as having seen our sibling, so the exact-rebinding claim in the comment stops being true of the code and an expiry says `unavailable` on the strength of an answer that never named our pane's agent",
435
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
436
+ "find": [
437
+ "\t\t\t\tagent.tabId === params.tabId &&\n\t\t\t\tagent.agentName === params.agentName &&\n\t\t\t\tagent.agent === params.kind;"
438
+ ],
439
+ "replace": [
440
+ "\t\t\t\t(agent.tabId === undefined || agent.tabId === params.tabId) &&\n\t\t\t\t(agent.agentName === undefined || agent.agentName === params.agentName) &&\n\t\t\t\t(agent.agent === undefined || agent.agent === params.kind);"
441
+ ],
442
+ "gate": ["bash", "run.sh", "check-herdr-fresh-call"],
443
+ "timeoutSeconds": 120,
444
+ "signature": "[QK:HFC-WITNESS-BINDING-COMPLETE]",
445
+ "signatureSource": "scripts/check-herdr-fresh-call.ts"
446
+ }
447
+ ]
448
+ }
@@ -0,0 +1,161 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "herdr-placement",
4
+ "mutants": [
5
+ {
6
+ "claim": "HP-UNREADABLE-IS-UNOBSERVED",
7
+ "title": "an unreadable herdr payload answered with an EMPTY row list turns 'nobody looked' into 'herdr was read and has none of your citizens' \u2014 a fabricated measurement about a read that did not happen (#116 S1)",
8
+ "subject": "pi-extensions/lib/herdr-placement.ts",
9
+ "find": ["\tif (!Array.isArray(panes)) return null;"],
10
+ "replace": ["\tif (!Array.isArray(panes)) return [];"],
11
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
12
+ "timeoutSeconds": 60,
13
+ "signature": "[QK:HP-UNREADABLE-IS-UNOBSERVED]",
14
+ "signatureSource": "scripts/check-herdr-placement.ts"
15
+ },
16
+ {
17
+ "claim": "HP-JOIN-CLAUDE-EXACT",
18
+ "title": "joining the claude axis on a PREFIX instead of the whole value lets one citizen attach to another citizen's pane \u2014 the exact-key discipline that makes this evidence rather than a guess (#116 S1)",
19
+ "subject": "pi-extensions/lib/herdr-placement.ts",
20
+ "find": [
21
+ "\tconst nativeSessionId = official.kind === \"id\" ? row.sessionValue : piNativeSessionIdFromPath(row.sessionValue);"
22
+ ],
23
+ "replace": [
24
+ "\tconst nativeSessionId =\n\t\tofficial.kind === \"id\" ? row.sessionValue.slice(0, 8) : piNativeSessionIdFromPath(row.sessionValue);"
25
+ ],
26
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
27
+ "timeoutSeconds": 60,
28
+ "signature": "[QK:HP-JOIN-CLAUDE-EXACT]",
29
+ "signatureSource": "scripts/check-herdr-placement.ts"
30
+ },
31
+ {
32
+ "claim": "HP-JOIN-PI-FILENAME",
33
+ "title": "accepting any filename tail as the pi native session id drops the uuid shape check, so a renamed or foreign .jsonl joins a citizen to the wrong pane (#116 S1)",
34
+ "subject": "pi-extensions/lib/herdr-placement.ts",
35
+ "find": ["\treturn PI_SESSION_UUID.test(candidate) ? candidate : null;"],
36
+ "replace": ["\treturn candidate;"],
37
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
38
+ "timeoutSeconds": 60,
39
+ "signature": "[QK:HP-JOIN-PI-FILENAME]",
40
+ "signatureSource": "scripts/check-herdr-placement.ts"
41
+ },
42
+ {
43
+ "claim": "HP-JOIN-NEVER-GUESSES",
44
+ "title": "coercing an UNMEASURED agent_session kind into the claude rule invents a join rule for a herdr version nobody here has run (#116 S1)",
45
+ "subject": "pi-extensions/lib/herdr-placement.ts",
46
+ "find": ["\tif (row.sessionKind !== official.kind) return null;"],
47
+ "replace": ["\tif (false && row.sessionKind !== official.kind) return null;"],
48
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
49
+ "timeoutSeconds": 60,
50
+ "signature": "[QK:HP-JOIN-NEVER-GUESSES]",
51
+ "signatureSource": "scripts/check-herdr-placement.ts"
52
+ },
53
+ {
54
+ "claim": "HP-AMBIGUOUS-NOT-A-PANE",
55
+ "title": "letting the FIRST pane win when two claim one native session id settles an ambiguity by arrival order and prints a guess as a placement (#116 S1)",
56
+ "subject": "pi-extensions/lib/herdr-placement.ts",
57
+ "find": ["\t\t\tbyNativeSessionId.delete(key);"],
58
+ "replace": ["\t\t\tambiguous.delete(key);"],
59
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
60
+ "timeoutSeconds": 60,
61
+ "signature": "[QK:HP-AMBIGUOUS-NOT-A-PANE]",
62
+ "signatureSource": "scripts/check-herdr-placement.ts"
63
+ },
64
+ {
65
+ "claim": "HP-NO-HERDR-IS-UNOBSERVED",
66
+ "title": "answering a host with NO placement owner with `none` claims a measurement that never ran \u2014 the blank-filling docs/mux-launch-rail.md 7 refuses (#116 S1)",
67
+ "subject": "pi-extensions/lib/herdr-placement.ts",
68
+ "find": ["\tif (index === null) return UNOBSERVED_PLACEMENT;"],
69
+ "replace": ["\tif (index === null) return NO_PLACEMENT;"],
70
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
71
+ "timeoutSeconds": 60,
72
+ "signature": "[QK:HP-NO-HERDR-IS-UNOBSERVED]",
73
+ "signatureSource": "scripts/check-herdr-placement.ts"
74
+ },
75
+ {
76
+ "claim": "HP-ONE-READ-PER-LISTING",
77
+ "title": "moving the placement read INSIDE the per-citizen observer spawns one placement-owner call per record and is the first half of a discovery watcher (#116 S1)",
78
+ "subject": "pi-extensions/lib/entwurf-fact-provider.ts",
79
+ "find": [
80
+ "\tconst observe = deps.observe ?? makeObservePeerFacts((deps.readPlacementIndex ?? readHerdrPlacementIndex)());"
81
+ ],
82
+ "replace": [
83
+ "\tconst observe =\n\t\tdeps.observe ??\n\t\t((identity) => makeObservePeerFacts((deps.readPlacementIndex ?? readHerdrPlacementIndex)())(identity));"
84
+ ],
85
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
86
+ "timeoutSeconds": 60,
87
+ "signature": "[QK:HP-ONE-READ-PER-LISTING]",
88
+ "signatureSource": "scripts/check-herdr-placement.ts"
89
+ },
90
+ {
91
+ "claim": "HP-PLACEMENT-NOT-DISPATCH",
92
+ "title": "letting a dispatch module SEE the placement axis is how a pane that is one server restart from being false reaches a routing decision (Hard Rule 16, #116 S1)",
93
+ "subject": "pi-extensions/lib/entwurf-v2-production.ts",
94
+ "find": ["import { isOutOfSocketDomainGardenIdConflict } from \"./entwurf-facts.ts\";"],
95
+ "replace": [
96
+ "import { isOutOfSocketDomainGardenIdConflict } from \"./entwurf-facts.ts\";\nimport type { PlacementObservation } from \"./herdr-placement.ts\";"
97
+ ],
98
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
99
+ "timeoutSeconds": 60,
100
+ "signature": "[QK:HP-PLACEMENT-NOT-DISPATCH]",
101
+ "signatureSource": "scripts/check-herdr-placement.ts"
102
+ },
103
+ {
104
+ "claim": "HP-JOIN-OFFICIAL-ONLY",
105
+ "title": "reading a session report without checking WHO reported it lets a third-party integration stand in for the placement owner \u2014 and that the placement owner reported it is the whole reason mux-launch-rail section 7 admits this join as exact evidence (#116 S1)",
106
+ "subject": "pi-extensions/lib/herdr-placement.ts",
107
+ "find": ["\tif (row.agent !== official.agent) return null;"],
108
+ "replace": ["\tif (false && row.agent !== official.agent) return null;"],
109
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
110
+ "timeoutSeconds": 60,
111
+ "signature": "[QK:HP-JOIN-OFFICIAL-ONLY]",
112
+ "signatureSource": "scripts/check-herdr-placement.ts"
113
+ },
114
+ {
115
+ "claim": "HP-JOIN-BACKEND-BOUND",
116
+ "title": "handing a pane to a citizen whose backend the reporting integration does not speak for turns a native-id collision into a confident wrong placement (#116 S1)",
117
+ "subject": "pi-extensions/lib/herdr-placement.ts",
118
+ "find": [
119
+ "\t\treturn entry.backend === identity.backend ? { kind: \"herdr-pane\", paneId: entry.paneId } : UNOBSERVED_PLACEMENT;"
120
+ ],
121
+ "replace": ["\t\treturn { kind: \"herdr-pane\", paneId: entry.paneId };"],
122
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
123
+ "timeoutSeconds": 60,
124
+ "signature": "[QK:HP-JOIN-BACKEND-BOUND]",
125
+ "signatureSource": "scripts/check-herdr-placement.ts"
126
+ },
127
+ {
128
+ "claim": "HP-DECLINED-IS-UNOBSERVED",
129
+ "title": "reporting none after declining to read part of the listing claims a complete negative read that never happened \u2014 the report we refused might have been that citizen's (#116 S1)",
130
+ "subject": "pi-extensions/lib/herdr-placement.ts",
131
+ "find": ["\treturn index.declinedReports > 0 ? UNOBSERVED_PLACEMENT : NO_PLACEMENT;"],
132
+ "replace": ["\treturn NO_PLACEMENT;"],
133
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
134
+ "timeoutSeconds": 60,
135
+ "signature": "[QK:HP-DECLINED-IS-UNOBSERVED]",
136
+ "signatureSource": "scripts/check-herdr-placement.ts"
137
+ },
138
+ {
139
+ "claim": "HP-READ-ENV-GATED-NULL-NEVER-EMPTY",
140
+ "title": "the placement read stops requiring herdr's own env fact by value, so a host that merely has the variable set spawns a binary this process was never told to use",
141
+ "subject": "pi-extensions/lib/entwurf-peer-observe.ts",
142
+ "find": ["\tif (env.HERDR_ENV !== \"1\") return null;"],
143
+ "replace": ["\tif (env.HERDR_ENV === \"0\") return null;"],
144
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
145
+ "timeoutSeconds": 120,
146
+ "signature": "[QK:HP-READ-ENV-GATED-NULL-NEVER-EMPTY]",
147
+ "signatureSource": "scripts/check-herdr-placement.ts"
148
+ },
149
+ {
150
+ "claim": "HP-READ-TIMEOUT-BOUNDED",
151
+ "title": "the one placement read loses its bound, so a placement owner that never answers hangs the whole peer listing behind a column that was only ever a convenience",
152
+ "subject": "pi-extensions/lib/entwurf-peer-observe.ts",
153
+ "find": ["\t\t\ttimeout: HERDR_READ_TIMEOUT_MS,"],
154
+ "replace": [""],
155
+ "gate": ["bash", "run.sh", "check-herdr-placement"],
156
+ "timeoutSeconds": 120,
157
+ "signature": "[QK:HP-READ-TIMEOUT-BOUNDED]",
158
+ "signatureSource": "scripts/check-herdr-placement.ts"
159
+ }
160
+ ]
161
+ }