mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-21 04:10:47 +02:00
feat(gstack2): add capability readiness process
This commit is contained in:
@@ -611,8 +611,12 @@ Data is stored in [Supabase](https://supabase.com) (open source Firebase alterna
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For GStack 2, first run `gstack doctor` (or `gstack doctor --json`) when the
|
||||
optional runtime is installed. For skill discovery problems, use the standard
|
||||
For GStack 2, run `gstack doctor --capability browser|design|diagram|pdf|ios`
|
||||
(optionally `--json`) for a focused, non-mutating readiness result. It reports
|
||||
pure-judgment availability, platform support, preview consent, install consent,
|
||||
and runtime readiness separately; see
|
||||
[`docs/gstack-2/CAPABILITY-READINESS.md`](docs/gstack-2/CAPABILITY-READINESS.md).
|
||||
For skill discovery problems, use the standard
|
||||
installer's list and reinstall commands; do not manually copy host paths. The
|
||||
entries below apply to legacy 1.x installations.
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# Capability readiness
|
||||
|
||||
GStack's six public skills provide pure judgment without the optional runtime.
|
||||
When a workflow needs executable evidence, inspect one of the five user-facing
|
||||
capabilities without changing the machine:
|
||||
|
||||
```bash
|
||||
gstack doctor --capability browser
|
||||
gstack doctor --capability design --json
|
||||
gstack doctor --capability diagram
|
||||
gstack doctor --capability pdf
|
||||
gstack doctor --capability ios
|
||||
```
|
||||
|
||||
The focused result deliberately keeps five questions separate:
|
||||
|
||||
| Axis | Meaning |
|
||||
|---|---|
|
||||
| `judgment` | `available` even when no optional runtime exists. |
|
||||
| `platform` | `supported` or `unsupported`; physical iOS is macOS-only. |
|
||||
| `consent.preview` | Whether consent is required before an uncached public signed-manifest metadata request. Doctor never makes that request or grants consent. |
|
||||
| `consent.install` | Separate consent required only after the complete preview. Doctor never installs or records consent. |
|
||||
| `readiness` | Whether the selected local runtime capability is usable now. |
|
||||
|
||||
Readiness has exactly five states:
|
||||
|
||||
- `ready`: capability and managed-runtime checks passed.
|
||||
- `degraded`: the capability passed, but the managed runtime reported a warning
|
||||
such as recovery from an interrupted upgrade.
|
||||
- `unavailable`: the capability is not selected, no runtime is active, or runtime
|
||||
inspection could not establish availability.
|
||||
- `unsupported`: the platform cannot provide the capability with GStack's
|
||||
existing architecture.
|
||||
- `failed`: an installed/selected capability failed its readiness evidence.
|
||||
|
||||
The JSON form follows the runtime's result convention: `ok` is true only for
|
||||
`ready` and `degraded`; a non-ready focused doctor exits 1. Usage errors still
|
||||
exit 2 through the standard CLI error envelope.
|
||||
|
||||
## Consent and setup flow
|
||||
|
||||
```text
|
||||
pure judgment (always available)
|
||||
|
|
||||
v
|
||||
capability doctor (local, non-mutating)
|
||||
|
|
||||
+-- ready/degraded --> capability-dependent work
|
||||
|
|
||||
+-- unsupported ----> judgment-only work or supported platform
|
||||
|
|
||||
+-- unavailable/failed
|
||||
|
|
||||
v
|
||||
ask for preview consent
|
||||
|
|
||||
v
|
||||
packaged bootstrap preview (no install)
|
||||
|
|
||||
v
|
||||
ask for install consent
|
||||
|
|
||||
v
|
||||
packaged bootstrap install
|
||||
|
|
||||
v
|
||||
capability doctor again
|
||||
```
|
||||
|
||||
Preview consent is not install consent. Deferring either does not block pure
|
||||
judgment. Follow the packaged bootstrap contract for exact dependency closure,
|
||||
compressed bytes, signature checks, and atomic installation. Do not run setup
|
||||
from a standards-installed skill directory, add a browser provider, replace the
|
||||
local Chromium/Playwright backend, or replace the physical-iOS
|
||||
DebugBridge/CoreDevice harness.
|
||||
+19
-6
@@ -25,7 +25,13 @@ import {
|
||||
runExternalEffect,
|
||||
updateRunWorkflow,
|
||||
} from "./state.js";
|
||||
import { runDoctor, formatDoctor } from "./doctor.js";
|
||||
import {
|
||||
CAPABILITY_READINESS_CAPABILITIES,
|
||||
capabilityReadiness,
|
||||
formatCapabilityReadiness,
|
||||
runDoctor,
|
||||
formatDoctor,
|
||||
} from "./doctor.js";
|
||||
import { cleanupRuntime } from "./cleanup.js";
|
||||
import {
|
||||
ContextClient,
|
||||
@@ -157,11 +163,18 @@ async function initCommand({ args, home, cwd, stdout, legacyAlias = false }) {
|
||||
}
|
||||
|
||||
async function doctorCommand({ args, home, cwd, stdout }) {
|
||||
const parsed = parseFlags(args, new Set(["--json", "--skill-api"]));
|
||||
const parsed = parseFlags(args, new Set(["--json", "--skill-api", "--capability"]));
|
||||
if (parsed.positionals.length) throw cliError("Doctor accepts only named options", "USAGE");
|
||||
const capability = parsed.values.get("--capability");
|
||||
if (capability && !CAPABILITY_READINESS_CAPABILITIES.includes(capability)) {
|
||||
throw cliError(`Unknown capability: ${capability}. Choose ${CAPABILITY_READINESS_CAPABILITIES.join(", ")}.`, "USAGE");
|
||||
}
|
||||
const report = await runDoctor({ home, cwd, expectedSkillApi: parsed.values.get("--skill-api") });
|
||||
write(stdout, parsed.flags.has("--json") ? `${JSON.stringify(report, null, 2)}\n` : formatDoctor(report));
|
||||
return report.ok ? 0 : 1;
|
||||
const result = capability ? capabilityReadiness(report, capability) : report;
|
||||
write(stdout, parsed.flags.has("--json")
|
||||
? `${JSON.stringify(result, null, 2)}\n`
|
||||
: capability ? formatCapabilityReadiness(result) : formatDoctor(result));
|
||||
return result.ok ? 0 : 1;
|
||||
}
|
||||
|
||||
async function configCommand({ args, home, cwd, stdout }) {
|
||||
@@ -673,7 +686,7 @@ function parseFlags(args, allowed) {
|
||||
continue;
|
||||
}
|
||||
if (!allowed.has(arg)) throw cliError(`Unknown option: ${arg}`, "USAGE");
|
||||
if (["--source", "--version", "--url", "--older-than-hours", "--max-pages", "--max-depth", "--max-links", "--skill-api"].includes(arg)) {
|
||||
if (["--source", "--version", "--url", "--older-than-hours", "--max-pages", "--max-depth", "--max-links", "--skill-api", "--capability"].includes(arg)) {
|
||||
const value = args[++index];
|
||||
if (value == null || value.startsWith("--")) throw cliError(`${arg} requires a value`, "USAGE");
|
||||
values.set(arg, value);
|
||||
@@ -750,7 +763,7 @@ function usage() {
|
||||
"Usage:\n" +
|
||||
" gstack init # initialize state only\n" +
|
||||
" gstack setup # compatibility alias for init\n" +
|
||||
" gstack doctor [--skill-api <version>] [--json]\n" +
|
||||
" gstack doctor [--capability browser|design|diagram|pdf|ios] [--skill-api <version>] [--json]\n" +
|
||||
" gstack paths [--json|--shell]\n" +
|
||||
" gstack runtime path <bundle-relative-path>\n" +
|
||||
" gstack config get [key]\n" +
|
||||
|
||||
@@ -17,6 +17,14 @@ import {
|
||||
managedBunRelativePath,
|
||||
} from "./install.js";
|
||||
|
||||
export const CAPABILITY_READINESS_CAPABILITIES = Object.freeze([
|
||||
"browser",
|
||||
"design",
|
||||
"diagram",
|
||||
"pdf",
|
||||
"ios",
|
||||
]);
|
||||
|
||||
export async function runDoctor(options = {}) {
|
||||
const paths = resolveRuntimePaths(options);
|
||||
const checks = [];
|
||||
@@ -183,6 +191,82 @@ export async function runDoctor(options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
export function capabilityReadiness(report, capability, options = {}) {
|
||||
if (!CAPABILITY_READINESS_CAPABILITIES.includes(capability)) {
|
||||
throw new TypeError(`Unknown capability: ${capability}`);
|
||||
}
|
||||
const platform = options.platform ?? process.platform;
|
||||
const checkedAt = report.checkedAt;
|
||||
const judgment = {
|
||||
status: "available",
|
||||
message: "Pure-judgment skill guidance is available without the optional runtime.",
|
||||
};
|
||||
if (capability === "ios" && platform !== "darwin") {
|
||||
return {
|
||||
ok: false,
|
||||
capability,
|
||||
checkedAt,
|
||||
judgment,
|
||||
platform: { status: "unsupported", platform, message: "Physical iOS requires macOS and the existing CoreDevice harness." },
|
||||
consent: {
|
||||
preview: { status: "not-applicable", granted: false },
|
||||
install: { status: "not-applicable", granted: false },
|
||||
},
|
||||
readiness: { status: "unsupported", message: "The runtime capability is unsupported on this platform." },
|
||||
nextAction: "Continue with pure judgment or move the physical-iOS workflow to macOS.",
|
||||
};
|
||||
}
|
||||
|
||||
const capabilityCheck = report.checks.find((check) => check.id === `capability:${capability}`);
|
||||
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 === "fail") status = "failed";
|
||||
else status = "unavailable";
|
||||
|
||||
const needsSetup = status === "unavailable" || status === "failed";
|
||||
const messages = {
|
||||
ready: "The selected capability passed its runtime readiness checks.",
|
||||
degraded: "The capability check passed, but the managed runtime reported a degraded condition.",
|
||||
unavailable: "The optional runtime capability is not installed or cannot currently be inspected.",
|
||||
failed: "The selected capability is installed but failed readiness checks.",
|
||||
};
|
||||
return {
|
||||
ok: status === "ready" || status === "degraded",
|
||||
capability,
|
||||
checkedAt,
|
||||
judgment,
|
||||
platform: { status: "supported", platform },
|
||||
consent: {
|
||||
preview: {
|
||||
status: needsSetup ? "required" : "not-required",
|
||||
granted: false,
|
||||
message: needsSetup
|
||||
? "Consent is required before an uncached signed-manifest metadata preview; this command does not grant it."
|
||||
: "No setup preview is needed for the current readiness state.",
|
||||
},
|
||||
install: {
|
||||
status: needsSetup ? "required-after-preview" : "not-required",
|
||||
granted: false,
|
||||
message: needsSetup
|
||||
? "Install consent is separate and may be requested only after the complete preview; this command does not grant it."
|
||||
: "No install is needed for the current readiness state.",
|
||||
},
|
||||
},
|
||||
readiness: {
|
||||
status,
|
||||
message: messages[status],
|
||||
evidence: [runtimeCheck, capabilityCheck].filter(Boolean),
|
||||
},
|
||||
nextAction: needsSetup
|
||||
? `Ask for preview consent, then run the packaged bootstrap preview for ${capability}; continue judgment-only work if setup is deferred.`
|
||||
: status === "degraded"
|
||||
? "Review the managed-runtime warning before capability-dependent evidence work."
|
||||
: "Proceed with capability-dependent work.",
|
||||
};
|
||||
}
|
||||
|
||||
async function inspectRuntimeBun(activeRoot, manifest) {
|
||||
const relative = managedBunRelativePath();
|
||||
const declared = manifest?.tools?.bun;
|
||||
@@ -328,3 +412,16 @@ export function formatDoctor(report) {
|
||||
for (const check of report.checks) lines.push(`${symbol[check.status]} ${check.id}: ${check.message}`);
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
export function formatCapabilityReadiness(report) {
|
||||
const lines = [
|
||||
`gstack capability readiness: ${report.capability}`,
|
||||
`judgment: ${report.judgment.status}`,
|
||||
`platform: ${report.platform.status}`,
|
||||
`preview consent: ${report.consent.preview.status}`,
|
||||
`install consent: ${report.consent.install.status}`,
|
||||
`readiness: ${report.readiness.status}`,
|
||||
`next: ${report.nextAction}`,
|
||||
];
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
@@ -516,6 +516,8 @@ The package/runtime compatibility tuple is \`schemaVersion=1\`, \`runtimeVersion
|
||||
|
||||
Every optional-runtime tool result must satisfy \`references/support/execution-result-contract.json\` before it is presented as success. Success requires non-empty evidence. Empty or malformed output and explicit degraded, unsupported, or failed statuses remain non-success with stable codes; human renderings must preserve that status and code.
|
||||
|
||||
Use \`gstack doctor --capability browser|design|diagram|pdf|ios\` (optionally \`--json\`) for a non-mutating, capability-specific readiness result. Its independent axes must remain distinct: pure judgment availability, platform support, preview consent, install consent, and runtime readiness. Readiness is exactly \`ready\`, \`degraded\`, \`unavailable\`, \`unsupported\`, or \`failed\`. Doctor never grants or persists consent, previews metadata, or installs anything. \`unavailable\` means setup may be offered; \`failed\` means selected runtime evidence failed; \`unsupported\` is a platform boundary; and \`degraded\` means the capability passed while the managed runtime has a warning.
|
||||
|
||||
The developer-only fallback is \`node references/support/runtime-bootstrap.mjs install --source <reviewed-checkout> --capability <name> --yes\`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment.
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -23,4 +23,6 @@ The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.
|
||||
|
||||
Every optional-runtime tool result must satisfy `references/support/execution-result-contract.json` before it is presented as success. Success requires non-empty evidence. Empty or malformed output and explicit degraded, unsupported, or failed statuses remain non-success with stable codes; human renderings must preserve that status and code.
|
||||
|
||||
Use `gstack doctor --capability browser|design|diagram|pdf|ios` (optionally `--json`) for a non-mutating, capability-specific readiness result. Its independent axes must remain distinct: pure judgment availability, platform support, preview consent, install consent, and runtime readiness. Readiness is exactly `ready`, `degraded`, `unavailable`, `unsupported`, or `failed`. Doctor never grants or persists consent, previews metadata, or installs anything. `unavailable` means setup may be offered; `failed` means selected runtime evidence failed; `unsupported` is a platform boundary; and `degraded` means the capability passed while the managed runtime has a warning.
|
||||
|
||||
The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source <reviewed-checkout> --capability <name> --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment.
|
||||
|
||||
@@ -23,4 +23,6 @@ The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.
|
||||
|
||||
Every optional-runtime tool result must satisfy `references/support/execution-result-contract.json` before it is presented as success. Success requires non-empty evidence. Empty or malformed output and explicit degraded, unsupported, or failed statuses remain non-success with stable codes; human renderings must preserve that status and code.
|
||||
|
||||
Use `gstack doctor --capability browser|design|diagram|pdf|ios` (optionally `--json`) for a non-mutating, capability-specific readiness result. Its independent axes must remain distinct: pure judgment availability, platform support, preview consent, install consent, and runtime readiness. Readiness is exactly `ready`, `degraded`, `unavailable`, `unsupported`, or `failed`. Doctor never grants or persists consent, previews metadata, or installs anything. `unavailable` means setup may be offered; `failed` means selected runtime evidence failed; `unsupported` is a platform boundary; and `degraded` means the capability passed while the managed runtime has a warning.
|
||||
|
||||
The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source <reviewed-checkout> --capability <name> --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment.
|
||||
|
||||
@@ -23,4 +23,6 @@ The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.
|
||||
|
||||
Every optional-runtime tool result must satisfy `references/support/execution-result-contract.json` before it is presented as success. Success requires non-empty evidence. Empty or malformed output and explicit degraded, unsupported, or failed statuses remain non-success with stable codes; human renderings must preserve that status and code.
|
||||
|
||||
Use `gstack doctor --capability browser|design|diagram|pdf|ios` (optionally `--json`) for a non-mutating, capability-specific readiness result. Its independent axes must remain distinct: pure judgment availability, platform support, preview consent, install consent, and runtime readiness. Readiness is exactly `ready`, `degraded`, `unavailable`, `unsupported`, or `failed`. Doctor never grants or persists consent, previews metadata, or installs anything. `unavailable` means setup may be offered; `failed` means selected runtime evidence failed; `unsupported` is a platform boundary; and `degraded` means the capability passed while the managed runtime has a warning.
|
||||
|
||||
The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source <reviewed-checkout> --capability <name> --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment.
|
||||
|
||||
@@ -23,4 +23,6 @@ The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.
|
||||
|
||||
Every optional-runtime tool result must satisfy `references/support/execution-result-contract.json` before it is presented as success. Success requires non-empty evidence. Empty or malformed output and explicit degraded, unsupported, or failed statuses remain non-success with stable codes; human renderings must preserve that status and code.
|
||||
|
||||
Use `gstack doctor --capability browser|design|diagram|pdf|ios` (optionally `--json`) for a non-mutating, capability-specific readiness result. Its independent axes must remain distinct: pure judgment availability, platform support, preview consent, install consent, and runtime readiness. Readiness is exactly `ready`, `degraded`, `unavailable`, `unsupported`, or `failed`. Doctor never grants or persists consent, previews metadata, or installs anything. `unavailable` means setup may be offered; `failed` means selected runtime evidence failed; `unsupported` is a platform boundary; and `degraded` means the capability passed while the managed runtime has a warning.
|
||||
|
||||
The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source <reviewed-checkout> --capability <name> --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment.
|
||||
|
||||
@@ -23,4 +23,6 @@ The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.
|
||||
|
||||
Every optional-runtime tool result must satisfy `references/support/execution-result-contract.json` before it is presented as success. Success requires non-empty evidence. Empty or malformed output and explicit degraded, unsupported, or failed statuses remain non-success with stable codes; human renderings must preserve that status and code.
|
||||
|
||||
Use `gstack doctor --capability browser|design|diagram|pdf|ios` (optionally `--json`) for a non-mutating, capability-specific readiness result. Its independent axes must remain distinct: pure judgment availability, platform support, preview consent, install consent, and runtime readiness. Readiness is exactly `ready`, `degraded`, `unavailable`, `unsupported`, or `failed`. Doctor never grants or persists consent, previews metadata, or installs anything. `unavailable` means setup may be offered; `failed` means selected runtime evidence failed; `unsupported` is a platform boundary; and `degraded` means the capability passed while the managed runtime has a warning.
|
||||
|
||||
The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source <reviewed-checkout> --capability <name> --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment.
|
||||
|
||||
@@ -23,4 +23,6 @@ The package/runtime compatibility tuple is `schemaVersion=1`, `runtimeVersion=2.
|
||||
|
||||
Every optional-runtime tool result must satisfy `references/support/execution-result-contract.json` before it is presented as success. Success requires non-empty evidence. Empty or malformed output and explicit degraded, unsupported, or failed statuses remain non-success with stable codes; human renderings must preserve that status and code.
|
||||
|
||||
Use `gstack doctor --capability browser|design|diagram|pdf|ios` (optionally `--json`) for a non-mutating, capability-specific readiness result. Its independent axes must remain distinct: pure judgment availability, platform support, preview consent, install consent, and runtime readiness. Readiness is exactly `ready`, `degraded`, `unavailable`, `unsupported`, or `failed`. Doctor never grants or persists consent, previews metadata, or installs anything. `unavailable` means setup may be offered; `failed` means selected runtime evidence failed; `unsupported` is a platform boundary; and `degraded` means the capability passed while the managed runtime has a warning.
|
||||
|
||||
The developer-only fallback is `node references/support/runtime-bootstrap.mjs install --source <reviewed-checkout> --capability <name> --yes`; show its trust warning and use it only when the user explicitly selects a checkout they reviewed. If the packaged bootstrap is unavailable, stop capability setup instead of guessing a checkout-relative command. Deferring installation records no consent and must not block pure judgment.
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { main } from "../runtime/cli.js";
|
||||
import { capabilityReadiness, formatCapabilityReadiness } from "../runtime/doctor.js";
|
||||
import { setupRuntime } from "../runtime/setup.js";
|
||||
|
||||
function report(checks: Array<Record<string, unknown>>) {
|
||||
return {
|
||||
ok: !checks.some((check) => check.status === "fail"),
|
||||
home: "/fixture/.gstack",
|
||||
checkedAt: "2026-07-20T00:00:00.000Z",
|
||||
checks,
|
||||
};
|
||||
}
|
||||
|
||||
function capture() {
|
||||
let value = "";
|
||||
return { stream: { write: (chunk: string) => { value += chunk; } }, value: () => value };
|
||||
}
|
||||
|
||||
describe("capability readiness", () => {
|
||||
test("keeps pure judgment, preview consent, and install consent separate when unavailable", () => {
|
||||
const result = capabilityReadiness(report([
|
||||
{ id: "managed-runtime", status: "fail", message: "No active managed runtime" },
|
||||
{ id: "capability:pdf", status: "warn", message: "not installed" },
|
||||
]), "pdf");
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
judgment: { status: "available" },
|
||||
platform: { status: "supported" },
|
||||
consent: {
|
||||
preview: { status: "required", granted: false },
|
||||
install: { status: "required-after-preview", granted: false },
|
||||
},
|
||||
readiness: { status: "unavailable" },
|
||||
});
|
||||
});
|
||||
|
||||
test("distinguishes ready, degraded, and failed runtime states", () => {
|
||||
const ready = capabilityReadiness(report([
|
||||
{ id: "managed-runtime", status: "pass", message: "active" },
|
||||
{ id: "capability:design", status: "pass", message: "runnable" },
|
||||
]), "design");
|
||||
const degraded = capabilityReadiness(report([
|
||||
{ id: "managed-runtime", status: "warn", message: "recovered" },
|
||||
{ id: "capability:design", status: "pass", message: "runnable" },
|
||||
]), "design");
|
||||
const failed = capabilityReadiness(report([
|
||||
{ id: "managed-runtime", status: "pass", message: "active" },
|
||||
{ id: "capability:diagram", status: "fail", message: "launcher metadata missing" },
|
||||
]), "diagram");
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
test("reports physical iOS as unsupported without turning off pure judgment", () => {
|
||||
const result = capabilityReadiness(report([]), "ios", { platform: "linux" });
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
judgment: { status: "available" },
|
||||
platform: { status: "unsupported", platform: "linux" },
|
||||
consent: {
|
||||
preview: { status: "not-applicable" },
|
||||
install: { status: "not-applicable" },
|
||||
},
|
||||
readiness: { status: "unsupported" },
|
||||
});
|
||||
});
|
||||
|
||||
test("human output exposes every state axis", () => {
|
||||
const result = capabilityReadiness(report([
|
||||
{ id: "managed-runtime", status: "pass", message: "active" },
|
||||
{ id: "capability:browser", status: "pass", message: "launched" },
|
||||
]), "browser");
|
||||
expect(formatCapabilityReadiness(result)).toContain("judgment: available");
|
||||
expect(formatCapabilityReadiness(result)).toContain("preview consent: not-required");
|
||||
expect(formatCapabilityReadiness(result)).toContain("install consent: not-required");
|
||||
expect(formatCapabilityReadiness(result)).toContain("readiness: ready");
|
||||
});
|
||||
|
||||
test("doctor capability JSON is non-mutating and uses the unified result envelope", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-capability-doctor-"));
|
||||
const home = path.join(root, "home");
|
||||
const stdout = capture();
|
||||
const stderr = capture();
|
||||
try {
|
||||
await setupRuntime({ home, cwd: root });
|
||||
const exit = await main(["doctor", "--capability", "pdf", "--json"], {
|
||||
cwd: root,
|
||||
env: { ...process.env, GSTACK_HOME: home },
|
||||
stdout: stdout.stream,
|
||||
stderr: stderr.stream,
|
||||
});
|
||||
const result = JSON.parse(stdout.value());
|
||||
expect(exit).toBe(1);
|
||||
expect(stderr.value()).toBe("");
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
capability: "pdf",
|
||||
judgment: { status: "available" },
|
||||
readiness: { status: "unavailable" },
|
||||
});
|
||||
} finally {
|
||||
await fs.rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user