Snapshot and refresh contract
LSP workspace snapshot lifecycle and refresh guarantees for diagnostics and symbol services.
Document layout: Feature capability layoutconforms
## Purpose## Requirements
Snapshot and refresh contract Specification
Purpose
LSP workspace snapshot lifecycle and refresh guarantees for diagnostics and symbol services.
Requirements
Requirement: Hub authority: Decision [D-TOOL-LSP-0001]
The Beskid standard SHALL enforce the following migrated contract section. Accepted ADR decisions are binding; uppercase requirement keywords retain their BCP-14 meaning.
Hub owns snapshot/refresh MUST/SHOULD.
Stable ID: BSP-REQ-6C7F3ADC8DEE
Legacy source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/adr/0001-hub-authority/content.md
Source SHA-256: 4efdf60c73a5d7e1f88466b09a72fee61506815654d56db7bcb60b5fed1ec832
Scenario: Conformance exercises Decision
- GIVEN an implementation claims conformance with this capability
- WHEN behavior governed by this contract section is exercised
- THEN every MUST, SHALL, REQUIRED, prohibition, and accepted decision in the section is satisfied
Requirement: Invalidation: Decision [D-TOOL-LSP-0002]
The Beskid standard SHALL enforce the following migrated contract section. Accepted ADR decisions are binding; uppercase requirement keywords retain their BCP-14 meaning.
Invalidate on focus, manifest, lock; debounce watchers.
Stable ID: BSP-REQ-4C80F6D47104
Legacy source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/adr/0002-invalidation-on-focus-and-manifest/content.md
Source SHA-256: e422c1bff32c23eb0e8d3155395778c9eb4b7504c514c1749ff3c7564ac096c1
Scenario: Conformance exercises Decision
- GIVEN an implementation claims conformance with this capability
- WHEN behavior governed by this contract section is exercised
- THEN every MUST, SHALL, REQUIRED, prohibition, and accepted decision in the section is satisfied
Informative Source Provenance
The records below preserve migration history and are not normative except where text was extracted into a requirement above.
Source Record: Snapshot and refresh contract
Authority: informative provenance
Legacy path: /platform-spec/tooling/lsp/snapshot-and-refresh-contract/
Source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/content.md
SHA-256: 462bcd7396a7962595cfa19edf2743f63d26b807768d84eba3d70be7b9198d69
Migrated source text
<SpecSection title="What this feature specifies" id="what-this-feature-specifies">
`Snapshot and refresh contract` defines one operational contract that a newcomer can follow end-to-end: first the model, then execution flow, then strict guarantees, concrete examples, and verification guidance.
</SpecSection>
<SpecSection title="Implementation anchors" id="implementation-anchors">
- LSP diagnostics in `compiler/crates/beskid_lsp/src/diagnostics.rs`
- Analysis services in `compiler/crates/beskid_analysis/src/services/`
- Resolver implementation in `compiler/crates/beskid_analysis/src/resolve/resolver.rs`
- LSP tests in `compiler/crates/beskid_tests/src/analysis/resolve.rs`
</SpecSection>
<SpecSection title="Decisions" id="decisions">
No open decisions. **`D-TOOL-LSP-0001`** (hub authority), **`0002`** (invalidation on focus and manifest)—see **`adr/`** and the **ADRs** tab.
</SpecSection>
## Decisions
<!-- spec:generate:adr-index -->
No open decisions. Closed choices are normative ADRs under **`adr/`** (`D-TOOL-LSP-0001` … `D-TOOL-LSP-0002`); use the reader **ADRs** tab for expandable detail.
<!-- /spec:generate:adr-index -->
## Articles
<!-- spec:generate:article-index -->
- [Contracts and edge cases](./articles/contracts-and-edge-cases/)
- [Design model](./articles/design-model/)
- [Examples](./articles/examples/)
- [FAQ and troubleshooting](./articles/faq-and-troubleshooting/)
- [Flow and algorithm](./articles/flow-and-algorithm/)
- [Verification and traceability](./articles/verification-and-traceability/)
<!-- /spec:generate:article-index -->
Source Record: Hub authority
Authority: informative provenance
Legacy path: /platform-spec/tooling/lsp/snapshot-and-refresh-contract/adr/0001-hub-authority/
Source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/adr/0001-hub-authority/content.md
SHA-256: 4efdf60c73a5d7e1f88466b09a72fee61506815654d56db7bcb60b5fed1ec832
Migrated source text
## Context
One authority.
## Decision
Hub owns snapshot/refresh MUST/SHOULD.
## Consequences
IDE parity.
## Verification anchors
beskid_lsp tests.
Source Record: Invalidation
Authority: informative provenance
Legacy path: /platform-spec/tooling/lsp/snapshot-and-refresh-contract/adr/0002-invalidation-on-focus-and-manifest/
Source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/adr/0002-invalidation-on-focus-and-manifest/content.md
SHA-256: e422c1bff32c23eb0e8d3155395778c9eb4b7504c514c1749ff3c7564ac096c1
Migrated source text
## Context
Stale snapshots.
## Decision
Invalidate on focus, manifest, lock; debounce watchers.
## Consequences
Config notification.
## Verification anchors
session store.
Source Record: Contracts and edge cases
Authority: informative provenance
Legacy path: /platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/contracts-and-edge-cases/
Source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/contracts-and-edge-cases/content.md
SHA-256: 85fffffb42b29f635887df2f726047a883258a98addc51b44b73e0cc48e77e94
Migrated source text
## Contracts
- **Open buffer wins** — Disk index **must not** overwrite an open `Document` for the same `Uri`.
- **Monotonic versions** — Stale `didChange` versions **must** be ignored per LSP rules.
- **Generation-bound facts** — Open buffers own syntax documentation/diagnostics facts for the current text revision. Hard invalidation **must** clear those facts (fail closed) and rebuild via `rebuild_open_document_syntax_facts`; there is no `ANALYSIS_CACHE_VERSION` shape cache.
- **Diagnostic debounce** — Only the latest scheduled revision per URI may publish; superseded tasks **must** no-op.
- **Manifest URIs** — `.proj` files **must not** run Beskid semantic analysis snapshots.
- **Parity** — Diagnostic codes and severities **must** match `beskid analyze` for the same `CompilationContext`.
## Edge cases
| Case | Behavior |
| --- | --- |
| Source outside focused project tree | Fall back to focused `Project.proj` when path is under focus root (`project_context.rs`) |
| No resolvable manifest | Analysis snapshot is `None`; features degrade gracefully without fabricated graphs |
| Large workspace scan | Progress events **must** remain cancellable by subsequent invalidation |
| Rapid typing | Text-hash fast path avoids parse until pause; debounced diagnostics may lag one generation |
| Lockfile change during edit | Hard invalidation; user may see transient empty diagnostics until rescan completes |
## Meta invalidation
Changes to `project.mod` or mod AOT outputs **must** hard-invalidate compilation cache before re-running generators. Soft paths are opt-in and require proven stable capture keys.
Source Record: Design model
Authority: informative provenance
Legacy path: /platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/design-model/
Source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/design-model/content.md
SHA-256: 7290e3e304ead382dd8af7705bbbcb5a059aefc0a61a206449c18212c59638ce
Migrated source text
## Purpose
The Beskid LSP maintains open document buffers (`State.docs`), disk-backed workspace index entries (`State.workspace_index`), generation-bound syntax fact fields on each `Document` (definitions, hovers, symbols, completion, inlay hints, documentation, diagnostics), and a workspace-scoped **`beskid_queries::BeskidDatabase`** (`State.compilation_db`) that owns incremental invalidation. A separate **`CompilationContext`** cache keys off resolved `Project.proj` paths plus workspace member defaults. Lifecycle **must not** own a `DocumentAnalysisSnapshot` or `ANALYSIS_CACHE_VERSION` shape cache.
## Invalidation model
`didChange` **must** call `BeskidDatabase::ensure_file_text` for the edited URI before rebuilding generation-bound syntax facts or publishing diagnostics. Hard invalidation clears `compilation_context_cache`, resets the Salsa DB when warm, and clears open/index syntax facts until `rebuild_open_document_syntax_facts` rebinds them.
## Snapshot stores
```mermaid
flowchart LR
subgraph open [Open buffers]
DidOpen[didOpen / didChange]
Doc[Document + Salsa file_text]
Snap[syntax facts on Document]
DidOpen --> Doc --> Snap
end
subgraph disk [Disk index]
Scan[workspace_scan]
Idx[workspace_index]
Scan --> Idx
end
subgraph project [Project scope]
Ctx[compilation_context_cache]
Inv[invalidate_compilation_cache]
end
Snap --> Ctx
Inv --> Ctx
Inv --> Snap
```
| Store | Key | Invalidation trigger |
| --- | --- | --- |
| Open `Document` | LSP `Uri` | Text change, version bump, cache version mismatch |
| Disk snapshot | `Uri` (file not open) | Workspace scan, external file watcher refresh |
| `CompilationContext` | `(Project.proj path, member default)` | Manifest/lock/graph changes, explicit invalidation |
## Analysis snapshot contents
`build_document_analysis` calls `beskid_analysis::services::build_document_analysis_with_context` when a `CompilationContext` exists. Project manifest URIs (`.proj`) **must not** produce analysis snapshots—only Beskid sources.
## Hard vs soft invalidation
**Hard invalidation** clears `compilation_context_cache`, resets the warm Salsa DB, and clears per-document generation-bound syntax facts so every open buffer rebinds via `rebuild_open_document_syntax_facts`:
- `Project.proj`, `Workspace.proj`, or lockfile writes affecting dependency roots
- `project.mod` changes on attached `Mod` projects
- Full workspace rescan after `invalidate_compilation_cache` (`workspace_scan.rs`, `backend.rs`)
**Soft invalidation** (re-run meta without full graph rebuild) is permitted only when capture keys remain valid; otherwise the server **must** hard-invalidate per **[Incremental scheduling and determinism](/platform-spec/compiler/compiler-mods/incremental-scheduling-determinism/)**.
## Status reporting
Workspace scans emit `BeskidStatusParams` with `phase: workspace_scan` and throttled progress (`STATUS_EMIT_INTERVAL` 200ms) so the VS Code extension can mirror CLI-style progress in the status bar.
## Implementation anchors
- Document lifecycle: `compiler/crates/beskid_lsp/src/session/lifecycle.rs`
- Project context cache: `compiler/crates/beskid_lsp/src/session/project_context.rs`
- Workspace scan: `compiler/crates/beskid_lsp/src/workspace_scan.rs`
- Server wiring: `compiler/crates/beskid_lsp/src/server/backend.rs`
- Diagnostics publish debounce: 120ms coalescing in `schedule_publish_diagnostics`
Source Record: Examples
Authority: informative provenance
Legacy path: /platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/examples/
Source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/examples/content.md
SHA-256: 61b82d0abb776ebb78eabf1f7669f8c8fa2e2a87c3e100df4bad8d734415d46a
Migrated source text
## Focused project switch
1. User selects a workspace member in **Project** view.
2. Extension sends focused `Project.proj` URI to the LSP.
3. Server invalidates `compilation_context_cache` and schedules `refresh_workspace_scan`.
4. Diagnostics on open `.bd` files refresh against the new graph.
## Edit loop
1. `didChange` updates buffer text and hash.
2. Analysis rebuilds snapshot with current `CompilationContext`.
3. After ~120ms quiet period, `textDocument/publishDiagnostics` fires.
## Close and reopen
1. `didClose` removes open buffer entry.
2. `hydrate_disk_after_close` reads file from disk into `workspace_index`.
3. Reopen uses `didOpen` → fresh open snapshot (disk index entry removed).
## CI parity check
Run `beskid analyze --project <same Project.proj>` and compare diagnostic codes to the Problems panel for the same file generation—discrepancies indicate cache or focus mismatch, not intentional divergence.
Source Record: FAQ and troubleshooting
Authority: informative provenance
Legacy path: /platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/faq-and-troubleshooting/
Source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/faq-and-troubleshooting/content.md
SHA-256: e179e9c0287be2b65cf56dc4c0a8290ebc10b84989c13f9dbef690ca7651fcd6
Migrated source text
## Diagnostics stuck after `Project.lock` update
Trigger a manifest save or run **Beskid: Rescan workspace** (extension command) so the server calls `invalidate_compilation_cache` and rescans. Verify lock materialization succeeded with `beskid lock` in a terminal.
## Wrong project context for a file
Confirm **focused project** in the Project view matches the `Project.proj` owning the file. Multi-root workspaces need the member containing the source path.
## Slow initial open
Full workspace scan is O(files); excluded directories are skipped but large trees still cost IO. Narrow workspace roots or exclude generated folders from the VS Code workspace.
## Stale hover after mod emit
Hard-invalidate paths apply when generated sources touch disk. If emit stays in-memory only, invalidation follows document generation counters—clear and rebuild generation-bound syntax facts when changing that contract.
## `BESKID_WORKSPACE_MEMBER_FOR_META_DEFAULT`
Sets default workspace member for meta scheduling when the environment variable is present; must align with extension focus for consistent mod attachment.
Source Record: Flow and algorithm
Authority: informative provenance
Legacy path: /platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/flow-and-algorithm/
Source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/flow-and-algorithm/content.md
SHA-256: 9e3d05a2380b1ce60f2d0163dc885173812dcf10f1dd359f91aaadfdd82a8086
Migrated source text
## Open document refresh
```mermaid
sequenceDiagram
participant Editor
participant LSP as beskid_lsp
participant Analysis as beskid_analysis
Editor->>LSP: didChange (incremental)
LSP->>LSP: apply_document_changes
LSP->>LSP: text_hash unchanged?
alt changed
LSP->>Analysis: cached_compilation_context
LSP->>Analysis: build_document_analysis_with_context
LSP->>LSP: schedule_publish_diagnostics (120ms debounce)
end
LSP-->>Editor: publishDiagnostics
```
1. Apply LSP content changes to the open `Document`.
2. If the LSP version is stale, ignore the update.
3. Otherwise rebuild generation-bound syntax facts using the workspace Salsa DB and cached `CompilationContext`.
4. Coalesce diagnostic publish jobs per URI revision counter.
## Workspace scan algorithm
`scan_workspace(root, focused_project)`:
1. Walk directory tree, skipping `.git`, `target`, `node_modules`, `.beskid`, `out`, `bin`, `obj`, `.vs`.
2. Collect `.bd` and manifest paths; cap concurrent reads (`MAX_CONCURRENT_READS = 24`).
3. Call `invalidate_compilation_cache` before indexing when graph scope may have changed.
4. For each path, `analyze_document` or hydrate disk snapshot via `set_disk_snapshot` when not open.
5. Emit progress every 25 files or 200ms; finish with `phase: idle`.
Focused project URI steers which `Project.proj` seeds `CompilationContext` when a source file has ambiguous workspace membership.
## External disk change
`refresh_after_disk_change` and `hydrate_disk_after_close` re-read closed files into `workspace_index` without clobbering open buffers. Manifest edits trigger cache invalidation then a full or scoped rescan from `backend.rs` configuration handlers.
## Feature requests and snapshots
IDE features (`completion`, `hover`, `definition`, …) snapshot document text through `protocol/request.rs` helpers so handlers observe a consistent parse tree for the debounced generation.
## Tests
`compiler/crates/beskid_tests/src/analysis/resolve.rs` covers resolver behavior consumed by LSP project context; extend LSP-specific tests when changing invalidation or scan skip rules.
Source Record: Verification and traceability
Authority: informative provenance
Legacy path: /platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/verification-and-traceability/
Source: site/spec-content/platform-spec/tooling/lsp/snapshot-and-refresh-contract/articles/verification-and-traceability/content.md
SHA-256: 33ba39d53dbbce1c99c70cdd0d282307c499e3f5ae9c9ebaa05adb8e1405a140
Migrated source text
## Test anchors
| Concern | Location |
| --- | --- |
| Resolver + graph | `compiler/crates/beskid_tests/src/analysis/resolve.rs` |
| Analysis services | `compiler/crates/beskid_tests/src/analysis/pipeline/core.rs` |
| Document analysis build | `beskid_analysis::services` unit/integration tests |
## Traceability
| Requirement | Evidence |
| --- | --- |
| Hard invalidation clears facts then rebuild binds documentation | Lifecycle `hard_invalidation_clears_syntax_facts_until_rebuild` |
| Skip directories during scan | `should_skip_dir_for_scan` unit coverage in `workspace_scan.rs` |
| Invalidation on manifest change | `backend.rs` configuration handler paths |
| Diagnostic debounce | Revision counter logic in `schedule_publish_diagnostics` |
## Manual verification
1. Open multi-file project; edit one file; confirm diagnostics update after pause.
2. Change `Project.proj`; confirm all open files refresh.
3. Compare `beskid analyze` output to LSP Problems for same snapshot generation.
## Spec updates
Changes to cache keys, skip lists, or debounce intervals **must** update this article and the VS Code extension status contract when user-visible phases change.