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

This commit is contained in:
root
2025-10-15 19:20:13 +03:00
parent 8d153522b0
commit 0d8233dfb4
125 changed files with 9383 additions and 3306 deletions

View 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.

View 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;
}
}

View 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>

View 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.|

View 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; }
}