@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,57 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "control-socket-disconnect",
4
+ "mutants": [
5
+ {
6
+ "claim": "CONTROL-SOCKET-NO-UNCAUGHT",
7
+ "title": "the accepted connection carries no disconnect policy, so a peer that hangs up mid-exchange reaches the process as an uncaught exception and kills the resident session",
8
+ "subject": "pi-extensions/lib/entwurf-control-rpc.ts",
9
+ "find": [
10
+ "\tsocket.on(\"error\", (error: NodeJS.ErrnoException) => {",
11
+ "\t\tif (isPeerDisconnect(error)) return;",
12
+ "\t\tdiagnose(`[entwurf-control] control socket error (${error.code ?? \"no code\"}): ${error.message}`);",
13
+ "\t});"
14
+ ],
15
+ "replace": ["\t// the accepted connection no longer carries a disconnect policy"],
16
+ "gate": ["bash", "run.sh", "check-entwurf-control-rpc"],
17
+ "timeoutSeconds": 90,
18
+ "signature": "[QK:CONTROL-SOCKET-NO-UNCAUGHT]",
19
+ "signatureSource": "scripts/check-entwurf-control-rpc.ts"
20
+ },
21
+ {
22
+ "claim": "CONTROL-SOCKET-ABSORBS-PEER-DISCONNECT",
23
+ "title": "a peer that went away is diagnosed instead of absorbed, so ordinary client timeouts spray the resident session with stderr about someone else state",
24
+ "subject": "pi-extensions/lib/entwurf-control-rpc.ts",
25
+ "find": ["\t\tif (isPeerDisconnect(error)) return;"],
26
+ "replace": ["\t\t// a vanished peer is no longer absorbed"],
27
+ "gate": ["bash", "run.sh", "check-entwurf-control-rpc"],
28
+ "timeoutSeconds": 90,
29
+ "signature": "[QK:CONTROL-SOCKET-ABSORBS-PEER-DISCONNECT]",
30
+ "signatureSource": "scripts/check-entwurf-control-rpc.ts"
31
+ },
32
+ {
33
+ "claim": "CONTROL-SOCKET-DIAGNOSES-UNEXPECTED",
34
+ "title": "an error that is NOT a peer disconnect is swallowed silently, hiding exactly the class this listener was never meant to absorb",
35
+ "subject": "pi-extensions/lib/entwurf-control-rpc.ts",
36
+ "find": [
37
+ "\t\tdiagnose(`[entwurf-control] control socket error (${error.code ?? \"no code\"}): ${error.message}`);"
38
+ ],
39
+ "replace": ["\t\treturn;"],
40
+ "gate": ["bash", "run.sh", "check-entwurf-control-rpc"],
41
+ "timeoutSeconds": 90,
42
+ "signature": "[QK:CONTROL-SOCKET-DIAGNOSES-UNEXPECTED]",
43
+ "signatureSource": "scripts/check-entwurf-control-rpc.ts"
44
+ },
45
+ {
46
+ "claim": "CONTROL-SOCKET-POLICY-FIRST",
47
+ "title": "the policy is attached after setEncoding, leaving a window in which the first write on a freshly accepted connection can still kill the session",
48
+ "subject": "pi-extensions/entwurf-control.ts",
49
+ "find": ["\t\tattachAcceptedSocketDisconnectPolicy(socket);", "\t\tsocket.setEncoding(\"utf8\");"],
50
+ "replace": ["\t\tsocket.setEncoding(\"utf8\");", "\t\tattachAcceptedSocketDisconnectPolicy(socket);"],
51
+ "gate": ["bash", "run.sh", "check-entwurf-control-rpc"],
52
+ "timeoutSeconds": 90,
53
+ "signature": "[QK:CONTROL-SOCKET-POLICY-FIRST]",
54
+ "signatureSource": "scripts/check-entwurf-control-rpc.ts"
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,154 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "fresh-call-dispatch",
4
+ "mutants": [
5
+ {
6
+ "claim": "FCD-RAIL-IS-CAPABILITY",
7
+ "title": "the herdr marker is read loosely, so a process carrying HERDR_ENV=0 is treated as being inside herdr",
8
+ "subject": "pi-extensions/lib/fresh-call-dispatch.ts",
9
+ "find": ["\treturn env.HERDR_ENV === \"1\" ? \"herdr\" : \"tmux\";"],
10
+ "replace": ["\treturn env.HERDR_ENV !== undefined ? \"herdr\" : \"tmux\";"],
11
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
12
+ "timeoutSeconds": 120,
13
+ "signature": "[QK:FCD-RAIL-IS-CAPABILITY]",
14
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
15
+ },
16
+ {
17
+ "claim": "FCD-NO-RAIL-PARAMETER",
18
+ "title": "the request gains a caller-chosen rail, so someone can ask for tmux placement from inside herdr and fail after a mutation",
19
+ "subject": "pi-extensions/lib/fresh-call-dispatch.ts",
20
+ "find": ["export interface FreshCallRequest {"],
21
+ "replace": ["export interface FreshCallRequest {\n\treadonly rail?: FreshCallRail;"],
22
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
23
+ "timeoutSeconds": 120,
24
+ "signature": "[QK:FCD-NO-RAIL-PARAMETER]",
25
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
26
+ },
27
+ {
28
+ "claim": "FCD-NO-FALLBACK",
29
+ "title": "an incomplete herdr context falls back to tmux, opening a window the operator cannot see from where they are standing",
30
+ "subject": "pi-extensions/lib/fresh-call-dispatch.ts",
31
+ "find": ["\t\t\treturn { rail: \"herdr\", result: { ok: false, reason: context.reason } };"],
32
+ "replace": ["\t\t\treturn { rail: \"tmux\", result: freshCall(request as never, env, nonce) };"],
33
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
34
+ "timeoutSeconds": 120,
35
+ "signature": "[QK:FCD-NO-FALLBACK]",
36
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
37
+ },
38
+ {
39
+ "claim": "FCD-HERDR-BACKEND-FIRST",
40
+ "title": "the herdr branch runs the Codex preflight before refusing a non-pilot backend, answering an irrelevant question first",
41
+ "subject": "pi-extensions/lib/fresh-call-dispatch.ts",
42
+ "find": [
43
+ "\t\tconst run: HerdrRun = createHerdrRunner(context.context.bin, env, spawn ?? (spawnChildProcess as SpawnFn));"
44
+ ],
45
+ "replace": [
46
+ "\t\tif (request.backend === \"codex\") await codexFreshPreflight(env);\n\t\tconst run: HerdrRun = createHerdrRunner(context.context.bin, env, spawn ?? (spawnChildProcess as SpawnFn));"
47
+ ],
48
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
49
+ "timeoutSeconds": 120,
50
+ "signature": "[QK:FCD-HERDR-BACKEND-FIRST]",
51
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
52
+ },
53
+ {
54
+ "claim": "FCD-CODEX-PREFLIGHT-TMUX",
55
+ "title": "the Codex preflight moves after the composition, so a window opens before the capability question is asked",
56
+ "subject": "pi-extensions/lib/fresh-call-dispatch.ts",
57
+ "find": [
58
+ "\tconst targetMissing = request.backend === \"codex\" ? await codexFreshPreflight(env) : null;\n\tconst callerMissing =\n\t\ttargetMissing === null && request.callerNativeSessionId !== undefined && request.placement === undefined\n\t\t\t? codexCallerFreshPreflight(env)\n\t\t\t: null;\n\tconst missing = targetMissing ?? callerMissing;\n\tif (missing) return { rail: \"tmux\", result: { ok: false, reason: missing } };"
59
+ ],
60
+ "replace": [""],
61
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
62
+ "timeoutSeconds": 120,
63
+ "signature": "[QK:FCD-CODEX-PREFLIGHT-TMUX]",
64
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
65
+ },
66
+ {
67
+ "claim": "FCD-ONE-NONCE",
68
+ "title": "the rail mints its own second nonce, so the sibling calls back with a tag the caller never recorded",
69
+ "subject": "pi-extensions/lib/fresh-call-dispatch.ts",
70
+ "find": ["\t\treturn { rail: \"herdr\", result: await herdrFreshCall(request, run, env, nonce) };"],
71
+ "replace": ["\t\treturn { rail: \"herdr\", result: await herdrFreshCall(request, run, env, mintNonce()) };"],
72
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
73
+ "timeoutSeconds": 120,
74
+ "signature": "[QK:FCD-ONE-NONCE]",
75
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
76
+ },
77
+ {
78
+ "claim": "FCD-SURFACE-PARITY",
79
+ "title": "the MCP surface goes back to calling a rail directly, so the two public surfaces can drift on rail choice",
80
+ "subject": "mcp/entwurf-bridge/src/index.ts",
81
+ "find": [
82
+ "\t\t\tconst dispatched = await dispatchFreshCall({\n\t\t\t\tbackend,\n\t\t\t\tmodel,\n\t\t\t\ttask,\n\t\t\t\tcwd,\n\t\t\t\tplacement,\n\t\t\t\tcallerGardenId,\n\t\t\t\tcallerNativeSessionId,\n\t\t\t\tcallerCwd,\n\t\t\t});"
83
+ ],
84
+ "replace": [
85
+ "\t\t\tconst dispatched = { rail: \"tmux\" as const, result: { ok: false, reason: \"caller-identity-unavailable\" as const } };\n\t\t\tvoid selectFreshCallRail;"
86
+ ],
87
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
88
+ "timeoutSeconds": 120,
89
+ "signature": "[QK:FCD-SURFACE-PARITY]",
90
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
91
+ },
92
+ {
93
+ "claim": "FCD-PI-DYNAMIC-IMPORT",
94
+ "title": "pi reaches the composition root by a literal path again, which is how the lazy import fence erodes",
95
+ "subject": "pi-extensions/entwurf-control.ts",
96
+ "find": ["const FRESH_CALL_DISPATCH_MODULE = \"./lib/fresh-call-dispatch.ts\";"],
97
+ "replace": ["const FRESH_CALL_DISPATCH_MODULE = \"./lib/fresh-call-dispatch-renamed.ts\";"],
98
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
99
+ "timeoutSeconds": 120,
100
+ "signature": "[QK:FCD-PI-DYNAMIC-IMPORT]",
101
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
102
+ },
103
+ {
104
+ "claim": "FCD-RENDER-PER-RAIL",
105
+ "title": "both rails render through the tmux renderer, so a herdr caller is told their sibling is in a tmux window",
106
+ "subject": "pi-extensions/lib/fresh-call-dispatch.ts",
107
+ "find": [
108
+ "\treturn dispatched.rail === \"herdr\" ? renderHerdrFreshCall(dispatched.result) : renderFreshCall(dispatched.result);"
109
+ ],
110
+ "replace": ["\treturn renderFreshCall(dispatched.result as never);"],
111
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
112
+ "timeoutSeconds": 120,
113
+ "signature": "[QK:FCD-RENDER-PER-RAIL]",
114
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
115
+ },
116
+ {
117
+ "claim": "FCD-HERDR-VIEW-NOT-ADDRESS",
118
+ "title": "the herdr receipt stops saying its coordinates are a view, which is the first step to someone dispatching at a pane id",
119
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
120
+ "find": [
121
+ "` pane: ${r.herdrPaneId} terminal ${r.herdrTerminalId} — the tab's initial pane; herdr VIEW coordinates, not an address\\n` +"
122
+ ],
123
+ "replace": ["` pane: ${r.herdrPaneId} terminal ${r.herdrTerminalId}\\n` +"],
124
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
125
+ "timeoutSeconds": 120,
126
+ "signature": "[QK:FCD-HERDR-VIEW-NOT-ADDRESS]",
127
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
128
+ },
129
+ {
130
+ "claim": "FCD-RECOVERY-VISIBLE",
131
+ "title": "an unreclaimed orphan renders as if it had been closed, so nobody goes to look at the pane still sitting there",
132
+ "subject": "pi-extensions/lib/herdr-fresh-call.ts",
133
+ "find": [
134
+ "\t\t\treturn ` recovery: orphan-unreclaimed:${recovery.reason} (${recovery.paneId}) — this pane was NOT closed, on purpose\\n`;"
135
+ ],
136
+ "replace": ["\t\t\treturn ` recovery: closed ${recovery.paneId}\\n`;"],
137
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
138
+ "timeoutSeconds": 120,
139
+ "signature": "[QK:FCD-RECOVERY-VISIBLE]",
140
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
141
+ },
142
+ {
143
+ "claim": "FCD-NO-SYNC-SPAWN",
144
+ "title": "the composition root regains synchronous child authority, which is how a blocking runner gets wired back in without anyone reading the runner",
145
+ "subject": "pi-extensions/lib/fresh-call-dispatch.ts",
146
+ "find": ["import { spawn as spawnChildProcess } from \"node:child_process\";"],
147
+ "replace": ["import { spawn as spawnChildProcess, spawnSync } from \"node:child_process\";"],
148
+ "gate": ["bash", "run.sh", "check-fresh-call-dispatch"],
149
+ "timeoutSeconds": 120,
150
+ "signature": "[QK:FCD-NO-SYNC-SPAWN]",
151
+ "signatureSource": "scripts/check-fresh-call-dispatch.ts"
152
+ }
153
+ ]
154
+ }
@@ -0,0 +1,305 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "herdr-activation",
4
+ "mutants": [
5
+ {
6
+ "claim": "HAC-PI-USER-SCOPE-ONLY",
7
+ "title": "composing the generic install for a plugin activation writes a project `.pi/settings.json` the plugin has no project for — a second package identity that `--entwurf-control` then has to arbitrate, on a host the operator never asked to have a project registered in",
8
+ "subject": "run.sh",
9
+ "find": [" register_user_scope_citizen \"$plugin_runtime\""],
10
+ "replace": [" install_local_package \"${HOME}/plugin-has-no-project\""],
11
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
12
+ "timeoutSeconds": 300,
13
+ "signature": "[QK:HAC-PI-USER-SCOPE-ONLY]",
14
+ "signatureSource": "scripts/check-herdr-activation.ts"
15
+ },
16
+ {
17
+ "claim": "HAC-COMMAND-IS-DERIVED",
18
+ "title": "accepting any absolute directory as 'the plugin runtime' is a way to point every pi session at an executable of the caller's choosing — 'absolute' was never the contract, 'this host's stable runtime' was",
19
+ "subject": "scripts/register-pi-provider.py",
20
+ "find": [" if runtime_root != expected:"],
21
+ "replace": [" if False:"],
22
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
23
+ "timeoutSeconds": 300,
24
+ "signature": "[QK:HAC-COMMAND-IS-DERIVED]",
25
+ "signatureSource": "scripts/check-herdr-activation.ts"
26
+ },
27
+ {
28
+ "claim": "HAC-DEFAULT-MODE-UNCHANGED",
29
+ "title": "letting the plugin mode leak into the default branch rewrites the Claude wiring of every host that never asked for it — adding a mode is only safe while the hosts that do not use it keep their bytes",
30
+ "subject": "scripts/meta-bridge-state.py",
31
+ "find": [
32
+ " if PLUGIN_RUNTIME is not None:",
33
+ " return {\"type\": \"stdio\", \"command\": plugin_bin(\"entwurf-bridge\"), \"args\": [], \"env\": env}"
34
+ ],
35
+ "replace": [
36
+ " if True:",
37
+ " return {\"type\": \"stdio\", \"command\": plugin_bin(\"entwurf-bridge\"), \"args\": [], \"env\": env}"
38
+ ],
39
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
40
+ "timeoutSeconds": 300,
41
+ "signature": "[QK:HAC-DEFAULT-MODE-UNCHANGED]",
42
+ "signatureSource": "scripts/check-herdr-activation.ts"
43
+ },
44
+ {
45
+ "claim": "HAC-INVERSE-REFUSES-DRIFT",
46
+ "title": "the user-scope inverse is admitted by the ownership record, so without the drift check it deletes whatever now sits at our key — an operator who replaced the command with their own loses it to our uninstall",
47
+ "subject": "scripts/register-pi-provider.py",
48
+ "find": [" if isinstance(recorded_cmd, str) and live_cmd is not None and live_cmd != recorded_cmd:"],
49
+ "replace": [" if False:"],
50
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
51
+ "timeoutSeconds": 300,
52
+ "signature": "[QK:HAC-INVERSE-REFUSES-DRIFT]",
53
+ "signatureSource": "scripts/check-herdr-activation.ts"
54
+ },
55
+ {
56
+ "claim": "HAC-LEDGER-CERTIFIED",
57
+ "title": "a ledger that may name an atom outside P is a teardown aimed at a harness this lane promised to never write — OpenCode is installed on real hosts and the whole product boundary is that it receives nothing",
58
+ "subject": "scripts/herdr-activation.mjs",
59
+ "find": ["\t\tif (!ACTIVATABLE_BACKENDS.includes(backend)) {"],
60
+ "replace": ["\t\tif (false) {"],
61
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
62
+ "timeoutSeconds": 300,
63
+ "signature": "[QK:HAC-LEDGER-CERTIFIED]",
64
+ "signatureSource": "scripts/check-herdr-activation.ts"
65
+ },
66
+ {
67
+ "claim": "HAC-ADD-ONLY",
68
+ "title": "inferring removal from a shrinking request makes an unrelated Herdr integration uninstall silently tear down Entwurf wiring the operator still uses — Herdr's reinstall is the refresh trigger, not a statement of intent to remove",
69
+ "subject": "scripts/herdr-activation.mjs",
70
+ "find": ["\tconst retained = ACTIVATABLE_BACKENDS.filter((b) => already.includes(b) && !requested.includes(b));"],
71
+ "replace": ["\tconst retained = [];"],
72
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
73
+ "timeoutSeconds": 300,
74
+ "signature": "[QK:HAC-ADD-ONLY]",
75
+ "signatureSource": "scripts/check-herdr-activation.ts"
76
+ },
77
+ {
78
+ "claim": "HAC-ROOTS-CERTIFIED",
79
+ "title": "acting on a ledger whose recorded harness roots differ from this environment's undoes an activation against a config that was never ours — the override that decided where the bytes went is the same override the inverse needs",
80
+ "subject": "scripts/herdr-activation.mjs",
81
+ "find": ["\tif (mismatches.length > 0) {"],
82
+ "replace": ["\tif (false) {"],
83
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
84
+ "timeoutSeconds": 300,
85
+ "signature": "[QK:HAC-ROOTS-CERTIFIED]",
86
+ "signatureSource": "scripts/check-herdr-activation.ts"
87
+ },
88
+ {
89
+ "claim": "HAC-TEARDOWN-ORDER",
90
+ "title": "retiring the ledger before the artifacts it authorised leaves a half-removed host with no record of what remains, and removing the runtime first leaves both harnesses' configs naming a directory that is already gone",
91
+ "subject": "scripts/herdr-activation.mjs",
92
+ "find": [
93
+ "\tsteps.push(\"runtime\");",
94
+ "\t// The ledger is the retry authority, so it is retired after everything it authorised.",
95
+ "\tsteps.push(\"ledger\");"
96
+ ],
97
+ "replace": [
98
+ "\tsteps.push(\"ledger\");",
99
+ "\t// The ledger is the retry authority, so it is retired after everything it authorised.",
100
+ "\tsteps.push(\"runtime\");"
101
+ ],
102
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
103
+ "timeoutSeconds": 300,
104
+ "signature": "[QK:HAC-TEARDOWN-ORDER]",
105
+ "signatureSource": "scripts/check-herdr-activation.ts"
106
+ },
107
+ {
108
+ "claim": "HAC-PREFLIGHT-ALL-OR-NOTHING",
109
+ "title": "mutating before every authority is green is how a Claude failure strands a half-removed Pi: the teardown starts on a promise nobody checked and stops somewhere no record describes",
110
+ "subject": "scripts/herdr-plugin-deactivate.mjs",
111
+ "find": ["\tif (refused.length > 0) {"],
112
+ "replace": ["\tif (false) {"],
113
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
114
+ "timeoutSeconds": 300,
115
+ "signature": "[QK:HAC-PREFLIGHT-ALL-OR-NOTHING]",
116
+ "signatureSource": "scripts/check-herdr-activation.ts"
117
+ },
118
+ {
119
+ "claim": "HAC-NO-DEFERRED-IMPORT",
120
+ "title": "one lazy import on this path is unrecoverable: the process has already deleted the runtime it is executing from, so the load fails with ERR_MODULE_NOT_FOUND and the host is left with no runtime, an unretired ledger, and nothing to run the rest",
121
+ "subject": "scripts/herdr-plugin-deactivate.mjs",
122
+ "find": ["import { removeOwnedRuntime, resolveRuntimeLayout } from \"./herdr-runtime.mjs\";"],
123
+ "replace": ["const { removeOwnedRuntime, resolveRuntimeLayout } = await import(\"./herdr-runtime.mjs\");"],
124
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
125
+ "timeoutSeconds": 300,
126
+ "signature": "[QK:HAC-NO-DEFERRED-IMPORT]",
127
+ "signatureSource": "scripts/check-herdr-activation.ts"
128
+ },
129
+ {
130
+ "claim": "HAC-ENTRY-GUARD-REALPATH",
131
+ "title": "comparing argv[1] to import.meta.url unresolved makes the entry guard silently FALSE under a package manager that symlinks the package — pnpm links node_modules/@junghanacs/entwurf into its store — so the verb runs, prints nothing, exits 0, and every caller reads a teardown that never happened",
132
+ "subject": "scripts/herdr-plugin-deactivate.mjs",
133
+ "find": ["\t\treturn fs.realpathSync(a) === fs.realpathSync(b);"],
134
+ "replace": ["\t\treturn path.resolve(a) === path.resolve(b);"],
135
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
136
+ "timeoutSeconds": 300,
137
+ "signature": "[QK:HAC-ENTRY-GUARD-REALPATH]",
138
+ "signatureSource": "scripts/check-herdr-activation.ts"
139
+ },
140
+ {
141
+ "claim": "HAC-DEACTIVATE-ROUNDTRIP",
142
+ "title": "a teardown that retires the ledger while leaving the runtime behind reports a host as clean while its stable address still holds a runtime nothing records — the operator has no verb left that will find it",
143
+ "subject": "scripts/herdr-plugin-deactivate.mjs",
144
+ "find": ["\t\t} else if (step === \"runtime\") {"],
145
+ "replace": ["\t\t} else if (step === \"never\") {"],
146
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
147
+ "timeoutSeconds": 300,
148
+ "signature": "[QK:HAC-DEACTIVATE-ROUNDTRIP]",
149
+ "signatureSource": "scripts/check-herdr-activation.ts"
150
+ },
151
+ {
152
+ "claim": "HAC-LEDGER-STATE-MACHINE",
153
+ "subject": "scripts/herdr-activation.mjs",
154
+ "title": "a phase that may contradict its own component rows is a ledger that says 'active' over a component it already removed — every later decision reads it, and the retry that trusts it re-runs an inverse whose ownership state is gone",
155
+ "find": ["\t\tif (!PHASE_ALLOWS[parsed.phase].includes(component.state)) {"],
156
+ "replace": ["\t\tif (false) {"],
157
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
158
+ "timeoutSeconds": 600,
159
+ "signature": "[QK:HAC-LEDGER-STATE-MACHINE]",
160
+ "signatureSource": "scripts/check-herdr-activation.ts"
161
+ },
162
+ {
163
+ "claim": "HAC-TWO-STAGE-ADD-ONLY",
164
+ "subject": "scripts/herdr-plugin-activate.mjs",
165
+ "title": "not handing the installer the plugin mode wires Claude to the bare/clone command on a host where nothing entwurf is on PATH — the install reports success and every MCP call it configured cannot resolve",
166
+ "find": [
167
+ "\t\t\t\t: run(\"bash\", [path.join(PACKAGE_ROOT, \"scripts\", \"meta-bridge-install.sh\")], {",
168
+ "\t\t\t\t\t\t...env,",
169
+ "\t\t\t\t\t\tENTWURF_PLUGIN_RUNTIME: runtime.activeDir,",
170
+ "\t\t\t\t\t});"
171
+ ],
172
+ "replace": [
173
+ "\t\t\t\t: run(\"bash\", [path.join(PACKAGE_ROOT, \"scripts\", \"meta-bridge-install.sh\")], {",
174
+ "\t\t\t\t\t\t...env,",
175
+ "\t\t\t\t\t});"
176
+ ],
177
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
178
+ "timeoutSeconds": 600,
179
+ "signature": "[QK:HAC-TWO-STAGE-ADD-ONLY]",
180
+ "signatureSource": "scripts/check-herdr-activation.ts"
181
+ },
182
+ {
183
+ "claim": "HAC-RETRY-RESUMES",
184
+ "subject": "scripts/herdr-activation.mjs",
185
+ "title": "re-running a component inverse the ledger already recorded as removed is a permanent refusal, not an idempotent no-op: its ownership state is gone, its preflight fails, and one partial teardown becomes a host nobody can finish",
186
+ "find": ["\t\tif (states[backend] === \"active\") steps.push(backend);"],
187
+ "replace": ["\t\tsteps.push(backend);"],
188
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
189
+ "timeoutSeconds": 600,
190
+ "signature": "[QK:HAC-RETRY-RESUMES]",
191
+ "signatureSource": "scripts/check-herdr-activation.ts"
192
+ },
193
+ {
194
+ "claim": "HAC-XDG-RESOLUTION-SHARED",
195
+ "subject": "scripts/herdr-activation.mjs",
196
+ "title": "resolving the data root as `XDG_DATA_HOME || ''` builds `/entwurf/...` on a host with XDG unset while every writer uses `$HOME/.local/share/entwurf` — a preflight that looks at a file nobody writes and passes for the wrong reason",
197
+ "find": [
198
+ "\tconst dataHome = env.XDG_DATA_HOME || (home ? path.join(home, \".local\", \"share\") : null);\n\tif (!dataHome) {\n\t\tthrow new ActivationError(\"activation-xdg-root-unresolvable\", \"neither XDG_DATA_HOME nor HOME is set\");\n\t}\n\treturn path.join(dataHome, \"entwurf\");"
199
+ ],
200
+ "replace": ["\treturn path.join(env.XDG_DATA_HOME || \"\", \"entwurf\");"],
201
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
202
+ "timeoutSeconds": 600,
203
+ "signature": "[QK:HAC-XDG-RESOLUTION-SHARED]",
204
+ "signatureSource": "scripts/check-herdr-activation.ts"
205
+ },
206
+ {
207
+ "claim": "HAC-CLAUDE-PLAN-CERTIFIED",
208
+ "subject": "scripts/meta-bridge-state.py",
209
+ "title": "a preflight that re-implements the restore rules knows only the entry shapes whoever wrote it remembered — the first cut demanded `original` on every entry while `snapshot_array_items` writes `{originalExisted, added}`, so every real Claude activation failed its own preflight and the aggregate could never honestly claim preflight-all",
210
+ "find": [" if kind == \"array-items\":\n if \"originalExisted\" not in entry:"],
211
+ "replace": [" if kind == \"array-items-never\":\n if \"originalExisted\" not in entry:"],
212
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
213
+ "timeoutSeconds": 600,
214
+ "signature": "[QK:HAC-CLAUDE-PLAN-CERTIFIED]",
215
+ "signatureSource": "scripts/check-herdr-activation.ts"
216
+ },
217
+ {
218
+ "claim": "HAC-PHASE-TRANSITIONS",
219
+ "subject": "scripts/herdr-activation.mjs",
220
+ "title": "a forward run over a `deactivating` ledger re-wires components a teardown is midway through removing and overwrites the only record of where it stopped — the host ends up half-wired with nothing that describes it",
221
+ "find": ["\tif (ledger.phase === \"deactivating\") {"],
222
+ "replace": ["\tif (false) {"],
223
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
224
+ "timeoutSeconds": 600,
225
+ "signature": "[QK:HAC-PHASE-TRANSITIONS]",
226
+ "signatureSource": "scripts/check-herdr-activation.ts"
227
+ },
228
+ {
229
+ "claim": "HAC-CLAUDE-ROOTS-COMPLETE",
230
+ "subject": "scripts/herdr-activation.mjs",
231
+ "title": "a ledger that records only CLAUDE_CONFIG_DIR misses `$HOME/.claude.json`, where the user-scope MCP entry lives: measured, a moved HOME passes the root check and the MCP entry is left behind in the old home",
232
+ "find": ["\t\tclaudeUserConfig: Object.freeze({ path: path.join(home, \".claude.json\"), source: \"HOME\" }),"],
233
+ "replace": [
234
+ "\t\tclaudeUserConfig: Object.freeze({ path: path.join(home, \".claude.json\"), source: \"default\" }),"
235
+ ],
236
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
237
+ "timeoutSeconds": 600,
238
+ "signature": "[QK:HAC-CLAUDE-ROOTS-COMPLETE]",
239
+ "signatureSource": "scripts/check-herdr-activation.ts"
240
+ },
241
+ {
242
+ "claim": "HAC-AGGREGATE-PREFLIGHT-COMPLETE",
243
+ "subject": "scripts/herdr-plugin-activate.mjs",
244
+ "title": "preflighting only the provider half lets the first ledger write land on a host the pi PACKAGE writer was always going to refuse — the aggregate claims preflight-all while one of the two ownership records was never asked",
245
+ "find": ["\t\tpreflights.push([", "\t\t\t\"pi-package\","],
246
+ "replace": ["\t\tif (false) preflights.push([", "\t\t\t\"pi-package\","],
247
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
248
+ "timeoutSeconds": 600,
249
+ "signature": "[QK:HAC-AGGREGATE-PREFLIGHT-COMPLETE]",
250
+ "signatureSource": "scripts/check-herdr-activation.ts"
251
+ },
252
+ {
253
+ "claim": "HAC-RUNTIME-EXACT-VERSION",
254
+ "subject": "scripts/herdr-plugin-activate.mjs",
255
+ "title": "checking only that SOME usable tree of that name is present wires two harnesses at an address whose contents disagree with the journal that certified it — the provenance line then describes something else",
256
+ "find": ["\t\tverifyInstalledRuntime(runtime.activeDir, artifactCompleteness(journal.artifactIdentity));"],
257
+ "replace": [
258
+ "\t\tconst certifiedName = artifactCompleteness(journal.artifactIdentity).name;",
259
+ "\t\tverifyInstalledRuntime(runtime.activeDir, {",
260
+ "\t\t\tname: certifiedName,",
261
+ "\t\t\tversion: JSON.parse(",
262
+ "\t\t\t\tfs.readFileSync(path.join(runtime.activeDir, \"node_modules\", certifiedName, \"package.json\"), \"utf8\"),",
263
+ "\t\t\t).version,",
264
+ "\t\t});"
265
+ ],
266
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
267
+ "timeoutSeconds": 600,
268
+ "signature": "[QK:HAC-RUNTIME-EXACT-VERSION]",
269
+ "signatureSource": "scripts/check-herdr-activation.ts"
270
+ },
271
+ {
272
+ "claim": "HAC-LEDGER-IDENTITY-CERTIFIED",
273
+ "title": "letting the ledger carry a REQUESTED identity records an activation against an artifact nothing observed, and the teardown that reads it back has no digest or completeness pair to check the disk against",
274
+ "subject": "scripts/herdr-activation.mjs",
275
+ "find": ["\t\tcertifyArtifactIdentity(\"artifactIdentity\", parsed.artifactIdentity, \"ready\");"],
276
+ "replace": ["\t\tcertifyArtifactIdentity(\"artifactIdentity\", parsed.artifactIdentity, \"either\");"],
277
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
278
+ "timeoutSeconds": 420,
279
+ "signature": "[QK:HAC-LEDGER-IDENTITY-CERTIFIED]",
280
+ "signatureSource": "scripts/check-herdr-activation.ts"
281
+ },
282
+ {
283
+ "claim": "HAC-REBIND-LEGALITY",
284
+ "title": "treating a SOURCE change as an ordinary reinstall lets one acquisition authority inherit the other's activation without the operator ever asking — the npm branch would silently adopt a candidate's wiring, or the reverse",
285
+ "subject": "scripts/herdr-activation.mjs",
286
+ "find": ["\tif (ledger.artifactIdentity.kind !== target.kind) {"],
287
+ "replace": ["\tif (false && ledger.artifactIdentity.kind !== target.kind) {"],
288
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
289
+ "timeoutSeconds": 420,
290
+ "signature": "[QK:HAC-REBIND-LEGALITY]",
291
+ "signatureSource": "scripts/check-herdr-activation.ts"
292
+ },
293
+ {
294
+ "claim": "HAC-REBIND-ATOMIC-CHECKPOINT",
295
+ "title": "leaving already-active components labelled `active` through a rebind claims work the run has not redone: the ledger says those components are wired to the new artifact while nothing has re-pointed them",
296
+ "subject": "scripts/herdr-plugin-activate.mjs",
297
+ "find": ["\t\tif (disposition === \"rebind\" || states[backend] !== \"active\") states[backend] = \"pending\";"],
298
+ "replace": ["\t\tif (states[backend] !== \"active\") states[backend] = \"pending\";"],
299
+ "gate": ["bash", "run.sh", "check-herdr-activation"],
300
+ "timeoutSeconds": 420,
301
+ "signature": "[QK:HAC-REBIND-ATOMIC-CHECKPOINT]",
302
+ "signatureSource": "scripts/check-herdr-activation.ts"
303
+ }
304
+ ]
305
+ }