uw-resolve: package manager, WAM-fleet parity, Rust lowered tier + store-at-scale (D44–D93) - #4223
Merged
Merged
Conversation
…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
marked this pull request as ready for review
September 8, 2026 00:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.plremain 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/3versions 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
deterministic_recursion_classrecognizer 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.Benchmarks & profile (current)
examples/pkg_resolver/BENCHMARKS.mdrefreshed 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-8for 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