GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat: make oracleProperty the only campaign API and lint the older path by KyleAMathews · Pull Request #4 · TanStack/oracles

Conversation

@KyleAMathews

@KyleAMathews KyleAMathews commented Nov 9, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

🎯 Changes

oracleProperty is now the only campaign API in @tanstack/oracles. A new lint rule reports the older path in oracle files.

The maintainer decided (2026-11-09) that reach must come from oracleProperty's constructed histories. A constructed history is built from a generated history, so the steps before and after the required state stay random. The older path ran its own fast-check campaign with createOracleReach or runOracleCampaign. It had no constructed histories, so its reach either depended on the seed or relied on literal examples.

  • Rule oracle/oracle-property-only: in an oracle file, it reports fc.assert(...), fc.check(...), test.prop or it.prop from @fast-check/vitest, and any import of createOracleReach or runOracleCampaign. Modules listed in settings.oracles.implementationModules are exempt. The recommended config enables it.
  • Baseline for adopters: settings.oracles.legacyCampaigns: [{ file, reason }] exempts a listed file while it migrates. An entry without a reason is reported and exempts nothing. An entry whose file no longer has a legacy use is reported as stale. An entry for a file outside the oracle-file glob, or for an implementation module, is reported as invalid. The rule reads one file at a time, so it cannot stop a baseline from growing; an adopter checks that in its own diff check. This repository uses no baseline.
  • Fast-check imports: the rule also counts fc imported from @fast-check/vitest.
  • Removed: createOracleReach, runOracleCampaign, their modules, and their self-tests. The config oracle and the route oracle drop the dimensions that imported them. This is a breaking change, and its changeset names the replacement.
  • Migrated: this repository's lint oracles now run on oracleProperty: fixed-seed, probe-form, and replay-observation. The 2,450-line part-reach oracle is split into five oracle files (part-reach-forms-oracle, -modules-, -helpers-, -families-, and -laws-oracle.property.test.js) that share part-reach-oracle-support.js. All of these files have oracle in their names, so every oracle rule reads them. Each of the 42 reach states has a constructed history that splices its fixtures into the generated skeleton, so shrinking goes through the generated input. The run budgets do not change.

🔬 Oracle Coverage

  • Law: a generated campaign in an oracle file runs only through oracleProperty. Authority: the maintainer decision above and the header of oracle-property.ts.
  • Reach: the rule's oracle generates programs, and a constructed history enters each state: a raw campaign, a raw property, a legacy import, a reasoned exemption, a reasonless entry, and a stale entry.
  • RED: on the plugin before the rule existed, the rule's oracle fails 3 of 4 tests. On the parent commit, the rule reports this repository's raw campaigns and legacy imports.
  • GREEN: pnpm run lint:check reports 0 errors with no baseline. The plugin suite passes 4,284 of 4,284 tests, the package suite 54 of 54, and the framework self-oracle 98 of 98. The gap oracles pass. tsc, build, publint, sherif, and Prettier pass.
  • Mutants: each of these fails both of the rule's campaigns: ignoring .prop, accepting a reasonless entry, ignoring legacy imports, dropping the stale report, and not exempting implementation modules. In the route oracle, a classifier that claims a fixed seed for an unpathed seed fails 8 of 10 tests.
  • Limits: the rule does not see aliases, helpers, or a dynamic import() of the removed modules. An entry for a deleted or renamed file is not reported, because a settings path cannot be matched to a file soundly from one lint run. The part-reach support module is about 2,200 lines. The part-reach comparison ignores oracle-property-only reports, because its fixtures are raw campaigns on purpose; the rule's own oracle covers those reports.

A code review and a simplification pass ran on the first version, and six follow-up commits fix all ten review findings. A second, targeted review of those fixes found ten more issues, and five more commits fix them:

  • Each part-reach constructed history now writes only the fields that gate its state, by dotted path into the generated skeleton. The generated registrations, modules, and companions stay as generated. Before, 37 of the 42 builders replaced part of the skeleton with a constant fragment. A new law, lint.part-reach.constructions, checks this shape. Over 300 sampled skeletons, every construction still enters its state in 300 of 300, and its outputs now vary (for example, the export-star-cycle module goes from 1 distinct output to 300).
  • A malformed legacyCampaigns entry, an entry outside the oracle-file glob, or an entry for an implementation module is a configuration error. The rule reports it once, when it is created, and the lint run stops.
  • A declared state and a constructed history cover the @fast-check/vitest path.
  • probe-form-grammar.js is renamed probe-form-oracle-grammar.js, so the oracle rules read it.

Each commit message has its RED, GREEN, and mutant results. The README documents the rule and the legacyCampaigns setting.

✅ Checklist

  • I have tested this code locally. I ran the checks that CI runs.

🚀 Release Impact

  • This change affects published code, and I have generated changesets. Neither package is released yet, so the rule is folded into the plugin's existing changeset, and the API removal is folded into the framework's existing changeset.
  • This change is docs/CI/dev-only (no release).

TanStack DB pins these packages by commit. When it moves to a release with this change, it keeps local copies of createOracleReach and runOracleCampaign and lists its existing users in legacyCampaigns.

This pull request and its description were written by Isaac.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
KyleAMathews and others added 5 commits November 9, 2026 11:45
@KyleAMathews Isaac
feat(eslint-plugin-oracles): report raw campaigns and legacy reach AP…
fd807f9
…Is in oracle files

Law: in an oracle file, a generated-history campaign runs only through
oracleProperty. A raw campaign (fc.assert, fc.check, a .prop registration)
and an import of createOracleReach or runOracleCampaign are reported.
A reasoned settings.oracles.legacyCampaigns entry exempts its file's uses;
a reasonless entry is reported; an entry with no legacy use is stale.
Implementation modules are exempt.
Authority: maintainer decision 2026-11-09 (oracleProperty is the only
campaign API); oracle-property.ts header (constructed histories make reach
required, a raw campaign has none).

Reach: oracle-property-only oracle, fixed seed 20261109 and random campaigns, each
  state reached by constructed histories: raw campaign 24, raw property 30,
  legacy import 27, reasoned exemption 11, reasonless 9, stale 6,
  implementation exempt 7, other-file entry 8, quiet 37 (fixed seed).
RED: on the unchanged plugin the oracle fails 3 of 4 tests, the rule is
  absent (fixed seed counterexample: legacy import reports nothing).
GREEN: the oracle passes 4 of 4 tests.
Mutant: five hostile mutants each fail both campaigns: ignore .prop, accept
  a reasonless entry, ignore legacy imports, drop the stale report, exempt
  nothing as an implementation module.
Limits: aliased campaigns, helpers, re-exported fast-check bindings, and
  import() of legacy modules are not generated; a stale entry for a deleted
  file is not reported. The rule is enabled in configs.recommended, so this
  repo's own lint reports its existing raw campaigns until they migrate in
  a later commit of this branch.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
test(eslint-plugin-oracles): run the lint oracles through oracleProperty
471edd0
Migrate the three rule oracles that ran raw fast-check campaigns or the
legacy createOracleReach ledger onto oracleProperty, with the same laws
and the same models and drivers:

- fixed-seed: the census is an enumerated oracleProperty; the generated
  campaign has a constructed history for each of its 16 declared states.
- probe-form: the generated campaign has a constructed history for each
  site kind (one per MODEL key), each legal sub-state, and the undeclared
  guard; the census and the root-config severity checks are observations.
- replay-observation: the finite grammar is exhaustive, so its histories are
  enumerated and declare no reach states; its two pinned rejections become
  calibrations of the same check.

The raw campaigns in these files used to pass; the rule reported them.

part-reach is not migrated. It is a 2450-line oracle with 22 feature-family
campaigns, metamorphic and companion-edit laws, and 44 declared reach
states that need constructed histories. It is baselined in eslint.config.js
with a reason, as are the three legacy self-tests that the API removal will
delete. The baseline is visible, and the maintainer decides it.

part-reach's harness ignores oracle/oracle-property-only reports: its
fixtures are deliberately raw campaigns, and that rule has its own oracle.
no-disabled's pending-test count rises by one for the determinism test.

Law: each migrated oracle keeps its law, owner, and authority; a
generated campaign runs through oracleProperty, whose constructed
histories make each required state's reach required (maintainer decision,
2026-11-09).
Reach: fixed-seed: 16 declared states, each entered by its constructed
  history; the census enumerates 3 bindings. probe-form: 55 declared
  states, each entered by its constructed history. replay-observation: an
  exhaustive enumeration with no reach states.
RED: on the parent commit, oracle/oracle-property-only reports 3 raw
  campaigns and a legacy import in fixed-seed, 3 in probe-form, and 1
  import in replay-observation (npx eslint --no-fix packages).
GREEN: npx eslint --no-fix packages: 0 errors. The plugin's full suite:
  4201 of 4201 tests pass (9 files).
Mutant: each migrated oracle's two calibrations corrupt the trace (drop a
  report, add a wrong rule's report, shift a line, add a spurious report),
  and the same check rejects each; the rule's five hostile mutants from the
  previous commit each fail the rule's oracle.
Limits: part-reach's campaigns are raw and baselined; its reach is not
  gated anywhere until it migrates. The probe-form severity checks read the
  repo's root config, as before.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
test(eslint-plugin-oracles): port part-reach onto oracleProperty in f…
e382e7b
…ive files

The 2456-line part-reach oracle ran 21 raw feature-family campaigns, four
metamorphic and companion-edit laws, and a receiving-path law through
fc.assert, with one createOracleReach ledger of 42 declared states. It is
split along its feature families, with no baseline:

- part-reach-fixtures.js: the shared grammar, builder, model, drivers,
  receiving-path setup, and fixed skeletons (moved unchanged), plus the
  shared parts: the constructed-history search, the shared check, the
  calibrations, and familyOptions.
- part-reach-forms (8 properties): base grammar, call/member bodies, .for
  tables, options, let and for...of drivers, declaration spreads, generated
  properties.
- part-reach-modules (7 properties): export-star and named re-export cycles,
  string-keyed and type-model declarations, unparseable companion,
  JavaScript companions.
- part-reach-helpers (7 properties): imported registrars, caller-passed
  bodies and their layouts, helpers by module name and import form,
  namespace and default imports, oracle properties and lookalikes.
- part-reach-families (1 property): every family on at once.
- part-reach-laws: receiving-path Linter, fixed inputs (enumerated), order
  invariance, rerun equality, companion edits between lints, and the pinned
  dependency chain (enumerated).

Histories are grammar skeletons (programSkeleton), so they shrink; the plan
is buildProgram(skeleton, features) through `resolve`. Each gated state of
a property has a constructed history: rejection sampling from the grammar,
seeded from the generated history (constructedFor). The 42 states are gated
by the first campaign whose features enter them, and measured elsewhere.
Budgets are unchanged (30, 400, 300, 200, 40, 3000, 4). Every property has
the two reportCalibrations (a dropped and a spurious report), and the order
law has its own.

Law: every test registration in an oracle file reaches a declared part, or
the rule reports it (no-unit-test header; maintainer decision 2026-11-02).
Reach: every state is gated by at least one constructed history under its
  property's features; the test names print the reach counts. The 42 states
  are the same set as the original file's.
RED: on the parent commit, npx eslint --no-fix on part-reach-oracle reports
  oracle/oracle-property-only seven times (fc.assert at lines 1741, 1747,
  1848, 1896, 1902, 1909, 1922) and once for the createOracleReach import at
  line 81. The new files report none (see GREEN).
GREEN: npx eslint --no-fix packages/eslint-plugin-oracles: 0 errors. The
  plugin's full suite: 4284 of 4284 tests pass across 13 files (about 135 s
  on two workers).
Mutant: each property's calibrations corrupt the observed reports (drop one,
  add one, reorder change on the reordered lint, and on the rerun second
  lint) and the check rejects each, as the calibration tests show.
Limits: the 42-state set is kept, but states are gated only where a property
  declares them: `typeModels`, `all-families`, `registration-order`, and the
  receiving-path law gate none, and measure what they reach. Constructed
  histories are rejection samples from the grammar within 3000 draws; a state
  that the features cannot enter fails its construction loudly. keysOf keeps
  its filter of oracle/oracle-property-only, which is covered by
  oracle-property-only-oracle.property.test.js.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
chore(lint): remove the part-reach legacyCampaigns entry
404bf7d
part-reach is ported to oracleProperty in part-reach-fixtures.js and five
part-reach-*.property.test.js files (previous commit), so the baseline entry
for its old file is removed. The remaining three entries are the legacy
self-tests, which the API removal deletes.

Law: the baseline only shrinks (settings.oracles.legacyCampaigns, maintainer
direction: no baseline in TanStack/oracles).
Reach: not applicable (configuration).
RED: with the entry present and its file deleted, the entry is stale and
  nothing reports it, so the entry is removed to keep the baseline honest.
GREEN: npx eslint --no-fix packages: 0 errors (two pre-existing warnings).
Mutant: not applicable.
Limits: the stale-entry check reports only files that a lint run sees; the
  deleted file is not checked.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
feat(oracles): remove createOracleReach and runOracleCampaign
e07674a
A campaign run through createOracleReach or runOracleCampaign reaches a
declared state only where its seed draws one, so a passing run shows no
reach. oracleProperty is the only campaign API: its constructed histories
make each declared state's reach required, and measured states are counted
but never gated (maintainer decision, 2026-11-09).

Removed: src/oracle-reach.ts, src/oracle-campaign.ts, their re-exports, and
their self-tests (declared-reach-oracle, oracle-reach.test,
oracle-campaign.test). The route and config oracles drop the runner
dimension and the reach reader that imported them. oracleCampaignSeed stays,
since readOracleRunConfig uses it. The eslint.config.js legacyCampaigns
baseline and the oracle-campaign implementation entry are removed.
oracleCampaignSeed remains exported (the package export test lists it).

Law: oracleProperty is the only generated-history campaign API in
@tanstack/oracles (maintainer decision, 2026-11-09); its reach comes from
constructed histories.
Reach: no reach is claimed by the removal; the route and config oracles keep
  their reach states (the reader removal drops only the reach reader's
  histories, which now use replay requested, the same selection).
RED: the parent commit's oracle/oracle-property-only reported the legacy
  import in declared-reach, oracle-reach.test, and oracle-campaign.test, and
  the migrated part-reach files used the API before the port.
GREEN: npx eslint --no-fix packages: 0 errors. oracles package suite 54/54
  (6 files, the three self-tests removed). Plugin suite 4284/4284 (13
  files). Framework self-oracle 98/98. Gap fast oracles 19/19; the gap
  report's fixed-seed oracle passes. tsc, build, publint, sherif, prettier
  clean.
Mutant: the route oracle with a classifier that claims fixed seed for every
  unpathed seed fails 8 of 10 tests on the route oracle after the runner
  removal.
Limits: the removal is not a production behavior change. oracleCampaignSeed
  remains a public export. The route oracle's own-campaign histories are
  unchanged in law; only the runner branch was removed.

Co-authored-by: Isaac <[email protected]>
@coderabbitai

coderabbitai Bot commented Nov 9, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

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: 23b2b4c9-eb74-4255-97ed-c4dd441de5de
  • 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

  • X
  • Mastodon
  • Reddit
  • LinkedIn
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@changeset-bot

changeset-bot Bot commented Nov 9, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: c70d2ad

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes changesets to release 2 packages
Name Type
@tanstack/eslint-plugin-oracles Patch
@tanstack/oracles Patch

Click here to learn what changesets are, and how to add one.

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

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

KyleAMathews and others added 4 commits November 9, 2026 12:55
@KyleAMathews Isaac
fix(eslint-plugin-oracles): count fast-check through @fast-check/vite…
4827e83
…st; report invalid entries

R4-7: an `fc` imported from @fast-check/vitest runs campaigns too. The rule
counted only fast-check bindings, so `fc.assert` through that import was not
reported.

R4-8: the rule cannot see the base commit, so it cannot stop a baseline from
growing. Its header and changeset now say so, and a project enforces shrink
only in its own diff check. Entries the rule can never judge are reported as
`invalidEntry`: a file outside the oracle-file glob, or a framework
implementation module.

Also: the legacy-name import check stays after the APIs are removed, so a
project that still vendors them sees the reports (header note). The
`imported.name ?? imported.value` pair is one helper (importedName).

Law: a generated-history campaign in an oracle file runs through
oracleProperty, whatever fast-check binding it takes; a baseline entry is
judged only in an oracle file that is not an implementation module
(maintainer decision, 2026-11-09).
Reach: oracle-property-only oracle; the new `vitest` alias and `invalid`
  dimension are generated; the invalid state is entered by a constructed
  history ("an entry the rule cannot judge"), and the vitest form by the
  generated alias.
RED: R4-7: with the vitest alias in the grammar, the counterexample
  {"alias":"vitest", sites: [fc.assert]} fails against the rule that accepted
  only fast-check (1 of 4 tests failed). R4-8: with the invalid dimension and
  no rule support, 2 tests fail (counterexample: invalid "outside").
GREEN: the oracle passes 4 of 4 after each change. Plugin suite: see the
  pull request for the full CI set.
Mutant: accept only fast-check: 1 test fails. Never report an out-of-glob
  entry: "Failed Tests 2". Never report an implementation-module entry: not
  rerun separately; the invalid dimension's implementation case is generated
  in the same oracle, so it is part of the same kill.
Limits: the rule sees one file, not the base commit: additions are not
  stopped. An entry for a deleted file is not reported. Aliases and helpers
  are not followed.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
fix(oracles): give config-oracle's fixed histories distinct readers; …
18c11d6
…unexport oracleCampaignSeed

R4-10 (and S1): two fixed histories read `replay requested` twice, so they
did not pit a second reader against the selection. The second reads are now
`run config replays` ("a reconfiguration that would change a read selection")
and `replay witness attaches` ("a replay set after configuration"). Both
readers return the replay selection, so the law is the same. Coverage change:
these two histories now exercise the run-configuration and witness readers
instead of repeating the first one.

S5: oracleCampaignSeed has only the internal caller in oracle-config.ts, so it
is no longer exported; the package API snapshot drops it.

S9: the config-oracle header's uneven lines are reflowed. No content change.

Law: a configuration decides every oracle read; the replay selection is the
one the readers agree on (oracle-config.ts; maintainer decision, 2026-11-08).
Reach: config-oracle's declared states unchanged (the model's reached function
  reads the steps, and the two histories still enter their states).
RED: no RED is claimed: the duplicate reads were a coverage gap, not a wrong
  answer. The counterpart is the mutant below.
GREEN: packages/oracles config and package tests: 33 of 33 pass after the
  change, and the config oracle passes 11 of 11.
Mutant: the run-configuration reader made to return false: the config oracle
  fails 1 of 11 tests.
Limits: the edited histories cover two readers; the other readers keep the
  coverage they had. Unexporting oracleCampaignSeed is a breaking change for
  a consumer that imported it; no consumer in this repository does.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
docs: fold the unreleased rule into the plugin's patch changeset; doc…
3deefad
…ument the rule

R4-9: the packages are at 0.0.0 and nothing is released, so the removal of
createOracleReach and runOracleCampaign needs no changeset: the framework's
add changeset simply never had them. The separate minor changesets are
deleted. The rule is folded into the plugin's add changeset, a patch, so the
first release is 0.0.x as intended. The changeset now says the rule cannot
stop a baseline from growing.

README: a section for oracle/oracle-property-only: what it reports, the
exemptions, the legacyCampaigns setting with an example, and the entry checks
(no reason, stale, and invalid entries).

Law: documentation of an existing law; no behaviour change.
Reach: n/a (documentation).
RED/GREEN: prettier --check on README and .changeset passes.
Mutant: n/a.
Limits: the README describes the rule as it is; the entry checks are the
  ones in the rule's header and oracle.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
test(eslint-plugin-oracles): name the part-reach oracles so the rules…
d4f1988
… read them

The part-reach split named its five files and its support module without
"oracle", so recommended config (**/*oracle*), oracle/no-unit-test,
pure-model, replayable-seed, and oracle-property-only skipped them. Rename
them to *-oracle.property.test.js and part-reach-oracle-support.js.

The rules then reported 22 incomplete oracleProperty calls: each file passed
familyOptions(...) to oracleProperty, which hides the check from the rule.
Each call now passes `{ ...familyOptions({...}), check: checkPaths }`, so the
check is visible in the file and the family options are unchanged.
The receiving-linter property passes checkPaths explicitly.

Simplifications folded in: the REACH_ORACLE_DEBUG console block is removed;
withForms is one export beside BASE; usePartReachPaths registers the
receiving paths' setup and teardown once per file.

Law: unchanged (the same reach relation and check; see PART_REACH_AUTHORITY).
Reach: unchanged: 42 declared states, each gated by the family that enters it.
RED: npx eslint --no-fix on the renamed files (before this commit's inline
  edit): 22 errors, oracle/no-unit-test "does not pass an object literal with
  a check" in forms, helpers, modules, families, and the receiving linter.
GREEN: npx eslint --no-fix packages/eslint-plugin-oracles/tests/part-reach-*.js
  reports 0 problems. vitest tests/part-reach: 145 of 145 pass (5 files, about
  3m12s on 2 workers).
Mutant: the calibrations of every property stay (a dropped report and a
  spurious report on each receiving path), so the check still rejects the
  wrong observations.
Limits: the run-budget check (check-oracle-run-counts) is part of TanStack DB,
  not this repository, so it is not run here; this repository has no
  equivalent.

Co-authored-by: Isaac <[email protected]>
@pkg-pr-new

pkg-pr-new Bot commented Nov 9, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Open in StackBlitz

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

commit: c70d2ad

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

KyleAMathews and others added 7 commits November 9, 2026 13:09
@KyleAMathews Isaac
test(eslint-plugin-oracles): build constructed histories by splicing,…
f78f93d
… not sampling

A constructed history must shrink through the generated input. The
constructions were rejection samplers: they hashed the generated skeleton
into a seed, discarded it, and drew up to 3000 fresh skeletons with fc.sample
until one entered the state. The output did not depend on the generated input
except through the hash, a failed draw threw only after 3000 builds, and
every construction built 3000 skeletons, so the suite took about 3m12s.

Each state now has an explicit construction, a factory of the features that
gate it. It splices the fragments that enter the state into the generated
skeleton, in the generated shape (formIndex, frameworkOptions, import forms),
and keeps the rest. The fragments are the fixtures that the support module
already states, and the framework checks every `enters` at run time.

The hook helper allows a minute, since the receiving-path setup can exceed the
default ten seconds on a loaded machine.

The companion-edit state is still recorded by statesOf in this commit; the
edit-law change follows in the next commit.

Law: unchanged. Each declared state stays gated by a constructed history under
the features of its family; the reach relation and the check are the same.
Reach: 42 gated states, each with a construction: 300 of 300 generated
skeletons enter each state under its family's features, and every built
history is generable (programSkeleton.canShrinkWithoutContext), checked with
a standalone script before the suite ran.
RED: at d4f1988 the construction was constructedFor, which returned a skeleton
drawn from fc.sample(programSkeleton, { seed, numRuns: 3000 }) and ignored its
generated input except through the seed. The suite took 3m12s on 2 workers.
GREEN: npx vitest --run --maxWorkers=2 tests/part-reach: 145 of 145 pass in
43 s. npx eslint --no-fix on the part-reach files: 0 problems.
Mutant: the check is unchanged and still rejects the dropped and spurious
reports of every property (the calibrations).
Limits: a construction enters its state for every sample checked; a family
whose features cannot enter a state fails its construction loudly. The builder
fragments are fixed shapes, so the generated skeleton shrinks around them rather
than through them.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
test(eslint-plugin-oracles): restore the oracle budget and check the …
d13fe87
…second lint

Three fixes to the part-reach laws, each with its RED first.

R4-4: the oracle-properties family ran 30 runs, but the original campaign
ran 200 (the campaign call in 471edd0). It is restored to 200. Every other
family's budget was compared with the original and matches: 400 for both
cycle families, 200 for layouts and namespace imports, 300 for drivers and
JavaScript companions, 30 elsewhere, and the laws' 4, 3000, BASE_RUNS, and 40.

R4-5: editCheck passed when the second lint's observation was missing, though
the model owed it. RED: a calibration that drops the `after` observation was
added first, and with the old check it failed ("the second lint is not
recorded" was not rejected), as the calibration requires. Fixed: a missing
observation differs from the owed reports and fails the check. The law's
arbitrary is again the dependency filter, so every drawn program has a
companion to edit: only programs with a dependency are drawn.

R4-6: statesOf recorded companion-edited-between-lints for any program with a
dependency, at e382e7b (part-reach-fixtures.js:1573), so a family that never
edits claimed the state. The state is removed from statesOf. The companion-edit
law records it through `reached(plan, trace)`, from the `after` observation,
so a state is claimed only when a second lint ran.

Law: the edit law is unchanged in its statement (a companion edited between
two lints gives the reports of a fresh process). The check now owes the second
lint whenever the plan has a dependency.
Reach: the edit state is recorded only from the trace. Gated states: 42, as
before, each with a construction.
RED: (a) the old editCheck, restored temporarily, fails the calibration "the
  second lint is not recorded" (1 of 1 failed); (b) e382e7b:1573 claims the
  edit state for any dependency; (c) the oracle-properties budget at 471edd0 is
  200, at e07674a it was 30.
GREEN: the new check rejects the dropped second lint (the calibration passes).
  vitest tests/part-reach: 146 of 146 pass; the whole plugin suite: 4285 of
  4285 (13 files). eslint --no-fix on the part-reach files: 0 problems.
Mutant: the driver that skips the edit (rewriteCompanion removed) is rejected
  on both the fixed and random companion-edit campaigns, with an `after`
  violation (2 failed).
Limits: the reach claim is taken from the trace the driver records; a driver
  that edits but records no `after` observation is caught by the check, not by
  the reach claim. The run-budget check is part of TanStack DB, not this
  repository.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
refactor(eslint-plugin-oracles): name the probe-form grammar as an or…
3545df3
…acle file

Q-8: probe-form-grammar.js holds the grammar, MODEL, and predictReports that
two oracles share. Its name had no `oracle`, so the oracle rules, the
run-budget check, and the recommended config's file glob never read it. It
is renamed probe-form-oracle-grammar.js, and its two importers follow. The
rules report nothing on it, so there is no suppression and no rule change.

Law: unchanged (the probe form, oracle/probe-form).
Reach: unchanged.
RED: the parent's name is outside the oracle-file glob, so the oracle rules
  never linted it (the glob check reads the basename).
GREEN: npx eslint --no-fix on the renamed grammar and both importers: 0
  problems. The probe-form oracle passes.
Mutant: n/a (rename only).
Limits: none beyond the existing ones.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
fix(eslint-plugin-oracles): refuse malformed and unjudgeable entries …
a33990a
…at once; cover @fast-check/vitest

Q-1: a malformed legacyCampaigns entry (not an object with a string file, or
a reason that is not a string) crashed every oracle file with a TypeError.
The rule now validates the settings when it is created and throws one clear
message per bad entry, so the run stops with that message.
Q-3: an entry the rule cannot judge (a file outside the oracle-file glob, or
an implementation module) was reported once per linted file. It is now a
configuration error, validated at creation, so it stops the run once rather
than repeating per file. Decision: these are configuration errors, not
lint reports.
Q-4: the law statement names the configuration errors and @fast-check/vitest.
Q-5: a declared state and a constructed history for the @fast-check/vitest
path. Dropping that source fails the constructed history deterministically.

Law: a generated-history campaign in an oracle file runs through oracleProperty,
whatever fast-check binding takes it; a baseline entry the rule cannot judge
stops the run before any file is judged (maintainer decision, 2026-11-09).
Reach: the vitest state (declared; constructed history "a campaign through
  @fast-check/vitest"), the refused state (constructed "an entry outside the
  oracle glob"), and the malformed state (constructed "a malformed entry").
RED: with the outcome model, the oracle failed 3 of 5 tests before the rule
  change (counterexample: outsideGlob entry, which the rule reported instead
  of refusing; and the calibration found no clean configuration history).
GREEN: 5 of 5 after the change.
Mutants:
  - no malformed check at all (the four conditions removed): 2 tests fail,
    counterexample a bare-string entry.
  - a single condition removed (the object check only): the file check still
    catches it, and all 5 pass. This partial mutant is not a kill, which is
    why the full mutant is the one recorded.
  - @fast-check/vitest dropped from the sources: 2 tests fail, counterexample
    alias vitest with a campaign through the alias.
  - implementation-module validation removed: 2 tests fail.
Limits: the configuration errors are thrown from rule creation; ESLint then
  stops the run. A throw at creation is not reported per file by the rule
  itself, so the once-per-run behaviour depends on ESLint stopping on a rule
  exception. A stale entry for a deleted or renamed file is not reported.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
docs: state that a deleted entry is not reported and that bad entries…
a570f7e
… stop the run

Q-9: an entry for a deleted or renamed oracle file is never reported, because
no lint run sees that file, and the rule cannot check existence soundly: a
settings path can be a suffix of the repository path, so a check from the
lint root would report entries that exist. The README and the plugin changeset
now say so, and the limit is in the rule's header and oracle.
Q-1/Q-3: a malformed or unjudgeable entry is a configuration error that stops
the run (ESLint reports "Error while loading rule" with the message; verified
with the ESLint API on a bare-string entry). The README and changeset describe
that instead of a per-file report.

Law: documentation of the behaviour in the previous commit.
Reach: n/a.
RED/GREEN: prettier --check on README and the changeset passes; the stop
  behaviour was checked directly with ESLint's lintFiles.
Mutant: n/a.
Limits: the rule sees one file, so it cannot stop a baseline from growing.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
fix(eslint-plugin-oracles): constructions gate their state's fields a…
2597200
…nd keep the rest of the input

Round-2 review Q-2 (maintainer direction: reach keeps randomness). The
constructions replaced whole fixtures: 31 of the 42 replaced the generated
registration, module, or companions with a constant, so the part that
enters a state did not vary with the input, and shrinking could not move it.

Each construction is now a list of writes to its gating fields (a dotted
path into the generated skeleton, and the value that gates the state). The
generated registrations, modules, and companions are kept, and a write that
creates a missing element uses its generated-shape default. The neutral
module fields and slots that a state does not use are gated too, as the
fixtures left them; the registrar call is gated when a construction imports
by index.

Law: a constructed history changes only its gated fields; outside them it
equals the generated skeleton (lint.part-reach.constructions, new). Each
state's `enters` is still checked by its family's constructed history.
Reach: distinct constructed outputs over 300 skeletons (fixed seed 2234),
per construction, the registration (oracle.tests[0]), the module, and the
companions. RED (before) -> GREEN (after), distinct counts; entering is the
number of samples in which the state is entered:
  export-star-cycle                  tests0 1 -> 261; module 1 -> 300; companions 65 -> 218; entering 300 -> 300
  named-reexport-cycle              tests0 1 -> 261; module 1 -> 300; companions 65 -> 183; entering 300 -> 300
  javascript-companion-import-reaches-part  tests0 1 -> 145; companions 126 -> 216; entering 185 -> 300
  mutual-recursion-reaches-no-part  tests0 1 -> 261; module 1 -> 293; entering 300 -> 300
  companion-edited-between-lints    tests0 1 -> 145; companions 65 -> 216; entering 0 -> 300 (dependencyOfReached; statesOf cannot see it)
  form-each, form-call-body, form-member-body, form-for, form-options,
  let-assigned-driver, named-constant-arbitrary, imported-predicate,
  predicate-ignores-input, oracle-named-helper, prefixed-oracle-helper,
  caller-passed-body-helper, caller-body-wrapper, oracle-property-*
                                     tests0 1 -> 261; entering 300 -> 300
  The other constructions: each changes a field that gates the state; every
  one has tests0, module, or companions varying in every sample (see the
  table in the pull request).
RED: the mutant that builds form-each by replacing the generated oracle.tests[0]
  with a constant fails the new law (fixed and random campaigns), so the law
  rejects the fixture style the RED table measures.
GREEN: part-reach tests pass (150 of 150, six files) with the new builders.
Mutant: the same mutant gives an empty gate (whole element), and the law's
  gate-shape observation (a gate that names a whole element) rejects it. The
  first form of the law let it through; the gate-shape observation was added
  after that mutant passed.
Limits: the law checks the non-gated fields; each construction's entering is
  checked by its family's constructed history. Measured over 300 skeletons
  under the ALL features; the families use their own features, which the
  family runs check.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews Isaac
fix(eslint-plugin-oracles): one source for each part-reach check; nam…
c70d2ad
…e the support module's status; fix JSDoc and dead imports

Round-2 review items Q-6, Q-7, Q-10.

Q-6: familyOptions no longer returns `check`: each oracle file passes
`check: checkPaths` at its call site, so the check is one visible source.
The rule reads the literal at the call site. (No change to no-unit-test.)

Q-7: the support module's header says its name contains `oracle` (it does)
and that it declares no oracleProperty and registers no test: its
properties are owned by the oracle files that import it.

Q-10: the lintedOnItsOwn JSDoc was attached to withForms; it now sits on
lintedOnItsOwn, and withForms keeps its own. The laws file's unused imports
setupReceivingPaths and teardownReceivingPaths are removed.

Law: unchanged behaviour. Each oracle file's check is its own call site.
Reach: unchanged (no state or construction changed here).
RED: the old familyOptions returned a check that no call site used, so the
  call sites' own `check: checkPaths` replaced it. Nothing failed; the
  duplicate was the defect (review Q-6).
GREEN: eslint --no-fix on the part-reach files: 0 problems; part-reach tests
  pass (150 of 150, six files).
Mutant: removing `check: checkPaths` from one call site makes that file's
  oracleProperty fail (no check): the rule no-unit-test reports the call.
Limits: checks are per-file call sites; a future call site with no check is
  reported by no-unit-test, not by this file.

Co-authored-by: Isaac <[email protected]>
@KyleAMathews
KyleAMathews merged commit e1fb504 into main Nov 10, 2026
8 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@KyleAMathews
KyleAMathews deleted the feat/lint-oracle-property-ratchet branch November 10, 2026 15:31
@KyleAMathews KyleAMathews mentioned this pull request Nov 10, 2026
fix(eslint-plugin-oracles): rules never throw, and oracle rules cannot be disabled inline #6
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@KyleAMathews
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.