Skip to content

ci(fork-sync): add : never return type as 4th calibration signal (#2435)#2444

Merged
alexey-pelykh merged 1 commit intomainfrom
ci/2435-never-calibration-signal
Apr 20, 2026
Merged

ci(fork-sync): add : never return type as 4th calibration signal (#2435)#2444
alexey-pelykh merged 1 commit intomainfrom
ci/2435-never-calibration-signal

Conversation

@alexey-pelykh
Copy link
Copy Markdown

Summary

Extends scripts/check-throwing-stub-callers.mjs (ADR 0005 H7) with a fourth calibration signal: : never return type. Closes the hypothetical gap where a throwing-stub regression could be reintroduced without any of the existing three calibration signals (variadic-unknown signature / fork-attributed throw message / // Gutted in RemoteClaw fork marker comment).

The new signal is gated by an explicit typed-param exclusion — : never fires as a signal ONLY when the function has no typed non-variadic-unknown parameters. This preserves the existing implicit typed-helper exclusion:

Function : never Typed param Flagged?
function foo(): never { throw ... } (uncalibrated stub) new
exitHooksCliWithError(err: unknown): never typed ❌ preserved
throwGatewayAuthResolutionError(reason: string): never typed ❌ preserved
throwPathEscapesBoundary(params: {...}): never typed ❌ preserved
throwUnresolvedGatewaySecretInput(path: string): never typed ❌ preserved
listProviderModels(..._args: unknown[]): never { throw ... } variadic-unknown ✅ already caught (variadic-unknown + never-return)

Re-scope history

Original #2435 proposed a separate new scripts/check-throwing-stubs.mjs with a ~21-entry allowlist seed. A 360 evaluation on 2026-04-20 found:

Re-scoped to this extension approach. HQ ADR 0005 H7 description updated in parallel.

Changes

  • scripts/check-throwing-stub-callers.mjs:
    • New helpers: isUnknownArrayType, hasNeverReturnType, hasTypedNonVariadicUnknownParams
    • hasVariadicUnknownArgs refactored to delegate to isUnknownArrayType (DRY; single source of truth for unknown-array shape)
    • classifyStubFunction accepts returnType; computes neverReturnSignal = neverReturn && !hasTypedParams
    • classifyFixture(sourceText) exported for fixture-based testing
    • 10-case self-test suite via --self-test flag (zero-infrastructure; no new vitest config needed)
    • Header JSDoc updated
  • .github/workflows/ci.yml: new Self-test classifier (fixture-based) step BEFORE the main gate step in throwing-stub-callers-gate job — classifier regressions surface early

Verification

  • node scripts/check-throwing-stub-callers.mjs --self-test → 10/10 PASS
  • node scripts/check-throwing-stub-callers.mjs → 0 violations on main (shape unchanged)
  • pnpm check → clean (format + typecheck + lint + custom gates)
  • Fresh-context adversarial validation (session 37d6f4b6) → CLEAN, 6/6 AC PASS, attacks on union types / destructuring / rest params / overloads / CI wiring all resolved
  • Fresh-context polish pass (session 825b76a6) → CLEAN after 2 iterations (one DRY refactor applied)

Self-test fixture matrix

  1. Uncalibrated : never stub (no params) → FLAGGED ✅ new
  2. Typed unknown param (exitHooksCliWithError shape) → NOT flagged
  3. Typed string param (throwGatewayAuthResolutionError shape) → NOT flagged
  4. Typed object param (throwPathEscapesBoundary shape) → NOT flagged
  5. Variadic-unknown + : never (upstream-compat pattern) → FLAGGED (both signals)
  6. : never without throw body (loops forever) → NOT flagged
  7. : never with multi-statement body → NOT flagged (not a single-throw)
  8. Exported arrow function, uncalibrated : never → FLAGGED ✅ new
  9. Exported arrow function with typed param, : never → NOT flagged
  10. Non-exported : never throwing function → NOT flagged

Orthogonal observation (not fixed here)

.throwing-stub-callers-allowlist contains a stale entry for resolveAgentRuntimeOrThrow — the underlying stub was remediated in commit d4a01b9190 as part of #2408 fix. The gate correctly reports it as stale: Stale allowlist entries: 1. Worth cleaning up in a follow-up but orthogonal to this PR's scope.

Cascade impact

H7 delivered. Unblocks Phase 2 (#2441 composite gate depends on H7/H8/H9 landing). H8 (#2436) and H9 (#2437) remain parallelizable.

Test plan

  • Self-tests pass locally
  • Main gate passes on current main (no new violations)
  • pnpm check passes
  • Adversarial validation clean
  • Polish verdict clean

Closes #2435.

…2435)

Extends scripts/check-throwing-stub-callers.mjs (H7 of ADR 0005) to close
the hypothetical gap where a throwing stub is reintroduced without any of
the existing three calibration signals (variadic-unknown signature,
fork-attributed throw message, Gutted marker comment).

\`: never\` return type fires as a calibration signal only when the function
has no typed non-variadic-unknown parameters — this preserves the implicit
typed-helper exclusion. Legitimate typed error-throw helpers remain
unflagged:

  exitHooksCliWithError(err: unknown): never        -- typed param
  throwGatewayAuthResolutionError(reason: string): never
  throwPathEscapesBoundary(params: {...}): never
  throwUnresolvedGatewaySecretInput(path: string): never

Uncalibrated stubs now fire — the hypothetical
  function foo(): never { throw new Error("bar"); }
would be classified as a throwing stub if it had live callers.

Changes:
  - isUnknownArrayType / hasNeverReturnType / hasTypedNonVariadicUnknownParams
    helpers (isUnknownArrayType also DRYs the prior inline logic in
    hasVariadicUnknownArgs)
  - classifyStubFunction: accepts returnType; computes neverReturnSignal
    gated by typed-param exclusion; added to guard + signals list
  - classifyFixture(sourceText) export for fixture-based testing
  - 10-case self-test suite via --self-test flag (zero-infrastructure;
    wired into ci.yml BEFORE the main gate step so classifier regressions
    surface early)
  - Header JSDoc documents the new signal

Verification:
  - node scripts/check-throwing-stub-callers.mjs --self-test  -> 10/10 PASS
  - node scripts/check-throwing-stub-callers.mjs              -> 0 violations
  - pnpm check                                                -> clean
  - Fresh-context adversarial validation (session 37d6f4b6)   -> CLEAN
  - Fresh-context polish pass (session 825b76a6)              -> CLEAN after 2 iterations

Re-scope history: original #2435 proposed a separate new script with a
21-entry allowlist seed; 360 evaluation on 2026-04-20 found the naive
grep count was noise-inflated and the extension approach avoids FP
duplication with the existing gate. Issue title and AC re-scoped in
parallel. HQ ADR 0005 H7 description updated accordingly.

Orthogonal observation (not addressed here): .throwing-stub-callers-allowlist
entry for resolveAgentRuntimeOrThrow is now stale (remediated in commit
d4a01b9 as part of #2408 fix) — can be cleaned up in a follow-up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci(fork-sync): extend check-throwing-stub-callers.mjs with : never calibration signal (H7)

1 participant