on master help sign in
200 files changed+104022−31915
since splitsnapshots
unifiedsplit
.DS_Store .cargo/config.toml+17 .github/workflows/clippy.yml+25 .github/workflows/release-binaries.yml+85 .gitignore+29 .hermes/RECORD_MIGRATION_SPEC.md+48 .hermes/UNIFIED_NAMESPACE_BRIEF.md+447 .hermes/doccheck.py+344 .hermes/plans/2026-07-22-sources-in-records.md+212 .hermes/sql-pushdown-opportunities.md+102 Bool CLAUDE.md−115 Cargo.lock+2468−487 Cargo.toml+29−15 DESIGN.md−1396 README.md+6−47 base.md−610 crates/knot-compile-rt/Cargo.toml+14 crates/knot-compile-rt/src/lib.rs+117 crates/knot-compiler/Cargo.toml+26−1 crates/knot-compiler/build.rs+134−50 crates/knot-compiler/src/base.rs+461−82 crates/knot-compiler/src/builtins.rs+252 crates/knot-compiler/src/codegen.rs+19586−5919 crates/knot-compiler/src/decl_view.rs+176 crates/knot-compiler/src/desugar.rs+1187−679 crates/knot-compiler/src/effects.rs−999 crates/knot-compiler/src/infer.rs+13885−2419 crates/knot-compiler/src/lib.rs+8−3 crates/knot-compiler/src/linker.rs+42−6 crates/knot-compiler/src/lockfile.rs+601−383 crates/knot-compiler/src/main.rs+1060−67 crates/knot-compiler/src/modules.rs−272 crates/knot-compiler/src/nonterm.rs+384 crates/knot-compiler/src/refine_smt.rs+254 crates/knot-compiler/src/sql_lint.rs+1247 crates/knot-compiler/src/stack.rs+90 crates/knot-compiler/src/stratify.rs+672−233 crates/knot-compiler/src/types.rs+2159−288 crates/knot-compiler/src/unused.rs+403 crates/knot-compiler/src/unwind.rs+333 crates/knot-compiler/tests/base_listing.rs+73 crates/knot-compiler/tests/compile_boundary.rs+334 crates/knot-compiler/tests/compile_panic_containment.rs+51 crates/knot-compiler/tests/descriptor_roundtrip.rs+84 crates/knot-descriptor/Cargo.toml+6 crates/knot-descriptor/src/lib.rs+376 crates/knot-jit/Cargo.toml+25 crates/knot-jit/src/lib.rs+365 crates/knot-jit/tests/adts.rs+109 crates/knot-jit/tests/cli_overrides.rs+982 crates/knot-jit/tests/concurrency.rs+94 crates/knot-jit/tests/constraints.rs+115 crates/knot-jit/tests/crypto.rs+116 crates/knot-jit/tests/e2e.rs+149 crates/knot-jit/tests/errors.rs+111 crates/knot-jit/tests/extract.rs+438 crates/knot-jit/tests/functions.rs+304 crates/knot-jit/tests/harness.rs+139 crates/knot-jit/tests/http.rs+166 crates/knot-jit/tests/jit_e2e.rs+197 crates/knot-jit/tests/lists.rs+132 crates/knot-jit/tests/literal_pushdown.rs+155 crates/knot-jit/tests/migration.rs+485 crates/knot-jit/tests/migration_corner.rs+1431 crates/knot-jit/tests/nested_and_meta.rs+85 crates/knot-jit/tests/numeric_text.rs+154 crates/knot-jit/tests/patterns.rs+123 crates/knot-jit/tests/persistence.rs+968 crates/knot-jit/tests/refinements.rs+273 crates/knot-jit/tests/relations.rs+292 crates/knot-jit/tests/scalars.rs+238 crates/knot-jit/tests/single_value.rs+174 crates/knot-jit/tests/smoke.rs+31 crates/knot-jit/tests/text_bytes.rs+120 crates/knot-jit/tests/type_errors.rs+107 crates/knot-jit/tests/units.rs+103 crates/knot-jit/tests/views.rs+68 crates/knot-lsp/Cargo.toml+1 crates/knot-lsp/src/analysis.rs+740 crates/knot-lsp/src/builtins.rs+9 crates/knot-lsp/src/call_hierarchy.rs+295 crates/knot-lsp/src/code_action.rs+1804 crates/knot-lsp/src/code_lens.rs+124 crates/knot-lsp/src/completion.rs+1727 crates/knot-lsp/src/defs.rs+665 crates/knot-lsp/src/diagnostics.rs+223 crates/knot-lsp/src/document_highlight.rs+87 crates/knot-lsp/src/document_symbol.rs+254 crates/knot-lsp/src/folding.rs+153 crates/knot-lsp/src/formatting.rs+282 crates/knot-lsp/src/goto.rs+176 crates/knot-lsp/src/hover.rs+547 crates/knot-lsp/src/incremental.rs+535 crates/knot-lsp/src/inlay_hints.rs+1219 crates/knot-lsp/src/legend.rs+53 crates/knot-lsp/src/lib.rs+1 crates/knot-lsp/src/linked_editing.rs+334 crates/knot-lsp/src/main.rs+1281−3155 crates/knot-lsp/src/parsed_type.rs+743 crates/knot-lsp/src/references.rs+153 crates/knot-lsp/src/rename.rs+1090 crates/knot-lsp/src/selection_range.rs+182 crates/knot-lsp/src/semantic_tokens.rs+824 crates/knot-lsp/src/shared.rs+1147 crates/knot-lsp/src/signature_help.rs+285 crates/knot-lsp/src/state.rs+804 crates/knot-lsp/src/type_format.rs+345 crates/knot-lsp/src/utils.rs+556 crates/knot-lsp/src/workspace_diagnostics.rs+722 crates/knot-lsp/src/workspace_symbol.rs+212 crates/knot-runtime/Cargo.toml+17−6 crates/knot-runtime/src/db_dump.rs+143 crates/knot-runtime/src/lib.rs+21501−4926 crates/knot-runtime/src/log.rs+305 crates/knot-runtime/src/tui.rs+228−180 crates/knot/src/ast.rs+523−217 crates/knot/src/diagnostic.rs+102−76 crates/knot/src/format.rs+1523 crates/knot/src/lexer.rs+513−398 crates/knot/src/lib.rs+1 crates/knot/src/parser.rs+3071−2206 crates/knot/tests/parser.rs−3562 editors/vscode/package-lock.json+485 editors/vscode/package.json+6−4 editors/vscode/src/extension.ts+35−6 editors/vscode/syntaxes/knot.tmLanguage.json+3−7 examples/.gitignore+1 examples/apiserver.knot+92 examples/arith_aggregate.knot+23 examples/arith_aggregate.schema.lock+7 examples/associated_types.knot−21 examples/builtins.knot+31 examples/bytes.knot+54−36 examples/cons_pattern.knot+16 examples/constraints.knot+15−15 examples/constraints.schema.lock+6−9 examples/crypto.knot+51−31 examples/fold_dictionaries.knot+20 examples/fold_stream.knot+33 examples/fold_stream.schema.lock+7 examples/for_each.knot+12 examples/forall.knot+27 examples/groupby.knot+17−16 examples/groupby.schema.lock−8 examples/hash.knot+25 examples/hello.knot+7−6 examples/hello.schema.lock+5−6 examples/hole.knot+35 examples/implicit_dictionaries.knot+31 examples/inline_pushdown.knot+55 examples/inline_pushdown.schema.lock+7 examples/json.knot+24−20 examples/let_pushdown.knot+39 examples/log_test.knot+12 examples/maybe.knot+34−62 examples/migrate.knot+34 examples/modules/lib/core.knot−1 examples/modules/lib/helpers.knot−3 examples/modules/main.knot−10 examples/modules/main.schema.lock−21 examples/modules/transitive.knot−5 examples/modules/types.knot−5 examples/modules/utils.knot−3 examples/morph.knot+45 examples/nested.knot+27−17 examples/nested.schema.lock+5−6 examples/ord.knot+61 examples/query_opt.knot+87−88 examples/query_opt.schema.lock+7−11 examples/race.knot+30 examples/recursive.knot+18−24 examples/recursive.schema.lock+4−4 examples/result.knot+30−51 examples/routes.knot+33−40 examples/routes.schema.lock−12 examples/showcase.knot+407−268 examples/showcase.schema.lock+28−88 examples/stm_row_filter.knot+35 examples/stm_row_filter.schema.lock+7 examples/temporal.knot−29 examples/temporal.schema.lock−5 examples/todo.knot+16−20 examples/todo.schema.lock+5−6 examples/trait_replacement.knot+25 examples/trait_replacement.o examples/traits.knot−58 examples/uuid.knot+8 examples/views.knot−45 examples/views.schema.lock−15 examples/views_history.knot−49 examples/views_history.schema.lock−11 examples/when_unless.knot+7 knot.md−991 stdlib.md−723 tree-sitter-knot/grammar.js+16−30 tree-sitter-knot/queries/highlights.scm+3−2 tree-sitter-knot/src/grammar.json+67−4 tree-sitter-knot/src/node-types.json+33−33 tree-sitter-knot/src/parser.c+193−193
.DS_Storedeleted

Binary file not shown.

.cargo/config.tomlnew file+17
@@ -0,0 +1,17 @@
1+# Export the host binary's `knot_*` runtime symbols into the dynamic symbol
2+# table so code JIT-compiled at runtime (the `compile` builtin / knot-jit) can
3+# resolve them against the running process via dlsym(RTLD_DEFAULT). AOT builds
4+# also pass this via knot-compiler's linker (linker.rs); this config covers
5+# Rust-built binaries in this workspace (tests, dev tools).
6+#
7+# The flag is linker-specific: `--export-dynamic` is the GNU/ELF form (Linux),
8+# while macOS's ld64 (Mach-O) uses `-export_dynamic` — and rejects the GNU
9+# spelling with "linker command failed". Scope per-target so cross-building
10+# the macOS release binary isn't broken by the ELF flag. (macOS also gets
11+# `-force_load`/`-export_dynamic` from knot-compiler's build.rs, so it needs
12+# nothing here.)
13+[target.x86_64-unknown-linux-gnu]
14+rustflags = ["-C", "link-args=-Wl,--export-dynamic"]
15+
16+[target.aarch64-unknown-linux-gnu]
17+rustflags = ["-C", "link-args=-Wl,--export-dynamic"]
.github/workflows/clippy.ymlnew file+25
@@ -0,0 +1,25 @@
1+name: Clippy
2+
3+on:
4+ push:
5+ branches: [master]
6+ pull_request:
7+
8+env:
9+ CARGO_TERM_COLOR: always
10+
11+jobs:
12+ clippy:
13+ runs-on: ubuntu-latest
14+ steps:
15+ - uses: actions/checkout@v4
16+
17+ - name: Install Rust toolchain
18+ run: rustup component add clippy
19+
20+ - uses: Swatinem/rust-cache@v2
21+ with:
22+ key: clippy
23+
24+ - name: Run clippy
25+ run: cargo clippy --all-targets --workspace -- -D warnings
.github/workflows/release-binaries.ymlnew file+85
@@ -0,0 +1,85 @@
1+name: Release Binaries
2+
3+on:
4+ push:
5+ branches: [master]
6+ workflow_dispatch:
7+
8+permissions:
9+ contents: write
10+
11+jobs:
12+ version:
13+ runs-on: ubuntu-latest
14+ outputs:
15+ tag: ${{ steps.version.outputs.tag }}
16+ steps:
17+ - uses: actions/checkout@v4
18+
19+ - name: Compute version from commit date
20+ id: version
21+ run: |
22+ read YEAR MONTH DAY HOUR MINUTE <<< \
23+ "$(TZ=UTC git log -1 --format=%cd --date=format-local:'%Y %-m %-d %-H %-M')"
24+ TIME=$((HOUR * 100 + MINUTE))
25+ echo "tag=${YEAR}.${MONTH}.${DAY}.${TIME}" >> "$GITHUB_OUTPUT"
26+
27+ build:
28+ needs: version
29+ strategy:
30+ fail-fast: false
31+ matrix:
32+ include:
33+ - os: ubuntu-latest
34+ target: x86_64-unknown-linux-gnu
35+ name: linux-x86_64
36+ - os: ubuntu-24.04-arm
37+ target: aarch64-unknown-linux-gnu
38+ name: linux-aarch64
39+ - os: macos-15
40+ target: aarch64-apple-darwin
41+ name: macos-aarch64
42+ runs-on: ${{ matrix.os }}
43+ env:
44+ CARGO_TERM_COLOR: always
45+ steps:
46+ - uses: actions/checkout@v4
47+
48+ - name: Install Rust target
49+ run: rustup target add ${{ matrix.target }}
50+
51+ - uses: Swatinem/rust-cache@v2
52+ with:
53+ key: ${{ matrix.target }}
54+
55+ - name: Build knot, knot-lsp, and runtime
56+ run: cargo build --release --target ${{ matrix.target }} -p knot-compiler -p knot-lsp -p knot-runtime
57+
58+ - name: Package knot
59+ shell: bash
60+ run: |
61+ STAGE="knot-${{ needs.version.outputs.tag }}-${{ matrix.name }}"
62+ mkdir -p "$STAGE"
63+ cp "target/${{ matrix.target }}/release/knot" "$STAGE/"
64+ cp "target/${{ matrix.target }}/release/libknot_runtime.a" "$STAGE/"
65+ tar -czf "$STAGE.tar.gz" "$STAGE"
66+ shasum -a 256 "$STAGE.tar.gz" > "$STAGE.tar.gz.sha256"
67+
68+ - name: Package knot-lsp
69+ shell: bash
70+ run: |
71+ STAGE="knot-lsp-${{ needs.version.outputs.tag }}-${{ matrix.name }}"
72+ mkdir -p "$STAGE"
73+ cp "target/${{ matrix.target }}/release/knot-lsp" "$STAGE/"
74+ tar -czf "$STAGE.tar.gz" "$STAGE"
75+ shasum -a 256 "$STAGE.tar.gz" > "$STAGE.tar.gz.sha256"
76+
77+ - name: Upload to release
78+ uses: softprops/action-gh-release@v2
79+ with:
80+ tag_name: ${{ needs.version.outputs.tag }}
81+ name: Knot ${{ needs.version.outputs.tag }}
82+ files: |
83+ knot-*.tar.gz
84+ knot-*.tar.gz.sha256
85+ make_latest: true
.gitignore+29
@@ -2,6 +2,10 @@
22*.db
33*.db-shm
44*.db-wal
5+.DS_Store
6+/.vibe
7+/.vix
8+.opencode-tmp
59examples/hello
610examples/todo
711examples/traits
@@ -23,3 +27,28 @@ examples/views_history
2327examples/query_opt
2428examples/showcase
2529examples/crypto
30+examples/log_test
31+examples/fold_stream
32+examples/inline_pushdown
33+examples/hash
34+examples/let_pushdown
35+examples/cons_pattern
36+examples/required_args
37+examples/find_first
38+examples/when_unless
39+examples/for_each
40+examples/stm_row_filter
41+examples/uuid
42+examples/race
43+examples/builtins
44+examples/implicit_dictionaries
45+examples/ord
46+examples/trait_replacement
47+
48+# Schema lockfiles (regenerated on each build)
49+*.schema.lock
50+__pycache__/
51+*.pyc
52+
53+# Hermes agent workspace (local tooling/notes)
54+/.hermes/
.hermes/RECORD_MIGRATION_SPEC.mdnew file+48
@@ -0,0 +1,48 @@
1+# Knot record/`with` syntax migration spec
2+
3+The knot language DROPPED the old record delimiters. Colon and comma are gone
4+from record EXPRESSIONS, record PATTERNS, and `with` blocks. Record TYPES keep
5+their old syntax. There is NO field punning anymore.
6+
7+## The parser rule (so output must re-parse)
8+A record field value is a SINGLE ATOM or a PARENTHESIZED compound. After a
9+completed value, a bare lowercase identifier ALWAYS opens the next field. So a
10+value that is an application, binary/unary op, lambda, `if`, `do`, `case`,
11+`with`, or any multi-token expression MUST be wrapped in parentheses.
12+
13+Atomic values (NO parens needed): integer/float/text/bool literals, a variable
14+(`x`), a field-access chain (`r.a`, `a.b.c`), a constructor (`Just`, `Nothing`),
15+a nested record (`{...}`), a list (`[...]`), an already-parenthesized expr.
16+
17+## Transformations
18+
19+1. Record EXPRESSION `{f: v, g: w}` -> `{f v g w}`
20+ - drop `:` after each field name, drop `,` separators, use ONE space.
21+ - parenthesize non-atomic `v`/`w`:
22+ `{value: f value}` -> `{value (f value)}`
23+ `{n: 1 + 2}` -> `{n (1 + 2)}`
24+ `{name: e.name}` -> `{name e.name}` (field-access is atomic)
25+ `{x: {a: 1}}` -> `{x {a 1}}` (nested record, recurse)
26+
27+2. Record UPDATE — REMOVED. `{base | f: v, g: w}` / `{base | f v g w}` no
28+ longer parse. Use `base.unify base {f v g w}` instead (right-biased merge;
29+ it is update AND extension, and works on open rows like lambda params).
30+
31+3. `with` BLOCK `with {x: e, y: e2} body` -> `with {x e y e2} body`
32+ - field names are the bindings; same value-parenthesization rule.
33+
34+4. Record PATTERN `{f: p, g: q}` -> `{f p g q}`
35+ - PUNNING GONE: `Just {value}` -> `Just {value value}`
36+ `Just {value: x}` -> `Just {value x}`
37+ - empty patterns `Nothing {}` / `Just {}` unchanged.
38+
39+## DO NOT CHANGE
40+- Record TYPES in annotations/aliases: `{name: Text, age: Int}` (KEEP `:` `,`).
41+- Type annotations `x : Int`, signatures `f : a -> b`.
42+- Effect rows `IO {| e} {}` (the `{| e}` row-tail and trailing `{}` stay).
43+- Rust code itself — only the knot source inside string literals / .knot files.
44+
45+## Verify
46+After editing a file, run its test target and make it pass:
47+ cargo test -p <crate> --test <file_stem>
48+The compiler validates the new syntax; a green test run is the acceptance gate.
.hermes/UNIFIED_NAMESPACE_BRIEF.mdnew file+447
@@ -0,0 +1,447 @@
1+# Implementation brief — unified value/type namespace with type-directed explicit type arguments (erased)
2+
3+Scope: investigation only; no code changes. Anchors cite `crates/knot` and
4+`crates/knot-compiler` sources as of this writing.
5+
6+## 0. Verified starting facts
7+
8+- Parser emits a neutral `ExprKind::Constructor(name)` for any Upper name in
9+ expr position (parser.rs:2924-2927); lowercase → `ExprKind::Var`
10+ (parser.rs:2919-2922). No parser change needed for the *argument* side.
11+- Typechecker `Constructor(name)` arm: infer.rs:5064; `instantiate_ctor`
12+ (infer.rs:4935) returns `Some((data_ty, record_ty))` for a known data ctor,
13+ else the arm errors "unknown constructor" (infer.rs:5091-5096). This else
14+ branch is the unified-namespace hook. Confirmed today: `f = Int 1` in value
15+ position errors exactly there ("unknown constructor 'Int'").
16+- Application: infer-mode `App` arm at infer.rs:5313; generic path at
17+ infer.rs:5450-5457 (`arg_ty = infer_expr(arg); unify(func_ty, Fun(arg_ty,
18+ result))`). There is already a *directed* precedent: the higher-rank slot
19+ check at infer.rs:5435-5448 inspects the *function's* parameter type before
20+ deciding how to handle the argument (checks arg against `Ty::Forall`).
21+ There is NO separate check-mode `App` arm; `check_expr` falls through to
22+ infer+unify at infer.rs:6088-6093.
23+- Type binder form `\(T : Type)` is free syntax: any parenthesized
24+ typed-pattern is a parse error today ("expected ')' to close pattern
25+ group", parser.rs:3799 and 3922 — verified for both `\(T : Type)` and
26+ `\(x : Int 1)`). `parse_lambda` (parser.rs:3294) parses params via
27+ `parse_pat` (parser.rs:3709) in the loop at parser.rs:3310-3321.
28+- Erasure precedent: `ExprKind::TypeCtor` (ast.rs:293-297) is typed as its
29+ kind `Type -> ... -> Type` in infer (infer.rs:5868-5897) and erased to
30+ `knot_value_unit` in codegen (codegen.rs:5563-5567).
31+- `Ty::Con("Type", [])` is the kind marker (infer.rs:5874, 5890-5894);
32+ `Ty::Unit(..)` shows the "kind ≠ Type, no value inhabitants" pattern
33+ (types in infer.rs:376-381). A signature `f : Type -> Type` already
34+ typechecks today (`Type` elaborates via the default `Named` arm of
35+ `ast_type_to_ty`, infer.rs:4504-4506, to `Ty::Con("Type", [])`).
36+- `Ty::Forall(Vec<TyVar>, Box<Ty>)` exists (infer.rs:386);
37+ `skolemise_scheme` (infer.rs:3471) + the `check_expr` Forall arm
38+ (infer.rs:5912-5944) + `skolemise_forall_body` (infer.rs:1922) implement
39+ rigid-var scoping. Top-level `id : forall a. a -> a` works through
40+ pre-register (infer.rs:8114-8124 lifts outermost Forall into the Scheme).
41+- Inline `: forall` on an *infer-mode* lambda value is not rejected by
42+ syntax; it is handled (and effectively rejected via the escape check) at
43+ infer.rs:5801-5829 — message "polymorphic type escapes its scope … an
44+ inline `forall` annotation cannot make a monomorphic value polymorphic".
45+
46+## 1. TYPE BINDER — `\(T : Type)` AST + parser design
47+
48+Recommendation: **new `PatKind` variant**, not a lambda attribute or expr
49+wrapper.
50+
51+```rust
52+// ast.rs, in PatKind (after `Var`):
53+/// `(T : Type)` — a lambda-only type-witness binder. Binds the *type* `T`
54+/// in the body; erased at runtime (no value is passed).
55+TypeWitness { name: Name, kind: Type },
56+```
57+
58+Why a pattern:
59+- Lambda params are `Vec<Pat>` (ast.rs:218-221); every consumer already
60+ matches on `PatKind`, so the new form is additive at well-known sites
61+ (§6). A lambda-level attribute (`type_params: Vec<Name>`) would force
62+ positional alignment with `params` and complicate interleaving
63+ (`\(T : Type) x (U : Type) y -> …`); an `Expr` wrapper
64+ (`ExprKind::TyLam`) would duplicate the entire lambda handling in infer,
65+ codegen, desugar, effects, formatter, LSP — far from minimal-diff.
66+- Patterns already have a precedent for "binder with payload": `Constructor
67+ { name, payload }` (ast.rs:380).
68+
69+Parser change (small, two places):
70+- In `parse_lambda`'s param loop (parser.rs:3310-3321), before calling
71+ `parse_pat`, peek for `LParen Upper Colon` — or better, add the form
72+ inside `parse_pat_inner`'s `LParen` arm (parser.rs:3789-3802): after
73+ consuming `(`, if `peek` is `Upper(_)` *and* the token after is
74+ `TokenKind::Colon`, parse `Name : Type )` into `PatKind::TypeWitness`.
75+ Putting it in `parse_pat_inner` keeps one pattern entry point, but note
76+ `parse_pat` is also used for case arms — the new kind must then be
77+ *rejected in case patterns* (typechecker: `check_pattern` arm errors
78+ "type-witness binder is only allowed as a lambda parameter") unless you
79+ deliberately want type-level case. Recommend lambda-only for v1: enforce
80+ by a flag param (`parse_pat(allow_witness: bool)`) or by post-parse
81+ validation in `parse_lambda`.
82+- `push_pat_vars` (parser.rs:285-309): new arm — push nothing (a witness
83+ binds no *value* var) or push the name into a separate
84+ `bound_type_vars` stack if you want the parser to know `T` is in type
85+ scope (not strictly needed; the typechecker can own this).
86+
87+Signature surface form `\(T : Type) -> T -> T`:
88+- `ast::Type` does NOT need a new node for the *witness arrow itself* if
89+ you represent it as an ordinary `TypeKind::Function { param: Named
90+ "Type", result }` — i.e. the surface signature for the example is
91+ literally `f : Type -> T -> T`. But then `T` is an unbound lowercase var
92+ unless the *declaration* signature quantifies it: the natural surface
93+ spelling is `f : forall a. Type -> a -> a` … which is confusing.
94+- Recommended: add ONE new `TypeKind`:
95+
96+```rust
97+/// `(a : Type) -> T` — explicit type-witness arrow (dependent-ish arrow,
98+/// erased). Binds `a` in `T`.
99+TyArrow { var: Name, kind: Box<Type>, body: Box<Type> },
100+```
101+
102+ It elaborates in `ast_type_to_ty` (infer.rs:4406) to
103+ `Ty::Fun(Con("Type",[]), body)` with `annotation_vars[var]` bound to a
104+ fresh var *that is also registered as the witness* (see §2). The
105+ advantage over reusing `Function{Named "Type"}`: the name binding is
106+ explicit and scoped, and the formatter round-trips the user's syntax.
107+ Minimal alternative (zero new Type nodes): require users to write
108+ `f : forall a. Type -> a -> a` and treat the *first* `Type` argument
109+ positionally. Cheaper but worse UX and fragile with multiple witnesses.
110+
111+## 2. TYPE-WITNESS IN SCOPE
112+
113+Represent a bound `T` exactly like today's signature-bound rigid vars —
114+**reuse skolems + `annotation_vars`**, do not build a new mechanism:
115+
116+- When checking a lambda against an expected type (the `check_expr` Lambda
117+ arm, infer.rs:5959-5993, which peels `Fun` arrows), a peeled param whose
118+ type is `Ty::Con("Type", [])` paired with a `PatKind::TypeWitness{name}`
119+ binder should:
120+ 1. allocate a fresh TyVar `s`, insert into `self.skolems` (rigid),
121+ 2. `self.annotation_vars.insert(name, s)` — this is what makes a later
122+ param `x : T` (or an inner annotation `(e : T)`) resolve `T` to the
123+ same rigid var, because `ast_type_to_ty`'s `TypeKind::Var` arm
124+ (infer.rs:4509-4512) goes through `annotation_var`
125+ (infer.rs:4698-4706). NOTE: `TypeKind::Var` requires *lowercase*
126+ names; for uppercase witnesses (`T`) you need `TypeKind::Named("T")`
127+ to consult `annotation_vars` FIRST (before aliases/data) — a small,
128+ contained change at infer.rs:4408: at the top of the `Named` arm, `if
129+ let Some(v) = self.annotation_vars.get(name) { return Ty::Var(*v); }`
130+ guarded by a new flag (e.g. `in_witness_scope`) so ordinary
131+ annotations don't start resolving type names to vars.
132+ 3. NOT bind anything in the value scope (`self.bind`) — the witness has
133+ no value.
134+- At a call site, the witness var is just a scheme variable: when `f`'s
135+ scheme is instantiated (`instantiate_at`), the witness position becomes a
136+ fresh flexible var, and the explicit type argument unifies with it (§3).
137+ This is precisely how `forall a.` already scopes rigid vars through a
138+ body — no new scoping machinery.
139+- Drop the witness skolems at lambda scope exit (mirror the `for s in
140+ fresh_skolems { self.skolems.remove(&s); }` pattern at infer.rs:5941-5943)
141+ and restore the previous `annotation_vars` entry (mirror the save/restore
142+ in `ast_type_to_ty`'s Forall arm, infer.rs:4670-4692).
143+- Escape check: reuse the existing free-vars-in-env leak check
144+ (infer.rs:5930-5940) so a witness can't leak into the enclosing type.
145+
146+## 3. ARG REINTERPRETATION (type-directed application)
147+
148+The signal must live in the **function's type**: a leading parameter whose
149+type is the kind marker `Ty::Con("Type", [])`. That is already the
150+convention established by `TypeCtor` (infer.rs:5889-5896), so "this arrow
151+is a type arrow" is representable today with zero new `Ty` variants.
152+
153+In the infer-mode `App` arm (infer.rs:5313), extend the *existing* directed
154+precedent at infer.rs:5435-5448 (which already inspects the function's
155+param type before handling the arg):
156+
157+```rust
158+let func_ty = self.infer_expr(func);
159+let func_applied = self.apply(&func_ty);
160+if let Ty::Fun(arg_slot, ret_ty) = &func_applied {
161+ let arg_slot_resolved = self.apply(arg_slot);
162+ // NEW ARM — before the existing Forall arm:
163+ if matches!(arg_slot_resolved.peel_alias(), Ty::Con(n, args) if n == "Type" && args.is_empty()) {
164+ // The function expects a TYPE as its next argument. Reinterpret
165+ // the argument expression as a type.
166+ let ty_arg = self.expr_as_type(arg); // see below
167+ self.unify(arg_slot, &ty_arg, arg.span); // or unify_dir with polarity
168+ return (**ret_ty).clone();
169+ }
170+ if matches!(arg_slot_resolved, Ty::Forall(..)) { …existing… }
171+}
172+```
173+
174+`expr_as_type(arg)` — synthesize an `ast::Type` from the arg `Expr` and run
175+the existing `ast_type_to_ty`:
176+- `ExprKind::Constructor(name)` → `TypeKind::Named(name)` (aliases,
177+ parameterized data (`TyCon`), and plain `Con` all handled by the existing
178+ `Named` arm, infer.rs:4408-4508).
179+- `ExprKind::Var(name)` → `TypeKind::Var(name)` — resolves to an in-scope
180+ witness via `annotation_vars` (needs the §2 guard so lowercase works too,
181+ or map Var→Named for uniformity).
182+- `ExprKind::App{func, arg}` (e.g. `f (Maybe Int)`) →
183+ `TypeKind::App{func: expr_as_type(func), arg: expr_as_type(arg)}` —
184+ `ast_type_to_ty`'s App arm (infer.rs:4532-4562) handles `Con` arg
185+ accumulation, `TyCon`/`Var` HK application, and `expand_param_alias`
186+ (infer.rs:4353) already peels App spines.
187+- Anything else → error "expected a type expression".
188+
189+Important subtleties:
190+- `Int` / `Float` special case: the `Named` arm *errors* on bare `Int` in
191+ annotation mode (infer.rs:4409-4416: "bare `Int` requires a unit").
192+ `expr_as_type` should set `in_type_annotation = true` (consistent with
193+ how annotations behave) — so the explicit type arg for a dimensionless
194+ int is written `Int 1`, which parses in expr position as
195+ `App(Constructor("Int"), Lit(1))`. Handle by mapping that App shape to
196+ `TypeKind::UnitAnnotated` (ast.rs:498-501) when func is `Named
197+ "Int"/"Float"` and arg is `Lit(Int("1"))` — or more generally route
198+ through `parse_unit_type_arg` semantics. This is the main
199+ `Int 1`-collision risk (§7) and is solvable entirely inside
200+ `expr_as_type`.
201+- Also wire the same check into the two early-return App paths that bypass
202+ the generic path if they can precede a witness slot: the ctor-application
203+ path (infer.rs:5358-5364) and the lambda-last path (5402-5427). The
204+ lambda-last path triggers on `Var` heads with 2-arg signatures —
205+ `f Int (\x -> x)` where `f : Type -> (Int 1 -> Int 1) -> …` would hit it;
206+ make `takes_two_args` peeling (infer.rs:11474-11478) witness-aware or
207+ exclude functions whose first param is kind `Type`.
208+- Erasure of the arg: because the new App arm returns before ever calling
209+ `infer_expr(arg)` on the type argument, the argument's inferred-type
210+ record never binds a value type for it. For codegen, mark the arg span in
211+ a side set (`self.type_arg_spans.insert(arg.span)`) so codegen's
212+ `compile_app` can skip emitting it (§5). Record the *witness→type*
213+ binding (`(func.span, TyVar) → Ty`) in another side table
214+ (`witness_bindings`, same pattern as `show_calls` infer.rs:5472-5475 and
215+ `traverse_calls` 5480-5486) if codegen ever needs the resolved type
216+ (e.g. for runtime type-directed dispatch later). For pure erasure, the
217+ span set alone suffices.
218+
219+## 4. UNIFIED NAMESPACE — `Constructor(name)` else-branch
220+
221+Change infer.rs:5087-5096 from unconditional error to:
222+
223+1. If `instantiate_ctor` succeeded → existing value behavior (unchanged).
224+2. Else, if the name resolves as a *type* in scope — i.e. any of:
225+ - `self.aliases.contains_key(name)` or
226+ `self.param_aliases.contains_key(name)` (alias / parameterized alias),
227+ - `self.data_types.contains_key(name)` (a data type name used as a
228+ type — note `instantiate_ctor` looks up *constructor* names in
229+ `self.constructors`, which is keyed by ctor name, so a data type whose
230+ ctor names differ from the type name lands here),
231+ - `self.annotation_vars` contains `name` under the §2 witness flag,
232+ - `name` is `Int`/`Float`/`Text`/`Bool`/`Bytes`/`Uuid` (builtin types),
233+ then return the kind type `Ty::Con("Type", [])` (or `Type -> … -> Type`
234+ for a parameterized head, mirroring the fold at infer.rs:5889-5895) and
235+ record `(expr.span → name)` in a side table so codegen erases this
236+ occurrence to unit (exactly like `TypeCtor`, codegen.rs:5563-5567).
237+3. Else keep the "unknown constructor" error.
238+
239+This makes `map Int [1,2]` (type-witness argument position) work through §3
240+*and* lets a bare type name appear wherever a kind-`Type` value is
241+expected.
242+
243+Precedence when a name is BOTH a data ctor and a type (e.g. `data Box a =
244+Box {value: a}` — `Box` is both `self.constructors["Box"]` and
245+`self.data_types["Box"]`):
246+- Value position: `instantiate_ctor` succeeds → data ctor wins. Already
247+ the behavior; unchanged.
248+- Type-witness position (§3 arm): `expr_as_type` maps
249+ `Constructor("Box")` → `TypeKind::Named("Box")` → `ast_type_to_ty`
250+ resolves the *type* (data_types/aliases), never consulting
251+ `self.constructors`. Type wins.
252+- So precedence is positional, not global: **ctor in value position, type
253+ in witness position** — confirmed implementable because the two paths
254+ never share a lookup. The only overlap is the §4 else-branch, which only
255+ fires when the name is NOT a ctor, so no ambiguity is introduced there.
256+- `check_expr` expected-type-aware variant (optional refinement): in
257+ `check_expr`'s fallthrough (infer.rs:6088-6093), a `Constructor` expr
258+ whose expected type is kind `Type` could be routed directly to
259+ `expr_as_type`. Not required for v1 since §3's App-directed path covers
260+ the intended use.
261+
262+## 5. ERASURE / CODEGEN
263+
264+- Lambda params become runtime params in `compile_lambda_inner`
265+ (codegen.rs:10996; signature fixed at codegen.rs:11060-11065 as
266+ `(db, env, arg)`; multi-param currying at 11005-11022; `PendingLambda`
267+ carries `param_pat`, 11072-11079; binding emitted via the
268+ `PatKind::Var(name) => env.set(name, …)` pattern at codegen.rs:3728 and
269+ the general binder at 8081-8126).
270+- Dropping witness params: in `compile_lambda_inner`, filter
271+ `PatKind::TypeWitness` out of `params` BEFORE the curry split and before
272+ `pat_bound_names` (codegen.rs:11029-11032). Because infer already typed
273+ the lambda as `Fun(Con("Type"), rest)`, erasing the param in codegen
274+ changes the *runtime* arity relative to the static arity — this is fine
275+ only if call sites also drop the corresponding argument. Hence:
276+- Call-site erasure: `compile_app` (called from codegen.rs:5064) must skip
277+ arguments whose span is in the `type_arg_spans` side set (§3). The
278+ curried-application machinery (codegen.rs:8635 peels nested lambdas for
279+ arity; check how partial application counts args — grep
280+ `ExprKind::App` spines around codegen.rs:5054 and the `compile_app`
281+ body) must count only runtime args. This is the trickiest codegen bit:
282+ every place that computes a function's arity from its *type* or from the
283+ lambda's `params.len()` (e.g. codegen.rs:1792 `params.len()`, 2959)
284+ needs to subtract witness params. Audit those three sites.
285+- Type-ctor-field erasure precedent confirmed: `TypeCtor` →
286+ `knot_value_unit` (codegen.rs:5563-5567); the same treatment applies to
287+ a bare `Constructor(name)` reinterpreted as a type in value position
288+ (§4): side-table the span in infer, erase to unit in codegen's
289+ `Constructor` arm.
290+- `pat_bound_names` (codegen.rs:16561-16583), `pretty_pat`
291+ (codegen.rs:17313-17342): new arm — witness binds no names; pretty-prints
292+ as `(T : Type)`.
293+
294+## 6. ALL MATCH/VISITOR SITES FOR NEW NODES
295+
296+New `PatKind::TypeWitness` arms needed:
297+- knot/parser.rs: `push_pat_vars` (285) — push nothing.
298+- knot/format.rs: `render_pat` (2345-2401 region, exact fn at 2345) — print
299+ `(T : Type)`; also the multiline lambda paths at 1007/1020/1075 reuse
300+ `render_pat`.
301+- knot-compiler/infer.rs: `check_pattern` (6779) — new arm implementing §2
302+ (only valid against kind-`Type` expected, else error "type-witness
303+ parameter must have kind Type"); ensure infer-mode Lambda
304+ (5295-5311) gives a witness param `Ty::Con("Type",[])` instead of
305+ `self.fresh()` when no expected type (or better: require an annotation —
306+ see Risks).
307+- knot-compiler/desugar.rs: `pat_bound_names` (670-674 region), the
308+ pattern walks at 656/726/961/1188 — new arm returning no names, and
309+ ensure no desugar rewrite synthesizes/breaks witness pats.
310+- knot-compiler/codegen.rs: `pat_bound_names` (16561), `pretty_pat`
311+ (17313), binder emission (3728, 8081-8126), lambda compile
312+ (10996-11035).
313+- knot-compiler/unused.rs: pattern walk (410-411) — do not mark the type
314+ name as a used *value*; ideally record the type-name use so `data` types
315+ referenced only via witnesses aren't flagged unused.
316+- knot-compiler/effects.rs: `pat`-walking helper at 1922-1923 region +
317+ lambda-param inspection at 1324 and 1659 (both match
318+ `ast::PatKind::Var(name)` on lambda params — with a witness param these
319+ just don't match; confirm no index-based param/arg correspondence is
320+ assumed there — effects.rs:1324/1659 pair params with effect info, audit).
321+- knot-compiler/stratify.rs: expr walker (247 region) — patterns not
322+ matched there beyond existing kinds; add arm if the walker is
323+ exhaustive.
324+- knot-compiler/sql_lint.rs: pattern sites (it matched `ExprKind` at
325+ 242/1097 for TypeCtor; check for PatKind matches) — likely none; verify
326+ with a build after adding the variant (exhaustive matches will flag).
327+- knot-lsp: any `PatKind` matches (semantic_tokens, hover, defs,
328+ references, document_highlight, rename) — the compiler will flag
329+ exhaustive matches; ensure hover on `(T : Type)` reports the *type*
330+ binding, and rename/references treat witness occurrences (which are
331+ `TypeKind::Named`/`Var` in annotations + `Constructor`/`Var` exprs in
332+ type-arg position) consistently.
333+
334+New `TypeKind::TyArrow` arms needed (only if you take the recommended
335+signature node):
336+- knot/format.rs: `render_type` (search `TypeKind::Function` around
337+ format.rs; render as `(a : Type) -> …`).
338+- knot-compiler/infer.rs: `ast_type_to_ty` (4406), `expand_param_alias`
339+ spine peeling (4353-4364 — treat TyArrow as non-App head), the
340+ type-walkers (`collect_refined_names` 1428, `collect_free_unit_vars`
341+ 3866, subst/apply at 1549/1831/3699/3769/4251 — only if they pattern
342+ match `ast::Type`, most operate on `Ty`).
343+- knot-lsp: `collect_names_in_type` (code_action.rs:2089-2119),
344+ `defs.rs` resolve_type (564-594), completion.rs:325/390/1668,
345+ signature_help/type_format — add recursion into `body`, and register
346+ `var` as a local type binder for goto-def/rename.
347+- knot-compiler/types.rs: `resolve_type` (schema resolution) — should
348+ treat `TyArrow` as transparent to `body` (witnesses erased; a source
349+ field can't have kind Type) or reject it in source schemas.
350+
351+No new `ExprKind` is required for the core design (arguments stay
352+`Constructor`/`Var`; reinterpretation happens in infer). If you prefer an
353+explicit marker for erased type args post-inference, you could rewrite the
354+arg into `ExprKind::TypeCtor`-like node in a post-inference pass (the
355+codebase already rewrites AST post-inference — see
356+`resolve_result_markers` and the rewrite at infer.rs:11598), but the
357+span side-table is less invasive.
358+
359+## 7. RISKS
360+
361+1. **`Int 1` collision**: In value position `Int 1` is currently an error
362+ (verified), so no regression, but in *witness* position `f Int` where
363+ the witness should be a dimensionless int must spell `Int 1` — and in
364+ expr position that is `App(Constructor("Int"), Lit(Int "1"))`. The §3
365+ `expr_as_type` must map that shape to `TypeKind::UnitAnnotated` (or set
366+ `in_type_annotation` and synthesize the unit arg). If missed, users get
367+ the confusing "bare `Int` requires a unit" error from a value-position
368+ argument. Contained in `expr_as_type`, but must be designed explicitly.
369+2. **HKT interplay**: `Ty::TyCon`/`Ty::App` unify arms (infer.rs:2238-2310)
370+ treat `App(f,a)` vs `Relation`/`Int`/`Float`/`IO` specially. A witness
371+ bound to a `TyCon` (e.g. `f Maybe`) produces a kind `Type -> Type`
372+ argument — the §3 arm's `unify(arg_slot, ty_arg)` would equate
373+ `Con("Type",[])` with `TyCon("Maybe")` and FAIL. So the witness slot's
374+ *kind* must be checked structurally: arg_slot `Type` accepts only
375+ fully-applied types (`Con(..)`, `Record`, `Relation`, `Var`,
376+ `Alias`); a `TyCon`/partially-applied arg needs arg_slot
377+ `Fun(Type, Type)`. Implement a tiny `kind_of(&Ty) ->
378+ Option<Kind-as-Ty-shape>` and unify kinds instead of hard-coding
379+ `Con("Type",[])`. The `TypeCtor` kind-fold (infer.rs:5889-5895) is the
380+ template.
381+3. **Forall instantiation**: witness params generalize like ordinary vars
382+ (`generalize`/`instantiate_at`), so `f = \(T : Type) x -> x` gets
383+ `∀t. Type -> t -> t` — good. But `check_pattern`'s Forall arm
384+ (infer.rs:6787) binds *value* vars with polymorphic schemes; do NOT
385+ route witness binders through it. Also the escape check
386+ (infer.rs:5930-5940) will fire if a witness skolem leaks into the
387+ enclosing env — that's desired, but the error message should mention
388+ type witnesses (currently says "add an explicit `forall` annotation").
389+4. **Infer-mode lambdas without signatures**: infer.rs:5295-5311 gives
390+ every param a fresh var; a `\(T : Type)` param there would unify
391+ `Con("Type",[])` with a flexible var — fine — but then the *body*'s use
392+ of `T` must connect: the body annotation `(x : T)` resolves `T` via
393+ `annotation_vars` — works only if the infer-mode Lambda arm also runs
394+ the §2 registration when it sees `PatKind::TypeWitness` (give the param
395+ type `Con("Type",[])` and bind name→fresh skolem/var in
396+ `annotation_vars`). Without a signature the witness var stays flexible
397+ and will be generalized — acceptable, mirrors HM.
398+5. **Formatter round-trip**: two new syntaxes — `(T : Type)` in lambda
399+ params and the witness arg (plain `Int`/`Maybe Int` in arg position).
400+ The former round-trips via `render_pat`. The latter is *just an
401+ expression* — `map Int xs` formats as an ordinary app, which re-parses
402+ to the same AST. Round-trip safe by construction; add
403+ format_roundtrip tests (crates/knot/tests/format_roundtrip.rs) with
404+ both forms. Careful: `render_pat` for `TypeWitness` must include the
405+ parens or the reparse breaks (`\(T : Type) -> …` without parens is a
406+ different — invalid — pattern sequence).
407+6. **Ambiguity of `Constructor` in witness position when both a ctor and a
408+ type exist**: resolved positionally (§4), but the *error messages* must
409+ not regress: `unknown constructor` currently fires for type names used
410+ as values; after §4, `Box` alone (a non-nullary ctor) used as a value
411+ argument where a value is expected still works (ctor wins), while `map
412+ Box xs` reinterprets. Document the rule; add tests where a name is both
413+ (e.g. `data Maybe a = Just {value: a} | Nothing` + `data Box a = Box
414+ {value: a}`).
415+7. **Desugar before infer**: desugar rewrites lambdas for do-blocks and
416+ comprehensions (desugar.rs:1223-1292 synthesizes wildcard/var params);
417+ ensure no rewrite assumes all params are value patterns (e.g. eta-expansion
418+ or `__result` wrapping at infer.rs:5327-5347 operates on exprs, fine).
419+8. **`expand_param_alias` spine peeling** (infer.rs:4353-4364): a witness
420+ arg like `Pair Int Text` goes through this path already; but if the
421+ witness head is a *witness variable* (`\(F : Type -> Type) … f (F
422+ Int)`), `expr_as_type` yields `App(Var F, Int)` — `ast_type_to_ty`'s
423+ App arm handles `Ty::Var` func via `Ty::App` (4556-4558). Requires the
424+ §2 change so `Named "F"` resolves to the witness var first.
425+
426+## Recommended minimal-diff plan (ordered)
427+
428+1. `PatKind::TypeWitness{name, kind}` + parser LParen-arm extension +
429+ `push_pat_vars`/`render_pat`/pretty_pat arms; reject in case patterns.
430+2. infer: `check_pattern` arm (witness registration via skolems +
431+ `annotation_vars`, guarded); infer-mode Lambda arm support;
432+ `ast_type_to_ty` `Named` arm consults witness scope (flagged).
433+3. infer App arm: kind-`Type` param detection + `expr_as_type` + side
434+ tables (`type_arg_spans`, `witness_bindings`); kind checker for HK
435+ witnesses; extend lambda-last/ctor paths to not shadow it.
436+4. `Constructor` else-branch (§4) for bare type names in value position +
437+ erase-to-unit side table.
438+5. codegen: filter witness params in `compile_lambda_inner`, skip
439+ `type_arg_spans` args in `compile_app`, audit arity sites
440+ (codegen.rs:1792, 2959, 8635), erase marked `Constructor` exprs to unit.
441+6. Remaining visitor arms (§6) driven by compiler exhaustiveness errors;
442+ LSP hover/rename polish.
443+7. Tests: parser (crates/knot/tests/parser.rs), formatter round-trip,
444+ infer (crates/knot-compiler/tests/regress_infer*.rs), codegen end-to-end
445+ (`knot build` + run) covering: witness identity, witness used in body
446+ annotation, HK witness (`F : Type -> Type`), `Int 1` witness, name that
447+ is both ctor and type, leak/escape error, formatter round-trip.
.hermes/doccheck.pynew file+344
@@ -0,0 +1,344 @@
1+#!/usr/bin/env python3
2+"""Extract every fenced code block from the knot docs and try to compile it.
3+
4+Block classes and handling:
5+ - complete program (has `with {` AND a body expr after the closing brace,
6+ or is a bare `(do ...)`): compile as-is.
7+ - declaration fragment (bindings like `names ([...])`, `f \\x -> ...`, sigs):
8+ wrap in `with { ... } (do base.println "ok"; yield {})`.
9+ - reference signature (`name : T`, optionally `base.` prefixed): turn into a
10+ sig-check `checkN : T` + `checkN base.<name>` so a wrong sig fails compile.
11+
12+Usage: doccheck.py <doc.md> [--write-report]
13+Prints per-block PASS/FAIL with the first error line for failures.
14+"""
15+import re, os, subprocess, sys, tempfile
16+
17+WORK = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18+KNOT = os.path.join(WORK, "target/debug/knot")
19+
20+def extract_blocks(path):
21+ lines = open(path).read().splitlines()
22+ blocks = []
23+ i = 0
24+ while i < len(lines):
25+ m = re.match(r"^```([\w-]*)\s*$", lines[i])
26+ if m:
27+ lang = m.group(1)
28+ start = i + 2 # 1-indexed line of first content line
29+ j = i + 1
30+ body = []
31+ while j < len(lines) and not re.match(r"^```\s*$", lines[j]):
32+ body.append(lines[j]); j += 1
33+ blocks.append({"lang": lang, "body": body, "line": start})
34+ i = j
35+ i += 1
36+ return blocks
37+
38+def is_comment_or_blank(s):
39+ s = s.strip()
40+ return not s or s.startswith("--")
41+
42+SIG_RE = re.compile(r"^\s*(?:base\.)?([a-zA-Z][a-zA-Z0-9_.]*)\s*:\s*(.+)$")
43+
44+def looks_like_knot(body):
45+ """Heuristic: is this untagged block knot code (vs prose/output/diagram)?"""
46+ nonempty = [b for b in body if not is_comment_or_blank(b)]
47+ if not nonempty:
48+ return False
49+ for ln in nonempty:
50+ s = ln.strip()
51+ # obvious non-knot: paths, backticks, box-drawing, leading bullets/SQL
52+ if "/" in s or "`" in s or s.startswith(("│", "╭", "╰", "├", "└", "-", "*")):
53+ return False
54+ if re.match(r"^(SELECT|FROM|WHERE|INSERT|UPDATE|DELETE|CREATE|GET|POST|PUT|HTTP)\b", s, re.I):
55+ return False
56+ # must contain at least one knot-ish signal: a sig, binding, or known keyword
57+ text = "\n".join(nonempty)
58+ return bool(re.search(r"[a-zA-Z_]\s*:\s|\bwith\b|\bdo\b|\bcase\b|\\|->|\(do|base\.", text))
59+
60+def classify(body):
61+ text = "\n".join(body)
62+ nonempty = [b for b in body if not is_comment_or_blank(b)]
63+ if not nonempty:
64+ return "empty", None
65+ # complete program: an OUTER record-open `with {` at col 0 followed by a
66+ # top-level body. Both `with {name value ...} body` and the comprehension
67+ # form `with {name do ...} (do ...)` are programs when they start at col 0
68+ # (an *inner* comprehension would be indented inside an enclosing do).
69+ outer_with = re.search(r'^with \{', text, re.M)
70+ if outer_with:
71+ # is there a body after the last closing brace at col 0?
72+ # crude: last non-comment line that's not part of the with-record
73+ tail = [b for b in reversed(body) if not is_comment_or_blank(b)]
74+ if tail and not tail[0].strip().startswith("}"):
75+ # body exists if some top-level line after the with block
76+ return "program", text
77+ # with-block but maybe body is `(do ...)` right after }
78+ if re.search(r"^\}\s*$", text, re.M) and re.search(r"^\(do|^do\b", text, re.M):
79+ return "program", text
80+ return "program", text # try as program; if no body it'll error clearly
81+ stripped0 = nonempty[0].strip()
82+ if stripped0.startswith("(do") or stripped0 == "do" or stripped0.startswith("do "):
83+ return "program", text
84+ # Program-level declaration sequence (route/data/type/serve at top level),
85+ # possibly with a trailing body expression: wrap decls in `with { }`.
86+ if re.search(r'^(route|data|type|serve)\b', text, re.M):
87+ return "program_decls", body
88+ # Declaration(s) followed by a top-level `(do ...)` or `do ...` program body
89+ # (e.g. `*src : T` / `f \\x -> ...` fields then a `do` body): program_decls.
90+ if re.search(r'^\(?do\s*$', text, re.M) or (re.search(r'^\(do\b', text, re.M) and re.search(r'^[*&]?[a-zA-Z_]\w*\s*[:=]', text, re.M)):
91+ # only when there is at least one field-like declaration line above it
92+ if re.search(r'^[*&]?[a-zA-Z_]\w*\s*[:=\\]|^[*&]?[a-zA-Z_]\w*\s+do\b', text, re.M):
93+ return "program_decls", body
94+ # pure signature(s)?
95+ sigs = []
96+ allsig = True
97+ for b in nonempty:
98+ m = SIG_RE.match(b)
99+ if m and ("\\" not in b) and ("(" not in b.split(":",1)[0]):
100+ sigs.append((m.group(1), m.group(2)))
101+ else:
102+ allsig = False
103+ break
104+ if allsig and sigs:
105+ return "sigs", sigs
106+ # otherwise: declaration/expr fragment
107+ return "fragment", text
108+
109+DECL_START = re.compile(r'^\s*(?:[*&]?[a-zA-Z_]\w*|route\b|serve\b)\s*(?::|=|\bwhere\b|\\|\(|\bdo\b)', )
110+# Subset-constraint declaration: `*a.b <= *c.d` or `*a <= *a.b`. These are
111+# declarations, not expressions — keep them in the with-record.
112+CONSTRAINT_DECL = re.compile(r'^\s*\*[a-zA-Z_]\w*(?:\.\w+)?\s*<=')
113+# A `name <value>` with-field line (constant/function/derived with a body). The
114+# value may be a literal, record, lambda, `do` block, source read, or call.
115+FIELD_WITH_BODY = re.compile(r'^\s*[*&]?[a-zA-Z_]\w*\s+\S')
116+# Type-first signature / source declaration: `Type name` (a type, then a tall
117+# 2+-space gap or the type's trailing token, then the annotated name). Covers
118+# `Int 1 -> Int 1 addOne`, `Rel {name Text} *people`, `IO a -> IO b race`,
119+# `Maybe (Int 1) asInt`, record-type-led `{name Text} x`, constraint-led
120+# `(<>ctx) => T f`. The separator is 2+ spaces (tall whitespace).
121+TYPE_FIRST_DECL = re.compile(
122+ r'^\s*(?:[A-Z][\w.]*|Rel\b|\{|\(|forall\b|_).*?\s{2,}[*a-z_]\w*\s*$', )
123+EXPR_START = re.compile(r'^\s*[\(]?\s*(?:base\.|do\b|\(do\b|[a-zA-Z_][\w.]*\s*\()', )
124+
125+def _paren_balanced(s):
126+ depth = 0
127+ in_str = False
128+ for ch in s:
129+ if ch == '"':
130+ in_str = not in_str
131+ if in_str:
132+ continue
133+ if ch in '([{':
134+ depth += 1
135+ elif ch in ')]}':
136+ depth -= 1
137+ return depth <= 0
138+
139+def split_decls_exprs(lines):
140+ """Group a fragment's lines into declarations vs trailing bare expressions.
141+
142+ A declaration starts at a DECL_START line and continues while its parens are
143+ unbalanced OR it opens an indented body block (`name do`, `name \\args ->`)
144+ whose following lines are more indented. Lines that don't belong to any
145+ declaration and aren't declarations themselves are expressions.
146+ """
147+ decls, exprs = [], []
148+ cur = None # accumulating a multi-line declaration
149+ cur_indent = 0 # indent of the declaration's opening line
150+ def opens_block(s):
151+ # `... do`, `... ->`, `... =` at end of line opens an indented body, as
152+ # does `... -> case ... of` / `... case ... of` (arms follow indented).
153+ return bool(re.search(r'(\bdo\b|->|=|\bof)\s*$', s))
154+ for raw in lines:
155+ if is_comment_or_blank(raw):
156+ if cur is not None:
157+ cur.append(raw)
158+ continue
159+ s = raw.strip()
160+ indent = len(raw) - len(raw.lstrip())
161+ # a with-field: `name : T`, `name = v`, `name do ...`, `name \a -> ...`,
162+ # or `name <value>` (constant with a body). Dotted names (base.x) are not fields.
163+ starts_decl = (bool(DECL_START.match(raw)) or bool(FIELD_WITH_BODY.match(raw))
164+ or bool(TYPE_FIRST_DECL.match(raw))) \
165+ and not s.startswith("(") and "." not in s.split(None, 1)[0]
166+ # a subset-constraint `*a.b <= *c.d` IS a declaration despite the dotted name
167+ starts_decl = starts_decl or bool(CONSTRAINT_DECL.match(raw))
168+ if cur is not None:
169+ joined = "\n".join(cur)
170+ # continue if parens unbalanced OR this line is a more-indented body line
171+ if not _paren_balanced(joined) or indent > cur_indent:
172+ cur.append(raw)
173+ if _paren_balanced("\n".join(cur)) and indent <= cur_indent:
174+ decls.append("\n".join(cur)); cur = None
175+ continue
176+ # a same-or-less-indented balanced line closes the declaration
177+ decls.append(joined); cur = None
178+ if starts_decl:
179+ if _paren_balanced(raw) and not opens_block(s):
180+ decls.append(raw)
181+ else:
182+ cur = [raw]; cur_indent = indent
183+ else:
184+ exprs.append(raw)
185+ if cur is not None:
186+ decls.append("\n".join(cur))
187+ return decls, exprs
188+
189+def wrap_fragment(text):
190+ lines = text.splitlines()
191+ decls, exprs = split_decls_exprs(lines)
192+ out = ["with {"]
193+ out.extend(decls)
194+ out.append("}")
195+ if exprs:
196+ # Each bare expression line is an independent example (e.g. a reference
197+ # table of `single [...]` / `single []` cases). Emit one println per
198+ # expression so multi-expression blocks compile.
199+ body_lines = ["(do"]
200+ for e in exprs:
201+ e2 = re.sub(r'\s+--.*$', '', e).strip()
202+ if e2:
203+ body_lines.append(f" base.println (base.show ({e2}))")
204+ body_lines.append(" yield {})\n")
205+ out.extend(body_lines)
206+ else:
207+ out.append("(do\n base.println \"ok\"\n yield {})\n")
208+ return "\n".join(out)
209+
210+def wrap_program_decls(lines):
211+ """Wrap a program-level declaration sequence in `with { }`. The trailing
212+ top-level expression (a line at col 0 starting with `(` or `do`) becomes the
213+ body; everything else goes inside the record."""
214+ body_idx = None
215+ for i, raw in enumerate(lines):
216+ if re.match(r'^(\(|do\b)', raw) and not raw.startswith(' '):
217+ body_idx = i
218+ break
219+ decls = lines if body_idx is None else lines[:body_idx]
220+ body = [] if body_idx is None else lines[body_idx:]
221+ out = ["with {"]
222+ out.extend(decls)
223+ out.append("}")
224+ if body:
225+ out.extend(body)
226+ else:
227+ out.append("(do\n base.println \"ok\"\n yield {})\n")
228+ return "\n".join(out)
229+
230+# Builtins that are language KEYWORDS and cannot be referenced as `base.x`
231+# values (`base.not` fails to parse: `not` is a keyword). For these we verify
232+# only that the documented TYPE parses, by ascribing it to a type-correct
233+# lambda. ADT constructors likewise are not `base.x` values.
234+KEYWORD_LAMBDAS = {
235+ "not": "\\b -> not b",
236+ "atomic": None, # keyword block form; sig not value-bindable
237+ "where": None, "migrate": None, "serve": None, "route": None,
238+ "fetch": None, "retry": None,
239+}
240+CTORS = {"Just", "Nothing", "Ok", "Err", "LT", "EQ", "GT", "True", "False"}
241+
242+def wrap_sigs(sigs):
243+ out = ["with {"]
244+ for i, (name, ty) in enumerate(sigs):
245+ bare = name.split(".")[-1]
246+ tyc = ty.strip()
247+ lam = KEYWORD_LAMBDAS.get(bare)
248+ if bare in KEYWORD_LAMBDAS or bare in CTORS:
249+ if lam is None:
250+ # keyword block form: no value check possible; emit a placeholder
251+ # so the `with` is a valid program (type itself already trusted).
252+ out.append(f"kw{i} 0")
253+ continue
254+ out.append(f"check{i} : {tyc}")
255+ out.append(f"check{i} ({lam})")
256+ continue
257+ out.append(f"check{i} : {tyc}")
258+ # SIG_RE strips any leading `base.`; `name` is the path WITHOUT it
259+ # (e.g. `morph.textToBytes.into` or `sum`). Reference the full path.
260+ out.append(f"check{i} base.{name}")
261+ out.append("}")
262+ out.append("(do\n base.println \"ok\"\n yield {})\n")
263+ return "\n".join(out)
264+
265+def compile_src(src, tag):
266+ fd, srcf = tempfile.mkstemp(prefix=f"hermes-doccheck-{tag}-", suffix=".knot")
267+ os.close(fd)
268+ binf = srcf[:-5] # strip .knot
269+ with open(srcf, "w") as f:
270+ f.write(src)
271+ try:
272+ r = subprocess.run([KNOT, "build", srcf, "-o", binf],
273+ cwd=WORK, capture_output=True, text=True, timeout=60)
274+ ok = os.path.exists(binf)
275+ err = ""
276+ if not ok:
277+ first = next((l for l in (r.stderr + r.stdout).splitlines() if l.strip().startswith("Error")), "")
278+ err = first or (r.stderr + r.stdout).strip().splitlines()[:1]
279+ err = err if isinstance(err, str) else (err[0] if err else "unknown error")
280+ return ok, err
281+ except subprocess.TimeoutExpired:
282+ return False, "TIMEOUT"
283+ finally:
284+ for p in (srcf, binf):
285+ if os.path.exists(p):
286+ os.remove(p)
287+
288+def main():
289+ doc = sys.argv[1]
290+ blocks = extract_blocks(doc)
291+ # Build work items first, then compile in parallel (each block spawns the
292+ # compiler; serial is ~5s/block which times out on the big docs).
293+ work_items = []
294+ skipped = []
295+ for idx, b in enumerate(blocks):
296+ # `knot-skip` blocks are intentionally not compiled (document a feature
297+ # whose impl doesn't yet match the doc — doc-vs-impl gap). Report them
298+ # so the skip is visible, not silent.
299+ if b["lang"] == "knot-skip":
300+ skipped.append(b)
301+ continue
302+ if b["lang"] not in ("knot", ""):
303+ continue
304+ if b["lang"] == "" and not looks_like_knot(b["body"]):
305+ continue
306+ kind, payload = classify(b["body"])
307+ if kind == "empty":
308+ continue
309+ if kind == "program":
310+ src = payload
311+ elif kind == "program_decls":
312+ src = wrap_program_decls(payload)
313+ elif kind == "fragment":
314+ src = wrap_fragment(payload)
315+ elif kind == "sigs":
316+ src = wrap_sigs(payload)
317+ else:
318+ continue
319+ work_items.append({"idx": idx, "line": b["line"], "kind": kind, "src": src,
320+ "preview": next((l for l in b["body"] if not is_comment_or_blank(l)), "")[:60]})
321+
322+ from concurrent.futures import ThreadPoolExecutor
323+ def run(item):
324+ ok, err = compile_src(item["src"], f"{os.path.basename(doc)}-{item['idx']}")
325+ item["ok"] = ok; item["err"] = err
326+ return item
327+ results = []
328+ with ThreadPoolExecutor(max_workers=8) as ex:
329+ for item in ex.map(run, work_items):
330+ results.append(item)
331+ results.sort(key=lambda r: r["line"])
332+ npass = sum(1 for r in results if r["ok"])
333+ skip_note = f" ({len(skipped)} skipped: knot-skip)" if skipped else ""
334+ print(f"\n==== {doc}: {npass}/{len(results)} blocks compile{skip_note} ====")
335+ for s in skipped:
336+ preview = next((l for l in s["body"] if not is_comment_or_blank(l)), "")[:60]
337+ print(f"SKIP L{s['line']} {preview}")
338+ for r in results:
339+ if not r["ok"]:
340+ print(f"FAIL L{r['line']} [{r['kind']}] {r['preview']}")
341+ print(f" {r['err']}")
342+
343+if __name__ == "__main__":
344+ main()
.hermes/plans/2026-07-22-sources-in-records.mdnew file+212
@@ -0,0 +1,212 @@
1+# Plan: Sources in records with static-resolution enforcement
2+
3+## Goal
4+
5+Allow source declarations as fields of a record literal, so a record can group a
6+persisted source with its `data`/`type` and operations (a self-contained
7+"module"). Enforce the **static-resolution rule**: a source may only be used
8+where the compiler can resolve it, at compile time, to a specific declared source
9+with a known name + schema. This preserves the entire safety story — schema
10+lockfile, migrations, effect tracking, SQL pushdown.
11+
12+## Core design decision (locked with user): `*` is part of the NAME, not a prefix operator
13+
14+A source is a binding/field **whose name begins with `*`**. There is no `*`
15+prefix operator and no separate `SourceRef` atom — the sigil is folded into the
16+identifier, and "source-ness" is a property of the name.
17+
18+- **Declaration:** a field or top-level binding named `*todos` is a persisted
19+ source. `db = { *todos : [Todo] }` declares a source field literally named
20+ `*todos`. Bare top-level `*todos : [Todo]` keeps working (a top-level source
21+ binding named `*todos`).
22+- **Read:** `db.*todos` is ordinary field access on a field named `*todos`;
23+ yields the relation value `[Todo]`.
24+- **Write:** `=` is permitted **only** on a path whose final segment is a
25+ `*`-named field: `db.*todos = …`. Settable-ness comes from the field *being a
26+ source* (name starts with `*`), resolved from the declaration — not from any
27+ use-site marker.
28+- The lexer must treat `*` followed by a lowercase ident as a single
29+ `*`-prefixed identifier token in field/binding position (see Parser below).
30+
31+This removes the read/write ambiguity by construction: `db.todos` (a plain field)
32+is never a source; `db.*todos` (a `*`-named field) is always a source. The
33+distinction lives in the record's field names, not at the use site.
34+
35+## The static-resolution rule (agreed)
36+
37+A `*`-named source may only be used where the compiler can trace it to a
38+declaration:
39+
40+- ✅ `*todos` — bare top-level source binding (today).
41+- ✅ `db.*todos` where `db` is a **statically-known record literal** containing a
42+ `*todos : [Todo]` field — resolves to that declared source.
43+- ❌ A source in an **opaque** position — a lambda param, a `let` bound to a
44+ non-literal, a function return, a list element — anywhere the compiler can't
45+ trace the record back to a literal. The write/`*`-field reference there is a
46+ compile error (not read-only).
47+
48+## Design decisions (locked)
49+
50+- **Path-qualified source identity.** A source's identity is its access path:
51+ `todos` (top-level) or `db.*todos` (record field). The qualified path is the
52+ key for `source_schemas` / `source_types`, the effect name (`w db.*todos`),
53+ the lockfile key, the migration target, and the SQLite table name.
54+- **Table name = `_knot_` + path verbatim (NO mangling).** The physical SQLite
55+ table is `format!("_knot_{}", path)` used via `quote_ident`, and quoted
56+ identifiers accept `.`/`*` fine. `parse_table_aliases` reads to the closing
57+ quote (dots/`*` inside are harmless) and `ensure_index` is already injective
58+ via length-prefixing. So `_knot_db.*todos` is valid; no character mangling is
59+ needed. Just centralize the mapping in one `table_name(path)` helper.
60+- **Static resolution via literal tracing.** Resolution succeeds only when the
61+ record expression is a literal (or traces through `let`/`with` to one), like
62+ implicit-dictionary records and embedded `data`/`type` are resolved today.
63+- **No dynamic handles.** No runtime name/schema resolution, no fallback path.
64+- **Reads of plain relation fields stay free.** `db.todos` (non-`*` field) is a
65+ normal value and flows anywhere; only `*`-named fields are restricted.
66+- **Duplicate source names are a hard compile error — GLOBALLY, across all
67+ scopes.** A source's identity is its qualified path, and that namespace is
68+ **flat and program-wide**, not lexical: every source maps to one physical
69+ table (`_knot_<path>`) and one lockfile entry, so two sources that resolve to
70+ the same path cannot coexist **even in non-overlapping scopes**. `*todos` in
71+ one record and `*todos` in a different record, one in a function body and one
72+ at top level, two in separate `with` blocks that never interleave — all of
73+ these collide, because scope does not isolate the underlying table. The check
74+ is a whole-program uniqueness pass over every source declaration (top-level +
75+ every record-literal `*`-field), erroring on any repeated qualified path and
76+ naming all declaration sites. Never silently overwrite a
77+ `source_schemas`/`source_types` entry (today a duplicate `HashMap::insert`
78+ would clobber). This matters more once record fields can introduce sources,
79+ since collisions become easier to write accidentally.
80+
81+## Work items
82+
83+### 1. Lexer (`crates/knot/src/lexer.rs`)
84+- **Language-wide rule (user-locked): binary operators REQUIRE whitespace on both
85+ sides.** So `a * b`, `a + b`, `x - y`, `Float M * S` are infix; an operator
86+ char immediately followed by a letter is never infix. This makes `*`
87+ unambiguous with NO lookahead: `*` immediately followed by an ASCII lowercase
88+ letter ⇒ source identifier; otherwise (whitespace/EOF/non-letter after) ⇒
89+ operator.
90+- Recognize `*lower` as a single `TokenKind::StarIdent(String)` where the String
91+ INCLUDES the leading `*` (e.g. `"*todos"`). At the `b'*'` case, peek the next
92+ byte: if ASCII-lowercase, lex a StarIdent (`*` + ident-continuation chars);
93+ else emit `TokenKind::Star`.
94+- Existing `Star` (binary op) uses all have spaces, so they're unaffected. The
95+ `*name` in top-level source decls / `replace *name` / source refs currently
96+ lex as `Star`+`Lower`; after this change they're a single `StarIdent`, and the
97+ parser sites that matched `Star`+`Lower` must match `StarIdent` instead.
98+- Migration: existing no-space binops must be reformatted. Grep found only
99+ `f-8`-style subtractions in examples/bytes.knot and examples/crypto.knot (the
100+ `replace *name` hits are source refs, unaffected). Run the formatter over
101+ affected files; the formatter should emit spaces around binops going forward.
102+
103+### 2. AST (`crates/knot/src/ast.rs`)
104+- Field/binding names can now start with `*` (the `Name` is `"*todos"`). No
105+ change to `RecordField.name` (it's already a `Name` String).
106+- `DeclKind::Source { name, .. }`: `name` is `"*todos"` (or keep bare `todos`
107+ and treat "starts with `*`" as the source marker — **decide one canonical
108+ form**; prefer storing the name WITH the `*` so name-based dispatch is just a
109+ `starts_with('*')` check everywhere).
110+- `ExprKind::SourceRef(Name)` may become unnecessary for the record case (it's
111+ just `FieldAccess`/`Var` on a `*`-name), but keep it for top-level source
112+ reads if that simplifies effects. **Decide during impl:** likely unify so a
113+ source read/write is recognized by "name starts with `*`" rather than a
114+ dedicated node — this is the simplification the user's change enables.
115+
116+### 3. Parser (`crates/knot/src/parser.rs`)
117+- **Record-literal field arm** (≈2914 loop): accept `*name : Type` as a field
118+ whose name is `*name`. Reuse the existing `type`/`data`/sig-arm structure.
119+- **Top-level source decl** (`parse_source_or_view` ≈1180): now keyed by the
120+ `*`-name directly.
121+- **Set/ReplaceSet** (≈2011, 2030): target is a field-access path whose final
122+ segment starts with `*`; produce `Set{ target, value }` where `target` is the
123+ path expr. Validation that the final segment is a source moves to infer (it
124+ can resolve the path).
125+- **Field access**: `db.*todos` — after `.`, accept a `StarIdent`.
126+
127+### 4. Types env (`crates/knot-compiler/src/types.rs`)
128+- `source_schemas: HashMap<String,String>` keyed by qualified path
129+ (`todos`, `db.*todos`). Add a walk over top-level record-let literals
130+ registering each `*`-named field's source under `<record>.<field>`.
131+- **Global duplicate-name check (whole-program, scope-agnostic):** collect every
132+ source declaration — top-level `DeclKind::Source` plus every record-literal
133+ `*`-field reachable in the module — into one map keyed by qualified path. Any
134+ repeated path is a hard error naming the path and ALL declaration sites,
135+ regardless of whether the declarations' scopes overlap (they share one
136+ physical table / lockfile entry, so scope is irrelevant). Never silently
137+ overwrite. Run this once over the whole module, not per-scope.
138+- `schema_for_source` unchanged (operates on the field's type); only the key
139+ changes. Top-level sources keyed by bare `todos` (length-1).
140+
141+### 5. Infer (`crates/knot-compiler/src/infer.rs`)
142+- `source_types` keyed by qualified path (populate at ≈8787/8796 + the
143+ record-literal walk).
144+- **Source read** (`db.*todos`): type `IO {Reads "db.*todos"} [Todo]`. Resolve
145+ the root of the path to a static record literal (through `let_bindings` /
146+ `with` frames / top-level literals), confirm the field is a source.
147+- **Write** (`Set`/`ReplaceSet` ≈6385, 6440): resolve the target path to a
148+ source; effect `Writes path`; refinement + set/replace distinction by path.
149+- **Opacity enforcement:** if a `*`-field reference's root is not statically
150+ resolvable (lambda param, non-literal let, etc.) → compile error: "source
151+ `<path>` is not statically known; sources may only be used where their
152+ declaration is visible." This is the single choke point for the rule.
153+
154+### 6. Codegen (`crates/knot-compiler/src/codegen.rs`)
155+- `source_schemas`, `views`, `scalar_sources`, `source_refinements` keyed by
156+ qualified path.
157+- **Central `table_name(path) -> String`** = `format!("_knot_{}", path)`
158+ verbatim (no mangling — quoted idents accept `.`/`*`); replace the scattered
159+ `format!("_knot_{}", name)` sites. Runtime unchanged (still gets a string).
160+- Source read (≈3817), `Set` (≈4463), `ReplaceSet` (≈4717),
161+ `collect_direct_write_targets` (≈5330): resolve path → qualified name. The
162+ `if let SourceRef(name)` guards become path resolution + `starts_with('*')`.
163+
164+### 7. Lockfile (`crates/knot-compiler/src/lockfile.rs`)
165+- Walk top-level record literals for `*`-named source fields, registering under
166+ qualified paths so `check` sees them (≈619/636/645 loops). Lockfile key =
167+ qualified path. Migrations match on it. Single-name sources unchanged.
168+
169+### 8. Migrations / subset constraints
170+- `migrate` target and `<=` subset paths resolve record-literal source paths the
171+ same way (they already use `RelationPath`).
172+
173+### 9. Formatter / LSP
174+- Round-trip `*name : Type` record fields and `db.*todos` access.
175+- LSP `analysis.rs`: update source patterns to the `*`-name / path form.
176+
177+## Testing
178+
179+New `crates/knot-compiler/tests/regress_source_in_record.rs` (build + run via
180+the real binary, following `regress_implicit_dict.rs`):
181+1. **Read via path:** `db = { *todos : [Todo] }`; `ts <- db.*todos` reads rows.
182+2. **Write via path:** `db.*todos = union db.*todos […]` persists; re-read shows
183+ the row. Table created under the mangled name.
184+3. **replace via path:** `replace db.*todos = []` empties it.
185+4. **with-peel (decide):** does `with db (… *todos …)` bring the source field
186+ into scope as bare `*todos`? Resolution must still trace to the literal.
187+5. **Opacity error:** `process = \db -> db.*todos = …` → compile error naming
188+ the unresolved source. Opaque **read** `ts <- db.*todos` through a param —
189+ decide allow-or-error (leaning: error too, since it's still a source).
190+6. **Lockfile:** build twice → drift on `db.*todos` detected; `migrate` to
191+ `db.*todos` validates.
192+7. **Effects:** a fn writing `db.*todos` infers the write effect; `main` accepts.
193+8. **Duplicate-name errors (global, scope-agnostic):** (a) two `*todos` fields
194+ in one record literal; (b) `*todos` in two DIFFERENT records whose paths
195+ don't overlap lexically; (c) `*todos` in a function-body record vs a
196+ top-level `*todos`; (d) two top-level sources with the same name — every one
197+ is a hard compile error naming the duplicate path and all sites, even when
198+ the scopes never overlap.
199+
200+## Open questions to settle during implementation
201+- Lexer disambiguation of `*ident` vs `*` infix (spacing rule).
202+- Store source names WITH or WITHOUT the `*` in the various maps (canonical
203+ form). Prefer WITH for a uniform `starts_with('*')` dispatch.
204+- `with db` peel of a source field: support or require the qualified path.
205+- Opaque reads of `*`-fields: allowed or also static-only.
206+
207+## Verification
208+- `cargo build --workspace` clean.
209+- New regression suite green.
210+- Full `cargo test --workspace` green (watch lockfile, effects, SQL pushdown,
211+ codegen source guards).
212+- Ad-hoc end-to-end script building + running the examples against the binary.
.hermes/sql-pushdown-opportunities.mdnew file+102
@@ -0,0 +1,102 @@
1+# SQL Pushdown — Current Coverage & Opportunities
2+
3+## STATUS (updated during implementation)
4+- Delegation 1 (in flight): Feature A (arith-map→aggregate) + Feature B (map-dedup→SELECT DISTINCT). Confirmed gaps via `--debug` SQL.
5+- Remaining (scoped below): #1 GROUP BY, #3 elem-subquery→IN(SELECT), #5 EXISTS.
6+
7+## Empirically confirmed today (via `./prog --debug`)
8+- A: `sum (map (\i -> i.price * i.qty) items)` → ships all rows, computes in memory. Should be `SELECT SUM("price"*"qty") FROM _knot_items`. `base.sum:[a]->a` is DIRECT; the projection is a preceding `base.map`.
9+- B: `map (\p -> p.dept) people` → `SELECT name,dept FROM _knot_people` + temp `SELECT DISTINCT`. Should be `SELECT DISTINCT "dept" FROM _knot_people`. Relations are sets → map already dedups; this pushes dedup+projection into SQL.
10+
11+## Scoping for the hard three
12+
13+### #3 elem-subquery → IN (SELECT ...)
14+codegen.rs:12805. `elem` has two paths: (a) literal list→`IN (?,...)`, (b) dynamic haystack→`IN (SELECT value FROM json_each(?))` (whole relation JSON-encoded as a param, gated by `elem_pushdown_ok.dynamic`, Text/Bool/Uuid only).
15+GAP: when haystack is a do-block comprehension over a source (`do d <- *depts; where ...; yield d.dept`), it materializes + JSON-encodes instead of `needle IN (SELECT "dept" FROM _knot_depts WHERE ...)`.
16+IMPL: detect Do-block haystack → reuse `analyze_sql_plan` to get the inner query → emit `needle_sql IN (<inner sql>)`. Keep the float/type gates. The needle and inner are on different sources — needle is the outer bind_var's column.
17+NOTE: in a `where` clause the haystack must be a pure relation value; comprehension-in-where needs the `with`-bound form. Verify the accepted surface syntax first (my direct `where elem p.dept (do ...)` failed to typecheck — needs binding).
18+
19+### #5 EXISTS for count-guard
20+Shape: `where count (filter (\d -> ...) *depts) > 0` → `WHERE EXISTS (SELECT 1 FROM _knot_depts WHERE ...)`. Lower priority / less common. Only implement if cheap after #3 (similar subquery machinery).
21+
22+### #1 GROUP BY + per-group aggregate (biggest, riskiest)
23+TODAY: groupBy pushes every row into a temp relation, closes loops (arena), then `knot_relation_group_by` reads the temp + ORDER BYs keys to group in memory. Per-group aggregates run row-by-row in Knot.
24+TARGET: `SELECT "key", COUNT(*)/SUM(..)/AVG(..)/MIN(..)/MAX(..) FROM _knot_src GROUP BY "key"` for the shape `t <- *src; groupBy {k: t.key}; ...aggregate over group...`.
25+CONSTRAINTS discovered: keys must be plain field accesses on the primary bind (codegen.rs:10803-10835 enforces this — good, maps to GROUP BY cols). The groupBy codegen is deeply tied to temp-relation + arena-promotion + loop-closing machinery (~10690-10850). A SQL path must DETECT the simple single-source shape EARLY and bypass that machinery entirely, emitting a GroupBy terminal.
26+EFFORT: large. New QueryTerminal::GroupBy { key_cols, agg } + runtime read-back of grouped rows (needs a schema for the {key, group-relation} shape Knot produces). RISK: high — must not break the 31 examples (several use groupBy) or the nested-relation result shape.
27+RECOMMEND: do A+B+#3 first, keep suite green, THEN attempt #1 in isolation with heavy example coverage.
28+
29+Source of truth: `crates/knot-compiler/src/sql_lint.rs` (mirrors codegen),
30+`crates/knot-compiler/src/codegen.rs` (`aggregate_sql_func_runtime`,
31+`pipe_ops_order_pushable`, set-op terminals), `crates/knot-runtime/src/lib.rs`.
32+
33+## Already pushed down (verified in codegen)
34+- `where` comprehensions → `WHERE` (single- and multi-relation joins →
35+ `FROM a, b WHERE join-cond AND filter`; auto-indexes WHERE/ORDER BY cols)
36+- comparisons `==` `!=` `<` `>` `<=` `>=`, `&&` `||` `not`
37+- arithmetic `+` `-` `*` `++`; `/` `%` only with provably-nonzero literal divisor
38+- `contains` → `INSTR(...) > 0`; `elem` → `IN (...)` / `IN (SELECT json_each(?))`
39+- `count` → `COUNT(*)`; `countWhere` → `COUNT(*) WHERE`
40+- `sum`/`avg`/`minOn`/`maxOn` → `SUM`/`AVG`/`MIN`/`MAX` (int/text only)
41+- `sortBy` → `ORDER BY` (int/text projections only)
42+- `take N` → `LIMIT`; `drop M` → `OFFSET`; `drop M |> take N` → `LIMIT N OFFSET M`
43+- set ops `union`/`inter`/`diff` → `UNION`/`INTERSECT`/`EXCEPT`
44+- pipe chains in the fixed stage order
45+ filter* → sortBy? → map? → drop? → take? → aggregate?
46+
47+## Deliberate in-memory fallbacks (correctness, NOT gaps)
48+These stay in memory because SQLite semantics diverge from Knot — do NOT
49+"fix" these without changing semantics:
50+- **Float comparisons/ORDER BY/MIN/MAX**: SQLite -0.0/NaN-as-NULL vs Knot
51+ `total_cmp`. Fundamental.
52+- **`tag` columns (all-nullary ADTs) ordered comparisons**: SQLite compares
53+ constructor names alphabetically; Knot `Ord` uses declaration order.
54+- **json columns** (payload ADTs, nested records): runtime binds differently.
55+- **`toUpper`/`toLower`/`trim`/`length`**: SQLite UPPER/LOWER/TRIM are
56+ ASCII-only, LENGTH counts pre-NUL; Knot is Unicode-aware.
57+- `/` `%` with non-literal/zero-able divisor: SQLite NULLs, Knot panics.
58+
59+## Real pushdown OPPORTUNITIES (ranked)
60+
61+### 1. GROUP BY + per-group aggregate ← highest value
62+`groupBy {key: t.field}` currently calls `knot_relation_group_by` which
63+materialises the whole relation, inserts keys into a temp table, and ORDER
64+BYs to group in memory. A following per-group aggregate
65+(`count`/`sum`/`avg`/`min`/`max` over each group) is done row-by-row in Knot.
66+SQL could do `SELECT key, COUNT(*) ... GROUP BY key` in one pass.
67+ - codegen.rs:1316 declares the runtime fn; no GROUP BY SQL is emitted.
68+ - Effort: large (groupBy has complex key/rebinding semantics — see
69+ `validate_group_by_references`, temp-relation machinery ~10059-10748).
70+ - Win: the canonical DB workload; turns O(N) row shipping into O(groups).
71+
72+### 2. `distinct` / dedup → `SELECT DISTINCT`
73+No `DISTINCT` pushdown exists; dedup goes through `sql_dedup`
74+(runtime/lib.rs:6004) which round-trips rows. `SELECT DISTINCT cols`
75+for the common "yield a projection of unique rows" case.
76+ - Effort: medium. Win: moderate.
77+
78+### 3. `elem x (subquery-relation)` — uncorrelated IN subquery
79+`elem` pushes down for literal lists and `json_each` haystacks, but an
80+IN-haystack that is itself a pushed-down relation comprehension could become
81+`... WHERE x IN (SELECT ...)`. Currently a dynamic haystack is evaluated
82+outside SQL row scope and shipped.
83+ - Effort: medium. Win: avoids shipping the inner relation.
84+
85+### 4. Arithmetic projections in `map` that only feed a later aggregate
86+`map (\r -> r.a + r.b) |> sum` — the `+` is pushable as an atom but the
87+map-projection stage blocks the aggregate pushdown when it isn't the terminal.
88+Fold the arithmetic into the aggregate's SELECT expression.
89+ - Effort: small-medium (atoms already compile). Win: moderate.
90+
91+### 5. `length`/`count` of a *filtered* subrelation feeding a comparison
92+`where count (filter ...) > 0` style guards could become `EXISTS`.
93+ - Effort: medium. Win: moderate, less common shape.
94+
95+## Notes / constraints
96+- The lint (`sql_lint.rs`) MUST stay in lockstep with codegen — it
97+ re-implements every predicate so the "evaluated at runtime" info is accurate.
98+ Any new pushdown needs a mirrored lint predicate.
99+- Every pushdown entry point auto-indexes WHERE/ORDER BY columns
100+ (`ensure_indexes_for_sql`), so new pushdowns inherit index coverage.
101+- Float/tag/unicode fallbacks are documented in AGENTS.md/DESIGN.md as
102+ intentional — treat them as spec, not TODOs.
Boolnew file

No changes to the content.

CLAUDE.mddeleted−115
@@ -1,115 +0,0 @@
1−# Knot
2−
3−Knot is a functional relational programming language. Relations are the primary data structure, computation is pure and functional, and state is automatically persisted to SQLite.
4−
5−See `DESIGN.md` for the full language specification.
6−
7−## Project Structure
8−
9−Cargo workspace with three crates:
10−
11−```
12−crates/
13− knot/ Frontend library (lexer, parser, AST, diagnostics)
14− knot-runtime/ Rust staticlib linked into compiled programs (value representation, SQLite persistence)
15− knot-compiler/ Cranelift-based compiler producing native executables (CLI binary: knotc)
16−examples/ Example .knot programs
17−```
18−
19−## Build & Test
20−
21−```sh
22−cargo build # Build all crates
23−cargo test # Run all tests (263 parser/lexer + 110 compiler tests)
24−cargo test -p knot # Run only frontend tests
25−```
26−
27−## Compiling Knot Programs
28−
29−```sh
30−cargo run -p knot-compiler -- build examples/hello.knot
31−./examples/hello
32−```
33−
34−The compiler (`knotc`) looks for `libknot_runtime.a` next to its own executable. In a cargo workspace, both end up in `target/<profile>/`, so this works automatically. Override with `KNOT_RUNTIME_LIB=/path/to/libknot_runtime.a`.
35−
36−Compiled binaries create a `knot.db` SQLite database in the current directory for persistence.
37−
38−## Architecture
39−
40−### Compilation Pipeline
41−
42−```
43−source.knot → Lexer → Tokens → Parser → AST → Desugar → Type Inference → Effect Inference → Type Resolution → Cranelift IR → .o → cc link → executable
44−```
45−
46−### Frontend (`crates/knot/`)
47−
48−- **Lexer** (`lexer.rs`): Tokenizer with layout-sensitive newline handling
49−- **Parser** (`parser.rs`): Recursive-descent with Pratt expression parsing for operator precedence; `do` blocks are parsed in atom position so they can appear directly as function arguments (e.g. `f do ...` or `f (do ...)`)
50−- **AST** (`ast.rs`): Spanned AST nodes for all language constructs
51−- **Diagnostics** (`diagnostic.rs`): Error reporting with source locations
52−
53−Public API:
54−```rust
55−let lexer = knot::lexer::Lexer::new(&source);
56−let (tokens, lex_diags) = lexer.tokenize();
57−let parser = knot::parser::Parser::new(source, tokens);
58−let (module, parse_diags) = parser.parse_module();
59−```
60−
61−### Runtime (`crates/knot-runtime/`)
62−
63−Compiled as a `staticlib` (with `rlib` for workspace dependency resolution). All functions use `extern "C"` ABI, called by Cranelift-generated code via symbol references.
64−
65−- All Knot values are `*mut Value` (heap-allocated tagged enum); integers are unbounded (`num_bigint::BigInt`); IO actions are `Value::IO(fn_ptr, env)` thunks executed via `knot_io_run`
66−- `rusqlite` with `bundled` feature statically links SQLite into every compiled binary
67−- Source relations map to SQLite tables with schema derived from Knot types
68−- Schema descriptor format for records: `"name:text,age:int"` (passed as string constants from generated code)
69−- Schema descriptor format for direct ADT relations: `"#Circle:radius=float|Rect:width=float;height=float"` — `#` prefix signals ADT schema, `|` separates constructors, `:` separates name from fields, `;` separates fields, `=` separates field name from type; runtime creates wide table with `_tag TEXT` + all constructor fields as nullable columns
70−- Column type `tag` for enum-like ADT fields (all nullary constructors): stored as TEXT in SQLite, reconstructed as `Constructor(tag, Unit)` on read
71−- Automatic indexing: the runtime observes query patterns and creates indexes lazily — ADT tables get a `_tag` index at init; columns in `DELETE WHERE` and `UPDATE WHERE` clauses are auto-indexed on first use; `KnotDb` tracks created indexes in a per-session `HashSet` to avoid redundant DDL; uses `CREATE INDEX IF NOT EXISTS` for cross-session idempotency
72−- Nested relation fields (`[T]` inside records): stored in **child tables** (`_knot_{source}__{field}`) with `_parent_id` FK linking to parent's `_id`; parent tables with nested children get `_id INTEGER PRIMARY KEY AUTOINCREMENT`; schema descriptor format `field:[child_schema]` (bracket-aware parsing via `split_respecting_brackets`); deeply nested relations recurse arbitrarily
73−- Concurrency: `DB_PATH: Mutex<String>` stores the DB path globally so spawned threads can open their own connections; `THREAD_HANDLES: Mutex<Vec<JoinHandle<()>>>` tracks spawned threads; `knot_fork_io` deep-clones the IO value's environment (recursive `deep_clone_value` using `Box::new`, not arena), spawns an OS thread with its own `KnotDb` connection, stores the JoinHandle; `knot_threads_join` drains and joins all handles; each spawned thread gets its own arena allocator automatically
74−- STM retry: `RELATION_CHANGED: (Mutex<u64>, Condvar)` — global change counter incremented by `notify_relation_changed()` on every relation write (`set`, `full set`); `knot_stm_retry()` sets a thread-local `STM_RETRY` flag; `knot_stm_snapshot()` reads the current counter; `knot_stm_wait(snapshot)` blocks on the Condvar until counter exceeds snapshot; `knot_stm_check_and_clear()` checks and resets the thread-local retry flag; `knot_atomic_rollback(db)` rolls back the current savepoint
75−
76−### Compiler (`crates/knot-compiler/`)
77−
78−- **Desugaring** (`desugar.rs`): Transforms "pure comprehension" do blocks into nested calls to `__bind`, `__yield`, and `__empty`. A pure comprehension is a do block with only Bind/Where/Let/Yield statements (no bare side-effecting expressions or `groupBy`). Do blocks that are direct values of `set`/`full set` expressions are NOT desugared (preserves SQL optimization patterns). Do blocks containing `groupBy` are NOT desugared (require loop-based codegen for the collect/group/re-iterate pattern). Do blocks containing constructor pattern binds (`Ctor pat <- expr`) are NOT desugared (the desugarer cannot syntactically distinguish value pattern matches from monadic binds; direct codegen handles both). Do blocks containing IO-returning expressions are NOT desugared — the desugarer's `expr_is_io` recognizes both external IO builtins (`println`, `readFile`, `now`, etc.) and relation operations (`SourceRef`, `DerivedRef`, `Set`, `FullSet`, `At`) as IO, excluding these do blocks from desugaring so they go through the dedicated IO codegen path (`compile_io_do`) which runs each IO action with `knot_io_run` and wraps the result in `knot_io_pure`; this ensures IO do blocks inside if/then/else branches are typed correctly as IO rather than defaulting to relational. Mixed/sequential do blocks remain as `Do(stmts)` nodes for imperative codegen. The desugared `__bind`/`__yield`/`__empty` dispatch through Monad/Applicative/Alternative trait impls — the monad type is resolved at compile time via HKT unification (`monad_info` maps spans to `MonadKind::Relation`, `MonadKind::Adt(name)`, or `MonadKind::IO`), and codegen calls the appropriate `Monad_{type}_bind`, `Applicative_{type}_yield`, or `Alternative_{type}_empty` mangled function. This allows `do` blocks to work with any type implementing the Monad/Applicative/Alternative traits, not just `[]`.
79−- **Type inference** (`infer.rs`): Hindley-Milner type inference with row-polymorphic records and row-polymorphic variants, let-generalization, unification with occurs check, ADT constructor typing, trait method registration, associated type erasure, higher-kinded type polymorphism (`TyCon`/`App` type nodes with normalization-based unification), IO monad type (`Ty::IO(BTreeSet<IoEffect>, Box<Ty>)` with effect tracking for `Console`, `Fs`, `Network`, `Clock`, `Random`), and trait bound checking (explicit `TraitName a =>` constraints on function signatures are validated at call sites against known `impl` declarations; trait method calls automatically carry their trait's constraint). All relation operations return IO: `SourceRef` returns `IO {} [T]`, `DerivedRef` returns `IO {} [T]`, `Set`/`FullSet` return `IO {} {}`, `At` (temporal) returns `IO {} [T]`; `Set`/`FullSet` unify target and value by unwrapping IO from both sides. `Atomic` is typed as `IO {} a -> IO {} a` — body must be IO, enforced via unification; `in_atomic: bool` flag tracks context for `retry` validation. Built-in registrations include `fork : IO {} {} -> IO {} {}` and `retry : ∀a. a` (bottom type). Built-in ADTs registered in `register_builtins`: `Maybe a` (`Nothing`/`Just`), `Result e a` (`Err`/`Ok`), `Bool` (`True`/`False`) — `Bool` constructors map to the primitive `Ty::Bool` (not `Ty::Con`), so `True {}` and `False {}` produce the same type as `true`/`false` literals. Constructor patterns in `case` and do-bind create open variant types `<Ctor {} | r>` (the scrutinee only needs to *contain* the matched constructor), enabling row-polymorphic variant matching; when all constructors of a known data type are covered without a wildcard, the row variable is closed and exhaustiveness is checked normally; open variants require a wildcard `_` case
80−- **Effect inference** (`effects.rs`): Infers per-declaration effects (`{reads *rel}`, `{writes *rel}`, `{console}`, `{clock}`, etc.), checks IO-in-atomic constraints (atomic blocks must only contain DB interactions — no console/fs/clock/random), validates that atomic blocks actually interact with relations (have reads or writes), validates explicit effect annotations against inferred effects
81−- **Type resolution** (`types.rs`): Resolves aliases (including multi-variant ADTs to `ResolvedType::Adt`), computes SQLite schemas from Knot type annotations, collects subset constraints
82−- **Codegen** (`codegen.rs`): Cranelift IR generation — the `build_function` pattern moves `ctx`/`builder_ctx` out of `self` to avoid borrow conflicts while allowing `self.method()` calls during IR building
83−- **Linker** (`linker.rs`): Invokes `cc` with platform-appropriate flags
84−- **CLI** (`main.rs`): `knotc build <file.knot>` entry point
85−
86−Key codegen patterns:
87−- All values are pointer-typed (`ptr_type`) in Cranelift IR — uniform representation
88−- `do` blocks compile to nested loops with SSA block params for iteration counters
89−- `where` clauses become conditional branches; skip blocks jump to the loop's continue block
90−- Functions are constants bound to lambdas (`add = \x y -> x + y`); when a Fun's body is a Lambda, the compiler extracts its params for direct Cranelift function compilation (no closure overhead)
91−- Standalone lambdas compile as separate functions; free variables captured in a record-valued closure environment; multi-param lambdas (`\a b c -> body`) are curried into nested single-param lambdas at compile time
92−- Runtime functions are pre-declared as imports; `call_rt`/`call_rt_void` helpers emit calls
93−- `knot_relation_len` returns raw `usize`, not a boxed `Value` — use directly as loop bound
94−- Trait impl methods compile as mangled functions (`TraitName_TypeName_methodName`); a dispatcher function checks `knot_value_get_tag` at runtime and calls the matching impl; missing impls panic with a clear error message (except operator-mapped methods like `eq`/`compare`/`add`/`sub`/`mul`/`div`/`negate` which fall back to runtime functions for types without explicit impls)
95−- Operator trait dispatch: arithmetic operators (`+`, `-`, `*`, `/`) dispatch through `Num` trait methods (`add`, `sub`, `mul`, `div`); unary negation dispatches through `Num.negate`; `==` dispatches through `Eq.eq`; `!=` dispatches through `Eq.eq` then negates; comparison operators (`<`, `>`, `<=`, `>=`) dispatch through `Ord.compare` and check the resulting `Ordering` constructor tag; `&&`/`||`/`++` remain direct runtime calls (no trait). Primitive impls (Int, Float, Text, Bool) are registered as intrinsic codegen impls that delegate to runtime functions, avoiding circular dependencies (the prelude source does NOT contain these impls)
96−- Default trait methods: if an impl omits a method with a default body, the default is auto-compiled for that type
97−- `deriving (TraitName)` on data types auto-generates impls using the trait's default method bodies
98−- `show` calls `knot_value_show` (converts any value to Text representation)
99−- Standard library functions are registered as 1-param user_fns with currying: 1-param functions delegate directly to runtime; 2-param functions (filter, match, map, take, drop, contains) curry via `define_stdlib_fn_2` — outer function captures first arg in a closure env, inner closure extracts it and calls the runtime; 3-param functions (fold) use double currying via `define_stdlib_fn_3`
100−- Recursive derived relations: detected via `expr_contains_derived_ref` AST walk; compile to a body function `knot_user_<name>_body(db, self_val)` where self-references read from `self_val` param (via `__derived_self_<name>` env key), plus a wrapper that calls `knot_relation_fixpoint(db, body_fn_ptr, empty)` for fixed-point iteration
101−- Bool constructors: `True` and `False` are special-cased in codegen — bare `True`/`False` and applied `True {}`/`False {}` compile to `knot_value_bool(1)`/`knot_value_bool(0)` (not `knot_value_constructor`); pattern matching on `True`/`False` in `case` uses `knot_value_get_bool` instead of tag string comparison
102−- IO do-blocks: detected via `is_io_do_block` (checks if any statement binds from or is a bare IO-returning expression — includes both external IO builtins and relation operations like SourceRef/DerivedRef/Set/FullSet/At); compiled by `compile_io_do_as_thunk` which runs each IO action inline with `knot_io_run(db, io)` and wraps the final result in `knot_io_pure`; `compile_main` calls `knot_io_run(db, result)` on the main function's return value so IO main functions execute their effects; after IO run, `knot_threads_join()` is called to wait for all spawned threads before db close
103−- Concurrency codegen: `fork` registered as 1-param stdlib function via `define_stdlib_fn_1("fork", "knot_fork_io")`; `Atomic(inner)` compiles to a retry loop with three Cranelift blocks (loop_block, retry_block, done_block) — loop_block snapshots the change counter via `knot_stm_snapshot`, begins savepoint, runs body, checks `knot_stm_check_and_clear` retry flag with `brif`; retry_block calls `knot_atomic_rollback` then `knot_stm_wait(snapshot)` then jumps back to loop_block; done_block commits and continues; `Var("retry")` compiles to `knot_stm_retry()` call; all blocks explicitly sealed after predecessors are emitted
104−- IO runtime representation: `Value::IO(fn_ptr, env)` where fn_ptr has signature `extern "C" fn(db, env) -> *mut Value`; `knot_io_pure` wraps a value in a trivial IO (null fn_ptr, value as env); `knot_io_run` executes thunks or returns non-IO values as-is; `knot_io_bind` and `knot_io_then` compose IO actions via nested thunks with captured environments
105−- Effectful builtins (`println`, `readFile`, `now`, etc.) compile to `_io` wrapper functions (e.g. `knot_println_io`) that capture arguments and return `Value::IO` thunks instead of executing immediately
106−- HTTP client (`fetch`/`fetchWith`): compiler-special-cased forms that reuse route declarations for type safety; `compile_fetch` extracts the constructor's route entry metadata (method, path pattern, body/query/request-header field descriptors, response schema, response-header descriptors) at compile time and passes them as string constants alongside the compiled record payload to `knot_http_fetch_io`, which returns an IO thunk; the thunk builds the URL (substituting path params), serializes body fields to JSON, extracts route-declared request headers from payload fields (converting camelCase names to HTTP-Header-Case via `camel_to_header_case`), sets ad-hoc headers from `fetchWith` options, executes the HTTP request via `ureq`, parses the JSON response, and wraps it in a `Result` ADT (`Ok {value: parsed}` or `Err {error: {status, message}}`); when response headers are declared, the Ok value is wrapped as `{body: parsed, headers: {h1: v1, ...}}` with response headers parsed from the HTTP response; type inference (`try_infer_fetch`) fully handles these forms — it skips the constructor's `respond` field when type-checking the record argument, resolves the response type from `fetch_response_types`, and wraps the response type with `{body: T, headers: H}` when `fetch_response_headers` are present (both populated from route declarations during `pre_register`)
107−- `groupBy` in do-blocks: compiles as a two-phase loop — phase 1 collects qualifying rows into a temp relation (inside existing loops), closes those loops, then calls `knot_relation_group_by(db, temp, schema, key_cols)` which uses SQLite for grouping: inserts row indices + key column values into a temp table, executes `ORDER BY` on key columns, and groups consecutive rows with matching keys in O(n); phase 2 opens a new loop over the groups and rebinds the primary bind variable to each group (a sub-relation); field access on groups delegates to the first element via `knot_record_field`'s `Value::Relation` handling; codegen extracts key column names from the record key expression and schema from `source_schemas`
108−
109−## Supported Language Features
110−
111−Currently compiled: source declarations, type aliases, data declarations, functions, literals, records, field access, record updates, relation literals, nested relations (record fields holding `[T]` stored as JSON in SQLite, queryable via `m <- t.field` bind), binary/unary operations, if/else, do blocks (bind/where/yield/let/groupBy — pure comprehension do blocks are desugared to `__bind`/`__yield`/`__empty` calls that dispatch through Monad/Applicative/Alternative trait impls — works with any monad, not just `[]` (e.g., `Maybe`); monad type resolved at compile time via HKT unification; IO do blocks are excluded from desugaring and use the dedicated `compile_io_do` path; mixed/sequential do blocks use direct loop codegen; set-value do blocks preserved for SQL optimization; do blocks can appear in atom position as function arguments — `f do ...` and `f (do ...)` both work; `groupBy {key}` collects rows, groups by key, and rebinds the variable to each group sub-relation — field access on a group returns the first element's field, aggregates like `count` operate on the group), set expressions, lambdas, closures, function application, case expressions (with exhaustiveness checking for ADTs), constructors (including built-in `True {}`/`False {}` for `Bool` — interchangeable with `true`/`false` literals, usable in `case` patterns), atomic transactions, migrations (schema tracking + `migrate` blocks), schema lockfile (`<name>.schema.lock`), views (`*view = do { ... yield {...} }` with bidirectional read/write, constant column filtering, auto-fill on write), derived relations (`&name = expr` — read-only computed relations, compiled as 0-param functions recomputed on each access; recursive derived relations with self-references use Datalog-style fixpoint iteration via `knot_relation_fixpoint` runtime function, converging when the relation stabilizes), traits and impls (single-dispatch on runtime type tags for primitives and ADTs, default methods, deriving from defaults, supertrait enforcement, associated types with type-level pattern matching), trait bounds (`Display a => [a] -> [Text]` syntax — explicit constraints on function type signatures validated at call sites; trait method calls automatically carry constraints; multiple bounds via `Trait1 a => Trait2 a => Type`; compile-time error when a concrete type lacks a required impl), type inference (Hindley-Milner with row polymorphism for both records and variants, let-generalization, ADT/trait/view-aware, higher-kinded type polymorphism), higher-kinded types (trait params with kind annotations like `(f : Type -> Type)`, type-level application `f a` in method signatures, `[]` and ADT names as bare type constructors in impl args, `TyCon`/`App` internal representation with normalization-based unification), IO monad (`IO {effects} a` type for pure effect descriptions — all relation operations (`*rel`, `&rel`, `set`, `full set`, `@(...)`) return `IO {} value` with empty effect set, external builtins like `println`, `readFile`, `now` return IO with specific effects; IO do-blocks sequence actions via bind; effects tracked in the type: `IO {console} {}`, `IO {fs} Text`, `IO {clock} Int`, `IO {} [T]` for DB ops; `knot_io_run` executes IO thunks at the top level in `main`), concurrency (`fork : IO {} {} -> IO {} {}` fire-and-forget OS thread spawning with independent SQLite connections via WAL mode, deep-cloned environments; `retry` for STM-style atomic retry — inside `atomic` blocks, causes rollback and waits for relation changes via Condvar; `atomic : IO {} a -> IO {} a` runs IO body in a transaction with savepoint-based retry loop, body must contain only DB interactions), temporal queries (`*rel : [T] with history` enables history tracking, `*rel @(timestamp)` queries past state, `now` returns `IO {clock} Int`), pattern matching in do-bind (`Circle c <- *shapes` filters relation to matching constructors and destructures; `InProgress ip <- t.status` matches a single value and skips if no match), built-ins (println, print, show, union, count, now, filter, match, map, fold, single, diff, inter, sum, avg, toUpper, toLower, take, drop, length, trim, contains, reverse, chars, id, not, toJson, parseJson, readFile, writeFile, appendFile, fileExists, removeFile, listDir, readLine, randomInt, randomFloat, fork, retry — IO-returning builtins produce `IO {effect} a` values), effect inference (per-declaration effect tracking for reads/writes/console/clock/network/fs/random, IO-in-atomic validation, explicit annotation checking), subset constraints (`*orders.customer <= *people.name` for referential integrity, `*users <= *users.email` for uniqueness — validated at runtime before each write commits), routes (`route Name where` with HTTP method/path/body/query/response declarations, path prefix nesting, `route Name = A | B` composition, `headers {field: Type}` for typed request/response headers — request headers appear after query params and before `->`, response headers appear after the response type; header field names use camelCase and auto-convert to HTTP-Header-Case (`authorization` → `Authorization`, `contentType` → `Content-Type`); optional headers use `Maybe` type (`headers {authorization: Maybe Text}` — server gets `Nothing` if absent, `Just {value: "..."}` if present; fetch skips `Nothing` headers); request header fields become constructor fields like body/query/path params; when response headers are declared, `respond` takes two curried arguments (body, then headers record) and returns `{body: ..., headers: ...}`, with the listen loop extracting body for JSON and headers for HTTP response headers; desugared to ADTs with `respond` callback for per-branch response type safety, `listen port handler` starts `tiny_http` server with route table matching, JSON request/response serialization, `fetch url (Ctor {fields})` type-safe HTTP client reusing route declarations — response type inferred from route's `response:` annotation, returns `IO {network} (Result {status: Int, message: Text} ResponseType)` or `IO {network} (Result ... {body: ResponseType, headers: {h: T}})` when response headers declared, `fetchWith url {headers: [...]} (Ctor {fields})` variant with ad-hoc headers merged with route-declared headers, uses `ureq` for HTTP requests).
112−
113−## Version Control
114−
115−This repository uses **jujutsu (jj)**, not git. Use `jj` commands for all VCS operations.
Cargo.lock+2468−487
@@ -14,10 +14,30 @@ version = "0.5.2"
1414source = "registry+https://github.com/rust-lang/crates.io-index"
1515checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
1616dependencies = [
17− "crypto-common",
17+ "crypto-common 0.1.7",
1818 "generic-array",
1919]
2020
21+[[package]]
22+name = "aes"
23+version = "0.9.1"
24+source = "registry+https://github.com/rust-lang/crates.io-index"
25+checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138"
26+dependencies = [
27+ "cipher 0.5.2",
28+ "cpubits",
29+ "cpufeatures 0.3.0",
30+]
31+
32+[[package]]
33+name = "aho-corasick"
34+version = "1.1.4"
35+source = "registry+https://github.com/rust-lang/crates.io-index"
36+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
37+dependencies = [
38+ "memchr",
39+]
40+
2141[[package]]
2242name = "allocator-api2"
2343version = "0.2.21"
@@ -42,16 +62,43 @@ version = "0.6.0"
4262source = "registry+https://github.com/rust-lang/crates.io-index"
4363checksum = "8454c8a44ce2cb9cc7e7fae67fc6128465b343b92c6631e94beca3c8d1524ea5"
4464dependencies = [
45− "unicode-width 0.2.0",
65+ "unicode-width",
4666 "yansi",
4767]
4868
69+[[package]]
70+name = "arrayref"
71+version = "0.3.9"
72+source = "registry+https://github.com/rust-lang/crates.io-index"
73+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
74+
75+[[package]]
76+name = "arrayvec"
77+version = "0.7.6"
78+source = "registry+https://github.com/rust-lang/crates.io-index"
79+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
80+
4981[[package]]
5082name = "ascii"
5183version = "1.1.0"
5284source = "registry+https://github.com/rust-lang/crates.io-index"
5385checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
5486
87+[[package]]
88+name = "atomic"
89+version = "0.6.1"
90+source = "registry+https://github.com/rust-lang/crates.io-index"
91+checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"
92+dependencies = [
93+ "bytemuck",
94+]
95+
96+[[package]]
97+name = "atomic-waker"
98+version = "1.1.2"
99+source = "registry+https://github.com/rust-lang/crates.io-index"
100+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
101+
55102[[package]]
56103name = "autocfg"
57104version = "1.5.0"
@@ -64,6 +111,21 @@ version = "0.22.1"
64111source = "registry+https://github.com/rust-lang/crates.io-index"
65112checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
66113
114+[[package]]
115+name = "bit-set"
116+version = "0.5.3"
117+source = "registry+https://github.com/rust-lang/crates.io-index"
118+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
119+dependencies = [
120+ "bit-vec",
121+]
122+
123+[[package]]
124+name = "bit-vec"
125+version = "0.6.3"
126+source = "registry+https://github.com/rust-lang/crates.io-index"
127+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
128+
67129[[package]]
68130name = "bitflags"
69131version = "1.3.2"
@@ -72,9 +134,23 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
72134
73135[[package]]
74136name = "bitflags"
75−version = "2.11.0"
137+version = "2.11.1"
138+source = "registry+https://github.com/rust-lang/crates.io-index"
139+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
140+
141+[[package]]
142+name = "blake3"
143+version = "1.8.5"
76144source = "registry+https://github.com/rust-lang/crates.io-index"
77−checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
145+checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce"
146+dependencies = [
147+ "arrayref",
148+ "arrayvec",
149+ "cc",
150+ "cfg-if",
151+ "constant_time_eq",
152+ "cpufeatures 0.3.0",
153+]
78154
79155[[package]]
80156name = "block-buffer"
@@ -85,6 +161,16 @@ dependencies = [
85161 "generic-array",
86162]
87163
164+[[package]]
165+name = "block-buffer"
166+version = "0.12.1"
167+source = "registry+https://github.com/rust-lang/crates.io-index"
168+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
169+dependencies = [
170+ "hybrid-array",
171+ "zeroize",
172+]
173+
88174[[package]]
89175name = "bumpalo"
90176version = "3.20.2"
@@ -95,10 +181,25 @@ dependencies = [
95181]
96182
97183[[package]]
98−name = "cassowary"
99−version = "0.3.0"
184+name = "bytemuck"
185+version = "1.25.0"
186+source = "registry+https://github.com/rust-lang/crates.io-index"
187+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
188+
189+[[package]]
190+name = "bytes"
191+version = "1.11.1"
192+source = "registry+https://github.com/rust-lang/crates.io-index"
193+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
194+
195+[[package]]
196+name = "bzip2"
197+version = "0.6.1"
100198source = "registry+https://github.com/rust-lang/crates.io-index"
101−checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
199+checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
200+dependencies = [
201+ "libbz2-rs-sys",
202+]
102203
103204[[package]]
104205name = "castaway"
@@ -111,11 +212,13 @@ dependencies = [
111212
112213[[package]]
113214name = "cc"
114−version = "1.2.56"
215+version = "1.2.60"
115216source = "registry+https://github.com/rust-lang/crates.io-index"
116−checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
217+checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
117218dependencies = [
118219 "find-msvc-tools",
220+ "jobserver",
221+ "libc",
119222 "shlex",
120223]
121224
@@ -125,6 +228,12 @@ version = "1.0.4"
125228source = "registry+https://github.com/rust-lang/crates.io-index"
126229checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
127230
231+[[package]]
232+name = "cfg_aliases"
233+version = "0.2.1"
234+source = "registry+https://github.com/rust-lang/crates.io-index"
235+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
236+
128237[[package]]
129238name = "chacha20"
130239version = "0.9.1"
@@ -132,8 +241,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
132241checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
133242dependencies = [
134243 "cfg-if",
135− "cipher",
136− "cpufeatures",
244+ "cipher 0.4.4",
245+ "cpufeatures 0.2.17",
246+]
247+
248+[[package]]
249+name = "chacha20"
250+version = "0.10.1"
251+source = "registry+https://github.com/rust-lang/crates.io-index"
252+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
253+dependencies = [
254+ "cfg-if",
255+ "cpufeatures 0.3.0",
256+ "rand_core 0.10.1",
137257]
138258
139259[[package]]
@@ -143,8 +263,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
143263checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
144264dependencies = [
145265 "aead",
146− "chacha20",
147− "cipher",
266+ "chacha20 0.9.1",
267+ "cipher 0.4.4",
148268 "poly1305",
149269 "zeroize",
150270]
@@ -161,16 +281,32 @@ version = "0.4.4"
161281source = "registry+https://github.com/rust-lang/crates.io-index"
162282checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
163283dependencies = [
164− "crypto-common",
165− "inout",
284+ "crypto-common 0.1.7",
285+ "inout 0.1.4",
166286 "zeroize",
167287]
168288
289+[[package]]
290+name = "cipher"
291+version = "0.5.2"
292+source = "registry+https://github.com/rust-lang/crates.io-index"
293+checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
294+dependencies = [
295+ "crypto-common 0.2.2",
296+ "inout 0.2.2",
297+]
298+
299+[[package]]
300+name = "cmov"
301+version = "0.5.4"
302+source = "registry+https://github.com/rust-lang/crates.io-index"
303+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
304+
169305[[package]]
170306name = "compact_str"
171−version = "0.8.1"
307+version = "0.9.0"
172308source = "registry+https://github.com/rust-lang/crates.io-index"
173−checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
309+checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
174310dependencies = [
175311 "castaway",
176312 "cfg-if",
@@ -180,6 +316,33 @@ dependencies = [
180316 "static_assertions",
181317]
182318
319+[[package]]
320+name = "const-oid"
321+version = "0.10.2"
322+source = "registry+https://github.com/rust-lang/crates.io-index"
323+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
324+
325+[[package]]
326+name = "constant_time_eq"
327+version = "0.4.2"
328+source = "registry+https://github.com/rust-lang/crates.io-index"
329+checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
330+
331+[[package]]
332+name = "convert_case"
333+version = "0.10.0"
334+source = "registry+https://github.com/rust-lang/crates.io-index"
335+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
336+dependencies = [
337+ "unicode-segmentation",
338+]
339+
340+[[package]]
341+name = "cpubits"
342+version = "0.1.1"
343+source = "registry+https://github.com/rust-lang/crates.io-index"
344+checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
345+
183346[[package]]
184347name = "cpufeatures"
185348version = "0.2.17"
@@ -189,28 +352,60 @@ dependencies = [
189352 "libc",
190353]
191354
355+[[package]]
356+name = "cpufeatures"
357+version = "0.3.0"
358+source = "registry+https://github.com/rust-lang/crates.io-index"
359+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
360+dependencies = [
361+ "libc",
362+]
363+
364+[[package]]
365+name = "cranelift-assembler-x64"
366+version = "0.131.0"
367+source = "registry+https://github.com/rust-lang/crates.io-index"
368+checksum = "6edb5bdd1af46714e3224a017fabbbd57f70df4e840eb5ad6a7429dc456119d6"
369+dependencies = [
370+ "cranelift-assembler-x64-meta",
371+]
372+
373+[[package]]
374+name = "cranelift-assembler-x64-meta"
375+version = "0.131.0"
376+source = "registry+https://github.com/rust-lang/crates.io-index"
377+checksum = "a819599186e1b1a1f88d464e06045696afc7aa3e0cc018aa0b2999cb63d1d088"
378+dependencies = [
379+ "cranelift-srcgen",
380+]
381+
192382[[package]]
193383name = "cranelift-bforest"
194−version = "0.116.1"
384+version = "0.131.0"
195385source = "registry+https://github.com/rust-lang/crates.io-index"
196−checksum = "e15d04a0ce86cb36ead88ad68cf693ffd6cda47052b9e0ac114bc47fd9cd23c4"
386+checksum = "36e2c152d488e03c87b913bc2ed3414416eb1e0d66d61b49af60bf456a9665c7"
197387dependencies = [
198388 "cranelift-entity",
389+ "wasmtime-internal-core",
199390]
200391
201392[[package]]
202393name = "cranelift-bitset"
203−version = "0.116.1"
394+version = "0.131.0"
204395source = "registry+https://github.com/rust-lang/crates.io-index"
205−checksum = "7c6e3969a7ce267259ce244b7867c5d3bc9e65b0a87e81039588dfdeaede9f34"
396+checksum = "b6559d4fbc253d1396e1f6beeae57fa88a244f02aaf0cde2a735afd3492d9b2e"
397+dependencies = [
398+ "wasmtime-internal-core",
399+]
206400
207401[[package]]
208402name = "cranelift-codegen"
209−version = "0.116.1"
403+version = "0.131.0"
210404source = "registry+https://github.com/rust-lang/crates.io-index"
211−checksum = "2c22032c4cb42558371cf516bb47f26cdad1819d3475c133e93c49f50ebf304e"
405+checksum = "96d9315d98d6e0a64454d4c83be2ee0e8055c3f80c3b2d7bcad7079f281a06ff"
212406dependencies = [
213407 "bumpalo",
408+ "cranelift-assembler-x64",
214409 "cranelift-bforest",
215410 "cranelift-bitset",
216411 "cranelift-codegen-meta",
@@ -219,53 +414,59 @@ dependencies = [
219414 "cranelift-entity",
220415 "cranelift-isle",
221416 "gimli",
222− "hashbrown 0.14.5",
417+ "hashbrown 0.16.1",
418+ "libm",
223419 "log",
224420 "regalloc2",
225421 "rustc-hash",
226422 "serde",
227423 "smallvec",
228424 "target-lexicon",
425+ "wasmtime-internal-core",
229426]
230427
231428[[package]]
232429name = "cranelift-codegen-meta"
233−version = "0.116.1"
430+version = "0.131.0"
234431source = "registry+https://github.com/rust-lang/crates.io-index"
235−checksum = "c904bc71c61b27fc57827f4a1379f29de64fe95653b620a3db77d59655eee0b8"
432+checksum = "d89c00a88081c55e3087c45bebc77e0cc973de2d7b44ef6a943c7122647b89f5"
236433dependencies = [
434+ "cranelift-assembler-x64-meta",
237435 "cranelift-codegen-shared",
436+ "cranelift-srcgen",
437+ "heck",
238438]
239439
240440[[package]]
241441name = "cranelift-codegen-shared"
242−version = "0.116.1"
442+version = "0.131.0"
243443source = "registry+https://github.com/rust-lang/crates.io-index"
244−checksum = "40180f5497572f644ce88c255480981ae2ec1d7bb4d8e0c0136a13b87a2f2ceb"
444+checksum = "879f77c497a1eb6273482aa1ac3b23cb8563ff04edb39ed5dfcfd28c8deff8f5"
245445
246446[[package]]
247447name = "cranelift-control"
248−version = "0.116.1"
448+version = "0.131.0"
249449source = "registry+https://github.com/rust-lang/crates.io-index"
250−checksum = "26d132c6d0bd8a489563472afc171759da0707804a65ece7ceb15a8c6d7dd5ef"
450+checksum = "498dc1f17a6910c88316d49c7176d8fa97cf10c30859c32a266040449317f963"
251451dependencies = [
252452 "arbitrary",
253453]
254454
255455[[package]]
256456name = "cranelift-entity"
257−version = "0.116.1"
457+version = "0.131.0"
258458source = "registry+https://github.com/rust-lang/crates.io-index"
259−checksum = "4b2d0d9618275474fbf679dd018ac6e009acbd6ae6850f6a67be33fb3b00b323"
459+checksum = "c2acba797f6a46042ce82aaf7680d0c3567fe2001e238db9df649fd104a2727f"
260460dependencies = [
261461 "cranelift-bitset",
462+ "wasmtime-internal-core",
262463]
263464
264465[[package]]
265466name = "cranelift-frontend"
266−version = "0.116.1"
467+version = "0.131.0"
267468source = "registry+https://github.com/rust-lang/crates.io-index"
268−checksum = "4fac41e16729107393174b0c9e3730fb072866100e1e64e80a1a963b2e484d57"
469+checksum = "4dca3df1d107d98d88f159ad1d5eaa2d5cdb678b3d5bcfadc6fc83d8ebb448ea"
269470dependencies = [
270471 "cranelift-codegen",
271472 "log",
@@ -275,15 +476,35 @@ dependencies = [
275476
276477[[package]]
277478name = "cranelift-isle"
278−version = "0.116.1"
479+version = "0.131.0"
480+source = "registry+https://github.com/rust-lang/crates.io-index"
481+checksum = "f62dd18116d88bed649871feceda79dad7b59cc685ea8998c2b3e64d0e689602"
482+
483+[[package]]
484+name = "cranelift-jit"
485+version = "0.131.0"
279486source = "registry+https://github.com/rust-lang/crates.io-index"
280−checksum = "1ca20d576e5070044d0a72a9effc2deacf4d6aa650403189d8ea50126483944d"
487+checksum = "0a4942770ce6662b44d903493d7c5b00f9a986a713a61aae148306eaef21ebd4"
488+dependencies = [
489+ "anyhow",
490+ "cranelift-codegen",
491+ "cranelift-control",
492+ "cranelift-entity",
493+ "cranelift-module",
494+ "cranelift-native",
495+ "libc",
496+ "log",
497+ "region",
498+ "target-lexicon",
499+ "wasmtime-internal-jit-icache-coherence",
500+ "windows-sys 0.61.2",
501+]
281502
282503[[package]]
283504name = "cranelift-module"
284−version = "0.116.1"
505+version = "0.131.0"
285506source = "registry+https://github.com/rust-lang/crates.io-index"
286−checksum = "4d55612bebcf16ff7306c8a6f5bdb6d45662b8aa1ee058ecce8807ad87db719b"
507+checksum = "fb5ca0d214ecee44405ea9f0c65a5318b41ac469e8258fd9fe944e564c1c1b0b"
287508dependencies = [
288509 "anyhow",
289510 "cranelift-codegen",
@@ -292,9 +513,9 @@ dependencies = [
292513
293514[[package]]
294515name = "cranelift-native"
295−version = "0.116.1"
516+version = "0.131.0"
296517source = "registry+https://github.com/rust-lang/crates.io-index"
297−checksum = "b8dee82f3f1f2c4cba9177f1cc5e350fe98764379bcd29340caa7b01f85076c7"
518+checksum = "f843b80360d7fdf61a6124642af7597f6d55724cf521210c34af8a1c66daca6e"
298519dependencies = [
299520 "cranelift-codegen",
300521 "libc",
@@ -303,9 +524,9 @@ dependencies = [
303524
304525[[package]]
305526name = "cranelift-object"
306−version = "0.116.1"
527+version = "0.131.0"
307528source = "registry+https://github.com/rust-lang/crates.io-index"
308−checksum = "aad5a6d3e379493c3f8b35dc61c93d0bf5f27003bbe20614e0200b0ec372ef52"
529+checksum = "b9d212d15015c374333b11b833111b7c7e686bfaec02385af53611050bce7e9d"
309530dependencies = [
310531 "anyhow",
311532 "cranelift-codegen",
@@ -316,6 +537,12 @@ dependencies = [
316537 "target-lexicon",
317538]
318539
540+[[package]]
541+name = "cranelift-srcgen"
542+version = "0.131.0"
543+source = "registry+https://github.com/rust-lang/crates.io-index"
544+checksum = "090ee5de58c6f17eb5e3a5ae8cf1695c7efea04ec4dd0ecba6a5b996c9bad7dc"
545+
319546[[package]]
320547name = "crc32fast"
321548version = "1.5.0"
@@ -342,12 +569,14 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
342569
343570[[package]]
344571name = "crossterm"
345−version = "0.28.1"
572+version = "0.29.0"
346573source = "registry+https://github.com/rust-lang/crates.io-index"
347−checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
574+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
348575dependencies = [
349− "bitflags 2.11.0",
576+ "bitflags 2.11.1",
350577 "crossterm_winapi",
578+ "derive_more",
579+ "document-features",
351580 "mio",
352581 "parking_lot",
353582 "rustix",
@@ -372,10 +601,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
372601checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
373602dependencies = [
374603 "generic-array",
375− "rand_core",
604+ "rand_core 0.6.4",
376605 "typenum",
377606]
378607
608+[[package]]
609+name = "crypto-common"
610+version = "0.2.2"
611+source = "registry+https://github.com/rust-lang/crates.io-index"
612+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
613+dependencies = [
614+ "hybrid-array",
615+]
616+
617+[[package]]
618+name = "csscolorparser"
619+version = "0.6.2"
620+source = "registry+https://github.com/rust-lang/crates.io-index"
621+checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf"
622+dependencies = [
623+ "lab",
624+ "phf",
625+]
626+
627+[[package]]
628+name = "ctutils"
629+version = "0.4.2"
630+source = "registry+https://github.com/rust-lang/crates.io-index"
631+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
632+dependencies = [
633+ "cmov",
634+]
635+
379636[[package]]
380637name = "curve25519-dalek"
381638version = "4.1.3"
@@ -383,9 +640,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
383640checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
384641dependencies = [
385642 "cfg-if",
386− "cpufeatures",
643+ "cpufeatures 0.2.17",
387644 "curve25519-dalek-derive",
388− "digest",
645+ "digest 0.10.7",
389646 "fiat-crypto",
390647 "rustc_version",
391648 "subtle",
@@ -400,7 +657,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
400657dependencies = [
401658 "proc-macro2",
402659 "quote",
403− "syn",
660+ "syn 2.0.117",
404661]
405662
406663[[package]]
@@ -423,7 +680,7 @@ dependencies = [
423680 "proc-macro2",
424681 "quote",
425682 "strsim",
426− "syn",
683+ "syn 2.0.117",
427684]
428685
429686[[package]]
@@ -434,7 +691,64 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
434691dependencies = [
435692 "darling_core",
436693 "quote",
437− "syn",
694+ "syn 2.0.117",
695+]
696+
697+[[package]]
698+name = "dashmap"
699+version = "6.1.0"
700+source = "registry+https://github.com/rust-lang/crates.io-index"
701+checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
702+dependencies = [
703+ "cfg-if",
704+ "crossbeam-utils",
705+ "hashbrown 0.14.5",
706+ "lock_api",
707+ "once_cell",
708+ "parking_lot_core",
709+]
710+
711+[[package]]
712+name = "deflate64"
713+version = "0.1.12"
714+source = "registry+https://github.com/rust-lang/crates.io-index"
715+checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
716+
717+[[package]]
718+name = "deltae"
719+version = "0.3.2"
720+source = "registry+https://github.com/rust-lang/crates.io-index"
721+checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4"
722+
723+[[package]]
724+name = "deranged"
725+version = "0.5.8"
726+source = "registry+https://github.com/rust-lang/crates.io-index"
727+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
728+dependencies = [
729+ "powerfmt",
730+]
731+
732+[[package]]
733+name = "derive_more"
734+version = "2.1.1"
735+source = "registry+https://github.com/rust-lang/crates.io-index"
736+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
737+dependencies = [
738+ "derive_more-impl",
739+]
740+
741+[[package]]
742+name = "derive_more-impl"
743+version = "2.1.1"
744+source = "registry+https://github.com/rust-lang/crates.io-index"
745+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
746+dependencies = [
747+ "convert_case",
748+ "proc-macro2",
749+ "quote",
750+ "rustc_version",
751+ "syn 2.0.117",
438752]
439753
440754[[package]]
@@ -443,19 +757,41 @@ version = "0.10.7"
443757source = "registry+https://github.com/rust-lang/crates.io-index"
444758checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
445759dependencies = [
446− "block-buffer",
447− "crypto-common",
760+ "block-buffer 0.10.4",
761+ "crypto-common 0.1.7",
762+]
763+
764+[[package]]
765+name = "digest"
766+version = "0.11.3"
767+source = "registry+https://github.com/rust-lang/crates.io-index"
768+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
769+dependencies = [
770+ "block-buffer 0.12.1",
771+ "const-oid",
772+ "crypto-common 0.2.2",
773+ "ctutils",
774+ "zeroize",
448775]
449776
450777[[package]]
451778name = "displaydoc"
452−version = "0.2.5"
779+version = "0.2.6"
453780source = "registry+https://github.com/rust-lang/crates.io-index"
454−checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
781+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
455782dependencies = [
456783 "proc-macro2",
457784 "quote",
458− "syn",
785+ "syn 2.0.117",
786+]
787+
788+[[package]]
789+name = "document-features"
790+version = "0.2.12"
791+source = "registry+https://github.com/rust-lang/crates.io-index"
792+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
793+dependencies = [
794+ "litrs",
459795]
460796
461797[[package]]
@@ -475,7 +811,7 @@ checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
475811dependencies = [
476812 "curve25519-dalek",
477813 "ed25519",
478− "sha2",
814+ "sha2 0.10.9",
479815 "subtle",
480816]
481817
@@ -501,6 +837,15 @@ dependencies = [
501837 "windows-sys 0.61.2",
502838]
503839
840+[[package]]
841+name = "euclid"
842+version = "0.22.14"
843+source = "registry+https://github.com/rust-lang/crates.io-index"
844+checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06"
845+dependencies = [
846+ "num-traits",
847+]
848+
504849[[package]]
505850name = "fallible-iterator"
506851version = "0.3.0"
@@ -513,18 +858,51 @@ version = "0.1.9"
513858source = "registry+https://github.com/rust-lang/crates.io-index"
514859checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
515860
861+[[package]]
862+name = "fancy-regex"
863+version = "0.11.0"
864+source = "registry+https://github.com/rust-lang/crates.io-index"
865+checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
866+dependencies = [
867+ "bit-set",
868+ "regex",
869+]
870+
516871[[package]]
517872name = "fiat-crypto"
518873version = "0.2.9"
519874source = "registry+https://github.com/rust-lang/crates.io-index"
520875checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
521876
877+[[package]]
878+name = "filedescriptor"
879+version = "0.8.3"
880+source = "registry+https://github.com/rust-lang/crates.io-index"
881+checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"
882+dependencies = [
883+ "libc",
884+ "thiserror 1.0.69",
885+ "winapi",
886+]
887+
522888[[package]]
523889name = "find-msvc-tools"
524890version = "0.1.9"
525891source = "registry+https://github.com/rust-lang/crates.io-index"
526892checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
527893
894+[[package]]
895+name = "finl_unicode"
896+version = "1.4.0"
897+source = "registry+https://github.com/rust-lang/crates.io-index"
898+checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5"
899+
900+[[package]]
901+name = "fixedbitset"
902+version = "0.4.2"
903+source = "registry+https://github.com/rust-lang/crates.io-index"
904+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
905+
528906[[package]]
529907name = "flate2"
530908version = "1.1.9"
@@ -533,6 +911,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
533911dependencies = [
534912 "crc32fast",
535913 "miniz_oxide",
914+ "zlib-rs",
536915]
537916
538917[[package]]
@@ -544,12 +923,24 @@ dependencies = [
544923 "bitflags 1.3.2",
545924]
546925
926+[[package]]
927+name = "fnv"
928+version = "1.0.7"
929+source = "registry+https://github.com/rust-lang/crates.io-index"
930+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
931+
547932[[package]]
548933name = "foldhash"
549934version = "0.1.5"
550935source = "registry+https://github.com/rust-lang/crates.io-index"
551936checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
552937
938+[[package]]
939+name = "foldhash"
940+version = "0.2.0"
941+source = "registry+https://github.com/rust-lang/crates.io-index"
942+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
943+
553944[[package]]
554945name = "form_urlencoded"
555946version = "1.2.2"
@@ -560,45 +951,113 @@ dependencies = [
560951]
561952
562953[[package]]
563−name = "generic-array"
564−version = "0.14.7"
954+name = "futures-channel"
955+version = "0.3.33"
565956source = "registry+https://github.com/rust-lang/crates.io-index"
566−checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
957+checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
567958dependencies = [
568− "typenum",
569− "version_check",
959+ "futures-core",
960+ "futures-sink",
570961]
571962
572963[[package]]
573−name = "getrandom"
574−version = "0.2.17"
964+name = "futures-core"
965+version = "0.3.33"
575966source = "registry+https://github.com/rust-lang/crates.io-index"
576−checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
577−dependencies = [
578− "cfg-if",
579− "libc",
580− "wasi",
581−]
967+checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
582968
583969[[package]]
584−name = "getrandom"
585−version = "0.3.4"
970+name = "futures-io"
971+version = "0.3.33"
972+source = "registry+https://github.com/rust-lang/crates.io-index"
973+checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
974+
975+[[package]]
976+name = "futures-sink"
977+version = "0.3.33"
978+source = "registry+https://github.com/rust-lang/crates.io-index"
979+checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
980+
981+[[package]]
982+name = "futures-task"
983+version = "0.3.33"
984+source = "registry+https://github.com/rust-lang/crates.io-index"
985+checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
986+
987+[[package]]
988+name = "futures-util"
989+version = "0.3.33"
990+source = "registry+https://github.com/rust-lang/crates.io-index"
991+checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
992+dependencies = [
993+ "futures-core",
994+ "futures-io",
995+ "futures-sink",
996+ "futures-task",
997+ "memchr",
998+ "pin-project-lite",
999+ "slab",
1000+]
1001+
1002+[[package]]
1003+name = "generic-array"
1004+version = "0.14.7"
1005+source = "registry+https://github.com/rust-lang/crates.io-index"
1006+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
1007+dependencies = [
1008+ "typenum",
1009+ "version_check",
1010+]
1011+
1012+[[package]]
1013+name = "getrandom"
1014+version = "0.2.17"
1015+source = "registry+https://github.com/rust-lang/crates.io-index"
1016+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
1017+dependencies = [
1018+ "cfg-if",
1019+ "js-sys",
1020+ "libc",
1021+ "wasi",
1022+ "wasm-bindgen",
1023+]
1024+
1025+[[package]]
1026+name = "getrandom"
1027+version = "0.3.4"
5861028source = "registry+https://github.com/rust-lang/crates.io-index"
5871029checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
5881030dependencies = [
5891031 "cfg-if",
5901032 "libc",
591− "r-efi",
1033+ "r-efi 5.3.0",
1034+ "wasip2",
1035+]
1036+
1037+[[package]]
1038+name = "getrandom"
1039+version = "0.4.2"
1040+source = "registry+https://github.com/rust-lang/crates.io-index"
1041+checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
1042+dependencies = [
1043+ "cfg-if",
1044+ "js-sys",
1045+ "libc",
1046+ "r-efi 6.0.0",
1047+ "rand_core 0.10.1",
5921048 "wasip2",
1049+ "wasip3",
1050+ "wasm-bindgen",
5931051]
5941052
5951053[[package]]
5961054name = "gimli"
597−version = "0.31.1"
1055+version = "0.33.0"
5981056source = "registry+https://github.com/rust-lang/crates.io-index"
599−checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
1057+checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c"
6001058dependencies = [
601− "fallible-iterator",
1059+ "fnv",
1060+ "hashbrown 0.16.1",
6021061 "indexmap",
6031062 "stable_deref_trait",
6041063]
@@ -615,9 +1074,7 @@ version = "0.15.5"
6151074source = "registry+https://github.com/rust-lang/crates.io-index"
6161075checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
6171076dependencies = [
618− "allocator-api2",
619− "equivalent",
620− "foldhash",
1077+ "foldhash 0.1.5",
6211078]
6221079
6231080[[package]]
@@ -625,14 +1082,28 @@ name = "hashbrown"
6251082version = "0.16.1"
6261083source = "registry+https://github.com/rust-lang/crates.io-index"
6271084checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
1085+dependencies = [
1086+ "allocator-api2",
1087+ "equivalent",
1088+ "foldhash 0.2.0",
1089+]
1090+
1091+[[package]]
1092+name = "hashbrown"
1093+version = "0.17.0"
1094+source = "registry+https://github.com/rust-lang/crates.io-index"
1095+checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
1096+dependencies = [
1097+ "foldhash 0.2.0",
1098+]
6281099
6291100[[package]]
6301101name = "hashlink"
631−version = "0.10.0"
1102+version = "0.11.0"
6321103source = "registry+https://github.com/rust-lang/crates.io-index"
633−checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
1104+checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230"
6341105dependencies = [
635− "hashbrown 0.15.5",
1106+ "hashbrown 0.16.1",
6361107]
6371108
6381109[[package]]
@@ -641,20 +1112,143 @@ version = "0.5.0"
6411112source = "registry+https://github.com/rust-lang/crates.io-index"
6421113checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
6431114
1115+[[package]]
1116+name = "hex"
1117+version = "0.4.3"
1118+source = "registry+https://github.com/rust-lang/crates.io-index"
1119+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1120+
1121+[[package]]
1122+name = "hmac"
1123+version = "0.13.0"
1124+source = "registry+https://github.com/rust-lang/crates.io-index"
1125+checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
1126+dependencies = [
1127+ "digest 0.11.3",
1128+]
1129+
1130+[[package]]
1131+name = "http"
1132+version = "1.4.0"
1133+source = "registry+https://github.com/rust-lang/crates.io-index"
1134+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
1135+dependencies = [
1136+ "bytes",
1137+ "itoa",
1138+]
1139+
1140+[[package]]
1141+name = "http-body"
1142+version = "1.1.0"
1143+source = "registry+https://github.com/rust-lang/crates.io-index"
1144+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
1145+dependencies = [
1146+ "bytes",
1147+ "http",
1148+]
1149+
1150+[[package]]
1151+name = "http-body-util"
1152+version = "0.1.4"
1153+source = "registry+https://github.com/rust-lang/crates.io-index"
1154+checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
1155+dependencies = [
1156+ "bytes",
1157+ "futures-core",
1158+ "http",
1159+ "http-body",
1160+ "pin-project-lite",
1161+]
1162+
1163+[[package]]
1164+name = "httparse"
1165+version = "1.10.1"
1166+source = "registry+https://github.com/rust-lang/crates.io-index"
1167+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
1168+
6441169[[package]]
6451170name = "httpdate"
6461171version = "1.0.3"
6471172source = "registry+https://github.com/rust-lang/crates.io-index"
6481173checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
6491174
1175+[[package]]
1176+name = "hybrid-array"
1177+version = "0.4.13"
1178+source = "registry+https://github.com/rust-lang/crates.io-index"
1179+checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
1180+dependencies = [
1181+ "typenum",
1182+]
1183+
1184+[[package]]
1185+name = "hyper"
1186+version = "1.11.0"
1187+source = "registry+https://github.com/rust-lang/crates.io-index"
1188+checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
1189+dependencies = [
1190+ "atomic-waker",
1191+ "bytes",
1192+ "futures-channel",
1193+ "futures-core",
1194+ "http",
1195+ "http-body",
1196+ "httparse",
1197+ "itoa",
1198+ "pin-project-lite",
1199+ "smallvec",
1200+ "tokio",
1201+ "want",
1202+]
1203+
1204+[[package]]
1205+name = "hyper-rustls"
1206+version = "0.27.9"
1207+source = "registry+https://github.com/rust-lang/crates.io-index"
1208+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
1209+dependencies = [
1210+ "http",
1211+ "hyper",
1212+ "hyper-util",
1213+ "rustls",
1214+ "tokio",
1215+ "tokio-rustls",
1216+ "tower-service",
1217+ "webpki-roots",
1218+]
1219+
1220+[[package]]
1221+name = "hyper-util"
1222+version = "0.1.20"
1223+source = "registry+https://github.com/rust-lang/crates.io-index"
1224+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
1225+dependencies = [
1226+ "base64",
1227+ "bytes",
1228+ "futures-channel",
1229+ "futures-util",
1230+ "http",
1231+ "http-body",
1232+ "hyper",
1233+ "ipnet",
1234+ "libc",
1235+ "percent-encoding",
1236+ "pin-project-lite",
1237+ "socket2",
1238+ "tokio",
1239+ "tower-service",
1240+ "tracing",
1241+]
1242+
6501243[[package]]
6511244name = "icu_collections"
652−version = "2.1.1"
1245+version = "2.2.0"
6531246source = "registry+https://github.com/rust-lang/crates.io-index"
654−checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
1247+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
6551248dependencies = [
6561249 "displaydoc",
6571250 "potential_utf",
1251+ "utf8_iter",
6581252 "yoke",
6591253 "zerofrom",
6601254 "zerovec",
@@ -662,9 +1256,9 @@ dependencies = [
6621256
6631257[[package]]
6641258name = "icu_locale_core"
665−version = "2.1.1"
1259+version = "2.2.0"
6661260source = "registry+https://github.com/rust-lang/crates.io-index"
667−checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
1261+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
6681262dependencies = [
6691263 "displaydoc",
6701264 "litemap",
@@ -675,9 +1269,9 @@ dependencies = [
6751269
6761270[[package]]
6771271name = "icu_normalizer"
678−version = "2.1.1"
1272+version = "2.2.0"
6791273source = "registry+https://github.com/rust-lang/crates.io-index"
680−checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
1274+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
6811275dependencies = [
6821276 "icu_collections",
6831277 "icu_normalizer_data",
@@ -689,15 +1283,15 @@ dependencies = [
6891283
6901284[[package]]
6911285name = "icu_normalizer_data"
692−version = "2.1.1"
1286+version = "2.2.0"
6931287source = "registry+https://github.com/rust-lang/crates.io-index"
694−checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
1288+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
6951289
6961290[[package]]
6971291name = "icu_properties"
698−version = "2.1.2"
1292+version = "2.2.0"
6991293source = "registry+https://github.com/rust-lang/crates.io-index"
700−checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
1294+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
7011295dependencies = [
7021296 "icu_collections",
7031297 "icu_locale_core",
@@ -709,15 +1303,15 @@ dependencies = [
7091303
7101304[[package]]
7111305name = "icu_properties_data"
712−version = "2.1.2"
1306+version = "2.2.0"
7131307source = "registry+https://github.com/rust-lang/crates.io-index"
714−checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
1308+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
7151309
7161310[[package]]
7171311name = "icu_provider"
718−version = "2.1.1"
1312+version = "2.2.0"
7191313source = "registry+https://github.com/rust-lang/crates.io-index"
720−checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
1314+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
7211315dependencies = [
7221316 "displaydoc",
7231317 "icu_locale_core",
@@ -728,6 +1322,12 @@ dependencies = [
7281322 "zerovec",
7291323]
7301324
1325+[[package]]
1326+name = "id-arena"
1327+version = "2.3.0"
1328+source = "registry+https://github.com/rust-lang/crates.io-index"
1329+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
1330+
7311331[[package]]
7321332name = "ident_case"
7331333version = "1.0.1"
@@ -747,9 +1347,9 @@ dependencies = [
7471347
7481348[[package]]
7491349name = "idna_adapter"
750−version = "1.2.1"
1350+version = "1.2.2"
7511351source = "registry+https://github.com/rust-lang/crates.io-index"
752−checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
1352+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
7531353dependencies = [
7541354 "icu_normalizer",
7551355 "icu_properties",
@@ -757,12 +1357,14 @@ dependencies = [
7571357
7581358[[package]]
7591359name = "indexmap"
760−version = "2.13.0"
1360+version = "2.14.0"
7611361source = "registry+https://github.com/rust-lang/crates.io-index"
762−checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
1362+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
7631363dependencies = [
7641364 "equivalent",
765− "hashbrown 0.16.1",
1365+ "hashbrown 0.17.0",
1366+ "serde",
1367+ "serde_core",
7661368]
7671369
7681370[[package]]
@@ -783,6 +1385,15 @@ dependencies = [
7831385 "generic-array",
7841386]
7851387
1388+[[package]]
1389+name = "inout"
1390+version = "0.2.2"
1391+source = "registry+https://github.com/rust-lang/crates.io-index"
1392+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
1393+dependencies = [
1394+ "hybrid-array",
1395+]
1396+
7861397[[package]]
7871398name = "instability"
7881399version = "0.3.12"
@@ -793,23 +1404,62 @@ dependencies = [
7931404 "indoc",
7941405 "proc-macro2",
7951406 "quote",
796− "syn",
1407+ "syn 2.0.117",
7971408]
7981409
1410+[[package]]
1411+name = "ipnet"
1412+version = "2.12.0"
1413+source = "registry+https://github.com/rust-lang/crates.io-index"
1414+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
1415+
7991416[[package]]
8001417name = "itertools"
801−version = "0.13.0"
1418+version = "0.14.0"
8021419source = "registry+https://github.com/rust-lang/crates.io-index"
803−checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
1420+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
8041421dependencies = [
8051422 "either",
8061423]
8071424
8081425[[package]]
8091426name = "itoa"
810−version = "1.0.17"
1427+version = "1.0.18"
1428+source = "registry+https://github.com/rust-lang/crates.io-index"
1429+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
1430+
1431+[[package]]
1432+name = "jobserver"
1433+version = "0.1.35"
1434+source = "registry+https://github.com/rust-lang/crates.io-index"
1435+checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
1436+dependencies = [
1437+ "getrandom 0.4.2",
1438+ "libc",
1439+]
1440+
1441+[[package]]
1442+name = "js-sys"
1443+version = "0.3.95"
1444+source = "registry+https://github.com/rust-lang/crates.io-index"
1445+checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
1446+dependencies = [
1447+ "cfg-if",
1448+ "futures-util",
1449+ "once_cell",
1450+ "wasm-bindgen",
1451+]
1452+
1453+[[package]]
1454+name = "kasuari"
1455+version = "0.4.12"
8111456source = "registry+https://github.com/rust-lang/crates.io-index"
812−checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
1457+checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899"
1458+dependencies = [
1459+ "hashbrown 0.16.1",
1460+ "portable-atomic",
1461+ "thiserror 2.0.18",
1462+]
8131463
8141464[[package]]
8151465name = "knot"
@@ -818,24 +1468,62 @@ dependencies = [
8181468 "ariadne",
8191469]
8201470
1471+[[package]]
1472+name = "knot-compile-rt"
1473+version = "0.1.0"
1474+dependencies = [
1475+ "knot-jit",
1476+ "knot-runtime",
1477+ "libc",
1478+]
1479+
8211480[[package]]
8221481name = "knot-compiler"
8231482version = "0.1.0"
8241483dependencies = [
1484+ "blake3",
8251485 "cranelift-codegen",
8261486 "cranelift-frontend",
1487+ "cranelift-jit",
8271488 "cranelift-module",
8281489 "cranelift-native",
8291490 "cranelift-object",
1491+ "gimli",
1492+ "indexmap",
8301493 "knot",
1494+ "knot-descriptor",
8311495 "knot-runtime",
1496+ "libc",
1497+ "object",
8321498 "target-lexicon",
1499+ "z3",
1500+]
1501+
1502+[[package]]
1503+name = "knot-descriptor"
1504+version = "0.1.0"
1505+
1506+[[package]]
1507+name = "knot-jit"
1508+version = "0.1.0"
1509+dependencies = [
1510+ "cranelift-codegen",
1511+ "cranelift-frontend",
1512+ "cranelift-jit",
1513+ "cranelift-module",
1514+ "cranelift-native",
1515+ "knot",
1516+ "knot-compile-rt",
1517+ "knot-compiler",
1518+ "knot-runtime",
1519+ "libc",
8331520]
8341521
8351522[[package]]
8361523name = "knot-lsp"
8371524version = "0.1.0"
8381525dependencies = [
1526+ "crossbeam-channel",
8391527 "knot",
8401528 "knot-compiler",
8411529 "lsp-server",
@@ -848,49 +1536,96 @@ dependencies = [
8481536name = "knot-runtime"
8491537version = "0.1.0"
8501538dependencies = [
1539+ "blake3",
8511540 "chacha20poly1305",
8521541 "crossterm",
1542+ "dashmap",
8531543 "ed25519-dalek",
854− "getrandom 0.3.4",
855− "num-bigint",
856− "num-integer",
857− "num-traits",
1544+ "getrandom 0.4.2",
1545+ "knot-descriptor",
1546+ "libc",
8581547 "ratatui",
8591548 "rusqlite",
8601549 "serde_json",
8611550 "tiny_http",
1551+ "unicode-width",
8621552 "ureq",
8631553 "x25519-dalek",
8641554]
8651555
1556+[[package]]
1557+name = "lab"
1558+version = "0.11.0"
1559+source = "registry+https://github.com/rust-lang/crates.io-index"
1560+checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
1561+
1562+[[package]]
1563+name = "lazy_static"
1564+version = "1.5.0"
1565+source = "registry+https://github.com/rust-lang/crates.io-index"
1566+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
1567+
1568+[[package]]
1569+name = "leb128fmt"
1570+version = "0.1.0"
1571+source = "registry+https://github.com/rust-lang/crates.io-index"
1572+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
1573+
1574+[[package]]
1575+name = "libbz2-rs-sys"
1576+version = "0.2.5"
1577+source = "registry+https://github.com/rust-lang/crates.io-index"
1578+checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c"
1579+
8661580[[package]]
8671581name = "libc"
868−version = "0.2.183"
1582+version = "0.2.185"
8691583source = "registry+https://github.com/rust-lang/crates.io-index"
870−checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
1584+checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
1585+
1586+[[package]]
1587+name = "libm"
1588+version = "0.2.16"
1589+source = "registry+https://github.com/rust-lang/crates.io-index"
1590+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
8711591
8721592[[package]]
8731593name = "libsqlite3-sys"
874−version = "0.32.0"
1594+version = "0.37.0"
8751595source = "registry+https://github.com/rust-lang/crates.io-index"
876−checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7"
1596+checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
8771597dependencies = [
8781598 "cc",
8791599 "pkg-config",
8801600 "vcpkg",
8811601]
8821602
1603+[[package]]
1604+name = "line-clipping"
1605+version = "0.3.7"
1606+source = "registry+https://github.com/rust-lang/crates.io-index"
1607+checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8"
1608+dependencies = [
1609+ "bitflags 2.11.1",
1610+]
1611+
8831612[[package]]
8841613name = "linux-raw-sys"
885−version = "0.4.15"
1614+version = "0.12.1"
8861615source = "registry+https://github.com/rust-lang/crates.io-index"
887−checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
1616+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
8881617
8891618[[package]]
8901619name = "litemap"
891−version = "0.8.1"
1620+version = "0.8.2"
1621+source = "registry+https://github.com/rust-lang/crates.io-index"
1622+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
1623+
1624+[[package]]
1625+name = "litrs"
1626+version = "1.0.0"
8921627source = "registry+https://github.com/rust-lang/crates.io-index"
893−checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
1628+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
8941629
8951630[[package]]
8961631name = "lock_api"
@@ -909,13 +1644,19 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
9091644
9101645[[package]]
9111646name = "lru"
912−version = "0.12.5"
1647+version = "0.16.4"
9131648source = "registry+https://github.com/rust-lang/crates.io-index"
914−checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
1649+checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
9151650dependencies = [
916− "hashbrown 0.15.5",
1651+ "hashbrown 0.16.1",
9171652]
9181653
1654+[[package]]
1655+name = "lru-slab"
1656+version = "0.1.2"
1657+source = "registry+https://github.com/rust-lang/crates.io-index"
1658+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
1659+
9191660[[package]]
9201661name = "lsp-server"
9211662version = "0.7.9"
@@ -943,50 +1684,120 @@ dependencies = [
9431684]
9441685
9451686[[package]]
946−name = "memchr"
947−version = "2.8.0"
1687+name = "lzma-rust2"
1688+version = "0.16.5"
9481689source = "registry+https://github.com/rust-lang/crates.io-index"
949−checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
1690+checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b"
1691+dependencies = [
1692+ "sha2 0.11.0",
1693+]
9501694
9511695[[package]]
952−name = "miniz_oxide"
953−version = "0.8.9"
1696+name = "mac_address"
1697+version = "1.1.8"
9541698source = "registry+https://github.com/rust-lang/crates.io-index"
955−checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1699+checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
9561700dependencies = [
957− "adler2",
958− "simd-adler32",
1701+ "nix",
1702+ "winapi",
9591703]
9601704
9611705[[package]]
962−name = "mio"
963−version = "1.1.1"
1706+name = "mach2"
1707+version = "0.4.3"
9641708source = "registry+https://github.com/rust-lang/crates.io-index"
965−checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
1709+checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
9661710dependencies = [
9671711 "libc",
968− "log",
969− "wasi",
970− "windows-sys 0.61.2",
9711712]
9721713
9731714[[package]]
974−name = "num-bigint"
975−version = "0.4.6"
1715+name = "memchr"
1716+version = "2.8.0"
9761717source = "registry+https://github.com/rust-lang/crates.io-index"
977−checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
1718+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
1719+
1720+[[package]]
1721+name = "memmem"
1722+version = "0.1.1"
1723+source = "registry+https://github.com/rust-lang/crates.io-index"
1724+checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
1725+
1726+[[package]]
1727+name = "memoffset"
1728+version = "0.9.1"
1729+source = "registry+https://github.com/rust-lang/crates.io-index"
1730+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
9781731dependencies = [
979− "num-integer",
980− "num-traits",
1732+ "autocfg",
1733+]
1734+
1735+[[package]]
1736+name = "minimal-lexical"
1737+version = "0.2.1"
1738+source = "registry+https://github.com/rust-lang/crates.io-index"
1739+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
1740+
1741+[[package]]
1742+name = "miniz_oxide"
1743+version = "0.8.9"
1744+source = "registry+https://github.com/rust-lang/crates.io-index"
1745+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1746+dependencies = [
1747+ "adler2",
1748+ "simd-adler32",
9811749]
9821750
9831751[[package]]
984−name = "num-integer"
985−version = "0.1.46"
1752+name = "mio"
1753+version = "1.2.0"
9861754source = "registry+https://github.com/rust-lang/crates.io-index"
987−checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
1755+checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
9881756dependencies = [
989− "num-traits",
1757+ "libc",
1758+ "log",
1759+ "wasi",
1760+ "windows-sys 0.61.2",
1761+]
1762+
1763+[[package]]
1764+name = "nix"
1765+version = "0.29.0"
1766+source = "registry+https://github.com/rust-lang/crates.io-index"
1767+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
1768+dependencies = [
1769+ "bitflags 2.11.1",
1770+ "cfg-if",
1771+ "cfg_aliases",
1772+ "libc",
1773+ "memoffset",
1774+]
1775+
1776+[[package]]
1777+name = "nom"
1778+version = "7.1.3"
1779+source = "registry+https://github.com/rust-lang/crates.io-index"
1780+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
1781+dependencies = [
1782+ "memchr",
1783+ "minimal-lexical",
1784+]
1785+
1786+[[package]]
1787+name = "num-conv"
1788+version = "0.2.1"
1789+source = "registry+https://github.com/rust-lang/crates.io-index"
1790+checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
1791+
1792+[[package]]
1793+name = "num-derive"
1794+version = "0.4.2"
1795+source = "registry+https://github.com/rust-lang/crates.io-index"
1796+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
1797+dependencies = [
1798+ "proc-macro2",
1799+ "quote",
1800+ "syn 2.0.117",
9901801]
9911802
9921803[[package]]
@@ -998,14 +1809,23 @@ dependencies = [
9981809 "autocfg",
9991810]
10001811
1812+[[package]]
1813+name = "num_threads"
1814+version = "0.1.7"
1815+source = "registry+https://github.com/rust-lang/crates.io-index"
1816+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
1817+dependencies = [
1818+ "libc",
1819+]
1820+
10011821[[package]]
10021822name = "object"
1003−version = "0.36.7"
1823+version = "0.39.1"
10041824source = "registry+https://github.com/rust-lang/crates.io-index"
1005−checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
1825+checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b"
10061826dependencies = [
10071827 "crc32fast",
1008− "hashbrown 0.15.5",
1828+ "hashbrown 0.17.0",
10091829 "indexmap",
10101830 "memchr",
10111831]
@@ -1022,6 +1842,15 @@ version = "0.3.1"
10221842source = "registry+https://github.com/rust-lang/crates.io-index"
10231843checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
10241844
1845+[[package]]
1846+name = "ordered-float"
1847+version = "4.6.0"
1848+source = "registry+https://github.com/rust-lang/crates.io-index"
1849+checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
1850+dependencies = [
1851+ "num-traits",
1852+]
1853+
10251854[[package]]
10261855name = "parking_lot"
10271856version = "0.12.5"
@@ -1046,10 +1875,14 @@ dependencies = [
10461875]
10471876
10481877[[package]]
1049−name = "paste"
1050−version = "1.0.15"
1878+name = "pbkdf2"
1879+version = "0.13.0"
10511880source = "registry+https://github.com/rust-lang/crates.io-index"
1052−checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
1881+checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
1882+dependencies = [
1883+ "digest 0.11.3",
1884+ "hmac",
1885+]
10531886
10541887[[package]]
10551888name = "percent-encoding"
@@ -1058,573 +1891,1547 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
10581891checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
10591892
10601893[[package]]
1061−name = "pkg-config"
1062−version = "0.3.32"
1894+name = "pest"
1895+version = "2.8.6"
10631896source = "registry+https://github.com/rust-lang/crates.io-index"
1064−checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
1897+checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
1898+dependencies = [
1899+ "memchr",
1900+ "ucd-trie",
1901+]
10651902
10661903[[package]]
1067−name = "poly1305"
1068−version = "0.8.0"
1904+name = "pest_derive"
1905+version = "2.8.6"
10691906source = "registry+https://github.com/rust-lang/crates.io-index"
1070−checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
1907+checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
10711908dependencies = [
1072− "cpufeatures",
1073− "opaque-debug",
1074− "universal-hash",
1909+ "pest",
1910+ "pest_generator",
10751911]
10761912
10771913[[package]]
1078−name = "potential_utf"
1079−version = "0.1.4"
1914+name = "pest_generator"
1915+version = "2.8.6"
10801916source = "registry+https://github.com/rust-lang/crates.io-index"
1081−checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
1917+checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
10821918dependencies = [
1083− "zerovec",
1919+ "pest",
1920+ "pest_meta",
1921+ "proc-macro2",
1922+ "quote",
1923+ "syn 2.0.117",
10841924]
10851925
10861926[[package]]
1087−name = "proc-macro2"
1088−version = "1.0.106"
1927+name = "pest_meta"
1928+version = "2.8.6"
10891929source = "registry+https://github.com/rust-lang/crates.io-index"
1090−checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
1930+checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
10911931dependencies = [
1092− "unicode-ident",
1932+ "pest",
1933+ "sha2 0.10.9",
10931934]
10941935
10951936[[package]]
1096−name = "quote"
1097−version = "1.0.45"
1937+name = "phf"
1938+version = "0.11.3"
10981939source = "registry+https://github.com/rust-lang/crates.io-index"
1099−checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
1940+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
11001941dependencies = [
1101− "proc-macro2",
1942+ "phf_macros",
1943+ "phf_shared",
11021944]
11031945
11041946[[package]]
1105−name = "r-efi"
1106−version = "5.3.0"
1947+name = "phf_codegen"
1948+version = "0.11.3"
11071949source = "registry+https://github.com/rust-lang/crates.io-index"
1108−checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
1950+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
1951+dependencies = [
1952+ "phf_generator",
1953+ "phf_shared",
1954+]
11091955
11101956[[package]]
1111−name = "rand_core"
1112−version = "0.6.4"
1957+name = "phf_generator"
1958+version = "0.11.3"
11131959source = "registry+https://github.com/rust-lang/crates.io-index"
1114−checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
1960+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
11151961dependencies = [
1116− "getrandom 0.2.17",
1962+ "phf_shared",
1963+ "rand 0.8.6",
11171964]
11181965
11191966[[package]]
1120−name = "ratatui"
1121−version = "0.29.0"
1967+name = "phf_macros"
1968+version = "0.11.3"
11221969source = "registry+https://github.com/rust-lang/crates.io-index"
1123−checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
1970+checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
11241971dependencies = [
1125− "bitflags 2.11.0",
1126− "cassowary",
1127− "compact_str",
1128− "crossterm",
1129− "indoc",
1130− "instability",
1131− "itertools",
1132− "lru",
1133− "paste",
1134− "strum",
1135− "unicode-segmentation",
1136− "unicode-truncate",
1137− "unicode-width 0.2.0",
1972+ "phf_generator",
1973+ "phf_shared",
1974+ "proc-macro2",
1975+ "quote",
1976+ "syn 2.0.117",
11381977]
11391978
11401979[[package]]
1141−name = "redox_syscall"
1142−version = "0.5.18"
1980+name = "phf_shared"
1981+version = "0.11.3"
11431982source = "registry+https://github.com/rust-lang/crates.io-index"
1144−checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
1983+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
11451984dependencies = [
1146− "bitflags 2.11.0",
1985+ "siphasher",
11471986]
11481987
11491988[[package]]
1150−name = "regalloc2"
1151−version = "0.11.2"
1989+name = "pin-project-lite"
1990+version = "0.2.17"
11521991source = "registry+https://github.com/rust-lang/crates.io-index"
1153−checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a"
1154−dependencies = [
1155− "allocator-api2",
1156− "bumpalo",
1157− "hashbrown 0.15.5",
1158− "log",
1159− "rustc-hash",
1160− "smallvec",
1161−]
1992+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
11621993
11631994[[package]]
1164−name = "ring"
1165−version = "0.17.14"
1995+name = "pkg-config"
1996+version = "0.3.33"
11661997source = "registry+https://github.com/rust-lang/crates.io-index"
1167−checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
1168−dependencies = [
1169− "cc",
1170− "cfg-if",
1171− "getrandom 0.2.17",
1172− "libc",
1173− "untrusted",
1174− "windows-sys 0.52.0",
1175−]
1998+checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
11761999
11772000[[package]]
1178−name = "rusqlite"
1179−version = "0.34.0"
2001+name = "poly1305"
2002+version = "0.8.0"
11802003source = "registry+https://github.com/rust-lang/crates.io-index"
1181−checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143"
2004+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
11822005dependencies = [
1183− "bitflags 2.11.0",
1184− "fallible-iterator",
1185− "fallible-streaming-iterator",
1186− "hashlink",
1187− "libsqlite3-sys",
1188− "smallvec",
2006+ "cpufeatures 0.2.17",
2007+ "opaque-debug",
2008+ "universal-hash",
11892009]
11902010
11912011[[package]]
1192−name = "rustc-hash"
1193−version = "2.1.1"
2012+name = "portable-atomic"
2013+version = "1.13.1"
11942014source = "registry+https://github.com/rust-lang/crates.io-index"
1195−checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
2015+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
11962016
11972017[[package]]
1198−name = "rustc_version"
1199−version = "0.4.1"
2018+name = "potential_utf"
2019+version = "0.1.5"
12002020source = "registry+https://github.com/rust-lang/crates.io-index"
1201−checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
2021+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
12022022dependencies = [
1203− "semver",
2023+ "zerovec",
12042024]
12052025
12062026[[package]]
1207−name = "rustix"
1208−version = "0.38.44"
2027+name = "powerfmt"
2028+version = "0.2.0"
2029+source = "registry+https://github.com/rust-lang/crates.io-index"
2030+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
2031+
2032+[[package]]
2033+name = "ppmd-rust"
2034+version = "1.4.0"
12092035source = "registry+https://github.com/rust-lang/crates.io-index"
1210−checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
2036+checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
2037+
2038+[[package]]
2039+name = "prettyplease"
2040+version = "0.2.37"
2041+source = "registry+https://github.com/rust-lang/crates.io-index"
2042+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
12112043dependencies = [
1212− "bitflags 2.11.0",
1213− "errno",
1214− "libc",
1215− "linux-raw-sys",
1216− "windows-sys 0.59.0",
2044+ "proc-macro2",
2045+ "syn 2.0.117",
12172046]
12182047
12192048[[package]]
1220−name = "rustls"
1221−version = "0.23.37"
2049+name = "proc-macro2"
2050+version = "1.0.106"
12222051source = "registry+https://github.com/rust-lang/crates.io-index"
1223−checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
2052+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
12242053dependencies = [
1225− "log",
1226− "once_cell",
1227− "ring",
1228− "rustls-pki-types",
1229− "rustls-webpki",
1230− "subtle",
1231− "zeroize",
2054+ "unicode-ident",
12322055]
12332056
12342057[[package]]
1235−name = "rustls-pki-types"
1236−version = "1.14.0"
2058+name = "quinn"
2059+version = "0.11.11"
12372060source = "registry+https://github.com/rust-lang/crates.io-index"
1238−checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
2061+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
12392062dependencies = [
1240− "zeroize",
2063+ "bytes",
2064+ "cfg_aliases",
2065+ "pin-project-lite",
2066+ "quinn-proto",
2067+ "quinn-udp",
2068+ "rustc-hash",
2069+ "rustls",
2070+ "socket2",
2071+ "thiserror 2.0.18",
2072+ "tokio",
2073+ "tracing",
2074+ "web-time",
12412075]
12422076
12432077[[package]]
1244−name = "rustls-webpki"
1245−version = "0.103.9"
2078+name = "quinn-proto"
2079+version = "0.11.16"
12462080source = "registry+https://github.com/rust-lang/crates.io-index"
1247−checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
2081+checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
12482082dependencies = [
2083+ "bytes",
2084+ "getrandom 0.4.2",
2085+ "lru-slab",
2086+ "rand 0.10.2",
2087+ "rand_pcg",
12492088 "ring",
2089+ "rustc-hash",
2090+ "rustls",
12502091 "rustls-pki-types",
1251− "untrusted",
2092+ "slab",
2093+ "thiserror 2.0.18",
2094+ "tinyvec",
2095+ "tracing",
2096+ "web-time",
12522097]
12532098
12542099[[package]]
1255−name = "rustversion"
1256−version = "1.0.22"
2100+name = "quinn-udp"
2101+version = "0.5.15"
12572102source = "registry+https://github.com/rust-lang/crates.io-index"
1258−checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
2103+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
2104+dependencies = [
2105+ "cfg_aliases",
2106+ "libc",
2107+ "once_cell",
2108+ "socket2",
2109+ "tracing",
2110+ "windows-sys 0.61.2",
2111+]
12592112
12602113[[package]]
1261−name = "ryu"
1262−version = "1.0.23"
2114+name = "quote"
2115+version = "1.0.45"
12632116source = "registry+https://github.com/rust-lang/crates.io-index"
1264−checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
2117+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
2118+dependencies = [
2119+ "proc-macro2",
2120+]
12652121
12662122[[package]]
1267−name = "scopeguard"
1268−version = "1.2.0"
2123+name = "r-efi"
2124+version = "5.3.0"
12692125source = "registry+https://github.com/rust-lang/crates.io-index"
1270−checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
2126+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
12712127
12722128[[package]]
1273−name = "semver"
1274−version = "1.0.27"
2129+name = "r-efi"
2130+version = "6.0.0"
12752131source = "registry+https://github.com/rust-lang/crates.io-index"
1276−checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
2132+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
12772133
12782134[[package]]
1279−name = "serde"
1280−version = "1.0.228"
2135+name = "rand"
2136+version = "0.8.6"
12812137source = "registry+https://github.com/rust-lang/crates.io-index"
1282−checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
2138+checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
12832139dependencies = [
1284− "serde_core",
1285− "serde_derive",
2140+ "rand_core 0.6.4",
12862141]
12872142
12882143[[package]]
1289−name = "serde_core"
1290−version = "1.0.228"
2144+name = "rand"
2145+version = "0.10.2"
12912146source = "registry+https://github.com/rust-lang/crates.io-index"
1292−checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
2147+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
12932148dependencies = [
1294− "serde_derive",
2149+ "chacha20 0.10.1",
2150+ "getrandom 0.4.2",
2151+ "rand_core 0.10.1",
12952152]
12962153
12972154[[package]]
1298−name = "serde_derive"
1299−version = "1.0.228"
2155+name = "rand_core"
2156+version = "0.6.4"
13002157source = "registry+https://github.com/rust-lang/crates.io-index"
1301−checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
2158+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
13022159dependencies = [
1303− "proc-macro2",
1304− "quote",
1305− "syn",
2160+ "getrandom 0.2.17",
13062161]
13072162
13082163[[package]]
1309−name = "serde_json"
1310−version = "1.0.149"
2164+name = "rand_core"
2165+version = "0.10.1"
13112166source = "registry+https://github.com/rust-lang/crates.io-index"
1312−checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
1313−dependencies = [
1314− "itoa",
1315− "memchr",
1316− "serde",
1317− "serde_core",
1318− "zmij",
1319−]
2167+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
13202168
13212169[[package]]
1322−name = "serde_repr"
1323−version = "0.1.20"
2170+name = "rand_pcg"
2171+version = "0.10.2"
13242172source = "registry+https://github.com/rust-lang/crates.io-index"
1325−checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
2173+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
13262174dependencies = [
1327− "proc-macro2",
1328− "quote",
1329− "syn",
2175+ "rand_core 0.10.1",
13302176]
13312177
13322178[[package]]
1333−name = "sha2"
1334−version = "0.10.9"
2179+name = "ratatui"
2180+version = "0.30.0"
13352181source = "registry+https://github.com/rust-lang/crates.io-index"
1336−checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
2182+checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc"
2183+dependencies = [
2184+ "instability",
2185+ "ratatui-core",
2186+ "ratatui-crossterm",
2187+ "ratatui-macros",
2188+ "ratatui-termwiz",
2189+ "ratatui-widgets",
2190+]
2191+
2192+[[package]]
2193+name = "ratatui-core"
2194+version = "0.1.0"
2195+source = "registry+https://github.com/rust-lang/crates.io-index"
2196+checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293"
2197+dependencies = [
2198+ "bitflags 2.11.1",
2199+ "compact_str",
2200+ "hashbrown 0.16.1",
2201+ "indoc",
2202+ "itertools",
2203+ "kasuari",
2204+ "lru",
2205+ "strum",
2206+ "thiserror 2.0.18",
2207+ "unicode-segmentation",
2208+ "unicode-truncate",
2209+ "unicode-width",
2210+]
2211+
2212+[[package]]
2213+name = "ratatui-crossterm"
2214+version = "0.1.0"
2215+source = "registry+https://github.com/rust-lang/crates.io-index"
2216+checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3"
13372217dependencies = [
13382218 "cfg-if",
1339− "cpufeatures",
1340− "digest",
2219+ "crossterm",
2220+ "instability",
2221+ "ratatui-core",
13412222]
13422223
13432224[[package]]
1344−name = "shlex"
1345−version = "1.3.0"
2225+name = "ratatui-macros"
2226+version = "0.7.0"
13462227source = "registry+https://github.com/rust-lang/crates.io-index"
1347−checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
2228+checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4"
2229+dependencies = [
2230+ "ratatui-core",
2231+ "ratatui-widgets",
2232+]
13482233
13492234[[package]]
1350−name = "signal-hook"
1351−version = "0.3.18"
2235+name = "ratatui-termwiz"
2236+version = "0.1.0"
13522237source = "registry+https://github.com/rust-lang/crates.io-index"
1353−checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
2238+checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c"
2239+dependencies = [
2240+ "ratatui-core",
2241+ "termwiz",
2242+]
2243+
2244+[[package]]
2245+name = "ratatui-widgets"
2246+version = "0.3.0"
2247+source = "registry+https://github.com/rust-lang/crates.io-index"
2248+checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db"
2249+dependencies = [
2250+ "bitflags 2.11.1",
2251+ "hashbrown 0.16.1",
2252+ "indoc",
2253+ "instability",
2254+ "itertools",
2255+ "line-clipping",
2256+ "ratatui-core",
2257+ "strum",
2258+ "time",
2259+ "unicode-segmentation",
2260+ "unicode-width",
2261+]
2262+
2263+[[package]]
2264+name = "redox_syscall"
2265+version = "0.5.18"
2266+source = "registry+https://github.com/rust-lang/crates.io-index"
2267+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
2268+dependencies = [
2269+ "bitflags 2.11.1",
2270+]
2271+
2272+[[package]]
2273+name = "regalloc2"
2274+version = "0.15.1"
2275+source = "registry+https://github.com/rust-lang/crates.io-index"
2276+checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186"
13542277dependencies = [
2278+ "allocator-api2",
2279+ "bumpalo",
2280+ "hashbrown 0.17.0",
2281+ "log",
2282+ "rustc-hash",
2283+ "smallvec",
2284+]
2285+
2286+[[package]]
2287+name = "regex"
2288+version = "1.12.3"
2289+source = "registry+https://github.com/rust-lang/crates.io-index"
2290+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
2291+dependencies = [
2292+ "aho-corasick",
2293+ "memchr",
2294+ "regex-automata",
2295+ "regex-syntax",
2296+]
2297+
2298+[[package]]
2299+name = "regex-automata"
2300+version = "0.4.14"
2301+source = "registry+https://github.com/rust-lang/crates.io-index"
2302+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
2303+dependencies = [
2304+ "aho-corasick",
2305+ "memchr",
2306+ "regex-syntax",
2307+]
2308+
2309+[[package]]
2310+name = "regex-syntax"
2311+version = "0.8.10"
2312+source = "registry+https://github.com/rust-lang/crates.io-index"
2313+checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
2314+
2315+[[package]]
2316+name = "region"
2317+version = "3.0.2"
2318+source = "registry+https://github.com/rust-lang/crates.io-index"
2319+checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7"
2320+dependencies = [
2321+ "bitflags 1.3.2",
13552322 "libc",
1356− "signal-hook-registry",
2323+ "mach2",
2324+ "windows-sys 0.52.0",
13572325]
13582326
13592327[[package]]
1360−name = "signal-hook-mio"
1361−version = "0.2.5"
2328+name = "reqwest"
2329+version = "0.12.28"
13622330source = "registry+https://github.com/rust-lang/crates.io-index"
1363−checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
2331+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
13642332dependencies = [
2333+ "base64",
2334+ "bytes",
2335+ "futures-channel",
2336+ "futures-core",
2337+ "futures-util",
2338+ "http",
2339+ "http-body",
2340+ "http-body-util",
2341+ "hyper",
2342+ "hyper-rustls",
2343+ "hyper-util",
2344+ "js-sys",
2345+ "log",
2346+ "percent-encoding",
2347+ "pin-project-lite",
2348+ "quinn",
2349+ "rustls",
2350+ "rustls-pki-types",
2351+ "serde",
2352+ "serde_json",
2353+ "serde_urlencoded",
2354+ "sync_wrapper",
2355+ "tokio",
2356+ "tokio-rustls",
2357+ "tower",
2358+ "tower-http",
2359+ "tower-service",
2360+ "url",
2361+ "wasm-bindgen",
2362+ "wasm-bindgen-futures",
2363+ "web-sys",
2364+ "webpki-roots",
2365+]
2366+
2367+[[package]]
2368+name = "ring"
2369+version = "0.17.14"
2370+source = "registry+https://github.com/rust-lang/crates.io-index"
2371+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
2372+dependencies = [
2373+ "cc",
2374+ "cfg-if",
2375+ "getrandom 0.2.17",
13652376 "libc",
1366− "mio",
1367− "signal-hook",
2377+ "untrusted",
2378+ "windows-sys 0.52.0",
13682379]
13692380
13702381[[package]]
1371−name = "signal-hook-registry"
1372−version = "1.4.8"
2382+name = "rsqlite-vfs"
2383+version = "0.1.0"
13732384source = "registry+https://github.com/rust-lang/crates.io-index"
1374−checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
2385+checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d"
2386+dependencies = [
2387+ "hashbrown 0.16.1",
2388+ "thiserror 2.0.18",
2389+]
2390+
2391+[[package]]
2392+name = "rusqlite"
2393+version = "0.39.0"
2394+source = "registry+https://github.com/rust-lang/crates.io-index"
2395+checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e"
2396+dependencies = [
2397+ "bitflags 2.11.1",
2398+ "fallible-iterator",
2399+ "fallible-streaming-iterator",
2400+ "hashlink",
2401+ "libsqlite3-sys",
2402+ "smallvec",
2403+ "sqlite-wasm-rs",
2404+]
2405+
2406+[[package]]
2407+name = "rustc-hash"
2408+version = "2.1.2"
2409+source = "registry+https://github.com/rust-lang/crates.io-index"
2410+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
2411+
2412+[[package]]
2413+name = "rustc_version"
2414+version = "0.4.1"
2415+source = "registry+https://github.com/rust-lang/crates.io-index"
2416+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
2417+dependencies = [
2418+ "semver",
2419+]
2420+
2421+[[package]]
2422+name = "rustix"
2423+version = "1.1.4"
2424+source = "registry+https://github.com/rust-lang/crates.io-index"
2425+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
13752426dependencies = [
2427+ "bitflags 2.11.1",
13762428 "errno",
13772429 "libc",
2430+ "linux-raw-sys",
2431+ "windows-sys 0.61.2",
13782432]
13792433
13802434[[package]]
1381−name = "signature"
1382−version = "2.2.0"
2435+name = "rustls"
2436+version = "0.23.38"
13832437source = "registry+https://github.com/rust-lang/crates.io-index"
1384−checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
2438+checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21"
2439+dependencies = [
2440+ "log",
2441+ "once_cell",
2442+ "ring",
2443+ "rustls-pki-types",
2444+ "rustls-webpki",
2445+ "subtle",
2446+ "zeroize",
2447+]
13852448
13862449[[package]]
1387−name = "simd-adler32"
1388−version = "0.3.8"
2450+name = "rustls-pki-types"
2451+version = "1.14.0"
13892452source = "registry+https://github.com/rust-lang/crates.io-index"
1390−checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
2453+checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
2454+dependencies = [
2455+ "web-time",
2456+ "zeroize",
2457+]
13912458
13922459[[package]]
1393−name = "smallvec"
1394−version = "1.15.1"
2460+name = "rustls-webpki"
2461+version = "0.103.13"
13952462source = "registry+https://github.com/rust-lang/crates.io-index"
1396−checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
2463+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
2464+dependencies = [
2465+ "ring",
2466+ "rustls-pki-types",
2467+ "untrusted",
2468+]
13972469
13982470[[package]]
1399−name = "stable_deref_trait"
1400−version = "1.2.1"
2471+name = "rustversion"
2472+version = "1.0.22"
14012473source = "registry+https://github.com/rust-lang/crates.io-index"
1402−checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
2474+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
14032475
14042476[[package]]
1405−name = "static_assertions"
1406−version = "1.1.0"
2477+name = "ryu"
2478+version = "1.0.23"
14072479source = "registry+https://github.com/rust-lang/crates.io-index"
1408−checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
2480+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
14092481
14102482[[package]]
1411−name = "strsim"
1412−version = "0.11.1"
2483+name = "scopeguard"
2484+version = "1.2.0"
14132485source = "registry+https://github.com/rust-lang/crates.io-index"
1414−checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
2486+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
14152487
14162488[[package]]
1417−name = "strum"
1418−version = "0.26.3"
2489+name = "semver"
2490+version = "1.0.28"
2491+source = "registry+https://github.com/rust-lang/crates.io-index"
2492+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
2493+
2494+[[package]]
2495+name = "serde"
2496+version = "1.0.228"
14192497source = "registry+https://github.com/rust-lang/crates.io-index"
1420−checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
2498+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
14212499dependencies = [
1422− "strum_macros",
2500+ "serde_core",
2501+ "serde_derive",
14232502]
14242503
14252504[[package]]
1426−name = "strum_macros"
1427−version = "0.26.4"
2505+name = "serde_core"
2506+version = "1.0.228"
14282507source = "registry+https://github.com/rust-lang/crates.io-index"
1429−checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
2508+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
2509+dependencies = [
2510+ "serde_derive",
2511+]
2512+
2513+[[package]]
2514+name = "serde_derive"
2515+version = "1.0.228"
2516+source = "registry+https://github.com/rust-lang/crates.io-index"
2517+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
14302518dependencies = [
1431− "heck",
14322519 "proc-macro2",
14332520 "quote",
1434− "rustversion",
1435− "syn",
2521+ "syn 2.0.117",
14362522]
14372523
14382524[[package]]
1439−name = "subtle"
1440−version = "2.6.1"
2525+name = "serde_json"
2526+version = "1.0.149"
14412527source = "registry+https://github.com/rust-lang/crates.io-index"
1442−checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
2528+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
2529+dependencies = [
2530+ "itoa",
2531+ "memchr",
2532+ "serde",
2533+ "serde_core",
2534+ "zmij",
2535+]
2536+
2537+[[package]]
2538+name = "serde_repr"
2539+version = "0.1.20"
2540+source = "registry+https://github.com/rust-lang/crates.io-index"
2541+checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
2542+dependencies = [
2543+ "proc-macro2",
2544+ "quote",
2545+ "syn 2.0.117",
2546+]
2547+
2548+[[package]]
2549+name = "serde_urlencoded"
2550+version = "0.7.1"
2551+source = "registry+https://github.com/rust-lang/crates.io-index"
2552+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
2553+dependencies = [
2554+ "form_urlencoded",
2555+ "itoa",
2556+ "ryu",
2557+ "serde",
2558+]
2559+
2560+[[package]]
2561+name = "sha1"
2562+version = "0.11.0"
2563+source = "registry+https://github.com/rust-lang/crates.io-index"
2564+checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
2565+dependencies = [
2566+ "cfg-if",
2567+ "cpufeatures 0.3.0",
2568+ "digest 0.11.3",
2569+]
2570+
2571+[[package]]
2572+name = "sha2"
2573+version = "0.10.9"
2574+source = "registry+https://github.com/rust-lang/crates.io-index"
2575+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
2576+dependencies = [
2577+ "cfg-if",
2578+ "cpufeatures 0.2.17",
2579+ "digest 0.10.7",
2580+]
2581+
2582+[[package]]
2583+name = "sha2"
2584+version = "0.11.0"
2585+source = "registry+https://github.com/rust-lang/crates.io-index"
2586+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
2587+dependencies = [
2588+ "cfg-if",
2589+ "cpufeatures 0.3.0",
2590+ "digest 0.11.3",
2591+]
2592+
2593+[[package]]
2594+name = "shlex"
2595+version = "1.3.0"
2596+source = "registry+https://github.com/rust-lang/crates.io-index"
2597+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
2598+
2599+[[package]]
2600+name = "signal-hook"
2601+version = "0.3.18"
2602+source = "registry+https://github.com/rust-lang/crates.io-index"
2603+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
2604+dependencies = [
2605+ "libc",
2606+ "signal-hook-registry",
2607+]
2608+
2609+[[package]]
2610+name = "signal-hook-mio"
2611+version = "0.2.5"
2612+source = "registry+https://github.com/rust-lang/crates.io-index"
2613+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
2614+dependencies = [
2615+ "libc",
2616+ "mio",
2617+ "signal-hook",
2618+]
2619+
2620+[[package]]
2621+name = "signal-hook-registry"
2622+version = "1.4.8"
2623+source = "registry+https://github.com/rust-lang/crates.io-index"
2624+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
2625+dependencies = [
2626+ "errno",
2627+ "libc",
2628+]
2629+
2630+[[package]]
2631+name = "signature"
2632+version = "2.2.0"
2633+source = "registry+https://github.com/rust-lang/crates.io-index"
2634+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
2635+
2636+[[package]]
2637+name = "simd-adler32"
2638+version = "0.3.9"
2639+source = "registry+https://github.com/rust-lang/crates.io-index"
2640+checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
2641+
2642+[[package]]
2643+name = "siphasher"
2644+version = "1.0.2"
2645+source = "registry+https://github.com/rust-lang/crates.io-index"
2646+checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
2647+
2648+[[package]]
2649+name = "slab"
2650+version = "0.4.12"
2651+source = "registry+https://github.com/rust-lang/crates.io-index"
2652+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
2653+
2654+[[package]]
2655+name = "smallvec"
2656+version = "1.15.1"
2657+source = "registry+https://github.com/rust-lang/crates.io-index"
2658+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
2659+
2660+[[package]]
2661+name = "socket2"
2662+version = "0.6.5"
2663+source = "registry+https://github.com/rust-lang/crates.io-index"
2664+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
2665+dependencies = [
2666+ "libc",
2667+ "windows-sys 0.61.2",
2668+]
2669+
2670+[[package]]
2671+name = "sqlite-wasm-rs"
2672+version = "0.5.3"
2673+source = "registry+https://github.com/rust-lang/crates.io-index"
2674+checksum = "1b2c760607300407ddeaee518acf28c795661b7108c75421303dbefb237d3a36"
2675+dependencies = [
2676+ "cc",
2677+ "js-sys",
2678+ "rsqlite-vfs",
2679+ "wasm-bindgen",
2680+]
2681+
2682+[[package]]
2683+name = "stable_deref_trait"
2684+version = "1.2.1"
2685+source = "registry+https://github.com/rust-lang/crates.io-index"
2686+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
2687+
2688+[[package]]
2689+name = "static_assertions"
2690+version = "1.1.0"
2691+source = "registry+https://github.com/rust-lang/crates.io-index"
2692+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
2693+
2694+[[package]]
2695+name = "strsim"
2696+version = "0.11.1"
2697+source = "registry+https://github.com/rust-lang/crates.io-index"
2698+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
2699+
2700+[[package]]
2701+name = "strum"
2702+version = "0.27.2"
2703+source = "registry+https://github.com/rust-lang/crates.io-index"
2704+checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
2705+dependencies = [
2706+ "strum_macros",
2707+]
2708+
2709+[[package]]
2710+name = "strum_macros"
2711+version = "0.27.2"
2712+source = "registry+https://github.com/rust-lang/crates.io-index"
2713+checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
2714+dependencies = [
2715+ "heck",
2716+ "proc-macro2",
2717+ "quote",
2718+ "syn 2.0.117",
2719+]
2720+
2721+[[package]]
2722+name = "subtle"
2723+version = "2.6.1"
2724+source = "registry+https://github.com/rust-lang/crates.io-index"
2725+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
2726+
2727+[[package]]
2728+name = "syn"
2729+version = "1.0.109"
2730+source = "registry+https://github.com/rust-lang/crates.io-index"
2731+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
2732+dependencies = [
2733+ "proc-macro2",
2734+ "quote",
2735+ "unicode-ident",
2736+]
2737+
2738+[[package]]
2739+name = "syn"
2740+version = "2.0.117"
2741+source = "registry+https://github.com/rust-lang/crates.io-index"
2742+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
2743+dependencies = [
2744+ "proc-macro2",
2745+ "quote",
2746+ "unicode-ident",
2747+]
2748+
2749+[[package]]
2750+name = "sync_wrapper"
2751+version = "1.0.2"
2752+source = "registry+https://github.com/rust-lang/crates.io-index"
2753+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
2754+dependencies = [
2755+ "futures-core",
2756+]
2757+
2758+[[package]]
2759+name = "synstructure"
2760+version = "0.13.2"
2761+source = "registry+https://github.com/rust-lang/crates.io-index"
2762+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
2763+dependencies = [
2764+ "proc-macro2",
2765+ "quote",
2766+ "syn 2.0.117",
2767+]
2768+
2769+[[package]]
2770+name = "target-lexicon"
2771+version = "0.13.5"
2772+source = "registry+https://github.com/rust-lang/crates.io-index"
2773+checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
2774+
2775+[[package]]
2776+name = "terminfo"
2777+version = "0.9.0"
2778+source = "registry+https://github.com/rust-lang/crates.io-index"
2779+checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
2780+dependencies = [
2781+ "fnv",
2782+ "nom",
2783+ "phf",
2784+ "phf_codegen",
2785+]
2786+
2787+[[package]]
2788+name = "termios"
2789+version = "0.3.3"
2790+source = "registry+https://github.com/rust-lang/crates.io-index"
2791+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
2792+dependencies = [
2793+ "libc",
2794+]
2795+
2796+[[package]]
2797+name = "termwiz"
2798+version = "0.23.3"
2799+source = "registry+https://github.com/rust-lang/crates.io-index"
2800+checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
2801+dependencies = [
2802+ "anyhow",
2803+ "base64",
2804+ "bitflags 2.11.1",
2805+ "fancy-regex",
2806+ "filedescriptor",
2807+ "finl_unicode",
2808+ "fixedbitset",
2809+ "hex",
2810+ "lazy_static",
2811+ "libc",
2812+ "log",
2813+ "memmem",
2814+ "nix",
2815+ "num-derive",
2816+ "num-traits",
2817+ "ordered-float",
2818+ "pest",
2819+ "pest_derive",
2820+ "phf",
2821+ "sha2 0.10.9",
2822+ "signal-hook",
2823+ "siphasher",
2824+ "terminfo",
2825+ "termios",
2826+ "thiserror 1.0.69",
2827+ "ucd-trie",
2828+ "unicode-segmentation",
2829+ "vtparse",
2830+ "wezterm-bidi",
2831+ "wezterm-blob-leases",
2832+ "wezterm-color-types",
2833+ "wezterm-dynamic",
2834+ "wezterm-input-types",
2835+ "winapi",
2836+]
2837+
2838+[[package]]
2839+name = "thiserror"
2840+version = "1.0.69"
2841+source = "registry+https://github.com/rust-lang/crates.io-index"
2842+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
2843+dependencies = [
2844+ "thiserror-impl 1.0.69",
2845+]
2846+
2847+[[package]]
2848+name = "thiserror"
2849+version = "2.0.18"
2850+source = "registry+https://github.com/rust-lang/crates.io-index"
2851+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
2852+dependencies = [
2853+ "thiserror-impl 2.0.18",
2854+]
2855+
2856+[[package]]
2857+name = "thiserror-impl"
2858+version = "1.0.69"
2859+source = "registry+https://github.com/rust-lang/crates.io-index"
2860+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
2861+dependencies = [
2862+ "proc-macro2",
2863+ "quote",
2864+ "syn 2.0.117",
2865+]
2866+
2867+[[package]]
2868+name = "thiserror-impl"
2869+version = "2.0.18"
2870+source = "registry+https://github.com/rust-lang/crates.io-index"
2871+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
2872+dependencies = [
2873+ "proc-macro2",
2874+ "quote",
2875+ "syn 2.0.117",
2876+]
2877+
2878+[[package]]
2879+name = "time"
2880+version = "0.3.47"
2881+source = "registry+https://github.com/rust-lang/crates.io-index"
2882+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
2883+dependencies = [
2884+ "deranged",
2885+ "js-sys",
2886+ "libc",
2887+ "num-conv",
2888+ "num_threads",
2889+ "powerfmt",
2890+ "serde_core",
2891+ "time-core",
2892+]
2893+
2894+[[package]]
2895+name = "time-core"
2896+version = "0.1.8"
2897+source = "registry+https://github.com/rust-lang/crates.io-index"
2898+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
2899+
2900+[[package]]
2901+name = "tiny_http"
2902+version = "0.12.0"
2903+source = "registry+https://github.com/rust-lang/crates.io-index"
2904+checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
2905+dependencies = [
2906+ "ascii",
2907+ "chunked_transfer",
2908+ "httpdate",
2909+ "log",
2910+]
2911+
2912+[[package]]
2913+name = "tinystr"
2914+version = "0.8.3"
2915+source = "registry+https://github.com/rust-lang/crates.io-index"
2916+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
2917+dependencies = [
2918+ "displaydoc",
2919+ "zerovec",
2920+]
2921+
2922+[[package]]
2923+name = "tinyvec"
2924+version = "1.12.0"
2925+source = "registry+https://github.com/rust-lang/crates.io-index"
2926+checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
2927+dependencies = [
2928+ "tinyvec_macros",
2929+]
2930+
2931+[[package]]
2932+name = "tinyvec_macros"
2933+version = "0.1.1"
2934+source = "registry+https://github.com/rust-lang/crates.io-index"
2935+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
2936+
2937+[[package]]
2938+name = "tokio"
2939+version = "1.53.1"
2940+source = "registry+https://github.com/rust-lang/crates.io-index"
2941+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
2942+dependencies = [
2943+ "bytes",
2944+ "libc",
2945+ "mio",
2946+ "pin-project-lite",
2947+ "socket2",
2948+ "windows-sys 0.61.2",
2949+]
2950+
2951+[[package]]
2952+name = "tokio-rustls"
2953+version = "0.26.4"
2954+source = "registry+https://github.com/rust-lang/crates.io-index"
2955+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
2956+dependencies = [
2957+ "rustls",
2958+ "tokio",
2959+]
2960+
2961+[[package]]
2962+name = "tower"
2963+version = "0.5.3"
2964+source = "registry+https://github.com/rust-lang/crates.io-index"
2965+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
2966+dependencies = [
2967+ "futures-core",
2968+ "futures-util",
2969+ "pin-project-lite",
2970+ "sync_wrapper",
2971+ "tokio",
2972+ "tower-layer",
2973+ "tower-service",
2974+]
2975+
2976+[[package]]
2977+name = "tower-http"
2978+version = "0.6.11"
2979+source = "registry+https://github.com/rust-lang/crates.io-index"
2980+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
2981+dependencies = [
2982+ "bitflags 2.11.1",
2983+ "bytes",
2984+ "futures-util",
2985+ "http",
2986+ "http-body",
2987+ "pin-project-lite",
2988+ "tower",
2989+ "tower-layer",
2990+ "tower-service",
2991+ "url",
2992+]
2993+
2994+[[package]]
2995+name = "tower-layer"
2996+version = "0.3.3"
2997+source = "registry+https://github.com/rust-lang/crates.io-index"
2998+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
2999+
3000+[[package]]
3001+name = "tower-service"
3002+version = "0.3.3"
3003+source = "registry+https://github.com/rust-lang/crates.io-index"
3004+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
3005+
3006+[[package]]
3007+name = "tracing"
3008+version = "0.1.44"
3009+source = "registry+https://github.com/rust-lang/crates.io-index"
3010+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
3011+dependencies = [
3012+ "pin-project-lite",
3013+ "tracing-core",
3014+]
3015+
3016+[[package]]
3017+name = "tracing-core"
3018+version = "0.1.36"
3019+source = "registry+https://github.com/rust-lang/crates.io-index"
3020+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
3021+dependencies = [
3022+ "once_cell",
3023+]
3024+
3025+[[package]]
3026+name = "try-lock"
3027+version = "0.2.5"
3028+source = "registry+https://github.com/rust-lang/crates.io-index"
3029+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
3030+
3031+[[package]]
3032+name = "typed-path"
3033+version = "0.12.3"
3034+source = "registry+https://github.com/rust-lang/crates.io-index"
3035+checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
3036+
3037+[[package]]
3038+name = "typenum"
3039+version = "1.20.0"
3040+source = "registry+https://github.com/rust-lang/crates.io-index"
3041+checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
3042+
3043+[[package]]
3044+name = "ucd-trie"
3045+version = "0.1.7"
3046+source = "registry+https://github.com/rust-lang/crates.io-index"
3047+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
3048+
3049+[[package]]
3050+name = "unicode-ident"
3051+version = "1.0.24"
3052+source = "registry+https://github.com/rust-lang/crates.io-index"
3053+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
3054+
3055+[[package]]
3056+name = "unicode-segmentation"
3057+version = "1.13.2"
3058+source = "registry+https://github.com/rust-lang/crates.io-index"
3059+checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
3060+
3061+[[package]]
3062+name = "unicode-truncate"
3063+version = "2.0.1"
3064+source = "registry+https://github.com/rust-lang/crates.io-index"
3065+checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
3066+dependencies = [
3067+ "itertools",
3068+ "unicode-segmentation",
3069+ "unicode-width",
3070+]
3071+
3072+[[package]]
3073+name = "unicode-width"
3074+version = "0.2.2"
3075+source = "registry+https://github.com/rust-lang/crates.io-index"
3076+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
3077+
3078+[[package]]
3079+name = "unicode-xid"
3080+version = "0.2.6"
3081+source = "registry+https://github.com/rust-lang/crates.io-index"
3082+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
3083+
3084+[[package]]
3085+name = "universal-hash"
3086+version = "0.5.1"
3087+source = "registry+https://github.com/rust-lang/crates.io-index"
3088+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
3089+dependencies = [
3090+ "crypto-common 0.1.7",
3091+ "subtle",
3092+]
3093+
3094+[[package]]
3095+name = "untrusted"
3096+version = "0.9.0"
3097+source = "registry+https://github.com/rust-lang/crates.io-index"
3098+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
3099+
3100+[[package]]
3101+name = "ureq"
3102+version = "3.3.0"
3103+source = "registry+https://github.com/rust-lang/crates.io-index"
3104+checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0"
3105+dependencies = [
3106+ "base64",
3107+ "flate2",
3108+ "log",
3109+ "percent-encoding",
3110+ "rustls",
3111+ "rustls-pki-types",
3112+ "ureq-proto",
3113+ "utf8-zero",
3114+ "webpki-roots",
3115+]
3116+
3117+[[package]]
3118+name = "ureq-proto"
3119+version = "0.6.0"
3120+source = "registry+https://github.com/rust-lang/crates.io-index"
3121+checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c"
3122+dependencies = [
3123+ "base64",
3124+ "http",
3125+ "httparse",
3126+ "log",
3127+]
3128+
3129+[[package]]
3130+name = "url"
3131+version = "2.5.8"
3132+source = "registry+https://github.com/rust-lang/crates.io-index"
3133+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
3134+dependencies = [
3135+ "form_urlencoded",
3136+ "idna",
3137+ "percent-encoding",
3138+ "serde",
3139+]
3140+
3141+[[package]]
3142+name = "utf8-zero"
3143+version = "0.8.1"
3144+source = "registry+https://github.com/rust-lang/crates.io-index"
3145+checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
3146+
3147+[[package]]
3148+name = "utf8_iter"
3149+version = "1.0.4"
3150+source = "registry+https://github.com/rust-lang/crates.io-index"
3151+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
3152+
3153+[[package]]
3154+name = "utf8parse"
3155+version = "0.2.2"
3156+source = "registry+https://github.com/rust-lang/crates.io-index"
3157+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
3158+
3159+[[package]]
3160+name = "uuid"
3161+version = "1.23.1"
3162+source = "registry+https://github.com/rust-lang/crates.io-index"
3163+checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
3164+dependencies = [
3165+ "atomic",
3166+ "getrandom 0.4.2",
3167+ "js-sys",
3168+ "wasm-bindgen",
3169+]
3170+
3171+[[package]]
3172+name = "vcpkg"
3173+version = "0.2.15"
3174+source = "registry+https://github.com/rust-lang/crates.io-index"
3175+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
3176+
3177+[[package]]
3178+name = "version_check"
3179+version = "0.9.5"
3180+source = "registry+https://github.com/rust-lang/crates.io-index"
3181+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
3182+
3183+[[package]]
3184+name = "vtparse"
3185+version = "0.6.2"
3186+source = "registry+https://github.com/rust-lang/crates.io-index"
3187+checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0"
3188+dependencies = [
3189+ "utf8parse",
3190+]
3191+
3192+[[package]]
3193+name = "want"
3194+version = "0.3.1"
3195+source = "registry+https://github.com/rust-lang/crates.io-index"
3196+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
3197+dependencies = [
3198+ "try-lock",
3199+]
3200+
3201+[[package]]
3202+name = "wasi"
3203+version = "0.11.1+wasi-snapshot-preview1"
3204+source = "registry+https://github.com/rust-lang/crates.io-index"
3205+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
14433206
14443207[[package]]
1445−name = "syn"
1446−version = "2.0.117"
3208+name = "wasip2"
3209+version = "1.0.3+wasi-0.2.9"
14473210source = "registry+https://github.com/rust-lang/crates.io-index"
1448−checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
3211+checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
14493212dependencies = [
1450− "proc-macro2",
1451− "quote",
1452− "unicode-ident",
3213+ "wit-bindgen 0.57.1",
14533214]
14543215
14553216[[package]]
1456−name = "synstructure"
1457−version = "0.13.2"
3217+name = "wasip3"
3218+version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
14583219source = "registry+https://github.com/rust-lang/crates.io-index"
1459−checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
3220+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
14603221dependencies = [
1461− "proc-macro2",
1462− "quote",
1463− "syn",
3222+ "wit-bindgen 0.51.0",
14643223]
14653224
14663225[[package]]
1467−name = "target-lexicon"
1468−version = "0.13.5"
1469−source = "registry+https://github.com/rust-lang/crates.io-index"
1470−checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
1471−
1472−[[package]]
1473−name = "tiny_http"
1474−version = "0.12.0"
3226+name = "wasm-bindgen"
3227+version = "0.2.118"
14753228source = "registry+https://github.com/rust-lang/crates.io-index"
1476−checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
3229+checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
14773230dependencies = [
1478− "ascii",
1479− "chunked_transfer",
1480− "httpdate",
1481− "log",
3231+ "cfg-if",
3232+ "once_cell",
3233+ "rustversion",
3234+ "wasm-bindgen-macro",
3235+ "wasm-bindgen-shared",
14823236]
14833237
14843238[[package]]
1485−name = "tinystr"
1486−version = "0.8.2"
3239+name = "wasm-bindgen-futures"
3240+version = "0.4.68"
14873241source = "registry+https://github.com/rust-lang/crates.io-index"
1488−checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
3242+checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8"
14893243dependencies = [
1490− "displaydoc",
1491− "zerovec",
3244+ "js-sys",
3245+ "wasm-bindgen",
14923246]
14933247
14943248[[package]]
1495−name = "typenum"
1496−version = "1.19.0"
3249+name = "wasm-bindgen-macro"
3250+version = "0.2.118"
14973251source = "registry+https://github.com/rust-lang/crates.io-index"
1498−checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
3252+checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
3253+dependencies = [
3254+ "quote",
3255+ "wasm-bindgen-macro-support",
3256+]
14993257
15003258[[package]]
1501−name = "unicode-ident"
1502−version = "1.0.24"
3259+name = "wasm-bindgen-macro-support"
3260+version = "0.2.118"
15033261source = "registry+https://github.com/rust-lang/crates.io-index"
1504−checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
3262+checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
3263+dependencies = [
3264+ "bumpalo",
3265+ "proc-macro2",
3266+ "quote",
3267+ "syn 2.0.117",
3268+ "wasm-bindgen-shared",
3269+]
15053270
15063271[[package]]
1507−name = "unicode-segmentation"
1508−version = "1.12.0"
3272+name = "wasm-bindgen-shared"
3273+version = "0.2.118"
15093274source = "registry+https://github.com/rust-lang/crates.io-index"
1510−checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
3275+checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
3276+dependencies = [
3277+ "unicode-ident",
3278+]
15113279
15123280[[package]]
1513−name = "unicode-truncate"
1514−version = "1.1.0"
3281+name = "wasm-encoder"
3282+version = "0.244.0"
15153283source = "registry+https://github.com/rust-lang/crates.io-index"
1516−checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
3284+checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
15173285dependencies = [
1518− "itertools",
1519− "unicode-segmentation",
1520− "unicode-width 0.1.14",
3286+ "leb128fmt",
3287+ "wasmparser",
15213288]
15223289
15233290[[package]]
1524−name = "unicode-width"
1525−version = "0.1.14"
3291+name = "wasm-metadata"
3292+version = "0.244.0"
15263293source = "registry+https://github.com/rust-lang/crates.io-index"
1527−checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
3294+checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
3295+dependencies = [
3296+ "anyhow",
3297+ "indexmap",
3298+ "wasm-encoder",
3299+ "wasmparser",
3300+]
15283301
15293302[[package]]
1530−name = "unicode-width"
1531−version = "0.2.0"
3303+name = "wasmparser"
3304+version = "0.244.0"
15323305source = "registry+https://github.com/rust-lang/crates.io-index"
1533−checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
3306+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
3307+dependencies = [
3308+ "bitflags 2.11.1",
3309+ "hashbrown 0.15.5",
3310+ "indexmap",
3311+ "semver",
3312+]
15343313
15353314[[package]]
1536−name = "universal-hash"
1537−version = "0.5.1"
3315+name = "wasmtime-internal-core"
3316+version = "44.0.0"
15383317source = "registry+https://github.com/rust-lang/crates.io-index"
1539−checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
3318+checksum = "816a61a75275c6be435131fc625a4f5956daf24d9f9f59443e81cbef228929b3"
15403319dependencies = [
1541− "crypto-common",
1542− "subtle",
3320+ "hashbrown 0.16.1",
3321+ "libm",
15433322]
15443323
15453324[[package]]
1546−name = "untrusted"
1547−version = "0.9.0"
3325+name = "wasmtime-internal-jit-icache-coherence"
3326+version = "44.0.0"
15483327source = "registry+https://github.com/rust-lang/crates.io-index"
1549−checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
3328+checksum = "2fd683a94490bf755d016a09697b0955602c50106b1ded97d16983ab2ded9fed"
3329+dependencies = [
3330+ "cfg-if",
3331+ "libc",
3332+ "wasmtime-internal-core",
3333+ "windows-sys 0.61.2",
3334+]
15503335
15513336[[package]]
1552−name = "ureq"
1553−version = "2.12.1"
3337+name = "web-sys"
3338+version = "0.3.95"
15543339source = "registry+https://github.com/rust-lang/crates.io-index"
1555−checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
3340+checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
15563341dependencies = [
1557− "base64",
1558− "flate2",
1559− "log",
1560− "once_cell",
1561− "rustls",
1562− "rustls-pki-types",
1563− "url",
1564− "webpki-roots 0.26.11",
3342+ "js-sys",
3343+ "wasm-bindgen",
15653344]
15663345
15673346[[package]]
1568−name = "url"
1569−version = "2.5.8"
3347+name = "web-time"
3348+version = "1.1.0"
15703349source = "registry+https://github.com/rust-lang/crates.io-index"
1571−checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
3350+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
15723351dependencies = [
1573− "form_urlencoded",
1574− "idna",
1575− "percent-encoding",
1576− "serde",
3352+ "js-sys",
3353+ "wasm-bindgen",
15773354]
15783355
15793356[[package]]
1580−name = "utf8_iter"
1581−version = "1.0.4"
3357+name = "webpki-roots"
3358+version = "1.0.7"
15823359source = "registry+https://github.com/rust-lang/crates.io-index"
1583−checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
3360+checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
3361+dependencies = [
3362+ "rustls-pki-types",
3363+]
15843364
15853365[[package]]
1586−name = "vcpkg"
1587−version = "0.2.15"
3366+name = "wezterm-bidi"
3367+version = "0.2.3"
15883368source = "registry+https://github.com/rust-lang/crates.io-index"
1589−checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
3369+checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec"
3370+dependencies = [
3371+ "log",
3372+ "wezterm-dynamic",
3373+]
15903374
15913375[[package]]
1592−name = "version_check"
1593−version = "0.9.5"
3376+name = "wezterm-blob-leases"
3377+version = "0.1.1"
15943378source = "registry+https://github.com/rust-lang/crates.io-index"
1595−checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
3379+checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7"
3380+dependencies = [
3381+ "getrandom 0.3.4",
3382+ "mac_address",
3383+ "sha2 0.10.9",
3384+ "thiserror 1.0.69",
3385+ "uuid",
3386+]
15963387
15973388[[package]]
1598−name = "wasi"
1599−version = "0.11.1+wasi-snapshot-preview1"
3389+name = "wezterm-color-types"
3390+version = "0.3.0"
16003391source = "registry+https://github.com/rust-lang/crates.io-index"
1601−checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
3392+checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296"
3393+dependencies = [
3394+ "csscolorparser",
3395+ "deltae",
3396+ "lazy_static",
3397+ "wezterm-dynamic",
3398+]
16023399
16033400[[package]]
1604−name = "wasip2"
1605−version = "1.0.2+wasi-0.2.9"
3401+name = "wezterm-dynamic"
3402+version = "0.2.1"
16063403source = "registry+https://github.com/rust-lang/crates.io-index"
1607−checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
3404+checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac"
16083405dependencies = [
1609− "wit-bindgen",
3406+ "log",
3407+ "ordered-float",
3408+ "strsim",
3409+ "thiserror 1.0.69",
3410+ "wezterm-dynamic-derive",
16103411]
16113412
16123413[[package]]
1613−name = "webpki-roots"
1614−version = "0.26.11"
3414+name = "wezterm-dynamic-derive"
3415+version = "0.1.1"
16153416source = "registry+https://github.com/rust-lang/crates.io-index"
1616−checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
3417+checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b"
16173418dependencies = [
1618− "webpki-roots 1.0.6",
3419+ "proc-macro2",
3420+ "quote",
3421+ "syn 1.0.109",
16193422]
16203423
16213424[[package]]
1622−name = "webpki-roots"
1623−version = "1.0.6"
3425+name = "wezterm-input-types"
3426+version = "0.1.0"
16243427source = "registry+https://github.com/rust-lang/crates.io-index"
1625−checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
3428+checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e"
16263429dependencies = [
1627− "rustls-pki-types",
3430+ "bitflags 1.3.2",
3431+ "euclid",
3432+ "lazy_static",
3433+ "serde",
3434+ "wezterm-dynamic",
16283435]
16293436
16303437[[package]]
@@ -1664,15 +3471,6 @@ dependencies = [
16643471 "windows-targets",
16653472]
16663473
1667−[[package]]
1668−name = "windows-sys"
1669−version = "0.59.0"
1670−source = "registry+https://github.com/rust-lang/crates.io-index"
1671−checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
1672−dependencies = [
1673− "windows-targets",
1674−]
1675−
16763474[[package]]
16773475name = "windows-sys"
16783476version = "0.61.2"
@@ -1751,12 +3549,100 @@ name = "wit-bindgen"
17513549version = "0.51.0"
17523550source = "registry+https://github.com/rust-lang/crates.io-index"
17533551checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
3552+dependencies = [
3553+ "wit-bindgen-rust-macro",
3554+]
3555+
3556+[[package]]
3557+name = "wit-bindgen"
3558+version = "0.57.1"
3559+source = "registry+https://github.com/rust-lang/crates.io-index"
3560+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
3561+
3562+[[package]]
3563+name = "wit-bindgen-core"
3564+version = "0.51.0"
3565+source = "registry+https://github.com/rust-lang/crates.io-index"
3566+checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
3567+dependencies = [
3568+ "anyhow",
3569+ "heck",
3570+ "wit-parser",
3571+]
3572+
3573+[[package]]
3574+name = "wit-bindgen-rust"
3575+version = "0.51.0"
3576+source = "registry+https://github.com/rust-lang/crates.io-index"
3577+checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
3578+dependencies = [
3579+ "anyhow",
3580+ "heck",
3581+ "indexmap",
3582+ "prettyplease",
3583+ "syn 2.0.117",
3584+ "wasm-metadata",
3585+ "wit-bindgen-core",
3586+ "wit-component",
3587+]
3588+
3589+[[package]]
3590+name = "wit-bindgen-rust-macro"
3591+version = "0.51.0"
3592+source = "registry+https://github.com/rust-lang/crates.io-index"
3593+checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
3594+dependencies = [
3595+ "anyhow",
3596+ "prettyplease",
3597+ "proc-macro2",
3598+ "quote",
3599+ "syn 2.0.117",
3600+ "wit-bindgen-core",
3601+ "wit-bindgen-rust",
3602+]
3603+
3604+[[package]]
3605+name = "wit-component"
3606+version = "0.244.0"
3607+source = "registry+https://github.com/rust-lang/crates.io-index"
3608+checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
3609+dependencies = [
3610+ "anyhow",
3611+ "bitflags 2.11.1",
3612+ "indexmap",
3613+ "log",
3614+ "serde",
3615+ "serde_derive",
3616+ "serde_json",
3617+ "wasm-encoder",
3618+ "wasm-metadata",
3619+ "wasmparser",
3620+ "wit-parser",
3621+]
3622+
3623+[[package]]
3624+name = "wit-parser"
3625+version = "0.244.0"
3626+source = "registry+https://github.com/rust-lang/crates.io-index"
3627+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
3628+dependencies = [
3629+ "anyhow",
3630+ "id-arena",
3631+ "indexmap",
3632+ "log",
3633+ "semver",
3634+ "serde",
3635+ "serde_derive",
3636+ "serde_json",
3637+ "unicode-xid",
3638+ "wasmparser",
3639+]
17543640
17553641[[package]]
17563642name = "writeable"
1757−version = "0.6.2"
3643+version = "0.6.3"
17583644source = "registry+https://github.com/rust-lang/crates.io-index"
1759−checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
3645+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
17603646
17613647[[package]]
17623648name = "x25519-dalek"
@@ -1765,7 +3651,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
17653651checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
17663652dependencies = [
17673653 "curve25519-dalek",
1768− "rand_core",
3654+ "rand_core 0.6.4",
17693655 "serde",
17703656 "zeroize",
17713657]
@@ -1778,9 +3664,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
17783664
17793665[[package]]
17803666name = "yoke"
1781−version = "0.8.1"
3667+version = "0.8.3"
17823668source = "registry+https://github.com/rust-lang/crates.io-index"
1783−checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
3669+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
17843670dependencies = [
17853671 "stable_deref_trait",
17863672 "yoke-derive",
@@ -1789,34 +3675,56 @@ dependencies = [
17893675
17903676[[package]]
17913677name = "yoke-derive"
1792−version = "0.8.1"
3678+version = "0.8.2"
17933679source = "registry+https://github.com/rust-lang/crates.io-index"
1794−checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
3680+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
17953681dependencies = [
17963682 "proc-macro2",
17973683 "quote",
1798− "syn",
3684+ "syn 2.0.117",
17993685 "synstructure",
18003686]
18013687
3688+[[package]]
3689+name = "z3"
3690+version = "0.20.2"
3691+source = "registry+https://github.com/rust-lang/crates.io-index"
3692+checksum = "80c4de445f5c9e3013703a6b8a40c80b4a64c925f0a19e7d0a23a7a9b70e854d"
3693+dependencies = [
3694+ "log",
3695+ "z3-sys",
3696+]
3697+
3698+[[package]]
3699+name = "z3-sys"
3700+version = "0.11.0"
3701+source = "registry+https://github.com/rust-lang/crates.io-index"
3702+checksum = "c18b0a91a13522d21b3414847667de2b2056a721a3edcb5b6ee6858352d58db4"
3703+dependencies = [
3704+ "pkg-config",
3705+ "reqwest",
3706+ "serde_json",
3707+ "zip",
3708+]
3709+
18023710[[package]]
18033711name = "zerofrom"
1804−version = "0.1.6"
3712+version = "0.1.8"
18053713source = "registry+https://github.com/rust-lang/crates.io-index"
1806−checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
3714+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
18073715dependencies = [
18083716 "zerofrom-derive",
18093717]
18103718
18113719[[package]]
18123720name = "zerofrom-derive"
1813−version = "0.1.6"
3721+version = "0.1.7"
18143722source = "registry+https://github.com/rust-lang/crates.io-index"
1815−checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
3723+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
18163724dependencies = [
18173725 "proc-macro2",
18183726 "quote",
1819− "syn",
3727+ "syn 2.0.117",
18203728 "synstructure",
18213729]
18223730
@@ -1837,14 +3745,14 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
18373745dependencies = [
18383746 "proc-macro2",
18393747 "quote",
1840− "syn",
3748+ "syn 2.0.117",
18413749]
18423750
18433751[[package]]
18443752name = "zerotrie"
1845−version = "0.2.3"
3753+version = "0.2.4"
18463754source = "registry+https://github.com/rust-lang/crates.io-index"
1847−checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
3755+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
18483756dependencies = [
18493757 "displaydoc",
18503758 "yoke",
@@ -1853,9 +3761,9 @@ dependencies = [
18533761
18543762[[package]]
18553763name = "zerovec"
1856−version = "0.11.5"
3764+version = "0.11.6"
18573765source = "registry+https://github.com/rust-lang/crates.io-index"
1858−checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
3766+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
18593767dependencies = [
18603768 "yoke",
18613769 "zerofrom",
@@ -1864,17 +3772,90 @@ dependencies = [
18643772
18653773[[package]]
18663774name = "zerovec-derive"
1867−version = "0.11.2"
3775+version = "0.11.3"
18683776source = "registry+https://github.com/rust-lang/crates.io-index"
1869−checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
3777+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
18703778dependencies = [
18713779 "proc-macro2",
18723780 "quote",
1873− "syn",
3781+ "syn 2.0.117",
3782+]
3783+
3784+[[package]]
3785+name = "zip"
3786+version = "8.6.0"
3787+source = "registry+https://github.com/rust-lang/crates.io-index"
3788+checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
3789+dependencies = [
3790+ "aes",
3791+ "bzip2",
3792+ "constant_time_eq",
3793+ "crc32fast",
3794+ "deflate64",
3795+ "flate2",
3796+ "getrandom 0.4.2",
3797+ "hmac",
3798+ "indexmap",
3799+ "lzma-rust2",
3800+ "memchr",
3801+ "pbkdf2",
3802+ "ppmd-rust",
3803+ "sha1",
3804+ "time",
3805+ "typed-path",
3806+ "zeroize",
3807+ "zopfli",
3808+ "zstd",
18743809]
18753810
3811+[[package]]
3812+name = "zlib-rs"
3813+version = "0.6.6"
3814+source = "registry+https://github.com/rust-lang/crates.io-index"
3815+checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5"
3816+
18763817[[package]]
18773818name = "zmij"
18783819version = "1.0.21"
18793820source = "registry+https://github.com/rust-lang/crates.io-index"
18803821checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
3822+
3823+[[package]]
3824+name = "zopfli"
3825+version = "0.8.3"
3826+source = "registry+https://github.com/rust-lang/crates.io-index"
3827+checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
3828+dependencies = [
3829+ "bumpalo",
3830+ "crc32fast",
3831+ "log",
3832+ "simd-adler32",
3833+]
3834+
3835+[[package]]
3836+name = "zstd"
3837+version = "0.13.3"
3838+source = "registry+https://github.com/rust-lang/crates.io-index"
3839+checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
3840+dependencies = [
3841+ "zstd-safe",
3842+]
3843+
3844+[[package]]
3845+name = "zstd-safe"
3846+version = "7.2.4"
3847+source = "registry+https://github.com/rust-lang/crates.io-index"
3848+checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
3849+dependencies = [
3850+ "zstd-sys",
3851+]
3852+
3853+[[package]]
3854+name = "zstd-sys"
3855+version = "2.0.16+zstd.1.5.7"
3856+source = "registry+https://github.com/rust-lang/crates.io-index"
3857+checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
3858+dependencies = [
3859+ "cc",
3860+ "pkg-config",
3861+]
Cargo.toml+29−15
@@ -6,27 +6,41 @@ resolver = "3"
66version = "0.1.0"
77edition = "2024"
88
9−[profile.release]
10−panic = "abort"
11−
12−[profile.dev]
13−panic = "abort"
9+# panic = "unwind" (the default) is load-bearing: the runtime's catch_unwind
10+# recovery sites (HTTP worker 500 responses, race-loser cancellation, atomic
11+# rollback on handler panic) depend on it. Unwinding across the FFI boundary
12+# into Cranelift-generated code is sound because (a) every runtime function
13+# callable from generated code is `extern "C-unwind"`, and (b) the compiler
14+# emits .eh_frame unwind info for every generated function (knot-compiler's
15+# unwind.rs), so the system unwinder can walk those frames.
1416
1517[workspace.dependencies]
18+libc = "0.2"
1619knot = { path = "crates/knot" }
20+knot-descriptor = { path = "crates/knot-descriptor" }
1721knot-compiler = { path = "crates/knot-compiler" }
1822knot-runtime = { path = "crates/knot-runtime" }
19−cranelift-codegen = "0.116"
20−cranelift-frontend = "0.116"
21−cranelift-module = "0.116"
22−cranelift-object = "0.116"
23−cranelift-native = "0.116"
23+knot-jit = { path = "crates/knot-jit" }
24+knot-compile-rt = { path = "crates/knot-compile-rt" }
25+cranelift-codegen = "0.131"
26+cranelift-frontend = "0.131"
27+cranelift-module = "0.131"
28+cranelift-object = "0.131"
29+cranelift-jit = "0.131"
30+cranelift-native = "0.131"
2431target-lexicon = "0.13"
25−rusqlite = { version = "0.34", features = ["bundled", "collation"] }
32+rusqlite = { version = "0.39", features = ["bundled", "collation"] }
2633tiny_http = "0.12"
27−ureq = "2"
28−num-bigint = "0.4"
29−num-traits = "0.2"
30−num-integer = "0.1"
34+ureq = "3"
3135serde_json = "1"
3236ariadne = "0.6"
37+
38+# Strip DEBUG INFO (not symbols) from release binaries, incl. the released
39+# `knot` compiler. Debug info is the dominant size cost and is useless in a
40+# shipped compiler. Symbols are KEPT so panic backtraces stay readable — the
41+# runtime recovers from panics via catch_unwind (see the panic=unwind note
42+# above), and a stripped-of-symbols compiler would make those backtraces
43+# opaque. This is independent of the `-s` the linker adds to PRODUCED knot
44+# binaries (those need --export-dynamic/.dynsym for JIT; this is the compiler).
45+[profile.release]
46+strip = "debuginfo"
DESIGN.mddeleted−1396
@@ -1,1396 +0,0 @@
1−# Knot Language Design
2−
3−Knot is a functional relational programming language. Relations are the primary data structure, computation is pure and functional, and state is automatically persisted.
4−
5−## Core Principles
6−
7−1. **Relations are the data structure** — not lists, not arrays. `[T]` is a typed set of `T` values.
8−2. **Effects are inferred** — the compiler tracks reads and writes. No annotations needed.
9−3. **ADTs are native to relations** — a `[Shape]` holds circles and rects in one relation. The tag is an implementation detail.
10−4. **No keys** — relations are sets. Identity is structural. The runtime handles indexing.
11−5. **State is visible** — source relations (mutable, persisted) are prefixed with `*`, derived relations (read-only) with `&`. Every reference site shows whether you're touching state. No ORM, no SQL.
12−
13−## Data Model
14−
15−### Relations
16−
17−A relation is a typed set of values. Duplicate values cannot exist — it's a set.
18−
19−```knot
20−-- Literal relation (constant — pure, no DB references)
21−names = ["Alice", "Bob", "Carol"]
22−
23−-- Empty relation
24−none = []
25−```
26−
27−### Declarations
28−
29−There are five kinds of top-level declarations:
30−
31−```knot
32−-- Source: stored in DB, mutable via `set`
33−*people : [Person]
34−*orders : [{customer: Text, amount: Int}]
35−
36−-- View: defined by a query over source relations, settable (writes propagate back)
37−*openTodos = do
38− t <- *todos
39− yield {title: t.title, owner: t.owner, priority: t.priority, status: Open {}}
40−
41−-- Constant: a pure expression with no DB references (zero-argument function)
42−maxRetries = 3
43−defaultPriority = Low {}
44−httpCodes = [{code: 200, name: "OK"}, {code: 404, name: "Not Found"}]
45−
46−-- Derived: references source relations, recomputed on access (read-only)
47−&seniors = do
48− people <- *people
49− yield (filter (\p -> p.age > 65) people)
50−
51−-- Type alias: just a name for a type
52−type Person = {name: Text, age: Int}
53−```
54−
55−The prefix determines mutability, the presence of a body determines whether it's stored or computed:
56−
57−| Declaration | Category | How the compiler knows |
58−|---|---|---|
59−| `*foo : [T]` | Source (persisted) | `*` prefix, no body |
60−| `*foo = expr` | View (read/write) | `*` prefix, has body |
61−| `&foo = expr` | Derived (read-only) | `&` prefix, has body |
62−| `foo = expr` (pure) | Constant | No prefix, no effects inferred |
63−| `type Foo = T` | Type alias | `type` keyword |
64−
65−### ADTs as Relation Schemas
66−
67−Every ADT defines a relation schema. Each constructor is a record variant. A `[T]` holds values of any variant of `T` in the same relation.
68−
69−```knot
70−data Shape
71− = Circle {radius: Float}
72− | Rect {width: Float, height: Float}
73−
74−*shapes : [Shape] -- source (no body)
75−```
76−
77−Single-variant types are equivalent to bare records:
78−
79−```knot
80−-- These are the same:
81−*people : [{name: Text, age: Int}]
82−*people : [Person]
83−```
84−
85−Constructors are the interface for building values, inserting, and querying. The tag/discriminator is an internal storage detail that never appears in the language.
86−
87−Every constructor requires `{}` — even those with no fields. This keeps the syntax uniform: a constructor is always `Name {fields}`, whether it has fields or not. There is no distinction between "a constructor" and "a constructor applied to a record."
88−
89−`Bool`, `Maybe`, and `Result` are built-in — their constructors (`True`/`False`, `Nothing`/`Just`, `Ok`/`Err`) are always available without a `data` declaration. `True {}` and `False {}` are interchangeable with the `true`/`false` literals and can be used in `case` patterns.
90−
91−```knot
92−data Maybe a = Nothing {} | Just {value: a}
93−data List a = Nil {} | Cons {head: a, tail: List a}
94−```
95−
96−### ADTs, Records, and Relations Compose Freely
97−
98−Any type can be a column type — including sum types, nested records, and nested relations.
99−
100−```knot
101−data Priority = Low {} | Medium {} | High {} | Critical {}
102−
103−data Status
104− = Open {}
105− | InProgress {assignee: Text}
106− | Resolved {resolution: Text}
107− | Blocked {reason: Text, dependencies: [{title: Text}]}
108−
109−*tickets : [{title: Text, priority: Priority, status: Status}]
110−```
111−
112−### Nested Relations
113−
114−A field can hold a `[]` — a set nested inside a row. This departs from SQL's first normal form restriction.
115−
116−```knot
117−type Person = {name: Text, age: Int}
118−
119−*teams : [{name: Text, members: [Person]}]
120−```
121−
122−#### Querying into Nested Relations
123−
124−Bind through multiple levels with `<-`:
125−
126−```knot
127−-- All people across all teams
128−&allMembers = do
129− teams <- *teams
130− let result = do
131− t <- teams
132− m <- t.members
133− yield {team: t.name, member: m.name}
134− yield result
135−
136−-- Engineers on large teams
137−&engineers = do
138− teams <- *teams
139− let result = do
140− t <- teams
141− where (count t.members) > 10
142− m <- t.members
143− where m.role == "engineer"
144− yield {team: t.name, name: m.name}
145− yield result
146−```
147−
148−#### Updating Nested Relations
149−
150−Use `set` with a `map` over the outer relation that transforms the nested relation:
151−
152−```knot
153−-- Add a member to a team
154−addMember = \teamName person -> do
155− teams <- *teams
156− set *teams = do
157− t <- teams
158− yield (if t.name == teamName
159− then {t | members: union t.members [person]}
160− else t)
161−
162−-- Remove a member from all teams
163−removePerson = \personName -> do
164− teams <- *teams
165− set *teams = do
166− t <- teams
167− yield {t | members: do
168− m <- t.members
169− where m.name != personName
170− yield m}
171−```
172−
173−#### Flattening and Nesting
174−
175−Convert between flat and nested representations:
176−
177−```knot
178−-- Flat relation
179−type FlatMembership = {team: Text, member: Text, age: Int}
180−*memberships : [FlatMembership]
181−
182−-- Nest: group a flat relation into nested structure
183−&nested = do
184− memberships <- *memberships
185− let result = do
186− t <- do m <- memberships; yield m.team
187− yield {name: t, members: do
188− m <- memberships
189− where m.team == t
190− yield {name: m.member, age: m.age}}
191− yield result
192−
193−-- Flatten: expand nested relation into flat rows
194−&flat = do
195− teams <- *teams
196− let result = do
197− t <- teams
198− m <- t.members
199− yield {team: t.name, member: m.name, age: m.age}
200− yield result
201−```
202−
203−#### Deeply Nested Relations
204−
205−Nesting is arbitrarily deep:
206−
207−```knot
208−type Course = {name: Text, students: [{name: Text, grades: [{subject: Text, score: Int}]}]}
209−
210−*departments : [{name: Text, courses: [Course]}]
211−
212−-- Find all failing grades across all departments
213−&failing = do
214− departments <- *departments
215− let result = do
216− d <- departments
217− c <- d.courses
218− s <- c.students
219− g <- s.grades
220− where g.score < 50
221− yield {dept: d.name, course: c.name, student: s.name, subject: g.subject, score: g.score}
222− yield result
223−```
224−
225−## Primitives
226−
227−### Trait Hierarchy
228−
229−`do` syntax is not hardcoded to `[]`. It desugars to trait methods, so any type implementing `Monad` gets `do`/`yield`/`<-` for free. This requires higher-kinded types in the type system.
230−
231−```knot
232−trait Functor (f : Type -> Type) where
233− map : (a -> b) -> f a -> f b
234−
235−trait Functor f => Applicative (f : Type -> Type) where
236− yield : a -> f a
237− ap : f (a -> b) -> f a -> f b
238−
239−trait Applicative m => Monad (m : Type -> Type) where
240− bind : (a -> m b) -> m a -> m b
241−
242−trait Applicative f => Alternative (f : Type -> Type) where
243− empty : f a
244− alt : f a -> f a -> f a
245−
246−trait Foldable (t : Type -> Type) where
247− fold : (b -> a -> b) -> b -> t a -> b
248−```
249−
250−### `do` Desugaring
251−
252−`do` syntax works for any `Monad`. Do blocks can appear anywhere an expression is expected, including as function arguments: `f do ...` or `f (do ...)`.
253−
254−- `x <- expr` desugars to `bind (\x -> ...) expr`
255−- `yield x` is `Applicative.yield`
256−- `where cond` desugars to `if cond then yield {} else empty` (requires `Alternative`)
257−
258−IO do blocks (those containing IO-returning expressions like `*rel`, `println`, `readFile`, `now`) are not desugared — they use a dedicated compilation path that sequences IO actions directly.
259−
260−```knot
261−-- do with [] (pure relation comprehension over plain values)
262−richOnes = \employees departments -> do
263− e <- employees
264− d <- departments
265− where e.dept == d.name
266− yield {e.name, e.salary, d.budget}
267−
268−-- IO do block (binds from *rel, which returns IO)
269−&richEmployees = do
270− employees <- *employees
271− departments <- *departments
272− yield (richOnes employees departments)
273−
274−-- do with Maybe
275−safeDivide = \a b -> do
276− where b != 0
277− yield (a / b)
278−
279−-- do with Result
280−parseConfig = \text -> do
281− json <- parseJson text
282− name <- getField "name" json
283− yield name
284−```
285−
286−### `[]` Trait Implementations
287−
288−`[]` implements the full hierarchy:
289−
290−```knot
291−impl Functor [] where
292− map f rel = do x <- rel; yield (f x)
293−
294−impl Applicative [] where
295− yield x = [x]
296− ap fs xs = do f <- fs; x <- xs; yield (f x)
297−
298−impl Monad [] where
299− bind = ... -- built-in
300−
301−impl Alternative [] where
302− empty = []
303− alt = union
304−
305−impl Foldable [] where
306− fold = ... -- built-in
307−```
308−
309−### The Only `[]`-Specific Primitive
310−
311−| Primitive | Type | Description |
312−|-----------|------|-------------|
313−| `set` | `*[a] -> [a] -> IO {} {}` | Set a persistent relation to a new value |
314−
315−Everything else comes from traits:
316−
317−| Operation | Trait method |
318−|-----------|-------------|
319−| `empty` | `Alternative.empty` |
320−| `yield` | `Applicative.yield` |
321−| `<-` (bind) | `Monad.bind` |
322−| `union` | `Alternative.alt` |
323−| `fold` | `Foldable.fold` |
324−| `map` | `Functor.map` |
325−
326−### Derived Operations
327−
328−Everything else is built from trait methods + `set`. The compiler recognizes these patterns and executes them as efficient set operations (hash joins, indexed lookups, etc.) — the traits define semantics, the runtime chooses the strategy.
329−
330−**`where`** — conditional empty (requires `Alternative`):
331−
332−```knot
333−where = \cond -> if cond then yield {} else empty
334−```
335−
336−**`filter`** — filter rows:
337−
338−```knot
339−filter = \p rel -> do
340− x <- rel
341− where (p x)
342− yield x
343−```
344−
345−**`join`** — combine relations on a condition:
346−
347−```knot
348−join = \a b -> do
349− x <- a
350− y <- b
351− where (x.id == y.id)
352− yield {x, y}
353−```
354−
355−**`diff`** — rows in one relation but not another:
356−
357−```knot
358−contains = \x rel -> fold (\acc r -> acc || r == x) False {} rel
359−
360−diff = \a b -> do
361− x <- a
362− where (not (contains x b))
363− yield x
364−```
365−
366−**`inter`** — rows in both relations:
367−
368−```knot
369−inter = \a b -> do
370− x <- a
371− where (contains x b)
372− yield x
373−```
374−
375−**`insert`** — add a value (union with a singleton):
376−
377−```knot
378−insert = \x rel -> set rel (union rel (yield x))
379−```
380−
381−**`delete`** — remove matching rows:
382−
383−```knot
384−delete = \p rel -> set rel (filter (\x -> not (p x)) rel)
385−```
386−
387−**`update`** — transform matching rows:
388−
389−```knot
390−update = \p f rel -> set rel (map (\x -> if p x then f x else x) rel)
391−```
392−
393−**`count`**, **`sum`**, **`avg`** — folds:
394−
395−```knot
396−count = \rel -> fold (\n _ -> n + 1) 0 rel
397−sum = \f rel -> fold (\acc x -> acc + f x) 0 rel
398−```
399−
400−**`match`** — filter to one variant:
401−
402−```knot
403−match = \Circle shapes -> do
404− Circle c <- shapes
405− yield c
406−```
407−
408−## Querying
409−
410−### Comprehensions
411−
412−Relation comprehensions use `do` syntax with `yield` to produce rows. Since relation references (`*rel`, `&rel`) return `IO {} value`, you IO-bind to get the value, then use a pure comprehension on the plain value:
413−
414−```knot
415−&richEmployees = do
416− employees <- *employees
417− departments <- *departments
418− let result = do
419− e <- employees
420− d <- departments
421− where e.dept == d.name
422− where d.budget > 1_000_000
423− yield {e.name, e.salary, d.budget}
424− yield result
425−```
426−
427−The outer do-block is an IO do-block that binds from `*employees` (type `IO {} [Employee]`) and `*departments` (type `IO {} [Department]`). The inner `let result = do ...` is a pure comprehension over plain relation values. `<-` draws from a relation (like a `FROM` clause). `where` filters (like a `WHERE` clause). `yield` emits a row into the result relation.
428−
429−### Pipe-Forward Composition
430−
431−Derived combinators like `filter` compose with `|>`:
432−
433−```knot
434−&highEarners = do
435− employees <- *employees
436− yield (employees
437− |> filter (\e -> e.salary > 150000)
438− |> map (\e -> {name: e.name, salary: e.salary}))
439−```
440−
441−### Querying by Variant: `match`
442−
443−`match` filters to one variant and exposes its fields:
444−
445−```knot
446−&circles = do -- : IO {} [{radius: Float}]
447− shapes <- *shapes
448− yield (shapes |> match Circle)
449−
450−&rects = do -- : IO {} [{width: Float, height: Float}]
451− shapes <- *shapes
452− yield (shapes |> match Rect)
453−
454−&bigCircles = do
455− circles <- &circles
456− yield (filter (\c -> c.radius > 10) circles)
457−```
458−
459−### Pattern Matching in Comprehensions
460−
461−Pattern matching on `<-` filters and binds in one step:
462−
463−```knot
464−&bigCircleAreas = do
465− shapes <- *shapes
466− let result = do
467− Circle c <- shapes
468− where c.radius > 10
469− yield {area: pi * c.radius * c.radius}
470− yield result
471−
472−&blockedDetails = do
473− tickets <- *tickets
474− let result = do
475− t <- tickets
476− Blocked {dependencies} <- t.status
477− dep <- dependencies
478− yield {t.title, dep}
479− yield result
480−```
481−
482−### Cross-Variant Operations
483−
484−Operate on the whole relation with `case`:
485−
486−```knot
487−scale = \factor -> do
488− shapes <- *shapes
489− set *shapes = do
490− s <- shapes
491− yield (case s of
492− Circle {radius} -> Circle {radius: radius * factor}
493− Rect {width, height} -> Rect {width: width * factor, height: height * factor})
494−```
495−
496−### Pattern Matching on Relations
497−
498−```knot
499−describe = \rel -> case rel of
500− [] -> "empty"
501− [{name: n}] -> "just " ++ n
502− _ -> show (count rel) ++ " rows"
503−```
504−
505−### Grouping
506−
507−`groupBy` partitions a relation by key fields, like SQL's `GROUP BY`. After `groupBy`, the bound variable becomes a sub-relation (the group), enabling aggregation:
508−
509−```knot
510−&workload = do
511− todos <- *todos
512− let result = do
513− t <- todos
514− where t.done == 0
515− groupBy {t.owner}
516− yield {owner: t.owner, count: count t}
517− yield result
518−```
519−
520−The key expression is a record literal whose fields select the grouping columns. After `groupBy {t.owner}`, `t` is rebound from a single row to a sub-relation of all rows sharing that `owner` value. Field access on a group (e.g. `t.owner`) returns the shared key value. Aggregate functions like `count` operate on the whole group.
521−
522−Multiple key fields group by their combination:
523−
524−```knot
525−&summary = do
526− orders <- *orders
527− let result = do
528− o <- orders
529− groupBy {o.region, o.status}
530− yield {region: o.region, status: o.status, total: count o}
531− yield result
532−```
533−
534−Grouping is executed via SQLite — key columns are inserted into a temp table and sorted with `ORDER BY`, then consecutive rows with matching keys are collected into groups.
535−
536−## Effects and the IO Monad
537−
538−### Unified Effect Model
539−
540−All state operations in Knot return IO values. The IO type carries an effect set that distinguishes DB operations from external effects:
541−
542−- **DB operations** return `IO {} value` — the empty effect set `{}` indicates pure database interaction with no external side effects. Source refs (`*rel`), derived refs (`&rel`), `set`, `full set`, and temporal queries (`@(timestamp)`) all return `IO {} value`.
543−- **External effects** carry specific tags: `IO {console} {}`, `IO {fs} Text`, `IO {network} Result`, `IO {clock} Int`, `IO {random} Float`.
544−
545−This unified model means all stateful code lives in IO do-blocks, while pure comprehensions over plain values remain non-IO.
546−
547−### The IO Type
548−
549−Effectful functions return descriptions of effects (`IO {effects} a`) rather than performing them. IO values are thunks that execute when run.
550−
551−```knot
552−-- DB operations return IO with empty effects
553−*people : [Person]
554−-- *people : IO {} [Person]
555−
556−-- println returns an IO action with console effect
557−println : a -> IO {console} {}
558−
559−-- readFile returns an IO action with fs effect
560−readFile : Text -> IO {fs} Text
561−
562−-- now returns an IO action with clock effect
563−now : IO {clock} Int
564−```
565−
566−### IO Do-Blocks
567−
568−IO do-blocks sequence effects. The `<-` operator runs an IO action and binds its result. Since relation references return IO, you bind to get the plain value, then use pure comprehensions:
569−
570−```knot
571−main = do
572− people <- *people -- IO {} [Person] → binds [Person]
573− content <- readFile "input.txt" -- IO {fs} Text → binds Text
574− println content -- IO {console} {}
575− t <- now -- IO {clock} Int → binds Int
576− println ("time: " ++ show t)
577− yield {}
578−-- overall type: IO {fs, console, clock} {}
579−```
580−
581−The pattern for querying relations is: IO-bind to get the value, then pure comprehension on the plain value:
582−
583−```knot
584−&richEmployees = do
585− employees <- *employees -- IO bind: [Employee] from IO {} [Employee]
586− let result = do -- pure comprehension on the value
587− e <- employees
588− where e.salary > 100000
589− yield e
590− yield result
591−```
592−
593−The compiler detects whether a do-block is IO or relational based on the types of bound expressions. IO do-blocks work correctly in all positions, including as branches of `if`/`then`/`else`.
594−
595−### DB Effect Inference
596−
597−DB effects are still inferred as fine-grained capabilities (`{reads *rel}`, `{writes *rel}`), but all relation access returns IO values:
598−
599−```knot
600−-- Pure (inferred: no effects)
601−formatName = \n -> toUpper (take 1 n) ++ drop 1 n
602−
603−-- DB read (inferred: {reads *people})
604−&seniors = do
605− people <- *people
606− yield (filter (\p -> p.age > 65) people)
607−
608−-- DB write (inferred: {reads *people, writes *people})
609−birthday = \name -> do
610− people <- *people
611− set *people = do
612− p <- people
613− yield (if p.name == name then {p | age: p.age + 1} else p)
614−```
615−
616−### Effect Annotations
617−
618−Effect signatures are inferred but can be written explicitly:
619−
620−```knot
621−birthday : {reads *people, writes *people} Text -> IO {} {}
622−birthday = \name -> do
623− people <- *people
624− set *people = do
625− p <- people
626− yield (if p.name == name then {p | age: p.age + 1} else p)
627−```
628−
629−If the body uses a capability not listed in the signature, the compiler rejects it.
630−
631−### IO and Transactions
632−
633−`atomic` takes an IO body and runs it in a transaction. The body must only contain DB interactions (empty effect set) — no external IO (console, fs, etc.) is allowed inside `atomic`.
634−
635−```knot
636−atomic : IO {} a -> IO {} a
637−```
638−
639−```knot
640−-- DB writes go in `atomic`, IO happens after commit
641−handleOrder = \req -> do
642− orderId <- atomic do
643− orders <- *orders
644− set *orders = union orders [{item: req.body.item, qty: 1}]
645− newOrders <- *orders
646− yield (count newOrders)
647− println ("New order #" ++ show orderId)
648− yield {orderId}
649−```
650−
651−#### `retry`
652−
653−`retry` is used inside `atomic` blocks to implement STM (Software Transactional Memory) style concurrency. When executed, `retry` causes the transaction to rollback and wait until some relation changes, then re-executes the entire `atomic` block.
654−
655−```knot
656−retry : forall a. a -- bottom type, never returns
657−```
658−
659−The compiler enforces that `retry` is only used inside `atomic`. This enables blocking waits on relation state without busy-polling:
660−
661−```knot
662−-- Wait until a condition is met
663−waitForReady = atomic do
664− status <- *status
665− where (count (filter (\s -> s.ready) status)) == 0
666− retry
667−```
668−
669−### File System
670−
671−Built-in functions for file I/O. All return `IO {fs}` values.
672−
673−| Function | Type | Description |
674−|----------|------|-------------|
675−| `readFile` | `Text -> IO {fs} Text` | Read entire file contents as text |
676−| `writeFile` | `Text -> Text -> IO {fs} {}` | Write text to a file (creates or overwrites) |
677−| `appendFile` | `Text -> Text -> IO {fs} {}` | Append text to a file |
678−| `fileExists` | `Text -> IO {fs} Bool` | Check whether a path exists |
679−| `removeFile` | `Text -> IO {fs} {}` | Delete a file |
680−| `listDir` | `Text -> IO {fs} [Text]` | List directory entries as a relation of filenames |
681−
682−```knot
683−-- Copy a file (IO do-block)
684−copyFile = \src dst -> do
685− content <- readFile src
686− writeFile dst content
687−
688−-- Append a log line
689−log = \msg -> appendFile "app.log" (msg ++ "\n")
690−
691−-- List .knot files
692−knotFiles = do
693− files <- listDir "."
694− yield (filter (\f -> contains ".knot" f) files)
695−
696−-- Conditional read
697−loadConfig = \path -> do
698− exists <- fileExists path
699− if exists
700− then readFile path
701− else yield "{}"
702−```
703−
704−### Concurrency
705−
706−#### `fork`
707−
708−`fork` runs an IO action on a new OS thread. It is fire-and-forget — the forked action runs independently. Each spawned thread gets its own SQLite connection (WAL mode enables concurrent access). The main thread waits for all spawned threads before exiting.
709−
710−```knot
711−fork : IO {} {} -> IO {} {}
712−```
713−
714−Do blocks can be passed as arguments without parentheses: `fork do ...`.
715−
716−```knot
717−*counter : [{n: Int}]
718−
719−increment = do
720− c <- *counter
721− set *counter = [{n: (fold (\_ x -> x.n) 0 c) + 1}]
722−
723−main = do
724− set *counter = [{n: 0}]
725− fork do
726− increment
727− increment
728− fork do
729− increment
730− increment
731− -- main waits for all threads before exiting
732−```
733−
734−#### `fork` + `atomic` + `retry`
735−
736−The combination of `fork`, `atomic`, and `retry` enables STM-style concurrent coordination:
737−
738−```knot
739−*tasks : [{id: Int, status: Text}]
740−
741−waitForCompletion = \id -> atomic do
742− tasks <- *tasks
743− let task = do
744− t <- tasks
745− where t.id == id
746− where t.status == "done"
747− yield t
748− where (count task) == 0
749− retry
750− yield task
751−
752−main = do
753− set *tasks = [{id: 1, status: "pending"}]
754− fork do
755− -- simulate work
756− atomic do
757− set *tasks = [{id: 1, status: "done"}]
758− result <- waitForCompletion 1
759− println result
760−```
761−
762−SQLite WAL mode ensures that concurrent readers and writers do not block each other. Each thread operates on its own connection, and `atomic` provides transaction isolation within a thread.
763−
764−### Routes
765−
766−Routes are first-class. A `route` declaration defines an ADT and its HTTP mapping in one place. Each line maps a method + typed path to a constructor. The constructor's fields are the union of path params, query params, body fields, and request headers.
767−
768−- `/{name: Type}` in the path — path parameter
769−- `?{name: Type, ...}` after the path — query parameters
770−- `{name: Type, ...}` after the verb — request body
771−- `headers {name: Type, ...}` after query params — request headers
772−- `headers {name: Type, ...}` after response type — response headers
773−
774−Header field names use camelCase and auto-convert to HTTP-Header-Case (`authorization` → `Authorization`, `contentType` → `Content-Type`, `xRequestId` → `X-Request-Id`). Optional headers use `Maybe` type.
775−
776−Constructors are bare names — their fields are automatically the union of path, query, body, and header params.
777−
778−```knot
779−route Api where
780− GET /todos/{user: Text}?{page: Int, limit: Int} = GetTodos
781− POST {title: Text, owner: Text, priority: Priority} /todos = AddTodo
782− PUT {owner: Text, person: Text} /todos/{title: Text}/assign = AssignTodo
783− GET /workload = GetWorkload
784−```
785−
786−Dispatch is pattern matching — the compiler ensures exhaustive handling:
787−
788−```knot
789−serve : Api -> Response
790−serve = \req -> case req of
791− GetTodos {user, page, limit, respond} -> do
792− result <- pendingFor user page limit
793− respond result
794− AddTodo {title, owner, priority, respond} -> do
795− atomic (add title owner priority)
796− respond {ok: True {}}
797− AssignTodo {title, owner, person, respond} -> do
798− atomic (assign title owner person)
799− respond {ok: True {}}
800− GetWorkload {respond} -> do
801− result <- &workload
802− respond result
803−
804−main = listen 8080 serve
805−```
806−
807−No string routes, no untyped params, no missing handlers.
808−
809−#### Typed Responses
810−
811−Return types can be declared per-endpoint:
812−
813−```knot
814−route Api where
815− GET /todos/{user: Text} -> [{title: Text, priority: Priority}] = GetTodos
816− POST {title: Text, owner: Text} /todos -> {ok: Bool} = AddTodo
817− GET /workload -> [{owner: Text, count: Int}] = GetWorkload
818−```
819−
820−The compiler checks that each `serve` branch returns the declared type.
821−
822−#### Typed Headers
823−
824−Request and response headers are declared with the `headers` keyword:
825−
826−```knot
827−route Api where
828− GET /todos headers {authorization: Text} -> [Todo] headers {xTotalCount: Int, xPage: Int} = GetTodos
829− POST {title: Text} /todos headers {authorization: Text, xIdempotencyKey: Text} -> {id: Int} = CreateTodo
830− GET /health -> {status: Text} = HealthCheck
831−```
832−
833−Request headers become constructor fields, just like body/query/path params. The handler destructures them:
834−
835−```knot
836−serve = \req -> case req of
837− GetTodos {authorization, respond} ->
838− let todos = allTodos
839− respond todos {xTotalCount: length todos, xPage: 1}
840− CreateTodo {title, authorization, xIdempotencyKey, respond} ->
841− let id = addTodo title
842− respond {id: id} {}
843− HealthCheck {respond} -> respond {status: "ok"}
844−```
845−
846−When response headers are declared, `respond` takes two arguments — body then headers record. Without response headers, `respond` takes just the body.
847−
848−Optional headers use `Maybe`:
849−
850−```knot
851−route Api where
852− GET /todos headers {authorization: Maybe Text} -> [Todo] = GetTodos
853−```
854−
855−The server gets `Nothing {}` if the header is absent, `Just {value: "..."}` if present. In `fetch`, `Nothing` headers are skipped.
856−
857−On the fetch side, request headers are sent automatically from constructor fields. When response headers are declared, the result wraps as `{body: ResponseType, headers: {h: T}}`:
858−
859−```knot
860−result <- fetch "https://api.example.com" (GetTodos {authorization: "Bearer tok"})
861−-- result : IO {network} (Result ... {body: [Todo], headers: {xTotalCount: Int, xPage: Int}})
862−```
863−
864−#### Path Prefixes
865−
866−Factor out common path prefixes with nesting:
867−
868−```knot
869−route Api where
870− /todos
871− GET /{user: Text} = GetTodos
872− POST {title: Text, owner: Text} / = AddTodo
873− PUT {owner: Text, person: Text} /{title: Text}/assign = AssignTodo
874− /admin
875− GET /stats = Stats
876− POST /reset = Reset
877−```
878−
879−Prefixes nest arbitrarily:
880−
881−```knot
882−route Api where
883− /api/v1
884− /users
885− GET / = ListUsers
886− GET /{id: Int} = GetUser
887− POST {name: Text, email: Text} / = CreateUser
888− /teams
889− GET / = ListTeams
890− GET /{id: Int}/members = GetMembers
891−```
892−
893−#### Route Composition
894−
895−Routes compose — combine multiple route types:
896−
897−```knot
898−route TodoApi where
899− /todos
900− GET /{user: Text} = ListTodos
901− POST {title: Text, owner: Text} / = CreateTodo
902−
903−route AdminApi where
904− /admin
905− GET /stats = Stats
906− POST /reset = Reset
907−
908−route Api = TodoApi | AdminApi
909−```
910−
911−### Transaction Boundaries
912−
913−DB writes within handlers must use `atomic`. IO happens outside `atomic`:
914−
915−```knot
916−serve = \req -> case req of
917− CreateOrder {item, qty, respond} -> do
918− orderId <- atomic do
919− orders <- *orders
920− set *orders = union orders [{item: item, qty: qty}]
921− newOrders <- *orders
922− yield (count newOrders)
923− println ("New order #" ++ show orderId)
924− respond {orderId: orderId}
925−```
926−
927−For sub-transaction boundaries:
928−
929−```knot
930−batchTransfer = \transfers ->
931− map (\t -> atomic (transfer t.from t.to t.amount)) transfers
932−```
933−
934−## Persistence
935−
936−### Mutation
937−
938−All mutation is done through `set`, which replaces a persistent relation with a new value. The runtime diffs the old and new sets to apply minimal changes. Since relation references return IO, you bind to get the current value first:
939−
940−```knot
941−-- Insert: union with a singleton
942−addPerson = do
943− people <- *people
944− set *people = union people [{name: "Alice", age: 30}]
945−
946−-- Update: map with a conditional
947−birthday = \name -> do
948− people <- *people
949− set *people = do
950− p <- people
951− yield (if p.name == "Alice" then {p | age: p.age + 1} else p)
952−
953−-- Delete: filter to keep the rest
954−removePerson = \name -> do
955− people <- *people
956− set *people = do
957− p <- people
958− where p.name != name
959− yield p
960−```
961−
962−### Identity is Structural
963−
964−Relations are sets. Two rows are the same row iff all their fields are equal. Setting a relation to a value that includes a duplicate is a no-op for that row.
965−
966−```knot
967−-- Adding an already-existing row changes nothing
968−set *people = union *people [{name: "Alice", age: 30}]
969−set *people = union *people [{name: "Alice", age: 30}] -- no change
970−```
971−
972−No surrogate IDs, no key declarations. Data identifies itself.
973−
974−### Subset Constraints (Optional)
975−
976−For referential integrity and uniqueness, express value relationships:
977−
978−```knot
979−*orders : [{customer: Text, amount: Int}]
980−*users : [{email: Text, name: Text}]
981−
982−-- Referential integrity: every order's customer must appear in people's names
983−*orders.customer <= *people.name
984−
985−-- Uniqueness: email determines the full row (each email maps to at most one row)
986−*users <= *users.email
987−```
988−
989−### Indexing
990−
991−Automatic. The runtime observes query patterns and indexes accordingly. No `CREATE INDEX`, no key declarations.
992−
993−## Views
994−
995−A `*`-prefixed relation with a body is a **view** — a bidirectional query over source relations. Reads compute the query; writes propagate back to the underlying sources.
996−
997−```knot
998−&seniorStaff = do -- read-only (& prefix)
999− employees <- *employees
1000− yield (filter (\e -> e.salary > 100000) employees)
1001−
1002−*openTodos = do -- settable (* prefix)
1003− t <- *todos
1004− yield {title: t.title, owner: t.owner, priority: t.priority, status: Open {}}
1005−```
1006−
1007−### Column Provenance
1008−
1009−The compiler tracks each column in a view's `yield`:
1010−
1011−| Kind | Syntax in `yield` | On read | On write | In view type |
1012−|------|-------------------|---------|----------|--------------|
1013−| **source** | `t.column` | passthrough | passthrough | yes |
1014−| **constant** | literal or constructor | filter | auto-fill | no |
1015−| **computed** | `expr` | computed | error | yes (read-only) |
1016−
1017−For `*openTodos` above:
1018−
1019−| Column | Kind | Read | Write |
1020−|--------|------|------|-------|
1021−| `title` | source (`t.title`) | passthrough | passthrough |
1022−| `owner` | source (`t.owner`) | passthrough | passthrough |
1023−| `priority` | source (`t.priority`) | passthrough | passthrough |
1024−| `status` | constant (`Open {}`) | filter | auto-fill |
1025−
1026−The constant column is hidden from the type — its value is fixed by definition:
1027−
1028−```knot
1029−*openTodos : [{title: Text, owner: Text, priority: Priority}]
1030−```
1031−
1032−Writing through a view auto-fills constants and propagates source columns:
1033−
1034−```knot
1035−-- Insert through view — status auto-filled as Open {}
1036−addOpenTodo = do
1037− openTodos <- *openTodos
1038− set *openTodos = union openTodos [{title: "New task", owner: "Alice", priority: High {}}]
1039−-- Compiler rewrites →
1040−-- set *todos = union *todos [{title: "New task", owner: "Alice", priority: High {}, status: Open {}}]
1041−
1042−-- Delete through view — only affects rows matching the constant
1043−removeAliceTodos = do
1044− openTodos <- *openTodos
1045− set *openTodos = do
1046− t <- openTodos
1047− where t.owner != "Alice"
1048− yield t
1049−-- Only removes Alice's Open todos; resolved/in-progress ones are untouched
1050−```
1051−
1052−Multiple constants create narrow slices:
1053−
1054−```knot
1055−*criticalOpen = do
1056− t <- *todos
1057− yield {title: t.title, owner: t.owner, status: Open {}, priority: Critical {}}
1058−
1059−-- Type: [{title: Text, owner: Text}]
1060−-- Reads: only critical open todos
1061−-- Writes: auto-fills status=Open, priority=Critical
1062−```
1063−
1064−### Recursive Derived Relations
1065−
1066−Datalog-style transitive closure:
1067−
1068−```knot
1069−*manages : [{manager: Text, report: Text}]
1070−
1071−&reportsTo : [{ancestor: Text, descendant: Text}] = do
1072− manages <- *manages
1073− reportsTo <- &reportsTo
1074− yield (union
1075− (do m <- manages
1076− yield {m.manager, m.report})
1077− (do r <- reportsTo
1078− m <- manages
1079− where r.descendant == m.manager
1080− yield {r.ancestor, m.report}))
1081−```
1082−
1083−The compiler checks stratification.
1084−
1085−## Schema Evolution
1086−
1087−The compiler maintains a **schema lockfile** (`schema.lock`) that records the persisted schema. The lockfile uses the same syntax as source code — it's valid Knot containing only type declarations, data definitions, and relation signatures.
1088−
1089−### The Lockfile
1090−
1091−```knot
1092−-- schema.lock (auto-generated, do not edit)
1093−-- Commit to source control.
1094−
1095−data Priority = Low {} | Medium {} | High {} | Critical {}
1096−
1097−data Status
1098− = Open {}
1099− | InProgress {assignee: Text}
1100− | Resolved {resolution: Text}
1101−
1102−*people : [{name: Text, age: Int, email: Text}]
1103−
1104−*todos : [{title: Text, owner: Text, priority: Priority, status: Status}]
1105−
1106−migrate *people
1107− from {name: Text, age: Int}
1108− to {name: Text, age: Int, email: Text}
1109− using (\old -> {old | email: old.name ++ "@unknown.com"})
1110−```
1111−
1112−Since it's valid Knot, it can be parsed by the same compiler frontend — no separate schema format. Migrations are recorded in the lockfile so the compiler can detect if a migration is accidentally removed from source.
1113−
1114−### How It Works
1115−
1116−On each compile, the compiler diffs the source types against `schema.lock`:
1117−
1118−| Change | Compiler action |
1119−|--------|-----------------|
1120−| Add `Maybe` field to record | Auto-update lockfile |
1121−| Add variant to ADT | Auto-update lockfile |
1122−| Add new relation | Auto-add to lockfile |
1123−| Add new `migrate` block | Auto-add to lockfile |
1124−| Remove field or variant | Error: require `migrate` |
1125−| Add non-Maybe field | Error: require `migrate` |
1126−| Change field type | Error: require `migrate` |
1127−| Remove a `migrate` block | Error: migration exists in lockfile |
1128−| Remove relation | Warning (data will be orphaned) |
1129−
1130−### Migrations
1131−
1132−Breaking changes require a `migrate` block:
1133−
1134−```knot
1135−migrate *people
1136− from {name: Text, age: Int}
1137− to {name: Text, age: Int, email: Text}
1138− using (\old -> {old | email: old.name ++ "@unknown.com"})
1139−```
1140−
1141−ADT migrations use pattern matching:
1142−
1143−```knot
1144−migrate *todos
1145− from {title: Text, owner: Text, priority: Priority, status: <Open {} | InProgress {assignee: Text} | Resolved {resolution: Text}>}
1146− to {title: Text, owner: Text, priority: Priority, status: Status}
1147− using (\old -> {old | status: case old.status of
1148− InProgress {assignee} -> Resolved {resolution: "closed by " ++ assignee}
1149− other -> other})
1150−```
1151−
1152−After a successful compile, `schema.lock` is updated.
1153−
1154−### Runtime
1155−
1156−The runtime stores the compiled schema version in the database. On startup it compares against the stored version and applies any pending migrations in order. Already-applied migrations are skipped.
1157−
1158−`migrate` blocks accumulate in source code. The lockfile tracks all migrations — if a migration present in the lockfile is missing from source, the compiler rejects the build. This prevents accidental deletion. Old migrations can be pruned only by explicitly removing them from both source and lockfile.
1159−
1160−## Temporal Queries
1161−
1162−Optional history tracking:
1163−
1164−```knot
1165−*employees : [{name: Text, salary: Int}]
1166− with history
1167−
1168−salaryLastYear = \name -> do
1169− t <- now
1170− employees <- *employees @(t - 365 days)
1171− yield (employees
1172− |> filter (\e -> e.name == name)
1173− |> map (\e -> e.salary)
1174− |> single)
1175−```
1176−
1177−## Type System
1178−
1179−### Row Polymorphism
1180−
1181−Functions can be generic over records and relations with specific fields:
1182−
1183−```knot
1184−getName : {name: Text | r} -> Text
1185−getName = \r -> r.name
1186−```
1187−
1188−### Row-Polymorphic Variants
1189−
1190−Functions can be generic over any ADT that has a particular variant:
1191−
1192−```knot
1193−countOpen = \rel ->
1194− rel |> filter (\r -> case r.status of Open {} -> True {}; _ -> False {}) |> count
1195−
1196−-- Inferred: [{status: <Open {} | r> | s}] -> Int
1197−-- Works on tickets, issues, orders — anything with an Open status variant
1198−```
1199−
1200−### Traits
1201−
1202−Traits define shared behavior that types can implement. Syntax follows Rust: `trait` for definition, `impl` for implementation.
1203−
1204−```knot
1205−trait Display a where
1206− display : a -> Text
1207−
1208−impl Display Int where
1209− display n = showInt n
1210−
1211−impl Display Text where
1212− display t = t
1213−```
1214−
1215−#### Trait Bounds
1216−
1217−Trait bounds constrain type variables. They are inferred but can be written explicitly:
1218−
1219−```knot
1220−-- Bounds are inferred from usage
1221−printAll = \rel -> do
1222− r <- rel
1223− yield (display r)
1224−
1225−-- Inferred: Display a => [a] -> [Text]
1226−
1227−-- Explicit bound (optional)
1228−printAll : Display a => [a] -> [Text]
1229−```
1230−
1231−#### Multiple Bounds
1232−
1233−```knot
1234−sortAndShow : Ord a => Display a => [a] -> [Text]
1235−```
1236−
1237−#### Associated Types
1238−
1239−Traits can have associated types:
1240−
1241−```knot
1242−trait Collection c where
1243− type Item c
1244− empty : c
1245− add : Item c -> c -> c
1246− toRel : c -> [Item c]
1247−
1248−impl Collection [a] where
1249− type Item [a] = a
1250− empty = []
1251− add x rel = union rel (yield x)
1252− toRel = id
1253−```
1254−
1255−#### Deriving
1256−
1257−Common traits are auto-derived:
1258−
1259−```knot
1260−data Priority = Low {} | Medium {} | High {} | Critical {}
1261− deriving (Eq, Ord, Display)
1262−
1263−data Shape
1264− = Circle {radius: Float}
1265− | Rect {width: Float, height: Float}
1266− deriving (Eq, Display)
1267−```
1268−
1269−#### Relation-Specific Traits
1270−
1271−The standard library defines traits that interact with the relational model:
1272−
1273−```knot
1274−-- Types that can be used in where clauses and joins
1275−trait Eq a where
1276− (==) : a -> a -> Bool
1277−
1278−-- Types that support ordering
1279−trait Ord a where
1280− compare : a -> a -> Ordering
1281−```
1282−
1283−#### Trait-Polymorphic Queries
1284−
1285−Traits compose naturally with relational queries:
1286−
1287−```knot
1288−-- Works on any relation whose rows are displayable
1289−report = \rel -> do
1290− r <- rel
1291− yield {summary: display r}
1292−
1293−-- Works on any relation with a numeric column named `amount`
1294−totalAmount : Num n => [{amount: n | r}] -> n
1295−totalAmount = \rel -> fold (\acc r -> acc + r.amount) 0 rel
1296−```
1297−
1298−#### Default Implementations
1299−
1300−```knot
1301−trait Summarize a where
1302− summary : a -> Text
1303− detailed : a -> Text
1304− detailed x = summary x -- default: same as summary
1305−
1306−impl Summarize Shape where
1307− summary s = case s of
1308− Circle {radius} -> "circle r=" ++ display radius
1309− Rect {width, height} -> display width ++ "x" ++ display height
1310− -- detailed uses the default
1311−```
1312−
1313−### Type Inference
1314−
1315−Full Hindley-Milner style inference extended with row polymorphism and trait bounds. Type signatures are always optional — the compiler infers trait bounds from usage just like it infers everything else.
1316−
1317−## Full Example
1318−
1319−```knot
1320−data Priority = Low {} | Medium {} | High {} | Critical {}
1321−
1322−data Status
1323− = Open {}
1324− | InProgress {assignee: Text}
1325− | Resolved {resolution: Text}
1326−
1327−*todos : [{title: Text, owner: Text, priority: Priority, status: Status}]
1328−
1329−route Api where
1330− GET /todos/{user: Text} -> [{title: Text, priority: Priority}] = GetTodos
1331− POST {title: Text, owner: Text, priority: Priority}
1332− /todos -> {ok: Bool} = AddTodo
1333− PUT {owner: Text, person: Text} /todos/{title: Text}/assign -> {ok: Bool} = AssignTodo
1334− PUT {owner: Text, msg: Text} /todos/{title: Text}/resolve -> {ok: Bool} = ResolveTodo
1335− GET /workload -> [{owner: Text, count: Int}] = GetWorkload
1336−
1337−formatTitle = \title -> toUpper (take 1 title) ++ drop 1 title
1338−
1339−pendingFor = \user -> do
1340− todos <- *todos
1341− let result = do
1342− t <- todos
1343− where t.owner == user
1344− Open {} <- t.status
1345− yield {t.title, t.priority}
1346− yield result
1347−
1348−add = \title owner priority -> do
1349− todos <- *todos
1350− set *todos = union todos [{title: formatTitle title, owner: owner, priority: priority, status: Open {}}]
1351−
1352−assign = \title owner person -> do
1353− todos <- *todos
1354− set *todos = do
1355− t <- todos
1356− yield (if t.title == title && t.owner == owner
1357− then {t | status: InProgress {assignee: person}}
1358− else t)
1359−
1360−resolve = \title owner msg -> do
1361− todos <- *todos
1362− set *todos = do
1363− t <- todos
1364− yield (if t.title == title && t.owner == owner
1365− then {t | status: Resolved {resolution: msg}}
1366− else t)
1367−
1368−&workload = do
1369− todos <- *todos
1370− let result = do
1371− t <- todos
1372− Open {} <- t.status
1373− groupBy {t.owner}
1374− yield {owner: t.owner, count: count t}
1375− yield result
1376−
1377−serve : Api -> Response
1378−serve = \req -> case req of
1379− GetTodos {user, respond} -> do
1380− result <- pendingFor user
1381− respond result
1382− AddTodo {title, owner, priority, respond} -> do
1383− atomic (add title owner priority)
1384− respond {ok: True {}}
1385− AssignTodo {title, owner, person, respond} -> do
1386− atomic (assign title owner person)
1387− respond {ok: True {}}
1388− ResolveTodo {title, owner, msg, respond} -> do
1389− atomic (resolve title owner msg)
1390− respond {ok: True {}}
1391− GetWorkload {respond} -> do
1392− result <- &workload
1393− respond result
1394−
1395−main = listen 8080 serve
1396−```
README.md+6−47
@@ -1,58 +1,17 @@
11# Knot
22
3−<img src="logo.png" width="300px" align="center">
4−
5−Knot is a functional relational programming language. Relations are the primary data structure, computation is pure and functional, and state is automatically persisted to SQLite.
3+Knot is a functional relational programming language. Relations are the
4+primary data structure, computation is pure and functional, and state is
5+automatically persisted to SQLite. Effects are inferred and tracked in the
6+type system.
67
78## Quick Start
89
910```sh
10−# Build
1111cargo build
12−
13−# Compile and run a Knot program
1412cargo run -p knot-compiler -- build examples/hello.knot
1513./examples/hello
1614```
1715
18−## Documentation
19−
20−- [Language Design](DESIGN.md) — full language specification
21−- [Standard Library](stdlib.md) — all built-in functions, traits, and types
22−
23−## Project Structure
24−
25−```
26−crates/
27− knot/ Frontend (lexer, parser, AST, diagnostics)
28− knot-runtime/ Runtime library (value representation, SQLite persistence)
29− knot-compiler/ Cranelift-based compiler (CLI: knotc)
30−examples/ Example .knot programs
31−```
32−
33−## Example
34−
35−```knot
36−type Person = {name: Text, age: Int}
37−*people : [Person]
38−
39−&seniors = do
40− people <- *people
41− yield (filter (\p -> p.age > 65) people)
42−
43−main = do
44− set *people = [
45− {name: "Alice", age: 30},
46− {name: "Bob", age: 70}
47− ]
48− seniors <- &seniors
49− println (count seniors)
50− yield {}
51−```
52−
53−## Tests
54−
55−```sh
56−cargo test # All tests
57−cargo test -p knot # Frontend only
58−```
16+A compiled binary creates a `<name>.db` SQLite file for persistence and a
17+`<name>.schema.lock` file for migration tracking.
base.mddeleted−610
@@ -1,610 +0,0 @@
1−# Knot Standard Library
2−
3−Every function listed here is built in to the compiler. No imports needed.
4−
5−## IO
6−
7−Effectful functions return `IO` values — pure descriptions of side effects. IO actions are executed when the program runs (via `main`). Use do-blocks with `<-` to sequence IO actions and extract their results.
8−
9−```knot
10−println : a -> IO {console} {}
11−```
12−Print a value followed by a newline. Works on any type. Returns an IO action.
13−
14−```knot
15−print : a -> IO {console} {}
16−```
17−Print a value without a trailing newline.
18−
19−```knot
20−readLine : IO {console} Text
21−```
22−Read a line from stdin.
23−
24−```knot
25−show : a -> Text
26−```
27−Convert any value to its text representation. Records print as `{field: value, ...}`, relations as `[v1, v2, ...]`, constructors as `Tag {fields}`. This is a pure function (no IO).
28−
29−```knot
30−fork : IO {} {} -> IO {} {}
31−```
32−Run an IO action on a new OS thread (fire-and-forget). Each thread gets its own SQLite connection (WAL mode). Main waits for all threads before exiting.
33−
34−```knot
35−atomic : IO {} a -> IO {} a
36−```
37−Run an IO body in a database transaction. Body must contain only DB operations. Supports `retry` for STM-style waiting.
38−
39−```knot
40−retry : a
41−```
42−Inside `atomic` blocks only. Rolls back the transaction and waits for a relation change, then re-executes.
43−
44−### IO Do-Blocks
45−
46−Use `do` to sequence IO actions:
47−
48−```knot
49−main = do
50− println "What is your name?"
51− name <- readLine
52− println ("Hello, " ++ name)
53− yield {}
54−```
55−
56−The `<-` operator runs an IO action and binds its result. Bare IO expressions (like `println`) are also executed. The overall block type is `IO {union of effects} result`.
57−
58−## Relations
59−
60−Source relations (`*rel`) and derived relations (`&rel`) return `IO {} [T]`. Use `<-` in an IO do-block to unwrap the relation value before passing it to these functions.
61−
62−```knot
63−union : [a] -> [a] -> [a]
64−```
65−Set union of two relations. Duplicates (by structural equality) are removed.
66−
67−```knot
68−count : [a] -> Int
69−```
70−Number of rows in a relation.
71−
72−```knot
73−filter : (a -> Bool) -> [a] -> [a]
74−```
75−Keep rows where the predicate returns `True`.
76−
77−```knot
78−-- Direct call
79−filter (\p -> p.age > 65) *people
80−
81−-- With pipe
82−*people |> filter (\p -> p.age > 65)
83−```
84−
85−```knot
86−map : (a -> b) -> [a] -> [b]
87−```
88−Apply a function to every row. Duplicates in the result are removed (relations are sets).
89−
90−```knot
91−map (\p -> {p.name, p.age}) *people
92−```
93−
94−```knot
95−fold : (b -> a -> b) -> b -> [a] -> b
96−```
97−Left fold over a relation. The accumulator function is curried: `fold f init rel`.
98−
99−```knot
100−-- Sum
101−fold (\acc x -> acc + x) 0 [1, 2, 3] -- 6
102−
103−-- Build text
104−fold (\acc p -> acc ++ p.name ++ " ") "" *people
105−```
106−
107−```knot
108−single : [a] -> a
109−```
110−Extract the single element from a one-element relation. Panics if the relation is empty or has more than one element.
111−
112−```knot
113−single [{name: "Alice"}] -- {name: "Alice"}
114−```
115−
116−```knot
117−diff : [a] -> [a] -> [a]
118−```
119−Set difference. Returns rows in the first relation that are not in the second.
120−
121−```knot
122−diff [1, 2, 3, 4, 5] [2, 4] -- [1, 3, 5]
123−
124−-- Remove known users from a list
125−diff *allUsers *bannedUsers
126−```
127−
128−```knot
129−inter : [a] -> [a] -> [a]
130−```
131−Set intersection. Returns rows that appear in both relations.
132−
133−```knot
134−inter [1, 2, 3, 4, 5] [2, 4, 6] -- [2, 4]
135−
136−-- Find users who are also admins
137−inter *users *admins
138−```
139−
140−```knot
141−sum : (a -> b) -> [a] -> b
142−```
143−Sum a numeric projection over a relation. The projection function extracts the value to sum from each row. Works with both `Int` and `Float`.
144−
145−```knot
146−sum (\x -> x) [10, 20, 30] -- 60
147−sum (\o -> o.amount) *orders -- total of all order amounts
148−*orders |> sum (\o -> o.amount) -- same with pipe
149−```
150−
151−```knot
152−avg : (a -> Float) -> [a] -> Float
153−```
154−Average a numeric projection over a relation. Always returns `Float`. Returns `0.0` for an empty relation.
155−
156−```knot
157−avg (\x -> x) [10.0, 20.0, 30.0] -- 20.0
158−avg (\e -> e.salary) *employees -- mean salary
159−```
160−
161−## Text
162−
163−```knot
164−toUpper : Text -> Text
165−```
166−Convert text to uppercase.
167−
168−```knot
169−toLower : Text -> Text
170−```
171−Convert text to lowercase.
172−
173−```knot
174−take : Int -> Text -> Text
175−```
176−First *n* characters of a text value.
177−
178−```knot
179−take 5 "hello world" -- "hello"
180−```
181−
182−```knot
183−drop : Int -> Text -> Text
184−```
185−Skip the first *n* characters.
186−
187−```knot
188−drop 6 "hello world" -- "world"
189−```
190−
191−```knot
192−length : Text -> Int
193−```
194−Number of characters in a text value.
195−
196−```knot
197−trim : Text -> Text
198−```
199−Strip leading and trailing whitespace.
200−
201−```knot
202−contains : Text -> Text -> Bool
203−```
204−Check whether a text value contains a substring. The first argument is the needle, the second is the haystack.
205−
206−```knot
207−contains "ell" "hello" -- True
208−"hello" |> contains "ell" -- True
209−```
210−
211−```knot
212−reverse : Text -> Text
213−```
214−Reverse a text value.
215−
216−```knot
217−chars : Text -> [Text]
218−```
219−Convert text into a relation of single-character text values.
220−
221−```knot
222−chars "abc" -- ["a", "b", "c"]
223−```
224−
225−## Utility
226−
227−```knot
228−id : a -> a
229−```
230−Identity function. Returns its argument unchanged.
231−
232−```knot
233−not : Bool -> Bool
234−```
235−Boolean negation. Function form of the `!` operator.
236−
237−```knot
238−now : IO {clock} Int
239−```
240−Current time in milliseconds since the Unix epoch. Returns an IO action. Use `<-` in a do-block to get the value:
241−
242−```knot
243−main = do
244− t <- now
245− println ("Time: " ++ show t)
246− yield {}
247−```
248−
249−```knot
250−randomInt : Int -> IO {random} Int
251−```
252−Random integer in `[0, bound)`. Returns an IO action.
253−
254−```knot
255−randomFloat : IO {random} Float
256−```
257−Random float in `[0.0, 1.0)`. Returns an IO action.
258−
259−## JSON
260−
261−```knot
262−toJson : a -> Text
263−```
264−Convert any value to its JSON text representation. Records become JSON objects, relations become JSON arrays, `Int`/`Float` become numbers, `Text` becomes a JSON string, `Bool` becomes `true`/`false`, and `{}` becomes `{}`.
265−
266−```knot
267−toJson {name: "Alice", age: 30} -- "{\"name\":\"Alice\",\"age\":30}"
268−toJson [1, 2, 3] -- "[1,2,3]"
269−```
270−
271−```knot
272−parseJson : Text -> a
273−```
274−Parse a JSON string into a Knot value. JSON objects become records, arrays become relations, strings become `Text`, integers become `Int`, decimals become `Float`, booleans become `Bool`, and `null` becomes `{}`. Handles standard JSON escape sequences.
275−
276−```knot
277−parseJson "{\"x\": 10}" -- {x: 10}
278−parseJson "[1, 2, 3]" -- [1, 2, 3]
279−
280−-- Round-trip
281−let json = toJson {name: "Bob"}
282−parseJson json -- {name: "Bob"}
283−```
284−
285−## Bytes
286−
287−```knot
288−bytesLength : Bytes -> Int
289−```
290−Number of bytes in a byte string.
291−
292−```knot
293−bytesSlice : Int -> Int -> Bytes -> Bytes
294−```
295−Extract a sub-range. `bytesSlice start len bytes` returns `len` bytes starting at offset `start`.
296−
297−```knot
298−bytesConcat : Bytes -> Bytes -> Bytes
299−```
300−Concatenate two byte strings.
301−
302−```knot
303−bytesGet : Int -> Bytes -> Int
304−```
305−Get the byte value (0–255) at the given index.
306−
307−```knot
308−textToBytes : Text -> Bytes
309−```
310−UTF-8 encode a text value into bytes.
311−
312−```knot
313−bytesToText : Bytes -> Text
314−```
315−UTF-8 decode bytes into text.
316−
317−```knot
318−bytesToHex : Bytes -> Text
319−```
320−Encode bytes as a hexadecimal text string.
321−
322−```knot
323−bytesFromHex : Text -> Bytes
324−```
325−Decode a hexadecimal text string into bytes.
326−
327−```knot
328−bytesToHex (textToBytes "hi") -- "6869"
329−bytesFromHex "6869" |> bytesToText -- "hi"
330−```
331−
332−## File System
333−
334−All file system functions return `IO {fs}` values.
335−
336−```knot
337−readFile : Text -> IO {fs} Text
338−```
339−Read the entire contents of a file as text. Returns an IO action.
340−
341−```knot
342−main = do
343− content <- readFile "config.json"
344− println content
345− yield {}
346−```
347−
348−```knot
349−writeFile : Text -> Text -> IO {fs} {}
350−```
351−Write text to a file. Creates the file if it doesn't exist, overwrites if it does. The first argument is the path, the second is the contents.
352−
353−```knot
354−writeFile "output.txt" "hello"
355−```
356−
357−```knot
358−appendFile : Text -> Text -> IO {fs} {}
359−```
360−Append text to a file. Creates the file if it doesn't exist.
361−
362−```knot
363−main = do
364− t <- now
365− appendFile "app.log" ("event at " ++ show t ++ "\n")
366−```
367−
368−```knot
369−fileExists : Text -> IO {fs} Bool
370−```
371−Check whether a file or directory exists at the given path.
372−
373−```knot
374−loadConfig = \path -> do
375− exists <- fileExists path
376− if exists
377− then readFile path
378− else yield "{}"
379−```
380−
381−```knot
382−removeFile : Text -> IO {fs} {}
383−```
384−Delete a file.
385−
386−```knot
387−listDir : Text -> IO {fs} [Text]
388−```
389−List the entries of a directory as a relation of filenames.
390−
391−```knot
392−main = do
393− files <- listDir "."
394− println files
395− yield {}
396−```
397−
398−## Server
399−
400−```knot
401−listen : Int -> (a -> b) -> {}
402−```
403−Start an HTTP server on the given port with a handler function. The handler receives a route ADT value and returns a response. Has the `{network}` effect. See `route` declarations in the language spec for defining typed endpoints.
404−
405−## Concurrency
406−
407−```knot
408−fork : IO {} {} -> IO {} {}
409−```
410−Run an IO action on a new OS thread. Fire-and-forget — the spawned thread runs independently. Each thread gets its own SQLite connection (WAL mode). Main waits for all threads before exiting. Do blocks can be passed without parentheses: `fork do ...`.
411−
412−```knot
413−main = do
414− fork do
415− println "from thread"
416− println "from main"
417− yield {}
418−```
419−
420−```knot
421−atomic : IO {} a -> IO {} a
422−```
423−Run an IO body as a database transaction. Body must contain only DB operations (no console, fs, etc.). Supports `retry` for STM-style waiting.
424−
425−```knot
426−transfer = \from to amount -> atomic do
427− accounts <- *accounts
428− set *accounts = do
429− a <- accounts
430− yield (if a.name == from then {a | balance: a.balance - amount}
431− else if a.name == to then {a | balance: a.balance + amount}
432− else a)
433−```
434−
435−```knot
436−retry : a
437−```
438−Inside `atomic` blocks only. Rolls back the transaction and waits for a relation to change, then re-executes. Implements STM (Software Transactional Memory).
439−
440−```knot
441−waitForReady = atomic do
442− status <- *status
443− where (count (filter (\s -> s.ready) status)) == 0
444− retry
445−```
446−
447−## IO Type Syntax
448−
449−IO types can be annotated in type signatures:
450−
451−```knot
452−IO {console} {} -- IO action with console effect, returns unit
453−IO {fs} Text -- IO action with fs effect, returns Text
454−IO {clock, random} Int -- IO action with multiple effects
455−```
456−
457−Effects tracked in IO types: `console`, `fs`, `network`, `clock`, `random`.
458−
459−## Traits
460−
461−The following traits are defined in the prelude. All types that implement a trait can use its methods without imports.
462−
463−### Eq
464−
465−```knot
466−trait Eq a where
467− eq : a -> a -> Bool
468−```
469−Structural equality as a trait method. Built-in impls for `Int`, `Float`, `Text`, `Bool`.
470−
471−### Ord
472−
473−```knot
474−trait Eq a => Ord a where
475− compare : a -> a -> Int
476−```
477−Ordering comparison. Returns `-1`, `0`, or `1`. Requires `Eq`. Built-in impls for `Int`, `Float`, `Text`.
478−
479−### Num
480−
481−```knot
482−trait Eq a => Num a where
483− add : a -> a -> a
484− sub : a -> a -> a
485− mul : a -> a -> a
486− div : a -> a -> a
487− negate : a -> a
488−```
489−Numeric operations as trait methods. Requires `Eq`. Built-in impls for `Int` and `Float`. Use as a trait bound for generic numeric code:
490−
491−```knot
492−double : Num a => a -> a
493−double = \x -> add x x
494−
495−double 21 -- 42
496−double 1.5 -- 3.0
497−```
498−
499−The methods wrap the corresponding built-in operators (`+`, `-`, `*`, `/`, unary `-`), but can be passed as values and used in higher-order functions:
500−
501−```knot
502−fold add 0 [1, 2, 3] -- 6
503−```
504−
505−### Display
506−
507−```knot
508−trait Display a where
509− display : a -> Text
510−```
511−Convert a value to its text representation. Built-in impls for `Int`, `Float`, `Text`, `Bool`. The `Text` impl returns the value as-is; the others delegate to `show`.
512−
513−Use as a trait bound for generic formatting:
514−
515−```knot
516−displayAll : Display a => [a] -> [Text]
517−displayAll = \rel -> do
518− r <- rel
519− yield (display r)
520−```
521−
522−Implement for your own types:
523−
524−```knot
525−data Color = Red {} | Green {} | Blue {}
526−
527−impl Display Color where
528− display c = case c of
529− Red {} -> "red"
530− Green {} -> "green"
531− Blue {} -> "blue"
532−```
533−
534−### Functor
535−
536−```knot
537−trait Functor (f : Type -> Type) where
538− map : (a -> b) -> f a -> f b
539−```
540−
541−### Applicative
542−
543−```knot
544−trait Functor f => Applicative (f : Type -> Type) where
545− yield : a -> f a
546− ap : f (a -> b) -> f a -> f b
547−```
548−
549−### Monad
550−
551−```knot
552−trait Applicative m => Monad (m : Type -> Type) where
553− bind : (a -> m b) -> m a -> m b
554−```
555−
556−### Alternative
557−
558−```knot
559−trait Applicative f => Alternative (f : Type -> Type) where
560− empty : f a
561− alt : f a -> f a -> f a
562−```
563−Built-in impl for `[]`: `empty = []`, `alt = union`.
564−
565−### Foldable
566−
567−```knot
568−trait Foldable (t : Type -> Type) where
569− fold : (b -> a -> b) -> b -> t a -> b
570−```
571−
572−## Operators
573−
574−These are not functions but are available as infix operators:
575−
576−| Operator | Type | Description |
577−|----------|------|-------------|
578−| `+` | `Int -> Int -> Int` | Addition (also `Float`) |
579−| `-` | `Int -> Int -> Int` | Subtraction |
580−| `*` | `Int -> Int -> Int` | Multiplication |
581−| `/` | `Int -> Int -> Int` | Division |
582−| `==` | `a -> a -> Bool` | Structural equality |
583−| `!=` | `a -> a -> Bool` | Structural inequality |
584−| `<` | `a -> a -> Bool` | Less than |
585−| `>` | `a -> a -> Bool` | Greater than |
586−| `<=` | `a -> a -> Bool` | Less than or equal |
587−| `>=` | `a -> a -> Bool` | Greater than or equal |
588−| `&&` | `Bool -> Bool -> Bool` | Logical and |
589−| `\|\|` | `Bool -> Bool -> Bool` | Logical or |
590−| `++` | `Text -> Text -> Text` | Text concatenation |
591−| `\|>` | `a -> (a -> b) -> b` | Pipe forward |
592−| `-` (unary) | `Int -> Int` | Negation |
593−| `!` (unary) | `Bool -> Bool` | Logical not |
594−
595−## Currying
596−
597−All multi-argument standard library functions support partial application. You can pass fewer arguments to get back a function:
598−
599−```knot
600−-- Partially apply filter
601−isOld = filter (\p -> p.age > 65)
602−isOld *people -- same as: filter (\p -> p.age > 65) *people
603−
604−-- Pipe-forward works because filter returns a function after one arg
605−*people |> filter (\p -> p.age > 65) |> map (\p -> p.name)
606−
607−-- Partially apply fold
608−sum = fold (\acc x -> acc + x) 0
609−sum [1, 2, 3] -- 6
610−```
crates/knot-compile-rt/Cargo.tomlnew file+14
@@ -0,0 +1,14 @@
1+[package]
2+name = "knot-compile-rt"
3+version.workspace = true
4+edition.workspace = true
5+
6+[lib]
7+# Staticlib so the linker can embed this into compiled knot programs alongside
8+# libknot_runtime.a; rlib so the knot binary / tests can use it as a normal dep.
9+crate-type = ["staticlib", "rlib"]
10+
11+[dependencies]
12+knot-jit = { workspace = true }
13+knot-runtime = { workspace = true }
14+libc = { workspace = true }
crates/knot-compile-rt/src/lib.rsnew file+117
@@ -0,0 +1,117 @@
1+//! The `compile` builtin's JIT glue, linked into compiled knot programs as a
2+//! separate staticlib archive (alongside libknot_runtime.a). knot-runtime
3+//! cannot depend on the compiler directly (knot-compiler's build.rs builds
4+//! knot-runtime — a dep here would recurse), so this crate bridges: it depends
5+//! on knot-jit (→ knot-compiler) and knot-runtime, exposes the compile
6+//! implementation as a `#[no_mangle]` extern, and registers it into
7+//! knot-runtime's `COMPILE_IMPL` slot at program startup.
8+
9+use std::ffi::c_void;
10+
11+/// Adapter bridging the JIT's `c_void`-typed set-code signature to the
12+/// runtime's `knot_value_set_code`. Both pointer args are opaque to the JIT
13+/// (a `Value*` and a `CodeHandle*`), so the casts are just pointer repoints.
14+unsafe extern "C" fn set_code(v: *mut c_void, handle: *const c_void) {
15+ unsafe {
16+ knot_runtime::knot_value_set_code(v.cast(), handle.cast());
17+ }
18+}
19+
20+/// The compile implementation registered into knot-runtime. Matches the
21+/// `CompileImpl` signature in knot-runtime: takes the program source and a db
22+/// handle, writes the inferred type into the out-params, returns the forced
23+/// value (or null on any compile error).
24+///
25+/// `out_ty` is a freshly `malloc`'d, NUL-free buffer of length `*out_ty_len`
26+/// (caller frees with `libc::free`), or null when the type is unknown.
27+///
28+/// # Safety
29+///
30+/// Called from knot-runtime via the registered function pointer. `src_ptr` /
31+/// `expected_ptr` must point to `src_len` / `expected_len` valid bytes (need
32+/// not be NUL-terminated); `db` must be a live knot-runtime db handle. Any
33+/// non-null out-param pointer must be valid for writes of its payload.
34+#[unsafe(no_mangle)]
35+pub unsafe extern "C" fn knot_compile_impl(
36+ src_ptr: *const u8,
37+ src_len: usize,
38+ db: *mut c_void,
39+ expected_ptr: *const u8,
40+ expected_len: usize,
41+ out_ty_ptr: *mut *mut u8,
42+ out_ty_len: *mut usize,
43+ out_rels_ptr: *mut *mut u8,
44+ out_rels_len: *mut usize,
45+ out_err_ptr: *mut *mut u8,
46+ out_err_len: *mut usize,
47+) -> *mut knot_runtime::Value {
48+ // Null out-params up front so a compile error leaves them well-defined.
49+ unsafe {
50+ knot_runtime::compile_out_params_init([
51+ (out_ty_ptr, out_ty_len),
52+ (out_rels_ptr, out_rels_len),
53+ (out_err_ptr, out_err_len),
54+ ]);
55+ }
56+
57+ let src_bytes = unsafe { std::slice::from_raw_parts(src_ptr, src_len) };
58+ let Ok(source) = std::str::from_utf8(src_bytes) else {
59+ return std::ptr::null_mut();
60+ };
61+ // The host's expected type as a source-annotation string; empty = unpinned
62+ // (no subsumption check). Forwarded to the JIT so it runs the real-type
63+ // subsumption inside its own inference context.
64+ let expected: Option<&str> = if expected_ptr.is_null() || expected_len == 0 {
65+ None
66+ } else {
67+ let b = unsafe { std::slice::from_raw_parts(expected_ptr, expected_len) };
68+ std::str::from_utf8(b).ok()
69+ };
70+
71+ // This is the `knot_compile_impl` a *compiled program* links — the
72+ // `base.compile` boundary that runs in the host's process. Contain any
73+ // panic (in compilation or the JIT'd snippet) so it surfaces as an Err
74+ // instead of unwinding across `extern "C"` into the host. Shared with the
75+ // compiler binary's impl via `run_compile_contained` so the two can't drift.
76+ unsafe {
77+ knot_runtime::run_compile_contained(
78+ db,
79+ out_ty_ptr,
80+ out_ty_len,
81+ out_rels_ptr,
82+ out_rels_len,
83+ out_err_ptr,
84+ out_err_len,
85+ |db| {
86+ // A clean compile error: report it via out_err (as null), which
87+ // the shared helper leaves to the runtime's `Result.Err`. We
88+ // signal it by writing the message here and returning None.
89+ match knot_jit::compile_and_run(
90+ source,
91+ db,
92+ expected,
93+ Some(set_code as unsafe extern "C" fn(*mut c_void, *const c_void)),
94+ ) {
95+ Ok(cv) => Some(knot_runtime::CompileOutcome {
96+ // knot-jit treats the value as opaque *mut c_void; here
97+ // it's a knot-runtime Value again.
98+ value: cv.value as *mut knot_runtime::Value,
99+ ty: cv.ty,
100+ relations: cv.relations,
101+ }),
102+ Err(e) => {
103+ knot_runtime::compile_write_out(&e.0, out_err_ptr, out_err_len);
104+ None
105+ }
106+ }
107+ },
108+ )
109+ }
110+}
111+
112+/// Install `knot_compile_impl` into knot-runtime's `COMPILE_IMPL` slot. Called
113+/// once from the generated program's `main` before user code runs.
114+#[unsafe(no_mangle)]
115+pub extern "C-unwind" fn knot_compile_rt_init() {
116+ knot_runtime::knot_register_compile_impl(knot_compile_impl as *mut c_void);
117+}
crates/knot-compiler/Cargo.toml+26−1
@@ -8,15 +8,40 @@ name = "knot_compiler"
88path = "src/lib.rs"
99
1010[[bin]]
11−name = "knotc"
11+name = "knot"
1212path = "src/main.rs"
1313
1414[dependencies]
1515knot = { workspace = true }
16+knot-descriptor = { workspace = true }
1617knot-runtime = { workspace = true }
1718cranelift-codegen = { workspace = true }
1819cranelift-frontend = { workspace = true }
1920cranelift-module = { workspace = true }
2021cranelift-object = { workspace = true }
2122cranelift-native = { workspace = true }
23+libc = { workspace = true }
2224target-lexicon = { workspace = true }
25+# .eh_frame emission for generated functions (see src/unwind.rs) — versions
26+# pinned to what cranelift 0.131 itself uses.
27+gimli = { version = "0.33", default-features = false, features = ["write"] }
28+object = { version = "0.39", default-features = false, features = ["write"] }
29+# Z3 for compile-time refinement-predicate implication (SMT subtyping between
30+# refined types). `gh-release` statically links a prebuilt libz3 (downloaded at
31+# build time), so the compiler binary is self-contained — no runtime Z3 dep.
32+z3 = { version = "0.20", default-features = false, features = ["gh-release"] }
33+# Content-hash of the embedded runtime archive, used at link time to reject a
34+# stale on-disk libknot_runtime.a (replaces the old mtime heuristic).
35+blake3 = { version = "1", default-features = false }
36+# Order-preserving map for record/variant field storage: source definition order
37+# is retained so implicit-field (^name) resolution can try fields in
38+# bottom-to-top declaration order instead of BTreeMap's alphabetical order.
39+indexmap = { version = "2", default-features = false }
40+cranelift-jit = "0.131"
41+
42+[build-dependencies]
43+blake3 = { version = "1", default-features = false }
44+
45+[dev-dependencies]
46+knot-descriptor = { workspace = true }
47+
crates/knot-compiler/build.rs+134−50
@@ -4,13 +4,44 @@ fn is_valid_lib(p: &Path) -> bool {
44 p.exists() && std::fs::metadata(p).map(|m| m.len() > 0).unwrap_or(false)
55}
66
7−/// Check that `lib` is at least as new as the runtime source file.
7+/// Max mtime across all runtime source files: every `*.rs` under
8+/// `crates/knot-runtime/src` (recursively — the crate has lib.rs, log.rs,
9+/// tui.rs and may gain more) plus the runtime crate's Cargo.toml.
10+fn runtime_src_mtime(workspace_root: &Path) -> Option<std::time::SystemTime> {
11+ fn consider(p: &Path, newest: &mut Option<std::time::SystemTime>) {
12+ if let Ok(m) = std::fs::metadata(p).and_then(|m| m.modified())
13+ && newest.map(|n| m > n).unwrap_or(true)
14+ {
15+ *newest = Some(m);
16+ }
17+ }
18+ fn walk(dir: &Path, newest: &mut Option<std::time::SystemTime>) {
19+ if let Ok(rd) = std::fs::read_dir(dir) {
20+ for entry in rd.flatten() {
21+ let p = entry.path();
22+ if p.is_dir() {
23+ walk(&p, newest);
24+ } else if p.extension().map(|e| e == "rs").unwrap_or(false) {
25+ consider(&p, newest);
26+ }
27+ }
28+ }
29+ }
30+ let mut newest = None;
31+ walk(&workspace_root.join("crates/knot-runtime/src"), &mut newest);
32+ consider(
33+ &workspace_root.join("crates/knot-runtime/Cargo.toml"),
34+ &mut newest,
35+ );
36+ newest
37+}
38+
39+/// Check that `lib` is at least as new as every runtime source file.
840fn is_fresh_lib(lib: &Path, workspace_root: &Path) -> bool {
941 if !is_valid_lib(lib) {
1042 return false;
1143 }
12− let src = workspace_root.join("crates/knot-runtime/src/lib.rs");
13− let src_mtime = std::fs::metadata(&src).and_then(|m| m.modified()).ok();
44+ let src_mtime = runtime_src_mtime(workspace_root);
1445 let lib_mtime = std::fs::metadata(lib).and_then(|m| m.modified()).ok();
1546 match (src_mtime, lib_mtime) {
1647 (Some(s), Some(l)) => l >= s,
@@ -28,7 +59,12 @@ fn main() {
2859 let dest = out_path.join("libknot_runtime.a");
2960 let _ = std::fs::remove_file(&dest);
3061
31− // Find libknot_runtime.a — try multiple strategies:
62+ // Find libknot_runtime.a — try multiple strategies. NOTE: this build
63+ // script deliberately does NOT spawn a nested `cargo build` — doing so
64+ // deadlocks on the package-cache lock whenever the outer build holds it
65+ // (the common case). Staleness is instead handled at LINK time by
66+ // `rebuild_runtime_at_exe` in main.rs, which runs as its own process and
67+ // can safely invoke cargo. Here we only locate an existing archive.
3268 let runtime_path = None
3369 // 1. Explicit env var override
3470 .or_else(|| {
@@ -37,68 +73,116 @@ fn main() {
3773 .map(PathBuf::from)
3874 .filter(|p| is_valid_lib(p))
3975 })
40− // 2. Walk up from OUT_DIR (works in normal `cargo build` within workspace)
41− .or_else(|| {
42− out_path
43− .ancestors()
44− .map(|p| p.join("libknot_runtime.a"))
45− .find(|p| is_fresh_lib(p, &workspace_root))
46− })
47− // 3. Check workspace target directory, respecting CARGO_TARGET_DIR
76+ // 2. Walk up from OUT_DIR (works in normal `cargo build` within
77+ // workspace), but stop at the cargo target directory boundary. Going
78+ // past it would let a stray libknot_runtime.a in an unrelated
79+ // ancestor (e.g. $HOME) be embedded on mtime alone.
4880 .or_else(|| {
4981 let target_dir = std::env::var("CARGO_TARGET_DIR")
5082 .map(PathBuf::from)
5183 .unwrap_or_else(|_| workspace_root.join("target"));
52− ["release", "debug"]
53− .iter()
54− .map(|profile| target_dir.join(profile).join("libknot_runtime.a"))
84+ // Canonicalize so the `..`-laden workspace_root path compares
85+ // correctly against cargo's canonical OUT_DIR ancestors.
86+ let boundary = std::fs::canonicalize(&target_dir).unwrap_or(target_dir.clone());
87+ out_path
88+ .ancestors()
89+ .take_while(|p| p.starts_with(&boundary))
90+ .map(|p| p.join("libknot_runtime.a"))
91+ .chain(
92+ ["release", "debug"]
93+ .iter()
94+ .map(|profile| target_dir.join(profile).join("libknot_runtime.a")),
95+ )
5596 .find(|p| is_fresh_lib(p, &workspace_root))
56− })
57− // 4. Build the runtime ourselves (needed for `cargo install` where the
58− // staticlib isn't produced as a dependency artifact). Uses a separate
59− // target-dir inside OUT_DIR to avoid cargo lock contention.
60− .or_else(|| try_build_runtime(&workspace_root, &out_path));
97+ });
6198
6299 // Copy the runtime into OUT_DIR so include_bytes! can find it with a stable path
63100 if let Some(src) = runtime_path {
64101 std::fs::copy(&src, &dest).expect("failed to copy libknot_runtime.a to OUT_DIR");
65102 println!("cargo:rustc-cfg=has_embedded_runtime");
103+ // Stamp the content hash of the archive we're embedding. At link time
104+ // the compiler hashes any on-disk candidate and compares — a match
105+ // means byte-identical bits (fresh), regardless of mtimes or which
106+ // archive produced them.
107+ let bytes = std::fs::read(&dest).expect("failed to read embedded runtime for hashing");
108+ let hash = blake3::hash(&bytes).to_hex();
109+ println!("cargo:rustc-env=KNOT_EMBEDDED_RUNTIME_HASH={hash}");
66110 }
67111
68112 println!("cargo:rustc-check-cfg=cfg(has_embedded_runtime)");
69− println!("cargo:rerun-if-changed=../knot-runtime/src/lib.rs");
70−}
71113
72−fn try_build_runtime(workspace_root: &Path, out_path: &Path) -> Option<PathBuf> {
73− let runtime_manifest = workspace_root.join("crates/knot-runtime/Cargo.toml");
74− if !runtime_manifest.exists() {
75− return None;
76− }
77−
78− let target_dir = out_path.join("rt-build");
79− let profile = std::env::var("PROFILE").unwrap_or_else(|_| "release".into());
80− let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
81−
82− let mut cmd = std::process::Command::new(cargo);
83− cmd.arg("build")
84− .arg("-p")
85− .arg("knot-runtime")
86− .arg("--target-dir")
87− .arg(&target_dir)
88− .current_dir(workspace_root);
89− if profile == "release" {
90− cmd.arg("--release");
114+ // ── JIT compile-runtime archive (knot-compile-rt) ──
115+ // Always linked into compiled programs so `base.compile` works. Found in
116+ // the target dir (the `knot` binary depends on knot-compile-rt, so cargo
117+ // builds it first); embedded so the compiler binary stays self-contained.
118+ // No self-spawned build here (unlike the runtime fallback) — knot-compile-rt
119+ // is always a normal dependency of the binary that drives linking.
120+ {
121+ let crt_dest = out_path.join("libknot_compile_rt.a");
122+ let _ = std::fs::remove_file(&crt_dest);
123+ let target_dir = std::env::var("CARGO_TARGET_DIR")
124+ .map(PathBuf::from)
125+ .unwrap_or_else(|_| workspace_root.join("target"));
126+ let crt_src = out_path
127+ .ancestors()
128+ .take_while(|p| p.starts_with(&target_dir))
129+ .map(|p| p.join("libknot_compile_rt.a"))
130+ .find(|p| is_valid_lib(p))
131+ .or_else(|| {
132+ ["release", "debug"]
133+ .iter()
134+ .map(|prof| target_dir.join(prof).join("libknot_compile_rt.a"))
135+ .find(|p| is_valid_lib(p))
136+ });
137+ if let Some(src) = crt_src {
138+ std::fs::copy(&src, &crt_dest).expect("failed to copy libknot_compile_rt.a to OUT_DIR");
139+ println!("cargo:rustc-cfg=has_embedded_compile_rt");
140+ }
91141 }
142+ println!("cargo:rustc-check-cfg=cfg(has_embedded_compile_rt)");
143+ println!("cargo:rerun-if-changed=../knot-compile-rt/src");
144+ println!("cargo:rerun-if-changed=../knot-compile-rt/Cargo.toml");
145+ // Watch the whole runtime source tree (cargo tracks directories
146+ // recursively), not just lib.rs — log.rs/tui.rs edits must also
147+ // refresh the embedded runtime.
148+ println!("cargo:rerun-if-changed=../knot-runtime/src");
149+ println!("cargo:rerun-if-changed=../knot-runtime/Cargo.toml");
92150
93− eprintln!("build.rs: building knot-runtime staticlib...");
94− if cmd.status().map(|s| s.success()).unwrap_or(false) {
95− let lib_path = target_dir
96− .join(if profile == "release" { "release" } else { "debug" })
97− .join("libknot_runtime.a");
98− if is_valid_lib(&lib_path) {
99− return Some(lib_path);
151+ // A-experiment: whole-archive link the knot-runtime staticlib into the
152+ // knot binary and export its knot_* symbols into the dynamic table so the
153+ // JIT (running in-process for compile-time predicate eval) can resolve
154+ // them via dlsym(RTLD_DEFAULT). The runtime is found the same way as the
155+ // embedded archive above; this only ADDS link directives.
156+ // --allow-multiple-definition: the staticlib bundles compiler_builtins
157+ // objects that the binary already links from rustc's rlib — whole-archive
158+ // would otherwise double-define them. The first (rustc) definition wins.
159+ {
160+ let target_dir = std::env::var("CARGO_TARGET_DIR")
161+ .map(PathBuf::from)
162+ .unwrap_or_else(|_| workspace_root.join("target"));
163+ let rt = out_path
164+ .ancestors()
165+ .take_while(|p| p.starts_with(&target_dir))
166+ .map(|p| p.join("libknot_runtime.a"))
167+ .find(|p| is_valid_lib(p))
168+ .or_else(|| {
169+ ["release", "debug"]
170+ .iter()
171+ .map(|prof| target_dir.join(prof).join("libknot_runtime.a"))
172+ .find(|p| is_valid_lib(p))
173+ });
174+ if let Some(rt) = rt {
175+ if cfg!(target_os = "macos") {
176+ println!("cargo:rustc-link-arg=-Wl,-force_load,{}", rt.display());
177+ println!("cargo:rustc-link-arg=-Wl,-export_dynamic");
178+ } else {
179+ println!("cargo:rustc-link-arg=-Wl,--allow-multiple-definition");
180+ println!(
181+ "cargo:rustc-link-arg=-Wl,--whole-archive,{},--no-whole-archive",
182+ rt.display()
183+ );
184+ println!("cargo:rustc-link-arg=-Wl,--export-dynamic");
185+ }
100186 }
101187 }
102−
103− None
104188}
crates/knot-compiler/src/base.rs+461−82
@@ -1,107 +1,486 @@
1−//! Built-in trait declarations and standard implementations.
1+//! Standard prelude: ordinary polymorphic functions injected into every program.
22//!
3−//! Defines the core trait hierarchy (Eq, Ord, Num, Semigroup, Display, Functor,
4−//! Applicative, Monad, Alternative, Foldable) and primitive/[] implementations. Trait
5−//! declarations and simple impls are parsed from Knot source; complex [] impls
6−//! for HKT traits (Functor, Applicative, Monad, Foldable) are registered
7−//! directly in codegen to avoid span collision issues.
3+//! The prelude is a small set of plain functions. Builtin operator semantics
4+//! (`+`, `<`, `++`, unary `-`, `==`) are enforced intrinsically by the type
5+//! checker and code generator, and monadic do-blocks dispatch structurally, so
6+//! no trait declarations are needed here.
7+//!
8+//! A `.knot` file is a single expression, so the prelude is injected by
9+//! wrapping the program: `with { …prelude record… } <program>`. The program's
10+//! own bindings shadow the prelude's (a `with` body's later/literal bindings
11+//! win for the same name).
812
913use knot::ast;
10−use std::collections::HashSet;
1114
12−/// Knot source for built-in trait declarations and simple implementations.
13−/// Complex [] impls for HKT traits are registered directly in codegen.
15+/// Byte offset added to every parsed prelude span so prelude spans can never
16+/// collide with user-file spans (bug B39). Chosen far above any plausible real
17+/// file size and above `desugar::SYNTH_SPAN_BASE` (1 << 31) so it also clears
18+/// the synthesized monad-span range.
19+pub(crate) const PRELUDE_SPAN_OFFSET: usize = 1 << 40;
20+
21+/// Knot source for the standard prelude. The ONLY binding the prelude splices
22+/// into scope is `base` — a record holding every standard-library function.
23+/// User code refers to `base.println`, `base.map`, etc.; nothing else enters
24+/// the environment by default.
25+///
26+/// The prelude's own polymorphic helpers (`min`/`max`/`when`/`unless`) are
27+/// defined as nested lambdas inside the `base` record. The data declaration
28+/// `Ordering` is hoisted out (a `data` decl cannot be a record *field value*),
29+/// then re-exposed as `base.Ordering` by the compiler's ctor routing.
1430const PRELUDE_SOURCE: &str = r#"
15−trait Eq a where
16− eq : a -> a -> Bool
31+{
32+base {
33+_ min (\a b -> ? (a < b)
34+ Bool.True {} a
35+ Bool.False {} b)
1736
18−data Ordering = LT {} | EQ {} | GT {}
37+_ max (\a b -> ? (a > b)
38+ Bool.True {} a
39+ Bool.False {} b)
1940
20−trait Eq a => Ord a where
21− compare : a -> a -> Ordering
41+-- `the (Type) value`: type ascription as a regular type-witness function. The
42+-- leading `\(Type T)` parameter is an erased type argument (no runtime
43+-- representation); `the` returns its value argument unchanged, so
44+-- `base.the (Int 1) 41` is `41` checked to be an `Int 1`. The compiler consumes
45+-- the type argument at the call site (the type-witness diversion) and erases it.
46+_ the (\(Type T) -> \x -> x)
2247
23−trait Functor (f : Type -> Type) where
24− map : (a -> b) -> f a -> f b
48+Bool -> IO {} -> IO {} when (\cond action -> ? (cond)
49+ Bool.True {} action
50+ Bool.False {} yield {})
2551
26−trait Functor f => Applicative (f : Type -> Type) where
27− yield : a -> f a
28− ap : f (a -> b) -> f a -> f b
52+Bool -> IO {} -> IO {} unless (\cond action -> ? (cond)
53+ Bool.True {} yield {}
54+ Bool.False {} action)
2955
30−trait Applicative m => Monad (m : Type -> Type) where
31− bind : (a -> m b) -> m a -> m b
56+-- Structured logging. `log` carries a `(<>logCtx)` fold constraint: at each
57+-- callsite the compiler merges every in-scope `logCtx` record (`base.unify`
58+-- from `{}`, innermost scope wins) and passes the merged record as the hidden
59+-- dictionary, so the caller writes only the level and message. The body reads
60+-- the merged context via `^logCtx` and emits through `knot_emit_log`
61+-- (`emitLog`). The `debug`/`info`/`warn`/`error` wrappers fix the level; each
62+-- is self-contained (a record field can't reference a sibling bare) and
63+-- re-declares the constraint so the caller's context threads through.
64+(<>logCtx) => Level -> Text -> IO {} log (\level msg -> emitLog level msg ^logCtx)
3265
33−trait Applicative f => Alternative (f : Type -> Type) where
34− empty : f a
35− alt : f a -> f a -> f a
66+(<>logCtx) => Text -> IO {} debug (\msg -> emitLog (Level.Debug {}) msg ^logCtx)
3667
37−trait Foldable (t : Type -> Type) where
38− fold : (b -> a -> b) -> b -> t a -> b
68+(<>logCtx) => Text -> IO {} info (\msg -> emitLog (Level.Info {}) msg ^logCtx)
3969
40−trait Eq a => Num a where
41− add : a -> a -> a
42− sub : a -> a -> a
43− mul : a -> a -> a
44− div : a -> a -> a
45− negate : a -> a
70+(<>logCtx) => Text -> IO {} warn (\msg -> emitLog (Level.Warn {}) msg ^logCtx)
4671
47−trait Semigroup a where
48− append : a -> a -> a
72+(<>logCtx) => Text -> IO {} error (\msg -> emitLog (Level.Error {}) msg ^logCtx)
4973
50−trait Display a where
51− display : a -> Text
74+-- List ADT namespace (`base.list.*`). Each field is a codegen builtin
75+-- (`Var(listX)` resolves to the registered knot_list_* function value), so
76+-- the prelude record needs no self-reference for recursion. `nil` is the
77+-- `listNil` builtin (a 1-arg fn that ignores its argument and returns the
78+-- empty list); call it as `base.list.nil {}` so the element type stays
79+-- polymorphic per call site. The rest are the function values themselves.
80+list {
81+nil listNil
82+cons listCons
83+isNil listIsNil
84+head listHead
85+tail listTail
86+length listLength
87+map listMap
88+filter listFilter
89+fold listFold
90+reverse listReverse
91+append listAppend
92+fromRelation listFromRelation
93+toRelation listToRelation
94+}
5295
53−impl Display Int where
54− display x = show x
96+-- Vec namespace (`base.vec.*`): the concrete-sequence overloads of the data
97+-- ops, resolved via `^count` (etc.) against the relation-typed `base.*`
98+-- forms. The `^` resolver searches the full record with name+type matching,
99+-- so a `Vec a` argument reaches `base.vec.count` (nested here) even though
100+-- `base.count : [a] -> Int u` fails to unify with it.
101+vec {
102+count vecCount
103+}
55104
56−impl Display Float where
57− display x = show x
105+-- Num namespace (`base.num.*`): numeric negation, resolved TYPE-DIRECTED via
106+-- `^neg`. Each impl has an EXPLICIT concrete signature so the `^` resolver's
107+-- unify-based match picks by the argument's numeric type (an un-annotated body
108+-- could infer polymorphic and mis-dispatch — see `morph`). Both wrap the
109+-- `valueNegate` runtime primitive; the signature is what steers `^neg`.
110+-- Numeric negation is written `(^neg) x` (unary `-x` was removed).
111+num {
112+int { Int u -> Int u neg valueNegate }
113+float { Float u -> Float u neg valueNegate }
114+}
58115
59−impl Display Text where
60− display x = x
116+-- Morph namespace (`base.morph.*`): type-directed conversions resolved by the
117+-- `^into` projection. Each `<from>To<to>` record holds an `into : S -> T`
118+-- function with an EXPLICIT concrete signature (an un-annotated body could
119+-- infer a polymorphic type and silently mis-dispatch — see the `^into`
120+-- resolver's unify-based candidate match). Fallible conversions return
121+-- `Maybe`. Conversions needing a primitive not exposed as a base builtin are
122+-- written as lambdas over the existing builtins.
123+morph {
124+textToBytes { Text -> Bytes into textToBytes }
125+bytesToText { Bytes -> Maybe Text into bytesToText }
126+bytesToHex { Bytes -> Text into bytesToHex }
127+textToBytesFromHex { Text -> Maybe Bytes into bytesFromHex }
128+intToFloat { Int 1 -> Float 1 into intToFloat }
129+textToInt { Text -> Maybe (Int 1) into textToInt }
130+textToFloat { Text -> Maybe (Float 1) into textToFloat }
131+intToText { Int 1 -> Text into (\n -> show n) }
132+floatToText { Float 1 -> Text into (\f -> show f) }
133+boolToText { Bool -> Text into (\b -> show b) }
134+}
135+}
136+}
137+"#;
61138
62−impl Display Bool where
63− display x = show x
139+/// Stdlib builtins exposed as fields of `base` (`base.map`, `base.filter`, …).
140+/// Each is registered by codegen as a curried function value in `global_fns`;
141+/// the prelude record references them as `name: Var(name)` so the `base`
142+/// record assembles to real function values. Dispatch-only builtins
143+/// (`println`, `show`, `now`, …) and intrinsic constructors
144+/// (`Just`/`Nothing`/`Ok`/`Err`/`True`/`False`) are routed separately by the
145+/// compiler, not via this record.
146+pub(crate) const BASE_STDLIB_FNS: &[&str] = &[
147+ "all",
148+ "any",
149+ "appendFile",
150+ "avg",
151+ "bytesConcat",
152+ "bytesFromHex",
153+ "bytesGet",
154+ "bytesLength",
155+ "bytesSlice",
156+ "bytesToHex",
157+ "bytesToText",
158+ "chars",
159+ "contains",
160+ "countWhere",
161+ "decrypt",
162+ "diff",
163+ "distinct",
164+ "dress",
165+ "drop",
166+ "endsWith",
167+ "elem",
168+ "encrypt",
169+ "fileExists",
170+ "filter",
171+ "fold",
172+ "forEach",
173+ "fork",
174+ "hash",
175+ "hexDecode",
176+ "id",
177+ "inter",
178+ "length",
179+ "listDir",
180+ "map",
181+ "filterCtor",
182+ "maxOn",
183+ "minOn",
184+ "not",
185+ "parseJson",
186+ "race",
187+ "randomInt",
188+ "readFile",
189+ "removeFile",
190+ "reverse",
191+ "run",
192+ "run1",
193+ "sign",
194+ "sleep",
195+ "sortBy",
196+ "sortByDesc",
197+ "startsWith",
198+ "strip",
199+ "stripFloatUnit",
200+ "stripUnit",
201+ "take",
202+ "textToBytes",
203+ "toAsciiLower",
204+ "toAsciiUpper",
205+ "abs",
206+ "intMin",
207+ "intMax",
208+ "clamp",
209+ "unify",
210+ "toJson",
211+ "toLower",
212+ "toUpper",
213+ "traverse",
214+ "trim",
215+ "trimAscii",
216+ "ltrimAscii",
217+ "rtrimAscii",
218+ "byteLength",
219+ "upsertBy",
220+ "verify",
221+ "withFloatUnit",
222+ "withUnit",
223+ "writeFile",
224+ // Numeric/text conversions.
225+ "floor",
226+ "intToFloat",
227+ "textToInt",
228+ "textToFloat",
229+ // Relation query forms as first-class function values (`base.count`,
230+ // `base.union`, `base.sum`, `base.bind`). Each is registered as a curried
231+ // function value in codegen; the call-site SQL-pushdown optimization
232+ // recognizes both the bare (`count rel`) and namespaced (`base.count rel`)
233+ // application head. `bind` here is relation flatMap (`knot_relation_bind`).
234+ "count",
235+ "union",
236+ "sum",
237+ "bind",
238+ // Console IO builtins (registered as stdlib function values in codegen).
239+ "println",
240+ "print",
241+ "putLine",
242+ "show",
243+ // Value → evaluable Knot source (dependency-collecting). Registered as a
244+ // stdlib function value in codegen.
245+ "extract",
246+ // In-process JIT compile+eval: `compile : Text -> Maybe a`. Registered as
247+ // a stdlib function value in codegen (calls knot_builtin_compile, which
248+ // dispatches through the runtime's registered compile implementation).
249+ "compile",
250+ // 0-arg IO builtins. Each is a re-runnable IO thunk (`Value::IO(thunk, _)`)
251+ // produced fresh by the bare-`Var` dispatch, so holding the action as a
252+ // record field is safe — forcing `base.now` twice runs `knot_now` twice.
253+ "now",
254+ "readLine",
255+ "randomFloat",
256+ "randomUuid",
257+ "generateKeyPair",
258+ "generateSigningKeyPair",
259+];
64260
65−impl Alternative [] where
66− empty = []
67− alt a b = union a b
68−"#;
261+/// Names a bare user-span `Var` may NOT reference directly; the hard gate
262+/// (option A) requires `base.<name>` instead. This is every stdlib builtin
263+/// plus the prelude's polymorphic helpers. Constructors
264+/// (`Just`/`Nothing`/`Ok`/`Err`/`True`/`False`) are deliberately NOT here —
265+/// they route through the dedicated `base.Ctor` arms, which gate separately.
266+pub(crate) fn is_gated_stdlib(name: &str) -> bool {
267+ BASE_STDLIB_FNS.contains(&name) || matches!(name, "min" | "max" | "when" | "unless")
268+}
69269
70−/// Parse the prelude source and prepend its declarations to the user's module.
71−/// Skips traits and their impls if the user already defines the same trait.
72−pub fn inject_prelude(module: &mut ast::Module) {
73− // Collect user-defined trait names to avoid conflicts
74− let user_traits: HashSet<String> = module
75− .decls
76− .iter()
77− .filter_map(|d| {
78− if let ast::DeclKind::Trait { name, .. } = &d.node {
79− Some(name.clone())
80− } else {
81− None
82− }
83− })
84− .collect();
270+/// Server special forms (`fetch`/`fetchWith`/`listen`/`listenOn`) and the STM
271+/// primitive `retry`. These are compile-time macros, NOT `base` record fields,
272+/// but the bare form is still gated for consistency — the user must write
273+/// `base.fetch`, `base.listen`, `base.retry`. Codegen routes the namespaced
274+/// head through the same macro dispatch (`server_form_name`); `retry` keeps its
275+/// own "only inside `atomic`" check, which fires first.
276+pub(crate) fn is_gated_special_form(name: &str) -> bool {
277+ matches!(
278+ name,
279+ "fetch" | "fetchWith" | "listen" | "listenOn" | "retry" | "todo" | "trace"
280+ )
281+}
85282
86− // Parse prelude
283+/// Parse the prelude source and return the fully-assembled `base` record
284+/// expression (the prelude helpers `min`/`max`/`when`/`unless` plus every
285+/// stdlib builtin as a `name: Var(name)` field), with all spans shifted by
286+/// `PRELUDE_SPAN_OFFSET`. This is the single source of truth for the `base`
287+/// record's VALUE; infer (`bind_base_record`) and codegen
288+/// (`define_base_record`) each compile it to bind `base` globally — there is
289+/// no `with` wrapper around the program.
290+pub(crate) fn prelude_base_record() -> ast::Expr {
87291 let lexer = knot::lexer::Lexer::new(PRELUDE_SOURCE);
88− let (tokens, _) = lexer.tokenize();
292+ let (tokens, lex_diags) = lexer.tokenize();
293+ assert!(
294+ !lex_diags
295+ .iter()
296+ .any(|d| d.severity == knot::diagnostic::Severity::Error),
297+ "prelude failed to lex: {:?}",
298+ lex_diags
299+ );
89300 let parser = knot::parser::Parser::new(PRELUDE_SOURCE.to_string(), tokens);
90− let (prelude_module, _) = parser.parse_module();
91−
92− // Filter out traits/impls that the user already defines
93− let filtered: Vec<ast::Decl> = prelude_module
94− .decls
95− .into_iter()
96− .filter(|d| match &d.node {
97− ast::DeclKind::Trait { name, .. } => !user_traits.contains(name),
98− ast::DeclKind::Impl { trait_name, .. } => !user_traits.contains(trait_name),
99− _ => true,
100− })
101− .collect();
102−
103− // Prepend prelude declarations before user declarations
104− let mut all_decls = filtered;
105− all_decls.append(&mut module.decls);
106− module.decls = all_decls;
301+ let (mut prelude_record, parse_diags) = parser.parse_file_expr();
302+ assert!(
303+ !parse_diags
304+ .iter()
305+ .any(|d| d.severity == knot::diagnostic::Severity::Error),
306+ "prelude failed to parse: {:?}",
307+ parse_diags
308+ );
309+
310+ // Inject the stdlib builtins as fields of the nested `base` record.
311+ let dummy_span = ast::Span::new(0, 0);
312+ let mut base_record = None;
313+ if let ast::ExprKind::Record(outer) = &mut prelude_record.node
314+ && let Some(base_field) = outer.iter_mut().find(|f| f.name == "base")
315+ {
316+ if let ast::ExprKind::Record(base_fields) = &mut base_field.value.node {
317+ for name in BASE_STDLIB_FNS {
318+ base_fields.push(ast::RecordField {
319+ name: knot::ast::FieldName::Named((*name).to_string()),
320+ // Reference the stdlib fn by its FLATTENED `base.<name>`
321+ // key — a `Var` string containing a dot, unutterable in
322+ // user source, so it always resolves to the stdlib fn and
323+ // is never shadowed by a user decl named `<name>`.
324+ value: ast::Spanned::new(
325+ ast::ExprKind::Var(crate::infer::Binding::User(format!("base.{}", name))),
326+ dummy_span,
327+ ),
328+ sig: None,
329+ doc: None,
330+ });
331+ }
332+ }
333+ base_record = Some(base_field.value.clone());
334+ }
335+ let mut record = base_record.expect("prelude source has no `base` field");
336+ // Elaborate the constrained `base.*` fns (`log`, `info`, …): prepend the
337+ // hidden `__dict_<field>` parameter and rewrite the body's `^field` to it.
338+ // The prelude is parsed raw (no `desugar` pass), so without this their
339+ // `^field` would resolve against the prelude scope, not the dictionary
340+ // the callsite splices in.
341+ crate::desugar::elaborate_all_implicit_dicts(&mut record);
342+ shift_expr_spans(&mut record, PRELUDE_SPAN_OFFSET);
343+ record
344+}
345+
346+// ── Prelude span shifting (bug B39) ──────────────────────────────────
347+//
348+// Add `offset` to every declaration/expression/statement/pattern span in a
349+// prelude decl so prelude spans can never alias user-file spans in
350+// `monad_info` (and the other span-keyed inference maps). Type spans are left
351+// alone — they never key `monad_info`. Mirrors the AST shape walked by
352+// `unused::walk_decl`; keep the two in sync when the AST grows a node.
353+//
354+// "Every span" includes the standalone `name_span`/`api_span` fields, not just
355+// the `Spanned` wrappers: inference keys a punned record field's binder on
356+// `FieldPat::name_span`, so leaving it unshifted leaks a raw PRELUDE_SOURCE
357+// offset into `local_type_info` — a span the LSP cannot tell apart from a
358+// user span, since its provenance filter can only compare byte ranges. It
359+// then anchors an inlay hint at that offset in the user's file.
360+
361+fn shift_expr_spans(e: &mut ast::Expr, offset: usize) {
362+ use ast::ExprKind::*;
363+ e.span.start += offset;
364+ e.span.end += offset;
365+ match &mut e.node {
366+ TypeLiteral(_) => {}
367+ NameOf(inner) => shift_expr_spans(inner, offset),
368+ App { func, arg } => {
369+ shift_expr_spans(func, offset);
370+ shift_expr_spans(arg, offset);
371+ }
372+ With { record, body, .. } => {
373+ shift_expr_spans(record, offset);
374+ shift_expr_spans(body, offset);
375+ }
376+ Lambda { params, body, .. } => {
377+ for p in params {
378+ shift_pat_spans(p, offset);
379+ }
380+ shift_expr_spans(body, offset);
381+ }
382+ BinOp { lhs, rhs, .. } => {
383+ shift_expr_spans(lhs, offset);
384+ shift_expr_spans(rhs, offset);
385+ }
386+ UnaryOp { operand, .. } => shift_expr_spans(operand, offset),
387+ Case { scrutinee, arms } => {
388+ shift_expr_spans(scrutinee, offset);
389+ for arm in arms {
390+ shift_pat_spans(&mut arm.pat, offset);
391+ shift_expr_spans(&mut arm.body, offset);
392+ }
393+ }
394+ Do(stmts) => {
395+ for s in stmts {
396+ shift_stmt_spans(s, offset);
397+ }
398+ }
399+ Set { target, value } => {
400+ shift_expr_spans(target, offset);
401+ shift_expr_spans(value, offset);
402+ }
403+ Atomic(inner) | Refine(inner) => shift_expr_spans(inner, offset),
404+ TimeUnitLit { value, .. } => shift_expr_spans(value, offset),
405+ Record(fields) => {
406+ for fl in fields {
407+ shift_expr_spans(&mut fl.value, offset);
408+ }
409+ }
410+ List(items) => {
411+ for it in items {
412+ shift_expr_spans(it, offset);
413+ }
414+ }
415+ FieldAccess { expr, .. } | Annot { expr, .. } => shift_expr_spans(expr, offset),
416+ Serve {
417+ api_span, handlers, ..
418+ } => {
419+ api_span.start += offset;
420+ api_span.end += offset;
421+ for h in handlers {
422+ h.endpoint_span.start += offset;
423+ h.endpoint_span.end += offset;
424+ shift_expr_spans(&mut h.body, offset);
425+ }
426+ }
427+ Lit(_)
428+ | Var(_)
429+ | Constructor(_)
430+ | SourceRef { .. }
431+ | ImplicitRef(_)
432+ | CollectFold(_)
433+ | TypeHole => {}
434+ TypeCtor { .. } | SourceDecl { .. } | SubsetConstraint { .. } => {}
435+ ast::ExprKind::ArgDecl { .. } => {},
436+ RouteDecl { .. } => {}
437+ }
438+}
439+
440+fn shift_stmt_spans(s: &mut ast::Stmt, offset: usize) {
441+ use ast::StmtKind::*;
442+ s.span.start += offset;
443+ s.span.end += offset;
444+ match &mut s.node {
445+ Bind { pat, expr } => {
446+ shift_pat_spans(pat, offset);
447+ shift_expr_spans(expr, offset);
448+ }
449+ Where { cond } => shift_expr_spans(cond, offset),
450+ GroupBy { key } => shift_expr_spans(key, offset),
451+ Expr(e) => shift_expr_spans(e, offset),
452+ }
453+}
454+
455+fn shift_pat_spans(p: &mut ast::Pat, offset: usize) {
456+ use ast::PatKind::*;
457+ p.span.start += offset;
458+ p.span.end += offset;
459+ match &mut p.node {
460+ Var(_) | Wildcard | Lit(_) => {}
461+ Constructor { payload, .. } => shift_pat_spans(payload, offset),
462+ Record(fields) => {
463+ for fp in fields {
464+ // The field-name token's own span. For a punned field
465+ // (`{value}`) this IS the binder's span, and inference records
466+ // it in `binding_types` — so it must be shifted like any other
467+ // binder span, or it escapes as a raw prelude offset.
468+ fp.name_span.start += offset;
469+ fp.name_span.end += offset;
470+ if let Some(inner) = &mut fp.pattern {
471+ shift_pat_spans(inner, offset);
472+ }
473+ }
474+ }
475+ List(items) => {
476+ for it in items {
477+ shift_pat_spans(it, offset);
478+ }
479+ }
480+ Cons { head, tail } => {
481+ shift_pat_spans(head, offset);
482+ shift_pat_spans(tail, offset);
483+ }
484+ Annot { pat, .. } => shift_pat_spans(pat, offset),
485+ }
107486}
crates/knot-compiler/src/builtins.rsnew file+252
@@ -0,0 +1,252 @@
1+//! Centralized builtin name tables.
2+//!
3+//! These lists are the single source of truth for which builtins perform IO
4+//! and which are forbidden inside `atomic` blocks. Both the effect inferencer
5+//! (`effects.rs`) and the LSP (`knot_lsp::builtins`) reference this module so
6+//! the lists never drift apart.
7+
8+/// Builtins that perform a console effect (`println`, `print`, `logInfo`, ...).
9+pub const CONSOLE_BUILTINS: &[&str] = &[
10+ "println", "putLine", "print", "readLine", "logInfo", "logWarn", "logError", "logDebug",
11+];
12+
13+/// Builtins that read/manipulate the wall clock or sleep.
14+pub const CLOCK_BUILTINS: &[&str] = &["now", "sleep"];
15+
16+/// Builtins that consume randomness.
17+pub const RANDOM_BUILTINS: &[&str] = &[
18+ "randomInt",
19+ "randomFloat",
20+ "randomUuid",
21+ "generateKeyPair",
22+ "generateSigningKeyPair",
23+ "encrypt",
24+];
25+
26+/// Builtins that perform network IO.
27+pub const NETWORK_BUILTINS: &[&str] = &["listen", "listenOn", "fetch", "fetchWith"];
28+
29+/// Builtins that touch the filesystem.
30+pub const FS_BUILTINS: &[&str] = &[
31+ "readFile",
32+ "writeFile",
33+ "appendFile",
34+ "fileExists",
35+ "removeFile",
36+ "listDir",
37+];
38+
39+/// Concurrency builtins (`fork`, `retry`, `race`). These do not contribute IO
40+/// effects the same way as console/fs/etc.: `fork` propagates the effect row of
41+/// its spawned IO through its own result (`IO r {} -> IO r {}`), `retry` is the
42+/// STM primitive used inside atomic to trigger a retry, and `race` propagates
43+/// the effect rows of its arguments through its result.
44+pub const CONCURRENCY_BUILTINS: &[&str] = &["fork", "retry", "race"];
45+
46+/// Pure builtins. These are listed so the LSP can recognize them as user-callable
47+/// and so they sort sensibly in completion lists. They have no effects.
48+pub const PURE_BUILTINS: &[&str] = &[
49+ "show",
50+ "union",
51+ "count",
52+ "filter",
53+ "filterCtor",
54+ "map",
55+ "fold",
56+ "any",
57+ "all",
58+ "diff",
59+ "inter",
60+ "sum",
61+ "avg",
62+ "toUpper",
63+ "toLower",
64+ "take",
65+ "drop",
66+ "length",
67+ "trim",
68+ "contains",
69+ "elem",
70+ "reverse",
71+ "chars",
72+ "id",
73+ "not",
74+ "toJson",
75+ "parseJson",
76+ "decrypt",
77+ "sign",
78+ "verify",
79+ "upsertBy",
80+ "countWhere",
81+ "minOn",
82+ "maxOn",
83+ "sortBy",
84+ "floor",
85+ "intToFloat",
86+ "textToInt",
87+ "textToFloat",
88+ "stripUnit",
89+ "withUnit",
90+ "stripFloatUnit",
91+ "withFloatUnit",
92+ "strip",
93+ "dress",
94+ // The unimplemented hole: polymorphic `∀a. a`, aborts at runtime with a
95+ // full source-context report. Effect-free in the type system (like
96+ // `retry`); dispatch-only at codegen, never a real `base` record field.
97+ "todo",
98+ // The tracer: polymorphic `∀a. a -> a`, prints the traced value (with the
99+ // same source-context + in-scope-bindings report as `todo`) and returns it
100+ // unchanged. Dispatch-only at codegen, never a real `base` record field.
101+ "trace",
102+];
103+
104+/// Set of builtin function names that perform some IO effect. Used by the LSP
105+/// to mark them with the `effectful` semantic-token modifier and to filter
106+/// them out of atomic-context completions.
107+pub const EFFECTFUL_BUILTINS: &[&str] = &[
108+ // console
109+ "println",
110+ "putLine",
111+ "print",
112+ "readLine",
113+ "logInfo",
114+ "logWarn",
115+ "logError",
116+ "logDebug",
117+ // clock
118+ "now",
119+ "sleep",
120+ // random
121+ "randomInt",
122+ "randomFloat",
123+ "randomUuid",
124+ "generateKeyPair",
125+ "generateSigningKeyPair",
126+ "encrypt",
127+ // network
128+ "listen",
129+ "listenOn",
130+ "fetch",
131+ "fetchWith",
132+ // fs
133+ "readFile",
134+ "writeFile",
135+ "appendFile",
136+ "fileExists",
137+ "removeFile",
138+ "listDir",
139+ // concurrency
140+ "fork",
141+ "retry",
142+ "race",
143+];
144+
145+/// Zero-argument IO builtins: referencing the bare name *is* the IO action,
146+/// so a bare `Var` reference legitimately carries the builtin's effects. Every
147+/// other effectful builtin takes arguments and performs no IO until applied —
148+/// a bare reference to it (e.g. `let f = println`, never called) is pure, and
149+/// its effects must only manifest at the call site.
150+pub const NULLARY_IO_BUILTINS: &[&str] = &[
151+ "now",
152+ "readLine",
153+ "randomFloat",
154+ "randomUuid",
155+ "generateKeyPair",
156+ "generateSigningKeyPair",
157+];
158+
159+/// Builtins whose effects cannot be rolled back by the savepoint-based atomic
160+/// machinery. The effect checker rejects any of these inside an `atomic` block;
161+/// the LSP also filters them from atomic-context completion lists.
162+///
163+/// `fork` and `retry` are intentionally absent: `fork`'s spawned IO runs in an
164+/// independent transaction (its effects propagate through the type but the work
165+/// happens on its own connection), and `retry` is the STM primitive used inside
166+/// atomic to trigger a retry.
167+pub const ATOMIC_DISALLOWED_BUILTINS: &[&str] = &[
168+ "println",
169+ "putLine",
170+ "print",
171+ "readLine",
172+ "logInfo",
173+ "logWarn",
174+ "logError",
175+ "logDebug",
176+ "now",
177+ "sleep",
178+ "randomInt",
179+ "randomFloat",
180+ "randomUuid",
181+ "generateKeyPair",
182+ "generateSigningKeyPair",
183+ "encrypt",
184+ "listen",
185+ "listenOn",
186+ "fetch",
187+ "fetchWith",
188+ "readFile",
189+ "writeFile",
190+ "appendFile",
191+ "fileExists",
192+ "removeFile",
193+ "listDir",
194+ "race",
195+];
196+
197+/// Built-in trait method names (Eq.eq, Ord.compare, Num.add, etc.). Resolved
198+/// at codegen time via runtime tag dispatch.
199+pub const TRAIT_METHOD_BUILTINS: &[&str] = &[
200+ "eq", "compare", "ap", "bind", "alt", "empty", "add", "sub", "mul", "div", "negate", "append",
201+ "yield", "display",
202+];
203+
204+/// Bytes builtins. Pure but kept separate so the LSP/codegen can recognize
205+/// them as user-callable and they sort sensibly in completion lists.
206+pub const BYTES_BUILTINS: &[&str] = &[
207+ "bytesLength",
208+ "bytesSlice",
209+ "bytesConcat",
210+ "textToBytes",
211+ "bytesToText",
212+ "bytesToHex",
213+ "bytesFromHex",
214+ "hexDecode",
215+ "bytesGet",
216+ "hash",
217+];
218+
219+/// Internal desugaring helpers emitted by the desugarer. Not user-facing.
220+pub const INTERNAL_BUILTINS: &[&str] = &["__bind", "__yield", "__empty"];
221+
222+/// Single source of truth: every name the compiler treats as a builtin.
223+/// Used by `codegen::is_builtin_name` (free-variable filtering) and the LSP
224+/// completion suggestions. Adding a new builtin should only require editing
225+/// this file and the codegen registration site.
226+pub const ALL_BUILTINS: &[&[&str]] = &[
227+ CONSOLE_BUILTINS,
228+ CLOCK_BUILTINS,
229+ RANDOM_BUILTINS,
230+ NETWORK_BUILTINS,
231+ FS_BUILTINS,
232+ CONCURRENCY_BUILTINS,
233+ PURE_BUILTINS,
234+ TRAIT_METHOD_BUILTINS,
235+ BYTES_BUILTINS,
236+ INTERNAL_BUILTINS,
237+];
238+
239+/// True if `name` is a known builtin (any category).
240+pub fn is_builtin(name: &str) -> bool {
241+ ALL_BUILTINS.iter().any(|list| list.contains(&name))
242+}
243+
244+/// True if `name` is an effectful builtin — i.e. calling it produces an IO
245+/// value. Excludes `retry` (the STM primitive, typed `∀a. a` — no IO).
246+/// `fork` is included: it returns `IO {} {}` and its argument's effects are
247+/// decoupled from the caller, but callers redundantly check `|| name == "fork"`
248+/// to ensure it's recognized. `race` is included because the result IO
249+/// inherits the effect row of its arguments.
250+pub fn is_io_builtin(name: &str) -> bool {
251+ EFFECTFUL_BUILTINS.contains(&name) && !matches!(name, "retry")
252+}
crates/knot-compiler/src/codegen.rs+19586−5919

This diff is too big to show here.

crates/knot-compiler/src/decl_view.rsnew file+176

This diff is too big to show here.

crates/knot-compiler/src/desugar.rs+1187−679

This diff is too big to show here.

crates/knot-compiler/src/effects.rsdeleted−999

This diff is too big to show here.

crates/knot-compiler/src/infer.rs+13885−2419

This diff is too big to show here.

crates/knot-compiler/src/lib.rs+8−3

This diff is too big to show here.

crates/knot-compiler/src/linker.rs+42−6

This diff is too big to show here.

crates/knot-compiler/src/lockfile.rs+601−383

This diff is too big to show here.

crates/knot-compiler/src/main.rs+1060−67

This diff is too big to show here.

crates/knot-compiler/src/modules.rsdeleted−272

This diff is too big to show here.

crates/knot-compiler/src/nonterm.rsnew file+384

This diff is too big to show here.

crates/knot-compiler/src/refine_smt.rsnew file+254

This diff is too big to show here.

crates/knot-compiler/src/sql_lint.rsnew file+1247

This diff is too big to show here.

crates/knot-compiler/src/stack.rsnew file+90

This diff is too big to show here.

crates/knot-compiler/src/stratify.rs+672−233

This diff is too big to show here.

crates/knot-compiler/src/types.rs+2159−288

This diff is too big to show here.

crates/knot-compiler/src/unused.rsnew file+403

This diff is too big to show here.

crates/knot-compiler/src/unwind.rsnew file+333

This diff is too big to show here.

crates/knot-compiler/tests/base_listing.rsnew file+73

This diff is too big to show here.

crates/knot-compiler/tests/compile_boundary.rsnew file+334

This diff is too big to show here.

crates/knot-compiler/tests/compile_panic_containment.rsnew file+51

This diff is too big to show here.

crates/knot-compiler/tests/descriptor_roundtrip.rsnew file+84

This diff is too big to show here.

crates/knot-descriptor/Cargo.tomlnew file+6

This diff is too big to show here.

crates/knot-descriptor/src/lib.rsnew file+376

This diff is too big to show here.

crates/knot-jit/Cargo.tomlnew file+25

This diff is too big to show here.

crates/knot-jit/src/lib.rsnew file+365

This diff is too big to show here.

crates/knot-jit/tests/adts.rsnew file+109

This diff is too big to show here.

crates/knot-jit/tests/cli_overrides.rsnew file+982

This diff is too big to show here.

crates/knot-jit/tests/concurrency.rsnew file+94

This diff is too big to show here.

crates/knot-jit/tests/constraints.rsnew file+115

This diff is too big to show here.

crates/knot-jit/tests/crypto.rsnew file+116

This diff is too big to show here.

crates/knot-jit/tests/e2e.rsnew file+149

This diff is too big to show here.

crates/knot-jit/tests/errors.rsnew file+111

This diff is too big to show here.

crates/knot-jit/tests/extract.rsnew file+438

This diff is too big to show here.

crates/knot-jit/tests/functions.rsnew file+304

This diff is too big to show here.

crates/knot-jit/tests/harness.rsnew file+139

This diff is too big to show here.

crates/knot-jit/tests/http.rsnew file+166

This diff is too big to show here.

crates/knot-jit/tests/jit_e2e.rsnew file+197

This diff is too big to show here.

crates/knot-jit/tests/lists.rsnew file+132

This diff is too big to show here.

crates/knot-jit/tests/literal_pushdown.rsnew file+155

This diff is too big to show here.

crates/knot-jit/tests/migration.rsnew file+485

This diff is too big to show here.

crates/knot-jit/tests/migration_corner.rsnew file+1431

This diff is too big to show here.

crates/knot-jit/tests/nested_and_meta.rsnew file+85

This diff is too big to show here.

crates/knot-jit/tests/numeric_text.rsnew file+154

This diff is too big to show here.

crates/knot-jit/tests/patterns.rsnew file+123

This diff is too big to show here.

crates/knot-jit/tests/persistence.rsnew file+968

This diff is too big to show here.

crates/knot-jit/tests/refinements.rsnew file+273

This diff is too big to show here.

crates/knot-jit/tests/relations.rsnew file+292

This diff is too big to show here.

crates/knot-jit/tests/scalars.rsnew file+238

This diff is too big to show here.

crates/knot-jit/tests/single_value.rsnew file+174

This diff is too big to show here.

crates/knot-jit/tests/smoke.rsnew file+31

This diff is too big to show here.

crates/knot-jit/tests/text_bytes.rsnew file+120

This diff is too big to show here.

crates/knot-jit/tests/type_errors.rsnew file+107

This diff is too big to show here.

crates/knot-jit/tests/units.rsnew file+103

This diff is too big to show here.

crates/knot-jit/tests/views.rsnew file+68

This diff is too big to show here.

crates/knot-lsp/Cargo.toml+1

This diff is too big to show here.

crates/knot-lsp/src/analysis.rsnew file+740

This diff is too big to show here.

crates/knot-lsp/src/builtins.rsnew file+9

This diff is too big to show here.

crates/knot-lsp/src/call_hierarchy.rsnew file+295

This diff is too big to show here.

crates/knot-lsp/src/code_action.rsnew file+1804

This diff is too big to show here.

crates/knot-lsp/src/code_lens.rsnew file+124

This diff is too big to show here.

crates/knot-lsp/src/completion.rsnew file+1727

This diff is too big to show here.

crates/knot-lsp/src/defs.rsnew file+665

This diff is too big to show here.

crates/knot-lsp/src/diagnostics.rsnew file+223

This diff is too big to show here.

crates/knot-lsp/src/document_highlight.rsnew file+87

This diff is too big to show here.

crates/knot-lsp/src/document_symbol.rsnew file+254

This diff is too big to show here.

crates/knot-lsp/src/folding.rsnew file+153

This diff is too big to show here.

crates/knot-lsp/src/formatting.rsnew file+282

This diff is too big to show here.

crates/knot-lsp/src/goto.rsnew file+176

This diff is too big to show here.

crates/knot-lsp/src/hover.rsnew file+547

This diff is too big to show here.

crates/knot-lsp/src/incremental.rsnew file+535

This diff is too big to show here.

crates/knot-lsp/src/inlay_hints.rsnew file+1219

This diff is too big to show here.

crates/knot-lsp/src/legend.rsnew file+53

This diff is too big to show here.

crates/knot-lsp/src/lib.rsnew file+1

This diff is too big to show here.

crates/knot-lsp/src/linked_editing.rsnew file+334

This diff is too big to show here.

crates/knot-lsp/src/main.rs+1281−3155

This diff is too big to show here.

crates/knot-lsp/src/parsed_type.rsnew file+743

This diff is too big to show here.

crates/knot-lsp/src/references.rsnew file+153

This diff is too big to show here.

crates/knot-lsp/src/rename.rsnew file+1090

This diff is too big to show here.

crates/knot-lsp/src/selection_range.rsnew file+182

This diff is too big to show here.

crates/knot-lsp/src/semantic_tokens.rsnew file+824

This diff is too big to show here.

crates/knot-lsp/src/shared.rsnew file+1147

This diff is too big to show here.

crates/knot-lsp/src/signature_help.rsnew file+285

This diff is too big to show here.

crates/knot-lsp/src/state.rsnew file+804

This diff is too big to show here.

crates/knot-lsp/src/type_format.rsnew file+345

This diff is too big to show here.

crates/knot-lsp/src/utils.rsnew file+556

This diff is too big to show here.

crates/knot-lsp/src/workspace_diagnostics.rsnew file+722

This diff is too big to show here.

crates/knot-lsp/src/workspace_symbol.rsnew file+212

This diff is too big to show here.

crates/knot-runtime/Cargo.toml+17−6

This diff is too big to show here.

crates/knot-runtime/src/db_dump.rsnew file+143

This diff is too big to show here.

crates/knot-runtime/src/lib.rs+21501−4926

This diff is too big to show here.

crates/knot-runtime/src/log.rsnew file+305

This diff is too big to show here.

crates/knot-runtime/src/tui.rs+228−180

This diff is too big to show here.

crates/knot/src/ast.rs+523−217

This diff is too big to show here.

crates/knot/src/diagnostic.rs+102−76

This diff is too big to show here.

crates/knot/src/format.rsnew file+1523

This diff is too big to show here.

crates/knot/src/lexer.rs+513−398

This diff is too big to show here.

crates/knot/src/lib.rs+1

This diff is too big to show here.

crates/knot/src/parser.rs+3071−2206

This diff is too big to show here.

crates/knot/tests/parser.rsdeleted−3562

This diff is too big to show here.

editors/vscode/package-lock.json+485

This diff is too big to show here.

editors/vscode/package.json+6−4

This diff is too big to show here.

editors/vscode/src/extension.ts+35−6

This diff is too big to show here.

editors/vscode/syntaxes/knot.tmLanguage.json+3−7

This diff is too big to show here.

examples/.gitignorenew file+1

This diff is too big to show here.

examples/apiserver.knotnew file+92

This diff is too big to show here.

examples/arith_aggregate.knotnew file+23

This diff is too big to show here.

examples/arith_aggregate.schema.locknew file+7

This diff is too big to show here.

examples/associated_types.knotdeleted−21

This diff is too big to show here.

examples/builtins.knotnew file+31

This diff is too big to show here.

examples/bytes.knot+54−36

This diff is too big to show here.

examples/cons_pattern.knotnew file+16

This diff is too big to show here.

examples/constraints.knot+15−15

This diff is too big to show here.

examples/constraints.schema.lock+6−9

This diff is too big to show here.

examples/crypto.knot+51−31

This diff is too big to show here.

examples/fold_dictionaries.knotnew file+20

This diff is too big to show here.

examples/fold_stream.knotnew file+33

This diff is too big to show here.

examples/fold_stream.schema.locknew file+7

This diff is too big to show here.

examples/for_each.knotnew file+12

This diff is too big to show here.

examples/forall.knotnew file+27

This diff is too big to show here.

examples/groupby.knot+17−16

This diff is too big to show here.

examples/groupby.schema.lockdeleted−8

This diff is too big to show here.

examples/hash.knotnew file+25

This diff is too big to show here.

examples/hello.knot+7−6

This diff is too big to show here.

examples/hello.schema.lock+5−6

This diff is too big to show here.

examples/hole.knotnew file+35

This diff is too big to show here.

examples/implicit_dictionaries.knotnew file+31

This diff is too big to show here.

examples/inline_pushdown.knotnew file+55

This diff is too big to show here.

examples/inline_pushdown.schema.locknew file+7

This diff is too big to show here.

examples/json.knot+24−20

This diff is too big to show here.

examples/let_pushdown.knotnew file+39

This diff is too big to show here.

examples/log_test.knotnew file+12

This diff is too big to show here.

examples/maybe.knot+34−62

This diff is too big to show here.

examples/migrate.knotnew file+34

This diff is too big to show here.

examples/modules/lib/core.knotdeleted−1

This diff is too big to show here.

examples/modules/lib/helpers.knotdeleted−3

This diff is too big to show here.

examples/modules/main.knotdeleted−10

This diff is too big to show here.

examples/modules/main.schema.lockdeleted−21

This diff is too big to show here.

examples/modules/transitive.knotdeleted−5

This diff is too big to show here.

examples/modules/types.knotdeleted−5

This diff is too big to show here.

examples/modules/utils.knotdeleted−3

This diff is too big to show here.

examples/morph.knotnew file+45

This diff is too big to show here.

examples/nested.knot+27−17

This diff is too big to show here.

examples/nested.schema.lock+5−6

This diff is too big to show here.

examples/ord.knotnew file+61

This diff is too big to show here.

examples/query_opt.knot+87−88

This diff is too big to show here.

examples/query_opt.schema.lock+7−11

This diff is too big to show here.

examples/race.knotnew file+30

This diff is too big to show here.

examples/recursive.knot+18−24

This diff is too big to show here.

examples/recursive.schema.lock+4−4

This diff is too big to show here.

examples/result.knot+30−51

This diff is too big to show here.

examples/routes.knot+33−40

This diff is too big to show here.

examples/routes.schema.lockdeleted−12

This diff is too big to show here.

examples/showcase.knot+407−268

This diff is too big to show here.

examples/showcase.schema.lock+28−88

This diff is too big to show here.

examples/stm_row_filter.knotnew file+35

This diff is too big to show here.

examples/stm_row_filter.schema.locknew file+7

This diff is too big to show here.

examples/temporal.knotdeleted−29

This diff is too big to show here.

examples/temporal.schema.lockdeleted−5

This diff is too big to show here.

examples/todo.knot+16−20

This diff is too big to show here.

examples/todo.schema.lock+5−6

This diff is too big to show here.

examples/trait_replacement.knotnew file+25

This diff is too big to show here.

examples/trait_replacement.onew file

Binary file not shown.

examples/traits.knotdeleted−58

This diff is too big to show here.

examples/uuid.knotnew file+8

This diff is too big to show here.

examples/views.knotdeleted−45

This diff is too big to show here.

examples/views.schema.lockdeleted−15

This diff is too big to show here.

examples/views_history.knotdeleted−49

This diff is too big to show here.

examples/views_history.schema.lockdeleted−11

This diff is too big to show here.

examples/when_unless.knotnew file+7

This diff is too big to show here.

knot.mddeleted−991

This diff is too big to show here.

stdlib.mddeleted−723

This diff is too big to show here.

tree-sitter-knot/grammar.js+16−30

This diff is too big to show here.

tree-sitter-knot/queries/highlights.scm+3−2

This diff is too big to show here.

tree-sitter-knot/src/grammar.json+67−4

This diff is too big to show here.

tree-sitter-knot/src/node-types.json+33−33

This diff is too big to show here.

tree-sitter-knot/src/parser.c+193−193

This diff is too big to show here.