From a16c984f9393db91426b43d8c5871911e4219bf3 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Tue, 4 Aug 2026 10:47:09 -0500 Subject: [PATCH] fix(robosoft): core-integration read the Tracker's gate shape off the Core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 5 reported "no gates" against a Core that had evaluated six of them. The Core was right; the assertion was wrong. The canonical `EvaluationResult` nests per-kind results under `results` (`gate`, `artifact`, `compliance`). It is the TRACKER's own DTO that flattens them to `gates`, and this robot reaches the Core THROUGH the gateway, so it receives the canonical shape and never the flattened one. A second defect sat in the same helper and would have survived the first fix: `summarizeVerdict` compared each verdict against `'failed'` while the Core emits `FAIL`. Even given the right array it would have reported zero failures on a run where all six gates failed — a green-looking summary over a red result, which is worse than no summary at all. Verified against a captured REST response rather than argued, and run against the UNFIXED helper first so the change is known to matter: before gates=0 · failed=0 (and the check said "no gates") after gates=6 · failed=6 Checked for the same shape elsewhere in robosoft/: no other robot reads `.gates` or compares against a lowercase verdict. Found while running this robot against a live two-cluster stack for the first time — it is excluded from the default runner and had never executed. The same run found a real product defect (the MCP chart's runAsUser, fixed in evolith#425); this one was the instrument. Co-Authored-By: Claude Opus 5 --- robosoft/robots/core-integration.robot.mjs | 26 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/robosoft/robots/core-integration.robot.mjs b/robosoft/robots/core-integration.robot.mjs index 47904d34..2e59e34d 100644 --- a/robosoft/robots/core-integration.robot.mjs +++ b/robosoft/robots/core-integration.robot.mjs @@ -127,8 +127,18 @@ export default { // may be "failed" (the probe manifest has findings) — that is a REAL Core // evaluation, which is exactly what proves the round-trip. const data = unwrap(r.body); - check('Core returns a real evaluation verdict (gates evaluated)', r.body?.success === true && Array.isArray(data.gates), { - detail: Array.isArray(data.gates) ? summarizeVerdict(data) : r.body?.error?.message || 'no gates', + // `results.gate`, NOT `gates`. The Core's canonical EvaluationResult nests + // per-kind results under `results` (`gate`, `artifact`, `compliance`); it is + // the TRACKER's own DTO that flattens them to `gates`, and this robot talks + // to the Core through the gateway, so it gets the canonical shape. + // + // Asserting the flattened key made this step report "no gates" against a + // Core that had evaluated six of them — a defect in the instrument that read + // as a defect in the product. Measured against a captured REST response: + // `data.gates` is undefined, `data.results.gate` has 6 entries. + const gates = gatesOf(data); + check('Core returns a real evaluation verdict (gates evaluated)', r.body?.success === true && Array.isArray(gates), { + detail: Array.isArray(gates) ? summarizeVerdict(data) : r.body?.error?.message || 'no gates', }); } @@ -177,8 +187,16 @@ function parseMcpInner(body) { } /** One-line summary of an evaluation verdict from the Core's `data` payload. */ +/** The canonical EvaluationResult nests gates under `results.gate`. */ +function gatesOf(data) { + return Array.isArray(data?.results?.gate) ? data.results.gate : undefined; +} + function summarizeVerdict(data) { - const gates = Array.isArray(data.gates) ? data.gates : []; - const failed = gates.filter((g) => String(g.verdict).toLowerCase() === 'failed').length; + const gates = gatesOf(data) ?? []; + // The Core emits `FAIL`, not `failed`. Comparing against 'failed' reported + // zero failures on a run where all six gates had failed — a green-looking + // summary over a red result, which is worse than no summary. + const failed = gates.filter((g) => /^fail/i.test(String(g.verdict))).length; return `gates=${gates.length} · failed=${failed}`; }