Skip to content

Fest scenario coverage: robustness hardening (cold start-state, merge version guard, dead liveness overload)#986

Merged
ankushdesai merged 3 commits into
masterfrom
fix/scenario-coverage-robustness
Jul 22, 2026
Merged

Fest scenario coverage: robustness hardening (cold start-state, merge version guard, dead liveness overload)#986
ankushdesai merged 3 commits into
masterfrom
fix/scenario-coverage-robustness

Conversation

@ankushdesai

Copy link
Copy Markdown
Member

End-to-end robustness pass over the Fest scenario-coverage feature (follow-up to #985). An adversarial audit across six dimensions (compiler/codegen, satisfaction detection, liveness exemption, report/merge/artifact, output-layout/CLI, feedback-steering) surfaced one real correctness bug and two hygiene/robustness gaps. All fixed here; verified end-to-end; full Release suite green (762/762).

Fixes

1. A cold start state was trivially "covered" (correctness).
A scenario whose start state is cold was marked satisfied on its RegisterMonitor start-entry — before any behavior was observed — so it reported [covered] in every schedule (a coverage measurement that lies). Satisfaction now ignores the monitor's first (start) state entry via a small pure predicate ScenarioSteering.IsSatisfyingEntry(isInHotState, isStartEntry), so such a scenario is a gap until observed behavior re-enters a cold state. The compiler also now warns on a cold start state. Normal start hot state ... cold state Done scenarios are unaffected.

Empirically confirmed: a probe scenario observing a never-sent event flipped from [covered] triggered in 5 schedules[GAP]; the example ClientServer scenarios still report 4/5 covered.

2. merge-scenario-coverage ignored its own schema Version (robustness).
MergeDirectory deserialized every artifact as v1 regardless of version, so a future-schema artifact would be silently miscounted. It now skips newer-version artifacts loudly — which is what the Version field was written for.

3. Dead CheckLivenessTemperature(int) overload (hygiene).
Unlike the live parameterless method, it lacked the coverage-monitor liveness exemption — a latent footgun if ever wired up. Removed.

Tests

  • IsSatisfyingEntry_ColdStateSatisfies_ExceptTheStartEntry — locks the cold-start semantics.
  • MergeDirectory_SkipsArtifactsFromANewerSchemaVersion — a version: 999 artifact is skipped, not counted.
  • Full Release UnitTests suite: 762/762 (was 760; +2).

Considered and not changed

  • Rejected a "floating-point priority non-determinism" flag: it's a List linear-scan insertion (not unordered iteration); same-seed runs produce bit-identical doubles → deterministic order.
  • Deferred as low-risk / not reachable in the current architecture: a theoretical race on PModule static maps (PChecker parallelism is process-based), Count-only change detection, the mtime tie-break in merge dedupe, and summed-vs-unioned distinct-timeline counts across test cases.

🤖 Generated with Claude Code

… version guard, dead liveness overload)

End-to-end robustness pass over the scenario-coverage feature. Fixes one real
correctness bug and two hygiene/robustness gaps surfaced by an adversarial audit:

- Cold START state no longer trivially "covered". A scenario whose start state is
  `cold` was marked satisfied on its RegisterMonitor start-entry — before any
  behavior was observed — so it reported [covered] in every schedule (a coverage
  measurement that lies). Satisfaction now ignores the monitor's first (start)
  state entry (ScenarioSteering.IsSatisfyingEntry), so such a scenario is a gap
  until observed behavior re-enters a cold state. The compiler also warns on a cold
  start state. Normal hot-start -> cold-accept scenarios are unaffected.

- merge-scenario-coverage now honors the artifact schema version: an artifact from a
  newer schema is skipped loudly instead of being silently miscounted as v1 (this is
  what the Version field was written for).

- Removed the dead CheckLivenessTemperature(int) overload, which (unlike the live
  parameterless one) lacked the coverage-monitor liveness exemption — a latent
  footgun if ever wired up.

Tests: added IsSatisfyingEntry cases (locks the cold-start semantics) and a merge
version-skip test; verified end-to-end (cold-start scenario -> gap + warning; example
scenarios still 4/5 covered). Full Release suite green (762/762).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 21, 2026 21:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens the Fest scenario-coverage pipeline end-to-end by fixing a correctness hole in scenario satisfaction (cold start state being counted as “covered” before any observed behavior), adding a forward-compatibility guard to the merge tool, and removing a dead/unsafe liveness-check overload.

Changes:

  • Prevent scenarios from being marked satisfied on the monitor’s initial (start) cold-state entry by introducing ScenarioSteering.IsSatisfyingEntry(isInHotState, isStartEntry) and wiring it into scenario satisfaction detection.
  • Add a schema-version guard in merge-scenario-coverage to loudly skip artifacts with a newer Version than the tool supports.
  • Remove the unused Monitor.CheckLivenessTemperature(int) overload and add unit coverage for the new semantics + version-skipping behavior.

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
Tst/UnitTests/ScenarioCoverageTest.cs Adds unit tests for cold-start satisfaction semantics and skipping newer-schema artifacts during merge.
Src/PCompiler/CompilerCore/TypeChecker/MachineChecker.cs Adds a compiler warning when a scenario’s start state is marked cold (accepting).
Src/PChecker/CheckerCore/SystematicTesting/Strategies/Feedback/Coverage/ScenarioSteering.cs Introduces the centralized predicate defining when a state entry should satisfy a scenario.
Src/PChecker/CheckerCore/SystematicTesting/Strategies/Feedback/Coverage/ScenarioComplianceObserver.cs Tracks and excludes the monitor’s first (start) state entry from satisfaction detection.
Src/PChecker/CheckerCore/SystematicTesting/ScenarioCoverageMerger.cs Skips artifacts whose schema Version is newer than SchemaVersion to avoid silent miscounts.
Src/PChecker/CheckerCore/Runtime/Specifications/Monitor.cs Removes the dead CheckLivenessTemperature(int) overload.
eval/fest-scenario-coverage/README.md Documents the “observed behavior required” rule and the cold-start warning guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

ankushdesai and others added 2 commits July 21, 2026 22:06
…leteness)

A soundness/completeness verification pass (independent review panel + falsification
probes) confirmed the feature is SOUND, but found its highest-risk behaviors were
guarded only by manual probes. This adds automated tests for them:

- InternalsVisibleTo(UnitTests) on PCheckerCore so the internal
  ScenarioComplianceObserver can be driven directly from tests.
- Observer_ColdStartEntryDoesNotSatisfy_ButReEntryDoes: pins the cold-start fix
  END-TO-END at the observer (previously only the leaf predicate was tested).
- Observer_RefreshPicksUpMonitorsPopulatedAfterConstruction_AndNormalPathSatisfies:
  pins the lazy Refresh that avoids the first-iteration / -s 1 silent-drop hazard,
  and the normal hot-start -> cold-accept satisfaction path.
- MergeDirectory_SkipsCorruptJson_AndMergesTheValidArtifact,
  MergeDirectory_EmptyDirectory_ReportsZeroTestCasesWithoutCrashing,
  ZeroScenarios_ReportOmitsBlock_AndWriteIsNoOp: the merge/report graceful-
  degradation and zero-scenario contracts (previously probe-only).

Liveness-exemption non-leak: verified SOUND by inspection (the per-monitor exemption
uses `continue`/membership at ControlledRuntime.cs:932 and Monitor.cs:623, scoped to
PModule.coverageMonitors, never IsSpec) and by a control experiment (a coexisting
scenario does not change liveness behavior versus a bare spec). No regression fixture
is added: the in-process unit-test harness does not detect liveness for these models
(a bare liveness spec relocated into it does not fire either), so such a fixture would
be vacuous/flaky. The termination-path exemption is already guarded by the existing
ScenarioCoverageBasic fixture (a scenario left hot there must not fail the run).

Full Release suite green (767/767).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e the test suite

Two parts: (1) fix a pre-existing compiler concurrency bug that made parallel
in-process compilation crash intermittently ("Operations that change non-concurrent
collections must have exclusive access ... corrupted its state"), surfaced while
adding tests; (2) add the remaining high-value automated tests a verification pass
identified.

Compiler concurrency — static mutable state shared across concurrent in-process
compilations (the unit-test harness compiles many fixtures in parallel). Each backend
codegen is a shared singleton, so [ThreadStatic] (not instance fields) is the correct
isolation; every field is written before read within a single compile:
- InferMachineCreates._visitedFunctions  [ThreadStatic]  — the confirmed crash cause
  (always-run type-check path shared by every backend).
- PExCodeGenerator._globalParams, TransformASTPass.{context,continuationNumber,callNum}
  [ThreadStatic] (PEx; mirrors PChecker's already-ThreadStatic _globalParams, which PEx missed).
- Uclid5CodeGenerator.useLocalPrefix  [ThreadStatic] + per-compile reset (PVerifier).
- PObserve Constants._reservedWords -> Lazy<HashSet<string>> (thread-safe lazy init,
  preserving original timing so reflection still captures every reserved word).

Tests:
- GoldenCodegenTests.ScenarioIsCoverageMonitor_SpecIsNot_MetadataBeforeRegister: the
  reliable liveness-exemption NON-LEAK guard — codegen adds the scenario to
  PModule.coverageMonitors but NOT the real spec, and emits coverage metadata before
  RegisterMonitor. (A runtime fixture can't test this: the in-process harness doesn't
  detect liveness for these models.)
- FeedbackGuidedStrategyTest: DiscardedScheduleDoesNotConsumeNovelty (the
  diversity-gate-before-novelty ordering) + SuiteStateIsPerInstance (no shared static).
- ScenarioWarningsTest: a no-cold-state and a cold-start-state scenario each warn; a
  well-formed hot-start/cold-accept scenario does not; all still compile.
- ScenarioCoverageTest.SameSeed_ProducesIdenticalScenarioArtifact_DifferentSeedDiffers.

Wording: the merged report now labels cross-test-case timelines "satisfying timelines
(summed per test case)" instead of "unique ..." — it is a per-test-case sum (raw
timelines are gone by merge time), so "unique" over-counted a shared timeline. Pinned
assertions updated.

Full Release suite green (774/774).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ankushdesai

Copy link
Copy Markdown
Member Author

Summary — scenario-coverage robustness + soundness/completeness verification

This PR hardens the Fest scenario-coverage feature, verifies it is sound, closes the test gaps around its high-risk seams, and fixes a pre-existing compiler concurrency bug found along the way. Full Release suite green: 774/774. Three commits:

1. Robustness fixes (bd75ffee3)

  • Cold start state no longer trivially "covered" — a scenario whose start state is cold was marked satisfied on its RegisterMonitor start-entry (before any behavior), so it reported [covered] in every schedule. Satisfaction now ignores the monitor's first (start) entry (ScenarioSteering.IsSatisfyingEntry); the compiler also warns on a cold start state. Normal hotcold scenarios unaffected.
  • merge-scenario-coverage honors the artifact schema Version — a newer-schema artifact is skipped loudly instead of silently miscounted as v1.
  • Removed the dead CheckLivenessTemperature(int) overload that lacked the coverage-monitor exemption (latent footgun).

2. Automated tests for the high-risk seams (7f87f4787)

Previously only leaf/pure logic was tested. Added: cold-start fix end-to-end at the observer; the Refresh lazy-init (-s 1 silent-drop hazard); merge/report graceful-degradation (corrupt JSON, empty dir) and zero-scenario contracts. ([InternalsVisibleTo(UnitTests)] added to PCheckerCore so the internal observer is testable.)

3. Compiler parallel-compile race fix + suite completion (463f89993)

While adding tests, parallel in-process compilation intermittently crashed with "Operations that change non-concurrent collections must have exclusive access … corrupted its state." A whole-compiler audit found 5 static mutable fields shared across concurrent compiles (each backend codegen is a singleton, so [ThreadStatic] is the right isolation; every field is written before read within a compile):

  • InferMachineCreates._visitedFunctions (the crash cause; always-run type-check path)
  • PEx PExCodeGenerator._globalParams, TransformASTPass.{context,continuationNumber,callNum}
  • PVerifier Uclid5CodeGenerator.useLocalPrefix (+ per-compile reset)
  • PObserve Constants._reservedWordsLazy<T>

Plus the remaining tests: liveness-exemption non-leak guard (codegen test: a real spec is not added to coverageMonitors while the scenario is, metadata before RegisterMonitor), FeedbackGuidedStrategy integration (diversity-gate-before-novelty ordering; per-instance suite state), MachineChecker warnings, and same-seed determinism. Also relabeled the merged report's cross-test-case count to "satisfying timelines (summed per test case)" (it is a per-test-case sum, not global-distinct).

Verification

Soundness was checked by an adversarial review pass with falsification probes (no false-covered, no false liveness bugs, no crashes, deterministic). Notably, the liveness-exemption non-leak was empirically bisected — a real spec + a coexisting scenario behaves identically to a bare spec, i.e. the scenario does not suppress real liveness enforcement — and is now guarded by the codegen test above.

🤖 Generated with Claude Code

@ankushdesai
ankushdesai merged commit f8a8304 into master Jul 22, 2026
10 checks passed
@ankushdesai
ankushdesai deleted the fix/scenario-coverage-robustness branch July 22, 2026 00:11
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.

2 participants