Fest scenario coverage: robustness hardening (cold start-state, merge version guard, dead liveness overload)#986
Conversation
… 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>
There was a problem hiding this comment.
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-coverageto loudly skip artifacts with a newerVersionthan 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.
…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>
Summary — scenario-coverage robustness + soundness/completeness verificationThis PR hardens the Fest 1. Robustness fixes (
|
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
coldstart state was trivially "covered" (correctness).A scenario whose start state is
coldwas marked satisfied on itsRegisterMonitorstart-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 predicateScenarioSteering.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. Normalstart hot state ... cold state Donescenarios 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 report4/5 covered.2.
merge-scenario-coverageignored its own schemaVersion(robustness).MergeDirectorydeserialized every artifact as v1 regardless ofversion, so a future-schema artifact would be silently miscounted. It now skips newer-version artifacts loudly — which is what theVersionfield 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— aversion: 999artifact is skipped, not counted.UnitTestssuite: 762/762 (was 760; +2).Considered and not changed
Listlinear-scan insertion (not unordered iteration); same-seed runs produce bit-identical doubles → deterministic order.PModulestatic 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