Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
d69f832
Add isolated fuzz case evaluation
MesTTo Jul 28, 2026
343237a
Add deterministic MeTTa fuzz kernel
MesTTo Jul 28, 2026
26ad09b
Align collapse result bags with Hyperon
MesTTo Jul 28, 2026
18a738a
Add replayable MeTTa fuzz generators
MesTTo Jul 28, 2026
0eefd81
Classify nondeterministic fuzz properties
MesTTo Jul 28, 2026
5ffdc8c
Add deterministic MeTTa fuzz runner
MesTTo Jul 28, 2026
00a80a1
Add generator-aware decision shrinking
MesTTo Jul 28, 2026
05da44c
Integrate deterministic fuzz shrinking
MesTTo Jul 28, 2026
26dd9df
Preserve fuzz failure evidence
MesTTo Jul 28, 2026
6ab5041
Fix lazy error propagation for NaN arguments
MesTTo Jul 29, 2026
549f52e
Preserve negative zero in MeTTa formatting
MesTTo Jul 29, 2026
038dce0
Add bit-faithful float fuzzing and replay
MesTTo Jul 29, 2026
4ea739c
Add MeTTa-native text generators
MesTTo Jul 29, 2026
e38200e
Validate MeTTa custom generator protocols
MesTTo Jul 29, 2026
ecc2f57
Classify log-enabled? as a world-effect embedded op
MesTTo Jul 29, 2026
d37a8d0
Cache normal-form and table-key verdicts by atom identity
MesTTo Jul 29, 2026
1d7bddf
Restore Prettier formatting on drifted files
MesTTo Jul 29, 2026
4b7131c
Expand continuation scope vars through bindings
MesTTo Jul 29, 2026
16d8d55
Admit signature-typed constructions as inert data
MesTTo Jul 29, 2026
22d50e8
Cache impure-head and NaN scans over shared subtrees
MesTTo Jul 29, 2026
fa8a2f5
Run grammar generation on a kernel machine
MesTTo Jul 29, 2026
1014300
Enumerate exhaustive domains with a choice-vector cursor
MesTTo Jul 29, 2026
cb133fc
Pass state handles opaquely to grounded operations
MesTTo Jul 30, 2026
e7c5b84
Charge fuel per tail transfer and chain past bound variables
MesTTo Jul 30, 2026
7977859
Cap native evaluation nesting, exempting open atoms
MesTTo Jul 30, 2026
814e8f0
Loop compiled impure tail calls through a fuel-budgeted driver
MesTTo Jul 30, 2026
ecc351a
Reject malformed switch arms when generating the fuzz module
MesTTo Jul 30, 2026
9e0da54
Collect user-relation results before they branch in call helpers
MesTTo Jul 30, 2026
1144c7c
Validate every custom generator mode as a single result
MesTTo Jul 30, 2026
be0419c
Try custom shrink choices before descending into children
MesTTo Jul 30, 2026
3f65854
Never accept a shrink candidate that grows the decision tree
MesTTo Jul 30, 2026
d45fc00
Add model-based state machines to the fuzz library
MesTTo Jul 30, 2026
9a2111f
Add bounded state reachability to the fuzz library
MesTTo Jul 30, 2026
29a5101
Stop counting Error payloads as recursive calls for tabling
MesTTo Jul 30, 2026
5b23c1d
Build repeated generators with a flat accumulator
MesTTo Jul 30, 2026
49cf055
Revoke a table whose memo is never read
MesTTo Jul 30, 2026
6bda4ce
Decode public fuzz results into typed outcomes
MesTTo Jul 30, 2026
0dd43b2
Discover declared fuzz tests as data
MesTTo Jul 30, 2026
bee91a8
Bound reachability by state depth, not by expansion rounds
MesTTo Jul 30, 2026
7aceaf5
Run declared suites from the command line with metta fuzz and metta r…
MesTTo Jul 30, 2026
cd77725
Persist counterexamples so a later run replays them first
MesTTo Jul 30, 2026
06fb818
Ship the fuzz package under both scopes, with a README that is run
MesTTo Jul 30, 2026
8798a9d
Document property testing on the website, and check the pages against…
MesTTo Jul 30, 2026
ce1a56f
Decode both values of a failure, and check the hosts run a whole prop…
MesTTo Jul 30, 2026
f457331
Size the exhaustive budget against what a case costs
MesTTo Jul 30, 2026
c884921
Say why the two grounded-key switches stay parallel
MesTTo Jul 30, 2026
2b845b2
Prepare 2.8.0
MesTTo Jul 30, 2026
eb957f6
Point new readers at property testing from the front page
MesTTo Jul 30, 2026
4f5d93c
Stop the runaway-fuel test from asserting how fast the machine is
MesTTo Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,107 @@
# MeTTaScript 2.8.0

A property-testing library written in MeTTa, `collapse` matching current Hyperon, and interpreter work that
makes deep tail recursion finish instead of overflowing.

## Property testing: `@mettascript/fuzz`

State a property that should hold for every input, and the library generates inputs, finds a counterexample,
and shrinks it to the smallest one that still fails:

```metta
!(import! &self fuzz)

(: reverse-involution (-> Atom FuzzProperty))
(= (reverse-involution $xs) (expect-atom-equal (reverse (reverse $xs)) $xs))

!(fuzz-check reverse-involution (gen-list (gen-int -100 100) 0 40) reverse-involution
(fuzz-config (Runs 200)))
```

The policy is MeTTa. Generation, shrinking, the run loop, the state machines, and the search are rewrite
rules in `packages/fuzz/src/metta`, and TypeScript supplies only what a representation needs: a random
source, a structural key over atoms, and a versioned atom codec.

What the library covers:

- Generators as data, from scalars and text through `gen-tuple`, `gen-list`, `gen-map`, `gen-bind`,
`gen-filter`, `gen-recursive`, and `gen-custom`. Because a generator is an atom rather than a function,
the same declaration can be replayed from a seed, shrunk, or enumerated.
- Grammar and type-directed generation, so a generated term is a sentence of a declared language or a
well-typed term rather than a random tree.
- Shrinking under a named order, `mettascript-shrink-v1`, so the smallest form is stable across runs rather
than a function of the seed. The result says whether it reached a local minimum, or stopped early and why.
- Exhaustive checking with a choice-vector cursor. `FuzzExhaustivelyVerified` carries the domain count and
means the whole domain passed. A domain that does not fit the bound is reported as an incomplete run,
never as verified.
- Model-based state machines: describe the system as a model you trust and the real thing you do not, and a
divergence shrinks to the shortest command sequence that separates them.
- Bounded reachability, breadth first, over a transition relation that may return several next states. A
reported witness is replayed from the initial state before it is trusted, and the four answers stay
distinct: a witness, exhaustion of a finite model, nothing found at or below `MaxDepth`, and a cutoff.
- Expectation combinators that carry a tag and details, so a failure says what was wrong rather than
returning `False`.

Every run is a function of its seed. The random source, the shrink order, the replay keys, and the
enumeration order are all named and versioned, so a reported failure reproduces.

## Running suites from the command line

Declare tests as data and let the CLI find them:

```bash
metta fuzz suite.metta # run every (FuzzTest ...) declaration
metta fuzz --exhaustive suite.metta # enumerate each domain instead
metta fuzz --corpus regressions suite.metta # replay stored counterexamples, record new ones
metta reach suite.metta # run every (FuzzReachTest ...) declaration
```

Exit codes make the command a test gate: 0 for a pass or a definitive answer, 1 for a property failure, 2
for invalid input or corrupt stored data, 3 for an incomplete run. Results go to stdout and diagnostics to
stderr, so `--json` prints exactly one document.

Reading a suite runs its declarations, not its `!` queries, so discovery cannot become a way to execute
whatever else a file would have done. `import!` and `register-module!` are kept, because a property defined
in an imported file would be undefined without them.

`--corpus <dir>` keeps found counterexamples as text so a later run replays them before generating anything
new, one file per case named for the digest of its contents. Commit the directory and the failure travels
with the code that caused it. Values are stored through the versioned codec rather than printed, so a
counterexample of `NaN` or `-0.0` comes back exactly, and a corpus file that cannot be read stops the run
instead of quietly dropping a known failure.

## `collapse` returns a plain expression

`collapse` now matches current Hyperon and returns a plain expression of its ordered results. Zero
results produce `()`, one result produces `(value)`, and multiple results produce `(first second ...)`.
The comma symbol is ordinary data when passed to `superpose`.

Programs that compared an empty collapse with `(,)` must compare it with `()`. Programs that first bound
the collapsed expression and then removed its leading comma with `cdr-atom` should use the bound expression
directly. The existing `superpose (cdr-atom (collapse ...))` computed-tuple idiom is unchanged because
`superpose` evaluates that well-typed argument before splitting it.

## Deep recursion finishes instead of overflowing

Tail calls now carry a fuel budget through the interpreter's trampoline and through the compiled impure
driver, and a chain continues past bound variables rather than stopping at the first one. Native evaluation
nesting is capped, with open atoms exempt because an unbound-variable self-expansion mints fresh variables
at constant logical depth and would otherwise branch until memory ran out. Programs that used to report
`StackOverflow` on a deep but flat recursion now complete; a run that genuinely cannot terminate is cut by
fuel and says so.

Tabling learned to stop paying for itself where it does not help. `Error` payloads no longer count as
recursive calls when deciding whether a call is worth tabling, which is what had made the prelude's own
`let*` rule look doubly recursive and got the busiest control construct in the language memoized with keys
that could never hit. On a 400-step accumulator loop that cost 6.53s and 1479MB; it is now 2.53s and 227MB,
with the curve linear and matching the tabling-disabled baseline. A table whose memo goes unread is also
revoked now, per functor, after 256 stored entries with no read. Refusing a memo can only cost time and
never change a result, and the byte-identical differential against untabled evaluation is unchanged.

Smaller engine fixes: negative zero survives formatting, a NaN argument propagates errors lazily like any
other, state handles pass to grounded operations opaquely, and impure-head and NaN scans are cached over
shared subtrees.

# MeTTaScript 2.7.0

Leveled logging you can leave in the code, and two interpreter forms that now carry the types they always
Expand Down
2 changes: 1 addition & 1 deletion compat/browser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/browser",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion compat/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/core",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion compat/das-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/das-client",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion compat/das-gateway/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/das-gateway",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion compat/debug/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/debug",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion compat/edsl/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/edsl",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
43 changes: 43 additions & 0 deletions compat/fuzz/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@metta-ts/fuzz",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"files": [
"dist"
],
"scripts": {
"build": "tsup src/index.ts --format esm --dts --clean"
},
"dependencies": {
"@mettascript/fuzz": "workspace:*"
},
"author": "MesTTo",
"engines": {
"node": ">=20"
},
"sideEffects": true,
"module": "./dist/index.js",
"publishConfig": {
"access": "public"
},
"description": "Compatibility shim for @mettascript/fuzz.",
"repository": {
"type": "git",
"url": "git+https://github.com/MesTTo/MeTTaScript.git",
"directory": "packages/fuzz"
},
"homepage": "https://github.com/MesTTo/MeTTaScript#readme",
"bugs": {
"url": "https://github.com/MesTTo/MeTTaScript/issues"
}
}
5 changes: 5 additions & 0 deletions compat/fuzz/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2026 MesTTo
//
// SPDX-License-Identifier: MIT

export * from "@mettascript/fuzz";
9 changes: 9 additions & 0 deletions compat/fuzz/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"types": ["node"]
}
}
2 changes: 1 addition & 1 deletion compat/grapher/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/grapher",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion compat/hyperon/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/hyperon",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion compat/libraries/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/libraries",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion compat/node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/node",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion compat/prolog/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/prolog",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion compat/py/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@metta-ts/py",
"version": "2.7.0",
"version": "2.8.0",
"license": "MIT",
"type": "module",
"main": "./dist/index.js",
Expand Down
6 changes: 3 additions & 3 deletions corpus/b4_nondeterm.metta
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; `collapse` converts a nondeterministic result into an explicit comma tuple
; `collapse` converts a nondeterministic result into a tuple
; We don't use it above, because the order of non-deterministic results
; is not guaranteed
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
!(assertEqual
(collapse
(match &self (= (shape) $x) $x))
(,))
())

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Expand All @@ -56,7 +56,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
!(assertEqual
(collapse (shape))
(, (shape)))
((shape)))
!(assertEqualToResult
(shape)
((shape)))
Expand Down
2 changes: 1 addition & 1 deletion corpus/d5_auto_types.metta
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Auto type-checking can be enabled
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
!(assertEqualToResult
(collapse (+ 5 "S"))
((, (Error (+ 5 "S") (BadArgType 2 Number String)))))
(((Error (+ 5 "S") (BadArgType 2 Number String)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Expand Down
2 changes: 1 addition & 1 deletion corpus/test_stdlib.metta
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
(()))
!(assertEqualToResult
(assertIncludes (superpose (41 42 43)) (A))
((Error (assertIncludes (superpose (41 42 43)) (A)) (assertIncludes error: (A) not included in result: (, 41 42 43)))))
((Error (assertIncludes (superpose (41 42 43)) (A)) (assertIncludes error: (A) not included in result: (41 42 43)))))

!(assertEqualToResult
(= (f) (+ 1 2))
Expand Down
37 changes: 20 additions & 17 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,38 @@
"type": "module",
"description": "Runnable TypeScript examples for MeTTa TS. Run with: npx tsx examples/<file>.ts",
"scripts": {
"quickstart": "tsx quickstart.ts",
"grounded-ops": "tsx grounded-ops.ts",
"async": "tsx async.ts",
"browser-interop": "tsx browser-interop/smoke.ts",
"browser-interop:bundle-check": "node browser-interop/check-pure-bundle.mjs",
"concurrency": "tsx concurrency.ts",
"custom-matching": "tsx custom-matching.ts",
"edsl": "tsx edsl.ts",
"grapher": "tsx grapher.ts",
"grapher-gif": "tsx grapher-gif.ts",
"grounded-ops": "tsx grounded-ops.ts",
"hyperon-api": "tsx hyperon-api.ts",
"js-interop": "tsx js-interop.ts",
"json-module": "tsx json-module.ts",
"matching": "tsx matching.ts",
"nondeterminism": "tsx nondeterminism.ts",
"stdlib": "tsx stdlib.ts",
"standard-libraries": "metta-ts standard-libraries.metta",
"types": "tsx types.ts",
"js-interop": "tsx js-interop.ts",
"python-interop": "tsx python-interop.ts",
"parallel-matcher": "tsx parallel-matcher.ts",
"prolog-interop": "tsx prolog-interop.ts",
"browser-interop": "tsx browser-interop/smoke.ts",
"browser-interop:bundle-check": "node browser-interop/check-pure-bundle.mjs",
"json-module": "tsx json-module.ts",
"custom-matching": "tsx custom-matching.ts",
"concurrency": "tsx concurrency.ts",
"transactions": "tsx transactions.ts",
"property-testing": "tsx property-testing.ts",
"property-testing:cli": "metta fuzz property-testing.metta",
"python-interop": "tsx python-interop.ts",
"quickstart": "tsx quickstart.ts",
"scaling": "tsx scaling.ts",
"parallel-matcher": "tsx parallel-matcher.ts",
"grapher": "tsx grapher.ts",
"grapher-gif": "tsx grapher-gif.ts",
"typecheck": "tsc --noEmit"
"standard-libraries": "metta-ts standard-libraries.metta",
"stdlib": "tsx stdlib.ts",
"transactions": "tsx transactions.ts",
"typecheck": "tsc --noEmit",
"types": "tsx types.ts"
},
"dependencies": {
"@mettascript/browser": "workspace:*",
"@mettascript/core": "workspace:*",
"@mettascript/edsl": "workspace:*",
"@mettascript/fuzz": "workspace:*",
"@mettascript/grapher": "workspace:*",
"@mettascript/hyperon": "workspace:*",
"@mettascript/node": "workspace:*",
Expand Down
Loading
Loading