feat(audit): decorate write endpoints in 4 services (AUDIT-002 wave C)

Sprint SPRINT_20260408_004 AUDIT-002, second completion criterion
("at least write endpoints decorated with AuditActionAttribute").

Vertical slice using the existing .Audited(module, action, resourceType)
helper from AuditedRouteGroupExtensions:

- Graph.Api (4 endpoints):
  * POST /api/graphs/builds        -> graph.create graph_build
  * POST /api/graphs/overlays      -> graph.create graph_overlay
  * POST /graphs/{g}/saved-views   -> graph.create graph_saved_view
  * DELETE /graphs/{g}/saved-views/{v} -> graph.delete graph_saved_view

- SbomService (4 endpoints):
  * POST /sbom/upload + /api/v1/sbom/upload -> sbom.create sbom
  * POST /entrypoints              -> sbom.update sbom_entrypoint
  * POST /internal/orchestrator/sources  -> sbom.create orchestrator_source
  * POST /internal/orchestrator/control  -> sbom.update orchestrator_control

- Policy.Gateway ExceptionApproval (4 governance endpoints):
  * POST /exception/request        -> policy.create  exception_approval_request
  * POST /exception/{id}/approve   -> policy.approve ""
  * POST /exception/{id}/reject    -> policy.reject  ""
  * POST /exception/{id}/cancel    -> policy.cancel  ""

- Notifier EscalationEndpoints (9 endpoints):
  * policies CRUD                   -> notifier.{create,update,delete} escalation_policy
  * on-call schedules CRUD          -> notifier.{create,update,delete} oncall_schedule
  * escalation start/escalate/stop  -> notifier.execute incident_escalation

All 4 projects build clean. Events will flow to Timeline
/api/v1/audit/ingest once the services boot and execute these endpoints.

BinaryIndex uses MVC controllers — audit decoration for that style
requires a different wiring approach and is deferred to a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-19 22:26:44 +03:00
parent abb9012c69
commit 4cbe58fc80
5 changed files with 46 additions and 22 deletions

View File

@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Audit.Emission;
using StellaOps.Graph.Api.Contracts;
using StellaOps.Graph.Api.Security;
using StellaOps.Graph.Indexer.Ingestion.Sbom;
@@ -47,7 +48,7 @@ public static class CartographerEndpoints
CartographerJobId = jobId,
GraphSnapshotId = state.GraphSnapshotId
});
});
}).Audited("graph", "create", resourceType: "graph_build");
app.MapGet("/api/graphs/builds/{jobId}", (string jobId) =>
{
@@ -94,7 +95,7 @@ public static class CartographerEndpoints
Status = state.Status,
GraphSnapshotId = state.GraphSnapshotId
});
});
}).Audited("graph", "create", resourceType: "graph_overlay");
app.MapGet("/api/graphs/overlays/{jobId}", (string jobId) =>
{

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using StellaOps.Audit.Emission;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Graph.Api.Contracts;
using StellaOps.Graph.Api.Security;
@@ -450,7 +451,8 @@ public static class CompatibilityEndpoints
var created = await store.CreateAsync(tenantId, graphId, request, ct);
return Results.Created($"/graphs/{graphId}/saved-views/{created.ViewId}", created);
})
.RequireTenant();
.RequireTenant()
.Audited("graph", "create", resourceType: "graph_saved_view");
app.MapDelete("/graphs/{graphId}/saved-views/{viewId}", async Task<IResult> (
string graphId,
@@ -476,7 +478,8 @@ public static class CompatibilityEndpoints
? Results.NoContent()
: Results.NotFound();
})
.RequireTenant();
.RequireTenant()
.Audited("graph", "delete", resourceType: "graph_saved_view");
}
private static async Task<(string? TenantId, IResult? Failure)> AuthorizeAsync(