up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
173
src/__Libraries/StellaOps.Provenance/DocumentStubs.cs
Normal file
173
src/__Libraries/StellaOps.Provenance/DocumentStubs.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.Provenance;
|
||||
|
||||
// Minimal stubs for document serialization without external dependencies.
|
||||
public abstract class DocumentValue
|
||||
{
|
||||
public virtual object? Value => null;
|
||||
|
||||
public virtual DocumentObject AsDocumentObject =>
|
||||
this as DocumentObject ?? throw new InvalidCastException("Value is not a DocumentObject.");
|
||||
|
||||
public virtual DocumentArray AsDocumentArray =>
|
||||
this as DocumentArray ?? throw new InvalidCastException("Value is not a DocumentArray.");
|
||||
|
||||
public virtual string AsString => Value?.ToString() ?? string.Empty;
|
||||
public virtual int AsInt32 => Convert.ToInt32(Value);
|
||||
public virtual long AsInt64 => Convert.ToInt64(Value);
|
||||
public virtual double AsDouble => Convert.ToDouble(Value);
|
||||
public virtual bool AsBoolean => Convert.ToBoolean(Value);
|
||||
|
||||
internal static DocumentValue Wrap(object? value) =>
|
||||
value switch
|
||||
{
|
||||
null => DocumentNull.Value,
|
||||
DocumentValue doc => doc,
|
||||
string s => new DocumentString(s),
|
||||
bool b => new DocumentBoolean(b),
|
||||
int i => new DocumentInt32(i),
|
||||
long l => new DocumentInt64(l),
|
||||
double d => new DocumentDouble(d),
|
||||
IEnumerable<DocumentValue> docEnumerable => new DocumentArray(docEnumerable),
|
||||
IEnumerable enumerable => new DocumentArray(enumerable.Cast<object?>()),
|
||||
_ => new DocumentRaw(value)
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class DocumentNull : DocumentValue
|
||||
{
|
||||
public static readonly DocumentNull ValueInstance = new();
|
||||
public new static DocumentNull Value => ValueInstance;
|
||||
}
|
||||
|
||||
public sealed class DocumentString : DocumentValue
|
||||
{
|
||||
public DocumentString(string value) => Value = value;
|
||||
public override object? Value { get; }
|
||||
public override string ToString() => Value?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
public sealed class DocumentBoolean : DocumentValue
|
||||
{
|
||||
public DocumentBoolean(bool value) => Value = value;
|
||||
public override object? Value { get; }
|
||||
}
|
||||
|
||||
public sealed class DocumentInt32 : DocumentValue
|
||||
{
|
||||
public DocumentInt32(int value) => Value = value;
|
||||
public override object? Value { get; }
|
||||
}
|
||||
|
||||
public sealed class DocumentInt64 : DocumentValue
|
||||
{
|
||||
public DocumentInt64(long value) => Value = value;
|
||||
public override object? Value { get; }
|
||||
}
|
||||
|
||||
public sealed class DocumentDouble : DocumentValue
|
||||
{
|
||||
public DocumentDouble(double value) => Value = value;
|
||||
public override object? Value { get; }
|
||||
}
|
||||
|
||||
public sealed class DocumentRaw : DocumentValue
|
||||
{
|
||||
public DocumentRaw(object value) => Value = value;
|
||||
public override object? Value { get; }
|
||||
}
|
||||
|
||||
public record struct DocumentElement(string Name, DocumentValue Value);
|
||||
|
||||
public class DocumentObject : DocumentValue, IEnumerable<KeyValuePair<string, DocumentValue>>
|
||||
{
|
||||
private readonly Dictionary<string, object?> _values = new(StringComparer.Ordinal);
|
||||
|
||||
public DocumentObject()
|
||||
{
|
||||
}
|
||||
|
||||
public DocumentObject(string key, object? value)
|
||||
{
|
||||
_values[key] = value;
|
||||
}
|
||||
|
||||
public DocumentValue this[string key]
|
||||
{
|
||||
get => DocumentValue.Wrap(_values[key]);
|
||||
set => _values[key] = value;
|
||||
}
|
||||
|
||||
public void Add(string key, object? value) => _values.Add(key, value);
|
||||
|
||||
public IEnumerable<DocumentElement> Elements => _values.Select(kvp => new DocumentElement(kvp.Key, DocumentValue.Wrap(kvp.Value)));
|
||||
|
||||
public bool Contains(string key) => ContainsKey(key);
|
||||
|
||||
public bool ContainsKey(string key) => _values.ContainsKey(key);
|
||||
|
||||
public override object? Value => this;
|
||||
|
||||
public override DocumentObject AsDocumentObject => this;
|
||||
|
||||
public IEnumerator<KeyValuePair<string, DocumentValue>> GetEnumerator() =>
|
||||
_values.Select(kvp => new KeyValuePair<string, DocumentValue>(kvp.Key, DocumentValue.Wrap(kvp.Value))).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
public class DocumentArray : DocumentValue, IEnumerable<DocumentValue>
|
||||
{
|
||||
private readonly List<object?> _items = new();
|
||||
|
||||
public DocumentArray()
|
||||
{
|
||||
}
|
||||
|
||||
public DocumentArray(IEnumerable items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public DocumentArray(IEnumerable<object?> items)
|
||||
: this()
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public DocumentValue this[int index] => DocumentValue.Wrap(_items[index]);
|
||||
|
||||
public void Add(DocumentObject doc) => _items.Add(doc);
|
||||
public void Add(object? value) => _items.Add(value);
|
||||
|
||||
public int Count => _items.Count;
|
||||
|
||||
public override object? Value => this;
|
||||
|
||||
public override DocumentArray AsDocumentArray => this;
|
||||
|
||||
public IEnumerator<DocumentValue> GetEnumerator() => _items.Select(DocumentValue.Wrap).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
public static class DocumentValueExtensions
|
||||
{
|
||||
public static DocumentObject AsDocumentObject(this object? value) => DocumentValue.Wrap(value).AsDocumentObject;
|
||||
public static DocumentArray AsDocumentArray(this object? value) => DocumentValue.Wrap(value).AsDocumentArray;
|
||||
public static string AsString(this object? value) => DocumentValue.Wrap(value).AsString;
|
||||
public static int AsInt32(this object? value) => DocumentValue.Wrap(value).AsInt32;
|
||||
public static long AsInt64(this object? value) => DocumentValue.Wrap(value).AsInt64;
|
||||
public static double AsDouble(this object? value) => DocumentValue.Wrap(value).AsDouble;
|
||||
public static bool AsBoolean(this object? value) => DocumentValue.Wrap(value).AsBoolean;
|
||||
}
|
||||
42
src/__Libraries/StellaOps.Provenance/DsseProvenanceModels.cs
Normal file
42
src/__Libraries/StellaOps.Provenance/DsseProvenanceModels.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Provenance;
|
||||
|
||||
public sealed class DsseKeyInfo
|
||||
{
|
||||
public string KeyId { get; set; } = default!; // e.g. "cosign:SHA256-PKIX:..."
|
||||
public string? Issuer { get; set; } // e.g. Fulcio issuer, KMS URI, X.509 CN
|
||||
public string? Algo { get; set; } // "ECDSA" | "RSA" | "Ed25519" | "Dilithium"
|
||||
}
|
||||
|
||||
public sealed class DsseRekorInfo
|
||||
{
|
||||
public long LogIndex { get; set; } // Rekor log index
|
||||
public string Uuid { get; set; } = default!; // Rekor entry UUID
|
||||
public long? IntegratedTime { get; set; } // unix timestamp (seconds)
|
||||
public long? MirrorSeq { get; set; } // optional mirror sequence in Proof-Market ledger
|
||||
}
|
||||
|
||||
public sealed class DsseChainLink
|
||||
{
|
||||
public string Type { get; set; } = default!; // e.g. "build" | "sbom" | "scan"
|
||||
public string Id { get; set; } = default!; // e.g. "att:build#..."
|
||||
public string Digest { get; set; } = default!; // sha256 of DSSE envelope or payload
|
||||
}
|
||||
|
||||
public sealed class DsseProvenance
|
||||
{
|
||||
public string EnvelopeDigest { get; set; } = default!; // sha256 of envelope (not payload)
|
||||
public string PayloadType { get; set; } = default!; // "application/vnd.in-toto+json"
|
||||
public DsseKeyInfo Key { get; set; } = new();
|
||||
public DsseRekorInfo? Rekor { get; set; }
|
||||
public IReadOnlyCollection<DsseChainLink>? Chain { get; set; }
|
||||
}
|
||||
|
||||
public sealed class TrustInfo
|
||||
{
|
||||
public bool Verified { get; set; } // local cryptographic verification
|
||||
public string? Verifier { get; set; } // e.g. "Authority@stella"
|
||||
public int? Witnesses { get; set; } // number of verified transparency witnesses
|
||||
public double? PolicyScore { get; set; } // lattice / policy score (0..1)
|
||||
}
|
||||
140
src/__Libraries/StellaOps.Provenance/ProvenanceExtensions.cs
Normal file
140
src/__Libraries/StellaOps.Provenance/ProvenanceExtensions.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
namespace StellaOps.Provenance;
|
||||
|
||||
public static class ProvenanceExtensions
|
||||
{
|
||||
private const string ProvenanceFieldName = "provenance";
|
||||
private const string DsseFieldName = "dsse";
|
||||
private const string TrustFieldName = "trust";
|
||||
private const string ChainFieldName = "chain";
|
||||
private static DocumentValue StringOrNull(string? value) =>
|
||||
value is null ? DocumentNull.Value : new DocumentString(value);
|
||||
|
||||
/// <summary>
|
||||
/// Attach DSSE provenance + trust info to an event document in-place.
|
||||
/// Designed for generic DocumentObject-based event envelopes.
|
||||
/// </summary>
|
||||
public static DocumentObject AttachDsseProvenance(
|
||||
this DocumentObject eventDoc,
|
||||
DsseProvenance dsse,
|
||||
TrustInfo trust)
|
||||
{
|
||||
if (eventDoc is null) throw new ArgumentNullException(nameof(eventDoc));
|
||||
if (dsse is null) throw new ArgumentNullException(nameof(dsse));
|
||||
if (trust is null) throw new ArgumentNullException(nameof(trust));
|
||||
|
||||
var dsseDoc = new DocumentObject
|
||||
{
|
||||
{ "envelopeDigest", dsse.EnvelopeDigest },
|
||||
{ "payloadType", dsse.PayloadType },
|
||||
{ "key", new DocumentObject
|
||||
{
|
||||
{ "keyId", dsse.Key.KeyId },
|
||||
{ "issuer", StringOrNull(dsse.Key.Issuer) },
|
||||
{ "algo", StringOrNull(dsse.Key.Algo) }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (dsse.Rekor is not null)
|
||||
{
|
||||
var rekorDoc = new DocumentObject
|
||||
{
|
||||
{ "logIndex", dsse.Rekor.LogIndex },
|
||||
{ "uuid", dsse.Rekor.Uuid }
|
||||
};
|
||||
|
||||
if (dsse.Rekor.IntegratedTime is not null)
|
||||
rekorDoc.Add("integratedTime", dsse.Rekor.IntegratedTime);
|
||||
|
||||
if (dsse.Rekor.MirrorSeq is not null)
|
||||
rekorDoc.Add("mirrorSeq", dsse.Rekor.MirrorSeq);
|
||||
|
||||
dsseDoc.Add("rekor", rekorDoc);
|
||||
}
|
||||
|
||||
if (dsse.Chain is not null && dsse.Chain.Count > 0)
|
||||
{
|
||||
var chainArray = new DocumentArray();
|
||||
foreach (var link in dsse.Chain)
|
||||
{
|
||||
chainArray.Add(new DocumentObject
|
||||
{
|
||||
{ "type", link.Type },
|
||||
{ "id", link.Id },
|
||||
{ "digest", link.Digest }
|
||||
});
|
||||
}
|
||||
|
||||
dsseDoc.Add(ChainFieldName, chainArray);
|
||||
}
|
||||
|
||||
var trustDoc = new DocumentObject
|
||||
{
|
||||
{ "verified", trust.Verified },
|
||||
{ "verifier", StringOrNull(trust.Verifier) }
|
||||
};
|
||||
|
||||
if (trust.Witnesses is not null)
|
||||
trustDoc.Add("witnesses", trust.Witnesses);
|
||||
|
||||
if (trust.PolicyScore is not null)
|
||||
trustDoc.Add("policyScore", trust.PolicyScore);
|
||||
|
||||
var provenanceDoc = new DocumentObject
|
||||
{
|
||||
{ DsseFieldName, dsseDoc }
|
||||
};
|
||||
|
||||
eventDoc[ProvenanceFieldName] = provenanceDoc;
|
||||
eventDoc[TrustFieldName] = trustDoc;
|
||||
|
||||
return eventDoc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to query for "cryptographically proven" events:
|
||||
/// kind + subject.digest.sha256 + presence of Rekor logIndex + trust.verified = true.
|
||||
/// </summary>
|
||||
public static DocumentObject BuildProvenVexFilter(
|
||||
string kind,
|
||||
string subjectDigestSha256)
|
||||
{
|
||||
return new DocumentObject
|
||||
{
|
||||
{ "kind", kind },
|
||||
{ "subject.digest.sha256", subjectDigestSha256 },
|
||||
{ $"{ProvenanceFieldName}.{DsseFieldName}.rekor.logIndex", new DocumentObject("$exists", true) },
|
||||
{ $"{TrustFieldName}.verified", true }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to query for events influencing policy without solid provenance.
|
||||
/// </summary>
|
||||
public static DocumentObject BuildUnprovenEvidenceFilter(
|
||||
IEnumerable<string> kinds)
|
||||
{
|
||||
var kindsArray = new DocumentArray(kinds);
|
||||
|
||||
return new DocumentObject
|
||||
{
|
||||
{
|
||||
"kind", new DocumentObject("$in", kindsArray)
|
||||
},
|
||||
{
|
||||
"$or", new DocumentArray
|
||||
{
|
||||
new DocumentObject
|
||||
{
|
||||
{ $"{TrustFieldName}.verified", new DocumentObject("$ne", true) }
|
||||
},
|
||||
new DocumentObject
|
||||
{
|
||||
{ $"{ProvenanceFieldName}.{DsseFieldName}.rekor.logIndex",
|
||||
new DocumentObject("$exists", false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
203
src/__Libraries/StellaOps.Provenance/ProvenanceJsonParser.cs
Normal file
203
src/__Libraries/StellaOps.Provenance/ProvenanceJsonParser.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Provenance;
|
||||
|
||||
public static class ProvenanceJsonParser
|
||||
{
|
||||
public static (DsseProvenance Dsse, TrustInfo Trust) Parse(JsonElement root, TrustInfo? trustOverride = null)
|
||||
{
|
||||
var dsse = ParseDsse(root);
|
||||
var trust = trustOverride ?? ParseTrust(root) ?? throw new InvalidOperationException("Provenance metadata missing trust block.");
|
||||
return (dsse, trust);
|
||||
}
|
||||
|
||||
public static (DsseProvenance Dsse, TrustInfo Trust) Parse(string json, TrustInfo? trustOverride = null)
|
||||
{
|
||||
using var document = JsonDocument.Parse(json);
|
||||
return Parse(document.RootElement, trustOverride);
|
||||
}
|
||||
|
||||
public static async Task<(DsseProvenance Dsse, TrustInfo Trust)> ParseAsync(
|
||||
Stream utf8JsonStream,
|
||||
TrustInfo? trustOverride = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var document = await JsonDocument.ParseAsync(utf8JsonStream, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
using (document)
|
||||
{
|
||||
return Parse(document.RootElement, trustOverride);
|
||||
}
|
||||
}
|
||||
|
||||
private static DsseProvenance ParseDsse(JsonElement root)
|
||||
{
|
||||
if (!root.TryGetProperty("dsse", out var dsseElement) || dsseElement.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
throw new InvalidOperationException("Provenance metadata missing dsse block.");
|
||||
}
|
||||
|
||||
var keyElement = GetRequiredProperty(dsseElement, "key");
|
||||
var dsse = new DsseProvenance
|
||||
{
|
||||
EnvelopeDigest = GetRequiredString(dsseElement, "envelopeDigest"),
|
||||
PayloadType = GetRequiredString(dsseElement, "payloadType"),
|
||||
Key = new DsseKeyInfo
|
||||
{
|
||||
KeyId = GetRequiredString(keyElement, "keyId"),
|
||||
Issuer = GetOptionalString(keyElement, "issuer"),
|
||||
Algo = GetOptionalString(keyElement, "algo"),
|
||||
},
|
||||
Chain = ParseChain(dsseElement)
|
||||
};
|
||||
|
||||
if (dsseElement.TryGetProperty("rekor", out var rekorElement) && rekorElement.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
dsse.Rekor = new DsseRekorInfo
|
||||
{
|
||||
LogIndex = GetInt64(rekorElement, "logIndex"),
|
||||
Uuid = GetRequiredString(rekorElement, "uuid"),
|
||||
IntegratedTime = GetOptionalInt64(rekorElement, "integratedTime"),
|
||||
MirrorSeq = GetOptionalInt64(rekorElement, "mirrorSeq")
|
||||
};
|
||||
}
|
||||
|
||||
return dsse;
|
||||
}
|
||||
|
||||
private static IReadOnlyCollection<DsseChainLink>? ParseChain(JsonElement dsseElement)
|
||||
{
|
||||
if (!dsseElement.TryGetProperty("chain", out var chainElement) || chainElement.ValueKind != JsonValueKind.Array || chainElement.GetArrayLength() == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var links = new List<DsseChainLink>(chainElement.GetArrayLength());
|
||||
foreach (var entry in chainElement.EnumerateArray())
|
||||
{
|
||||
if (entry.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = GetOptionalString(entry, "type");
|
||||
var id = GetOptionalString(entry, "id");
|
||||
var digest = GetOptionalString(entry, "digest");
|
||||
|
||||
if (string.IsNullOrEmpty(type) || string.IsNullOrEmpty(id) || string.IsNullOrEmpty(digest))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
links.Add(new DsseChainLink
|
||||
{
|
||||
Type = type,
|
||||
Id = id,
|
||||
Digest = digest
|
||||
});
|
||||
}
|
||||
|
||||
return links.Count == 0 ? null : links;
|
||||
}
|
||||
|
||||
private static TrustInfo? ParseTrust(JsonElement root)
|
||||
{
|
||||
if (!root.TryGetProperty("trust", out var trustElement) || trustElement.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var trust = new TrustInfo
|
||||
{
|
||||
Verified = trustElement.TryGetProperty("verified", out var verified) && verified.ValueKind == JsonValueKind.True,
|
||||
Verifier = GetOptionalString(trustElement, "verifier"),
|
||||
Witnesses = trustElement.TryGetProperty("witnesses", out var witnessesElement) && witnessesElement.TryGetInt32(out var witnesses)
|
||||
? witnesses
|
||||
: null,
|
||||
PolicyScore = trustElement.TryGetProperty("policyScore", out var scoreElement) && scoreElement.TryGetDouble(out var score)
|
||||
? score
|
||||
: null
|
||||
};
|
||||
|
||||
return trust;
|
||||
}
|
||||
|
||||
private static JsonElement GetRequiredProperty(JsonElement parent, string name)
|
||||
{
|
||||
if (!parent.TryGetProperty(name, out var property) || property.ValueKind == JsonValueKind.Null)
|
||||
{
|
||||
throw new InvalidOperationException($"Provenance metadata missing required property {name}.");
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
private static string GetRequiredString(JsonElement parent, string name)
|
||||
{
|
||||
var element = GetRequiredProperty(parent, name);
|
||||
if (element.ValueKind is JsonValueKind.String)
|
||||
{
|
||||
var value = element.GetString();
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Provenance metadata property {name} must be a non-empty string.");
|
||||
}
|
||||
|
||||
private static string? GetOptionalString(JsonElement parent, string name)
|
||||
{
|
||||
if (!parent.TryGetProperty(name, out var element))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return element.ValueKind == JsonValueKind.String ? element.GetString() : null;
|
||||
}
|
||||
|
||||
private static long GetInt64(JsonElement parent, string name)
|
||||
{
|
||||
if (!parent.TryGetProperty(name, out var element))
|
||||
{
|
||||
throw new InvalidOperationException($"Provenance metadata missing {name}.");
|
||||
}
|
||||
|
||||
if (element.TryGetInt64(out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (element.ValueKind == JsonValueKind.String && long.TryParse(element.GetString(), out value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Provenance metadata property {name} must be an integer.");
|
||||
}
|
||||
|
||||
private static long? GetOptionalInt64(JsonElement parent, string name)
|
||||
{
|
||||
if (!parent.TryGetProperty(name, out var element))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (element.TryGetInt64(out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
if (element.ValueKind == JsonValueKind.String && long.TryParse(element.GetString(), out value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Concelier\__Libraries\StellaOps.Concelier.Models\StellaOps.Concelier.Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user