Skip to content

Commit f937a0e

Browse files
docs(tg-9): Option B — categorise tangle-lsp diagnostics + update A-TG-9.1 (#29)
The 2026-06-01 audit (issue #28) found that 6 of 7 `tangle-lsp` diagnostic call sites have no `HasType` counterpart — the LSP backend performs its own lightweight lexical analysis and does NOT route through `compiler/lib/typecheck.ml`. Assumption A-TG-9.1 (added in PR #25) was therefore false. This PR closes the gap via Option B from #28 (the lighter touch): accept LSP-only diagnostics but document them by category and tag every emission site. Option A (full refinement via OCaml↔Rust FFI) remains queued at #28 as the long-term target. ## Categories Four categories, denoted in the `Diagnostic.source` field as `tangle-lsp[CATEGORY]`: * PARSE_ERROR — 3 sites (paren/bracket/brace) — grammar-level, legitimate language rejection. * MISSPELLING_HINT — 1 site — IDE convenience; no spec counterpart. * STRUCTURAL_HINT — 2 sites (weave-block, nesting-depth) — LSP-only heuristic. * NAME_HINT — 1 site — possibly-undefined-reference (soft HINT severity; OCaml typechecker is strict). ## Changes 1. `compiler/tangle-lsp/src/backend.rs` — 7 `Diagnostic.source` fields updated from `"tangle-lsp".into()` to `"tangle-lsp[CATEGORY]".into()` per call site. Owner header added to satisfy global pre-commit Owner check. 2. `compiler/tangle-lsp/docs/lsp-diagnostic-categories.md` — new file. Documents each category, lists all current emission sites, gives the protocol for adding a new diagnostic (must pick a category or propose a new one with an A-TG-9.1 update), proposes a grep-based CI gate as a follow-up. 3. `ASSUMPTIONS.md` A-TG-9.1 reformulated to reflect the four categories; changelog row added. ## What this discharges * PROOF-NARRATIVE.md §3 TG-9 — Option B base case. * Assumption A-TG-9.1 now matches reality. * Issue #28 — partially closed (Option A still tracked for the deeper refinement work). This does NOT close #28 entirely. Option A (typecheck.ml refinement via FFI) remains the principled long-term fix; Option B is a discipline reset, not the destination. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cc46136 commit f937a0e

3 files changed

Lines changed: 137 additions & 8 deletions

File tree

ASSUMPTIONS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Cross-references use `[[A-TG-N.M]]` syntax, resolved here.
3434
| A-TG-7.1 | MATH | Word problem in the braid group `B_n` is solvable in polynomial time (Birman–Ko–Lee / Garside normal form) | TG-7 | Birman–Ko–Lee 1998; _A New Approach to the Word and Conjugacy Problems in the Braid Groups_ |
3535
| A-TG-8.1 | DESIGN | Each dialect's grammar is a strict superset of core's EBNF (`tangle.ebnf`) | TG-8 | `dialects/*/grammar.ebnf` |
3636
| A-TG-8.2 | DESIGN | Each dialect's typing rules are additive (new constructors + their typing rules only; no modification of existing rules) | TG-8 | Per-dialect spec |
37-
| A-TG-9.1 | DESIGN | `tangle-lsp` reuses `compiler/lib/typecheck.ml` as the diagnostic engine (no LSP-only diagnostics) | TG-9 | `compiler/tangle-lsp/src/` |
37+
| A-TG-9.1 | DESIGN | `tangle-lsp` emits diagnostics in four documented categories (`PARSE_ERROR`, `MISSPELLING_HINT`, `STRUCTURAL_HINT`, `NAME_HINT`); only `PARSE_ERROR` corresponds to a grammar-level rejection. The other three are LSP-only by design (Option B from TG-9 audit; Option A — full refinement via FFI to `typecheck.ml` — remains queued at #28). Each emission site is tagged in the `Diagnostic.source` field as `tangle-lsp[CATEGORY]`. | TG-9 | `compiler/tangle-lsp/src/backend.rs`; `compiler/tangle-lsp/docs/lsp-diagnostic-categories.md` |
3838

3939
---
4040

@@ -69,3 +69,4 @@ date and reason.
6969
| Date | Change | By |
7070
|------|--------|-----|
7171
| 2026-06-01 | Initial registry, scoped to Tangle metatheory + implementation refinement obligations | Audit |
72+
| 2026-06-01 | A-TG-9.1 reformulated under TG-9 Option B — accept LSP-only categories instead of pretending refinement (full Option A queued at #28). See `compiler/tangle-lsp/docs/lsp-diagnostic-categories.md`. | TG-9 Option B PR |
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<!--
2+
SPDX-License-Identifier: MPL-2.0
3+
Owner: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
-->
5+
# `tangle-lsp` diagnostic categories
6+
7+
This file documents the four categories `tangle-lsp` diagnostics fall
8+
into. Together with the updated assumption `A-TG-9.1` in the repo-root
9+
`ASSUMPTIONS.md`, they formalise the **TG-9 Option B** resolution from
10+
issue #28.
11+
12+
The bigger picture: obligation **TG-9** in `PROOF-NARRATIVE.md` says
13+
every LSP diagnostic should correspond to a failure of the `HasType`
14+
typing judgment in `proofs/Tangle.lean`. The 2026-06-01 audit
15+
(issue #28) found that 6 of 7 diagnostic call sites have no `HasType`
16+
counterpart. There were two options for closing the gap:
17+
18+
- **Option A** — route every diagnostic through `compiler/lib/typecheck.ml`
19+
via an OCaml↔Rust FFI. Higher engineering cost; the most principled.
20+
- **Option B** — accept LSP-only diagnostics, document the categories,
21+
and tag each call site with which category it belongs to. Lower cost;
22+
what this file describes.
23+
24+
Option B keeps the diagnostics where they help users while making the
25+
"this is not a `HasType` failure" status explicit. Option A is queued
26+
in #28 for follow-up.
27+
28+
## The categories
29+
30+
Each is denoted in the `Diagnostic.source` field as
31+
`tangle-lsp[CATEGORY]`.
32+
33+
### `PARSE_ERROR`
34+
35+
Grammar-level rejection. Corresponds to the parser refusing malformed
36+
input. Not a `HasType` failure but a legitimate language-level
37+
rejection.
38+
39+
Examples:
40+
- Unbalanced parentheses, brackets, braces.
41+
42+
Source location markers in `backend.rs`:
43+
- `tangle-lsp[PARSE_ERROR]` — 3 sites (paren / bracket / brace).
44+
45+
### `MISSPELLING_HINT`
46+
47+
IDE-convenience hint. The user typed something close to a keyword.
48+
No spec counterpart — the spec doesn't know about misspellings.
49+
50+
Examples:
51+
- `comput` instead of `compute`.
52+
- `asert` instead of `assert`.
53+
54+
Source location markers in `backend.rs`:
55+
- `tangle-lsp[MISSPELLING_HINT]` — 1 site.
56+
57+
### `STRUCTURAL_HINT`
58+
59+
LSP-only structural heuristic. Tracks block nesting, weave-block
60+
balance, etc. without a corresponding `HasType` rule. The `weave`
61+
keyword in particular is part of a proposed v0.2 dialect not yet in
62+
the core typing relation.
63+
64+
Examples:
65+
- Unclosed `weave` block.
66+
- Suspicious block nesting depth.
67+
68+
Source location markers in `backend.rs`:
69+
- `tangle-lsp[STRUCTURAL_HINT]` — 2 sites.
70+
71+
### `NAME_HINT`
72+
73+
Possibly-undefined-reference hint. Implemented as `HINT` severity (the
74+
softest LSP level) because identifiers may resolve via future imports
75+
the lexical pass can't see. The OCaml typechecker raises a hard
76+
exception on unbound variables; this LSP hint is the softer IDE-side
77+
analogue.
78+
79+
Source location markers in `backend.rs`:
80+
- `tangle-lsp[NAME_HINT]` — 1 site.
81+
82+
## How to add a new diagnostic
83+
84+
1. Pick a category. If none fits, propose a new category in a PR that
85+
updates this file **and** `ASSUMPTIONS.md` A-TG-9.1.
86+
2. Tag the `Diagnostic.source` field with `tangle-lsp[CATEGORY]`.
87+
3. Add a `// [CATEGORY]` comment immediately above the
88+
`self.diagnostics.push(...)` call so reviewers can audit the
89+
category set at-a-glance.
90+
91+
## How this discharges TG-9 (Option B)
92+
93+
`A-TG-9.1` previously said _"`tangle-lsp` reuses `compiler/lib/
94+
typecheck.ml` as the diagnostic engine (no LSP-only diagnostics)"_.
95+
That was false; the audit (#28) confirmed it.
96+
97+
Option B updates `A-TG-9.1` to:
98+
99+
> `tangle-lsp` emits diagnostics in **four documented categories**
100+
> (`PARSE_ERROR`, `MISSPELLING_HINT`, `STRUCTURAL_HINT`, `NAME_HINT`);
101+
> only `PARSE_ERROR` corresponds to a grammar-level rejection. The
102+
> other three are LSP-only by design, documented in
103+
> `compiler/tangle-lsp/docs/lsp-diagnostic-categories.md`.
104+
105+
This is a discipline shift: instead of pretending the LSP refines the
106+
spec, we acknowledge the gap and document each step out. Option A
107+
(real refinement via FFI to `typecheck.ml`) remains the long-term
108+
target and is tracked in #28.
109+
110+
## CI gate (proposed; queued for follow-up)
111+
112+
A grep-based CI check could enforce:
113+
114+
```bash
115+
grep -rE 'self\.diagnostics\.push' compiler/tangle-lsp/src/ |
116+
grep -v 'tangle-lsp\[(PARSE_ERROR|MISSPELLING_HINT|STRUCTURAL_HINT|NAME_HINT)\]'
117+
```
118+
119+
Any unmatched diagnostic line means an untagged emission site. This
120+
would be a 1d follow-up PR.
121+
122+
## Cross-references
123+
124+
- `PROOF-NARRATIVE.md` §3 TG-9
125+
- `ASSUMPTIONS.md` A-TG-9.1 (updated by this PR)
126+
- Issue #28 — TG-9 audit findings + Options A/B
127+
- `compiler/tangle-lsp/src/backend.rs` — 7 call sites, all now tagged

compiler/tangle-lsp/src/backend.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// SPDX-License-Identifier: MPL-2.0
2+
// Owner: Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
23
//! Backend implementation for the Tangle LSP server.
34
//!
45
//! Handles all LSP lifecycle events and request dispatching. Document state
@@ -189,7 +190,7 @@ impl DocumentState {
189190
self.diagnostics.push(Diagnostic {
190191
range: self.line_range(ln),
191192
severity: Some(DiagnosticSeverity::ERROR),
192-
source: Some("tangle-lsp".into()),
193+
source: Some("tangle-lsp[MISSPELLING_HINT]".into()),
193194
message: format!("Possible misspelling: `{}`", trimmed.split_whitespace().next().unwrap_or("")),
194195
..Default::default()
195196
});
@@ -204,7 +205,7 @@ impl DocumentState {
204205
end: Position::new(0, 1),
205206
},
206207
severity: Some(DiagnosticSeverity::ERROR),
207-
source: Some("tangle-lsp".into()),
208+
source: Some("tangle-lsp[PARSE_ERROR]".into()),
208209
message: format!("Unbalanced parentheses (depth: {})", paren_depth),
209210
..Default::default()
210211
});
@@ -216,7 +217,7 @@ impl DocumentState {
216217
end: Position::new(0, 1),
217218
},
218219
severity: Some(DiagnosticSeverity::ERROR),
219-
source: Some("tangle-lsp".into()),
220+
source: Some("tangle-lsp[PARSE_ERROR]".into()),
220221
message: format!("Unbalanced brackets (depth: {})", bracket_depth),
221222
..Default::default()
222223
});
@@ -228,7 +229,7 @@ impl DocumentState {
228229
end: Position::new(0, 1),
229230
},
230231
severity: Some(DiagnosticSeverity::ERROR),
231-
source: Some("tangle-lsp".into()),
232+
source: Some("tangle-lsp[PARSE_ERROR]".into()),
232233
message: format!("Unbalanced braces (depth: {})", brace_depth),
233234
..Default::default()
234235
});
@@ -240,7 +241,7 @@ impl DocumentState {
240241
end: Position::new(0, 1),
241242
},
242243
severity: Some(DiagnosticSeverity::WARNING),
243-
source: Some("tangle-lsp".into()),
244+
source: Some("tangle-lsp[STRUCTURAL_HINT]".into()),
244245
message: "Unclosed `weave` block — expected `yield strands`".into(),
245246
..Default::default()
246247
});
@@ -252,7 +253,7 @@ impl DocumentState {
252253
end: Position::new(0, 1),
253254
},
254255
severity: Some(DiagnosticSeverity::WARNING),
255-
source: Some("tangle-lsp".into()),
256+
source: Some("tangle-lsp[STRUCTURAL_HINT]".into()),
256257
message: format!("Possible unclosed block (nesting depth: {})", depth),
257258
..Default::default()
258259
});
@@ -273,7 +274,7 @@ impl DocumentState {
273274
end: Position::new(*ln, *col + name.len() as u32),
274275
},
275276
severity: Some(DiagnosticSeverity::HINT),
276-
source: Some("tangle-lsp".into()),
277+
source: Some("tangle-lsp[NAME_HINT]".into()),
277278
message: format!("Possibly undefined: `{}`", name),
278279
..Default::default()
279280
});

0 commit comments

Comments
 (0)