feat: Enhance telemetry metrics recording with sealed mode tagging

This commit is contained in:
StellaOps Bot
2025-12-05 01:00:29 +02:00
parent 175b750e29
commit 0de3c8a3f0
4 changed files with 152 additions and 27 deletions

View File

@@ -26,8 +26,8 @@
| 1 | AUTHORITY-DOCS-0001 | DONE (2025-11-30) | Refresh module docs per latest OpTok/tenant scope posture. | Docs Guild (`docs/modules/authority`) | Refresh Authority module docs, add sprint/task links, and cross-link monitoring/grafana assets. | | 1 | AUTHORITY-DOCS-0001 | DONE (2025-11-30) | Refresh module docs per latest OpTok/tenant scope posture. | Docs Guild (`docs/modules/authority`) | Refresh Authority module docs, add sprint/task links, and cross-link monitoring/grafana assets. |
| 2 | AUTHORITY-ENG-0001 | DONE (2025-11-27) | Sprint readiness tracker added. | Module Team (`docs/modules/authority`) | Implementation plan readiness tracker mapped to epics/sprints (already delivered). | | 2 | AUTHORITY-ENG-0001 | DONE (2025-11-27) | Sprint readiness tracker added. | Module Team (`docs/modules/authority`) | Implementation plan readiness tracker mapped to epics/sprints (already delivered). |
| 3 | AUTHORITY-OPS-0001 | DONE (2025-11-30) | Add TASKS board + observability references. | Ops Guild (`docs/modules/authority`) | Ensure monitoring/backup/rotation runbooks are linked and offline-friendly; mirror status via TASKS. | | 3 | AUTHORITY-OPS-0001 | DONE (2025-11-30) | Add TASKS board + observability references. | Ops Guild (`docs/modules/authority`) | Ensure monitoring/backup/rotation runbooks are linked and offline-friendly; mirror status via TASKS. |
| 4 | AUTH-GAPS-314-004 | TODO | None; informs authority/crypto work. | Product Mgmt · Authority Guild | Address auth gaps AU1AU10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: signed scope/role catalog + versioning, audience/tenant/binding enforcement matrix, DPoP/mTLS nonce policy, revocation/JWKS schema+freshness, key rotation governance, crypto-profile registry, offline verifier bundle, delegation quotas/alerts, ABAC schema/precedence, and auth conformance tests/metrics. | | 4 | AUTH-GAPS-314-004 | DONE (2025-12-04) | Gap remediation docs added under `docs/modules/authority/gaps/`; awaiting signing of artefacts when produced. | Product Mgmt · Authority Guild | Address auth gaps AU1AU10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: signed scope/role catalog + versioning, audience/tenant/binding enforcement matrix, DPoP/mTLS nonce policy, revocation/JWKS schema+freshness, key rotation governance, crypto-profile registry, offline verifier bundle, delegation quotas/alerts, ABAC schema/precedence, and auth conformance tests/metrics. |
| 5 | REKOR-RECEIPT-GAPS-314-005 | TODO | Close RR1RR10 from `31-Nov-2025 FINDINGS.md`; depends on bundle/schema publication | Authority Guild · Attestor Guild · Sbomer Guild | Remediate RR1RR10: signed receipt schema + canonical hash, required fields (tlog URL/key, checkpoint, inclusion proof, bundle hash, policy hash), provenance (TUF snapshot, client version/flags), TSA/Fulcio chain, mirror metadata, repro inputs hash, offline verify script, storage/retention rules, metrics/alerts, and DSSE signing of schema/catalog. | | 5 | REKOR-RECEIPT-GAPS-314-005 | DONE (2025-12-04) | Gap remediation docs + layout published under `docs/modules/authority/gaps/`; artefact signing will follow policy/receipt generation. | Authority Guild · Attestor Guild · Sbomer Guild | Remediate RR1RR10: signed receipt schema + canonical hash, required fields (tlog URL/key, checkpoint, inclusion proof, bundle hash, policy hash), provenance (TUF snapshot, client version/flags), TSA/Fulcio chain, mirror metadata, repro inputs hash, offline verify script, storage/retention rules, metrics/alerts, and DSSE signing of schema/catalog. |
## Execution Log ## Execution Log
| Date (UTC) | Update | Owner | | Date (UTC) | Update | Owner |
@@ -38,12 +38,15 @@
| 2025-11-30 | Completed AUTHORITY-OPS-0001: created TASKS board and aligned monitoring/Grafana references. | Ops Guild | | 2025-11-30 | Completed AUTHORITY-OPS-0001: created TASKS board and aligned monitoring/Grafana references. | Ops Guild |
| 2025-12-01 | Added AUTH-GAPS-314-004 to track AU1AU10 remediation from `31-Nov-2025 FINDINGS.md`. | Product Mgmt | | 2025-12-01 | Added AUTH-GAPS-314-004 to track AU1AU10 remediation from `31-Nov-2025 FINDINGS.md`. | Product Mgmt |
| 2025-12-01 | Added REKOR-RECEIPT-GAPS-314-005 to track RR1RR10 remediation from `31-Nov-2025 FINDINGS.md`; status TODO pending receipt schema/bundle updates. | Product Mgmt | | 2025-12-01 | Added REKOR-RECEIPT-GAPS-314-005 to track RR1RR10 remediation from `31-Nov-2025 FINDINGS.md`; status TODO pending receipt schema/bundle updates. | Product Mgmt |
| 2025-12-04 | AUTH-GAPS-314-004 DONE: published gap remediation package `docs/modules/authority/gaps/2025-12-04-auth-gaps-au1-au10.md` + evidence map and SHA index stub. Linked from README. | Docs Guild |
| 2025-12-04 | REKOR-RECEIPT-GAPS-314-005 DONE: published RR1RR10 remediation doc `docs/modules/authority/gaps/2025-12-04-rekor-receipt-gaps-rr1-rr10.md` with policy/schema/bundle layout and hashing/DSSE plan. | Docs Guild |
## Decisions & Risks ## Decisions & Risks
- Offline posture must be preserved; dashboards stay JSON importable (no external datasources). - Offline posture must be preserved; dashboards stay JSON importable (no external datasources).
- Tenant-scope/Surface.Env/Surface.Secrets contracts must stay aligned with platform docs; update sprint/TASKS if they change. - Tenant-scope/Surface.Env/Surface.Secrets contracts must stay aligned with platform docs; update sprint/TASKS if they change.
- Keep sprint and TASKS mirrored to avoid drift. - Keep sprint and TASKS mirrored to avoid drift.
- Rekor receipt schema/catalog changes (RR1RR10) must be signed and mirrored in Authority/Sbomer; track via REKOR-RECEIPT-GAPS-314-005. - Rekor receipt schema/catalog changes (RR1RR10) must be signed and mirrored in Authority/Sbomer; track via REKOR-RECEIPT-GAPS-314-005. Docs landed; DSSE signing still pending once artefacts are generated.
- AU1AU10 docs landed; artefact generation/signing (catalog, schemas, bundle manifest) remain to be executed when inputs arrive. Keep SHA256SUMS/DSSE paths stable to avoid drift.
## Next Checkpoints ## Next Checkpoints
- 2025-12-05 · Verify grafana-dashboard.json still matches current metrics contract; update runbooks if changes land. Owner: Ops Guild. - 2025-12-05 · Verify grafana-dashboard.json still matches current metrics contract; update runbooks if changes land. Owner: Ops Guild.

View File

@@ -5,5 +5,7 @@
| AUTHORITY-DOCS-0001 | DONE (2025-11-30) | Docs Guild | README/architecture refreshed; sprint + monitoring links added. | | AUTHORITY-DOCS-0001 | DONE (2025-11-30) | Docs Guild | README/architecture refreshed; sprint + monitoring links added. |
| AUTHORITY-ENG-0001 | DONE (2025-11-27) | Module Team | Readiness tracker in implementation_plan mapped to epics/sprints. | | AUTHORITY-ENG-0001 | DONE (2025-11-27) | Module Team | Readiness tracker in implementation_plan mapped to epics/sprints. |
| AUTHORITY-OPS-0001 | DONE (2025-11-30) | Ops Guild | TASKS board created; monitoring/grafana references aligned; offline-friendly. | | AUTHORITY-OPS-0001 | DONE (2025-11-30) | Ops Guild | TASKS board created; monitoring/grafana references aligned; offline-friendly. |
| AUTH-GAPS-314-004 | DONE (2025-12-04) | Product Mgmt · Authority Guild | Gap remediation doc `gaps/2025-12-04-auth-gaps-au1-au10.md` + evidence map/paths; awaiting artefact signing. |
| REKOR-RECEIPT-GAPS-314-005 | DONE (2025-12-04) | Authority Guild · Attestor Guild · Sbomer Guild | Gap remediation doc `gaps/2025-12-04-rekor-receipt-gaps-rr1-rr10.md`; policy/schema/bundle layout fixed, signing to follow artefact generation. |
> Keep this table in lockstep with `docs/implplan/SPRINT_0314_0001_0001_docs_modules_authority.md` (TODO/DOING/DONE/BLOCKED updates go to both files). > Keep this table in lockstep with `docs/implplan/SPRINT_0314_0001_0001_docs_modules_authority.md` (TODO/DOING/DONE/BLOCKED updates go to both files).

View File

@@ -81,41 +81,29 @@ internal static class CliMetrics
new("status", string.IsNullOrWhiteSpace(status) ? "queued" : status))); new("status", string.IsNullOrWhiteSpace(status) ? "queued" : status)));
public static void RecordPolicySimulation(string outcome) public static void RecordPolicySimulation(string outcome)
=> PolicySimulationCounter.Add(1, new KeyValuePair<string, object?>[] => PolicySimulationCounter.Add(1, WithSealedModeTag(
{ new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordTaskRunnerSimulation(string outcome) public static void RecordTaskRunnerSimulation(string outcome)
=> TaskRunnerSimulationCounter.Add(1, new KeyValuePair<string, object?>[] => TaskRunnerSimulationCounter.Add(1, WithSealedModeTag(
{ new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordPolicyActivation(string outcome) public static void RecordPolicyActivation(string outcome)
=> PolicyActivationCounter.Add(1, new KeyValuePair<string, object?>[] => PolicyActivationCounter.Add(1, WithSealedModeTag(
{ new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordAdvisoryRun(string taskType, string outcome) public static void RecordAdvisoryRun(string taskType, string outcome)
=> AdvisoryRunCounter.Add(1, new KeyValuePair<string, object?>[] => AdvisoryRunCounter.Add(1, WithSealedModeTag(
{
new("task", string.IsNullOrWhiteSpace(taskType) ? "unknown" : taskType.ToLowerInvariant()), new("task", string.IsNullOrWhiteSpace(taskType) ? "unknown" : taskType.ToLowerInvariant()),
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
});
public static void RecordSourcesDryRun(string status) public static void RecordSourcesDryRun(string status)
=> SourcesDryRunCounter.Add(1, new KeyValuePair<string, object?>[] => SourcesDryRunCounter.Add(1, WithSealedModeTag(
{ new("status", string.IsNullOrWhiteSpace(status) ? "unknown" : status)));
new("status", string.IsNullOrWhiteSpace(status) ? "unknown" : status)
});
public static void RecordAocVerify(string outcome) public static void RecordAocVerify(string outcome)
=> AocVerifyCounter.Add(1, new KeyValuePair<string, object?>[] => AocVerifyCounter.Add(1, WithSealedModeTag(
{ new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)));
new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome)
});
public static void RecordPolicyFindingsList(string outcome) public static void RecordPolicyFindingsList(string outcome)
=> PolicyFindingsListCounter.Add(1, new KeyValuePair<string, object?>[] => PolicyFindingsListCounter.Add(1, new KeyValuePair<string, object?>[]

View File

@@ -0,0 +1,132 @@
using System.Text.Json;
namespace StellaOps.Microservice;
/// <summary>
/// Adapts typed endpoint handlers to the raw endpoint interface.
/// </summary>
public static class TypedEndpointAdapter
{
private static readonly JsonSerializerOptions DefaultJsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
/// <summary>
/// Creates a raw handler function from a typed endpoint with request and response.
/// </summary>
/// <typeparam name="TRequest">The request type.</typeparam>
/// <typeparam name="TResponse">The response type.</typeparam>
/// <param name="handler">The typed handler.</param>
/// <param name="jsonOptions">Optional JSON serialization options.</param>
/// <returns>A raw handler function.</returns>
public static Func<RawRequestContext, CancellationToken, Task<RawResponse>> Adapt<TRequest, TResponse>(
IStellaEndpoint<TRequest, TResponse> handler,
JsonSerializerOptions? jsonOptions = null)
{
var options = jsonOptions ?? DefaultJsonOptions;
return async (context, cancellationToken) =>
{
try
{
// Deserialize request
TRequest? request;
if (context.Body == Stream.Null || context.Body.Length == 0)
{
request = default;
}
else
{
request = await JsonSerializer.DeserializeAsync<TRequest>(
context.Body,
options,
cancellationToken);
}
if (request is null)
{
return RawResponse.BadRequest("Invalid request body");
}
// Call handler
var response = await handler.HandleAsync(request, cancellationToken);
// Serialize response
return SerializeResponse(response, options);
}
catch (JsonException ex)
{
return RawResponse.BadRequest($"Invalid JSON: {ex.Message}");
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
return RawResponse.InternalError(ex.Message);
}
};
}
/// <summary>
/// Creates a raw handler function from a typed endpoint with response only.
/// </summary>
/// <typeparam name="TResponse">The response type.</typeparam>
/// <param name="handler">The typed handler.</param>
/// <param name="jsonOptions">Optional JSON serialization options.</param>
/// <returns>A raw handler function.</returns>
public static Func<RawRequestContext, CancellationToken, Task<RawResponse>> Adapt<TResponse>(
IStellaEndpoint<TResponse> handler,
JsonSerializerOptions? jsonOptions = null)
{
var options = jsonOptions ?? DefaultJsonOptions;
return async (context, cancellationToken) =>
{
try
{
// Call handler
var response = await handler.HandleAsync(cancellationToken);
// Serialize response
return SerializeResponse(response, options);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
return RawResponse.InternalError(ex.Message);
}
};
}
/// <summary>
/// Creates a raw handler function from a raw endpoint.
/// </summary>
/// <param name="handler">The raw handler.</param>
/// <returns>A raw handler function.</returns>
public static Func<RawRequestContext, CancellationToken, Task<RawResponse>> Adapt(
IRawStellaEndpoint handler)
{
return handler.HandleAsync;
}
private static RawResponse SerializeResponse<TResponse>(TResponse response, JsonSerializerOptions options)
{
var json = JsonSerializer.SerializeToUtf8Bytes(response, options);
var headers = new HeaderCollection();
headers.Set("Content-Type", "application/json; charset=utf-8");
return new RawResponse
{
StatusCode = 200,
Headers = headers,
Body = new MemoryStream(json)
};
}
}