@phyzess/pappra 0.1.0-beta.13 → 0.1.0-beta.18

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 (357) hide show
  1. package/README.md +134 -15
  2. package/assets/integrations/skills/pappra/SKILL.md +28 -7
  3. package/assets/integrations/skills/pappra/references/collaboration.md +633 -0
  4. package/assets/integrations/skills/pappra/references/environment.md +360 -0
  5. package/assets/integrations/skills/pappra/references/hosts.md +20 -0
  6. package/assets/integrations/skills/pappra/references/operations.md +36 -5
  7. package/dist/adapters/pi/recovery-extension.d.ts +65 -0
  8. package/dist/adapters/pi/recovery-extension.js +238 -0
  9. package/dist/adapters/pi/session-transition-extension.d.ts +30 -0
  10. package/dist/adapters/pi/session-transition-extension.js +265 -0
  11. package/dist/app/automatic-handoff-events.d.ts +26 -0
  12. package/dist/app/automatic-handoff-events.js +134 -0
  13. package/dist/app/bounded-file.d.ts +2 -0
  14. package/dist/app/bounded-file.js +2 -0
  15. package/dist/app/capability-evidence/claude.d.ts +37 -0
  16. package/dist/app/capability-evidence/claude.js +206 -0
  17. package/dist/app/capability-evidence/codex.d.ts +35 -0
  18. package/dist/app/capability-evidence/codex.js +193 -0
  19. package/dist/app/capability-evidence/index.d.ts +33 -0
  20. package/dist/app/capability-evidence/index.js +117 -0
  21. package/dist/app/capability-evidence/pi.d.ts +73 -0
  22. package/dist/app/capability-evidence/pi.js +669 -0
  23. package/dist/app/capability-evidence/shared.d.ts +69 -0
  24. package/dist/app/capability-evidence/shared.js +195 -0
  25. package/dist/app/collab-auto-handoff.d.ts +55 -0
  26. package/dist/app/collab-auto-handoff.js +384 -0
  27. package/dist/app/collab-bindings.d.ts +57 -0
  28. package/dist/app/collab-bindings.js +1006 -0
  29. package/dist/app/collab-bootstrap-transport.d.ts +163 -0
  30. package/dist/app/collab-bootstrap-transport.js +769 -0
  31. package/dist/app/collab-bootstrap.d.ts +137 -0
  32. package/dist/app/collab-bootstrap.js +3455 -0
  33. package/dist/app/collab-handoff.d.ts +12 -0
  34. package/dist/app/collab-handoff.js +511 -0
  35. package/dist/app/collab-messages.d.ts +2 -0
  36. package/dist/app/collab-messages.js +2 -0
  37. package/dist/app/collab-notify.d.ts +156 -0
  38. package/dist/app/collab-notify.js +1208 -0
  39. package/dist/app/collab-preflight.d.ts +34 -0
  40. package/dist/app/collab-preflight.js +96 -0
  41. package/dist/app/collab-recovery-executor.d.ts +161 -0
  42. package/dist/app/collab-recovery-executor.js +1153 -0
  43. package/dist/app/collab-recovery-projection.d.ts +103 -0
  44. package/dist/app/collab-recovery-projection.js +659 -0
  45. package/dist/app/collab-recovery.d.ts +25 -0
  46. package/dist/app/collab-recovery.js +429 -0
  47. package/dist/app/collab-resources.d.ts +16 -0
  48. package/dist/app/collab-resources.js +278 -0
  49. package/dist/app/collab-role-events.d.ts +22 -0
  50. package/dist/app/collab-role-events.js +329 -0
  51. package/dist/app/collab-role-orchestration.d.ts +53 -0
  52. package/dist/app/collab-role-orchestration.js +601 -0
  53. package/dist/app/collab-role-plan.d.ts +81 -0
  54. package/dist/app/collab-role-plan.js +504 -0
  55. package/dist/app/collab-role-repair.d.ts +109 -0
  56. package/dist/app/collab-role-repair.js +821 -0
  57. package/dist/app/collab-rollover.d.ts +263 -0
  58. package/dist/app/collab-rollover.js +1112 -0
  59. package/dist/app/collab-route.d.ts +64 -0
  60. package/dist/app/collab-route.js +240 -0
  61. package/dist/app/collab-session-transition.d.ts +221 -0
  62. package/dist/app/collab-session-transition.js +3334 -0
  63. package/dist/app/collab-state.d.ts +7 -0
  64. package/dist/app/collab-state.js +162 -0
  65. package/dist/app/collab-task-store.d.ts +22 -0
  66. package/dist/app/collab-task-store.js +204 -0
  67. package/dist/app/collab-work-execution.d.ts +127 -0
  68. package/dist/app/collab-work-execution.js +1027 -0
  69. package/dist/app/collab-work-plan.d.ts +87 -0
  70. package/dist/app/collab-work-plan.js +437 -0
  71. package/dist/app/collaboration.d.ts +8 -2
  72. package/dist/app/collaboration.js +101 -12
  73. package/dist/app/doctor.js +108 -5
  74. package/dist/app/host-mcp.js +34 -8
  75. package/dist/app/host-probe.js +4 -4
  76. package/dist/app/init.d.ts +58 -4
  77. package/dist/app/init.js +768 -155
  78. package/dist/app/managed-pi-bootstrap-admission.d.ts +14 -0
  79. package/dist/app/managed-pi-bootstrap-admission.js +104 -0
  80. package/dist/app/managed-pi-bootstrap-refresh.d.ts +10 -0
  81. package/dist/app/managed-pi-bootstrap-refresh.js +78 -0
  82. package/dist/app/managed-pi-delivery.d.ts +8 -0
  83. package/dist/app/managed-pi-delivery.js +110 -0
  84. package/dist/app/managed-pi-handoff-receiver.d.ts +15 -0
  85. package/dist/app/managed-pi-handoff-receiver.js +263 -0
  86. package/dist/app/managed-pi-handoff-reference.d.ts +3 -0
  87. package/dist/app/managed-pi-handoff-reference.js +60 -0
  88. package/dist/app/managed-pi-herdr.d.ts +21 -0
  89. package/dist/app/managed-pi-herdr.js +185 -0
  90. package/dist/app/managed-pi-initial-admission.d.ts +28 -0
  91. package/dist/app/managed-pi-initial-admission.js +416 -0
  92. package/dist/app/managed-pi-initial-launch.d.ts +5 -0
  93. package/dist/app/managed-pi-initial-launch.js +61 -0
  94. package/dist/app/managed-pi-native-observation.d.ts +36 -0
  95. package/dist/app/managed-pi-native-observation.js +191 -0
  96. package/dist/app/managed-pi-readiness.d.ts +29 -0
  97. package/dist/app/managed-pi-readiness.js +288 -0
  98. package/dist/app/managed-pi-repair-stop.d.ts +21 -0
  99. package/dist/app/managed-pi-repair-stop.js +371 -0
  100. package/dist/app/managed-pi-resume-admission.d.ts +32 -0
  101. package/dist/app/managed-pi-resume-admission.js +618 -0
  102. package/dist/app/managed-pi-retirement.d.ts +28 -0
  103. package/dist/app/managed-pi-retirement.js +112 -0
  104. package/dist/app/managed-pi-sdk-control.d.ts +7 -0
  105. package/dist/app/managed-pi-sdk-control.js +169 -0
  106. package/dist/app/managed-pi-sdk-runtime.d.ts +3 -0
  107. package/dist/app/managed-pi-sdk-runtime.js +483 -0
  108. package/dist/app/managed-pi-source-admission.d.ts +14 -0
  109. package/dist/app/managed-pi-source-admission.js +323 -0
  110. package/dist/app/managed-pi-transition-host.d.ts +36 -0
  111. package/dist/app/managed-pi-transition-host.js +292 -0
  112. package/dist/app/managed-pi.d.ts +54 -0
  113. package/dist/app/managed-pi.js +248 -0
  114. package/dist/app/shell-readiness.d.ts +66 -0
  115. package/dist/app/shell-readiness.js +368 -0
  116. package/dist/app/task-pin.d.ts +2 -23
  117. package/dist/app/task-pin.js +2 -113
  118. package/dist/cli/commands/collaboration-binding.d.ts +13 -0
  119. package/dist/cli/commands/collaboration-binding.js +118 -0
  120. package/dist/cli/commands/collaboration-bootstrap.d.ts +10 -0
  121. package/dist/cli/commands/collaboration-bootstrap.js +84 -0
  122. package/dist/cli/commands/collaboration-capabilities.d.ts +13 -0
  123. package/dist/cli/commands/collaboration-capabilities.js +127 -0
  124. package/dist/cli/commands/collaboration-handoff.d.ts +13 -0
  125. package/dist/cli/commands/collaboration-handoff.js +102 -0
  126. package/dist/cli/commands/collaboration-message.d.ts +17 -0
  127. package/dist/cli/commands/collaboration-message.js +177 -0
  128. package/dist/cli/commands/collaboration-plan.d.ts +8 -0
  129. package/dist/cli/commands/collaboration-plan.js +108 -0
  130. package/dist/cli/commands/collaboration-preflight.d.ts +10 -0
  131. package/dist/cli/commands/collaboration-preflight.js +167 -0
  132. package/dist/cli/commands/collaboration-recovery-run.d.ts +46 -0
  133. package/dist/cli/commands/collaboration-recovery-run.js +224 -0
  134. package/dist/cli/commands/collaboration-recovery.d.ts +11 -0
  135. package/dist/cli/commands/collaboration-recovery.js +104 -0
  136. package/dist/cli/commands/collaboration-resources.d.ts +11 -0
  137. package/dist/cli/commands/collaboration-resources.js +96 -0
  138. package/dist/cli/commands/collaboration-role-plan.d.ts +9 -0
  139. package/dist/cli/commands/collaboration-role-plan.js +69 -0
  140. package/dist/cli/commands/collaboration-role-run.d.ts +11 -0
  141. package/dist/cli/commands/collaboration-role-run.js +128 -0
  142. package/dist/cli/commands/collaboration-rollover.d.ts +10 -0
  143. package/dist/cli/commands/collaboration-rollover.js +103 -0
  144. package/dist/cli/commands/collaboration-state.d.ts +10 -0
  145. package/dist/cli/commands/collaboration-state.js +96 -0
  146. package/dist/cli/commands/collaboration-transition.d.ts +10 -0
  147. package/dist/cli/commands/collaboration-transition.js +75 -0
  148. package/dist/cli/commands/collaboration-work-run.d.ts +34 -0
  149. package/dist/cli/commands/collaboration-work-run.js +178 -0
  150. package/dist/cli/commands/collaboration.d.ts +4 -0
  151. package/dist/cli/commands/collaboration.js +39 -4
  152. package/dist/cli/commands/continuity.js +2 -6
  153. package/dist/cli/commands/docs.d.ts +2 -0
  154. package/dist/cli/commands/docs.js +89 -15
  155. package/dist/cli/commands/host.d.ts +7 -1
  156. package/dist/cli/commands/host.js +122 -7
  157. package/dist/cli/commands/init.d.ts +20 -3
  158. package/dist/cli/commands/init.js +897 -232
  159. package/dist/cli/commands/mcp.d.ts +1 -0
  160. package/dist/cli/commands/mcp.js +11 -5
  161. package/dist/cli/commands/repair.d.ts +12 -0
  162. package/dist/cli/commands/repair.js +41 -0
  163. package/dist/cli/commands/runtime.d.ts +1 -1
  164. package/dist/cli/commands/runtime.js +139 -38
  165. package/dist/cli/commands/session.d.ts +7 -0
  166. package/dist/cli/commands/session.js +82 -0
  167. package/dist/cli/commands/task.js +19 -5
  168. package/dist/cli/run.js +866 -24
  169. package/dist/code/identity.d.ts +7 -0
  170. package/dist/code/identity.js +7 -3
  171. package/dist/code/registration.js +7 -0
  172. package/dist/code/service.d.ts +9 -0
  173. package/dist/code/service.js +16 -3
  174. package/dist/code/text.js +19 -7
  175. package/dist/collab/admission.d.ts +91 -66
  176. package/dist/collab/admission.js +21 -6
  177. package/dist/collab/canonical.d.ts +1 -1
  178. package/dist/collab/canonical.js +44 -3
  179. package/dist/collab/envelope.d.ts +2 -2
  180. package/dist/collab/envelope.js +12 -12
  181. package/dist/collab/first-work.d.ts +3 -0
  182. package/dist/collab/first-work.js +15 -0
  183. package/dist/collab/handoff.d.ts +16 -0
  184. package/dist/collab/handoff.js +39 -5
  185. package/dist/collab/index.d.ts +97 -1
  186. package/dist/collab/index.js +9 -1
  187. package/dist/collab/messages.js +126 -3
  188. package/dist/collab/persisted-messages.d.ts +123 -0
  189. package/dist/collab/persisted-messages.js +496 -0
  190. package/dist/collab/preflight.d.ts +1 -1
  191. package/dist/collab/resources.d.ts +13 -3
  192. package/dist/collab/resources.js +37 -6
  193. package/dist/collab/rollover.d.ts +311 -0
  194. package/dist/collab/rollover.js +1024 -0
  195. package/dist/collab/store.d.ts +1 -0
  196. package/dist/collab/store.js +1 -4
  197. package/dist/collab/types.d.ts +2 -354
  198. package/dist/collab/validation.d.ts +2 -10
  199. package/dist/collab/validation.js +2 -303
  200. package/dist/collab/work-plan.d.ts +105 -0
  201. package/dist/collab/work-plan.js +843 -0
  202. package/dist/continuity/context.d.ts +11 -2
  203. package/dist/continuity/context.js +100 -13
  204. package/dist/continuity/handoff-bundle.d.ts +296 -0
  205. package/dist/continuity/handoff-bundle.js +477 -0
  206. package/dist/continuity/index.d.ts +2 -0
  207. package/dist/continuity/index.js +1 -0
  208. package/dist/continuity/paths.js +10 -2
  209. package/dist/continuity/service.js +91 -15
  210. package/dist/continuity/task-pin.d.ts +24 -0
  211. package/dist/continuity/task-pin.js +590 -0
  212. package/dist/doctor.d.ts +1 -0
  213. package/dist/doctor.js +6 -0
  214. package/dist/experimental/index.d.ts +13 -0
  215. package/dist/experimental/index.js +25 -0
  216. package/dist/experimental/pi.d.ts +4 -0
  217. package/dist/experimental/pi.js +12 -0
  218. package/dist/hosts/claude/native.js +1 -1
  219. package/dist/hosts/codex/native.js +1 -1
  220. package/dist/hosts/codex/protocol.js +1 -1
  221. package/dist/hosts/integration/bootstrap-config.d.ts +58 -0
  222. package/dist/hosts/integration/bootstrap-config.js +254 -0
  223. package/dist/hosts/integration/bundle-export.d.ts +44 -0
  224. package/dist/hosts/integration/bundle-export.js +366 -3
  225. package/dist/hosts/integration/bundle.js +3 -0
  226. package/dist/hosts/integration/hook.js +36 -9
  227. package/dist/hosts/integration/pappra-bootstrap-extension.mjs +80 -0
  228. package/dist/hosts/integration/pappra-extension-runtime.mjs +59 -24631
  229. package/dist/hosts/integration/pappra-hook.mjs +61 -24749
  230. package/dist/hosts/integration/pappra-recovery-extension.mjs +259 -0
  231. package/dist/hosts/integration/pappra-session-transition-extension.mjs +276 -0
  232. package/dist/hosts/integration/pi-bootstrap-extension.d.ts +36 -0
  233. package/dist/hosts/integration/pi-bootstrap-extension.js +101 -0
  234. package/dist/hosts/integration/pi-bootstrap-guard.d.ts +125 -0
  235. package/dist/hosts/integration/pi-bootstrap-guard.js +438 -0
  236. package/dist/hosts/integration/pi-bootstrap-runtime.d.ts +63 -0
  237. package/dist/hosts/integration/pi-bootstrap-runtime.js +223 -0
  238. package/dist/hosts/integration/pi-native-events.d.ts +112 -0
  239. package/dist/hosts/integration/pi-native-events.js +348 -0
  240. package/dist/hosts/integration/pi-rollover-extension.d.ts +166 -0
  241. package/dist/hosts/integration/pi-rollover-extension.js +431 -0
  242. package/dist/hosts/integration/recovery-config.d.ts +48 -0
  243. package/dist/hosts/integration/recovery-config.js +117 -0
  244. package/dist/hosts/integration/service.d.ts +6 -1
  245. package/dist/hosts/integration/service.js +485 -35
  246. package/dist/hosts/integration/support.d.ts +7 -0
  247. package/dist/hosts/integration/support.js +9 -1
  248. package/dist/hosts/integration/templates.d.ts +3 -0
  249. package/dist/hosts/integration/templates.js +26 -3
  250. package/dist/hosts/pi/managed-host-identity.d.ts +2 -0
  251. package/dist/hosts/pi/managed-host-identity.js +40 -0
  252. package/dist/hosts/pi/managed-provider-diagnostics.d.ts +6 -0
  253. package/dist/hosts/pi/managed-provider-diagnostics.js +184 -0
  254. package/dist/hosts/pi/managed-sdk-session.d.ts +335 -0
  255. package/dist/hosts/pi/managed-sdk-session.js +1639 -0
  256. package/dist/hosts/pi/managed-session-extension.d.ts +20 -0
  257. package/dist/hosts/pi/managed-session-extension.js +84 -0
  258. package/dist/hosts/pi/managed-session.d.ts +177 -0
  259. package/dist/hosts/pi/managed-session.js +1557 -0
  260. package/dist/hosts/pi/protocol.js +1 -1
  261. package/dist/hosts/pi/query-result.js +1 -1
  262. package/dist/hosts/pi/resume-capability.d.ts +41 -0
  263. package/dist/hosts/pi/resume-capability.js +33 -0
  264. package/dist/hosts/pi/rpc.d.ts +182 -134
  265. package/dist/hosts/pi/rpc.js +230 -37
  266. package/dist/hosts/pilot.d.ts +97 -74
  267. package/dist/hosts/pilot.js +5 -1
  268. package/dist/hosts/version.d.ts +3 -0
  269. package/dist/hosts/version.js +18 -0
  270. package/dist/index.d.ts +0 -1
  271. package/dist/index.js +0 -1
  272. package/dist/infra/bounded-file.d.ts +19 -0
  273. package/dist/infra/bounded-file.js +66 -0
  274. package/dist/infra/fs.js +10 -2
  275. package/dist/infra/git.d.ts +29 -0
  276. package/dist/infra/git.js +45 -9
  277. package/dist/infra/json-property.d.ts +2 -0
  278. package/dist/infra/json-property.js +69 -0
  279. package/dist/infra/json.d.ts +4 -0
  280. package/dist/infra/json.js +24 -0
  281. package/dist/infra/owned-process-group.js +1 -1
  282. package/dist/infra/private-lock.d.ts +16 -0
  283. package/dist/infra/private-lock.js +148 -0
  284. package/dist/infra/private-state.d.ts +7 -0
  285. package/dist/infra/private-state.js +146 -0
  286. package/dist/infra/process-identity.d.ts +12 -0
  287. package/dist/infra/process-identity.js +142 -0
  288. package/dist/infra/validation.d.ts +5 -0
  289. package/dist/infra/validation.js +15 -0
  290. package/dist/init.d.ts +1 -0
  291. package/dist/init.js +6 -0
  292. package/dist/install/apply.d.ts +9 -1
  293. package/dist/install/apply.js +176 -12
  294. package/dist/install/dependencies.d.ts +38 -0
  295. package/dist/install/dependencies.js +282 -0
  296. package/dist/install/ownership.js +30 -1
  297. package/dist/install/references.d.ts +31 -0
  298. package/dist/install/references.js +208 -0
  299. package/dist/install/repair.d.ts +37 -0
  300. package/dist/install/repair.js +798 -0
  301. package/dist/install/reset-local.d.ts +16 -0
  302. package/dist/install/reset-local.js +304 -0
  303. package/dist/install/rollback.d.ts +3 -0
  304. package/dist/install/rollback.js +9 -3
  305. package/dist/install/runtime.d.ts +10 -6
  306. package/dist/install/runtime.js +299 -59
  307. package/dist/install/source-fingerprint.d.ts +2 -0
  308. package/dist/install/source-fingerprint.js +103 -0
  309. package/dist/knowledge/catalog.d.ts +1 -0
  310. package/dist/knowledge/catalog.js +143 -10
  311. package/dist/knowledge/journal.js +17 -7
  312. package/dist/knowledge/paths.js +2 -1
  313. package/dist/knowledge/refs.js +4 -0
  314. package/dist/knowledge/register-batch.d.ts +4 -0
  315. package/dist/knowledge/register-batch.js +225 -0
  316. package/dist/knowledge/relocate.d.ts +5 -0
  317. package/dist/knowledge/relocate.js +92 -0
  318. package/dist/knowledge/scan.js +103 -47
  319. package/dist/knowledge/service.d.ts +3 -0
  320. package/dist/knowledge/service.js +33 -1
  321. package/dist/knowledge/write.d.ts +4 -1
  322. package/dist/knowledge/write.js +1 -1
  323. package/dist/mcp/server.d.ts +4 -2
  324. package/dist/mcp/server.js +87 -136
  325. package/dist/schemas/collaboration-state-validation.d.ts +6 -0
  326. package/dist/schemas/collaboration-state-validation.js +342 -0
  327. package/dist/schemas/collaboration-state.d.ts +436 -0
  328. package/dist/schemas/collaboration-state.js +1 -0
  329. package/dist/schemas/collaboration.d.ts +335 -0
  330. package/dist/schemas/collaboration.js +272 -0
  331. package/dist/schemas/continuity.d.ts +8 -0
  332. package/dist/schemas/continuity.js +13 -3
  333. package/dist/schemas/doctor.d.ts +1 -0
  334. package/dist/schemas/doctor.js +1 -0
  335. package/dist/schemas/document.d.ts +61 -0
  336. package/dist/schemas/document.js +9 -0
  337. package/dist/schemas/errors.d.ts +1 -0
  338. package/dist/schemas/errors.js +1 -0
  339. package/dist/schemas/host.d.ts +24 -0
  340. package/dist/schemas/host.js +19 -0
  341. package/dist/schemas/install.d.ts +8 -2
  342. package/dist/schemas/install.js +11 -1
  343. package/dist/schemas/managed-pi-execution.d.ts +16 -0
  344. package/dist/schemas/managed-pi-execution.js +2 -0
  345. package/dist/schemas/outcome.d.ts +9 -5
  346. package/dist/workspace/config-file.js +1 -3
  347. package/dist/workspace/documents.js +12 -2
  348. package/dist/workspace/repositories.d.ts +7 -0
  349. package/dist/workspace/repositories.js +14 -5
  350. package/package.json +35 -35
  351. package/skills/pappra/SKILL.md +28 -7
  352. package/skills/pappra/references/collaboration.md +633 -0
  353. package/skills/pappra/references/environment.md +360 -0
  354. package/skills/pappra/references/hosts.md +20 -0
  355. package/skills/pappra/references/operations.md +36 -5
  356. package/dist/app/collab-from.d.ts +0 -35
  357. package/dist/app/collab-from.js +0 -84
@@ -1,11 +1,13 @@
1
+ import { object as isRecord } from '../infra/validation.js';
1
2
  import crypto from 'node:crypto';
2
3
  import fs from 'node:fs';
3
4
  import path from 'node:path';
4
5
  import { collaborationCommandSchema, collaborationGoalSchema, collaborationPreparedSchema, } from '../schemas/collaboration.js';
5
- import { admitLaunch, reserveLaunch, settleLaunch, } from '../collab/admission.js';
6
+ import { admitLaunch, reserveLaunch, settleLaunch, runnerForHarness, } from '../collab/admission.js';
6
7
  import { buildMessage, sha256File } from '../collab/envelope.js';
7
8
  import { findAttempt } from '../collab/lifecycle.js';
8
9
  import { submitMessage } from '../collab/messages.js';
10
+ import { readMessageFromTaskRoot } from './collab-messages.js';
9
11
  import { claimResources, releaseClaim } from '../collab/resources.js';
10
12
  import { initTask, nowIso, readRecord, taskRootFor } from '../collab/store.js';
11
13
  import { observePath, resolveRealpath } from '../infra/fs.js';
@@ -303,9 +305,6 @@ function publishCurrentResult(namespace, file, bytes) {
303
305
  };
304
306
  }
305
307
  }
306
- function isRecord(value) {
307
- return typeof value === 'object' && value !== null;
308
- }
309
308
  function assistantRecord(record) {
310
309
  if (!isRecord(record))
311
310
  return null;
@@ -556,8 +555,13 @@ export async function prepareCollaboration(input, options) {
556
555
  if (!path.isAbsolute(options.domainRoot)) {
557
556
  return failure('invalid', 'domain_root_required', 'Domain root must be absolute.');
558
557
  }
559
- if (!fs.existsSync(goal.assignee.cwd)) {
560
- return failure('needs_input', 'cwd_unavailable', 'Assignee cwd does not exist.');
558
+ const runnerName = runnerForHarness(goal.assignee.harness);
559
+ if (runnerName === null) {
560
+ return failure('invalid', 'assignee_harness_unsupported', `Assignee harness ${goal.assignee.harness} has no supported runner; the supported harnesses are pi, codex and claude.`);
561
+ }
562
+ const assigneeCwd = await observePath(goal.assignee.cwd).catch(() => null);
563
+ if (assigneeCwd?.kind !== 'directory') {
564
+ return failure('needs_input', 'cwd_unavailable', 'Assignee cwd is not a real existing directory.');
561
565
  }
562
566
  let taskRoot;
563
567
  let domainRoot;
@@ -626,6 +630,7 @@ export async function prepareCollaboration(input, options) {
626
630
  for (const claimId of claimIds) {
627
631
  releaseClaim(taskRoot, {
628
632
  claimId,
633
+ holder: goal.assignee,
629
634
  reason: 'prepare resource claim failed',
630
635
  });
631
636
  }
@@ -637,6 +642,7 @@ export async function prepareCollaboration(input, options) {
637
642
  for (const claimId of claimIds) {
638
643
  releaseClaim(taskRoot, {
639
644
  claimId,
645
+ holder: goal.assignee,
640
646
  reason: 'prepare resource epoch missing',
641
647
  });
642
648
  }
@@ -655,6 +661,7 @@ export async function prepareCollaboration(input, options) {
655
661
  for (const claimId of claimIds) {
656
662
  releaseClaim(taskRoot, {
657
663
  claimId,
664
+ holder: goal.assignee,
658
665
  reason: 'prepare failed before assignment',
659
666
  });
660
667
  }
@@ -667,6 +674,7 @@ export async function prepareCollaboration(input, options) {
667
674
  for (const claimId of claimIds) {
668
675
  releaseClaim(taskRoot, {
669
676
  claimId,
677
+ holder: goal.assignee,
670
678
  reason: 'prepare failed before assignment',
671
679
  });
672
680
  }
@@ -682,7 +690,7 @@ export async function prepareCollaboration(input, options) {
682
690
  attempt: goal.attempt,
683
691
  objective: goal.objective,
684
692
  acceptance: goal.acceptance,
685
- roles: ['implementer'],
693
+ roles: goal.roles,
686
694
  coordinator: {
687
695
  agent_id: goal.coordinator.agent_id,
688
696
  session_id: goal.coordinator.session_id,
@@ -707,7 +715,7 @@ export async function prepareCollaboration(input, options) {
707
715
  resource_epoch: resourceEpochs[item.path],
708
716
  })),
709
717
  runner: {
710
- name: 'pi-cli',
718
+ name: runnerName,
711
719
  mode: 'native',
712
720
  omitted_capabilities: [],
713
721
  },
@@ -723,6 +731,7 @@ export async function prepareCollaboration(input, options) {
723
731
  for (const claimId of claimIds) {
724
732
  releaseClaim(taskRoot, {
725
733
  claimId,
734
+ holder: goal.assignee,
726
735
  reason: 'prepare failed before assignment',
727
736
  });
728
737
  }
@@ -762,6 +771,7 @@ export async function prepareCollaboration(input, options) {
762
771
  for (const claimId of claimIds) {
763
772
  releaseClaim(taskRoot, {
764
773
  claimId,
774
+ holder: goal.assignee,
765
775
  reason: 'assignment registration failed',
766
776
  });
767
777
  }
@@ -808,6 +818,10 @@ export async function admitPrepared(preparedFile, capabilitiesFile, options = {}
808
818
  if (!bundle.ok)
809
819
  return bundle;
810
820
  const { prepared, goal, assignment } = bundle.value;
821
+ if (typeof options.controllerHarness === 'string' &&
822
+ assignment.assignee.harness !== options.controllerHarness) {
823
+ return failure('invalid', 'controller_harness_mismatch', `This controller launches ${options.controllerHarness} assignments only; the prepared assignment names ${assignment.assignee.harness}.`, 'Prepare the goal for the controller-supported harness or choose the matching controller.');
824
+ }
811
825
  const card = (() => {
812
826
  try {
813
827
  return JSON.parse(fs.readFileSync(capabilitiesFile, 'utf8'));
@@ -846,10 +860,13 @@ export async function admitPrepared(preparedFile, capabilitiesFile, options = {}
846
860
  value: { prepared, goal, assignment, admission, token },
847
861
  };
848
862
  }
863
+ /** Real controllers are bound to one harness; fixtures are not and stay explicit fixtures. */
864
+ const CONTROLLER_HARNESS = { 'pi-rpc': 'pi' };
849
865
  export async function startPrepared(preparedFile, options) {
850
866
  const clock = options.clock ?? Date.now();
851
867
  const admitted = await admitPrepared(preparedFile, options.capabilitiesFile, {
852
868
  clock,
869
+ controllerHarness: CONTROLLER_HARNESS[options.controller.id] ?? null,
853
870
  });
854
871
  if (!admitted.ok)
855
872
  return admitted;
@@ -862,7 +879,7 @@ export async function startPrepared(preparedFile, options) {
862
879
  return captured.failure;
863
880
  let token = admitted.value.token;
864
881
  let reservationId = null;
865
- if (options.controllerId === 'fixture') {
882
+ if (options.controller.id === 'fixture') {
866
883
  const reservation = reserveLaunch(prepared.task_root, {
867
884
  expectRevision: prepared.state_revision,
868
885
  clock,
@@ -1197,8 +1214,68 @@ export async function driveRun(started, commands, emit, { clock = Date.now() } =
1197
1214
  });
1198
1215
  continue;
1199
1216
  }
1217
+ const intentContentDigest = intent.content_digest;
1218
+ if (intentContentDigest === null) {
1219
+ send('cancel', command.request_id, 'cancel-intent-unverified', 'The registered CANCEL has no content digest; native cancellation was not called.', {
1220
+ intent: true,
1221
+ native: {
1222
+ called: false,
1223
+ reason: 'cancel_intent_digest_missing',
1224
+ },
1225
+ });
1226
+ continue;
1227
+ }
1228
+ const registered = await readMessageFromTaskRoot(started.prepared.task_root, intent.message_id);
1229
+ const registeredMessage = registered.ok
1230
+ ? registered.value.message
1231
+ : null;
1232
+ const entry = registered.ok ? registered.value.entry : null;
1233
+ const sameIdentity = (actual, expected) => actual !== null &&
1234
+ actual !== undefined &&
1235
+ actual.agent_id === expected.agent_id &&
1236
+ actual.session_id === expected.session_id &&
1237
+ (actual.harness ?? null) === (expected.harness ?? null);
1238
+ const intentApplied = registeredMessage !== null &&
1239
+ entry !== null &&
1240
+ intentContentDigest !== null &&
1241
+ registeredMessage.message_id === intent.message_id &&
1242
+ registeredMessage.type === 'CANCEL' &&
1243
+ registeredMessage.task_id === started.prepared.task_id &&
1244
+ registeredMessage.subtask_id === started.prepared.subtask_id &&
1245
+ registeredMessage.attempt === started.prepared.attempt &&
1246
+ registeredMessage.owner_epoch ===
1247
+ started.goal.coordinator.owner_epoch &&
1248
+ registeredMessage.content_digest === intentContentDigest &&
1249
+ sameIdentity(registeredMessage.from, started.goal.coordinator) &&
1250
+ sameIdentity(registeredMessage.to, started.assignment.assignee) &&
1251
+ entry.message_id === intent.message_id &&
1252
+ entry.type === 'CANCEL' &&
1253
+ entry.state === 'applied' &&
1254
+ entry.subtask_id === started.prepared.subtask_id &&
1255
+ entry.attempt === started.prepared.attempt &&
1256
+ entry.owner_epoch === started.goal.coordinator.owner_epoch &&
1257
+ entry.content_digest === intentContentDigest &&
1258
+ sameIdentity(entry.from, started.goal.coordinator) &&
1259
+ sameIdentity(entry.to, started.assignment.assignee);
1260
+ if (!registered.ok || !intentApplied) {
1261
+ send('cancel', command.request_id, 'cancel-intent-unverified', 'The registered CANCEL could not be verified as the applied intent for this run; native cancellation was not called.', {
1262
+ intent: true,
1263
+ native: {
1264
+ called: false,
1265
+ reason: registered.ok
1266
+ ? 'cancel_intent_not_applied'
1267
+ : registered.code,
1268
+ },
1269
+ });
1270
+ continue;
1271
+ }
1272
+ const cancellationIntent = {
1273
+ messageId: intent.message_id,
1274
+ contentDigest: intentContentDigest,
1275
+ fileDigest: registered.value.digest,
1276
+ };
1200
1277
  try {
1201
- const cancel = await started.session.cancel();
1278
+ const cancel = await started.session.cancel(cancellationIntent);
1202
1279
  send('cancel', command.request_id, 'cancel-requested', null, {
1203
1280
  intent: true,
1204
1281
  native: cancel,
@@ -1295,7 +1372,11 @@ export class FixtureController {
1295
1372
  }
1296
1373
  }
1297
1374
  export class PiRpcController {
1375
+ options;
1298
1376
  id = 'pi-rpc';
1377
+ constructor(options = {}) {
1378
+ this.options = options;
1379
+ }
1299
1380
  async start(token, request) {
1300
1381
  if (!validateLaunchToken(token, {
1301
1382
  taskRoot: request.taskRoot,
@@ -1312,6 +1393,7 @@ export class PiRpcController {
1312
1393
  };
1313
1394
  }
1314
1395
  const started = await startOwnedPiRpc({
1396
+ ...this.options,
1315
1397
  taskRoot: request.taskRoot,
1316
1398
  cwd: request.assignment.assignee.cwd,
1317
1399
  assignmentFile: request.prepared.assignment_path,
@@ -1416,7 +1498,7 @@ export class PiRpcController {
1416
1498
  };
1417
1499
  }
1418
1500
  },
1419
- cancel: async () => {
1501
+ cancel: async (intent) => {
1420
1502
  const namespaceProblem = liveNamespaceProblem();
1421
1503
  if (namespaceProblem !== null) {
1422
1504
  return {
@@ -1428,7 +1510,7 @@ export class PiRpcController {
1428
1510
  },
1429
1511
  };
1430
1512
  }
1431
- const cancel = await started.session.cancel();
1513
+ const cancel = await started.session.cancel(intent === undefined ? undefined : { intent });
1432
1514
  const entry = cancel !== null && typeof cancel === 'object'
1433
1515
  ? cancel
1434
1516
  : {};
@@ -1641,3 +1723,10 @@ export function inspectPreparedHistory(preparedFile) {
1641
1723
  },
1642
1724
  };
1643
1725
  }
1726
+ // Read-only work-plan inspection is exposed alongside the collaboration app entry so an
1727
+ // installed consumer can evaluate a persisted plan without a second assignment registry.
1728
+ export { COLLAB_WORK_PLAN_REPORT_SCHEMA, inspectWorkPlan, } from './collab-work-plan.js';
1729
+ // Read-only rollover inspection is exposed alongside the collaboration app entry so an
1730
+ // installed consumer can inspect the exact record and linked bundle. There is no apply
1731
+ // path here: caller JSON is never accepted as native stop proof.
1732
+ export { COLLAB_ROLLOVER_REPORT_SCHEMA, COLLAB_ROLLOVER_REQUEST_SCHEMA, inspectOrPlanRollover, loadRolloverRequest, } from './collab-rollover.js';
@@ -1,4 +1,4 @@
1
- import { join } from 'node:path';
1
+ import { isAbsolute, join, resolve } from 'node:path';
2
2
  import { homedir } from 'node:os';
3
3
  import { diagnosticCodes } from '../schemas/errors.js';
4
4
  import { DOCTOR_REPORT_SCHEMA, doctorReportSchema, } from '../schemas/doctor.js';
@@ -10,6 +10,9 @@ import { resolveWorkspaceRoot } from '../workspace/resolve.js';
10
10
  import { resolveUserDataDir, inspectRuntimeStatePassive, } from '../install/runtime.js';
11
11
  import { ensureStatePathBoundary, inspectOwnedFile, readInstallJournal, readWorkspaceManifest, rootIdentityOf, scanJournalDirectory, workspaceInstallPaths, } from '../install/ownership.js';
12
12
  import { detectPassiveHosts, detectPassiveTool } from '../hosts/detect.js';
13
+ import { inspectWorkspaceLocks } from '../install/repair.js';
14
+ import { inspectLocalResetState } from '../install/reset-local.js';
15
+ import { inspectPrivateLock } from '../infra/private-lock.js';
13
16
  function truncateText(value, limit) {
14
17
  return value.length <= limit
15
18
  ? value
@@ -127,7 +130,9 @@ async function inspectInstallState(root, observedAt) {
127
130
  actions.push(`Review ${entry.path}; rollback/uninstall will preserve the user-modified file.`);
128
131
  }
129
132
  if (entry.backupRef !== null) {
130
- const backupPath = join(root, ...entry.backupRef.split('/'));
133
+ const backupPath = isAbsolute(entry.backupRef)
134
+ ? resolve(entry.backupRef)
135
+ : join(root, ...entry.backupRef.split('/'));
131
136
  const backupBoundary = await ensureStatePathBoundary(root, backupPath);
132
137
  const realBackup = await resolveRealpath(backupPath);
133
138
  const backupEscapes = realBackup !== null &&
@@ -247,6 +252,19 @@ function runtimeCheck(targetVersion, machineRuntime, passive, machinePath, obser
247
252
  nextActions: ['Run pappra init to establish a workspace runtime target.'],
248
253
  });
249
254
  }
255
+ if (passive?.status === 'incompatible') {
256
+ return makeCheck({
257
+ id: 'runtime.state',
258
+ code: diagnosticCodes.runtimeStateIncompatible,
259
+ status: 'error',
260
+ severity: 'error',
261
+ title: 'Runtime state requires reinitialization',
262
+ summary: diagnosticsSummary(passive.diagnostics),
263
+ source: passive.runtimeRoot,
264
+ observedAt,
265
+ nextActions: passive.diagnostics.flatMap((entry) => entry.hint === null ? [] : [entry.hint]),
266
+ });
267
+ }
250
268
  if (machineRuntime === null) {
251
269
  if (passive === null) {
252
270
  return makeCheck({
@@ -663,6 +681,63 @@ export async function runDoctor(options = {}) {
663
681
  ],
664
682
  }));
665
683
  if (root !== null) {
684
+ const resetState = await inspectLocalResetState(root);
685
+ if (resetState === 'pending' || resetState === 'unreadable')
686
+ checks.push(makeCheck({
687
+ id: 'state.local-reset',
688
+ code: diagnosticCodes.initApplyResidual,
689
+ status: 'warning',
690
+ severity: 'warning',
691
+ title: 'Private local reset',
692
+ summary: `Local reset metadata is ${resetState}; retained archives were not changed.`,
693
+ source: join(root, '.pappra/local-archive/pending-reset.json'),
694
+ observedAt,
695
+ coverage: 'read-only local reset journal status; no process is stopped or adopted',
696
+ evidence: [],
697
+ nextActions: [
698
+ 'After stopping all affected writers, resume with pappra repair --reset-local --all-writers-stopped --apply.',
699
+ ],
700
+ }));
701
+ try {
702
+ const locks = await inspectWorkspaceLocks(root);
703
+ const needsRepair = locks.some((lock) => lock.state === 'dead' || lock.state === 'unknown');
704
+ checks.push(makeCheck({
705
+ id: 'state.writer-locks',
706
+ code: diagnosticCodes.document_locked,
707
+ status: needsRepair ? 'warning' : 'ok',
708
+ severity: needsRepair ? 'warning' : 'info',
709
+ title: 'Private writer locks',
710
+ summary: locks.length === 0
711
+ ? 'No private writer lock remains.'
712
+ : locks.map((lock) => `${lock.state}: ${lock.file}`).join('; '),
713
+ source: join(root, '.pappra/local'),
714
+ observedAt,
715
+ coverage: 'bounded install, knowledge and checkpoint lock inventory; process identity or positive owner-death evidence',
716
+ evidence: locks.map((lock) => fileEvidence(lock.file, observedAt, lock.digest)),
717
+ nextActions: needsRepair
718
+ ? [
719
+ 'Run pappra repair to inspect owner states; use --apply only for provably dead writers. Unknown/live owners are preserved.',
720
+ ]
721
+ : [],
722
+ }));
723
+ }
724
+ catch (error) {
725
+ checks.push(makeCheck({
726
+ id: 'state.writer-locks',
727
+ code: diagnosticCodes.document_locked,
728
+ status: 'error',
729
+ severity: 'error',
730
+ title: 'Private writer locks',
731
+ summary: `Cannot inspect private lock state: ${error instanceof Error ? error.message : String(error)}`,
732
+ source: join(root, '.pappra/local'),
733
+ observedAt,
734
+ coverage: 'private lock paths are unsafe or inventory was unavailable',
735
+ evidence: [],
736
+ nextActions: [
737
+ 'Inspect pappra repair output and the reported path; no lock was changed.',
738
+ ],
739
+ }));
740
+ }
666
741
  const workspaceInspection = await inspectWorkspaceConfig(root, {
667
742
  missing: 'info',
668
743
  });
@@ -886,6 +961,7 @@ export async function runDoctor(options = {}) {
886
961
  ],
887
962
  }));
888
963
  const selectedHosts = machine?.selectedHosts ?? [];
964
+ const observableHosts = ['pi', 'codex', 'claude'];
889
965
  // Passive observation only: a host install can target the private default root, a
890
966
  // project directory, or Codex's user configuration directory.
891
967
  const hostInstallRoots = (host) => {
@@ -898,7 +974,7 @@ export async function runDoctor(options = {}) {
898
974
  }
899
975
  return roots;
900
976
  };
901
- const installedHostBundle = (await Promise.all(selectedHosts.map(async (host) => {
977
+ const installedHostBundle = (await Promise.all(observableHosts.map(async (host) => {
902
978
  for (const base of hostInstallRoots(host)) {
903
979
  const path = join(base, 'pappra-host.json');
904
980
  if ((await observePath(path)).kind === 'file')
@@ -906,7 +982,34 @@ export async function runDoctor(options = {}) {
906
982
  }
907
983
  return null;
908
984
  }))).find((path) => path !== null) ?? null;
909
- const installedHostSkill = (await Promise.all(selectedHosts.map(async (host) => {
985
+ const hostLocks = (await Promise.all(observableHosts.flatMap((host) => hostInstallRoots(host).map(async (base) => ({
986
+ host,
987
+ base,
988
+ owner: await inspectPrivateLock(join(base, '.pappra-host.lock')),
989
+ }))))).filter((entry) => entry.owner.state !== 'missing');
990
+ if (hostLocks.length > 0)
991
+ checks.push(makeCheck({
992
+ id: 'host.install-locks',
993
+ code: diagnosticCodes.host_install_conflict,
994
+ status: hostLocks.every((entry) => entry.owner.state === 'alive')
995
+ ? 'partial'
996
+ : 'warning',
997
+ severity: hostLocks.every((entry) => entry.owner.state === 'alive')
998
+ ? 'info'
999
+ : 'warning',
1000
+ title: 'Host installation locks',
1001
+ summary: `${hostLocks.length} host lock(s) observed; live owners are retained, dead owners can be reclaimed, and unknown owners require an explicit stopped-writer assertion.`,
1002
+ source: hostLocks[0].owner.file,
1003
+ observedAt,
1004
+ coverage: 'known project/private/user host roots only',
1005
+ evidence: hostLocks.map((entry) => fileEvidence(entry.owner.file, observedAt, entry.owner.digest)),
1006
+ nextActions: hostLocks.map((entry) => entry.owner.state === 'alive'
1007
+ ? `Let the verified live ${entry.host} writer finish; --force cannot override it.`
1008
+ : entry.owner.state === 'dead'
1009
+ ? `Retry host install --host ${entry.host} --host-root ${JSON.stringify(entry.base)} to reclaim the proven dead owner.`
1010
+ : `Stop all writers for ${entry.base}, then run pappra host uninstall --host ${entry.host} --host-root ${JSON.stringify(entry.base)} --force --apply to archive an unknown lock.`),
1011
+ }));
1012
+ const installedHostSkill = (await Promise.all(observableHosts.map(async (host) => {
910
1013
  for (const base of hostInstallRoots(host)) {
911
1014
  const path = join(base, 'skills', 'pappra', 'SKILL.md');
912
1015
  if ((await observePath(path)).kind === 'file')
@@ -922,7 +1025,7 @@ export async function runDoctor(options = {}) {
922
1025
  severity: 'info',
923
1026
  title: 'Host integration',
924
1027
  summary: installedHostBundle === null
925
- ? 'No P09 host bundle manifest was observed under the selected host roots; no host, session, or model was started.'
1028
+ ? 'No host bundle manifest was observed under the known host roots; no host, session, or model was started.'
926
1029
  : `P09 host bundle manifest exists at ${installedHostBundle}; native trust, reload and callability remain unverified and no host was started.`,
927
1030
  source: installedHostBundle ?? root ?? homeDir,
928
1031
  observedAt,
@@ -1,6 +1,7 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
2
  import { dirname } from 'node:path';
3
+ import { observePath, readTextFileBounded } from '../infra/fs.js';
4
+ import { ensureStatePathBoundary, writeJsonAtomic, } from '../install/ownership.js';
4
5
  const MCP_ENTRY = { command: 'pappra', args: ['mcp'] };
5
6
  function mcpConfigPath(host, root) {
6
7
  if (host === 'claude')
@@ -19,10 +20,26 @@ function sameEntry(value) {
19
20
  args.length === MCP_ENTRY.args.length &&
20
21
  args.every((arg, index) => arg === MCP_ENTRY.args[index]));
21
22
  }
22
- async function readConfig(path) {
23
+ async function readConfig(path, root) {
23
24
  let text;
24
25
  try {
25
- text = await readFile(path, 'utf8');
26
+ const boundary = await ensureStatePathBoundary(root, path);
27
+ if (!boundary.ok)
28
+ return { ok: false, message: boundary.message };
29
+ const observation = await observePath(path);
30
+ if (observation.kind === 'missing') {
31
+ return { ok: true, value: {}, exists: false };
32
+ }
33
+ if (observation.kind !== 'file') {
34
+ return {
35
+ ok: false,
36
+ message: `MCP config is not a regular file: ${path}`,
37
+ };
38
+ }
39
+ const read = await readTextFileBounded(path);
40
+ if (!read.ok)
41
+ return { ok: false, message: read.message };
42
+ text = read.value.text;
26
43
  }
27
44
  catch (error) {
28
45
  if (typeof error === 'object' &&
@@ -72,7 +89,7 @@ export async function planHostMcp(host, root) {
72
89
  message: `No MCP registration is defined for ${host}.`,
73
90
  };
74
91
  }
75
- const config = await readConfig(target);
92
+ const config = await readConfig(target, root);
76
93
  if (!config.ok) {
77
94
  return {
78
95
  host,
@@ -150,7 +167,8 @@ export async function applyHostMcp(plan, options) {
150
167
  if (target === null) {
151
168
  return { ...plan, applied: false, verified: false, detail: plan.message };
152
169
  }
153
- const config = await readConfig(target);
170
+ const root = plan.host === 'pi' ? dirname(dirname(target)) : dirname(target);
171
+ const config = await readConfig(target, root);
154
172
  if (!config.ok) {
155
173
  return { ...plan, applied: false, verified: false, detail: config.message };
156
174
  }
@@ -161,8 +179,15 @@ export async function applyHostMcp(plan, options) {
161
179
  : {}),
162
180
  pappra: MCP_ENTRY,
163
181
  };
164
- await mkdir(dirname(target), { recursive: true });
165
- await writeFile(target, `${JSON.stringify({ ...config.value, mcpServers: nextServers }, null, 2)}\n`);
182
+ const written = await writeJsonAtomic(target, { ...config.value, mcpServers: nextServers }, root);
183
+ if (!written.ok) {
184
+ return {
185
+ ...plan,
186
+ applied: false,
187
+ verified: false,
188
+ detail: written.message,
189
+ };
190
+ }
166
191
  const verified = await verifyHostMcp(plan);
167
192
  return {
168
193
  ...plan,
@@ -210,7 +235,8 @@ export async function verifyHostMcp(plan) {
210
235
  if (plan.target === null) {
211
236
  return { ...plan, applied: false, verified: false, detail: plan.message };
212
237
  }
213
- const config = await readConfig(plan.target);
238
+ const root = plan.host === 'pi' ? dirname(dirname(plan.target)) : dirname(plan.target);
239
+ const config = await readConfig(plan.target, root);
214
240
  if (!config.ok) {
215
241
  return { ...plan, applied: false, verified: false, detail: config.message };
216
242
  }
@@ -3,7 +3,7 @@ import { mkdtempSync, rmSync } from 'node:fs';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
- import { HOST_SUPPORT } from '../hosts/integration/support.js';
6
+ import { HOST_SUPPORT, isSupportedVersion, } from '../hosts/integration/support.js';
7
7
  function commandVersion(command) {
8
8
  const result = spawnSync(command, ['--version'], {
9
9
  encoding: 'utf8',
@@ -42,10 +42,10 @@ export function probeHost(host) {
42
42
  version,
43
43
  pinnedVersion,
44
44
  protocol: 'version-only',
45
- ok: version === pinnedVersion,
45
+ ok: isSupportedVersion(host, version),
46
46
  details: { matchesPinnedVersion: version === pinnedVersion },
47
- message: version === pinnedVersion
48
- ? 'Version matches the pinned support matrix; lifecycle callability still needs a real session.'
47
+ message: isSupportedVersion(host, version)
48
+ ? 'Version matches an observed event contract; lifecycle callability still needs a real session.'
49
49
  : `Version ${version} does not match the pinned ${pinnedVersion}.`,
50
50
  };
51
51
  }
@@ -3,6 +3,7 @@ import type { DiagnosticCode } from '../schemas/errors.js';
3
3
  import { type InitChoices, type InitExternalAction, type InitPlan } from '../schemas/init.js';
4
4
  import { type MachineConfig } from '../schemas/machine.js';
5
5
  import { type WorkspaceConfig, type WorkspaceValidation } from '../schemas/workspace.js';
6
+ import { type GitRepositoryObservation } from '../infra/git.js';
6
7
  import type { MachineConfigInspection, WorkspaceConfigInspection } from '../workspace/config.js';
7
8
  import { type WorkspaceDiscovery } from '../workspace/discover.js';
8
9
  import { type PlannedFileContent } from '../install/plan.js';
@@ -96,6 +97,8 @@ export interface InitApplyResult {
96
97
  export interface InitApplyOptions {
97
98
  /** Fault-injection seam for tests; the CLI never supplies this hook. */
98
99
  readonly beforeWrite?: (relativePath: string, index: number) => Promise<void> | void;
100
+ /** Test seam after durable create, before its identity is journaled; never supplied by CLI. */
101
+ readonly afterWrite?: (relativePath: string, index: number) => Promise<void> | void;
99
102
  }
100
103
  export interface KnowledgeCandidate {
101
104
  readonly path: string;
@@ -113,6 +116,27 @@ export interface RepositoryCandidate {
113
116
  readonly dirty: boolean | null;
114
117
  readonly configured: boolean;
115
118
  }
119
+ export type InitPathAdditionKind = 'repository' | 'knowledge';
120
+ /** Read-only snapshot of one exact, explicitly entered workspace-relative path. */
121
+ export interface InitPathObservation {
122
+ readonly kind: InitPathAdditionKind;
123
+ readonly requestedPath: string;
124
+ /** Canonical workspace-relative path suitable for an existing InitChoices field. */
125
+ readonly path: string;
126
+ readonly realPath: string;
127
+ readonly targetKind: 'file' | 'directory';
128
+ readonly device: number;
129
+ readonly inode: number;
130
+ /** Actual bounded Git metadata observation for repository additions. */
131
+ readonly git: GitRepositoryObservation | null;
132
+ }
133
+ export type InitPathInspectionResult = {
134
+ readonly ok: true;
135
+ readonly observation: InitPathObservation;
136
+ } | {
137
+ readonly ok: false;
138
+ readonly diagnostics: readonly Diagnostic[];
139
+ };
116
140
  export interface PlanInitOptions {
117
141
  readonly root: string;
118
142
  readonly choices?: InitChoices | null;
@@ -123,6 +147,8 @@ export interface PlanInitOptions {
123
147
  readonly discoveredAt?: string;
124
148
  /** Reuse an immediately preceding inspectInitContext result. */
125
149
  readonly context?: InitContext;
150
+ /** In-memory UI observations; planning re-inspects each path before use. */
151
+ readonly pathObservations?: readonly InitPathObservation[];
126
152
  }
127
153
  /** Resolves the explicit root and refuses to create a hidden nested workspace. */
128
154
  export declare function resolveInitRoot(rootPath: string): Promise<InitRootResult>;
@@ -151,6 +177,32 @@ export interface DerivedInitChoices {
151
177
  readonly rules: readonly InitChoiceRule[];
152
178
  readonly candidates: InitChoiceCandidates;
153
179
  }
180
+ export interface CommonInitDefaults {
181
+ readonly name: string;
182
+ readonly knowledge: InitChoices['knowledge'];
183
+ readonly code: Pick<InitChoices['code'], 'symbols' | 'prepare'>;
184
+ readonly excludes: readonly string[];
185
+ }
186
+ /**
187
+ * Pure discovery-based selections shared by derived and interactive setup.
188
+ * Mode-specific repositories, validation, graph and host choices stay with
189
+ * their callers.
190
+ */
191
+ export declare function deriveCommonInitDefaults(discovery: WorkspaceDiscovery): CommonInitDefaults;
192
+ /**
193
+ * Converts an existing valid workspace/machine pair into a lossless editable
194
+ * selection draft. Extensions and continuity are intentionally not part of
195
+ * InitChoices; plan reconstruction preserves them from the existing config.
196
+ */
197
+ export declare function existingInitChoices(config: WorkspaceConfig, machine: MachineConfig | null): InitChoices;
198
+ export type InitEditableField = 'name' | 'newDocuments' | 'excludes' | 'repositoryPath' | 'knowledgePath';
199
+ /**
200
+ * Immediate UI field feedback using the same name schema, portable-path rules,
201
+ * literal-prefix exclusion policy and workspace-relative semantics as planning.
202
+ */
203
+ export declare function validateInitField(field: InitEditableField, value: string): string | null;
204
+ /** Inspects one user-entered path only; it never broadens workspace discovery. */
205
+ export declare function inspectInitPathAddition(root: string, kind: InitPathAdditionKind, inputPath: string, excludes?: readonly string[], location?: string | null): Promise<InitPathInspectionResult>;
154
206
  export declare function detectedHostVersion(command: string, pathEnv: string): string | null;
155
207
  /**
156
208
  * Derives deterministic init selections from discovery alone. Every value is
@@ -166,7 +218,7 @@ export declare function deriveInitChoices(discovery: WorkspaceDiscovery, options
166
218
  */
167
219
  export declare function planWorkspaceInit(options: PlanInitOptions): Promise<InitPreparation>;
168
220
  /**
169
- * Applies only create operations from a plan, after revalidating root,
221
+ * Applies create and managed replacement operations from a plan, after revalidating root,
170
222
  * configuration digest, target absence and exact bytes. External actions are
171
223
  * reported as pending and never executed here.
172
224
  */
@@ -179,8 +231,10 @@ export interface RuntimeFlowResult {
179
231
  }
180
232
  export declare function bindAndRecordRuntime(root: string, installed: RuntimeInstallResult): Promise<RuntimeFlowResult>;
181
233
  /**
182
- * Converges the runtime binding by installing a durable copy of the package that
183
- * is currently executing. Reusing an already verified copy is cheap: the install
184
- * machinery short-circuits before packing.
234
+ * Ensures and binds the runtime package that is currently executing.
235
+ *
236
+ * The installer reuses an existing runtime only after its manifest, package
237
+ * contents, executable and smoke checks pass; a regular file is not proof of a
238
+ * verified durable runtime.
185
239
  */
186
240
  export declare function ensureDefaultRuntime(root: string, targetVersion: string): Promise<RuntimeFlowResult>;