From 52a610170641ea370330a9e98b6a66dbdf3c736a Mon Sep 17 00:00:00 2001 From: Sinabina Date: Tue, 21 Jul 2026 13:59:29 -0700 Subject: [PATCH] fix(gstack2): treat hard runtime failure as not-ready in capability readiness A capability whose binary launches while the managed runtime hard-fails (e.g. skill-API mismatch) was reported as `degraded`/ok:true/exit 0, diverging from plain `gstack doctor` (ok:false/exit 1) for the identical report. Split the branch so runtime `warn` stays `degraded` and runtime `fail` maps to `failed`, and add a regression test for the runtime-fail + capability-pass case. Co-Authored-By: Claude Opus 4.8 (1M context) --- runtime/doctor.js | 3 ++- test/gstack2-capability-readiness.test.ts | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/runtime/doctor.js b/runtime/doctor.js index 70fcff94b..21ab58c98 100644 --- a/runtime/doctor.js +++ b/runtime/doctor.js @@ -247,7 +247,8 @@ export function capabilityReadiness(report, capability, options = {}) { const runtimeCheck = report.checks.find((check) => check.id === "managed-runtime"); let status; if (capabilityCheck?.status === "pass" && runtimeCheck?.status === "pass") status = "ready"; - else if (capabilityCheck?.status === "pass") status = "degraded"; + else if (capabilityCheck?.status === "pass" && runtimeCheck?.status === "warn") status = "degraded"; + else if (capabilityCheck?.status === "pass") status = "failed"; else if (capabilityCheck?.status === "fail") status = "failed"; else status = "unavailable"; diff --git a/test/gstack2-capability-readiness.test.ts b/test/gstack2-capability-readiness.test.ts index fc5e8ee73..444a14ab6 100644 --- a/test/gstack2-capability-readiness.test.ts +++ b/test/gstack2-capability-readiness.test.ts @@ -52,11 +52,18 @@ describe("capability readiness", () => { { id: "managed-runtime", status: "pass", message: "active" }, { id: "capability:diagram", status: "fail", message: "launcher metadata missing" }, ]), "diagram"); + // A hard runtime failure under a launchable capability is not a warning: + // it must not report ok:true, matching plain `gstack doctor`'s exit code. + const runtimeFailed = capabilityReadiness(report([ + { id: "managed-runtime", status: "fail", message: "incompatible skill API" }, + { id: "capability:browser", status: "pass", message: "launched" }, + ]), "browser"); expect(ready).toMatchObject({ ok: true, readiness: { status: "ready" } }); expect(degraded).toMatchObject({ ok: true, readiness: { status: "degraded" } }); expect(failed).toMatchObject({ ok: false, readiness: { status: "failed" } }); expect(failed.consent.install.status).toBe("required-after-preview"); + expect(runtimeFailed).toMatchObject({ ok: false, readiness: { status: "failed" } }); }); test("reports physical iOS as unsupported without turning off pure judgment", () => {