Skip to content

test: nightly parser fuzz lane — parser input fails as typed AppErrors, never hangs (#1414) - #1438

Merged
thymikee merged 12 commits into
mainfrom
devin/1785159046-parser-fuzz-lane
Jul 28, 2026
Merged

test: nightly parser fuzz lane — parser input fails as typed AppErrors, never hangs (#1414)#1438
thymikee merged 12 commits into
mainfrom
devin/1785159046-parser-fuzz-lane

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #1414 (Track A of #1412). Parsers are the front door for agent-authored input; nothing asserted they fail well. This adds a fuzz lane over the five parser entry points and enforces exactly one invariant:

checkCase(target, input):
  try { target.run(input) } catch (e) {
    e instanceof AppError                       || FAIL untyped-throw
    normalizeError(e).hint is non-empty         || FAIL empty-hint
  }
  case must terminate within the budget         || FAIL hang   (watchdog, see below)

Targets (scripts/fuzz/targets.ts): parseArgs (strict flags), parseSelectorChain, parseReplayScriptDetailed, readCliBatchStepsJson, parseMaestroProgram. Adding a parser to the lane is one entry in that array.

It already caught a real bug. readQuotedReplayToken scans for the closing quote and then hands the literal to JSON.parse, so a .ad line like fill @e1 --text "hello wor\ld" (invalid escape, raw tab, or \^@) escaped as a bare SyntaxError. Now a typed INVALID_ARGS with a hint naming the escaping rule; three of those inputs are pinned in the corpus and in src/replay/__tests__/script.test.ts.

Cases come from fast-check, sharing #1437's hazard vocabulary. scripts/fuzz/arbitraries.ts builds each target's inputs from SELECTOR_VALUE_HAZARDS (exported from src/__tests__/test-utils/property-arbitraries.ts) plus a fuzz-only rejection list, over structured bases borrowed from selectorChainArb / replayScriptArb / fc.json() — so a hazard added for the property suite reaches the fuzz lane too. fast-check owns the loop, which buys shrinking: an injected throw new Error in parseSelectorChain reports "\u202evisible=false" (14 chars) plus --seed 1 (path 30:0:0:0:0:1), not the 20k-char string that first tripped it.

Hang detection is external, because it has to be. No in-process timer can interrupt a synchronous parser loop, so cases run in a worker thread over a one-case request/response protocol; when a case outlives --case-timeout-ms the parent terminates the wedged thread, reports the exact input, and starts a fresh worker so one hang can't cascade into every later case. The worker posts ready first, so startup (thread spawn, type stripping, parser imports) is never charged to a case budget.

The harness is tested against itself. scripts/fuzz/self-check-targets.ts holds three broken-on-purpose targets (bare Error, AppError with a blank hint, for(;;){}) that only registry.ts resolves, so a normal run never sees them. pnpm fuzz:parsers --self-check (nightly step) and scripts/fuzz/harness.test.ts require each failure kind to be reported — otherwise a regressed classifier or watchdog would leave every test green.

Envelope, promotion, lanes.

  • Every run — pass or fail — writes <artifact-dir>/run-envelope.json on obs: scheduled-lane health, freshness telemetry, and standard artifact envelope #1430's shared contract (scripts/lib/lane-envelope.ts, now on main and imported here); the nightly uploads it with if: always() and echoes it into the step summary, so a green scheduled run is auditable. scripts/fuzz/envelope.ts is only the mapping: lane facts under data (mode, per-target cases/failures/durations, failures, repro commands) plus data.stage: 'error' for a run that could not complete itself, since the shared result is pass | fail. configHash hashes everything that decides what a seed generates (arbitraries, generate.ts, targets, invariant, and test: property-based testing foundation (fast-check) over parse/print and geometry kernels #1437's shared hazards) and tool records fast-check's installed version, so a generator edit or dependency upgrade cannot change the case set while the provenance looks unchanged.
  • A nightly discovery reaches the unit lane by promotion, not hand-editing: each failure prints repro: and promote: … --append-corpus, and --input-file --append-corpus appends the downloaded artifact to scripts/fuzz/corpus/regressions.json (sorted, deduped).
  • scripts/fuzz/corpus-replay.test.ts replays the whole corpus plus every target's seeds through that same worker watchdog (runCases(target, cases, 5_000)), so a promoted hang case fails against its per-case budget instead of wedging the unit job. It and harness.test.ts live in the serialized subprocess-stub project; the corpus file runs in ~1.5s.
  • The nightly job Parser Fuzz Lane rides replays-nightly.yml (device-free, ubuntu, 50k cases/target, seeded by github.run_number).

Verification, locally:

  • 20,000 cases × 5 targets in 15s (measured 29s at 50k, well inside the 20-minute job); --self-check and the fuzz tests stable across repeat runs.
  • Deliberate throw new Error(...) / for(;;){} injected into a parseSelectorChain branch are reported as untyped-throw / hang, shrunk, with working repro commands; the same injection makes the corpus replay test fail in 5.00s rather than hang (all reverted).
  • pnpm format:check, pnpm lint, pnpm typecheck, pnpm check:fallow --base origin/main, and the affected unit projects green.

Link to Devin session: https://app.devin.ai/sessions/45070ada3381444c89ac3bb95b79a611
Requested by: @thymikee

@thymikee thymikee self-assigned this Jul 27, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.87 MB 1.87 MB +226 B
JS gzip 598.6 kB 598.7 kB +87 B
npm tarball 714.2 kB 714.3 kB +138 B
npm unpacked 2.50 MB 2.50 MB +327 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.3 ms 26.0 ms -0.2 ms
CLI --help 56.5 ms 56.5 ms +0.0 ms

Top changed chunks: no changes in the largest emitted chunks.

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-07-28 09:29 UTC

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Runtime verification — parser fuzz lane ✅

Tested end-to-end from the shell on this branch (Node 22, no UI).

Failure detection actually works (the key claim)

Temporarily injected faults into parseSelectorChain and reverted each:

throw new Error('x')      -> [selector] untyped-throw: Error: x (at parseSelectorChain ...:133:33)
                             repro:  pnpm fuzz:parsers --input-file .tmp/fuzz/selector-untyped-throw-0.json   exit 1
for(;;){}                 -> [selector] hang: case 2 did not finish within 1000ms   exit 1, 3s wall (no wedge)
AppError(hint: '   ')     -> [selector] empty-hint: AppError INVALID_ARGS has no hint: injected   exit 1

The printed repro: command reports Case still violates the invariant (selector). while injected and Case passes the invariant now (selector). after revert.

Green runs, determinism, scale

pnpm fuzz:parsers → 5 targets × 2000 cases, 0 failures, exit 0, 4.7s. All 5 --target runs green.
Determinism checked by hashing generateCases output (a green run prints no case data): seed 7 twice → d3da022ade36bdc1; seed 8 → e598d765af0d19cd.
--iterations 20000 seeds 2/3 → exit 0 in 20.4s / 19.3s, so 50k/target ≈ 50s, inside the 20-min job timeout.

Corpus, replay lane, workflow

--append-corpus grew the corpus 8→11 sorted/deduped entries; second run appended 0. --replay-corpus green. vitest run --project unit-core scripts/fuzz → 17/17 passed, 38ms test time. Workflow YAML parses; Parser Fuzz Lane job command matches the package script; running the exact CI command shape locally exits 0 (actionlint not installed on the test machine).

Replay-script quoted-token fix (before/after)
before (origin/main): 'fill @e1 --text "hello wor\ld"' -> AppError: false, code UNKNOWN (leaked SyntaxError)
after  (this branch): -> AppError: true, code INVALID_ARGS, hint "Quoted replay values are JSON strings: ..."
same for raw tab and raw NUL; 'hello world' still parses normally

Repo tree clean (git status --porcelain empty) after all injections were reverted. Unrelated: the Coverage job fails here and on origin/main head alike (ios-system-ui help-topic bench coverage).

@devin-ai-integration
devin-ai-integration Bot force-pushed the devin/1785159046-parser-fuzz-lane branch from d70a045 to cf3c5d0 Compare July 27, 2026 14:14
@thymikee

Copy link
Copy Markdown
Member

Review: changes requested

This is directionally solid—the target registry, deterministic mutator, worker cursor, and replay-script regression are good foundations—but it is not ready for the #1412/#1414 contract yet.

Readiness evidence is also incomplete: GitHub has no workflow_dispatch run for this head, so the new scheduled job has not executed at d70a0458…. The Coverage check is currently red on the inherited ios-system-ui help-benchmark gap. That gap is outside this diff, but the PR cannot receive a clean readiness verdict while it remains red.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@thymikee addressed all three in 5a382f6.

[P1] Scheduled-lane envelope. #1430 is not merged, so there is nothing to consume yet — I implemented the equivalent here rather than blocking. scripts/fuzz/envelope.ts writes <artifact-dir>/run-envelope.json on every run, pass or fail:

{ "schemaVersion": 1, "lane": "parser-fuzz", "result": "pass",
  "startedAt": "", "finishedAt": "", "durationMs": 2229,
  "provenance": { "commitSha": …, "ref": …, "workflowRunId": …, "workflowRunAttempt": …,
                  "nodeVersion": "v22.14.0", "tool": "scripts/fuzz/run.ts", "corpusEntries": 8 },
  "config": { "mode": "generate", "seed": 1, "iterations": 300, "caseTimeoutMs": 2000, "targets": [...] },
  "targetRuns": [ { "target": "cli-args", "cases": 300, "failures": 0, "durationMs": 490 }, ... ],
  "failures": [ /* incl. artifact path */ ], "reproCommands": [ ... ] }

The upload step is now if: always() with a run-scoped artifact name, and the envelope is echoed into $GITHUB_STEP_SUMMARY, so a green scheduled run is auditable rather than silent. When #1430 lands, this module becomes the mapping onto its shared writer — the flagged monitoring integration then has a fixed input instead of a second definition. If you'd rather this PR wait and rebase onto #1430's writer instead, say so and I'll hold it.

[P1] Artifact → corpus promotion. Correct — the replay path returned before promotion could run. --input-file --append-corpus now promotes the downloaded artifact, and every failure prints both commands:

repro:    pnpm fuzz:parsers --input-file .tmp/fuzz/selector-untyped-throw-0.json
promote:  pnpm fuzz:parsers --input-file .tmp/fuzz/selector-untyped-throw-0.json --append-corpus

Tested end-to-end in scripts/fuzz/harness.test.ts against a scratch corpus (AGENT_DEVICE_FUZZ_CORPUS): the first promote appends one sorted entry and exits 1, the second appends nothing (Regression corpus already contains every failing case.). No hand-editing, and no seam in src/.

[P2] Tests of the invariant/watchdog itself. Added scripts/fuzz/self-check-targets.ts — three broken-on-purpose targets (bare Error, AppError with a blank hint, for(;;){}) resolvable only through scripts/fuzz/registry.ts, so a normal run never sees them. harness.test.ts (in unit-core) asserts the classifier on the first two and runs --self-check as a child process for all three, i.e. through the real worker + watchdog path; the nightly job runs pnpm fuzz:parsers --self-check before fuzzing:

ok   self-check-untyped-throw: expected untyped-throw, got untyped-throw
ok   self-check-empty-hint: expected empty-hint, got empty-hint
ok   self-check-hang: expected hang, got hang

Writing that test found a real watchdog bug: worker startup (type stripping + parser imports) counted against the per-case budget, so under load a fast untyped throw could be misreported as a hang. The worker now posts ready before the first case and the watchdog starts counting there. 3× repeat runs of the fuzz tests are stable.

On the readiness evidence: a workflow_dispatch on this head runs the whole Replay Nightly workflow (Android/iOS/macOS device suites, ~80 min each) because the fuzz job rides it. Happy to trigger it if you want that evidence — I did not want to burn the device runners unasked. The ios-system-ui Coverage gap is gone after the rebase onto origin/main (e8b779c), so that check should be green here now.

Locally green: pnpm format:check, pnpm lint, pnpm typecheck, pnpm check:fallow --base origin/main, pnpm check:layering, pnpm test:unit (518 files / 4578 tests).

@thymikee

Copy link
Copy Markdown
Member

Re-review at head 5a382f6: artifact-to-corpus promotion and the tests-of-tests are fixed, but three P1 blockers remain. First, issue 1430 makes the shared standard artifact envelope and scheduled health/freshness integration a birth requirement for this new lane; the PR still uses a lane-specific envelope with no shared monitoring integration. Second, if the self-check fails, the fuzz step never runs, so no envelope is written; the always-run upload warns and the summary then fails while trying to read the missing file. Every terminal path needs a valid envelope and summary. Third, the watchdog interval starts before the worker reports ready, so slow worker startup can still be misclassified as a hung parser case. Start case timing only after ready and add a delayed-startup regression test. All PR checks are green, but no Replay Nightly run exists for this head, so the scheduled route itself is unvalidated.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

@thymikee all three addressed in 0f4d99a.

1. Shared envelope (#1430). #1430 has no branch or PR yet, so instead of a second lane-specific shape I extracted the standard writer to scripts/scheduled-lane/envelope.ts — lane-agnostic: schemaVersion, lane, result (pass/fail/error), startedAt/finishedAt/durationMs, provenance (commit SHA, ref, workflow, run id/number/attempt, node version, tool), config, and a lane-specific details payload. The fuzz lane is now a thin mapping onto it (scripts/fuzz/envelope.ts), so #1430 either adopts this module as-is or changes one file for all lanes. The health/freshness job itself is #1430's deliverable and needs API scope I shouldn't add here; happy to hold this PR behind #1430 if you'd rather the lane be born against the real writer.

2. Every terminal path has an envelope. Self-check and fuzz now write to separate artifact subdirs (.tmp/fuzz/self-check, .tmp/fuzz/run), the fuzz step is if: always() so a failing self-check no longer suppresses the fuzz envelope, --self-check and --input-file replay both write envelopes (details.mode), and a crash inside the harness writes result: "error". The summary step loops over whatever envelopes exist and prints an explicit "no run envelope was produced" line instead of failing on a missing cat target. Three new tests assert an envelope for a passing run, a failing run, and a self-check run.

3. Watchdog arms only after ready. The per-case interval is no longer created up front — armCaseWatchdog() runs on the ready message. Startup gets its own separate 60s budget so a wedged import still can't hang the lane, but it can never be charged to a case. Regression test: AGENT_DEVICE_FUZZ_STARTUP_DELAY_MS=1200 with --case-timeout-ms 300 must still report untyped-throw and never hang.

Local: format:check, typecheck, lint, check:fallow --base origin/main (no issues in 25 changed files), fuzz unit lane 25/25.

On the unvalidated scheduled route: workflow_dispatch on Replay Nightly also fires the ~80-min Android/iOS/macOS device jobs, so I still haven't triggered it unasked — say the word and I will, or I can split the fuzz lane into its own workflow file if you'd prefer it dispatchable in isolation.

@thymikee

Copy link
Copy Markdown
Member

Re-review at head 0f4d99a: the artifact promotion, tests-of-tests, self-check failure handling, and worker-ready watchdog issues are fixed. Two blockers remain. First, umbrella issue 1412 explicitly requires issue 1430 health/freshness monitoring to land before or with this scheduled lane; this PR adds an envelope producer but no derived scheduled-lane health consumer, dark-lane alerting, or freshness exposure, and no Replay Nightly run exists for this head. Second, malformed workflow-dispatch options throw in readFuzzOptions before run() enters its envelope-writing catch, so the claim that every config/crash terminal path produces an envelope is still false. Move option parsing under the guarded outcome path and cover malformed input. Ordinary PR checks are green, but the scheduled route is not yet ship-ready.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Both addressed in dd700b6.

1. Envelope on the option-parse path. Option parsing now happens inside the guarded path: main() catches a readFuzzOptions throw and writes an error envelope using fallbackFuzzOptions(argv), which recovers --artifact-dir positionally so the envelope still lands where monitoring looks even when another flag is what blew up. Covered by two tests: --iterations lots (rejected value) and --not-a-flag (unknown option) both exit 1 and leave result: "error" behind.

2. Health/freshness consumer (#1430). #1430 still has no branch, so rather than ship the lane without its watcher I built the consumer here:

  • scripts/scheduled-lane/discover.ts derives lanes from every schedule:-triggered workflow in .github/workflows/ — no hand-maintained list, so a new lane cannot opt out of being watched.
  • scripts/scheduled-lane/health-model.ts classifies each lane from recorded run fields only (conclusion + timestamp + declared cadence, never log text): healthy, failing (two consecutive failed cadences), dark (no run within two cadences), pending (no history to judge — e.g. a workflow not yet on the default branch, which must not be mistaken for darkness). Unit-tested per category, including the "one red night is not an outage" boundary.
  • scripts/scheduled-lane/health.ts (pnpm lanes:health) writes lane-health.json with the freshness fields (hoursSinceLastRun, hoursSinceLastSuccess, consecutiveFailures, state, lastRunUrl) for obs: repo-health snapshot command aggregating existing analyzers into one JSON #1423's snapshot, prints the table to the step summary, and opens/pings one alert issue for dark or failing lanes. Runs as Scheduled Lane Health daily; --dry-run (and the no-token path) never touches issues.

Run against the live API just now, which is itself the dark-lane evidence you asked for — it immediately caught a real outage rather than a synthetic one:

| Lane                           | Workflow                        | State   | Cadence   | Since last success | Why                                        |
| Conformance Differential       | conformance-differential.yml    | healthy | every 24h | 10.1h              | ran within cadence                         |
| Conformance Regenerate Verify  | conformance-regenerate.yml      | healthy | every 24h | 9.1h               | ran within cadence                         |
| Perf Nightly                   | perf-nightly.yml                | healthy | every 24h | 11.1h              | ran within cadence                         |
| Replay Nightly                 | replays-nightly.yml             | failing | every 24h | never              | 10 consecutive scheduled runs failed       |
| Scheduled Lane Health          | scheduled-lane-health.yml       | pending | every 24h | never              | workflow not on the default branch yet     |

Replay Nightly has failed its last 10 scheduled runs — that is the device-suite lane the fuzz job rides on, and nothing was alerting on it before this.

Blocked on you for the scheduled-route run. I tried to dispatch it and my token can't: POST .../actions/workflows/253723110/dispatches → 403 Resource not accessible by integration. Please either run gh workflow run replays-nightly.yml --ref devin/1785159046-parser-fuzz-lane -f fuzz-iterations=5000, or say the word and I'll move the fuzz job into its own parser-fuzz-nightly.yml (schedule + dispatch) so it's dispatchable without dragging the ~80-min device jobs — which are red anyway, per the table above, so a whole-workflow dispatch will not come back green regardless of the fuzz job.

Local gates: format:check, typecheck, lint, check:fallow --base origin/main (no issues in 31 changed files), check:layering, and the scripts unit lane (37 tests) all green.

@thymikee

Copy link
Copy Markdown
Member

Re-review at head dd700b6: malformed fuzz options are now guarded and tested, but the new scheduled-health path has three P1 blockers. First, scheduled-lane-health.yml omits actions: read while calling the Actions workflow-runs API; with an explicit permissions map the omitted scope is none, so collection will 403. Second, health.ts has no top-level terminal error path, so an API failure exits before writing its snapshot or own required error envelope. Third, a readable workflow with zero scheduled runs is marked dark immediately, contradicting the two-cadence grace and falsely alerting for newly born lanes before their first due run. Add the permission, make terminal failures emit an error envelope, and model first-due/two-cadence grace with dispatched-route evidence. Ordinary PR checks are green, but neither scheduled route has run on this head.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Re-review at dd700b6 — all three P1s fixed in 5454b62.

1. actions: read — added to scheduled-lane-health.yml, with a comment recording why (an explicit map makes every omitted scope none, so the runs API would have 403'd). Correct call: the whole job is a workflow-runs reader.

2. Terminal error pathhealth.ts main() now runs the body under a guard, and reportTerminalFailure() writes both the snapshot (lanes: [], error: <message>) and a result: 'error' envelope, then exits 1. That exit code is deliberately different from an unhealthy lane, which stays exit 0 + alert issue: a broken monitor is a defect in the monitoring, an unhealthy lane is the monitoring working. Covered end-to-end by a test that points the script at an unreachable GITHUB_API_URL and asserts result === 'error', a non-empty details.error, and a non-null snapshot error.

3. First-due grace — the zero-run case now goes through classifyNeverRan(), which compares the workflow's registration date (GET /actions/workflows/{file}created_at, plumbed through as LaneHistory.registeredAt) against the same 2 × cadence budget a stopped lane gets. So: registered ≤ 48h ago → pending ("added 5.0h ago, first run not yet 48h overdue"); older with still no run → dark; registration date unreadable → pending, never a guess. Three tests pin those boundaries.

Live dry-run against the real API on this head (classification unchanged, and the new registration lookup doesn't perturb it):

| Conformance Differential      | healthy | 11.1h | ran within cadence
| Conformance Regenerate Verify | healthy | 10.1h | ran within cadence
| Perf Nightly                  | healthy | 12.1h | ran within cadence
| Replay Nightly                | failing | never | 10 consecutive scheduled runs failed
| Scheduled Lane Health         | pending | never | workflow not on the default branch yet

Local gates: format/typecheck/lint green, fallow ✓ No issues in 31 changed files, full vitest 519 files / 4597 tests green (the node --test smoke files fail identically on origin/main here with ERR_UNKNOWN_FILE_EXTENSION — a missing local build, not this diff).

Still blocked on the dispatched-route evidence, and I need you for it. Both attempts get 403 Resource not accessible by integration on POST .../actions/workflows/{id}/dispatches, so I cannot produce a scheduled-route run for this head myself. Two things worth deciding together, given the table above:

  • Replay Nightly has failed its last 10 scheduled runs, so dispatching the whole workflow won't come back green regardless of the fuzz job — that pre-existing device-suite breakage is exactly the dark-lane class this monitor exists to surface, and it went unnoticed for ten nights.
  • If you'd rather have a dispatchable route that isn't hostage to the device suites, say so and I'll split the fuzz job into its own parser-fuzz-nightly.yml; a workflow_dispatch then proves the route in ~1 minute instead of ~80.

Otherwise, from an account with dispatch rights: gh workflow run replays-nightly.yml --ref devin/1785159046-parser-fuzz-lane -f fuzz-iterations=5000 and gh workflow run scheduled-lane-health.yml --ref devin/1785159046-parser-fuzz-lane; I'll take it from the results.

@thymikee

Copy link
Copy Markdown
Member

Latest delta fixes actions: read and API-failure terminal envelopes.

One P1 remains: first-run grace is anchored to workflow created_at, which predates the schedule: trigger when scheduling is added to an existing workflow. An old workflow with no scheduled runs will return an old creation date plus empty history and alert immediately instead of waiting two cadences. Track schedule registration/first observation (or conservatively remain pending) and cover this production transition.

Also replace the new raw spawnSync in health-model.test.ts with the repository exec helper per the hard process-execution rule, and clean the temporary directory.

No live scheduled/dispatch evidence exists yet; iOS Smoke is currently red on an unrelated pre-existing timing assertion, so this head is not fully green.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Fixed in ccfd56c.

P1 — grace anchored to the wrong date. You're right, and created_at was worse than wrong: for the exact production transition (adding schedule: to a workflow that's existed for months) it gives an old date and empty history, i.e. an immediate false dark alert on the first run of the monitor. The API can't answer "when was this scheduled" at all, so I dropped that call and asked git instead:

git log -1 --format=%cI --pickaxe-regex -S '^\s*schedule:' -- .github/workflows/<file>

That's the commit that changed the number of schedule: occurrences, i.e. when the trigger appeared. LaneHistory.registeredAt became scheduleRegisteredAt, and classifyNeverRan() measures the same 2 × cadence budget against it. Unreadable date → pending, never a guess; and since a shallow clone can't answer, the health workflow now checks out with fetch-depth: 0 (with a comment saying why, since removing it would silently make every lane permanently pending — failing open, not closed). Covered: just-scheduled-old-workflow → pending, long-scheduled-never-ran → dark, unknown date → pending, plus two tests on the git lookup itself (real workflow returns an ISO date; unknown file returns null).

Process-execution rule. Replaced with runCmdSync from src/utils/exec.ts (allowFailure: true, since asserting the non-zero exit is the point) in both the test and the new schedule-registration.ts — it imports fine under --experimental-strip-types, so scripts have no excuse here. Temp dirs now go through an afterEach that rmSynces them.

Local: format/typecheck/lint green, fallow ✓ No issues in 32 changed files, scripts/scheduled-lane + scripts/fuzz 42/42. Live dry-run on this head still classifies correctly (Replay Nightly failing on 10 consecutive runs, Scheduled Lane Health pending as not-on-default-branch).

On the red check: all 22 checks on this PR are green (Coverage, Smoke Tests, Integration Tests, CodeQL included) — if the iOS Smoke you're seeing is a mac lane outside the PR check set, agreed it's pre-existing and I'd rather not fold a timing-assertion fix into this diff; point me at it and I'll open a separate PR.

Still no dispatched-route evidence: POST .../dispatches gives me 403 Resource not accessible by integration on both workflows, so that one genuinely needs your hands (or a green light to split the fuzz job into its own parser-fuzz-nightly.yml, which would make the route provable in ~1 min instead of riding the ~80-min device suites that are currently failing anyway).

@thymikee

Copy link
Copy Markdown
Member

The prior raw-process and temporary-directory cleanup findings are fixed, and the schedule-introduction anchor is the right design.

One portability blocker remains: git log --pickaxe-regex -S "^\\s*schedule:" returns no match on this supported macOS checkout, so scheduleRegisteredAt() becomes null and a never-run lane stays pending forever. The POSIX form ^[[:space:]]*schedule: finds the expected commit. Use a portable pattern (or avoid shorthand), add a real old-workflow/later-schedule transition test, and rerun the documented local dry-run.

Current PR checks are green, but there is still no schedule/workflow_dispatch execution of either new route.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Good catch, and a nasty class of bug: fixed in 6549f9d.

\s^[[:space:]]*schedule:. The reason this one deserved the P1 label is that a no-match is indistinguishable from "cannot tell" in my own design, so on macOS it degraded to pending forever — the monitor would look healthy while never being able to alert. The constant now carries that rationale in a comment so nobody re-shortens it.

Added the transition test you asked for, against a real scratch git repo rather than a fixture: commit 1 adds a workflow with only workflow_dispatch (dated 2025-01-02), commit 2 adds schedule: (dated 2026-06-07). Two assertions:

  • scheduleRegisteredAt() returns the 2026 date, not the 2025 creation commit — which is exactly what the \s form failed to do, so this test would have caught the portability bug had the pattern silently matched nothing (null ≠ the expected date).
  • feeding that date to laneHealth() with an empty run history yields pending at +1h and dark at +49h, i.e. the full two-cadence grace measured from scheduling, not from file creation.

Documented local dry-run rerun on this head:

| Conformance Differential      | healthy | every 24h | 13.1h | ran within cadence
| Conformance Regenerate Verify | healthy | every 24h | 12.1h | ran within cadence
| Perf Nightly                  | healthy | every 24h | 14.0h | ran within cadence
| Replay Nightly                | failing | every 24h | never | 10 consecutive scheduled runs failed
| Scheduled Lane Health         | pending | every 24h | never | workflow not on the default branch yet

Gates: format/typecheck/lint green, fallow ✓ No issues in 32 changed files, scripts/scheduled-lane 17/17.

That leaves only the live scheduled/dispatch execution, which I still can't produce — POST .../actions/workflows/{id}/dispatches returns 403 Resource not accessible by integration for my token on both routes. Fastest path if you'd rather not spend an ~80-minute device-suite run (which is red on main anyway, per the table): tell me to split the fuzz job into its own parser-fuzz-nightly.yml and the dispatch becomes a ~1-minute proof. Otherwise a gh workflow run from your side on devin/1785159046-parser-fuzz-lane and I'll analyze whatever comes back.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Jul 27, 2026
@thymikee

Copy link
Copy Markdown
Member

Re-reviewed the latest delta: the POSIX schedule regex works on macOS, and the scratch-repo regression proves an old workflow’s later schedule-introduction commit drives pending→dark classification. Process execution and temporary cleanup follow repository rules. All head checks are green; no new code finding.

Residual validation risk: neither new scheduled route has executed via schedule or workflow_dispatch at this head.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Thanks — agreed on the residual risk, and I want to be precise about why it can't be retired from my side, because one half of it can't be retired from anyone's side pre-merge.

Just retried both routes at head 6549f9d:

gh workflow run scheduled-lane-health.yml --ref devin/1785159046-parser-fuzz-lane -f dry-run=true
→ HTTP 404: workflow scheduled-lane-health.yml not found on the default branch

gh workflow run replays-nightly.yml --ref devin/1785159046-parser-fuzz-lane -f fuzz-iterations=5000 -f fuzz-seed=42
→ HTTP 403: Resource not accessible by integration

Two different walls:

  1. Scheduled Lane Health is undispatchable by construction. GitHub only accepts workflow_dispatch for workflows that exist on the default branch, so a brand-new scheduled workflow cannot be executed on any branch before it merges — not by me, not by you. Its first real run is necessarily post-merge. (Incidentally, this is the same fact the lane classifier already models: it reports itself as pending — workflow not on the default branch yet precisely so this state doesn't masquerade as dark.)
  2. Replay Nightly is dispatchable, but not by my token — 403 on the dispatch endpoint. This one is purely a permissions matter and needs a human gh workflow run on the branch.

What that means for the merge decision, honestly stated: the fuzz harness itself is proven by the unit lane (self-check targets exercise the real worker/watchdog path in CI on every run, and 22/22 head checks are green), so what remains unproven is specifically the workflow wiring — step ordering, artifact upload, summary rendering — for the two scheduled routes. My suggestion stands: if you want that verified before merge rather than after, approving the split into a standalone parser-fuzz-nightly.yml doesn't help the 404 case either, so the pragmatic sequence is:

  • you dispatch Replay Nightly once on this branch (proves the fuzz job wiring end-to-end, ~80 min, and will be red overall from the pre-existing device-suite failures — read the Parser Fuzz Lane job in isolation);
  • Scheduled Lane Health gets validated on its first post-merge run, and I'm happy to watch that run and fix any wiring fallout immediately.

If you'd rather not merge anything unvalidated, the alternative is to land the health lane in a follow-up PR after the fuzz lane, which at least reduces the unproven surface to one workflow. Your call — tell me which and I'll set it up.

@thymikee

Copy link
Copy Markdown
Member

Reviewed against #1414. Approve with changes — the lane's core is right (real production parsers verified by signature: parseArgs, parseSelectorChain, parseReplayScriptDetailed, readCliBatchStepsJson, parseMaestroProgram; the invariant is exactly the spec'd one; the worker-thread + SharedArrayBuffer watchdog is genuinely necessary since no in-process timeout can interrupt a synchronous loop; and it already caught a real bug — the readQuotedReplayToken raw SyntaxError, now pinned in the regression corpus). Four changes before merge:

  1. P1 — strip the bundled obs: scheduled-lane health, freshness telemetry, and standard artifact envelope #1430 implementation. scripts/scheduled-lane/* + .github/workflows/scheduled-lane-health.yml (904 of the 2,499 lines) is issue obs: scheduled-lane health, freshness telemetry, and standard artifact envelope #1430's own deliverable, not test: nightly fuzz lane — parser inputs must fail as typed AppErrors, never hang #1414's. It also collides head-on with PR test(daemon): seeded concurrency torture lane for session/lease/lock invariants #1439, which ships a different implementation of the same watcher under scripts/scheduled-lane-health/ and creates the same workflow filename. obs: scheduled-lane health, freshness telemetry, and standard artifact envelope #1430 gets one dedicated PR (see the coordination comment on obs: scheduled-lane health, freshness telemetry, and standard artifact envelope #1430). Keep only the thin per-lane envelope writer this lane needs.
  2. P1 — rebase onto main and reuse the test: property-based testing foundation (fast-check) over parse/print and geometry kernels #1437 foundation. This branch was cut before test: property-based testing foundation (fast-check) over parse/print and geometry kernels #1437 merged: mutate.ts's custom mulberry32 PRNG + 7 mutators + 44-entry HOSTILE_CHUNKS duplicates property-arbitraries.ts's hazard vocabulary and forgoes fast-check's shrinking entirely — a failing input here can be 20k chars with no minimization. Rebuild the generator layer on fast-check arbitraries sharing one hazard list with property-arbitraries.ts (~-100 LOC, shrinking for free). The worker/watchdog layer stays as-is.
  3. P2 — scripts/fuzz/harness.test.ts spawns full node subprocesses per case and landed in the parallel unit-core project. That is the documented contention-flake class this repo already serializes via the subprocess-stub project — route it there.
  4. P3 — AGENT_DEVICE_FUZZ_STARTUP_DELAY_MS in worker.ts is a test-only seam that slipped past the DI-seam gate because it only scans src/. Worth removing or justifying inline; also a data point for gate-of-gates: derived manifest proving every check is owned, wired, and reachable #1429 (the gate-of-gates should know the seam gate's scope).

🤖 Review by Claude Code

thymikee and others added 7 commits July 28, 2026 06:02
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…tests (#1414)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…tchdog after ready (#1414)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… consumer (#1414, #1430)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ace (#1414, #1430)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… helper in tests (#1414, #1430)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…1414, #1430)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
devin-ai-integration Bot added a commit that referenced this pull request Jul 28, 2026
Strip the scheduled-lane-health watcher (scripts + workflow + PR gate) —
it is #1430's deliverable and collides with PR #1438's workflow of the
same filename; keep only this lane's #1430 envelope writer.

Move the torture lane under test/integration/nightly/ so it is out of the
test:integration:node glob, and run it via an explicit, disclosed PR step
plus the nightly sweep.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…op the bundled lane-health work (#1414)

- Strip scripts/scheduled-lane/* and scheduled-lane-health.yml: that watcher is #1430's own
  deliverable and collides with PR #1439's implementation of the same lane. What this lane owes
  (a per-run envelope) moves into scripts/fuzz/envelope.ts.
- Rebase onto #1437 and rebuild the generator layer on fast-check: cases come from arbitraries
  sharing SELECTOR_VALUE_HAZARDS with the property suite, and counterexamples are shrunk, so a
  failure names a minimal input plus fast-check's seed/path instead of a 20k-char random string.
- Route harness.test.ts into the serialized subprocess-stub project.
- Drop the AGENT_DEVICE_FUZZ_STARTUP_DELAY_MS test seam: the ready handshake is now proven by a
  case budget far below real worker startup.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration
devin-ai-integration Bot force-pushed the devin/1785159046-parser-fuzz-lane branch from 6549f9d to bca12c8 Compare July 28, 2026 06:11
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Re: the four changes — all addressed in bca12c8 (rebased onto main, force-pushed).

  1. P1 — bundled obs: scheduled-lane health, freshness telemetry, and standard artifact envelope #1430 stripped. scripts/scheduled-lane/* and scheduled-lane-health.yml are gone (-904 LOC), so nothing collides with test(daemon): seeded concurrency torture lane for session/lease/lock invariants #1439's scripts/scheduled-lane-health/. What this lane genuinely owes — one run envelope per terminal path — moved into scripts/fuzz/envelope.ts, with the cross-lane version called out as obs: scheduled-lane health, freshness telemetry, and standard artifact envelope #1430's deliverable.

  2. P1 — rebuilt on test: property-based testing foundation (fast-check) over parse/print and geometry kernels #1437. mutate.ts (mulberry32 + 7 mutators + 44 HOSTILE_CHUNKS) is deleted; scripts/fuzz/arbitraries.ts generates from SELECTOR_VALUE_HAZARDS (now exported from property-arbitraries.ts) plus a rejection-only list, over bases borrowed from selectorChainArb / replayScriptArb / fc.json(). fast-check drives the loop via fc.check(fc.asyncProperty(...)), so counterexamples are shrunk: an injected throw new Error in parseSelectorChain now reports "\u202evisible=false" (14 chars, 49 cases, --seed 1 (path 30:0:0:0:0:1)) instead of a 20k-char string; same for an injected for(;;) under the hang path. Throughput 20k cases × 5 targets in 15s.

    The worker/watchdog layer stays as-is in spirit, but cases now go over the wire one at a time, since fast-check picks the next input (shrink candidates included). A hang terminates the wedged thread and the runner starts a fresh one, so one hang can't cascade into every later case.

  3. P2 — harness.test.ts moved to the serialized subprocess-stub project, with the reason (subprocess per case + a deliberate hang) noted at the include site.

  4. P3 — seam removed. AGENT_DEVICE_FUZZ_STARTUP_DELAY_MS is gone. The regression it guarded is now proven seam-free: the test runs with --case-timeout-ms 50, far below real worker startup, and passes only because the budget arms on the worker's ready handshake.

Gates: format:check, typecheck, lint, fallow (No issues in 35 changed files), fuzz corpus lane 17/17, harness 10/10.

@thymikee

Copy link
Copy Markdown
Member

P1 — The checked-in regression corpus currently bypasses the worker watchdog: scripts/fuzz/corpus-replay.test.ts calls checkCase in-process and explicitly accepts that a regressed hang will hang Vitest. That violates #1414’s per-case timeout / deterministic-fast corpus contract: a promoted hang case must fail fast in PR CI, not wedge the unit job. Replay corpus cases through the worker-backed watchdog. Separately, #1430 now explicitly requires #1414 to adopt the shared scripts/lib/lane-envelope.ts; the remaining lane-local scripts/fuzz/envelope.ts is a merge dependency until that shared module is available/adopted. Current checks are green, but there is still no live 50k nightly run.

@thymikee thymikee removed the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Jul 28, 2026
…1414)

A promoted hang case used to wedge the unit job until the CI timeout, because corpus replay called
checkCase in-process. It now goes through the same worker-backed watchdog the nightly lane uses, so
such a case fails against a 5s per-case budget; the file moves to the serialized subprocess-stub
project with the rest of the worker-driven fuzz tests.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Re: corpus replay bypassing the watchdog / shared envelope / live nightly.

Corpus watchdog — fixed in 97b61f2. You're right, and the comment I'd left in that file was rationalizing the gap rather than closing it. corpus-replay.test.ts now replays every corpus case (and every target seed) through runCases — the same worker + watchdog the nightly lane uses — with a 5s per-case budget; the file moved to the serialized subprocess-stub project alongside harness.test.ts.

Verified both directions: green, it runs 9 tests in 1.6s; with a for(;;) injected into parseSelectorChain for a corpus input, the test fails in 5.00s with the watchdog's hang instead of wedging Vitest. One worker per target, not per case, so the cost is startup only. (This also surfaced that the worker needs --experimental-strip-types in its own execArgv: under Vitest the parent's carries none, so the worker couldn't even load there.)

Shared envelope. scripts/lib/lane-envelope.ts doesn't exist on main yet, so I can't adopt it in this PR. scripts/fuzz/envelope.ts is deliberately a thin writer over one flat record, so the swap is an import change plus deleting the file. Happy to treat #1430 as a hard merge dependency and adopt it here before merge, or land this and adopt in a one-file follow-up — your call.

Live 50k nightly. Still can't produce one pre-merge: my token gets 403 Resource not accessible by integration on actions/workflows/.../dispatches. Nearest local evidence is 20k cases × 5 targets in 15s, so 50k ≈ 40s, well inside the 20-minute job. If you want a real dispatched run: gh workflow run replays-nightly.yml --ref devin/1785159046-parser-fuzz-lane -f fuzz-iterations=50000 — the workflow as a whole will still be red from the pre-existing device-suite failures, only the Parser Fuzz Lane job is meaningful.

@thymikee

Copy link
Copy Markdown
Member

The corpus-watchdog P1 is fixed at 97b61f2: corpus/seeds now run through runCases(..., 5_000) in the serialized subprocess-stub project, and exact-head coverage is green. The #1430 shared-envelope dependency remains: this PR still has lane-local scripts/fuzz/envelope.ts while #1430 requires #1414 to adopt scripts/lib/lane-envelope.ts. Also refresh the PR body before signoff—it still describes the retired SharedArrayBuffer cursor/mulberry32/no-fast-check implementation and ~40ms in-process corpus replay, while current code uses fast-check plus parent-thread worker termination and the worker-backed corpus took ~1.5s in CI.

…1414)

A wedged parser was surfacing as a bare 'Test timed out in 5000ms' instead of the named hang:
failure that says which input wedged, because the file's vitest timeout was shorter than the
watchdog budget times the number of replayed cases.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Runtime re-verification of the fast-check rewrite ✅

Shell-only testing on 97b61f2 (Node 22), plus one follow-up fix in 5ec4233.

Failure detection + shrinking (the key claim)

Injected faults into parseSelectorChain, reverted after each:

throw new Error('injected-untyped')  ->  selector: 80 cases, 1 failures (248ms)
  fast-check replay: --seed 1 (path 52:0:3:2:2:2)
  [selector] untyped-throw: Error: injected-untyped (at parseSelectorChain ...:133:37)
  input:    "\u200btext=Login"     <- shrunk to 11 chars
  repro / promote commands printed;  exit 1

for(;;){}  --case-timeout-ms 500    ->  [selector] hang: case did not finish within 500ms
  same shrunk input, artifact kind "hang", run finished all 80 cases in 6s (no wedge)

repro: reports "Case still violates the invariant" while injected and "Case passes the invariant now" after revert. promote: appended once (8→9 entries); the second run said "Regression corpus already contains every failing case".
Worker restart proven directly: runCases([hang, good, hang, good, '']) → only the two hanging inputs are reported; good cases after a hang pass.
--self-checkok for untyped-throw, empty-hint and hang.

Green runs, determinism, scale timings

pnpm fuzz:parsers → 5 targets × 2000, 0 failures, exit 0, 3.6s. Determinism checked by hashing fc.sample(arbitraryForTarget(t), {seed}) (a green run prints no case data): seed 1 twice → 547e8a5b5c6b3e0b, seed 2 → 1e651357ac0fab27; with a fault injected the same seed reproduces the identical shrunk counterexample and path.
--iterations 20000 → 12.8s / 13.1s (seeds 2, 3); --iterations 50000 (nightly scale) → 29.1s. All exit 0.

Vitest lanes and adversarial CLI

npx vitest run --project subprocess-stub scripts/fuzz → 19/19 passed, ~13s; no fuzz test remains in unit-core (vitest list --project unit-core | grep -c fuzz = 0). With a hang injected on a corpus input, corpus-replay.test.ts fails in ~5s/case instead of hanging.
--iterations 0, --bogus-flag, --target nope, a nonexistent --input-file, and a garbage artifact each exit 1 with a clear error and still write an envelope (result: error).

One real nit came out of this and is fixed in 5ec4233: a widespread hang in the generated-input test surfaced as a bare vitest Test timed out in 5000ms rather than the named hang: failure, because the file's vitest timeout was shorter than watchdog-budget × replayed cases. The timeout is now derived from both.

Repo tree clean (git status --porcelain empty) after every injection was reverted; nothing committed from the test run.

…elope

main landed scripts/lib/lane-envelope.ts, which #1430 requires this lane to use:
scripts/fuzz/envelope.ts is now only the mapping onto it, with the lane's own facts under `data`
and a `stage` field carrying the crash case the shared two-value `result` cannot express.
Conflicts were in package.json (format globs: keep scripts/fuzz alongside the new
scripts/mutation + scripts/lib) and docs/agents/testing.md (both lanes' sections).
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Merged main (conflicts in package.json format globs and docs/agents/testing.md, both keep-both) — and since #1430's scripts/lib/lane-envelope.ts landed in the meantime, the shared-envelope dependency is now discharged in 058343a.

scripts/fuzz/envelope.ts is just the mapping onto laneEnvelope() now; the lane's own facts live under data:

{ "schemaVersion": 1, "lane": "parser-fuzz", "commit": "", "tool": { "node": "v22.14.0", "harness": "scripts/fuzz/run.ts" },
  "configHash": "sha256:f605b3c5b8b4e14a", "seed": "1", "result": "pass",
  "data": { "stage": "complete", "config": { "mode": "generate",  }, "targetRuns": [], "failures": [], "reproCommands": [] } }

Two mapping decisions worth a look:

  • the shared result is pass | fail, so a run that couldn't complete itself (crash, unparsable options) is result: "fail" with data.stage: "error" rather than a third result value; the harness tests assert that pair.
  • configHash hashes arbitraries.ts + targets.ts + invariant.ts — the modules that decide what a given seed generates — so drift analysis can separate "the same seed means different inputs now" from "the parsers changed".

Green after the merge: format:check, typecheck, lint, fallow (No issues in 84 changed files), subprocess-stub scripts/fuzz 19/19, plus pnpm fuzz:parsers and --self-check re-run. (pnpm check:affected --run fails locally in its tsdown build step with the unrun import error that also reproduces on plain origin/main on this machine — CI is the arbiter there.)

devin-ai-integration Bot added a commit that referenced this pull request Jul 28, 2026
Strip the scheduled-lane-health watcher (scripts + workflow + PR gate) —
it is #1430's deliverable and collides with PR #1438's workflow of the
same filename; keep only this lane's #1430 envelope writer.

Move the torture lane under test/integration/nightly/ so it is out of the
test:integration:node glob, and run it via an explicit, disclosed PR step
plus the nightly sweep.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@thymikee

Copy link
Copy Markdown
Member

P2 — The shared-envelope migration resolves the #1430 sequencing blocker, but its drift provenance is incomplete. configHash claims to identify the inputs that decide a case set, yet harnessHash() covers only arbitraries.ts, targets.ts, and invariant.ts; it omits generate.ts (numRuns/property/shrink execution) and the fast-check version, while tool records only Node plus a constant source path. A fast-check upgrade or generation-loop change can therefore change the same seed’s inputs without changing the envelope provenance. Include fast-check’s version and hash every case-generation input (at least generate.ts, with appropriate package/lock provenance). Also update the PR-body sentence saying the shared module is not on main—it is now merged and already imported. All current checks are green; no watchdog regression remains.

configHash now covers every input that decides what a seed generates (generate.ts and the shared property arbitraries, not just the arbitraries/targets/invariant), and tool records fast-check's installed version. A generation-loop edit or a fast-check upgrade previously changed the case set while the envelope looked unchanged. A test recomputes the hash with each input omitted so a future omission fails.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Fixed in af78435 — the provenance was indeed hashing a subset.

-tool: { node, harness }
+tool: { node, 'fast-check': fastCheckVersion(), harness }   // read from the installed package
-configHash: sha256(arbitraries.ts, targets.ts, invariant.ts)
+configHash: sha256(arbitraries.ts, generate.ts, targets.ts, invariant.ts,
+                   src/__tests__/test-utils/property-arbitraries.ts)

generate.ts (numRuns/property/shrink loop) and #1437's shared hazard list are both case-generation inputs, so they're in the hash now; fast-check's version rides in tool rather than the hash because it isn't repo content — the lockfile pins it, the envelope records what actually ran. Sample: "fast-check": "4.9.0", configHash: "sha256:b2bc03c4b4ed29d6".

To stop the list silently going stale again, harness.test.ts recomputes the digest with each input omitted and asserts the real hash matches none of them, so dropping a file from the set fails rather than quietly narrowing coverage.

PR body updated too: the envelope paragraph now says the shared module is on main and imported, and describes the data/stage mapping and the hash contents.

Green after this: format:check, typecheck, lint, fallow clean, subprocess-stub scripts/fuzz 19/19.

thymikee added a commit that referenced this pull request Jul 28, 2026
…invariants (#1439)

* test(daemon): seeded concurrency torture lane for session/lease/lock invariants

Refs #1416

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): drive torture lane through real lock plan + review fixes

- derive each op's lock plan from production resolveRequestExecutionLockKeys
  via a fake device-inventory provider, so reverting the router's same-device
  serialization trips the overlap invariant (verified)
- assert exact replay: full scheduler trace, terminal outcome, contention
- assert real same-device lock contention in the sweep + a forced 2-client case
- split harness into bindings/invariants/envelope modules (all <500 LOC)
- emit #1430 scheduled-lane envelope (schema/SHA/hash/seed range/duration/result)
  and upload it from the nightly workflow

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): pass claim data as plain view accessors (fallow)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): gate lock plan on shouldLockSessionExecution; sweep replay + forced-device contention; whole-lane envelope

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): add real-scope runLocked serialization guard; whole-lane envelope duration

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): allow seed 0 replay; add seed-0 regression (TORTURE_SEED must accept 0)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* obs(#1430): add scheduled-lane freshness/cadence health watcher

Discovers schedule: workflows from .github/workflows/, reads recent
scheduled runs via the GitHub API, and opens/pings a tracking issue when
a lane misses or fails two consecutive cadences. Pure model unit-tested
and gated on PRs; API I/O + issue open/ping run nightly.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): give scheduled-lane watcher a two-cadence newborn grace

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): anchor lane grace on schedule-introduction, not workflow age

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): derive schedule-activation semantically via git, through runCmdSync

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): add merge-commit regression pinning first-parent + committer time

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): pin scheduled-lane-health issue-write route via stubbed fetch

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): unbundle #1430 watcher; make torture lane nightly-only

Strip the scheduled-lane-health watcher (scripts + workflow + PR gate) —
it is #1430's deliverable and collides with PR #1438's workflow of the
same filename; keep only this lane's #1430 envelope writer.

Move the torture lane under test/integration/nightly/ so it is out of the
test:integration:node glob, and run it via an explicit, disclosed PR step
plus the nightly sweep.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* docs: fix stale torture-lane paths after nightly/ move

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(daemon): adopt shared lane-envelope for torture lane

Rebase onto main (post-#1441) and replace the lane-local LaneEnvelope
dialect with the shared scripts/lib/lane-envelope.ts builder, so the
#1430 health watcher parses one schema: commitSha->commit, sourceHash->
configHash, seedRange/runs moved into the typed data payload, and the
sweep encoded as seed "<start>-<end>".

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@thymikee

Copy link
Copy Markdown
Member

Re-reviewed af78435: envelope provenance now covers fast-check’s installed version, generate.ts, all other case-generation modules, and the shared hazard source; the regression test proves generate.ts affects the hash. The PR body is current, merge state is CLEAN, and all exact-head checks pass. No remaining code-review findings. Residual only: the first live 50k scheduled/dispatch run remains unobserved.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Thanks. On the residual: I can't produce it — POST /actions/workflows/{id}/dispatches returns 403 Resource not accessible by integration for my token. If you want it before merge:

gh workflow run replays-nightly.yml --ref devin/1785159046-parser-fuzz-lane -f fuzz-iterations=50000

Only the Parser Fuzz Lane job is meaningful there — the workflow also fires the device suites, which have been failing independently. Locally that scale runs in 29s across the five targets.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Jul 28, 2026
@thymikee
thymikee merged commit 006c4ca into main Jul 28, 2026
29 checks passed
@thymikee
thymikee deleted the devin/1785159046-parser-fuzz-lane branch July 28, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test: nightly fuzz lane — parser inputs must fail as typed AppErrors, never hang

1 participant