Resolve Concelier/Excititor merge conflicts
This commit is contained in:
		
							
								
								
									
										86
									
								
								src/StellaOps.Zastava.Core/Contracts/AdmissionDecision.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/StellaOps.Zastava.Core/Contracts/AdmissionDecision.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| namespace StellaOps.Zastava.Core.Contracts; | ||||
|  | ||||
| /// <summary> | ||||
| /// Envelope returned by the admission webhook to the Kubernetes API server. | ||||
| /// </summary> | ||||
| public sealed record class AdmissionDecisionEnvelope | ||||
| { | ||||
|     public required string SchemaVersion { get; init; } | ||||
|  | ||||
|     public required AdmissionDecision Decision { get; init; } | ||||
|  | ||||
|     public static AdmissionDecisionEnvelope Create(AdmissionDecision decision, ZastavaContractVersions.ContractVersion contract) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(decision); | ||||
|         return new AdmissionDecisionEnvelope | ||||
|         { | ||||
|             SchemaVersion = contract.ToString(), | ||||
|             Decision = decision | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public bool IsSupported() | ||||
|         => ZastavaContractVersions.IsAdmissionDecisionSupported(SchemaVersion); | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// Canonical admission decision payload. | ||||
| /// </summary> | ||||
| public sealed record class AdmissionDecision | ||||
| { | ||||
|     public required string AdmissionId { get; init; } | ||||
|  | ||||
|     [JsonPropertyName("namespace")] | ||||
|     public required string Namespace { get; init; } | ||||
|  | ||||
|     public required string PodSpecDigest { get; init; } | ||||
|  | ||||
|     public IReadOnlyList<AdmissionImageVerdict> Images { get; init; } = Array.Empty<AdmissionImageVerdict>(); | ||||
|  | ||||
|     public required AdmissionDecisionOutcome Decision { get; init; } | ||||
|  | ||||
|     public int TtlSeconds { get; init; } | ||||
|  | ||||
|     public IReadOnlyDictionary<string, string>? Annotations { get; init; } | ||||
| } | ||||
|  | ||||
| public enum AdmissionDecisionOutcome | ||||
| { | ||||
|     Allow, | ||||
|     Deny | ||||
| } | ||||
|  | ||||
| public sealed record class AdmissionImageVerdict | ||||
| { | ||||
|     public required string Name { get; init; } | ||||
|  | ||||
|     public required string Resolved { get; init; } | ||||
|  | ||||
|     public bool Signed { get; init; } | ||||
|  | ||||
|     [JsonPropertyName("hasSbomReferrers")] | ||||
|     public bool HasSbomReferrers { get; init; } | ||||
|  | ||||
|     public PolicyVerdict PolicyVerdict { get; init; } | ||||
|  | ||||
|     public IReadOnlyList<string> Reasons { get; init; } = Array.Empty<string>(); | ||||
|  | ||||
|     public AdmissionRekorEvidence? Rekor { get; init; } | ||||
|  | ||||
|     public IReadOnlyDictionary<string, string>? Metadata { get; init; } | ||||
| } | ||||
|  | ||||
| public enum PolicyVerdict | ||||
| { | ||||
|     Pass, | ||||
|     Warn, | ||||
|     Fail, | ||||
|     Error | ||||
| } | ||||
|  | ||||
| public sealed record class AdmissionRekorEvidence | ||||
| { | ||||
|     public string? Uuid { get; init; } | ||||
|  | ||||
|     public bool? Verified { get; init; } | ||||
| } | ||||
							
								
								
									
										179
									
								
								src/StellaOps.Zastava.Core/Contracts/RuntimeEvent.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/StellaOps.Zastava.Core/Contracts/RuntimeEvent.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,179 @@ | ||||
| namespace StellaOps.Zastava.Core.Contracts; | ||||
|  | ||||
| /// <summary> | ||||
| /// Envelope published by the observer towards Scanner runtime ingestion. | ||||
| /// </summary> | ||||
| public sealed record class RuntimeEventEnvelope | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Contract identifier consumed by negotiation logic (<c>zastava.runtime.event@v1</c>). | ||||
|     /// </summary> | ||||
|     public required string SchemaVersion { get; init; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Runtime event payload. | ||||
|     /// </summary> | ||||
|     public required RuntimeEvent Event { get; init; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Creates an envelope using the provided runtime contract version. | ||||
|     /// </summary> | ||||
|     public static RuntimeEventEnvelope Create(RuntimeEvent runtimeEvent, ZastavaContractVersions.ContractVersion contract) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(runtimeEvent); | ||||
|         return new RuntimeEventEnvelope | ||||
|         { | ||||
|             SchemaVersion = contract.ToString(), | ||||
|             Event = runtimeEvent | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Checks whether the envelope schema is supported by the current runtime. | ||||
|     /// </summary> | ||||
|     public bool IsSupported() | ||||
|         => ZastavaContractVersions.IsRuntimeEventSupported(SchemaVersion); | ||||
| } | ||||
|  | ||||
| /// <summary> | ||||
| /// Canonical runtime event emitted by the observer. | ||||
| /// </summary> | ||||
| public sealed record class RuntimeEvent | ||||
| { | ||||
|     public required string EventId { get; init; } | ||||
|  | ||||
|     public required DateTimeOffset When { get; init; } | ||||
|  | ||||
|     public required RuntimeEventKind Kind { get; init; } | ||||
|  | ||||
|     public required string Tenant { get; init; } | ||||
|  | ||||
|     public required string Node { get; init; } | ||||
|  | ||||
|     public required RuntimeEngine Runtime { get; init; } | ||||
|  | ||||
|     public required RuntimeWorkload Workload { get; init; } | ||||
|  | ||||
|     public RuntimeProcess? Process { get; init; } | ||||
|  | ||||
|     [JsonPropertyName("loadedLibs")] | ||||
|     public IReadOnlyList<RuntimeLoadedLibrary> LoadedLibraries { get; init; } = Array.Empty<RuntimeLoadedLibrary>(); | ||||
|  | ||||
|     public RuntimePosture? Posture { get; init; } | ||||
|  | ||||
|     public RuntimeDelta? Delta { get; init; } | ||||
|  | ||||
|     public IReadOnlyList<RuntimeEvidence> Evidence { get; init; } = Array.Empty<RuntimeEvidence>(); | ||||
|  | ||||
|     public IReadOnlyDictionary<string, string>? Annotations { get; init; } | ||||
| } | ||||
|  | ||||
| public enum RuntimeEventKind | ||||
| { | ||||
|     ContainerStart, | ||||
|     ContainerStop, | ||||
|     Drift, | ||||
|     PolicyViolation, | ||||
|     AttestationStatus | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimeEngine | ||||
| { | ||||
|     public required string Engine { get; init; } | ||||
|  | ||||
|     public string? Version { get; init; } | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimeWorkload | ||||
| { | ||||
|     public required string Platform { get; init; } | ||||
|  | ||||
|     [JsonPropertyName("namespace")] | ||||
|     public string? Namespace { get; init; } | ||||
|  | ||||
|     public string? Pod { get; init; } | ||||
|  | ||||
|     public string? Container { get; init; } | ||||
|  | ||||
|     public string? ContainerId { get; init; } | ||||
|  | ||||
|     public string? ImageRef { get; init; } | ||||
|  | ||||
|     public RuntimeWorkloadOwner? Owner { get; init; } | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimeWorkloadOwner | ||||
| { | ||||
|     public string? Kind { get; init; } | ||||
|  | ||||
|     public string? Name { get; init; } | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimeProcess | ||||
| { | ||||
|     public int Pid { get; init; } | ||||
|  | ||||
|     public IReadOnlyList<string> Entrypoint { get; init; } = Array.Empty<string>(); | ||||
|  | ||||
|     [JsonPropertyName("entryTrace")] | ||||
|     public IReadOnlyList<RuntimeEntryTrace> EntryTrace { get; init; } = Array.Empty<RuntimeEntryTrace>(); | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimeEntryTrace | ||||
| { | ||||
|     public string? File { get; init; } | ||||
|  | ||||
|     public int? Line { get; init; } | ||||
|  | ||||
|     public string? Op { get; init; } | ||||
|  | ||||
|     public string? Target { get; init; } | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimeLoadedLibrary | ||||
| { | ||||
|     public required string Path { get; init; } | ||||
|  | ||||
|     public long? Inode { get; init; } | ||||
|  | ||||
|     public string? Sha256 { get; init; } | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimePosture | ||||
| { | ||||
|     public bool? ImageSigned { get; init; } | ||||
|  | ||||
|     public string? SbomReferrer { get; init; } | ||||
|  | ||||
|     public RuntimeAttestation? Attestation { get; init; } | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimeAttestation | ||||
| { | ||||
|     public string? Uuid { get; init; } | ||||
|  | ||||
|     public bool? Verified { get; init; } | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimeDelta | ||||
| { | ||||
|     public string? BaselineImageDigest { get; init; } | ||||
|  | ||||
|     public IReadOnlyList<string> ChangedFiles { get; init; } = Array.Empty<string>(); | ||||
|  | ||||
|     public IReadOnlyList<RuntimeNewBinary> NewBinaries { get; init; } = Array.Empty<RuntimeNewBinary>(); | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimeNewBinary | ||||
| { | ||||
|     public required string Path { get; init; } | ||||
|  | ||||
|     public string? Sha256 { get; init; } | ||||
| } | ||||
|  | ||||
| public sealed record class RuntimeEvidence | ||||
| { | ||||
|     public required string Signal { get; init; } | ||||
|  | ||||
|     public string? Value { get; init; } | ||||
| } | ||||
							
								
								
									
										173
									
								
								src/StellaOps.Zastava.Core/Contracts/ZastavaContractVersions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/StellaOps.Zastava.Core/Contracts/ZastavaContractVersions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | ||||
| namespace StellaOps.Zastava.Core.Contracts; | ||||
|  | ||||
| /// <summary> | ||||
| /// Centralises schema identifiers and version negotiation rules for Zastava contracts. | ||||
| /// </summary> | ||||
| public static class ZastavaContractVersions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Current local runtime event contract (major version 1). | ||||
|     /// </summary> | ||||
|     public static ContractVersion RuntimeEvent { get; } = new("zastava.runtime.event", new Version(1, 0)); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Current local admission decision contract (major version 1). | ||||
|     /// </summary> | ||||
|     public static ContractVersion AdmissionDecision { get; } = new("zastava.admission.decision", new Version(1, 0)); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Determines whether the provided schema string is supported for runtime events. | ||||
|     /// </summary> | ||||
|     public static bool IsRuntimeEventSupported(string schemaVersion) | ||||
|         => ContractVersion.TryParse(schemaVersion, out var candidate) && candidate.IsCompatibleWith(RuntimeEvent); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Determines whether the provided schema string is supported for admission decisions. | ||||
|     /// </summary> | ||||
|     public static bool IsAdmissionDecisionSupported(string schemaVersion) | ||||
|         => ContractVersion.TryParse(schemaVersion, out var candidate) && candidate.IsCompatibleWith(AdmissionDecision); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Selects the newest runtime event contract shared between the local implementation and a remote peer. | ||||
|     /// </summary> | ||||
|     public static ContractVersion NegotiateRuntimeEvent(IEnumerable<string> offeredSchemaVersions) | ||||
|         => Negotiate(RuntimeEvent, offeredSchemaVersions); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Selects the newest admission decision contract shared between the local implementation and a remote peer. | ||||
|     /// </summary> | ||||
|     public static ContractVersion NegotiateAdmissionDecision(IEnumerable<string> offeredSchemaVersions) | ||||
|         => Negotiate(AdmissionDecision, offeredSchemaVersions); | ||||
|  | ||||
|     private static ContractVersion Negotiate(ContractVersion local, IEnumerable<string> offered) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(offered); | ||||
|  | ||||
|         ContractVersion? best = null; | ||||
|         foreach (var entry in offered) | ||||
|         { | ||||
|             if (!ContractVersion.TryParse(entry, out var candidate)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (!candidate.Schema.Equals(local.Schema, StringComparison.Ordinal)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (candidate.Version.Major != local.Version.Major) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (candidate.Version > local.Version) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (best is null || candidate.Version > best.Value.Version) | ||||
|             { | ||||
|                 best = candidate; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return best ?? local; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Represents a schema + semantic version pairing in canonical form. | ||||
|     /// </summary> | ||||
|     public readonly record struct ContractVersion | ||||
|     { | ||||
|         public ContractVersion(string schema, Version version) | ||||
|         { | ||||
|             if (string.IsNullOrWhiteSpace(schema)) | ||||
|             { | ||||
|                 throw new ArgumentException("Schema cannot be null or whitespace.", nameof(schema)); | ||||
|             } | ||||
|  | ||||
|             Schema = schema.Trim(); | ||||
|             Version = new Version(Math.Max(version.Major, 0), Math.Max(version.Minor, 0)); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Schema identifier (e.g. <c>zastava.runtime.event</c>). | ||||
|         /// </summary> | ||||
|         public string Schema { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Major/minor version recognised by the implementation. | ||||
|         /// </summary> | ||||
|         public Version Version { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Canonical string representation (schema@vMajor.Minor). | ||||
|         /// </summary> | ||||
|         public override string ToString() | ||||
|             => $"{Schema}@v{Version.ToString(2, CultureInfo.InvariantCulture)}"; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Determines whether a remote contract is compatible with the local definition. | ||||
|         /// </summary> | ||||
|         public bool IsCompatibleWith(ContractVersion local) | ||||
|         { | ||||
|             if (!Schema.Equals(local.Schema, StringComparison.Ordinal)) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if (Version.Major != local.Version.Major) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             return Version <= local.Version; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Attempts to parse a schema string in canonical format. | ||||
|         /// </summary> | ||||
|         public static bool TryParse(string? value, out ContractVersion contract) | ||||
|         { | ||||
|             contract = default; | ||||
|             if (string.IsNullOrWhiteSpace(value)) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             var trimmed = value.Trim(); | ||||
|             var separator = trimmed.IndexOf('@'); | ||||
|             if (separator < 0) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             var schema = trimmed[..separator]; | ||||
|             if (!schema.Contains('.', StringComparison.Ordinal)) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             var versionToken = trimmed[(separator + 1)..]; | ||||
|             if (versionToken.Length == 0) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if (versionToken[0] is 'v' or 'V') | ||||
|             { | ||||
|                 versionToken = versionToken[1..]; | ||||
|             } | ||||
|  | ||||
|             if (!Version.TryParse(versionToken, out var parsed)) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             var canonical = new Version(Math.Max(parsed.Major, 0), Math.Max(parsed.Minor, 0)); | ||||
|             contract = new ContractVersion(schema, canonical); | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/StellaOps.Zastava.Core/GlobalUsings.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/StellaOps.Zastava.Core/GlobalUsings.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| global using System.Collections.Generic; | ||||
| global using System.Collections.Immutable; | ||||
| global using System.Diagnostics; | ||||
| global using System.Diagnostics.Metrics; | ||||
| global using System.Security.Cryptography; | ||||
| global using System.Text; | ||||
| global using System.Text.Json; | ||||
| global using System.Text.Json.Serialization; | ||||
| global using System.Text.Json.Serialization.Metadata; | ||||
| global using System.Globalization; | ||||
							
								
								
									
										59
									
								
								src/StellaOps.Zastava.Core/Hashing/ZastavaHashing.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/StellaOps.Zastava.Core/Hashing/ZastavaHashing.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| using StellaOps.Zastava.Core.Serialization; | ||||
|  | ||||
| namespace StellaOps.Zastava.Core.Hashing; | ||||
|  | ||||
| /// <summary> | ||||
| /// Produces deterministic multihashes for runtime and admission payloads. | ||||
| /// </summary> | ||||
| public static class ZastavaHashing | ||||
| { | ||||
|     public const string DefaultAlgorithm = "sha256"; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Serialises the payload using canonical options and computes a multihash string. | ||||
|     /// </summary> | ||||
|     public static string ComputeMultihash<T>(T value, string? algorithm = null) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(value); | ||||
|         var bytes = ZastavaCanonicalJsonSerializer.SerializeToUtf8Bytes(value); | ||||
|         return ComputeMultihash(bytes, algorithm); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Computes a multihash string from the provided payload. | ||||
|     /// </summary> | ||||
|     public static string ComputeMultihash(ReadOnlySpan<byte> payload, string? algorithm = null) | ||||
|     { | ||||
|         var normalized = NormalizeAlgorithm(algorithm); | ||||
|         var digest = normalized switch | ||||
|         { | ||||
|             "sha256" => SHA256.HashData(payload), | ||||
|             "sha512" => SHA512.HashData(payload), | ||||
|             _ => throw new NotSupportedException($"Hash algorithm '{normalized}' is not supported.") | ||||
|         }; | ||||
|  | ||||
|         return $"{normalized}-{ToBase64Url(digest)}"; | ||||
|     } | ||||
|  | ||||
|     private static string NormalizeAlgorithm(string? algorithm) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(algorithm)) | ||||
|         { | ||||
|             return DefaultAlgorithm; | ||||
|         } | ||||
|  | ||||
|         var normalized = algorithm.Trim().ToLowerInvariant(); | ||||
|         return normalized switch | ||||
|         { | ||||
|             "sha-256" or "sha256" => "sha256", | ||||
|             "sha-512" or "sha512" => "sha512", | ||||
|             _ => normalized | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     private static string ToBase64Url(ReadOnlySpan<byte> bytes) | ||||
|     { | ||||
|         var base64 = Convert.ToBase64String(bytes); | ||||
|         return base64.TrimEnd('=').Replace('+', '-').Replace('/', '_'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,110 @@ | ||||
| namespace StellaOps.Zastava.Core.Serialization; | ||||
|  | ||||
| /// <summary> | ||||
| /// Deterministic serializer used for runtime/admission contracts. | ||||
| /// </summary> | ||||
| public static class ZastavaCanonicalJsonSerializer | ||||
| { | ||||
|     private static readonly JsonSerializerOptions CompactOptions = CreateOptions(writeIndented: false); | ||||
|     private static readonly JsonSerializerOptions PrettyOptions = CreateOptions(writeIndented: true); | ||||
|  | ||||
|     private static readonly IReadOnlyDictionary<Type, string[]> PropertyOrderOverrides = new Dictionary<Type, string[]> | ||||
|     { | ||||
|         { typeof(RuntimeEventEnvelope), new[] { "schemaVersion", "event" } }, | ||||
|         { typeof(RuntimeEvent), new[] { "eventId", "when", "kind", "tenant", "node", "runtime", "workload", "process", "loadedLibs", "posture", "delta", "evidence", "annotations" } }, | ||||
|         { typeof(RuntimeEngine), new[] { "engine", "version" } }, | ||||
|         { typeof(RuntimeWorkload), new[] { "platform", "namespace", "pod", "container", "containerId", "imageRef", "owner" } }, | ||||
|         { typeof(RuntimeWorkloadOwner), new[] { "kind", "name" } }, | ||||
|         { typeof(RuntimeProcess), new[] { "pid", "entrypoint", "entryTrace" } }, | ||||
|         { typeof(RuntimeEntryTrace), new[] { "file", "line", "op", "target" } }, | ||||
|         { typeof(RuntimeLoadedLibrary), new[] { "path", "inode", "sha256" } }, | ||||
|         { typeof(RuntimePosture), new[] { "imageSigned", "sbomReferrer", "attestation" } }, | ||||
|         { typeof(RuntimeAttestation), new[] { "uuid", "verified" } }, | ||||
|         { typeof(RuntimeDelta), new[] { "baselineImageDigest", "changedFiles", "newBinaries" } }, | ||||
|         { typeof(RuntimeNewBinary), new[] { "path", "sha256" } }, | ||||
|         { typeof(RuntimeEvidence), new[] { "signal", "value" } }, | ||||
|         { typeof(AdmissionDecisionEnvelope), new[] { "schemaVersion", "decision" } }, | ||||
|         { typeof(AdmissionDecision), new[] { "admissionId", "namespace", "podSpecDigest", "images", "decision", "ttlSeconds", "annotations" } }, | ||||
|         { typeof(AdmissionImageVerdict), new[] { "name", "resolved", "signed", "hasSbomReferrers", "policyVerdict", "reasons", "rekor", "metadata" } }, | ||||
|         { typeof(AdmissionRekorEvidence), new[] { "uuid", "verified" } }, | ||||
|         { typeof(ZastavaContractVersions.ContractVersion), new[] { "schema", "version" } } | ||||
|     }; | ||||
|  | ||||
|     public static string Serialize<T>(T value) | ||||
|         => JsonSerializer.Serialize(value, CompactOptions); | ||||
|  | ||||
|     public static string SerializeIndented<T>(T value) | ||||
|         => JsonSerializer.Serialize(value, PrettyOptions); | ||||
|  | ||||
|     public static byte[] SerializeToUtf8Bytes<T>(T value) | ||||
|         => JsonSerializer.SerializeToUtf8Bytes(value, CompactOptions); | ||||
|  | ||||
|     public static T Deserialize<T>(string json) | ||||
|         => JsonSerializer.Deserialize<T>(json, CompactOptions)!; | ||||
|  | ||||
|     private static JsonSerializerOptions CreateOptions(bool writeIndented) | ||||
|     { | ||||
|         var options = new JsonSerializerOptions | ||||
|         { | ||||
|             PropertyNamingPolicy = JsonNamingPolicy.CamelCase, | ||||
|             DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, | ||||
|             DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, | ||||
|             WriteIndented = writeIndented, | ||||
|             Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping | ||||
|         }; | ||||
|  | ||||
|         var baselineResolver = options.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver(); | ||||
|         options.TypeInfoResolver = new DeterministicTypeInfoResolver(baselineResolver); | ||||
|         options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, allowIntegerValues: false)); | ||||
|         return options; | ||||
|     } | ||||
|  | ||||
|     private sealed class DeterministicTypeInfoResolver : IJsonTypeInfoResolver | ||||
|     { | ||||
|         private readonly IJsonTypeInfoResolver inner; | ||||
|  | ||||
|         public DeterministicTypeInfoResolver(IJsonTypeInfoResolver inner) | ||||
|         { | ||||
|             this.inner = inner ?? throw new ArgumentNullException(nameof(inner)); | ||||
|         } | ||||
|  | ||||
|         public JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) | ||||
|         { | ||||
|             var info = inner.GetTypeInfo(type, options); | ||||
|             if (info is null) | ||||
|             { | ||||
|                 throw new InvalidOperationException($"Unable to resolve JsonTypeInfo for '{type}'."); | ||||
|             } | ||||
|  | ||||
|             if (info.Kind is JsonTypeInfoKind.Object && info.Properties is { Count: > 1 }) | ||||
|             { | ||||
|                 var ordered = info.Properties | ||||
|                     .OrderBy(property => GetPropertyOrder(type, property.Name)) | ||||
|                     .ThenBy(property => property.Name, StringComparer.Ordinal) | ||||
|                     .ToArray(); | ||||
|  | ||||
|                 info.Properties.Clear(); | ||||
|                 foreach (var property in ordered) | ||||
|                 { | ||||
|                     info.Properties.Add(property); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return info; | ||||
|         } | ||||
|  | ||||
|         private static int GetPropertyOrder(Type type, string propertyName) | ||||
|         { | ||||
|             if (PropertyOrderOverrides.TryGetValue(type, out var order)) | ||||
|             { | ||||
|                 var index = Array.IndexOf(order, propertyName); | ||||
|                 if (index >= 0) | ||||
|                 { | ||||
|                     return index; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return int.MaxValue; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										20
									
								
								src/StellaOps.Zastava.Core/StellaOps.Zastava.Core.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/StellaOps.Zastava.Core/StellaOps.Zastava.Core.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net10.0</TargetFramework> | ||||
|     <LangVersion>preview</LangVersion> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|     <TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Diagnostics.Abstractions" Version="8.0.0" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\StellaOps.Authority\StellaOps.Auth.Client\StellaOps.Auth.Client.csproj" /> | ||||
|     <ProjectReference Include="..\StellaOps.Auth.Security\StellaOps.Auth.Security.csproj" /> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
							
								
								
									
										10
									
								
								src/StellaOps.Zastava.Core/TASKS.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/StellaOps.Zastava.Core/TASKS.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| # Zastava Core Task Board | ||||
|  | ||||
| | ID | Status | Owner(s) | Depends on | Description | Exit Criteria | | ||||
| |----|--------|----------|------------|-------------|---------------| | ||||
| | ZASTAVA-CORE-12-201 | DOING (2025-10-19) | Zastava Core Guild | — | Define runtime event/admission DTOs, hashing helpers, and versioning strategy. | DTOs cover runtime events and admission verdict envelopes with canonical JSON schema; hashing helpers accept payloads and yield deterministic multihash outputs; version negotiation rules documented and exercised by serialization tests. | | ||||
| | ZASTAVA-CORE-12-202 | DOING (2025-10-19) | Zastava Core Guild | — | Provide configuration/logging/metrics utilities shared by Observer/Webhook. | Shared options bind from configuration with validation; logging scopes/metrics exporters registered via reusable DI extension; integration test host demonstrates Observer/Webhook consumption with deterministic instrumentation. | | ||||
| | ZASTAVA-CORE-12-203 | DOING (2025-10-19) | Zastava Core Guild | — | Authority client helpers, OpTok caching, and security guardrails for runtime services. | Typed Authority client surfaces OpTok retrieval + renewal with configurable cache; guardrails enforce DPoP/mTLS expectations and emit structured audit logs; negative-path tests cover expired/invalid tokens and configuration toggles. | | ||||
| | ZASTAVA-OPS-12-204 | DOING (2025-10-19) | Zastava Core Guild | — | Operational runbooks, alert rules, and dashboard exports for runtime plane. | Runbooks capture install/upgrade/rollback + incident handling; alert rules and dashboard JSON exported for Prometheus/Grafana bundle; docs reference Offline Kit packaging and verification checklist. | | ||||
|  | ||||
| > Remark (2025-10-19): Prerequisites reviewed—none outstanding. ZASTAVA-CORE-12-201, ZASTAVA-CORE-12-202, ZASTAVA-CORE-12-203, and ZASTAVA-OPS-12-204 moved to DOING for Wave 0 kickoff. | ||||
		Reference in New Issue
	
	Block a user