opencastle 0.27.0 → 0.27.2
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/bin/cli.mjs +6 -0
- package/dist/cli/agents.d.ts +3 -0
- package/dist/cli/agents.d.ts.map +1 -0
- package/dist/cli/agents.js +161 -0
- package/dist/cli/agents.js.map +1 -0
- package/dist/cli/baselines.d.ts +3 -0
- package/dist/cli/baselines.d.ts.map +1 -0
- package/dist/cli/baselines.js +128 -0
- package/dist/cli/baselines.js.map +1 -0
- package/dist/cli/convoy/dashboard-types.d.ts +146 -0
- package/dist/cli/convoy/dashboard-types.d.ts.map +1 -0
- package/dist/cli/convoy/dashboard-types.js +2 -0
- package/dist/cli/convoy/dashboard-types.js.map +1 -0
- package/dist/cli/convoy/engine.d.ts +67 -2
- package/dist/cli/convoy/engine.d.ts.map +1 -1
- package/dist/cli/convoy/engine.js +2036 -28
- package/dist/cli/convoy/engine.js.map +1 -1
- package/dist/cli/convoy/engine.test.js +1659 -70
- package/dist/cli/convoy/engine.test.js.map +1 -1
- package/dist/cli/convoy/event-schemas.d.ts +9 -0
- package/dist/cli/convoy/event-schemas.d.ts.map +1 -0
- package/dist/cli/convoy/event-schemas.js +185 -0
- package/dist/cli/convoy/event-schemas.js.map +1 -0
- package/dist/cli/convoy/events.d.ts +12 -1
- package/dist/cli/convoy/events.d.ts.map +1 -1
- package/dist/cli/convoy/events.js +186 -13
- package/dist/cli/convoy/events.js.map +1 -1
- package/dist/cli/convoy/events.test.js +325 -28
- package/dist/cli/convoy/events.test.js.map +1 -1
- package/dist/cli/convoy/expertise.d.ts +16 -0
- package/dist/cli/convoy/expertise.d.ts.map +1 -0
- package/dist/cli/convoy/expertise.js +121 -0
- package/dist/cli/convoy/expertise.js.map +1 -0
- package/dist/cli/convoy/expertise.test.d.ts +2 -0
- package/dist/cli/convoy/expertise.test.d.ts.map +1 -0
- package/dist/cli/convoy/expertise.test.js +96 -0
- package/dist/cli/convoy/expertise.test.js.map +1 -0
- package/dist/cli/convoy/export.test.js +1 -0
- package/dist/cli/convoy/export.test.js.map +1 -1
- package/dist/cli/convoy/formula.d.ts +19 -0
- package/dist/cli/convoy/formula.d.ts.map +1 -0
- package/dist/cli/convoy/formula.js +142 -0
- package/dist/cli/convoy/formula.js.map +1 -0
- package/dist/cli/convoy/formula.test.d.ts +2 -0
- package/dist/cli/convoy/formula.test.d.ts.map +1 -0
- package/dist/cli/convoy/formula.test.js +342 -0
- package/dist/cli/convoy/formula.test.js.map +1 -0
- package/dist/cli/convoy/gates.d.ts +128 -0
- package/dist/cli/convoy/gates.d.ts.map +1 -0
- package/dist/cli/convoy/gates.js +606 -0
- package/dist/cli/convoy/gates.js.map +1 -0
- package/dist/cli/convoy/gates.test.d.ts +2 -0
- package/dist/cli/convoy/gates.test.d.ts.map +1 -0
- package/dist/cli/convoy/gates.test.js +976 -0
- package/dist/cli/convoy/gates.test.js.map +1 -0
- package/dist/cli/convoy/health.d.ts +11 -0
- package/dist/cli/convoy/health.d.ts.map +1 -1
- package/dist/cli/convoy/health.js +54 -0
- package/dist/cli/convoy/health.js.map +1 -1
- package/dist/cli/convoy/health.test.js +56 -1
- package/dist/cli/convoy/health.test.js.map +1 -1
- package/dist/cli/convoy/issues.d.ts +8 -0
- package/dist/cli/convoy/issues.d.ts.map +1 -0
- package/dist/cli/convoy/issues.js +98 -0
- package/dist/cli/convoy/issues.js.map +1 -0
- package/dist/cli/convoy/issues.test.d.ts +2 -0
- package/dist/cli/convoy/issues.test.d.ts.map +1 -0
- package/dist/cli/convoy/issues.test.js +107 -0
- package/dist/cli/convoy/issues.test.js.map +1 -0
- package/dist/cli/convoy/knowledge.d.ts +5 -0
- package/dist/cli/convoy/knowledge.d.ts.map +1 -0
- package/dist/cli/convoy/knowledge.js +116 -0
- package/dist/cli/convoy/knowledge.js.map +1 -0
- package/dist/cli/convoy/knowledge.test.d.ts +2 -0
- package/dist/cli/convoy/knowledge.test.d.ts.map +1 -0
- package/dist/cli/convoy/knowledge.test.js +87 -0
- package/dist/cli/convoy/knowledge.test.js.map +1 -0
- package/dist/cli/convoy/lessons.d.ts +17 -0
- package/dist/cli/convoy/lessons.d.ts.map +1 -0
- package/dist/cli/convoy/lessons.js +149 -0
- package/dist/cli/convoy/lessons.js.map +1 -0
- package/dist/cli/convoy/lessons.test.d.ts +2 -0
- package/dist/cli/convoy/lessons.test.d.ts.map +1 -0
- package/dist/cli/convoy/lessons.test.js +135 -0
- package/dist/cli/convoy/lessons.test.js.map +1 -0
- package/dist/cli/convoy/lock.d.ts +13 -0
- package/dist/cli/convoy/lock.d.ts.map +1 -0
- package/dist/cli/convoy/lock.js +88 -0
- package/dist/cli/convoy/lock.js.map +1 -0
- package/dist/cli/convoy/lock.test.d.ts +2 -0
- package/dist/cli/convoy/lock.test.d.ts.map +1 -0
- package/dist/cli/convoy/lock.test.js +136 -0
- package/dist/cli/convoy/lock.test.js.map +1 -0
- package/dist/cli/convoy/log-merge.test.d.ts +2 -0
- package/dist/cli/convoy/log-merge.test.d.ts.map +1 -0
- package/dist/cli/convoy/log-merge.test.js +147 -0
- package/dist/cli/convoy/log-merge.test.js.map +1 -0
- package/dist/cli/convoy/merge.d.ts +4 -0
- package/dist/cli/convoy/merge.d.ts.map +1 -1
- package/dist/cli/convoy/merge.js +18 -1
- package/dist/cli/convoy/merge.js.map +1 -1
- package/dist/cli/convoy/merge.test.js +6 -7
- package/dist/cli/convoy/merge.test.js.map +1 -1
- package/dist/cli/convoy/partition.d.ts +51 -0
- package/dist/cli/convoy/partition.d.ts.map +1 -0
- package/dist/cli/convoy/partition.js +186 -0
- package/dist/cli/convoy/partition.js.map +1 -0
- package/dist/cli/convoy/partition.test.d.ts +2 -0
- package/dist/cli/convoy/partition.test.d.ts.map +1 -0
- package/dist/cli/convoy/partition.test.js +315 -0
- package/dist/cli/convoy/partition.test.js.map +1 -0
- package/dist/cli/convoy/pipeline.test.js +6 -0
- package/dist/cli/convoy/pipeline.test.js.map +1 -1
- package/dist/cli/convoy/store.d.ts +99 -7
- package/dist/cli/convoy/store.d.ts.map +1 -1
- package/dist/cli/convoy/store.js +764 -31
- package/dist/cli/convoy/store.js.map +1 -1
- package/dist/cli/convoy/store.test.js +1810 -18
- package/dist/cli/convoy/store.test.js.map +1 -1
- package/dist/cli/convoy/types.d.ts +427 -5
- package/dist/cli/convoy/types.d.ts.map +1 -1
- package/dist/cli/convoy/types.js +42 -1
- package/dist/cli/convoy/types.js.map +1 -1
- package/dist/cli/log.d.ts +11 -0
- package/dist/cli/log.d.ts.map +1 -1
- package/dist/cli/log.js +114 -2
- package/dist/cli/log.js.map +1 -1
- package/dist/cli/run/adapters/claude.d.ts +2 -0
- package/dist/cli/run/adapters/claude.d.ts.map +1 -1
- package/dist/cli/run/adapters/claude.js +89 -49
- package/dist/cli/run/adapters/claude.js.map +1 -1
- package/dist/cli/run/adapters/claude.test.d.ts +2 -0
- package/dist/cli/run/adapters/claude.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/claude.test.js +205 -0
- package/dist/cli/run/adapters/claude.test.js.map +1 -0
- package/dist/cli/run/adapters/copilot.d.ts +1 -0
- package/dist/cli/run/adapters/copilot.d.ts.map +1 -1
- package/dist/cli/run/adapters/copilot.js +84 -46
- package/dist/cli/run/adapters/copilot.js.map +1 -1
- package/dist/cli/run/adapters/copilot.test.d.ts +2 -0
- package/dist/cli/run/adapters/copilot.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/copilot.test.js +195 -0
- package/dist/cli/run/adapters/copilot.test.js.map +1 -0
- package/dist/cli/run/adapters/cursor.d.ts +1 -0
- package/dist/cli/run/adapters/cursor.d.ts.map +1 -1
- package/dist/cli/run/adapters/cursor.js +83 -47
- package/dist/cli/run/adapters/cursor.js.map +1 -1
- package/dist/cli/run/adapters/cursor.test.d.ts +2 -0
- package/dist/cli/run/adapters/cursor.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/cursor.test.js +129 -0
- package/dist/cli/run/adapters/cursor.test.js.map +1 -0
- package/dist/cli/run/adapters/opencode.d.ts +1 -0
- package/dist/cli/run/adapters/opencode.d.ts.map +1 -1
- package/dist/cli/run/adapters/opencode.js +81 -47
- package/dist/cli/run/adapters/opencode.js.map +1 -1
- package/dist/cli/run/adapters/opencode.test.d.ts +2 -0
- package/dist/cli/run/adapters/opencode.test.d.ts.map +1 -0
- package/dist/cli/run/adapters/opencode.test.js +119 -0
- package/dist/cli/run/adapters/opencode.test.js.map +1 -0
- package/dist/cli/run/executor.js +1 -1
- package/dist/cli/run/executor.js.map +1 -1
- package/dist/cli/run/schema.d.ts.map +1 -1
- package/dist/cli/run/schema.js +245 -4
- package/dist/cli/run/schema.js.map +1 -1
- package/dist/cli/run/schema.test.js +669 -0
- package/dist/cli/run/schema.test.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +362 -22
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/types.d.ts +85 -2
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/watch.d.ts +15 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +279 -0
- package/dist/cli/watch.js.map +1 -0
- package/package.json +5 -1
- package/src/cli/agents.ts +177 -0
- package/src/cli/baselines.ts +143 -0
- package/src/cli/convoy/TELEMETRY.md +203 -0
- package/src/cli/convoy/dashboard-types.ts +141 -0
- package/src/cli/convoy/engine.test.ts +1937 -70
- package/src/cli/convoy/engine.ts +2350 -40
- package/src/cli/convoy/event-schemas.ts +195 -0
- package/src/cli/convoy/events.test.ts +384 -39
- package/src/cli/convoy/events.ts +202 -16
- package/src/cli/convoy/expertise.test.ts +128 -0
- package/src/cli/convoy/expertise.ts +163 -0
- package/src/cli/convoy/export.test.ts +1 -0
- package/src/cli/convoy/formula.test.ts +405 -0
- package/src/cli/convoy/formula.ts +174 -0
- package/src/cli/convoy/gates.test.ts +1169 -0
- package/src/cli/convoy/gates.ts +774 -0
- package/src/cli/convoy/health.test.ts +64 -2
- package/src/cli/convoy/health.ts +80 -2
- package/src/cli/convoy/issues.test.ts +143 -0
- package/src/cli/convoy/issues.ts +136 -0
- package/src/cli/convoy/knowledge.test.ts +101 -0
- package/src/cli/convoy/knowledge.ts +132 -0
- package/src/cli/convoy/lessons.test.ts +188 -0
- package/src/cli/convoy/lessons.ts +164 -0
- package/src/cli/convoy/lock.test.ts +181 -0
- package/src/cli/convoy/lock.ts +103 -0
- package/src/cli/convoy/log-merge.test.ts +179 -0
- package/src/cli/convoy/merge.test.ts +6 -7
- package/src/cli/convoy/merge.ts +19 -1
- package/src/cli/convoy/partition.test.ts +423 -0
- package/src/cli/convoy/partition.ts +232 -0
- package/src/cli/convoy/pipeline.test.ts +6 -0
- package/src/cli/convoy/store.test.ts +2041 -20
- package/src/cli/convoy/store.ts +945 -46
- package/src/cli/convoy/types.ts +278 -4
- package/src/cli/log.ts +120 -2
- package/src/cli/run/adapters/claude.test.ts +234 -0
- package/src/cli/run/adapters/claude.ts +45 -5
- package/src/cli/run/adapters/copilot.test.ts +224 -0
- package/src/cli/run/adapters/copilot.ts +34 -4
- package/src/cli/run/adapters/cursor.test.ts +144 -0
- package/src/cli/run/adapters/cursor.ts +33 -2
- package/src/cli/run/adapters/opencode.test.ts +135 -0
- package/src/cli/run/adapters/opencode.ts +30 -2
- package/src/cli/run/executor.ts +1 -1
- package/src/cli/run/schema.test.ts +758 -0
- package/src/cli/run/schema.ts +300 -25
- package/src/cli/run.ts +341 -21
- package/src/cli/types.ts +86 -1
- package/src/cli/watch.ts +298 -0
- package/src/dashboard/dist/_astro/{index.DtnyD8a5.css → index.6L3_HsPT.css} +1 -1
- package/src/dashboard/dist/data/.gitkeep +0 -0
- package/src/dashboard/dist/data/convoy-list.json +1 -0
- package/src/dashboard/dist/data/overall-stats.json +24 -0
- package/src/dashboard/dist/index.html +701 -3
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/public/data/.gitkeep +0 -0
- package/src/dashboard/public/data/convoy-list.json +1 -0
- package/src/dashboard/public/data/overall-stats.json +24 -0
- package/src/dashboard/scripts/etl.test.ts +210 -0
- package/src/dashboard/scripts/etl.ts +108 -0
- package/src/dashboard/scripts/integration-test.ts +504 -0
- package/src/dashboard/src/pages/index.astro +854 -15
- package/src/dashboard/src/styles/dashboard.css +557 -1
- package/src/orchestrator/prompts/generate-convoy.prompt.md +212 -13
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { statSync, realpathSync, lstatSync, writeFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { normalize, join, resolve } from 'node:path';
|
|
4
|
+
// ── Path normalization ────────────────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* Normalize a file path for partition comparison.
|
|
7
|
+
* - Rejects glob patterns (* or ?)
|
|
8
|
+
* - Strips leading ./ and /
|
|
9
|
+
* - Replaces backslashes with forward slashes
|
|
10
|
+
* - Resolves . and .. via path.normalize()
|
|
11
|
+
* - Preserves trailing slash for directories
|
|
12
|
+
*/
|
|
13
|
+
export function normalizePath(p) {
|
|
14
|
+
if (p.includes('*') || p.includes('?')) {
|
|
15
|
+
throw new Error(`Glob patterns are not allowed in file paths: "${p}"`);
|
|
16
|
+
}
|
|
17
|
+
// Record whether the path indicates a directory (trailing slash)
|
|
18
|
+
const hasTrailingSlash = p.endsWith('/') || p.endsWith('\\');
|
|
19
|
+
// Normalize separators to forward slash
|
|
20
|
+
let result = p.replace(/\\/g, '/');
|
|
21
|
+
// Strip trailing slashes before further processing
|
|
22
|
+
result = result.replace(/\/+$/, '');
|
|
23
|
+
// Strip leading './' (may be multiple, e.g. '././')
|
|
24
|
+
result = result.replace(/^(\.\/)+/, '');
|
|
25
|
+
// Strip leading '/'
|
|
26
|
+
result = result.replace(/^\/+/, '');
|
|
27
|
+
// Reject any .. path segment — even those that would not escape the root.
|
|
28
|
+
// All usage of .. is rejected for safety, not just escaping traversals.
|
|
29
|
+
if (/(^|\/)\.\.(\/|$)/.test(result)) {
|
|
30
|
+
throw new Error(`Path traversal detected: "${p}" resolves to a path containing ".." segments`);
|
|
31
|
+
}
|
|
32
|
+
// Resolve '.' and '..' segments
|
|
33
|
+
result = normalize(result).replace(/\\/g, '/');
|
|
34
|
+
// normalize can introduce leading './' (e.g. for '.') — strip it again
|
|
35
|
+
result = result.replace(/^(\.\/)+/, '');
|
|
36
|
+
result = result.replace(/^\/+/, '');
|
|
37
|
+
// Restore trailing slash for directories (but not when result is '.' or empty)
|
|
38
|
+
if (hasTrailingSlash && result !== '.' && result !== '') {
|
|
39
|
+
result += '/';
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
// ── Overlap detection ─────────────────────────────────────────────────────────
|
|
44
|
+
/**
|
|
45
|
+
* Returns true if path a and path b overlap (exact match or prefix containment).
|
|
46
|
+
* Example: 'src/auth/' overlaps 'src/auth/service.ts' in both directions.
|
|
47
|
+
*/
|
|
48
|
+
export function pathsOverlap(a, b) {
|
|
49
|
+
if (a === b)
|
|
50
|
+
return true;
|
|
51
|
+
// Treat each path as a potential directory prefix
|
|
52
|
+
const aDir = a.endsWith('/') ? a : a + '/';
|
|
53
|
+
const bDir = b.endsWith('/') ? b : b + '/';
|
|
54
|
+
return b.startsWith(aDir) || a.startsWith(bDir);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Validate that tasks within the same parallel phase do not have overlapping file partitions.
|
|
58
|
+
* Tasks in different phases (sequential) are allowed to share files.
|
|
59
|
+
*/
|
|
60
|
+
export function validateFilePartitions(_tasks, phases) {
|
|
61
|
+
const isCaseSensitive = determineFsCaseSensitivity();
|
|
62
|
+
const conflicts = [];
|
|
63
|
+
for (let phaseIdx = 0; phaseIdx < phases.length; phaseIdx++) {
|
|
64
|
+
const phaseTasks = phases[phaseIdx];
|
|
65
|
+
for (let i = 0; i < phaseTasks.length; i++) {
|
|
66
|
+
for (let j = i + 1; j < phaseTasks.length; j++) {
|
|
67
|
+
const taskA = phaseTasks[i];
|
|
68
|
+
const taskB = phaseTasks[j];
|
|
69
|
+
// Empty files arrays are not partitioned — skip
|
|
70
|
+
if (!taskA.files.length || !taskB.files.length)
|
|
71
|
+
continue;
|
|
72
|
+
const normalizedA = taskA.files.map(normalizePath);
|
|
73
|
+
const normalizedB = taskB.files.map(normalizePath);
|
|
74
|
+
const overlapping = [];
|
|
75
|
+
for (const fileA of normalizedA) {
|
|
76
|
+
for (const fileB of normalizedB) {
|
|
77
|
+
const directOverlap = pathsOverlap(fileA, fileB);
|
|
78
|
+
// On case-insensitive filesystems, also check lowercased paths
|
|
79
|
+
const ciOverlap = !isCaseSensitive && pathsOverlap(fileA.toLowerCase(), fileB.toLowerCase());
|
|
80
|
+
if ((directOverlap || ciOverlap) && !overlapping.includes(fileA)) {
|
|
81
|
+
overlapping.push(fileA);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (overlapping.length > 0) {
|
|
86
|
+
conflicts.push({ phase: phaseIdx, taskA: taskA.id, taskB: taskB.id, overlapping });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { valid: conflicts.length === 0, conflicts };
|
|
92
|
+
}
|
|
93
|
+
// ── Filesystem case-sensitivity probe ────────────────────────────────────────
|
|
94
|
+
/**
|
|
95
|
+
* Probe whether the filesystem is case-sensitive by creating a mixed-case temp file
|
|
96
|
+
* and checking if the lowercase path resolves to the same inode.
|
|
97
|
+
*
|
|
98
|
+
* Uses realpathSync per LES-003: on macOS, os.tmpdir() returns /var/... which is a
|
|
99
|
+
* symlink to /private/var/... — realpathSync resolves this to the canonical path.
|
|
100
|
+
*
|
|
101
|
+
* Returns true if case-sensitive (git-compatible default), false if case-insensitive.
|
|
102
|
+
*/
|
|
103
|
+
export function determineFsCaseSensitivity() {
|
|
104
|
+
const base = realpathSync(tmpdir());
|
|
105
|
+
const mixedCase = join(base, `OpenCastle_CaseSensitivity_${Date.now()}`);
|
|
106
|
+
const lowerCase = mixedCase.toLowerCase();
|
|
107
|
+
try {
|
|
108
|
+
writeFileSync(mixedCase, '');
|
|
109
|
+
try {
|
|
110
|
+
const statMixed = statSync(mixedCase);
|
|
111
|
+
const statLower = statSync(lowerCase);
|
|
112
|
+
// Same inode → same file → case-insensitive
|
|
113
|
+
return statMixed.ino !== statLower.ino;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// stat(lowerCase) threw → file not found at lowercase path → case-sensitive
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
try {
|
|
122
|
+
rmSync(mixedCase);
|
|
123
|
+
}
|
|
124
|
+
catch { /* ignore cleanup errors */ }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// ── Symlink security scan ─────────────────────────────────────────────────────
|
|
128
|
+
/**
|
|
129
|
+
* Before task execution: scan each file in the task's files[] partition.
|
|
130
|
+
* If any resolved symlink target escapes the basePath directory, throw symlink_escape.
|
|
131
|
+
*/
|
|
132
|
+
export function scanSymlinks(files, basePath) {
|
|
133
|
+
const realBase = realpathSync(resolve(basePath));
|
|
134
|
+
for (const file of files) {
|
|
135
|
+
const absPath = join(realBase, normalizePath(file));
|
|
136
|
+
let stat;
|
|
137
|
+
try {
|
|
138
|
+
stat = lstatSync(absPath);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
continue; // file doesn't exist yet — skip
|
|
142
|
+
}
|
|
143
|
+
if (stat.isSymbolicLink()) {
|
|
144
|
+
let realTarget;
|
|
145
|
+
try {
|
|
146
|
+
realTarget = realpathSync(absPath);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw new Error(`symlink_escape: symlink at "${file}" could not be resolved`);
|
|
150
|
+
}
|
|
151
|
+
if (!realTarget.startsWith(realBase + '/') && realTarget !== realBase) {
|
|
152
|
+
throw new Error(`symlink_escape: "${file}" is a symlink that resolves outside the partition`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* After task execution: scan files[] in the worktree for new symlinks that escape the partition.
|
|
159
|
+
* Throws symlink_escape_post_task if any symlink target is outside worktreePath.
|
|
160
|
+
*/
|
|
161
|
+
export function scanNewSymlinks(worktreePath, files) {
|
|
162
|
+
const realBase = realpathSync(resolve(worktreePath));
|
|
163
|
+
for (const file of files) {
|
|
164
|
+
const absPath = join(realBase, normalizePath(file));
|
|
165
|
+
let stat;
|
|
166
|
+
try {
|
|
167
|
+
stat = lstatSync(absPath);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (stat.isSymbolicLink()) {
|
|
173
|
+
let realTarget;
|
|
174
|
+
try {
|
|
175
|
+
realTarget = realpathSync(absPath);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
throw new Error(`symlink_escape_post_task: "${file}" is a new symlink that cannot be resolved`);
|
|
179
|
+
}
|
|
180
|
+
if (!realTarget.startsWith(realBase + '/') && realTarget !== realBase) {
|
|
181
|
+
throw new Error(`symlink_escape_post_task: "${file}" is a new symlink that escapes the partition`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=partition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition.js","sourceRoot":"","sources":["../../../src/cli/convoy/partition.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition.test.d.ts","sourceRoot":"","sources":["../../../src/cli/convoy/partition.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
// ── Mock node:fs so all tests run without touching disk ───────────────────────
|
|
3
|
+
// The spy-per-test pattern (afterEach restoreAllMocks) keeps tests isolated.
|
|
4
|
+
vi.mock('node:fs', async (importOriginal) => {
|
|
5
|
+
const actual = await importOriginal();
|
|
6
|
+
return {
|
|
7
|
+
...actual,
|
|
8
|
+
writeFileSync: vi.fn(),
|
|
9
|
+
statSync: vi.fn(),
|
|
10
|
+
rmSync: vi.fn(),
|
|
11
|
+
realpathSync: vi.fn(),
|
|
12
|
+
lstatSync: vi.fn(),
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
// Import after vi.mock so we receive the mocked module
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import { normalizePath, pathsOverlap, validateFilePartitions, determineFsCaseSensitivity, scanSymlinks, scanNewSymlinks, } from './partition.js';
|
|
18
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
19
|
+
function makeTask(id, files, depends_on = []) {
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
prompt: `Prompt for ${id}`,
|
|
23
|
+
agent: 'developer',
|
|
24
|
+
timeout: '30s',
|
|
25
|
+
depends_on,
|
|
26
|
+
files,
|
|
27
|
+
description: '',
|
|
28
|
+
max_retries: 0,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function makeFakeStats(isSymlink, ino = 1) {
|
|
32
|
+
return {
|
|
33
|
+
isSymbolicLink: () => isSymlink,
|
|
34
|
+
ino,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
vi.restoreAllMocks();
|
|
39
|
+
});
|
|
40
|
+
// ── normalizePath ─────────────────────────────────────────────────────────────
|
|
41
|
+
describe('normalizePath', () => {
|
|
42
|
+
it('strips leading ./', () => {
|
|
43
|
+
expect(normalizePath('./src/auth/')).toBe('src/auth/');
|
|
44
|
+
});
|
|
45
|
+
it('strips multiple leading ./', () => {
|
|
46
|
+
expect(normalizePath('././src/auth/service.ts')).toBe('src/auth/service.ts');
|
|
47
|
+
});
|
|
48
|
+
it('strips leading /', () => {
|
|
49
|
+
expect(normalizePath('/src/auth/service.ts')).toBe('src/auth/service.ts');
|
|
50
|
+
});
|
|
51
|
+
it('replaces backslashes with forward slashes', () => {
|
|
52
|
+
expect(normalizePath('src\\auth\\service.ts')).toBe('src/auth/service.ts');
|
|
53
|
+
});
|
|
54
|
+
it('throws for paths containing .. segments (resolves within root)', () => {
|
|
55
|
+
expect(() => normalizePath('src/auth/../lib/index.ts')).toThrow('Path traversal detected');
|
|
56
|
+
});
|
|
57
|
+
it('resolves . segments', () => {
|
|
58
|
+
expect(normalizePath('src/./auth/./service.ts')).toBe('src/auth/service.ts');
|
|
59
|
+
});
|
|
60
|
+
it('preserves trailing slash for directories', () => {
|
|
61
|
+
expect(normalizePath('src/auth/')).toBe('src/auth/');
|
|
62
|
+
});
|
|
63
|
+
it('preserves trailing slash after stripping leading ./', () => {
|
|
64
|
+
expect(normalizePath('./src/auth/')).toBe('src/auth/');
|
|
65
|
+
});
|
|
66
|
+
it('does not add trailing slash for files', () => {
|
|
67
|
+
expect(normalizePath('src/auth/service.ts')).toBe('src/auth/service.ts');
|
|
68
|
+
});
|
|
69
|
+
it('handles backslash-terminated paths as directories', () => {
|
|
70
|
+
expect(normalizePath('src\\auth\\')).toBe('src/auth/');
|
|
71
|
+
});
|
|
72
|
+
it('throws for paths containing .. segments via backslash', () => {
|
|
73
|
+
expect(() => normalizePath('src\\auth\\..\\lib\\')).toThrow('Path traversal detected');
|
|
74
|
+
});
|
|
75
|
+
it('throws for paths containing * glob', () => {
|
|
76
|
+
expect(() => normalizePath('src/**/*.ts')).toThrow('Glob patterns are not allowed in file paths: "src/**/*.ts"');
|
|
77
|
+
});
|
|
78
|
+
it('throws for paths containing ? glob', () => {
|
|
79
|
+
expect(() => normalizePath('src/auth?.ts')).toThrow('Glob patterns are not allowed in file paths: "src/auth?.ts"');
|
|
80
|
+
});
|
|
81
|
+
it('throws for path starting with ..', () => {
|
|
82
|
+
expect(() => normalizePath('../etc/passwd')).toThrow('Path traversal detected');
|
|
83
|
+
});
|
|
84
|
+
it('throws for path with escaping .. segments', () => {
|
|
85
|
+
expect(() => normalizePath('foo/../../bar')).toThrow('Path traversal detected');
|
|
86
|
+
});
|
|
87
|
+
it('throws for path with non-escaping .. segment', () => {
|
|
88
|
+
expect(() => normalizePath('foo/../bar')).toThrow('Path traversal detected');
|
|
89
|
+
});
|
|
90
|
+
it('does not throw for relative path starting with ./', () => {
|
|
91
|
+
expect(() => normalizePath('./foo/bar')).not.toThrow();
|
|
92
|
+
expect(normalizePath('./foo/bar')).toBe('foo/bar');
|
|
93
|
+
});
|
|
94
|
+
it('does not throw for plain relative path', () => {
|
|
95
|
+
expect(() => normalizePath('foo/bar')).not.toThrow();
|
|
96
|
+
expect(normalizePath('foo/bar')).toBe('foo/bar');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
// ── pathsOverlap ──────────────────────────────────────────────────────────────
|
|
100
|
+
describe('pathsOverlap', () => {
|
|
101
|
+
it('exact match returns true', () => {
|
|
102
|
+
expect(pathsOverlap('src/auth/service.ts', 'src/auth/service.ts')).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
it('directory prefix overlaps its child file (a is prefix of b)', () => {
|
|
105
|
+
expect(pathsOverlap('src/auth/', 'src/auth/service.ts')).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
it('directory prefix overlaps its child file (b is prefix of a)', () => {
|
|
108
|
+
expect(pathsOverlap('src/auth/service.ts', 'src/auth/')).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
it('non-trailing-slash directory prefix overlaps its child file', () => {
|
|
111
|
+
expect(pathsOverlap('src/auth', 'src/auth/service.ts')).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
it('child file overlaps non-trailing-slash directory prefix', () => {
|
|
114
|
+
expect(pathsOverlap('src/auth/service.ts', 'src/auth')).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
it('sibling directories do not overlap', () => {
|
|
117
|
+
expect(pathsOverlap('src/auth/', 'src/billing/')).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
it('prefix without slash does not match different directory with same prefix', () => {
|
|
120
|
+
// 'src/auth' should NOT overlap 'src/auth-utils/' (different dir)
|
|
121
|
+
expect(pathsOverlap('src/auth', 'src/auth-utils/')).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
it('completely different paths do not overlap', () => {
|
|
124
|
+
expect(pathsOverlap('src/auth/service.ts', 'src/billing/invoice.ts')).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
it('nested directory overlaps parent', () => {
|
|
127
|
+
expect(pathsOverlap('src/', 'src/auth/service.ts')).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
it('same file in different directories does not overlap', () => {
|
|
130
|
+
expect(pathsOverlap('src/auth/index.ts', 'src/billing/index.ts')).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
// ── validateFilePartitions ────────────────────────────────────────────────────
|
|
134
|
+
describe('validateFilePartitions', () => {
|
|
135
|
+
// Stub determineFsCaseSensitivity to always return true (case-sensitive)
|
|
136
|
+
// so partition test results are deterministic regardless of the host OS.
|
|
137
|
+
function stubCaseSensitive() {
|
|
138
|
+
vi.mocked(fs.realpathSync).mockReturnValue('/tmp/probe');
|
|
139
|
+
vi.mocked(fs.writeFileSync).mockReturnValue(undefined);
|
|
140
|
+
vi.mocked(fs.rmSync).mockReturnValue(undefined);
|
|
141
|
+
vi.mocked(fs.statSync)
|
|
142
|
+
.mockReturnValueOnce(makeFakeStats(false, 100))
|
|
143
|
+
.mockImplementationOnce(() => {
|
|
144
|
+
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
it('returns valid when no overlap between parallel tasks', () => {
|
|
148
|
+
stubCaseSensitive();
|
|
149
|
+
const taskA = makeTask('a', ['src/auth/']);
|
|
150
|
+
const taskB = makeTask('b', ['src/billing/']);
|
|
151
|
+
const phases = [[taskA, taskB]];
|
|
152
|
+
const result = validateFilePartitions([taskA, taskB], phases);
|
|
153
|
+
expect(result.valid).toBe(true);
|
|
154
|
+
expect(result.conflicts).toHaveLength(0);
|
|
155
|
+
});
|
|
156
|
+
it('returns conflict when parallel tasks have overlapping files', () => {
|
|
157
|
+
stubCaseSensitive();
|
|
158
|
+
const taskA = makeTask('a', ['src/auth/']);
|
|
159
|
+
const taskB = makeTask('b', ['src/auth/service.ts']);
|
|
160
|
+
const phases = [[taskA, taskB]];
|
|
161
|
+
const result = validateFilePartitions([taskA, taskB], phases);
|
|
162
|
+
expect(result.valid).toBe(false);
|
|
163
|
+
expect(result.conflicts).toHaveLength(1);
|
|
164
|
+
expect(result.conflicts[0]).toMatchObject({
|
|
165
|
+
phase: 0,
|
|
166
|
+
taskA: 'a',
|
|
167
|
+
taskB: 'b',
|
|
168
|
+
});
|
|
169
|
+
expect(result.conflicts[0].overlapping).toContain('src/auth/');
|
|
170
|
+
});
|
|
171
|
+
it('sequential tasks (different phases) with overlapping files are valid', () => {
|
|
172
|
+
stubCaseSensitive();
|
|
173
|
+
const taskA = makeTask('a', ['src/auth/']);
|
|
174
|
+
const taskB = makeTask('b', ['src/auth/service.ts'], ['a']);
|
|
175
|
+
// Phase 0: [taskA], Phase 1: [taskB]
|
|
176
|
+
const phases = [[taskA], [taskB]];
|
|
177
|
+
const result = validateFilePartitions([taskA, taskB], phases);
|
|
178
|
+
expect(result.valid).toBe(true);
|
|
179
|
+
expect(result.conflicts).toHaveLength(0);
|
|
180
|
+
});
|
|
181
|
+
it('returns valid when files arrays are empty', () => {
|
|
182
|
+
stubCaseSensitive();
|
|
183
|
+
const taskA = makeTask('a', []);
|
|
184
|
+
const taskB = makeTask('b', []);
|
|
185
|
+
const phases = [[taskA, taskB]];
|
|
186
|
+
const result = validateFilePartitions([taskA, taskB], phases);
|
|
187
|
+
expect(result.valid).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
it('skips conflict check when one task has empty files array', () => {
|
|
190
|
+
stubCaseSensitive();
|
|
191
|
+
const taskA = makeTask('a', ['src/auth/']);
|
|
192
|
+
const taskB = makeTask('b', []);
|
|
193
|
+
const phases = [[taskA, taskB]];
|
|
194
|
+
const result = validateFilePartitions([taskA, taskB], phases);
|
|
195
|
+
expect(result.valid).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
it('detects multiple conflicts across task pairs', () => {
|
|
198
|
+
stubCaseSensitive();
|
|
199
|
+
const taskA = makeTask('a', ['src/auth/', 'src/shared/']);
|
|
200
|
+
const taskB = makeTask('b', ['src/auth/login.ts']);
|
|
201
|
+
const taskC = makeTask('c', ['src/shared/utils.ts']);
|
|
202
|
+
const phases = [[taskA, taskB, taskC]];
|
|
203
|
+
const result = validateFilePartitions([taskA, taskB, taskC], phases);
|
|
204
|
+
expect(result.valid).toBe(false);
|
|
205
|
+
expect(result.conflicts).toHaveLength(2);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
// ── determineFsCaseSensitivity ────────────────────────────────────────────────
|
|
209
|
+
describe('determineFsCaseSensitivity', () => {
|
|
210
|
+
it('returns true (case-sensitive) when stat throws for the lowercase path', () => {
|
|
211
|
+
vi.mocked(fs.realpathSync).mockReturnValue('/private/tmp');
|
|
212
|
+
vi.mocked(fs.writeFileSync).mockReturnValue(undefined);
|
|
213
|
+
vi.mocked(fs.rmSync).mockReturnValue(undefined);
|
|
214
|
+
vi.mocked(fs.statSync)
|
|
215
|
+
.mockReturnValueOnce(makeFakeStats(false, 100)) // mixed-case file exists
|
|
216
|
+
.mockImplementationOnce(() => {
|
|
217
|
+
// lowercase path throws → case-sensitive filesystem
|
|
218
|
+
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
|
|
219
|
+
});
|
|
220
|
+
expect(determineFsCaseSensitivity()).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
it('returns false (case-insensitive) when stat returns the same inode for both cases', () => {
|
|
223
|
+
vi.mocked(fs.realpathSync).mockReturnValue('/private/tmp');
|
|
224
|
+
vi.mocked(fs.writeFileSync).mockReturnValue(undefined);
|
|
225
|
+
vi.mocked(fs.rmSync).mockReturnValue(undefined);
|
|
226
|
+
vi.mocked(fs.statSync)
|
|
227
|
+
.mockReturnValueOnce(makeFakeStats(false, 42)) // mixed-case
|
|
228
|
+
.mockReturnValueOnce(makeFakeStats(false, 42)); // lowercase — same inode
|
|
229
|
+
expect(determineFsCaseSensitivity()).toBe(false);
|
|
230
|
+
});
|
|
231
|
+
it('calls rmSync in finally (cleanup) and returns true (safe default) when statSync throws unexpectedly', () => {
|
|
232
|
+
vi.mocked(fs.realpathSync).mockReturnValue('/private/tmp');
|
|
233
|
+
vi.mocked(fs.writeFileSync).mockReturnValue(undefined);
|
|
234
|
+
vi.mocked(fs.rmSync).mockReturnValue(undefined);
|
|
235
|
+
vi.mocked(fs.statSync).mockImplementation(() => {
|
|
236
|
+
throw new Error('unexpected disk error');
|
|
237
|
+
});
|
|
238
|
+
// The function handles errors gracefully: assumes case-sensitive (safe default)
|
|
239
|
+
expect(determineFsCaseSensitivity()).toBe(true);
|
|
240
|
+
// Cleanup always runs via finally
|
|
241
|
+
expect(fs.rmSync).toHaveBeenCalled();
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
// ── scanSymlinks ──────────────────────────────────────────────────────────────
|
|
245
|
+
describe('scanSymlinks', () => {
|
|
246
|
+
const BASE = '/project/worktree';
|
|
247
|
+
it('passes silently when no symlinks exist in files list', () => {
|
|
248
|
+
vi.mocked(fs.realpathSync).mockReturnValue(BASE);
|
|
249
|
+
vi.mocked(fs.lstatSync).mockReturnValue(makeFakeStats(false));
|
|
250
|
+
expect(() => scanSymlinks(['src/auth/service.ts'], BASE)).not.toThrow();
|
|
251
|
+
});
|
|
252
|
+
it('passes when symlink resolves within the partition (safe symlink)', () => {
|
|
253
|
+
vi.mocked(fs.realpathSync)
|
|
254
|
+
.mockReturnValueOnce(BASE) // resolve(basePath)
|
|
255
|
+
.mockReturnValueOnce(`${BASE}/src/auth/target.ts`); // symlink target
|
|
256
|
+
vi.mocked(fs.lstatSync).mockReturnValue(makeFakeStats(true));
|
|
257
|
+
expect(() => scanSymlinks(['src/auth/link.ts'], BASE)).not.toThrow();
|
|
258
|
+
});
|
|
259
|
+
it('throws symlink_escape when symlink resolves outside the partition', () => {
|
|
260
|
+
vi.mocked(fs.realpathSync)
|
|
261
|
+
.mockReturnValueOnce(BASE)
|
|
262
|
+
.mockReturnValueOnce('/etc/passwd'); // escapes
|
|
263
|
+
vi.mocked(fs.lstatSync).mockReturnValue(makeFakeStats(true));
|
|
264
|
+
expect(() => scanSymlinks(['src/evil-link.ts'], BASE)).toThrow('symlink_escape');
|
|
265
|
+
});
|
|
266
|
+
it('skips files that do not exist yet (lstatSync throws)', () => {
|
|
267
|
+
vi.mocked(fs.realpathSync).mockReturnValue(BASE);
|
|
268
|
+
vi.mocked(fs.lstatSync).mockImplementation(() => {
|
|
269
|
+
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' });
|
|
270
|
+
});
|
|
271
|
+
expect(() => scanSymlinks(['src/not-yet-created.ts'], BASE)).not.toThrow();
|
|
272
|
+
});
|
|
273
|
+
it('throws symlink_escape when realpathSync cannot resolve the symlink target', () => {
|
|
274
|
+
vi.mocked(fs.realpathSync)
|
|
275
|
+
.mockReturnValueOnce(BASE)
|
|
276
|
+
.mockImplementationOnce(() => {
|
|
277
|
+
throw new Error('broken symlink');
|
|
278
|
+
});
|
|
279
|
+
vi.mocked(fs.lstatSync).mockReturnValue(makeFakeStats(true));
|
|
280
|
+
expect(() => scanSymlinks(['src/broken-link.ts'], BASE)).toThrow('symlink_escape');
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
// ── scanNewSymlinks ───────────────────────────────────────────────────────────
|
|
284
|
+
describe('scanNewSymlinks', () => {
|
|
285
|
+
const BASE = '/project/worktree';
|
|
286
|
+
it('passes silently when no symlinks were created', () => {
|
|
287
|
+
vi.mocked(fs.realpathSync).mockReturnValue(BASE);
|
|
288
|
+
vi.mocked(fs.lstatSync).mockReturnValue(makeFakeStats(false));
|
|
289
|
+
expect(() => scanNewSymlinks(BASE, ['src/auth/service.ts'])).not.toThrow();
|
|
290
|
+
});
|
|
291
|
+
it('passes when new symlink resolves within the worktree', () => {
|
|
292
|
+
vi.mocked(fs.realpathSync)
|
|
293
|
+
.mockReturnValueOnce(BASE)
|
|
294
|
+
.mockReturnValueOnce(`${BASE}/src/auth/target.ts`);
|
|
295
|
+
vi.mocked(fs.lstatSync).mockReturnValue(makeFakeStats(true));
|
|
296
|
+
expect(() => scanNewSymlinks(BASE, ['src/auth/link.ts'])).not.toThrow();
|
|
297
|
+
});
|
|
298
|
+
it('throws symlink_escape_post_task when new symlink escapes worktree', () => {
|
|
299
|
+
vi.mocked(fs.realpathSync)
|
|
300
|
+
.mockReturnValueOnce(BASE)
|
|
301
|
+
.mockReturnValueOnce('/home/attacker/secret');
|
|
302
|
+
vi.mocked(fs.lstatSync).mockReturnValue(makeFakeStats(true));
|
|
303
|
+
expect(() => scanNewSymlinks(BASE, ['src/evil-link.ts'])).toThrow('symlink_escape_post_task');
|
|
304
|
+
});
|
|
305
|
+
it('throws symlink_escape_post_task when new symlink cannot be resolved', () => {
|
|
306
|
+
vi.mocked(fs.realpathSync)
|
|
307
|
+
.mockReturnValueOnce(BASE)
|
|
308
|
+
.mockImplementationOnce(() => {
|
|
309
|
+
throw new Error('broken symlink');
|
|
310
|
+
});
|
|
311
|
+
vi.mocked(fs.lstatSync).mockReturnValue(makeFakeStats(true));
|
|
312
|
+
expect(() => scanNewSymlinks(BASE, ['src/new-broken-link.ts'])).toThrow('symlink_escape_post_task');
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
//# sourceMappingURL=partition.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partition.test.js","sourceRoot":"","sources":["../../../src/cli/convoy/partition.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -73,6 +73,8 @@ function makeEngineFactory(runResults) {
|
|
|
73
73
|
return {
|
|
74
74
|
run: vi.fn().mockResolvedValue(result),
|
|
75
75
|
resume: vi.fn().mockResolvedValue(makeConvoyResult()),
|
|
76
|
+
retryFailed: vi.fn(),
|
|
77
|
+
injectTask: vi.fn(),
|
|
76
78
|
};
|
|
77
79
|
});
|
|
78
80
|
}
|
|
@@ -358,6 +360,8 @@ describe('pipeline record persistence', () => {
|
|
|
358
360
|
return makeConvoyResult();
|
|
359
361
|
}),
|
|
360
362
|
resume: vi.fn(),
|
|
363
|
+
retryFailed: vi.fn(),
|
|
364
|
+
injectTask: vi.fn(),
|
|
361
365
|
}));
|
|
362
366
|
const result = await createPipelineOrchestrator({
|
|
363
367
|
spec: makePipelineSpec({ depends_on_convoy: ['./a.yaml'] }),
|
|
@@ -538,6 +542,8 @@ describe('pipeline resume', () => {
|
|
|
538
542
|
const mockEngine = {
|
|
539
543
|
run: vi.fn().mockResolvedValue(makeConvoyResult()),
|
|
540
544
|
resume: vi.fn().mockResolvedValue(resumedResult),
|
|
545
|
+
retryFailed: vi.fn(),
|
|
546
|
+
injectTask: vi.fn(),
|
|
541
547
|
};
|
|
542
548
|
const factory = vi.fn().mockReturnValue(mockEngine);
|
|
543
549
|
const result = await createPipelineOrchestrator({
|