up
Some checks failed
Build Test Deploy / authority-container (push) Has been cancelled
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled
Build Test Deploy / build-test (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Build Test Deploy / authority-container (push) Has been cancelled
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled
Build Test Deploy / build-test (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
This commit is contained in:
23
src/StellaOps.Vexer.Policy/AGENTS.md
Normal file
23
src/StellaOps.Vexer.Policy/AGENTS.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# AGENTS
|
||||
## Role
|
||||
Centralizes policy configuration, provider trust weights, and justification guardrails applied to Vexer consensus decisions.
|
||||
## Scope
|
||||
- Policy models for tier weighting, provider overrides, justification allowlists, and conflict escalation.
|
||||
- Configuration binding helpers (YAML/JSON) and validation of operator-supplied policy bundles.
|
||||
- Evaluation services that expose policy revisions and change tracking to WebService/Worker.
|
||||
- Documentation anchors for policy schema and upgrade guidance.
|
||||
## Participants
|
||||
- WebService consumes policy bindings to authorize ingest/export operations and to recompute consensus.
|
||||
- Worker schedules reconciliation runs using policy revisions from this module.
|
||||
- CLI exposes policy inspection commands based on exported descriptors.
|
||||
## Interfaces & contracts
|
||||
- `IVexPolicyProvider`, `IVexPolicyEvaluator`, and immutable policy snapshot value objects.
|
||||
- Validation diagnostics APIs surfacing structured errors and warnings for operators.
|
||||
## In/Out of scope
|
||||
In: policy schema definition, binding/validation, evaluation utilities, audit logging helpers.
|
||||
Out: persistence/migrations, HTTP exposure, connector-specific trust logic (lives in Core/Connectors).
|
||||
## Observability & security expectations
|
||||
- Emit structured events on policy load/update with revision IDs, but do not log full sensitive policy documents.
|
||||
- Maintain deterministic error ordering for reproducible diagnostics.
|
||||
## Tests
|
||||
- Policy fixtures and regression coverage will live in `../StellaOps.Vexer.Policy.Tests` once scaffolded; leverage snapshot comparisons for YAML bindings.
|
||||
227
src/StellaOps.Vexer.Policy/IVexPolicyProvider.cs
Normal file
227
src/StellaOps.Vexer.Policy/IVexPolicyProvider.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Vexer.Core;
|
||||
|
||||
namespace StellaOps.Vexer.Policy;
|
||||
|
||||
public interface IVexPolicyProvider
|
||||
{
|
||||
VexPolicySnapshot GetSnapshot();
|
||||
}
|
||||
|
||||
public interface IVexPolicyEvaluator
|
||||
{
|
||||
string Version { get; }
|
||||
|
||||
VexPolicySnapshot Snapshot { get; }
|
||||
|
||||
double GetProviderWeight(VexProvider provider);
|
||||
|
||||
bool IsClaimEligible(VexClaim claim, VexProvider provider, out string? rejectionReason);
|
||||
}
|
||||
|
||||
public sealed record VexPolicySnapshot(
|
||||
string Version,
|
||||
VexConsensusPolicyOptions ConsensusOptions,
|
||||
IVexConsensusPolicy ConsensusPolicy,
|
||||
ImmutableArray<VexPolicyIssue> Issues)
|
||||
{
|
||||
public static readonly VexPolicySnapshot Default = new(
|
||||
VexConsensusPolicyOptions.BaselineVersion,
|
||||
new VexConsensusPolicyOptions(),
|
||||
new BaselineVexConsensusPolicy(),
|
||||
ImmutableArray<VexPolicyIssue>.Empty);
|
||||
}
|
||||
|
||||
public sealed record VexPolicyIssue(
|
||||
string Code,
|
||||
string Message,
|
||||
VexPolicyIssueSeverity Severity);
|
||||
|
||||
public enum VexPolicyIssueSeverity
|
||||
{
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
public sealed class VexPolicyProvider : IVexPolicyProvider
|
||||
{
|
||||
private readonly IOptionsMonitor<VexPolicyOptions> _options;
|
||||
private readonly ILogger<VexPolicyProvider> _logger;
|
||||
|
||||
public VexPolicyProvider(
|
||||
IOptionsMonitor<VexPolicyOptions> options,
|
||||
ILogger<VexPolicyProvider> logger)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public VexPolicySnapshot GetSnapshot()
|
||||
{
|
||||
var options = _options.CurrentValue ?? new VexPolicyOptions();
|
||||
return BuildSnapshot(options);
|
||||
}
|
||||
|
||||
private VexPolicySnapshot BuildSnapshot(VexPolicyOptions options)
|
||||
{
|
||||
var issues = ImmutableArray.CreateBuilder<VexPolicyIssue>();
|
||||
|
||||
if (!TryNormalizeWeights(options.Weights, out var weightOptions, issues))
|
||||
{
|
||||
issues.Add(new VexPolicyIssue(
|
||||
"weights.invalid",
|
||||
"Weight configuration is invalid; falling back to defaults.",
|
||||
VexPolicyIssueSeverity.Warning));
|
||||
weightOptions = new VexConsensusPolicyOptions();
|
||||
}
|
||||
|
||||
var overrides = NormalizeOverrides(options.ProviderOverrides, issues);
|
||||
|
||||
var consensusOptions = new VexConsensusPolicyOptions(
|
||||
options.Version ?? VexConsensusPolicyOptions.BaselineVersion,
|
||||
weightOptions.VendorWeight,
|
||||
weightOptions.DistroWeight,
|
||||
weightOptions.PlatformWeight,
|
||||
weightOptions.HubWeight,
|
||||
weightOptions.AttestationWeight,
|
||||
overrides);
|
||||
|
||||
var policy = new BaselineVexConsensusPolicy(consensusOptions);
|
||||
var snapshot = new VexPolicySnapshot(
|
||||
consensusOptions.Version,
|
||||
consensusOptions,
|
||||
policy,
|
||||
issues.ToImmutable());
|
||||
|
||||
if (snapshot.Issues.Length > 0)
|
||||
{
|
||||
foreach (var issue in snapshot.Issues)
|
||||
{
|
||||
_logger.LogWarning("Policy issue {Code}: {Message}", issue.Code, issue.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private static bool TryNormalizeWeights(
|
||||
VexPolicyWeightOptions options,
|
||||
out VexConsensusPolicyOptions normalized,
|
||||
ImmutableArray<VexPolicyIssue>.Builder issues)
|
||||
{
|
||||
var hasAny = options is not null &&
|
||||
(options.Vendor.HasValue || options.Distro.HasValue ||
|
||||
options.Platform.HasValue || options.Hub.HasValue || options.Attestation.HasValue);
|
||||
|
||||
if (!hasAny)
|
||||
{
|
||||
normalized = new VexConsensusPolicyOptions();
|
||||
return true;
|
||||
}
|
||||
|
||||
var vendor = Clamp(options.Vendor, nameof(options.Vendor), issues);
|
||||
var distro = Clamp(options.Distro, nameof(options.Distro), issues);
|
||||
var platform = Clamp(options.Platform, nameof(options.Platform), issues);
|
||||
var hub = Clamp(options.Hub, nameof(options.Hub), issues);
|
||||
var attestation = Clamp(options.Attestation, nameof(options.Attestation), issues);
|
||||
|
||||
normalized = new VexConsensusPolicyOptions(
|
||||
VexConsensusPolicyOptions.BaselineVersion,
|
||||
vendor ?? 1.0,
|
||||
distro ?? 0.9,
|
||||
platform ?? 0.7,
|
||||
hub ?? 0.5,
|
||||
attestation ?? 0.6);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static double? Clamp(double? value, string fieldName, ImmutableArray<VexPolicyIssue>.Builder issues)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (double.IsNaN(value.Value) || double.IsInfinity(value.Value))
|
||||
{
|
||||
issues.Add(new VexPolicyIssue(
|
||||
$"weights.{fieldName}.invalid",
|
||||
$"{fieldName} must be a finite number.",
|
||||
VexPolicyIssueSeverity.Warning));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.Value < 0 || value.Value > 1)
|
||||
{
|
||||
issues.Add(new VexPolicyIssue(
|
||||
$"weights.{fieldName}.range",
|
||||
$"{fieldName} must be between 0 and 1; value {value.Value.ToString(CultureInfo.InvariantCulture)} was clamped.",
|
||||
VexPolicyIssueSeverity.Warning));
|
||||
return Math.Clamp(value.Value, 0, 1);
|
||||
}
|
||||
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
private static ImmutableDictionary<string, double> NormalizeOverrides(
|
||||
IDictionary<string, double>? overrides,
|
||||
ImmutableArray<VexPolicyIssue>.Builder issues)
|
||||
{
|
||||
if (overrides is null || overrides.Count == 0)
|
||||
{
|
||||
return ImmutableDictionary<string, double>.Empty;
|
||||
}
|
||||
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, double>(StringComparer.Ordinal);
|
||||
foreach (var kvp in overrides)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(kvp.Key))
|
||||
{
|
||||
issues.Add(new VexPolicyIssue(
|
||||
"overrides.key.missing",
|
||||
"Encountered provider override with empty key; ignoring entry.",
|
||||
VexPolicyIssueSeverity.Warning));
|
||||
continue;
|
||||
}
|
||||
|
||||
var weight = Clamp(kvp.Value, $"overrides.{kvp.Key}", issues) ?? kvp.Value;
|
||||
builder[kvp.Key.Trim()] = weight;
|
||||
}
|
||||
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class VexPolicyEvaluator : IVexPolicyEvaluator
|
||||
{
|
||||
private readonly IVexPolicyProvider _provider;
|
||||
|
||||
public VexPolicyEvaluator(IVexPolicyProvider provider)
|
||||
{
|
||||
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
|
||||
}
|
||||
|
||||
public string Version => Snapshot.Version;
|
||||
|
||||
public VexPolicySnapshot Snapshot => _provider.GetSnapshot();
|
||||
|
||||
public double GetProviderWeight(VexProvider provider)
|
||||
=> Snapshot.ConsensusPolicy.GetProviderWeight(provider);
|
||||
|
||||
public bool IsClaimEligible(VexClaim claim, VexProvider provider, out string? rejectionReason)
|
||||
=> Snapshot.ConsensusPolicy.IsClaimEligible(claim, provider, out rejectionReason);
|
||||
}
|
||||
|
||||
public static class VexPolicyServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddVexPolicy(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IVexPolicyProvider, VexPolicyProvider>();
|
||||
services.AddSingleton<IVexPolicyEvaluator, VexPolicyEvaluator>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
16
src/StellaOps.Vexer.Policy/StellaOps.Vexer.Policy.csproj
Normal file
16
src/StellaOps.Vexer.Policy/StellaOps.Vexer.Policy.csproj
Normal file
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Vexer.Core\StellaOps.Vexer.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
9
src/StellaOps.Vexer.Policy/TASKS.md
Normal file
9
src/StellaOps.Vexer.Policy/TASKS.md
Normal file
@@ -0,0 +1,9 @@
|
||||
If you are working on this file you need to read docs/ARCHITECTURE_VEXER.md and ./AGENTS.md).
|
||||
# TASKS
|
||||
| Task | Owner(s) | Depends on | Notes |
|
||||
|---|---|---|---|
|
||||
|VEXER-POLICY-01-001 – Policy schema & binding|Team Vexer Policy|VEXER-CORE-01-001|DONE (2025-10-15) – Established `VexPolicyOptions`, options binding, and snapshot provider covering baseline weights/overrides.|
|
||||
|VEXER-POLICY-01-002 – Policy evaluator service|Team Vexer Policy|VEXER-POLICY-01-001|DONE (2025-10-15) – `VexPolicyEvaluator` exposes immutable snapshots to consensus and normalizes rejection reasons.|
|
||||
|VEXER-POLICY-01-003 – Operator diagnostics & docs|Team Vexer Policy|VEXER-POLICY-01-001|TODO – Surface structured diagnostics (CLI/WebService) and author policy upgrade guidance in docs/ARCHITECTURE_VEXER.md appendix.|
|
||||
|VEXER-POLICY-01-004 – Policy schema validation & YAML binding|Team Vexer Policy|VEXER-POLICY-01-001|TODO – Add strongly-typed YAML/JSON binding, schema validation, and deterministic diagnostics for operator-supplied policy bundles.|
|
||||
|VEXER-POLICY-01-005 – Policy change tracking & telemetry|Team Vexer Policy|VEXER-POLICY-01-002|TODO – Emit revision history, expose snapshot digests via CLI/WebService, and add structured logging/metrics for policy reloads.|
|
||||
25
src/StellaOps.Vexer.Policy/VexPolicyOptions.cs
Normal file
25
src/StellaOps.Vexer.Policy/VexPolicyOptions.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Vexer.Policy;
|
||||
|
||||
public sealed class VexPolicyOptions
|
||||
{
|
||||
public string? Version { get; set; }
|
||||
|
||||
public VexPolicyWeightOptions Weights { get; set; } = new();
|
||||
|
||||
public IDictionary<string, double>? ProviderOverrides { get; set; }
|
||||
}
|
||||
|
||||
public sealed class VexPolicyWeightOptions
|
||||
{
|
||||
public double? Vendor { get; set; }
|
||||
|
||||
public double? Distro { get; set; }
|
||||
|
||||
public double? Platform { get; set; }
|
||||
|
||||
public double? Hub { get; set; }
|
||||
|
||||
public double? Attestation { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user