Aggregation-Only Guard Library Reference

WITHDRAWN (2026-09-27, SPRINT_20260927_003 PCD-62). The AOC libraries (StellaOps.Aoc, StellaOps.Aoc.AspNetCore), the StellaOps.Aoc.Analyzers Roslyn analyzer and the CLI Aoc plug-in are no longer built or shipped. Their sources are frozen under src/__Obsoleted/Aoc/ and src/__Obsoleted/Cli/ (CODE_OF_CONDUCT 15.4). No service referenced them, and the plug-in’s direct-PostgreSQL verify form (--postgres option) read an aoc_documents table that no migration creates. This page describes the withdrawn code and is kept for reference. Still live and separate: the aoc:verify scope. The core CLI aoc command was withdrawn on 2026-10-01 (SPRINT_20260928_008 PCD-153); the Platform /api/v1/aoc/* compatibility endpoints (Development and testing only) and the Console AOC pages were withdrawn on 2026-10-08 (SPRINT_20261007_002 CCP-005 and CCP-004).

Libraries: StellaOps.Aoc, StellaOps.Aoc.AspNetCore (project references; not published as NuGet packages) Source: src/Aoc/__Libraries/ Audience: owners of code that accepts raw upstream payloads, Platform maintainers, QA

The Aggregation-Only Contract (AOC) guard library enforces the ingestion rules summarised in the AOC guardrails checklist and specified in ../architecture.md. Use the guard wherever raw advisory, VEX, or similar upstream payloads are accepted, so that forbidden fields are rejected before they reach storage.

Consumer status (2026-09-26). The services that wired this guard (Concelier, Excititor) are retired and frozen under src/__Obsoleted/Concelier/. At commit 942a18727c no live deployable calls AddAocGuard or RequireAocGuard; see ../architecture.md §7.

Packages

StellaOps.Aoc

StellaOps.Aoc.AspNetCore

Minimal API integration

AocGuardOptions properties are init-only, so Configure<AocGuardOptions>(o => o.X = ...) does not compile. Register a configured instance instead, or pass guardOptions per endpoint:

using Microsoft.Extensions.Options;
using StellaOps.Aoc;
using StellaOps.Aoc.AspNetCore.Routing;
using StellaOps.Aoc.AspNetCore.Results;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAocGuard();
builder.Services.AddSingleton(Options.Create(AocGuardOptions.Default with
{
    RequireSignatureMetadata = true,
    RequireTenant = true,
}));

var app = builder.Build();

app.MapPost("/ingest", (IngestionRequest request) => Results.Accepted())
    .RequireAocGuard<IngestionRequest>(request => request.Payload)
    .ProducesProblem(StatusCodes.Status400BadRequest);

app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        var feature = context.Features.Get<IExceptionHandlerFeature>();
        if (feature?.Error is AocGuardException guardException)
        {
            await AocHttpResults.Problem(context, guardException).ExecuteAsync(context);
            return;
        }

        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
    });
});

Key points:

Allowed top-level fields

AocGuardOptions.Default allows the required fields (tenant, source, upstream, content, linkset) plus _id, identifiers, attributes, supersedes, createdAt/created_at, ingestedAt/ingested_at, links, advisory_key, and statements. Unknown fields produce ERR_AOC_007. Field names compare case-insensitively. To extend the allowlist:

var options = AocGuardOptions.Default with
{
    AllowedTopLevelFields = AocGuardOptions.Default.AllowedTopLevelFields.Add("experimental_field"),
};

Worker / repository usage

Inject IAocGuard wherever documents are persisted and call ValidateOrThrow before the write:

public sealed class RawDocumentRepository
{
    private readonly IAocGuard _guard;

    public RawDocumentRepository(IAocGuard guard) => _guard = guard;

    public Task WriteAsync(JsonDocument document, CancellationToken cancellationToken)
    {
        _guard.ValidateOrThrow(document.RootElement);
        // proceed with storage logic
        return Task.CompletedTask;
    }
}

Configuration tips

Testing guidance