@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.
- package/dist/events.d.ts +336 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +11 -0
- package/dist/events.js.map +1 -0
- package/dist/gating/conflict-detector.d.ts +59 -0
- package/dist/gating/conflict-detector.d.ts.map +1 -0
- package/dist/gating/conflict-detector.js +101 -0
- package/dist/gating/conflict-detector.js.map +1 -0
- package/dist/gating/dispatch-gate.d.ts +42 -0
- package/dist/gating/dispatch-gate.d.ts.map +1 -0
- package/dist/gating/dispatch-gate.js +197 -0
- package/dist/gating/dispatch-gate.js.map +1 -0
- package/dist/gating/index.d.ts +9 -0
- package/dist/gating/index.d.ts.map +1 -0
- package/dist/gating/index.js +8 -0
- package/dist/gating/index.js.map +1 -0
- package/dist/gating/types.d.ts +98 -0
- package/dist/gating/types.d.ts.map +1 -0
- package/dist/gating/types.js +8 -0
- package/dist/gating/types.js.map +1 -0
- package/dist/handlers/event-bridge.d.ts +56 -0
- package/dist/handlers/event-bridge.d.ts.map +1 -0
- package/dist/handlers/event-bridge.js +140 -0
- package/dist/handlers/event-bridge.js.map +1 -0
- package/dist/handlers/index.d.ts +15 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +14 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/sdlc-code-review-handler.d.ts +119 -0
- package/dist/handlers/sdlc-code-review-handler.d.ts.map +1 -0
- package/dist/handlers/sdlc-code-review-handler.js +131 -0
- package/dist/handlers/sdlc-code-review-handler.js.map +1 -0
- package/dist/handlers/sdlc-create-story-handler.d.ts +97 -0
- package/dist/handlers/sdlc-create-story-handler.d.ts.map +1 -0
- package/dist/handlers/sdlc-create-story-handler.js +91 -0
- package/dist/handlers/sdlc-create-story-handler.js.map +1 -0
- package/dist/handlers/sdlc-dev-story-handler.d.ts +121 -0
- package/dist/handlers/sdlc-dev-story-handler.d.ts.map +1 -0
- package/dist/handlers/sdlc-dev-story-handler.js +288 -0
- package/dist/handlers/sdlc-dev-story-handler.js.map +1 -0
- package/dist/handlers/sdlc-phase-handler.d.ts +32 -0
- package/dist/handlers/sdlc-phase-handler.d.ts.map +1 -0
- package/dist/handlers/sdlc-phase-handler.js +166 -0
- package/dist/handlers/sdlc-phase-handler.js.map +1 -0
- package/dist/handlers/types.d.ts +132 -0
- package/dist/handlers/types.d.ts.map +1 -0
- package/dist/handlers/types.js +10 -0
- package/dist/handlers/types.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/learning/failure-classifier.d.ts +23 -0
- package/dist/learning/failure-classifier.d.ts.map +1 -0
- package/dist/learning/failure-classifier.js +75 -0
- package/dist/learning/failure-classifier.js.map +1 -0
- package/dist/learning/finding-classifier.d.ts +25 -0
- package/dist/learning/finding-classifier.d.ts.map +1 -0
- package/dist/learning/finding-classifier.js +37 -0
- package/dist/learning/finding-classifier.js.map +1 -0
- package/dist/learning/finding-lifecycle.d.ts +69 -0
- package/dist/learning/finding-lifecycle.d.ts.map +1 -0
- package/dist/learning/finding-lifecycle.js +162 -0
- package/dist/learning/finding-lifecycle.js.map +1 -0
- package/dist/learning/finding-store.d.ts +16 -0
- package/dist/learning/finding-store.d.ts.map +1 -0
- package/dist/learning/finding-store.js +26 -0
- package/dist/learning/finding-store.js.map +1 -0
- package/dist/learning/findings-injector.d.ts +34 -0
- package/dist/learning/findings-injector.d.ts.map +1 -0
- package/dist/learning/findings-injector.js +140 -0
- package/dist/learning/findings-injector.js.map +1 -0
- package/dist/learning/index.d.ts +8 -0
- package/dist/learning/index.d.ts.map +1 -0
- package/dist/learning/index.js +10 -0
- package/dist/learning/index.js.map +1 -0
- package/dist/learning/relevance-scorer.d.ts +25 -0
- package/dist/learning/relevance-scorer.d.ts.map +1 -0
- package/dist/learning/relevance-scorer.js +49 -0
- package/dist/learning/relevance-scorer.js.map +1 -0
- package/dist/learning/types.d.ts +55 -0
- package/dist/learning/types.d.ts.map +1 -0
- package/dist/learning/types.js +36 -0
- package/dist/learning/types.js.map +1 -0
- package/dist/orchestrator/graph-orchestrator.d.ts +208 -0
- package/dist/orchestrator/graph-orchestrator.d.ts.map +1 -0
- package/dist/orchestrator/graph-orchestrator.js +213 -0
- package/dist/orchestrator/graph-orchestrator.js.map +1 -0
- package/dist/run-manifest/cli-flags.d.ts +11 -0
- package/dist/run-manifest/cli-flags.d.ts.map +1 -0
- package/dist/run-manifest/cli-flags.js +10 -0
- package/dist/run-manifest/cli-flags.js.map +1 -0
- package/dist/run-manifest/index.d.ts +10 -0
- package/dist/run-manifest/index.d.ts.map +1 -0
- package/dist/run-manifest/index.js +10 -0
- package/dist/run-manifest/index.js.map +1 -0
- package/dist/run-model/cli-flags.d.ts +27 -0
- package/dist/run-model/cli-flags.d.ts.map +1 -0
- package/dist/run-model/cli-flags.js +31 -0
- package/dist/run-model/cli-flags.js.map +1 -0
- package/dist/run-model/index.d.ts +21 -0
- package/dist/run-model/index.d.ts.map +1 -0
- package/dist/run-model/index.js +19 -0
- package/dist/run-model/index.js.map +1 -0
- package/dist/run-model/per-story-state.d.ts +62 -0
- package/dist/run-model/per-story-state.d.ts.map +1 -0
- package/dist/run-model/per-story-state.js +70 -0
- package/dist/run-model/per-story-state.js.map +1 -0
- package/dist/run-model/recovery-history.d.ts +56 -0
- package/dist/run-model/recovery-history.d.ts.map +1 -0
- package/dist/run-model/recovery-history.js +83 -0
- package/dist/run-model/recovery-history.js.map +1 -0
- package/dist/run-model/run-manifest.d.ts +146 -0
- package/dist/run-model/run-manifest.d.ts.map +1 -0
- package/dist/run-model/run-manifest.js +481 -0
- package/dist/run-model/run-manifest.js.map +1 -0
- package/dist/run-model/schemas.d.ts +117 -0
- package/dist/run-model/schemas.d.ts.map +1 -0
- package/dist/run-model/schemas.js +83 -0
- package/dist/run-model/schemas.js.map +1 -0
- package/dist/run-model/supervisor-lock.d.ts +104 -0
- package/dist/run-model/supervisor-lock.d.ts.map +1 -0
- package/dist/run-model/supervisor-lock.js +284 -0
- package/dist/run-model/supervisor-lock.js.map +1 -0
- package/dist/run-model/types.d.ts +74 -0
- package/dist/run-model/types.d.ts.map +1 -0
- package/dist/run-model/types.js +8 -0
- package/dist/run-model/types.js.map +1 -0
- package/dist/run-model/verification-result.d.ts +60 -0
- package/dist/run-model/verification-result.d.ts.map +1 -0
- package/dist/run-model/verification-result.js +55 -0
- package/dist/run-model/verification-result.js.map +1 -0
- package/dist/verification/checks/acceptance-criteria-evidence-check.d.ts +21 -0
- package/dist/verification/checks/acceptance-criteria-evidence-check.d.ts.map +1 -0
- package/dist/verification/checks/acceptance-criteria-evidence-check.js +159 -0
- package/dist/verification/checks/acceptance-criteria-evidence-check.js.map +1 -0
- package/dist/verification/checks/build-check.d.ts +52 -0
- package/dist/verification/checks/build-check.d.ts.map +1 -0
- package/dist/verification/checks/build-check.js +160 -0
- package/dist/verification/checks/build-check.js.map +1 -0
- package/dist/verification/checks/index.d.ts +15 -0
- package/dist/verification/checks/index.d.ts.map +1 -0
- package/dist/verification/checks/index.js +15 -0
- package/dist/verification/checks/index.js.map +1 -0
- package/dist/verification/checks/phantom-review-check.d.ts +29 -0
- package/dist/verification/checks/phantom-review-check.d.ts.map +1 -0
- package/dist/verification/checks/phantom-review-check.js +70 -0
- package/dist/verification/checks/phantom-review-check.js.map +1 -0
- package/dist/verification/checks/trivial-output-check.d.ts +47 -0
- package/dist/verification/checks/trivial-output-check.d.ts.map +1 -0
- package/dist/verification/checks/trivial-output-check.js +72 -0
- package/dist/verification/checks/trivial-output-check.js.map +1 -0
- package/dist/verification/index.d.ts +13 -0
- package/dist/verification/index.d.ts.map +1 -0
- package/dist/verification/index.js +13 -0
- package/dist/verification/index.js.map +1 -0
- package/dist/verification/types.d.ts +149 -0
- package/dist/verification/types.d.ts.map +1 -0
- package/dist/verification/types.js +12 -0
- package/dist/verification/types.js.map +1 -0
- package/dist/verification/verification-pipeline.d.ts +65 -0
- package/dist/verification/verification-pipeline.d.ts.map +1 -0
- package/dist/verification/verification-pipeline.js +149 -0
- package/dist/verification/verification-pipeline.js.map +1 -0
- package/graphs/sdlc-pipeline.dot +42 -0
- 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 @@
|
|
|
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"}
|