The core of lichen gets smaller and its parts get clear boundaries. Code that was spread through lichen-site and lichen-server moves into self-contained crates, and everything optional attaches to the core through a hook registry instead of being named by it.
Names. What the code called an Ask is now a distinct …Request type for site messages, probes, publishing and git operations, with the site request table in requests.rs.
New leaf crates. lichen-ssrf (URL and address validation), lichen-types (permission, principal, shared request, site-record and build data), lichen-sandbox-policy (permission lists and the sandbox compiler), lichen-git-command (the one place a git command is built, with its hermetic environment), lichen-scripts (script runtimes, child protocol, worker launch), lichen-shortcodes (parsing, caching, orchestration and path preparation) and lichen-three-way-merge. Each builds alone.
One hook registry. Add-ons register ordered startup, after-build handlers, publication targets and shell routes with the core, and the core dispatches typed events through one shared dispatcher with queued events for every hook, replacing the earlier special channels. Startup is registered across the threads that own each step, the redundant routing view is gone, and features are validated at startup (git is required before git's startup effects run). A later clean-up leaves one listener table with plain functions, add-on lists, one start, one reply rule, and an AdminSecrets that keeps the owner's key in memory.
Git as an add-on. Git owns its migrations, history route and admin area; the site process's git and the hub are git's; the batch route and its flags are git's (features.publish goes); git's hook listeners, its code that sat in core files, its start steps and publishing are written in git and come in through one assembly entry. With git off the three-way merge takes a simpler path and a diverged edit ends as a conflict copy with nothing lost; with git on the merge is as before.
Feature boundaries and close. Helper code compiles only with the features that own it, each crate builds alone, and the phase's clippy warnings are fixed. The code counter counts Rust only.
MINIMAL-CORE, the whole phase in one reading PR. Plan: https://tangled.org/notplants-bot.bsky.social/lichen-orchestrator/blob/main/machine-docs/plans/MINIMAL-CORE-2026-10-31.md
Only the phase's Rust product code is here: every .rs file under crates/<crate>/src/ (no tests folder in its path), which is what e2e/ci/count_product_code_lines.py counts as product. The head is the base plus one commit that sets each of those files to its content at the end tip (added, changed, deleted and renamed files alike). Tests, docs, scripts and non-Rust files (including Cargo.toml and Cargo.lock) are not in it, so the crates here are not meant to build on their own. Reading copy; nothing here is to be merged.
The earlier per-unit reading PRs on this repository for units 1 to 6 (#75 to #80) stay open and unchanged; this PR covers them and the later units.
Source PRs on lichen.page.review (product), in order #
- #3632 use request names
- #3634 extract the SSRF guard and ratchet core size
- #3636 extract permission types and policy
- #3640 extract shared request and build data
- #3642 extract git command construction
- #3644 scripts engine leaf
- #3646 shortcode syntax and cache leaf
- #3649 Move shortcode orchestration and path preparation to leaf crates
- #3651 Use Lichen path mapping for its own builds
- #3653 Assemble ordered after-build handlers
- #3655 Assemble the registry foundation
- #3657 Dispatch publication targets through the registry
- #3659 Register admin startup and validate fixed features
- #3661 Register main identity initialization
- #3663 Assemble shell routes through the hook registry
- #3665 Register main Git listener startup
- #3667 Register admin hub wake startup
- #3669 Extract the three-way merge leaf
- #3671 Select optional Git for text reconciliation
- #3673 Match helper compilation to feature owners
- #3675 Assemble shell routes as ordinary contributions
- #3677 Register ordered startup across its owning threads
- #3679 Share typed hook dispatch across build and publication
- #3681 Remove the redundant startup routing view
- #3683 Require Git before enabled startup effects
- #3685 Remove unused build notifications
- #3687 One hook dispatcher, queued events for every hook
- #3689 Git owns its migrations, history route and admin area
- #3691 The site process's git and the hub are git's; the lane asks git
- #3693 The batch route is git's; features.publish goes; the count is Rust only
- #3695 git's hook listeners are written in git
- #3697 git's code in core files moves into git
- #3699 git's start steps are one call each, and publishing is git's
- #3701 git comes into the site through its one assembly entry (MINIMAL-CORE G6d)
- #3703 one listener table, git's publish as one listener, core's admin grants, the admin thread's one context (MINIMAL-CORE hook clean-up)
- #3705 the start and the opening as plain functions, listeners as plain functions in one literal (MINIMAL-CORE hook clean-up)
- #3707 Hook clean-up: add-on lists, one start, the owner's data, one reply rule, AdminSecrets with the owner's key in memory
- #3709 MINIMAL-CORE close — each crate builds alone, the phase's clippy warnings, the crossing keeps git's code and the room's conflict
Base, end tip, count #
Base abe00d2f3eaf34883529612266651f8bb646f645. End tip f6e08810d52bbc3923249c43af3f9537559a3f68. Head 362c88c8967c532db8d0f89831885d577d70e027 (202 files: 170 added or changed, 32 deleted).
Counter, base to end tip (the phase as lichen.page.review has it):
count-product-code-lines abe00d2f3eaf..f6e08810d52b: NET +369 · counted +12293 -12043 · moved out of the counted set +119 · comment-only deletions flagged 161 · macros flagged 0 · tests-folder code flagged 0
Counter, base to head (this PR):
count-product-code-lines abe00d2f3eaf..362c88c8967c: NET +250 · counted +12283 -12033 · moved out of the counted set +0 · comment-only deletions flagged 161 · macros flagged 0 · tests-folder code flagged 0
The counter lines differ only in non-Rust lines that this PR leaves out, as the operator asked for Rust product code only: the end tip's count includes 119 lines of frontend JavaScript and TypeScript in the 'moved out of the counted set' list (frontends/builtin/app/app.js 10, frontends/hugo-cms/app.js 100, three .tsx/.ts files 8 in all and lichen-ts/src/site.ts 1) and a +10 -10 in frontends/builtin/ssr/app-ssr.js, which crates/lichen-site/src/ssr/runtime.rs includes. Every Rust file's figures are identical (counted +12283 -12033 on both sides of that included file).
Check #
git diff base head lists only counter-product Rust files, and the head's content equals the end tip's for each of them.
Record #
MINIMAL-CORE COMPLETE at f6e08810d52bbc3923249c43af3f9537559a3f68 (coordination record of 2026-11-07).