@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
@@ -7,11 +7,14 @@ import { homedir, tmpdir } from 'node:os';
7
7
  import { getPackageInfo } from '../package-info.js';
8
8
  import { createDiagnostic } from '../schemas/diagnostics.js';
9
9
  import { diagnosticCodes } from '../schemas/errors.js';
10
- import { installJournalSchema, runtimeManifestSchema, runtimePackageManifestSchema, runtimeSourceSchema, } from '../schemas/install.js';
10
+ import { installJournalSchema, installLockSchema, runtimeManifestSchema, runtimePackageManifestSchema, runtimeSourceSchema, RUNTIME_MANIFEST_SCHEMA, RUNTIME_DIGEST_SCHEME, } from '../schemas/install.js';
11
11
  import { isPathInside } from '../schemas/paths.js';
12
12
  import { errorMessage } from '../schemas/outcome.js';
13
+ import { fingerprintSelfSource } from './source-fingerprint.js';
14
+ import { inspectRuntimeReferences } from './references.js';
13
15
  import { observePath, readFileBounded, readTextFileBounded, resolveRealpath, sha256Digest, } from '../infra/fs.js';
14
- import { acquireInstallLock, readJsonDocument, releaseInstallLock, scanJournalDirectory, writeInstallJournal, } from './ownership.js';
16
+ import { materializeInstalledDependencies, readPackageDependencyManifest, } from './dependencies.js';
17
+ import { acquireInstallLock, ensureStatePathBoundary, readJsonDocument, releaseInstallLock, scanJournalDirectory, writeInstallJournal, } from './ownership.js';
15
18
  export function resolveUserDataDir(environment = process.env, platform = process.platform, home = homedir()) {
16
19
  if (environment['PAPPRA_DATA_HOME'] !== undefined) {
17
20
  return resolve(environment['PAPPRA_DATA_HOME']);
@@ -69,7 +72,7 @@ function isoNow() {
69
72
  async function hashPackageDirectory(directory) {
70
73
  const parts = [];
71
74
  const visit = async (current) => {
72
- const entries = (await readdir(current, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name));
75
+ const entries = (await readdir(current, { withFileTypes: true })).sort((left, right) => Buffer.compare(Buffer.from(left.name), Buffer.from(right.name)));
73
76
  for (const entry of entries) {
74
77
  const path = join(current, entry.name);
75
78
  const rel = relative(directory, path).split(sep).join('/');
@@ -175,6 +178,7 @@ async function packSelfPackage(packageRoot, packageName, version) {
175
178
  if (manifest.name !== packageName || manifest.version !== version) {
176
179
  throw new Error(`Running package ${String(manifest.name)}@${String(manifest.version)} does not match ${packageName}@${version}.`);
177
180
  }
181
+ const dependenciesAvailable = (await readPackageDependencyManifest(packageRoot)) !== null;
178
182
  const directory = await mkdtemp(join(tmpdir(), 'pappra-self-'));
179
183
  // Pack a staging copy: npm refuses to pack reliably from inside a dependency
180
184
  // tree (a known "Exit handler never called!" failure on Linux CI).
@@ -198,6 +202,16 @@ async function packSelfPackage(packageRoot, packageName, version) {
198
202
  force: true,
199
203
  });
200
204
  }
205
+ let hasInstalledDependencies = dependenciesAvailable;
206
+ if (hasInstalledDependencies) {
207
+ // Reuse the exact installed dependency closure instead of packing a copy
208
+ // from the registry: payloads are copied without nested node_modules and
209
+ // shared exact versions are hoisted once under the staging root.
210
+ hasInstalledDependencies = (await materializeInstalledDependencies({
211
+ sourceRoot: packageRoot,
212
+ targetRoot: staging,
213
+ })).ok;
214
+ }
201
215
  const npm = npmCommand();
202
216
  const packed = await runCommand(npm.executable, [
203
217
  ...npm.args,
@@ -230,7 +244,13 @@ async function packSelfPackage(packageRoot, packageName, version) {
230
244
  }
231
245
  const tarball = join(directory, filename);
232
246
  const bytes = await readFile(tarball);
233
- return { directory, tarball, digest: sha256Digest(bytes) };
247
+ return {
248
+ directory,
249
+ packageDirectory: staging,
250
+ dependenciesAvailable: hasInstalledDependencies,
251
+ tarball,
252
+ digest: sha256Digest(bytes),
253
+ };
234
254
  }
235
255
  async function readPackageManifest(directory, boundaryRoot) {
236
256
  const manifestPath = join(directory, 'package.json');
@@ -256,7 +276,7 @@ function binPath(packageManifest, packageName) {
256
276
  return packageManifest.bin;
257
277
  return (packageManifest.bin[packageName] ?? packageManifest.bin['pappra'] ?? null);
258
278
  }
259
- async function verifyInstalledRuntime(runtimeRoot, expectedVersion, expectedPackageName, timeoutMs) {
279
+ async function verifyInstalledRuntime(runtimeRoot, expectedVersion, expectedPackageName, timeoutMs, verifiedDigest) {
260
280
  const packageRoot = join(runtimeRoot, 'node_modules', ...expectedPackageName.split('/'));
261
281
  const runtimeReal = await resolveRealpath(runtimeRoot);
262
282
  const packageManifest = runtimeReal === null
@@ -294,7 +314,7 @@ async function verifyInstalledRuntime(runtimeRoot, expectedVersion, expectedPack
294
314
  }
295
315
  return {
296
316
  manifest: packageManifest,
297
- digest: await packageDigest(packageRoot),
317
+ digest: verifiedDigest ?? (await packageDigest(packageRoot)),
298
318
  executableRelative: relative(runtimeRoot, executablePath)
299
319
  .split(sep)
300
320
  .join('/'),
@@ -309,6 +329,66 @@ async function readRuntimeManifest(runtimeRoot) {
309
329
  const read = await readJsonDocument(join(runtimeRoot, 'install-manifest.json'), runtimeManifestSchema);
310
330
  return read.value;
311
331
  }
332
+ /** Recognize an unsupported format without adopting or executing its content. */
333
+ async function incompatibleRuntimeDiagnostic(runtimeRoot) {
334
+ const file = join(runtimeRoot, 'install-manifest.json');
335
+ if ((await observePath(file)).kind !== 'file')
336
+ return null;
337
+ const read = await readTextFileBounded(file);
338
+ if (!read.ok)
339
+ return null;
340
+ let raw;
341
+ try {
342
+ raw = JSON.parse(read.value.text);
343
+ }
344
+ catch {
345
+ return null;
346
+ }
347
+ if (typeof raw !== 'object' ||
348
+ raw === null ||
349
+ !('schema' in raw) ||
350
+ typeof raw.schema !== 'string' ||
351
+ !raw.schema.startsWith('pappra/runtime-manifest/') ||
352
+ (raw.schema === RUNTIME_MANIFEST_SCHEMA &&
353
+ 'digestScheme' in raw &&
354
+ raw.digestScheme === RUNTIME_DIGEST_SCHEME))
355
+ return null;
356
+ return createDiagnostic({
357
+ code: diagnosticCodes.runtimeStateIncompatible,
358
+ severity: 'error',
359
+ message: `Unsupported runtime state format at ${file}; this is not a content-corruption diagnosis.`,
360
+ source: file,
361
+ hint: 'This beta does not migrate old state. Use the current package and run pappra init --reconfigure --apply to initialize its runtime; preserve any old data you still need. Nothing was removed.',
362
+ });
363
+ }
364
+ /** Repair verifies published content without executing it or ignoring its journal. */
365
+ export async function verifyRuntimeForRepair(runtimeRoot, version, packageName, installId) {
366
+ const manifest = await readRuntimeManifest(runtimeRoot);
367
+ if (manifest === null ||
368
+ manifest.installId !== installId ||
369
+ manifest.runtimeRoot !== runtimeRoot ||
370
+ manifest.runtimeVersion !== version ||
371
+ manifest.packageName !== packageName ||
372
+ !isPathInside(runtimeRoot, manifest.executable))
373
+ return false;
374
+ const packageRoot = join(runtimeRoot, 'node_modules', ...packageName.split('/'));
375
+ const manifestObservation = await observePath(join(runtimeRoot, 'install-manifest.json'));
376
+ const packageObservation = await observePath(packageRoot);
377
+ if (manifestObservation.kind !== 'file' ||
378
+ packageObservation.kind !== 'directory')
379
+ return false;
380
+ for (const file of [packageRoot, manifest.executable]) {
381
+ if (!(await ensureStatePathBoundary(runtimeRoot, file)).ok)
382
+ return false;
383
+ const real = await resolveRealpath(file);
384
+ if (real === null || !isPathInside(runtimeRoot, real))
385
+ return false;
386
+ }
387
+ const executable = await observePath(manifest.executable);
388
+ return (executable.kind === 'file' &&
389
+ (await packageDigest(packageRoot).catch(() => null)) ===
390
+ manifest.packageDigest);
391
+ }
312
392
  async function scanRuntimeJournals(version, paths) {
313
393
  const directory = await scanJournalDirectory(paths.journalsDir);
314
394
  if (directory.kind === 'missing') {
@@ -340,7 +420,7 @@ async function scanRuntimeJournals(version, paths) {
340
420
  }
341
421
  return { journal, unreadable, directoryUnreadable: false };
342
422
  }
343
- async function completeExistingRuntime(version, paths, packageName, timeoutMs) {
423
+ async function completeExistingRuntime(version, paths, packageName, timeoutMs, expectedSelfFingerprint) {
344
424
  const observation = await observePath(paths.runtimeRoot);
345
425
  if (observation.kind === 'missing')
346
426
  return null;
@@ -354,6 +434,19 @@ async function completeExistingRuntime(version, paths, packageName, timeoutMs) {
354
434
  }
355
435
  const manifest = await readRuntimeManifest(paths.runtimeRoot);
356
436
  if (manifest === null) {
437
+ const incompatible = await incompatibleRuntimeDiagnostic(paths.runtimeRoot);
438
+ if (incompatible !== null) {
439
+ return {
440
+ ok: false,
441
+ status: 'incompatible',
442
+ version,
443
+ runtimeRoot: paths.runtimeRoot,
444
+ manifest: null,
445
+ journal: null,
446
+ diagnostics: [incompatible],
447
+ residualPaths: [],
448
+ };
449
+ }
357
450
  return failureResult('corrupt', `Runtime has no valid manifest: ${paths.runtimeRoot}`, {
358
451
  version,
359
452
  runtimeRoot: paths.runtimeRoot,
@@ -371,6 +464,15 @@ async function completeExistingRuntime(version, paths, packageName, timeoutMs) {
371
464
  source: paths.runtimeRoot,
372
465
  });
373
466
  }
467
+ if (expectedSelfFingerprint !== undefined &&
468
+ manifest.sourceFingerprint !== expectedSelfFingerprint)
469
+ return failureResult('failed', 'This version already contains a different self package payload or resolved dependency identity.', {
470
+ version,
471
+ runtimeRoot: paths.runtimeRoot,
472
+ manifest,
473
+ hint: 'Bump the package version before installing a rebuilt self runtime; an existing version is never silently reused or overwritten for different source bytes.',
474
+ source: paths.runtimeRoot,
475
+ });
374
476
  const packageRoot = join(paths.runtimeRoot, 'node_modules', ...packageName.split('/'));
375
477
  const digest = await packageDigest(packageRoot).catch(() => null);
376
478
  const executableObservation = await observePath(manifest.executable);
@@ -385,7 +487,10 @@ async function completeExistingRuntime(version, paths, packageName, timeoutMs) {
385
487
  source: paths.runtimeRoot,
386
488
  });
387
489
  }
388
- const verified = await verifyInstalledRuntime(paths.runtimeRoot, version, packageName, timeoutMs);
490
+ const verified = await verifyInstalledRuntime(paths.runtimeRoot, version, packageName, timeoutMs,
491
+ // Content was checked before executing the smoke command. Reuse does not
492
+ // publish a new manifest, so hashing it again would discard the result.
493
+ digest);
389
494
  if (verified === null) {
390
495
  return failureResult('corrupt', `Runtime smoke check failed: ${paths.runtimeRoot}`, {
391
496
  version,
@@ -467,7 +572,42 @@ export async function installRuntime(options) {
467
572
  source: paths.lockPath,
468
573
  });
469
574
  }
470
- const existing = await completeExistingRuntime(version, paths, packageName, timeoutMs);
575
+ let expectedSelfFingerprint;
576
+ if (options.source.kind === 'self') {
577
+ try {
578
+ const sourceRead = await readTextFileBounded(join(resolve(options.source.path ?? ''), 'package.json'));
579
+ const sourceManifest = sourceRead.ok
580
+ ? JSON.parse(sourceRead.value.text)
581
+ : null;
582
+ if (typeof sourceManifest === 'object' &&
583
+ sourceManifest !== null &&
584
+ 'name' in sourceManifest &&
585
+ sourceManifest.name === packageName &&
586
+ 'version' in sourceManifest &&
587
+ typeof sourceManifest.version === 'string' &&
588
+ sourceManifest.version !== version) {
589
+ // A newer CLI may verify and reuse an older workspace target. It cannot
590
+ // replace that target with its own differently versioned payload.
591
+ const older = await completeExistingRuntime(version, paths, packageName, timeoutMs);
592
+ if (older !== null)
593
+ return older;
594
+ return failureResult('failed', 'The running package version does not match the requested runtime and no verified target is available.', {
595
+ version,
596
+ runtimeRoot: paths.runtimeRoot,
597
+ hint: 'Install the exact workspace runtime version from a versioned package or tarball.',
598
+ });
599
+ }
600
+ expectedSelfFingerprint = await fingerprintSelfSource(resolve(options.source.path ?? ''), packageName, version);
601
+ }
602
+ catch (error) {
603
+ return failureResult('failed', `Cannot identify the running package source: ${errorMessage(error)}`, {
604
+ version,
605
+ runtimeRoot: paths.runtimeRoot,
606
+ hint: 'Review the package payload and resolved dependencies; bump the package version for rebuilt source.',
607
+ });
608
+ }
609
+ }
610
+ const existing = await completeExistingRuntime(version, paths, packageName, timeoutMs, expectedSelfFingerprint);
471
611
  if (existing !== null)
472
612
  return existing;
473
613
  const lock = await acquireInstallLock(paths.lockPath, 'install-runtime', paths.runtimeRoot, dataDir);
@@ -479,7 +619,7 @@ export async function installRuntime(options) {
479
619
  source: paths.lockPath,
480
620
  });
481
621
  }
482
- const afterLock = await completeExistingRuntime(version, paths, packageName, timeoutMs);
622
+ const afterLock = await completeExistingRuntime(version, paths, packageName, timeoutMs, expectedSelfFingerprint);
483
623
  if (afterLock !== null) {
484
624
  await releaseInstallLock(paths.lockPath, lock.lock.token);
485
625
  return afterLock;
@@ -505,6 +645,9 @@ export async function installRuntime(options) {
505
645
  else if (options.source.kind === 'self') {
506
646
  const packageRoot = resolve(options.source.path ?? '');
507
647
  selfPackage = await packSelfPackage(packageRoot, packageName, version);
648
+ if ((await fingerprintSelfSource(packageRoot, packageName, version)) !==
649
+ expectedSelfFingerprint)
650
+ throw new Error('Self source changed while packing; no runtime will be published. Rebuild with a new version and retry.');
508
651
  source = {
509
652
  kind: 'self',
510
653
  packageName,
@@ -608,30 +751,37 @@ export async function installRuntime(options) {
608
751
  if (!(await writeJournal())) {
609
752
  throw new Error(`Cannot update install journal: ${journalFile}`);
610
753
  }
611
- const npm = npmCommand();
612
- const installArgs = [
613
- ...npm.args,
614
- 'install',
615
- '--prefix',
616
- stage,
617
- '--ignore-scripts',
618
- '--no-audit',
619
- '--no-fund',
620
- '--no-package-lock',
621
- '--save-exact',
622
- ...(options.offline === true ? ['--offline'] : []),
623
- source.kind === 'tarball' || source.kind === 'self'
624
- ? source.kind === 'tarball'
625
- ? source.path
626
- : source.tarball
627
- : `${source.packageName}@${source.version}`,
628
- ];
629
- const installed = await runCommand(npm.executable, installArgs, {
630
- cwd: stage,
631
- timeoutMs,
632
- });
633
- if (installed.timedOut || installed.code !== 0) {
634
- throw new Error(`Package manager install failed (${installed.timedOut ? 'timeout' : `exit ${String(installed.code)}`}): ${installed.stderr.slice(0, 1000)}`);
754
+ // Reuse installed versions when available so self installs do not depend on npm registry/cache.
755
+ if (options.source.kind === 'self' &&
756
+ selfPackage?.dependenciesAvailable === true) {
757
+ await cp(selfPackage.packageDirectory, join(stage, 'node_modules', ...packageName.split('/')), { recursive: true, dereference: true, force: true });
758
+ }
759
+ else {
760
+ const npm = npmCommand();
761
+ const installArgs = [
762
+ ...npm.args,
763
+ 'install',
764
+ '--prefix',
765
+ stage,
766
+ '--ignore-scripts',
767
+ '--no-audit',
768
+ '--no-fund',
769
+ '--no-package-lock',
770
+ '--save-exact',
771
+ ...(options.offline === true ? ['--offline'] : []),
772
+ source.kind === 'tarball' || source.kind === 'self'
773
+ ? source.kind === 'tarball'
774
+ ? source.path
775
+ : source.tarball
776
+ : `${source.packageName}@${source.version}`,
777
+ ];
778
+ const installed = await runCommand(npm.executable, installArgs, {
779
+ cwd: stage,
780
+ timeoutMs,
781
+ });
782
+ if (installed.timedOut || installed.code !== 0) {
783
+ throw new Error(`Package manager install failed (${installed.timedOut ? 'timeout' : `exit ${String(installed.code)}`}): ${installed.stderr.slice(0, 1000)}`);
784
+ }
635
785
  }
636
786
  if (selfPackage !== null) {
637
787
  await rm(selfPackage.directory, { recursive: true, force: true }).catch(() => undefined);
@@ -647,7 +797,7 @@ export async function installRuntime(options) {
647
797
  if (!(await writeJournal()))
648
798
  throw new Error('Cannot update install journal.');
649
799
  const manifest = {
650
- schema: 'pappra/runtime-manifest/1',
800
+ schema: RUNTIME_MANIFEST_SCHEMA,
651
801
  installId,
652
802
  packageName,
653
803
  runtimeVersion: version,
@@ -655,6 +805,8 @@ export async function installRuntime(options) {
655
805
  executable: join(paths.runtimeRoot, ...verification.executableRelative.split('/')),
656
806
  source,
657
807
  packageDigest: verification.digest,
808
+ digestScheme: RUNTIME_DIGEST_SCHEME,
809
+ sourceFingerprint: await fingerprintSelfSource(join(stage, 'node_modules', ...packageName.split('/')), packageName, version),
658
810
  smoke: verification.smoke,
659
811
  verifiedAt: isoNow(),
660
812
  };
@@ -744,7 +896,7 @@ async function runtimePackageName(runtimeRoot) {
744
896
  const read = await readJsonDocument(join(runtimeRoot, 'install-manifest.json'), runtimeManifestSchema);
745
897
  return read.value?.packageName ?? getPackageInfo().name;
746
898
  }
747
- export async function inspectRuntimeState(version, dataDir = resolveUserDataDir()) {
899
+ export async function inspectRuntimeState(version, dataDir = resolveUserDataDir(), heldLockToken) {
748
900
  const paths = runtimePaths(version, dataDir);
749
901
  if (!validVersion(version)) {
750
902
  return {
@@ -803,7 +955,11 @@ export async function inspectRuntimeState(version, dataDir = resolveUserDataDir(
803
955
  ],
804
956
  };
805
957
  }
806
- if (lockObservation.kind !== 'missing') {
958
+ const ownLock = heldLockToken === undefined
959
+ ? null
960
+ : await readJsonDocument(paths.lockPath, installLockSchema);
961
+ if (lockObservation.kind !== 'missing' &&
962
+ (heldLockToken === undefined || ownLock?.value?.token !== heldLockToken)) {
807
963
  return {
808
964
  version,
809
965
  status: 'locked',
@@ -835,7 +991,11 @@ export async function inspectRuntimeState(version, dataDir = resolveUserDataDir(
835
991
  }
836
992
  return {
837
993
  version,
838
- status: complete.status === 'reused' ? 'complete' : 'corrupt',
994
+ status: complete.status === 'reused'
995
+ ? 'complete'
996
+ : complete.status === 'incompatible'
997
+ ? 'incompatible'
998
+ : 'corrupt',
839
999
  runtimeRoot: paths.runtimeRoot,
840
1000
  manifest: complete.manifest,
841
1001
  journal: complete.journal,
@@ -843,6 +1003,66 @@ export async function inspectRuntimeState(version, dataDir = resolveUserDataDir(
843
1003
  };
844
1004
  }
845
1005
  export async function removeRuntime(options) {
1006
+ if (!validVersion(options.version))
1007
+ return removeRuntimeAt(options);
1008
+ const dataDir = resolve(options.dataDir ?? resolveUserDataDir());
1009
+ const paths = runtimePaths(options.version, dataDir);
1010
+ const lock = await acquireInstallLock(paths.lockPath, 'remove-runtime', paths.runtimeRoot, dataDir);
1011
+ if (!lock.ok || !lock.lock)
1012
+ return {
1013
+ ok: false,
1014
+ status: 'locked',
1015
+ runtimeRoot: paths.runtimeRoot,
1016
+ residualPaths: [paths.lockPath],
1017
+ diagnostics: [
1018
+ createDiagnostic({
1019
+ code: diagnosticCodes.initApplyResidual,
1020
+ severity: 'error',
1021
+ message: lock.message ?? 'Runtime removal is locked.',
1022
+ source: paths.lockPath,
1023
+ }),
1024
+ ],
1025
+ };
1026
+ let result;
1027
+ try {
1028
+ result = await removeRuntimeAt(options, lock.lock.token);
1029
+ }
1030
+ catch (error) {
1031
+ result = {
1032
+ ok: false,
1033
+ status: 'failed',
1034
+ runtimeRoot: paths.runtimeRoot,
1035
+ residualPaths: [],
1036
+ diagnostics: [
1037
+ createDiagnostic({
1038
+ code: diagnosticCodes.initApplyFailed,
1039
+ severity: 'error',
1040
+ message: errorMessage(error),
1041
+ source: paths.runtimeRoot,
1042
+ }),
1043
+ ],
1044
+ };
1045
+ }
1046
+ const released = await releaseInstallLock(paths.lockPath, lock.lock.token);
1047
+ return released.ok
1048
+ ? result
1049
+ : {
1050
+ ...result,
1051
+ ok: false,
1052
+ status: 'failed',
1053
+ residualPaths: [...result.residualPaths, paths.lockPath],
1054
+ diagnostics: [
1055
+ ...result.diagnostics,
1056
+ createDiagnostic({
1057
+ code: released.code,
1058
+ severity: 'error',
1059
+ message: released.message,
1060
+ source: paths.lockPath,
1061
+ }),
1062
+ ],
1063
+ };
1064
+ }
1065
+ async function removeRuntimeAt(options, heldLockToken) {
846
1066
  const dataDir = resolve(options.dataDir ?? resolveUserDataDir());
847
1067
  const paths = runtimePaths(options.version, dataDir);
848
1068
  if (!validVersion(options.version)) {
@@ -862,27 +1082,38 @@ export async function removeRuntime(options) {
862
1082
  residualPaths: [],
863
1083
  };
864
1084
  }
865
- const references = options.activeReferences ?? [];
866
- if (references.length > 0 || options.activeReferences === undefined) {
1085
+ if ((await observePath(paths.runtimeRoot)).kind === 'missing')
1086
+ return {
1087
+ ok: true,
1088
+ status: 'missing',
1089
+ runtimeRoot: paths.runtimeRoot,
1090
+ diagnostics: [],
1091
+ residualPaths: [],
1092
+ };
1093
+ const references = await inspectRuntimeReferences(dataDir, options.version);
1094
+ const activeCount = references.active.length + (options.activeReferences?.length ?? 0);
1095
+ if (activeCount > 0 || options.forceUnknownReferences !== true) {
867
1096
  return {
868
1097
  ok: false,
869
- status: 'in-use',
1098
+ status: activeCount > 0 ? 'in-use' : 'unknown',
870
1099
  runtimeRoot: paths.runtimeRoot,
871
1100
  diagnostics: [
872
1101
  createDiagnostic({
873
1102
  code: diagnosticCodes.initApplyResidual,
874
1103
  severity: 'error',
875
- message: options.activeReferences === undefined
876
- ? 'Runtime removal requires an explicit active-reference check.'
877
- : `Runtime is referenced by ${references.length} active workspace(s).`,
1104
+ message: activeCount > 0
1105
+ ? `Runtime is referenced by ${activeCount} verified or explicitly reported active workspace(s).`
1106
+ : 'Global reference coverage is unknown: only explicitly registered workspace roots were checked. An empty caller list is not proof of no references.',
878
1107
  source: paths.runtimeRoot,
879
- hint: 'Resolve active references and retry; Pappra does not guess that a runtime is unused.',
1108
+ hint: activeCount > 0
1109
+ ? 'Rebind or explicitly retire those workspace bindings; --force never bypasses a verified reference.'
1110
+ : 'Review registered and unregistered workspace usage, then use the explicit CLI --force policy only to accept unknown coverage.',
880
1111
  }),
881
1112
  ],
882
1113
  residualPaths: [],
883
1114
  };
884
1115
  }
885
- const state = await inspectRuntimeState(options.version, dataDir);
1116
+ const state = await inspectRuntimeState(options.version, dataDir, heldLockToken);
886
1117
  if (state.status === 'missing') {
887
1118
  return {
888
1119
  ok: true,
@@ -904,7 +1135,7 @@ export async function removeRuntime(options) {
904
1135
  if (state.status !== 'complete' || state.manifest === null) {
905
1136
  return {
906
1137
  ok: false,
907
- status: 'corrupt',
1138
+ status: state.status === 'incompatible' ? 'incompatible' : 'corrupt',
908
1139
  runtimeRoot: paths.runtimeRoot,
909
1140
  diagnostics: state.diagnostics,
910
1141
  residualPaths: [],
@@ -1098,9 +1329,16 @@ export async function inspectRuntimeStatePassive(version, dataDir = resolveUserD
1098
1329
  }
1099
1330
  const manifestRead = await readJsonDocument(manifestPath, runtimeManifestSchema);
1100
1331
  if (!manifestRead.exists || manifestRead.value === null) {
1332
+ const incompatible = manifestRead.exists
1333
+ ? await incompatibleRuntimeDiagnostic(paths.runtimeRoot)
1334
+ : null;
1101
1335
  return {
1102
1336
  version,
1103
- status: manifestRead.exists ? 'unreadable' : 'corrupt',
1337
+ status: incompatible !== null
1338
+ ? 'incompatible'
1339
+ : manifestRead.exists
1340
+ ? 'unreadable'
1341
+ : 'corrupt',
1104
1342
  runtimeRoot: paths.runtimeRoot,
1105
1343
  manifest: null,
1106
1344
  journal: null,
@@ -1108,17 +1346,19 @@ export async function inspectRuntimeStatePassive(version, dataDir = resolveUserD
1108
1346
  lockPresent,
1109
1347
  packageDigestMatch: false,
1110
1348
  executableExists: false,
1111
- diagnostics: manifestRead.diagnostics.length > 0
1112
- ? manifestRead.diagnostics
1113
- : [
1114
- createDiagnostic({
1115
- code: diagnosticCodes.initApplyFailed,
1116
- severity: 'error',
1117
- message: `Runtime manifest is missing: ${paths.runtimeRoot}`,
1118
- source: paths.runtimeRoot,
1119
- hint: 'Inspect the partial runtime; doctor does not overwrite it.',
1120
- }),
1121
- ],
1349
+ diagnostics: incompatible !== null
1350
+ ? [incompatible]
1351
+ : manifestRead.diagnostics.length > 0
1352
+ ? manifestRead.diagnostics
1353
+ : [
1354
+ createDiagnostic({
1355
+ code: diagnosticCodes.initApplyFailed,
1356
+ severity: 'error',
1357
+ message: `Runtime manifest is missing: ${paths.runtimeRoot}`,
1358
+ source: paths.runtimeRoot,
1359
+ hint: 'Inspect the partial runtime; doctor does not overwrite it.',
1360
+ }),
1361
+ ],
1122
1362
  };
1123
1363
  }
1124
1364
  const manifest = manifestRead.value;
@@ -0,0 +1,2 @@
1
+ /** Payload bytes plus actual resolved package-manifest/edge identities; no absolute paths. */
2
+ export declare function fingerprintSelfSource(packageRoot: string, packageName: string, version: string): Promise<string>;
@@ -0,0 +1,103 @@
1
+ import { readdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { observePath, readFileBounded, readTextFileBounded, resolveRealpath, sha256Digest, } from '../infra/fs.js';
4
+ import { isPathInside } from '../schemas/paths.js';
5
+ import { readPackageDependencyManifest, resolveInstalledDependency, } from './dependencies.js';
6
+ /** Exactly the payload entries staged by the self-package installer. */
7
+ const SELF_PAYLOAD = [
8
+ 'package.json',
9
+ 'README.md',
10
+ 'LICENSE',
11
+ 'THIRD_PARTY_NOTICES.md',
12
+ 'dist',
13
+ 'assets',
14
+ 'skills',
15
+ ];
16
+ /** Payload bytes plus actual resolved package-manifest/edge identities; no absolute paths. */
17
+ export async function fingerprintSelfSource(packageRoot, packageName, version) {
18
+ const canonical = await resolveRealpath(packageRoot);
19
+ if (!canonical)
20
+ throw new Error('Running package root cannot be resolved.');
21
+ const parts = [];
22
+ let files = 0;
23
+ let bytes = 0;
24
+ const active = new Set();
25
+ const visit = async (relative) => {
26
+ const path = join(canonical, relative);
27
+ const presence = await observePath(path);
28
+ if (presence.kind === 'missing') {
29
+ parts.push(`missing:${relative}`);
30
+ return;
31
+ }
32
+ const real = await resolveRealpath(path);
33
+ if (!real || !isPathInside(canonical, real))
34
+ throw new Error(`Self payload escapes its package root: ${relative}`);
35
+ const target = await observePath(real);
36
+ if (target.kind === 'directory') {
37
+ if (active.has(real))
38
+ throw new Error('Self payload contains a directory cycle.');
39
+ active.add(real);
40
+ parts.push(`dir:${relative}`);
41
+ for (const entry of (await readdir(real)).sort())
42
+ await visit(`${relative}/${entry}`);
43
+ active.delete(real);
44
+ }
45
+ else if (target.kind === 'file') {
46
+ if (++files > 8192)
47
+ throw new Error('Self payload exceeds the fingerprint file budget.');
48
+ const read = await readFileBounded(real, 64 * 1024 * 1024);
49
+ if (!read.ok)
50
+ throw new Error(`Cannot fingerprint self payload: ${relative}`);
51
+ bytes += read.value.size;
52
+ if (bytes > 256 * 1024 * 1024)
53
+ throw new Error('Self payload exceeds the fingerprint byte budget.');
54
+ parts.push(`file:${relative}:${read.value.digest}`);
55
+ }
56
+ else
57
+ throw new Error(`Unsupported self payload entry: ${relative}`);
58
+ };
59
+ const manifest = await readTextFileBounded(join(canonical, 'package.json'));
60
+ if (!manifest.ok)
61
+ throw new Error('Running package manifest is unreadable.');
62
+ const decoded = JSON.parse(manifest.value.text);
63
+ if (decoded.name !== packageName || decoded.version !== version)
64
+ throw new Error('Running package name/version differs from the requested runtime; bump the package version before reinstalling.');
65
+ for (const entry of SELF_PAYLOAD)
66
+ await visit(entry);
67
+ const visited = new Set();
68
+ const graph = new Set();
69
+ const dependencies = async (directory) => {
70
+ const real = await resolveRealpath(directory);
71
+ if (!real || visited.has(real))
72
+ return;
73
+ visited.add(real);
74
+ if (visited.size > 512)
75
+ throw new Error('Resolved dependency identity budget exceeded.');
76
+ const read = await readTextFileBounded(join(real, 'package.json'));
77
+ const declared = await readPackageDependencyManifest(real);
78
+ if (!read.ok || !declared)
79
+ throw new Error('Cannot read a resolved dependency manifest.');
80
+ const value = JSON.parse(read.value.text);
81
+ const edges = [];
82
+ for (const [kind, names] of [
83
+ ['required', declared.required],
84
+ ['optional', declared.optional],
85
+ ]) {
86
+ for (const name of [...names].sort()) {
87
+ const resolved = await resolveInstalledDependency(real, name);
88
+ if (!resolved) {
89
+ edges.push(`${kind}:${name}:missing`);
90
+ continue;
91
+ }
92
+ const resolvedManifest = await readTextFileBounded(join(resolved, 'package.json'));
93
+ if (!resolvedManifest.ok)
94
+ throw new Error('Cannot read a resolved dependency identity.');
95
+ edges.push(`${kind}:${name}:${resolvedManifest.value.digest}`);
96
+ await dependencies(resolved);
97
+ }
98
+ }
99
+ graph.add(`${String(value.name)}@${String(value.version)}:${read.value.digest}:${edges.join('|')}`);
100
+ };
101
+ await dependencies(canonical);
102
+ return sha256Digest([...parts, ...[...graph].sort().map((entry) => `dependency:${entry}`)].join('\n'));
103
+ }
@@ -13,6 +13,7 @@ export interface CatalogFileState {
13
13
  readonly catalog: DocumentCatalog | null;
14
14
  readonly diagnostics: readonly Diagnostic[];
15
15
  readonly valid: boolean;
16
+ readonly recordDigests?: ReadonlyMap<string, string>;
16
17
  }
17
18
  export interface KnowledgeCatalogs {
18
19
  readonly paths: KnowledgePaths;