docs consolidation
This commit is contained in:
@@ -1,428 +1,124 @@
|
||||
# 10 · Plug‑in SDK Guide — **Stella Ops**
|
||||
*(v 1.5 — 11 Jul 2025 · template install, no reload, IoC)*
|
||||
|
||||
---
|
||||
|
||||
## 0 Audience & Scope
|
||||
Guidance for developers who extend Stella Ops with schedule jobs, scanner adapters, TLS providers, notification channels, etc. Everything here is OSS; commercial variants simply ship additional signed plug‑ins.
|
||||
|
||||
---
|
||||
|
||||
## 1 Prerequisites
|
||||
|
||||
| Tool | Min Version |
|
||||
| ----------------------- | ----------------------------------------------------------------- |
|
||||
| .NET SDK | {{ dotnet }} |
|
||||
| **StellaOps templates** | install once via `bash dotnet new install StellaOps.Templates::*` |
|
||||
| **Cosign** | 2.3 + — used to sign DLLs |
|
||||
| xUnit | 2.6 |
|
||||
| Docker CLI | only if your plug‑in shells out to containers |
|
||||
|
||||
---
|
||||
|
||||
## 2 Repository & Build Output
|
||||
|
||||
Every plug‑in is hosted in **`git.stella‑ops.org`**.
|
||||
At publish time it must copy its signed artefacts to:
|
||||
|
||||
~~~text
|
||||
src/backend/Stella.Ops.Plugin.Binaries/<MyPlugin>/
|
||||
├── MyPlugin.dll
|
||||
└── MyPlugin.dll.sig
|
||||
~~~
|
||||
|
||||
The back‑end scans this folder on start‑up, verifies the **Cosign** signature, confirms the `[StellaPluginVersion]` gate, then loads the DLL inside an **isolated AssemblyLoadContext** to avoid dependency clashes
|
||||
|
||||
---
|
||||
|
||||
## 3 Project Scaffold
|
||||
|
||||
Generate with the installed template:
|
||||
|
||||
~~~bash
|
||||
dotnet new stellaops-plugin-schedule \
|
||||
-n MyPlugin.Schedule \
|
||||
--output src
|
||||
~~~
|
||||
|
||||
Result:
|
||||
|
||||
~~~text
|
||||
src/
|
||||
├─ MyPlugin.Schedule/
|
||||
│ ├─ MyJob.cs
|
||||
│ └─ MyPlugin.Schedule.csproj
|
||||
└─ tests/
|
||||
└─ MyPlugin.Schedule.Tests/
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
## 4 MSBuild Wiring
|
||||
|
||||
Add this to **`MyPlugin.Schedule.csproj`** so the signed DLL + `.sig` land in the canonical plug‑in folder:
|
||||
|
||||
~~~xml
|
||||
<PropertyGroup>
|
||||
<StellaPluginOut>$(SolutionDir)src/backend/Stella.Ops.Plugin.Binaries/$(MSBuildProjectName)</StellaPluginOut>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<ProjectReference Include="..\..\StellaOps.Common\StellaOps.Common.csproj"
|
||||
PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CopyStellaPlugin" AfterTargets="Publish">
|
||||
<MakeDir Directories="$(StellaPluginOut)" />
|
||||
<Copy SourceFiles="$(PublishDir)$(AssemblyName).dll;$(PublishDir)$(AssemblyName).dll.sig"
|
||||
DestinationFolder="$(StellaPluginOut)" />
|
||||
</Target>
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
## 5 Dependency‑Injection Entry‑point
|
||||
|
||||
Back‑end auto‑discovers restart‑time bindings through two mechanisms:
|
||||
|
||||
1. **Service binding metadata** for simple contracts.
|
||||
2. **`IDependencyInjectionRoutine`** implementations when you need full control.
|
||||
|
||||
### 5.1 Service binding metadata
|
||||
|
||||
Annotate implementations with `[ServiceBinding]` to declare their lifetime and service contract.
|
||||
The loader honours scoped lifetimes and will register the service before executing any custom DI routines.
|
||||
|
||||
~~~csharp
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.DependencyInjection;
|
||||
|
||||
[ServiceBinding(typeof(IJob), ServiceLifetime.Scoped, RegisterAsSelf = true)]
|
||||
public sealed class MyJob : IJob
|
||||
{
|
||||
// IJob dependencies can now use scoped services (PostgreSQL connections, etc.)
|
||||
}
|
||||
~~~
|
||||
|
||||
Use `RegisterAsSelf = true` when you also want to resolve the concrete type.
|
||||
Set `ReplaceExisting = true` to override default descriptors if the host already provides one.
|
||||
|
||||
### 5.2 Dependency injection routines
|
||||
|
||||
For advanced scenarios continue to expose a routine:
|
||||
|
||||
~~~csharp
|
||||
namespace StellaOps.DependencyInjection;
|
||||
|
||||
public sealed class IoCConfigurator : IDependencyInjectionRoutine
|
||||
{
|
||||
public IServiceCollection Register(IServiceCollection services, IConfiguration cfg)
|
||||
{
|
||||
services.AddSingleton<IJob, MyJob>(); // schedule job
|
||||
services.Configure<MyPluginOptions>(cfg.GetSection("Plugins:MyPlugin"));
|
||||
return services;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
---
|
||||
|
||||
## 6 Schedule Plug‑ins
|
||||
|
||||
### 6.1 Minimal Job
|
||||
|
||||
~~~csharp
|
||||
using StellaOps.Scheduling; // contract
|
||||
|
||||
[StellaPluginVersion("2.0.0")]
|
||||
public sealed class MyJob : IJob
|
||||
{
|
||||
public async Task ExecuteAsync(CancellationToken ct)
|
||||
{
|
||||
Console.WriteLine("Hello from plug‑in!");
|
||||
await Task.Delay(500, ct);
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
### 6.2 Cron Registration
|
||||
|
||||
```csharp
|
||||
services.AddCronJob<MyJob>("0 15 * * *"); // everyday
|
||||
```
|
||||
|
||||
15:00
|
||||
Cron syntax follows Hangfire rules
|
||||
|
||||
## 7 Scanner Adapters
|
||||
|
||||
Implement IScannerRunner.
|
||||
Register inside Configure:
|
||||
```csharp
|
||||
services.AddScanner<MyAltScanner>("alt"); // backend
|
||||
```
|
||||
|
||||
selects by --engine alt
|
||||
If the engine needs a side‑car container, include a Dockerfile in your repo and document resource expectations.
|
||||
## 8 Packaging & Signing
|
||||
|
||||
```bash
|
||||
dotnet publish -c Release -p:PublishSingleFile=true -o out
|
||||
cosign sign --key $COSIGN_KEY out/MyPlugin.Schedule.dll # sign binary only
|
||||
sha256sum out/MyPlugin.Schedule.dll > out/.sha256 # optional checksum
|
||||
zip MyPlugin.zip out/* README.md
|
||||
```
|
||||
|
||||
Unsigned DLLs are refused when StellaOps:Security:DisableUnsigned=false.
|
||||
|
||||
## 9 Deployment
|
||||
|
||||
```bash
|
||||
docker cp MyPlugin.zip <backend>:/opt/plugins/ && docker restart <backend>
|
||||
```
|
||||
|
||||
Check /health – "plugins":["MyPlugin.Schedule@2.0.0"].
|
||||
(Hot‑reload was removed to keep the core process simple and memory‑safe.)
|
||||
|
||||
## 10 Configuration Patterns
|
||||
|
||||
| Need | Pattern |
|
||||
| ------------ | --------------------------------------------------------- |
|
||||
| Settings | Plugins:MyPlugin:* in appsettings.json. |
|
||||
| Secrets | Redis secure:<plugin>:<key> (encrypted per TLS provider). |
|
||||
| Dynamic cron | Implement ICronConfigurable; UI exposes editor. |
|
||||
|
||||
## 11 Testing & CI
|
||||
|
||||
| Layer | Tool | Gate |
|
||||
| ----------- | -------------------------- | ------------------- |
|
||||
| Unit | xUnit + Moq | ≥ 50 % lines |
|
||||
| Integration | Testcontainers ‑ run in CI | Job completes < 5 s |
|
||||
| Style | dotnet | format 0 warnings |
|
||||
|
||||
Use the pre‑baked workflow in StellaOps.Templates as starting point.
|
||||
|
||||
## 12 Publishing to the Community Marketplace
|
||||
|
||||
Tag Git release plugin‑vX.Y.Z and attach the signed ZIP.
|
||||
Submit a PR to stellaops/community-plugins.json with metadata & git URL.
|
||||
On merge, the plug‑in shows up in the UI Marketplace.
|
||||
|
||||
## 13 Common Pitfalls
|
||||
|
||||
| Symptom | Root cause | Fix |
|
||||
| ------------------- | -------------------------- | ------------------------------------------- |
|
||||
| NotDetected | .sig missing | cosign sign … |
|
||||
| VersionGateMismatch | Backend 2.1 vs plug‑in 2.0 | Re‑compile / bump attribute |
|
||||
| FileLoadException | Duplicate | StellaOps.Common Ensure PrivateAssets="all" |
|
||||
| Redis | timeouts Large writes | Batch or use PostgreSQL |
|
||||
|
||||
---
|
||||
|
||||
## 14 Plugin Version Compatibility (v2.0)
|
||||
|
||||
**IMPORTANT:** All plugins **must** declare a `[StellaPluginVersion]` attribute. Plugins without this attribute will be rejected by the host loader.
|
||||
|
||||
Declare your plugin's version and host compatibility requirements:
|
||||
|
||||
```csharp
|
||||
using StellaOps.Plugin.Versioning;
|
||||
|
||||
// In AssemblyInfo.cs or any file at assembly level
|
||||
[assembly: StellaPluginVersion("1.2.0", MinimumHostVersion = "1.0.0", MaximumHostVersion = "2.0.0")]
|
||||
```
|
||||
|
||||
| Property | Purpose | Required |
|
||||
|----------|---------|----------|
|
||||
| `pluginVersion` (constructor) | Your plugin's semantic version | **Yes** |
|
||||
| `MinimumHostVersion` | Lowest host version that can load this plugin | Recommended |
|
||||
| `MaximumHostVersion` | Highest host version supported | Recommended for cross-major compatibility |
|
||||
| `RequiresSignature` | Whether signature verification is mandatory (default: true) | No |
|
||||
|
||||
### Version Compatibility Rules
|
||||
|
||||
1. **Attribute Required:** Plugins without `[StellaPluginVersion]` are rejected
|
||||
2. **Minimum Version:** Host version must be ≥ `MinimumHostVersion`
|
||||
3. **Maximum Version:** Host version must be ≤ `MaximumHostVersion` (if specified)
|
||||
4. **Strict Major Version:** If `MaximumHostVersion` is not specified, the plugin is assumed to only support the same major version as `MinimumHostVersion`
|
||||
|
||||
### Examples
|
||||
|
||||
```csharp
|
||||
// Plugin works with host 1.0.0 through 2.x (explicit range)
|
||||
[assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "1.0.0", MaximumHostVersion = "2.99.99")]
|
||||
|
||||
// Plugin works with host 2.x only (strict - no MaximumHostVersion means same major version)
|
||||
[assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "2.0.0")]
|
||||
|
||||
// Plugin version 3.0.0 with no host constraints (uses plugin major version as reference)
|
||||
[assembly: StellaPluginVersion("3.0.0")]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15 Plugin Host Configuration (v2.0)
|
||||
|
||||
Configure the plugin loader with security-first defaults in `PluginHostOptions`:
|
||||
|
||||
```csharp
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
// Version enforcement (all default to true for security)
|
||||
HostVersion = new Version(2, 0, 0),
|
||||
EnforceVersionCompatibility = true, // Reject incompatible plugins
|
||||
RequireVersionAttribute = true, // Reject plugins without [StellaPluginVersion]
|
||||
StrictMajorVersionCheck = true, // Reject plugins crossing major version boundaries
|
||||
|
||||
// Signature verification (opt-in, requires infrastructure)
|
||||
EnforceSignatureVerification = true,
|
||||
SignatureVerifier = new CosignPluginVerifier(new CosignVerifierOptions
|
||||
{
|
||||
PublicKeyPath = "/keys/cosign.pub",
|
||||
UseRekorTransparencyLog = true,
|
||||
AllowUnsigned = false
|
||||
})
|
||||
};
|
||||
|
||||
var result = await PluginHost.LoadPluginsAsync(options, logger);
|
||||
|
||||
// Check for failures
|
||||
if (result.HasFailures)
|
||||
{
|
||||
foreach (var failure in result.Failures)
|
||||
{
|
||||
logger.LogError("Plugin {Path} failed: {Reason} - {Message}",
|
||||
failure.AssemblyPath, failure.Reason, failure.Message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Host Options Reference
|
||||
|
||||
| Option | Default | Purpose |
|
||||
|--------|---------|---------|
|
||||
| `HostVersion` | null | The host application version for compatibility checking |
|
||||
| `EnforceVersionCompatibility` | **true** | Reject plugins that fail version checks |
|
||||
| `RequireVersionAttribute` | **true** | Reject plugins without `[StellaPluginVersion]` |
|
||||
| `StrictMajorVersionCheck` | **true** | Reject plugins that don't explicitly support the host's major version |
|
||||
| `EnforceSignatureVerification` | false | Reject plugins without valid signatures |
|
||||
| `SignatureVerifier` | null | The verifier implementation (e.g., `CosignPluginVerifier`) |
|
||||
|
||||
### Failure Reasons
|
||||
|
||||
| Reason | Description |
|
||||
|--------|-------------|
|
||||
| `LoadError` | Assembly could not be loaded (missing dependencies, corrupt file) |
|
||||
| `SignatureInvalid` | Signature verification failed |
|
||||
| `IncompatibleVersion` | Plugin version constraints not satisfied |
|
||||
| `MissingVersionAttribute` | Plugin lacks required `[StellaPluginVersion]` attribute |
|
||||
|
||||
---
|
||||
|
||||
## 16 Fail-Fast Options Validation (v2.0)
|
||||
|
||||
Use the fail-fast validation pattern to catch configuration errors at startup:
|
||||
|
||||
```csharp
|
||||
using StellaOps.DependencyInjection.Validation;
|
||||
|
||||
// Register options with automatic startup validation
|
||||
services.AddOptionsWithValidation<MyPluginOptions, MyPluginOptionsValidator>(
|
||||
MyPluginOptions.SectionName);
|
||||
|
||||
// Or with data annotations
|
||||
services.AddOptionsWithDataAnnotations<MyPluginOptions>(
|
||||
MyPluginOptions.SectionName);
|
||||
```
|
||||
|
||||
Create validators using the base class:
|
||||
|
||||
```csharp
|
||||
public sealed class MyPluginOptionsValidator : OptionsValidatorBase<MyPluginOptions>
|
||||
{
|
||||
protected override string SectionPrefix => "Plugins:MyPlugin";
|
||||
|
||||
protected override void ValidateOptions(MyPluginOptions options, ValidationContext context)
|
||||
{
|
||||
context
|
||||
.RequireNotEmpty(options.BaseUrl, nameof(options.BaseUrl))
|
||||
.RequirePositive(options.TimeoutSeconds, nameof(options.TimeoutSeconds))
|
||||
.RequireInRange(options.MaxRetries, nameof(options.MaxRetries), 0, 10);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17 Available Templates (v2.0)
|
||||
|
||||
Install and use the official plugin templates:
|
||||
|
||||
```bash
|
||||
# Install from local templates directory
|
||||
dotnet new install ./templates
|
||||
|
||||
# Or install from NuGet
|
||||
dotnet new install StellaOps.Templates
|
||||
|
||||
# Create a connector plugin
|
||||
dotnet new stellaops-plugin-connector -n MyCompany.AcmeConnector
|
||||
|
||||
# Create a scheduled job plugin
|
||||
dotnet new stellaops-plugin-scheduler -n MyCompany.CleanupJob
|
||||
```
|
||||
|
||||
Templates include:
|
||||
- Plugin entry point with version attribute
|
||||
- Options class with data annotations
|
||||
- Options validator with fail-fast pattern
|
||||
- DI routine registration
|
||||
- README with build/sign instructions
|
||||
|
||||
---
|
||||
|
||||
## 18 Migration Guide: v2.0 to v2.1
|
||||
|
||||
### Breaking Change: Version Attribute Required
|
||||
|
||||
As of v2.1, all plugins **must** include a `[StellaPluginVersion]` attribute. Plugins without this attribute will be rejected with `MissingVersionAttribute` failure.
|
||||
|
||||
**Before (v2.0):** Optional, plugins without attribute loaded with warning.
|
||||
**After (v2.1):** Required, plugins without attribute are rejected.
|
||||
|
||||
### Migration Steps
|
||||
|
||||
1. Add the version attribute to your plugin's AssemblyInfo.cs:
|
||||
```csharp
|
||||
[assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "2.0.0", MaximumHostVersion = "2.99.99")]
|
||||
```
|
||||
|
||||
2. If your plugin must support multiple major host versions, explicitly set `MaximumHostVersion`:
|
||||
```csharp
|
||||
// Supports host 1.x through 3.x
|
||||
[assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "1.0.0", MaximumHostVersion = "3.99.99")]
|
||||
```
|
||||
|
||||
3. Rebuild and re-sign your plugin.
|
||||
|
||||
### Opt-out (Not Recommended)
|
||||
|
||||
If you must load legacy plugins without version attributes:
|
||||
```csharp
|
||||
var options = new PluginHostOptions
|
||||
{
|
||||
RequireVersionAttribute = false, // Allow unversioned plugins (NOT recommended)
|
||||
StrictMajorVersionCheck = false // Allow cross-major version loading
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Change Log
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| v2.1 | 2025-12-14 | **Breaking:** `[StellaPluginVersion]` attribute now required by default. Added `RequireVersionAttribute`, `StrictMajorVersionCheck` options. Added `MissingVersionAttribute` failure reason. |
|
||||
| v2.0 | 2025-12-14 | Added StellaPluginVersion attribute, Cosign verification options, fail-fast validation, new templates |
|
||||
| v1.5 | 2025-07-11 | Template install, no hot-reload, IoC conventions |
|
||||
# Plugin SDK Guide
|
||||
|
||||
This guide explains how StellaOps loads, validates, and wires restart-time plugins. It is intentionally cross-cutting: module-specific plugin contracts (Authority identity providers, Concelier connectors, Scanner analyzers, CLI command modules, etc.) live in the corresponding module dossiers under `docs/modules/`.
|
||||
|
||||
## 1) What a "plugin" means in StellaOps
|
||||
|
||||
StellaOps uses plugins to extend behavior without losing:
|
||||
- Determinism (stable ordering, stable identifiers, replayable outputs).
|
||||
- Offline posture (no hidden outbound calls; explicit trust roots and caches).
|
||||
- Security boundaries (no client-controlled identity injection; signed artifacts where enforced).
|
||||
|
||||
Most services load plugins at process start (restart-time). Hot-reload is not a goal: restart-time loading keeps memory and dependency isolation predictable.
|
||||
|
||||
## 2) Service plugin loading model (restart-time)
|
||||
|
||||
Service plugins are loaded by the `StellaOps.Plugin` library:
|
||||
- Discovery occurs in a configured plugin directory.
|
||||
- Assemblies are loaded in an isolated `AssemblyLoadContext`.
|
||||
- A compatibility gate runs before DI registration:
|
||||
- version attribute presence and host compatibility
|
||||
- optional signature verification
|
||||
|
||||
### 2.1 Plugin directory and discovery patterns
|
||||
|
||||
Default behavior (from `StellaOps.Plugin.Hosting.PluginHostOptions`):
|
||||
- Base directory: `AppContext.BaseDirectory` (unless overridden).
|
||||
- Plugin directory:
|
||||
- `<PrimaryPrefix>.PluginBinaries` when `PrimaryPrefix` is set, otherwise `PluginBinaries`.
|
||||
- Discovery glob(s):
|
||||
- `<prefix>.Plugin.*.dll` for each configured prefix.
|
||||
|
||||
Hosts may override the directory and/or add explicit `searchPatterns` in their config. Use module operations docs to see the authoritative configuration for a given service.
|
||||
|
||||
### 2.2 Deterministic ordering
|
||||
|
||||
The loader is deterministic:
|
||||
- When no explicit order is configured, discovered plugin assemblies are sorted by filename (case-insensitive).
|
||||
- When an explicit order is configured, that order is applied first and the remainder stays sorted.
|
||||
|
||||
## 3) Version compatibility requirements
|
||||
|
||||
Plugins should declare the assembly-level attribute:
|
||||
```csharp
|
||||
using StellaOps.Plugin.Versioning;
|
||||
|
||||
[assembly: StellaPluginVersion("1.2.3", MinimumHostVersion = "1.0.0")]
|
||||
```
|
||||
|
||||
The host can enforce:
|
||||
- Required attribute presence (`RequireVersionAttribute`).
|
||||
- Compatibility bounds (`MinimumHostVersion` / `MaximumHostVersion`).
|
||||
- Strict major compatibility when `MaximumHostVersion` is not set (`StrictMajorVersionCheck`).
|
||||
|
||||
## 4) Dependency injection wiring
|
||||
|
||||
StellaOps supports two DI registration mechanisms.
|
||||
|
||||
### 4.1 Simple bindings via `ServiceBindingAttribute`
|
||||
|
||||
Annotate implementations with `ServiceBindingAttribute`:
|
||||
```csharp
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.DependencyInjection;
|
||||
|
||||
[ServiceBinding(typeof(IMyContract), ServiceLifetime.Singleton, RegisterAsSelf = true)]
|
||||
public sealed class MyPluginService : IMyContract
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Advanced wiring via `IDependencyInjectionRoutine`
|
||||
|
||||
For full control, include a concrete `IDependencyInjectionRoutine` implementation. The host discovers and runs all routines in loaded plugin assemblies:
|
||||
```csharp
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.DependencyInjection;
|
||||
|
||||
public sealed class MyPluginDi : IDependencyInjectionRoutine
|
||||
{
|
||||
public IServiceCollection Register(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSingleton<IMyContract, MyPluginService>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5) Signing and verification (Cosign)
|
||||
|
||||
When enabled, the host can verify plugin assemblies using Cosign (`cosign verify-blob`). The signature file is expected adjacent to the assembly:
|
||||
- `MyPlugin.dll`
|
||||
- `MyPlugin.dll.sig`
|
||||
|
||||
Verification is performed by `StellaOps.Plugin.Security.CosignPluginVerifier` and controlled by host configuration (for example `EnforceSignatureVerification` plus verifier options).
|
||||
|
||||
Offline note: verification can be performed without transparency log access when the host is configured accordingly (for example by ignoring tlog or using an offline receipt flow).
|
||||
|
||||
## 6) Repo layout (this monorepo)
|
||||
|
||||
In this repository, plugin binaries are typically staged under module-specific `*.PluginBinaries` directories (examples):
|
||||
- `src/StellaOps.Authority.PluginBinaries/`
|
||||
- `src/Concelier/StellaOps.Concelier.PluginBinaries/`
|
||||
|
||||
The authoritative loader configuration is owned by the host module and documented in its operations/architecture docs.
|
||||
|
||||
## 7) Testing expectations
|
||||
|
||||
Plugins should ship tests that protect determinism and compatibility:
|
||||
- Stable ordering of outputs and collections.
|
||||
- Stable timestamps (UTC ISO-8601).
|
||||
- Fixture-backed inputs for offline operation.
|
||||
- Compatibility checks for host version boundaries.
|
||||
|
||||
Reference tests for the generic plugin host live under:
|
||||
- `src/__Libraries/__Tests/StellaOps.Plugin.Tests/`
|
||||
|
||||
## 8) Where to go next
|
||||
|
||||
- Authority plugins and operations: `docs/modules/authority/`
|
||||
- Concelier connectors and operations: `docs/modules/concelier/`
|
||||
- Scanner analyzers and operations: `docs/modules/scanner/`
|
||||
- CLI command modules: `docs/modules/cli/`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user