Aggregation-Only Guard Library Reference
Packages:
StellaOps.Aoc,StellaOps.Aoc.AspNetCore
Related tasks:WEB-AOC-19-001,WEB-AOC-19-003,DEVOPS-AOC-19-001
Audience: Concelier/Excititor service owners, Platform guild, QA
The Aggregation-Only Contract (AOC) guard library enforces the canonical ingestion rules described in the Aggregation-Only Contract reference (see also the condensed AOC guardrails checklist). Service owners should use the guard whenever raw advisory or VEX payloads are accepted, so that forbidden fields are rejected long before they reach PostgreSQL.
Packages
StellaOps.Aoc
IAocGuard/AocWriteGuard— validate JSON payloads and emitAocGuardResult.AocGuardOptions— toggles for signature enforcement, tenant requirements, and required top-level fields.AocViolation/AocViolationCode— structured violations surfaced to callers.AocError— canonical error DTO (code,message,violations[]) re-used by HTTP helpers, CLI tooling, and telemetry.ServiceCollectionExtensions.AddAocGuard()— DI helper that registers the singleton guard.AocGuardExtensions.ValidateOrThrow()— throwsAocGuardExceptionwhen validation fails.
StellaOps.Aoc.AspNetCore
AocGuardEndpointFilter<TRequest>— Minimal API endpoint filter that evaluates request payloads through the guard before invoking handlers.AocHttpResults.Problem()— produces an RFC 7807 payload that includes violation codes, suitable for API responses.
Minimal API integration
using StellaOps.Aoc;
using StellaOps.Aoc.AspNetCore.Routing;
using StellaOps.Aoc.AspNetCore.Results;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAocGuard();
builder.Services.Configure<AocGuardOptions>(options =>
{
options.RequireSignatureMetadata = true;
options.RequireTenant = true;
});
var app = builder.Build();
app.MapPost("/ingest", async (IngestionRequest request, IAocGuard guard, ILogger<Program> logger) =>
{
// additional application logic
return Results.Accepted();
})
.RequireAocGuard<IngestionRequest>(
request => new object?[] { request.Payload },
serializerOptions: null,
guardOptions: null)
.ProducesProblem(StatusCodes.Status400BadRequest)
.WithTags("AOC");
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var exceptionHandler = context.Features.Get<IExceptionHandlerFeature>();
if (exceptionHandler?.Error is AocGuardException guardException)
{
var result = AocHttpResults.Problem(context, guardException);
await result.ExecuteAsync(context);
return;
}
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
});
});
Key points:
- Register the guard singleton before wiring repositories or worker services.
- Use
AocGuardEndpointFilter<TRequest>to protect Minimal API endpoints. ThepayloadSelectorcan yield multiple payloads (e.g. batch ingestion) and the filter will validate each one. - Prefer the
RequireAocGuardextension when wiring endpoints; it wrapsAddEndpointFilterand handles single-payload scenarios without additional boilerplate. - Wrap guard exceptions with
AocHttpResults.Problemto ensure clients receive machine-readable codes (ERR_AOC_00x). The helper now emits the serializedAocErrorunder theerrorextension for consumers that want a typed payload.
Allowed top-level fields
AocWriteGuard enforces the contract’s top-level allowlist: _id, tenant, source, upstream, content, identifiers, linkset, supersedes, createdAt/created_at, ingestedAt/ingested_at, and attributes. Unknown fields produce ERR_AOC_007 violations. When staging schema changes, extend the allowlist through AocGuardOptions.AllowedTopLevelFields:
builder.Services.Configure<AocGuardOptions>(options =>
{
options.AllowedTopLevelFields =
options.AllowedTopLevelFields.Add("experimental_field");
});
Worker / repository usage
Inject IAocGuard (or a module-specific wrapper such as IVexRawWriteGuard) anywhere documents are persisted. Call ValidateOrThrow before writes to guarantee fail-fast behaviour, for example:
public sealed class AdvisoryRawRepository
{
private readonly IAocGuard _guard;
public AdvisoryRawRepository(IAocGuard guard) => _guard = guard;
public Task WriteAsync(JsonDocument document, CancellationToken cancellationToken)
{
_guard.ValidateOrThrow(document.RootElement);
// proceed with storage logic
}
}
Configuration tips
- Adjust
AocGuardOptions.RequiredTopLevelFieldswhen staging new schema changes. All configured names are case-insensitive. - Extend
AllowedTopLevelFieldsfor temporary schema experiments so that guard runs stay clean while the contract is updated. - Set
RequireSignatureMetadata = falsefor legacy feeds that do not provide signature envelopes yet; track the waiver in the module backlog. - Use module-specific wrappers (
AddConcelierAocGuards,AddExcititorAocGuards) to combine guard registration with domain exceptions and metrics.
Testing guidance
- Unit-test guard behaviour with fixture payloads (see
src/Aoc/__Tests). - Service-level tests should assert that ingestion endpoints return
ERR_AOC_*codes viaAocHttpResults. - CI must run
stella aoc verifyonce CLI support lands (DEVOPS-AOC-19-002). - Roslyn analyzer enforcement (
WEB-AOC-19-003) will ensure the guard is registered; keep services wired through the shared extensions to prepare for that gate.
For questions or updates, coordinate with the BE‑Base Platform guild and reference WEB-AOC-19-001.
