-
Notifications
You must be signed in to change notification settings - Fork 0
feat(gstack2): add capability readiness process #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Browser-backed readiness requires an explicit provider choice, so “do not ... add a browser provider” can mislead users into avoiding the supported configuration step. Clarify that the prohibition applies to adding a new/alternate provider implementation, not selecting an existing provider. Prompt for AI agents |
||
| local Chromium/Playwright backend, or replace the physical-iOS | ||
| DebugBridge/CoreDevice harness. | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -27,7 +27,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, | ||||||
|
|
@@ -154,11 +160,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)) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: An explicitly empty capability is silently ignored, so Prompt for AI agents
Suggested change
|
||||||
| 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; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: A capability assessment can roll back and rewrite a pending runtime pointer, despite this command’s non-mutating contract. The focused path still runs Prompt for AI agents |
||||||
| 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 }) { | ||||||
|
|
@@ -724,7 +737,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); | ||||||
|
|
@@ -801,7 +814,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" + | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,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 = []; | ||
|
|
@@ -209,6 +217,83 @@ 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"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Prompt for AI agents |
||
| 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"; | ||
|
|
||
| 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; | ||
|
|
@@ -388,3 +473,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`; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -514,6 +514,8 @@ Some retained helpers are shell scripts. \`gstack doctor\` verifies Bash and, on | |||||
|
|
||||||
| The package/runtime compatibility tuple is \`schemaVersion=1\`, \`runtimeVersion=2.0.0\`, and \`skillApi=2.0\`; the machine-readable copy is \`references/support/runtime-contract.json\`. An incompatible active runtime is unavailable, not permission to upgrade it. | ||||||
|
|
||||||
| 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. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The documented command fails if copied because Prompt for AI agents
Suggested change
|
||||||
|
|
||||||
| The developer-only fallback is \`node references/support/runtime-bootstrap.mjs install --source <reviewed-checkout> --capability <name> [matching browser flags] --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. | ||||||
| `; | ||||||
| } | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The flow sends every
failedresult through bootstrap preview/install, but several failed states cannot be repaired that way. Separateunavailablesetup fromfailedevidence-specific remediation so users do not reinstall an already-selected capability unnecessarily.Prompt for AI agents