feat(advisory-ai): Add deployment guide, Dockerfile, and Helm chart for on-prem packaging
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Introduced a comprehensive deployment guide for AdvisoryAI, detailing local builds, remote inference toggles, and scaling guidance.
- Created a multi-role Dockerfile for building WebService and Worker images.
- Added a docker-compose file for local and offline deployment.
- Implemented a Helm chart for Kubernetes deployment with persistence and remote inference options.
- Established a new API endpoint `/advisories/summary` for deterministic summaries of observations and linksets.
- Introduced a JSON schema for risk profiles and a validator to ensure compliance with the schema.
- Added unit tests for the risk profile validator to ensure functionality and error handling.
This commit is contained in:
StellaOps Bot
2025-11-23 00:35:33 +02:00
parent 2e89a92d92
commit 8d78dd219b
33 changed files with 1254 additions and 259 deletions

View File

@@ -0,0 +1,19 @@
using System.Reflection;
using Json.Schema;
namespace StellaOps.Policy.RiskProfile.Schema;
public static class RiskProfileSchemaProvider
{
private const string SchemaResource = "StellaOps.Policy.RiskProfile.Schemas.risk-profile-schema@1.json";
public static JsonSchema GetSchema()
{
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(SchemaResource)
?? throw new InvalidOperationException($"Schema resource '{SchemaResource}' not found.");
using var reader = new StreamReader(stream);
var schemaText = reader.ReadToEnd();
return JsonSchema.FromText(schemaText);
}
}

View File

@@ -0,0 +1,126 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stellaops.dev/schemas/risk-profile-schema@1.json",
"title": "StellaOps RiskProfile v1",
"type": "object",
"required": [ "id", "version", "signals", "weights", "overrides" ],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"minLength": 1,
"description": "Stable identifier for the risk profile (slug or URN)."
},
"version": {
"type": "string",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[A-Za-z0-9.-]+)?$",
"description": "SemVer for the profile definition."
},
"description": {
"type": "string",
"description": "Human-readable summary of the profile intent."
},
"signals": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": [ "name", "source", "type" ],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Logical signal key (e.g., reachability, kev, exploit_chain)."
},
"source": {
"type": "string",
"minLength": 1,
"description": "Upstream provider or calculation origin."
},
"type": {
"type": "string",
"enum": [ "boolean", "numeric", "categorical" ]
},
"path": {
"type": "string",
"description": "JSON Pointer to the signal in the evidence document."
},
"transform": {
"type": "string",
"description": "Optional transform applied before weighting (e.g., log, normalize)."
},
"unit": {
"type": "string",
"description": "Optional unit for numeric signals."
}
}
}
},
"weights": {
"type": "object",
"minProperties": 1,
"additionalProperties": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"description": "Weight per signal name; weights are normalized by the consumer."
},
"overrides": {
"type": "object",
"required": [ "severity", "decisions" ],
"additionalProperties": false,
"properties": {
"severity": {
"type": "array",
"items": {
"type": "object",
"required": [ "when", "set" ],
"additionalProperties": false,
"properties": {
"when": {
"type": "object",
"description": "Predicate over signals (key/value equals).",
"minProperties": 1,
"additionalProperties": { "type": [ "string", "number", "boolean" ] }
},
"set": {
"type": "string",
"enum": [ "critical", "high", "medium", "low", "informational" ]
}
}
}
},
"decisions": {
"type": "array",
"items": {
"type": "object",
"required": [ "when", "action" ],
"additionalProperties": false,
"properties": {
"when": {
"type": "object",
"description": "Predicate over signals (key/value equals).",
"minProperties": 1,
"additionalProperties": { "type": [ "string", "number", "boolean" ] }
},
"action": {
"type": "string",
"enum": [ "allow", "review", "deny" ]
},
"reason": {
"type": "string"
}
}
}
}
}
},
"metadata": {
"type": "object",
"description": "Free-form metadata with stable keys.",
"additionalProperties": { "type": [ "string", "number", "boolean", "array", "object", "null" ] }
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JsonSchema.Net" Version="5.3.0" />
<PackageReference Include="System.Text.Json" Version="10.0.0-rc.2.25519.1" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Schemas\risk-profile-schema@1.json" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,30 @@
using System.Text.Json;
using Json.Schema;
using StellaOps.Policy.RiskProfile.Schema;
namespace StellaOps.Policy.RiskProfile.Validation;
public sealed class RiskProfileValidator
{
private readonly JsonSchema _schema;
public RiskProfileValidator() : this(RiskProfileSchemaProvider.GetSchema())
{
}
public RiskProfileValidator(JsonSchema schema)
{
_schema = schema ?? throw new ArgumentNullException(nameof(schema));
}
public ValidationResults Validate(string json)
{
if (string.IsNullOrWhiteSpace(json))
{
throw new ArgumentException("Risk profile payload is required.", nameof(json));
}
using var document = JsonDocument.Parse(json);
return _schema.Validate(document.RootElement);
}
}