From 9d500194fe8dd1fe74e594d5277d48fa61d937e1 Mon Sep 17 00:00:00 2001 From: Daniil Koryto Date: Wed, 29 Apr 2026 17:28:54 +0300 Subject: [PATCH] fix: show rollback verification blockers --- CHANGELOG.md | 2 ++ src/cli/render.ts | 45 ++++++++++++++++++++++++++------------- src/install/contracts.ts | 3 +++ src/install/index.ts | 30 ++++++++++++++++++++++++++ test/cli.test.ts | 30 ++++++++++++++++++++++++++ test/install/flow.test.ts | 2 ++ 6 files changed, 97 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9994ded..b946ccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Accepted Kilo versions at or above `@kilocode/cli >=7.2.0` instead of pre-blocking future Kilo releases with the previous `7.2.0`-only gate. +- Kept local verification blockers and mismatches visible in rollback output + so users can see which Kilo layer prevented setup. - Promoted `moonshotai/Kimi-K2.6` to the recommended validated curated default, with installer-managed `limit.output = 8192` for Kilo `7.2.0` compatibility. diff --git a/src/cli/render.ts b/src/cli/render.ts index 17520ea..3b25bd1 100644 --- a/src/cli/render.ts +++ b/src/cli/render.ts @@ -112,21 +112,7 @@ function renderHumanResult(result: InstallFlowResult): string { output.push(`Verification target: ${result.verificationTarget.modelRef}`); } - if ((result.blockers?.length ?? 0) > 0) { - output.push("Blockers:"); - - for (const blocker of result.blockers ?? []) { - output.push(formatVerificationBlocker(blocker)); - } - } - - if ((result.mismatches?.length ?? 0) > 0) { - output.push("Mismatches:"); - - for (const mismatch of result.mismatches ?? []) { - output.push(formatVerificationMismatch(mismatch)); - } - } + appendHumanVerificationDetails(output, result); appendHumanResultNotices(output, result.notices); output.push(""); @@ -150,6 +136,11 @@ function renderHumanResult(result: InstallFlowResult): string { "GonkaGate Kilo setup rolled back after a failed install attempt.", redactSecretBearingText(result.message), ]; + if (result.verificationTarget !== undefined) { + output.push(`Verification target: ${result.verificationTarget.modelRef}`); + } + + appendHumanVerificationDetails(output, result); appendHumanResultNotices(output, result.notices); output.push(""); @@ -169,6 +160,30 @@ function renderHumanResult(result: InstallFlowResult): string { return output.join("\n"); } +function appendHumanVerificationDetails( + output: string[], + result: { + blockers?: readonly EffectiveConfigVerificationBlocker[]; + mismatches?: readonly EffectiveConfigVerificationMismatch[]; + }, +): void { + if ((result.blockers?.length ?? 0) > 0) { + output.push("Blockers:"); + + for (const blocker of result.blockers ?? []) { + output.push(formatVerificationBlocker(blocker)); + } + } + + if ((result.mismatches?.length ?? 0) > 0) { + output.push("Mismatches:"); + + for (const mismatch of result.mismatches ?? []) { + output.push(formatVerificationMismatch(mismatch)); + } + } +} + function finalizeCliExecution( outcome: CliExecutionOutcome, options: { diff --git a/src/install/contracts.ts b/src/install/contracts.ts index e9d643f..f6035e7 100644 --- a/src/install/contracts.ts +++ b/src/install/contracts.ts @@ -118,9 +118,12 @@ export interface FailedInstallResult export interface RolledBackInstallResult extends InstallFlowResultBase, InstallFlowProgress { + blockers?: readonly EffectiveConfigVerificationBlocker[]; errorCode: "installation_rolled_back"; + mismatches?: readonly EffectiveConfigVerificationMismatch[]; ok: false; status: "rolled_back"; + verificationTarget?: EffectiveConfigVerificationTarget; } export interface InstalledInstallResult diff --git a/src/install/index.ts b/src/install/index.ts index 346568d..8d2744d 100644 --- a/src/install/index.ts +++ b/src/install/index.ts @@ -27,6 +27,11 @@ import { verifyEffectiveKiloConfig, } from "./verify-effective.js"; import type { ManagedArtifactRollbackAction } from "./contracts/managed-artifact.js"; +import type { + EffectiveConfigVerificationBlocker, + EffectiveConfigVerificationMismatch, + EffectiveConfigVerificationTarget, +} from "./contracts/effective-config.js"; import type { CliOptions } from "../cli/contracts.js"; import type { InstallDependencies } from "./deps.js"; import { isInstallError, isInstallErrorCode } from "./errors.js"; @@ -338,8 +343,11 @@ async function buildInstallFailureResult( return buildInstallErrorResult(rollbackError, progressState); } + const verificationDetails = getRolledBackVerificationDetails(error); + return { ...progressState, + ...verificationDetails, context: createInstallFlowContext(), errorCode: "installation_rolled_back", message: `Installation failed and installer-owned writes were rolled back. ${formatInstallFailureMessage( @@ -350,6 +358,28 @@ async function buildInstallFailureResult( }; } +function getRolledBackVerificationDetails(error: unknown): { + blockers?: readonly EffectiveConfigVerificationBlocker[]; + mismatches?: readonly EffectiveConfigVerificationMismatch[]; + verificationTarget?: EffectiveConfigVerificationTarget; +} { + if (isInstallErrorCode(error, "effective_config_blocked")) { + return { + blockers: error.details.blockers, + verificationTarget: error.details.target, + }; + } + + if (isInstallErrorCode(error, "effective_config_mismatch")) { + return { + mismatches: error.details.mismatches, + verificationTarget: error.details.target, + }; + } + + return {}; +} + function buildInstallErrorResult( error: unknown, progressState: InstallProgressState, diff --git a/test/cli.test.ts b/test/cli.test.ts index 6b85d1f..f4f1d66 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -462,6 +462,36 @@ test("CLI emits structured JSON failed payloads for resolved-config mismatches", assert.match(stdout.contents, /"errorCode": "installation_rolled_back"/); }); +test("human-readable rollback output includes verification blockers", async () => { + const stdout = createBufferWriter(); + const stderr = createBufferWriter(); + const dependencies = createCliDependencies({ + env: { + GONKAGATE_API_KEY: "gp-from-env", + KILO_CONFIG: "/workspace/session/kilo.json", + }, + repository: true, + seedFiles: [ + { + contents: '{\n "model": "openai/gpt-4.1"\n}\n', + path: "/workspace/session/kilo.json", + }, + ], + }); + + const result = await run(["--yes"], { + dependencies, + stderr, + stdout, + }); + + assert.equal(result.exitCode, 1); + assert.match(stdout.contents, /rolled back/i); + assert.match(stdout.contents, /Blockers:/); + assert.match(stdout.contents, /KILO_CONFIG/); + assert.match(stdout.contents, /model/); +}); + test("human-readable success output still includes the minimal next step", async () => { const stdout = createBufferWriter(); const stderr = createBufferWriter(); diff --git a/test/install/flow.test.ts b/test/install/flow.test.ts index 27c5bbf..e999d94 100644 --- a/test/install/flow.test.ts +++ b/test/install/flow.test.ts @@ -152,6 +152,7 @@ test("runInstallFlow rolls managed writes back when durable verification fails", assert.equal(result.status, "rolled_back"); assert.equal(result.errorCode, "installation_rolled_back"); + assert.equal(result.blockers?.[0]?.layer, "inferred_non_local"); assert.equal(fs.readText(managedPaths.secretPath), undefined); assert.equal(fs.readText(managedPaths.userConfigDefaultPath), undefined); assert.equal(fs.readText(managedPaths.projectConfigDefaultPath), undefined); @@ -278,6 +279,7 @@ test("runInstallFlow still rolls managed writes back when KILO_CONFIG blocks dur assert.equal(result.status, "rolled_back"); assert.equal(result.errorCode, "installation_rolled_back"); + assert.equal(result.blockers?.[0]?.layer, "KILO_CONFIG"); assert.equal(fs.readText(managedPaths.secretPath), undefined); assert.equal(fs.readText(managedPaths.userConfigDefaultPath), undefined); assert.equal(fs.readText(managedPaths.projectConfigDefaultPath), undefined);