lattice_plugin_trace/lib.rs
1//! The buffer-backed plugin boundary-trace views: `plugin-trace-mode` and the
2//! `:plugin-trace` ex-command (PO.4).
3//!
4//! A pure PROVIDER crate (the `lattice-plugin-manager` shape): it owns
5//! `plugin-trace-mode` (major, read-only) + the `:plugin-trace` ex-command, and
6//! reads the host's `PluginTracer` (PO.1/PO.3) via the `PluginTracerHandle`
7//! service at activation. Everything wires through the generic [`SubsystemBoot`]
8//! seam, so adding it to the editor is **one line** in the host's Phase-B install
9//! list and **zero** host internals — no `Editor::` method, no host `Action`
10//! variant (the mode-ownership acid test).
11//!
12//! Open flow, all generic primitives (no provider-specific host code):
13//! 1. `:plugin-trace` (registered here) returns
14//! [`Effect::OpenSyntheticBuffer`](lattice_grammar::Effect::OpenSyntheticBuffer)
15//! `{ name: "*plugin-trace*", mode_id: "plugin-trace-mode" }`.
16//! 2. The host generically ensures that buffer under `plugin-trace-mode` +
17//! activates it (`Editor::open_synthetic_buffer`).
18//! 3. `PluginTraceMode::on_activate` seeds from the tracer ring + subscribes to
19//! `PluginTracePushed` for the live tail (off-thread).
20//!
21//! The per-plugin `*plugin-trace:<name>*` view + the `:plugins` manager `t`
22//! drill-in land in PO.4.2; the live `plugin.trace-level` option in PO.4.3.
23//!
24//! Design: `docs/dev/architecture/plugin-observability.md` §6.
25
26use std::sync::Arc;
27
28use lattice_grammar::{Args, Effect, ExCommandSpec, LatencyClass, SurfaceForm};
29use lattice_mode::SubsystemBoot;
30
31mod format;
32mod mode;
33
34pub use format::{
35 SHARED_BUFFER_NAME, TRACE_MODE_ID, format_trace_line, parse_per_plugin_name,
36 per_plugin_buffer_name,
37};
38pub use mode::PluginTraceMode;
39
40/// Install the plugin-trace views: register `plugin-trace-mode` + the
41/// `:plugin-trace` ex-command. Seated in the host's Phase-B install list (before
42/// the registry freeze), so it registers directly via `boot.modes_mut()` /
43/// `boot.commands_mut()`.
44///
45/// No dependency on the loader/tracer being installed first: the mode resolves
46/// `PluginTracerHandle` at *activation* (by which point boot is complete), and
47/// the ex-command returns a static effect. A missing tracer service at activation
48/// degrades to an empty buffer, never a panic.
49pub fn install(boot: &mut impl SubsystemBoot) {
50 boot.modes_mut()
51 .register(PluginTraceMode)
52 .expect("plugin-trace-mode registers without conflict");
53
54 boot.commands_mut().register_ex_command(
55 "plugin-trace",
56 "Open the shared plugin boundary-trace buffer (`*plugin-trace*`) — every \
57 loaded plugin's host↔guest calls, interleaved and tagged by plugin id, \
58 with timing / traps / capability denials. Read-only, live-tailing. Per \
59 plugin, drill in with `t` on a `:plugins` row. Raise verbosity with \
60 `:set plugin.trace-level=debug` (off by default — no per-call noise).",
61 plugin_trace_spec(),
62 );
63}
64
65/// The `:plugin-trace` ex-command: takes no argument, returns the generic
66/// open-synthetic-buffer effect for the shared view. `Reflex` — it does no
67/// blocking work on the dispatch path (the host runs the open; the mode seeds +
68/// tails off-thread).
69fn plugin_trace_spec() -> ExCommandSpec {
70 ExCommandSpec {
71 latency_class: LatencyClass::Reflex,
72 accepts_bang: false,
73 accepts_range: false,
74 parse_args: Arc::new(|_line: &str, _bang: bool| Ok(Args::None)),
75 apply: Arc::new(|_ctx| {
76 Ok(Effect::OpenSyntheticBuffer {
77 name: SHARED_BUFFER_NAME.to_string(),
78 mode_id: TRACE_MODE_ID.to_string(),
79 content: None,
80 cursor: None,
81 activate_minor: None,
82 })
83 }),
84 args_schema: Vec::new(),
85 surface_form: SurfaceForm::Keyword,
86 }
87}