amsd-pipeline-shared 0.5.26 → 0.5.27

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
- const isExternal = (text) => sig.external.test(text) && !(takesMost && sig.ownBudget.test(text));
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
@@ -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.26",
3
+ "version": "0.5.27",
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": {