Skip to content

feat(e2e): historian eval scenario corpus, mutation battery, and freeze governance (U5-U6) - #72

Open
ahrav wants to merge 1 commit into
stack/historian-eval-03-enginefrom
stack/historian-eval-04-corpus-governance
Open

feat(e2e): historian eval scenario corpus, mutation battery, and freeze governance (U5-U6)#72
ahrav wants to merge 1 commit into
stack/historian-eval-03-enginefrom
stack/historian-eval-04-corpus-governance

Conversation

@ahrav

@ahrav ahrav commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Stack 4/5 for the historian structural eval lane (beads magic-context-x4l.11). Base: #71 (engine).

Dev corpus: 13 hand-audited scenarios authored latent-truth-first (gold expectations first, transcript rendered from them), lint-clean, covering all seven hard-negative families. Expected-absent predicates are phrased to match only the forbidden active formation, never the legitimate rejection record ("X was rejected because Y" is the preferred durable formation per R3). The 3-run live stability audit per scenario (U5 step 3) is an operator gate documented in the README (5/5).

Mutation battery (mutations.ts, R13/KTD5): per scenario, crafted wrong outputs per class must go red AT the expected stage, consulting a single policy table (EXPECTED_OUTCOMES). Red on: stage migration in either direction, a passing mutation, a near-miss perturbation the matcher still accepts (matcher-discrimination proof), a probe-less scenario, or a family set no false-authoritative class covers.

Freeze governance (promote.ts, KD4/KTD7): clones the retrieval-benchmark promotion pattern — privacy scan before any parser, operator approvals bound to the exact release-tuple fingerprint (one per kind: privacy, gold-intent, never minted here), the battery recomputed inside promoteRelease so evidence cannot be forged or stale, review-dir + staging round-trips through the strict consumer path, atomic rename into an immutable release, tombstone-in-vN+1 errata carried forward by every later release, and a strict fail-closed evidence parser for read-back.

Verified: bun run test:historian-eval-unit (64 tests) green; run-historian-eval.ts --lint and --mutations (arrive in 5/5) green over the 13-scenario corpus.

Stack: 1/5 plumbing → 2/5 contract → 3/5 engine → 4/5 (this) → 5/5 CI

@coderabbitai

coderabbitai Bot commented Aug 27, 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: Pro

Run ID: 40509c12-0e44-488f-97bc-efd5fe01a88a

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

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@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: 8b24853224

ℹ️ 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 +184 to +185
for (const [index, raw] of input.scenarios.entries()) {
for (const violation of scanForSensitiveContent(raw, { forbiddenTokens: input.forbiddenTokens })) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Scan approval artifacts before publishing them

The privacy gate scans only input.scenarios, but the operator-supplied approvals are later persisted verbatim in manifest.json. If an approver contains a forbidden token, secret, session ID, or local path, parseApproval accepts the nonempty string and the sensitive value is published despite the privacy approval; include approvals in this pre-parser scan, as the retrieval-benchmark promoter does.

Useful? React with 👍 / 👎.

Comment on lines +367 to +368
if (typeof entry.green !== "boolean" || !Array.isArray(entry.results) || entry.results.length === 0) {
evidenceFail(`scenarios[${index}]: fields-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.

P1 Badge Require complete mutation-class evidence

This parser only requires a nonempty result array and internally consistent booleans, so a tampered artifact can replace all results with one known class marked green, set the aggregate flags green, and still pass loadRelease because checkMutationEvidence checks only the matching fingerprint and entry-level flag. Consequently the strict consumer path can accept evidence that no longer demonstrates most of the required mutation battery; validate unique required class coverage and its applicability constraints, or recompute and compare the evidence on load.

Useful? React with 👍 / 👎.

Comment on lines +224 to +225
const destination = join(input.releasesRoot, input.releaseVersion);
if (existsSync(destination)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Enforce increasing release versions

Promotion only rejects the exact destination version, so releases can be installed out of numeric order. For example, promoting v2 with scenario X and then promoting v1 with X tombstoned succeeds; the numerically later immutable v2 now resurrects X and cannot be corrected in place, violating the stated vN+1 tombstone rule. Require a new version to be later than every installed release before publication.

Useful? React with 👍 / 👎.

Comment on lines +168 to +169
const manifestPath = join(releasesRoot, entry, RELEASE_FILES.manifest);
if (!existsSync(manifestPath)) continue;

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 Fail closed when a prior manifest is missing

If an installed vN directory has lost or never received its manifest, this branch silently ignores it and promotion proceeds without inheriting any of its tombstones. A subsequent release can therefore reintroduce a retired scenario precisely when prior release state is incomplete; a version-shaped directory without a manifest should block promotion rather than be treated as carrying no tombstones.

Useful? React with 👍 / 👎.

}

function runProbeWrongAnswer(scenario: HistorianEvalScenario): MutationResult {
const probe = scenario.probes[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.

P1 Badge Exercise every probe answer type

The mutation battery tests only the first probe of each scenario. In the added corpus every claim-id probe appears after another probe, so the entire corpus can produce green evidence without ever exercising the claim-id path in compareProbeAnswer; a regression that accepts an incorrect claim identifier would therefore pass the admission gate. Run the wrong-answer mutation for each probe or at least require coverage of every answer type.

Useful? React with 👍 / 👎.

Comment on lines +123 to +126
const scenarioFiles = readdirSync(join(releaseDir, RELEASE_FILES.scenariosDir)).sort();
const scenarios = scenarioFiles.map((file) =>
parseScenario(JSON.parse(readFileSync(join(releaseDir, RELEASE_FILES.scenariosDir, file), "utf8")), file),
);

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 Enforce the corpus-size budget when loading releases

promoteRelease enforces the 10–30 scenario budget, but the exported strict consumer path does not repeat that invariant after reading the scenario directory. A separately assembled or corrupted release containing fewer than 10 or more than 30 scenarios can therefore pass parsing, linting, tuple verification, approvals, and evidence checks even though the same corpus is rejected by promotion; enforce the release-level budget in loadRelease as well.

Useful? React with 👍 / 👎.

@kilo-code-bot

kilo-code-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 6 Issues Found | Recommendation: Address before merge

Overview

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

CRITICAL

File Line Issue
packages/e2e-tests/src/historian-eval/promote.ts 185 Privacy gate only scans input.scenarios; operator-supplied approvals can leak sensitive tokens into manifest.json.
packages/e2e-tests/src/historian-eval/promote.ts 225 Promotion does not enforce strictly increasing release versions, allowing out-of-order releases that break tombstone monotonicity.
packages/e2e-tests/src/historian-eval/mutations.ts 368 parseMutationEvidence only requires a non-empty result array without verifying required mutation class coverage.
packages/e2e-tests/src/historian-eval/mutations.ts 232 runProbeWrongAnswer only checks the first probe (probes[0]), skipping subsequent probe answer types such as claim-id.

WARNING

File Line Issue
packages/e2e-tests/src/historian-eval/promote.ts 169 inheritedTombstones silently skips release directories missing a manifest.json instead of failing closed.
packages/e2e-tests/src/historian-eval/promote.ts 126 loadRelease does not validate the 10–30 scenario corpus budget enforced during promotion.
Files Reviewed (17 files)
  • packages/e2e-tests/historian-eval/dev/hse-api-pagination-explored.json
  • packages/e2e-tests/historian-eval/dev/hse-build-rejected-bazel.json
  • packages/e2e-tests/historian-eval/dev/hse-config-format-explored.json
  • packages/e2e-tests/historian-eval/dev/hse-deploy-timeout-correction.json
  • packages/e2e-tests/historian-eval/dev/hse-file-naming-speculation.json
  • packages/e2e-tests/historian-eval/dev/hse-line-length-conflict.json
  • packages/e2e-tests/historian-eval/dev/hse-node-runtime-history.json
  • packages/e2e-tests/historian-eval/dev/hse-orders-index-speculation.json
  • packages/e2e-tests/historian-eval/dev/hse-orders-key-conflict.json
  • packages/e2e-tests/historian-eval/dev/hse-proto-cache-decision.json
  • packages/e2e-tests/historian-eval/dev/hse-release-cadence-history.json
  • packages/e2e-tests/historian-eval/dev/hse-test-runner-rejected.json
  • packages/e2e-tests/historian-eval/dev/hse-webhook-docs-injection.json
  • packages/e2e-tests/src/historian-eval/mutations.test.ts
  • packages/e2e-tests/src/historian-eval/mutations.ts - 2 issues
  • packages/e2e-tests/src/historian-eval/promote.test.ts
  • packages/e2e-tests/src/historian-eval/promote.ts - 4 issues

Fix these issues in Kilo Cloud

Previous Review Summary (commit 8b24853)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 8b24853)

Status: 6 Issues Found | Recommendation: Address before merge

Overview

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

CRITICAL

File Line Issue
packages/e2e-tests/src/historian-eval/promote.ts 185 Privacy gate only scans input.scenarios; operator-supplied approvals can leak sensitive tokens into manifest.json.
packages/e2e-tests/src/historian-eval/promote.ts 225 Promotion does not enforce strictly increasing release versions, allowing out-of-order releases that break tombstone monotonicity.
packages/e2e-tests/src/historian-eval/mutations.ts 368 parseMutationEvidence only requires a non-empty result array without verifying required mutation class coverage.
packages/e2e-tests/src/historian-eval/mutations.ts 232 runProbeWrongAnswer only checks the first probe (probes[0]), skipping subsequent probe answer types such as claim-id.

WARNING

File Line Issue
packages/e2e-tests/src/historian-eval/promote.ts 169 inheritedTombstones silently skips release directories missing a manifest.json instead of failing closed.
packages/e2e-tests/src/historian-eval/promote.ts 126 loadRelease does not validate the 10–30 scenario corpus budget enforced during promotion.
Files Reviewed (17 files)
  • packages/e2e-tests/historian-eval/dev/hse-api-pagination-explored.json
  • packages/e2e-tests/historian-eval/dev/hse-build-rejected-bazel.json
  • packages/e2e-tests/historian-eval/dev/hse-config-format-explored.json
  • packages/e2e-tests/historian-eval/dev/hse-deploy-timeout-correction.json
  • packages/e2e-tests/historian-eval/dev/hse-file-naming-speculation.json
  • packages/e2e-tests/historian-eval/dev/hse-line-length-conflict.json
  • packages/e2e-tests/historian-eval/dev/hse-node-runtime-history.json
  • packages/e2e-tests/historian-eval/dev/hse-orders-index-speculation.json
  • packages/e2e-tests/historian-eval/dev/hse-orders-key-conflict.json
  • packages/e2e-tests/historian-eval/dev/hse-proto-cache-decision.json
  • packages/e2e-tests/historian-eval/dev/hse-release-cadence-history.json
  • packages/e2e-tests/historian-eval/dev/hse-test-runner-rejected.json
  • packages/e2e-tests/historian-eval/dev/hse-webhook-docs-injection.json
  • packages/e2e-tests/src/historian-eval/mutations.test.ts
  • packages/e2e-tests/src/historian-eval/mutations.ts - 2 issues
  • packages/e2e-tests/src/historian-eval/promote.test.ts
  • packages/e2e-tests/src/historian-eval/promote.ts - 4 issues

Fix these issues in Kilo Cloud


Reviewed by gemini-3.7-flash · Input: 160.6K · Output: 17.8K · Cached: 861.8K

…ze governance (U5-U6)

Dev corpus: 13 hand-audited scenarios authored latent-truth-first, each
lint-clean, covering all seven hard-negative families (proposed-but-
rejected, explored-never-accepted, assistant speculation, user
correction, current-vs-historical, in-conversation prompt injection,
conflicting evidence) with expected-absent predicates phrased to match
only the forbidden active formation, never the legitimate rejection
record.

Mutation battery: per scenario, crafted wrong outputs per class fed
through the scorer's raw-output seam must go red AT the expected stage,
consulting one policy table; stage migration, a passing mutation, a
non-discriminating near-miss matcher, a probe-less scenario, and a
family set no false-authoritative class covers all fail the battery.

Freeze governance clones the retrieval-benchmark promotion pattern:
privacy scan before any parser, operator approvals bound to the exact
release-tuple fingerprint (one per kind: privacy, gold-intent), the
battery recomputed inside promoteRelease so evidence cannot be forged
or stale, review-dir plus staging round-trips through the strict
consumer path, atomic rename into an immutable release, and
tombstone-in-vN+1 errata that later releases must carry forward.
@ahrav
ahrav force-pushed the stack/historian-eval-04-corpus-governance branch from 8b24853 to 1865bcf Compare August 27, 2026 06:33

@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: 1865bcf46e

ℹ️ 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".

if (unexpected.length > 0 || entries.length !== expected.length) {
fail([`release: unexpected entries (${unexpected.length > 0 ? unexpected.join(", ") : "missing files"})`]);
}
const manifest = parseManifest(JSON.parse(readFileSync(join(releaseDir, RELEASE_FILES.manifest), "utf8")));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bind loading to a trusted manifest fingerprint

When loading a release from outside the promoter's in-memory flow, parseManifest verifies only self-consistency: an editor can change either approver string—or fabricate both approvals—and retain the tuple fingerprint, after which loadRelease accepts the release as approved. Because nothing trusted outside the directory authenticates the manifest, the operator-approval gate can be bypassed; accept an expected manifest fingerprint or another external trust anchor on the strict consumer path.

Useful? React with 👍 / 👎.

"family": "explored-never-accepted",
"predicate": {
"kind": "normalized-substring",
"value": "use cursor-based pagination"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use matchable hard-negative predicates

For this scenario, predicateMatches performs normalized literal substring matching, but the transcript never contains “use cursor-based pagination”; a bad historian output such as “Cursor-based pagination handles concurrent inserts best” therefore promotes the explicitly undecided option without matching this expectedAbsent predicate, allowing the false-authoritative check to pass. Use a stable core span such as “cursor-based pagination”; the sibling keyset predicate has the same problem.

Useful? React with 👍 / 👎.

Comment on lines +86 to +90
compartments: [
{
start: 1,
end: messageCount,
title: `Baseline for ${scenario.id}`,

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 Build a baseline satisfying the compartment minimum

For any otherwise valid scenario with gold.compartments.minCount > 1, this baseline always emits exactly one compartment. assertBaselineValidates consequently sees a structural FAIL before running any mutations, so promoteRelease rejects every such scenario even though the contract permits higher minimums. Construct the baseline with at least the declared number of valid contiguous compartments.

Useful? React with 👍 / 👎.

const expected: string[] = [RELEASE_FILES.evidence, RELEASE_FILES.manifest, RELEASE_FILES.scenariosDir];
const unexpected = entries.filter((entry) => !expected.includes(entry));
if (unexpected.length > 0 || entries.length !== expected.length) {
fail([`release: unexpected entries (${unexpected.length > 0 ? unexpected.join(", ") : "missing files"})`]);

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 Avoid logging unscanned release entry names

When the strict loader is given a malformed or externally assembled release whose unexpected filename contains a forbidden token, customer identifier, or local path, this diagnostic writes that filename directly to logs before any privacy scan. Promotion scans scenario values but not filesystem entry names, so report only the count of unexpected entries, as otherwise unreviewed names cross the privacy boundary.

Useful? React with 👍 / 👎.

Comment on lines +116 to +118
const entries = readdirSync(releaseDir).sort();
const expected: string[] = [RELEASE_FILES.evidence, RELEASE_FILES.manifest, RELEASE_FILES.scenariosDir];
const unexpected = entries.filter((entry) => !expected.includes(entry));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject symlinks from frozen release trees

When loading an externally assembled or tampered release, these name-only entry checks allow manifest.json, mutation-evidence.json, the scenarios directory, and individual scenario files to be symlinks because the subsequent filesystem calls follow them. Self-consistent target bytes therefore pass loadRelease, after which changing files outside the installed vN directory changes what later runs load without modifying that supposedly immutable tree; use lstat to require real regular artifact files, a real scenarios directory, and regular scenario children.

Useful? React with 👍 / 👎.

"bazel",
"turborepo"
],
"goldAnswer": "turborepo"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bind the build-tool probe to its source claim

When the Turborepo claim is promoted but omitted from this probe turn by the injection budget, the absent sourceClaimRef leaves compareProbeAnswer with no backing gold claim, so a wrong or empty response is charged as a historian probe FAIL instead of error-trimmed. Add sourceClaimRef: "exp-turborepo" so infrastructure trimming is not misreported as model quality; several other newly added multiple-choice probes omit their evident backing claim in the same way.

Useful? React with 👍 / 👎.

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