diff --git a/packages/cli/bin/engine.js b/packages/cli/bin/engine.js index 7a6ccba..f5c7d28 100644 --- a/packages/cli/bin/engine.js +++ b/packages/cli/bin/engine.js @@ -50,6 +50,11 @@ import { renderHumanArchitecturePackageOutcome, runHumanArchitecturePackage, } from "./human-architecture-package.js"; +import { renderHumanVersion } from "./human-version.js"; +import { + renderHumanHandoffOutcome, + runHumanHandoff, +} from "./human-handoff.js"; import { createSystemFilePicker } from "./system-file-picker.js"; import { consumeInvocationContext, @@ -448,6 +453,19 @@ function print(value) { return; } + if ( + value.operation === "version" + && ["completed", "unavailable", "execution_error"].includes(value.status) + ) { + const presentation = humanAuditPresentationOptions({ + args, + env: process.env, + output: process.stdout, + }); + process.stdout.write(`${renderHumanVersion(value, { styled: presentation.styled })}\n`); + return; + } + if ( value.operation === "audit" && ["needs_input", "needs_confirmation", "needs_permission"].includes(value.status) @@ -601,7 +619,7 @@ async function main() { if (command === "version") { const authority = await resolveExecutionAuthority({ cwd: optionValue("--cwd"), - launcher_version: VERSION, + launcher_version: invocationContext.launcher_version, }); const ready = authority.state === "ready"; const invalid = authority.state === "invalid_toolchain"; @@ -610,7 +628,7 @@ async function main() { status: ready ? "completed" : invalid ? "execution_error" : "unavailable", operation: "version", ...(ready ? { cli_version: authority.engine.version } : {}), - launcher_version: VERSION, + launcher_version: invocationContext.launcher_version, authority, ...(!ready ? { error: authority.state, @@ -1207,6 +1225,7 @@ async function main() { } if (command === "handoff") { + const cwd = optionValue("--cwd") ?? process.cwd(); const resumeToken = optionValue("--resume"); const source = {}; const files = [ @@ -1224,13 +1243,15 @@ async function main() { } } } catch { - print({ + const failure = { contract: CLI_INTERACTION_CONTRACT, status: "execution_error", operation: "handoff", error: "invalid_handoff_input_file", message: "Handoff requires readable Task Graph, Executor, tool, and review JSON files.", - }); + }; + if (json) print(failure); + else process.stdout.write(`${renderHumanHandoffOutcome(failure)}\n`); return 2; } let receipt; @@ -1239,13 +1260,87 @@ async function main() { try { receipt = JSON.parse(await readFile(receiptPath, "utf8")); } catch { - print({ + const failure = { contract: CLI_INTERACTION_CONTRACT, status: "execution_error", operation: "handoff", error: "invalid_execution_receipt_file", message: "The execution receipt JSON could not be read and parsed.", + }; + if (json) print(failure); + else process.stdout.write(`${renderHumanHandoffOutcome(failure)}\n`); + return 2; + } + } + if (!json) { + if (["--resume", "--select", "--confirm", "--choice"].some((option) => + args.includes(option))) { + process.stderr.write([ + "Human Mode does not accept structured Handoff decisions.", + "Use rally handoff --json with explicit resume and decision options for the Agent/CI protocol.", + ].join("\n") + "\n"); + return 2; + } + if (process.stdin.isTTY !== true) { + process.stderr.write([ + "Non-TTY Human Mode cannot coordinate Handoff decisions safely.", + "Use rally handoff --json for the resumable Agent/CI protocol.", + ].join("\n") + "\n"); + return 2; + } + const { createClackPromptAdapter, createPlainPromptAdapter } = await import( + "./prompt-adapters.js" + ); + const presentation = humanAuditPresentationOptions({ + args, + env: process.env, + output: process.stdout, + }); + const prompt = presentation.plain + ? createPlainPromptAdapter({ input: process.stdin, output: process.stderr }) + : await createClackPromptAdapter({ input: process.stdin, output: process.stderr }); + const sourceReportId = source.task_graph?.source_report?.id; + const verifyAction = typeof sourceReportId === "string" + ? createNextAction(invocationContext, [ + "verify", + "--cwd", + path.resolve(cwd), + "--report", + path.resolve(cwd, ".launchrally", "reports", sourceReportId, "record.json"), + "--scope", + "full", + ]) + : null; + try { + const result = await runHumanHandoff({ + source, + receipt, + prompt, + runHandoff, + verifyAction, + loadReceipt: async (receiptFile) => { + try { + return JSON.parse(await readFile(path.resolve(receiptFile), "utf8")); + } catch { + const error = new Error("The execution receipt JSON could not be read and parsed."); + error.code = "invalid_execution_receipt_file"; + throw error; + } + }, }); + return ["unavailable", "execution_error", "stale_input"].includes(result.status) + ? 2 + : 0; + } catch (error) { + if (error instanceof PromptCancelledError) { + process.stderr.write("Handoff cancelled. LaunchRally executed no external Task.\n"); + return 130; + } + process.stdout.write(`${renderHumanHandoffOutcome({ + status: "execution_error", + error: error.code ?? "handoff_human_mode_failed", + message: error.message, + })}\n`); return 2; } } diff --git a/packages/cli/bin/human-handoff.js b/packages/cli/bin/human-handoff.js new file mode 100644 index 0000000..fccb2ed --- /dev/null +++ b/packages/cli/bin/human-handoff.js @@ -0,0 +1,226 @@ +import { terminalSafeText } from "./human-architect.js"; + +function list(values, render = terminalSafeText, fallback = "None") { + return values?.length > 0 + ? values.map((value) => ` - ${render(value)}`) + : [` - ${fallback}`]; +} + +function candidateLines(candidate) { + return [ + `${candidate.recommended ? "Recommended" : "Alternative"}: ${terminalSafeText(candidate.batch_id)}`, + ` Executor: ${terminalSafeText(candidate.executor_id)}`, + ` Tasks: ${candidate.task_ids.map(terminalSafeText).join(", ")}`, + ` Authority: ${terminalSafeText(candidate.effect_class)} on ${terminalSafeText(candidate.target)}`, + ` Availability: ${candidate.available ? "available" : terminalSafeText(candidate.unavailable_reason)}`, + ` Tools: ${candidate.tools.map((tool) => `${terminalSafeText(tool.executable)}@${terminalSafeText(tool.exact_version)}`).join(", ")}`, + ` Authentication: ${terminalSafeText(candidate.authentication_state)} (${candidate.auth_assumptions.map(terminalSafeText).join(", ")})`, + ` Secret handling: ${terminalSafeText(candidate.secret_handling)}`, + ` Cancellation: ${terminalSafeText(candidate.cancellation)}`, + ` Partial failure: ${terminalSafeText(candidate.partial_failure)}`, + ]; +} + +export function renderHumanHandoffDiscovery(result) { + const lines = [ + "External Executor Discovery", + "LaunchRally does not install, log in, request credentials, or execute external Tasks.", + "Ready authority boundaries:", + ...list(result.preview?.user_visible_effects), + ]; + if (result.candidates?.length > 0) { + lines.push( + "Reviewed Executor candidates:", + ...result.candidates.flatMap(candidateLines), + ); + } + if (result.recovery) { + lines.push( + "Executor recovery:", + ` Reason: ${terminalSafeText(result.recovery.reason)}`, + ` Tool: ${terminalSafeText(result.recovery.tool.executable)}@${terminalSafeText(result.recovery.tool.exact_version)}`, + ` Official manual: ${terminalSafeText(result.recovery.official_manual.url)}`, + ...(result.recovery.installation_instructions?.length > 0 + ? [ + " User-managed commands (not executed):", + ...result.recovery.installation_instructions.map(({ command }) => + ` - ${[command.executable, ...command.arguments].map(terminalSafeText).join(" ")}`), + ] + : []), + ); + } + if (result.manual_path) { + lines.push( + "Manual or custom Executor path is available without granting authority.", + `Required capabilities: ${result.manual_path.required_capabilities?.map(terminalSafeText).join(", ") || "none"}`, + ); + } + return lines.join("\n"); +} + +export function renderHumanHandoffAuthority(result) { + const handoff = result.handoff_package; + const authority = handoff.authority_batch; + return [ + "Exact External Authority Preview", + `Handoff Package: ${terminalSafeText(handoff.handoff_id)}`, + `Environment: ${terminalSafeText(handoff.environment)}`, + `Executor: ${terminalSafeText(handoff.executor.id)}`, + "Tasks:", + ...list(handoff.task_ids), + "Allowed effects:", + ...list(authority.allowed_effects), + "Prohibited effects:", + ...list(authority.prohibited_effects), + "User-visible effects:", + ...list(authority.user_visible_effects), + `Authentication: ${terminalSafeText(authority.executor_requirements.authentication_state)} (${authority.executor_requirements.auth_assumptions.map(terminalSafeText).join(", ")})`, + `Secret handling: ${terminalSafeText(authority.executor_requirements.secret_handling)}`, + `Cancellation: ${terminalSafeText(authority.coordination.cancellation)}`, + `Partial failure: ${terminalSafeText(authority.coordination.partial_failure)}`, + "Confirmation grants only this exact external authority. LaunchRally still performs no external execution.", + ].join("\n"); +} + +export function renderHumanHandoffReceipt(result) { + const receipt = result.execution_receipt; + const outcomes = result.execution_outcomes ?? []; + return [ + receipt ? "Execution Receipt Review" : "Approved Handoff Package", + `Handoff Package: ${terminalSafeText(result.handoff_package?.handoff_id)}`, + `Approval: ${terminalSafeText(result.handoff_package?.approval?.state)}`, + ...(receipt ? [ + `Receipt: ${terminalSafeText(receipt.receipt_id)}`, + "Classification: claim only; not Machine Evidence; verification remains unverified.", + "Reported Task outcomes:", + ...list(outcomes, (outcome) => [ + terminalSafeText(outcome.task_id), + terminalSafeText(outcome.receipt_state), + outcome.remaining_work?.state === "required" + ? `remaining work ${terminalSafeText(outcome.remaining_work.coordination)}` + : "no remaining work reported", + ].join(" — ")), + ] : [ + "Run the approved package with the selected external Executor outside LaunchRally, then submit its normalized Execution Receipt.", + "The receipt must be secret-free and exactly bound to this Handoff Package.", + ]), + ].join("\n"); +} + +export function renderHumanHandoffOutcome(result, { verifyAction } = {}) { + if (result.status === "completed" && result.next?.operation === "verify") { + return [ + "Fresh Verify Requires a Separate Command", + "The Execution Receipt remains a claim and has not been promoted to Machine Evidence.", + `Verify scope: ${terminalSafeText(result.next.scope)}`, + "Task requests:", + ...list(result.next.task_requests, (request) => + `${terminalSafeText(request.task_id)} — ${terminalSafeText(request.scope)}; Evidence: ${request.evidence_targets.map(terminalSafeText).join(", ")}`), + "Handoff does not carry the source Report contents or Check IDs required to start targeted Verify safely.", + "Run this complete Human Verify command:", + terminalSafeText(verifyAction?.display ?? "A complete Verify command is unavailable."), + ...(verifyAction?.disclosure + ? [`Launcher entry: ${terminalSafeText(verifyAction.disclosure)}`] + : []), + ].join("\n"); + } + if (result.outcome === "manual_or_custom_selected") { + return [ + "Manual or Custom Executor Selected", + "No external authority was granted and no external execution was started.", + ].join("\n"); + } + if ( + ["deferred", "receipt_deferred"].includes(result.outcome) + || (result.status === "resumable" && result.state === "verification_pending") + ) { + let detail = "No external authority was granted."; + if (result.state === "verification_pending") { + detail = "The Execution Receipt remains a claim and fresh Verify remains pending."; + } else if (result.outcome === "receipt_deferred") { + detail = "The approved package remains external; no receipt was accepted."; + } + return [ + "Handoff Deferred", + detail, + "Run Handoff again with current inputs when you are ready to continue.", + ].join("\n"); + } + if (result.status === "denied") { + return [ + "External Authority Declined", + "No external authority was granted and no external Task was executed by LaunchRally.", + ].join("\n"); + } + if (result.status === "cancelled") { + return [ + "Handoff Cancelled", + `Authority granted before cancellation: ${result.safety?.authority_granted ? "yes" : "no"}`, + "LaunchRally did not execute an external Task.", + ].join("\n"); + } + if (result.status === "stale_input") { + return [ + "Handoff Inputs Are Stale", + "Refresh the Task Graph and reviewed Executor inputs before granting authority.", + ].join("\n"); + } + if (result.status === "execution_error" || result.status === "unavailable") { + return [ + "Handoff Could Not Continue", + `Error: ${terminalSafeText(result.error)}`, + ...(result.message ? [`Message: ${terminalSafeText(result.message)}`] : []), + "No additional authority was granted by this failure.", + ].join("\n"); + } + return [ + "Handoff Complete", + `Outcome: ${terminalSafeText(result.outcome ?? result.status)}`, + "LaunchRally did not execute an external Task.", + ].join("\n"); +} + +export async function runHumanHandoff({ + source, + receipt, + prompt, + runHandoff, + loadReceipt, + verifyAction, +}) { + let result; + let suppliedReceipt = receipt; + try { + await prompt.start?.("handoff"); + result = await runHandoff(source, {}); + while (["needs_input", "needs_confirmation", "resumable", "partial_completion"] + .includes(result.status)) { + const response = await prompt.respondHandoff(result, { + receiptAvailable: suppliedReceipt !== undefined, + }); + const options = { resume_token: result.resume_token }; + if (result.request?.kind === "executor_selection") { + options.selection = response.selection; + } else if (result.request?.kind === "authority_confirmation") { + options.confirmation = response.confirmation; + } else if (result.request?.kind === "execution_receipt") { + if (response.choice === "submit") { + if (suppliedReceipt === undefined) { + suppliedReceipt = await loadReceipt(response.receipt_path); + } + options.receipt = suppliedReceipt; + } else { + options.choice = response.choice; + } + } else { + options.choice = response.choice; + } + result = await runHandoff({}, options); + if (response.choice === "defer") break; + } + await prompt.finishHandoff(result, { verifyAction }); + return result; + } finally { + await prompt.close(); + } +} diff --git a/packages/cli/bin/human-version.js b/packages/cli/bin/human-version.js new file mode 100644 index 0000000..31f6c0a --- /dev/null +++ b/packages/cli/bin/human-version.js @@ -0,0 +1,28 @@ +import { terminalSafeText } from "./human-architect.js"; + +function styledText(value, style, enabled) { + return enabled ? `\u001B[${style}m${value}\u001B[0m` : value; +} + +function nextActionLabel(authority) { + const action = authority?.next_action; + if (!action) return "unavailable"; + if (action.operation === "none") return "none"; + return action.operation ?? "unavailable"; +} + +export function renderHumanVersion(result, { styled = false } = {}) { + const authority = result.authority ?? {}; + const engineLabel = authority.source === "project_toolchain" + ? "Project Engine" + : "Selected Engine"; + return [ + styledText("LaunchRally Version", "1;36", styled), + `Launcher: ${styledText(terminalSafeText(result.launcher_version), "1", styled)}`, + `${engineLabel}: ${styledText(terminalSafeText(result.cli_version ?? authority.engine?.version), "1", styled)}`, + `Authority: ${styledText(terminalSafeText(authority.state), "1", styled)} (${terminalSafeText(authority.source)})`, + `Compatibility: ${terminalSafeText(authority.engine?.compatibility)}`, + `Materialization: ${terminalSafeText(authority.materialization?.state)}`, + `Next action: ${terminalSafeText(nextActionLabel(authority))}`, + ].join("\n"); +} diff --git a/packages/cli/bin/prompt-adapters.js b/packages/cli/bin/prompt-adapters.js index 8080f8b..b90074f 100644 --- a/packages/cli/bin/prompt-adapters.js +++ b/packages/cli/bin/prompt-adapters.js @@ -22,6 +22,12 @@ import { renderHumanArchitecturePackageOutcome, renderHumanArchitecturePackagePreview, } from "./human-architecture-package.js"; +import { + renderHumanHandoffAuthority, + renderHumanHandoffDiscovery, + renderHumanHandoffOutcome, + renderHumanHandoffReceipt, +} from "./human-handoff.js"; import { renderHumanInit, renderHumanInitFullPreview } from "./human-init.js"; function styleTextSupportsArrays() { @@ -237,9 +243,79 @@ function operationTitle(operation) { if (operation === "verify") return "LaunchRally Verify"; if (operation === "architect") return "LaunchRally Architect"; if (operation === "architecture-package") return "LaunchRally Architecture Package"; + if (operation === "handoff") return "LaunchRally External Executor Handoff"; return "LaunchRally Audit"; } +function handoffChoiceOptions(result, { receiptAvailable = false } = {}) { + if (result.request?.kind === "executor_selection") { + return result.candidates.filter(({ available }) => available).map((candidate) => ({ + label: `${candidate.recommended ? "Recommended — " : ""}${candidate.executor_id}: ${candidate.effect_class} on ${candidate.target}`, + value: candidate.batch_id, + })); + } + if (result.request?.kind === "authority_confirmation") { + return [ + { label: "Confirm this exact external authority", value: "confirm" }, + { label: "Decline", value: "deny" }, + { label: "Cancel", value: "cancel" }, + ]; + } + if (result.request?.kind === "execution_receipt") { + return [ + { + label: receiptAvailable + ? "Submit the supplied normalized Execution Receipt" + : "Submit a normalized Execution Receipt file", + value: "submit", + }, + { label: "Defer receipt review", value: "defer" }, + { label: "Cancel external coordination", value: "cancel" }, + ]; + } + if (result.request?.kind === "fresh_verification") { + return [ + { label: "Prepare the fresh Verify command", value: "verify" }, + { label: "Defer fresh Verify", value: "defer" }, + { label: "Cancel", value: "cancel" }, + ]; + } + const labels = { + show_install_instructions: "Show reviewed user-managed installation instructions", + open_official_manual: "Show the official manual", + manual_or_custom: "Use a manual or custom Executor", + defer: "Defer Handoff", + cancel: "Cancel", + refresh: "Refresh Handoff inputs", + }; + return (result.request?.choices ?? []).map((choice) => ({ + label: labels[choice] ?? choice, + value: choice, + })); +} + +function handoffInitialValue(result) { + if (result.request?.kind === "executor_selection") { + return result.candidates.find(({ available, recommended }) => available && recommended) + ?.batch_id; + } + if (result.request?.kind === "authority_confirmation") return "deny"; + if (["execution_receipt", "fresh_verification"].includes(result.request?.kind)) { + return "defer"; + } + return result.request?.choices?.includes("defer") ? "defer" : undefined; +} + +function handoffPresentation(result) { + if (result.request?.kind === "authority_confirmation") { + return { title: "Exact authority preview", content: renderHumanHandoffAuthority(result) }; + } + if (["execution_receipt", "fresh_verification"].includes(result.request?.kind)) { + return { title: "Receipt and verification", content: renderHumanHandoffReceipt(result) }; + } + return { title: "Executor discovery", content: renderHumanHandoffDiscovery(result) }; +} + function terminalSafeText(value) { return stripVTControlCharacters(String(value)) .replace(/[\u0000-\u001f\u007f-\u009f]/gu, " ") @@ -1088,6 +1164,35 @@ export function createPlainPromptAdapter({ async finishArchitecturePackage(result, bundle) { write(output, renderHumanArchitecturePackageOutcome(result, bundle)); }, + async respondHandoff(result, context = {}) { + const presentation = handoffPresentation(result); + write(output, presentation.content); + const value = await choose( + result.request?.kind === "authority_confirmation" + ? "Grant only this exact external authority?" + : result.request?.kind === "execution_receipt" + ? "Review the external execution receipt:" + : result.request?.kind === "fresh_verification" + ? "Choose the fresh verification action:" + : "Choose how to continue:", + handoffChoiceOptions(result, context), + handoffInitialValue(result), + ); + if (result.request?.kind === "executor_selection") return { selection: value }; + if (result.request?.kind === "authority_confirmation") return { confirmation: value }; + if (result.request?.kind === "execution_receipt" && value === "submit") { + return { + choice: value, + ...(context.receiptAvailable + ? {} + : { receipt_path: (await ask("Execution Receipt JSON path:")).trim() }), + }; + } + return { choice: value }; + }, + async finishHandoff(result, context = {}) { + write(output, renderHumanHandoffOutcome(result, context)); + }, async close() { signals.off("SIGINT", handleInterrupt); readline.close(); @@ -1524,6 +1629,36 @@ export async function createClackPromptAdapter({ common, ); }, + async respondHandoff(result, context = {}) { + const presentation = handoffPresentation(result); + clack.note(presentation.content, presentation.title, common); + const value = cancelled(await clack.select({ + ...common, + message: result.request?.kind === "authority_confirmation" + ? "Grant only this exact external authority?" + : result.request?.kind === "execution_receipt" + ? "Review the external execution receipt" + : result.request?.kind === "fresh_verification" + ? "Choose the fresh verification action" + : "Choose how to continue", + options: handoffChoiceOptions(result, context), + initialValue: handoffInitialValue(result), + }), clack, output, "Handoff"); + if (result.request?.kind === "executor_selection") return { selection: value }; + if (result.request?.kind === "authority_confirmation") return { confirmation: value }; + if (result.request?.kind === "execution_receipt" && value === "submit") { + return { + choice: value, + ...(context.receiptAvailable + ? {} + : { receipt_path: (await ask("Execution Receipt JSON path:", { required: true })).trim() }), + }; + } + return { choice: value }; + }, + async finishHandoff(result, context = {}) { + clack.note(renderHumanHandoffOutcome(result, context), "Handoff outcome", common); + }, async close() { signals.off("SIGINT", handleInterrupt); }, diff --git a/packages/cli/bin/rally.js b/packages/cli/bin/rally.js index 35b11f0..1abef38 100755 --- a/packages/cli/bin/rally.js +++ b/packages/cli/bin/rally.js @@ -19,10 +19,12 @@ const REPOSITORY_OPERATIONS = new Set([ "audit", "architect", "architecture-package", + "handoff", "init", "plan", "providers", "verify", + "version", ]); const BUNDLED_ENGINE = fileURLToPath(new URL("./engine.js", import.meta.url)); @@ -134,6 +136,9 @@ async function main() { launcher_version: VERSION, }); if (authority.state !== "ready") { + if (command === "version") { + return await runEngine(BUNDLED_ENGINE, arguments_, invocationContext); + } const cwd = path.resolve(optionValue(arguments_, "--cwd") ?? process.cwd()); printAuthorityFailure( command, diff --git a/release/artifacts.json b/release/artifacts.json index bb5d323..cda9532 100644 --- a/release/artifacts.json +++ b/release/artifacts.json @@ -166,8 +166,10 @@ "bin/human-architecture-package.js", "bin/human-authenticated-journey.js", "bin/human-audit.js", + "bin/human-handoff.js", "bin/human-init.js", "bin/human-verify.js", + "bin/human-version.js", "bin/invocation-context.js", "bin/prompt-adapters.js", "bin/rally.js", diff --git a/scripts/test-release-artifacts.mjs b/scripts/test-release-artifacts.mjs index 05f670a..f3f13a2 100644 --- a/scripts/test-release-artifacts.mjs +++ b/scripts/test-release-artifacts.mjs @@ -20,7 +20,7 @@ import { createRequire, syncBuiltinESMExports } from "node:module"; import { createServer } from "node:https"; import { setTimeout as delay } from "node:timers/promises"; import { fileURLToPath, pathToFileURL } from "node:url"; -import { promisify } from "node:util"; +import { promisify, stripVTControlCharacters } from "node:util"; import { hasClaudeInstalledPlugin } from "./native-plugin-state.mjs"; import { createIsolatedNativeEnvironment } from "./native-environment.mjs"; import { assertNoConsumerInstallScripts } from "./release-artifact-policy.mjs"; @@ -113,6 +113,32 @@ async function runNpm(arguments_, options = {}) { : run("npm", arguments_, options); } +async function runInPty(command, arguments_, options = {}) { + if (process.platform === "win32") return null; + const runner = [ + "import errno, fcntl, os, pty, struct, subprocess, sys, termios", + "master, slave = pty.openpty()", + "fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack('HHHH', 30, 100, 0, 0))", + "child = subprocess.Popen(sys.argv[1:], stdin=slave, stdout=slave, stderr=slave, close_fds=True)", + "os.close(slave)", + "chunks = []", + "while True:", + " try:", + " chunk = os.read(master, 4096)", + " except OSError as error:", + " if error.errno == errno.EIO:", + " break", + " raise", + " if not chunk:", + " break", + " chunks.append(chunk)", + "os.close(master)", + "sys.stdout.buffer.write(b''.join(chunks))", + "raise SystemExit(child.wait())", + ].join("\n"); + return run("python3", ["-c", runner, command, ...arguments_], options); +} + async function createArtifactNpmStub( temporaryRoot, cleanProject, @@ -2253,6 +2279,35 @@ async function runInstallationJourneys({ || projectVersion.cli_version !== version || projectVersion.authority?.source !== "project_toolchain" ) throw new Error("project_engine_delegation_failed"); + let humanProjectVersion = "typed_runner_unavailable"; + if (process.platform !== "win32") { + const humanVersion = (await runInPty(launcher, [ + "version", + "--cwd", + activeRepository, + ], { + cwd: temporaryRoot, + env: launcherEnvironment, + })).stdout; + const semanticHumanVersion = stripVTControlCharacters(humanVersion); + for (const expected of [ + "LaunchRally Version", + `Launcher: ${version}`, + `Project Engine: ${version}`, + "Authority: ready (project_toolchain)", + "Compatibility: native", + "Materialization: ready", + "Next action: none", + ]) { + if (!semanticHumanVersion.includes(expected)) { + throw new Error(`packed_project_version_human_summary_missing:${expected}`); + } + } + if (/"(?:contract|schema_version|authority)"\s*:/u.test(semanticHumanVersion)) { + throw new Error("packed_project_version_human_raw_json_leak"); + } + humanProjectVersion = "concise_project_engine_summary"; + } const manifestContent = await readFile( path.join(activeRepository, ".launchrally", "manifest.yaml"), @@ -2860,6 +2915,7 @@ async function runInstallationJourneys({ : "artifact_equivalent_audit_and_follow_up", user_prefix: "installed_and_verified", project_engine: "initialized_and_delegated", + human_project_version: humanProjectVersion, fresh_clone: "restored_offline", registry_permission: "cache_miss_approved_and_denied", invalid_authority: "corruption_failed_closed", diff --git a/test/handoff.test.js b/test/handoff.test.js index 5b714a1..ff7282f 100644 --- a/test/handoff.test.js +++ b/test/handoff.test.js @@ -916,7 +916,7 @@ test("the public JSON CLI exposes typed discovery and resumable authority previe assert.equal(preview.status, "needs_confirmation"); assert.equal(preview.handoff_package.approval.state, "required"); - const human = await execFileAsync(process.execPath, [ + await assert.rejects(execFileAsync(process.execPath, [ ...cliClockArguments, engine, "handoff", @@ -928,12 +928,10 @@ test("the public JSON CLI exposes typed discovery and resumable authority previe toolsPath, "--reviewed-executors", reviewsPath, - ]); - assert.match(human.stdout, /LaunchRally External Executor Handoff/u); - assert.match(human.stdout, /does not install, log in, request credentials, or execute/u); - assert.match(human.stdout, /provider_configuration on identity_authentication/u); - assert.match(human.stdout, /cancellation supported_between_effects/u); - assert.match(human.stdout, /partial failure reported_per_task/u); - assert.match(human.stdout, /authentication user_managed_unverified/u); - assert.match(human.stdout, /secret handling external_reference_only/u); + ]), (error) => { + assert.equal(error.code, 2); + assert.match(error.stderr, /Non-TTY Human Mode cannot coordinate Handoff decisions safely/u); + assert.match(error.stderr, /Use rally handoff --json/u); + return true; + }); }); diff --git a/test/launcher.test.js b/test/launcher.test.js index 0c6b8fd..c44a430 100644 --- a/test/launcher.test.js +++ b/test/launcher.test.js @@ -4,8 +4,12 @@ import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { promisify } from "node:util"; +import { pathToFileURL } from "node:url"; +import { promisify, stripVTControlCharacters } from "node:util"; +import { computeExecutorDescriptorDigest } from "../packages/contracts/src/index.js"; +import { runHandoff } from "../packages/core/src/index.js"; +import { sha256 } from "../packages/core/src/local-history.js"; import { materializeExactToolchain, writeExactToolchain, @@ -13,9 +17,97 @@ import { const execFileAsync = promisify(execFile); const launcher = path.resolve("packages/cli/bin/rally.js"); +const engine = path.resolve("packages/cli/bin/engine.js"); const currentVersion = JSON.parse(await readFile("package.json", "utf8")).version; const pythonAvailable = process.platform !== "win32" && spawnSync("python3", ["--version"]).status === 0; +const ptyRunner = [ + "import errno, os, pty, subprocess, sys", + "master, slave = pty.openpty()", + "child = subprocess.Popen(sys.argv[1:], stdin=slave, stdout=slave, stderr=slave, close_fds=True)", + "os.close(slave)", + "chunks = []", + "while True:", + " try:", + " chunk = os.read(master, 4096)", + " except OSError as error:", + " if error.errno == errno.EIO:", + " break", + " raise", + " if not chunk:", + " break", + " chunks.append(chunk)", + "os.close(master)", + "sys.stdout.buffer.write(b''.join(chunks))", + "raise SystemExit(child.wait())", +].join("\n"); +const answeringPtyRunner = [ + "import errno, os, pty, select, subprocess, sys", + "answers = sys.argv[1].splitlines()", + "master, slave = pty.openpty()", + "child = subprocess.Popen(sys.argv[2:], stdin=slave, stdout=slave, stderr=slave, close_fds=True)", + "os.close(slave)", + "chunks = []", + "observed = b''", + "answered = 0", + "while True:", + " ready, _, _ = select.select([master], [], [], 5)", + " if not ready:", + " child.terminate()", + " break", + " try:", + " chunk = os.read(master, 4096)", + " except OSError as error:", + " if error.errno == errno.EIO:", + " break", + " raise", + " if not chunk:", + " break", + " chunks.append(chunk)", + " observed += chunk", + " prompts = observed.count(b'Choose 1-')", + " while answered < min(prompts, len(answers)):", + " os.write(master, answers[answered].encode() + b'\\n')", + " answered += 1", + "os.close(master)", + "sys.stdout.buffer.write(b''.join(chunks))", + "raise SystemExit(child.wait())", +].join("\n"); +const clackPtyRunner = [ + "import errno, fcntl, os, pty, select, struct, subprocess, sys, termios", + "patterns = [value.encode() for value in sys.argv[1].split('|||')]", + "master, slave = pty.openpty()", + "fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack('HHHH', 30, 100, 0, 0))", + "child = subprocess.Popen(sys.argv[2:], stdin=slave, stdout=slave, stderr=slave, close_fds=True)", + "os.close(slave)", + "chunks = []", + "observed = b''", + "answered = 0", + "while True:", + " ready, _, _ = select.select([master], [], [], 5)", + " if not ready:", + " child.terminate()", + " break", + " try:", + " chunk = os.read(master, 4096)", + " except OSError as error:", + " if error.errno == errno.EIO:", + " break", + " raise", + " if not chunk:", + " break", + " chunks.append(chunk)", + " observed += chunk", + " if answered < len(patterns) and patterns[answered] in observed:", + " os.write(master, b'\\r')", + " answered += 1", + "os.close(master)", + "sys.stdout.buffer.write(b''.join(chunks))", + "raise SystemExit(child.wait())", +].join("\n"); +const fixedClock = pathToFileURL( + path.resolve("test/helpers/fixed-provider-knowledge-clock.js"), +).href; function validManifest() { const unknown = (reason) => ({ state: "unknown", reason }); @@ -87,6 +179,93 @@ async function projectWithEngine( return repository; } +async function writeHandoffInputs(repository, { available = true } = {}) { + const fixture = JSON.parse(await readFile( + path.resolve("test/fixtures/phase-1-contracts/handoff.valid.json"), + "utf8", + )); + const executor = structuredClone(fixture.executor); + const currentPlatform = `${process.platform}-${process.arch}`; + if (!executor.platforms.includes(currentPlatform)) { + executor.platforms.push(currentPlatform); + } + executor.prohibited_effects = [ + ...fixture.task_graph.tasks[0].prohibited_effects, + ]; + executor.trust.digest = computeExecutorDescriptorDigest(executor); + const source = { + task_graph: structuredClone(fixture.task_graph), + executor_descriptors: [executor], + reviewed_executors: [{ + descriptor_id: executor.descriptor_id, + descriptor_version: executor.descriptor_version, + digest: executor.trust.digest, + }], + tool_observations: [{ + tool_id: "codex_cli", + executable: "codex", + detected_version: available ? "0.147.0" : null, + state: available ? "available" : "missing", + }], + }; + const paths = {}; + for (const [field, file] of [ + ["task_graph", "task-graph.json"], + ["executor_descriptors", "executors.json"], + ["tool_observations", "tools.json"], + ["reviewed_executors", "reviewed-executors.json"], + ]) { + paths[field] = path.join(repository, file); + await writeFile(paths[field], `${JSON.stringify(source[field], null, 2)}\n`); + } + return { fixture, paths, source }; +} + +function handoffArguments(repository, paths) { + return [ + "handoff", + "--cwd", + repository, + "--task-graph", + paths.task_graph, + "--executors", + paths.executor_descriptors, + "--tools", + paths.tool_observations, + "--reviewed-executors", + paths.reviewed_executors, + ]; +} + +function assertNoHandoffToken(output) { + assert.doesNotMatch(output, /lrhandoff_[A-Za-z0-9_-]{43}/u); +} + +async function deterministicApprovedHandoff(source) { + let state; + const dependencies = { + platform: `${process.platform}-${process.arch}`, + now: () => "2026-08-13T00:00:00.000Z", + store_state: async (next) => { + state = structuredClone(next); + return "handoff_resume_test"; + }, + load_state: async () => structuredClone(state), + save_state: async (next) => { + state = structuredClone(next); + }, + }; + const discovered = await runHandoff(source, {}, dependencies); + const preview = await runHandoff({}, { + resume_token: discovered.resume_token, + selection: discovered.request.choices[0], + }, dependencies); + return runHandoff({}, { + resume_token: preview.resume_token, + confirmation: "confirm", + }, dependencies); +} + test("rally delegates repository operations to the validated project Engine", async () => { const repository = await projectWithEngine([ "process.stdout.write(JSON.stringify({", @@ -110,6 +289,348 @@ test("rally delegates repository operations to the validated project Engine", as }); }); +test("public rally version renders a concise Human summary through the materialized Project Engine", { + skip: pythonAvailable ? false : "A local Python 3 PTY is required.", +}, async () => { + const repository = await projectWithEngine( + `await import(${JSON.stringify(pathToFileURL(engine).href)});\n`, + currentVersion, + ); + + const { stdout } = await execFileAsync("python3", [ + "-c", + ptyRunner, + process.execPath, + launcher, + "version", + "--cwd", + repository, + ]); + + const semanticOutput = stripVTControlCharacters(stdout); + assert.match(semanticOutput, /LaunchRally Version/u); + assert.match(semanticOutput, new RegExp(`Launcher: ${currentVersion.replaceAll(".", "\\.")}`, "u")); + assert.match(semanticOutput, new RegExp(`Project Engine: ${currentVersion.replaceAll(".", "\\.")}`, "u")); + assert.match(semanticOutput, /Authority: ready \(project_toolchain\)/u); + assert.match(semanticOutput, /Compatibility: native/u); + assert.match(semanticOutput, /Materialization: ready/u); + assert.match(semanticOutput, /Next action: none/u); + assert.doesNotMatch(semanticOutput, /"contract"\s*:/u); + assert.doesNotMatch(semanticOutput, /"schema_version"\s*:/u); + assert.doesNotMatch(semanticOutput, /"authority"\s*:/u); +}); + +test("public rally version preserves the exact JSON contract through the Project Engine", async () => { + const repository = await projectWithEngine( + `await import(${JSON.stringify(pathToFileURL(engine).href)});\n`, + currentVersion, + ); + + const result = JSON.parse((await execFileAsync(process.execPath, [ + launcher, + "version", + "--json", + "--cwd", + repository, + ])).stdout); + + assert.equal(result.contract, "launchrally.dev/cli/v2"); + assert.equal(result.status, "completed"); + assert.equal(result.operation, "version"); + assert.equal(result.cli_version, currentVersion); + assert.equal(result.launcher_version, currentVersion); + assert.equal(result.authority.state, "ready"); + assert.equal(result.authority.source, "project_toolchain"); + assert.equal(result.authority.engine.compatibility, "native"); + assert.equal(result.authority.materialization.state, "ready"); +}); + +test("public rally handoff keeps resume tokens internal through receipt review and Verify routing", { + skip: pythonAvailable ? false : "A local Python 3 PTY is required.", +}, async () => { + const repository = await projectWithEngine( + `await import(${JSON.stringify(pathToFileURL(engine).href)});\n`, + currentVersion, + ); + const { paths, source } = await writeHandoffInputs(repository); + const approved = await deterministicApprovedHandoff(source); + const receiptPath = path.join(repository, "execution-receipt.json"); + await writeFile(receiptPath, `${JSON.stringify({ + schema_version: "launchrally.dev/execution-receipt/v1", + receipt_id: "receipt_public_human_01", + handoff: { + id: approved.handoff_package.handoff_id, + schema_version: approved.handoff_package.schema_version, + digest: sha256(approved.handoff_package), + }, + executor: structuredClone(approved.handoff_package.executor), + reported_at: "2026-08-13T00:01:00.000Z", + task_results: [{ + task_id: "task_configure_identity", + state: "partial", + claim_codes: ["execution_partial"], + }], + classification: { + claim_only: true, + machine_evidence: false, + verification_status: "unverified", + }, + retention: { + raw_stdout_retained: false, + raw_stderr_retained: false, + response_body_retained: false, + sensitive_data_retained: false, + }, + }, null, 2)}\n`); + + const { stdout } = await execFileAsync("python3", [ + "-c", + answeringPtyRunner, + "1\n1\n1\n1\n", + process.execPath, + launcher, + ...handoffArguments(repository, paths), + "--plain", + "--receipt", + receiptPath, + ], { + env: { + ...process.env, + TERM: "xterm-256color", + NODE_OPTIONS: `--import=${fixedClock}`, + }, + }); + + assert.match(stdout, /LaunchRally External Executor Handoff/u); + assert.match(stdout, /External Executor Discovery/u); + assert.match(stdout, /Recommended: batch_executor_provider_config_task_configure_identity/u); + assert.match(stdout, /Exact External Authority Preview/u); + assert.match(stdout, /Approved Handoff Package/u); + assert.match(stdout, /Execution Receipt Review/u); + assert.match(stdout, /remaining work retry_unfinished_effects/u); + assert.match(stdout, /Fresh Verify Requires a Separate Command/u); + assert.match(stdout, /Run this complete Human Verify command:/u); + assert.match(stdout, new RegExp([ + `npm exec --package=@launchrally/cli@${currentVersion.replaceAll(".", "\\.")} -- rally verify`, + `--cwd '${repository.replaceAll("'", "'\\\\''")}'`, + `--report '${path.join(repository, ".launchrally/reports/report_task_graph_01/record.json").replaceAll("'", "'\\\\''")}'`, + "--scope full", + ].join(" "), "u")); + assert.match(stdout, /Handoff does not carry the source Report contents or Check IDs required to start targeted Verify safely/u); + assert.doesNotMatch(stdout, /Resume token:/u); + assertNoHandoffToken(stdout); + assert.doesNotMatch(stdout, /"contract"\s*:/u); +}); + +test("public rally handoff preserves the complete Agent Mode resume chain", async () => { + const repository = await projectWithEngine( + `await import(${JSON.stringify(pathToFileURL(engine).href)});\n`, + currentVersion, + ); + const { paths } = await writeHandoffInputs(repository); + + const invokeHandoff = async (arguments_) => JSON.parse((await execFileAsync(process.execPath, [ + launcher, + ...arguments_, + "--json", + ], { + env: { ...process.env, NODE_OPTIONS: `--import=${fixedClock}` }, + })).stdout); + const discovered = await invokeHandoff(handoffArguments(repository, paths)); + + assert.equal(discovered.contract, "launchrally.dev/handoff-interaction/v1"); + assert.equal(discovered.status, "needs_input"); + assert.equal(discovered.state, "executor_discovery"); + assert.equal(discovered.request.kind, "executor_selection"); + assert.match(discovered.resume_token, /^lrhandoff_[A-Za-z0-9_-]{43}$/u); + assert.equal(discovered.safety.authority_granted, false); + + const preview = await invokeHandoff([ + "handoff", "--cwd", repository, + "--resume", discovered.resume_token, + "--select", discovered.request.choices[0], + ]); + assert.equal(preview.status, "needs_confirmation"); + assert.equal(preview.state, "authority_preview"); + assert.equal(preview.resume_token, discovered.resume_token); + assert.equal(preview.handoff_package.approval.state, "required"); + + const approved = await invokeHandoff([ + "handoff", "--cwd", repository, + "--resume", preview.resume_token, + "--confirm", "confirm", + ]); + assert.equal(approved.status, "resumable"); + assert.equal(approved.state, "receipt_review"); + assert.equal(approved.resume_token, discovered.resume_token); + assert.equal(approved.handoff_package.approval.state, "approved"); + + const receiptPath = path.join(repository, "agent-execution-receipt.json"); + await writeFile(receiptPath, `${JSON.stringify({ + schema_version: "launchrally.dev/execution-receipt/v1", + receipt_id: "receipt_public_agent_01", + handoff: { + id: approved.handoff_package.handoff_id, + schema_version: approved.handoff_package.schema_version, + digest: sha256(approved.handoff_package), + }, + executor: structuredClone(approved.handoff_package.executor), + reported_at: "2026-08-13T00:01:00.000Z", + task_results: [{ + task_id: "task_configure_identity", + state: "reported_succeeded", + claim_codes: ["configuration_submitted"], + }], + classification: { + claim_only: true, + machine_evidence: false, + verification_status: "unverified", + }, + retention: { + raw_stdout_retained: false, + raw_stderr_retained: false, + response_body_retained: false, + sensitive_data_retained: false, + }, + }, null, 2)}\n`); + const reviewed = await invokeHandoff([ + "handoff", "--cwd", repository, + "--resume", approved.resume_token, + "--receipt", receiptPath, + ]); + assert.equal(reviewed.status, "partial_completion"); + assert.equal(reviewed.state, "receipt_review"); + assert.equal(reviewed.resume_token, discovered.resume_token); + assert.equal(reviewed.request.kind, "fresh_verification"); + assert.equal(reviewed.execution_receipt.classification.machine_evidence, false); + + const routed = await invokeHandoff([ + "handoff", "--cwd", repository, + "--resume", reviewed.resume_token, + "--choice", "verify", + ]); + assert.equal(routed.status, "completed"); + assert.equal(routed.state, "completed"); + assert.equal(routed.resume_token, null); + assert.equal(routed.next.operation, "verify"); + assert.equal(routed.next.scope, "targeted"); + assert.equal(routed.next.fresh_evidence_required, true); +}); + +test("default TTY Handoff uses styled safe-default selection and authority denial", { + skip: pythonAvailable ? false : "A local Python 3 PTY is required.", +}, async () => { + const repository = await projectWithEngine( + `await import(${JSON.stringify(pathToFileURL(engine).href)});\n`, + currentVersion, + ); + const { paths } = await writeHandoffInputs(repository); + const styledEnv = { + ...process.env, + TERM: "xterm-256color", + NODE_OPTIONS: `--import=${fixedClock}`, + }; + delete styledEnv.NO_COLOR; + + const { stdout } = await execFileAsync("python3", [ + "-c", + clackPtyRunner, + "Choose how to continue|||Grant only this exact external authority?", + process.execPath, + launcher, + ...handoffArguments(repository, paths), + ], { + env: styledEnv, + }); + + assert.match(stdout, /LaunchRally External Executor Handoff/u); + assert.match(stdout, /Recommended/u); + assert.match(stdout, /Exact External Authority Preview/u); + assert.match(stdout, /External Authority Declined/u); + assert.match(stdout, /\u001B\[/u); + assert.doesNotMatch(stdout, /Resume token:/u); + assertNoHandoffToken(stdout); + assert.doesNotMatch(stdout, /"contract"\s*:/u); +}); + +test("TTY Handoff presents unavailable recovery instructions and can defer safely", { + skip: pythonAvailable ? false : "A local Python 3 PTY is required.", +}, async () => { + const repository = await projectWithEngine( + `await import(${JSON.stringify(pathToFileURL(engine).href)});\n`, + currentVersion, + ); + const { paths } = await writeHandoffInputs(repository, { available: false }); + + const { stdout } = await execFileAsync("python3", [ + "-c", + answeringPtyRunner, + "1\n2\n", + process.execPath, + launcher, + ...handoffArguments(repository, paths), + "--plain", + ], { + env: { ...process.env, NODE_OPTIONS: `--import=${fixedClock}` }, + }); + + assert.match(stdout, /Executor recovery/u); + assert.match(stdout, /Reason: missing_tool/u); + assert.match(stdout, /Official manual: https:\/\/developers\.openai\.com\/codex\/cli/u); + assert.match(stdout, /npm install --global @openai\/codex@0\.147\.0/u); + assert.match(stdout, /Handoff Deferred/u); + assert.match(stdout, /No external authority was granted/u); + assert.doesNotMatch(stdout, /Resume token:/u); + assertNoHandoffToken(stdout); +}); + +test("TTY Handoff supports the manual path and cancellation without granting authority", { + skip: pythonAvailable ? false : "A local Python 3 PTY is required.", +}, async () => { + const manualRepository = await projectWithEngine( + `await import(${JSON.stringify(pathToFileURL(engine).href)});\n`, + currentVersion, + ); + const manual = await writeHandoffInputs(manualRepository); + await writeFile(manual.paths.executor_descriptors, "[]\n"); + await writeFile(manual.paths.reviewed_executors, "[]\n"); + await writeFile(manual.paths.tool_observations, "[]\n"); + const manualOutput = (await execFileAsync("python3", [ + "-c", + answeringPtyRunner, + "1\n", + process.execPath, + launcher, + ...handoffArguments(manualRepository, manual.paths), + "--plain", + ], { + env: { ...process.env, NODE_OPTIONS: `--import=${fixedClock}` }, + })).stdout; + assert.match(manualOutput, /Manual or Custom Executor Selected/u); + assert.match(manualOutput, /No external authority was granted/u); + assertNoHandoffToken(manualOutput); + + const cancelRepository = await projectWithEngine( + `await import(${JSON.stringify(pathToFileURL(engine).href)});\n`, + currentVersion, + ); + const cancel = await writeHandoffInputs(cancelRepository); + const cancelOutput = (await execFileAsync("python3", [ + "-c", + answeringPtyRunner, + "1\n3\n", + process.execPath, + launcher, + ...handoffArguments(cancelRepository, cancel.paths), + "--plain", + ], { + env: { ...process.env, NODE_OPTIONS: `--import=${fixedClock}` }, + })).stdout; + assert.match(cancelOutput, /Handoff Cancelled/u); + assert.match(cancelOutput, /Authority granted before cancellation: no/u); + assertNoHandoffToken(cancelOutput); +}); + test("rally delegates through an existing v1 bin/rally.js compatibility Engine", async () => { const repository = await projectWithEngine( "process.stdout.write('compatible project Engine');\n", @@ -490,11 +1011,21 @@ test("a terminating signal preserves the delegated Engine signal exit status", { test("every repository operation resolves to the project Engine", async () => { const repository = await projectWithEngine([ - "const operations = new Set([\"audit\", \"architect\", \"architecture-package\", \"init\", \"plan\", \"providers\", \"verify\"]);", + "const operations = new Set([\"audit\", \"architect\", \"architecture-package\", \"handoff\", \"init\", \"plan\", \"providers\", \"verify\", \"version\"]);", "process.stdout.write(process.argv.slice(2).find((value) => operations.has(value)));", "", ].join("\n")); - const operations = ["audit", "architect", "architecture-package", "init", "plan", "providers", "verify"]; + const operations = [ + "audit", + "architect", + "architecture-package", + "handoff", + "init", + "plan", + "providers", + "verify", + "version", + ]; const selected = []; for (const operation of operations) { @@ -527,9 +1058,9 @@ test("older, matching, and newer project pins all outrank the Launcher", async ( assert.deepEqual(selected, ["0.1.0", "0.2.2", "0.3.2", currentVersion]); }); -test("version and toolchain status remain Launcher bootstrap operations", async () => { +test("version delegates to the project Engine while toolchain status remains a Launcher operation", async () => { const repository = await projectWithEngine( - "process.stdout.write(\"project Engine must not run\");\n", + "process.stdout.write(JSON.stringify({ delegated: process.argv.includes(\"--version\") }));\n", "0.3.2", ); @@ -551,9 +1082,7 @@ test("version and toolchain status remain Launcher bootstrap operations", async assert.deepEqual({ version: { - cli: version.cli_version, - launcher: version.launcher_version, - source: version.authority.source, + delegated: version.delegated, }, status: { operation: status.operation, @@ -561,9 +1090,7 @@ test("version and toolchain status remain Launcher bootstrap operations", async }, }, { version: { - cli: "0.3.2", - launcher: currentVersion, - source: "project_toolchain", + delegated: true, }, status: { operation: "toolchain_status", diff --git a/test/release.test.js b/test/release.test.js index a474847..5a50e37 100644 --- a/test/release.test.js +++ b/test/release.test.js @@ -1472,6 +1472,9 @@ test("packed artifacts complete installation, delegation, lifecycle, and full ve npm_exec: "artifact_equivalent_audit_and_follow_up", user_prefix: "installed_and_verified", project_engine: "initialized_and_delegated", + human_project_version: process.platform === "win32" + ? "typed_runner_unavailable" + : "concise_project_engine_summary", fresh_clone: "restored_offline", registry_permission: "cache_miss_approved_and_denied", invalid_authority: "corruption_failed_closed",