Skip to content

fix(e2e): harden the scorecard per parallel review - #183

Open
ahrav wants to merge 3 commits into
stack/scorecard-05-report-scriptfrom
stack/scorecard-06-review-hardening
Open

fix(e2e): harden the scorecard per parallel review#183
ahrav wants to merge 3 commits into
stack/scorecard-05-report-scriptfrom
stack/scorecard-06-review-hardening

Conversation

@ahrav

@ahrav ahrav commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

Stack 6/6 of the release scorecard. Findings from three parallel reviews (invariant-test-review, typescript-code-reviewer, reduce-complexity) and a final ponytail-review, applied as three commits.

Shared parsing (refactor): boolean/finiteNumber/text/textArray/nullable/countRecord move into contract-primitives; one parseSystemVersionTuple (src/historian-eval/system-tuple.ts) serves the historian, metamorphic, and scorecard parsers and bounds each tuple field to the shape the runner writes. The paired-delta parser admits only the endpoint partition the estimator writes into each list and rejects duplicate estimate keys. Lane builders sort by code unit rather than localeCompare so identical runs fingerprint identically on every host.

Correctness (fix):

  • A lane that ran under other pre-registered settings or another build identity now drops its report (incomplete, no numbers); only a run that did not finish keeps its counts.
  • Holdout adapter requires the paired-delta lane to be present.
  • Baseline comparison keys on (endpoint, family); an estimate that vanishes at one endpoint is a blocking family-missing row.
  • Observed lane identities pass through the policy parser before reaching the report; retrieval slots share one scored holdout population; parity counts only completed cases.
  • Privacy allowlist no longer exempts generic keys; releaseFingerprints is exempted by parent key.
  • Tests now drive deriveOutcome to an allowed promotion at the tolerance boundary, mutate every cross-field guard of the report parser, spy on readFileSync to prove no lane read precedes freeze binding, and cover a failed gate, a non-comparable baseline, and every pre-registration disjunct.

Complexity (refactor): one factory for the three contract error classes, single owners for interval parsing and closed vocabularies, node:util.parseArgs in the script, dead exports removed.

Unapplied review findings

  • comparison.ts: no-noise-floor adverse rows are non-blocking per plan R15. Under noiseFloorSource: "none" every regression is therefore non-blocking. Kept as the plan specifies; worth a policy-owner decision.
  • Dreamer and retrieval reports fail the shared privacy scan as published (XML manifest closing tags read as paths; absolute workingDirectory). Follow-up tasks below.
  • createScorecardAdapter has no production caller until a recomputers module exports scorecard for scripts/prospective-holdout.ts.

Follow-up beads tasks (epic magic-context-x4l)

Filed after merge review: gate probes for the four unproduced gates; historian hard-negative family beside falseAuthoritativeMatches; build identity on incident-pool and dreamer reports; dreamer and retrieval scannable summary artifacts; release workflow collecting lane artifacts; tolerant baseline reader; remaining metric producers.

Testing

  • bun run --cwd packages/e2e-tests typecheck, bun run --cwd packages/plugin typecheck
  • bun run test:scorecard-unit (62), test:prospective-unit (125), test:paired-delta-unit (300), test:historian-eval-unit (301), test:metamorphic-unit (197), test:dreamer-eval-unit (165)
  • bun test packages/plugin/scripts/retrieval-benchmark (239), bun test packages/e2e-tests/scripts/run-test-selection.test.ts

Stack

Depends on #182 and completes the stack.

…e parsers

The three lane parsers and the scorecard each re-derived boolean, number,
text, nullable, and count-record checks, and three of them restated the
seven-key system tuple by hand. Two copies had already drifted: one
count record accepted zero where another required one, and one interval
parser rejected inverted bounds where another did not. A tuple field
added in one place would have gone unnoticed in the others, and the
tuple is compared by fingerprint as a build identity.

Move the shared checks into the contract primitives and parse the
system tuple through one function that also bounds each field to the
shape the runner writes, so a lane string with prose in it cannot reach
a report. The paired-delta parser now admits only the endpoint partition
the estimator writes into each list and rejects duplicate estimate keys,
and report builders sort by code unit rather than locale so identical
runs fingerprint identically on every host.
…every guard with a test

Review found three ways evidence the policy rejected could still shape
the report, and a set of report guards no test exercised.

A paired-delta or identity-projected lane that ran under other settings
or another build is a different experiment, so its report is dropped
rather than kept for reliability counts; only a run that did not finish
keeps its counts. The holdout adapter now requires the paired-delta lane
to be present before certifying paired facts. Baseline comparison keys
on the (endpoint, family) pair, so an estimate that vanishes at one
endpoint is a blocking family-missing row instead of no row. Observed
lane identities are admitted through the policy's own parser, retrieval
slots read one scored holdout population, and parity counts only
completed cases.

The shared privacy allowlist no longer exempts generic key names; the
retrieval release-fingerprint record is exempted by its parent key so
other artifacts keep the hash check. Tests now drive the production
outcome deriver to an allowed promotion at the tolerance boundary,
mutate every cross-field guard of the report parser, prove the loader
reads no lane file before freeze binding, and cover a failed gate, a
non-comparable baseline, and the remaining pre-registration checks.
Remove duplicated scorecard helpers and dead exports so each shared
behavior has one owner: one factory builds the contract error classes,
interval parsing and closed vocabularies live beside the types they
describe, and the operator script relies on node:util for flag parsing.
Make the test selection fail when the script test is missing instead of
filtering the gap out of the assertion meant to catch it.
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Essentials

Run ID: d1a039fa-294e-474d-88a1-61d299bff512

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f10498970

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +210 to +211
for (const [innerKey, inner] of Object.entries(child)) {
if (typeof inner === "string") scanString(inner, `${path}.${segment}.${innerKey}`, forbidden, violations, true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scan keys inside the fingerprint record

When an artifact contains releaseFingerprints, this special branch scans only each property's value and never scans innerKey, unlike the normal object traversal. Consequently, sensitive names such as {"releaseFingerprints":{"/home/alice/secret":"safe"}} produce no privacy violation, and if the value itself triggers a violation its diagnostic path can expose the unredacted key. Apply the usual key scan/redaction before scanning each direct fingerprint value.

Useful? React with 👍 / 👎.

let values: Partial<Record<keyof typeof OPTIONS, string>>;
try {
values = parseNodeArgs({ args: [...argv], strict: true, allowPositionals: false, options: OPTIONS }).values;
} catch (error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: duplicate-flag rejection silently dropped by the node:util.parseArgs migration.

parseNodeArgs is called without multiple: true for any option, and Node's parseArgs treats a repeated string option as last-value-wins, not an error — strict: true only guards unknown options/type mismatches. So run-scorecard --baseline a.json --baseline b.json (or any other flag given twice) now silently resolves to the last value instead of throwing, in a script that gates releases.

The old behavior was covered by a test asserting /given twice/ on --out twice — it was deleted in this PR rather than replaced with an equivalent check under the new parser, so the regression has no test coverage.

Consider validating argv for duplicate flags before/after calling parseNodeArgs, or accepting this as an intentional behavior change (and documenting it in the PR description if so).

Comment on lines +148 to +151
function scoredHoldoutScenarios(report: BenchmarkReport, mode: QueryMode): BenchmarkReport["evidence"]["scenarios"] {
const laneRestricted = new Set(report.evidence.cases.filter((entry) => entry.laneRestricted).map((entry) => entry.caseId));
return report.evidence.scenarios.filter((scenario) =>
scenario.partition === "holdout" && scenario.mode === mode && !laneRestricted.has(scenario.queryId.split(":", 1)[0]!));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated exclusion logic risks drift. scoredHoldoutScenarios re-implements the "exclude lane-restricted case scenarios" filter that already exists in aggregateReportQuality() (packages/plugin/scripts/retrieval-benchmark/report.ts, ~line 278). Both compute the same laneRestricted set from report.evidence.cases and filter evidence.scenarios on it, but as two separate implementations. If the exclusion rule changes (e.g., what counts as "lane-restricted", or how queryId is parsed to a case id), it's easy to update one and miss the other, silently producing an inconsistent duplicate-rate-at-50 reading vs. the other retrieval aggregates.

Worth factoring into one shared helper that both call sites use.

@claude

claude Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review summary

Reviewed the diff for this scorecard-hardening PR (30 files: shared parsing primitives extraction, correctness fixes to lane/comparison logic, and complexity reduction). The refactor is internally consistent — traced the de Morgan's-law rewrites in pairedDeltaConformanceReasons, the report: null-on-incomplete semantics in evidence.ts, the AdverseRow.endpoint non-null tightening, and the shared contract-primitives.ts/system-tuple.ts extraction — all match their updated tests.

Two findings posted inline, most-severe first:

  1. run-scorecard.ts (bug, release-gating CLI): the switch to node:util's parseArgs silently dropped the old "flag given twice" rejection — repeated string options are now last-value-wins rather than an error, and the corresponding test was deleted rather than replaced.
  2. families.ts: scoredHoldoutScenarios() duplicates the "exclude lane-restricted case scenarios" logic already in aggregateReportQuality() — a drift risk if the exclusion rule changes in only one place.

No security issues found (the privacy-allowlist narrowing in packages/plugin/scripts/retrieval-benchmark/privacy.ts looks correct and is well covered by tests). Everything else traced closely checked out against its own updated tests.

if (measured !== (row.endpoint !== null && row.noiseLabel !== null && row.delta !== null && row.interval !== null)) {
fail(`${label}: shape-invalid`);
}
if (measured !== (row.noiseLabel !== null && row.delta !== null && row.interval !== null)) fail(`${label}: shape-invalid`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Partial non-null measured fields bypass validation for family-missing rows

When measured is false (row.kind === "family-missing"), the condition (row.noiseLabel !== null && row.delta !== null && row.interval !== null) checks only whether all three fields are simultaneously non-null. If 1 or 2 fields are non-null (for example, delta: -0.5 while interval: null and noiseLabel: null), the compound conjunction evaluates to false, causing measured !== false to evaluate to false !== false and pass validation.

For family-missing rows, all three measured fields must be null, whereas for adverse-interval rows all three must be non-null.

Suggested change
if (measured !== (row.noiseLabel !== null && row.delta !== null && row.interval !== null)) fail(`${label}: shape-invalid`);
if (measured ? (row.noiseLabel === null || row.delta === null || row.interval === null) : (row.noiseLabel !== null || row.delta !== null || row.interval !== null)) fail(`${label}: shape-invalid`);

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

fail("report.body.outcome.blockingRegressionCount: cross-field-invalid");
}
if (body.outcome.blockingRegressionCount !== blockingCount(body.adverseDeltas)) fail("report.body.outcome.blockingRegressionCount: cross-field-invalid");
unique(body.adverseDeltas.map((row) => `${row.kind}:${estimateKey(row)}`), "report.body.adverseDeltas");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Adverse deltas uniqueness check allows duplicate entries across different kinds

Keying the uniqueness set on ${row.kind}:${estimateKey(row)} permits two adverse rows for the exact same (endpoint, familyId) pair if one has kind: "adverse-interval" and the other has kind: "family-missing".

Because an estimate family at a given endpoint is either evaluated as an adverse interval regression or missing from the current release (not both), adverse rows should be unique on estimateKey(row) alone.

Suggested change
unique(body.adverseDeltas.map((row) => `${row.kind}:${estimateKey(row)}`), "report.body.adverseDeltas");
unique(body.adverseDeltas.map(estimateKey), "report.body.adverseDeltas");

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/e2e-tests/src/scorecard/report-contract.ts 272 Partial non-null measured fields bypass validation for family-missing rows
packages/e2e-tests/src/scorecard/report-contract.ts 400 Adverse deltas uniqueness check allows duplicate entries across different kinds
Files Reviewed (30 files)
  • packages/e2e-tests/README.md
  • packages/e2e-tests/scripts/run-scorecard.test.ts
  • packages/e2e-tests/scripts/run-scorecard.ts
  • packages/e2e-tests/scripts/run-test-selection.test.ts
  • packages/e2e-tests/scripts/run-test-selection.ts
  • packages/e2e-tests/src/contract-primitives.ts
  • packages/e2e-tests/src/historian-eval/scorer.test.ts
  • packages/e2e-tests/src/historian-eval/scorer.ts
  • packages/e2e-tests/src/historian-eval/system-tuple.ts
  • packages/e2e-tests/src/metamorphic-eval/metamorphic.test.ts
  • packages/e2e-tests/src/metamorphic-eval/report.ts
  • packages/e2e-tests/src/paired-delta/estimator.ts
  • packages/e2e-tests/src/paired-delta/report.test.ts
  • packages/e2e-tests/src/paired-delta/report.ts
  • packages/e2e-tests/src/scorecard/comparison.test.ts
  • packages/e2e-tests/src/scorecard/comparison.ts
  • packages/e2e-tests/src/scorecard/evidence.test.ts
  • packages/e2e-tests/src/scorecard/evidence.ts
  • packages/e2e-tests/src/scorecard/families.test.ts
  • packages/e2e-tests/src/scorecard/families.ts
  • packages/e2e-tests/src/scorecard/gates.test.ts
  • packages/e2e-tests/src/scorecard/gates.ts
  • packages/e2e-tests/src/scorecard/policy.test.ts
  • packages/e2e-tests/src/scorecard/policy.ts
  • packages/e2e-tests/src/scorecard/report-contract.ts - 2 issues
  • packages/e2e-tests/src/scorecard/report.test.ts
  • packages/e2e-tests/src/scorecard/report.ts
  • packages/e2e-tests/src/scorecard/test-fixtures.ts
  • packages/plugin/scripts/retrieval-benchmark/privacy.test.ts
  • packages/plugin/scripts/retrieval-benchmark/privacy.ts

Fix these issues in Kilo Cloud


Reviewed by gemini-3.7-flash · Input: 519.1K · Output: 26.7K · Cached: 1.2M

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.

1 participant