Skip to main content

omena_lsp_server/
boundary.rs

1use crate::diagnostics_scheduler::{
2    RustDiagnosticsSchedulerBoundaryV0, rust_diagnostics_scheduler_contract,
3};
4use crate::disk_cache::{DiskDiagnosticsCacheBoundaryV0, disk_diagnostics_cache_contract};
5use crate::query_reuse::{RustQueryReuseBoundaryV0, rust_query_reuse_contract};
6use crate::workspace_runtime_registry::{
7    WorkspaceRuntimeRegistryBoundaryV0, workspace_runtime_registry_contract,
8};
9use crate::{
10    CANCEL_REQUEST_METHOD, CASCADE_AT_POSITION_REQUEST, CLEAR_CACHES_REQUEST,
11    EXPLAIN_HOVER_TRACE_REQUEST, EXPLAIN_REQUEST, NODE_TEXT_DOCUMENT_SYNC_KIND,
12    STYLE_CONTEXT_INDEX_REQUEST,
13};
14use omena_tsgo_client::{OmenaTsgoClientBoundarySummaryV0, summarize_omena_tsgo_client_boundary};
15use serde::Serialize;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
18#[serde(rename_all = "camelCase")]
19#[non_exhaustive]
20pub enum CacheStorageRungV0 {
21    InitializationOptions,
22    Environment,
23    Platform,
24    Workspace,
25    Disabled,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
29#[serde(rename_all = "camelCase")]
30#[non_exhaustive]
31pub enum CacheWriteSurfaceKindV0 {
32    LspWorkspaceCache,
33    BridgeExternalSifCache,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
37#[serde(rename_all = "camelCase")]
38pub struct CacheWriteSurfaceV0 {
39    pub root_kind: CacheWriteSurfaceKindV0,
40    pub resolved_rung: CacheStorageRungV0,
41    pub root_shape: &'static str,
42    pub cache_directories: Vec<&'static str>,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
46#[serde(rename_all = "camelCase")]
47pub struct OmenaLspServerBoundarySummaryV0 {
48    pub schema_version: &'static str,
49    pub product: &'static str,
50    pub server_name: &'static str,
51    pub migration_status: &'static str,
52    pub transport_contract: &'static str,
53    pub trust_boundary: LspTrustBoundaryV0,
54    pub capabilities: OmenaLspServerCapabilitiesV0,
55    pub handler_surfaces: Vec<LspHandlerSurfaceV0>,
56    pub migration_phases: Vec<LspMigrationPhaseV0>,
57    pub blocking_work_policy: Vec<&'static str>,
58    pub tsgo_client_boundary: OmenaTsgoClientBoundarySummaryV0,
59    pub source_provider_adapter: SourceProviderDirectRustAdapterV0,
60    pub workspace_runtime_registry: WorkspaceRuntimeRegistryBoundaryV0,
61    pub diagnostics_scheduler: RustDiagnosticsSchedulerBoundaryV0,
62    pub query_reuse: RustQueryReuseBoundaryV0,
63    pub disk_diagnostics_cache: DiskDiagnosticsCacheBoundaryV0,
64    pub thin_client_endpoint: ThinClientEndpointV0,
65    pub multi_editor_distribution: MultiEditorDistributionV0,
66    pub node_parity_contracts: Vec<&'static str>,
67    pub next_decoupling_targets: Vec<&'static str>,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
71#[serde(rename_all = "camelCase")]
72pub struct LspTrustBoundaryV0 {
73    pub product: &'static str,
74    pub network_access: &'static str,
75    pub verification_owner: &'static str,
76    pub request_path_policy: Vec<&'static str>,
77    pub forbidden_runtime_capabilities: Vec<&'static str>,
78    /// Every owned cache root the LSP process may write, including writes
79    /// performed by the bridge below the query layer. The typed rung keeps
80    /// editor, environment, platform, workspace, and disabled resolution
81    /// distinguishable without weakening the `neverFetch` network invariant.
82    pub disk_write_surfaces: Vec<CacheWriteSurfaceV0>,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct OmenaLspServerCapabilitiesV0 {
88    pub text_document_sync: u8,
89    pub definition_provider: bool,
90    pub hover_provider: bool,
91    pub color_provider: bool,
92    pub completion_provider: CompletionProviderCapabilityV0,
93    pub code_action_provider: CodeActionProviderCapabilityV0,
94    pub references_provider: bool,
95    pub code_lens_provider: ResolveProviderCapabilityV0,
96    pub document_link_provider: ResolveProviderCapabilityV0,
97    pub workspace_symbol_provider: bool,
98    pub rename_provider: RenameProviderCapabilityV0,
99    pub workspace: WorkspaceCapabilityV0,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
103#[serde(rename_all = "camelCase")]
104pub struct CompletionProviderCapabilityV0 {
105    pub trigger_characters: Vec<&'static str>,
106    pub resolve_provider: bool,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
110#[serde(rename_all = "camelCase")]
111pub struct CodeActionProviderCapabilityV0 {
112    pub code_action_kinds: Vec<&'static str>,
113    pub resolve_provider: bool,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
117#[serde(rename_all = "camelCase")]
118pub struct ResolveProviderCapabilityV0 {
119    pub resolve_provider: bool,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
123#[serde(rename_all = "camelCase")]
124pub struct RenameProviderCapabilityV0 {
125    pub prepare_provider: bool,
126}
127
128#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
129#[serde(rename_all = "camelCase")]
130pub struct WorkspaceCapabilityV0 {
131    pub workspace_folders: WorkspaceFoldersCapabilityV0,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
135#[serde(rename_all = "camelCase")]
136pub struct WorkspaceFoldersCapabilityV0 {
137    pub supported: bool,
138    pub change_notifications: bool,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
142#[serde(rename_all = "camelCase")]
143pub struct LspHandlerSurfaceV0 {
144    pub method: &'static str,
145    pub node_owner: &'static str,
146    pub rust_owner_target: &'static str,
147    pub migration_state: &'static str,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
151#[serde(rename_all = "camelCase")]
152pub struct LspMigrationPhaseV0 {
153    pub phase: &'static str,
154    pub goal: &'static str,
155    pub exit_gate: &'static str,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
159#[serde(rename_all = "camelCase")]
160pub struct ThinClientEndpointV0 {
161    pub product: &'static str,
162    pub endpoint_name: &'static str,
163    pub transport_contract: &'static str,
164    pub command_owner: &'static str,
165    pub standalone_package: &'static str,
166    pub split_repository: &'static str,
167    pub cargo_install_command: &'static str,
168    pub node_fallback_allowed: bool,
169    pub file_watcher_globs: Vec<&'static str>,
170    pub host_responsibilities: Vec<&'static str>,
171    pub rust_responsibilities: Vec<&'static str>,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
175#[serde(rename_all = "camelCase")]
176pub struct MultiEditorDistributionV0 {
177    pub product: &'static str,
178    pub owner: &'static str,
179    pub distribution_model: &'static str,
180    pub supported_editors: Vec<&'static str>,
181    pub install_surfaces: Vec<&'static str>,
182    pub documentation: Vec<&'static str>,
183    pub endpoint_policy: Vec<&'static str>,
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
187#[serde(rename_all = "camelCase")]
188pub struct SourceProviderDirectRustAdapterV0 {
189    pub product: &'static str,
190    pub candidate_owner: &'static str,
191    pub style_definition_owner: &'static str,
192    pub type_fact_owner: &'static str,
193    pub request_path_policy: Vec<&'static str>,
194    pub provider_surfaces: Vec<&'static str>,
195}
196
197pub fn summarize_omena_lsp_server_boundary() -> OmenaLspServerBoundarySummaryV0 {
198    OmenaLspServerBoundarySummaryV0 {
199        schema_version: "0",
200        product: "omena-lsp-server.boundary",
201        server_name: "omena-css",
202        migration_status: "rustStable",
203        transport_contract: "LSP stdio or IPC JSON-RPC",
204        trust_boundary: lsp_trust_boundary_contract(),
205        capabilities: current_node_lsp_capability_contract(),
206        handler_surfaces: lsp_handler_surfaces(),
207        migration_phases: lsp_migration_phases(),
208        blocking_work_policy: vec![
209            "noFullWorkspaceProgramOnRequestPath",
210            "queuedRequestCancellationBeforeProviderWork",
211            "dispatchedRequestCancellationAtCompletionBoundary",
212            "noMidComputationCancellationClaim",
213            "workerQueriesUseSnapshotReadView",
214            "tsgoProviderCancellationTokenBoundary",
215            "backgroundIndexAndTypeFactWarmup",
216            "staleOrUnresolvableFastReturn",
217        ],
218        tsgo_client_boundary: summarize_omena_tsgo_client_boundary(),
219        source_provider_adapter: source_provider_direct_rust_adapter_contract(),
220        workspace_runtime_registry: workspace_runtime_registry_contract(),
221        diagnostics_scheduler: rust_diagnostics_scheduler_contract(),
222        query_reuse: rust_query_reuse_contract(),
223        disk_diagnostics_cache: disk_diagnostics_cache_contract(),
224        thin_client_endpoint: thin_client_endpoint_contract(),
225        multi_editor_distribution: multi_editor_distribution_contract(),
226        node_parity_contracts: vec![
227            "initializeCapabilities",
228            "textDocumentSync",
229            "workspaceFolders",
230            "dynamicFileWatchers",
231            "diagnosticsPush",
232            "codeLensRefresh",
233        ],
234        next_decoupling_targets: vec![],
235    }
236}
237
238pub fn lsp_trust_boundary_contract() -> LspTrustBoundaryV0 {
239    let lsp_rung = current_cache_storage_rung();
240    let bridge_rung = current_bridge_cache_storage_rung();
241    LspTrustBoundaryV0 {
242        product: "omena-lsp-server.trust-boundary",
243        network_access: "neverFetch",
244        verification_owner: "omena-cli.lock-provenance",
245        request_path_policy: vec![
246            "analysisTimeUsesLocalWorkspaceOnly",
247            "recordedSifEvidenceReadFromDiskWithoutWorkspaceLockAuthority",
248            "attestationVerificationOwnedByCli",
249            // The CLI records immutable canonical-url + SIF-hash verdicts and
250            // content-addressed bundles. Bridge verifies them offline; LSP
251            // never treats a lock as trust authority or uses the network.
252            "recordedShardVerdictsVerifiedOfflineWithoutNetworkAuthority",
253            "noRegistryFetchOnLspRequestPath",
254            "noTransparencyLogLookupOnLspRequestPath",
255            // Cache roots may be editor- or platform-owned after resolution;
256            // the durable invariant is containment by this declared set, not
257            // physical placement below the opened repository.
258            "cacheWritesConfinedToDeclaredOwnedRootsNeverNetwork",
259        ],
260        forbidden_runtime_capabilities: vec![
261            "registryHttpClient",
262            "sigstoreBundleVerifier",
263            "transparencyLogClient",
264            "socketNetworkIo",
265        ],
266        disk_write_surfaces: declared_cache_write_surfaces_for_rungs(lsp_rung, bridge_rung),
267    }
268}
269
270pub(crate) fn current_cache_storage_rung() -> CacheStorageRungV0 {
271    let config = crate::cache_root::LspCacheStorageConfigV0::standalone(None);
272    crate::cache_root::process_cache_roots(
273        &config,
274        "<workspaceIdentity>",
275        std::path::Path::new("<workspaceFolder>"),
276    )
277    .source
278    .into()
279}
280
281fn current_bridge_cache_storage_rung() -> CacheStorageRungV0 {
282    let has_environment_override = [
283        crate::cache_root::OMENA_CACHE_DIR_ENV,
284        crate::cache_root::OMENA_GLOBAL_CACHE_DIR_ENV,
285    ]
286    .into_iter()
287    .any(|name| std::env::var_os(name).is_some_and(|value| !value.is_empty()));
288    if has_environment_override {
289        CacheStorageRungV0::Environment
290    } else {
291        CacheStorageRungV0::Platform
292    }
293}
294
295pub(crate) fn declared_cache_write_surfaces_for_rungs(
296    lsp_rung: CacheStorageRungV0,
297    bridge_rung: CacheStorageRungV0,
298) -> Vec<CacheWriteSurfaceV0> {
299    let mut surfaces = Vec::new();
300    if let Some(root_shape) = cache_root_shape(CacheWriteSurfaceKindV0::LspWorkspaceCache, lsp_rung)
301    {
302        surfaces.push(CacheWriteSurfaceV0 {
303            root_kind: CacheWriteSurfaceKindV0::LspWorkspaceCache,
304            resolved_rung: lsp_rung,
305            root_shape,
306            cache_directories: vec![
307                "diagnostics-cache-v1",
308                "source-document-index-v1",
309                "source-type-fact-cache-v1",
310                "workspace-occurrence-shards-v2",
311            ],
312        });
313    }
314    if let Some(root_shape) =
315        cache_root_shape(CacheWriteSurfaceKindV0::BridgeExternalSifCache, bridge_rung)
316    {
317        surfaces.push(CacheWriteSurfaceV0 {
318            root_kind: CacheWriteSurfaceKindV0::BridgeExternalSifCache,
319            resolved_rung: bridge_rung,
320            root_shape,
321            cache_directories: vec!["external-sif-v0"],
322        });
323    }
324    surfaces
325}
326
327fn cache_root_shape(
328    kind: CacheWriteSurfaceKindV0,
329    rung: CacheStorageRungV0,
330) -> Option<&'static str> {
331    match (kind, rung) {
332        (CacheWriteSurfaceKindV0::LspWorkspaceCache, CacheStorageRungV0::InitializationOptions) => {
333            Some("<initializationWorkspaceStorage>/omena/workspaces/<workspaceIdentityHash>/**")
334        }
335        (CacheWriteSurfaceKindV0::LspWorkspaceCache, CacheStorageRungV0::Environment) => {
336            Some("<environmentCacheDir>/omena/workspaces/<workspaceIdentityHash>/**")
337        }
338        (CacheWriteSurfaceKindV0::LspWorkspaceCache, CacheStorageRungV0::Platform) => {
339            Some("<platformCacheHome>/omena/workspaces/<workspaceIdentityHash>/**")
340        }
341        (CacheWriteSurfaceKindV0::LspWorkspaceCache, CacheStorageRungV0::Workspace) => {
342            Some("<workspaceFolder>/.cache/omena/**")
343        }
344        (
345            CacheWriteSurfaceKindV0::BridgeExternalSifCache,
346            CacheStorageRungV0::InitializationOptions,
347        ) => {
348            Some("<initializationGlobalStorage>/omena/workspaces/<bridgeWorkspaceIdentityHash>/**")
349        }
350        (CacheWriteSurfaceKindV0::BridgeExternalSifCache, CacheStorageRungV0::Environment) => {
351            Some("<environmentCacheDir>/omena/workspaces/<bridgeWorkspaceIdentityHash>/**")
352        }
353        (CacheWriteSurfaceKindV0::BridgeExternalSifCache, CacheStorageRungV0::Platform) => {
354            Some("<platformCacheHome>/omena/workspaces/<bridgeWorkspaceIdentityHash>/**")
355        }
356        (CacheWriteSurfaceKindV0::BridgeExternalSifCache, CacheStorageRungV0::Workspace) => {
357            Some("<bridgeWorkspaceRoot>/.cache/omena/**")
358        }
359        (_, CacheStorageRungV0::Disabled) => None,
360    }
361}
362
363pub(crate) fn declared_disk_diagnostics_storage_locations() -> Vec<CacheWriteSurfaceV0> {
364    declared_cache_write_surfaces_for_rungs(
365        current_cache_storage_rung(),
366        CacheStorageRungV0::Disabled,
367    )
368    .into_iter()
369    .filter(|surface| surface.root_kind == CacheWriteSurfaceKindV0::LspWorkspaceCache)
370    .map(|mut surface| {
371        surface.cache_directories = vec!["diagnostics-cache-v1"];
372        surface
373    })
374    .collect()
375}
376
377impl From<crate::cache_root::CacheRootSourceV0> for CacheStorageRungV0 {
378    fn from(source: crate::cache_root::CacheRootSourceV0) -> Self {
379        match source {
380            crate::cache_root::CacheRootSourceV0::InitializationOptions => {
381                Self::InitializationOptions
382            }
383            crate::cache_root::CacheRootSourceV0::Environment => Self::Environment,
384            crate::cache_root::CacheRootSourceV0::Platform => Self::Platform,
385            crate::cache_root::CacheRootSourceV0::Workspace => Self::Workspace,
386            crate::cache_root::CacheRootSourceV0::Disabled => Self::Disabled,
387        }
388    }
389}
390
391pub fn source_provider_direct_rust_adapter_contract() -> SourceProviderDirectRustAdapterV0 {
392    SourceProviderDirectRustAdapterV0 {
393        product: "omena-lsp-server.source-provider-direct-rust-adapter",
394        candidate_owner: "omena-query/sourceSyntaxIndex",
395        style_definition_owner: "omena-query/styleHoverCandidates",
396        type_fact_owner: "omena-tsgo-client",
397        request_path_policy: vec![
398            "noNodeWorkspaceTypeResolverOnSourceProviderPath",
399            "buildQuerySourceSyntaxIndexOnDocumentChange",
400            "dedupeTargetAwareSourceCandidates",
401            "consumeQueryStyleHoverCandidates",
402            "consumeQuerySassModuleSources",
403            "consumeConfiguredPackageManifestPaths",
404            "consumeTsgoTypeFactsForTypedCxProjection",
405            "consumeSassPartialEvaluatorGeneratedSelectors",
406            "useOpenedDocumentIndexesBeforeWorkspaceFallback",
407            "unresolvedCandidatesRemainFastDiagnostics",
408        ],
409        provider_surfaces: vec![
410            "textDocument/hover",
411            "textDocument/definition",
412            "textDocument/references",
413            "textDocument/completion",
414            "textDocument/publishDiagnostics",
415            CASCADE_AT_POSITION_REQUEST,
416            STYLE_CONTEXT_INDEX_REQUEST,
417            EXPLAIN_HOVER_TRACE_REQUEST,
418            EXPLAIN_REQUEST,
419        ],
420    }
421}
422
423pub fn thin_client_endpoint_contract() -> ThinClientEndpointV0 {
424    ThinClientEndpointV0 {
425        product: "omena-lsp-server.thin-client-endpoint",
426        endpoint_name: "omena-css.thin-client-runtime-endpoint",
427        transport_contract: "LSP stdio JSON-RPC",
428        command_owner: "dist/bin/<platform>-<arch>/omena-lsp-server",
429        standalone_package: "omena-lsp-server",
430        split_repository: env!("CARGO_PKG_REPOSITORY"),
431        cargo_install_command: concat!(
432            "cargo install omena-lsp-server --version ",
433            env!("CARGO_PKG_VERSION")
434        ),
435        node_fallback_allowed: false,
436        file_watcher_globs: vec![
437            "**/*.module.{scss,css,less}",
438            "**/*.{ts,tsx,js,jsx,mts,cts,mjs,cjs,d.ts,vue,html,svelte,astro,md,mdx,liquid,twig,njk,nunjucks,hbs,handlebars,erb,ejs,html.eex,heex}",
439            "**/tsconfig*.json",
440            "**/jsconfig*.json",
441            "**/package.json",
442            "**/vite.config.{ts,mts,cts,js,mjs,cjs}",
443            "**/webpack.config.{ts,mts,cts,js,mjs,cjs}",
444        ],
445        host_responsibilities: vec![
446            "resolvePackagedRustBinary",
447            "resolveStandaloneRustCommand",
448            "buildThinClientServerOptions",
449            "prepareEditorStorageRoots",
450            "passStorageInitializationOptions",
451            "declareStaticDocumentSelector",
452            "startLanguageClient",
453            "registerStaticFileWatchers",
454            "requestServerOwnedCacheClear",
455            "translateShowReferencesArguments",
456            "renderHoverTracePanel",
457            "surfaceStartupErrors",
458        ],
459        rust_responsibilities: vec![
460            "ownLspLifecycle",
461            "ownWorkspaceState",
462            "ownDiagnosticsScheduling",
463            "ownProviderExecution",
464            "ownTsgoClientLifecycle",
465            "resolveAndClearDeclaredOwnedCachePaths",
466        ],
467    }
468}
469
470pub fn multi_editor_distribution_contract() -> MultiEditorDistributionV0 {
471    MultiEditorDistributionV0 {
472        product: "omena-lsp-server.multi-editor-distribution",
473        owner: "omena-lsp-server/distribution",
474        distribution_model: "standaloneRustLspServerWithThinEditorHosts",
475        supported_editors: vec!["vscode", "neovim", "zed"],
476        install_surfaces: vec![
477            "vsixBundledDistBinary",
478            "cargoInstallOmenaLspServer",
479            "repoLocalDistBin",
480        ],
481        documentation: vec![
482            "client/src/extension.ts",
483            "docs/clients/neovim.md",
484            "docs/clients/zed.md",
485        ],
486        endpoint_policy: vec![
487            "standaloneRustServerIsPrimaryMultiEditorEndpoint",
488            "nodeLspServerIsNotPrimaryEndpoint",
489            "editorClientsDoNotImplementProviderSemantics",
490            "editorsMayRunBesideNativeTypeScriptServer",
491        ],
492    }
493}
494
495pub fn current_node_lsp_capability_contract() -> OmenaLspServerCapabilitiesV0 {
496    OmenaLspServerCapabilitiesV0 {
497        text_document_sync: NODE_TEXT_DOCUMENT_SYNC_KIND,
498        definition_provider: true,
499        hover_provider: true,
500        color_provider: true,
501        completion_provider: CompletionProviderCapabilityV0 {
502            trigger_characters: vec!["'", "\"", "`", ",", ".", "$", "@", "-"],
503            resolve_provider: false,
504        },
505        code_action_provider: CodeActionProviderCapabilityV0 {
506            code_action_kinds: vec!["quickfix", "refactor.extract", "refactor.inline"],
507            resolve_provider: false,
508        },
509        references_provider: true,
510        code_lens_provider: ResolveProviderCapabilityV0 {
511            resolve_provider: false,
512        },
513        document_link_provider: ResolveProviderCapabilityV0 {
514            resolve_provider: false,
515        },
516        workspace_symbol_provider: true,
517        rename_provider: RenameProviderCapabilityV0 {
518            prepare_provider: true,
519        },
520        workspace: WorkspaceCapabilityV0 {
521            workspace_folders: WorkspaceFoldersCapabilityV0 {
522                supported: true,
523                change_notifications: true,
524            },
525        },
526    }
527}
528
529pub fn lsp_handler_surfaces() -> Vec<LspHandlerSurfaceV0> {
530    vec![
531        style_provider_handler("textDocument/definition"),
532        style_provider_handler("textDocument/hover"),
533        style_provider_handler("textDocument/completion"),
534        style_provider_handler("textDocument/codeAction"),
535        style_provider_handler("textDocument/references"),
536        style_provider_handler("textDocument/codeLens"),
537        style_provider_handler("textDocument/documentColor"),
538        style_provider_handler("textDocument/colorPresentation"),
539        style_provider_handler("textDocument/documentLink"),
540        style_provider_handler("workspace/symbol"),
541        style_provider_handler("textDocument/prepareRename"),
542        style_provider_handler("textDocument/rename"),
543        runtime_handler("initialized"),
544        runtime_handler("textDocument/didOpen"),
545        runtime_handler("textDocument/didChange"),
546        runtime_handler("textDocument/didClose"),
547        runtime_handler("workspace/didChangeWatchedFiles"),
548        runtime_handler("workspace/didChangeConfiguration"),
549        runtime_handler("workspace/didChangeWorkspaceFolders"),
550        diagnostics_handler("textDocument/publishDiagnostics"),
551        query_inspection_handler(CASCADE_AT_POSITION_REQUEST),
552        query_inspection_handler(STYLE_CONTEXT_INDEX_REQUEST),
553        query_inspection_handler(EXPLAIN_HOVER_TRACE_REQUEST),
554        query_inspection_handler(EXPLAIN_REQUEST),
555        runtime_handler(CLEAR_CACHES_REQUEST),
556        runtime_handler(CANCEL_REQUEST_METHOD),
557    ]
558}
559
560fn style_provider_handler(method: &'static str) -> LspHandlerSurfaceV0 {
561    LspHandlerSurfaceV0 {
562        method,
563        node_owner: "server/lsp-server/src/providers",
564        rust_owner_target: "omena-lsp-server/providers/style-source",
565        migration_state: "providerParity",
566    }
567}
568
569fn runtime_handler(method: &'static str) -> LspHandlerSurfaceV0 {
570    LspHandlerSurfaceV0 {
571        method,
572        node_owner: "server/lsp-server/src/handler-registration.ts",
573        rust_owner_target: "omena-lsp-server/runtime",
574        migration_state: "implemented",
575    }
576}
577
578fn diagnostics_handler(method: &'static str) -> LspHandlerSurfaceV0 {
579    LspHandlerSurfaceV0 {
580        method,
581        node_owner: "server/lsp-server/src/diagnostics-scheduler.ts",
582        rust_owner_target: "omena-lsp-server/diagnostics",
583        migration_state: "implemented",
584    }
585}
586
587fn query_inspection_handler(method: &'static str) -> LspHandlerSurfaceV0 {
588    LspHandlerSurfaceV0 {
589        method,
590        node_owner: "server/lsp-server/src/query-inspection",
591        rust_owner_target: "omena-lsp-server/query-inspection",
592        migration_state: "implemented",
593    }
594}
595
596pub fn lsp_migration_phases() -> Vec<LspMigrationPhaseV0> {
597    vec![
598        LspMigrationPhaseV0 {
599            phase: "phase-0-boundary",
600            goal: "declare Rust LSP capability and handler parity with the Node server",
601            exit_gate: "rust/omena-lsp-server/boundary",
602        },
603        LspMigrationPhaseV0 {
604            phase: "phase-1-shell",
605            goal: "own initialize, shutdown, text sync, workspace folders, and watcher state in Rust",
606            exit_gate: "rust/omena-lsp-server/runtime-loop",
607        },
608        LspMigrationPhaseV0 {
609            phase: "phase-2-style-providers",
610            goal: "serve style-side hover, definition, references, diagnostics, and code lens from Rust",
611            exit_gate: "rust/omena-lsp-server/provider-parity",
612        },
613        LspMigrationPhaseV0 {
614            phase: "phase-3-source-providers",
615            goal: "replace Node WorkspaceTypeResolver hot path with a long-lived tsgo client and Rust query runtime",
616            exit_gate: "rust/omena-tsgo-client/boundary",
617        },
618        LspMigrationPhaseV0 {
619            phase: "phase-4-thin-client",
620            goal: "shrink the VS Code extension to UI commands and Rust LSP process orchestration",
621            exit_gate: "rust/omena-lsp-server/thin-client-boundary",
622        },
623    ]
624}
625
626#[cfg(test)]
627mod cache_storage_boundary_tests {
628    use super::*;
629
630    #[test]
631    fn declared_cache_surface_table_is_typed_and_exhaustive_by_rung() {
632        let rows = [
633            (
634                CacheStorageRungV0::InitializationOptions,
635                "<initializationWorkspaceStorage>/omena/workspaces/<workspaceIdentityHash>/**",
636                "<initializationGlobalStorage>/omena/workspaces/<bridgeWorkspaceIdentityHash>/**",
637            ),
638            (
639                CacheStorageRungV0::Environment,
640                "<environmentCacheDir>/omena/workspaces/<workspaceIdentityHash>/**",
641                "<environmentCacheDir>/omena/workspaces/<bridgeWorkspaceIdentityHash>/**",
642            ),
643            (
644                CacheStorageRungV0::Platform,
645                "<platformCacheHome>/omena/workspaces/<workspaceIdentityHash>/**",
646                "<platformCacheHome>/omena/workspaces/<bridgeWorkspaceIdentityHash>/**",
647            ),
648            (
649                CacheStorageRungV0::Workspace,
650                "<workspaceFolder>/.cache/omena/**",
651                "<bridgeWorkspaceRoot>/.cache/omena/**",
652            ),
653        ];
654
655        for (rung, expected_lsp_root, expected_bridge_root) in rows {
656            let surfaces = declared_cache_write_surfaces_for_rungs(rung, rung);
657            assert_eq!(surfaces.len(), 2, "rung={rung:?}");
658            assert_eq!(
659                surfaces[0],
660                CacheWriteSurfaceV0 {
661                    root_kind: CacheWriteSurfaceKindV0::LspWorkspaceCache,
662                    resolved_rung: rung,
663                    root_shape: expected_lsp_root,
664                    cache_directories: vec![
665                        "diagnostics-cache-v1",
666                        "source-document-index-v1",
667                        "source-type-fact-cache-v1",
668                        "workspace-occurrence-shards-v2",
669                    ],
670                }
671            );
672            assert_eq!(
673                surfaces[1],
674                CacheWriteSurfaceV0 {
675                    root_kind: CacheWriteSurfaceKindV0::BridgeExternalSifCache,
676                    resolved_rung: rung,
677                    root_shape: expected_bridge_root,
678                    cache_directories: vec!["external-sif-v0"],
679                }
680            );
681        }
682
683        assert!(
684            declared_cache_write_surfaces_for_rungs(
685                CacheStorageRungV0::Disabled,
686                CacheStorageRungV0::Disabled,
687            )
688            .is_empty(),
689            "disabled resolution must declare no writable cache surface"
690        );
691
692        let split = declared_cache_write_surfaces_for_rungs(
693            CacheStorageRungV0::Platform,
694            CacheStorageRungV0::Workspace,
695        );
696        assert_eq!(split[0].resolved_rung, CacheStorageRungV0::Platform);
697        assert_eq!(split[1].resolved_rung, CacheStorageRungV0::Workspace);
698    }
699}