Skip to content

Formal: converge the Quint specification on one kernel model with profile instances #165

Description

@lan17

Summary

The Quint specification should converge on one kernel library that states every portable DialCache rule exactly once, with every conformance profile written as a composition of that library over the profile's own bounded state. The goal is a formal base that covers all behavior and features of the TypeScript implementation, is easy to update when behavior changes, and keeps validating ports through the same replay corpus, drivers and mutation evidence as today. This is the third version of this issue. The first two described the kernel as a single state machine that every profile instantiates and that generates the corpus; the two-profile pilot (#171, #172) showed that shape is achievable for behavior but not for generation cost, and the review recorded in the comments below produced this version. The evidence guarantees are unchanged.

Where the specification is today

The boundary between the specification and the implementations works. Quint generates histories; the shared coordinator hands drivers fixtures and external commands and never its predictions; drivers report observations; the coordinator asserts them. Witness classification, observation validation and the mutation catalog are shared and language neutral. A port supplies a native implementation, a controlled test environment, a driver and a transport.

The boundary inside the specification does not. Behavior is defined more than once:

Model kind Files Lines Role
Conformance profiles 15 6,492 Generate histories; each is a bounded re-implementation of the cache with integer encodings
Verification models 7 2,760 Readable sum-typed slices with the semantic invariants
Rule checks 1 61 Finite symbolic checks over cache-rules; the only Apalache target
Connection monitors 5 294 Check that a profile's captured records agree with cache-contract
Wire vector models 4 1,033 Deterministic transforms (keys, frames, envelopes, invalidation)
Shared libraries 5 256 cache-rules, cache-contract, observation encodings, wire text, cohort boundaries
Kernel pilot 5 1,374 One kernel of pure transitions with layers and effects views (#171, #172)

cache-rules.qnt fixed the worst duplication: 18 files import the age, expiry, deadline and fence judgments instead of restating them. But a profile still restates ownership, publication authority, request memo lifetime, flight registration and diagnostics in its own transitions; a verification model states the same rule a third time; the connection monitors reconstruct ownership and timing facts independently and check the profile against them; and witness classification lives in 25 JavaScript modules (2,276 lines) that read private trace fields to decide whether a history reached a boundary. A behavior change touches a verification model, one or more profiles, their receipt invariants, a connection monitor and a witness classifier.

What the pilot established

The pilot wrote one kernel of pure transitions and expressed the layers and effects profiles as instances of it. It settled the design risk the previous version named as the one that should stop the migration: one kernel with one settlement constant reproduces both the layers boundary (adapter completions inside the call) and the effects boundary (held gates) step for step on twelve fixed histories, both ports replay the result unchanged, and a dependency-following lint proves the views assign no kernel state and that witness state feeds no transition.

It also measured what instantiation costs, and the cost is structural. Quint records every state variable in every ITF state and has no projection flag, so a view exports the whole kernel record: 2.7 to 2.8 times the original's bytes per state, 61 to 83 percent of them the kernel state the view mostly never touches. Under the generation lane's own command (512 traces per profile) both views exhaust Node's default heap before writing a trace; with a 12 GB heap the effects view completes at 2.3 times the original's wall time, 2.75 times its bytes and 7 GB of memory. Sampling cost after the witness monitor was made incremental is 1.0 to 1.2 times the original for layers and 1.6 to 1.8 for effects with each model's own invariants, but that pairing compares nine original invariants against five kernel ones; without invariants the views cost 1.9 and 3.5 times, and the effects view's transitions alone cost about double a 200-line profile because they carry request, local and diagnostic state that profile never had. Every further feature the kernel absorbs (recovery, shadow, admission, policy) would grow the record every view exports. The pilot's own report records generation parity as unmet.

Two other results shape this version. The witness monitor written in Quint computed eleven labels at 13 to 15 microseconds per step and about 23 lines of Quint plus controls per label, against about 5 lines per label in today's JavaScript classifiers, and none of its labels were among the 410 required ones; moving 410 labels into a per-step monitor would tax every sampled step of every profile. And a review of the goal found that what a porter consumes is the coordinator protocol, the contract table, SPEC.md and the corpus, not Quint module structure: the kernel's value is single authority for maintainers and for resolving ambiguity, not a porter reading path.

What the contract is

Kernel initialization and transitions, together with explicit environment assumptions, define allowed behavior. Independent properties, exported regressions and witnesses check that behavior; they do not by themselves define every permitted execution. Progress is stated as bounded-progress obligations checked on every explored state, never as eventual completion: a completion witness establishes one successful execution, and a bounded-progress invariant establishes completion within a declared budget for every eligible call the simulation reached. Each such obligation says what makes a call eligible, when its step budget starts, which scheduling steps count, and which environment assumptions hold. A disabled caller deadline alone does not exclude a call whose source work has settled. Fixtures with intentionally unbounded source work are excluded by name. A call that becomes eligible near the end of a sampled trace may have too few remaining steps to test its budget; a passing invariant then establishes only that no eligible call exceeded its budget while incomplete. Such an obligation is reported as inconclusive because the trace ended, the inconclusive count is part of the diversity report, and it is distinct from a discharged obligation.

Target architecture: one library, composed profiles

1. A kernel library states every portable rule once. The library is a set of concern modules, each with its types and pure transitions: flights and ownership (registration, coalescing, joining, completion of owned callers), request memo and scope lifetime, local storage (insert, LRU order, capacity eviction, expiry), remote frames and watermarks (fences, tracked invalidation), deadlines and budgets (read and source), publication and refill authority, stale recovery, shadow jobs, runtime policy capture, admission, and diagnostics. cache-rules stays the judgment layer under them. Each module owns a record type for its part of the state and transitions of the form pure def f(state: T, inputs...): T or a record of results. Success is measured by whether a behavior change has one authoritative implementation, not by a line count; the exact module boundaries are fixed by the first migration slice from a concern map of the fifteen profiles.

2. Profiles compose the library over their own bounded state. A profile declares only the state its fixtures exercise (a profile without a local layer has no local storage variable), owns the nondet choice of inputs, restricts the environment, and projects its observations. Every assignment in a profile is the result of a library transition; a profile contains no rule logic of its own. The public action vocabulary the drivers are keyed on (mbt::actionTaken, mbt::nondetPicks with { choice }) is unchanged: the profile's actions remain the wrappers. Traces keep their current shape and size because the profile exports only its own state.

3. The settlement boundary is an explicit environment restriction, as before. The pilot's design stands: fine-grained effect steps in the library, and each profile states which external responses are immediately available and which remain held. Ownership, publication authority, timeout decisions and accepted values are governed by the library's transitions. Draining ready work must not silently advance deadline time, release unresolved gates, or discard observable effects. Effect ordering and clock capture happen at the internal step where the event occurs. The projection collapses auto-settled internal steps into one observation per public input and never manufactures an outcome.

4. Witnesses are labels computed from public inputs and projected observations, in the shared JavaScript evaluator. The rule is unchanged: a label may depend only on the input history and the observations the drivers assert, so credit can never depend on state no driver checks. The mechanism changes: the evaluator already classifies once per corpus, carries per-label provenance and is consumed by the Go port by hash, and it costs no sampled step and no trace bytes. Classifiers that read private trace fields today are rewritten to read inputs and public observations only, and a test enforces that they read nothing else; labels that depended on private storage state (LRU order, memo contents) are redefined around a later public probe read as their checkpoint. Each label keeps a positive regression and a distinguishing negative. The Quint witness monitor from the pilot is deleted.

5. Independent checks survive; duplicate transition logic does not. The connection monitors reconstruct source starts, settlement receipts, fence acquisition and clock capture from external inputs independently of the library's records and check the composed profiles against them, one monitor per concern. Verification models fold into module lemmas where they restate a rule the library now owns, are kept where they state a property nothing else states, and are deleted when they do neither. The rule-checks model stays as the symbolic boundary check over cache-rules.

6. Wire vector models stay separate. Key encoding, frames, envelopes and invalidation transitions are deterministic transforms and are correctly specified as vectors.

7. The implementation is the only second implementation. Drivers, the transport, the driver-facing coordinator contract, the completion gates and the trace shape are unchanged. A port is validated exactly as today: it replays the corpus, passes the witness gates, and its mutation lane shows the corpus would catch its mistakes.

8. Coverage is complete and stated. Every behavioral feature and option of the TypeScript implementation that a port must reproduce maps to contract IDs and to the profiles that exercise it, or is recorded as a wire vector or as intentionally out of scope (non-portable, such as metrics adapters). The map is produced by the first slice and maintained as a validated table; a feature without a profile is a coverage gap to close, not a footnote.

Why composition rather than instantiation

Instantiation removes every duplicated line, including the profile's state declaration and action wiring, at the price the pilot measured: every profile exports and updates every feature's state. Composition leaves that wiring in each profile (a few dozen lines of declarations and wrapper actions that call library transitions) and removes only the rules, which is where the duplication that costs maintainers lives. It keeps trace sizes, generation cost, replay cost and the trace readers unchanged, and it lets each profile migrate in place: the rewritten profile replays its own preserved corpus against the old text before the old text is deleted in the same change. That is the "not fully DRY" the owner accepted in exchange for a base that fits the lane and can be updated one module at a time.

What this is not

It is not one model whose step enables every action over every domain. The bounded profiles exist for a good reason and remain. It is not a porter reading path: porters consume the contracts, the specification prose, the coordinator and the corpus; the library is the maintainers' single authority and the place ambiguity in the prose is resolved. It is not a change to how ports are validated.

Evidence guarantees

These are unchanged from the previous version and none depends on the library.

1. Per-label witness provenance. Done (#170): every credited label records its trace identity, whether the trace is sampled or a named regression, and the public checkpoint steps; sampled and regression counts are reported separately.

2. A deterministic reproducer for each new challenge. Done for new challenges (#170); 60 existing challenges remain in a reported backlog and are backfilled as their models are touched.

3. Fault mapping by meaning. Not started. Every model challenge maps to relevant native mutation evidence in both ports, or carries a precise model-only or not-applicable explanation, in a validated many-to-many table; today 67 model challenges stand against 13 native mutants per port, a mapping backlog rather than a measure of strength.

4. Witness-to-failure linkage at the distinguishing checkpoint. Not started. The chain is contract, relevant fault, distinguishing history, failure at the observable checkpoint that establishes the consequence, joined over the provenance from guarantee 1.

5. Fault coupling as a report. Not started. Each catalog fault is injected once in the library module that owns the rule and the detecting profiles are listed; a profile that reaches the rule but does not detect the fault is a gap to investigate, not a gate.

6. Structural enforcement of the thin-profile rule and witness isolation. Done for today's shape (#170); the thin-profile rule changes with this version: a profile may assign its own state, but the assigned values must come from library transitions, and no profile definition may contain rule logic (see tooling). Witness isolation moves to the evaluator: a test that classifiers read only inputs and public observations.

7. Single-authority citations. Not started. Every contract ID cites exactly one authoritative definition in semantic-cases.json: a library module definition for behavioral contracts, or a definition in an explicitly scoped wire module for the wire contracts. Monitors and lemmas are cited as independent checks and tagged as such.

8. Corpus diversity signal. Done (#170): distinct action and observation sequences and per-label hit counts, gated against a recorded baseline.

9. Fresh-seed exploration stays in the aggregate. Done (#164).

Tooling changes

  • Corpus differential. The pilot runner's history comparison becomes the migration tool: a generated scheduler replays a profile's preserved sampled histories and exported regressions through the rewritten profile in a few Quint processes and compares the driver-asserted observation at every step with the original's trace. It runs in the pull request that rewrites the profile and on later pull requests that touch the library, path filtered.
  • Lint. The dependency-following lint keeps its resolved-reference walk and gains the composition rule: within a profile module, every state assignment's value must resolve to a library transition or a field of one, and no profile definition may branch on cache semantics (the walk reports any comparison or arithmetic over library record fields outside a wrapper's input choice). Its baseline over today's profiles is the migration work list.
  • Deletions from the pilot. The layers and effects views, the Quint witness monitor and its controls, the frozen-monitor and generation-parity measurements, and the kernel-pilot workflow job are deleted as the first slice lands; the pilot kernel's pure transitions seed the library modules and the pilot README's measurements move into this issue's record.
  • Trace readers and fixtures are unchanged. Profiles keep plain s and input variables and today's integer encodings until a profile's own change replaces them; variant encodings ({ tag, value }) are introduced only where a profile's observation schema changes with them.
  • Docs. SPEC.md and PORTING.md cite library definitions as the authority behind each contract; the reading path for porters stays the contracts, the specification prose and the coordinator. Docs: language-neutral behavior pages, generated per-language references, and executed examples #166 generates behavior pages from the semantic cases and library definitions.

Sequence

  1. Update this issue (this version).
  2. Concern map and coverage audit: map every profile's state fields, public actions, rule logic and witness private reads onto concerns; audit the TypeScript feature surface against contracts and profiles; record the module boundaries, the profile migration order grouped by the modules they compose, and the coverage gaps. No code changes.
  3. Library extraction and the first in-place rewrite: turn the pilot kernel's transitions into concern modules; rewrite the layers profile in place as a composition; replay its preserved corpus and regressions through the rewrite against the old text; delete the pilot views, the Quint monitor and the parity measurement; land the corpus differential and the composition lint. This slice fixes the module boundaries.
  4. Evidence guarantees 3 and 4 in parallel with step 3, and the reproducer backlog for the profiles being rewritten.
  5. Rewrite the remaining profiles in place, grouped by the modules they compose (lifecycle: effects, core, scope, independent, source-budgets, local-clock; then recovery and recovery-read; then policy, runtime-boundaries, admission, local-failure; then shadow and shadow-layers), each with the corpus differential, the composition lint clean, its verification model folded or kept by the rule above, its connection monitor retargeted, and its witness classifiers made public-only. Close coverage gaps found in step 2 as the module that owns the feature lands.
  6. Guarantees 5 and 7 as the modules stabilize; SPEC.md, PORTING.md and README.md updated to cite the library; Docs: language-neutral behavior pages, generated per-language references, and executed examples #166.

Per-profile migration criteria

A profile rewrite lands only when:

  • Every sampled history and every exported regression of the profile's current corpus replays through the rewritten profile with step-by-step agreement on the driver-asserted observation, in the same job. Any disagreement is resolved in the library, never by adjusting a projection or patching the rewrite to match the old text.
  • The composition lint reports no rule logic in the profile.
  • The profile carries every invariant it had, or a written equivalence argument per dropped one, and its fault challenges still fail at their declared steps.
  • Both ports replay the regenerated corpus and the witness gates pass with the profile's baseline within tolerance.
  • The profile's generation time in the same job is within 1.5 times the old text's, and its trace bytes per state within 1.2 times.
  • The old text is deleted in the same change.

Risks and how to see them early

  • Wiring drifts into rule logic. The composition lint is the guard; its report is reviewed on every profile pull request.
  • Module boundaries are wrong. The first slice is the test: if the layers rewrite needs a transition that crosses two modules' records repeatedly, the boundary moves before a second profile is written.
  • Public-only witnesses lose sampled hits. Labels redefined around a probe read may be reached less often by random sampling; the diversity baseline shows it, and a named regression pins the label until the profile's schedule is adjusted.
  • A library change breaks a profile it does not exercise. The corpus differential runs on library-touching pull requests for every migrated profile.
  • Stop rule. If two consecutive profile rewrites exceed 1.5 times the old text's generation time or need library changes that grow other profiles' state, the approach is re-examined here before a third.

Acceptance criteria

  • A kernel library under formal/ of concern modules with sum-typed state and every portable rule stated once; cache-rules imported, not restated. Every behavioral contract ID cites one authoritative library definition in semantic-cases.json; wire contracts cite an explicitly scoped wire module.
  • Every conformance profile declares only its own bounded state, owns its input choice and environment restriction, and assigns state only through library transitions; the composition lint reports no rule logic and no witness feedback in any profile.
  • Every required witness label is computed by the shared evaluator from inputs and projected observations only, with a positive regression and a distinguishing negative; a test proves the classifiers read nothing else.
  • Witness evidence carries per-label provenance, diversity counts and a baseline gate (done).
  • Every model challenge carries a checked mapping and a deterministic reproducer or is in the reported backlog; every challenge maps to native evidence in both ports or carries a precise explanation.
  • The connection monitors are library-facing, one per concern, with their challenges intact. Rule checks and the wire vector models remain.
  • A validated coverage table maps every TypeScript behavioral feature and option to contract IDs and profiles, or records it as a wire vector or out of scope; no feature is unmapped.
  • Bounded-progress obligations are stated with eligibility, budget start, counted steps and assumptions, hold on every explored state, and report inconclusive obligations separately.
  • Full validation passes: models and regressions, both ports' replay of the complete corpus, all witness gates, both mutation catalogs, exploration on a fresh seed, at today's generation and replay budgets.

Reference points

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions