Skip to content

fix(oracle-gaps): deterministic stop sessions and stdout lead, a constructed failing owner, and a copy's random stream - #54

Merged
KyleAMathews merged 9 commits into
mainfrom
fix/gap-e2e-determinism
Nov 13, 2026
Merged

KyleAMathews merged 9 commits into
mainfrom
fix/gap-e2e-determinism

Conversation

@KyleAMathews

@KyleAMathews KyleAMathews commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR fixes three defects that the e2e gap oracle and the klona validation found while #48 was being written.

  • (a) Determinism of the stop law's observation. The stopped observation listed session directories by name, .oracle-gaps-<pid>, and the pid differs on every run. As a result, determinism: constructed history: every outcome class failed on main. The stop law reads only whether a later run kept an earlier run's directory, so the observation now holds counts, which no name or order changes: the session directories present once the run ended, and how many of them the previous stopped run had left. (A first version numbered them session#n in readdir order, which sorts by pid, so the numbering of two directories still depended on the pids; the review caught it.) The stdout lead holds no timing line: the progress lines with seconds go to stderr, and the stdout summary has none.

  • (b) A construction defect in the oracle. The constructed history "a failing owner under a config that --config names" owes the state blocked by brokenOwner. Law 14 (maintainer disposition G5, 2026-11-11) owes that state only when the failing owner, file b, owns a mutant that no passing owner kills. The construction owned only file a's function and relied on its generated base for something that b owns. Seed 520648412 drew a base with nothing that b owns. The history now constructs that ownership: a second function that only b owns. This is a fix to the oracle, not to production.

  • (c) A mutant's copy drew from another random stream than its original. The capture seeds Math.random, crypto.getRandomValues, and crypto.randomUUID per test, by the test's key. In a mutant batch, the test file is a copy named for its batch (.oracle-gaps-<pid>-<n>-<file>), so the key held the copy's path. The unmutated runs run the original file, so every mutant copy drew a different stream than the baseline. The effects:

    • A test that observes a draw read as changed under a mutant that changed nothing.
    • An oracleProperty campaign with no --seed draws its seed from Math.random, so its random units ran other histories under every mutant.

    This explains the klona run without --seed reported in fix(oracle-gaps): report a shortcut for speed as extra-work, and let a work bound kill it #48: there, all 10 speed shortcuts came out as not-judged. The batch now names each copy's original file, and the copy seeds its streams by the test's key in that file. Pinning one seed per check, the fix proposed first, is not needed.

  • (d) Determinism of the stdout lead (found while merging main). Since feat(oracle-gaps): every streamed line starts with oracle-gaps: <kind> #46 the stream has no blank line, so the observed stdout lead is the whole stdout, and its notes name the run's scratch directory (.scratch-<random>). The same determinism test failed at observation 0 (cli). Each lead line now names the scratch directory as the rest of the trace does.

Evidence

  • Law:
    • (a): a determinism test compares relations, not process ids (the framework's determinism rule: "a driver records relations ... rather than clock readings or random ids"). The stop law is unchanged (maintainer decision, 2026-11-11).
    • (b): law 14 and G5, as above.
    • (c): law 3, "the CLI's capture must not change what a test observes", and the capture's own rule that a test sees the same random values in every run.
  • Reach: the e2e states a run after a stopped one that left its session, blocked by brokenOwner, and the new a draw from Math.random repeats in a mutant's copy. The last is reached by the witness w_draws, which observes a Math.random draw beside a cache write that nothing observes.
  • RED (on the parent of each fix):
  • GREEN:
  • Mutants:
    • (a) A CLI that keeps stale session directories: "the run after a stopped one left 1 of its session directories in the package". The count observation sees the law as strictly as the names did.
    • (c) The parent commit's copy-path seed is the hostile mutant: the RED above.
  • Klona validation: with this branch's CLI and no --seed, the r16 copy gives 218 mutants: 208 killed, 8 not-observed, and 2 not-distinguished. Those are the classes of a seeded run, with no not-judged. Before the fix, the same run gave 10 not-judged.
  • Limits:
    • (a) judges only the identity of session directories within one history.
    • (c) covers the ambient sources that the capture seeds. A test that reads entropy another way (crypto.randomBytes, a native addon) is not seeded by the capture.
    • A separate batch-dependence defect remains open as oracle-gaps: a mutant's outcome can depend on its batch (setup-error in a full run, killed with --only) #53. In the klona r20 run, two mutants were a setup-error in a full run but were killed under --only. That is not this mechanism: the seed defect affected every copy, so --only would show it too. The hypothesis is builtin-prototype state carried between copies in one worker, because the reset removes only new global names. A deterministic witness needs control over batch composition, which the runner does not give today.

Size

  • Production code: capture-setup.mjs +11 lines and check.mjs +3 lines (source; the CLI ships as source).
  • Tests: the e2e model and oracle, +50 lines.

This pull request and its description were written by Isaac.

KyleAMathews and others added 6 commits November 12, 2026 13:30
…tity, not pid

The stop law reads only whether a later run kept an earlier run's
session directory, but the driver observed the directories by name,
`.oracle-gaps-<pid>`, which differs on every run, so the determinism
test of "every outcome class" failed. Each directory is now the nth
distinct one of its history (`session#n`).

RED: determinism: constructed history: every outcome class, "two runs
recorded different traces at observation 1 (stopped)". GREEN after.
Mutant (the CLI keeps stale session directories): "the run after a
stopped one left its session directory session#0 in the package".

Co-authored-by: Isaac <no-reply@databricks.com>
…w it from the base

The constructed history "a failing owner under a config that --config
names" owes the state "blocked by brokenOwner", which the law owes only
when the failing owner, file b, owns a mutant that no passing owner
kills (law 14; maintainer disposition G5, 2026-11-11). The construction
owned only file a's function and relied on its generated base for
something b owns, so a base with nothing b owns missed the state. It
now adds a second function that only b owns.

RED: ORACLE_SEED=520648412 ORACLE_PATH=0 replay of "random seed: a
failing owner under a config that --config names": "did not enter
blocked by brokenOwner". GREEN: the same replay and the fixed-seed unit
pass.

Co-authored-by: Isaac <no-reply@databricks.com>
…iginal drew

The capture seeds the ambient random source per test, so a test's draws
repeat in every run. The witness w_draws observes a Math.random draw
beside a cache write that nothing observes: its if-false mutant changes
only the cache, so it is owed not-observed.

RED: "src/sut.ts:w_draws:74:if-false#0: not-judged, owed not-observed".
A mutant runs in a copy of its test file, named for its batch
(.oracle-gaps-<pid>-<n>-<file>), and the capture seeded the source by
the copy's path, while the unmutated runs run the original file: every
copy draws another stream than the baseline. The klona run without
--seed showed the same thing through oracleProperty's drawn campaign
seed, so a random unit's histories changed under each mutant.

Co-authored-by: Isaac <no-reply@databricks.com>
The capture seeds Math.random, crypto.getRandomValues, and
crypto.randomUUID per test, so a test sees the same values in every run.
It seeded them by the test's key, whose file is the copy's path in a
mutant batch (.oracle-gaps-<pid>-<n>-<file>), while the unmutated runs run
the original file: every mutant drew another stream. A test that
observes a draw then reads as changed under a mutant that changes
nothing (not-judged where not-observed is owed), and an oracleProperty
campaign with no --seed draws its seed from Math.random, so its random
units ran other histories under every mutant (the klona run without
--seed: 10 false not-judged gaps). The batch now names each copy's
original file, and the copy seeds by the key in that file.

GREEN: e2e "every outcome class" passes, w_draws not-observed.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@changeset-bot

changeset-bot Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ba86902

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@tanstack/oracles Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7558fe17-610a-480e-8cff-8c67328d732e
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@pkg-pr-new

pkg-pr-new Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/TanStack/oracles/@tanstack/eslint-plugin-oracles@54
npm i https://pkg.pr.new/TanStack/oracles/@tanstack/oracles@54

commit: ba86902

KyleAMathews and others added 2 commits November 12, 2026 21:47
The witness module keeps both sides: w_draws beside #48's work-law
witnesses (w_fast*, w_fastclocked, w_fastturn, w_fastplain, and w_cold,
still last in file a), and the test record's draws constant beside
work and timed.

Co-authored-by: Isaac <no-reply@databricks.com>
Since #46 the stream has no blank line, so the observed stdout lead is
the whole stdout, and its notes name the run's scratch directory
(.scratch-<random>), which differs between two runs of one history. The
determinism test of "every outcome class" failed at observation 0
("cli") on main after #46. Each lead line now names the scratch
directory as the rest of the trace does.

RED (on the merge of main): "two runs of constructed history: every
outcome class recorded different traces at observation 0 (cli)", the
first difference in the results note's .scratch-<id> path. GREEN after.

Co-authored-by: Isaac <no-reply@databricks.com>
@KyleAMathews KyleAMathews changed the title fix(oracle-gaps): deterministic stop sessions, a constructed failing owner, and a copy's random stream fix(oracle-gaps): deterministic stop sessions and stdout lead, a constructed failing owner, and a copy's random stream Nov 13, 2026
…t ordered names

Review of #54: session#n numbered the directories in readdir order,
which sorts by pid, so with two directories present the numbering
depended on the pids (a replica: the kept one was not session#0 in 518
of 1000 trials). The observation now holds counts, which no name or
order changes: the session directories present once the run ended, and
how many of them the previous stopped run had left. The law is as
strict: any kept directory is a violation.

GREEN: "fixed seed 20261105: every outcome class" and its determinism
unit. Mutant (the CLI keeps stale session directories): "the run after a
stopped one left 1 of its session directories in the package". The
stdout lead holds no timing line: the progress lines with seconds
(unmutated, mutant phase) go to stderr through log, and the stdout
summary (report.mjs) has none.

Co-authored-by: Isaac <no-reply@databricks.com>
@KyleAMathews
KyleAMathews merged commit affeb1a into main Nov 13, 2026
31 checks passed
@KyleAMathews
KyleAMathews deleted the fix/gap-e2e-determinism branch November 13, 2026 05:32
KyleAMathews added a commit that referenced this pull request Nov 13, 2026
The fs recorder's wrappers reuse #48's keepProperties.

Co-authored-by: Isaac <no-reply@databricks.com>
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