Repository navigation
feat(oracle-gaps): every streamed line starts with oracle-gaps: <kind> - #46
Merged
Merged
Conversation
Every line the CLI writes for a check starts with `oracle-gaps: ` and its kind, on stdout and stderr, cold and through the daemon; one decisive baseline line comes before any mutant; and a done line ends every run (completed, refused, or stopped). The e2e, classify, and static oracles judge it, with four new e2e calibrations and one static one. RED on main (81a5451): classify `stdout`, "the stdout's line after the baseline is "", not the decisive ..."; static `report-shaped ending`, "the refusal's last line is not `oracle-gaps: done refused`"; e2e `config`, "the run named its config as [], owed ["oracle-gaps: config vitest.config.ts"]". Co-authored-by: Isaac <no-reply@databricks.com>
…alibration The calibration "the stdout summary omits a gap" read a gap line by its old indented form, so on the prefixed stdout it found none and could not run. It now reads `oracle-gaps: gap …`, as law 8 does. Co-authored-by: Isaac <no-reply@databricks.com>
Agents act on a check's results as they stream, so every line the CLI writes for a check, on stdout and stderr, cold and through the daemon, now starts with `oracle-gaps: ` and its kind: config, note, baseline, blocked, progress, gap, busy, or done. One decisive baseline line comes before any mutant runs (`baseline all <n> oracle files pass`, or `baseline <k> of <n> oracle files failed (provisional)`), and every run ends with a done line: `completed, <n> gaps[ (provisional)], report <path>`, `refused[, report <path>]`, `error, log <path>`, or `stopped (<signal>)`. The done line replaces the stdout's `Full report:`, `output:`, and `completed:` lines and the refusal's `output:` line. Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
runClisAtOnce ignored stdout, so law 15 read no done line for the two daemon checks that start at once and failed "a second daemon check of the budget witness" and "an edit sequence through the daemon" with "ends with undefined, not its done line". It now reads both streams, as runCli does. Co-authored-by: Isaac <no-reply@databricks.com>
…m's prefix A config that failed to load printed Vite's "failed to load config from …" on stderr without the stream's prefix, and a config with no default export also printed esbuild's bundling error for the wrapper's default import. Vite's logger now writes `oracle-gaps: note vite: …`, and the wrapper reads the config's default export at run time, so a config without one fails when it loads, as under Vitest, with no esbuild output. Law 15 found both (configThrows, configNoDefault). Co-authored-by: Isaac <no-reply@databricks.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
🦋 Changeset detectedLatest commit: 606e525 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
…nfig's forms The review of #46 (F1 to F5, F8) found what law 15 did not judge: - stdout's lines: the summary body and a test's console output were not lines of the stream. streamOf now judges stdout as it judges stderr, and file a has a test that writes to the process's stdout and stderr; a completed run names where that output went in one note; - every ending: the done line ends both streams, once each, of the kind the exit status says (0 completed, 2 refused, 3 error, else stopped); - a signal after the completed line (stopRuns `after`, with the CLI's exit held 500 ms): the run ends as it completed, with one done line; - a config refused through the daemon prints the cold run's notes; - the config's forms: a promise of a config (Vite awaits it), a null default, and a syntax error, each judged as Vitest judges it. The classify oracle's law 8 now reads the stdout summary as stream lines (summary, legend, function, note) and rejects any other line. New e2e calibrations: a raw line on stdout, two done lines, and a daemon refusal without the cold run's notes. Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
…onfig semantics The review of #46 (F1 to F5, F7 to F11), against law 15's extension: - F1: the stdout summary is lines of the stream: `summary` for the totals, `legend` for each directive, `function` for each heading, and `note` for the provisional count, unowned counts, files left out, and the results' path. No line is blank; summary.md holds the report to read. - F2: a test's own writes to the process's stdout and stderr go to test-output.log in the output directory, and one note names it. - F3: once the run writes its ending, a stop signal no longer cuts it off, and the supervisor writes the stopped line only when the run did not end with its own code (0, 2, or 3): one done line per stream. - F4: Vite's notes go through the check's log, which the daemon relays, so a refusal through the daemon prints the cold run's lines. - F5: the wrapper awaits the config's default, a promise included, and refuses one that is not an object with Vite's wording. - F7: a config that does not parse is refused with esbuild's first error, parsed silently before Vite bundles it. - F8: the done line ends stdout and stderr for every ending. - F9 to F11: the README says which endings print no done line, fixes the heartbeat line, and says that --help is not a check. Co-authored-by: Isaac <no-reply@databricks.com>
The calibration "the stdout's baseline comes after its gaps" split the stdout at its first blank line, which the stdout no longer has. It now moves every line before the `oracle-gaps: summary` line after the rest, the same wrong answer in the stdout's new form. Co-authored-by: Isaac <no-reply@databricks.com>
The full fixed seed failed "a declaration fault: setupThrows" with "the run names its tests' console output in 0 notes". There the setup file throws before any test runs, so file a's test that writes to the process's streams never runs, nothing is captured, and the law (a note names the log when output was captured) owes no note. The check owed one in every completed run; it now owes one exactly when file a's baseline loads, as the model's baseline says. Co-authored-by: Isaac <no-reply@databricks.com>
… 15's The calibration "a refusal through the daemon drops the cold run's notes" searched candidates for 433 s; it is now bound to the constructed history "a declaration fault: configThrows" (#31's history binding). The other new calibrations, measured at ab18c60 (58 to 79 s each), are recorded in e2e-shards.mjs, two of them moved to e2e-5 for balance. Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
commit: |
…es a stop during warm-up The re-review of #46: - N2: 2d09b99 owed the test-output note whenever file a's baseline loads, a rule the law ("a note names the log when output was captured") does not state. The driver now observes whether the run captured output, the note is owed exactly then, and a run whose file a test wrote must have captured it. A new dimension, consoleOutput, gives file a no writing test; "a load failure beside a hang" pins it off and enters "a completed run whose tests wrote nothing". A calibration names a log never captured. - N1: stop runs now have a `when`: while the witness spins, after the completed line, or while the config warms (`warm`, through the CLI's ORACLE_GAPS_WARM_PROBE hook). A stop during warm-up ends with one stopped line per stream, and is never reported as a refusal. Co-authored-by: Isaac <no-reply@databricks.com>
ORACLE_GAPS_WARM_PROBE names a marker: the first context's warm-up writes it and then waits, as a config load can, on a service in the run's process group that a stop signal ends (as it ends esbuild's), while a handler that exits 200 ms after the signal holds the process (as Vitest's does). Unset, it does nothing. It lets the oracle stop a run during warm-up on purpose. Co-authored-by: Isaac <no-reply@databricks.com>
…ists Co-authored-by: Isaac <no-reply@databricks.com>
…one line The re-review of #46: - N1: a SIGINT while the config warmed cut off the config's load; the run reported the error as a refusal, wrote `done refused`, and then died by the signal, so the supervisor wrote `done stopped` as well. The run now installs its own stop handler first: a stop ends it at once with no line, unless it is writing its ending. It tells the supervisor, over an IPC channel, that it writes its ending; the supervisor writes the stopped line only when no such message came, and decides at the run's `close`, after its messages. - N3: the supervisor installs its stop handlers before it starts the run. A signal that comes while Node still starts the CLI, before its first line runs, still ends it with no done line; the README says so. Co-authored-by: Isaac <no-reply@databricks.com>
The third review of #46: ORACLE_GAPS_WARM_PROBE is now ORACLE_GAPS_TEST_WARM_PROBE, and ORACLE_GAPS_HOLD_EXIT_MS, the other hook the CLI's oracle sets, ORACLE_GAPS_TEST_HOLD_EXIT_MS. Their comments say that they are test-only, and so does the README. Co-authored-by: Isaac <no-reply@databricks.com>
The coordinator's decision of 2026-11-12 on P10: when the run's process dies on its own, with no ending of its own and no stop from the CLI (a V8 out-of-memory abort is a SIGABRT), the CLI still writes the done line, on both streams: `oracle-gaps: done error, the run's process exited with <signal or code>`, and exits 3. Stop runs gain `when` `abort`: a test of file a aborts the process (`GAP_ABORT`, in worker threads), and the run is owed exactly that line. "every outcome class" enters the new state "a run whose process aborted". Co-authored-by: Isaac <no-reply@databricks.com>
process.abort() is unavailable in worker threads, so the test threw and the run completed; process.kill(process.pid, 'SIGABRT') ends the run's process, as a V8 out-of-memory abort does. Co-authored-by: Isaac <no-reply@databricks.com>
The coordinator's decision of 2026-11-12 on P10: when the run's process ends without its ending and without a stop from the CLI (a V8 out-of-memory abort is a SIGABRT), the CLI exited 128 plus the signal with no done line. It now writes `oracle-gaps: done error, the run's process exited with <signal or code>` on both streams and exits 3, with the remedy when the run's stderr, which the CLI now passes on, said that the heap ran out (NODE_OPTIONS=--max-old-space-size, or fewer --contexts). It kills what is left of the group at the run's exit and waits at most 2 s for the run's stderr to close. Co-authored-by: Isaac <no-reply@databricks.com>
The calibration "a refused run is reported as complete" set exit 0 and left the refused done line, so law 15 (the done line's kind matches the exit) rejected it before the law it targets, ownership. It now also gives both streams the completed done line, as a run that says it completed would print, so only the refusal law rejects it. Its owed law and law 15 are unchanged. Co-authored-by: Isaac <no-reply@databricks.com>
KyleAMathews
added a commit
that referenced
this pull request
Nov 13, 2026
Keeps #46's line protocol and law 15 with the work law: the extra-work gap kind in the streamed-gap pattern, the extra-work legend line under the new oracle-gaps: legend prefix, and the README's work law before the protocol section. Co-authored-by: Isaac <no-reply@databricks.com>
KyleAMathews
added a commit
that referenced
this pull request
Nov 13, 2026
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
added a commit
that referenced
this pull request
Nov 13, 2026
…tructed failing owner, and a copy's random stream (#54) * test(oracle-gaps): record a stopped run's session directories by identity, 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> * test(oracle-gaps): construct the failing owner's ownership, not borrow 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> * test(oracle-gaps): a mutant's copy draws from Math.random what its original 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> * fix(oracle-gaps): seed a copy's ambient randomness by its original test 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> * docs(changeset): a mutant's copy draws what its original draws Co-authored-by: Isaac <no-reply@databricks.com> * test(oracle-gaps): name the scratch directory in the stdout lead 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> * test(oracle-gaps): observe a stop's session directories as counts, not 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> --------- Co-authored-by: Isaac <no-reply@databricks.com>
KyleAMathews
added a commit
that referenced
this pull request
Nov 13, 2026
…its one done line (#46) Co-authored-by: Isaac <no-reply@databricks.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Agents act on a gap check's results as they stream. With this PR, every line that the
oracle-gapsCLI writes for a check, on stdout and stderr, starts withoracle-gaps:and then its kind. Onegrep '^oracle-gaps: 'finds all of them, and the word after the prefix filters one kind. This is the maintainer's request of 2026-11-12, relayed by the coordinator. The coordinator also decided:--max-gaps: an agent stops a runaway run itself with SIGINT or SIGTERM, which stops cleanly since fix(oracle-gaps): a per-mutant budget, a heartbeat, signal-safe workers, and no copies beside the tests #40. A fixed cap would cut off a first run with 70 to 130 real gaps.The line formats:
configoracle-gaps: config vitest.config.ts, ororacle-gaps: config none, Vitest's default discoverybaselineoracle-gaps: baseline pass tests/a.test.ts: 3 tests,oracle-gaps: baseline fail tests/b.test.ts: 1 of 2 tests failedbaseline(decisive)oracle-gaps: baseline all 4 oracle files pass, ororacle-gaps: baseline 1 of 4 oracle files failed (provisional)blockedoracle-gaps: blocked <file> fails with no mutant active at <test>, so the mutants it owns wait for it: …gaporacle-gaps: gap <kind> L<line> <id> <change>progressoracle-gaps: progress judged 25 of 120 mutants: 7 gaps so far, 18 not gaps, and theunmutated:andmutant phase:timingsbusyoracle-gaps: busy <mutant id or baseline> in <test file> for <n>ssummaryoracle-gaps: summary <run id>: 120 mutants, 98 killed, 12 gaps, 10 unowned (warnings). …legendoracle-gaps: legend <class>: <what it means and what to do>, once for each class of gap the run foundfunctionoracle-gaps: function fn <name> (<source>) (owners: <files>), the heading over that function's gap linesnoteoracle-gaps: note …: the--hang-limitnote, Vite's own messages (note vite: …), ordinary files left out, the provisional count, unowned counts, the results' path, and where the tests' console output went (note test output in <path>)done(last)oracle-gaps: done completed, 12 gaps, report <path>/summary.md, with(provisional)after the count when an oracle file failed. Alsooracle-gaps: done refused[, report <path>],oracle-gaps: done error, log <path>, andoracle-gaps: done stopped (SIGINT)failing:,law:,counterexample:,replay:), and a refusal'srefused:andfix:block on stderr. No line is blank.summary,legend,function,gap,note). I chose kinds over leaving the summary out of stdout, so that the terminal still shows each gap class's directive and each function's owners.summary.mdholds the report for reading.test-output.login the output directory, and one note names that log.--helpis not a check, so its usage text has no prefix.Fixes from the review (F1 to F11)
note vite:line. Vite's notes now go through the check's log, which the daemon relays.--helpis not a check.Fixes from the second review (N1 to N3)
done refused, and died by the signal, so the supervisor wrotedone stoppedtoo. Now the run installs its own stop handler first: a stop ends it at once, with no line, unless it is writing its ending. Before it writes its ending, it says so to the supervisor over an IPC channel. The supervisor writes the stopped line only when no such message came, and decides at the run'sclose.consoleOutput, gives file a no writing test.ORACLE_GAPS_TEST_WARM_PROBElets the oracle stop a run while its config warms. When set, the first warm-up waits on a service in the run's process group that a stop ends, as it ends esbuild's service. Meanwhile a handler that exits 200 ms after the signal holds the process, as Vitest's does. The hook does nothing when unset.P10: a run whose process dies on its own
When the run's process ended without its ending and without a stop from the CLI, the CLI exited 128 plus the signal and wrote no done line. A V8 out-of-memory abort is a SIGABRT, so it gave exit 134 and no line. The CLI now writes
oracle-gaps: done error, the run's process exited with <signal or code>on both streams and exits 3 (the coordinator's decision of 2026-11-12). It passes the run's stderr on, and when that stderr says the heap ran out, the line adds the remedy: give Node more memory (NODE_OPTIONS=--max-old-space-size=<MB>) or run fewer--contexts. The CLI's test-only hooks are nowORACLE_GAPS_TEST_WARM_PROBEandORACLE_GAPS_TEST_HOLD_EXIT_MS, as the third review asked.Evidence
Law: law 15 of the end-to-end gap oracle, "the stream's lines are greppable":
The classify oracle's law 8 and the static oracle's refusal law check the same formats from their own boundaries. Authority: the maintainer's request and the coordinator's decisions of 2026-11-12, above.
Reach:
a stop signal after the completed line,a config refusal through the daemon,refused configNull, andrefused configSyntaxpromiseblocked by brokenOwner, eachrefused <fault>, the stop states, and law 9's daemon states.File a of every fixture has a test that writes to the process's stdout and stderr.
RED: on 8260bda (the reviewed head), with the extended oracles (86d391e):
stdoutreport-shaped endinggreppable streamgreppable streamconfiggreppable stream✘ [ERROR]lineOn
main(81a5451), the first version of law 15 was RED in the same three oracles.GREEN: on this branch, merged with
mainat cc7bf09:tsc --noEmit, prettier,e2e-shards.mjs check, andpr-shards.mjs checkMutant: I put each hostile mutant into this branch, and the oracle rejected each one under the intended law:
gapLinewithout the prefix: classifystdout, "0 gap lines for 1 gaps".report-shaped ending.stdout.nostopline): e2egreppable streamat checkpointstopped.lateverdict): e2egreppable streamatcli.stoppedalways, F3): e2egreppable streamatstopped, "stderr has 2 done lines and ends with "oracle-gaps: done stopped (SIGINT)"".vitenotes, F4): e2egreppable streamatincremental, "the refusal through the daemon at start prints the notes […], and the cold run […]".Third round:
greppable stream, "the run stopped while its config warmed reported a refusal".greppable stream, "the run names its tests' console output in 1 notes, owed 0 (captured: false)".a stop signal while the config warms, anda completed run whose tests wrote nothing. New calibration: a run names a test-output log it never captured, bound to the history "a load failure beside a hang".P10:
greppable stream, "the run sent SIGINT, workers as threads (abort): stderr has 0 done lines". The new stop timingaborthas a test of file a send its process SIGABRT, in worker threads.128+sigpath,greppable stream, "stderr has 0 done lines".Calibrations:
Model fixes during the review work:
summaryline.Limits:
oracle-gaps: done error, log <path>. No legal history reaches one, and its own message lines (oracle-gaps internal error: …, and a Node message's extra lines) have no prefix (F6).ORACLE_GAPS_TEST_WARM_PROBEhook, which models the esbuild service and Vitest's handler. The real race was seen once in 40 tries.<package>/.oracle-gaps-<pid>stays in the package, as fix(oracle-gaps): keep the daemon's state, bundles, and default output in the OS temp directory #43 decided: its wrapper configs import the package's config and must resolve the package's modules.Code weight
The production changes are modest:
streamLine(kind, text)helper and averdictLineinstream.mjscheck.mjsreport.mjsending.mjs, and the supervisor's one-done-line rule inindex.mjscapture-setup.mjsThe usage text, the README, and the pending changesets name the new lines.
This pull request and its description were written by Isaac.