Skip to content

feat(core): LambdaDelta notebook host — λδ reads and mutates notes#36

Merged
hyperpolymath merged 1 commit into
mainfrom
claude/project-scope-planning-5zoaze
Jul 4, 2026
Merged

feat(core): LambdaDelta notebook host — λδ reads and mutates notes#36
hyperpolymath merged 1 commit into
mainfrom
claude/project-scope-planning-5zoaze

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

What this is

The moment λδ first touches a note. Building on the merged L0 kernel (#35), this adds the notebook host — the note-aware builtins registered into a kernel through the seam.

Critically, this preserves the kernel/host split (spec §7): the new code lives in core/src/lambdadelta_host.rs, outside core/src/lambdadelta/. The kernel still knows nothing about notes; an embedder with a different data model would write its own host exactly like this one. Nexia-List is simply the first host.

A note is a letter we send to our future self. — this is the layer where computation starts amplifying the letter.

Bridge — a note becomes data (spec §2)

  • note_to_value renders a note as its immutable snapshot map: :id :title :content :attrs :links :backlinks :position :size :prototype :type :created-at :modified-at, with #uuid/#inst tagged ids and instants.
  • Total, lossless JSON attribute ↔ λδ value mapping (object keys ↔ keywords). Documented lossy edges on write: keyword/symbol → string, set → array, function → null.

Builtins (spec §4)

Kind Builtins
Readers (pure) notes note title content attrs links backlinks position attr search resolve-title agents run-agent
Mutators (!) create-note! set-title! set-content! (derives [[wiki-links]]) set-attr! remove-attr! move-note! resize-note! link! unlink! delete-note!

Field accessors work on a note map (as self in a formula) or an id, so (content self) reads directly with no round-trip. Mutators are id-based, return the updated note map, and surface errors as values (unknown id → LdError::User).

WASM

WasmNotebook::evalLambdadelta(src) runs notebook-aware λδ in the browser — a thin wrapper over the seam (share the notebook via Rc<RefCell>, install the host, evaluate, reclaim). The pure lambdadeltaEval from L0 stays for host-free evaluation.

Why it matters

This unblocks L1 formulas. The canonical example is a passing test:

(count (words (content (note (resolve-title "Alpha")))))   ; → word count

Verification

  • cargo test79 green (9 new host tests: formula-over-a-note, search, set-attr!/create-note!/link!/set-content! mutating the real notebook, note-map-as-self, unknown-id-errors; + the kernel & core suites; + seam doc-tests).
  • cargo clippy --all-targets -- -D warnings — clean (default and --features wasm).
  • cargo fmt --check — clean. cargo check --features wasm — clean. Production code stays panic-free.

Next (per the agreed "1 then 2")

Host bindings first (this PR) → then finish the language core: hygienic defmacro + gensym/macroexpand and multimethods (defmulti/defmethod). Also still deferred: evaluation-context gating (formula pure vs. action may-mutate, spec §5), let/fn destructuring.

Note: estate governance checks are pre-existing estate policy; rust-ci is the relevant product gate.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PAEqVP7Y3bZDhtAaPUTbps


Generated by Claude Code

The moment λδ first touches a note. New `core/src/lambdadelta_host.rs` lives
OUTSIDE the kernel (spec §7): the kernel stays note-agnostic, and this module
registers the notebook builtins INTO a kernel through the seam
(`Interp::register_builtin`). Nexia-List is simply the first host.

Bridge (spec §2):
- `note_to_value` renders a note as an immutable snapshot map
  (:id :title :content :attrs :links :backlinks :position :size :prototype
  :type :created-at :modified-at) with `#uuid`/`#inst` tagged ids/instants.
- Total, lossless JSON attribute ↔ λδ value mapping (object keys ↔ keywords;
  documented lossy edges: keyword/set/symbol on write, functions → null).

Builtins (spec §4):
- Readers (pure): notes, note, title, content, attrs, links, backlinks,
  position, attr, search, resolve-title, agents, run-agent. Field accessors
  work on a note *map* (as `self`) or an id, so formulas read `self` directly.
- Mutators (`!`): create-note!, set-title!, set-content! (derives [[wiki-links]]),
  set-attr!, remove-attr!, move-note!, resize-note!, link!, unlink!, delete-note!.
  Effects are id-based and return the updated note map; errors are values.

WASM: `WasmNotebook::evalLambdadelta` runs notebook-aware λδ in the browser —
a thin wrapper over the seam (share the notebook, install the host, eval,
reclaim). The pure `lambdadeltaEval` from L0 remains for host-free evaluation.

This unblocks L1 formulas: the canonical example
`(count (words (content (note (resolve-title "Alpha")))))` is a passing test.

Verification: `cargo test` 79 green (9 new host tests + kernel + core suite +
seam doc-tests); clippy `-D warnings` clean (default + wasm); fmt clean;
`cargo check --features wasm` clean. Production code stays panic-free.

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

sonarqubecloud Bot commented Jul 4, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 62 issues detected

Severity Count
🔴 Critical 1
🟠 High 30
🟡 Medium 31

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Issue in scorecard.yml",
    "type": "missing_workflow",
    "file": "scorecard.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Issue in boj-build.yml",
    "type": "missing_timeout_minutes",
    "file": "boj-build.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "missing_timeout_minutes",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "missing_timeout_minutes",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in codeql.yml",
    "type": "missing_timeout_minutes",
    "file": "codeql.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath
hyperpolymath marked this pull request as ready for review July 4, 2026 12:10
@hyperpolymath
hyperpolymath merged commit 43ff829 into main Jul 4, 2026
24 checks passed
@hyperpolymath
hyperpolymath deleted the claude/project-scope-planning-5zoaze branch July 4, 2026 12:11
hyperpolymath added a commit that referenced this pull request Jul 9, 2026
…lude (#43)

## What this is

The **language core, completed** (spec §3) — the second half of "1 then
2": **full macro hygiene**, procedural `defmacro`, **multimethods**, and
a **sugar prelude**. Builds on the L0 kernel (#35) and notebook host
(#36); the kernel/host seam is untouched, so the notebook host inherits
all of this for free.

You chose **B — full set-of-scopes hygiene now, in one pass** — and
that's what this is: not the weaker Clojure auto-gensym model.

## Full hygiene (and it's genuinely full)

Kohlbecker-style **marks**, encoded in symbol names. Each macro
expansion gets a fresh mark; quasiquote tags the symbols a *template
introduces* with it. Both hygiene properties are implemented **and
tested**:

| Property | Test | Result |
|---|---|---|
| Introduced bindings never capture | `(defmacro m [e] \`(let [x 2] ~e))
(let [x 1] (m x))` | **1**, not 2 |
| Free identifiers are referentially transparent | `(defmacro inc [n]
\`(+ ~n 1)) (let [+ -] (inc 5))` | **6**, not 4 |

So the prelude's `and`/`or`/`if-let` use plain `v`/`tmp` with **no
`foo#`** — "macro-introduced bindings never capture… *without the author
asking*" (spec §3), delivered literally.

Mechanics: marked free identifiers fall back to the definition (global)
env; special forms **and** macros dispatch on the *base* name (so a
marked `let`/`if`/user-macro in a template still works); `Display`
strips marks so output stays readable.

## What's in this PR

- **`defmacro`** — procedural transformer over *unevaluated* forms;
`gensym`, `macroexpand`, `macroexpand-1`.
- **Multimethods** (spec §3, open dispatch — not class-OO): `defmulti
name dispatch-fn` + `defmethod name dispatch-val [args] …` with
`:default`. Dispatch on *any* function of the args — e.g. a note's
`:type`:
  ```clojure
  (defmulti describe :type)
  (defmethod describe "task" [n] (str "task:" (:title n)))
  (defmethod describe :default [n] "other")
  ```
- **Prelude** (`prelude.rs`) — hygienic sugar written in λδ itself,
loaded at `Interp::new()`: `when unless cond and or -> ->> if-let
when-let`.

## Files

`value.rs` (mark primitives + `MultiMethod` + mark-stripping display) ·
`mod.rs` (macro/multimethod registries, mark counters, prelude load) ·
`eval.rs` (hygiene-aware symbol resolution, base-name dispatch,
`expand_macro`, `defmacro`/`defmulti`/`defmethod`, quasiquote marking) ·
`builtins.rs` (`gensym`/`macroexpand`) · `prelude.rs` (new) · `tests.rs`
(11 new tests).

## Deliberately deferred

`let`/`fn` destructuring; an explicit unhygienic escape hatch (if ever
justified); `case`/`match` (pattern matching). Each layers on without
touching the seam.

## Verification

- `cargo test` — **81 lib tests green** (11 new: user macros, both
hygiene properties, multimethods, all prelude sugar, `gensym`,
`macroexpand`) + host + golden + invariants + doc-tests.
- `cargo clippy --all-targets -- -D warnings` — clean (default **and**
`--features wasm`).
- `cargo fmt --check` — clean. Production kernel stays **panic-free**
(Hypatia-clean).

> **Note:** estate governance `workflow_audit` findings are pre-existing
estate policy, out of scope; `rust-ci` is the gate here.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01PAEqVP7Y3bZDhtAaPUTbps

---
_Generated by [Claude
Code](https://claude.ai/code/session_01PAEqVP7Y3bZDhtAaPUTbps)_

Co-authored-by: Claude <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Jul 16, 2026
Verified against the code rather than TOPOLOGY.md, which is stale:

  claim                     TOPOLOGY/wiki       reality
  λδ substrate              "planned"           built (#35, #36, #43 merged)
  core size                 ~623 LOC, 12 tests  5,344 LOC, 90 tests green

λδ is the largest subsystem in the core (~4,050 LOC, ~76%): reader, value
model, evaluator + Budget, hygienic macros, multimethods, prelude, and the
notebook host seam. Calling it "planned" materially misrepresents the project
to every audience the wiki serves.

- Home.md: substrate row planned -> built; status paragraph now cites measured
  figures and flags that TOPOLOGY.md predates the three LambdaDelta merges.
- Developer.md: add the λδ substrate as a first-class stack layer; Track A
  marked substantially landed, with the outstanding work named (.ld packages,
  surfacing L2+ through the disclosure ladder).

Toolchain verified end-to-end while checking this: `just build-wasm` (with
wasm-bindgen-cli 0.2.126, matching Cargo.lock), `just build`, and `just test`
all green — 90 Rust + 10 UI tests, including "TEA update delegates to the
wasm core".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Jul 17, 2026
…fix the Justfile (#46)

## Why

`TOPOLOGY.md` predated the three LambdaDelta merges (#35, #36, #43) and
never mentioned λδ. Because it is *the* status source, every downstream
doc inherited its stale figures — including the wiki landed in #45. This
re-derives the countable facts from the tree and corrects the cascade,
then fixes the Justfile gaps found along the way.

## TOPOLOGY was understating the project on six axes

| Claim | Reality |
|---|---|
| λδ absent entirely | **Built, not planned** — reader, value model,
evaluator + Budget sandbox, hygienic macros, multimethods, prelude, host
seam. ~3,400 LOC excl. tests, **~64% of the core**, its largest
subsystem. Now its own dashboard row |
| "12 passing unit tests" | **90** (81 unit + 3 exchange + 2 golden + 2
property + 2 doc) |
| WASM bridge "40%, in progress" | **Wired and exercised** — `Main.res`
loads the wasm at boot, `WasmStore` binds 21 of 31 exports, ui-ci runs a
TEA↔WASM contract test against the real bundle |
| CI "10%, landing in a parallel workstream" | `rust-ci.yml` +
`ui-ci.yml` **load-bearing since #22**, SHA-pinned, green on main |
| Web/PWA "no service worker yet (planned)" | Service worker +
webmanifest + icon **ship and are registered** (`Main.res:88`, #27) |
| UI "no drag-and-drop; GraphView placeholder" | Note drag
**implemented** (`View.res:397`); GraphView renders a circular layout |

**Overall ~35% → ~65%.** The critical path has **moved off the WASM
bridge onto the UI surface**: 10 exports are unbound in ReScript,
including both λδ entry points (`lambdadeltaEval`, `evalLambdadelta`) —
the substrate is reachable from JS but nothing calls it.

Also corrects **a mixed-basis error I introduced in #45**: λδ was
reported as "~76% of the core" by dividing 4,049 LOC (*with* tests) by
5,344 (*without*). Like-for-like it is 3,407/5,344 = **~64%**.

## Justfile

- **`just test` could not work on a clean checkout** — the UI tests
import generated `*.res.js` and the wasm bindings, so type-check failed
with 4 `TS2307` errors. Now `test → build → build-wasm`. Warm no-op
costs ~0.45s, so it is not a tax on the inner loop; added `test-rust`
for the Rust-only loop.
- **`just check` was weaker than CI** (no `--all-targets`, no
`--features wasm`, ran from `core/`) — it could pass locally while CI
failed. Now mirrors `rust-ci.yml` exactly.
- **`just doctor` ignored the wasm toolchain** — the one thing that
actually blocks the browser build. Now checks the wasm32 target and
compares the wasm-bindgen CLI against `Cargo.lock`, printing the exact
fix command on drift.
- **`crg-grade` and `crg-badge` were dead on arrival** — Make-style
`$$(...)` under just/sh expanded `$$` to the shell PID, so `(` was a
syntax error. They failed 100% of the time. Rewritten as bash shebang
recipes.

## Verification

- `just check` — pass (now compiles the `wasm` feature, which the old
one never did)
- `just test` from a **fully cleaned tree** (no `web/wasm`, no
`*.res.js`) — rebuilds and passes **90 Rust + 10 UI**
- `just doctor`, `crg-grade` (→ `D`), `crg-badge` — all run; doctor's
FAIL path verified by hiding wasm-bindgen from `PATH`
- `asciidoctor` renders README/ROADMAP/QUICKSTART-DEV clean; `just
wiki-sync dry` still previews correctly

## Deliberately not done — needs your call

- **`READINESS.md` grade D is probably stale.** All three *Path to C*
criteria now appear met (product CI on every PR ✅, UI tests in CI ✅,
WASM bridge built + smoke-tested ✅). The grade is assigned by audit, so
I corrected the facts and flagged a re-audit rather than self-promoting.
`just crg-badge` publishes that letter, so the badge is currently
understating the project.
- **`must-spdx-headers` is a broken gate** (pre-existing, untouched
here). It runs `find . | head -20`, so it checks an arbitrary,
readdir-order-dependent fifth of the tree *and* walks vendored
`node_modules/`. It currently fails on `@rescript/react`'s own `.res`
files — **every tracked source file does have SPDX**. Worse,
`contractile.just` has drifted from its source:
`contractiles/must/Mustfile.a2ml` contains no `spdx-headers` rule at
all, and its `no-hardcoded-paths` rule (severity: **critical**) is
missing from the generated file. The `contractile` tool isn't installed
to regenerate, and this is your standards ecosystem — so I left it
alone.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants