yuurei 0.0.1

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 (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/dist/artifact/collector.d.ts +15 -0
  4. package/dist/artifact/collector.d.ts.map +1 -0
  5. package/dist/artifact/collector.js +87 -0
  6. package/dist/artifact/collector.js.map +1 -0
  7. package/dist/artifact/types.d.ts +10 -0
  8. package/dist/artifact/types.d.ts.map +1 -0
  9. package/dist/artifact/types.js +2 -0
  10. package/dist/artifact/types.js.map +1 -0
  11. package/dist/cell/digest.d.ts +10 -0
  12. package/dist/cell/digest.d.ts.map +1 -0
  13. package/dist/cell/digest.js +13 -0
  14. package/dist/cell/digest.js.map +1 -0
  15. package/dist/cell/resolver.d.ts +18 -0
  16. package/dist/cell/resolver.d.ts.map +1 -0
  17. package/dist/cell/resolver.js +41 -0
  18. package/dist/cell/resolver.js.map +1 -0
  19. package/dist/cell/types.d.ts +48 -0
  20. package/dist/cell/types.d.ts.map +1 -0
  21. package/dist/cell/types.js +2 -0
  22. package/dist/cell/types.js.map +1 -0
  23. package/dist/cli/clean.d.ts +16 -0
  24. package/dist/cli/clean.d.ts.map +1 -0
  25. package/dist/cli/clean.js +24 -0
  26. package/dist/cli/clean.js.map +1 -0
  27. package/dist/cli/doctor.d.ts +29 -0
  28. package/dist/cli/doctor.d.ts.map +1 -0
  29. package/dist/cli/doctor.js +160 -0
  30. package/dist/cli/doctor.js.map +1 -0
  31. package/dist/cli/exit-codes.d.ts +15 -0
  32. package/dist/cli/exit-codes.d.ts.map +1 -0
  33. package/dist/cli/exit-codes.js +18 -0
  34. package/dist/cli/exit-codes.js.map +1 -0
  35. package/dist/cli/init.d.ts +32 -0
  36. package/dist/cli/init.d.ts.map +1 -0
  37. package/dist/cli/init.js +143 -0
  38. package/dist/cli/init.js.map +1 -0
  39. package/dist/cli/inspect.d.ts +4 -0
  40. package/dist/cli/inspect.d.ts.map +1 -0
  41. package/dist/cli/inspect.js +24 -0
  42. package/dist/cli/inspect.js.map +1 -0
  43. package/dist/cli/output.d.ts +5 -0
  44. package/dist/cli/output.d.ts.map +1 -0
  45. package/dist/cli/output.js +5 -0
  46. package/dist/cli/output.js.map +1 -0
  47. package/dist/cli/profile-list.d.ts +3 -0
  48. package/dist/cli/profile-list.d.ts.map +1 -0
  49. package/dist/cli/profile-list.js +14 -0
  50. package/dist/cli/profile-list.js.map +1 -0
  51. package/dist/cli/run.d.ts +22 -0
  52. package/dist/cli/run.d.ts.map +1 -0
  53. package/dist/cli/run.js +95 -0
  54. package/dist/cli/run.js.map +1 -0
  55. package/dist/cli/trace-show.d.ts +3 -0
  56. package/dist/cli/trace-show.d.ts.map +1 -0
  57. package/dist/cli/trace-show.js +22 -0
  58. package/dist/cli/trace-show.js.map +1 -0
  59. package/dist/config/discovery.d.ts +3 -0
  60. package/dist/config/discovery.d.ts.map +1 -0
  61. package/dist/config/discovery.js +18 -0
  62. package/dist/config/discovery.js.map +1 -0
  63. package/dist/config/schema.d.ts +21 -0
  64. package/dist/config/schema.d.ts.map +1 -0
  65. package/dist/config/schema.js +19 -0
  66. package/dist/config/schema.js.map +1 -0
  67. package/dist/config/yuurei-config.d.ts +4 -0
  68. package/dist/config/yuurei-config.d.ts.map +1 -0
  69. package/dist/config/yuurei-config.js +10 -0
  70. package/dist/config/yuurei-config.js.map +1 -0
  71. package/dist/cost/noop.d.ts +7 -0
  72. package/dist/cost/noop.d.ts.map +1 -0
  73. package/dist/cost/noop.js +10 -0
  74. package/dist/cost/noop.js.map +1 -0
  75. package/dist/cost/types.d.ts +21 -0
  76. package/dist/cost/types.d.ts.map +1 -0
  77. package/dist/cost/types.js +2 -0
  78. package/dist/cost/types.js.map +1 -0
  79. package/dist/index.d.ts +3 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +111 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/isolation/config-root.d.ts +9 -0
  84. package/dist/isolation/config-root.d.ts.map +1 -0
  85. package/dist/isolation/config-root.js +10 -0
  86. package/dist/isolation/config-root.js.map +1 -0
  87. package/dist/isolation/env.d.ts +2 -0
  88. package/dist/isolation/env.d.ts.map +1 -0
  89. package/dist/isolation/env.js +17 -0
  90. package/dist/isolation/env.js.map +1 -0
  91. package/dist/isolation/index.d.ts +9 -0
  92. package/dist/isolation/index.d.ts.map +1 -0
  93. package/dist/isolation/index.js +21 -0
  94. package/dist/isolation/index.js.map +1 -0
  95. package/dist/isolation/level0.d.ts +20 -0
  96. package/dist/isolation/level0.d.ts.map +1 -0
  97. package/dist/isolation/level0.js +51 -0
  98. package/dist/isolation/level0.js.map +1 -0
  99. package/dist/isolation/level1.d.ts +13 -0
  100. package/dist/isolation/level1.d.ts.map +1 -0
  101. package/dist/isolation/level1.js +58 -0
  102. package/dist/isolation/level1.js.map +1 -0
  103. package/dist/isolation/tempdir.d.ts +53 -0
  104. package/dist/isolation/tempdir.d.ts.map +1 -0
  105. package/dist/isolation/tempdir.js +87 -0
  106. package/dist/isolation/tempdir.js.map +1 -0
  107. package/dist/isolation/types.d.ts +37 -0
  108. package/dist/isolation/types.d.ts.map +1 -0
  109. package/dist/isolation/types.js +2 -0
  110. package/dist/isolation/types.js.map +1 -0
  111. package/dist/profile/loader.d.ts +9 -0
  112. package/dist/profile/loader.d.ts.map +1 -0
  113. package/dist/profile/loader.js +95 -0
  114. package/dist/profile/loader.js.map +1 -0
  115. package/dist/profile/manifest.d.ts +29 -0
  116. package/dist/profile/manifest.d.ts.map +1 -0
  117. package/dist/profile/manifest.js +28 -0
  118. package/dist/profile/manifest.js.map +1 -0
  119. package/dist/profile/types.d.ts +31 -0
  120. package/dist/profile/types.d.ts.map +1 -0
  121. package/dist/profile/types.js +2 -0
  122. package/dist/profile/types.js.map +1 -0
  123. package/dist/run/id.d.ts +7 -0
  124. package/dist/run/id.d.ts.map +1 -0
  125. package/dist/run/id.js +15 -0
  126. package/dist/run/id.js.map +1 -0
  127. package/dist/run/layout.d.ts +27 -0
  128. package/dist/run/layout.d.ts.map +1 -0
  129. package/dist/run/layout.js +45 -0
  130. package/dist/run/layout.js.map +1 -0
  131. package/dist/run/pipeline.d.ts +45 -0
  132. package/dist/run/pipeline.d.ts.map +1 -0
  133. package/dist/run/pipeline.js +246 -0
  134. package/dist/run/pipeline.js.map +1 -0
  135. package/dist/run/signals.d.ts +53 -0
  136. package/dist/run/signals.d.ts.map +1 -0
  137. package/dist/run/signals.js +74 -0
  138. package/dist/run/signals.js.map +1 -0
  139. package/dist/runtime/claude-code/args.d.ts +8 -0
  140. package/dist/runtime/claude-code/args.d.ts.map +1 -0
  141. package/dist/runtime/claude-code/args.js +13 -0
  142. package/dist/runtime/claude-code/args.js.map +1 -0
  143. package/dist/runtime/claude-code/index.d.ts +11 -0
  144. package/dist/runtime/claude-code/index.d.ts.map +1 -0
  145. package/dist/runtime/claude-code/index.js +167 -0
  146. package/dist/runtime/claude-code/index.js.map +1 -0
  147. package/dist/runtime/claude-code/paths.d.ts +3 -0
  148. package/dist/runtime/claude-code/paths.d.ts.map +1 -0
  149. package/dist/runtime/claude-code/paths.js +6 -0
  150. package/dist/runtime/claude-code/paths.js.map +1 -0
  151. package/dist/runtime/codex/args.d.ts +21 -0
  152. package/dist/runtime/codex/args.d.ts.map +1 -0
  153. package/dist/runtime/codex/args.js +35 -0
  154. package/dist/runtime/codex/args.js.map +1 -0
  155. package/dist/runtime/codex/index.d.ts +13 -0
  156. package/dist/runtime/codex/index.d.ts.map +1 -0
  157. package/dist/runtime/codex/index.js +299 -0
  158. package/dist/runtime/codex/index.js.map +1 -0
  159. package/dist/runtime/codex/paths.d.ts +3 -0
  160. package/dist/runtime/codex/paths.d.ts.map +1 -0
  161. package/dist/runtime/codex/paths.js +6 -0
  162. package/dist/runtime/codex/paths.js.map +1 -0
  163. package/dist/runtime/detect.d.ts +5 -0
  164. package/dist/runtime/detect.d.ts.map +1 -0
  165. package/dist/runtime/detect.js +42 -0
  166. package/dist/runtime/detect.js.map +1 -0
  167. package/dist/runtime/exec.d.ts +24 -0
  168. package/dist/runtime/exec.d.ts.map +1 -0
  169. package/dist/runtime/exec.js +132 -0
  170. package/dist/runtime/exec.js.map +1 -0
  171. package/dist/runtime/registry.d.ts +4 -0
  172. package/dist/runtime/registry.d.ts.map +1 -0
  173. package/dist/runtime/registry.js +23 -0
  174. package/dist/runtime/registry.js.map +1 -0
  175. package/dist/runtime/reserved-paths.d.ts +22 -0
  176. package/dist/runtime/reserved-paths.d.ts.map +1 -0
  177. package/dist/runtime/reserved-paths.js +30 -0
  178. package/dist/runtime/reserved-paths.js.map +1 -0
  179. package/dist/runtime/types.d.ts +105 -0
  180. package/dist/runtime/types.d.ts.map +1 -0
  181. package/dist/runtime/types.js +2 -0
  182. package/dist/runtime/types.js.map +1 -0
  183. package/dist/trace/reader.d.ts +4 -0
  184. package/dist/trace/reader.d.ts.map +1 -0
  185. package/dist/trace/reader.js +9 -0
  186. package/dist/trace/reader.js.map +1 -0
  187. package/dist/trace/redact.d.ts +22 -0
  188. package/dist/trace/redact.d.ts.map +1 -0
  189. package/dist/trace/redact.js +124 -0
  190. package/dist/trace/redact.js.map +1 -0
  191. package/dist/trace/schema.d.ts +49 -0
  192. package/dist/trace/schema.d.ts.map +1 -0
  193. package/dist/trace/schema.js +53 -0
  194. package/dist/trace/schema.js.map +1 -0
  195. package/dist/trace/writer.d.ts +17 -0
  196. package/dist/trace/writer.d.ts.map +1 -0
  197. package/dist/trace/writer.js +45 -0
  198. package/dist/trace/writer.js.map +1 -0
  199. package/dist/util/fs.d.ts +17 -0
  200. package/dist/util/fs.d.ts.map +1 -0
  201. package/dist/util/fs.js +61 -0
  202. package/dist/util/fs.js.map +1 -0
  203. package/dist/util/hash.d.ts +3 -0
  204. package/dist/util/hash.d.ts.map +1 -0
  205. package/dist/util/hash.js +7 -0
  206. package/dist/util/hash.js.map +1 -0
  207. package/dist/util/json.d.ts +8 -0
  208. package/dist/util/json.d.ts.map +1 -0
  209. package/dist/util/json.js +24 -0
  210. package/dist/util/json.js.map +1 -0
  211. package/dist/util/logger.d.ts +8 -0
  212. package/dist/util/logger.d.ts.map +1 -0
  213. package/dist/util/logger.js +16 -0
  214. package/dist/util/logger.js.map +1 -0
  215. package/dist/version.d.ts +24 -0
  216. package/dist/version.d.ts.map +1 -0
  217. package/dist/version.js +26 -0
  218. package/dist/version.js.map +1 -0
  219. package/docs/assets/yuurei-top.jpg +0 -0
  220. package/package.json +61 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Default minimum age before a `yuurei-*` temp dir counts as orphaned. A
3
+ * run's temp root is only written directly at creation time (level1 writes
4
+ * `home/` right after), so an actively-used root can look idle for a long
5
+ * time; the threshold exists so the sweep never touches a directory a
6
+ * (possibly hung) live run still owns.
7
+ */
8
+ export declare const ORPHAN_TEMP_DIR_MIN_AGE_MS: number;
9
+ export interface OrphanTempDir {
10
+ /** Absolute path to the orphaned directory. */
11
+ path: string;
12
+ /** Milliseconds since the directory was last modified. */
13
+ ageMs: number;
14
+ }
15
+ export interface FindOrphanTempDirsOptions {
16
+ /**
17
+ * Directory to scan for `yuurei-*` entries. Defaults to `os.tmpdir()`.
18
+ * Injectable for tests so the sweep can run against a scratch root.
19
+ */
20
+ root?: string;
21
+ /** Only directories older than this many milliseconds count. Defaults to `ORPHAN_TEMP_DIR_MIN_AGE_MS`. */
22
+ olderThanMs?: number;
23
+ }
24
+ export declare function createTempDir(): Promise<string>;
25
+ export declare function removeTempDir(path: string): Promise<void>;
26
+ /**
27
+ * Lists `yuurei-*` temp directories directly under the temp root that are
28
+ * old enough to be considered orphaned — left behind by a `SIGKILL`, power
29
+ * loss, or hard crash that bypassed the signal handler and `finally`-block
30
+ * cleanup (design doc §9.1, §15 safety regression). Only real directories
31
+ * are considered: a planted `yuurei-*` symlink or plain file with the prefix
32
+ * is ignored, so the sweep can never be tricked into deleting its target.
33
+ * Directories owned by another user are skipped too: `os.tmpdir()` is
34
+ * per-user on macOS, but a Linux host with a shared `/tmp` puts other
35
+ * people's `yuurei-*` runs in the same root, and this sweep deletes.
36
+ * Unreadable roots and entries are skipped rather than propagated.
37
+ */
38
+ export declare function findOrphanTempDirs(options?: FindOrphanTempDirsOptions): Promise<OrphanTempDir[]>;
39
+ export interface RemoveOrphanTempDirsResult {
40
+ removed: OrphanTempDir[];
41
+ failed: {
42
+ path: string;
43
+ error: string;
44
+ }[];
45
+ }
46
+ /**
47
+ * Finds and removes orphaned temp directories (see `findOrphanTempDirs`).
48
+ * Removal is best-effort: a failed deletion is reported in the result
49
+ * rather than aborting the sweep, so one unremovable directory does not
50
+ * hide the rest.
51
+ */
52
+ export declare function removeOrphanTempDirs(options?: FindOrphanTempDirsOptions): Promise<RemoveOrphanTempDirsResult>;
53
+ //# sourceMappingURL=tempdir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tempdir.d.ts","sourceRoot":"","sources":["../../src/isolation/tempdir.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,QAAsB,CAAC;AAE9D,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0GAA0G;IAC1G,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAErD;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,CAAC,EAAE,yBAAyB,GAClC,OAAO,CAAC,aAAa,EAAE,CAAC,CA8B1B;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,CAAC,EAAE,yBAAyB,GAClC,OAAO,CAAC,0BAA0B,CAAC,CAcrC"}
@@ -0,0 +1,87 @@
1
+ import { lstat, mkdtemp, readdir, rm } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ const PREFIX = 'yuurei-';
5
+ /**
6
+ * Default minimum age before a `yuurei-*` temp dir counts as orphaned. A
7
+ * run's temp root is only written directly at creation time (level1 writes
8
+ * `home/` right after), so an actively-used root can look idle for a long
9
+ * time; the threshold exists so the sweep never touches a directory a
10
+ * (possibly hung) live run still owns.
11
+ */
12
+ export const ORPHAN_TEMP_DIR_MIN_AGE_MS = 24 * 60 * 60 * 1000;
13
+ export async function createTempDir() {
14
+ return mkdtemp(join(tmpdir(), PREFIX));
15
+ }
16
+ export async function removeTempDir(path) {
17
+ await rm(path, { recursive: true, force: true });
18
+ }
19
+ /**
20
+ * Lists `yuurei-*` temp directories directly under the temp root that are
21
+ * old enough to be considered orphaned — left behind by a `SIGKILL`, power
22
+ * loss, or hard crash that bypassed the signal handler and `finally`-block
23
+ * cleanup (design doc §9.1, §15 safety regression). Only real directories
24
+ * are considered: a planted `yuurei-*` symlink or plain file with the prefix
25
+ * is ignored, so the sweep can never be tricked into deleting its target.
26
+ * Directories owned by another user are skipped too: `os.tmpdir()` is
27
+ * per-user on macOS, but a Linux host with a shared `/tmp` puts other
28
+ * people's `yuurei-*` runs in the same root, and this sweep deletes.
29
+ * Unreadable roots and entries are skipped rather than propagated.
30
+ */
31
+ export async function findOrphanTempDirs(options) {
32
+ const root = options?.root ?? tmpdir();
33
+ const olderThanMs = options?.olderThanMs ?? ORPHAN_TEMP_DIR_MIN_AGE_MS;
34
+ const now = Date.now();
35
+ const orphans = [];
36
+ // Undefined on platforms without POSIX uids, where there is no ownership
37
+ // to compare and the other guards above still apply.
38
+ const uid = process.getuid?.();
39
+ let entries;
40
+ try {
41
+ entries = await readdir(root, { withFileTypes: true });
42
+ }
43
+ catch {
44
+ return orphans;
45
+ }
46
+ for (const entry of entries) {
47
+ if (!entry.name.startsWith(PREFIX))
48
+ continue;
49
+ // withFileTypes reads d_type; excludes symlinks (type 'symlink') and files.
50
+ if (!entry.isDirectory())
51
+ continue;
52
+ const path = join(root, entry.name);
53
+ // lstat (not stat) so a symlink swapped in after readdir is not followed.
54
+ const info = await lstat(path).catch(() => null);
55
+ if (info === null || !info.isDirectory())
56
+ continue;
57
+ if (uid !== undefined && info.uid !== uid)
58
+ continue;
59
+ const ageMs = now - info.mtimeMs;
60
+ if (ageMs >= olderThanMs)
61
+ orphans.push({ path, ageMs });
62
+ }
63
+ return orphans;
64
+ }
65
+ /**
66
+ * Finds and removes orphaned temp directories (see `findOrphanTempDirs`).
67
+ * Removal is best-effort: a failed deletion is reported in the result
68
+ * rather than aborting the sweep, so one unremovable directory does not
69
+ * hide the rest.
70
+ */
71
+ export async function removeOrphanTempDirs(options) {
72
+ const result = { removed: [], failed: [] };
73
+ for (const orphan of await findOrphanTempDirs(options)) {
74
+ try {
75
+ await rm(orphan.path, { recursive: true, force: true });
76
+ result.removed.push(orphan);
77
+ }
78
+ catch (error) {
79
+ result.failed.push({
80
+ path: orphan.path,
81
+ error: error instanceof Error ? error.message : String(error),
82
+ });
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+ //# sourceMappingURL=tempdir.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tempdir.js","sourceRoot":"","sources":["../../src/isolation/tempdir.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { ResolvedCell } from '../cell/types.js';
2
+ export type IsolationStrategy = 'level0' | 'level1';
3
+ export interface IsolationContext {
4
+ strategy: IsolationStrategy;
5
+ /** Absolute path to the temporary root created for this run. */
6
+ rootDir: string;
7
+ /** Temporary HOME directory. Set for level1, null for level0 (arg/config-root swap only). */
8
+ homeDir: string | null;
9
+ env: Record<string, string>;
10
+ /** Corresponds to the `--keep` CLI flag: skip cleanup in `dispose()` for debugging. */
11
+ keep: boolean;
12
+ }
13
+ /**
14
+ * Discriminated union so callers cannot reach `Runtime.execute()` without
15
+ * narrowing to `verified: true` first — see the fail-closed principle in
16
+ * design doc §9.1 / §12.2: isolation that cannot be verified must not run.
17
+ */
18
+ export type IsolationReport = {
19
+ verified: true;
20
+ strategy: IsolationStrategy;
21
+ checkedAt: string;
22
+ } | {
23
+ verified: false;
24
+ strategy: IsolationStrategy;
25
+ checkedAt: string;
26
+ findings: string[];
27
+ };
28
+ /**
29
+ * Builds a temporary execution environment isolated from the user's global
30
+ * runtime configuration, verifies that isolation, and tears it down.
31
+ */
32
+ export interface Isolation {
33
+ create(cell: ResolvedCell): Promise<IsolationContext>;
34
+ verify(context: IsolationContext): Promise<IsolationReport>;
35
+ dispose(context: IsolationContext): Promise<void>;
36
+ }
37
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/isolation/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEpD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,6FAA6F;IAC7F,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,uFAAuF;IACvF,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAClE;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE5F;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACtD,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC5D,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/isolation/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import type { Profile, ResolvedProfile } from './types.js';
2
+ /**
3
+ * Loads `profile.yaml` and every file under `config/` into resolved
4
+ * content, then digests that content. The digest is computed over actual
5
+ * file contents, not the profile name, so two profiles with the same name
6
+ * but different config never collide (design doc §7.3).
7
+ */
8
+ export declare function loadProfile(profile: Profile): Promise<ResolvedProfile>;
9
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/profile/loader.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAkB,MAAM,YAAY,CAAC;AAE3E;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CA4B5E"}
@@ -0,0 +1,95 @@
1
+ import { readdir, readFile, realpath, stat } from 'node:fs/promises';
2
+ import { join, relative } from 'node:path';
3
+ import { parse } from 'yaml';
4
+ import { canonicalJsonStringify } from '../util/json.js';
5
+ import { sha256Digest } from '../util/hash.js';
6
+ import { isPathWithin, pathExists } from '../util/fs.js';
7
+ import { ProfileYamlSchema } from '../config/schema.js';
8
+ import { EXIT_CODES, YuureiError } from '../cli/exit-codes.js';
9
+ /**
10
+ * Loads `profile.yaml` and every file under `config/` into resolved
11
+ * content, then digests that content. The digest is computed over actual
12
+ * file contents, not the profile name, so two profiles with the same name
13
+ * but different config never collide (design doc §7.3).
14
+ */
15
+ export async function loadProfile(profile) {
16
+ const profileYamlRaw = await readFile(join(profile.sourceDir, 'profile.yaml'), 'utf8');
17
+ const profileYaml = ProfileYamlSchema.parse(parse(profileYamlRaw));
18
+ const configDir = join(profile.sourceDir, 'config');
19
+ const rawConfigFiles = (await pathExists(configDir)) ? await readAllFiles(configDir) : {};
20
+ const content = { profileYaml, configFiles: rawConfigFiles };
21
+ // Digest over base64 (not raw Buffers) so JSON serialization of the
22
+ // canonical form is stable and deterministic for binary content.
23
+ const digestContent = {
24
+ profileYaml,
25
+ configFiles: Object.fromEntries(Object.entries(rawConfigFiles).map(([path, { content, mode }]) => [
26
+ path,
27
+ { content: content.toString('base64'), mode },
28
+ ])),
29
+ };
30
+ const digest = sha256Digest(canonicalJsonStringify(digestContent));
31
+ return {
32
+ name: profile.name,
33
+ runtime: profileYaml.runtime,
34
+ content,
35
+ digest,
36
+ };
37
+ }
38
+ async function readAllFiles(rootDir) {
39
+ const result = {};
40
+ const rootReal = await realpath(rootDir);
41
+ // `ancestors` holds the realpath of every directory on the CURRENT walk
42
+ // branch (root to here), not every directory ever visited. A global
43
+ // visited set would incorrectly drop a second, non-cyclic symlink to an
44
+ // already-seen sibling directory (e.g. two symlinks both pointing at
45
+ // config/shared/) and make the digest depend on readdir() ordering.
46
+ // Tracking only the current branch still catches every real cycle,
47
+ // including a symlink pointing back at a plain (non-symlink) ancestor.
48
+ async function walk(currentDir, ancestors) {
49
+ const entries = await readdir(currentDir, { withFileTypes: true });
50
+ for (const entry of entries) {
51
+ const fullPath = join(currentDir, entry.name);
52
+ if (entry.isSymbolicLink()) {
53
+ // realpath() MUST come before isPathWithin(): isPathWithin() swallows a
54
+ // realpath failure via .catch(() => resolve(child)), which makes a dangling
55
+ // symlink appear to be within the boundary and pass the guard.
56
+ const target = await realpath(fullPath).catch(() => null);
57
+ if (target === null) {
58
+ throw new YuureiError(`profile config contains a dangling symlink: ${relative(rootDir, fullPath)}`, EXIT_CODES.CONFIG_ERROR);
59
+ }
60
+ if (!(await isPathWithin(rootReal, target))) {
61
+ throw new YuureiError(`profile config symlink escapes the profile config directory: ${relative(rootDir, fullPath)}`, EXIT_CODES.CONFIG_ERROR);
62
+ }
63
+ const targetStat = await stat(target);
64
+ if (targetStat.isDirectory()) {
65
+ if (ancestors.has(target)) {
66
+ throw new YuureiError(`profile config symlink forms a cycle: ${relative(rootDir, fullPath)}`, EXIT_CODES.CONFIG_ERROR);
67
+ }
68
+ await walk(fullPath, new Set([...ancestors, target]));
69
+ }
70
+ else if (targetStat.isFile()) {
71
+ result[relative(rootDir, fullPath)] = {
72
+ content: await readFile(target),
73
+ mode: targetStat.mode,
74
+ };
75
+ }
76
+ }
77
+ else if (entry.isDirectory()) {
78
+ const target = await realpath(fullPath);
79
+ if (ancestors.has(target)) {
80
+ throw new YuureiError(`profile config symlink forms a cycle: ${relative(rootDir, fullPath)}`, EXIT_CODES.CONFIG_ERROR);
81
+ }
82
+ await walk(fullPath, new Set([...ancestors, target]));
83
+ }
84
+ else if (entry.isFile()) {
85
+ result[relative(rootDir, fullPath)] = {
86
+ content: await readFile(fullPath),
87
+ mode: (await stat(fullPath)).mode,
88
+ };
89
+ }
90
+ }
91
+ }
92
+ await walk(rootDir, new Set([rootReal]));
93
+ return result;
94
+ }
95
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/profile/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAG/D;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAgB;IAChD,MAAM,cAAc,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;IACvF,MAAM,WAAW,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;IAEnE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACpD,MAAM,cAAc,GAAG,CAAC,MAAM,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE1F,MAAM,OAAO,GAAmB,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC;IAE7E,oEAAoE;IACpE,iEAAiE;IACjE,MAAM,aAAa,GAAG;QACpB,WAAW;QACX,WAAW,EAAE,MAAM,CAAC,WAAW,CAC7B,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAChE,IAAI;YACJ,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE;SAC9C,CAAC,CACH;KACF,CAAC;IACF,MAAM,MAAM,GAAG,YAAY,CAAC,sBAAsB,CAAC,aAAa,CAAC,CAAC,CAAC;IAEnE,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,OAAO,EAAE,WAAW,CAAC,OAAO;QAC5B,OAAO;QACP,MAAM;KACP,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,YAAY,CACzB,OAAe;IAEf,MAAM,MAAM,GAAsD,EAAE,CAAC;IACrE,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEzC,wEAAwE;IACxE,oEAAoE;IACpE,wEAAwE;IACxE,qEAAqE;IACrE,oEAAoE;IACpE,mEAAmE;IACnE,uEAAuE;IACvE,KAAK,UAAU,IAAI,CAAC,UAAkB,EAAE,SAA8B;QACpE,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAE9C,IAAI,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;gBAC3B,wEAAwE;gBACxE,4EAA4E;gBAC5E,+DAA+D;gBAC/D,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;gBAC1D,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;oBACpB,MAAM,IAAI,WAAW,CACnB,+CAA+C,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,EAC5E,UAAU,CAAC,YAAY,CACxB,CAAC;gBACJ,CAAC;gBACD,IAAI,CAAC,CAAC,MAAM,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;oBAC5C,MAAM,IAAI,WAAW,CACnB,gEAAgE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,EAC7F,UAAU,CAAC,YAAY,CACxB,CAAC;gBACJ,CAAC;gBACD,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;gBACtC,IAAI,UAAU,CAAC,WAAW,EAAE,EAAE,CAAC;oBAC7B,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;wBAC1B,MAAM,IAAI,WAAW,CACnB,yCAAyC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,EACtE,UAAU,CAAC,YAAY,CACxB,CAAC;oBACJ,CAAC;oBACD,MAAM,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;gBACxD,CAAC;qBAAM,IAAI,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;oBAC/B,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,GAAG;wBACpC,OAAO,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC;wBAC/B,IAAI,EAAE,UAAU,CAAC,IAAI;qBACtB,CAAC;gBACJ,CAAC;YACH,CAAC;iBAAM,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBAC/B,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBACxC,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC1B,MAAM,IAAI,WAAW,CACnB,yCAAyC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,EACtE,UAAU,CAAC,YAAY,CACxB,CAAC;gBACJ,CAAC;gBACD,MAAM,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;YACxD,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC1B,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,GAAG;oBACpC,OAAO,EAAE,MAAM,QAAQ,CAAC,QAAQ,CAAC;oBACjC,IAAI,EAAE,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI;iBAClC,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { ResolvedProfileRef } from '../cell/types.js';
2
+ import type { ProfileYaml } from '../config/schema.js';
3
+ /** What is recorded about one materialized profile config file. */
4
+ export interface ProfileFileManifest {
5
+ digest: string;
6
+ /** Permission bits the file was materialized with, as `writeFileTree` applies them. */
7
+ mode: number;
8
+ bytes: number;
9
+ }
10
+ /**
11
+ * The durable record of which profile a run used, written to
12
+ * `resolved-profile.json`.
13
+ */
14
+ export interface ProfileManifest {
15
+ name: string;
16
+ digest: string;
17
+ profileYaml: ProfileYaml;
18
+ configFiles: Record<string, ProfileFileManifest>;
19
+ }
20
+ /**
21
+ * Reduces a resolved profile to what §10.1 records, a digest of the profile
22
+ * content rather than the content itself.
23
+ *
24
+ * Redacting the bytes instead does not work: `redactSecrets` matches
25
+ * secret-shaped *text*, and a `Buffer` serializes to a numeric array, so the
26
+ * patterns never see a string to match.
27
+ */
28
+ export declare function toProfileManifest(resolved: ResolvedProfileRef): ProfileManifest;
29
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/profile/manifest.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,mEAAmE;AACnE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAClD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,eAAe,CAkB/E"}
@@ -0,0 +1,28 @@
1
+ import { sha256Digest } from '../util/hash.js';
2
+ /**
3
+ * Reduces a resolved profile to what §10.1 records, a digest of the profile
4
+ * content rather than the content itself.
5
+ *
6
+ * Redacting the bytes instead does not work: `redactSecrets` matches
7
+ * secret-shaped *text*, and a `Buffer` serializes to a numeric array, so the
8
+ * patterns never see a string to match.
9
+ */
10
+ export function toProfileManifest(resolved) {
11
+ const configFiles = {};
12
+ for (const [path, file] of Object.entries(resolved.content.configFiles)) {
13
+ configFiles[path] = {
14
+ digest: sha256Digest(file.content),
15
+ // The same mask `writeFileTree` chmods with (`util/fs.ts`), so this
16
+ // records the bits that actually landed, not the profile's raw stat mode.
17
+ mode: file.mode & 0o777,
18
+ bytes: file.content.byteLength,
19
+ };
20
+ }
21
+ return {
22
+ name: resolved.name,
23
+ digest: resolved.digest,
24
+ profileYaml: resolved.content.profileYaml,
25
+ configFiles,
26
+ };
27
+ }
28
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/profile/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAuB/C;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAA4B;IAC5D,MAAM,WAAW,GAAwC,EAAE,CAAC;IAC5D,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACxE,WAAW,CAAC,IAAI,CAAC,GAAG;YAClB,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;YAClC,oEAAoE;YACpE,0EAA0E;YAC1E,IAAI,EAAE,IAAI,CAAC,IAAI,GAAG,KAAK;YACvB,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;SAC/B,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW;QACzC,WAAW;KACZ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { ProfileYaml } from '../config/schema.js';
2
+ /**
3
+ * A profile's fully-loaded content: the parsed profile.yaml plus a snapshot
4
+ * of every file under the profile's `config/`.
5
+ *
6
+ * INVARIANT: every key of `configFiles` is a relative path produced by
7
+ * `path.relative()` over a walk rooted at that profile's `config/`, so no key
8
+ * escapes its destination directory when re-joined. Enforced at the only
9
+ * producer (`loadProfile`), which is the filesystem boundary.
10
+ */
11
+ export interface ProfileContent {
12
+ profileYaml: ProfileYaml;
13
+ configFiles: Record<string, {
14
+ content: Buffer;
15
+ mode: number;
16
+ }>;
17
+ }
18
+ export interface Profile {
19
+ name: string;
20
+ runtime: string;
21
+ /** Absolute path to the profile's directory (containing profile.yaml + config/). */
22
+ sourceDir: string;
23
+ }
24
+ export interface ResolvedProfile {
25
+ name: string;
26
+ runtime: string;
27
+ /** Fully-loaded profile.yaml content plus a snapshot of config/ file contents. */
28
+ content: ProfileContent;
29
+ digest: string;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/profile/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/profile/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Generates a sortable, filesystem-safe run id: a compact UTC timestamp
3
+ * plus a short random suffix (e.g. `20260908T150112Z-a3f9`). Used as a
4
+ * directory name under `.yuurei/runs/`, so it must contain no `:` or `/`.
5
+ */
6
+ export declare function generateRunId(now?: Date): string;
7
+ //# sourceMappingURL=id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/run/id.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,IAAiB,GAAG,MAAM,CAO5D"}
package/dist/run/id.js ADDED
@@ -0,0 +1,15 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ /**
3
+ * Generates a sortable, filesystem-safe run id: a compact UTC timestamp
4
+ * plus a short random suffix (e.g. `20260908T150112Z-a3f9`). Used as a
5
+ * directory name under `.yuurei/runs/`, so it must contain no `:` or `/`.
6
+ */
7
+ export function generateRunId(now = new Date()) {
8
+ const timestamp = now
9
+ .toISOString()
10
+ .replace(/[-:]/g, '')
11
+ .replace(/\.\d+Z$/, 'Z');
12
+ const suffix = randomBytes(2).toString('hex');
13
+ return `${timestamp}-${suffix}`;
14
+ }
15
+ //# sourceMappingURL=id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/run/id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAY,IAAI,IAAI,EAAE;IAClD,MAAM,SAAS,GAAG,GAAG;SAClB,WAAW,EAAE;SACb,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,OAAO,GAAG,SAAS,IAAI,MAAM,EAAE,CAAC;AAClC,CAAC"}
@@ -0,0 +1,27 @@
1
+ type RunIdGenerator = () => string;
2
+ /** The `.yuurei/runs/<run-id>/` layout described in design doc §10. */
3
+ export interface RunLayout {
4
+ runDir: string;
5
+ workspaceDir: string;
6
+ tracePath: string;
7
+ resolvedProfilePath: string;
8
+ stdoutPath: string;
9
+ stderrPath: string;
10
+ artifactsPath: string;
11
+ patchPath: string;
12
+ }
13
+ /**
14
+ * Creates a run layout whose run id is unique: the run's directory under
15
+ * `runs/` is claimed with an atomic `mkdir(..., { recursive: false })`.
16
+ * When that fails with `EEXIST` because another invocation already claimed
17
+ * the same directory, this retries with a freshly generated run id. That is
18
+ * what prevents two concurrent `yuurei run` invocations from sharing a run
19
+ * directory (design doc §13 "avoiding collisions across parallel
20
+ * executions").
21
+ */
22
+ export declare function createUniqueRunLayout(yuureiDir: string, generateId?: RunIdGenerator, maxAttempts?: number): Promise<{
23
+ runId: string;
24
+ layout: RunLayout;
25
+ }>;
26
+ export {};
27
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/run/layout.ts"],"names":[],"mappings":"AAIA,KAAK,cAAc,GAAG,MAAM,MAAM,CAAC;AAEnC,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAgBD;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,UAAU,GAAE,cAA8B,EAC1C,WAAW,SAAI,GACd,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC,CAiB/C"}
@@ -0,0 +1,45 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { generateRunId } from './id.js';
4
+ function runLayout(yuureiDir, runId) {
5
+ const runDir = join(yuureiDir, 'runs', runId);
6
+ return {
7
+ runDir,
8
+ workspaceDir: join(runDir, 'workspace'),
9
+ tracePath: join(runDir, 'trace.json'),
10
+ resolvedProfilePath: join(runDir, 'resolved-profile.json'),
11
+ stdoutPath: join(runDir, 'stdout.log'),
12
+ stderrPath: join(runDir, 'stderr.log'),
13
+ artifactsPath: join(runDir, 'artifacts.json'),
14
+ patchPath: join(runDir, 'patch.diff'),
15
+ };
16
+ }
17
+ /**
18
+ * Creates a run layout whose run id is unique: the run's directory under
19
+ * `runs/` is claimed with an atomic `mkdir(..., { recursive: false })`.
20
+ * When that fails with `EEXIST` because another invocation already claimed
21
+ * the same directory, this retries with a freshly generated run id. That is
22
+ * what prevents two concurrent `yuurei run` invocations from sharing a run
23
+ * directory (design doc §13 "avoiding collisions across parallel
24
+ * executions").
25
+ */
26
+ export async function createUniqueRunLayout(yuureiDir, generateId = generateRunId, maxAttempts = 5) {
27
+ const runsDir = join(yuureiDir, 'runs');
28
+ await mkdir(runsDir, { recursive: true });
29
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
30
+ const runId = generateId();
31
+ const layout = runLayout(yuureiDir, runId);
32
+ try {
33
+ await mkdir(layout.runDir, { recursive: false });
34
+ await mkdir(layout.workspaceDir, { recursive: true });
35
+ return { runId, layout };
36
+ }
37
+ catch (error) {
38
+ if (error.code !== 'EEXIST' || attempt === maxAttempts) {
39
+ throw error;
40
+ }
41
+ }
42
+ }
43
+ throw new Error(`failed to create a unique run layout for ${yuureiDir}`);
44
+ }
45
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/run/layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAgBxC,SAAS,SAAS,CAAC,SAAiB,EAAE,KAAa;IACjD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9C,OAAO;QACL,MAAM;QACN,YAAY,EAAE,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC;QACvC,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;QACrC,mBAAmB,EAAE,IAAI,CAAC,MAAM,EAAE,uBAAuB,CAAC;QAC1D,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;QACtC,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;QACtC,aAAa,EAAE,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC;QAC7C,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;KACtC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,SAAiB,EACjB,aAA6B,aAAa,EAC1C,WAAW,GAAG,CAAC;IAEf,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QACxD,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;YACjD,MAAM,KAAK,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACtD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;gBAClF,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,4CAA4C,SAAS,EAAE,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { type CellResolutionInput } from '../cell/resolver.js';
2
+ import type { ResolvedCell } from '../cell/types.js';
3
+ import type { Isolation, IsolationStrategy } from '../isolation/types.js';
4
+ import type { Runtime } from '../runtime/types.js';
5
+ import type { Trace } from '../trace/schema.js';
6
+ export interface RunPipelineInput extends CellResolutionInput {
7
+ yuureiDir: string;
8
+ isolationStrategy: 'level0' | 'level1';
9
+ keep: boolean;
10
+ /** null/undefined = no timeout is enforced (current default behaviour). */
11
+ timeoutMs?: number | null;
12
+ /** Maximum bytes retained for each saved log and artifact. */
13
+ maxArtifactBytes?: number;
14
+ /** Test seam only. Defaults to the real registry; production callers omit it. */
15
+ resolveRuntime?: (id: string) => Runtime;
16
+ /**
17
+ * Test seam only. Defaults to the real factory; production callers omit it.
18
+ * Lets a test wrap the isolation to put a barrier inside `dispose()` and
19
+ * prove the signal handler waits for the shared cleanup lifecycle.
20
+ */
21
+ createIsolation?: (strategy: IsolationStrategy) => Isolation;
22
+ /**
23
+ * Called synchronously for each non-fatal warning as it occurs (e.g. a
24
+ * credential file that could not be scrubbed on cleanup). This is the
25
+ * *only* way such a warning reaches the caller when a later step
26
+ * (execute/normalize/trace write/artifact collection) throws, since no
27
+ * RunPipelineResult is ever returned on that path — the throw propagates
28
+ * and the caller never sees a return value to read warnings off.
29
+ */
30
+ onWarning?: (message: string) => void;
31
+ }
32
+ export interface RunPipelineResult {
33
+ runId: string;
34
+ runDir: string;
35
+ cell: ResolvedCell;
36
+ trace: Trace;
37
+ }
38
+ /**
39
+ * The single place that orders a run's execution:
40
+ * resolve → isolate → verify (fail-closed) → prepare → execute →
41
+ * normalize → write trace → collect artifacts. No other module should
42
+ * re-implement this ordering (design doc §12.2 fail-closed principle).
43
+ */
44
+ export declare function runPipeline(input: RunPipelineInput): Promise<RunPipelineResult>;
45
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/run/pipeline.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAOrD,OAAO,KAAK,EAAE,SAAS,EAAoB,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG5F,OAAO,KAAK,EAAe,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGhE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAIhD,MAAM,WAAW,gBAAiB,SAAQ,mBAAmB;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iFAAiF;IACjF,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,SAAS,CAAC;IAC7D;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA2PrF"}