@elie-laloum/outpost 4.0.0 → 4.2.0

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 (240) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +10 -6
  3. package/SECURITY.md +2 -0
  4. package/dist/adapters/agents/codex-adapter.js +2 -0
  5. package/dist/adapters/agents/codex-adapter.js.map +1 -1
  6. package/dist/adapters/agents/codex-provider.d.ts +2 -0
  7. package/dist/adapters/agents/codex-provider.js +32 -0
  8. package/dist/adapters/agents/codex-provider.js.map +1 -0
  9. package/dist/adapters/agents/codex-request.js +2 -1
  10. package/dist/adapters/agents/codex-request.js.map +1 -1
  11. package/dist/adapters/agents/gemini-request.js +2 -0
  12. package/dist/adapters/agents/gemini-request.js.map +1 -1
  13. package/dist/adapters/agents/settings.types.d.ts +5 -0
  14. package/dist/adapters/agents/settings.types.js.map +1 -1
  15. package/dist/adapters/models/openai-compatible.constants.d.ts +4 -0
  16. package/dist/adapters/models/openai-compatible.constants.js +10 -0
  17. package/dist/adapters/models/openai-compatible.constants.js.map +1 -0
  18. package/dist/adapters/models/openai-compatible.d.ts +3 -0
  19. package/dist/adapters/models/openai-compatible.js +78 -0
  20. package/dist/adapters/models/openai-compatible.js.map +1 -0
  21. package/dist/adapters/models/openai-compatible.types.d.ts +14 -0
  22. package/dist/adapters/models/openai-compatible.types.js +2 -0
  23. package/dist/adapters/models/openai-compatible.types.js.map +1 -0
  24. package/dist/adapters/models/openai-http.d.ts +1 -0
  25. package/dist/adapters/models/openai-http.js +30 -0
  26. package/dist/adapters/models/openai-http.js.map +1 -0
  27. package/dist/adapters/models/openai-request.d.ts +4 -0
  28. package/dist/adapters/models/openai-request.js +46 -0
  29. package/dist/adapters/models/openai-request.js.map +1 -0
  30. package/dist/adapters/models/openai-response.d.ts +3 -0
  31. package/dist/adapters/models/openai-response.js +65 -0
  32. package/dist/adapters/models/openai-response.js.map +1 -0
  33. package/dist/application/execution.types.d.ts +2 -0
  34. package/dist/application/execution.types.js.map +1 -1
  35. package/dist/application/outpost.types.d.ts +5 -0
  36. package/dist/application/outpost.types.js.map +1 -1
  37. package/dist/application/recovery-archive.d.ts +4 -0
  38. package/dist/application/recovery-archive.js +29 -0
  39. package/dist/application/recovery-archive.js.map +1 -0
  40. package/dist/application/recovery-archive.types.d.ts +10 -0
  41. package/dist/application/recovery-archive.types.js +2 -0
  42. package/dist/application/recovery-archive.types.js.map +1 -0
  43. package/dist/application/recovery-inspection.js +3 -0
  44. package/dist/application/recovery-inspection.js.map +1 -1
  45. package/dist/application/recovery-inspection.types.d.ts +2 -0
  46. package/dist/application/recovery-inspection.types.js.map +1 -1
  47. package/dist/application/recovery-retention.d.ts +2 -1
  48. package/dist/application/recovery-retention.js +9 -1
  49. package/dist/application/recovery-retention.js.map +1 -1
  50. package/dist/application/recovery-retention.types.d.ts +6 -0
  51. package/dist/application/recovery-retention.types.js.map +1 -1
  52. package/dist/application/remote-backup.js +8 -0
  53. package/dist/application/remote-backup.js.map +1 -1
  54. package/dist/application/remote-workspace.types.d.ts +2 -0
  55. package/dist/application/remote-workspace.types.js.map +1 -1
  56. package/dist/application/sandbox-agents.js +2 -1
  57. package/dist/application/sandbox-agents.js.map +1 -1
  58. package/dist/application/sandbox-dispatch.js +9 -0
  59. package/dist/application/sandbox-dispatch.js.map +1 -1
  60. package/dist/application/sandbox-provision.js +6 -0
  61. package/dist/application/sandbox-provision.js.map +1 -1
  62. package/dist/application/storage-reservation.js +3 -0
  63. package/dist/application/storage-reservation.js.map +1 -1
  64. package/dist/application/transport-inspection.constants.d.ts +2 -0
  65. package/dist/application/transport-inspection.constants.js +10 -0
  66. package/dist/application/transport-inspection.constants.js.map +1 -0
  67. package/dist/application/transport-inspection.d.ts +2 -0
  68. package/dist/application/transport-inspection.js +103 -0
  69. package/dist/application/transport-inspection.js.map +1 -0
  70. package/dist/application/transport-retention.d.ts +4 -0
  71. package/dist/application/transport-retention.js +153 -0
  72. package/dist/application/transport-retention.js.map +1 -0
  73. package/dist/cli/commands.js +55 -18
  74. package/dist/cli/commands.js.map +1 -1
  75. package/dist/cli/init-authentication.constants.d.ts +13 -0
  76. package/dist/cli/init-authentication.constants.js +10 -0
  77. package/dist/cli/init-authentication.constants.js.map +1 -0
  78. package/dist/cli/init-authentication.d.ts +4 -0
  79. package/dist/cli/init-authentication.js +43 -0
  80. package/dist/cli/init-authentication.js.map +1 -0
  81. package/dist/cli/init-command.js +74 -15
  82. package/dist/cli/init-command.js.map +1 -1
  83. package/dist/cli/main.constants.d.ts +102 -2
  84. package/dist/cli/main.constants.js +148 -46
  85. package/dist/cli/main.constants.js.map +1 -1
  86. package/dist/cli/scaffold-files.js +4 -3
  87. package/dist/cli/scaffold-files.js.map +1 -1
  88. package/dist/cli/scaffold-validation.js +19 -0
  89. package/dist/cli/scaffold-validation.js.map +1 -1
  90. package/dist/cli/scaffold.constants.d.ts +0 -5
  91. package/dist/cli/scaffold.constants.js +0 -5
  92. package/dist/cli/scaffold.constants.js.map +1 -1
  93. package/dist/cli/scaffold.js +13 -0
  94. package/dist/cli/scaffold.js.map +1 -1
  95. package/dist/cli/scaffold.types.d.ts +3 -0
  96. package/dist/cli/scaffold.types.js.map +1 -1
  97. package/dist/cli/starters.js +9 -2
  98. package/dist/cli/starters.js.map +1 -1
  99. package/dist/domain/conversation.types.d.ts +2 -0
  100. package/dist/domain/conversation.types.js.map +1 -1
  101. package/dist/domain/model.types.d.ts +15 -0
  102. package/dist/domain/model.types.js +2 -0
  103. package/dist/domain/model.types.js.map +1 -0
  104. package/dist/domain/transport.constants.d.ts +2 -0
  105. package/dist/domain/transport.constants.js +3 -0
  106. package/dist/domain/transport.constants.js.map +1 -0
  107. package/dist/domain/transport.d.ts +6 -0
  108. package/dist/domain/transport.js +21 -0
  109. package/dist/domain/transport.js.map +1 -0
  110. package/dist/domain/transport.types.d.ts +31 -0
  111. package/dist/domain/transport.types.js +2 -0
  112. package/dist/domain/transport.types.js.map +1 -0
  113. package/dist/index.d.ts +18 -1
  114. package/dist/index.js +8 -0
  115. package/dist/index.js.map +1 -1
  116. package/dist/infrastructure/artifact-store.js +10 -0
  117. package/dist/infrastructure/artifact-store.js.map +1 -1
  118. package/dist/infrastructure/artifact-store.types.d.ts +3 -1
  119. package/dist/infrastructure/artifact-store.types.js.map +1 -1
  120. package/dist/infrastructure/conversations/capture.js +1 -1
  121. package/dist/infrastructure/conversations/capture.js.map +1 -1
  122. package/dist/infrastructure/conversations.d.ts +2 -0
  123. package/dist/infrastructure/conversations.js +2 -0
  124. package/dist/infrastructure/conversations.js.map +1 -1
  125. package/dist/infrastructure/inspection-file.js +3 -3
  126. package/dist/infrastructure/inspection-file.js.map +1 -1
  127. package/dist/infrastructure/inspection-hash.js +4 -3
  128. package/dist/infrastructure/inspection-hash.js.map +1 -1
  129. package/dist/infrastructure/inspection-path.d.ts +1 -0
  130. package/dist/infrastructure/inspection-path.js +18 -0
  131. package/dist/infrastructure/inspection-path.js.map +1 -0
  132. package/dist/infrastructure/journal.js +6 -0
  133. package/dist/infrastructure/journal.js.map +1 -1
  134. package/dist/infrastructure/journal.types.d.ts +3 -0
  135. package/dist/infrastructure/journal.types.js.map +1 -1
  136. package/dist/infrastructure/local-transport.d.ts +3 -0
  137. package/dist/infrastructure/local-transport.js +146 -0
  138. package/dist/infrastructure/local-transport.js.map +1 -0
  139. package/dist/infrastructure/local-transport.types.d.ts +3 -0
  140. package/dist/infrastructure/local-transport.types.js +2 -0
  141. package/dist/infrastructure/local-transport.types.js.map +1 -0
  142. package/dist/infrastructure/resource-activity-inspection.d.ts +2 -1
  143. package/dist/infrastructure/resource-activity-inspection.js +2 -2
  144. package/dist/infrastructure/resource-activity-inspection.js.map +1 -1
  145. package/dist/infrastructure/resource-activity.js +42 -14
  146. package/dist/infrastructure/resource-activity.js.map +1 -1
  147. package/dist/infrastructure/resource-activity.types.d.ts +2 -0
  148. package/dist/infrastructure/resource-activity.types.js.map +1 -1
  149. package/dist/infrastructure/s3-transport.d.ts +4 -0
  150. package/dist/infrastructure/s3-transport.js +144 -0
  151. package/dist/infrastructure/s3-transport.js.map +1 -0
  152. package/dist/infrastructure/s3-transport.types.d.ts +6 -0
  153. package/dist/infrastructure/s3-transport.types.js +2 -0
  154. package/dist/infrastructure/s3-transport.types.js.map +1 -0
  155. package/dist/infrastructure/storage-inventory.types.d.ts +2 -1
  156. package/dist/infrastructure/storage-inventory.types.js.map +1 -1
  157. package/dist/infrastructure/storage-reservations.types.d.ts +2 -0
  158. package/dist/infrastructure/storage-reservations.types.js.map +1 -1
  159. package/dist/infrastructure/task-queue-bullmq-resources.d.ts +13 -0
  160. package/dist/infrastructure/task-queue-bullmq-resources.js +118 -0
  161. package/dist/infrastructure/task-queue-bullmq-resources.js.map +1 -0
  162. package/dist/infrastructure/task-queue-bullmq-state.d.ts +5 -0
  163. package/dist/infrastructure/task-queue-bullmq-state.js +26 -0
  164. package/dist/infrastructure/task-queue-bullmq-state.js.map +1 -0
  165. package/dist/infrastructure/task-queue-bullmq.constants.d.ts +7 -0
  166. package/dist/infrastructure/task-queue-bullmq.constants.js +79 -0
  167. package/dist/infrastructure/task-queue-bullmq.constants.js.map +1 -0
  168. package/dist/infrastructure/task-queue-bullmq.d.ts +3 -0
  169. package/dist/infrastructure/task-queue-bullmq.js +124 -0
  170. package/dist/infrastructure/task-queue-bullmq.js.map +1 -0
  171. package/dist/infrastructure/task-queue-bullmq.types.d.ts +17 -0
  172. package/dist/infrastructure/task-queue-bullmq.types.js +2 -0
  173. package/dist/infrastructure/task-queue-bullmq.types.js.map +1 -0
  174. package/dist/infrastructure/transport-archive.constants.d.ts +6 -0
  175. package/dist/infrastructure/transport-archive.constants.js +7 -0
  176. package/dist/infrastructure/transport-archive.constants.js.map +1 -0
  177. package/dist/infrastructure/transport-archive.d.ts +5 -0
  178. package/dist/infrastructure/transport-archive.js +157 -0
  179. package/dist/infrastructure/transport-archive.js.map +1 -0
  180. package/dist/infrastructure/transport-archive.types.d.ts +17 -0
  181. package/dist/infrastructure/transport-archive.types.js +2 -0
  182. package/dist/infrastructure/transport-archive.types.js.map +1 -0
  183. package/dist/infrastructure/transport-artifact-store.d.ts +3 -0
  184. package/dist/infrastructure/transport-artifact-store.js +31 -0
  185. package/dist/infrastructure/transport-artifact-store.js.map +1 -0
  186. package/dist/infrastructure/transport-artifact-store.types.d.ts +4 -0
  187. package/dist/infrastructure/transport-artifact-store.types.js +2 -0
  188. package/dist/infrastructure/transport-artifact-store.types.js.map +1 -0
  189. package/dist/infrastructure/transport-checkpoint.d.ts +5 -0
  190. package/dist/infrastructure/transport-checkpoint.js +105 -0
  191. package/dist/infrastructure/transport-checkpoint.js.map +1 -0
  192. package/dist/infrastructure/transport-checkpoint.types.d.ts +9 -0
  193. package/dist/infrastructure/transport-checkpoint.types.js +2 -0
  194. package/dist/infrastructure/transport-checkpoint.types.js.map +1 -0
  195. package/dist/infrastructure/transport-conversations.d.ts +4 -0
  196. package/dist/infrastructure/transport-conversations.js +69 -0
  197. package/dist/infrastructure/transport-conversations.js.map +1 -0
  198. package/dist/infrastructure/transport-conversations.types.d.ts +4 -0
  199. package/dist/infrastructure/transport-conversations.types.js +2 -0
  200. package/dist/infrastructure/transport-conversations.types.js.map +1 -0
  201. package/dist/infrastructure/transport-envelope.d.ts +7 -0
  202. package/dist/infrastructure/transport-envelope.js +47 -0
  203. package/dist/infrastructure/transport-envelope.js.map +1 -0
  204. package/dist/infrastructure/transport-journal.d.ts +6 -0
  205. package/dist/infrastructure/transport-journal.js +93 -0
  206. package/dist/infrastructure/transport-journal.js.map +1 -0
  207. package/dist/infrastructure/transport-journal.types.d.ts +10 -0
  208. package/dist/infrastructure/transport-journal.types.js +2 -0
  209. package/dist/infrastructure/transport-journal.types.js.map +1 -0
  210. package/dist/infrastructure/transport-json.d.ts +6 -0
  211. package/dist/infrastructure/transport-json.js +46 -0
  212. package/dist/infrastructure/transport-json.js.map +1 -0
  213. package/dist/infrastructure/transport-reservations.d.ts +5 -0
  214. package/dist/infrastructure/transport-reservations.js +89 -0
  215. package/dist/infrastructure/transport-reservations.js.map +1 -0
  216. package/dist/infrastructure/transport-reservations.types.d.ts +4 -0
  217. package/dist/infrastructure/transport-reservations.types.js +2 -0
  218. package/dist/infrastructure/transport-reservations.types.js.map +1 -0
  219. package/dist/infrastructure/transport.constants.d.ts +8 -0
  220. package/dist/infrastructure/transport.constants.js +9 -0
  221. package/dist/infrastructure/transport.constants.js.map +1 -0
  222. package/dist/infrastructure/workflow-checkpoint.js +7 -0
  223. package/dist/infrastructure/workflow-checkpoint.js.map +1 -1
  224. package/dist/infrastructure/workflow-checkpoint.types.d.ts +3 -1
  225. package/dist/infrastructure/workflow-checkpoint.types.js.map +1 -1
  226. package/dist/providers/agents.d.ts +1 -1
  227. package/dist/providers/agents.js.map +1 -1
  228. package/dist/providers/daytona-command.constants.d.ts +2 -0
  229. package/dist/providers/daytona-command.constants.js +13 -0
  230. package/dist/providers/daytona-command.constants.js.map +1 -0
  231. package/dist/providers/daytona-command.js +9 -3
  232. package/dist/providers/daytona-command.js.map +1 -1
  233. package/dist/providers/daytona-output.d.ts +4 -0
  234. package/dist/providers/daytona-output.js +37 -0
  235. package/dist/providers/daytona-output.js.map +1 -0
  236. package/dist/providers/daytona-terminal.js +3 -1
  237. package/dist/providers/daytona-terminal.js.map +1 -1
  238. package/dist/providers/vercel-command.js +2 -2
  239. package/dist/providers/vercel-command.js.map +1 -1
  240. package/package.json +31 -6
@@ -1,3 +1,4 @@
1
+ import { transportConversations } from "./transport-conversations.ts";
1
2
  import type { ConversationFormat } from "./conversations.types.ts";
2
3
  import { captureConversation } from "./conversations/capture.ts";
3
4
  import { locateConversation } from "./conversations/locate.ts";
@@ -13,6 +14,7 @@ export { projectKey } from "./conversations/paths.ts";
13
14
  export { relocateTranscript } from "./conversations/relocate.ts";
14
15
  export { restoreConversation } from "./conversations/restore.ts";
15
16
  export declare const conversations: {
17
+ transported: typeof transportConversations;
16
18
  native: typeof nativeConversations;
17
19
  locate: typeof locateConversation;
18
20
  capture: typeof captureConversation;
@@ -1,3 +1,4 @@
1
+ import { transportConversations } from "./transport-conversations.js";
1
2
  import { homedir } from "node:os";
2
3
  import { join, resolve } from "node:path";
3
4
  import { captureConversation } from "./conversations/capture.js";
@@ -14,6 +15,7 @@ export { projectKey } from "./conversations/paths.js";
14
15
  export { relocateTranscript } from "./conversations/relocate.js";
15
16
  export { restoreConversation } from "./conversations/restore.js";
16
17
  export const conversations = {
18
+ transported: transportConversations,
17
19
  native: nativeConversations,
18
20
  locate: locateConversation,
19
21
  capture: captureConversation,
@@ -1 +1 @@
1
- {"version":3,"file":"conversations.js","sourceRoot":"","sources":["../../src/infrastructure/conversations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAMjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,MAAM,EAAE,mBAAmB;IAC3B,MAAM,EAAE,kBAAkB;IAC1B,OAAO,EAAE,mBAAmB;IAC5B,OAAO,EAAE,mBAAmB;IAC5B,OAAO,EAAE,kBAAkB;IAC3B,UAAU;IACV,UAAU,CAAC,EAAU,EAAE,UAAkB,EAAE,IAAI,GAAG,OAAO,EAAE;QACzD,OAAO,CAAC,EAAE,CAAC,CAAC;QACZ,OAAO,IAAI,CACT,IAAI,EACJ,SAAS,EACT,UAAU,EACV,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,EAC/B,GAAG,EAAE,QAAQ,CACd,CAAC;IACJ,CAAC;IACD,SAAS,CACP,MAA0B,EAC1B,UAAkB,EAClB,IAAI,GAAG,OAAO,EAAE;QAEhB,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAChE,CAAC;IACD,WAAW,EAAE,UAAU;CACxB,CAAC","sourcesContent":["import { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport type { ConversationFormat } from \"./conversations.types.ts\";\nimport { captureConversation } from \"./conversations/capture.ts\";\nimport { conversationLayout } from \"./conversations/layout.ts\";\nimport { locateConversation } from \"./conversations/locate.ts\";\nimport { nativeConversations } from \"./conversations/native-store.ts\";\nimport { projectKey, remotePath, validId } from \"./conversations/paths.ts\";\nimport { relocateTranscript } from \"./conversations/relocate.ts\";\nimport { restoreConversation } from \"./conversations/restore.ts\";\n\nexport type {\n ConversationFormat,\n ConversationLocation,\n} from \"./conversations.types.ts\";\nexport { captureConversation } from \"./conversations/capture.ts\";\nexport { locateConversation } from \"./conversations/locate.ts\";\nexport { nativeConversations } from \"./conversations/native-store.ts\";\nexport { projectKey } from \"./conversations/paths.ts\";\nexport { relocateTranscript } from \"./conversations/relocate.ts\";\nexport { restoreConversation } from \"./conversations/restore.ts\";\n\nexport const conversations = {\n native: nativeConversations,\n locate: locateConversation,\n capture: captureConversation,\n restore: restoreConversation,\n rewrite: relocateTranscript,\n projectKey,\n claudePath(id: string, repository: string, home = homedir()): string {\n validId(id);\n return join(\n home,\n \".claude\",\n \"projects\",\n projectKey(resolve(repository)),\n `${id}.jsonl`,\n );\n },\n directory(\n format: ConversationFormat,\n repository: string,\n home = homedir(),\n ): string {\n return conversationLayout(format).directory(repository, home);\n },\n destination: remotePath,\n};\n"]}
1
+ {"version":3,"file":"conversations.js","sourceRoot":"","sources":["../../src/infrastructure/conversations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAMjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,WAAW,EAAE,sBAAsB;IACnC,MAAM,EAAE,mBAAmB;IAC3B,MAAM,EAAE,kBAAkB;IAC1B,OAAO,EAAE,mBAAmB;IAC5B,OAAO,EAAE,mBAAmB;IAC5B,OAAO,EAAE,kBAAkB;IAC3B,UAAU;IACV,UAAU,CAAC,EAAU,EAAE,UAAkB,EAAE,IAAI,GAAG,OAAO,EAAE;QACzD,OAAO,CAAC,EAAE,CAAC,CAAC;QACZ,OAAO,IAAI,CACT,IAAI,EACJ,SAAS,EACT,UAAU,EACV,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,EAC/B,GAAG,EAAE,QAAQ,CACd,CAAC;IACJ,CAAC;IACD,SAAS,CACP,MAA0B,EAC1B,UAAkB,EAClB,IAAI,GAAG,OAAO,EAAE;QAEhB,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAChE,CAAC;IACD,WAAW,EAAE,UAAU;CACxB,CAAC","sourcesContent":["import { transportConversations } from \"./transport-conversations.ts\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport type { ConversationFormat } from \"./conversations.types.ts\";\nimport { captureConversation } from \"./conversations/capture.ts\";\nimport { conversationLayout } from \"./conversations/layout.ts\";\nimport { locateConversation } from \"./conversations/locate.ts\";\nimport { nativeConversations } from \"./conversations/native-store.ts\";\nimport { projectKey, remotePath, validId } from \"./conversations/paths.ts\";\nimport { relocateTranscript } from \"./conversations/relocate.ts\";\nimport { restoreConversation } from \"./conversations/restore.ts\";\n\nexport type {\n ConversationFormat,\n ConversationLocation,\n} from \"./conversations.types.ts\";\nexport { captureConversation } from \"./conversations/capture.ts\";\nexport { locateConversation } from \"./conversations/locate.ts\";\nexport { nativeConversations } from \"./conversations/native-store.ts\";\nexport { projectKey } from \"./conversations/paths.ts\";\nexport { relocateTranscript } from \"./conversations/relocate.ts\";\nexport { restoreConversation } from \"./conversations/restore.ts\";\n\nexport const conversations = {\n transported: transportConversations,\n native: nativeConversations,\n locate: locateConversation,\n capture: captureConversation,\n restore: restoreConversation,\n rewrite: relocateTranscript,\n projectKey,\n claudePath(id: string, repository: string, home = homedir()): string {\n validId(id);\n return join(\n home,\n \".claude\",\n \"projects\",\n projectKey(resolve(repository)),\n `${id}.jsonl`,\n );\n },\n directory(\n format: ConversationFormat,\n repository: string,\n home = homedir(),\n ): string {\n return conversationLayout(format).directory(repository, home);\n },\n destination: remotePath,\n};\n"]}
@@ -1,7 +1,7 @@
1
- import { lstat, open, realpath } from "node:fs/promises";
2
- import { resolve } from "node:path";
1
+ import { lstat, open } from "node:fs/promises";
3
2
  import { positive } from "../domain/errors.js";
4
3
  import { inspectionFileFlags } from "./inspection-file.constants.js";
4
+ import { isDirectInspectionPath } from "./inspection-path.js";
5
5
  function unchanged(before, after) {
6
6
  return (before.dev === after.dev &&
7
7
  before.ino === after.ino &&
@@ -12,7 +12,7 @@ function unchanged(before, after) {
12
12
  export async function readInspectionFile(path, maxBytes) {
13
13
  positive(maxBytes, "maxBytes");
14
14
  const before = await lstat(path);
15
- if (!before.isFile() || (await realpath(path)) !== resolve(path))
15
+ if (!before.isFile() || !(await isDirectInspectionPath(path)))
16
16
  throw new Error("Inspection path changed");
17
17
  if (before.size > maxBytes)
18
18
  throw new Error("Inspection file is too large");
@@ -1 +1 @@
1
- {"version":3,"file":"inspection-file.js","sourceRoot":"","sources":["../../src/infrastructure/inspection-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAErE,SAAS,SAAS,CAAC,MAAa,EAAE,KAAY;IAC5C,OAAO,CACL,MAAM,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG;QACxB,MAAM,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG;QACxB,MAAM,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI;QAC1B,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO;QAChC,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,CACjC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAY,EACZ,QAAgB;IAEhB,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC;QAC9D,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,IAAI,GAAG,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;QAC1C,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YAC9B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CACnC,MAAM,EACN,MAAM,EACN,MAAM,CAAC,MAAM,GAAG,MAAM,EACtB,MAAM,CACP,CAAC;YACF,IAAI,CAAC,SAAS;gBAAE,MAAM;YACtB,MAAM,IAAI,SAAS,CAAC;QACtB,CAAC;QACD,IAAI,MAAM,GAAG,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QACvE,IACE,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YACrC,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;YAErC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;AACH,CAAC","sourcesContent":["import { lstat, open, realpath } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { Stats } from \"node:fs\";\nimport { positive } from \"../domain/errors.ts\";\nimport { inspectionFileFlags } from \"./inspection-file.constants.ts\";\n\nfunction unchanged(before: Stats, after: Stats): boolean {\n return (\n before.dev === after.dev &&\n before.ino === after.ino &&\n before.size === after.size &&\n before.mtimeMs === after.mtimeMs &&\n before.ctimeMs === after.ctimeMs\n );\n}\n\nexport async function readInspectionFile(\n path: string,\n maxBytes: number,\n): Promise<Buffer> {\n positive(maxBytes, \"maxBytes\");\n const before = await lstat(path);\n if (!before.isFile() || (await realpath(path)) !== resolve(path))\n throw new Error(\"Inspection path changed\");\n if (before.size > maxBytes) throw new Error(\"Inspection file is too large\");\n const file = await open(path, inspectionFileFlags);\n try {\n const opened = await file.stat();\n if (!opened.isFile() || !unchanged(before, opened))\n throw new Error(\"Inspection file changed\");\n const buffer = Buffer.alloc(maxBytes + 1);\n let length = 0;\n while (length < buffer.length) {\n const { bytesRead } = await file.read(\n buffer,\n length,\n buffer.length - length,\n length,\n );\n if (!bytesRead) break;\n length += bytesRead;\n }\n if (length > maxBytes) throw new Error(\"Inspection file is too large\");\n if (\n !unchanged(opened, await file.stat()) ||\n !unchanged(opened, await lstat(path))\n )\n throw new Error(\"Inspection file changed\");\n return buffer.subarray(0, length);\n } finally {\n await file.close();\n }\n}\n"]}
1
+ {"version":3,"file":"inspection-file.js","sourceRoot":"","sources":["../../src/infrastructure/inspection-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,SAAS,SAAS,CAAC,MAAa,EAAE,KAAY;IAC5C,OAAO,CACL,MAAM,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG;QACxB,MAAM,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG;QACxB,MAAM,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI;QAC1B,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO;QAChC,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,CACjC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAY,EACZ,QAAgB;IAEhB,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,IAAI,GAAG,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC5E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;QAC1C,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YAC9B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CACnC,MAAM,EACN,MAAM,EACN,MAAM,CAAC,MAAM,GAAG,MAAM,EACtB,MAAM,CACP,CAAC;YACF,IAAI,CAAC,SAAS;gBAAE,MAAM;YACtB,MAAM,IAAI,SAAS,CAAC;QACtB,CAAC;QACD,IAAI,MAAM,GAAG,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QACvE,IACE,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YACrC,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;YAErC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;AACH,CAAC","sourcesContent":["import { lstat, open } from \"node:fs/promises\";\nimport type { Stats } from \"node:fs\";\nimport { positive } from \"../domain/errors.ts\";\nimport { inspectionFileFlags } from \"./inspection-file.constants.ts\";\nimport { isDirectInspectionPath } from \"./inspection-path.ts\";\n\nfunction unchanged(before: Stats, after: Stats): boolean {\n return (\n before.dev === after.dev &&\n before.ino === after.ino &&\n before.size === after.size &&\n before.mtimeMs === after.mtimeMs &&\n before.ctimeMs === after.ctimeMs\n );\n}\n\nexport async function readInspectionFile(\n path: string,\n maxBytes: number,\n): Promise<Buffer> {\n positive(maxBytes, \"maxBytes\");\n const before = await lstat(path);\n if (!before.isFile() || !(await isDirectInspectionPath(path)))\n throw new Error(\"Inspection path changed\");\n if (before.size > maxBytes) throw new Error(\"Inspection file is too large\");\n const file = await open(path, inspectionFileFlags);\n try {\n const opened = await file.stat();\n if (!opened.isFile() || !unchanged(before, opened))\n throw new Error(\"Inspection file changed\");\n const buffer = Buffer.alloc(maxBytes + 1);\n let length = 0;\n while (length < buffer.length) {\n const { bytesRead } = await file.read(\n buffer,\n length,\n buffer.length - length,\n length,\n );\n if (!bytesRead) break;\n length += bytesRead;\n }\n if (length > maxBytes) throw new Error(\"Inspection file is too large\");\n if (\n !unchanged(opened, await file.stat()) ||\n !unchanged(opened, await lstat(path))\n )\n throw new Error(\"Inspection file changed\");\n return buffer.subarray(0, length);\n } finally {\n await file.close();\n }\n}\n"]}
@@ -1,9 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
- import { lstat, open, readlink, realpath } from "node:fs/promises";
3
- import { dirname, resolve } from "node:path";
2
+ import { lstat, open, readlink } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
4
  import { OutpostError } from "../domain/errors.js";
5
5
  import { inspectionFileFlags } from "./inspection-file.constants.js";
6
6
  import { inspectionHashChunkBytes } from "./inspection-hash.constants.js";
7
+ import { isDirectInspectionPath } from "./inspection-path.js";
7
8
  function unchanged(before, after) {
8
9
  return (before.dev === after.dev &&
9
10
  before.ino === after.ino &&
@@ -13,7 +14,7 @@ function unchanged(before, after) {
13
14
  }
14
15
  export async function hashInspectionEntry(path, maxBytes = Number.MAX_SAFE_INTEGER) {
15
16
  const before = await lstat(path);
16
- if ((await realpath(dirname(path))) !== resolve(dirname(path)))
17
+ if (!(await isDirectInspectionPath(dirname(path))))
17
18
  throw new Error("Hash path traverses a symlink");
18
19
  const hash = createHash("sha256");
19
20
  if (before.isSymbolicLink()) {
@@ -1 +1 @@
1
- {"version":3,"file":"inspection-hash.js","sourceRoot":"","sources":["../../src/infrastructure/inspection-hash.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { lstat, open, readlink, realpath } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\nimport type { Stats } from \"node:fs\";\nimport { OutpostError } from \"../domain/errors.ts\";\nimport { inspectionFileFlags } from \"./inspection-file.constants.ts\";\nimport { inspectionHashChunkBytes } from \"./inspection-hash.constants.ts\";\nimport type { InspectionHash } from \"./inspection-hash.types.ts\";\n\nfunction unchanged(before: Stats, after: Stats): boolean {\n return (\n before.dev === after.dev &&\n before.ino === after.ino &&\n before.size === after.size &&\n before.mtimeMs === after.mtimeMs &&\n before.ctimeMs === after.ctimeMs\n );\n}\n\nexport async function hashInspectionEntry(\n path: string,\n maxBytes = Number.MAX_SAFE_INTEGER,\n): Promise<InspectionHash> {\n const before = await lstat(path);\n if ((await realpath(dirname(path))) !== resolve(dirname(path)))\n throw new Error(\"Hash path traverses a symlink\");\n const hash = createHash(\"sha256\");\n if (before.isSymbolicLink()) {\n const target = await readlink(path, { encoding: \"buffer\" });\n if (target.length > maxBytes)\n throw new OutpostError(\"workspace\", \"Checksum byte limit exceeded\", {\n reason: \"CHECKSUM_LIMIT\",\n });\n if (!unchanged(before, await lstat(path)))\n throw new Error(\"Hash source changed\");\n return {\n kind: \"symlink\",\n bytes: target.length,\n sha256: hash.update(target).digest(\"hex\"),\n };\n }\n if (!before.isFile()) throw new Error(\"Hash source is not a regular file\");\n if (before.size > maxBytes)\n throw new OutpostError(\"workspace\", \"Checksum byte limit exceeded\", {\n reason: \"CHECKSUM_LIMIT\",\n });\n const file = await open(path, inspectionFileFlags);\n try {\n const opened = await file.stat();\n if (!opened.isFile() || !unchanged(before, opened))\n throw new Error(\"Hash source changed\");\n const buffer = Buffer.alloc(inspectionHashChunkBytes);\n let bytes = 0;\n while (bytes < opened.size) {\n const result = await file.read(\n buffer,\n 0,\n Math.min(buffer.length, opened.size - bytes),\n bytes,\n );\n if (!result.bytesRead) throw new Error(\"Hash source truncated\");\n bytes += result.bytesRead;\n hash.update(buffer.subarray(0, result.bytesRead));\n }\n if (\n !unchanged(opened, await file.stat()) ||\n !unchanged(opened, await lstat(path))\n )\n throw new Error(\"Hash source changed\");\n return { kind: \"file\", bytes, sha256: hash.digest(\"hex\") };\n } finally {\n await file.close();\n }\n}\n"]}
1
+ {"version":3,"file":"inspection-hash.js","sourceRoot":"","sources":["../../src/infrastructure/inspection-hash.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { lstat, open, readlink } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { Stats } from \"node:fs\";\nimport { OutpostError } from \"../domain/errors.ts\";\nimport { inspectionFileFlags } from \"./inspection-file.constants.ts\";\nimport { inspectionHashChunkBytes } from \"./inspection-hash.constants.ts\";\nimport { isDirectInspectionPath } from \"./inspection-path.ts\";\nimport type { InspectionHash } from \"./inspection-hash.types.ts\";\n\nfunction unchanged(before: Stats, after: Stats): boolean {\n return (\n before.dev === after.dev &&\n before.ino === after.ino &&\n before.size === after.size &&\n before.mtimeMs === after.mtimeMs &&\n before.ctimeMs === after.ctimeMs\n );\n}\n\nexport async function hashInspectionEntry(\n path: string,\n maxBytes = Number.MAX_SAFE_INTEGER,\n): Promise<InspectionHash> {\n const before = await lstat(path);\n if (!(await isDirectInspectionPath(dirname(path))))\n throw new Error(\"Hash path traverses a symlink\");\n const hash = createHash(\"sha256\");\n if (before.isSymbolicLink()) {\n const target = await readlink(path, { encoding: \"buffer\" });\n if (target.length > maxBytes)\n throw new OutpostError(\"workspace\", \"Checksum byte limit exceeded\", {\n reason: \"CHECKSUM_LIMIT\",\n });\n if (!unchanged(before, await lstat(path)))\n throw new Error(\"Hash source changed\");\n return {\n kind: \"symlink\",\n bytes: target.length,\n sha256: hash.update(target).digest(\"hex\"),\n };\n }\n if (!before.isFile()) throw new Error(\"Hash source is not a regular file\");\n if (before.size > maxBytes)\n throw new OutpostError(\"workspace\", \"Checksum byte limit exceeded\", {\n reason: \"CHECKSUM_LIMIT\",\n });\n const file = await open(path, inspectionFileFlags);\n try {\n const opened = await file.stat();\n if (!opened.isFile() || !unchanged(before, opened))\n throw new Error(\"Hash source changed\");\n const buffer = Buffer.alloc(inspectionHashChunkBytes);\n let bytes = 0;\n while (bytes < opened.size) {\n const result = await file.read(\n buffer,\n 0,\n Math.min(buffer.length, opened.size - bytes),\n bytes,\n );\n if (!result.bytesRead) throw new Error(\"Hash source truncated\");\n bytes += result.bytesRead;\n hash.update(buffer.subarray(0, result.bytesRead));\n }\n if (\n !unchanged(opened, await file.stat()) ||\n !unchanged(opened, await lstat(path))\n )\n throw new Error(\"Hash source changed\");\n return { kind: \"file\", bytes, sha256: hash.digest(\"hex\") };\n } finally {\n await file.close();\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function isDirectInspectionPath(path: string): Promise<boolean>;
@@ -0,0 +1,18 @@
1
+ import { lstat, realpath } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ export async function isDirectInspectionPath(path) {
4
+ let current = resolve(path);
5
+ if ((await realpath(current)) === current)
6
+ return true;
7
+ if (process.platform !== "win32")
8
+ return false;
9
+ while (true) {
10
+ if ((await lstat(current)).isSymbolicLink())
11
+ return false;
12
+ const parent = dirname(current);
13
+ if (parent === current)
14
+ return true;
15
+ current = parent;
16
+ }
17
+ }
18
+ //# sourceMappingURL=inspection-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspection-path.js","sourceRoot":"","sources":["../../src/infrastructure/inspection-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,IAAY;IACvD,IAAI,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IAC/C,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,EAAE;YAAE,OAAO,KAAK,CAAC;QAC1D,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,IAAI,MAAM,KAAK,OAAO;YAAE,OAAO,IAAI,CAAC;QACpC,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;AACH,CAAC","sourcesContent":["import { lstat, realpath } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\n\nexport async function isDirectInspectionPath(path: string): Promise<boolean> {\n let current = resolve(path);\n if ((await realpath(current)) === current) return true;\n if (process.platform !== \"win32\") return false;\n while (true) {\n if ((await lstat(current)).isSymbolicLink()) return false;\n const parent = dirname(current);\n if (parent === current) return true;\n current = parent;\n }\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { transportJournal } from "./transport-journal.js";
1
2
  import { lock } from "./git/lock.js";
2
3
  import { markJournalClosed } from "./journal-retention.js";
3
4
  import { randomUUID } from "node:crypto";
@@ -5,6 +6,11 @@ import { mkdir, open, rm } from "node:fs/promises";
5
6
  import { dirname, join, resolve } from "node:path";
6
7
  import { reporter } from "./reporter.js";
7
8
  export async function journal(repository, logging = {}, label) {
9
+ if (logging && logging !== "stdout" && logging.transporter) {
10
+ if (logging.file !== undefined)
11
+ throw new Error("Provide a journal file or transporter, not both");
12
+ return transportJournal(logging.transporter, logging.verbose ?? false, label);
13
+ }
8
14
  const file = logging && logging !== "stdout"
9
15
  ? resolve(repository, logging.file ??
10
16
  join(".outpost", "logs", `${label ? label.replace(/[^A-Za-z0-9_-]/g, "-").slice(0, 64) + "-" : ""}${new Date().toISOString().replaceAll(":", "-")}-${randomUUID()}.jsonl`))
@@ -1 +1 @@
1
- {"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/infrastructure/journal.ts"],"names":[],"mappings":"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","sourcesContent":["import { lock } from \"./git/lock.ts\";\nimport { markJournalClosed } from \"./journal-retention.ts\";\nimport { randomUUID } from \"node:crypto\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { mkdir, open, rm } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport type { Journal, Logging } from \"./journal.types.ts\";\nimport { reporter } from \"./reporter.ts\";\n\nexport type { Logging } from \"./journal.types.ts\";\n\nexport async function journal(\n repository: string,\n logging: Logging = {},\n label?: string,\n): Promise<Journal> {\n const file =\n logging && logging !== \"stdout\"\n ? resolve(\n repository,\n logging.file ??\n join(\n \".outpost\",\n \"logs\",\n `${label ? label.replace(/[^A-Za-z0-9_-]/g, \"-\").slice(0, 64) + \"-\" : \"\"}${new Date().toISOString().replaceAll(\":\", \"-\")}-${randomUUID()}.jsonl`,\n ),\n )\n : undefined;\n let handle: FileHandle | undefined,\n pending = Promise.resolve();\n let failure: unknown;\n let release: (() => Promise<void>) | undefined;\n let closing: Promise<void> | undefined;\n const managed =\n !!logging && logging !== \"stdout\" && logging.file === undefined;\n const display = reporter({ ...(label ? { label } : {}) });\n if (file) {\n await mkdir(dirname(file), { recursive: true });\n release = await lock(repository, `journal:${file}`);\n try {\n await rm(`${file}.closed.json`, { force: true });\n handle = await open(file, managed ? \"wx\" : \"a\", 0o600);\n await handle.write(\n JSON.stringify({\n kind: \"dispatch-start\",\n at: new Date().toISOString(),\n label,\n }) + \"\\n\",\n );\n } catch (error) {\n await handle?.close();\n await release();\n throw error;\n }\n }\n return {\n ...(file ? { file } : {}),\n record(event) {\n if (!logging || closing) return;\n if (event.kind === \"raw\" && logging !== \"stdout\" && !logging.verbose)\n return;\n const line =\n JSON.stringify({\n at: new Date().toISOString(),\n ...(label ? { label } : {}),\n ...event,\n }) + \"\\n\";\n if (logging === \"stdout\") display(event);\n else\n pending = pending\n .then(async () => {\n await handle?.write(line);\n })\n .catch((error) => {\n failure = error;\n });\n },\n close() {\n closing ??= (async () => {\n try {\n await pending;\n await handle?.close();\n if (failure) throw failure;\n if (file && managed) await markJournalClosed(file);\n } finally {\n await release?.();\n }\n })();\n return closing;\n },\n };\n}\n"]}
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/infrastructure/journal.ts"],"names":[],"mappings":"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","sourcesContent":["import { transportJournal } from \"./transport-journal.ts\";\nimport { lock } from \"./git/lock.ts\";\nimport { markJournalClosed } from \"./journal-retention.ts\";\nimport { randomUUID } from \"node:crypto\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { mkdir, open, rm } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport type { Journal, Logging } from \"./journal.types.ts\";\nimport { reporter } from \"./reporter.ts\";\n\nexport type { Logging } from \"./journal.types.ts\";\n\nexport async function journal(\n repository: string,\n logging: Logging = {},\n label?: string,\n): Promise<Journal> {\n if (logging && logging !== \"stdout\" && logging.transporter) {\n if (logging.file !== undefined)\n throw new Error(\"Provide a journal file or transporter, not both\");\n return transportJournal(\n logging.transporter,\n logging.verbose ?? false,\n label,\n );\n }\n const file =\n logging && logging !== \"stdout\"\n ? resolve(\n repository,\n logging.file ??\n join(\n \".outpost\",\n \"logs\",\n `${label ? label.replace(/[^A-Za-z0-9_-]/g, \"-\").slice(0, 64) + \"-\" : \"\"}${new Date().toISOString().replaceAll(\":\", \"-\")}-${randomUUID()}.jsonl`,\n ),\n )\n : undefined;\n let handle: FileHandle | undefined,\n pending = Promise.resolve();\n let failure: unknown;\n let release: (() => Promise<void>) | undefined;\n let closing: Promise<void> | undefined;\n const managed =\n !!logging && logging !== \"stdout\" && logging.file === undefined;\n const display = reporter({ ...(label ? { label } : {}) });\n if (file) {\n await mkdir(dirname(file), { recursive: true });\n release = await lock(repository, `journal:${file}`);\n try {\n await rm(`${file}.closed.json`, { force: true });\n handle = await open(file, managed ? \"wx\" : \"a\", 0o600);\n await handle.write(\n JSON.stringify({\n kind: \"dispatch-start\",\n at: new Date().toISOString(),\n label,\n }) + \"\\n\",\n );\n } catch (error) {\n await handle?.close();\n await release();\n throw error;\n }\n }\n return {\n ...(file ? { file } : {}),\n record(event) {\n if (!logging || closing) return;\n if (event.kind === \"raw\" && logging !== \"stdout\" && !logging.verbose)\n return;\n const line =\n JSON.stringify({\n at: new Date().toISOString(),\n ...(label ? { label } : {}),\n ...event,\n }) + \"\\n\";\n if (logging === \"stdout\") display(event);\n else\n pending = pending\n .then(async () => {\n await handle?.write(line);\n })\n .catch((error) => {\n failure = error;\n });\n },\n close() {\n closing ??= (async () => {\n try {\n await pending;\n await handle?.close();\n if (failure) throw failure;\n if (file && managed) await markJournalClosed(file);\n } finally {\n await release?.();\n }\n })();\n return closing;\n },\n };\n}\n"]}
@@ -1,10 +1,13 @@
1
+ import type { Transport, TransportReference } from "../domain/transport.types.ts";
1
2
  import type { AgentEvent } from "../domain/agent.types.ts";
2
3
  export type Logging = false | "stdout" | {
3
4
  readonly file?: string;
5
+ readonly transporter?: Transport;
4
6
  readonly verbose?: boolean;
5
7
  };
6
8
  export type Journal = {
7
9
  file?: string;
10
+ reference?: TransportReference;
8
11
  record(event: AgentEvent): void;
9
12
  close(): Promise<void>;
10
13
  };
@@ -1 +1 @@
1
- {"version":3,"file":"journal.types.js","sourceRoot":"","sources":["../../src/infrastructure/journal.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { AgentEvent } from \"../domain/agent.types.ts\";\n\nexport type Logging =\n false | \"stdout\" | { readonly file?: string; readonly verbose?: boolean };\n\nexport type Journal = {\n file?: string;\n record(event: AgentEvent): void;\n close(): Promise<void>;\n};\n"]}
1
+ {"version":3,"file":"journal.types.js","sourceRoot":"","sources":["../../src/infrastructure/journal.types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n Transport,\n TransportReference,\n} from \"../domain/transport.types.ts\";\nimport type { AgentEvent } from \"../domain/agent.types.ts\";\n\nexport type Logging =\n | false\n | \"stdout\"\n | {\n readonly file?: string;\n readonly transporter?: Transport;\n readonly verbose?: boolean;\n };\n\nexport type Journal = {\n file?: string;\n reference?: TransportReference;\n record(event: AgentEvent): void;\n close(): Promise<void>;\n};\n"]}
@@ -0,0 +1,3 @@
1
+ import type { Transport } from "../domain/transport.types.ts";
2
+ import type { LocalTransportOptions } from "./local-transport.types.ts";
3
+ export declare function localTransport(options: LocalTransportOptions): Transport;
@@ -0,0 +1,146 @@
1
+ import { mkdir, lstat, open, rename, rm, readdir } from "node:fs/promises";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { setTimeout } from "node:timers/promises";
5
+ import { TransportConflict, transportKey, transportCondition, } from "../domain/transport.js";
6
+ import { lock } from "./git/lock.js";
7
+ import { OutpostError } from "../domain/errors.js";
8
+ import { readInspectionFile } from "./inspection-file.js";
9
+ import { decodeObject, encodeObject, readLimit } from "./transport-envelope.js";
10
+ import { transportDefaults } from "./transport.constants.js";
11
+ async function privateDirectory(path) {
12
+ const parent = dirname(path);
13
+ if (parent !== path) {
14
+ await privateDirectory(parent);
15
+ await mkdir(path, { recursive: true, mode: 0o700 });
16
+ }
17
+ if (!(await lstat(path)).isDirectory())
18
+ throw new Error("Transport directories must not be symlinks");
19
+ }
20
+ export function localTransport(options) {
21
+ const root = resolve(options.directory), objects = join(root, "objects");
22
+ const path = (key) => join(objects, `${transportKey(key)}.object`);
23
+ const read = async (key, options = {}) => {
24
+ options.signal?.throwIfAborted();
25
+ const limit = readLimit(options.maxBytes);
26
+ try {
27
+ const target = path(key);
28
+ const info = await lstat(target);
29
+ const data = await readInspectionFile(target, Math.min(info.size, limit + transportDefaults.headerBytes) || 1);
30
+ options.signal?.throwIfAborted();
31
+ return decodeObject(data, key, limit);
32
+ }
33
+ catch (error) {
34
+ if (error &&
35
+ typeof error === "object" &&
36
+ "code" in error &&
37
+ error.code === "ENOENT")
38
+ return undefined;
39
+ throw error;
40
+ }
41
+ };
42
+ async function mutate(key, options, action) {
43
+ transportKey(key);
44
+ transportCondition(options.ifRevision);
45
+ await privateDirectory(root);
46
+ const signal = AbortSignal.any([
47
+ AbortSignal.timeout(transportDefaults.lockMs),
48
+ ...(options.signal ? [options.signal] : []),
49
+ ]);
50
+ let release;
51
+ while (!release) {
52
+ signal.throwIfAborted();
53
+ try {
54
+ release = await lock(root, key);
55
+ }
56
+ catch (error) {
57
+ if (!(error instanceof OutpostError) || error.code !== "conflict")
58
+ throw error;
59
+ await setTimeout(transportDefaults.retryMs, undefined, { signal });
60
+ }
61
+ }
62
+ try {
63
+ if ((await read(key))?.revision !== (options.ifRevision ?? undefined))
64
+ throw new TransportConflict(key);
65
+ options.signal?.throwIfAborted();
66
+ return await action();
67
+ }
68
+ finally {
69
+ await release();
70
+ }
71
+ }
72
+ return {
73
+ name: "local",
74
+ read,
75
+ write(key, input, options) {
76
+ const bytes = Buffer.from(input);
77
+ return mutate(key, options, async () => {
78
+ const target = path(key);
79
+ await privateDirectory(dirname(target));
80
+ const { entry, data } = encodeObject(key, bytes);
81
+ const temporary = `${target}.${randomUUID()}.tmp`;
82
+ try {
83
+ const file = await open(temporary, "wx", 0o600);
84
+ try {
85
+ await file.writeFile(data, { signal: options.signal });
86
+ await file.sync();
87
+ }
88
+ finally {
89
+ await file.close();
90
+ }
91
+ options.signal?.throwIfAborted();
92
+ await rename(temporary, target);
93
+ if (process.platform !== "win32") {
94
+ const parent = await open(dirname(target), "r");
95
+ try {
96
+ await parent.sync();
97
+ }
98
+ finally {
99
+ await parent.close();
100
+ }
101
+ }
102
+ return entry;
103
+ }
104
+ finally {
105
+ await rm(temporary, { force: true });
106
+ }
107
+ });
108
+ },
109
+ remove(key, options) {
110
+ if (options.ifRevision === null)
111
+ return Promise.reject(new Error("Transport removal requires an existing revision"));
112
+ return mutate(key, options, () => rm(path(key), { force: true }));
113
+ },
114
+ async *list(prefix = "", options = {}) {
115
+ if (prefix)
116
+ transportKey(prefix.replace(/\/$/, ""));
117
+ async function* scan(directory, base) {
118
+ const entries = await readdir(directory, { withFileTypes: true }).catch((error) => {
119
+ if (error.code === "ENOENT")
120
+ return [];
121
+ throw error;
122
+ });
123
+ for (const entry of entries) {
124
+ options.signal?.throwIfAborted();
125
+ const name = `${base}${entry.name}`;
126
+ if (entry.isDirectory())
127
+ yield* scan(join(directory, entry.name), `${name}/`);
128
+ if (entry.isFile() && name.endsWith(".object"))
129
+ yield name.slice(0, -7);
130
+ if (entry.isSymbolicLink())
131
+ throw new Error("Transport objects must not be symlinks");
132
+ }
133
+ }
134
+ for await (const key of scan(objects, "")) {
135
+ if (!key.startsWith(prefix))
136
+ continue;
137
+ const object = await read(key, options.signal ? { signal: options.signal } : {});
138
+ if (object) {
139
+ const { bytes: _bytes, ...entry } = object;
140
+ yield entry;
141
+ }
142
+ }
143
+ },
144
+ };
145
+ }
146
+ //# sourceMappingURL=local-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-transport.js","sourceRoot":"","sources":["../../src/infrastructure/local-transport.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, lstat, open, rename, rm, readdir } from \"node:fs/promises\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { setTimeout } from \"node:timers/promises\";\nimport {\n TransportConflict,\n transportKey,\n transportCondition,\n} from \"../domain/transport.ts\";\nimport type {\n Transport,\n TransportWriteOptions,\n} from \"../domain/transport.types.ts\";\nimport type { LocalTransportOptions } from \"./local-transport.types.ts\";\nimport { lock } from \"./git/lock.ts\";\nimport { OutpostError } from \"../domain/errors.ts\";\nimport { readInspectionFile } from \"./inspection-file.ts\";\nimport { decodeObject, encodeObject, readLimit } from \"./transport-envelope.ts\";\nimport { transportDefaults } from \"./transport.constants.ts\";\n\nasync function privateDirectory(path: string): Promise<void> {\n const parent = dirname(path);\n if (parent !== path) {\n await privateDirectory(parent);\n await mkdir(path, { recursive: true, mode: 0o700 });\n }\n if (!(await lstat(path)).isDirectory())\n throw new Error(\"Transport directories must not be symlinks\");\n}\n\nexport function localTransport(options: LocalTransportOptions): Transport {\n const root = resolve(options.directory),\n objects = join(root, \"objects\");\n const path = (key: string) => join(objects, `${transportKey(key)}.object`);\n const read: Transport[\"read\"] = async (key, options = {}) => {\n options.signal?.throwIfAborted();\n const limit = readLimit(options.maxBytes);\n try {\n const target = path(key);\n const info = await lstat(target);\n const data = await readInspectionFile(\n target,\n Math.min(info.size, limit + transportDefaults.headerBytes) || 1,\n );\n options.signal?.throwIfAborted();\n return decodeObject(data, key, limit);\n } catch (error) {\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n )\n return undefined;\n throw error;\n }\n };\n async function mutate<T>(\n key: string,\n options: TransportWriteOptions,\n action: () => Promise<T>,\n ): Promise<T> {\n transportKey(key);\n transportCondition(options.ifRevision);\n await privateDirectory(root);\n const signal = AbortSignal.any([\n AbortSignal.timeout(transportDefaults.lockMs),\n ...(options.signal ? [options.signal] : []),\n ]);\n let release: (() => Promise<void>) | undefined;\n while (!release) {\n signal.throwIfAborted();\n try {\n release = await lock(root, key);\n } catch (error) {\n if (!(error instanceof OutpostError) || error.code !== \"conflict\")\n throw error;\n await setTimeout(transportDefaults.retryMs, undefined, { signal });\n }\n }\n try {\n if ((await read(key))?.revision !== (options.ifRevision ?? undefined))\n throw new TransportConflict(key);\n options.signal?.throwIfAborted();\n return await action();\n } finally {\n await release();\n }\n }\n return {\n name: \"local\",\n read,\n write(key, input, options) {\n const bytes = Buffer.from(input);\n return mutate(key, options, async () => {\n const target = path(key);\n await privateDirectory(dirname(target));\n const { entry, data } = encodeObject(key, bytes);\n const temporary = `${target}.${randomUUID()}.tmp`;\n try {\n const file = await open(temporary, \"wx\", 0o600);\n try {\n await file.writeFile(data, { signal: options.signal });\n await file.sync();\n } finally {\n await file.close();\n }\n options.signal?.throwIfAborted();\n await rename(temporary, target);\n if (process.platform !== \"win32\") {\n const parent = await open(dirname(target), \"r\");\n try {\n await parent.sync();\n } finally {\n await parent.close();\n }\n }\n return entry;\n } finally {\n await rm(temporary, { force: true });\n }\n });\n },\n remove(key, options) {\n if (options.ifRevision === null)\n return Promise.reject(\n new Error(\"Transport removal requires an existing revision\"),\n );\n return mutate(key, options, () => rm(path(key), { force: true }));\n },\n async *list(prefix = \"\", options = {}) {\n if (prefix) transportKey(prefix.replace(/\\/$/, \"\"));\n async function* scan(\n directory: string,\n base: string,\n ): AsyncIterable<string> {\n const entries = await readdir(directory, { withFileTypes: true }).catch(\n (error) => {\n if (error.code === \"ENOENT\") return [];\n throw error;\n },\n );\n for (const entry of entries) {\n options.signal?.throwIfAborted();\n const name = `${base}${entry.name}`;\n if (entry.isDirectory())\n yield* scan(join(directory, entry.name), `${name}/`);\n if (entry.isFile() && name.endsWith(\".object\"))\n yield name.slice(0, -7);\n if (entry.isSymbolicLink())\n throw new Error(\"Transport objects must not be symlinks\");\n }\n }\n for await (const key of scan(objects, \"\")) {\n if (!key.startsWith(prefix)) continue;\n const object = await read(\n key,\n options.signal ? { signal: options.signal } : {},\n );\n if (object) {\n const { bytes: _bytes, ...entry } = object;\n yield entry;\n }\n }\n },\n };\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export interface LocalTransportOptions {
2
+ readonly directory: string;
3
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=local-transport.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-transport.types.js","sourceRoot":"","sources":["../../src/infrastructure/local-transport.types.ts"],"names":[],"mappings":"","sourcesContent":["export interface LocalTransportOptions {\n readonly directory: string;\n}\n"]}
@@ -1,2 +1,3 @@
1
- import type { ResourceInspection } from "./resource-activity.types.ts";
1
+ import type { ResourceActivityRecord, ResourceInspection } from "./resource-activity.types.ts";
2
+ export declare function resourceRecord(value: unknown): ResourceActivityRecord;
2
3
  export declare function inspectResourceActivity(repository: string, maxEntries?: number): Promise<ResourceInspection>;
@@ -54,7 +54,7 @@ function identity(value) {
54
54
  started: value.started,
55
55
  };
56
56
  }
57
- function record(value) {
57
+ export function resourceRecord(value) {
58
58
  object(value);
59
59
  invariant(value.version === 1, "Unknown activity version");
60
60
  text(value.id);
@@ -114,7 +114,7 @@ export async function inspectResourceActivity(repository, maxEntries = defaults.
114
114
  break;
115
115
  }
116
116
  try {
117
- const value = record(JSON.parse((await readInspectionFile(file, defaults.maxRecordBytes)).toString("utf8")));
117
+ const value = resourceRecord(JSON.parse((await readInspectionFile(file, defaults.maxRecordBytes)).toString("utf8")));
118
118
  invariant(entry.name === `${value.id}.json`, "Activity identity mismatch");
119
119
  entries.push({
120
120
  path: file,
@@ -1 +1 @@
1
- {"version":3,"file":"resource-activity-inspection.js","sourceRoot":"","sources":["../../src/infrastructure/resource-activity-inspection.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, opendir, realpath } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { invariant, positive } from \"../domain/errors.ts\";\nimport { observeOwnership } from \"./git/process-identity.ts\";\nimport type { LocalProcessIdentity } from \"./git/process-identity.types.ts\";\nimport { readInspectionFile } from \"./inspection-file.ts\";\nimport {\n resourceActivityDefaults as defaults,\n resourceOperationKinds,\n resourcePhases,\n} from \"./resource-activity.constants.ts\";\nimport type {\n ResourceActivityRecord,\n ResourceInspection,\n ResourceInspectionEntry,\n ResourceOperation,\n ResourceOperationResult,\n} from \"./resource-activity.types.ts\";\nimport type { StorageIssue } from \"./storage-inventory.types.ts\";\n\nfunction object(value: unknown): asserts value is Record<string, unknown> {\n invariant(\n !!value && typeof value === \"object\" && !Array.isArray(value),\n \"Invalid activity object\",\n );\n}\nfunction text(value: unknown): asserts value is string {\n invariant(\n typeof value === \"string\" &&\n value.length > 0 &&\n value.length <= defaults.maxText,\n \"Invalid activity text\",\n );\n}\nfunction date(value: unknown): asserts value is string {\n text(value);\n invariant(Number.isFinite(Date.parse(value)), \"Invalid activity time\");\n}\nfunction operation(value: unknown): ResourceOperation {\n object(value);\n invariant(\n typeof value.count === \"number\" &&\n Number.isSafeInteger(value.count) &&\n value.count > 0,\n \"Invalid activity count\",\n );\n date(value.startedAt);\n const kind = resourceOperationKinds.find((kind) => kind === value.kind);\n invariant(kind, \"Invalid activity operation\");\n return { count: value.count, startedAt: value.startedAt, kind };\n}\nfunction result(value: unknown): ResourceOperationResult {\n const base = operation(value);\n object(value);\n text(value.id);\n date(value.finishedAt);\n invariant(\n value.outcome === \"completed\" || value.outcome === \"failed\",\n \"Invalid activity outcome\",\n );\n return {\n ...base,\n id: value.id,\n finishedAt: value.finishedAt,\n outcome: value.outcome,\n };\n}\nfunction identity(value: unknown): LocalProcessIdentity | undefined {\n if (value === undefined) return undefined;\n object(value);\n text(value.host);\n text(value.boot);\n text(value.namespace);\n text(value.started);\n return {\n host: value.host,\n boot: value.boot,\n namespace: value.namespace,\n started: value.started,\n };\n}\nfunction record(value: unknown): ResourceActivityRecord {\n object(value);\n invariant(value.version === 1, \"Unknown activity version\");\n text(value.id);\n text(value.provider);\n text(value.workspace);\n date(value.createdAt);\n date(value.updatedAt);\n invariant(\n typeof value.pid === \"number\" &&\n Number.isSafeInteger(value.pid) &&\n value.pid > 0 &&\n value.pid <= 2_147_483_647,\n \"Invalid activity owner\",\n );\n invariant(\n value.placement === \"host\" ||\n value.placement === \"mounted\" ||\n value.placement === \"remote\",\n \"Invalid activity placement\",\n );\n const phase = resourcePhases.find((phase) => phase === value.phase);\n invariant(phase, \"Invalid activity phase\");\n invariant(\n Array.isArray(value.operations) &&\n value.operations.length <= resourceOperationKinds.length,\n \"Invalid activity operations\",\n );\n const owner = identity(value.identity);\n return {\n version: 1,\n id: value.id,\n pid: value.pid,\n ...(owner ? { identity: owner } : {}),\n provider: value.provider,\n placement: value.placement,\n workspace: value.workspace,\n createdAt: value.createdAt,\n updatedAt: value.updatedAt,\n phase,\n operations: value.operations.map(operation),\n ...(value.lastOperation === undefined\n ? {}\n : { lastOperation: result(value.lastOperation) }),\n ...(value.lastFailure === undefined\n ? {}\n : { lastFailure: result(value.lastFailure) }),\n };\n}\n\nexport async function inspectResourceActivity(\n repository: string,\n maxEntries: number = defaults.maxRecords,\n): Promise<ResourceInspection> {\n positive(maxEntries, \"maxEntries\");\n const entries: ResourceInspectionEntry[] = [];\n const issues: StorageIssue[] = [];\n let path = repository;\n try {\n for (const part of [\".outpost\", \"locks\", defaults.directory]) {\n path = join(path, part);\n const info = await lstat(path);\n invariant(\n info.isDirectory() &&\n !info.isSymbolicLink() &&\n (await realpath(path)) === path,\n \"Unsafe activity directory\",\n );\n }\n for await (const entry of await opendir(path)) {\n const file = join(path, entry.name);\n if (entries.length >= Math.min(maxEntries, defaults.maxRecords)) {\n issues.push({ path, code: \"RESOURCE_ENTRY_LIMIT\" });\n break;\n }\n try {\n const value = record(\n JSON.parse(\n (await readInspectionFile(file, defaults.maxRecordBytes)).toString(\n \"utf8\",\n ),\n ),\n );\n invariant(\n entry.name === `${value.id}.json`,\n \"Activity identity mismatch\",\n );\n entries.push({\n path: file,\n record: value,\n ownership: await observeOwnership(value.pid, value.identity),\n });\n } catch {\n entries.push({\n path: file,\n ownership: {\n status: \"unknown\",\n reason: \"RESOURCE_RECORD_UNREADABLE\",\n },\n });\n issues.push({ path: file, code: \"RESOURCE_RECORD_UNREADABLE\" });\n }\n }\n } catch (cause) {\n if (!(\n cause &&\n typeof cause === \"object\" &&\n \"code\" in cause &&\n cause.code === \"ENOENT\"\n ))\n issues.push({ path, code: \"RESOURCE_DIRECTORY_UNAVAILABLE\" });\n }\n entries.sort((left, right) => left.path.localeCompare(right.path));\n return {\n scope: \"recorded-sandboxes\",\n complete: issues.length === 0,\n entries,\n issues,\n };\n}\n"]}
1
+ {"version":3,"file":"resource-activity-inspection.js","sourceRoot":"","sources":["../../src/infrastructure/resource-activity-inspection.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstat, opendir, realpath } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { invariant, positive } from \"../domain/errors.ts\";\nimport { observeOwnership } from \"./git/process-identity.ts\";\nimport type { LocalProcessIdentity } from \"./git/process-identity.types.ts\";\nimport { readInspectionFile } from \"./inspection-file.ts\";\nimport {\n resourceActivityDefaults as defaults,\n resourceOperationKinds,\n resourcePhases,\n} from \"./resource-activity.constants.ts\";\nimport type {\n ResourceActivityRecord,\n ResourceInspection,\n ResourceInspectionEntry,\n ResourceOperation,\n ResourceOperationResult,\n} from \"./resource-activity.types.ts\";\nimport type { StorageIssue } from \"./storage-inventory.types.ts\";\n\nfunction object(value: unknown): asserts value is Record<string, unknown> {\n invariant(\n !!value && typeof value === \"object\" && !Array.isArray(value),\n \"Invalid activity object\",\n );\n}\nfunction text(value: unknown): asserts value is string {\n invariant(\n typeof value === \"string\" &&\n value.length > 0 &&\n value.length <= defaults.maxText,\n \"Invalid activity text\",\n );\n}\nfunction date(value: unknown): asserts value is string {\n text(value);\n invariant(Number.isFinite(Date.parse(value)), \"Invalid activity time\");\n}\nfunction operation(value: unknown): ResourceOperation {\n object(value);\n invariant(\n typeof value.count === \"number\" &&\n Number.isSafeInteger(value.count) &&\n value.count > 0,\n \"Invalid activity count\",\n );\n date(value.startedAt);\n const kind = resourceOperationKinds.find((kind) => kind === value.kind);\n invariant(kind, \"Invalid activity operation\");\n return { count: value.count, startedAt: value.startedAt, kind };\n}\nfunction result(value: unknown): ResourceOperationResult {\n const base = operation(value);\n object(value);\n text(value.id);\n date(value.finishedAt);\n invariant(\n value.outcome === \"completed\" || value.outcome === \"failed\",\n \"Invalid activity outcome\",\n );\n return {\n ...base,\n id: value.id,\n finishedAt: value.finishedAt,\n outcome: value.outcome,\n };\n}\nfunction identity(value: unknown): LocalProcessIdentity | undefined {\n if (value === undefined) return undefined;\n object(value);\n text(value.host);\n text(value.boot);\n text(value.namespace);\n text(value.started);\n return {\n host: value.host,\n boot: value.boot,\n namespace: value.namespace,\n started: value.started,\n };\n}\nexport function resourceRecord(value: unknown): ResourceActivityRecord {\n object(value);\n invariant(value.version === 1, \"Unknown activity version\");\n text(value.id);\n text(value.provider);\n text(value.workspace);\n date(value.createdAt);\n date(value.updatedAt);\n invariant(\n typeof value.pid === \"number\" &&\n Number.isSafeInteger(value.pid) &&\n value.pid > 0 &&\n value.pid <= 2_147_483_647,\n \"Invalid activity owner\",\n );\n invariant(\n value.placement === \"host\" ||\n value.placement === \"mounted\" ||\n value.placement === \"remote\",\n \"Invalid activity placement\",\n );\n const phase = resourcePhases.find((phase) => phase === value.phase);\n invariant(phase, \"Invalid activity phase\");\n invariant(\n Array.isArray(value.operations) &&\n value.operations.length <= resourceOperationKinds.length,\n \"Invalid activity operations\",\n );\n const owner = identity(value.identity);\n return {\n version: 1,\n id: value.id,\n pid: value.pid,\n ...(owner ? { identity: owner } : {}),\n provider: value.provider,\n placement: value.placement,\n workspace: value.workspace,\n createdAt: value.createdAt,\n updatedAt: value.updatedAt,\n phase,\n operations: value.operations.map(operation),\n ...(value.lastOperation === undefined\n ? {}\n : { lastOperation: result(value.lastOperation) }),\n ...(value.lastFailure === undefined\n ? {}\n : { lastFailure: result(value.lastFailure) }),\n };\n}\n\nexport async function inspectResourceActivity(\n repository: string,\n maxEntries: number = defaults.maxRecords,\n): Promise<ResourceInspection> {\n positive(maxEntries, \"maxEntries\");\n const entries: ResourceInspectionEntry[] = [];\n const issues: StorageIssue[] = [];\n let path = repository;\n try {\n for (const part of [\".outpost\", \"locks\", defaults.directory]) {\n path = join(path, part);\n const info = await lstat(path);\n invariant(\n info.isDirectory() &&\n !info.isSymbolicLink() &&\n (await realpath(path)) === path,\n \"Unsafe activity directory\",\n );\n }\n for await (const entry of await opendir(path)) {\n const file = join(path, entry.name);\n if (entries.length >= Math.min(maxEntries, defaults.maxRecords)) {\n issues.push({ path, code: \"RESOURCE_ENTRY_LIMIT\" });\n break;\n }\n try {\n const value = resourceRecord(\n JSON.parse(\n (await readInspectionFile(file, defaults.maxRecordBytes)).toString(\n \"utf8\",\n ),\n ),\n );\n invariant(\n entry.name === `${value.id}.json`,\n \"Activity identity mismatch\",\n );\n entries.push({\n path: file,\n record: value,\n ownership: await observeOwnership(value.pid, value.identity),\n });\n } catch {\n entries.push({\n path: file,\n ownership: {\n status: \"unknown\",\n reason: \"RESOURCE_RECORD_UNREADABLE\",\n },\n });\n issues.push({ path: file, code: \"RESOURCE_RECORD_UNREADABLE\" });\n }\n }\n } catch (cause) {\n if (!(\n cause &&\n typeof cause === \"object\" &&\n \"code\" in cause &&\n cause.code === \"ENOENT\"\n ))\n issues.push({ path, code: \"RESOURCE_DIRECTORY_UNAVAILABLE\" });\n }\n entries.sort((left, right) => left.path.localeCompare(right.path));\n return {\n scope: \"recorded-sandboxes\",\n complete: issues.length === 0,\n entries,\n issues,\n };\n}\n"]}
@@ -1,3 +1,5 @@
1
+ import { TransportConflict } from "../domain/transport.js";
2
+ import { jsonBytes } from "./transport-json.js";
1
3
  import { randomUUID } from "node:crypto";
2
4
  import { mkdir, open, realpath, rename, rm, opendir } from "node:fs/promises";
3
5
  import { join } from "node:path";
@@ -18,9 +20,11 @@ async function activityDirectory(repository) {
18
20
  export async function registerResourceActivity(options) {
19
21
  for (const text of [options.workspace, options.provider])
20
22
  invariant(text.length > 0 && text.length <= defaults.maxText, "Resource activity metadata exceeds its size limit");
21
- const directory = await activityDirectory(options.repository);
23
+ const remote = options.transporter;
24
+ const directory = remote ? "" : await activityDirectory(options.repository);
25
+ let revision;
22
26
  const id = randomUUID();
23
- const path = join(directory, `${id}.json`);
27
+ const path = remote ? `resources/${id}.json` : join(directory, `${id}.json`);
24
28
  const createdAt = new Date().toISOString();
25
29
  const identity = await localProcessIdentity();
26
30
  let record = {
@@ -36,18 +40,25 @@ export async function registerResourceActivity(options) {
36
40
  phase: "allocating",
37
41
  operations: [],
38
42
  };
39
- const unlock = await lockStorageMutation(options.repository);
43
+ const unlock = remote
44
+ ? async () => { }
45
+ : await lockStorageMutation(options.repository);
40
46
  try {
41
- let count = 0;
42
- for await (const _entry of await opendir(directory))
43
- invariant(++count < defaults.maxRecords, "Too many resource activity records; inspect retained ownership before continuing");
44
- const file = await open(path, "wx", 0o600);
45
- try {
46
- await file.writeFile(JSON.stringify(record));
47
- await file.sync();
47
+ if (remote) {
48
+ revision = (await remote.write(path, jsonBytes(record), { ifRevision: null })).revision;
48
49
  }
49
- finally {
50
- await file.close();
50
+ if (!remote) {
51
+ let count = 0;
52
+ for await (const _entry of await opendir(directory))
53
+ invariant(++count < defaults.maxRecords, "Too many resource activity records; inspect retained ownership before continuing");
54
+ const file = await open(path, "wx", 0o600);
55
+ try {
56
+ await file.writeFile(JSON.stringify(record));
57
+ await file.sync();
58
+ }
59
+ finally {
60
+ await file.close();
61
+ }
51
62
  }
52
63
  }
53
64
  finally {
@@ -61,7 +72,13 @@ export async function registerResourceActivity(options) {
61
72
  return next;
62
73
  }
63
74
  async function verify() {
64
- invariant((await realpath(directory)) === directory, "Resource activity directory changed");
75
+ invariant(!removed, "Resource activity is closed");
76
+ if (remote) {
77
+ if ((await remote.read(path))?.revision !== revision)
78
+ throw new TransportConflict(path);
79
+ return;
80
+ }
81
+ invariant(remote || (await realpath(directory)) === directory, "Resource activity directory changed");
65
82
  invariant(!removed, "Resource activity is closed");
66
83
  const current = (await readInspectionFile(path, defaults.maxRecordBytes)).toString("utf8");
67
84
  invariant(current === JSON.stringify(record), "Resource activity identity changed; refusing mutation");
@@ -72,6 +89,12 @@ export async function registerResourceActivity(options) {
72
89
  const value = { ...next(), updatedAt: new Date().toISOString() };
73
90
  const data = JSON.stringify(value);
74
91
  invariant(Buffer.byteLength(data) <= defaults.maxRecordBytes, "Resource activity record exceeds its size limit");
92
+ if (remote) {
93
+ invariant(revision, "Resource revision unavailable");
94
+ revision = (await remote.write(path, jsonBytes(value), { ifRevision: revision })).revision;
95
+ record = value;
96
+ return;
97
+ }
75
98
  const temporary = `${path}.${randomUUID()}.tmp`;
76
99
  const file = await open(temporary, "wx", 0o600);
77
100
  try {
@@ -150,7 +173,12 @@ export async function registerResourceActivity(options) {
150
173
  invariant(record.operations.length === 0, "Resource operations remain unsettled; activity record retained");
151
174
  try {
152
175
  await verify();
153
- await rm(path);
176
+ if (remote) {
177
+ invariant(revision, "Resource revision unavailable");
178
+ await remote.remove(path, { ifRevision: revision });
179
+ }
180
+ else
181
+ await rm(path);
154
182
  }
155
183
  catch (cause) {
156
184
  if (!(cause &&