v0.12.0
Added
-
HTML render splice (#563).
lexd convert --to html(via
lex_babel::serialize_to_html_with_registry) now actually splices
handler-rendered HTML into the output for annotations whose
registered handler returnsRenderOut::String. The default
<!-- lex:label -->... body ...<!-- /lex:label -->rendering
is replaced by the handler's raw HTML; the annotation's body
events are suppressed (the handler owns the full rendering of its
labelled node). Mechanism: AST-walk and event-walk visit
annotations in matching document order; the HTML builder
maintains a counter, looks up the matching plan entry on
Event::StartAnnotation, and emits a sentinel comment that
string-replaces with the handler's HTML after DOM serialization.
Handler diagnostics fromon_rendercontinue to surface via
HtmlExportOutcome::diagnostics.RenderOut::WireAstoutputs fall through to default rendering
with the existing format-shape-mismatch diagnostic;
WireAst → HTMLconversion is a follow-up.- Document-level annotations (extracted into the IR's synthetic
frontmatterblock before events are emitted) are not splice
targets — the existing frontmatter path handles them as
metadata. Per-element annotations (the actual extension use
case) splice correctly. - Multi-annotation documents with trailing annotations may hit
an IR-ordering quirk that breaks the counter-based alignment.
Single-annotation documents and clearly-separated annotations
work; see test
multi_annotation_splice_is_a_known_limitationfor context.
-
lexd labels emit <doc> [--label X]... [--namespace N]...
(#564). Pull-based
NDJSON export — one record per labelled annotation / verbatim in
the document. Output uses the wirePosition/Rangetypes so
the same parser that consumes LSP hover and extension hook
payloads consumes emit output unchanged. Body shapes are tagged:
{kind: "none"},{kind: "text", text: "…"}, or{kind: "lex", wire: [...]}.--labeland--namespaceare repeatable and
intersect. No registry boot required —to_wire_nodeproduces
the wire form without schema lookup, so this command runs against
documents whose namespaces aren't registered. Exit 0 on success
(including zero matches), 2 on parse failure. -
Resolver machinery (#546 item A, partial).
lex-extension-host
gains a pluggable [Fetcher] trait, aFetcherRegistry, and a
content-keyedResolverCache(24-hour TTL for mutable refs,
indefinite for immutable). The four remote schemes
(github:/gitlab:/https:/git+ssh:) ship as stubFetcher
impls that returnFetchError::Unimplemented— same observable
behaviour as before, but plugged into the new dispatch so an
implementer's PR per lex#562
only needs to swap the stub for a real fetcher.path:stays
built-in (no cache, no fetcher) — it's special-cased in
resolve_namespace_withbefore the registry is consulted. -
resolve_namespace_with(uri, workspace_root, ®istry, &cache)
for callers that want explicit control over the registry + cache
(hosts constructing one cache + registry at boot time; tests
using tempdir caches and custom fetchers). The existing
resolve_namespace(uri, workspace_root)is now a convenience
wrapper usingdefault_fetcher_registry()and
ResolverCache::user_default()(XDG-cache-home aware). -
End-to-end validation:
tests/resolver_http_e2e.rsexercises the
full pipeline (URI parse → registry dispatch → cache miss →
fetch → cache hit on second resolve) with a hand-rolled HTTP
Fetcheragainst astd::net::TcpListenermock server. Proves
the machinery works for a non-stub fetcher without depending on
any external network. -
sha2workspace dependency, used byResolverCachefor the
content-key hash function (hash(scheme + body + rev + subdir)
→ 64-char hex directory name under the cache root). -
Extract-to-include LSP command
(#497).
lexd-lspregisters a new workspace commandlex.extractToInclude
that splits a selected slice of a Lex document out into a new file
referenced via:: lex.include src="…" ::. The command takes
positional arguments[uri, range, src], validates the target
path against the configured includes root (returns distinct
ExtractErrorvariants for empty / URL / absolute / root-escape /
existing-target / missing-parent-dir cases), indent-shifts the
selection so its shallowest non-blank line lands at column 0,
parses the shifted text as a Lex fragment, and returns an atomic
WorkspaceEditcontainingCreateFile+ target-content
TextEdit+ host-replaceTextEdit. All logic lives in
lex_lsp::features::extractso per-editor shims stay thin —
editor wiring is tracked at
#498. The deeper
GeneralContainerhost-policy check (e.g. rejecting a Session
selected for extraction into a Definition body) is reserved for
a follow-up; theExtractError::ContainerPolicyvariant stays in
the enum for it.
Fixed
lexd inspectsilently dropped verbatim blocks when source
mentionedlex.includein prose
(#505). The CLI's
expand_includes_to_sourceround-trips source through
resolve_from_source→serialize_to_lex_with_rules→ re-parse
whenever the literal stringlex.includeappears anywhere — even
in backticked prose or proposal docs describing the feature. The
Lex serializer wrote a verbatim block's subject line directly
after the preceding paragraph (the parser consumes the
separating blank line as part of the verbatim's preamble, so it
isn't represented as aBlankLineGroupin the AST and no other
visitor emits it). On the re-parse the paragraph absorbed the
subject and the verbatim — plus everything it bracketed — silently
disappeared from the resolved tree.visit_verbatim_blocknow
emits the leading blank line, suppressed when the verbatim is the
first child of aSubject:-style container opener (Definition,
verbatim group) where a blank at column 0 would terminate the
container.
Changed
- Trust-matrix flip (lex#528 PR 12d).
lex_fmt::boot_registry
now installs the OS-appropriateSandboxon the trust gate (via
the newlex_extension_host::sandbox::os_default()factory) and
passes the sameArc<dyn Sandbox>to every
SubprocessHandler::spawn_with_sandboxcall. The effect is
per-OS:- Linux: declared-pure subprocess handlers (
capabilities: { fs: false, net: false }) auto-trust under the default
engine — no prompt, no--enable-handlersflag. The kernel
enforces the declared capability set viaLinuxSandbox
(seccomp + landlock). This is the user-visible matrix flip
promised by §8 of the proposal. - macOS: pure handlers continue to prompt because
MacosSandbox::supportsis pinned tofalsepending a
hardened(deny default)SBPL profile. The same prompt UX
Windows uses. - Windows + other: pure handlers continue to prompt —
NullSandboxis the fallback.lex#528PR 12c (Windows Job
Objects + restricted tokens) is unscheduled; the trust gate
routes those subprocesses to the prompt path until it ships.
Full subprocess handlers (anycapabilitiesdeclaringfsor
net) always prompt regardless of OS —supportsreturns
falsefor non-pure shapes on every impl.
- Linux: declared-pure subprocess handlers (
Added
lex_extension_host::sandbox::os_default() -> Arc<dyn Sandbox>:
factory selecting the per-target default sandbox. Used by the
δ-phase trust-matrix flip (above); also available to embedders
building a customEnginewho want the same per-OS dispatch.lex-extension-host::sandbox::MacosSandbox: macOS implementation of
theSandboxtrait (lex#528 PR 12b). Installs a Sandbox Profile
Language (SBPL) policy via the libSystemsandbox_initAPI inside
apre_exechook so it applies to the child afterfork()and
survivesexecve(). The v1 profile deniesnetwork*and reads of
/etc(covering both probe-fixture targets) while keeping other
operations permissive enough for the system loader to bring up a
Rust binary.supports()returnsfalsefor every capability
shape on macOS until a hardened(deny default)profile lands —
the current(allow default)profile still permits writes
anywhere on disk and reads outside/etc, so auto-trusting on it
would let apure-declared handler silently exfiltrate or modify
user data. The trust gate routes pure handlers to the prompt path
on macOS, same as Windows or no-landlock Linux, until the
hardened profile work ships.sandbox_initis deprecated since
macOS 10.8 but still resolvable in libSystem through Sequoia —
same dependency Apple'ssandbox-executility relies on.lex-extension-host::sandbox::LinuxSandbox: Linux implementation of
theSandboxtrait (lex#528 PR 12a). Combineslandlock(filesystem
allowlist — dynamic-loader paths + the handler binary itself) and
seccompiler(network-stack syscall deny) installed via apre_exec
hook so the policy applies to the child afterfork()and survives
execve().supports()returnstrueonly for the
Capabilities::is_pure()shape; finer capability shapes report
unsupported until the schema grows the corresponding fields. Build
surface remains MIT/Apache (landlockMIT/Apache,seccompiler
Apache/BSD) —lex-extension-hostand downstream consumers stay MIT.lex-extension-host::sandbox: new module hosting theSandbox
trait (the OS-level sandbox facade), aSandboxErrorerror type,
and aNullSandboxno-op default that reports
supports(_) == falsefor every capability set on every platform.
Foundation for the δ-phase trust matrix flip (lex#528): per-OS
sandbox implementations (12a Linux via seccomp+landlock, 12b macOS
via sandbox-exec, 12c Windows via Job Objects + restricted tokens)
plug in behind this trait; the trust gate consults
Sandbox::supports(caps)to decide whether a declared-pure handler
can auto-trust without a prompt.SubprocessHandler::spawn_with_sandboxcompanion to the existing
spawn. TakesArc<dyn Sandbox>so the host can install one
instance and share it withTrustGate. The worker thread calls
apply_to(&mut cmd, capabilities)beforecmd.spawn()so the
kernel enforces declared capabilities from the child's first
instruction.spawn(the existing entry point) now delegates to
spawn_with_sandboxwithNullSandbox— no behaviour change for
current callers.SpawnError::Sandbox(String)variant for policy-install failures
on the host side. Same handling as other spawn errors today;
retry-with-prompt path is future work alongside PR 12d.TrustGate::set_sandbox(Arc<dyn Sandbox>)and
TrustGate::sandbox()accessor (returns the Arc by clone). The
default install isNullSandbox, so β/γ behaviour is preserved
(every subprocess prompts). When PR 12a/b/c land,lex-fmt
swaps in the OS-appropriate impl and shares the same Arc across
the gate and the transport — guaranteeing the auto-trust decision
is anchored on the sandbox that actually enforces policy.
Changed
- Renamed the
lex-enginecrate tolex-fmtand positioned it as the
canonical Rust embedder API for the lex document format. The
boot_registry/ExtensionSetup/BootOutcometypes move
unchanged; only the crate name (and theusepaths inlexd/
lexd-lsp) change. Sets up PR 11'sEngine::builder()to land in
a discoverable place (use lex_fmt::Engine) rather than under a
name that says "boot helper".lex-engine0.11.0 stays published
on crates.io for historical reference; future releases publish as
lex-fmt. TrustGate::evaluatenow short-circuits toTrustedfor the
(transport=Subprocess, capability=Pure)pair when
sandbox.supports(Capabilities::default())returnstrue. With
NullSandbox(the default), this branch is inactive —
observable behaviour is unchanged. Tests cover both the
supported and unsupported paths via a mock sandbox.