yvgude--lean-ctx
26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
193 行
11 KiB
Markdown
193 行
11 KiB
Markdown
# Addon Bootstrap Engine — Phase 2 (implemented)
|
|
|
|
> Status: **implemented** in `rust/src/core/addons/bootstrap.rs` (+ manifest,
|
|
> policy, store, CLI wiring). This document is the design of record for the
|
|
> GitLab epic *Addon Bootstrap Engine* (`root/lean-ctx#1105`, subtasks
|
|
> `#1106`–`#1110`). Where the shipped behaviour refines the original plan it is
|
|
> noted inline.
|
|
|
|
## Problem
|
|
|
|
Today `lean-ctx addon add` is **declarative**: it appends a
|
|
`[[gateway.servers]]` entry to the global config and records the install — it
|
|
never fetches or installs a package. That is sufficient for **ephemeral
|
|
runners** (`npx`, `uvx`), which download and execute their package lazily on the
|
|
first tool call. So `repomix` and `serena` already install on add.
|
|
|
|
It is **not** sufficient for tools that need a real, one-time bootstrap before a
|
|
runnable command exists:
|
|
|
|
| Tool | Why a runner can't do it |
|
|
|-----------|----------------------------------------------------------------------|
|
|
| Headroom | Ships as `headroom-ai[all]`; `uvx --from` breaks on its entry points — needs `uv tool install`. |
|
|
| Graphify | `uv tool install "graphifyy[mcp]"` **and** a pre-built `graph.json`. |
|
|
| Cognee | Clone + `uv sync`; no single-command runner. |
|
|
| Letta | `npm i -g` plus a long-running server instance. |
|
|
|
|
For these the registry stays **listed** (homepage + manual instructions). The
|
|
bootstrap engine closes that gap: a declarative `[install]` block that lean-ctx
|
|
can execute idempotently, with a clean uninstall path and the same security bar
|
|
as the rest of the addon system.
|
|
|
|
## Goals / non-goals
|
|
|
|
**Goals**
|
|
- One declarative `[install]` block per addon, pinned and auditable.
|
|
- Idempotent install + reliable uninstall (no orphaned global packages).
|
|
- Reuse the existing trust/audit pipeline — no new ad-hoc shell-outs.
|
|
- Keep Phase-1 behaviour unchanged when no `[install]` block is present.
|
|
|
|
**Non-goals**
|
|
- No arbitrary script execution (no `curl | sh`, no inline shell).
|
|
- No secret provisioning (Mem0 / Claude-Context keys stay the user's job; the
|
|
engine only documents and validates the required env names).
|
|
- No new network fetch of the registry itself (still compiled-in / signed override).
|
|
|
|
## The `[install]` manifest block
|
|
|
|
```toml
|
|
[install]
|
|
manager = "uv" # one of: pip | uv | cargo | npm | brew | dotnet
|
|
package = "headroom-ai[mcp]" # the package spec the manager understands
|
|
version = "0.27.0" # MANDATORY exact pin (no ranges, no "latest")
|
|
bin = "headroom" # binary the [mcp] command expects (PATH idempotency)
|
|
# verify = ["headroom", "--version"]# optional argv probe; exit 0 ⇒ already installed
|
|
```
|
|
|
|
Rules (enforced by `AddonInstall::validate()`, called from `manifest.validate()`):
|
|
- `manager` ∈ a fixed allowlist (`uv`/`pip`/`cargo`/`npm`/`brew`/`dotnet`). Each manager
|
|
maps to a **fixed argv template** the engine owns — the manifest never supplies
|
|
raw shell.
|
|
- `version` is required and must be an exact pin (empty / `latest` / `*` are
|
|
rejected).
|
|
- `package`, `version`, `bin` and every `verify` element are rejected if they
|
|
contain shell metacharacters (`| ; & $ \` > <`, newlines) — defence-in-depth,
|
|
since the engine never uses a shell.
|
|
- **Idempotency check** (shipped refinement): `verify` is *optional*. With no
|
|
`verify`, the engine checks whether `bin` resolves on `PATH`; `verify` is an
|
|
escape hatch (argv, exit 0 ⇒ installed) for tools whose presence needs a
|
|
deeper probe.
|
|
- The block is only meaningful together with a runnable `[mcp]` block whose
|
|
`command` is produced by the install (e.g. `headroom`).
|
|
|
|
### Manager → argv templates (engine-owned)
|
|
|
|
| `manager` | install argv | uninstall argv |
|
|
|-----------|--------------------------------------------------|----------------------------------|
|
|
| `uv` | `uv tool install {package}=={version}` | `uv tool uninstall {base}` |
|
|
| `pip` | `pip install --user {package}=={version}` | `pip uninstall -y {base}` |
|
|
| `cargo` | `cargo install {base} --version {version}` | `cargo uninstall {base}` |
|
|
| `npm` | `npm install -g {package}@{version}` | `npm rm -g {base}` |
|
|
| `brew` | `brew install {package}` (formula carries the pin, e.g. `node@22`) | `brew uninstall {base}` |
|
|
| `dotnet` | `dotnet tool install --global {package} --version {version}` | `dotnet tool uninstall --global {base}` |
|
|
|
|
`{base}` is `{package}` with extras and any inline version stripped
|
|
(`headroom-ai[mcp]` → `headroom-ai`), keeping an npm scope intact
|
|
(`@scope/pkg`). The manifest chooses a manager + package + pin; it **cannot**
|
|
influence the flags or inject extra argv. Every value is passed as a *discrete*
|
|
argv element via `std::process::Command` — no shell, no interpolation.
|
|
|
|
## Install lifecycle
|
|
|
|
The executor lives in `addons/bootstrap.rs` (`ensure_installed` / `uninstall`)
|
|
and is orchestrated by the CLI (`cli/addon_cmd.rs`) *after* consent and *before*
|
|
the health probe. The core `addons/install.rs` stays pure — it only persists the
|
|
receipt — so its unit tests never spawn a process.
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
add["addon add <name>"] --> has{has [install]?}
|
|
has -- no --> wire["wire [[gateway.servers]] (Phase 1)"]
|
|
has -- yes --> gate["bootstrap gate: validate + consent"]
|
|
gate --> present{already present? (verify argv)}
|
|
present -- yes --> wire
|
|
present -- no --> run["run engine-owned install argv (pinned)"]
|
|
run --> verify["run verify argv → must exit 0"]
|
|
verify -- ok --> record["record install receipt (manager, package, version, bin)"]
|
|
record --> wire
|
|
verify -- fail --> rollback["best-effort uninstall + abort, no wiring"]
|
|
```
|
|
|
|
- **Idempotency**: check presence *first* (`verify` argv, else `bin` on `PATH`);
|
|
if already satisfied, skip the manager entirely and just wire. Re-running `add`
|
|
is safe and reports `Already installed — skipped`.
|
|
- **Pre-flight**: when an install *is* needed, verify the manager itself exists
|
|
(`LEANCTX_BOOTSTRAP_<MGR>` override path, else the bare name on `PATH`) before
|
|
spawning it. A missing manager fails with an install hint
|
|
(`install uv → https://docs.astral.sh/uv/…`) instead of a raw spawn error, and
|
|
the `add` disclosure shows a `requires: <manager> on PATH — ✓/✗` line up front
|
|
so the gap is visible *before* consent.
|
|
- **Receipt**: `<data_dir>/addons/installed.json` carries an `install` record
|
|
(manager, package, version, bin) — content-only, no timestamps, so it stays
|
|
determinism-friendly (#498). `remove` reads it to uninstall.
|
|
- **Uninstall**: `addon remove` runs the manager's uninstall argv for packages
|
|
*this engine installed* (tracked by receipt) — never something the user had
|
|
already. It is best-effort: a failed uninstall logs a note but never blocks the
|
|
unwire that already succeeded.
|
|
- **Failure**: a non-zero manager exit aborts `add` before anything is wired. A
|
|
clean install whose `bin` is not yet on `PATH` is a non-fatal warning (a PATH
|
|
setup issue, not a failed install), and the subsequent health probe still
|
|
guards a truly broken wiring.
|
|
|
|
## Security gates
|
|
|
|
The bootstrap surface is gated at four layers (shipped):
|
|
|
|
- **Structural validation** (`AddonInstall::validate()`, hard error): unknown
|
|
manager, missing/floating version, or shell metacharacters in
|
|
`package`/`version`/`bin`/`verify` reject the manifest. Because it runs from
|
|
`manifest.validate()`, every path is covered — `addon add`, `addon audit`,
|
|
`from_path`, and the registry validator (so a bad block fails CI's
|
|
`bundled_registry_passes_security_validator`).
|
|
- **Capability coherence**: a declared `[install]` block makes
|
|
`trust::wiring_uses_network` return `true`, so an addon that *also* declares
|
|
`[capabilities] network = "none"` trips the existing `cap_net_underdeclared`
|
|
audit — same gate as the `npx`/`uvx` runner case.
|
|
- **Consent**: the `add` preview prints the **exact** install + uninstall argv,
|
|
the manager, the package and the pin *before anything runs*, then requires the
|
|
standard yes/no (`--yes` to skip in CI). `add` itself is the user's explicit,
|
|
consented action.
|
|
- **Policy floor**: `addons.allow_bootstrap` (global-only). Default **on** — the
|
|
whole point is that `add` installs — but a team that forbids local
|
|
package-manager execution sets it to `false`, and `policy::gate` refuses any
|
|
`[install]` addon before a single command runs.
|
|
|
|
## What this unlocks — and the honest migration status
|
|
|
|
The engine is generic across all five managers. Registry entries flip to
|
|
install-on-add **only when the tool actually ships a clean, pinned,
|
|
runnable-out-of-the-box MCP server** — never with fabricated wiring.
|
|
|
|
| Tool | Status | Why |
|
|
|-----------|---------------|-----|
|
|
| **Headroom** | ✅ migrated | `uv tool install "headroom-ai[mcp]"` (pinned `0.27.0`) → `headroom mcp serve`; a local, secret-free stdio MCP server. The flagship install-on-add. |
|
|
| Graphify | listed | Package installs cleanly (`graphifyy[mcp]`), but its MCP server needs a **pre-built `graph.json`** (`python -m graphify.serve graph.json`) — no out-of-the-box server to probe. |
|
|
| Cognee | listed | MCP server needs a **repo clone + `uv sync`** (upstream issue #1815); no working pinned one-liner yet. |
|
|
| Letta | listed | A pinned `letta-mcp-server` package exists, but the server needs `LETTA_API_KEY` + a Letta backend to start — key-gated, not one-click. |
|
|
|
|
Mem0 and Claude-Context likewise remain key-gated: the engine *could* install
|
|
their package, but cannot provision `MEM0_API_KEY` / `OPENAI_API_KEY` + Milvus —
|
|
those stay documented prerequisites. Each tool above flips to installable with a
|
|
**one-line registry change** (an `[install]` + `[mcp]` block) the moment upstream
|
|
ships a clean server — no further engine work.
|
|
|
|
## Rollout — done
|
|
|
|
1. ✅ `[install]` parsing + validator gates.
|
|
2. ✅ Install/uninstall executor (`bootstrap.rs`), gated by `addons.allow_bootstrap`.
|
|
3. ✅ Headroom migrated; the bundled registry stays green on
|
|
`bundled_registry_passes_security_validator`. Graphify/Cognee/Letta wait on a
|
|
clean upstream MCP server (see table) rather than shipping broken wiring.
|
|
|
|
## Operational notes & open questions
|
|
|
|
- **Manager path override** (shipped): set `LEANCTX_BOOTSTRAP_<MANAGER>` (e.g.
|
|
`LEANCTX_BOOTSTRAP_UV=/opt/uv`) to pin the exact manager binary for locked-down
|
|
environments; otherwise the manager is resolved from `PATH`.
|
|
- Open: per-manager cache/location detection for richer "already present" checks
|
|
(today: `verify` argv, else `bin` on `PATH`).
|
|
- Partly shipped: `add` now pre-flights the manager's existence (with an install
|
|
hint); a full `doctor` sweep over *already-installed* addons is still open.
|
|
- Open: Windows support for the manager templates (the executable probe already
|
|
falls back to "is a file" off-unix; argv templates assume POSIX managers).
|