@elie-laloum/outpost 4.1.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 (189) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +9 -5
  3. package/SECURITY.md +2 -0
  4. package/dist/adapters/models/openai-compatible.constants.d.ts +4 -0
  5. package/dist/adapters/models/openai-compatible.constants.js +10 -0
  6. package/dist/adapters/models/openai-compatible.constants.js.map +1 -0
  7. package/dist/adapters/models/openai-compatible.d.ts +3 -0
  8. package/dist/adapters/models/openai-compatible.js +78 -0
  9. package/dist/adapters/models/openai-compatible.js.map +1 -0
  10. package/dist/adapters/models/openai-compatible.types.d.ts +14 -0
  11. package/dist/adapters/models/openai-compatible.types.js +2 -0
  12. package/dist/adapters/models/openai-compatible.types.js.map +1 -0
  13. package/dist/adapters/models/openai-http.d.ts +1 -0
  14. package/dist/adapters/models/openai-http.js +30 -0
  15. package/dist/adapters/models/openai-http.js.map +1 -0
  16. package/dist/adapters/models/openai-request.d.ts +4 -0
  17. package/dist/adapters/models/openai-request.js +46 -0
  18. package/dist/adapters/models/openai-request.js.map +1 -0
  19. package/dist/adapters/models/openai-response.d.ts +3 -0
  20. package/dist/adapters/models/openai-response.js +65 -0
  21. package/dist/adapters/models/openai-response.js.map +1 -0
  22. package/dist/application/execution.types.d.ts +2 -0
  23. package/dist/application/execution.types.js.map +1 -1
  24. package/dist/application/outpost.types.d.ts +5 -0
  25. package/dist/application/outpost.types.js.map +1 -1
  26. package/dist/application/recovery-archive.d.ts +4 -0
  27. package/dist/application/recovery-archive.js +29 -0
  28. package/dist/application/recovery-archive.js.map +1 -0
  29. package/dist/application/recovery-archive.types.d.ts +10 -0
  30. package/dist/application/recovery-archive.types.js +2 -0
  31. package/dist/application/recovery-archive.types.js.map +1 -0
  32. package/dist/application/recovery-inspection.js +3 -0
  33. package/dist/application/recovery-inspection.js.map +1 -1
  34. package/dist/application/recovery-inspection.types.d.ts +2 -0
  35. package/dist/application/recovery-inspection.types.js.map +1 -1
  36. package/dist/application/recovery-retention.d.ts +2 -1
  37. package/dist/application/recovery-retention.js +9 -1
  38. package/dist/application/recovery-retention.js.map +1 -1
  39. package/dist/application/recovery-retention.types.d.ts +6 -0
  40. package/dist/application/recovery-retention.types.js.map +1 -1
  41. package/dist/application/remote-backup.js +8 -0
  42. package/dist/application/remote-backup.js.map +1 -1
  43. package/dist/application/remote-workspace.types.d.ts +2 -0
  44. package/dist/application/remote-workspace.types.js.map +1 -1
  45. package/dist/application/sandbox-agents.js +2 -1
  46. package/dist/application/sandbox-agents.js.map +1 -1
  47. package/dist/application/sandbox-dispatch.js +9 -0
  48. package/dist/application/sandbox-dispatch.js.map +1 -1
  49. package/dist/application/sandbox-provision.js +6 -0
  50. package/dist/application/sandbox-provision.js.map +1 -1
  51. package/dist/application/storage-reservation.js +3 -0
  52. package/dist/application/storage-reservation.js.map +1 -1
  53. package/dist/application/transport-inspection.constants.d.ts +2 -0
  54. package/dist/application/transport-inspection.constants.js +10 -0
  55. package/dist/application/transport-inspection.constants.js.map +1 -0
  56. package/dist/application/transport-inspection.d.ts +2 -0
  57. package/dist/application/transport-inspection.js +103 -0
  58. package/dist/application/transport-inspection.js.map +1 -0
  59. package/dist/application/transport-retention.d.ts +4 -0
  60. package/dist/application/transport-retention.js +153 -0
  61. package/dist/application/transport-retention.js.map +1 -0
  62. package/dist/domain/conversation.types.d.ts +2 -0
  63. package/dist/domain/conversation.types.js.map +1 -1
  64. package/dist/domain/model.types.d.ts +15 -0
  65. package/dist/domain/model.types.js +2 -0
  66. package/dist/domain/model.types.js.map +1 -0
  67. package/dist/domain/transport.constants.d.ts +2 -0
  68. package/dist/domain/transport.constants.js +3 -0
  69. package/dist/domain/transport.constants.js.map +1 -0
  70. package/dist/domain/transport.d.ts +6 -0
  71. package/dist/domain/transport.js +21 -0
  72. package/dist/domain/transport.js.map +1 -0
  73. package/dist/domain/transport.types.d.ts +31 -0
  74. package/dist/domain/transport.types.js +2 -0
  75. package/dist/domain/transport.types.js.map +1 -0
  76. package/dist/index.d.ts +17 -0
  77. package/dist/index.js +8 -0
  78. package/dist/index.js.map +1 -1
  79. package/dist/infrastructure/artifact-store.js +10 -0
  80. package/dist/infrastructure/artifact-store.js.map +1 -1
  81. package/dist/infrastructure/artifact-store.types.d.ts +3 -1
  82. package/dist/infrastructure/artifact-store.types.js.map +1 -1
  83. package/dist/infrastructure/conversations/capture.js +1 -1
  84. package/dist/infrastructure/conversations/capture.js.map +1 -1
  85. package/dist/infrastructure/conversations.d.ts +2 -0
  86. package/dist/infrastructure/conversations.js +2 -0
  87. package/dist/infrastructure/conversations.js.map +1 -1
  88. package/dist/infrastructure/inspection-file.js +3 -3
  89. package/dist/infrastructure/inspection-file.js.map +1 -1
  90. package/dist/infrastructure/inspection-hash.js +4 -3
  91. package/dist/infrastructure/inspection-hash.js.map +1 -1
  92. package/dist/infrastructure/inspection-path.d.ts +1 -0
  93. package/dist/infrastructure/inspection-path.js +18 -0
  94. package/dist/infrastructure/inspection-path.js.map +1 -0
  95. package/dist/infrastructure/journal.js +6 -0
  96. package/dist/infrastructure/journal.js.map +1 -1
  97. package/dist/infrastructure/journal.types.d.ts +3 -0
  98. package/dist/infrastructure/journal.types.js.map +1 -1
  99. package/dist/infrastructure/local-transport.d.ts +3 -0
  100. package/dist/infrastructure/local-transport.js +146 -0
  101. package/dist/infrastructure/local-transport.js.map +1 -0
  102. package/dist/infrastructure/local-transport.types.d.ts +3 -0
  103. package/dist/infrastructure/local-transport.types.js +2 -0
  104. package/dist/infrastructure/local-transport.types.js.map +1 -0
  105. package/dist/infrastructure/resource-activity-inspection.d.ts +2 -1
  106. package/dist/infrastructure/resource-activity-inspection.js +2 -2
  107. package/dist/infrastructure/resource-activity-inspection.js.map +1 -1
  108. package/dist/infrastructure/resource-activity.js +42 -14
  109. package/dist/infrastructure/resource-activity.js.map +1 -1
  110. package/dist/infrastructure/resource-activity.types.d.ts +2 -0
  111. package/dist/infrastructure/resource-activity.types.js.map +1 -1
  112. package/dist/infrastructure/s3-transport.d.ts +4 -0
  113. package/dist/infrastructure/s3-transport.js +144 -0
  114. package/dist/infrastructure/s3-transport.js.map +1 -0
  115. package/dist/infrastructure/s3-transport.types.d.ts +6 -0
  116. package/dist/infrastructure/s3-transport.types.js +2 -0
  117. package/dist/infrastructure/s3-transport.types.js.map +1 -0
  118. package/dist/infrastructure/storage-inventory.types.d.ts +2 -1
  119. package/dist/infrastructure/storage-inventory.types.js.map +1 -1
  120. package/dist/infrastructure/storage-reservations.types.d.ts +2 -0
  121. package/dist/infrastructure/storage-reservations.types.js.map +1 -1
  122. package/dist/infrastructure/task-queue-bullmq-resources.d.ts +13 -0
  123. package/dist/infrastructure/task-queue-bullmq-resources.js +118 -0
  124. package/dist/infrastructure/task-queue-bullmq-resources.js.map +1 -0
  125. package/dist/infrastructure/task-queue-bullmq-state.d.ts +5 -0
  126. package/dist/infrastructure/task-queue-bullmq-state.js +26 -0
  127. package/dist/infrastructure/task-queue-bullmq-state.js.map +1 -0
  128. package/dist/infrastructure/task-queue-bullmq.constants.d.ts +7 -0
  129. package/dist/infrastructure/task-queue-bullmq.constants.js +79 -0
  130. package/dist/infrastructure/task-queue-bullmq.constants.js.map +1 -0
  131. package/dist/infrastructure/task-queue-bullmq.d.ts +3 -0
  132. package/dist/infrastructure/task-queue-bullmq.js +124 -0
  133. package/dist/infrastructure/task-queue-bullmq.js.map +1 -0
  134. package/dist/infrastructure/task-queue-bullmq.types.d.ts +17 -0
  135. package/dist/infrastructure/task-queue-bullmq.types.js +2 -0
  136. package/dist/infrastructure/task-queue-bullmq.types.js.map +1 -0
  137. package/dist/infrastructure/transport-archive.constants.d.ts +6 -0
  138. package/dist/infrastructure/transport-archive.constants.js +7 -0
  139. package/dist/infrastructure/transport-archive.constants.js.map +1 -0
  140. package/dist/infrastructure/transport-archive.d.ts +5 -0
  141. package/dist/infrastructure/transport-archive.js +157 -0
  142. package/dist/infrastructure/transport-archive.js.map +1 -0
  143. package/dist/infrastructure/transport-archive.types.d.ts +17 -0
  144. package/dist/infrastructure/transport-archive.types.js +2 -0
  145. package/dist/infrastructure/transport-archive.types.js.map +1 -0
  146. package/dist/infrastructure/transport-artifact-store.d.ts +3 -0
  147. package/dist/infrastructure/transport-artifact-store.js +31 -0
  148. package/dist/infrastructure/transport-artifact-store.js.map +1 -0
  149. package/dist/infrastructure/transport-artifact-store.types.d.ts +4 -0
  150. package/dist/infrastructure/transport-artifact-store.types.js +2 -0
  151. package/dist/infrastructure/transport-artifact-store.types.js.map +1 -0
  152. package/dist/infrastructure/transport-checkpoint.d.ts +5 -0
  153. package/dist/infrastructure/transport-checkpoint.js +105 -0
  154. package/dist/infrastructure/transport-checkpoint.js.map +1 -0
  155. package/dist/infrastructure/transport-checkpoint.types.d.ts +9 -0
  156. package/dist/infrastructure/transport-checkpoint.types.js +2 -0
  157. package/dist/infrastructure/transport-checkpoint.types.js.map +1 -0
  158. package/dist/infrastructure/transport-conversations.d.ts +4 -0
  159. package/dist/infrastructure/transport-conversations.js +69 -0
  160. package/dist/infrastructure/transport-conversations.js.map +1 -0
  161. package/dist/infrastructure/transport-conversations.types.d.ts +4 -0
  162. package/dist/infrastructure/transport-conversations.types.js +2 -0
  163. package/dist/infrastructure/transport-conversations.types.js.map +1 -0
  164. package/dist/infrastructure/transport-envelope.d.ts +7 -0
  165. package/dist/infrastructure/transport-envelope.js +47 -0
  166. package/dist/infrastructure/transport-envelope.js.map +1 -0
  167. package/dist/infrastructure/transport-journal.d.ts +6 -0
  168. package/dist/infrastructure/transport-journal.js +93 -0
  169. package/dist/infrastructure/transport-journal.js.map +1 -0
  170. package/dist/infrastructure/transport-journal.types.d.ts +10 -0
  171. package/dist/infrastructure/transport-journal.types.js +2 -0
  172. package/dist/infrastructure/transport-journal.types.js.map +1 -0
  173. package/dist/infrastructure/transport-json.d.ts +6 -0
  174. package/dist/infrastructure/transport-json.js +46 -0
  175. package/dist/infrastructure/transport-json.js.map +1 -0
  176. package/dist/infrastructure/transport-reservations.d.ts +5 -0
  177. package/dist/infrastructure/transport-reservations.js +89 -0
  178. package/dist/infrastructure/transport-reservations.js.map +1 -0
  179. package/dist/infrastructure/transport-reservations.types.d.ts +4 -0
  180. package/dist/infrastructure/transport-reservations.types.js +2 -0
  181. package/dist/infrastructure/transport-reservations.types.js.map +1 -0
  182. package/dist/infrastructure/transport.constants.d.ts +8 -0
  183. package/dist/infrastructure/transport.constants.js +9 -0
  184. package/dist/infrastructure/transport.constants.js.map +1 -0
  185. package/dist/infrastructure/workflow-checkpoint.js +7 -0
  186. package/dist/infrastructure/workflow-checkpoint.js.map +1 -1
  187. package/dist/infrastructure/workflow-checkpoint.types.d.ts +3 -1
  188. package/dist/infrastructure/workflow-checkpoint.types.js.map +1 -1
  189. package/package.json +26 -5
@@ -42,7 +42,7 @@ export async function captureConversation(format, id, repository, lease, staging
42
42
  await rm(temporary, { force: true });
43
43
  }
44
44
  if (layout.sidecars) {
45
- const remoteSidecars = posix.join(posix.dirname(remote), id, "subagents");
45
+ const remoteSidecars = posix.join(posix.dirname(remote.replaceAll("\\", "/")), id, "subagents");
46
46
  const listed = options.local
47
47
  ? {
48
48
  status: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../../src/infrastructure/conversations/capture.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, posix, resolve } from \"node:path\";\nimport { OutpostError } from \"../../domain/errors.ts\";\nimport type { SandboxLease } from \"../../domain/sandbox.types.ts\";\nimport type {\n ConversationFormat,\n ConversationLocation,\n} from \"../conversations.types.ts\";\nimport { safeDestination } from \"../files.ts\";\nimport type { CaptureOptions } from \"./capture.types.ts\";\nimport { transcriptSearchBytes } from \"./conversation.constants.ts\";\nimport { files } from \"./files.ts\";\nimport { conversationLayout } from \"./layout.ts\";\nimport { locateConversation } from \"./locate.ts\";\nimport { validId } from \"./paths.ts\";\nimport { relocateTranscript } from \"./relocate.ts\";\n\nexport async function captureConversation(\n format: ConversationFormat,\n id: string,\n repository: string,\n lease: SandboxLease,\n staging: string,\n options: CaptureOptions = {},\n): Promise<ConversationLocation> {\n validId(id);\n const home = options.home ?? homedir();\n const layout = conversationLayout(format);\n const search = layout.remoteSearchRoot(lease.home);\n const pattern = layout.pattern(id);\n const result = options.local\n ? {\n status: 0,\n stdout: (await locateConversation(format, id, lease.root, lease.home))\n .file,\n }\n : await lease.invoke({\n executable: \"find\",\n arguments: [search, \"-type\", \"f\", \"-name\", pattern],\n retain: transcriptSearchBytes,\n });\n const remote = result.stdout.trim().split(\"\\n\").filter(Boolean)[0];\n if (result.status !== 0 || !remote)\n throw new OutpostError(\n \"session\",\n `Agent emitted conversation ${id} but its transcript is unavailable`,\n { id, search },\n );\n const file = layout.capturePath(id, repository, home, remote);\n await mkdir(staging, { recursive: true });\n const temporary = join(staging, `${randomUUID()}.jsonl`);\n await lease.download(remote, temporary);\n await mkdir(dirname(file), { recursive: true });\n try {\n await writeFile(\n file,\n relocateTranscript(\n await readFile(temporary, \"utf8\"),\n resolve(repository),\n ),\n { mode: 0o600 },\n );\n } finally {\n await rm(temporary, { force: true });\n }\n if (layout.sidecars) {\n const remoteSidecars = posix.join(posix.dirname(remote), id, \"subagents\");\n const listed = options.local\n ? {\n status: 0,\n stdout: (await files(join(dirname(remote), id, \"subagents\"))).join(\n \"\\n\",\n ),\n }\n : await lease.invoke({\n executable: \"find\",\n arguments: [remoteSidecars, \"-type\", \"f\", \"-name\", \"*.jsonl\"],\n retain: transcriptSearchBytes,\n });\n if (listed.status === 0)\n for (const child of listed.stdout.split(\"\\n\").filter(Boolean)) {\n try {\n const scratch = join(staging, `${randomUUID()}.jsonl`);\n await lease.download(child, scratch);\n const destination = await safeDestination(\n join(dirname(file), id, \"subagents\"),\n posix.relative(\n remoteSidecars.replaceAll(\"\\\\\", \"/\"),\n child.replaceAll(\"\\\\\", \"/\"),\n ),\n );\n await mkdir(dirname(destination), { recursive: true });\n try {\n await writeFile(\n destination,\n relocateTranscript(\n await readFile(scratch, \"utf8\"),\n resolve(repository),\n ),\n { mode: 0o600 },\n );\n } finally {\n await rm(scratch, { force: true });\n }\n } catch (cause) {\n options.warn?.(\n `Could not capture child transcript: ${String(cause)}`,\n );\n }\n }\n }\n return { id, file, format };\n}\n"]}
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../../src/infrastructure/conversations/capture.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, posix, resolve } from \"node:path\";\nimport { OutpostError } from \"../../domain/errors.ts\";\nimport type { SandboxLease } from \"../../domain/sandbox.types.ts\";\nimport type {\n ConversationFormat,\n ConversationLocation,\n} from \"../conversations.types.ts\";\nimport { safeDestination } from \"../files.ts\";\nimport type { CaptureOptions } from \"./capture.types.ts\";\nimport { transcriptSearchBytes } from \"./conversation.constants.ts\";\nimport { files } from \"./files.ts\";\nimport { conversationLayout } from \"./layout.ts\";\nimport { locateConversation } from \"./locate.ts\";\nimport { validId } from \"./paths.ts\";\nimport { relocateTranscript } from \"./relocate.ts\";\n\nexport async function captureConversation(\n format: ConversationFormat,\n id: string,\n repository: string,\n lease: SandboxLease,\n staging: string,\n options: CaptureOptions = {},\n): Promise<ConversationLocation> {\n validId(id);\n const home = options.home ?? homedir();\n const layout = conversationLayout(format);\n const search = layout.remoteSearchRoot(lease.home);\n const pattern = layout.pattern(id);\n const result = options.local\n ? {\n status: 0,\n stdout: (await locateConversation(format, id, lease.root, lease.home))\n .file,\n }\n : await lease.invoke({\n executable: \"find\",\n arguments: [search, \"-type\", \"f\", \"-name\", pattern],\n retain: transcriptSearchBytes,\n });\n const remote = result.stdout.trim().split(\"\\n\").filter(Boolean)[0];\n if (result.status !== 0 || !remote)\n throw new OutpostError(\n \"session\",\n `Agent emitted conversation ${id} but its transcript is unavailable`,\n { id, search },\n );\n const file = layout.capturePath(id, repository, home, remote);\n await mkdir(staging, { recursive: true });\n const temporary = join(staging, `${randomUUID()}.jsonl`);\n await lease.download(remote, temporary);\n await mkdir(dirname(file), { recursive: true });\n try {\n await writeFile(\n file,\n relocateTranscript(\n await readFile(temporary, \"utf8\"),\n resolve(repository),\n ),\n { mode: 0o600 },\n );\n } finally {\n await rm(temporary, { force: true });\n }\n if (layout.sidecars) {\n const remoteSidecars = posix.join(\n posix.dirname(remote.replaceAll(\"\\\\\", \"/\")),\n id,\n \"subagents\",\n );\n const listed = options.local\n ? {\n status: 0,\n stdout: (await files(join(dirname(remote), id, \"subagents\"))).join(\n \"\\n\",\n ),\n }\n : await lease.invoke({\n executable: \"find\",\n arguments: [remoteSidecars, \"-type\", \"f\", \"-name\", \"*.jsonl\"],\n retain: transcriptSearchBytes,\n });\n if (listed.status === 0)\n for (const child of listed.stdout.split(\"\\n\").filter(Boolean)) {\n try {\n const scratch = join(staging, `${randomUUID()}.jsonl`);\n await lease.download(child, scratch);\n const destination = await safeDestination(\n join(dirname(file), id, \"subagents\"),\n posix.relative(\n remoteSidecars.replaceAll(\"\\\\\", \"/\"),\n child.replaceAll(\"\\\\\", \"/\"),\n ),\n );\n await mkdir(dirname(destination), { recursive: true });\n try {\n await writeFile(\n destination,\n relocateTranscript(\n await readFile(scratch, \"utf8\"),\n resolve(repository),\n ),\n { mode: 0o600 },\n );\n } finally {\n await rm(scratch, { force: true });\n }\n } catch (cause) {\n options.warn?.(\n `Could not capture child transcript: ${String(cause)}`,\n );\n }\n }\n }\n return { id, file, format };\n}\n"]}
@@ -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":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,UAAkB,EAClB,UAAmB,EAAE,EACrB,KAAc;IAEd,MAAM,IAAI,GACR,OAAO,IAAI,OAAO,KAAK,QAAQ;QAC7B,CAAC,CAAC,OAAO,CACL,UAAU,EACV,OAAO,CAAC,IAAI;YACV,IAAI,CACF,UAAU,EACV,MAAM,EACN,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,UAAU,EAAE,QAAQ,CACjJ,CACJ;QACH,CAAC,CAAC,SAAS,CAAC;IAChB,IAAI,MAA8B,EAChC,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAC9B,IAAI,OAAgB,CAAC;IACrB,IAAI,OAA0C,CAAC;IAC/C,IAAI,OAAkC,CAAC;IACvC,MAAM,OAAO,GACX,CAAC,CAAC,OAAO,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC;IAClE,MAAM,OAAO,GAAG,QAAQ,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1D,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,GAAG,IAAI,cAAc,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACvD,MAAM,MAAM,CAAC,KAAK,CAChB,IAAI,CAAC,SAAS,CAAC;gBACb,IAAI,EAAE,gBAAgB;gBACtB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,KAAK;aACN,CAAC,GAAG,IAAI,CACV,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC;YACtB,MAAM,OAAO,EAAE,CAAC;YAChB,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO;QACL,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,MAAM,CAAC,KAAK;YACV,IAAI,CAAC,OAAO,IAAI,OAAO;gBAAE,OAAO;YAChC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,IAAI,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,OAAO;gBAClE,OAAO;YACT,MAAM,IAAI,GACR,IAAI,CAAC,SAAS,CAAC;gBACb,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3B,GAAG,KAAK;aACT,CAAC,GAAG,IAAI,CAAC;YACZ,IAAI,OAAO,KAAK,QAAQ;gBAAE,OAAO,CAAC,KAAK,CAAC,CAAC;;gBAEvC,OAAO,GAAG,OAAO;qBACd,IAAI,CAAC,KAAK,IAAI,EAAE;oBACf,MAAM,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC5B,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oBACf,OAAO,GAAG,KAAK,CAAC;gBAClB,CAAC,CAAC,CAAC;QACT,CAAC;QACD,KAAK;YACH,OAAO,KAAK,CAAC,KAAK,IAAI,EAAE;gBACtB,IAAI,CAAC;oBACH,MAAM,OAAO,CAAC;oBACd,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC;oBACtB,IAAI,OAAO;wBAAE,MAAM,OAAO,CAAC;oBAC3B,IAAI,IAAI,IAAI,OAAO;wBAAE,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;gBACrD,CAAC;wBAAS,CAAC;oBACT,MAAM,OAAO,EAAE,EAAE,CAAC;gBACpB,CAAC;YACH,CAAC,CAAC,EAAE,CAAC;YACL,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC","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"]}