@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
@@ -18,7 +18,7 @@ export const RESOURCE_KINDS = Object.freeze([...PATH_KINDS, ...OPAQUE_KINDS].sor
18
18
  /**
19
19
  * Resolve the longest existing ancestor with realpath and append the not-yet-existing
20
20
  * suffix. Symlink aliases therefore collapse to one key. The suffix keeps the caller's
21
- * spelling, so a differently-cased non-existent target is not normalised.
21
+ * spelling for I/O; comparison keys account for the containing filesystem's case behavior.
22
22
  */
23
23
  export function canonicalizeTarget(input) {
24
24
  if (typeof input !== 'string' || input.trim().length === 0)
@@ -29,7 +29,7 @@ export function canonicalizeTarget(input) {
29
29
  let probe = path.normalize(input);
30
30
  for (;;) {
31
31
  try {
32
- const real = fs.realpathSync(probe);
32
+ const real = fs.realpathSync.native(probe);
33
33
  return {
34
34
  path: path.join(real, ...missing.slice().reverse()),
35
35
  existing: missing.length === 0,
@@ -47,8 +47,49 @@ export function canonicalizeTarget(input) {
47
47
  }
48
48
  }
49
49
  }
50
+ /** Probe existing directory aliases without creating files or assuming all macOS volumes match. */
51
+ function caseInsensitiveAt(input) {
52
+ let probe = input;
53
+ for (;;) {
54
+ try {
55
+ const observed = fs.statSync(probe);
56
+ if (!observed.isDirectory()) {
57
+ probe = path.dirname(probe);
58
+ continue;
59
+ }
60
+ const name = path.basename(probe);
61
+ const flipped = name.replace(/[a-zA-Z]/, (letter) => letter === letter.toLowerCase()
62
+ ? letter.toUpperCase()
63
+ : letter.toLowerCase());
64
+ if (flipped !== name) {
65
+ try {
66
+ const alias = fs.statSync(path.join(path.dirname(probe), flipped));
67
+ return alias.dev === observed.dev && alias.ino === observed.ino;
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ }
73
+ }
74
+ catch {
75
+ // Missing suffixes inherit the closest existing directory's behavior.
76
+ }
77
+ const parent = path.dirname(probe);
78
+ if (parent === probe)
79
+ return process.platform === 'win32';
80
+ probe = parent;
81
+ }
82
+ }
83
+ function comparisonPath(input) {
84
+ const resolved = canonicalizeTarget(input);
85
+ return caseInsensitiveAt(resolved.nearest_ancestor)
86
+ ? resolved.path.normalize('NFC').toUpperCase().toLowerCase()
87
+ : resolved.path;
88
+ }
50
89
  /** `same`, `ancestor`, `descendant` or `disjoint` on path-segment boundaries. */
51
90
  export function pathRelation(left, right) {
91
+ left = comparisonPath(left);
92
+ right = comparisonPath(right);
52
93
  if (left === right)
53
94
  return 'same';
54
95
  const a = left.split(path.sep).filter(Boolean);
@@ -110,7 +151,7 @@ export function describeResource(input) {
110
151
  return {
111
152
  kind,
112
153
  scope,
113
- key: `${kind}:${resolved.path}`,
154
+ key: `${kind}:${comparisonPath(resolved.path)}`,
114
155
  path: resolved.path,
115
156
  existing: resolved.existing,
116
157
  label: input.label ?? `${kind} ${resolved.path}`,
@@ -1,11 +1,10 @@
1
1
  import type { Message, MessageInput } from './types.js';
2
+ export { canonicalJson } from '../infra/json.js';
2
3
  export declare const MESSAGE_SCHEMA = "harness-collab/message/1";
3
4
  export declare const MESSAGE_TYPES: readonly string[];
4
5
  export declare const REPORT_STATUSES: readonly string[];
5
6
  export declare const BLOCK_KINDS: readonly string[];
6
7
  export declare function validateEnvelope(message: unknown): Message;
7
- /** Stable JSON with sorted object keys, used for content-identity digests. */
8
- export declare function canonicalJson(value: unknown): string;
9
8
  /**
10
9
  * Content identity of a message: everything that decides what the message means.
11
10
  * `created_at` and `content_digest` are excluded so an honest resend is still the
@@ -15,6 +14,7 @@ export declare function contentDigest(message: Message): string;
15
14
  export declare function buildMessage(input: MessageInput, { clock }?: {
16
15
  clock?: number | undefined;
17
16
  }): Message;
17
+ export declare function isSafeMessageId(value: unknown): value is string;
18
18
  export declare function messageFile(taskRoot: string, messageId: string): string;
19
19
  export declare function readRegisteredMessage(taskRoot: string, messageId: string): Message | null;
20
20
  /**
@@ -2,6 +2,8 @@ import { errorCode, parseJson, object, text, isMessage } from './validation.js';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { createNonce, digestText, messagesDir, nowIso } from './store.js';
5
+ import { canonicalJson } from '../infra/json.js';
6
+ export { canonicalJson } from '../infra/json.js';
5
7
  // Message envelopes and their idempotency rules.
6
8
  //
7
9
  // Registration is an exclusive create of `messages/<message_id>.json`, which is atomic,
@@ -99,17 +101,6 @@ export function validateEnvelope(message) {
99
101
  // Unknown extension fields are tolerated on purpose; unknown semantics are not inferred.
100
102
  return message;
101
103
  }
102
- /** Stable JSON with sorted object keys, used for content-identity digests. */
103
- export function canonicalJson(value) {
104
- if (value === null || typeof value !== 'object')
105
- return JSON.stringify(value ?? null);
106
- if (Array.isArray(value))
107
- return `[${value.map((item) => canonicalJson(item)).join(',')}]`;
108
- if (!object(value))
109
- throw new Error('Canonical JSON requires an object');
110
- const keys = Object.keys(value).sort();
111
- return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
112
- }
113
104
  /**
114
105
  * Content identity of a message: everything that decides what the message means.
115
106
  * `created_at` and `content_digest` are excluded so an honest resend is still the
@@ -149,8 +140,17 @@ export function buildMessage(input, { clock = Date.now() } = {}) {
149
140
  message.content_digest = contentDigest(message);
150
141
  return message;
151
142
  }
143
+ /**
144
+ * Message ids are single path segments: a letter or digit followed by letters,
145
+ * digits, dot, dash or underscore, bounded at 128 characters. Anything else
146
+ * (`..`, separators, control characters) is rejected before any path is built.
147
+ */
148
+ const SAFE_MESSAGE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
149
+ export function isSafeMessageId(value) {
150
+ return typeof value === 'string' && SAFE_MESSAGE_ID.test(value);
151
+ }
152
152
  export function messageFile(taskRoot, messageId) {
153
- if (/[\\/\0]/.test(messageId))
153
+ if (!isSafeMessageId(messageId))
154
154
  throw new Error('Invalid message field: message.message_id');
155
155
  return path.join(messagesDir(taskRoot), `${messageId}.json`);
156
156
  }
@@ -0,0 +1,3 @@
1
+ import type { ResultEntry, RolloverIntent } from './types.js';
2
+ /** A native turn may prove succession while the attempt remains active. */
3
+ export declare function isNativeFirstWorkResult(result: ResultEntry, intent: RolloverIntent): boolean;
@@ -0,0 +1,15 @@
1
+ /** A native turn may prove succession while the attempt remains active. */
2
+ export function isNativeFirstWorkResult(result, intent) {
3
+ if (result.status !== 'running' ||
4
+ typeof result.native_ref !== 'object' ||
5
+ result.native_ref === null)
6
+ return false;
7
+ const ref = result.native_ref;
8
+ return (ref.schema === 'pappra/native-first-work/1' &&
9
+ ref.handoff_id === intent.handoff_id &&
10
+ ref.generation === intent.generation &&
11
+ ref.session_id === intent.to.session_id &&
12
+ ref.observation === 'native_agent_settled' &&
13
+ typeof ref.evidence_ref === 'string' &&
14
+ ref.evidence_ref.length > 0);
15
+ }
@@ -21,6 +21,7 @@ export declare function beginHandoff(record: TaskRecord, { handoffId, kind, subt
21
21
  other_state?: never;
22
22
  expected?: never;
23
23
  actual?: never;
24
+ claim_id?: never;
24
25
  handoff_id?: never;
25
26
  state?: never;
26
27
  } | {
@@ -31,6 +32,7 @@ export declare function beginHandoff(record: TaskRecord, { handoffId, kind, subt
31
32
  shared_resources: string[];
32
33
  expected?: never;
33
34
  actual?: never;
35
+ claim_id?: never;
34
36
  handoff_id?: never;
35
37
  state?: never;
36
38
  } | {
@@ -41,6 +43,7 @@ export declare function beginHandoff(record: TaskRecord, { handoffId, kind, subt
41
43
  other_state: string;
42
44
  expected?: never;
43
45
  actual?: never;
46
+ claim_id?: never;
44
47
  handoff_id?: never;
45
48
  state?: never;
46
49
  } | {
@@ -51,15 +54,28 @@ export declare function beginHandoff(record: TaskRecord, { handoffId, kind, subt
51
54
  reason: string;
52
55
  expected: number | null;
53
56
  actual: number;
57
+ claim_id?: never;
54
58
  handoff_id?: never;
55
59
  state?: never;
56
60
  } | {
57
61
  shared_resources?: never;
58
62
  other_handoff?: never;
59
63
  other_state?: never;
64
+ expected?: never;
65
+ actual?: never;
66
+ ok: false;
67
+ reason: string;
68
+ claim_id: string;
69
+ handoff_id?: never;
70
+ state?: never;
71
+ } | {
60
72
  reason?: never;
73
+ shared_resources?: never;
74
+ other_handoff?: never;
75
+ other_state?: never;
61
76
  expected?: never;
62
77
  actual?: never;
78
+ claim_id?: never;
63
79
  ok: true;
64
80
  handoff_id: string;
65
81
  state: string;
@@ -71,12 +71,46 @@ export function beginHandoff(record, { handoffId, kind, subtaskId = null, attemp
71
71
  actual: ownerEpoch ?? null,
72
72
  };
73
73
  }
74
- if (kind === 'writer') {
75
- const target = findAttempt(record, subtaskId, attempt);
76
- if (!target)
77
- return { ok: false, reason: 'unknown_attempt' };
78
- target.ownership = 'transferring';
74
+ const target = kind === 'writer' ? findAttempt(record, subtaskId, attempt) : null;
75
+ if (kind === 'writer' && !target)
76
+ return { ok: false, reason: 'unknown_attempt' };
77
+ const authority = kind === 'writer' ? target?.assignee : record.coordinator;
78
+ if (!authority ||
79
+ authority.agent_id !== from.agent_id ||
80
+ authority.session_id !== from.session_id)
81
+ return { ok: false, reason: 'unauthorized_sender' };
82
+ const seen = new Set();
83
+ for (const claimId of resources) {
84
+ if (seen.has(claimId))
85
+ return {
86
+ ok: false,
87
+ reason: 'duplicate_handoff_resource',
88
+ claim_id: claimId,
89
+ };
90
+ seen.add(claimId);
91
+ const matches = (record.claims ?? []).filter((claim) => claim.claim_id === claimId);
92
+ if (matches.length !== 1)
93
+ return {
94
+ ok: false,
95
+ reason: matches.length ? 'ambiguous_claim_id' : 'unknown_claim',
96
+ claim_id: claimId,
97
+ };
98
+ const claim = matches[0];
99
+ if (claim.released_at ||
100
+ claim.holder?.agent_id !== from.agent_id ||
101
+ claim.holder?.session_id !== from.session_id ||
102
+ (kind === 'writer' &&
103
+ ((claim.subtask_id !== null && claim.subtask_id !== subtaskId) ||
104
+ (claim.attempt !== null && claim.attempt !== attempt))))
105
+ return {
106
+ ok: false,
107
+ reason: 'handoff_resource_not_owned',
108
+ claim_id: claimId,
109
+ };
79
110
  }
111
+ // Validate the entire inventory before reserving an attempt or freezing dispatch.
112
+ if (target)
113
+ target.ownership = 'transferring';
80
114
  const at = nowIso(clock);
81
115
  const entry = {
82
116
  schema: 'harness-collab/handoff/1',
@@ -1,4 +1,4 @@
1
- export * as store from './store.js';
1
+ import * as storeInternals from './store.js';
2
2
  export * as canonical from './canonical.js';
3
3
  export * as resources from './resources.js';
4
4
  export * as envelope from './envelope.js';
@@ -6,4 +6,100 @@ export * as messages from './messages.js';
6
6
  export * as lifecycle from './lifecycle.js';
7
7
  export * as handoff from './handoff.js';
8
8
  export * as notify from './notify.js';
9
+ export * as workPlan from './work-plan.js';
10
+ export * as rollover from './rollover.js';
9
11
  export type * from './types.js';
12
+ declare const storeSurface: {
13
+ STATE_SCHEMA: "harness-collab/state/1";
14
+ LEDGER_SCHEMA: "harness-collab/ledger/1";
15
+ RECORD_FILE: "task.json";
16
+ LEDGER_FILE: "ledger.jsonl";
17
+ DEFAULT_DOMAIN_ROOT: string;
18
+ LockHeldError: typeof storeInternals.LockHeldError;
19
+ RevisionConflictError: typeof storeInternals.RevisionConflictError;
20
+ digestBytes: (value: string | Uint8Array) => string;
21
+ digestText: (value: string | Uint8Array) => string;
22
+ createNonce(): string;
23
+ nowIso(clock?: number): string;
24
+ isAbsoluteExistingDirectory(value: unknown): boolean;
25
+ resolveDomainRoot(input?: string | null): string;
26
+ taskRootFor(domainRoot: string, taskId: string): string;
27
+ recordPath: (taskRoot: string) => string;
28
+ ledgerPath: (taskRoot: string) => string;
29
+ lockPath: (taskRoot: string) => string;
30
+ messagesDir: (taskRoot: string) => string;
31
+ resultsDir: (taskRoot: string) => string;
32
+ nativeDir: (taskRoot: string) => string;
33
+ abortedDir: (taskRoot: string) => string;
34
+ brokenLocksDir: (taskRoot: string) => string;
35
+ notificationsDir: (taskRoot: string) => string;
36
+ domainIdOf(domainRoot: string): string;
37
+ readRecord(taskRoot: string): import("./types.js").TaskRecord | null;
38
+ readLock(taskRoot: string): {
39
+ held: boolean;
40
+ holder?: never;
41
+ holder_process_alive?: never;
42
+ transaction_files?: never;
43
+ } | {
44
+ held: boolean;
45
+ holder: import("./types.js").LockHolder | null;
46
+ holder_process_alive: boolean;
47
+ transaction_files: string[];
48
+ };
49
+ listTransactions(taskRoot: string): import("./types.js").TransactionFile[];
50
+ acquireLock(taskRoot: string, { actor, purpose, waitMs, pollMs, }?: {
51
+ actor?: string;
52
+ purpose?: string;
53
+ waitMs?: number;
54
+ pollMs?: number;
55
+ }): {
56
+ schema: string;
57
+ nonce: string;
58
+ actor: string;
59
+ purpose: string;
60
+ pid: number;
61
+ hostname: string;
62
+ acquired_at: string;
63
+ };
64
+ releaseLock(taskRoot: string, holder: import("./types.js").LockHolder | null): boolean;
65
+ archiveAbortedTransaction(taskRoot: string, file: string, { actor, at, }?: {
66
+ actor?: string;
67
+ reason?: string;
68
+ confirmHolderStopped?: boolean;
69
+ at?: string;
70
+ }): string;
71
+ transact<T = unknown>(taskRoot: string, { actor, purpose, expectRevision, waitMs, apply, clock, }: {
72
+ actor?: string;
73
+ purpose?: string;
74
+ expectRevision?: number | null | undefined;
75
+ waitMs?: number;
76
+ apply: (record: import("./types.js").TaskRecord) => import("./types.js").Mutation<T> | import("./types.js").Failure | null | undefined;
77
+ clock?: number;
78
+ }): import("./types.js").TransactionResult<T>;
79
+ newRecord({ taskId, domainRoot, taskRoot, coordinator, clock, }: {
80
+ taskId: string;
81
+ domainRoot: string;
82
+ taskRoot: string;
83
+ coordinator?: import("./types.js").Coordinator | null;
84
+ clock?: number;
85
+ }): import("./types.js").TaskRecord;
86
+ initTask(taskRoot: string, { taskId, coordinator, domainRoot: explicitDomainRoot, clock, }: {
87
+ taskId: string;
88
+ coordinator?: import("./types.js").Coordinator | null;
89
+ domainRoot?: string | null;
90
+ clock?: number;
91
+ }): {
92
+ created: boolean;
93
+ record: import("./types.js").TaskRecord | null;
94
+ };
95
+ doctor(taskRoot: string): {
96
+ task_root: string;
97
+ state_revision: null;
98
+ findings: import("./types.js").Finding[];
99
+ } | {
100
+ task_root: string;
101
+ state_revision: number;
102
+ findings: import("./types.js").Finding[];
103
+ };
104
+ };
105
+ export { storeSurface as store };
@@ -1,4 +1,4 @@
1
- export * as store from './store.js';
1
+ import * as storeInternals from './store.js';
2
2
  export * as canonical from './canonical.js';
3
3
  export * as resources from './resources.js';
4
4
  export * as envelope from './envelope.js';
@@ -6,3 +6,11 @@ export * as messages from './messages.js';
6
6
  export * as lifecycle from './lifecycle.js';
7
7
  export * as handoff from './handoff.js';
8
8
  export * as notify from './notify.js';
9
+ export * as workPlan from './work-plan.js';
10
+ export * as rollover from './rollover.js';
11
+ // The public barrel curates the store surface. `breakLock` stays in
12
+ // `./store.js` because the operator-only `collab recovery lock-break` command
13
+ // owns the confirmation flow; exposing the bare primitive invites a caller to
14
+ // break a live writer's lock without that evidence.
15
+ const { breakLock: _unsafeBreakLock, ...storeSurface } = storeInternals;
16
+ export { storeSurface as store };
@@ -35,6 +35,104 @@ function findMessage(record, messageId) {
35
35
  return ((record.messages ?? []).find((item) => item.message_id === messageId) ??
36
36
  null);
37
37
  }
38
+ function sameSender(sender, expected) {
39
+ return (expected?.agent_id === sender.agent_id &&
40
+ expected.session_id === sender.session_id);
41
+ }
42
+ /** An epoch identifies the generation of authority, not the agent holding it. */
43
+ function authorizeSender(record, message) {
44
+ let expected;
45
+ switch (message.type) {
46
+ case 'ASSIGN':
47
+ case 'CANCEL':
48
+ expected = record.coordinator;
49
+ break;
50
+ case 'HANDOFF': {
51
+ if (message.body.phase !== 'request') {
52
+ const handoff = record.handoffs.find((item) => item.handoff_id === message.body.handoff_id);
53
+ if (handoff &&
54
+ (sameSender(message.from, handoff.from) ||
55
+ sameSender(message.from, handoff.to)))
56
+ return null;
57
+ return { ok: false, reason: 'unauthorized_sender' };
58
+ }
59
+ if (message.body.kind === 'coordinator') {
60
+ expected = record.coordinator;
61
+ break;
62
+ }
63
+ expected = findAttempt(record, message.subtask_id, message.attempt)?.assignee;
64
+ break;
65
+ }
66
+ case 'ACK':
67
+ case 'BLOCK':
68
+ case 'CANCELLED': {
69
+ const referenced = message.reply_to
70
+ ? findMessage(record, message.reply_to)
71
+ : null;
72
+ const subtaskId = message.subtask_id ?? referenced?.subtask_id ?? null;
73
+ const attempt = message.attempt ?? referenced?.attempt ?? null;
74
+ const target = findAttempt(record, subtaskId, attempt);
75
+ if (message.type === 'ACK') {
76
+ const handoff = record.handoffs.find((item) => (item.handoff_id === message.body.handoff_id ||
77
+ item.handoff_id === message.reply_to) &&
78
+ (item.kind === 'coordinator' ||
79
+ (item.subtask_id === subtaskId && item.attempt === attempt)) &&
80
+ !['completed', 'aborted'].includes(item.state) &&
81
+ sameSender(item.from, item.kind === 'coordinator'
82
+ ? record.coordinator
83
+ : target?.assignee) &&
84
+ sameSender(message.from, item.to));
85
+ if (handoff)
86
+ return null;
87
+ }
88
+ if (!target)
89
+ return { ok: false, reason: 'unknown_attempt' };
90
+ if (message.type === 'BLOCK' &&
91
+ sameSender(message.from, record.coordinator))
92
+ return null;
93
+ expected = target.assignee;
94
+ break;
95
+ }
96
+ case 'ANSWER': {
97
+ const referenced = message.reply_to
98
+ ? findMessage(record, message.reply_to)
99
+ : null;
100
+ if (!referenced)
101
+ return { ok: false, reason: 'unknown_question' };
102
+ // Coordinator decisions and instructions are evidence consumed by work-plan
103
+ // and recovery validation. They are not clarification state transitions.
104
+ if (!['ASK', 'BLOCK'].includes(referenced.type)) {
105
+ return sameSender(message.from, record.coordinator)
106
+ ? null
107
+ : { ok: false, reason: 'unauthorized_sender' };
108
+ }
109
+ if (referenced.type === 'BLOCK' && referenced.body_kind === 'approval')
110
+ return { ok: false, reason: 'approval_not_auto_answerable' };
111
+ if (referenced.subtask_id && referenced.attempt) {
112
+ const target = findAttempt(record, referenced.subtask_id, referenced.attempt);
113
+ if ((referenced.type === 'BLOCK' && referenced.state !== 'applied') ||
114
+ !(sameSender(referenced.from, target?.assignee) ||
115
+ (sameSender(referenced.from, record.coordinator) &&
116
+ referenced.to !== null &&
117
+ sameSender(referenced.to, target?.assignee))))
118
+ return { ok: false, reason: 'stale_question' };
119
+ }
120
+ if ((message.subtask_id && message.subtask_id !== referenced.subtask_id) ||
121
+ (message.attempt && message.attempt !== referenced.attempt) ||
122
+ !sameSender(message.to, referenced.from))
123
+ return { ok: false, reason: 'reply_identity_mismatch' };
124
+ expected = referenced.to;
125
+ break;
126
+ }
127
+ // REPORT classifies a previous owner's result as evidence without changing the
128
+ // current attempt. Questions and status exchanges confer no execution authority.
129
+ default:
130
+ return null;
131
+ }
132
+ return sameSender(message.from, expected)
133
+ ? null
134
+ : { ok: false, reason: 'unauthorized_sender' };
135
+ }
38
136
  /**
39
137
  * Pure-ish apply function for `transact`: returns the outcome object or `{ok:false,...}`.
40
138
  */
@@ -62,6 +160,9 @@ export function applyMessage(record, message, { clock = Date.now(), nativeDir: _
62
160
  reason: 'task_mismatch',
63
161
  expected: record.task_id,
64
162
  };
163
+ const authorization = authorizeSender(record, message);
164
+ if (authorization)
165
+ return authorization;
65
166
  if (DISPATCH_TYPES.has(message.type)) {
66
167
  if (record.dispatch_frozen && message.type === 'ASSIGN') {
67
168
  return {
@@ -155,7 +256,7 @@ export function applyMessage(record, message, { clock = Date.now(), nativeDir: _
155
256
  subtask_id: subtaskId,
156
257
  attempt,
157
258
  };
158
- const transition = target.state === 'assigned'
259
+ const transition = target.state === 'assigned' && sameSender(message.from, target.assignee)
159
260
  ? transitionAttempt(record, {
160
261
  subtaskId,
161
262
  attempt,
@@ -178,6 +279,24 @@ export function applyMessage(record, message, { clock = Date.now(), nativeDir: _
178
279
  result: transition,
179
280
  };
180
281
  }
282
+ case 'ASK': {
283
+ // A question is evidence, not a transition. Blocking a clarification stays
284
+ // BLOCK(kind=clarification); ANSWER resolves only that state. This branch completes
285
+ // the advertised envelope vocabulary without adding authority or execution.
286
+ const subtaskId = message.subtask_id ?? null;
287
+ const attempt = message.attempt ?? null;
288
+ recordMessage(record, message, 'recorded', 'question recorded; no task state change', clock);
289
+ return {
290
+ record,
291
+ op: 'ask',
292
+ summary: { subtask_id: subtaskId, attempt },
293
+ result: {
294
+ ok: true,
295
+ awaiting: 'response',
296
+ state_changed: false,
297
+ },
298
+ };
299
+ }
181
300
  case 'ANSWER': {
182
301
  const referenced = message.reply_to
183
302
  ? findMessage(record, message.reply_to)
@@ -192,7 +311,9 @@ export function applyMessage(record, message, { clock = Date.now(), nativeDir: _
192
311
  const subtaskId = message.subtask_id ?? referenced?.subtask_id ?? null;
193
312
  const attempt = message.attempt ?? referenced?.attempt ?? null;
194
313
  const target = subtaskId && attempt ? findAttempt(record, subtaskId, attempt) : null;
195
- const transition = target &&
314
+ const transition = referenced !== null &&
315
+ ['ASK', 'BLOCK'].includes(referenced.type) &&
316
+ target &&
196
317
  target.state === 'needs_input' &&
197
318
  target.awaiting === 'clarification'
198
319
  ? transitionAttempt(record, {
@@ -209,7 +330,9 @@ export function applyMessage(record, message, { clock = Date.now(), nativeDir: _
209
330
  reason: 'no_open_clarification',
210
331
  state: target?.state ?? null,
211
332
  };
212
- recordMessage(record, message, 'applied', 'clarification answer', clock);
333
+ recordMessage(record, message, 'applied', referenced && ['ASK', 'BLOCK'].includes(referenced.type)
334
+ ? 'clarification answer'
335
+ : 'coordinator decision recorded; no task state change', clock);
213
336
  return {
214
337
  record,
215
338
  op: 'answer',