Conversation
RAT_EQ_DECIDE_CONV is wired into the compset under the polymorphic [=] key, which computeLib offers for equality at every type, and it decided whether the redex was a rat only by failing inside RAT_CMP_STEP -- after ratLib.RAT_CALC_CONV had already parsed, announcing an invented type variable each time. add_convs appends the conv after the rules the key holds when Refute loads, but not after those a later Datatype adds, so in an interactive session stepping through a script the conv really is reached first for every datatype defined from there on. Under the grammar ancestry a script header installs, that parse guesses: e REFUTE_TAC on the hotel example emitted 14454 "inventing new type variable names" messages, one pair per candidate equality on the compute substrate. Checking the redex type before any work removes them, with the same counterexample and the same 29630 candidates. The pin sets Globals.interactive itself: bin/hol run selftest leaves it false, and the parser announces a guess only when it is true, so without that the check passes against the unguarded conversion as well. Claude-Session: https://claude.ai/code/session_01XQTAPaX2Wnt2Bx5G7wVquU
A goal refuted by NARROWING_TAC, QUICKCHECK_TAC or MODEL_REFUTE_TAC was not always refuted by REFUTE_TAC. On the AVL rotation goal in examples/refuteAVLScript.sml, Only [Narrowing] found the counterexample in 0.7s while AllBackends returned Unknown after the whole 10s budget. Two independent causes, load-bearing in disjoint modes. No per-backend budget. One search_context was built from the config's timeout and every backend ran with its remaining time. Each QC backend deepens until search_expired, so whichever backend ran spent the whole budget: in sequential mode exhaustive took all 10s and narrowing never started. run_backend now derives a context per backend. Backends that run at once each get the whole timeout; backends that run in turn split what is left of it, a backend that exhausts or declines early donating its remainder to those behind it, so a sequential call stays inside the timeout. That matters because the sequential users are the probe sweeps in try_refute and Refute_Unused, which run one call per premise. A globally serialized substrate. The QC backends only looked parallel: with_term_tables held table_mutex and with_native_hooks held native_mutex for an entire test window -- and generated code wraps a whole run in with_term_tables -- while a window was bounded only by the deadline. Acquisition was a trylock/sleep spin with no queue, so a backend re-acquiring in a tight loop barged past a waiter polling every 10ms, which is why the loss was race-dependent. Those locks existed only because the state generated code reads was process-global under a save-set-run-restore discipline, which is dynamic scoping. The dispatch slot, deadline, ignored-hit filter and term tables are now per thread; native_mutex is gone, table_mutex covers only the registry lookup, and compiler_mutex remains but spans only the compile. reconstruction_forces stays process-wide: it is telemetry, and a reconstruction may be forced on a thread other than the one that ran the test. Narrowing also reported "search exhausted" when it had finished no depth at all, which reads as a completed search and helped hide this. Ablation, each mechanism removed separately and re-measured: without the per-backend budget the sequential rows return Unknown; without the re-entrant substrate the parallel rows do. Re-entrancy is what carries the parallel case that every tactic uses; the budget carries the sequential case, where backends never contend for the lock. all_phases_share_one_deadline pinned the contract this replaces. It is split into three: admission still shares the call's deadline, a parallel backend's deadline outlives it, and a lone sequential backend inherits what remains. Two pins are added for the mechanisms above. Claude-Session: https://claude.ai/code/session_01XQTAPaX2Wnt2Bx5G7wVquU
Every goal mentioning MEM or IN was rejected by extraction, so narrowing
bailed out before depth 1 on all of them. MEM x l is an overload for
x IN LIST_TO_SET l, and DefnBase.lookup_userdef is not obliged to hand
back a definition: for bool$IN it answers with GSPECIFICATION,
|- !f v. v IN GSPEC f <=> ?x. (v,T) = f x, whose second argument pattern
is GSPEC f. Extraction took that for IN's defining equations, found a
non-constructor pattern, and refused -- with the constant's own name
nowhere in the reason, so the refusal read as a set-comprehension
limitation rather than as membership being unavailable. The
("bool", "IN") intrinsic in the strict and lazy primitive tables
compiles membership as the application IN_DEF states, and the question
never reaches lookup_userdef.
The failure was silent in the worst way: NARROWING_TAC still prints a
report, just an Unknown one, in a few milliseconds. All three
NARROWING_TAC calls in examples/refuteHotelScript.sml were no-ops for
this reason, and the AA-tree example's the_fixed_insert_inserts row was
reporting the bail-out where it now runs a search.
Two selftest rows had written the defect down as expected behaviour,
which is why nothing caught it. "Refute native set-comprehension
refusal" asserted the exact string "non-constructor pattern: GSPEC f"
on a nub goal, and the full conformance matrix carried a NativeSML
inapplicability on the same goal. Both are repaired against measured
behaviour, not relaxed: the conformance row now demands that all three
substrates agree, and the former row keeps a comprehension case, which
is still not executable but is refused in preprocessing ("not
executable: GSPEC") before extraction sees it -- so the GSPEC f pattern
refusal is now unreachable rather than merely unexercised. Auto
fall-through keeps its own rows and no longer has this goal to measure
it, since native no longer falls through here.
Three pins are added: the intrinsic in each extraction mode, and the
end-to-end consequence, a MEM goal that Only [Narrowing] refutes with a
Genuine counterexample. Ablation: all three redden with the intrinsic
removed.
The hotel example's feels_safe conjecture was carried as prose with its
search omitted, on the stated grounds that the adaptive defaults did not
produce a genuine counterexample. Two separate things were wrong. The
extraction gap above, and the model: hkey had three constructors, the
attack needs three check-ins, and each check-in issues a key not issued
before, so with K0 initially issued the conjecture was true there. An
exhaustive forward search over hotel-valid traces found nothing at three
keys up to length 8 and a witness at length 5 with four. Isabelle's
original uses four keys. With K3 added and splits, no_checkin and
feels_safe defined, NARROWING_TAC refutes the conjecture under the plain
adaptive defaults, certified, at size 7 -- the size Isabelle's
quickcheck[narrowing] needs -- in 0.012s.
Claude-Session: https://claude.ai/code/session_01E1cvmgVowvtZmP3Q3dvEDu
Rewrite tests/selftest.sml (37.6k lines to 4.1k) so it exercises only the Refute signature: outcomes, certificates, messages, configuration and registrations. Mechanism-level checks (internal modules, counters, serializer and kodkodi transcript goldens, timing) are gone, and tests/goldens/ with them. Level 2 keeps the conformance matrix, narrowing table, corpus, MF acceptance tables and the differential and soundness suites as per-row tests. Claude-Session: https://claude.ai/code/session_01SSJxCK2hPm7jmv2Jy9aqDQ
Drop every library binding nothing references (test-only accessors, write-only telemetry counters, debug dumps, the Forl/Mono test structures, unreachable PNF and policy-update paths), fold duplicated helpers onto shared definitions, and simplify trivially equivalent code. No user-visible behaviour changes: the Refute signature, defaults, messages, candidate order and verdicts are untouched, and the level-1 and level-2 selftests plus theory_tests pass unchanged. README shrinks to a developer orientation; the user documentation lives in Manual/Description/Refute.smd. Claude-Session: https://claude.ai/code/session_01SSJxCK2hPm7jmv2Jy9aqDQ
Seven entry points on the Refute facade had no consumer anywhere in the tree: register_substrate, the four idempotent "restore the built-in" registrations (register_frac_type_rat, register_frac_type_real, register_function_display, register_fmap_display), register_backend_with_ceiling, and lookup_term_postprocessor. The restore group only ever meant "I overrode a built-in and want it back", a case Isabelle covers with unregister_* rather than with a restore function per built-in. Every registry stays. The substrates register themselves from Refute_EvalCompute, Refute_EvalCv and Refute_EvalSML; narrowing and kodkod declare their own certainty ceilings; and Refute.sml installs the Frac and display defaults directly through Refute_ModelFinder_Model. Only the facade re-exports go, so no search behaviour changes. The selftest keeps its backend-race ceiling test and its six postprocessor assertions by aliasing those two entry points from their own modules, and the CLAUDE.md rule confining the selftest to the Refute signature now names that as its one exception. Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
Rephrase every Refute.*.smd entry and Manual/Description/Refute.smd for brevity, keeping the transcripts and every distinct rule. Detail that was duplicated verbatim between the reference entries and the manual (upd_instantiate, upd_use_subtype, the codatatype witness shape) now lives in full in the reference entry and in summary in the manual. Corrections against the source: the depth field is smart-enumerator fuel and is not read by narrowing; the Kodkodi launcher also needs /bin/sleep; quickcheck and model_refute are refute_with over the typed search selection; abstract_generator's pred is a guard on generated values. Claude-Session: https://claude.ai/code/session_01N8Z5QawKrRYRbMA82shvG1
Backends trace from their own workers, so two of them can be inside HOL_MESG at once. Feedback.MESG_outstream is a plain callback, and a consumer that accumulates into a ref -- which is what the hook invites, and what the selftest's capture helper does -- silently loses one of two concurrent updates. The symptom was a level-2 trace missing a backend's "started" line while that same backend's Unknown reason still appeared in the outcome: it ran, its line was dropped. "trace 2 reports selection and the race" failed non-deterministically on that. Refute owns the worker pool, so it owns delivering one whole message at a time. Private.emit serializes every emission reachable from a backend; say and the new warn go through it, as do the model finder's four direct HOL_MESG/HOL_WARNING calls. Refute_Forl keeps its own HOL_WARNING: it depends on no other Refute module, and routing it here would invert that layering for a Kodkodi error path. Measured: an unsynchronized ref-accumulating consumer lost a line in 3 of 15 trials, a mutex-protected one in 0 of 15; the pin then passed 12 of 12 unchanged, so it needed no adjustment. Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
Refute_Gen.narrowing_terms indexes a numeric kind's values by position and flat_shape makes that position the alternative id, so an id denotes the same value at every depth -- the invariant primitive_value already relies on for the conversion side. The reconstruction side tabulated it instead, one arm per (depth, id). For a char carrier that is 256 arms per depth, 2816 across the default 0-10 window, twice over for the replay reconstruction; flat shapes then spelled out another 256 nullary alternatives per depth. The generated program reached ~410KB and took 17s to compile inside a 10s budget, holding the global compile lock while exhaustive and random -- 0.07s each on the same goal -- never ran. Key those arms on the id alone. Only a custom enumerator may hand back a different list per depth, so only it keeps the depth in the key. Flat all-nullary shapes emit a List.tabulate of the identical list rather than one line per value, keeping the program proportional to the window rather than to the carrier. Measured on examples/refuteExample07's string goal: 17.01s to 0.81s, and repeated calls no longer degrade -- a second call previously exceeded 60s. !s:string. s <> "x" now refutes in ~1s where it never finished in 60s. The example's counterexample went from 20-30% missed to 20 of 20 inside the default budget. Regression pin: "narrowing refutes char and string goals". Ablated by reverting this file, both goals time out at the 10s default, 4 of 4. Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
Quality-only pass, no intended behaviour change.
Four structural changes:
- [certainty_ceiling] becomes a field on the [backend] record.
[backend_registration], [register_backend_with_ceiling] and the
dual path through [resolve_backend_registrations] are gone; there
is one registration entry point again.
- Everything a backend worker emits now goes through
[Refute_Core.Private.emit]. That invariant was stated in a
comment but three sites in Refute_ModelFinder_Model and the mono
tracer called [HOL_MESG] directly. [MFMono.trace_msg] holds the
gate and the emission together and is exported so that
Refute_ModelFinder reports through it too.
- Depth stability is carried in the shape data, as [depth_stable]
on [Narrowing_sum_of_products], instead of being re-derived by
each consumer; every generator arm states its own answer.
- Refute_Cert_Model's seven [enable_*] policy fields were true at
every construction site, so they are deleted and their thirteen
read sites folded. [cases_allowed] and [induction_allowed] leave
[failed_state] with them: as constants they contributed nothing
to [same_state].
Smaller ones: [cleanup_timeout] is no longer a ref that nothing
assigns; the three copies of [add_reason] are one; the local
[list_compare] in Refute_ModelFinder_Scope no longer shadows
[Portable.list_compare]; Refute_SmartGen's write-only [degradation]
inference is deleted; Refute_Extract memoizes [spec_of] and
[exact_entries_of] per type and dedups entries through a Redblackset
rather than by comparing rendered SML source; [print_wf_cache] and
the scope-frontier trace are behind their trace level; and the
Holmakefile records the Refute_Core edge that holdep cannot see
through the structure alias.
Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
[Refute_ModelFinder_Mono.trace] is a bool ref that nothing in the tree ever assigns, so the calculus's own tracing -- a per-term dump and the final mtype assignment table -- could not be turned on. Delete it, along with what only fed it: [print_mcontext], [resolve_mtype], [resolve_atom], [extract_assigns], [annotation_from_bools] and [association_defined]. With the assignment table unread, [solve] answers satisfiability directly instead of extracting one annotation per variable up to [max_fresh] on every successful monotonicity check. The driver's abandonment diagnostic is not debris and stays: it moves to [Refute_Core.Private.say 2], which is where the rest of the model finder reports and what the deviation comment in [consider_definitional_axiom] already claims happens. Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
[Refute_EvalSML.update_term], [Refute_Narrow.update_function] and [Refute_ModelFinder_Model.make_update] were three spellings of [Term.mk_comb (combinSyntax.mk_update (point, value), base)], and [fun_term] inlined a fourth. The definition moves to [Refute_Util], which is the one module all three layers sit above; [Refute_ModelFinder_Util] re-exports it like the other shared term helpers, and [Refute_EvalSML] keeps the name because generated extraction code calls it by that path. Refute_Util gains combinSyntax, noted in its header: still no Refute-layer dependency, which is what that charter is about. Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
The global compset has no rule for = at :'a |-> 'b (finite_mapLib's
fragment covers FLOOKUP/FDOM/FUNION/DOMSUB only), so computeLib reduced
fm = fm' just for literally identical chains and left every other pair
stuck. A Test whose conclusion is such an equality could therefore
never yield a genuine counterexample on Compute: each premise-satisfying
draw surfaced as "evaluation stuck during testing", including pairs of
permuted chains denoting the same map, which display identically.
Refute_EvalFmap hangs FMAP_EQ_DECIDE_CONV on the shared ("=", "min")
key, guarded by an fmap type test, like Refute_EvalRat's rat equality.
F comes from a key where computeLib decides the two FLOOKUPs unequal and
congruence under f = g; T from proving both chains equal to a
sorted-dedup canonical chain via FUPDATE_EQ / FUPDATE_COMMUTES. Every
step is a proof, so undecidable cases still fail through and stay stuck.
Certification uses the same compset, so such hits now certify too.
Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
Thread_Attributes.uninterruptible clears the broadcast flag, so a Ctrl-C arriving inside a masked region is dropped outright rather than held. Three windows in ParList were affected: the fork/record pair, the mutex acquisition inside Multithreading.synchronized, and the mandatory join. uninterruptible_defer masks with Thread.InterruptDefer instead, leaving the broadcast flag as the caller had it, so an interrupt landing in the window is delivered once the caller's attributes come back. with_lock is synchronized without the tracing, built on the same primitive. stop_threads now closes the job queue before interrupting the workers: a job that answers its interrupt with `handle _` then costs the caller one job rather than all the work that remains. Directed-interrupt tests cannot see any of this -- directed interrupts are retained across a plain mask, only broadcasts are lost -- so the new selftest.sml drives the windows through the ParList_Test hooks. Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
Fixes for the confirmed and verified-plausible findings of the final
review. Each is ablation-validated: the mechanism is removed and the
named pin goes red, and red only there.
* Refute_QC: an expired search no longer reports completeness.
search_expired doubles as the sampling loop's termination condition,
so the veto is taken at the point the loop already tests.
* Refute_Cert_Narrow: a witness hole no longer diverts the existential
case split; cover_variable spends from cover_budget instead.
* Refute_EvalEnum: quiet_theory_work restores the message flags on the
raising path as well as the normal one.
* Refute_ModelFinder_HOL: a refute_simp clause is keyed on the head
constant under a negation. strip_imp_only reaches the head that a
conclusion read as [~c = T] hides behind bool$~.
* Refute_ModelFinder_Preproc: a binder shadowing an outer variable is
not a static argument.
* Refute_Extract: definition_theorem recovers a constant's equations
from its home theory when a descendant's specification has shadowed
the DefnBase entry -- pred_set's GSPECIFICATION hides bool$IN.
Three pins that this pass rewrote were unable to discriminate as
written: two asserted results unreachable for their goals, and one read
its baseline from the ambient global state the defect corrupts upstream.
They now set their own baseline and assert where the worlds diverge.
The Cv substrate is deleted. NativeSML and Compute between them cover
every plan it compiled, so it earned no selection of its own; with it
gone the cv_compute automation changes it required revert to develop.
Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
Nothing in the library called Refute from inside a running search: Refute_Unused drives its probes from the top level, and the only exerciser was a selftest stub. The capability nevertheless cost three mechanisms, because a backend that re-entered would compile a second substrate test inside the enclosing call's open theory bracket and then wait on a theory lock its own caller holds. refute_problem now refuses such a call where the caller still has a stack to report it on, rather than supporting it. The context token already reaches backend workers, so the existing test catches them. With that settled the theory bracket has at most one holder, and bracket_depth, bracket_owner and holds_theory_bracket go: a second open waits on theory_lock exactly as another thread's would. The comment that justified re-entrancy described Cv opening its bracket while compiling, which stopped being true when Cv was deleted. The selftest pin is inverted rather than dropped, and is validated by ablation: restoring the old `run ()` branch reddens it and nothing else. register_backend's reference entry and the Description manual gain the restriction, since backend authors are the ones it binds. Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
push_quantifiers_inward orders a cluster of like binders by a cost model: quantifying a variable multiplies the combined size of the components mentioning it by that variable's domain cardinality. The cost table was reversed relative to the list it was indexed by, so each binder was charged its mirror's cardinality. This is faithful to nitpick_preproc.ML, where T_costs is built innermost-first (de Bruijn order) but indexed by the outermost-first bound number that `flip` produces. It is a deliberate divergence from upstream, not an accidental one: the ordering is a heuristic over logically equivalent formulas, so no answer changes either way, but a search running under a deadline turns a bigger encoding into Unknown where a smaller one would have found a model. On a size-balanced cluster the mirrored index picks the expensive binder for the outermost position -- the worst available choice, and symmetrically so, whichever binder is the expensive one. The table now holds cardinalities rather than types, so typical_card_of_type runs once per binder instead of once per query inside the n! * n permutation search. Upstream precomputes this too; the port lost it when it moved from de Bruijn indices to terms. Also split gather into a linear gather_rev plus one reverse, rather than appending a singleton at every binder. Claude-Session: https://claude.ai/code/session_01Lj7exoafZqUZjGQnskC47w
Correct the Description chapter, the Docfiles entries and the README against the source, and trim prose that carried no information. Corrections: sequential runs split the remaining deadline among the backends not yet started; the certainty ceiling ends a sequential search too; smart generators serve exhaustive testing only; an upd_instantiate pin with type variables is rejected by the updater, not reported as inconclusive; merge_type_vars merges into the alphabetically first variable; every word constant outside the encoded tier is refused, not a named list; sat_solver "smart" prefers a configured native or external solver over SAT4J; four term postprocessors are built in, not two; harvest_registrations sweeps the ancestry plus the current theory; export_refute_psimp does not require conditional equations, export_refute_unfold applies the same constant-head rule, and the check runs when the tables are built; the Hilbert-choice veto fires on guard insertion; a polymorphic goal only forfeits NoCounterexample; register_generator drops the type's abstract generator; unfolding lives in _HOL, not _Preproc. Claude-Session: https://claude.ai/code/session_017ygKa1f65dGr4Rxpb8x2FA
# Conflicts: # src/IndDef/IndDefLib.sml # src/IndDef/selftest.sml
…docs Every claim about how closely a module follows Isabelle's Nitpick or Quickcheck was checked against the Isabelle2025-2 sources and the kodkodi-1.5.7 component; the false ones are corrected, prose only. Peephole is not a faithful port: three divergences are now named in its header. Mono's "complete M3 calculus" is upstream's monotonicity calculus minus the Id/set-product rules, Pure cases, bounteous_consts and tracing. Nut's "bug-for-bug" FIXME annotated correct code; the deliberate NatToInt bound correction is now marked instead. Two Kodkod "deviation" markers and one in Mono annotated code identical to upstream. The genuine-slot accounting, the two-round harvest cap and the certification promotion are recorded as divergences rather than parity. Core's function-inversion comment denied a recogniser that exists; QC's plan pseudo-code was mis-scoped and dropped a bound subtraction; Preproc's branch stands in for upstream's Free and Var cases; refuteScript's wfrec' port adds a WF guard and a different fallthrough. Narrowing, Forl and ForlSat comments, the README, the manual and the examples README are corrected on solver selection, CNF-file cleanup, JNI platforms, Eps_psimp, the polymorphic-goal limitation, random-function laziness and manual section numbers. Nine citations of selftest pins removed in c8bc036 are dropped, and "Part 7" references now point at Part 6. Claude-Session: https://claude.ai/code/session_01XTaiRGwPRK34zdimMPeEy9
parallel_builds/core kept HolRefute out of the otknl build because it once INCLUDEd num/theories/cv_compute/automation, which that build omits. The Cv substrate and the dependency went in f6ae9ca, so the guard and its comment were stale. Verified with an --otknl build of the tree up to parallel_builds/core plus HolRefute's examples and theory_tests, with only the OpenTheory article export stubbed (the opentheory tool is not installed here). Claude-Session: https://claude.ai/code/session_01XTaiRGwPRK34zdimMPeEy9
# Conflicts: # src/IndDef/IndDefLib.sml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds
src/HolRefute, a counterexample generator for HOL4 goals, built on Poly/ML only. It provides four diagnostic tactics (REFUTE_TAC,QUICKCHECK_TAC,NARROWING_TAC,MODEL_REFUTE_TAC), the SML entry points behind them, a user manual chapter (Manual/Description/Refute.smd), 33 help docfiles, a selftest with level-2 acceptance tables, theory-hygiene tests, and an executable example corpus adapted from Isabelle's Nitpick manual and example suites.The library has four backends: exhaustive and random Quickcheck, native narrowing, and a Kodkod-based finite model finder. Substrates are untrusted accelerators; every counterexample is replayed and, where replay succeeds, turned into a HOL theorem. HolRefute leaves no type, constant, theorem or binding in the user's theory on any path (success, failure, timeout, interrupt); its only ambient effect is teaching the global
EVALcompset closed:ratarithmetic and:realinv.What is ported from Isabelle/HOL, and how closely
Model finder: a port of Nitpick
The model finder is a module-for-module port of Nitpick (Isabelle2025-2,
src/HOL/Tools/Nitpick). The pipeline, the intermediate languages and the trust model are Nitpick's; the adaptation is concentrated in the HOL-facing layer.nitpick_preproc.MLRefute_ModelFinder_Preprocconjuncts_oforder, axiom-side skolemisation depth).nitpick_mono.MLRefute_ModelFinder_MonoIdand set products stay unfolded (no nut builtin), the Pure meta-connective cases,bounteous_constsand tracing are not ported, andis_harmless_axiomis narrowed.nitpick_scope.MLRefute_ModelFinder_Scopenitpick_rep.MLRefute_ModelFinder_ReprelationTheory) rather than sets of pairs, and the application boundaries are kept.nitpick_nut.MLRefute_ModelFinder_NutBoundNameconvention. Thenutdatatype and its operator enumerations are unchanged;cstgains the word and char operations. OneNatToInttotality bound is deliberately corrected against upstream and marked in the code.nitpick_kodkod.MLRefute_ModelFinder_KodkodNumof a negative integer, curried relations).nitpick_peephole.MLRefute_ModelFinder_Peepholes_all/s_existon an empty declaration,rel_expr_intersectsonAtomSeq,s_join'sUnivclauses) plus the HOL4-side bit-width choice.nitpick_model.MLRefute_ModelFinder_Modelnitpick_hol.MLRefute_ModelFinder_HOLsimps; typedef, quotient and codatatype registries and the ersatz table are re-derived from HOL4 theory data.nitpick.ML(pick_them_nits_in_term)Refute_ModelFindermerged_type_var_table_for_terms, since HOL4 has no sorts.kodkod.MLRefute_Forlkodkodi-1.5.7component, without Isabelle's launcher.kodkod_sat.MLRefute_ForlSat*_HOMEvariables.prop_logic.ML, cdclite part ofsat_solver.MLRefute_PropSatNitpick.thyrefuteScript.smlunknown,wf',wfrec',card',sum',safe_The), with fmap rows added.nitpick_commands.MLconfigrecord withupd_*updaters andREFUTE_TAC_WITH, not Isar syntax.The verdict model is Nitpick's:
Genuine/QuasiGenuine/Potentialis decided by the encoding,max_potentialis charged as in Nitpick (only a genuine model spends amax_genuineslot, where Nitpick's sound branch also charges quasi-genuine ones), and Refute trusts Kodkodi exactly as Nitpick trusts Kodkod (aGenuineverdict with no certificate is valid). Deliberate deviations, all documented in the README and manual:if ?x. P x then @P else unknownwhere Isabelle leaves the occurrence unguarded and constrains it only through the witness-conditionalEps_psimp; a guard vetoesNoCounterexample.NoCounterexampleis a total claim. Anything bounds-relative reportsUnknownwith a reason, and a value-positionunknownin any harvested axiom vetoes it.:charare exact native carriers (2^wand 256 atoms), which turn smart binarization off. Nitpick has no counterpart.realis rational-valued, as in Nitpick. Polymorphic goals are tested at configured monomorphic instances and never earnNoCounterexample, where Nitpick varies the type variables' cardinalities like any other type.sat_solver = "smart"prefers a configured JNI or external solver; Nitpick's smart order never picks a JNI solver by itself.ARBin the raw definition; only the user's equations are harvested, so the same spurious-counterexample risk as Nitpick applies.Quickcheck: design ported, execution re-engineered
exhaustive_generators.ML(theRefute_QCcomment cites the upstream lines). Smart generators follow Isabelle's predicate-compiler design:Refute_SmartGenmode-checks Horn SCCs for positive and negated first-order modes and flattens function equations into graph clauses;upd_allow_function_inversiondefaults off like Isabelle's flag, andupd_use_subtypematchesuse_subtype.random_fun_liftdoes, but eagerly rather than lazily.Refute_Eval.plan) that two substrates run: in-process SML extraction (Refute_Extract) andcomputeLib. Both consume one PRNG in the same order, so a seed reproduces the candidate stream on either.abstract_generators.MLbecomesabstract_generator;find_unused_assms.MLbecomesRefute_Unused(check_/find_/print_unused_assms), with the same maximal-droppable-set semantics.quickcheck_common.MLis not ported: budgets, iterative size deepening, the backend pool and certainty ceilings areRefute_Core's own.Narrowing: generators ported, engine native
The type representation (
Narrowing_sum_of_products),finitize_functionsand the quantifier-pulling pass are ported fromnarrowing_generators.ML. The Haskell engines (Narrowing_Engine.hs,PNF_Narrowing_Engine.hs) are replaced by a native SML narrowing engine (Refute_Narrow,Refute_QC_Narrow), so no Haskell toolchain is needed. Finite function inputs areffunupdate chains defined inrefuteScript.smlinstead of Isabelle's type-scopedConstantnames.HOL4-only additions with no Isabelle counterpart
Refute_Cert,Refute_Cert_Narrow,Refute_Cert_Model): QC hits are replayed withcomputeLib, narrowing replays its case tree, and model-finder values are replayed through Skolem provenance, bounded synthesis, one-layer cases, single-property induction, Presburger andREAL_ARITH. Replay is fail-closed and never weakens the encoding's certainty.ParList, below).theory_tests/checks that a descendant theory inherits nothing.register_generator_family, fmap),Refute_EvalFmapfor ground finite-map equality, and the rat/real compsets.Changes outside
src/HolRefutesrc/portableML/poly/concurrent/ParList.{sig,sml},selftest.sml,Holmakefile(new)A small racing/mapping combinator library over Poly/ML threads:
map_with_workers,get_some_with_workers,get_some,get_first, anduninterruptible_wait.Refute_Coreruns backend admission and the backend race through it (map_with_workersfor admission,get_some_with_workersfor the concurrent pool,get_firstforupd_sequential).Refute_QCusesuninterruptible_waitso the theory-revert cleanup after an interrupted run still completes.Multithreading,Future,Timeoutand nowParListare built by the kernel band with--poly_not_holand linked into sigobj. Naming this directory in HolRefute'sINCLUDESwould rebuild it with the overlay in scope and create anSref -> Overlaydependency that breaks the next kernel bootstrap insrc/bool(documented in HolRefute's Holmakefile).Thread_Attributes.uninterruptibleclears the broadcast flag, and HOL's REPL delivers Ctrl-C asThread.broadcastInterrupt, which Poly/ML drops rather than defers for a thread not accepting broadcasts. A masked join therefore swallowed Ctrl-C.ParListmasks withInterruptDeferinstead and keeps joins observant. Workers are interrupted, neverThread.killed, since a killed worker can leak the process-global theory mutex. Directed-interrupt tests cannot see any of this, soselftest.smldrives the masked windows throughParList_Testhooks. The Holmakefile change only wires that selftest in underHOLSELFTESTLEVEL.src/IndDef:KeyedThmSetand the[coinduction]attributeKeyedThmSet.{sig,sml}(new). TheThmSetData-backed "theorems keyed by the constants their clauses talk about" machinery thatIndDefLibimplemented inline forrule_inductionis extracted into a factory parameterised by which clause part carries the key.IndDefLibnow instantiates it withHypothesis; the exported names, set type and map type are unchanged, so nothing downstream changes. What does change: malformed theorems get a diagnostic naming the set type and the part inspected, and non-implicational theorems are diagnosed instead of escaping as a baredest_impfailure.CoIndDefLib. Instantiates the same factory withConclusion, exportingcoinduction_map,thy_coinductions,add_coinduction,export_coinduction, and registers thecoinductiontheorem attribute.Hol_corelnnow exports its_coindtheorem automatically, asHol_relnalready did for_strongind.Refute_ModelFinder_HOLanswers that fromIndDefLib.rule_induction_mapandCoIndDefLib.coinduction_map(is_registered_gfp,registered_stem). Without a persistent coinduction registry, coinductive relations could only be recognised in the session that defined them.selftest.sml,Holmakefile. Tests for both key parts' diagnostics, the non-implicational case, the stored-attribute path, and a persisted well-formed export. The Holmakefile addsKeyedThmSet.uoto the selftest link line.Manual/Description/modern-syntax.smd. Documentscoinductionin the theorem-attribute table.src/coalgebras/pathScript.sml,selftest.sml,HolmakefilepathTheoryhad constructors,path_cases, injectivity and distinctness theorems but no case constant and noTypeBaseentry. This addspath_casewith itscompute/simpequations,case_cong,case_eq,case_elim, acaseoverload socase p of stopped_at x => ... | pcons x r q => ...parses, and aTypeBaseregistration usingpath_bisimulationas the induction principle.llist,ltree,itree,itreeTau,lbtree,path) validates each entry through its case constant andTypeBase.constructors_of;pathcould not be registered without both. The selftest's model-finder table exercisespathinjectivity, distinctness and bisimulation.pathnow behaves like the other coalgebraic types undercasesyntax,EVALand the simplifier. The coalgebras selftest gains simp andcase-syntax checks and aTypeBaseregistration check.path_11stays local since its two conjuncts are already exported.src/parallel_builds/core/HolmakefileAdds
../../HolRefutetoINCLUDESunderPOLY, for every kernel. This is what pulls HolRefute intobin/build; no sequence file changes. HolRefute builds under--otknlas well as the standard kernel.AGENTS.md(new symlink toCLAUDE.md)Lets agent tooling that reads
AGENTS.mdpick up the existing project notes. No content.Manual/Description,help/DocfilesA new
Refutechapter (wired intochapters.txt,libraries.smd, and the generated-file.gitignore), and 33Refute.*docfiles for the tactics, entry points, registries,config, and the unused-assumption probes.Testing
HOLSELFTESTLEVEL=2 Holmakeinsrc/HolRefuteis the quality gate: the selftest (level 2 adds cross-substrate conformance, the narrowing table, the corpus and the model-finder acceptance tables), thentheory_tests/. Model-finder rows needHOL4_KODKODIpointing at an unpackedkodkodi-1.5.7and a Java runtime; without it they report inconclusive and the theory scripts still build.Holmake examplesinsrc/HolRefutebuilds the example corpus.src/IndDef,src/coalgebrasandsrc/portableML/poly/concurrentselftests cover the changes described above.