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); keyedmarksmanit 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)
- gopls on real harness code (
cmd/harness/main.gowith a temp injected error at line 113, restored after):113:25 [gopls compiler][UnusedVar] declared and not used: x113:37 [gopls compiler][UndeclaredName] undefined: yy(Earlier, pre-fix:undefined: undefindedVariableError 7:14 on the scratch file — full pipeline incl. container spawn confirmed by docker ps polling during the turn.) - yaml:
line 3:1 Flow sequence in block collection must be sufficiently indented and end with a ](yaml-language-server). - dockerfile: 2 errors on
FRM alpine:3.22—No source image provided with FROM+Unknown instruction: FRM(dockerfile-language-server). - markdown:
lsp_symbolsreturnedString Title (line 1)for the test .md. - bash: container spawns on .sh access; raw probe shows the server parses
and logs
syntax errorvia 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:
- bash-language-server@5.6.0 (npm integrity sha512-DCuV+/BZAAozsp5...)
- yaml-language-server@1.24.0 (sha512-+HGcwu4M7IC...)
- dockerfile-language-server-nodejs@0.15.0 (sha512-iS3GUUAn1bC3s...)
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-08args 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.