@substrate-ai/sdlc 0.19.54

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 (166) hide show
  1. package/dist/events.d.ts +336 -0
  2. package/dist/events.d.ts.map +1 -0
  3. package/dist/events.js +11 -0
  4. package/dist/events.js.map +1 -0
  5. package/dist/gating/conflict-detector.d.ts +59 -0
  6. package/dist/gating/conflict-detector.d.ts.map +1 -0
  7. package/dist/gating/conflict-detector.js +101 -0
  8. package/dist/gating/conflict-detector.js.map +1 -0
  9. package/dist/gating/dispatch-gate.d.ts +42 -0
  10. package/dist/gating/dispatch-gate.d.ts.map +1 -0
  11. package/dist/gating/dispatch-gate.js +197 -0
  12. package/dist/gating/dispatch-gate.js.map +1 -0
  13. package/dist/gating/index.d.ts +9 -0
  14. package/dist/gating/index.d.ts.map +1 -0
  15. package/dist/gating/index.js +8 -0
  16. package/dist/gating/index.js.map +1 -0
  17. package/dist/gating/types.d.ts +98 -0
  18. package/dist/gating/types.d.ts.map +1 -0
  19. package/dist/gating/types.js +8 -0
  20. package/dist/gating/types.js.map +1 -0
  21. package/dist/handlers/event-bridge.d.ts +56 -0
  22. package/dist/handlers/event-bridge.d.ts.map +1 -0
  23. package/dist/handlers/event-bridge.js +140 -0
  24. package/dist/handlers/event-bridge.js.map +1 -0
  25. package/dist/handlers/index.d.ts +15 -0
  26. package/dist/handlers/index.d.ts.map +1 -0
  27. package/dist/handlers/index.js +14 -0
  28. package/dist/handlers/index.js.map +1 -0
  29. package/dist/handlers/sdlc-code-review-handler.d.ts +119 -0
  30. package/dist/handlers/sdlc-code-review-handler.d.ts.map +1 -0
  31. package/dist/handlers/sdlc-code-review-handler.js +131 -0
  32. package/dist/handlers/sdlc-code-review-handler.js.map +1 -0
  33. package/dist/handlers/sdlc-create-story-handler.d.ts +97 -0
  34. package/dist/handlers/sdlc-create-story-handler.d.ts.map +1 -0
  35. package/dist/handlers/sdlc-create-story-handler.js +91 -0
  36. package/dist/handlers/sdlc-create-story-handler.js.map +1 -0
  37. package/dist/handlers/sdlc-dev-story-handler.d.ts +121 -0
  38. package/dist/handlers/sdlc-dev-story-handler.d.ts.map +1 -0
  39. package/dist/handlers/sdlc-dev-story-handler.js +288 -0
  40. package/dist/handlers/sdlc-dev-story-handler.js.map +1 -0
  41. package/dist/handlers/sdlc-phase-handler.d.ts +32 -0
  42. package/dist/handlers/sdlc-phase-handler.d.ts.map +1 -0
  43. package/dist/handlers/sdlc-phase-handler.js +166 -0
  44. package/dist/handlers/sdlc-phase-handler.js.map +1 -0
  45. package/dist/handlers/types.d.ts +132 -0
  46. package/dist/handlers/types.d.ts.map +1 -0
  47. package/dist/handlers/types.js +10 -0
  48. package/dist/handlers/types.js.map +1 -0
  49. package/dist/index.d.ts +8 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +14 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/learning/failure-classifier.d.ts +23 -0
  54. package/dist/learning/failure-classifier.d.ts.map +1 -0
  55. package/dist/learning/failure-classifier.js +75 -0
  56. package/dist/learning/failure-classifier.js.map +1 -0
  57. package/dist/learning/finding-classifier.d.ts +25 -0
  58. package/dist/learning/finding-classifier.d.ts.map +1 -0
  59. package/dist/learning/finding-classifier.js +37 -0
  60. package/dist/learning/finding-classifier.js.map +1 -0
  61. package/dist/learning/finding-lifecycle.d.ts +69 -0
  62. package/dist/learning/finding-lifecycle.d.ts.map +1 -0
  63. package/dist/learning/finding-lifecycle.js +162 -0
  64. package/dist/learning/finding-lifecycle.js.map +1 -0
  65. package/dist/learning/finding-store.d.ts +16 -0
  66. package/dist/learning/finding-store.d.ts.map +1 -0
  67. package/dist/learning/finding-store.js +26 -0
  68. package/dist/learning/finding-store.js.map +1 -0
  69. package/dist/learning/findings-injector.d.ts +34 -0
  70. package/dist/learning/findings-injector.d.ts.map +1 -0
  71. package/dist/learning/findings-injector.js +140 -0
  72. package/dist/learning/findings-injector.js.map +1 -0
  73. package/dist/learning/index.d.ts +8 -0
  74. package/dist/learning/index.d.ts.map +1 -0
  75. package/dist/learning/index.js +10 -0
  76. package/dist/learning/index.js.map +1 -0
  77. package/dist/learning/relevance-scorer.d.ts +25 -0
  78. package/dist/learning/relevance-scorer.d.ts.map +1 -0
  79. package/dist/learning/relevance-scorer.js +49 -0
  80. package/dist/learning/relevance-scorer.js.map +1 -0
  81. package/dist/learning/types.d.ts +55 -0
  82. package/dist/learning/types.d.ts.map +1 -0
  83. package/dist/learning/types.js +36 -0
  84. package/dist/learning/types.js.map +1 -0
  85. package/dist/orchestrator/graph-orchestrator.d.ts +208 -0
  86. package/dist/orchestrator/graph-orchestrator.d.ts.map +1 -0
  87. package/dist/orchestrator/graph-orchestrator.js +213 -0
  88. package/dist/orchestrator/graph-orchestrator.js.map +1 -0
  89. package/dist/run-manifest/cli-flags.d.ts +11 -0
  90. package/dist/run-manifest/cli-flags.d.ts.map +1 -0
  91. package/dist/run-manifest/cli-flags.js +10 -0
  92. package/dist/run-manifest/cli-flags.js.map +1 -0
  93. package/dist/run-manifest/index.d.ts +10 -0
  94. package/dist/run-manifest/index.d.ts.map +1 -0
  95. package/dist/run-manifest/index.js +10 -0
  96. package/dist/run-manifest/index.js.map +1 -0
  97. package/dist/run-model/cli-flags.d.ts +27 -0
  98. package/dist/run-model/cli-flags.d.ts.map +1 -0
  99. package/dist/run-model/cli-flags.js +31 -0
  100. package/dist/run-model/cli-flags.js.map +1 -0
  101. package/dist/run-model/index.d.ts +21 -0
  102. package/dist/run-model/index.d.ts.map +1 -0
  103. package/dist/run-model/index.js +19 -0
  104. package/dist/run-model/index.js.map +1 -0
  105. package/dist/run-model/per-story-state.d.ts +62 -0
  106. package/dist/run-model/per-story-state.d.ts.map +1 -0
  107. package/dist/run-model/per-story-state.js +70 -0
  108. package/dist/run-model/per-story-state.js.map +1 -0
  109. package/dist/run-model/recovery-history.d.ts +56 -0
  110. package/dist/run-model/recovery-history.d.ts.map +1 -0
  111. package/dist/run-model/recovery-history.js +83 -0
  112. package/dist/run-model/recovery-history.js.map +1 -0
  113. package/dist/run-model/run-manifest.d.ts +146 -0
  114. package/dist/run-model/run-manifest.d.ts.map +1 -0
  115. package/dist/run-model/run-manifest.js +481 -0
  116. package/dist/run-model/run-manifest.js.map +1 -0
  117. package/dist/run-model/schemas.d.ts +117 -0
  118. package/dist/run-model/schemas.d.ts.map +1 -0
  119. package/dist/run-model/schemas.js +83 -0
  120. package/dist/run-model/schemas.js.map +1 -0
  121. package/dist/run-model/supervisor-lock.d.ts +104 -0
  122. package/dist/run-model/supervisor-lock.d.ts.map +1 -0
  123. package/dist/run-model/supervisor-lock.js +284 -0
  124. package/dist/run-model/supervisor-lock.js.map +1 -0
  125. package/dist/run-model/types.d.ts +74 -0
  126. package/dist/run-model/types.d.ts.map +1 -0
  127. package/dist/run-model/types.js +8 -0
  128. package/dist/run-model/types.js.map +1 -0
  129. package/dist/run-model/verification-result.d.ts +60 -0
  130. package/dist/run-model/verification-result.d.ts.map +1 -0
  131. package/dist/run-model/verification-result.js +55 -0
  132. package/dist/run-model/verification-result.js.map +1 -0
  133. package/dist/verification/checks/acceptance-criteria-evidence-check.d.ts +21 -0
  134. package/dist/verification/checks/acceptance-criteria-evidence-check.d.ts.map +1 -0
  135. package/dist/verification/checks/acceptance-criteria-evidence-check.js +159 -0
  136. package/dist/verification/checks/acceptance-criteria-evidence-check.js.map +1 -0
  137. package/dist/verification/checks/build-check.d.ts +52 -0
  138. package/dist/verification/checks/build-check.d.ts.map +1 -0
  139. package/dist/verification/checks/build-check.js +160 -0
  140. package/dist/verification/checks/build-check.js.map +1 -0
  141. package/dist/verification/checks/index.d.ts +15 -0
  142. package/dist/verification/checks/index.d.ts.map +1 -0
  143. package/dist/verification/checks/index.js +15 -0
  144. package/dist/verification/checks/index.js.map +1 -0
  145. package/dist/verification/checks/phantom-review-check.d.ts +29 -0
  146. package/dist/verification/checks/phantom-review-check.d.ts.map +1 -0
  147. package/dist/verification/checks/phantom-review-check.js +70 -0
  148. package/dist/verification/checks/phantom-review-check.js.map +1 -0
  149. package/dist/verification/checks/trivial-output-check.d.ts +47 -0
  150. package/dist/verification/checks/trivial-output-check.d.ts.map +1 -0
  151. package/dist/verification/checks/trivial-output-check.js +72 -0
  152. package/dist/verification/checks/trivial-output-check.js.map +1 -0
  153. package/dist/verification/index.d.ts +13 -0
  154. package/dist/verification/index.d.ts.map +1 -0
  155. package/dist/verification/index.js +13 -0
  156. package/dist/verification/index.js.map +1 -0
  157. package/dist/verification/types.d.ts +149 -0
  158. package/dist/verification/types.d.ts.map +1 -0
  159. package/dist/verification/types.js +12 -0
  160. package/dist/verification/types.js.map +1 -0
  161. package/dist/verification/verification-pipeline.d.ts +65 -0
  162. package/dist/verification/verification-pipeline.d.ts.map +1 -0
  163. package/dist/verification/verification-pipeline.js +149 -0
  164. package/dist/verification/verification-pipeline.js.map +1 -0
  165. package/graphs/sdlc-pipeline.dot +42 -0
  166. package/package.json +22 -0
@@ -0,0 +1,83 @@
1
+ /**
2
+ * RunManifest Zod schemas — Story 52-1 / Story 52-8.
3
+ *
4
+ * Provides runtime validation for the run manifest file.
5
+ * All schemas mirror the TypeScript interfaces in `types.ts`.
6
+ */
7
+ import { z } from 'zod';
8
+ import { CliFlagsSchema } from './cli-flags.js';
9
+ import { PerStoryStateSchema } from './per-story-state.js';
10
+ import { RecoveryEntrySchema, CostAccumulationSchema } from './recovery-history.js';
11
+ // Re-export for convenience (Story 52-8)
12
+ export { RecoveryEntrySchema, CostAccumulationSchema } from './recovery-history.js';
13
+ // ---------------------------------------------------------------------------
14
+ // Sub-schemas
15
+ // ---------------------------------------------------------------------------
16
+ /**
17
+ * Schema for a pending supervisor proposal.
18
+ * Uses z.union for extensible type field (follows v0.19.6 ReadinessFindingCategory pattern).
19
+ */
20
+ export const ProposalSchema = z.object({
21
+ id: z.string(),
22
+ created_at: z.string(),
23
+ description: z.string(),
24
+ type: z.union([
25
+ z.literal('retry'),
26
+ z.literal('fix'),
27
+ z.literal('escalate'),
28
+ z.literal('skip'),
29
+ z.string(), // extensible — accepts unknown future types
30
+ ]),
31
+ story_key: z.string().optional(),
32
+ payload: z.record(z.string(), z.unknown()).optional(),
33
+ });
34
+ // ---------------------------------------------------------------------------
35
+ // RunManifestSchema — primary schema
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * Zod schema for the full run manifest data.
39
+ * Validated on every read; write validates via JSON round-trip.
40
+ *
41
+ * `cost_accumulation` uses `.default({ per_story: {}, run_total: 0 })` so
42
+ * pre-Phase-D manifests that omit this field parse without error (AC7).
43
+ */
44
+ export const RunManifestSchema = z.object({
45
+ run_id: z.string(),
46
+ // Story 52-3: validate cli_flags with CliFlagsSchema so unknown halt_on values
47
+ // (e.g. 'severe') are caught at deserialization time (AC7). Unknown keys are
48
+ // stripped silently (Zod default). Output is cast back to Record<string,unknown>
49
+ // so RunManifestData.cli_flags type is unchanged for all existing consumers.
50
+ cli_flags: CliFlagsSchema.transform((v) => v),
51
+ story_scope: z.array(z.string()),
52
+ supervisor_pid: z.number().nullable(),
53
+ supervisor_session_id: z.string().nullable(),
54
+ per_story_state: z.record(z.string(), PerStoryStateSchema),
55
+ // Story 52-8: typed RecoveryEntry array (replaces placeholder from 52-1)
56
+ recovery_history: z.array(RecoveryEntrySchema),
57
+ // Story 52-8: typed CostAccumulation with .default() for backward compatibility.
58
+ // Pre-Phase-D manifests that omit cost_accumulation parse without error (AC7).
59
+ cost_accumulation: CostAccumulationSchema.default({ per_story: {}, run_total: 0 }),
60
+ pending_proposals: z.array(ProposalSchema),
61
+ generation: z.number().int().nonnegative(),
62
+ created_at: z.string(),
63
+ updated_at: z.string(),
64
+ });
65
+ // ---------------------------------------------------------------------------
66
+ // ManifestReadError
67
+ // ---------------------------------------------------------------------------
68
+ /**
69
+ * Error thrown when all read sources for a manifest fail.
70
+ *
71
+ * Includes `attempted_sources` listing each path/source tried,
72
+ * so callers can diagnose which files were corrupt or missing.
73
+ */
74
+ export class ManifestReadError extends Error {
75
+ /** List of sources (file paths or source names) that were attempted. */
76
+ attempted_sources;
77
+ constructor(message, attempted_sources) {
78
+ super(message);
79
+ this.name = 'ManifestReadError';
80
+ this.attempted_sources = attempted_sources;
81
+ }
82
+ }
83
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/run-model/schemas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * SupervisorLock — advisory lock preventing concurrent supervisor attachment.
3
+ *
4
+ * Story 52-2: Supervisor Locking and Ownership.
5
+ *
6
+ * Primary path: uses atomic exclusive file creation (`O_CREAT | O_EXCL` via
7
+ * the `'wx'` flag) on `.substrate/runs/{run-id}.lock` to simulate advisory
8
+ * flock. On filesystems that do not support this (ENOSYS / EOPNOTSUPP), the
9
+ * implementation automatically degrades to a PID-file at
10
+ * `.substrate/runs/{run-id}.pid`.
11
+ *
12
+ * Error message format (AC3, FR-R3 — must be exact):
13
+ * "Run {run-id} is already supervised by PID {pid}. Use --force to take over."
14
+ */
15
+ import type { ILogger } from '@substrate-ai/core';
16
+ import type { RunManifest } from './run-manifest.js';
17
+ /**
18
+ * Options for `SupervisorLock.acquire()`.
19
+ */
20
+ export interface SupervisorLockOptions {
21
+ /** When true, forcefully evict an existing supervisor (SIGTERM + wait). */
22
+ force?: boolean;
23
+ }
24
+ /**
25
+ * Advisory lock for the supervisor process.
26
+ *
27
+ * Usage:
28
+ * ```ts
29
+ * const lock = new SupervisorLock(runId, manifest)
30
+ * await lock.acquire(process.pid, sessionId, { force: opts.force })
31
+ * // ... supervisor work ...
32
+ * await lock.release()
33
+ * ```
34
+ *
35
+ * The lock is automatically released on process exit if `registerExitHandlers()`
36
+ * is called, or if the consuming code registers `process.once('exit', ...)`.
37
+ */
38
+ export declare class SupervisorLock {
39
+ readonly runId: string;
40
+ private readonly manifest;
41
+ private readonly baseDir;
42
+ private readonly logger;
43
+ /** Current lock mode — null until `acquire()` succeeds. */
44
+ private mode;
45
+ /** File handle held open to maintain the advisory lock (flock mode only). */
46
+ private lockHandle;
47
+ constructor(runId: string, manifest: RunManifest, logger?: ILogger);
48
+ /** Advisory lock file path (primary path). */
49
+ get lockPath(): string;
50
+ /** PID-file path (fallback path). */
51
+ get pidPath(): string;
52
+ /**
53
+ * Acquire exclusive ownership of the run.
54
+ *
55
+ * Attempts to open `.substrate/runs/{run-id}.lock` with `O_CREAT | O_EXCL`
56
+ * (the `'wx'` flag), which succeeds atomically only if the file does not
57
+ * exist. On success, writes `supervisor_pid` and `supervisor_session_id` to
58
+ * the manifest.
59
+ *
60
+ * On EEXIST (file exists → contended): reads the manifest's `supervisor_pid`,
61
+ * checks if the holder process is alive, and either throws a prescribed
62
+ * rejection error or evicts the holder (if `force: true`).
63
+ *
64
+ * On ENOSYS or EOPNOTSUPP (filesystem does not support exclusive open): logs
65
+ * a `warn`-level message and falls back to PID-file ownership.
66
+ *
67
+ * @throws Error with exact message: "Run {id} is already supervised by PID {pid}. Use --force to take over."
68
+ */
69
+ acquire(pid: number, sessionId: string, opts?: SupervisorLockOptions): Promise<void>;
70
+ /**
71
+ * Release ownership of the run.
72
+ *
73
+ * Removes the lock file (flock mode) or PID-file (fallback mode) and clears
74
+ * `supervisor_pid` / `supervisor_session_id` in the manifest atomically.
75
+ *
76
+ * Safe to call multiple times; subsequent calls are no-ops.
77
+ */
78
+ release(): Promise<void>;
79
+ /**
80
+ * Acquire ownership using a PID-file at `.substrate/runs/{run-id}.pid`.
81
+ *
82
+ * If the PID-file exists:
83
+ * - Dead PID (ESRCH from `kill(pid, 0)`) → overwrite without force (AC5)
84
+ * - Alive PID without force → throw prescribed rejection error (AC3)
85
+ * - Alive PID with force → SIGTERM + wait, then proceed (AC4)
86
+ */
87
+ private acquireViaPidFile;
88
+ private releaseViaPidFile;
89
+ /**
90
+ * Send SIGTERM to the existing supervisor and wait up to 500ms for it to exit.
91
+ *
92
+ * @throws Error if the process is still alive after 500ms.
93
+ */
94
+ private forceKillOwner;
95
+ /**
96
+ * Test whether a PID is alive by sending signal 0.
97
+ *
98
+ * Returns true if the process exists, false if ESRCH (not found).
99
+ * Other errors (e.g. EPERM) are treated as "alive" to avoid false stale
100
+ * detections when we lack permission to signal the process.
101
+ */
102
+ private isPidAlive;
103
+ }
104
+ //# sourceMappingURL=supervisor-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor-lock.d.ts","sourceRoot":"","sources":["../../src/run-model/supervisor-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AASpD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AASD;;;;;;;;;;;;;GAaG;AACH,qBAAa,cAAc;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,2DAA2D;IAC3D,OAAO,CAAC,IAAI,CAAwB;IAEpC,6EAA6E;IAC7E,OAAO,CAAC,UAAU,CAA0B;gBAEhC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,OAAO;IAYlE,8CAA8C;IAC9C,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,qCAAqC;IACrC,IAAI,OAAO,IAAI,MAAM,CAEpB;IAMD;;;;;;;;;;;;;;;;OAgBG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IA8F1F;;;;;;;OAOG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B9B;;;;;;;OAOG;YACW,iBAAiB;YA6CjB,iBAAiB;IAQ/B;;;;OAIG;YACW,cAAc;IAmB5B;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;CASnB"}
@@ -0,0 +1,284 @@
1
+ /**
2
+ * SupervisorLock — advisory lock preventing concurrent supervisor attachment.
3
+ *
4
+ * Story 52-2: Supervisor Locking and Ownership.
5
+ *
6
+ * Primary path: uses atomic exclusive file creation (`O_CREAT | O_EXCL` via
7
+ * the `'wx'` flag) on `.substrate/runs/{run-id}.lock` to simulate advisory
8
+ * flock. On filesystems that do not support this (ENOSYS / EOPNOTSUPP), the
9
+ * implementation automatically degrades to a PID-file at
10
+ * `.substrate/runs/{run-id}.pid`.
11
+ *
12
+ * Error message format (AC3, FR-R3 — must be exact):
13
+ * "Run {run-id} is already supervised by PID {pid}. Use --force to take over."
14
+ */
15
+ import { mkdir, open, unlink, readFile, writeFile } from 'node:fs/promises';
16
+ import { join } from 'node:path';
17
+ // Module-level default logger — consumers may inject their own via the constructor.
18
+ const defaultLogger = console;
19
+ // ---------------------------------------------------------------------------
20
+ // SupervisorLock
21
+ // ---------------------------------------------------------------------------
22
+ /**
23
+ * Advisory lock for the supervisor process.
24
+ *
25
+ * Usage:
26
+ * ```ts
27
+ * const lock = new SupervisorLock(runId, manifest)
28
+ * await lock.acquire(process.pid, sessionId, { force: opts.force })
29
+ * // ... supervisor work ...
30
+ * await lock.release()
31
+ * ```
32
+ *
33
+ * The lock is automatically released on process exit if `registerExitHandlers()`
34
+ * is called, or if the consuming code registers `process.once('exit', ...)`.
35
+ */
36
+ export class SupervisorLock {
37
+ runId;
38
+ manifest;
39
+ baseDir;
40
+ logger;
41
+ /** Current lock mode — null until `acquire()` succeeds. */
42
+ mode = null;
43
+ /** File handle held open to maintain the advisory lock (flock mode only). */
44
+ lockHandle = null;
45
+ constructor(runId, manifest, logger) {
46
+ this.runId = runId;
47
+ this.manifest = manifest;
48
+ // Co-locate lock files with the manifest
49
+ this.baseDir = manifest.baseDir;
50
+ this.logger = logger ?? defaultLogger;
51
+ }
52
+ // -------------------------------------------------------------------------
53
+ // Path helpers
54
+ // -------------------------------------------------------------------------
55
+ /** Advisory lock file path (primary path). */
56
+ get lockPath() {
57
+ return join(this.baseDir, `${this.runId}.lock`);
58
+ }
59
+ /** PID-file path (fallback path). */
60
+ get pidPath() {
61
+ return join(this.baseDir, `${this.runId}.pid`);
62
+ }
63
+ // -------------------------------------------------------------------------
64
+ // acquire()
65
+ // -------------------------------------------------------------------------
66
+ /**
67
+ * Acquire exclusive ownership of the run.
68
+ *
69
+ * Attempts to open `.substrate/runs/{run-id}.lock` with `O_CREAT | O_EXCL`
70
+ * (the `'wx'` flag), which succeeds atomically only if the file does not
71
+ * exist. On success, writes `supervisor_pid` and `supervisor_session_id` to
72
+ * the manifest.
73
+ *
74
+ * On EEXIST (file exists → contended): reads the manifest's `supervisor_pid`,
75
+ * checks if the holder process is alive, and either throws a prescribed
76
+ * rejection error or evicts the holder (if `force: true`).
77
+ *
78
+ * On ENOSYS or EOPNOTSUPP (filesystem does not support exclusive open): logs
79
+ * a `warn`-level message and falls back to PID-file ownership.
80
+ *
81
+ * @throws Error with exact message: "Run {id} is already supervised by PID {pid}. Use --force to take over."
82
+ */
83
+ async acquire(pid, sessionId, opts) {
84
+ const force = opts?.force ?? false;
85
+ // Ensure the run directory exists
86
+ await mkdir(this.baseDir, { recursive: true });
87
+ let fh;
88
+ try {
89
+ // 'wx' = O_CREAT | O_EXCL | O_WRONLY — atomic exclusive creation
90
+ // Succeeds only if the file does not already exist (no wait).
91
+ fh = await open(this.lockPath, 'wx');
92
+ }
93
+ catch (err) {
94
+ const e = err;
95
+ if (e.code === 'ENOSYS' || e.code === 'EOPNOTSUPP') {
96
+ // Filesystem does not support exclusive file creation → PID-file fallback
97
+ this.logger.warn(`[SupervisorLock] flock not available on this filesystem (${e.code}). ` +
98
+ `Falling back to PID-file for run ${this.runId}.`);
99
+ await this.acquireViaPidFile(pid, sessionId, opts);
100
+ return;
101
+ }
102
+ if (e.code === 'EEXIST') {
103
+ // Lock file already exists — determine if the holder is alive
104
+ let existingPid = null;
105
+ try {
106
+ const data = await this.manifest.read();
107
+ existingPid = data.supervisor_pid;
108
+ }
109
+ catch {
110
+ // Manifest unreadable — treat as stale lock
111
+ }
112
+ if (existingPid === null) {
113
+ // No PID in manifest → stale lock file from a crashed supervisor.
114
+ // Remove it and retry acquisition.
115
+ await unlink(this.lockPath).catch(() => undefined);
116
+ await this.acquire(pid, sessionId, opts);
117
+ return;
118
+ }
119
+ // Check if the recorded PID is still alive
120
+ const isAlive = this.isPidAlive(existingPid);
121
+ if (!isAlive) {
122
+ // Stale lock — dead process left the lock file behind
123
+ await unlink(this.lockPath).catch(() => undefined);
124
+ await this.acquire(pid, sessionId, opts);
125
+ return;
126
+ }
127
+ // Live holder — force eviction or reject
128
+ if (force) {
129
+ await this.forceKillOwner(existingPid);
130
+ await unlink(this.lockPath).catch(() => undefined);
131
+ await this.acquire(pid, sessionId, opts);
132
+ return;
133
+ }
134
+ throw new Error(`Run ${this.runId} is already supervised by PID ${existingPid}. Use --force to take over.`);
135
+ }
136
+ // Unknown error — propagate
137
+ throw err;
138
+ }
139
+ // Assign handle and mode before any post-open operations so that release()
140
+ // can always clean up if manifest.update() throws.
141
+ this.lockHandle = fh;
142
+ this.mode = 'flock';
143
+ try {
144
+ // Write our PID to the lock file for diagnostics (best-effort)
145
+ await fh.write(String(pid), 0, 'utf-8');
146
+ // Record ownership in the manifest
147
+ await this.manifest.update({ supervisor_pid: pid, supervisor_session_id: sessionId });
148
+ }
149
+ catch (postOpenErr) {
150
+ // Cleanup: close handle and remove the lock file so we don't leave
151
+ // a stale exclusive file behind on partial failure.
152
+ try {
153
+ await fh.close();
154
+ }
155
+ catch { /* ignore close error */ }
156
+ this.lockHandle = null;
157
+ this.mode = null;
158
+ await unlink(this.lockPath).catch(() => undefined);
159
+ throw postOpenErr;
160
+ }
161
+ }
162
+ // -------------------------------------------------------------------------
163
+ // release()
164
+ // -------------------------------------------------------------------------
165
+ /**
166
+ * Release ownership of the run.
167
+ *
168
+ * Removes the lock file (flock mode) or PID-file (fallback mode) and clears
169
+ * `supervisor_pid` / `supervisor_session_id` in the manifest atomically.
170
+ *
171
+ * Safe to call multiple times; subsequent calls are no-ops.
172
+ */
173
+ async release() {
174
+ if (this.mode === 'flock') {
175
+ // Close the file handle first, then unlink
176
+ if (this.lockHandle !== null) {
177
+ try {
178
+ await this.lockHandle.close();
179
+ }
180
+ catch {
181
+ // Best-effort — handle may already be closed
182
+ }
183
+ this.lockHandle = null;
184
+ }
185
+ await unlink(this.lockPath).catch(() => undefined);
186
+ }
187
+ else if (this.mode === 'pid-file') {
188
+ await this.releaseViaPidFile();
189
+ }
190
+ this.mode = null;
191
+ // Clear ownership fields in the manifest
192
+ await this.manifest.update({ supervisor_pid: null, supervisor_session_id: null });
193
+ }
194
+ // -------------------------------------------------------------------------
195
+ // Private: PID-file fallback (AC2, AC5)
196
+ // -------------------------------------------------------------------------
197
+ /**
198
+ * Acquire ownership using a PID-file at `.substrate/runs/{run-id}.pid`.
199
+ *
200
+ * If the PID-file exists:
201
+ * - Dead PID (ESRCH from `kill(pid, 0)`) → overwrite without force (AC5)
202
+ * - Alive PID without force → throw prescribed rejection error (AC3)
203
+ * - Alive PID with force → SIGTERM + wait, then proceed (AC4)
204
+ */
205
+ async acquireViaPidFile(pid, sessionId, opts) {
206
+ const force = opts?.force ?? false;
207
+ let existingPid = null;
208
+ // Attempt to read an existing PID-file
209
+ try {
210
+ const content = await readFile(this.pidPath, 'utf-8');
211
+ const parsed = parseInt(content.trim(), 10);
212
+ if (!isNaN(parsed)) {
213
+ existingPid = parsed;
214
+ }
215
+ }
216
+ catch (e) {
217
+ const err = e;
218
+ if (err.code !== 'ENOENT') {
219
+ throw e; // Unexpected error reading PID-file
220
+ }
221
+ // ENOENT: no PID-file → proceed to create one
222
+ }
223
+ if (existingPid !== null) {
224
+ const isAlive = this.isPidAlive(existingPid);
225
+ if (!isAlive) {
226
+ // AC5: stale PID-file (process crashed) → overwrite silently
227
+ }
228
+ else if (force) {
229
+ // AC4: force takeover — evict the existing supervisor
230
+ await this.forceKillOwner(existingPid);
231
+ }
232
+ else {
233
+ // AC3: live supervisor, no force → reject
234
+ throw new Error(`Run ${this.runId} is already supervised by PID ${existingPid}. Use --force to take over.`);
235
+ }
236
+ }
237
+ // Write PID-file atomically (flag 'w' truncates or creates)
238
+ await writeFile(this.pidPath, String(pid), { flag: 'w' });
239
+ await this.manifest.update({ supervisor_pid: pid, supervisor_session_id: sessionId });
240
+ this.mode = 'pid-file';
241
+ }
242
+ async releaseViaPidFile() {
243
+ await unlink(this.pidPath).catch(() => undefined);
244
+ }
245
+ // -------------------------------------------------------------------------
246
+ // Private: force eviction (AC4)
247
+ // -------------------------------------------------------------------------
248
+ /**
249
+ * Send SIGTERM to the existing supervisor and wait up to 500ms for it to exit.
250
+ *
251
+ * @throws Error if the process is still alive after 500ms.
252
+ */
253
+ async forceKillOwner(existingPid) {
254
+ process.kill(existingPid, 'SIGTERM');
255
+ // Brief settle period — give the process time to clean up
256
+ await new Promise((resolve) => setTimeout(resolve, 500));
257
+ // Confirm the process has exited
258
+ const stillAlive = this.isPidAlive(existingPid);
259
+ if (stillAlive) {
260
+ throw new Error(`Existing supervisor PID ${existingPid} did not exit after SIGTERM. Kill manually and retry.`);
261
+ }
262
+ }
263
+ // -------------------------------------------------------------------------
264
+ // Private: liveness check
265
+ // -------------------------------------------------------------------------
266
+ /**
267
+ * Test whether a PID is alive by sending signal 0.
268
+ *
269
+ * Returns true if the process exists, false if ESRCH (not found).
270
+ * Other errors (e.g. EPERM) are treated as "alive" to avoid false stale
271
+ * detections when we lack permission to signal the process.
272
+ */
273
+ isPidAlive(pid) {
274
+ try {
275
+ process.kill(pid, 0);
276
+ return true;
277
+ }
278
+ catch (e) {
279
+ const err = e;
280
+ return err.code !== 'ESRCH';
281
+ }
282
+ }
283
+ }
284
+ //# sourceMappingURL=supervisor-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor-lock.js","sourceRoot":"","sources":["../../src/run-model/supervisor-lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * RunManifest type definitions — Story 52-1 / Story 52-8.
3
+ *
4
+ * Provides TypeScript interfaces for the run manifest file stored at
5
+ * `.substrate/runs/{run-id}.json`.
6
+ */
7
+ import type { PerStoryState } from './per-story-state.js';
8
+ import type { RecoveryEntry, CostAccumulation } from './recovery-history.js';
9
+ export type { RecoveryEntry, CostAccumulation };
10
+ /**
11
+ * A pending supervisor proposal awaiting user confirmation.
12
+ */
13
+ export interface Proposal {
14
+ /** Unique proposal ID. */
15
+ id: string;
16
+ /** ISO-8601 timestamp when the proposal was created. */
17
+ created_at: string;
18
+ /** Short description of what is being proposed. */
19
+ description: string;
20
+ /** Proposal type (e.g. 'retry', 'fix', 'escalate'). */
21
+ type: string;
22
+ /** Story key this proposal pertains to, if any. */
23
+ story_key?: string;
24
+ /** Additional payload data for the proposal. */
25
+ payload?: Record<string, unknown>;
26
+ }
27
+ /**
28
+ * Full data shape for a run manifest stored on disk.
29
+ *
30
+ * All fields are required; optional values use `null`.
31
+ * `per_story_state` is typed as `Record<string, PerStoryState>` (story 52-4).
32
+ * Each key is a story key (e.g., '52-1'); the value tracks full per-story
33
+ * lifecycle state for manifest consumers across Epics 52–54.
34
+ */
35
+ export interface RunManifestData {
36
+ /** Unique run identifier (UUID). */
37
+ run_id: string;
38
+ /** CLI flags used to start this run. */
39
+ cli_flags: Record<string, unknown>;
40
+ /** Explicit story scope (empty = all pending stories). */
41
+ story_scope: string[];
42
+ /**
43
+ * Pipeline run status. Authoritative source — Dolt `pipeline_runs.status`
44
+ * is the degraded fallback. Consumers MUST read this field first.
45
+ */
46
+ run_status?: 'running' | 'completed' | 'failed' | 'stopped';
47
+ /**
48
+ * Number of supervisor-triggered restarts for this run.
49
+ * Authoritative source — Dolt `run_metrics.restarts` is the degraded fallback.
50
+ */
51
+ restart_count?: number;
52
+ /** PID of the attached supervisor process, or null if none. */
53
+ supervisor_pid: number | null;
54
+ /** Session ID of the supervisor process, or null if none. */
55
+ supervisor_session_id: string | null;
56
+ /** Per-story state keyed by story key (story 52-4). */
57
+ per_story_state: Record<string, PerStoryState>;
58
+ /** Log of recovery attempts for this run (Story 52-8). */
59
+ recovery_history: RecoveryEntry[];
60
+ /** Accumulated retry cost data (Story 52-8). */
61
+ cost_accumulation: CostAccumulation;
62
+ /** Pending proposals awaiting confirmation. */
63
+ pending_proposals: Proposal[];
64
+ /**
65
+ * Monotonic write counter. Incremented on every successful `write()`.
66
+ * Used to detect which file is newer after a mid-rename crash.
67
+ */
68
+ generation: number;
69
+ /** ISO-8601 timestamp when the manifest was first created. */
70
+ created_at: string;
71
+ /** ISO-8601 timestamp of the most recent write. */
72
+ updated_at: string;
73
+ }
74
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/run-model/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG5E,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAA;AAE/C;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAA;IACnB,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAMD;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAA;IACd,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,0DAA0D;IAC1D,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAA;IAC3D;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,+DAA+D;IAC/D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,6DAA6D;IAC7D,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,uDAAuD;IACvD,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IAC9C,0DAA0D;IAC1D,gBAAgB,EAAE,aAAa,EAAE,CAAA;IACjC,gDAAgD;IAChD,iBAAiB,EAAE,gBAAgB,CAAA;IACnC,+CAA+C;IAC/C,iBAAiB,EAAE,QAAQ,EAAE,CAAA;IAC7B;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAA;CACnB"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * RunManifest type definitions — Story 52-1 / Story 52-8.
3
+ *
4
+ * Provides TypeScript interfaces for the run manifest file stored at
5
+ * `.substrate/runs/{run-id}.json`.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/run-model/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * StoredVerificationSummary Zod schemas — Story 52-7.
3
+ *
4
+ * Defines the typed schemas for persisting VerificationSummary results to the
5
+ * run manifest's `per_story_state[storyKey].verification_result` field.
6
+ *
7
+ * Design notes:
8
+ * - These schemas mirror the shape of VerificationSummary / VerificationCheckResult
9
+ * from packages/sdlc/src/verification/types.ts WITHOUT importing from that module.
10
+ * This avoids a circular import between run-model and verification.
11
+ * - `status` uses `z.enum` (closed set) since verification statuses are fixed at
12
+ * `pass|warn|fail` only — distinct from PerStoryStatusSchema which uses the open
13
+ * extensible union pattern from v0.19.6.
14
+ * - All imports use `.js` extensions per monorepo convention.
15
+ */
16
+ import { z } from 'zod';
17
+ /**
18
+ * Schema for a single per-check verification result stored in the manifest.
19
+ *
20
+ * Mirrors VerificationCheckResult from packages/sdlc/src/verification/types.ts
21
+ * without importing from that module (avoids circular dependency).
22
+ */
23
+ export declare const StoredVerificationCheckResultSchema: z.ZodObject<{
24
+ checkName: z.ZodString;
25
+ status: z.ZodEnum<{
26
+ pass: "pass";
27
+ warn: "warn";
28
+ fail: "fail";
29
+ }>;
30
+ details: z.ZodString;
31
+ duration_ms: z.ZodNumber;
32
+ }, z.core.$strip>;
33
+ export type StoredVerificationCheckResult = z.infer<typeof StoredVerificationCheckResultSchema>;
34
+ /**
35
+ * Schema for the aggregated verification pipeline summary stored in the manifest.
36
+ *
37
+ * Mirrors VerificationSummary from packages/sdlc/src/verification/types.ts
38
+ * without importing from that module (avoids circular dependency).
39
+ */
40
+ export declare const StoredVerificationSummarySchema: z.ZodObject<{
41
+ storyKey: z.ZodString;
42
+ checks: z.ZodArray<z.ZodObject<{
43
+ checkName: z.ZodString;
44
+ status: z.ZodEnum<{
45
+ pass: "pass";
46
+ warn: "warn";
47
+ fail: "fail";
48
+ }>;
49
+ details: z.ZodString;
50
+ duration_ms: z.ZodNumber;
51
+ }, z.core.$strip>>;
52
+ status: z.ZodEnum<{
53
+ pass: "pass";
54
+ warn: "warn";
55
+ fail: "fail";
56
+ }>;
57
+ duration_ms: z.ZodNumber;
58
+ }, z.core.$strip>;
59
+ export type StoredVerificationSummary = z.infer<typeof StoredVerificationSummarySchema>;
60
+ //# sourceMappingURL=verification-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification-result.d.ts","sourceRoot":"","sources":["../../src/run-model/verification-result.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAMvB;;;;;GAKG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;iBAS9C,CAAA;AAEF,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA;AAM/F;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;iBAS1C,CAAA;AAEF,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAA"}