Skip to content

feat(oracles): module-level data is ownable; an owned function that reads it owns it - #21

Merged
KyleAMathews merged 6 commits into
mainfrom
overnight/a-f10
Nov 11, 2026
Merged

KyleAMathews merged 6 commits into
mainfrom
overnight/a-f10

Conversation

@KyleAMathews

Copy link
Copy Markdown
Contributor

Summary

A mutant inside module-level data had no owner. An ownership unit was a function only, so the report classed such a mutant unowned and never judged it. Two field reports show the gap: the table that parse reads in vercel/ms (31 field-drop mutants), and DEFAULT_DELIMITER in path-to-regexp.

This change makes module-level data an ownership unit:

  1. A module-level non-function declaration in a variable statement is its own unit, named by its variable (src/x.ts#UNIT_MS).
  2. An owned function that reads it brings it into that function's reference tree. The initializer's own references join too, for example devBuild in [devBuild].
  3. A function nested in the initializer, such as an object method, is a unit that only a reference reaches. Reading T.a() owns a, not the other methods of T.
  4. A whole-file entry owns the declaration.
  5. An owns entry may name the declaration directly.

Decisions for the maintainer

  • An owns entry may name a data unit (src/x.ts#TABLE), as it names a function. The refusal message now says so. A refusal would reject a name that the report prints in keys.
  • Reading a data unit brings only its own sites. api.run() on const api = { run() {}, other() {} } brings api and run, not other.
  • A data mutant's key changes from src/x.ts#<module>:... to src/x.ts#NAME:..., so saved keys move once. Before, these mutants had no owner unless a whole-file entry owned them.
  • Side effect: the function in const h = memo(() => …) now belongs to unit h, not <module>. The grammar does not generate this form.

Review round

The review of head 8d5187e found three problems. This PR corrects all three.

  • Containment versus reference: data owned every function that its text holds. Now a nested function is a separate unit that only a reference reaches.
  • A stray unit: a top-level for (let i …) loop variable became a data unit. Now its code stays in <module>.
  • Wording: the text said data mutants were always unowned before this change. A whole-file entry did own them, and the text now says so.

Changes

  • owners.mjs: functionsIn lists module-level data as units, and referencesOf reads a unit's initializer as a function body. The production change is 20 lines.
  • analysis.mjs: the bad-entry refusal names module-level variables too.
  • gap-report-model-oracle.mjs: a grammar dimension for module data, with a readData statement and a dataOwners field. The model gives each declaration its sites and owners, and treats data sites as code that runs at load. The model's refusal and both drivers share ownsNothing.
  • The static and end-to-end property tests add reach states, and laws 1 and 3 name data units.

Model corrections

The new grammar changes the generated histories. On its first run, the pinned seed of the end-to-end oracle found two reachable counterexamples. Commits 8cb57a0 and 8d5187e correct them. Both were older model defects, not production defects. Authority: law 4's class list and its unreached rule.

  • A probe in another shape (nearProbe) has a logical &&. Law 4 classes it not-distinguished, but the model classed it not-observed (src/sut.ts:f0:34:binary-swap#0).
  • Under the CLI, the hook's condition argument and the guard's false never run. Law 4 classes them unreached, but the model reached them (src/sut.ts:f0:34:binary-swap#2).

Merge note

The merge with main had one conflict, in the model's refusal(). The resolution keeps withheld: declareOracle from #17 and ownsNothing from this PR.

Evidence

  • Law: an owns entry owns its static reference tree (owners.mjs, maintainer decisions of 2026-11-05 and 2026-11-08). This change applies the law to module-level data. Authority for the extension: the coordinator's F10 brief, from the field reports.
  • Reach: the static oracle uses seed 20261105, with 40 runs. Constructed histories enter each data state: each shape through its reader, mixed readers, unowned readers only, a named entry, and a whole-file entry. They also enter the method states and the top-level loop. The end-to-end oracle enters its required state, "a mutant of module data killed through its reader".
  • RED (review): on 8d5187e, an object of two methods, with only used read, fails at law ownership: src/sut.ts:spare:31:arithmetic-swap#1: owned, owed unowned.
  • RED: on the parent, npx vitest --run --config oracle-gaps/vitest.config.mjs gap-report-static-oracle fails at law enumeration. The CLI names src/sut.ts:<module>:30:field-drop#0, but the model owes src/sut.ts:D_OBJ:30:field-drop#0.
  • GREEN: the static oracle passes 16 tests. The validity, classification, and probe-form oracles pass 31249 tests. The unit tests pass 55 tests. The end-to-end oracle passes its pinned seed in 203 s. Its first two runs failed on the two model defects, and those runs are their RED.
  • Mutant: each mutant fails at the named law.
    1. Containment comes back: law ownership, src/sut.ts:spare:31:arithmetic-swap#1: owned, owed unowned.
    2. A top-level loop variable is a unit again: law enumeration.
    3. A reader does not bring a data unit: law ownership, src/sut.ts:D_OBJ:30:field-drop#0: unowned, owed owned.
    4. The analysis does not read a unit's initializer: law ownership, src/sut.ts:devBuild:4:binary-swap#0: unowned, owed owned.
  • Limits: the grammar has four const shapes, read in place, and one top-level for loop. It does not generate these forms:
    • destructured declarations, which stay <module>
    • const h = memo(() => …), which is now unit h
    • imported data, reassigned let data, enums, export default objects, and module.exports = { ... }
    • a method of module data that refers to something
    • a data unit read through a shorthand property, which resolves to the property

This pull request and its description were written by Isaac.

KyleAMathews and others added 5 commits November 10, 2026 23:18
…ariable (F10)

A top-level variable whose initializer is not a function is its own unit:
a function that reads it brings it, and what its initializer refers to,
into its reference tree; an owns entry may name it; a whole-file entry
owns it. Its mutants were always unowned before.

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

Law 4 classes a surviving logical-operator mutant as not-distinguished.
The model applied that only to the old probe form's &&, so a probe in
another shape (the former form, or a guard of another name, let, or
initializer) was owed not-observed. The F10 grammar's fixed seed reached
it: src/sut.ts:f0:34:binary-swap#0: not-distinguished, owed not-observed.

Co-authored-by: Isaac <no-reply@databricks.com>
No hook is installed under the gap CLI, so `?.()` never evaluates the
condition it is passed, and `process` is defined, so a guard's `false`
is never read. The model reached both. The F10 grammar's fixed seed
reached it: src/sut.ts:f0:34:binary-swap#2: unreached, owed not-observed.

Co-authored-by: Isaac <no-reply@databricks.com>
…ement (F10 review)

A function that module data's initializer holds, such as an object
literal's method, is a unit of its own, owned where something refers to
it: reading one member no longer owns every function inside the object,
and the initializer's references are read outside the functions it
holds. A top-level for loop's variable is no unit. (The F10 commit said
data mutants were always unowned before: a whole-file entry owned them.)

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

# Conflicts:
#	packages/oracles/oracle-gaps/gap-report-model-oracle.mjs
@changeset-bot

changeset-bot Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: db92693

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 no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

@coderabbitai

coderabbitai Bot commented Nov 11, 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: b34b0640-d14c-4eac-8ac9-e0e34a240757
  • 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 11, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

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

commit: db92693

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

# Conflicts:
#	packages/oracles/oracle-gaps/gap-report-model-oracle.mjs
@KyleAMathews
KyleAMathews merged commit d65d70c into main Nov 11, 2026
8 checks passed
@KyleAMathews
KyleAMathews deleted the overnight/a-f10 branch November 11, 2026 09:28
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