amsd-pipeline-shared 0.5.4 → 0.5.6

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.
@@ -17,7 +17,7 @@ AGENTIC FIRST — decide this before anything else
17
17
  - The agentic layer of {{component}} is: {{agenticDescription}}. It lives in: {{agenticDirs}}.
18
18
  - If an agent caused it, repair the AGENTIC layer: sharpen the instruction, the answer schema and its field descriptions, the hand-off contract between the agents — so the agent is asked in a way that gets the right answer. Do NOT add deterministic code that tolerates, repairs or re-parses a wrong agent answer, and do not add a guard that hides an agent's behaviour.
19
19
  - Only a clear and outright coding bug gets a deterministic fix.
20
- - This abort was classified as: {{abortKind}} ({{layerHint}}). An agentic abort MUST be repaired agentically. For a crash, say which layer you found and why.
20
+ - This abort was classified as: {{abortKind}} ({{layerHint}}). An agentic abort MUST be repaired agentically. For a crash or a fault (the leg failed without giving a verdict: a stage with no result, a session that answered an error), the layer is yours to find — look for the agentic cause first — and say which and why.
21
21
  - An agentic repair is proven by REPLAY: replay the real reply or hand-off that failed — copy it into a fixture — against the schema, the contract or the rendered instruction in the RED test (it fails on the base, passes after), and assert on the rendered instruction or schema text that now prevents the abort.
22
22
 
23
23
  HOW YOU WORK — in this order
@@ -23,7 +23,7 @@ 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 }) {
26
+ function classify({ profile, portRoot, res, crash, aborted, since, signatures, evidence }) {
27
27
  const sig = signaturesFor(profile.classify.runtime, signatures);
28
28
  if (!res || typeof res !== 'object') return no('no-result', 'the leg gave no result');
29
29
  if (aborted) return no('aborted', 'the operator or a watcher ended the leg');
@@ -33,7 +33,17 @@ function classify({ profile, portRoot, res, crash, aborted, since, signatures })
33
33
  if (res.status === 'failed') {
34
34
  const reason = typeof res.reason === 'string' ? res.reason : '';
35
35
  const hit = reason && profile.agentic.abortSignatures.find((r) => r.test(reason));
36
- if (!hit) return no('not-a-crash', 'the leg failed on a verdict that names no agent-caused message — reported, not repaired');
36
+ if (!hit) {
37
+ // A FAILED LEG THAT GAVE NO VERDICT (operator, 2026-10-20, after run 11 leg 3: "these are the types of bugs I need doctor to resolve during a run"): the machinery failed without throwing
38
+ // — a stage's result with no exit, a session that answered an error. Which lines of the leg's evidence say so is the pipeline's profile (classify.faultMarks); a lane's own verdict
39
+ // has a number and its reason and matches none. The marked line is still refused if it names the machine, the network, the model service or a kill.
40
+ const line = evidence && profile.classify.faultMarks.length ? String(evidence).split('\n').find((l) => profile.classify.faultMarks.some((r) => r.test(l))) : null;
41
+ if (line) {
42
+ if (sig.external.test(line)) return no('external', `the failing line also names the machine, the network, the model service or a kill — not the pipeline: ${line.slice(0, 160)}`);
43
+ return { call: true, kind: 'fault', layer: 'undecided', why: `the leg failed without a verdict: ${line.trim().slice(0, 200)}` };
44
+ }
45
+ return no('not-a-crash', 'the leg failed on a verdict that names no agent-caused message — reported, not repaired');
46
+ }
37
47
  if (sig.external.test(reason)) return no('external', 'the agent-caused message also names the machine, the network, the model service or a kill — not the pipeline');
38
48
  return { call: true, kind: 'agentic-abort', layer: 'agentic', why: `an agent caused the abort: ${reason.slice(0, 200)}` };
39
49
  }
@@ -49,6 +59,7 @@ function classify({ profile, portRoot, res, crash, aborted, since, signatures })
49
59
  const parts = rel.split(path.sep);
50
60
  const inPort = !!portRoot && !rel.startsWith('..') && !path.isAbsolute(rel) && profile.scope.repairDirs.includes(parts[0]) && !parts.includes(sig.dependencyDir);
51
61
  if (!inPort) return no('outside-port', `the throw site ${site.file}:${site.line} is not the component's own code`);
62
+ if (crash.fault === true) return { call: true, kind: 'fault', layer: 'undecided', why: `a fault the component raised: ${String(crash.message).slice(0, 160)} — at ${rel}:${site.line}${crash.step ? ` (step: ${crash.step})` : ''}` };
52
63
  if (!sig.runtimeNames.has(crash.name) && !sig.runtimeMessage.test(crash.message || '')) return no('ambiguous', `an Error in ${rel}:${site.line} with no sign of a code defect — reported`);
53
64
  return { call: true, kind: 'port-defect', layer: 'code', why: `${crash.name}: ${String(crash.message).slice(0, 160)} — thrown at ${rel}:${site.line}${crash.step ? ` (step: ${crash.step})` : ''}` };
54
65
  }
@@ -27,7 +27,7 @@ function frames(stack) {
27
27
 
28
28
  /** What was thrown, whatever it was: an Error, or anything a program may throw. */
29
29
  function describe(e) {
30
- if (e instanceof Error) return { name: e.name || 'Error', message: String(e.message), stack: String(e.stack || '') };
30
+ if (e instanceof Error) return { name: e.name || 'Error', message: String(e.message), stack: String(e.stack || ''), ...(e.fault === true ? { fault: true, evidence: Array.isArray(e.evidence) ? e.evidence.map(String) : [] } : {}) };
31
31
  let text; try { text = typeof e === 'string' ? e : JSON.stringify(e); } catch { text = String(e); }
32
32
  return { name: 'NonError', message: String(text), stack: '' };
33
33
  }
@@ -29,7 +29,9 @@ async function handle(a) {
29
29
  const d = open(runDir, cfg, profile);
30
30
  const portRoot = path.join(install, profile.install.dest);
31
31
 
32
- const c = classify({ profile, portRoot, res, crash, aborted, since });
32
+ // the leg's evidence tail (the files the profile names), read BEFORE the decision: a failure that threw nothing is judged on what the run itself wrote
33
+ const evidence = profile.logs.evidence.map((f) => tail(path.join(runDir, f), profile.logs.evidenceTailLines)).join('\n');
34
+ const c = classify({ profile, portRoot, res, crash, aborted, since, evidence });
33
35
  if (!c.call) { d.event('not-called', { kind: c.kind, why: c.why }); return { handled: false, kind: c.kind, why: c.why }; }
34
36
  d.event('trigger', { kind: c.kind, layer: c.layer, why: c.why, step: (crash && crash.step) || '' });
35
37
 
@@ -77,6 +77,8 @@ function validate(input) {
77
77
  out.classify = {};
78
78
  if (need('classify', cl, 'runtime')) out.classify.runtime = str('classify.runtime', cl.runtime);
79
79
  if (need('classify', cl, 'refusals')) out.classify.refusals = rxs('classify.refusals', cl.refusals);
80
+ // THE LINES THAT MEAN "THE MACHINERY GAVE NO VERDICT" (optional; none = a failed leg is never taken for a fault): a pipeline's own knowledge, matched against the leg's evidence tail
81
+ out.classify.faultMarks = cl.faultMarks === undefined ? [] : rxs('classify.faultMarks', cl.faultMarks);
80
82
  }
81
83
 
82
84
  const se = p.settings;
@@ -229,9 +229,14 @@ qa_record_phase_baseline() {
229
229
  # B77 (RS-4, = B73): THE BASELINE IS RECORDED ONCE for a run and phase. A resume starts the phase again; the baseline it had must not move to wherever HEAD
230
230
  # is by then (that made "committed since the baseline" empty). A new phase, or a new run in the same log folder, has a different key and records its own.
231
231
  _key="${PHASE:-main}|${ORCH_RUN_ID:-}"
232
- if [ -f "$_bf" ] && [ -f "$_kf" ] && [ "$(cat "$_kf" 2>/dev/null)" = "$_key" ]; then
232
+ # A RESUME ADOPTS A LEGACY BASELINE (a sha with no key file, recorded by the code before B77 — found replaying run 11's gotransit lane): on a resume it is the run's own, so a
233
+ # valid commit is kept and the key is written for every later call. Without a resume (a new run in the same folder) a keyless value is not trusted, as before.
234
+ local _legacy=0
235
+ [ -f "$_bf" ] && [ ! -f "$_kf" ] && [ -n "${EPAM_RESUME_RUN:-}" ] && _legacy=1
236
+ if [ -f "$_bf" ] && { [ "$_legacy" = 1 ] || { [ -f "$_kf" ] && [ "$(cat "$_kf" 2>/dev/null)" = "$_key" ]; }; }; then
233
237
  _have=$(tr -d '[:space:]' < "$_bf" 2>/dev/null)
234
238
  if [ -n "$_have" ] && git -C "$_root" cat-file -e "${_have}^{commit}" 2>/dev/null; then
239
+ [ "$_legacy" = 1 ] && printf '%s\n' "$_key" > "$_kf"
235
240
  printf '%s' "$_have"
236
241
  return 0
237
242
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amsd-pipeline-shared",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
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": {