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
Produces deterministic VEX export artifacts, coordinates cache lookups, and bridges artifact storage with attestation generation.
## Scope
- Export orchestration pipeline: query signature resolution, cache lookup, snapshot building, attestation handoff.
- Format-neutral builder interfaces consumed by format-specific plug-ins.
- Artifact store abstraction wiring (S3/MinIO/filesystem) with offline-friendly packaging.
- Export metrics/logging and deterministic manifest emission.
## Participants
- WebService invokes the export engine to service `/vexer/export` requests.
- Attestation module receives built artifacts through this layer for signing.
- Worker reuses caching and artifact utilities for scheduled exports and GC routines.
## Interfaces & contracts
- `IExportEngine`, `IExportSnapshotBuilder`, cache provider interfaces, and artifact store adapters.
- Hook points for format plug-ins (JSON, JSONL, OpenVEX, CSAF, ZIP bundle).
## In/Out of scope
In: orchestration, caching, artifact store interactions, manifest metadata.
Out: format-specific serialization (lives in Formats.*), policy evaluation (Policy), HTTP presentation (WebService).
## Observability & security expectations
- Emit cache hit/miss counters, export durations, artifact sizes, and attestation timing logs.
- Ensure no sensitive tokens/URIs are logged.
## Tests
- Engine orchestration tests, cache behavior, and artifact lifecycle coverage will live in `../StellaOps.Vexer.Export.Tests`.

View File

@@ -0,0 +1,128 @@
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Vexer.Core;
using StellaOps.Vexer.Policy;
using StellaOps.Vexer.Storage.Mongo;
namespace StellaOps.Vexer.Export;
public interface IExportEngine
{
ValueTask<VexExportManifest> ExportAsync(VexExportRequestContext context, CancellationToken cancellationToken);
}
public sealed record VexExportRequestContext(
VexQuery Query,
VexExportFormat Format,
DateTimeOffset RequestedAt,
bool ForceRefresh = false);
public interface IVexExportDataSource
{
ValueTask<VexExportDataSet> FetchAsync(VexQuery query, CancellationToken cancellationToken);
}
public sealed record VexExportDataSet(
ImmutableArray<VexConsensus> Consensus,
ImmutableArray<VexClaim> Claims,
ImmutableArray<string> SourceProviders);
public sealed class VexExportEngine : IExportEngine
{
private readonly IVexExportStore _exportStore;
private readonly IVexPolicyEvaluator _policyEvaluator;
private readonly IVexExportDataSource _dataSource;
private readonly IReadOnlyDictionary<VexExportFormat, IVexExporter> _exporters;
private readonly ILogger<VexExportEngine> _logger;
public VexExportEngine(
IVexExportStore exportStore,
IVexPolicyEvaluator policyEvaluator,
IVexExportDataSource dataSource,
IEnumerable<IVexExporter> exporters,
ILogger<VexExportEngine> logger)
{
_exportStore = exportStore ?? throw new ArgumentNullException(nameof(exportStore));
_policyEvaluator = policyEvaluator ?? throw new ArgumentNullException(nameof(policyEvaluator));
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
if (exporters is null)
{
throw new ArgumentNullException(nameof(exporters));
}
_exporters = exporters.ToDictionary(x => x.Format);
}
public async ValueTask<VexExportManifest> ExportAsync(VexExportRequestContext context, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
var signature = VexQuerySignature.FromQuery(context.Query);
if (!context.ForceRefresh)
{
var cached = await _exportStore.FindAsync(signature, context.Format, cancellationToken).ConfigureAwait(false);
if (cached is not null)
{
_logger.LogInformation("Reusing cached export for {Signature} ({Format})", signature.Value, context.Format);
return cached with { FromCache = true };
}
}
var dataset = await _dataSource.FetchAsync(context.Query, cancellationToken).ConfigureAwait(false);
var exporter = ResolveExporter(context.Format);
var exportRequest = new VexExportRequest(
context.Query,
dataset.Consensus,
dataset.Claims,
context.RequestedAt);
var digest = exporter.Digest(exportRequest);
await using var buffer = new MemoryStream();
var result = await exporter.SerializeAsync(exportRequest, buffer, cancellationToken).ConfigureAwait(false);
var exportId = FormattableString.Invariant($"exports/{context.RequestedAt:yyyyMMddTHHmmssfffZ}/{digest.Digest}");
var manifest = new VexExportManifest(
exportId,
signature,
context.Format,
context.RequestedAt,
digest,
dataset.Claims.Length,
dataset.SourceProviders,
fromCache: false,
consensusRevision: _policyEvaluator.Version,
attestation: null,
sizeBytes: result.BytesWritten);
await _exportStore.SaveAsync(manifest, cancellationToken).ConfigureAwait(false);
_logger.LogInformation(
"Export generated for {Signature} ({Format}) size={SizeBytes} bytes",
signature.Value,
context.Format,
result.BytesWritten);
return manifest;
}
private IVexExporter ResolveExporter(VexExportFormat format)
=> _exporters.TryGetValue(format, out var exporter)
? exporter
: throw new InvalidOperationException($"No exporter registered for format '{format}'.");
}
public static class VexExportServiceCollectionExtensions
{
public static IServiceCollection AddVexExportEngine(this IServiceCollection services)
{
services.AddSingleton<IExportEngine, VexExportEngine>();
return services;
}
}

View File

@@ -0,0 +1,18 @@
<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" />
<ProjectReference Include="..\StellaOps.Vexer.Policy\StellaOps.Vexer.Policy.csproj" />
<ProjectReference Include="..\StellaOps.Vexer.Storage.Mongo\StellaOps.Vexer.Storage.Mongo.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
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-EXPORT-01-001 Export engine orchestration|Team Vexer Export|VEXER-CORE-01-003|DONE (2025-10-15) Export engine scaffolding with cache lookup, data source hooks, and deterministic manifest emission.|
|VEXER-EXPORT-01-002 Cache index & eviction hooks|Team Vexer Export|VEXER-EXPORT-01-001, VEXER-STORAGE-01-003|TODO Wire cache lookup/write path against `vex.cache` collection and add GC utilities for Worker to prune stale entries deterministically.|
|VEXER-EXPORT-01-003 Artifact store adapters|Team Vexer Export|VEXER-EXPORT-01-001|TODO Provide pluggable storage adapters (filesystem, S3/MinIO) with offline bundle packaging and hash verification.|
|VEXER-EXPORT-01-004 Attestation handoff integration|Team Vexer Export|VEXER-EXPORT-01-001, VEXER-ATTEST-01-001|TODO Connect export engine to attestation client, persist Rekor metadata, and reuse cached attestations.|