releases
SolverForge 0.9.x: Manifest-Owned Models and Runtime Tightening
SolverForge 0.9.x makes the scalar/list architecture explicit: planning_model! is the canonical model manifest, scalar metadata is descriptor-addressed, and the patch line tightens scoring and local search.
SolverForge 0.9.x is the runtime line where the model contract becomes manifest-owned and scalar/list terminology becomes explicit. The line starts at 0.9.0 and includes the 0.9.1 runtime patch for indexed existence scoring and local-search startup visibility.
Patch releases are folded into this line note instead of published as separate release-note pages.
This is the release that makes the current SolverForge model contract explicit.
The 0.8 line established retained jobs, snapshots, checkpoints, and lifecycle
events. The 0.9 line tightens the modeling layer underneath that runtime:
non-list planning variables are now consistently scalar, model metadata is
owned by a planning_model! manifest, and scalar construction plus local search
use declared model capabilities instead of proc-macro expansion order or hidden
fallback behavior.
Why this release matters
SolverForge applications increasingly need models that combine scalar and list planning variables: assignment fields, routing lists, sequencing lists, optional slots, retained lifecycle controls, and browser-visible progress all in the same generated service. That only works if the runtime has one deterministic view of the model.
0.9.x moves that responsibility to the model boundary itself:
// src/domain/mod.rs
solverforge::planning_model! {
root = "src/domain";
mod employee;
mod shift;
mod schedule;
pub use employee::Employee;
pub use shift::Shift;
pub use schedule::Schedule;
}
The manifest preserves ordinary Rust files and exports while giving SolverForge a stable model-owned place to validate entities, facts, planning solutions, scalar hooks, list-shadow wiring, and runtime metadata.
For users starting from solverforge-cli, this is already the normal shape.
Current CLI-generated projects target:
solverforge-cli 2.0.0solverforge 0.9.0solverforge-ui 0.6.1solverforge-maps 2.1.3
Run solverforge --version to confirm the exact scaffold targets in your
installed CLI.
What changed
planning_model! is the canonical manifest
Generated retained-runtime domains now require a planning_model! manifest. The
manifest lists the real Rust modules and public exports for the domain, then
SolverForge derives deterministic metadata from those modules.
That means module declaration order is no longer a hidden modeling contract.
Scalar runtime metadata is resolved by descriptor index and variable name, while
the compact generated variable_index remains an internal getter/setter dispatch
detail.
The practical result: split your domain into normal files, export the public
types from src/domain/mod.rs, and let the manifest own the model contract.
scalar is the public name for non-list variables
0.9.x finishes the terminology cleanup around planning-variable shape.
- Use
scalarfor single-value assignment variables. - Use
listfor ordered planning collections. - Treat
mixedas descriptive shorthand for a model or generated app that has both scalar and list variables. It is not a third planning-variable kind. - Do not use
standardas a model-shape word. It is only a demo data size label in current scaffolded projects.
The CLI enforces the variable-kind boundary directly: solverforge generate
variable --kind accepts scalar or list. The runtime still uses mixed-shape
reasoning where a ModelContext contains both variable families, but the public
variable families remain exactly scalar and list.
Scalar hooks are declared on the model
Nearby scalar neighborhoods and sorted scalar construction are explicit model capabilities. If a search or construction policy needs distance or ordering logic, declare that on the scalar planning variable:
#[planning_variable(
value_range = "workers",
allows_unassigned = true,
nearby_value_distance_meter = "worker_value_distance",
nearby_entity_distance_meter = "task_distance",
construction_entity_order_key = "task_priority",
construction_value_order_key = "worker_priority"
)]
pub worker: Option<usize>;
Nearby hooks guide local-search neighborhoods. Construction order keys guide construction-phase ordering. SolverForge evaluates construction order hooks against the live working solution at each construction step, so queue-style, weakest-fit, and strongest-fit heuristics track the current model state instead of a phase-start snapshot.
Construction routing is capability-driven
Construction phases now route through validated capabilities rather than broad special cases.
- Generic
first_fitandcheapest_insertionstay on the canonical scalar/list construction engine when matching list work is present. - Pure scalar construction routes through the descriptor-scalar construction boundary.
- Scalar-only sorted heuristics require the matching scalar construction hooks.
- List-only phases validate their list hook surface before the phase starts.
This gives configuration errors a clearer cause: the model either declares the capability a configured phase needs, or the phase is rejected up front.
Move selectors are cursor-based
Move selectors now use cursor/materialization semantics. open_cursor() yields
stable candidate indices and borrowable candidates, and the runtime materializes
owned moves only for the selected winner.
That matters for cartesian neighborhoods. They need preview-safe child selectors and cannot rely on broad owned-stream helpers as the public selector contract. The result is cleaner ownership, safer previews, and less accidental move allocation in search hot paths.
Selector and tabu correctness is tighter
The 0.9.0 fix set focuses heavily on selector legality and move identity:
- cartesian composites validate legality and stay preview-safe
- scalar nearby metadata matches ruin-recreate legality
- seeded scalar ruin-recreate streams are honored
- tabu signatures normalize self-inverse and undo identities
- acceptor hooks compare candidate moves against the correct undo signatures
- selector limits prune scalar nearby candidates before limiting
The theme is the same as the manifest work: the runtime should reason from one canonical model and one canonical move identity, not from incidental construction details.
Exact usize existence keys use indexed storage
Since 0.9.1, if_exists(...) and if_not_exists(...) keep the same public
constraint-stream shape while exact usize join keys use dense indexed
bookkeeping for direct and flattened existence constraints.
Other key shapes, including Option<usize>, newtype IDs, strings, and composite
keys, keep hashed storage. This makes common route-ID and assignment-ID checks
cheaper without changing application constraint code.
Local-search phase starts show the score
The console now shows the current score when local search starts:
0.002s ▶ Local Search started │ 0hard/-50soft
That score is calculated before local search begins, so the first local-search line shows exactly what construction handed to the search phase.
List ruin skips empty owners
The list-ruin selector samples only entities whose list is non-empty. Empty owners can still receive elements during ordinary list-change, sublist-change, or rebuild-style search; this only removes wasted ruin candidates.
Breaking changes
There are two public breaking areas.
First, generated retained-runtime domains now require planning_model!. If you
maintain a hand-written domain that previously relied only on individual derive
macros, add a domain manifest and export the model types there.
Second, direct solver APIs that build scalar runtime machinery now require
variable-index-aware access metadata. This affects scalar getter/setter
callbacks, ScalarVariableContext::new, scalar move constructors, scalar
selector constructors, and RuinMoveSelector. Most application code goes
through the facade, macros, or CLI scaffold and should not touch these APIs
directly.
Upgrade notes
For new applications:
cargo install solverforge-cli --force
solverforge new my-project
cd my-project
solverforge --version
For existing applications:
- Add or update
src/domain/mod.rsto usesolverforge::planning_model!. - Replace stale variable-kind wording with
scalarorlist; usemixedonly as a description for models or apps that contain both. - Declare nearby and construction hooks on scalar variables when your
solver.tomlpolicy uses those capabilities. - Update custom direct scalar-runtime code to the variable-index-aware APIs.
- Re-run the generated app tests and any retained lifecycle checks.
The latest 0.9.x patch is solverforge 0.9.1:
solverforge = { version = "0.9.1", features = ["serde", "console"] }
The crate metadata for solverforge 0.9.0 requires Rust 1.92. Projects using
the console, serde, decimal, or verbose-logging feature flags should keep
those feature selections explicit in Cargo.toml.
Patch History
| Version | Date | Notes |
|---|---|---|
0.9.1 |
2026-04-26 | Adds indexed exact-usize existence storage, local-search startup score output, and empty-owner filtering for list ruin. |
0.9.0 |
2026-04-24 | Introduces planning_model!, scalar/list public terminology, descriptor-addressed scalar metadata, and capability-driven construction routing. |
The current path
The recommended starting point remains the CLI:
cargo install solverforge-cli --force
solverforge new my-scheduler
cd my-scheduler
solverforge generate fact employee --field skill:String
solverforge generate entity shift --field starts_at:String --field ends_at:String
solverforge generate variable employee_idx --entity Shift --kind scalar --range employees --allows-unassigned
solverforge generate data --size standard
solverforge server
Use the solverforge-cli manual for command details, the current architecture article for the product shape, and the hospital scheduling use case for a complete retained-job application built on the current stack.
SolverForge 0.9.x is the line where that stack becomes stricter in the right place: the model owns its metadata, the runtime reads one canonical scalar/list contract, and generated applications start from the same surface users extend.