amsd-pipeline-shared 0.5.26 → 0.5.28
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.
|
@@ -23,12 +23,20 @@ function signaturesFor(runtime, injected) {
|
|
|
23
23
|
internalFrame: rxOf(s.internalFrame), dependencyDir: s.dependencyDir };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function classify({ profile, portRoot, res, crash, aborted, since, signatures, evidence }) {
|
|
26
|
+
function classify({ profile, portRoot, res, crash, aborted, since, signatures, evidence, runDir }) {
|
|
27
27
|
const sig = signaturesFor(profile.classify.runtime, signatures);
|
|
28
28
|
const takesMost = profile.classify.policy === 'all-but-refusals';
|
|
29
29
|
// WHAT IS EXTERNAL: the machine, the network, the model service, a kill. Under "all-but-refusals" a kill that happened inside the pipeline's OWN memory budget (the bounded wrapper's start banner is in the text:
|
|
30
30
|
// the cap the pipeline chose, so what ran inside it is the pipeline's to bound — run 11, jest workers inside a Writer session's cap) is not external
|
|
31
|
-
|
|
31
|
+
// An access error on the leg's OWN run folder is the code's too (live run 13, 2026-10-22: the driver copied a locked set into the run's
|
|
32
|
+
// project copy and could not clear it — "EACCES … rmdir '<run>/project/…'"): every path the message quotes is inside runDir
|
|
33
|
+
const ownRunPaths = (text) => {
|
|
34
|
+
if (!takesMost || !runDir || !/\bE(ACCES|PERM)\b/.test(text)) return false;
|
|
35
|
+
const paths = [...String(text).matchAll(/'(\/[^']+)'/g)].map((m) => path.resolve(m[1]));
|
|
36
|
+
const root = path.resolve(runDir);
|
|
37
|
+
return paths.length > 0 && paths.every((p) => p === root || p.startsWith(`${root}${path.sep}`));
|
|
38
|
+
};
|
|
39
|
+
const isExternal = (text) => sig.external.test(text) && !(takesMost && sig.ownBudget.test(text)) && !ownRunPaths(text);
|
|
32
40
|
if (!res || typeof res !== 'object') return no('no-result', 'the leg gave no result');
|
|
33
41
|
if (aborted) return no('aborted', 'the operator or a watcher ended the leg');
|
|
34
42
|
// AN AGENT-CAUSED ABORT (operator, 2026-10-20: look for the agentic solution first): a failed leg whose reason is one of the pipeline's own agent-caused
|
|
@@ -69,7 +77,10 @@ function classify({ profile, portRoot, res, crash, aborted, since, signatures, e
|
|
|
69
77
|
if (isExternal(text)) return no('external', 'the failure names the machine, the network, the model service or a kill — not the code');
|
|
70
78
|
if (sig.generalRefusal.test(text) || profile.classify.refusals.some((r) => r.test(text))) return no('refusal', 'the pipeline stopped on purpose');
|
|
71
79
|
const site = (crash.frames || []).find((f) => f && typeof f.file === 'string' && !sig.internalFrame.test(f.file)) || null;
|
|
72
|
-
|
|
80
|
+
// under "all-but-refusals" a crash whose stack names no file (every frame the runtime's own — live run 13, node's rimraf) is still a failed
|
|
81
|
+
// leg the doctor takes; its agent finds the layer
|
|
82
|
+
if (!site) return takesMost ? { call: true, kind: 'crash', layer: 'undecided', why: `${crash.name}: ${String(crash.message).slice(0, 160)} — no frame of the stack is in a file${crash.step ? ` (step: ${crash.step})` : ''}` }
|
|
83
|
+
: no('outside-port', 'no frame of the stack is in any file');
|
|
73
84
|
const rel = path.relative(portRoot || '', site.file);
|
|
74
85
|
const parts = rel.split(path.sep);
|
|
75
86
|
const inPort = !!portRoot && !rel.startsWith('..') && !path.isAbsolute(rel) && profile.scope.repairDirs.includes(parts[0]) && !parts.includes(sig.dependencyDir);
|
|
@@ -55,6 +55,9 @@ function die(e) {
|
|
|
55
55
|
function install(opts) {
|
|
56
56
|
if (!opts || !opts.runDir || !opts.file) throw new Error('crash record: install needs { runDir, file } — the file name is the pipeline\'s (profile crash.file), never a default');
|
|
57
57
|
runDir = opts.runDir; fileName = opts.file;
|
|
58
|
+
// THE WHOLE STACK: V8 keeps ten frames, and a throw from deep inside a library (node's rimraf, live run 13) lost the frame of the code
|
|
59
|
+
// that called it — the one the doctor locates the defect by
|
|
60
|
+
Error.stackTraceLimit = Infinity;
|
|
58
61
|
process.on('uncaughtException', die);
|
|
59
62
|
process.on('unhandledRejection', (r) => die(r));
|
|
60
63
|
}
|
|
@@ -33,7 +33,7 @@ async function handle(a) {
|
|
|
33
33
|
// the logs are cumulative across legs: the decision reads only what follows the last leg-start line in each file (the whole tail when the profile names none or the tail holds none)
|
|
34
34
|
const thisLeg = (text) => { if (!profile.logs.legStart) return text; const ls = text.split('\n'); let at = -1; ls.forEach((l, i) => { if (profile.logs.legStart.test(l)) at = i; }); return at < 0 ? text : ls.slice(at).join('\n'); };
|
|
35
35
|
const evidence = profile.logs.evidence.map((f) => thisLeg(tail(path.join(runDir, f), profile.logs.evidenceTailLines))).join('\n');
|
|
36
|
-
const c = classify({ profile, portRoot, res, crash, aborted, since, evidence });
|
|
36
|
+
const c = classify({ profile, portRoot, res, crash, aborted, since, evidence, runDir });
|
|
37
37
|
if (!c.call) { d.event('not-called', { kind: c.kind, why: c.why }); return { handled: false, kind: c.kind, why: c.why }; }
|
|
38
38
|
d.event('trigger', { kind: c.kind, layer: c.layer, why: c.why, step: (crash && crash.step) || '' });
|
|
39
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amsd-pipeline-shared",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.28",
|
|
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": {
|