on master help sign in
Remove historical content from documentation

Docs describe only the current state: - delete docs/logging-proposal.md (the design doc for the now-shipped structured logging; history lives in version control) - drop the deprecated-alias notes for logInfo/logWarn/logError/logDebug from base.md, stdlib.md, AGENTS.md

commit4f373611c11e475301bae4ccf87f3fa72c3c114b authoriko <mail@iko.soy> dateTue 11 Aug 2026, 05:15 (2 months ago) parentd0ba637
4 files changed+2−289
AGENTS.md+1−1 base.md+1−2 docs/logging-proposal.md−284 stdlib.md−2
AGENTS.md+1−1
@@ -65,7 +65,7 @@ let (module, parse_diags) = parser.parse_module();
6565
6666### Runtime (`crates/knot-runtime/`)
6767
68−Compiled as a `staticlib` (with `rlib` for workspace dependency resolution). All functions use `extern "C"` ABI, called by Cranelift-generated code via symbol references. `lib.rs` is the main runtime; `log.rs` implements the leveled, structured logger behind `base.debug`/`info`/`warn`/`error` (and the deprecated `logInfo`/`logWarn`/`logError`/`logDebug` aliases): TTY-colored on stderr or one JSON record per line, with `(<>logCtx)` context fields merged in (a multiline field renders as a `│`-guttered block on a TTY, `\n`-escaped inline in JSON). The runtime's own events — HTTP serve-loop errors, source migrations, watcher panics — emit through the same logger so JSON-mode stderr stays one record per line; `knot_todo` reports, the GC stats dump, and CLI/usage errors stay raw stderr. `--debug` arg scanned by `debug_enabled()` gates `debug`. `tui.rs` is the `db` subcommand's terminal-UI database explorer (parses `_knot_schema`, paginates rows, drills into individual records).
68+Compiled as a `staticlib` (with `rlib` for workspace dependency resolution). All functions use `extern "C"` ABI, called by Cranelift-generated code via symbol references. `lib.rs` is the main runtime; `log.rs` implements the leveled, structured logger behind `base.debug`/`info`/`warn`/`error`: TTY-colored on stderr or one JSON record per line, with `(<>logCtx)` context fields merged in (a multiline field renders as a `│`-guttered block on a TTY, `\n`-escaped inline in JSON). The runtime's own events — HTTP serve-loop errors, source migrations, watcher panics — emit through the same logger so JSON-mode stderr stays one record per line; `knot_todo` reports, the GC stats dump, and CLI/usage errors stay raw stderr. `--debug` arg scanned by `debug_enabled()` gates `debug`. `tui.rs` is the `db` subcommand's terminal-UI database explorer (parses `_knot_schema`, paginates rows, drills into individual records).
6969
7070Compiled programs expose a common CLI without any user wiring:
7171- `--debug` — enable `base.debug` output
base.md+1−2
@@ -364,8 +364,7 @@ record is one line.
364364
365365The runtime's own events (HTTP serve-loop errors, migrations, watcher panics)
366366emit through the same machinery, so in JSON mode every stderr line is a
367−well-formed record. `base.logInfo`/`logWarn`/`logError`/`logDebug` are
368−deprecated aliases kept for source compatibility.
367+well-formed record.
369368
370369### `base.show`
371370
docs/logging-proposal.mddeleted−284
@@ -1,284 +0,0 @@
1−# Knot logging — syntax proposal (v2, with `<>`)
2−
3−Structured, level-typed, lexically-scoped logging. Everything to stderr;
4−level filtering is the existing `--debug` flag. Context propagation uses a
5−new projection-collect operator `<>` that generalizes `^`. No trait system.
6−
7−---
8−
9−## 1. The level is a knot type
10−
11−```knot
12−data Level = Debug {} | Info {} | Warn {} | Error {}
13−```
14−
15−An ordinary ADT, registered as a builtin (like `Maybe`/`Result`). A
16−first-class value: pass it, store it, compute it, pattern-match it.
17−
18−## 2. `log` takes a plain message; context comes from `<>`
19−
20−```knot
21−base.log : Level -> Text -> IO {console} {}
22−```
23−
24−The caller passes a plain message string. Structured context is **not** part of
25−the call — it is collected from the enclosing `with`-scopes via `<>` and
26−appended by `log` itself (§5). Per-event structured fields are set through a
27−`logCtx` context scope rather than inline:
28−
29−```knot
30−base.log (Info {}) "user logged in"
31−with { logCtx {userId 42 ip "1.2.3.4"} } (base.log (Info {}) "user logged in")
32−```
33−
34−Terminal stderr (context splat only when non-empty):
35−```
36−INFO user logged in userId: 42, ip: "1.2.3.4"
37−```
38−
39−Non-terminal stderr keeps the existing JSON-lines path, with the merged
40−context fields splatted into the JSON object.
41−
42−## 3. Ergonomic wrappers
43−
44−```knot
45−base.debug = base.log (Debug {})
46−base.info = base.log (Info {})
47−base.warn = base.log (Warn {})
48−base.error = base.log (Error {})
49−```
50−
51−`base.info "..."` reads exactly like the old `base.logInfo "..."`. `base.log`
52−takes a *computed* level:
53−
54−```knot
55−base.log (case status >= 500 of
56− Bool.True {} -> Error {}
57− Bool.False {} -> Info {}) "request done"
58−```
59−
60−**Migration:** `logInfo/Warn/Error/Debug` are kept one release as deprecated
61−aliases (`base.logInfo = base.info`, etc.) — the message argument is unchanged
62−(`Text`), so call sites port by renaming — then removed.
63−
64−## 4. Filtering — the existing `--debug` flag, no more
65−
66−- `Debug` events emit only with `--debug` (today's behavior).
67−- `Info`/`Warn`/`Error` always emit.
68−
69−No per-module config, no runtime mutation. The runtime stays as simple as the
70−current `AtomicBool` check. Because the level is a value, a program can gate
71−its own logging without runtime support (see §7 note).
72−
73−## 5. Context propagation via `<>` — collect the `logCtx` field, don't project
74−
75−### 5a. Why not `^`
76−
77−`(^field)` requires a *unique* resolution: two in-scope records sharing a
78−field name is a hard **ambiguous projection** error, and re-binding the same
79−`with` field is rejected as **shadowing** (knot forbids shadowing). Both are
80−deliberate. So `^` alone cannot express "override the context in a nested
81−scope." (Verified by prototyping.)
82−
83−### 5b. `<>name` — fold the in-scope projections, innermost-first
84−
85−`<>` is the collecting counterpart to `^`. Where `^` needs exactly one match,
86−`<>` folds **every** match into an accumulator via a caller-supplied
87−function:
88−
89−```knot
90−<>name : (acc -> { | c} -> acc) -> acc -> acc
91−```
92−
93−**Why a fold and not a list (the decisive constraint, verified by
94−probing):** a knot list cannot hold differently-shaped records. The element
95−type is a single record shape — `[{requestId "abc"} {span "db"}]` is a hard
96−error ("record fields don't match"), *even under an explicit `[{ | c}]`
97−annotation*: each element must equal the one element type, not merely fit an
98−open row. So `<>` can never return the collected fragments as a list when
99−they have different shapes — and different shapes are the entire point of
100−collecting context. The fold avoids ever materializing a list: each fragment
101−is consumed **at its projection site, where its concrete type is known**, and
102−unified individually against the folder's open-row parameter. One record
103−against one row typechecks; two records forced into one list element does
104−not.
105−
106−- **Reach: all enclosing `with`-scopes (rule A).** `<>` walks *every*
107− enclosing scope innermost→outermost and collects the matching field from
108− each — it does NOT stop at the first scope with a match. (This is where it
109− departs from `^`'s finder, which early-exits at the nearest match: `^`
110− wants the single closest, `<>` wants the whole nested stack so inner and
111− outer context both contribute.) Every `with`-ed record carrying the field
112− participates, whether or not you "meant" it as context — so the field name
113− (`logCtx`) is a reserved-by-convention slot: don't put a record-typed
114− `logCtx` field on a record unless you intend it as log context.
115−- **Name-only collection (no type filter).** `<>` collects purely by field
116− name and unrolls the provided folder over every match; shape compatibility
117− is enforced by the fold itself, so a mis-shaped fragment (e.g. a `logCtx`
118− that isn't a record, or whose field types conflict on a shared key) fails
119− the fold's own `unify`/arithmetic at its precise site — a compile error,
120− not a silent skip. `logCtx` is reserved-by-convention: don't reuse it for
121− anything but log context.
122−- **The same finder as `^`, a different final step.** `^name` works in three
123− steps: (1) collect in-scope fields named `name`, (2) keep the ones that
124− unify with the expected type, (3) require *exactly one*. `<>` runs (1) —
125− extended to all scopes, per rule A — and (2), then changes only step (3):
126− instead of demanding uniqueness, it folds the unifying candidates,
127− **innermost scope first**, through the caller's function.
128−- **Empty folds to the initial accumulator.** No in-scope match → the fold
129− returns the initial `acc` unchanged, never an error. This is what lets
130− `base.log` be called with no context established (the merge is a no-op).
131−- **The folder sees each fragment's concrete type.** Because the fold runs
132− per-fragment at the projection site, the folder is applied to each
133− fragment's actual record shape, not to an open-row list element. The
134− accumulator accumulates the field-union.
135−
136−For logging, the folder is a record merge:
137−
138−```knot
139−merged = (<>logCtx) (\acc frag -> base.unify frag acc) {}
140−```
141−
142−(`base.unify` is right-biased, so folding innermost-first makes the
143−innermost fragment win per-field — the override semantics.)
144−
145−**Typing `<>` — the operator instantiates the folder per fragment.** The
146−naive desugar — "build a list, hand it to a normal `fold`" — fails twice
147−over. First, a knot list cannot hold differently-shaped records (above).
148−Second, a generic folder lambda is typed *once*: `(\acc frag -> base.unify
149−frag acc)` has `frag` and `acc` as unresolved parameters, and `base.unify` on
150−unresolved record params errors ("unify expects record arguments, got a
151−non-record type") — the compiler does NOT trace the lambda body once per
152−fold iteration on its own. (Verified: the unannotated fold chain fails.)
153−
154−So `<>` must drive the iteration itself. It knows each fragment's concrete
155−type — it collected them. For each candidate (innermost-first) it
156−**instantiates the folder fresh**, pinning `frag` to that fragment's concrete
157−record type and `acc` to the running field-union (initially the initial
158−value's type). Each instantiation is `base.unify` with a *known* `frag`,
159−which typechecks and yields the merged type. (Verified: with `frag`
160−annotated to each concrete shape and `acc` open, the chain
161−`f2 (f1 {} {span "db"}) {requestId "abc"}` compiles to the field-union
162−`{requestId, span}`.) The accumulator threads the union forward, so the
163−fold's result type is the merged context record.
164−
165−`<>` is therefore a type-checker-level fold over the collected candidates —
166−the folder is checked N times, once per candidate — not a runtime `fold` over
167−a materialized list. The candidate-collection machinery is shared with `^`;
168−the per-candidate instantiation is the new part. The general fix that would
169−let a *user-written* unannotated fold over records typecheck (making deferred
170−`unify` wait for call-site pinning) is a separate improvement and not
171−required for `<>`.
172−
173−### 5c. Context lives on the dictionaries you already have
174−
175−Because `<>` collects the `logCtx` field from **every** in-scope record,
176−context attaches to whatever dictionary you've `with`-ed — no dedicated
177−context record required. Each dictionary carries its own `logCtx` fragment
178−saying "when logging inside my scope, attach this":
179−
180−```knot
181−with { httpCtx {requestId "abc" handler "createUser" logCtx {requestId "abc"}} } (do
182− with { dbCtx {pool "primary" logCtx {span "db" query "insert"}} } (do
183− -- <>logCtx : [ {span "db" query "insert"}, {requestId "abc"} ]
184− base.info {msg "creating user"})))
185−```
186−
187−`base.log` folds the collected fragments **innermost-first, earlier entries
188−winning per-field**, with the event's own fields winning overall:
189−
190−```
191−INFO creating user {span: "db", query: "insert", requestId: "abc", msg: "creating user"}
192−```
193−
194−Inside the inner `with`, `<>logCtx` is `[{span "db" query "insert"},
195−{requestId "abc"}]`; folded innermost-first that merges to `{span "db", query
196−"insert", requestId "abc"}`. Outside, only the `httpCtx` fragment is in scope.
197−**Override-in-a-new-context falls out of ordinary nested `with` blocks — no
198−intrinsic, no special form.** The `with` structure *is* the scoping, and any
199−dictionary can carry context by adding a `logCtx` field.
200−
201−### 5d. The HTTP layer sets the base context
202−
203−`serve`/`listen` wrap each handler invocation in a dictionary carrying a
204−`logCtx` fragment:
205−
206−```knot
207−with { request {method <m> path <p> logCtx {requestId <fresh uuid> method <m> path <p>}} }
208− (handler ...)
209−```
210−
211−so every log inside a handler is request-scoped for free.
212−
213−## 6. Everything to stderr, in one shape
214−
215−**One unified output shape.** Everything the app emits to stderr — `base.log`,
216−its wrappers, and `trace` — goes through the same `emitStderr` sink and the
217−same line shape: a `msg` headline plus the remaining event fields splatted
218−(terminal), or one JSON-lines object (non-terminal). There is no separate
219−ad-hoc format for any producer.
220−
221−```json
222−{"level":"info","msg":"user logged in","userId":42,"requestId":"abc123","timestamp":1710900045.0}
223−```
224−
225−### 6a. `trace` is a `Debug`-level event
226−
227−`trace` (the lexical debugging probe) is folded into this infrastructure. A
228−`trace` emits a structured event at the `Debug` level through `emitStderr`, so
229−it is `--debug`-gated like all other debug logging and renders in the unified
230−shape:
231−
232−- `msg` = the trace call-site label (baked source context).
233−- `value` = the traced value.
234−- `scope` = the auto-captured in-scope bindings, as a nested record field
235− (replacing the old free-text "values in scope:" dump).
236−- `<>` `logCtx` context is merged in, exactly as for `base.log` — so a `trace`
237− inside a request handler carries `requestId`/method/path too.
238−
239−The old bespoke `eprintln!` "values in scope:" layout is removed; `trace` is
240−just `base.debug` with lexical-locals auto-attached, plus context.
241−
242−## 7. Out of scope (per constraints)
243−
244−- Per-module / per-level filtering config — `--debug` only.
245−- Typed sinks / a distinct `IO {log}` effect — stderr under `IO {console}`.
246−- A `base.hasFlag`-style builtin (used for illustration in §4) is a separate,
247− optional nicety, not required by this design.
248−
249−---
250−
251−## New surface
252−
253−| Item | Form | New machinery? |
254−|---|---|---|
255−| `Level` ADT | `data Level = Debug {} \| Info {} \| Warn {} \| Error {}` | builtin registration |
256−| core log | `base.log : Level -> Text -> IO {console} {}` | no |
257−| wrappers | `base.debug/info/warn/error` | no |
258−| `trace` | emits a `Debug` event (`msg`/`value`/`scope` + `<>` context) through the same sink | no (reroute existing) |
259−| context event | the merged `logCtx` record, built by `<>` inside `log` | no (existing records) |
260−| context collect | `<>name : (acc -> { \| c} -> acc) -> acc -> acc` — `^`'s finder, folding all matches at their projection sites; innermost-first; no-match → initial acc | **yes — one operator** |
261−| context scope | ordinary `with {dict {... logCtx {...}}} (...)` — any dictionary opts in via a `logCtx` field | no (existing `with`) |
262−| filtering | `--debug` gates `Debug` (incl. `trace`) | no (existing) |
263−| sink | one `emitStderr` — `msg` headline + field splat, terminal/JSON | **yes — one runtime fn** |
264−
265−**One new language feature: `<>`.** It generalizes the existing `^`
266−projection from "the unique match" to "all matches, innermost-first" — and in
267−doing so removes the need for any context-passing intrinsic. Everything else
268−is ordinary knot.
269−
270−## The `base.log` reference implementation (sketch)
271−
272−```knot
273−base.log (\level msg ->
274− with { ctx ((<>logCtx) (\acc frag -> base.unify frag acc) {}) } -- fold context, innermost wins
275− (emitStderr level msg ctx)) -- sink renders msg + ctx splat
276−```
277−
278−(`<>` folds the collected `logCtx` fragments through the caller's merge
279−function, each at its own projection site and concrete type — no list is ever
280−built, so no shared element type is needed. `base.unify` is right-biased, so
281−innermost-first folding makes the innermost fragment win per-field. The sink
282−`emitStderr` renders `LEVEL msg` plus the context fields as a `k: v` splat
283−(terminal) or splatted into the JSON-lines object — walking `ctx`'s fields,
284−which is the one thing pure knot can't express.)
stdlib.md−2
@@ -636,8 +636,6 @@ A context field whose value contains a newline renders on a TTY as a guttered
636636block beneath the log line (single-line fields stay inline as `k=v`); in JSON
637637mode the value stays `\n`-escaped on the single emitted line.
638638
639−`logInfo`/`logWarn`/`logError`/`logDebug` are deprecated aliases.
640−
641639### `show`
642640
643641```