Skip to content

Commit 5fee8d1

Browse files
oscharkoclaude
andauthored
fix(quality): clear the dev SonarCloud reliability findings blocking the gate (#2604)
* fix(quality): clear the dev SonarCloud reliability findings blocking the gate dev's SonarCloud quality gate has been failing on `new_reliability_rating = C` (threshold A) across several runs, which fails the `Coverage and SonarCloud` job and, through it, the fail-closed `ci` aggregate. Five findings drive it; all five are analyzer artifacts against correct code, so each is resolved by restating the same behavior in a shape the analyzer can follow rather than by changing semantics. typescript:S4822 (4x) — "promise inside a try". Four call sites share one deliberate, commented pattern: a `try` containing only a promise-returning call guards a SYNCHRONOUS throw from a non-conforming implementation, while the rejection is handled just after the block. Sonar sees only the floating promise. Each now builds the chain (including its `.catch`) inside the `try`: - deps.ts `reconcileTaskWorkspacesAtStartup` and registerSw.ts `cleanupDevServiceWorkers`/`registerSw` swallow both failure modes by design, so attaching `.catch` at the call site is exactly equivalent — and drops a now-redundant `pending` local plus its `ReturnType<...>` annotation. - opencodeRuntimeComposition.ts `startFacadeExecution` must NOT swallow the rejection: the awaiting request path maps it to 502/408 and settles the tool. It gets a no-op `.catch` tap instead, which does not consume the rejection for that path (it still observes it via `raceAbort`) and additionally suppresses an unhandled-rejection warning on the abort path, where the caller can stop awaiting before the facade settles. javascript:S3403 (1x) — `options.policy === "production"` reported as always false in verify-portable-runtime-signing.mjs. "production" is a real member of PORTABLE_VERIFICATION_POLICIES; the comparison is reachable. The all-`undefined` object literal in parseArgs pinned every property's inferred type to `undefined`, so the analyzer read every later value comparison as impossible. parseArgs now declares the three locals and assembles the object on return. No behavior change intended. Verified locally: typecheck, lint, format:check, keiko-ui install suite (59), scripts suite (1794) all green. The 6 failures in codingAutonomyQaMatrix.test.ts under a path-filtered server run reproduce identically with these changes stashed — pre-existing, filter-context only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(quality): cover verify-portable-runtime-signing argument parsing Restating `parseArgs` in the previous commit put its lines into SonarCloud's new-code window, where they were measured and found entirely uncovered: this script had no test file at all, which drove the PR's new-code coverage to 39.1% against an 80% threshold. Exports `parseArgs` behind the `@internal` marker the repo already uses for test-only seams (see deps.ts) and adds eight direct unit tests. A direct import rather than a spawned subprocess is deliberate: subprocess-driven script tests report 0% v8 coverage against the vitest parent, so they would not have moved the measurement. Cases: every supported policy returned verbatim (a regression pin for the javascript:S3403 fix — the previous all-`undefined` options literal made `options.policy === "production"` read as statically impossible), the optional verification input, order independence, and all six fail-closed paths (missing manifest, missing policy, unsupported policy, unsupported argument, flag without a value, empty flag value). `fail()` ends the process, so `process.exit` is trapped through a throw, matching release-script-lcov-mapping.test.mjs. The signing script's argument validation was untested until now, independent of the gate. Verified locally: lint, format:check, check:knip, and the full scripts suite (95 files, 1802 tests) green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(quality): parameterize the parseArgs fail-closed cases (S5976) SonarCloud flagged three of the six fail-closed cases as structurally identical (javascript:S5976). They differ only in the argv they feed, so they collapse into one it.each table without losing a case or its label. Verified: 8 tests still pass, eslint and prettier clean on the file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2bbc8af commit 5fee8d1

5 files changed

Lines changed: 132 additions & 64 deletions

File tree

packages/keiko-server/src/coding-runtime/opencodeRuntimeComposition.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,9 +1058,9 @@ function responseForToolResult(
10581058
}
10591059

10601060
// Start the facade call, containing only its SYNCHRONOUS throw (a facade that dies before
1061-
// returning a promise). The thrown value is carried back to the caller which surfaces it as an
1062-
// operator diagnostic before failing the request — while the returned promise's rejection is
1063-
// handled by the awaiting request path, so no promise ever lives inside a try (typescript:S4822).
1061+
// returning a promise). The thrown value is carried back to the caller, which surfaces it as an
1062+
// operator diagnostic before failing the request; the returned promise's rejection stays owned by
1063+
// the awaiting request path, which maps it to a 502/408 and settles the tool.
10641064
type FacadeStart =
10651065
| { readonly ok: true; readonly work: Promise<CodingToolResult> }
10661066
| { readonly ok: false; readonly error: unknown };
@@ -1073,10 +1073,13 @@ function startFacadeExecution(
10731073
admission: AdmittedToolRequest,
10741074
): FacadeStart {
10751075
try {
1076-
return {
1077-
ok: true,
1078-
work: facade.execute({ body, capability, headers, signal: admission.controller.signal }),
1079-
};
1076+
const work = facade.execute({ body, capability, headers, signal: admission.controller.signal });
1077+
// A no-op tap, NOT the error handler: attaching it does not consume the rejection for the
1078+
// awaiting request path, which still observes it through `raceAbort`. It keeps the promise from
1079+
// floating inside this `try` (typescript:S4822) and suppresses an unhandled-rejection warning on
1080+
// the abort path, where the caller can stop awaiting before the facade settles.
1081+
void work.catch(() => undefined);
1082+
return { ok: true, work };
10801083
} catch (error) {
10811084
return { ok: false, error };
10821085
}

packages/keiko-server/src/deps.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,18 +1688,18 @@ export function reconcileTaskWorkspacesAtStartup(
16881688
service: WorkspaceReconciliationService | undefined,
16891689
): void {
16901690
if (service === undefined) return;
1691-
// `service.reconcile()` is typed as always returning a Promise, but the call itself (property
1692-
// lookup + invocation) can still throw synchronously for a non-conforming implementation (e.g. a
1693-
// test double). Isolate that from the Promise-rejection path below so each failure mode is
1694-
// handled exactly once.
1695-
let pending: ReturnType<WorkspaceReconciliationService["reconcile"]>;
1691+
// Construction must never fail because of reconciliation, so BOTH failure modes are swallowed
1692+
// here and each is still handled exactly once: `.catch` absorbs the Promise rejection, and the
1693+
// surrounding `try` absorbs a synchronous throw from the call itself (property lookup +
1694+
// invocation), which a non-conforming implementation (e.g. a test double) can still raise even
1695+
// though `reconcile()` is typed as always returning a Promise. Attaching `.catch` at the call
1696+
// site rather than after the `try` keeps the promise from ever floating inside it
1697+
// (typescript:S4822).
16961698
try {
1697-
pending = service.reconcile();
1699+
void service.reconcile().catch(() => undefined);
16981700
} catch {
1699-
// construction must never fail because of reconciliation.
17001701
return;
17011702
}
1702-
void pending.catch(() => undefined);
17031703
}
17041704

17051705
function seedInitialProject(

packages/keiko-ui/src/app/components/desktop/install/registerSw.ts

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -112,30 +112,30 @@ function deleteKeikoShellCaches(): void {
112112
}
113113

114114
function cleanupDevServiceWorkers(sw: ServiceWorkerContainer): void {
115-
// Wrap the synchronous call too: although the spec says `getRegistrations()` returns a
116-
// Promise, a non-conforming runtime (or a test stub) could throw synchronously, and this
117-
// helper's whole contract is that it never breaks the app. Isolate that guard from the
118-
// already-`.catch`-handled Promise chain below so each failure mode is handled exactly once.
119-
let pending: ReturnType<ServiceWorkerContainer["getRegistrations"]>;
115+
// Silent failure by design: development cleanup must never break the app, so both failure modes
116+
// are swallowed and each is still handled exactly once. `.catch` absorbs the Promise rejection,
117+
// and the surrounding `try` absorbs a synchronous throw from the call itself, which a
118+
// non-conforming runtime (or a test stub) could still raise even though the spec says
119+
// `getRegistrations()` returns a Promise. Building the chain inside the `try` keeps the promise
120+
// from ever floating in it (typescript:S4822).
120121
try {
121-
pending = sw.getRegistrations();
122+
void sw
123+
.getRegistrations()
124+
.then((registrations) =>
125+
Promise.all(registrations.map((registration) => registration.unregister())),
126+
)
127+
.then(() => {
128+
deleteKeikoShellCaches();
129+
if (sw.controller === null) return;
130+
const reloadKey = "keiko.dev.service-worker-cleanup-reloaded";
131+
if (window.sessionStorage.getItem(reloadKey) === "true") return;
132+
window.sessionStorage.setItem(reloadKey, "true");
133+
window.location.reload();
134+
})
135+
.catch((_error: unknown) => undefined);
122136
} catch {
123-
// Silent failure by design. Development cleanup must never break the app.
124137
return;
125138
}
126-
void pending
127-
.then((registrations) =>
128-
Promise.all(registrations.map((registration) => registration.unregister())),
129-
)
130-
.then(() => {
131-
deleteKeikoShellCaches();
132-
if (sw.controller === null) return;
133-
const reloadKey = "keiko.dev.service-worker-cleanup-reloaded";
134-
if (window.sessionStorage.getItem(reloadKey) === "true") return;
135-
window.sessionStorage.setItem(reloadKey, "true");
136-
window.location.reload();
137-
})
138-
.catch((_error: unknown) => undefined);
139139
}
140140

141141
export function registerSw(): void {
@@ -150,26 +150,22 @@ export function registerSw(): void {
150150
return;
151151
}
152152

153-
// Fire-and-forget. Wrap the synchronous call too: although the spec says `register()`
154-
// returns a Promise, a non-conforming runtime (or a test stub) could throw synchronously,
155-
// and the helper's whole contract is that it never breaks the page. Isolate that guard from
156-
// the already-`.catch`-handled Promise chain below so each failure mode is handled exactly
157-
// once. `.catch` swallows the Promise rejection. Using `unknown` for the rejection value —
158-
// `register()` rejects with `DOMException` in spec but other runtimes may differ, and we
159-
// never inspect the error in production code.
160-
let pending: ReturnType<ServiceWorkerContainer["register"]>;
153+
// Fire-and-forget, silent failure by design: static shell caching must never break app startup,
154+
// so both failure modes are swallowed and each is still handled exactly once. `.catch` absorbs
155+
// the Promise rejection, and the surrounding `try` absorbs a synchronous throw from the call
156+
// itself, which a non-conforming runtime (or a test stub) could still raise even though the spec
157+
// says `register()` returns a Promise. Building the chain inside the `try` keeps the promise from
158+
// ever floating in it (typescript:S4822). Using `unknown` for the rejection value — `register()`
159+
// rejects with `DOMException` in spec but other runtimes may differ, and we never inspect the
160+
// error in production code.
161161
try {
162-
pending = sw.register("/sw.js", { scope: "/" });
162+
void sw
163+
.register("/sw.js", { scope: "/" })
164+
.then((registration) => {
165+
handleRegisteredServiceWorker(sw, registration);
166+
})
167+
.catch((_error: unknown) => undefined);
163168
} catch {
164-
// Silent failure by design. Static shell caching must never break app startup.
165169
return;
166170
}
167-
void pending
168-
.then((registration) => {
169-
handleRegisteredServiceWorker(sw, registration);
170-
})
171-
.catch((_error: unknown) => {
172-
// Silent failure by design. Static shell caching must never break app startup.
173-
return undefined;
174-
});
175171
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { afterEach, describe, expect, it, vi } from "vitest";
2+
3+
import { parseArgs } from "../verify-portable-runtime-signing.mjs";
4+
5+
// `fail()` ends the process. Route it through a throw so the rejection paths stay assertable in
6+
// band, matching the convention in release-script-lcov-mapping.test.mjs.
7+
function trapExit() {
8+
vi.spyOn(console, "error").mockImplementation(() => undefined);
9+
return vi.spyOn(process, "exit").mockImplementation((code) => {
10+
throw new Error(`process.exit(${String(code)})`);
11+
});
12+
}
13+
14+
afterEach(() => {
15+
vi.restoreAllMocks();
16+
});
17+
18+
describe("verify-portable-runtime-signing parseArgs", () => {
19+
it("returns every supported policy verbatim, so a later value comparison stays reachable", () => {
20+
// Regression pin for javascript:S3403: an all-`undefined` options literal used to pin each
21+
// property's inferred type to `undefined`, which made `options.policy === "production"` read as
22+
// statically impossible even though "production" is a real policy.
23+
for (const policy of ["staging", "development", "pull-request", "production"]) {
24+
expect(parseArgs(["--manifest", "/tmp/m.json", "--policy", policy])).toEqual({
25+
manifest: "/tmp/m.json",
26+
policy,
27+
verificationInput: undefined,
28+
});
29+
}
30+
});
31+
32+
it("accepts the optional verification input and is order-independent", () => {
33+
expect(
34+
parseArgs([
35+
"--policy",
36+
"production",
37+
"--verification-input",
38+
"/tmp/in.json",
39+
"--manifest",
40+
"/tmp/m.json",
41+
]),
42+
).toEqual({
43+
manifest: "/tmp/m.json",
44+
policy: "production",
45+
verificationInput: "/tmp/in.json",
46+
});
47+
});
48+
49+
it.each([
50+
["a missing manifest", ["--policy", "production"]],
51+
["a missing policy", ["--manifest", "/tmp/m.json"]],
52+
["a policy outside the supported set", ["--manifest", "/tmp/m.json", "--policy", "prod-ish"]],
53+
[
54+
"an unsupported argument",
55+
["--manifest", "/tmp/m.json", "--policy", "production", "--sign-anyway"],
56+
],
57+
["a flag present without its value", ["--manifest"]],
58+
["a flag value that is an empty string", ["--policy", ""]],
59+
])("fails closed on %s", (_case, argv) => {
60+
const exit = trapExit();
61+
expect(() => parseArgs(argv)).toThrow("process.exit(1)");
62+
expect(exit).toHaveBeenCalledWith(1);
63+
});
64+
});

scripts/verify-portable-runtime-signing.mjs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,35 +22,40 @@ function fail(message) {
2222
process.exit(1);
2323
}
2424

25-
function parseArgs(argv) {
26-
const options = { manifest: undefined, policy: undefined, verificationInput: undefined };
25+
// Declared-then-assigned rather than an all-`undefined` object literal: the literal pinned every
26+
// property's inferred type to `undefined`, which made later value comparisons (e.g.
27+
// `options.policy === "production"`) read as statically impossible (javascript:S3403).
28+
/** @internal Exported only for the deterministic argument-parsing tests. */
29+
export function parseArgs(argv) {
30+
let manifest;
31+
let policy;
32+
let verificationInput;
2733
for (let index = 0; index < argv.length; index += 1) {
2834
const arg = argv[index];
2935
const value = argv[index + 1];
3036
if (arg === "--manifest") {
31-
options.manifest = requiredValue(value, arg);
37+
manifest = requiredValue(value, arg);
3238
index += 1;
3339
continue;
3440
}
3541
if (arg === "--policy") {
36-
options.policy = requiredValue(value, arg);
42+
policy = requiredValue(value, arg);
3743
index += 1;
3844
continue;
3945
}
4046
if (arg === "--verification-input") {
41-
options.verificationInput = requiredValue(value, arg);
47+
verificationInput = requiredValue(value, arg);
4248
index += 1;
4349
continue;
4450
}
4551
fail(`unsupported argument: ${arg}`);
4652
}
47-
if (options.manifest === undefined) fail("pass --manifest <path>");
48-
if (options.policy === undefined)
49-
fail("pass --policy <staging|development|pull-request|production>");
50-
if (!PORTABLE_VERIFICATION_POLICIES.includes(options.policy)) {
51-
fail(`unsupported verification policy: ${options.policy}`);
53+
if (manifest === undefined) fail("pass --manifest <path>");
54+
if (policy === undefined) fail("pass --policy <staging|development|pull-request|production>");
55+
if (!PORTABLE_VERIFICATION_POLICIES.includes(policy)) {
56+
fail(`unsupported verification policy: ${policy}`);
5257
}
53-
return options;
58+
return { manifest, policy, verificationInput };
5459
}
5560

5661
function requiredValue(value, arg) {

0 commit comments

Comments
 (0)