Files
mopac-pmo/archive/REPORT-lsp-stack.md

5.9 KiB

REPORT — LSP stack in docker, wired into crush (MOPAC dev env)

Date: 2026-08-28 20:15 | Home: ~/projects/meta/MOPAC/lsp/ | Status: DONE (4.5/5 servers with diagnostics; bash wired but server emits none upstream)

What runs

Five language servers, all containerized, built from MOPAC/lsp/docker-compose.yml (docker compose --profile build build), on the dedicated bridge network mopac-lsp (isolated from other stacks; outbound HTTPS allowed because gopls fetches Go modules via proxy.golang.org at analysis time). stdio servers: one docker run -i --rm container per crush session via wrapper scripts (KNEL-AIMiddleware pattern, PID-suffixed names for concurrent sessions). No ports published (stdio over pipes). No host mounts beyond the workspace: gopls mounts the go.mod project root at its host path (URI parity) + named cache volumes mopac-lsp-gomodcache / mopac-lsp-gocache; marksman mounts cwd read-only; node servers mount nothing.

Server Version Base (digest-pinned) Diagnostics verified
gopls v0.23.0 golang:1.26-alpine sha256:28d89ee9... YES — semantic (see proof)
yaml-language-server 1.24.0 node:22-alpine sha256:c610fcdf... YES — "Flow sequence ... must end with ]" on bad yaml
dockerfile-language-server 0.15.0 node:22-alpine sha256:c610fcdf... YES — "Unknown instruction: FRM" on bad Dockerfile
marksman 2026-02-08 alpine:3.22 sha256:14358309... YES — lsp_symbols on .md (server emits few diagnostics by design)
bash-language-server 5.6.0 node:22-alpine sha256:c610fcdf... WIRED — container starts, initialize+publishDiagnostics flow; v5.6 upstream does not publish tree-sitter parse errors as diagnostics (same behavior in the KNEL stack). Hover/symbols/completion work.

marksman correction to the TASK text: it is an F# project, not a Go binary. No Go module exists to build; used the official musl release binary, pinned by release tag + build-time sha256 verification, DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 (no ICU package needed).

Crush wiring (how)

lsp block merged into ~/.config/crush/crush.json (global config; backup at ~/.config/crush/crush.json.bak-mopaclsp). Canonical snippet kept in sync at MOPAC/lsp/crush-lsp.json. Entries: gopls (filetypes go/mod/sum/work, root_markers go.mod|go.work, timeout 120 — cold start may download modules), bash-language-server, yaml-language-server, dockerfile-language-server (root_markers Dockerfile*), markdown (= marksman). No per-project setup is needed: crush auto-starts the matching server when a matching file is opened/viewed/edited in a matching root. Version bumps require only editing the Dockerfile ARG + compose arg + wrapper image tag, then rebuild.

Wiring gotchas (empirical, crush v0.87.0):

  • marksman entry must be keyed markdown (KNEL-style); keyed marksman it never matches .md files and no container ever spawns.
  • An LSP config entry keyed by catalog name inherits powernap defaults; explicit filetypes/root_markers in the entry are respected.
  • Fixed a real blocker in harness: go.mod was a module-less stub ("go 1.24" only) — gopls returns "no module path" and zero semantic diagnostics. Added module git.knownelement.com/reachableceo/MOPAC/harness (absorbed into harness commit 591d345 by the parallel phase-2 session).

Proof (headless crush turns, 2026-08-28)

  1. gopls on real harness code (cmd/harness/main.go with a temp injected error at line 113, restored after): 113:25 [gopls compiler][UnusedVar] declared and not used: x 113:37 [gopls compiler][UndeclaredName] undefined: yy (Earlier, pre-fix: undefined: undefindedVariable Error 7:14 on the scratch file — full pipeline incl. container spawn confirmed by docker ps polling during the turn.)
  2. yaml: line 3:1 Flow sequence in block collection must be sufficiently indented and end with a ] (yaml-language-server).
  3. dockerfile: 2 errors on FRM alpine:3.22No source image provided with FROM + Unknown instruction: FRM (dockerfile-language-server).
  4. markdown: lsp_symbols returned String Title (line 1) for the test .md.
  5. bash: container spawns on .sh access; raw probe shows the server parses and logs syntax error via window/logMessage but publishes an empty diagnostics array (upstream behavior, not a wiring failure).

Scratch files for re-verification kept in MOPAC/lsp/verify-scratch/; raw probes: lsp-handshake-test.sh, lsp-diagnostics-probe.sh.

Node-exception list (supply-chain ledger)

Sanctioned node-based exceptions — containerized only, never installed on host; base node:22-alpine digest-pinned (sha256:c610fcdfb1d5b4740dd70c284 ed3cb16bb857e0f7166196e36a5501df7a3aa32); npm pins installed at build time:

Failures / deviations

  • marksman mislabeled "Go binary" in TASK — it's F#; handled via pinned prebuilt musl binary (sha256-verified) instead of building from source.
  • gopls v0.23.0 requires go >= 1.26; initial golang:1.25-alpine base failed (requires go >= 1.26.0), switched to golang:1.26-alpine (digest recorded).
  • marksman musl binary needs libgcc/libstdc++ on alpine (added) and .NET ICU check bypassed via invariant-globalization env var.
  • Compose parses unquoted 2026-02-08 args as timestamps ("2026-02-08 00:00:00 +0000 UTC") — quote date-like build args (fixed in compose).
  • bash-language-server publishes no parse diagnostics (upstream); noted, options.bashIde.sourceErrorDiagnostics=true set anyway for future-proofing.
  • MOPAC/lsp is not yet under version control (MOPAC root has no git repo; harness/.git is separate). Not committed — awaiting founder instruction on which repo should own dev-env tooling.

Ports

None. All servers are stdio (docker run -i, no published ports). Network mopac-lsp exists for isolation + gopls egress only.