feat: Enhance telemetry metrics recording with sealed mode tagging
This commit is contained in:
@@ -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 AU1–AU10 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 AU1–AU10 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 RR1–RR10 from `31-Nov-2025 FINDINGS.md`; depends on bundle/schema publication | Authority Guild · Attestor Guild · Sbomer Guild | Remediate RR1–RR10: 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 RR1–RR10: 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 AU1–AU10 remediation from `31-Nov-2025 FINDINGS.md`. | Product Mgmt |
|
| 2025-12-01 | Added AUTH-GAPS-314-004 to track AU1–AU10 remediation from `31-Nov-2025 FINDINGS.md`. | Product Mgmt |
|
||||||
| 2025-12-01 | Added REKOR-RECEIPT-GAPS-314-005 to track RR1–RR10 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 RR1–RR10 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 RR1–RR10 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 (RR1–RR10) must be signed and mirrored in Authority/Sbomer; track via REKOR-RECEIPT-GAPS-314-005.
|
- Rekor receipt schema/catalog changes (RR1–RR10) 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.
|
||||||
|
- AU1–AU10 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.
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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?>[]
|
||||||
|
|||||||
132
src/__Libraries/StellaOps.Microservice/TypedEndpointAdapter.cs
Normal file
132
src/__Libraries/StellaOps.Microservice/TypedEndpointAdapter.cs
Normal 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)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user