Skip to content

uw-resolve: package manager, WAM-fleet parity, Rust lowered tier + store-at-scale (D44–D93) - #4223

Merged
s243a merged 97 commits into
mainfrom
claude/peerhailer-exploratory-docs-aodas5
Sep 8, 2026
Merged

uw-resolve: package manager, WAM-fleet parity, Rust lowered tier + store-at-scale (D44–D93)#4223
s243a merged 97 commits into
mainfrom
claude/peerhailer-exploratory-docs-aodas5

Conversation

@s243a

@s243a s243a commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Tracking PR for the uw-resolve package-manager work since #4203 — ledger rows D44–D93 in docs/proposals/JS_TARGETS_PARITY_PUNCHLIST.md. Every row was independently re-verified locally before landing.

What this is

One frozen SWI-Prolog spec — examples/pkg_resolver/resolver.pl, the "uw-resolve" package/dependency resolver — compiled to many WAM targets (wamjs/JavaScript, Go, Rust, ClojureScript, C, C++, …), each verified against SWI-Prolog as oracle via the contract corpus (51 scenarios), the seeded term differential (2,600 cases, 0 divergences), and the store differential (503 cases, 0 divergences). resolver.pl / resolver_store.pl remain frozen except in explicitly-authorized resolver rounds.

Resolver semantics (P0–P3)

Frozen-base + graduated freeze, named layers, a pkg-style CLI through the transpiled argparser, and P3 Debian semantics (deb/3 versions per Policy §5.6.12, Provides/virtual packages, alternatives groups, extended blocked shapes). Ported across wamjs, Go, Rust, and ClojureScript at corpus 51/51 + differential 2600/0.

Correctness

Cut/choice-point semantics audit (§9 barrier model), frameless-Y ITE-barrier re-audit across the fleet, and the H4 cyclic-dependency + H1 layered-backtracking fix (D69). Rust P3 parity closed the last term-differential gap (root-caused to the hand-written Rust runtime meta-call path, fixed at the generator source — never the spec).

Performance

  • Store-backed resolution on wamjs, Go, and Rust: JSONL → indexed UWFI/UWIX seek stores, bytes-read-proportional-to-query. Rust store resolves the 5k catalog in ~40 ms reading <1% of the store (~1.9–2.0× SWI's native resolve).
  • Rust deterministic lowered tier (regions 1–5 + a general deterministic_recursion_class recognizer in shared core): compiles hot predicate families to native Rust, eliminating WAM dispatch for them. Each region gated + shipped default-ON only on a real wall-clock A/B, byte-identical output. Cumulative: term-lane 5k resolve −80.7% vs the pristine interpreter; throughput ~9.0× → ~7.2× SWI.
  • Store-as-default at scale: a size→backend auto-switch ladder (D93) routes large catalogs to the indexed store automatically (~10.7× at 7.5k packages, break-even ~200–300), byte-identical across the switch, with a reserved rung for a future LMDB tier.

Benchmarks & profile (current)

examples/pkg_resolver/BENCHMARKS.md refreshed on one box (D90) with an explicit per-axis Rust-vs-SWI verdict: Rust beats SWI on startup; SWI still leads on throughput (~7.2×) and term resolve (~19.5×, gap collapsed from 98×) and store resolve (~2.0×, close). A full-tier hotspot profile (D91) shows dispatch is now negligible; the next levers are the runtime term representation (functor interning, alloc churn) and LMDB-at-scale — all captured in the consolidated backlog (WAM_RUST_LOWERED_TIER_THROUGHPUT_PLAN.md §10).

Verification

Standing gates, re-run locally under LC_ALL=C.UTF-8 for every merged row: contract corpus 51/51, term differential 2600/0, store differential 503/0, per-target probe suites, and the pkg-CLI suites. Merges use clean-extend + independent local re-verification; each is ledgered.

🤖 Generated with Claude Code

https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd

claude added 30 commits August 30, 2026 19:56
…esidual)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…r TS/Node consumer (D33)

Unblocks the CSV subset/reorder projection deferred as blocked-upstream in D31.
project_columns is distinct from the arity-defining columns option: it resolves
header names to 0-based indices at compile time (resolve_projection/5) and the
_typescript templates select/reorder exactly those fields. Pass-through defaults
keep no-projection TS output byte-identical; bash/PowerShell never reference the
projection vars so their output is unchanged. Unknown name / arity mismatch /
missing header each fail with a clear error.

15 TS-source tests green (4 new); test_csv_source, test_json_source_validation,
core TS/annotated/vanilla suites green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…d (python+wam regression)

Verified the D28 'optional 2-line is_guard_goal patch' is not clean: adding match
to the shared is_guard_goal/2 regresses python (no match guard renderer, no
catch-all) and wam (is_builtin_pred derives from is_guard_goal -> builtin_call
match/2 no runtime implements). Only clojure+ts have match guard renderers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Clean-extend merge of the string-tag delta (4 files, byte-identical op3 parent
verified). Distinct V.String term tag: unify/==/copy_term/compare/sort/write
handle it; string unifies only with an equal string. String-producing builtins
box strings (atom_string, string_concat, string_chars, string_to_atom A1,
number_string, split_string); atom_concat/atom_chars stay atoms; string/1 true
only for the tag, atom/1 false. Standard order matches SWI 9.0.4
(Var<Number<String<Atom<Compound). probe_string_tag green under node; builtins/
lowered/fact-sources suites green; CONFORMANCE_TARGETS=javascript 48/48; CI 16/16.

Residuals: compiled "foo" literals collapse to atom (shared WAM constant tokens);
writeq/~q nested-string quoting; fact-source cells stay atoms; no string_length/2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…/2 + writeq/~q quoting, D34 residuals)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…amjs-string-polish)

Clean-extend merge of the string-polish delta (4 files, byte-identical string-tag
parent verified). Adds string_length/2 (string/atom/number -> char length) and
writeq/1 with a recursive quoted writer reused by ~q: strings render "...", atoms
single-quoted only when needed (unquoted []/!/;/{}/lowercase-ident/ISO-graphic
except lone '.'; escapes \\ and \'). write/1 unchanged. probe_string_polish
green under node; builtins/lowered/fact-sources green; CONFORMANCE_TARGETS=javascript
48/48.

Residuals: compiled "foo" literals still collapse (wam_target.pl, out of scope);
fact-source cells stay atoms; no \n/\t escapes in writeq; brace terms not special-cased.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…ag (shared-file, fleet-verified)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…, Opus)

The Node JSON consumer now honors a declared schema(...): reads
dynamic_source_metadata/2's schema_fields and emits a typed record object per
JSON record (JSON.stringify of {field:value,...} in declared order, per-field
type coercion), the parity csharp_target already had. New _typescript_schema
templates + json_schema_projection_js/2; wrapper routes via non-empty
schema_fields, else falls through to flat-columns unchanged. Only non-additive
edit is one render-var line; bash/PS byte-identical; flat-columns TS untouched.

New isolated suite test_typescript_json_schema 6/6 green (2 node-exec);
test_typescript_source / json_source_validation / csv_source green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
test_tsv_support wrote-then-deleted 'test_data.tsv' in the repo root, which
collides with a checked-in file of the same name (an accidental commit from an
unrelated PR). Every run left the tracked file deleted, dirtying the tree.
Rename the scratch file to test_tsv_data.tsv (matching the test_headers.csv /
test_manual.csv scratch convention) so the tracked fixture is never touched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…Grok grok/wamjs-string-literal)

Clean-extend merge of the string-literal delta (byte-identical string-polish
parent verified). Touches two SHARED files with the safe design:
quote_wam_constant/2 spells Prolog strings as double-quoted tokens (atoms/numbers
unchanged); wam_classify_constant_token/2 strips the quotes and STILL returns
atom(Name) (no new string(_) Class, so other runtimes are unchanged) plus a new
exported wam_constant_token_is_string/1. JS-only consumers (constant_to_js_term/2,
lowered emitter T5/T6) build V.String when the signal is true.

Verified full-fleet: default conformance compiled+ran python/rust/c/cpp/go
through the changed shared code = ALL_PASS (no regression); JS arm 48/48;
probe_string_literal green; builtins/lowered/fact-sources green.

Residual: fact-source JSON/TSV cells still intern as atoms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…g/parallelism/LMDB gap items

- examples/cli_args/oracle/: peerhailer's cliArgs.js + 17-test corpus (from
  peerhailer @ 08ad35e, verified 17/17 green) as the transpilation oracle
- punchlist: A-series plan (A1 Prolog impl -> A2 wam_javascript -> A3 pattern
  targets -> A4 differential fuzz) + GP-PROF/GP-PAR/GP-LMDB gap rows
- Grok prompt for GP-PROF: opt-in JS-WAM runtime profiling (UW_PROFILE, stderr
  report, zero-change-when-off)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…ser (A1, Opus)

cli_args.pl mirrors oracle/cliArgs.js exactly: schema registry as data
(Name-schema(Options,Positionals)|group(Actions)), strict parse (-- terminator,
boolean/string/optional kinds, isLongFlag-vs-PEM rule, arity messages), verbatim
lenient fallback (legacy flag test, no -- handling), leading-globals scan, and
even the JS prototype-chain lookup quirks (--toString, --__proto__). Results are
tagged ok(Positional,Flags)/err(Message) - the JS edge maps err to CliError.

Verified two ways: plunit port of the 17-test corpus (17/17) and a seeded
5067-line differential harness vs the node oracle (0 divergences, 0 message
mismatches). Pure/det/tail-recursive, no cuts/throws/pcre - written for
transpilation (A2 wam_javascript, A3 pattern targets re-run the same harness
against the transpiled build).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…ing)

Clean-extend merge (byte-identical string-literal parent verified). Off by
default with the unprofiled loop byte-identical to the pre-GP-PROF path;
UW_PROFILE=1 prints a compact per-predicate table to stderr, UW_PROFILE=json a
stable-schema JSON object; Runtime.profile(...) for programmatic use. Metrics:
per-predicate calls/instr/ns/cps/max_cp_depth/lowered (lowered tier = call
counts only); global instructions/unify/trail/heap/backtracks/undos/wall_ns.
stdout stays byte-identical on and off. Overhead: off ~noise, on ~6-10%.

Suites green (3 profiling probes + lowered call-count check); JS-arm
conformance ALL_PASS with profiling off.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
… wam_javascript

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…atalogue (Opus)

Attempted to transpile the oracle-verified peerhailer argparser through the
pattern targets. Honest result: ~5% of predicates transpilable, 0% of the four
parse mechanisms - the definitive maturity measurement, catalogued as G-A3-1..17
in docs/proposals/A3_PATTERN_TRANSPILE_REPORT.md.

Fixed (7, all S, regression-tested): string/char builtin table; batch path
emitted unparseable TS for any two-goal clause (pre-existing flagship bug -
tests asserted substrings, never parsed the output); ,/;/-> in guard position;
unrendered goals silently deleted; guarded-tail discarded clause remainder;
annotated_js 'const x = (expr);' misparse; ts_output_goals dropped intermediate
assignments.

Open catalogue: G-A3-6 guard hoisting, G-A3-8 fact-fallback executes the
predicate (compiler hazard), G-A3-9 multi-output loops, G-A3-10 ITE in
recursive bodies, G-A3-11..16 smaller shapes. New 30-test
test_typescript_cli_args_shapes.pl carries fix regressions + executable gap
probes. TS/AJS/VJS/source/schema suites all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
G-A3-8: compile_facts/3 no longer executes the predicate being compiled - a
genuine ground-fact check (clause/2 inspection, never call/1) gates the fact
fallback; rules, non-ground facts, uninspectable builtins and undefined
predicates refuse with an actionable unsupported_lowering error naming the body
shape. The three shapes formerly killed at 20s/1.5GB refuse in <=12ms. Guard
lives inside compile_facts/3 so annotated_js/vanilla_js inherit it (verified
per target).

G-A3-13: true/false emit as JS boolean literals (branch values + fact cells).
G-A3-14: mid-sequence if-then-else bindings get let+assign with stable v<N>
names; unmapped variables refuse loudly instead of leaking _G identifiers.
G-A3-15: reversible string builtins honour the head output slot
(strict -> slot -> loose); both-arguments-known renders a check, not a rebind.
G-A3-11.1/.2/.4: arity-1 semidet signature + return true/false; CLI entry
passes every argument with per-token coercion (compiler-emitted entry now
drives substring_from/3 vs the SWI oracle); compile_module/3 refuses an
all-unsupported module and emits a warning banner for partial ones.

Left open (probe-pinned): G-A3-11.3 type inference, G-A3-6/-9/-10/-12/-16.
Shapes suite 30->55 tests with node --check + SWI-oracle gates; full regression
bar green (TS core, AJS, VJS, source, json-schema, native-lowering).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…ow-up, Opus)

The structural-recursion path's goal renderer had no ;/-> clause, so one ITE
anywhere refused the whole predicate into the (formerly hazardous) fact
fallback. Now ITE lowers by position via ts_struct_seq/15: TAIL - each branch
returns (recursive branch = loop continuation, exit branch = value; nested
else-if composes; no dead trailing return); VALUE - let _sN + per-branch
assignment read by following goals. Conditions render via ts_guard_condition/3;
classification reuses clause_body_analysis's if_then_else_goal/4 +
if_then_else_shared_output_vars/4 (core unmodified). ts_term_expr/3 and
ts_arith/3 now fail on unbound variables instead of emitting undefined/_G names
(silently-wrong -> loud refusal). Bare disjunction, bare if-then, no-shared-
output and failable-test branches keep refusing by design.

cli_args reality check: string_member/2 + first_char_index/4 now compile
oracle-correct (lowers-correctly 2->4); all four parse mechanisms still refuse
loudly - G-A3-9 (multi-output loops) is THE remaining blocker, its widened
blast radius pinned by probe gap_g_a3_9_now_reached_by_ite_loops_too.

Shapes suite 55->67 (12 new: 7 behavioural node-vs-SWI, 4 refusals, 1 probe);
full regression bar green (TS 68, AJS 17, VJS 22, source 11, schema 6,
native-lowering 12, shapes 67).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…3-9 (A3 follow-up, Opus)

A recursive predicate with N>1 outputs now returns one positional array
[out1,...,outN]. Output positions are found by a both-halves discipline
analysis (ts_struct_output_positions/5: every recursive clause only THREADS
the position, every base clause PRODUCES it - each half rejects a distinct
false positive). Tail calls stay 'return pred(...)' so the tuple flows
through; non-tail calls destructure const [_s0,_s1] = pred(...); G-A3-10's
ITE branch renderers compose unchanged. The single-output path is proven
byte-identical over a 12-shape comparison harness (default only changes when
the analysis positively finds >=2 outputs). Multi-output modules get a JSON
CLI entry (argv tokens JSON.parsed when possible, tuple JSON.stringify'd).
Calls TO a multi-output predicate from a clause body still refuse - now with
a diagnostic naming the tuple shape (G-A3-6 territory).

Payoff: lenient_loop/5's and scan_leading_globals/4's exact loop skeletons
now compile and match SWI (node-vs-oracle pinned); G-A3-9 is no longer the
blocker for any of the four parse mechanisms - the shared remainder is
G-A3-6 (cross-predicate calls) + G-A3-12/16.

Shapes suite 67->81 (both probes flipped; 3-output, ITE+tuple, exit-branch,
non-tail destructuring, lenient skeleton, CLI entry, inheritance, refusals);
full regression bar green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…nient mechanism compiles whole (A3 milestone, Opus)

MILESTONE: peerhailer's lenient parse mechanism (parse_lenient/3 + 13
transitive callees) compiles into one module via compile_module/3, node
--check clean, and matches the SWI oracle 10/10 on real argv lines including
the greedy legacy read, boolean bare flags, --flag=, -- empty-key, and
--__proto__. The only hand-written JS is a console.log.

G-A3-6: cross-predicate calls lowered by the CALLEE's output count (read
syntactically via ts_pred_outputs/3 - compiling never executes): semidet ->
boolean call in condition/guard position (negation composes); 1 output ->
const _sN = q(ins); N outputs -> tuple destructure. Failure semantics: only
the 0-output form carries failure (nested-if clause fall-through); det-with-
outputs throws on no-match. In-order guard placement (gopen markers +
ts_assemble) fixes the TDZ hoisting bug. New general clause lowering
(native_ts_general/3) gated as a rescue path on the clause-body path being
defective. Mutual recursion works via JS function-declaration hoisting
(verified, no compiler machinery).

G-A3-12: compounds = {$: tag, args: [...]} - distinguishable from
atom(string)/bool/list(array) with no throwing test; used in construction,
head patterns, =/2, ITE conditions (payload bound in then-branch only),
==/\== via a once-declared _uwEq. G-A3-16 (K-V head patterns) falls out.

Census: 30/43 cli_args predicates compile with no dropped goal (was 2).
Byte-identical harness: 26 shapes x 3 targets = 0 diffs. Shapes suite
81->105; full regression bar green. Remaining blocker for strict/globals/
schemaFor: semidet callee WITH outputs in condition position (probe-pinned),
plus ground-fact constant tables.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…er (A4-pattern, Opus)

parse_args/2 - all 43 predicates - compiles whole via compile_module/3 into
one module (40 functions + 3 inlined ground-fact tables, 0 refusals), node
--check clean, reproducible via examples/cli_args/patternjs/build.sh. The
production corpus passes 17/17 with ONE import line changed, and the seeded
5067-line differential vs the real cliArgs.js shows 0 divergences and 0
message mismatches (4150 ok + 917 errors - messages match too). Verified
through typescript, vanilla_js and annotated_js.

Closed en route:
- G-A3-18 semidet-with-outputs: module-private Symbol sentinel (_uwFail) +
  ts_pred_can_fail/2 least fixpoint over the call graph; exactly 9 cli_args
  predicates get the sentinel form, everything else keeps its det exit
  (byte-identical). Output analysis became a greatest fixpoint (cycles);
  head-built outputs recognised; arity mangling parse_args_2/_3.
- G-A3-19 ground-fact constant tables: lowered as a match against the table
  (bind/test/membership), inlined - never a call, never in the closure.
- G-A3-20 deferred head-output bindings + continuation duplication into ITE
  branches (closes the prior by-design refusal; lets parse_args/3 compile).
- Two pre-existing correctness bugs: V = Term silently rebound and DROPPED
  the test; vanilla_js tuple-strip rule ate 'args: [' inside compound
  literals.

Byte harness 26 shapes x 3 targets: 72/78 identical, 6 changed with stated
cause. Shapes suite 105->116 incl. two ENDGAME tests compiling the real
cli_args.pl and running it vs SWI. Full regression bar green. Remaining
minor items probe-pinned (standalone fact-table stringify, G-A3-11.3 types,
var-var ==, det-with-outputs-in-condition, enumeration refusal).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…Grok grok/wamjs-cli-args)

Clean-extend merge (byte-identical profiling parent verified; frozen A1 files
untouched). examples/cli_args/wamjs/: cli_args.pl compiles through the JS WAM
interpreter (reproducible build.sh, node --check clean), the production corpus
passes 17/17 with one import line changed, and the seeded 5067-line
differential vs the real cliArgs.js shows 0 divergences and 0 message
mismatches (verified locally post-rebuild). Shim is term conversion only;
/3 registries supported at the edge.

Three real runtime fixes the program forced (each probe-pinned):
- sub_string/5 builtin was missing (runtime had sub_atom/5 only)
- Y-register clobber across Call: a huge no-Allocate ground fact
  (default_registry/1) overwrote caller Y registers; Call/CallPc snapshot Y,
  Proceed restores - a JS-runtime workaround for frozen wam_target.pl fact
  compilation
- Execute of a builtin set halt instead of returning to the CP, killing the
  success path of a last-goal builtin

Perf: wamjs ~8.9s vs oracle ~0.18s on the 5067-line sample (interpreter tier).
Suites green (builtins incl. 3 new probes, lowered, fact-sources); JS-arm
conformance ALL_PASS. With A4-pattern already merged, the maturity
demonstration is complete on BOTH lanes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…se (D39, Fable)

New docs/WAM_FLEET_GAPS.md: target x deficiency-class matrix over all 18 WAM
targets, every cell marked verified (file:line evidence) or suspected (stated
reason); plus the recipe for running examples/cli_args as a fleet benchmark
and the completeness-band thesis (why hybrid-WAM targets rank above
pattern-only ones, and what even they were missing). 16 per-target
WAM_*_STATUS.md docs gained a whole-program-exercise section; ELIXIR status
doc created; WAM_BACKEND_CONVENTIONS.md gains section 7 (Execute of a
runtime-implemented builtin must take Proceed's return path - never halt,
never silently fail, never jump to PC 0) and section 8 (do not assume
Allocate framing protects Y registers - the X window aliases into Y space),
both marked adopted 2026-09 from the JS-runtime A2 findings.

Sharpened findings: the A2 Y-clobber is X-window overflow aliasing (X101=Y1)
under the +100/+200 numeric encoding - named-register runtimes structurally
immune, numeric flat/frame runtimes exposed; sub_string/5 dispatched nowhere
but cpp/JS; wat encodes Execute-of-unknown as a jump to PC 0; haskell's
pattern fact-fallback executes the predicate at compile time (the G-A3-8
hazard); wam_lua/wam_clojure have no conformance arms.

Docs only - no code changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…r (D40, Opus)

Port of the whole-program G-A3 machinery to clojure_target/clojurescript_target
in Clojure idiom: positional vector tuples destructured via let; compounds as
{$: tag :args [...]} maps (a tagged vector is indistinguishable from a list);
failure sentinel = private fresh Object tested with identical? (unforgeable by
any term or edge-crossing data); Clojure's structural = replaces TS's _uwEq
(verified (= true "true") is false, so the boolean/string distinction
survives); (declare ...) supplies what JS hoisting gave free; direct calls not
recur, with stack headroom MEASURED (~77x char-walk / ~100x argv-walk beyond
corpus bounds) and recur named as the limit for unbounded input.

Both call-graph fixpoints (greatest for outputs, least for fallibility) are
straight ports and reproduce the TS lane's answers exactly on the real program
- same nine sentinel predicates, same output sets - pinned as tests.

Gates (all re-verified locally post-merge): build.sh regenerates and nbb loads
clean; corpus 17/17 contract points / 25/25 argv-lines with exact error
messages (equivalence gate - ESM import swap impossible under nbb, stated in
the README); differential 5067 lines, 0 divergences, 0 message mismatches.
Emission: 45/46 pre-existing shapes byte-identical (1 required change:
declare). New 66-test test_clojurescript_cli_args_shapes.pl; cljs + TS
regression bars green. Also fixed en route: / silently truncating via the old
// mapping; runtime block emitted once, not per-mention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…argparser (profiler-grounded)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…erential (D41, Grok grok/wamjs-perf)

Clean-extend merge (byte-identical cli-args parent verified). The argparser
now builds in mixed emit mode: T4+ITE lowering with a depth-aware nested-\+
fold (lenient_loop/strict_loop/parse_args_3 lower), direct Runtime.op_* emit
(no per-instruction object allocation), cheap ==/2 ITE, and every-clause-
ground fact interning via copy_term into program.ground_memo (trail-safe: the
snapshot has no unbound cells so it is never trailed; write-mode GetList
aliasing avoided by copying).

Measured: 1.48x wall on the 5067-line differential on the author's box
(4.639s -> 3.144s); 1.58x reproduced here (8.90s -> 5.63s). Interpreter
instructions on the 200-line profile: 388,834 -> 20,667. Semantics gates all
unchanged: corpus 17/17, differential 0 divergences / 0 message mismatches,
lowered/builtins/fact-sources suites green, JS-arm conformance green.

Stretch >=5x not met - root cause stated: T4 still copy_tables the full
register file per clause fail and unifying ITE conditions snapshot_machine;
Execute of a non-self user predicate stays interpreted (cp=0 would steal the
query continuation - kept correct over fast). Next lever documented for
round 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
… Grok grok/wamjs-perf2)

Clean-extend merge (byte-identical perf parent verified). The whole argparser
now lowers with zero interpreter fallbacks (200-line profile instr=0): a
correct Execute-of-user path (lowered callee = JS return with CP untouched;
interpreted callee via execute_user_isolated - save CP, isolate with cp=0 so
the inner Proceed halts, restore - with permanent continuation-integrity
probes), liveness-gated Y-save (blanket save was a measured 4.98s regression;
now saved iff a Y register is live after the Call, with mode/build/read
restore so intern write-mode cannot leak), nil/cons + lite-ITE snapshot cuts,
and direct JS Call/Execute incl. Call-of-sub_string as op_builtin.

Measured: 2.187s on the 5067-line differential (2.12x vs the A2 interpreter,
1.50x vs round 1 on the author's box; 3.49s here = 2.55x vs our A2 baseline).
Round target of >=3x vs R1 not met; the residual cost is the lowered tier's
own snapshots (unbound-A1 T4 register copy, snapshot_lite on unifying ITE,
Y allocate on recursive Call) - next levers named.

Semantics gates unchanged and re-verified locally: corpus 17/17, differential
0 divergences / 0 message mismatches, lowered (15 tests incl. new
continuation + Call-of-builtin probes)/builtins/fact-sources green, JS-arm
conformance green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…act stores (dep-free default + opt-in lmdb)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…ec (uw-resolve)

Dependency resolution as Prolog relations, projected through the proven
argparser pipeline: declarative Linux environment setup (planner over existing
package managers, not a new ecosystem), Babashka as the bare-machine shell
host, and remote package-management logic over peerhailer (facts and signed
plans as data over the routed channel). Honest constraints named: backtracking
targets the WAM tier, bb needs the CONF-CLOJURE arm, version-ordering builtins
need the oracle-tested playbook, executors stay dumb. Phased P0-P4 with
argparser-style gates; incubate as examples/pkg_resolver/, extract to a
submodule when it grows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…k grok/wamjs-lmdb)

Clean-extend merge (byte-identical perf2 parent verified). Two new store forms
through the one CallFactStream path, D27 file(...) byte-identical:

- indexed(Prefix): dependency-free seek-based store. UWFI data file
  (length-prefixed records keeping original cell text so parse_term matches
  D27) + UWIX index (sorted key table, binary search via Buffer.compare,
  O(log n) fs.readSync seeks). Bound-A1 proof: 506 bytes read of a 992,872-
  byte data file in 29 seeks (reproduced locally; the probe requires
  bytes_read > 200 so a prefix mis-parse cannot pass). Builder:
  scripts/js_wam/uw_fact_index.js.
- lmdb(Dir): real LMDB via the lmdb npm package - the fleet's first optional-
  dependency tier: lazily required only when declared, absence is one loud
  actionable error (never a silent cross-backend swap), no repo package.json
  entry. Typed binary keys preserve D34 atom/string/number distinctions.
  Loader: scripts/js_wam/uw_fact_lmdb.js. The gated test arm ran in the
  author's env (lmdb 3.5.6 via /tmp-prefixed install); the missing-package
  error path is tested ungated.

Shared-semantics probe: same fixture through both backends matches SWI.
Unbound enumeration = source-file order; other-args-bound filters streamed
candidates. Suites green (fact-sources incl. new probes, builtins, lowered);
JS-arm conformance green; corpus 17/17; differential 0/0.

Residual, stated not started: secondary indexes, write paths, CSR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
… prompt

Proposal gains 2d: the TrixiePup64/Woof-CE frozen-base scenario as the
concrete P0 problem - four queries (layered install, version-ceiling
explanation, SFS-style layer closure, removal orphans) over one clause set,
the boundary apt is built to ignore. Grok prompt: examples/pkg_resolver/
P0 spec + contract corpus first, P1 wam_javascript build via the argparser
playbook with a >=2000-case seeded differential vs SWI - the first
demonstration program that BACKTRACKS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Extends the wamjs D43/D48 store design to the Go WAM lane:
resolver_store.pl (shared adapter, unmodified) compiles through wam_go
with its five store predicates served from dependency-free UWFI/UWIX
indexed seek stores instead of a loaded catalog term. New example
examples/pkg_resolver/go_store/.

Inventory: Go's graph-kernel fact sources were in-memory, eager-scan,
arg1-only - insufficient for indexed(Prefix), so the seek reader is
new (seekFactSource: binary-search the .idx table via ReadAt, read
only matching .data records, bytes-read counter mirroring the wamjs
reader, typed key tags, unbound-arg1 full scan, streams through the
existing choice-point machinery). lmdb(Dir) arm registered but fails
loud (Go cannot read the npm-lmdb format; never a silent swap).

Five runtime bugs found, fixed, probe-pinned (owned files only;
resolver_store.pl untouched): number_string/2 unimplemented;
atom_concat/3 forward-only; a double-quoted "-" interned with quotes;
first-arg try/retry/trust dispatch unrecognised (collapsed
multi-clause solutions); escape_go_string mis-escaped quotes.

B3 headline reproduced at merge: resolve_layered on the 5k catalog -
term path ~14.6s loading the whole 1.14 MB, store path 0.55s reading
11,025 / 1,142,225 bytes (0.97%, 880 seeks), identical 10-package
selection - ~19x faster reading under 1% of the store.

Verified at merge: store corpus 51/51 identical to term, store
differential 503/0 on the 5k catalog, the B3 payoff, Go term corpus
51/51 + differential 2600/0, cut 35/35, wamjs corpus 51/51. The
generated go_store .go files are gitignored build artifacts
(path-baked; build.sh regenerates), mirroring wamjs_store.

Ledger: D70 added; also backfills the missing D69 (H4/H1) row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Finish Grok's partial Rust P3 port (deb/3 Debian versions, provides/virtual
packages, alternatives groups, extended blocked shapes). The partial commit
built and passed corpus 51/51 but failed 53 seeded differential cases (all
p3g*, all returning fail where SWI resolves); non-P3 cases were clean.

Root cause was in the hand-written Rust runtime, not the spec (wamjs passes
all 2600 from the same resolver.pl). The deb/3 candidate order runs through
sort_versions_desc -> maplist(is_v3,..) + predsort(cmp_ver,..), which
meta-call user goals via call_goal_once + a nested run(). Two defects bit
only deb/3:
  1. backtrack() had no lower bound, so a failing meta-goal backtracked past
     the meta-call boundary into the caller's choice points.
  2. Yi-register trail entries from the nested run were unwound by the caller
     after call_goal_once restored the stack, clobbering the predsort output
     var; reverse/2 then saw an unbound list and failed.

Fixes at the generator source: a backtrack_floor field in state.rs.mustache
that call_goal_once raises to the entry choice-point depth and backtrack()
respects, plus dropping the nested Yi trail entries in call_goal_once (both
in wam_rust_target.pl). Committed crate regenerated by build.sh to match.

Verified on this branch (independent rebuild, LC_ALL=C.UTF-8): corpus 51/51,
term differential 2600/0/0, test_wam_rust_maplist_predsort.pl passes. Rust is
now term-current across all groups; clears the pre-P3 caveat from D69.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Replace the stale cross-target table (D51-era, mixed machines, outdated
39/2400 gate description) with one internally-comparable table measured on
this 4-core coordinator box (2026-09-06). Every leg gated first (corpus
51/51, term differential 2600/0, store differential 503/0) and only timed
when green; identical 10-package selection across all legs.

Headline vs SWI: startup B1 - Rust 0.059s and Go 0.086s beat SWI 0.109s.
Throughput B2 - wamjs 6.3x, Rust 8.8x, Go 11.3x, cljs 44x. Big-catalog
resolve B3 - wamjs store 9.4x (reads 0.63%), Go store 30x (reads 0.97%),
Rust term 98x, Go term 776x, cljs 1471x. Go store resolves the 5k catalog
in 0.593s touching 0.97% of the store, ~26x faster than Go term.

Rust store row reserved as pending (that lane is next). SWI B3 measurement
caveat and historical/other-box pruning figures documented in the file.
Only BENCHMARKS.md re-measured; no target code touched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Add the store-backed catalog path to the Rust WAM target (the D43/D48 store
design already on wamjs and Go). resolver_store.pl compiles through wam_rust
with its store predicates served from the dependency-free UWFI/UWIX indexed
seek store instead of a loaded catalog term; new examples/pkg_resolver/
rust_store/ mirrors go_store/.

The Rust lane's existing foreign fact sources were int-keyed edge lookups,
not P/2 atom rows, and there was no D43 seek reader. New seek reader
(seek_fact_source.rs.mustache, a port of Go's seekFactSource): binary-searched
sorted .idx + positioned .data reads via read_at, typed key tags, bytes-read
counter, a "seek_fact" native kind streaming __tuple__/2 rows through the
existing choice-point machinery (keyed seek on bound A1, full scan on unbound
A1 for the provides walk). Also implemented number_string/2 and chunked the
shared-WAM instruction vec! past 6000 (the ~9.9k-instruction store crate
OOM-killed rustc as one function; smaller crates stay byte-identical).
lmdb(Dir) registered but fails loud (no npm-lmdb reader), matching Go.

B3, verified here: resolve_layered on the 5k catalog resolves in 0.038s
reading 10,305 / 1,142,225 store bytes = 0.90% (820 reads), identical
10-package selection — ~51x faster than Rust term (1.93s) and the fastest
store leg in the fleet.

Verified here (LC_ALL=C.UTF-8): store corpus 51/51 identical to term, store
differential 503/0, B3 payoff. Term lane regenerated from the updated codegen
and re-gated: corpus 51/51, term differential 2600/0 (committed term crate
regenerated so committed artifacts match source). resolver_store.pl and
resolver.pl unmodified. BENCHMARKS.md Rust-store row filled in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Design record for closing the B2 raw-throughput gap vs SWI on the Rust WAM
target. The recorded profile says the lever is fewer WAM dispatches (lower
more predicates to native), not faster dispatch.

Stages the work deliberately: the hard resumable native tier (2b, mprolog
F3) was deferred for two still-unsolved obstructions (lowered->lowered calls
route through the interpreter; the per-re-entry save_regs snapshot costs ~what
dispatch saves). So: Stage 0 census (classify hot predicates), Stage 1 F11
self-tail-recursion -> native loop (lowest risk, gated + fallback), Stage 2
the resumable trampoline only on explicit approval after Stage-1 numbers and
only with a direct resume path + minimal saved-locals and per-solution cost
proven against baseline. Every stage keeps corpus 51/51 + term 2600/0 +
store 503/0 green. F3/F11 are ideas from Sasagawa's mprolog (Modified BSD),
never code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Stage 0 census + Stage 1 of the D74 lowered-tier plan. Census (over B2/B3
dispatch counts) shows F11-shaped predicates are 46.6% of B2 dispatches but
the safe, deep-enough subset is only ~12% (shallow catalog accessors); ~34%
is F11-shaped-but-2b-in-effect (deterministic bodies reached via run()), the
rest true 2b drivers.

Stage 1 implements wam_rust_f11_lowerable/4 (one tail-recursive clause +
pairwise non-unifiable heads + pure body) emitting a loop{}, 12 predicates
eligible. Measured net-negative: F11 on regresses B2 ~7% (two per-call
register snapshots, obstruction O2, exceed the dispatch removed for shallow
accessors), neutral B1/B3. So F11 is shipped implemented + probe-tested +
gated rust_f11_enabled/1 DEFAULT-OFF; the committed crate is baseline plus a
general deref_match_atom empty-list aliasing fix.

Verified (F11 off, both lanes): term corpus 51/51, term differential 2600/0;
store corpus 51/51, store differential 503/0. resolver.pl and
resolver_store.pl unmodified. Census confirms Stage 2 needs O2 (minimal
saved-locals) and O1 (direct lowered->lowered calls) to fuse deterministic
bodies into larger native regions before lowering pays. F11 idea from
Sasagawa's mprolog (Modified BSD), never code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Record the steering decision: build one general lowered execution model, not a
per-family special case, and target nondeterministic code too via mprolog's F3
resumable-choice-point idea (a choice point is a saved resume state; a second
solution is a jump). Covers the general deterministic recursion class (tail and
non-tail walkers, deterministic-in-practice bodies, index builders) plus the
nondet path sharing the same minimal-snapshot representation. Two cruxes
(deterministic region-fusion; nondet per-solution resume) measured before the
general build; a nondet NO-GO narrows the build to the deterministic class
rather than abandoning it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Record Kimi K2's adversarial design review of the Stage 2 resumable-lowering
design and fold its five hard soundness gates into the plan (§6a). The
amortize-over-larger-regions insight is sound; the build must clear: G-1
resume state needs backward liveness + forced argument-register liveness +
frame pinning; G-2 direct native calls need a det lattice + runtime post-call
choice-point-depth guard; G-3 cut across tiers needs an explicit passed
barrier saved/restored on the CP; G-4 non-unifiable heads prove clause
selection not single solution (needs a transitive at-most-one-solution
proof); G-5 resume CPs need activation identity and cut must invalidate the
predicate's own CP. Several map onto existing runtime machinery; the net-new
obligations are G-1 and G-4/G-5. Seven concrete failure scenarios become
stress tests alongside the 2600/503 differential.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Throwaway cost spike for the Rust lowered tier. Region fusion + minimal-locals
snapshot beats the interpreter and clears the two obstructions that made F11
net-negative. O2 crux: minimal snapshot 58ns vs full save_regs 904ns (15.5x,
up to 46x under pressure). Per-call machinery ~46x. The measured family
(matching_deps + dep_to_req) is 19% of all save_regs and 18.7% of all
backtracks over the real 2600 corpus. Projected ~15-21% B2 reduction from the
full deterministic class (~65-70% of dispatches). One mechanism extends to
nondet resume (10.6x/solution) via lowered_state on the ChoicePoint.

Honest caveat: numbers are from a byte-faithful cost model verified against
bytecode and triangulated from three measured quantities, not a wall-clock
A/B of a wired native region; the full build must validate with a real A/B.

Recommended scope: fuse matching_deps+dep_to_req, matching_versions+satisfies+
version_lt, key_dep_rows+dep_to_req, group_keyed+same_key, and re-enable the
F11 accessor bank under the minimal snapshot. Gated by the D76 soundness
checklist (G-1..G-5). Report + standalone prototype crate (not wired into any
lane) included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Stage 2 build round 1 of the lowered tier, staged-validation per the D77
spike and D76 soundness gates. matching_deps/4 (dep-list walk) fused with
dep_to_req/3 (pure 2-clause rewrite, called only from it) into one native
region: P1 dep_to_req inlined as a direct Rust call (no run() round-trip),
P2 rollback snapshot is three scalars (trail/heap/var_counter), not
save_regs; the recursion compiles to a native loop (no Allocate/Deallocate,
no per-element choice point).

Soundness gates G-1..G-5 satisfied by construction (minimal set + forced
arg-register liveness; pushes no CP / calls no interpreted code so CP-depth
is invariant and asserted, off-shape inputs decline to the interpreter;
no cut crosses a boundary; at-most-one-solution proven, not relying on head
non-unifiability; loop so no resume CP).

Verified both configs, both lanes: region ON (default) and OFF
(UW_REGION1_OFF=1) - term corpus 51/51, term differential 2600/0/0, store
corpus 51/51, store differential 503/0; region-on output byte-identical to
region-off across all 2600. 8/8 region stress tests.

Real wall-clock A/B (6-round interleaved, sha256-distinct binaries): OFF
23,272 ms vs ON 21,648 ms = -6.98% B2 (all six rounds negative), confirming
the spike's ~6% machinery-floor projection end to end. Default ON.
resolver.pl and resolver_store.pl unmodified. dep_to_req rewrite loop idea
credited to mprolog F11 (Modified BSD) where applicable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…on_lt (D79)

Stage 2 build round 2, same recipe as region 1. matching_versions/4 fused
with satisfies/2 (7 clauses) and version_lt/2 (the full Debian Policy
5.6.12 comparison chain) into one native region: recursion -> native loop,
the satisfies/version_lt chain inlined as Rust (P1), 3-scalar minimal
snapshot (P2). Shared satisfies/version_lt handled by a private native copy
for the region only, leaving the interpreted predicates intact so all other
call sites are byte-identical to region-off by construction.

Soundness: G-4 determinism proven via cuts + committed if-then-else bodies +
mutually-exclusive heads (not head non-unifiability alone); version_lt
matched to SWI via a 10x10 truth-table gate. Verified both configs, both
lanes (region 1 stays on): term 2600/0/0 + 51/51, store 503/0 + 51/51,
region-on output byte-identical to region-off. 15/15 region-2 stress tests.

Real wall-clock A/B (6-round interleaved, sha256-distinct binaries): OFF
21,962 ms vs ON 20,412 ms = -7.06% B2 (all six rounds negative), better than
proportional because fusion removes the whole chain. Cumulative Rust B2 vs
SWI ~9.0x -> ~8.2x after regions 1+2. Default ON. resolver.pl and
resolver_store.pl unmodified. mprolog-derived loop idea credited (Modified
BSD) where applicable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Stage 2 build round 3. 3a key_dep_rows/3 + dep_to_req/3 (region-1 shape,
dep_to_req reused verbatim, native loop). 3b group_keyed/2 + same_key/4
(nested native loops with an explicit accumulator over the materialised
input; solution order preserved; no resume-state choice point, stays
deterministic; one region serves both call sites via a generic
destructurer).

Soundness G-1..G-5 satisfied by construction (minimal snapshot + forced
arg-liveness; no CP/no interpreted call with asserted CP-depth and off-shape
decline; no cut crossing a boundary; at-most-one-solution on committed
bodies; loops so no resume CP). Verified both configs, both lanes (regions
1+2 stay on): term 2600/0/0 + 51/51, store 503/0 + 51/51, B3 output
byte-identical on vs off, store scale selection identical. 18/18 new stress
tests, full lib 175/175.

Real wall-clock B3 A/B (5k resolve_layered, 6-round interleaved,
sha256-distinct binaries): OFF ~1996 ms -> ON ~872 ms = -56% (all six
negative), large by design (~68% of B3 dispatches). B2 no regression; store
lane B3 neutral (index builders not on the store resolve path). Both default
ON. resolver.pl and resolver_store.pl unmodified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Final deterministic cleanup round of the lowered tier. build_tree/4 lowered
as region 4: it is a single clause with a hard -> commit on N=:=0, so it is
deterministic (at-most-one-solution, no resume-state choice point) and
belongs to the deterministic tier. Non-tail recursion handled as bounded
native recursion over the materialised input (balanced split, O(log N)
depth), P1 direct-native + P2 3-scalar minimal snapshot. Real B3 A/B (5k
resolve_layered, regions 1/2/3 on both legs, sha-distinct binaries): OFF
~911 ms -> ON ~539 ms = -41%, all six rounds negative, byte-identical output.
Default ON.

F11 accessor bank under P2 (bounded): did not flip. F11 clauses rebind
argument registers so the loop needs a per-iteration register snapshot and
the 3-scalar minimal snapshot cannot apply; dropping the 200-register clear
was correct (2600/0/0) but F11-ON still measured +4.2% B2 (remaining cost is
per-instruction vm.step String dispatch, a codegen rewrite out of scope for
shallow accessors). Reverted; rust_f11_enabled stays OFF. Amortization theory
confirmed.

Verified both configs, both lanes (regions 1/2/3 on): term 2600/0/0 + 51/51,
store 503/0 + 51/51, region-4 output byte-identical on vs off. cargo lib
186/186. resolver.pl and resolver_store.pl unmodified.

Deterministic tier complete: regions 1,2,3a,3b,4. Cumulative B3
resolve_layered ~2000 ms -> ~530 ms (~-73%) vs pristine interpreter; B2
~9.0x -> ~8.2x SWI. Remaining only for the nondet round: pick/7,
blocked_from/4, dep_breaks/5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Single-box (2026-09-07, 4 cores) re-measurement. All legs gated then timed
(corpus 51/51, term 2600/0, store 503/0). Rust term B2 ~23s -> 20.7s (~8.2x
SWI, was 8.8x at D72). Rust term B3 resolve_layered regions ON vs OFF = -74%
(OFF median 1937.7ms -> ON 504.8ms, 6-round interleaved drift-cancelling A/B,
sha-distinct verified-relink binaries), byte-identical output. Store legs
unchanged from D72 (index builders inert on the store resolve path).

Merge diligence: the benchmark subagent tripped a security classifier on a
measurement command; verified independently that the commit is docs-only and
in-scope (no code/spec/template/target changes), scanned both files clean,
and reproduced the headline resolve time locally (warm 5k runs reach the
report's ~505ms). Only BENCHMARKS.md + the measurement report changed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…b (D83)

Record the project owner's refinement (plan §6b): a recursion whose body makes
a nondeterministic call is deterministic overall when backtracking is forbidden
from re-entering that call past the iteration boundary (the per-step nondet call
is committed via once/1, a cut before the tail call, or ->). Such a recursion
lowers with the cheap deterministic mechanism (native loop + minimal snapshot,
no resume-state choice point, like regions 1-4) and the hard gates G-4/G-5 are
vacuous for it. The classifier must decide per driver (pick/7, blocked_from/4,
dep_breaks/5): committed-per-iteration -> deterministic path (preferred), or
genuinely alternative-exposing -> resume-state trampoline. PLAWK
(examples/plawk/) is the in-project reference: it compiles committed-choice
recursion to a deterministic native loop despite a nondet/meta call. Relayed to
the in-flight nondet spike.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Record the project owner's second PLAWK deterministic pattern (plan §6b): turn
the nondeterministic part into an aggregation (findall/bagof/setof/
aggregate_all, itself deterministic) and iterate deterministically over the
result. The backtracking is contained at the aggregate boundary; the hot outer
fold has no resume-state choice point, so it lowers the cheap way and G-4/G-5
are vacuous. Driver classification is now three-way: (a) committed-per-iteration
recursion, (b) aggregate-then-iterate, (c) genuinely alternative-exposing (only
c needs the trampoline). (b) applies only where a predicate already has the
shape, never by editing the frozen spec. PLAWK's native codegen
(examples/plawk/codegen/plawk_native_codegen.pl) is full of
findall(X, member(X,L), Xs) aggregate-then-iterate idioms. Relayed to the
in-flight nondet spike.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…D85)

Design-only classification of pick/7, blocked_from/4, dep_breaks/5 (resolver.pl
NOT modified, no trampoline built). dep_breaks/5 is class (a) committed-choice
- already deterministic (nondet dep_breaks_need in a -> condition, committed
before the tail self-call), lowerable via a recognizer widening with no spec
change. pick/7 is class (c) essential nondeterminism but dead code (no callers,
never in the corpus) - leave interpreted. blocked_from/4 is class (c) genuine
generator whose full multiplicity/order is observed by tests (findall over
explain_blocked), so it cannot be made deterministic without changing results;
only a high-risk aggregate-then-iterate subsystem refactor could, recorded as a
future resolver round.

Net: no free deterministic rewrite for the two class-(c) drivers, no trampoline
warranted for these this round, resolver.pl stays frozen. The one actionable
low-risk win is lowering dep_breaks/5 as a deterministic region via recognizer
widening. Awaiting human decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Record the project owner's guidance (plan §6c): the lowering recognizer must
target a family of deterministic recursion shapes, not assume a tail loop -
regions 3b (group_keyed) and 4 (build_tree) are already non-tail recursions
lowered via explicit accumulator/stack. Enumerated: tail recursion -> loop;
non-tail via explicit accumulator/stack; committed-choice; aggregate-then-
iterate; plus the concrete patterns the owner named to cover - linear
recursion, transitive closures, tree recursion, mutual recursion. The
eligibility recognizer is a classifier over this family (each with its native
emission), interpreter-decline outside it; a survey of PLAWK + the resolver
should drive a general, target-agnostic recognizer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Read-only survey (Sonnet) of the project's deterministic recursion patterns,
with the two coordinator soundness rulings written in. Eight patterns with
PLAWK/resolver file:line examples, marker x emission x status each: tail;
linear non-tail; non-tail via explicit accumulator (region 3b); tree (region
4); mutual recursion (SCC + whole-SCC at-most-one-solution); committed-choice
(dep_breaks/5, close_moving, PLAWK process_all/4); aggregate-then-iterate;
transitive closure. Meta-calls join the committed-choice class only under three
preconditions (closed-world targets, closure resolution, mode-sensitive
determinism); transitive-closure emission must reproduce the source's exact
aggregate/reduction operator. Ranked gaps and a compositional
deterministic_recursion_class/2 recognizer design (SCC decomposition ->
meta-call resolution -> self-call analysis -> commit/aggregate detection ->
memoized whole-SCC determinism proof -> termination classification -> emission
selection) with the runtime decline-guard as safety net. No code/spec touched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
First committed-choice region (plan §6b): a recognizer/emitter widening, no
resolver.pl change. Generalizes region 4's committing--> family to accept a
committing condition whose per-iteration goal contains a nondet sub-goal
(dep_breaks_need -> member/2): the -> commits the first solution before the sole
tail self-call, so the predicate is single-solution. Native loop over the
depends list, commit-or-continue per element, P2 3-scalar snapshot, no choice
point.

G-4 at-most-one-solution proven from the commit (not head-exclusivity), doubly
confirmed by first_broken/4's first-solution consumption; other gates as regions
1-4; G-5 vacuous.

Verified both configs, both lanes (regions 1-4 on): term 2600/0/0 + 51/51,
store 503/0 + 51/51, output byte-identical on vs off across all 2600, full lib
199/199. Real B2 A/B (16-round interleaved, sha-distinct binaries): median
-3.15%, 13/16 rounds negative, t~-4.6 significant, consistent with the ~4.9%
dispatch share. Default ON. resolver.pl and resolver_store.pl unmodified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
The general deterministic_recursion_class pipeline (taxonomy section 11). New
shared core src/unifyweaver/core/deterministic_recursion.pl carries the
target-agnostic classification (call-graph + SCC, meta-call preconditions,
self/SCC-call arity+position, commit/aggregate boundary, structural
at-most-one-solution proof, termination class); only stage-7 emission is
per-target in wam_rust_target.pl. Reusable by every WAM backend.

Lowers three sibling-gap predicates none of the hand-written regions covered:
filter_satisfies/3 (list_filter), key_pkg_rows/3 (list_map_index),
tree_lookup/3 (bst_descent). Detects but defers emission for close_moving,
the non-tail mutual SCCs, and process_all/4-style meta-calls. Regions 1-5
untouched and win on overlap; the runtime decline-guard is the safety net.

Verified both configs, both lanes (regions 1-5 on): term 2600/0/0 + 51/51,
store 503/0 + 51/51, output byte-identical on vs off over all 2600 term cases
and scale-5000. cargo lib 220, classifier plunit 12, 21 genrec stress tests.
Real B3 A/B (20-round interleaved, sha-distinct binaries): scale-1000 -18.09%,
scale-5000 -23.84% (2 preds) / -21.38% (all 3), non-overlapping ranges,
default ON. resolver.pl and resolver_store.pl unmodified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Single-box (2026-09-07, 4 cores) re-measure of the complete Rust lowered tier
(regions 1-5 + general recognizer). All legs gated then timed. Headline: Rust
term B3 resolve_layered full-tier ON-vs-OFF = -80.7% (pristine ~1927ms ->
~372ms, 8-round interleaved, sha-distinct binaries), up from D82's -74%.

Honest Rust-vs-SWI per axis: startup Rust 0.048s beats SWI 0.113s; throughput
B2 SWI still ~7.2x faster (Rust 19.1s, down from 8.8x as regions landed); term
resolve B3 SWI ~19.5x (gap 98x->26x->19.5x); store resolve B3 SWI ~2.0x (Rust
store 0.040s, within a factor of two). Rust beats SWI only on startup; the
Rust-vs-Rust percentages close the gap, they do not overtake. Store legs
unchanged from D82.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
…D91)

Profile of the full lowered tier (same UW_PROF census + callgrind Ir as the old
65/21/12 baseline). Dispatch collapsed (step/backtrack/restore_regs self-Ir to
3.3% B2 / 0.3% B3) - the lowered tier maxed its lever. New hotspots, workloads
diverged: B2 is backtrack+allocate bound (~61% memory mgmt, ~16% deref_heap,
~11% functor-strings; 479k backtracks); B3 is sort+deref bound (~49% memory,
~24% deref, ~22% functor-string+compare; one msort that eager-derefs then
sorts on f/N strings). Declined-predicate interpreter fallback is only ~3% B2 /
~0% B3 of time, so more lowering has a ceiling of <=3%/~0%.

Ranked next lever: (1) intern functors (u32, kill f/N strings) ~11% B2 + ~20%
B3, cross-cutting; (2) cut Value-alloc churn (deref memoization, backtrack
saved-state, msort key-caching); (3) store path as default at scale (~10x at
B3); (4) more genrec (ceiling <=3%, do not lead). Strategic pivot from lowering
more predicates to optimizing the runtime term representation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Single ordered view of remaining work (plan section 10), so it isn't scattered
across ledger rows. Ordered by the D91 profile's expected payoff: (1) runtime
term-representation levers (intern functors u32; cut Value-alloc churn) - the
actual next-biggest now dispatch is gone; (2) genuine LMDB catalog tier on Rust
(user-requested) - fails loud today, wire a real Rust LMDB reader then measure
vs the working indexed seek store before committing; (3) blocked_from/4 resolver
round; (4) mutual-recursion stack-safe cyclic emission; (5) remaining genrec
emitters (ceiling <=3%); (6) subsume regions 1-5 into genrec; (7) cross-target
reuse of the shared-core classifier + store-as-default; (8) the shelved 2b
trampoline. Store-as-default-at-scale is in flight separately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
Fold the project owner's reasoning into backlog item 2 (plan section 10): LMDB
should win at scale because it is memory-mapped (OS page cache keeps hot pages
resident, no syscall-per-read, working set can exceed RAM) vs the indexed
store's explicit ReadAt reads - predicting a second scale crossover
(term -> indexed store -> lmdb), so the store switch is a ladder and the LMDB
investigation must find the indexed-store->LMDB crossover specifically.
Reference the in-repo graph-algorithm cache models to reuse rather than restart:
WAM_RUST_LMDB_CRATE_DECISION.md, WAM_LMDB_RESIDENT_INTERNING_IMPLEMENTATION_PLAN.md,
WAM_HASKELL_LMDB_CACHE_TIERS.md, the boundary-cache benchmark, and the Rust store
crate's existing LMDB scaffolding (boundary_cache.rs, lazy_lmdb_lookup).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
New resolve_auto.mjs: an ordered size->backend ladder that makes the D43
indexed seek store the automatic default at scale. Default threshold 500
packages (below->term, at/above->indexed store), overridable and forceable,
demotes to term when no store (never a wrong/failed resolve). Backends are an
enum/extension point so a future LMDB tier slots in as a third rung. Driver-
level routing only - frozen resolver.pl/resolver_store.pl and the crates are
unchanged; the store shim accepts the same term-format case line, so the switch
just picks which binary reads each case.

Crossover: term load+resolve linear (50ms@371 -> 403ms@7522), store resolve
flat ~40ms, break-even ~200-300 packages, ~10.7x at 7522 (binary-level).

Verified: all four gates pass (term 2600/0/0 + 51/51, store 503/0 + 51/51);
run_auto_switch_test.sh confirms auto->store, force-store, force-term,
small->term, mixed-stream, and store-unavailable fallback are all
byte-identical to the term oracle. resolver.pl and resolver_store.pl unmodified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PJqq7jMzces6bMwTnPbpd
@s243a s243a changed the title uw-resolve package manager + WAM-fleet parity (D44–D71) uw-resolve: package manager, WAM-fleet parity, Rust lowered tier + store-at-scale (D44–D93) Sep 7, 2026
@s243a
s243a marked this pull request as ready for review September 8, 2026 00:01
@s243a
s243a merged commit ef148c5 into main Sep 8, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants