diff --git a/docs/implplan/SPRINT_0314_0001_0001_docs_modules_authority.md b/docs/implplan/SPRINT_0314_0001_0001_docs_modules_authority.md index 661f8d601..3282acb28 100644 --- a/docs/implplan/SPRINT_0314_0001_0001_docs_modules_authority.md +++ b/docs/implplan/SPRINT_0314_0001_0001_docs_modules_authority.md @@ -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. | | 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. | -| 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. | -| 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. | +| 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 | 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 | 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-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-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 - 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. - 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 - 2025-12-05 · Verify grafana-dashboard.json still matches current metrics contract; update runbooks if changes land. Owner: Ops Guild. diff --git a/docs/modules/authority/TASKS.md b/docs/modules/authority/TASKS.md index 3de5fa990..4eb708c58 100644 --- a/docs/modules/authority/TASKS.md +++ b/docs/modules/authority/TASKS.md @@ -5,5 +5,7 @@ | 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-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). diff --git a/src/Cli/StellaOps.Cli/Telemetry/CliMetrics.cs b/src/Cli/StellaOps.Cli/Telemetry/CliMetrics.cs index 1498310b4..a82af1d2c 100644 --- a/src/Cli/StellaOps.Cli/Telemetry/CliMetrics.cs +++ b/src/Cli/StellaOps.Cli/Telemetry/CliMetrics.cs @@ -81,41 +81,29 @@ internal static class CliMetrics new("status", string.IsNullOrWhiteSpace(status) ? "queued" : status))); public static void RecordPolicySimulation(string outcome) - => PolicySimulationCounter.Add(1, new KeyValuePair[] - { - new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) - }); + => PolicySimulationCounter.Add(1, WithSealedModeTag( + new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome))); public static void RecordTaskRunnerSimulation(string outcome) - => TaskRunnerSimulationCounter.Add(1, new KeyValuePair[] - { - new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) - }); + => TaskRunnerSimulationCounter.Add(1, WithSealedModeTag( + new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome))); public static void RecordPolicyActivation(string outcome) - => PolicyActivationCounter.Add(1, new KeyValuePair[] - { - new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) - }); + => PolicyActivationCounter.Add(1, WithSealedModeTag( + new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome))); public static void RecordAdvisoryRun(string taskType, string outcome) - => AdvisoryRunCounter.Add(1, new KeyValuePair[] - { + => AdvisoryRunCounter.Add(1, WithSealedModeTag( 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) - => SourcesDryRunCounter.Add(1, new KeyValuePair[] - { - new("status", string.IsNullOrWhiteSpace(status) ? "unknown" : status) - }); + => SourcesDryRunCounter.Add(1, WithSealedModeTag( + new("status", string.IsNullOrWhiteSpace(status) ? "unknown" : status))); public static void RecordAocVerify(string outcome) - => AocVerifyCounter.Add(1, new KeyValuePair[] - { - new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome) - }); + => AocVerifyCounter.Add(1, WithSealedModeTag( + new("outcome", string.IsNullOrWhiteSpace(outcome) ? "unknown" : outcome))); public static void RecordPolicyFindingsList(string outcome) => PolicyFindingsListCounter.Add(1, new KeyValuePair[] diff --git a/src/__Libraries/StellaOps.Microservice/TypedEndpointAdapter.cs b/src/__Libraries/StellaOps.Microservice/TypedEndpointAdapter.cs new file mode 100644 index 000000000..2a4dfb278 --- /dev/null +++ b/src/__Libraries/StellaOps.Microservice/TypedEndpointAdapter.cs @@ -0,0 +1,132 @@ +using System.Text.Json; + +namespace StellaOps.Microservice; + +/// +/// Adapts typed endpoint handlers to the raw endpoint interface. +/// +public static class TypedEndpointAdapter +{ + private static readonly JsonSerializerOptions DefaultJsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true + }; + + /// + /// Creates a raw handler function from a typed endpoint with request and response. + /// + /// The request type. + /// The response type. + /// The typed handler. + /// Optional JSON serialization options. + /// A raw handler function. + public static Func> Adapt( + IStellaEndpoint 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( + 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); + } + }; + } + + /// + /// Creates a raw handler function from a typed endpoint with response only. + /// + /// The response type. + /// The typed handler. + /// Optional JSON serialization options. + /// A raw handler function. + public static Func> Adapt( + IStellaEndpoint 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); + } + }; + } + + /// + /// Creates a raw handler function from a raw endpoint. + /// + /// The raw handler. + /// A raw handler function. + public static Func> Adapt( + IRawStellaEndpoint handler) + { + return handler.HandleAsync; + } + + private static RawResponse SerializeResponse(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) + }; + } +}