@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
@@ -0,0 +1,429 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { collaborationRecoveryRequestSchema, } from '../schemas/collaboration.js';
5
+ import { checkOwnerEpoch, resolveUncertainty, transitionAttempt, } from '../collab/lifecycle.js';
6
+ import { breakLock, lockPath, readLock, transact } from '../collab/store.js';
7
+ import { assertTaskStoreCurrent, openTaskStore, } from './collab-task-store.js';
8
+ import { readBoundedFile } from './bounded-file.js';
9
+ export const COLLAB_RECOVERY_SCHEMA = 'pappra/collaboration-recovery/1';
10
+ const MAX_REQUEST_BYTES = 1024 * 1024;
11
+ const MAX_ROWS = 50;
12
+ const MAX_LOCK_ENTRIES = 256;
13
+ const MAX_LOCK_FILE_BYTES = 8 * 1024 * 1024;
14
+ const MAX_LOCK_TOTAL_BYTES = 16 * 1024 * 1024;
15
+ function invalidRequest(message, nextAction) {
16
+ return {
17
+ ok: false,
18
+ code: 'invalid_request',
19
+ message,
20
+ nextAction,
21
+ };
22
+ }
23
+ export function loadRecoveryRequest(file, expectedAction) {
24
+ if (!path.isAbsolute(file)) {
25
+ return invalidRequest('Recovery request path must be absolute.', 'Pass an absolute request JSON path.');
26
+ }
27
+ const read = readBoundedFile(file, MAX_REQUEST_BYTES);
28
+ if (!read.ok) {
29
+ return invalidRequest('Recovery request must be a readable regular JSON file no larger than 1 MiB.', 'Inspect the request path and file kind; nothing was written.');
30
+ }
31
+ let parsed;
32
+ try {
33
+ parsed = JSON.parse(read.text);
34
+ }
35
+ catch {
36
+ return invalidRequest('Recovery request is not valid JSON.', 'Rewrite the request as strict JSON; nothing was written.');
37
+ }
38
+ const validated = collaborationRecoveryRequestSchema.safeParse(parsed);
39
+ if (!validated.success) {
40
+ const issues = validated.error.issues
41
+ .slice(0, 8)
42
+ .map((issue) => {
43
+ const field = issue.path.length ? issue.path.join('.') : '(root)';
44
+ return `${field}:${issue.code}`;
45
+ })
46
+ .join(', ');
47
+ return invalidRequest(`Recovery request does not match the strict schema (${issues || 'invalid shape'}).`, 'Fix the request schema; nothing was written.');
48
+ }
49
+ if (validated.data.action !== expectedAction) {
50
+ return invalidRequest('Recovery request action does not match the invoked recovery command.', `Use a ${expectedAction} request; nothing was written.`);
51
+ }
52
+ return { ok: true, value: { request: validated.data, path: file } };
53
+ }
54
+ function fingerprintOf(entries) {
55
+ return `sha256:${crypto
56
+ .createHash('sha256')
57
+ .update(JSON.stringify({
58
+ schema: 'pappra/task-lock-fingerprint/1',
59
+ entries: [...entries].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0),
60
+ }))
61
+ .digest('hex')}`;
62
+ }
63
+ /**
64
+ * Stable content fingerprint of the task write-lock directory. It hashes only
65
+ * names and bounded regular-file bytes; PID liveness is an inspection
66
+ * observation and is not part of the content identity.
67
+ */
68
+ export function taskLockFingerprint(taskRoot) {
69
+ const directory = lockPath(taskRoot);
70
+ let names;
71
+ try {
72
+ names = fs.readdirSync(directory).sort();
73
+ }
74
+ catch (error) {
75
+ const code = typeof error === 'object' && error !== null && 'code' in error
76
+ ? String(error.code)
77
+ : null;
78
+ if (code === 'ENOENT' || code === 'ENOTDIR')
79
+ return {
80
+ status: 'available',
81
+ fingerprint: fingerprintOf([]),
82
+ entries: 0,
83
+ bytes: 0,
84
+ reason: null,
85
+ };
86
+ return {
87
+ status: 'unavailable',
88
+ fingerprint: null,
89
+ entries: 0,
90
+ bytes: 0,
91
+ reason: 'lock_snapshot_unreadable',
92
+ };
93
+ }
94
+ if (names.length > MAX_LOCK_ENTRIES)
95
+ return {
96
+ status: 'unavailable',
97
+ fingerprint: null,
98
+ entries: names.length,
99
+ bytes: 0,
100
+ reason: 'lock_snapshot_entry_limit',
101
+ };
102
+ const entries = [];
103
+ let bytes = 0;
104
+ for (const name of names) {
105
+ const read = readBoundedFile(path.join(directory, name), MAX_LOCK_FILE_BYTES);
106
+ if (!read.ok)
107
+ return {
108
+ status: 'unavailable',
109
+ fingerprint: null,
110
+ entries: names.length,
111
+ bytes,
112
+ reason: 'lock_snapshot_file_unsafe',
113
+ };
114
+ bytes += read.bytes.length;
115
+ if (bytes > MAX_LOCK_TOTAL_BYTES)
116
+ return {
117
+ status: 'unavailable',
118
+ fingerprint: null,
119
+ entries: names.length,
120
+ bytes,
121
+ reason: 'lock_snapshot_size_limit',
122
+ };
123
+ entries.push({ name, digest: read.digest });
124
+ }
125
+ return {
126
+ status: 'available',
127
+ fingerprint: fingerprintOf(entries),
128
+ entries: entries.length,
129
+ bytes,
130
+ reason: null,
131
+ };
132
+ }
133
+ function bounded(items) {
134
+ return {
135
+ rows: items.slice(0, MAX_ROWS),
136
+ total: items.length,
137
+ omitted: Math.max(0, items.length - MAX_ROWS),
138
+ truncated: items.length > MAX_ROWS,
139
+ };
140
+ }
141
+ function uncertaintySummary(entry) {
142
+ return {
143
+ uncertainty_id: entry.uncertainty_id,
144
+ kind: entry.kind,
145
+ subtask_id: entry.subtask_id,
146
+ attempt: entry.attempt,
147
+ opened_at: entry.opened_at,
148
+ resolved_at: entry.resolved_at,
149
+ resolved_by: entry.resolved_by ?? null,
150
+ resolution_recorded: entry.resolved_at !== null,
151
+ };
152
+ }
153
+ export async function inspectRecovery(taskRoot) {
154
+ const opened = await openTaskStore(taskRoot);
155
+ if (!opened.ok)
156
+ return opened;
157
+ const record = opened.value.record;
158
+ const lock = readLock(opened.value.taskRoot);
159
+ const snapshot = taskLockFingerprint(opened.value.taskRoot);
160
+ const allUncertainty = record.uncertainty ?? [];
161
+ const open = bounded(allUncertainty.filter((entry) => !entry.resolved_at));
162
+ const resolved = bounded(allUncertainty.filter((entry) => entry.resolved_at !== null));
163
+ const transactionCount = lock.transaction_files?.length ?? 0;
164
+ const needsInput = open.total > 0 ||
165
+ transactionCount > 0 ||
166
+ snapshot.status === 'unavailable' ||
167
+ (lock.held && lock.holder_process_alive === false);
168
+ return {
169
+ ok: true,
170
+ value: {
171
+ schema: COLLAB_RECOVERY_SCHEMA,
172
+ ok: !needsInput,
173
+ status: needsInput ? 'needs_input' : 'ready',
174
+ action: 'inspect',
175
+ task_root: opened.value.taskRoot,
176
+ task_id: record.task_id,
177
+ state_revision: record.state_revision,
178
+ coordinator_epoch: record.coordinator?.owner_epoch ?? null,
179
+ uncertainty: {
180
+ open: open.rows.map(uncertaintySummary),
181
+ resolved: resolved.rows.map(uncertaintySummary),
182
+ counts: { open: open.total, resolved: resolved.total },
183
+ omitted: {
184
+ open: open.omitted,
185
+ resolved: resolved.omitted,
186
+ },
187
+ truncated: open.truncated || resolved.truncated,
188
+ },
189
+ lock: {
190
+ held: lock.held,
191
+ holder: lock.held ? lock.holder : null,
192
+ // Liveness is an observation only; it is not native ownership proof.
193
+ holder_process_alive: lock.held ? lock.holder_process_alive : false,
194
+ transaction_count: transactionCount,
195
+ transaction_files: transactionCount > 0
196
+ ? (lock.transaction_files ?? [])
197
+ .slice(0, MAX_ROWS)
198
+ .map((file) => path.basename(file))
199
+ : [],
200
+ transaction_truncated: transactionCount > MAX_ROWS,
201
+ fingerprint: snapshot.fingerprint,
202
+ fingerprint_status: snapshot.status,
203
+ fingerprint_reason: snapshot.reason,
204
+ entries: snapshot.entries,
205
+ bytes: snapshot.bytes,
206
+ },
207
+ limits: [
208
+ 'stored_state_only',
209
+ 'pid_liveness_is_not_stop_proof',
210
+ 'no_native_query',
211
+ `lock_snapshot_max_entries=${MAX_LOCK_ENTRIES}`,
212
+ `lock_snapshot_max_bytes=${MAX_LOCK_TOTAL_BYTES}`,
213
+ ],
214
+ },
215
+ };
216
+ }
217
+ function requestSummary(request) {
218
+ if (request.action === 'reconcile')
219
+ return {
220
+ action: request.action,
221
+ actor: request.actor,
222
+ owner_epoch: request.owner_epoch,
223
+ uncertainty_id: request.uncertainty_id,
224
+ set_state: request.set_state ?? null,
225
+ ...(request.expect_revision === undefined
226
+ ? {}
227
+ : { expect_revision: request.expect_revision }),
228
+ };
229
+ return {
230
+ action: request.action,
231
+ actor: request.actor,
232
+ owner_epoch: request.owner_epoch,
233
+ expected_lock_fingerprint: request.expected_lock_fingerprint,
234
+ };
235
+ }
236
+ export async function planRecovery(taskRoot, request) {
237
+ const opened = await openTaskStore(taskRoot);
238
+ if (!opened.ok)
239
+ return opened;
240
+ return {
241
+ ok: true,
242
+ value: {
243
+ schema: COLLAB_RECOVERY_SCHEMA,
244
+ ok: true,
245
+ status: 'planned',
246
+ action: request.action,
247
+ applied: false,
248
+ task_root: opened.value.taskRoot,
249
+ task_id: opened.value.record.task_id,
250
+ request: requestSummary(request),
251
+ limits: ['plan_validates_request_and_namespace_only', 'no_reservation'],
252
+ },
253
+ };
254
+ }
255
+ export async function applyRecovery(taskRoot, request) {
256
+ const opened = await openTaskStore(taskRoot);
257
+ if (!opened.ok)
258
+ return opened;
259
+ const changed = await assertTaskStoreCurrent(opened.value);
260
+ if (changed)
261
+ return changed;
262
+ if (request.action === 'lock-break') {
263
+ const epoch = checkOwnerEpoch(opened.value.record, request.owner_epoch);
264
+ if (!epoch.ok)
265
+ return {
266
+ ok: false,
267
+ code: 'stale_epoch',
268
+ message: 'The task owner epoch does not match the recovery request.',
269
+ nextAction: 'Inspect the task and use the current owner epoch.',
270
+ };
271
+ const snapshot = taskLockFingerprint(opened.value.taskRoot);
272
+ if (snapshot.status !== 'available')
273
+ return {
274
+ ok: false,
275
+ code: 'lock_fingerprint_unavailable',
276
+ message: 'The task lock content cannot be fingerprinted within the recovery limits.',
277
+ nextAction: 'Inspect the lock snapshot diagnostics; do not break it from this inspection.',
278
+ };
279
+ if (snapshot.fingerprint !== request.expected_lock_fingerprint)
280
+ return {
281
+ ok: false,
282
+ code: 'lock_changed',
283
+ message: 'The task lock no longer matches the inspected fingerprint.',
284
+ nextAction: 'Inspect the current lock again before making any recovery decision.',
285
+ };
286
+ const lock = readLock(opened.value.taskRoot);
287
+ if (!lock.held)
288
+ return {
289
+ ok: false,
290
+ code: 'no_lock',
291
+ message: 'The task write lock is no longer held.',
292
+ nextAction: 'Inspect the task; nothing was archived.',
293
+ };
294
+ try {
295
+ const result = breakLock(opened.value.taskRoot, {
296
+ actor: request.actor,
297
+ reason: request.reason,
298
+ confirmHolderStopped: true,
299
+ });
300
+ if (!result.broken)
301
+ return {
302
+ ok: false,
303
+ code: 'no_lock',
304
+ message: 'The task write lock was not held.',
305
+ nextAction: 'Inspect the task; nothing was archived.',
306
+ };
307
+ const archive = result.record;
308
+ return {
309
+ ok: true,
310
+ value: {
311
+ schema: COLLAB_RECOVERY_SCHEMA,
312
+ ok: true,
313
+ status: 'broken',
314
+ action: 'lock-break',
315
+ applied: true,
316
+ task_root: opened.value.taskRoot,
317
+ task_id: opened.value.record.task_id,
318
+ archived_lock: result.evidence,
319
+ previous_holder: archive.previous_holder,
320
+ previous_holder_process_alive: archive.previous_holder_process_alive,
321
+ uncommitted_transactions: archive.uncommitted_transactions,
322
+ limits: [
323
+ 'operator_stop_assertion_not_native_proof',
324
+ 'task_lock_only',
325
+ ],
326
+ },
327
+ };
328
+ }
329
+ catch {
330
+ return {
331
+ ok: false,
332
+ code: 'lock_break_uncertain',
333
+ message: 'The lock break may have archived the lock but could not be confirmed.',
334
+ nextAction: 'Inspect broken-locks and the task lock before retrying; do not assume success or failure.',
335
+ };
336
+ }
337
+ }
338
+ let outcome;
339
+ try {
340
+ outcome = transact(opened.value.taskRoot, {
341
+ actor: request.actor,
342
+ purpose: 'reconcile',
343
+ expectRevision: request.expect_revision,
344
+ apply: (record) => {
345
+ const epoch = checkOwnerEpoch(record, request.owner_epoch);
346
+ if (!epoch.ok)
347
+ return {
348
+ ok: false,
349
+ reason: 'stale_epoch',
350
+ expected: epoch.expected,
351
+ actual: epoch.actual,
352
+ };
353
+ const target = (record.uncertainty ?? []).find((entry) => entry.uncertainty_id === request.uncertainty_id);
354
+ if (request.set_state && !target)
355
+ return {
356
+ ok: false,
357
+ reason: 'unknown_uncertainty',
358
+ uncertainty_id: request.uncertainty_id,
359
+ };
360
+ if (request.set_state && (!target?.subtask_id || !target?.attempt))
361
+ return {
362
+ ok: false,
363
+ reason: 'transition_context_missing',
364
+ detail: 'set_state requires an uncertainty entry with subtask and attempt context',
365
+ };
366
+ const resolved = resolveUncertainty(record, {
367
+ uncertaintyId: request.uncertainty_id,
368
+ resolution: request.resolution,
369
+ resolutionEvidence: request.resolution_evidence,
370
+ actor: request.actor,
371
+ });
372
+ if (!resolved.ok)
373
+ return resolved;
374
+ let transition = null;
375
+ if (request.set_state) {
376
+ const changedState = transitionAttempt(record, {
377
+ subtaskId: target.subtask_id,
378
+ attempt: target.attempt,
379
+ to: request.set_state,
380
+ actor: request.actor,
381
+ reason: `reconciled: ${request.resolution}`,
382
+ evidence: request.resolution_evidence,
383
+ });
384
+ if (!changedState.ok)
385
+ return changedState;
386
+ transition = changedState;
387
+ }
388
+ return {
389
+ record,
390
+ op: 'reconcile',
391
+ summary: {
392
+ uncertainty_id: request.uncertainty_id,
393
+ set_state: request.set_state ?? null,
394
+ },
395
+ result: {
396
+ uncertainty_id: request.uncertainty_id,
397
+ resolution_recorded: true,
398
+ state_transition: transition?.state ?? null,
399
+ },
400
+ };
401
+ },
402
+ });
403
+ }
404
+ catch {
405
+ return {
406
+ ok: false,
407
+ code: 'reconcile_uncertain',
408
+ message: 'The reconciliation may already be committed, but its transaction evidence could not be confirmed.',
409
+ nextAction: 'Inspect the task record and ledger before any explicit retry; do not assume the resolution or transition was unapplied.',
410
+ };
411
+ }
412
+ const { state_revision, result, ...rest } = outcome;
413
+ return {
414
+ ok: true,
415
+ value: {
416
+ schema: COLLAB_RECOVERY_SCHEMA,
417
+ ok: outcome.ok === true,
418
+ status: outcome.ok === true ? 'applied' : 'refused',
419
+ action: 'reconcile',
420
+ applied: outcome.ok === true,
421
+ task_root: opened.value.taskRoot,
422
+ task_id: opened.value.record.task_id,
423
+ ...(outcome.ok === true
424
+ ? { state_revision, result: result ?? null }
425
+ : rest),
426
+ limits: ['recorded_resolution_not_native_proof'],
427
+ },
428
+ };
429
+ }
@@ -0,0 +1,16 @@
1
+ import { type CollaborationResourceRequest } from '../schemas/collaboration.js';
2
+ import { type TaskStoreResult } from './collab-task-store.js';
3
+ export declare const COLLAB_RESOURCES_SCHEMA = "pappra/collaboration-resources/1";
4
+ export type ResourceAction = 'list' | 'claim' | 'release';
5
+ export interface LoadedResourceRequest {
6
+ readonly request: CollaborationResourceRequest;
7
+ readonly path: string;
8
+ }
9
+ /**
10
+ * Read and strictly validate one bounded resource request. Diagnostic text only
11
+ * names field paths/codes and never echoes request body values.
12
+ */
13
+ export declare function loadResourceRequest(file: string, expectedAction: 'claim' | 'release'): TaskStoreResult<LoadedResourceRequest>;
14
+ export declare function planResourceRequest(taskRoot: string, request: CollaborationResourceRequest): Promise<TaskStoreResult<Record<string, unknown>>>;
15
+ export declare function applyResourceRequest(taskRoot: string, request: CollaborationResourceRequest): Promise<TaskStoreResult<Record<string, unknown>>>;
16
+ export declare function listTaskResources(taskRoot: string): Promise<TaskStoreResult<Record<string, unknown>>>;