Repository navigation
v4.10.0
-
Added (#1201, #1202):
fsl_core::obligation::catalog, a solver-free list of the
obligations a model owes -- one row per authored site and aspect (type bounds,
invariant/trans/reachable/leadsTotruth and definedness,terminal
definedness, action guard/body/ensuresdefinedness andensurestruth,
initsatisfiability, deadlock freedom), each marked when it is statically
vacuous. Rows come from the model's declarations rather than from engine
output, so a check no engine performs still has a row. Nothing reads the
catalog yet; CLI, Worker and Public Kernel output are unchanged. See
docs/design/DESIGN-obligation-catalog.md. -
Added (#1202, #1221): the obligation catalog also lists
leadsTodeadlines,
the ranking-proof rows of adecreasesclause (measure definedness, lower
bound, no deadlock, one step row per action, and thehelpfulfairness and
stickiness rows), and two model-definedness rows on every site:NoOverflow
fori64overflow andKeyInDomainfor aMapindex outside its finite key
domain. AKeyInDomainrow is marked statically vacuous only when every value
the index can take lies inside the key type, judged from the index forms a
declaration bounds rather than from the typecheckinfers, and provided the
state satisfies its type bounds. CLI, Worker and Public Kernel output are
unchanged. -
Added (#1237):
fslc mutate --fail-on-survivorsand--min-kill-rate Rmake a mutation run usable as a CI gate. Either flag adds agateobject (fail_on_survivors,min_kill_rate,judged,survived,kill_rate,dropped,violations,passed) to the envelope;resultstaysmutatedand the run exits 1 whengate.passedis false.--fail-on-survivorsfails on any judged survivor;--min-kill-rate R(a number in[0, 1], otherwise a usage error) fails unless the published four-decimalsummary.kill_rateis>= R. A run that judged no mutant fails either flag withno_judged_mutantsrather than passing on no evidence; built-in mutants dropped by--max-mutantsare recorded asgate.droppedand do not fail the gate. Without either flag the output and exit code are unchanged, and a baseline that does not verify is still re-emitted with its own exit code. Equivalent-mutant exclusion is not part of this change. -
Changed (#1226): an invocation that passed
--instances/--valuesnaming
something arequirements/businessdocument does not declare asentity/
number(including a rawtype X = lo..hirange) used to run with exit 0/1
and report the verdict of a model the override did not shrink as asked — the
name was dropped, or, for a raw rangeXthat types a requirements process
field, the override'sloreplaced the field's default initial value (so
--values X=0..1could flip the verdict); it now fails with exit 2 before
verification. Migration: override only declaredentity/number/process
names; to shrink a raw range, declare it asnumber Xwith
verify { values X = lo..hi }and override that, or edit thetypebounds.
Separately,fslc diffon arequirementsdocument whoseverifyblock
carriesvalues X = a..bfor a rawtype X = lo..hithat types aprocess
field, when the scope changed, no longer reports a falsebehavior_removed
(old_to_newrefinement_failed). It used to forwardXto the re-scoped old
model resolved to the type'slo..hi, which moved the field's default initial
value fromatolo; the re-scoped old model now keeps the document's own
bound, so only the real differences are reported. Specs without such a field
diff as before. -
Changed (#1234), breaking for stored
fslc analyzenode IDs of compose
specs: a compose component action now has one public name,alias.action,
built from theusealias and the action name, souse X as a__bwith action
cisa__b.c(it wasa.b__c, and some outputs printed the internal
a__b__c), matching the frozen Python reference. Every command that names a
component action uses it: verify on every engine (traces,action_coverage,
action_profile,cost.properties[].name, vacuity warnings, induction CTIs,
and the BMCsemanticserroraction 'bank.settle' body evaluation has a non-partial failure, likewise forguardandensures; it printedbank__settle),
sweep, scenarios, explain, testgen, conformance, mutate, refine, diff, html,
ledger, analyze, theuntaggedwarnings ofcheck --strict-tags, and
fslc replay, including names built from the action such
as_requires_failed_bank.settleand_partial_bank.settle(a guard partial
operation's_partial_op_prefix becomes_partial_in the same release,
#1244). Lists keyed by these names (reachables,
action_coverage,action_profile, inductionk_used,cost.properties,
scenariosreach_*andrespond_*, the leadsTo warnings offslc scenarios,
refineaction_mapandprogress, and the ai-review
conservation findings) are ordered by the name they print, soacct.gosorts
beforeacct2.go, and a glue actionz__a, printedz.a, sorts before the
component actionz.b.fslc refinesuccess output now printsaction_map
keys and values asa__b.go(the keys werea.b__go) andprogress.*.actions
asacct.back(it was the internalacct__back). A v1 replay trace can now write
bank.settle, the name testgen emits, so a generated test's actions replay
conformantinstead of failing withbad_call. v1 replay still accepts the
olderbank__settlefor the same action, looked up in the model's table of
component actions rather than rewritten; sync actions and non-compose actions
such asfoo__barkeep exact-name matching.fslc replay --from-logwith a
maps automapping matches a log action writtena__b.cas well as the
a.b__cit matched before. Replay'sstate_mismatch.action
andbad_call.actionreportbank.settlewhere they used to echo
bank__settle. The breaking part: infslc analyzeoutput (the default graph,
--profile ai-review, and--export tag-review), the node IDs of a component
action and of its requires, effect, and ensures clauses move from
action:bank__settle,guard:bank__settle:0,effect:bank__settle:0, and
ensures:bank__settle:0toaction:bank.settle,guard:bank.settle:0,
effect:bank.settle:0, andensures:bank.settle:0, and edge IDs and endpoints
follow. Thenamefield of those nodes moves the same way (bank__settleto
bank.settle,bank__settle:0tobank.settle:0), and with aliasa__btheir
labelspells the actiona__b.cinstead ofa.b__c(a__b.c requires 0).
--export tag-reviewprints a component
action'snameasbank.settle, and anundecided:record of a component
action, shown byhtml,ledger, and--profile ai-review, has
declarationaction bank.settleandnodeaction:bank.settle. In the
projecttraceability_graphprojection offslc analyze <manifest>, a
refinement'saction_map:andstutter_map:node IDs and labels and its
maps_actionendpoints use the same name (design:action:bank.settle), so
every edge ends at a node the graph declares; before, a compose layer left
edges ending at undeclaredaction:bank__settlenodes, dropped the
lower_anchoredges from requirements covering an abstract component action,
and reported a spurioustraceability_gap. All of these IDs now differ from
the frozen Python reference, which keepsaction:bank__settle; this is
intended. To migrate, rewrite stored node IDs and--focusarguments from
alias__actiontoalias.action:--focus action:bank.settlealready
resolved for an alias without__and keeps resolving, while
--focus action:bank__settlenow fails with "unknown analyze focus node"
(exit 2). State and property node IDs are unchanged.fslc documentclaim
IDs and fingerprints are unchanged, because the document projection rejects
compose specs. For a compose spec,fslc testgennow lists the keys of each
scenario's expected state in state declaration order (bank.cleared,
bank.pending,audit.balance,audit.log,withdrawnfor
specs/bank_system.fsl) in the vitest, swift, kotlin, dart, and phpunit
scaffolds, as pytest already did; they used to list the compose spec's own
state first and component state after it in name order. The values and the
partial-match check are unchanged, so a generated test passes or fails as
before. Non-compose specs change in two corner cases. A keyed list now
orders a name containing__by the.form it prints, sostep__a
(printedstep.a) sorts beforestep2; it sorted after.fslc testgennow
accepts a state whose name has two__separators, such asa__b__c
(printeda.b__c), and generates every target; it failed with "testgen
concrete vector.initial has unknown state fields: a.b__c" (exit 2). Every other non-compose output is unchanged. -
Changed (#1244), breaking for stored
fslc conformanceandfslc replay
output: a partial operation reached in an action's guard (requiresor
let, such asrequires 2 / x >= 0withx == 0) is now named
_partial_<action>byconformance(vectors[].outcome.name) andreplay
(violation.name), the nameverifyalready gave it on every engine and the
name a body partial operation already had everywhere. It was
_partial_op_<action>, so the same failure could not be matched between a
conformance vector and the verify result for one spec. For a compose
component action the name is_partial_alias.action(#1234), such as
_partial_bank.settle; it was_partial_op_bank__settleinconformance
and_partial_op_bank.settleinreplay(_partial_op_a.b__cfor alias
a__band actionc). Thekindstayspartial_op. To migrate,
rewrite stored conformance vectors, replay results, or test expectations
from_partial_op_<action>to_partial_<action>. -
Changed (#1251), breaking for
fslc mutateconsumers that readstatus
orsummary: a mutant the oracle could not judge is no longer
counted as killed. Z3 not being creatable (previouslykilled_by:"internal"),
a BMC call the solver could not decide (solverunknown, a backend failure, or
an unreadable model; previouslykilled_by:"build_spec"for built-ins and
invalidfor external mutants), and an acceptance/forbidden or implements
oracle error (previously the error text, orrefinement, as the built-in
killer andinvalidfor external mutants) now givestatus:"error",
killed_by:null, anderror:{stage,message}withstageone ofsolver,
bmc,requirements,implements. These mutants are excluded from both
sides ofkill_rate, counted in the newsummary.errored(also per source in
summary.by_source; like #1262'ssummary.inconclusive, the key appears only
when non-zero, so a fully decided run's envelope is unchanged), and listed in a
note. With--fail-on-survivorsor
--min-kill-rate, any such mutant adds the gate violationoracle_errorsand
thegate.erroredcount (when non-zero), so the gate fails closed (exit 1); without a gate
the exit code is unchanged. A BMC rejection that is a finding about the mutant
(for example an action body undefined in a reachable state) is still the
build_speckill. A built-in mutant that does not lower/build is now
invalidwithinvalid:{kind:"semantics",message}, as an external one
already was, instead of abuild_speckill.errorstays distinct from #1262's budgetinconclusive; for one mutant
killed>error>inconclusive>survived, decided in one place for
built-in and external mutants. An implements oracle error is nowerror
instead of arefinementkill (an impl parameter or key domain that differs
from the abstraction's is a refinement verdict,map_out_of_bounds, not an
error; see #1295). The_bounds_<state>attribution
for a removed init assignment, which used to overwrite every outcome of such
a mutant, now applies only to a judged one: a kill (including a semantic
build_speckill) or a survivor is re-attributed to_bounds_<state>as
before, while an oracle error (including the formerinternal) keeps
status:"error"and a mutant that does not build staysinvalid. The
verifier marks solver failures withVerifyError::is_solver_failure.
Migration: readsummary.errored/status:"error"alongside
killed/survived/invalid(total=killed + survived + invalid + errored + inconclusive, absent counts read as 0); a gated run that now fails withoracle_errorshad mutants whose
kills were never established — rerun, or fix the
oracle error reported inerror.message. -
Fixed (#1021):
fslc htmldrew averifyresult outside a short hand list
as a neutralinfobadge, sounknown_budgetanderrorlooked harmless in
the report. The Result badge (hero summary and status section) now has an
explicit class for every verdictverifypublishes:verifiedandproved
areok;violated,reachable_failed,refinement_failed,
impl_violated, anderrorarebad;unknown_ctiandunknown_budget
arewarn. Any other value, or a missingresult, is drawn as abadbadge
that reads<value> (unrecognized verdict)instead of a neutral one, so a
verdict added later without a class shows up in the report. A test infslc
checks each listed verdict's class againstoutcome_class, the vocabulary's
owner, whichfsl-toolscannot see. Coverage markers and witnesskind
badges are unchanged. Theunknown_budget,error, and unrecognized-value
badges now differ from the frozen Python reference, which draws theminfo;
this is intended. JSON output and exit codes are unchanged. -
Fixed (#1190):
fslc kernelfailed withsemantics: public Kernel cannot type identifier 'k'on a statement-levelforall k: K { … }whose body had a
partial operation that read the binder (m[k] = s.at(k),m[k] = 1 / (k + 1),
or a correctly guardedm[k] = if k < s.size() then s[k] else 0), although
verifyandexplainhandled the same spec. The Public Kernel's
partial_operationsnow expands a statement-levelforalllike a quantifier:
one entry per candidate for each partial operation in the body and in the
binder'swhere(previously not listed at all), with the binder replaced by
the candidate and the failure condition guarded by the candidate's membership
andwhere. Aforallbody operation that does not read the binder, which
was listed once with an unguarded failure condition, is now listed once per
candidate. A statement-levelforallwith no partial operation in its
binder,whereor body is still not expanded and lists nothing, so
forall k in 0..x { m[k] = 1 }with a non-constant bound keeps producing a
Kernel. One that does have a partial operation now needs constant bounds,
like a quantifier:forall k in 0..x { m[k] = 2 / x }, which used to list
the division once unguarded, now fails with'x' is not an integer const
(the BMC engine already rejected it the same way). Commands built on the
Public Kernel (testgen,testplan,document) no longer stop with the
"cannot type identifier" error. Verification verdicts are
unchanged, and no spec or fixture in the repository corpus changes its
fslc kerneloutput. -
Fixed (#1191): a partial operation reached in an action's
requiresorlet
(requires s.head() == 0orlet h = s.head()on an empty sequence,
requires 2 / x == 0withx == 0) madeverify --engine explicit, the
defaultautoengine, andverify --engine bmcon a deterministic init return
result: error, kind: semantics(exit 2) with the raw evaluation message. They
now reportviolated/partial_op/_partial_<action>(exit 1) with the
same replayable trace the symbolic engine already gave for a nondeterministic
init, asdocs/manual/LANGUAGE.md§6 states. Thelocof such a guard
partial_opis now, in every engine, the span the Public Kernel's
partial_operationsgives that site: therequiresclause, or the action for
alet. It used to be the action body's first statement, ornullfor an
action without a body (rust/fslc/tests/fixtures/replay_trace.fsl'spartial
now reports line 27). Apartial_opin the body keeps its location. The concrete
action enumeration that explicit, Monitor BFS, the BMC/Worker boundary pre-pass,
refinement's self-violation walk,scenarios' cover and response walks, and
trace replay share no longer aborts on such a guard: it is that action's
partial_op, is not a deadlock, and is skipped by walks that only follow
successful steps. Commands that take averifybaseline follow it:fslc ledgeron such a spec now exits 1 and lists thepartial_oprow instead of
exiting 2 over a ledger that claimed nothing was detected
(rust/fslc/tests/fixtures/replay_trace.fsl). A spec whose guards never reach
a partial operation is unchanged. -
Fixed (#1226):
verify/sweep--instances NAME=N/--values NAME=LO..HI
on arequirementsorbusinessdocument accepted aNAMEthe document does
not declare asentity/number— a typo, or the name of a raw
type X = lo..hirange — and echoed it underbounds_overrides. The name was
dropped, so--values X=0..2returned a verdict over the full declared domain
ofX, except that for a raw range typing a requirementsprocessfield the
override'slosilently became the field's default initial value. The
native CLI now rejects such a name asdocs/manual/LANGUAGE.mdand the frozen
Python reference already did:result: "error",kind: "semantics",
verify instances references undeclared entity 'NAME' at 1:1/
verify values references undeclared number 'NAME' at 1:1, exit 2 — the same
message a kernelspecalready produced. A requirementsprocessname counts
as an entity (it is one after lowering), and the business dialect, which has no
number, rejects every--values. Kernelspecbehavior and every override
of a declared name are unchanged.fslc diff, which forwards a document's own
verifybounds to the re-scoped old model, now forwards only the declared
names; this changes its output only for a raw range typing a requirements
processfield (see the Changed entry). -
Fixed (#1246): the impl self-consistency precondition that runs before every
refinement correspondence check (fslc refinesingle and chain, the
fslc chainrefine layer, an inlineimplementsseam incheck/verify,
governance preservations,fslc diff,fslc mutate's implements oracle) now
stops at the same 50,000-state budget as the correspondence check (#1041).
It walked the whole reachable set unbounded first, so #1041's original
reproducer still aborted above 6 GB; it now reportsunknown_budget
(exit 1,states_explored: 50000) at about 1.2 GB. A precondition cutoff is
reported exactly as a correspondence cutoff, through the same consumers.
Migration: an impl whose reachable set within the depth reaches 50,000 states
now reportsunknown_budgeteven where it used to report a decided result
because the unbounded precondition happened to fit in memory — a
self-violation found beyond the first 50,000 states (violated/
impl_violated), or a correspondence failure the walk found before its own
budget (refinement_failed). Lower the depth or narrow both layers'
verify {}domains.fslc diffandfslc mutateread this cutoff as they read a
correspondence cutoff (#1262): where they used to report a self-violation
found past the budget (impl_violated/ killed),fslc diffnow reports an
unknown_budgetdirection that fails its gate, andfslc mutatereports the
mutant asinconclusive. -
Fixed (#1258):
verify --engine bmcreportedverifiedand--engine inductionreportedproved(exit 0) for a spec whoseinitreaches a
failing operation, e.g.init { d = 0 x = 6 / d }or a finiteMapread
outside its key domain, while--engine explicitstops on the same spec with
division by zero(exit 2). The symbolic init encoding totalized those
operations, and a failing deterministic init also disabled the concrete
pre-pass; a nondeterministic init never had it. BMC (and so the induction
base case, everyverify_boundedcaller, and the browser Worker) now asks
after the search whether init is defined: a//%by zero, a partialSeq
operation, aMapkey outside its domain (read or assignment target), or
checked i64 overflow that init evaluation reaches is akind: "semantics"
error, exit 2, whose message names the failure with the explicit engine's
wording and the init statement, e.g.division by zero in init at 9:5. It
supersedes the search's verdict. An operation on the unreached side of an
initif, conditional, orand/or/=>stays defined, and the step-0
type bounds the search asserts are assumed, as before. An init overflow that
used to surface asinteger model value is unavailablenow reads e.g.
integer overflow in multiplication in init at 9:5.Migration: a spec that was
verified/provedonly because of this defect
now exits 2; guard the init operation with an initif(or fix the initial
value). No spec underspecs/,examples/, or the test fixtures changes
verdict, exit code, or message. When init contains an operation that can fail,
cost.propertiesgains one{"kind": "init", "name": "definedness"}entry
(andcost.solver.checkscounts it); an init with nothing that can fail builds
no solver term and asks nothing, so its output is byte-identical.
fslc kernel'spartial_operationsandfslc explain'sauto_checksstill
list action sites only. -
Fixed (#1260):
fslc kernellisted a partial operation in the body of a
statement-levelifwith an unguarded failure condition, so for
if gate { y = 2 / x }it printedx == 0although the division is never
evaluated whengateis false (verify and the runtime already agreed it is
not). The Public Kernel'spartial_operations[].failure_conditionis now
guarded by theifcondition,gate and x == 0, as an expression-level
if-then-elsealready was; an operation in theelsebranch is guarded by
not gate, and nested statementifs andforalls stack their guards
(forall k: K { if k > 0 { m[k] = 2 / x } }lists one entry per candidate,
each guarded byk > 0withkreplaced by the candidate). An operation in
theifcondition itself stays unguarded by it. Kernel consumers that read
failure_condition(testgen,testplan,document) see the narrower
condition. Verification verdicts are unchanged, and no spec or fixture in
the repository corpus changes itsfslc kerneloutput. -
Fixed (#1262), breaking for
fslc diff/fslc mutateconsumers:fslc diff
and thefslc mutateimplements oracle no longer treat a refinement
correspondence check cut off by its fixed state budget (50,000 states, #1041)
as a pass. Afslc diffdirection that hits the budget now reports
result: "unknown_budget"withstates_explored(nochecked_to_depth)
instead ofrefines, and the diff adds anunknown_budgetfinding (also in
summary) that fails the gate whatever--forbidsays, likeimpl_violated;
fslc diff --gitandfslc approval diffreport the same and exit 1. Before,
both directions of such a diff could reportrefines, giving
no_semantic_change/ exit 0 even under--forbid behavior_removedwhen the
difference lay beyond the budget. Afslc mutatemutant whose implements check
hits the budget is nowstatus: "inconclusive"with
inconclusive: {"reason": "unknown_budget", "states_explored": N}and
killed_by: null, instead ofsurvived; it is excluded fromkill_rate,
counted insummary.inconclusiveandsummary.by_source.*.inconclusive
(present only when non-zero), and fails a requested--fail-on-survivors/
--min-kill-rategate with the violationinconclusive(gate.inconclusive
carries the count).RefinementCheck's fields are now private: Rust callers of
fsl_runtime::check_refinementread the outcome throughverdict()and the
names/depth/action map through accessors. Migration: a diff that used to exit 0
withno_semantic_changeon budget-scale specs now exits 1 with
unknown_budget; lower--depthor narrow both specs'verify {}domains. A
mutate consumer that assumed every mutant iskilled,survivedorinvalid
must handleinconclusive; runs without a cut-off mutant print exactly the same
JSON as before. -
Fixed (#1283): when an enum-valued state was left unconstrained (for example
itsinitassignment removed),fslc verify --engine bmccould fail with
error/semanticsnegative enum ordinal in solver model(exit 2) instead
of reporting the_bounds_<state>type-bound violation, depending on which
ordinal the solver picked. The counterexample projection now keeps a negative
ordinal as a raw integer witness, exactly like an ordinal past the last
member, so the run reportsviolated/_bounds_<state>(exit 1). In
fslc mutate, the same failure no longer surfaces asbuild_spec: the
built-inassignment_removeof such an init line no longer lists
"build_spec"in--oracle-attributionkillers, and the equivalent
external mutant is nowkilledby_bounds_<state>instead ofinvalid, so
built-in and external verdicts agree. (Which outcomes the built-in
init-assignment special case re-attributes to_bounds_<state>is described
under #1251.)
Migration: a spec that relied on the previouserrorexit 2 for this shape
now getsviolatedexit 1. -
Fixed (#1295):
fslc refine,fslc verify's inlineimplements, and the
fslc mutateimplements oracle stopped withresult:"error"/kind:"type"
(exit 2) instead of a refinement verdict when the impl's domain differed from
the abstraction's: an action-correspondence argument outside the abstract
action's declared parameter domain (an impl parameter type wider than the
abstraction's: "parameter 'v' does not belong to its declared domain for
action 'push'"), or a state-mapping expression that reads an implMapat a
key outside its finite key domain (for example an impl key type narrower than
the abstraction's: "map index outside finite key domain"). Both are now
refinement_failed/kind:"map_out_of_bounds"(exit 1) at the step or
initwhere they occur, the kind already documented for a shrunken impl
against a full-size abstract. The documented check order is kept: a false
abstract guard is stillabs_requires_failed, and a stutter or action
mapping's own checks run before the escape is reported. ASeqindex out of
range in a mapping, aMapread outside its domain in an
action-correspondence argument, and other abstract-guard evaluation errors
still stop with an error.
Infslc mutate, atype_bound_*mutant that widens or narrows such a type is
killed byrefinementas before, and--oracle-attributionnow lists
refinementamong itskillers(the attribution pass used to drop the error
and list none). Migration: a spec whoserefine/verifyexited 2 with one of
these messages now exits 1 with a locatedmap_out_of_boundsfinding; align
the impl's parameter or key type with the abstraction's, or map it explicitly. -
Fixed (#1296): a refinement whose corresponded abstract action hits a partial
operation in its own body at the mapped state (action go() { y = 4 / y }
reached aty = 0, ors.pop()on an emptySeq) was reported asrefines
(exit 0) whenever the impl step left the mapped state unchanged, and as
abs_state_mismatchwith the unchanged pre-state shown as
abs_after_expectedwhen it changed it: the abstract step'spartial_opwas
recorded and then ignored. It is now
refinement_failed/kind:"abs_partial_op"(exit 1) at that step, with
abs_before/abs_after_actualand noabs_after_expected(the abstraction
has no defined successor there). Every consumer of the refinement verdict
reports it:fslc refine,fslc check/fslc verifyinlineimplements
(implements.violation.kind),fslc diff(the direction whose abstraction is
partial), governance preservations (native and Worker: only
governance.preservations[].resultbecomes"refinement_failed"; the
top-levelresultand the exit code offslc checkon the governance file
are unchanged, as for any other failed preservation), and thefslc mutateimplements oracle, which now kills a mutant
that reaches such a state (killed_by:"refinement"). A false abstract guard
is stillabs_requires_failed, and the stutter, guard, and out-of-domain
checks keep their order.abs_partial_opis a newkindvalue; a consumer
that matches on the documented kind list should add it. Migration: a spec or
mapping thatrefinestoday may now fail withabs_partial_op; guard the
abstract action (requires y != 0) or make its body total, or keep the impl
from invoking it in that state; a refinement that failed with
abs_state_mismatchfor this reason now reportsabs_partial_opinstead.
The other violations
the abstract step reports -- an abstractensuresthat is false, a partial
operation inside an abstractensuresor post-state check (Seqoperations;/%are total there), a post-state
type-bound violation, and an abstract invariant (ortrans) violation -- are
notabs_partial_opbutabs_step_violation(#1302). An abstract guard
or leadingletwhose evaluation fails (for examplelet q = 4 / y) still
stops withresult:"error"(exit 2). -
Fixed (#1302): a refinement whose corresponded abstract step is rejected by
the abstraction itself — the successor violates an abstract type bound
(action go() { y = y + 5 }withy: 0..4), an abstract invariant or
trans, the abstract action'sensuresis false, or a post-state check
(Seqoperations;/%are total there) orensureshits a partial operation (ensures 4 / y == 0aty = 0) — was
reported asrefines(exit 0) whenever the impl step left the mapped state
unchanged, and asabs_state_mismatchwith the rolled-back pre-state shown as
abs_after_expectedwhen it changed it. It is nowrefinement_failed/
kind:"abs_step_violation"(exit 1) at that step, withabs_violation
({"kind", "name"}of the abstract violation:type_bound/invariant/
trans/ensures/partial_op),abs_after_attempted(the successor the
abstraction rejected), and noabs_after_expected. Every consumer of the
refinement verdict reports it:fslc refine,fslc check/fslc verify
inlineimplements(implements.violation.kind),fslc diff(the failing
direction, withabs_violation), governance preservations (native and
Worker: onlygovernance.preservations[].resultbecomes
"refinement_failed"; the top-levelresultand the exit code offslc checkon the governance file are unchanged, as for any other failed
preservation), and thefslc mutate
implements oracle, which now kills a mutant that reaches such a step
(killed_by:"refinement"). Therefinesnote for an abstraction with
ensuresnow reads "abs ensures are checked only on the abstract steps the
impl's transitions correspond to within checked_to_depth; verify/prove the
abstract spec separately".abs_step_violation,abs_violationand
abs_after_attemptedare new; a consumer that matches on the documented kind
list or on the note text should add or update them. Migration: a refinement
thatrefinestoday may now fail withabs_step_violation; the abstract-side
conditions now checked on every corresponded step are the abstract type
bounds, invariants andtransat the abstract successor, and the abstract
action'sensures(including partial operations in any of them; in the invariants andtrans
onlySeqoperations can be partial, as/%are total there). Fix the
abstract action (or itsensures/ invariant) or keep the impl from invoking
it in that state; a refinement that failed withabs_state_mismatchfor this
reason now reportsabs_step_violationinstead. A false abstract guard is
stillabs_requires_failed, a body partial operation stillabs_partial_op,
and an abstract guard whose evaluation fails is unchanged. -
Unified (#1201): the verifier's per-action partial-operation candidate check
(action_has_partial_operation_candidate) moved unchanged intofsl-core, next
to the partial-operation inventory, so the verifier and the obligation catalog
decide whether an action can reach a partial operation with one function.
Verification output is unchanged.