amsd-pipeline-shared 0.5.28 → 0.5.29
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.
|
@@ -173,6 +173,35 @@ function codelineSets({ install, project, codelines = [], env = {} }) {
|
|
|
173
173
|
} finally { if (prev === undefined) delete process.env.EPAM_BROWNFIELD; else process.env.EPAM_BROWNFIELD = prev; }
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
// ── 3b. permissions ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
177
|
+
/**
|
|
178
|
+
* THE RUN CAN WRITE WHAT IT COPIES (operator, 2026-10-23: "Add permissions check to pre-launch checks"; live run 13 died on EACCES — the
|
|
179
|
+
* install's codeline sets are locked read-only and the run's copies kept that mode). A trial copy of the install's project (as a run takes
|
|
180
|
+
* it: without runs/ and prompts/) is made with the launch's OWN copy function into a probe folder, and every copied path must be writable;
|
|
181
|
+
* and a run folder the launch reuses (a retry) must hold nothing the run cannot write over — every path owned by the user the launch runs as
|
|
182
|
+
* (the run makes its own paths writable as it clears them; one another user owns, it cannot). No copy function given: not checked — RED.
|
|
183
|
+
*/
|
|
184
|
+
function permissions({ install, project, runDir, copy, uid = process.getuid() }) {
|
|
185
|
+
if (typeof copy !== 'function') return res('permissions', false, 'not checked: the launch gave no copy function — what the run copies cannot be proven writable');
|
|
186
|
+
const notWritable = (root) => { const bad = []; const walk = (p) => { let st; try { st = fs.lstatSync(p); } catch { return; } if (st.isSymbolicLink()) return;
|
|
187
|
+
try { fs.accessSync(p, fs.constants.W_OK); } catch { bad.push(p); } if (st.isDirectory()) { let es = []; try { es = fs.readdirSync(p); } catch { bad.push(p); } for (const n of es) walk(path.join(p, n)); } };
|
|
188
|
+
walk(root); return bad; };
|
|
189
|
+
const src = path.join(orchOf(install), 'projects', project);
|
|
190
|
+
const probe = fs.mkdtempSync(path.join(os.tmpdir(), 'prelaunch-permissions-'));
|
|
191
|
+
let copied = [];
|
|
192
|
+
try {
|
|
193
|
+
for (const n of fs.readdirSync(src)) if (n !== 'runs' && n !== 'prompts') copy(path.join(src, n), path.join(probe, n));
|
|
194
|
+
copied = notWritable(probe).map((p) => path.relative(probe, p));
|
|
195
|
+
} catch (e) { copied = [`the trial copy failed: ${e.message}`]; }
|
|
196
|
+
finally { spawnSync('chmod', ['-R', 'u+rwX', probe]); fs.rmSync(probe, { recursive: true, force: true }); }
|
|
197
|
+
const notOwned = (root) => { const bad = []; const walk = (p) => { let st; try { st = fs.lstatSync(p); } catch { return; } if (st.uid !== uid) bad.push(p);
|
|
198
|
+
if (st.isDirectory() && !st.isSymbolicLink()) { let es = []; try { es = fs.readdirSync(p); } catch { return; } for (const n of es) walk(path.join(p, n)); } }; walk(root); return bad; };
|
|
199
|
+
const left = runDir && fs.existsSync(runDir) ? notOwned(runDir).map((p) => path.relative(runDir, p) || '.') : [];
|
|
200
|
+
const why = [copied.length && `${copied.length} copied path(s) not writable: ${copied.slice(0, 5).join(', ')}`,
|
|
201
|
+
left.length && `the run folder holds ${left.length} path(s) another user owns — the run cannot write over them: ${left.slice(0, 5).join(', ')}`].filter(Boolean).join('; ');
|
|
202
|
+
return res('permissions', !why, why || `the launch's copy of ${project} is writable, and the run folder holds nothing another user owns`, { copied, left });
|
|
203
|
+
}
|
|
204
|
+
|
|
176
205
|
// ── 4. cost ──────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
177
206
|
/** Every model a provider set's ladders name (startModel, model, models, from/to), from its own declaration. */
|
|
178
207
|
function ladderModels(install, set) {
|
|
@@ -295,6 +324,7 @@ function prelaunch(o) {
|
|
|
295
324
|
// the variant (brownfield) is the run's, from the env the launch hands over — not this process's own environment
|
|
296
325
|
if (o.codeline) results.push(cache({ install: o.install, project: o.project, codeline: o.codeline, variant: (o.env || {}).EPAM_BROWNFIELD === '1' ? 'brownfield' : '', maxRebuild: o.maxRebuild || 0 }));
|
|
297
326
|
if (Array.isArray(o.codelines)) results.push(codelineSets({ install: o.install, project: o.project, codelines: o.codelines, env: o.env || {} }));
|
|
327
|
+
if (o.copy) results.push(permissions({ install: o.install, project: o.project, runDir: o.runDir, copy: o.copy }));
|
|
298
328
|
results.push(cost({ install: o.install, set: o.set || o.env.EPAM_PROVIDER_SET || 'claude', tmpDir: o.outDir }));
|
|
299
329
|
results.push(alone({ install: o.install }));
|
|
300
330
|
results.push(memory({ needMb: Number(o.needMb || 4096) }));
|
|
@@ -307,7 +337,7 @@ function prelaunch(o) {
|
|
|
307
337
|
return report;
|
|
308
338
|
}
|
|
309
339
|
|
|
310
|
-
module.exports = { prelaunch, prelaunchCli, preflight, langfuse, langfuseEnv, seed, cache, codelineSets, cost, ladderModels, alone, memory, noTimeout, checklist, ancestors };
|
|
340
|
+
module.exports = { prelaunch, prelaunchCli, preflight, langfuse, langfuseEnv, seed, cache, codelineSets, permissions, cost, ladderModels, alone, memory, noTimeout, checklist, ancestors };
|
|
311
341
|
|
|
312
342
|
/**
|
|
313
343
|
* THE COMMAND, for every pipeline:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amsd-pipeline-shared",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.29",
|
|
4
4
|
"description": "The AMSD pipeline's shared components, one source for the bash pipeline and its Claude workflows: the model-call gateway (llm-handler.sh — retry, ladder, self-heal, cost) and the libraries it runs.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"bin": {
|