lsp-max 26.7.3

Law-state LSP runtime: max LSP 3.18 coverage, process-mining conformance, receipt-chain admission
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

A fork of `tower-lsp` (Tower-based LSP framework) upgraded into **lsp-max**: a "law-state runtime projected through LSP." It maximizes LSP 3.18 capability coverage and adds a custom `max/*` protocol surface (snapshots, conformance vectors, receipts, repair plans, gates). Primary clients are agents, CI, and release gates — the editor is just one client.

**Read `AGENTS.md` before substantive work. It is the project constitution and its laws are enforced by tooling** (the `crates/anti-llm-cheat-lsp` server detects violations). The most load-bearing laws:

- **Never reference plain `tower-lsp`/`tower_lsp`** in code, manifests, tests, or docs — outside explicit negative-control fixtures. Everything is `lsp-max`.
- **No victory language** in code, comments, reports, or commit messages ("done", "all clean", "fully admitted", "solved", "guaranteed"). Use bounded statuses only: `ADMITTED`, `CANDIDATE`, `BLOCKED`, `REFUSED`, `UNKNOWN`, `PARTIAL`, `OPEN`, etc.
- **Test stdout / log messages are not receipts.** Claims of admission require receipt artifacts (path, digest, boundary, checkpoint, negative-control result).
- The CLI component is named exactly **`clap-noun-verb`** — never invent a "CLAP" authority.
- The LSP surface is **read-only**: it emits diagnostics/hovers/intents but never mutates files directly.
- `ConformanceVector` carries `admitted`/`refused`/`unknown` law-axis sets; **Unknown must never collapse into Admitted or Refused**.

## Versioning

Workspace version uses **CalVer (YY.M.D)**, not SemVer. The current version is `26.6.21`. Version-law violations are a diagnostic family (`ANTI-LLM-VERSION-*`).

## Sibling repo dependencies (build prerequisite)

The workspace does not build standalone. It requires sibling checkouts at:

- `../lsp-types-max` — path dependency, the LSP type authority (with `proposed` 3.18 features)
- `../wasm4pm-compat` — `[patch.crates-io]`; the **sole baseline type authority** for process-mining types
- `../wasm4pm` — `[patch.crates-io]`; the execution engine

Architectural mandate (enforced by `just dx-verify`, which greps the sibling repos): no intermediary type crates (`wasm4pm_types`, `ocel_core` are forbidden), and the words `legacy`/`deprecated`/`shim`/`facade`/`backward compatibility` must not appear anywhere in `../wasm4pm-compat` or `../wasm4pm` — including in comments you write.

## Commands

Build orchestration is via `just` (run `just` alone to list recipes):

```sh
just test               # cargo test --workspace
just test-e2e           # cargo test --test e2e
just test-pre-publish   # dx-verify + dx-polish + tests including --include-ignored
just dx-polish          # cargo fmt --all + cargo clippy --workspace --all-targets --all-features -- -D warnings
just dx-verify          # architectural boundary scan across sibling repos
```

Single test / single crate:

```sh
cargo test -p <crate-name> <test_name>
cargo test -p lsp-max-cli nouns::conformance   # run all tests in one noun module
cargo test --test test_lsp318_capabilities     # one root integration-test file in tests/
cargo test -p anti-llm-cheat-lsp --test dogfood     # example-crate integration test
```

wasm4pm-lsp example (breeds, conformance, COG-010):

```sh
# Run conformance runner — dispatches all 10 breeds against paper fixtures,
# writes ocel/reports/{breed_id}.json with measured fitness and provenance.
cargo run --bin conformance-runner --manifest-path examples/wasm4pm-lsp/Cargo.toml

# COG-010 oracle injection scan — writes tests/receipts/cog010-scan.json.
cargo test -p wasm4pm-lsp cog010_no_oracle_injection -- --nocapture
```

Clippy with `-D warnings` is the bar; run `just dx-polish` before considering a change complete.

**`scripts/` directory**: Contains utility scripts for law compliance, receipt validation, and diagnostic automation:
- **`scripts/check-law-compliance.sh`** — Detects plain `tower-lsp` references and victory language.
- **`scripts/validate-receipt-chain.sh`** — Verifies SHA256 digests and boundary markers.
- Other scripts support conformance scoring, admission law tracing, and gate-state inspection.

## ANDON Gate — PreToolUse Hook (Λ_CD^runtime)

A `PreToolUse` hook in `.claude/settings.json` runs `lsp-max-cli gate check` before every **Bash, Edit, and Write** tool call.

- **Exit 0** — gate is clear; the tool proceeds.
- **Exit 1** — ANDON is ACTIVE; the tool is blocked until the gate clears.

This enforces `Λ_CD^runtime`: no shell-side action (build, test, release, format) and no file mutation may proceed while an active ANDON signal is present. Resolve all `WASM4PM-*` and `GGEN-*` diagnostics to clear the gate.

## Fresh Web Session Bootstrap

On a new cloud/web session, sibling repos and `lsp-max-cli` are absent — the workspace will not
build and the ANDON gate will not function. The `SessionStart` hook (`.claude/session-start.sh`)
runs automatically and reports which components are OPEN. To advance from OPEN to ADMITTED, run
the setup script once per environment:

```sh
bash .claude/setup.sh
```

This installs `just`, clones the three sibling repos at `../`, and builds `lsp-max-cli`.
After setup, the PreToolUse ANDON gate and PostToolUse diagnostic snapshot will function.

Status meanings from the hook output:
- `ADMITTED` — present and functional
- `CANDIDATE` — just installed/cloned; build not yet verified
- `OPEN` — absent; setup required before that component can be used

## Tooling & DX Workflow

Build orchestration uses `just` recipes as the primary development interface. Key recipes:

- **`just test`** — Run `cargo test --workspace`. Executes unit and integration tests across all crates; use for validation during development.
- **`just test-e2e`** — Run `cargo test --test e2e`. Runs end-to-end integration tests against live LSP surfaces; validates gate conformance and agent workflows.
- **`just test-pre-publish`** — Run `dx-verify` + `dx-polish` + tests with `--include-ignored`. Full pre-release pipeline: architectural boundary scan, code quality gates, and ignored test suites.
- **`just dx-verify`** — Architectural boundary scan across sibling repos (`../lsp-types-max`, `../wasm4pm-compat`, `../wasm4pm`). Enforces: no plain `tower-lsp` references, no forbidden type crates, no legacy/deprecated/shim/facade language in sibling repos. **Must pass before any merge.**
- **`just dx-polish`** — `cargo fmt --all` + `cargo clippy --workspace --all-targets --all-features -- -D warnings`. Code formatting and lint enforcement; `-D warnings` is non-negotiable.

Run scripts directly when debugging compliance issues or validating reception chains; they are not automatically invoked by recipes.

## Remote Execution — Claude Code Web

Sessions run in Anthropic-managed ephemeral containers. Key constraints:

- **Git commits are the only persistence.** Container state is lost between sessions. Push before the session ends.
- **Sibling repos are NOT auto-cloned.** `../lsp-types-max`, `../wasm4pm-compat`, `../wasm4pm` must be present for the workspace to build. In a remote container, check with `ls ../lsp-types-max ../wasm4pm-compat ../wasm4pm` before attempting compilation.
- **`lsp-max-cli` may not be in PATH.** In a freshly cloned container without a pre-built binary, `gate check` returns exit 0 (compositor not running = gate clear). Build with `cargo build -p lsp-max-cli` to wire the CLI.
- **Setup script is cached per environment.** If the environment has a setup script that builds the CLI and fetches dependencies, subsequent sessions reuse it.

### Session Status Check (run at session start)

```bash
git log --oneline -5          # what was last committed?
git status                    # uncommitted work?
ls ../lsp-types-max 2>/dev/null || echo "sibling repos missing — build will fail"
lsp-max-cli gate check        # gate clear?
```

### Parallel Subagent Patterns

Each subagent gets a fresh 1M token context window. Use parallel agents for independent crate work:

```
# Spawn 3 agents in one message (independent files = safe to parallelize)
Agent(description="Update compositor declare.rs", isolation="worktree", ...)
Agent(description="Update anti-llm-cheat-lsp server.rs", isolation="worktree", ...)
Agent(description="Run tests and collect results", ...)
```

Rules:
- **Parallel only when agents touch DIFFERENT files.** Overlapping edits cause conflicts.
- Use `isolation: "worktree"` for agents that write code.
- Use foreground (default) when you need results before proceeding.
- Use `run_in_background: true` for independent research.

Every subagent prompt MUST include a gate check as its first Bash action — subagents do NOT inherit the parent session's PreToolUse hooks:

```bash
lsp-max-cli gate check || { echo "ANDON gate blocked"; exit 1; }
```

### Skills System

Skills in `.claude/skills/<name>/SKILL.md` load on-demand via `/<name>`:

```
/dx-workflow        — format + lint + verify + test pipeline
/rule-pack-server   — RulePackServer implementation pattern
/gate-conformance   — gate state, ANDON resolution, Λ_CD predicate
/van-der-aalst      — DFG, Declare, OCEL accumulation
/session-continuity — remote session patterns, git persistence, PR watching
```

Skills keep the root context lean — load only what the current task needs.

### PR Watching

After pushing a branch and opening a PR, subscribe to activity:

```
subscribe_pr_activity(pr_number=<N>)
```

PR events (`<github-webhook-activity>` tags) arrive automatically. On each event:
- CI failure → diagnose, fix, push
- Review comment → fix if clear; check with user if ambiguous
- PR merged/closed → unsubscribe

## Workspace architecture

The five-layer model: (1) actuation grammar → (2) local LSP state surface → (3) law-state runtime → (4) knowledge hooks → (5) autonomic LSP mesh. Crates map onto it:

- **Root crate (`src/`)** — the LSP server framework: `LanguageServer` trait, `LspService`, `Server` over stdio/TCP, plus `gate.rs`, `diagnostics.rs`, `composition.rs` for the law-state surface. Re-exports the `RulePackServer` bridge trait and associated types: `ClassifiedFindings`, `Finding`, `ValidatedRulePackSet`, `WorkspaceIndex`.
- **`lsp-max-protocol`** — the `max/*` method declarations, capability vectors, `MaxDiagnostic`, `ConformanceVector`, receipts, analysis bundles. This is where protocol types live; change them here, not in consumers.
- **`lsp-max-runtime`** — typestate machine, phases, transitions, runtime snapshotting.
- **`lsp-max-agent`** — agent integration and analysis bundles.
- **`lsp-max-macros`** — internal proc macros (e.g. `#[tower_lsp::async_trait]` equivalent surface).
- **`crates/lsp-max-cli`** — actuation grammar: noun/verb CLI built on `clap-noun-verb` (filename = noun, `#[verb]` = action). 31 noun modules — see Noun/Verb Pattern section below. `gate check` and `gate list` verbs are wired here.
- **`crates/lsp-max-client`** — LSP *client* framework (drives servers in tests).
- **`crates/lsp-max-base`**, **`-live`**, **`-lsif`**, **`-specgen`** — base protocol types, live surfaces, LSIF export/conformance, and codegen from the official LSP 3.18 `metaModel.json`.
- **`crates/lsp-max-adapters/`** — ported `auto-lsp` stack (`lsp-max-ast-core`, `lsp-max-ast-codegen`, `lsp-max-ast`): tree-sitter-driven AST/codegen layer. Tree-sitter observes; it never admits.
- **`crates/lsp-max-compositor/`** — multi-server fan-out and merge. Contains `declare.rs` (Van der Aalst Declare constraint model, 9 constraint types) and `dfg.rs` (Directly-Follows Graph with fitness/precision metrics). `FlushCoordinator` uses adaptive quorum debounce and accumulates OCEL 2.0 events via `take_ocel_events()`.
- **`crates/wasm4pm-lsp`**, **`crates/gc005-wasm4pm-adapter`** — process-mining LSP surfaces over the wasm4pm engine; dogfood tests (`dogfood_gc00*.rs`) validate gate conformance.
- **`examples/wasm4pm-lsp/`** — standalone cognitive-breeds example (not in workspace crates). Contains 10 `CognitiveBreed` implementations (`src/breeds/`), a conformance runner binary (`src/bin/conformance_runner.rs`), OCPN models, OCEL fitness reports, and paper fixtures. `src/lib.rs` exposes `pub mod breeds;` so the runner binary can reference `wasm4pm_lsp::breeds::dispatch`. Governed by COG laws 1–12; see `examples/wasm4pm-lsp/CLAUDE.md` for local guidance. LLM breed (`llm.rs`) requires `ANTHROPIC_API_KEY`; returns `None` gracefully when absent.
- **`crates/playground`** — dev-dependency harness with demo binaries (`dogfood_harness`, `lsif_demo`).
- **`crates/anti-llm-cheat-lsp`** — the diagnostic canary: an LSP that detects reintroduction of plain `tower-lsp`, fake receipts, fake routes, and victory language. Implements `RulePackServer` via a `scan_uri_classified` override that bridges the internal AhoCorasick engine into `ClassifiedFindings`. Virtual document `anti-llm://process-model` surfaces a live DFG + Declare conformance report from active diagnostics. Other examples (`pattern-lsp`, `clap-noun-verb-lsp`, `axum-lsp`, `bevy-lsp`, `tex-lsp`, `wasm4pm-compat-lsp`) are workspace members and must keep compiling.

## RulePackServer — implementing an LSP server

`RulePackServer` (`src/rule_pack_server.rs`) is the bridge trait for building diagnostic LSP servers. Five abstract methods; everything else is defaulted:

```rust
impl RulePackServer for MyServer {
    fn rule_packs(&self) -> &ValidatedRulePackSet { &self.rule_packs }
    fn grammar(&self) -> tree_sitter::Language { tree_sitter_rust::LANGUAGE.into() }
    fn server_name(&self) -> &'static str { "my-server" }
    fn client(&self) -> &Client { &self.client }
    fn adapter(&self) -> &AutoLspAdapter { self.ast_adapter.inner() }
}
```

Default implementations handle `did_open/change/close`, `WorkspaceIndex` upsert, AST parsing, and the `publish_findings_classified` → `scan_uri_classified` pipeline.

Override `scan_uri_classified` when the server has its own engine (e.g., AhoCorasick) that must be bridged:

```rust
fn scan_uri_classified(&self, uri: &DocumentUri, _content: &str) -> ClassifiedFindings {
    let findings: Vec<Finding> = self.engine_scan(uri)
        .into_iter()
        .map(|d| {
            let lsp_diag = d.to_lsp();
            let max_diag = MaxDiagnostic {
                lsp: lsp_diag.clone(),
                law_axis: LawAxis::Custom(d.category.clone()),
                ..MaxDiagnostic::default()
            };
            (max_diag, lsp_diag)
        })
        .collect();
    (findings, vec![]) // (sync, background)
}
```

`WorkspaceIndex` is re-exported from `lsp_max::WorkspaceIndex`. Wire it in the server struct and return `Some(&self.workspace_index)` from `workspace_index()`. The default `handle_did_*` methods call `upsert`/`remove` automatically.

Key types re-exported from `lsp_max`:
- `ClassifiedFindings = (Vec<Finding>, Vec<Finding>)` — sync and background findings
- `Finding = (MaxDiagnostic, Diagnostic)` — one finding as a (max, lsp) pair
- `ValidatedRulePackSet` — monoid newtype; use `::empty()` for engine-bridge servers with no TOML packs
- `WorkspaceIndex = Arc<DashMap<String, IndexedDoc>>`

`LawAxis` variants (from `lsp_max::max_protocol`): `Protocol`, `Type`, `Fixture`, `Documentation`, `Release`, `Hook`, `Repair`, `Receipt`, `Security`, `Autopoiesis`, `Domain`, `Custom(String)`. Use `Custom` for domain-specific categories.

## Van der Aalst Process Mining — compositor and virtual docs

`crates/lsp-max-compositor/` contains inline Van der Aalst process mining primitives:

**`declare.rs`** — Declare constraint model (LTL-based declarative specification):
- 9 constraint types: `Init`, `End`, `Response`, `Precedence`, `ExactlyOne`, `NotCoExistence`, `RespondedExistence`, `Absence`, `ChainResponse`
- `DeclareModel::compositor()` — normative model for the compositor flush pipeline
- `DeclareModel::anti_llm_detection()` — normative model for the detection pipeline
- `extract_traces(events: &[Value]) -> HashMap<String, Vec<String>>` — builds per-case traces from OCEL events
- `model.check(&traces)` → `Vec<ConstraintViolation>` — conformance checking

**`dfg.rs`** — Directly-Follows Graph (Van der Aalst core discovery primitive):
- `DirectlyFollowsGraph::from_traces()` / `from_events()` — build DFG from traces or OCEL events
- `fitness_against_model(&normative_arcs)` → `Option<f64>` — fraction of normative arcs present
- `precision_against_model(&normative_arcs)` → `Option<f64>` — fraction of DFG arcs in normative model
- `to_mermaid()`, `to_dot()` — Mermaid flowchart and Graphviz DOT renderers

`FlushCoordinator` runs both after every flush: Declare violation warnings are emitted via `tracing::warn!`; DFG fitness is logged via `tracing::debug!`. Accumulated OCEL 2.0 events are accessible via `take_ocel_events()` (drains) or `ocel_event_count()` (snapshot).

**`anti-llm://process-model`** virtual document: served by `anti-llm-cheat-lsp` on request. Renders a live markdown document containing:
- DFG summary (node count, edge count, transitions) derived from current `AntiLlmDiagnostic` observations
- Mermaid flowchart of the DFG
- Declare conformance report with violation table
- Fitness score and activity legend (maps diagnostic code prefixes to DFG activity names)

Activities: `VictoryLanguageDetected`, `FakeReceiptDetected`, `FakeRouteDetected`, `VersionViolationDetected`, `ForbiddenRefDetected`, `ProcessViolationDetected`, `GgenViolationDetected`, `CheatDetected`, `ScanComplete` (synthetic terminal).

## lsp-max-cli: Noun/Verb Pattern

The CLI is a **noun/verb actuation grammar**. Each file in `crates/lsp-max-cli/src/nouns/` is one noun; each `#[verb]`-annotated function is one action.

### Three-tier structure (every noun file follows this)

```rust
// 1. Domain Tier — domain types only (no logic)
#[derive(Debug, Clone, Serialize)]
pub struct SomeThing { pub field: String }

// 2. Service Tier — business logic against AutonomicMesh
pub struct SomeService { state_path: String }

impl SomeService {
    pub fn new() -> Self { Self { state_path: crate::nouns::get_state_path() } }

    pub fn do_thing(&self, id: &str) -> std::result::Result<SomeThing, String> {
        let mesh = AutonomicMesh::load_from_file(&self.state_path).map_err(|e| e.to_string())?;
        let inst = mesh.instances.get(id).ok_or_else(|| format!("Instance not found: {id}"))?;
        Ok(SomeThing { field: inst.conformance_score().to_string() })
    }
}

impl Default for SomeService { fn default() -> Self { Self::new() } }

// 3. CLI Tier — result wrapper + #[verb] function
#[derive(Serialize)]
pub struct DoThingResult { pub thing: SomeThing, pub status: String }

/// One-line doc shown in --help.
#[verb("do-thing")]
pub fn do_thing(instance_id: String) -> Result<DoThingResult> {
    let svc = SomeService::new();
    let thing = svc.do_thing(&instance_id).map_err(NounVerbError::execution_error)?;
    let status = "CANDIDATE".to_string();
    Ok(DoThingResult { thing, status })
}
```

### Registration

Add `pub mod your_noun;` to `crates/lsp-max-cli/src/nouns/mod.rs`. The `linkme` crate auto-discovers `#[verb]` items; no manual wiring is needed beyond the `mod` declaration.

### Current nouns (31 total, in mod.rs order)

`admission`, `agent`, `alias`, `batch`, `client`, `config`, `conformance`, `diagnostics`, `doctor`, `event`, `export`, `gate`, `history`, `hook`, `import`, `logs`, `metamodel`, `metrics`, `ocel`, `plugin`, `process`, `receipt`, `repair`, `rpc`, `server`, `snapshot`, `state`, `swarm`, `task`, `telemetry`, `workspace`

### State path resolution

`crate::nouns::get_state_path()` reads `$LSP_MAX_STATE_PATH`, falling back to `.mesh_state.json`. Services with their own `state_path` field use it directly; services that call `new()` read from the env at call time.

### `AutonomicMesh::load_from_file` — critical bootstrap behavior

When the given path does not exist but the **parent directory is writable**, `load_from_file` bootstraps a default mesh, writes it to that path, and returns `Ok`. It does **not** return an error for a nonexistent file when the directory exists. This affects test design — see Testing Patterns below.

### HookEvent serialization format

`HookEvent` is an externally-tagged Serde enum. The serialized form is:

```json
{ "VariantName": { "instance_id": "...", ... } }
```

The outer key (variant name) is the activity name used in process-mining verbs (`process dfg`, `process variants`, etc.) and in OCEL 2.0 export (`ocel export`). Extract it with:

```rust
let activity = raw.as_object().and_then(|m| m.keys().next()).unwrap_or("Unknown");
```

## Testing Patterns for Noun Modules

Two patterns apply depending on whether the service reads `state_path` from a struct field or from the env var via `get_state_path()`.

### Pattern A — service-layer tests (struct field injection)

Use when the service struct has a `state_path: String` field that you set directly.

```rust
fn make_temp_svc() -> (tempfile::NamedTempFile, MyService) {
    let mut mesh = AutonomicMesh::new();
    mesh.add_instance(LspInstance::new("test-inst"));
    let f = tempfile::NamedTempFile::new().unwrap();
    mesh.save_to_file(f.path().to_str().unwrap()).unwrap();
    let svc = MyService { state_path: f.path().to_str().unwrap().to_string() };
    (f, svc) // hold f alive; dropping it deletes the file
}

#[test]
fn thing_returns_ok() {
    let (_f, svc) = make_temp_svc();
    assert!(svc.do_thing("test-inst").is_ok());
}
```

### Pattern B — verb-level tests (env var guard)

Use when the service calls `get_state_path()` internally (e.g. `ClientService`, `ServerService`, `PluginService`, `ConfigService`).

```rust
struct StateGuard { prev: Option<String>, _f: tempfile::NamedTempFile }

impl StateGuard {
    fn new() -> Self {
        let mut mesh = AutonomicMesh::new();
        mesh.add_instance(LspInstance::new("test-inst"));
        let f = tempfile::NamedTempFile::new().unwrap();
        mesh.save_to_file(f.path().to_str().unwrap()).unwrap();
        let prev = std::env::var("LSP_MAX_STATE_PATH").ok();
        // SAFETY: under TEST_ENV_LOCK, single-threaded env mutation.
        unsafe { std::env::set_var("LSP_MAX_STATE_PATH", f.path()) };
        Self { prev, _f: f }
    }
}

impl Drop for StateGuard {
    fn drop(&mut self) {
        // SAFETY: restoring env under TEST_ENV_LOCK.
        unsafe {
            match self.prev.take() {
                Some(v) => std::env::set_var("LSP_MAX_STATE_PATH", v),
                None => std::env::remove_var("LSP_MAX_STATE_PATH"),
            }
        }
    }
}

#[test]
fn thing_ok() {
    let _lock = crate::nouns::TEST_ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
    let _g = StateGuard::new();
    let svc = MyService::new();
    assert!(svc.do_thing().is_ok());
}
```

`crate::nouns::TEST_ENV_LOCK` (a `static Mutex<()>` in `mod.rs`) serializes all tests that mutate env vars.

### Unsafe env mutation (Rust 1.83+)

`std::env::set_var` and `remove_var` are `unsafe` in Rust 1.83+. Always wrap in `unsafe {}` and include a `// SAFETY: under TEST_ENV_LOCK.` comment.

### "Missing state file" test antipattern — critical

`load_from_file` bootstraps a mesh when the parent directory is writable. This means:

```rust
// WRONG — /tmp/ is writable, so load_from_file writes the file and returns Ok
let svc = MyService { state_path: "/tmp/nonexistent-test.json".to_string() };
assert!(svc.do_thing().is_err()); // FAILS
```

Route through a **non-existent directory** instead:

```rust
// CORRECT — /tmp/no-such-dir-lsp-max/X/ doesn't exist, bootstrap write fails → Err
let svc = MyService { state_path: "/tmp/no-such-dir-lsp-max/X/state.json".to_string() };
assert!(svc.do_thing().is_err()); // PASSES
```

### Per-verb test requirements

Each verb should have at minimum:
- **Success** — happy path returns `Ok`
- **Falsification** — verify the result contents (not just `is_ok()`)
- **Counterfactual** — error path returns `Err` (bad arg, missing state, unknown instance)

## Code layout conventions

- Keep files ≤ 500 LOC; split into submodules in a subdirectory matching the module name (`src/service/` next to `src/service.rs` is the established pattern).
- Integration tests live in root `tests/` (one file per concern, e.g. `test_lsp318_capabilities.rs`) and per-crate `tests/` for dogfood suites.
- LSP 3.18 feature claims require transcript + negative control + receipt; a feature row is `SUPPORTED_WITH_TRANSCRIPT`, `REFUSED_BY_LAW_WITH_RECEIPT`, or `BLOCKED` — never "implied" or "covered by normal LSP".

## External consumers of lsp-max (e.g. sibling LSP crates)

When a crate outside this workspace depends on `lsp-max` and also declares its own `tokio` dependency, it must include `"io-std"` in tokio features — `tokio::io::stdin` / `tokio::io::stdout` are behind that feature gate and are not inherited transitively from lsp-max.

When constructing `CodeAction` literals, always use `..Default::default()` for the trailing fields rather than exhaustive field listing — lsp-types-max may add fields as LSP 3.18 evolves and exhaustive structs break at the call site.

## Common Anti-Patterns

Avoid these patterns; they are enforced by linting, gate rules, and code review:

1. **Using half-finished features** — Don't reference proposed LSP 3.18 features unless they are marked `SUPPORTED_WITH_TRANSCRIPT` with a receipt. `CANDIDATE` or `BLOCKED` features may be removed or changed before stabilization.

2. **Error handling for impossible cases** — Don't add error branches for conditions the type system already guarantees are impossible (e.g., handling a `None` from a `let x = val.unwrap()` path). Well-typed code is self-documenting; redundant error handling obscures intent.

3. **Inventing explanatory comments** — Don't comment *what* the code does; well-named functions and variables already convey that. Comments describe *why* and *context*: design decisions, invariants, and non-obvious coupling.

4. **Exhaustive struct literals** — When constructing `CodeAction`, `Diagnostic`, `LSPRequest`, or similar types, use `..Default::default()` for trailing fields, not exhaustive field lists. As LSP 3.18 evolves, new fields may be added; exhaustive structs will break at the call site.

5. **Collapsing `Unknown` into `Admitted` or `Refused`** — `ConformanceVector` law-axis sets distinguish three states. Never coerce `Unknown` to either polarity; it signals a gap in tracing or a precondition not yet met.

6. **Using victory language** — Avoid "done", "all clean", "fully admitted", "solved", "guaranteed" in code, comments, commit messages, or reports. Use bounded statuses: `ADMITTED`, `CANDIDATE`, `BLOCKED`, `REFUSED`, `UNKNOWN`, `PARTIAL`, `OPEN`, etc.

7. **Tautological test assertions** — Don't assert on return values that are always identical regardless of success (e.g., `stop()` always returns `Stopped`). Assert on observable state via a subsequent query (`status()` after `stop()` checks the OS).

8. **Writable `/tmp/` paths in error tests** — `load_from_file` bootstraps when the parent dir is writable. Use `/tmp/no-such-dir-lsp-max/X/state.json` (non-existent parent) for error-path tests.

## Debugging & Troubleshooting

### WASM4PM Build Failures

Symptom: `cargo build` fails with "unresolved import `wasm4pm_compat`" or similar.

**Check**: Verify sibling checkout:
```sh
ls ../wasm4pm ../wasm4pm-compat ../lsp-types-max
```

If missing, clone the repos into parent directory and re-run `just test`. The workspace uses `[patch.crates-io]` and path dependencies; they must be present in the filesystem, not fetched from crates.io.

### Reception Validation Failures

Symptom: Diagnostic `ANTI-LLM-CHEAT-LSP-RECEIPT-INVALID` or SHA256 chain mismatch.

**Check**: Verify the receipt artifact chain:
```sh
scripts/validate-receipt-chain.sh <receipt-path>
```

Look for boundary markers (`-----BEGIN RECEIPT-----`), valid SHA256 digests, and checkpoint closure. Receipts are not valid if they lack boundary markers or have digest mismatches.

### Conformance Score Anomalies

Symptom: A feature row shows low conformance or unexpected `REFUSED` status despite code changes.

**Check**: Trace admission law axes:
```sh
cargo test test_conformance_vector -- --nocapture
cargo test -p lsp-max-protocol test_law_axis_admission -- --nocapture
```

Use the output to identify which law axis is refusing admission (e.g., transcript missing, negative control missing, receipt not signed). Conformance is cumulative; all law axes must pass.

### Tower-LSP Reference Detection

Symptom: Clippy or `dx-verify` fails with "forbidden: plain tower-lsp reference".

**Check**: Run the compliance scanner:
```sh
scripts/check-law-compliance.sh
```

This greps for plain `tower-lsp`, `tower_lsp`, victory language, and fake receipt markers across the codebase. Fix any matches by renaming to `lsp-max` and ensuring all diagnostic claims have receipt artifacts.

### Gate State Inspection

Symptom: ANDON gate is set, blocking Bash commands; unclear what diagnostics are active.

**Check**: Query gate state:
```sh
lsp-max-cli gate check  # Exit 0 = gate clear; exit 1 = gate set
lsp-max-cli gate list   # List active WASM4PM-* / GGEN-* code families + agent_scope
```

Or inspect the `.claude/settings.json` `PreToolUse` hook directly to see which gates are wired. Resolve all listed diagnostics to clear the ANDON signal.