# Excititor Connector Packaging Guide

> **Audience:** teams implementing new Excititor provider plug‑ins (CSAF feeds,
> OpenVEX attestations, etc.)  
> **Prerequisites:** read `docs/ARCHITECTURE_EXCITITOR.md` and the module
> `AGENTS.md` in `src/StellaOps.Excititor.Connectors.Abstractions/`.

The Excititor connector SDK gives you:

- `VexConnectorBase` – deterministic logging, SHA‑256 helpers, time provider.
- `VexConnectorOptionsBinder` – strongly typed YAML/JSON configuration binding.
- `IVexConnectorOptionsValidator<T>` – custom validation hooks (offline defaults, auth invariants).
- `VexConnectorDescriptor` & metadata helpers for consistent telemetry.

This guide explains how to package a connector so the Excititor Worker/WebService
can load it via the plugin host.

---

## 1. Project layout

Start from the template under
`docs/dev/templates/excititor-connector/`. It contains:

```
Excititor.MyConnector/
├── src/
│   ├── Excititor.MyConnector.csproj
│   ├── MyConnectorOptions.cs
│   ├── MyConnector.cs
│   └── MyConnectorPlugin.cs
└── manifest/
    └── connector.manifest.yaml
```

Key points:

- Target `net10.0`, enable `TreatWarningsAsErrors`, reference the
  `StellaOps.Excititor.Connectors.Abstractions` project (or NuGet once published).
- Keep project ID prefix `StellaOps.Excititor.Connectors.<Provider>` so the
  plugin loader can discover it with the default search pattern.

### 1.1 csproj snippet

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\..\src\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
  </ItemGroup>
</Project>
```

Adjust the `ProjectReference` for your checkout (or switch to a NuGet package
once published).

---

## 2. Implement the connector

1. **Options model** – create an options POCO with data-annotation attributes.
   Bind it via `VexConnectorOptionsBinder.Bind<TOptions>` in your connector
   constructor or `ValidateAsync`.
2. **Validator** – implement `IVexConnectorOptionsValidator<TOptions>` to add
   complex checks (e.g., ensure both `clientId` and `clientSecret` are present).
3. **Connector** – inherit from `VexConnectorBase`. Implement:
   - `ValidateAsync` – run binder/validators, log configuration summary.
   - `FetchAsync` – stream raw documents to `context.RawSink`.
   - `NormalizeAsync` – convert raw documents into `VexClaimBatch` via
     format-specific normalizers (`context.Normalizers`).
4. **Plugin adapter** – expose the connector via a plugin entry point so the
   host can instantiate it.

### 2.1 Options binding example

```csharp
public sealed class MyConnectorOptions
{
    [Required]
    [Url]
    public string CatalogUri { get; set; } = default!;

    [Required]
    public string ApiKey { get; set; } = default!;

    [Range(1, 64)]
    public int MaxParallelRequests { get; set; } = 4;
}

public sealed class MyConnectorOptionsValidator : IVexConnectorOptionsValidator<MyConnectorOptions>
{
    public void Validate(VexConnectorDescriptor descriptor, MyConnectorOptions options, IList<string> errors)
    {
        if (!options.CatalogUri.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
        {
            errors.Add("CatalogUri must use HTTPS.");
        }
    }
}
```

Bind inside the connector:

```csharp
private readonly MyConnectorOptions _options;

public MyConnector(VexConnectorDescriptor descriptor, ILogger<MyConnector> logger, TimeProvider timeProvider)
    : base(descriptor, logger, timeProvider)
{
    // `settings` comes from the orchestrator; validators registered via DI.
    _options = VexConnectorOptionsBinder.Bind<MyConnectorOptions>(
        descriptor,
        VexConnectorSettings.Empty,
        validators: new[] { new MyConnectorOptionsValidator() });
}
```

Replace `VexConnectorSettings.Empty` with the actual settings from context
inside `ValidateAsync`.

---

## 3. Plugin adapter & manifest

Create a simple plugin class that implements
`StellaOps.Plugin.IConnectorPlugin`. The Worker/WebService plugin host uses
this contract today.

```csharp
public sealed class MyConnectorPlugin : IConnectorPlugin
{
    private static readonly VexConnectorDescriptor Descriptor =
        new("excititor:my-provider", VexProviderKind.Vendor, "My Provider VEX");

    public string Name => Descriptor.DisplayName;

    public bool IsAvailable(IServiceProvider services) => true; // inject feature flags if needed

    public IFeedConnector Create(IServiceProvider services)
    {
        var logger = services.GetRequiredService<ILogger<MyConnector>>();
        var timeProvider = services.GetRequiredService<TimeProvider>();
        return new MyConnector(Descriptor, logger, timeProvider);
    }
}
```

> **Note:** the Excititor Worker currently instantiates connectors through the
> shared `IConnectorPlugin` contract. Once a dedicated Excititor plugin interface
> lands you simply swap the base interface; the descriptor/connector code
> remains unchanged.

Provide a manifest describing the assembly for operational tooling:

```yaml
# manifest/connector.manifest.yaml
id: excititor-my-provider
assembly: StellaOps.Excititor.Connectors.MyProvider.dll
entryPoint: StellaOps.Excititor.Connectors.MyProvider.MyConnectorPlugin
description: >
  Official VEX feed for ExampleCorp products (CSAF JSON, daily updates).
tags:
  - excititor
  - csaf
  - vendor
```

Store manifests under `/opt/stella/excititor/plugins/<connector>/manifest/` in
production so the deployment tooling can inventory and verify plug‑ins.

---

## 4. Packaging workflow

1. `dotnet publish -c Release` → copy the published DLLs to
   `/opt/stella/excititor/plugins/<Provider>/`.
2. Place `connector.manifest.yaml` next to the binaries.
3. Restart the Excititor Worker or WebService (hot reload not supported yet).
4. Verify logs: `VEX-ConnectorLoader` should list the connector descriptor.

### 4.1 Offline kits

- Add the connector folder (binaries + manifest) to the Offline Kit bundle.
- Include a `settings.sample.yaml` demonstrating offline-friendly defaults.
- Document any external dependencies (e.g., SHA mirrors) in the manifest `notes`
  field.

---

## 5. Testing checklist

- Unit tests around options binding & validators.
- Integration tests (future `StellaOps.Excititor.Connectors.Abstractions.Tests`)
  verifying deterministic logging scopes:
  `logger.BeginScope` should produce `vex.connector.id`, `vex.connector.kind`,
  and `vex.connector.operation`.
- Deterministic SHA tests: repeated `CreateRawDocument` calls with identical
  content must return the same digest.

---

## 6. Reference template

See `docs/dev/templates/excititor-connector/` for the full quick‑start including:

- Sample options class + validator.
- Connector implementation inheriting from `VexConnectorBase`.
- Plugin adapter + manifest.

Copy the directory, rename namespaces/IDs, then iterate on provider-specific
logic.

---

*Last updated: 2025-10-17*
