| 1 | <!-- English translation of adr/0024-ide-sdk-and-stable-contracts.md. Canonical Russian: ../../adr/0024-ide-sdk-and-stable-contracts.md --> |
| 2 | |
| 3 | # ADR 0024: SDK for CascadeIDE - stable contracts for internal extension and future plugins |
| 4 | |
| 5 | **Status:** Proposed |
| 6 | **Date:** 2026-04-08 |
| 7 | |
| 8 | ## Related ADRs |
| 9 | |
| 10 | | ADR | Role | |
| 11 | |-----|------| |
| 12 | | [0005](0005-defer-dynamic-plugins-mef.md) | deferred plugins | |
| 13 | | [0006](0006-presentation-layers-and-feature-slices.md) | layer/slice boundaries | |
| 14 | | [0008](0008-mcp-contracts-and-testable-infrastructure.md) | contracts and testing infrastructure | |
| 15 | | [0013](0013-command-surface-and-discoverability.md) | command surface | |
| 16 | | [0019](0019-shared-git-core-ide-and-git-mcp.md) | common core as a precedent | |
| 17 | | [0023](0023-markdown-diagrams-language-tooling.md) | external instruments/LSPs as contracts | |
| 18 | | [0026](0026-markdown-preview-surfaces-and-placement.md) | geometry preview Markdown in TOML | |
| 19 | | [0025](0025-sdk-attention-zones-and-capabilities.md) | zones of attention PFD/MFD/… in SDK and capabilities | |
| 20 | |
| 21 | ## Summary |
| 22 | |
| 23 | - **CascadeIDE SDK** - stable contracts for internal extension and future plugins. |
| 24 | - Versioning of public APIs; border with “everything internal”. |
| 25 | - Communication with attention zones - [0025](0025-sdk-attention-zones-and-capabilities.md). |
| 26 | |
| 27 | --- |
| 28 | ## Context |
| 29 | |
| 30 | CascadeIDE is actively developing “from the inside”: new panels, tools, diagnostic channels, UI modes and integration with external processes (LSP/DAP/MCP) are being added. In this case: |
| 31 | |
| 32 | - Ease of development depends on the **mental model**: where are the boundaries, what dependencies are acceptable, what is considered “core”, what is a “feature”. |
| 33 | - `MainWindowViewModel` and UI-shell inevitably become a place where “everything is asked” if there are no explicit contracts. |
| 34 | - Plugins (dynamic loading of DLL/MEF) are **deferred** for now ([0005](0005-defer-dynamic-plugins-mef.md)), but when we return to them, we will need “where to insert”, otherwise the plugin-host will appear before the form of slots/contracts is stabilized. |
| 35 | |
| 36 | We need an “SDK” - but first of all **for us**, so that: |
| 37 | |
| 38 | - reduce coupling between features, |
| 39 | - ensure extensibility without chaos, |
| 40 | - prepare the base for future plugins without premature plugin-host. |
| 41 | |
| 42 | --- |
| 43 | |
| 44 | ## Solution |
| 45 | |
| 46 | Adopt the approach: **SDK = stable IDE extension contracts** rather than a must-have plugin system today. |
| 47 | |
| 48 | SDK v1 includes: |
| 49 | |
| 50 | 1. **Explicit public interfaces/contracts** between shell and features (and between features), designed as a separate layer (folder/project), with minimal dependencies. |
| 51 | - Delivery format: **separate project** `CascadeIDE.Contracts` (or a similar name), not a folder inside `cascade-ide`. |
| 52 | |
| 53 | 2. **Capability model** (declaration of capabilities), so that a feature can “connect” to the shell and to each other without direct references to specific implementations. |
| 54 | |
| 55 | 3. **Capability registry - hybrid (follows from the current IDE logic):** |
| 56 | - **Code-first registration** capabilities features at startup (without MEF/reflection “for growth”). |
| 57 | - **Data overlay for presentation**: UI modes/layouts (TOML) can enable/disable or change presentation capabilities, but do not replace their semantics. |
| 58 | - **Introspection**: shell can assemble a “capability map” for diagnostics/telemetry/agent. |
| 59 | |
| 60 | 4. **Command surface as a contract**: commands (and their discoverability) are formalized not through “knowing the insides” of a feature, but through a general registration/metadata contract (in the spirit of [0013](0013-command-surface-and-discoverability.md)). |
| 61 | |
| 62 | 5. **Out-of-proc as the norm for integrations** (LSP/DAP/MCP/CLI): protocols and DTOs are part of the “SDK” in the sense of stable contracts (see [0008](0008-mcp-contracts-and-testable-infrastructure.md)). In-proc extensions are not prohibited, but must be within the scope of contracts. |
| 63 | |
| 64 | 6. **Contract marking `Experimental`/`Stable` is for internal clarity, not a public promise.** |
| 65 | At the active-dev stage (far before alpha), the goal is not “forever compatibility”, but discipline of boundaries: by default everything is `Experimental`, and `Stable` appears only where the team consciously wants to rely on the contract. You can break `Experimental` freely; break `Stable` - consciously (via ADR/migration note), SemVer - when a product need appears. |
| 66 | |
| 67 | 7. **Deferred plugin-host**: dynamic loading of plugins remains deferred (see [0005](0005-defer-dynamic-plugins-mef.md)), but the future plugin-host must connect through the same SDK contracts as the “internal” features. |
| 68 | |
| 69 | ### Cockpit Attention Model (PFD/MFD/Forward/EICAS/HUD) and SDK |
| 70 | The semantics of zones is described in [0021](0021-pfd-mfd-cockpit-attention-model.md). **Explicit binding of UI‑capabilities to attention zones at the `CascadeIDE.Contracts` level** is a separate solution and phased implementation: [0025](0025-sdk-attention-zones-and-capabilities.md). So [0024](0024-ide-sdk-and-stable-contracts.md) remains about the general SDK, and the cockpit axis is not mixed with the registry/plugin discussion. |
| 71 | |
| 72 | --- |
| 73 | |
| 74 | ## Capability registry / capability-map (API and principles) |
| 75 | |
| 76 | Goal: The capability layer should be strong enough to hold boundaries and a mental model, but simple enough to actually be used. |
| 77 | |
| 78 | ### What we consider capability |
| 79 | |
| 80 | - **Service capability**: “I provide a service” (contract/interface + implementation). |
| 81 | - **Command capability**: “I provide a command” (discoverability, category, hotkeys, availability). |
| 82 | - **UI surface capability**: “I provide a UI slot/surface” (panel/page/tab), while the inclusion/layout remains overlay (TOML). |
| 83 | |
| 84 | ### Code-first module registration (no MEF/reflection) |
| 85 | |
| 86 | - The register of modules is formed **explicitly by code** (manual list), without scanning assemblies. |
| 87 | - Each feature has one registration entry point, for example `ICascadeFeatureModule` → `Register(ICapabilityRegistry registry)`. |
| 88 | |
| 89 | ### Keys and dependencies |
| 90 | |
| 91 | - Capabilities are identified by **string ids** (constants in the SDK/contracts), for example: `git.core`, `docs.markdown.exportExpanded`. |
| 92 | - The capability-descriptor supports **explicit dependencies** (`Requires`) for explainability (“why the capability is not active/visible”). |
| 93 | |
| 94 | ### Capability-map (introspection) |
| 95 | |
| 96 | The recording layer is required to collect a “capability map” as an immutable description: |
| 97 | |
| 98 | - list of service/command/ui capabilities with metadata (id, owner module, stability, tags, requires); |
| 99 | - the “available/enabled” state can be calculated by the shell taking into account overlay (for example UiMode TOML) and runtime conditions. |
| 100 | |
| 101 | Capability-map is used for: |
| 102 | |
| 103 | - diagnostics and explainability (“why the button/panel is missing”), |
| 104 | - telemetry and UI/agent snapshots (introspection), |
| 105 | - future plugin-host (the external module must be registered in the same way). |
| 106 | |
| 107 | ### TOML overlay (presentation) |
| 108 | |
| 109 | UI modes/layouts (TOML) must refer to capabilities **by id**, controlling presentation (visibility/placement), but not replacing the semantics of capabilities. |
| 110 | |
| 111 | --- |
| 112 | |
| 113 | ## Minimum API (draft contracts) |
| 114 | |
| 115 | Below is the minimum form of contracts for `CascadeIDE.Contracts` (without being tied to a DI container/framework): |
| 116 | |
| 117 | - `ICascadeFeatureModule` |
| 118 | - `string Id { get; }` |
| 119 | - `void Register(ICapabilityRegistry registry)` |
| 120 | |
| 121 | - `ICapabilityRegistry` |
| 122 | - `void RegisterService(ServiceCapabilityDescriptor descriptor)` |
| 123 | - `void RegisterCommand(CommandCapabilityDescriptor descriptor)` |
| 124 | - `void RegisterUiSurface(UiSurfaceCapabilityDescriptor descriptor)` *(optional for MVP)* |
| 125 | - `CapabilityMap BuildMap()` *(for introspection/diagnostics)* |
| 126 | |
| 127 | - `CapabilityMap` |
| 128 | - `IReadOnlyList<ServiceCapabilityDescriptor> Services` |
| 129 | - `IReadOnlyList<CommandCapabilityDescriptor> Commands` |
| 130 | - `IReadOnlyList<UiSurfaceCapabilityDescriptor> UiSurfaces` |
| 131 | |
| 132 | - General descriptor fields: |
| 133 | - `string Id` (strict identifier) |
| 134 | - `string OwnerModuleId` |
| 135 | - `ApiStability Stability` + `[ApiStability]` |
| 136 | - `string[] Tags` |
| 137 | - `string[] Requires` |
| 138 | |
| 139 | Explanation: `CapabilityMap` describes “what is registered”, and “whether it is enabled” and “how it is allocated” is calculated by the shell taking into account TOML overlay and runtime conditions. |
| 140 | |
| 141 | --- |
| 142 | |
| 143 | ## What is NOT the goal (v1) |
| 144 | |
| 145 | - Do not introduce the mandatory “plugins from DLL folder” mechanism now. |
| 146 | - Do not promise binary compatibility for third-party extensions “forever”. |
| 147 | - Do not turn the SDK into a “god API” without boundaries (on the contrary, the goal is to reduce the surface). |
| 148 | |
| 149 | --- |
| 150 | |
| 151 | ## Practical principles for SDK design |
| 152 | |
| 153 | - **Minimal surface**: contracts should describe “what is needed”, not “how it is done.” |
| 154 | - **Dependencies are directed outwards**: features depend on contracts, not on specific features. |
| 155 | - **DTO is separate from UI**: portable data models do not depend on Avalonia controls. |
| 156 | - **Testability**: contracts are easy to mock; execution is transferred to services. |
| 157 | - **Security by default**: everything that runs code/processes/network requests is through explicit gateways and settings. |
| 158 | |
| 159 | --- |
| 160 | |
| 161 | ## How to reflect `Stable`/`Experimental` in code and documentation |
| 162 | |
| 163 | Minimum contract (no bureaucracy, useful already in active-dev): |
| 164 | - **Namespace signal**: there are two “roots” in `CascadeIDE.Contracts`: |
| 165 | - `CascadeIDE.Contracts.Experimental.*` - everything is new by default. |
| 166 | - `CascadeIDE.Contracts.Stable.*` is what the team consciously relies on. |
| 167 | - **Marker attribute**: single `ApiStabilityAttribute` + enum (`Experimental`, `Stable`) for quick searches/analysis and future expansion (for example `Deprecated`). |
| 168 | - **Why both**: namespace gives “default visibility” (and simplifies dependency rules), the attribute gives a point label and a basis for future analyzers/reports. |
| 169 | - **Documentation**: in the ADR and/or in the README of the contract project, we fix the rule “by default Experimental”, the criteria for transferring to Stable and the expectation of migration during breaking-change. |
| 170 | |
| 171 | --- |
| 172 | |
| 173 | ## Consequences |
| 174 | |
| 175 | - New features are added through clear slots/contracts, fewer “hidden” connections. |
| 176 | - Development is accelerated: it’s easier to understand where to add logic, easier to test, fewer regressions from random dependencies. |
| 177 | - A smooth base appears for future plugins: when the plugin-host ceases to be deferred, the “SDK form” will already exist. |
| 178 | |
| 179 | --- |
| 180 | |
| 181 | ## Rejected alternatives |
| 182 | |
| 183 | - “SDK = immediately plugin-host (MEF/DLL loading)” - rejected as premature complication ([0005](0005-defer-dynamic-plugins-mef.md)). |
| 184 | - “No SDK, everything through direct links” - rejected: worsens the mental model, accelerates the growth of connectivity and size of the shell composer. |
| 185 | |
| 186 | --- |
| 187 | |
| 188 | ## Open questions |
| 189 | |
| 190 | ## Visualization and documentation capability-map (accepted direction) |
| 191 | |
| 192 | Minimal path (v1), without separate UI: |
| 193 | |
| 194 | - **The principle of “thin snapshots”** (applicable to both capability-maps and UI snapshots/other dumps): |
| 195 | - by default we return **summary + hash** (and, if necessary, a short list of ids/counters); |
| 196 | - “thick” data is obtained **by request** (filters/pagination) or through a **dump file**. |
| 197 | - **JSON dump file** capability-map is available in diagnostics/logs (for explainability and debugging of integrations), returning `path + hash`. |
| 198 | - Capability-map can be included in **MCP/UI snapshot** only in **summary** form; details - with a separate command or through a dump file (so as not to inflate the context). |
| 199 | |
| 200 | Next step (when needed): |
| 201 | |
| 202 | - **Capabilities** page/tab in Debug/Power (or a separate document in `docs/`) with grouping by `OwnerModuleId` and filter by `Stable/Experimental`. |