feat: Add UI benchmark driver and scenarios for graph interactions
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
- Introduced `ui_bench_driver.mjs` to read scenarios and fixture manifest, generating a deterministic run plan. - Created `ui_bench_plan.md` outlining the purpose, scope, and next steps for the benchmark. - Added `ui_bench_scenarios.json` containing various scenarios for graph UI interactions. - Implemented tests for CLI commands, ensuring bundle verification and telemetry defaults. - Developed schemas for orchestrator components, including replay manifests and event envelopes. - Added mock API for risk management, including listing and statistics functionalities. - Implemented models for risk profiles and query options to support the new API.
This commit is contained in:
@@ -257,6 +257,7 @@ internal sealed class SurfaceManifestPublisher : ISurfaceManifestPublisher
|
||||
ArtifactDocumentFormat.ComponentFragmentJson => "layer.fragments",
|
||||
ArtifactDocumentFormat.ObservationJson => "observation.json",
|
||||
ArtifactDocumentFormat.SurfaceManifestJson => "surface.manifest",
|
||||
ArtifactDocumentFormat.CompositionRecipeJson => "composition.recipe",
|
||||
ArtifactDocumentFormat.CycloneDxJson => "cdx-json",
|
||||
ArtifactDocumentFormat.CycloneDxProtobuf => "cdx-protobuf",
|
||||
ArtifactDocumentFormat.SpdxJson => "spdx-json",
|
||||
|
||||
@@ -265,8 +265,8 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor
|
||||
pins["policy"] = policy;
|
||||
}
|
||||
|
||||
var (artifactHashes, merkle) = ComputeDeterminismHashes(payloads);
|
||||
merkleRoot = merkle;
|
||||
var (artifactHashes, recipeBytes, recipeSha256) = BuildCompositionRecipe(payloads);
|
||||
merkleRoot = recipeSha256;
|
||||
|
||||
var report = new
|
||||
{
|
||||
@@ -277,12 +277,26 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor
|
||||
concurrencyLimit = _determinism.ConcurrencyLimit,
|
||||
pins = pins,
|
||||
artifacts = artifactHashes,
|
||||
merkleRoot = merkle
|
||||
merkleRoot = recipeSha256
|
||||
};
|
||||
|
||||
var evidence = new Determinism.DeterminismEvidence(artifactHashes, merkle);
|
||||
var evidence = new Determinism.DeterminismEvidence(artifactHashes, recipeSha256);
|
||||
context.Analysis.Set(ScanAnalysisKeys.DeterminismEvidence, evidence);
|
||||
|
||||
// Publish composition recipe as a manifest artifact for offline replay.
|
||||
payloads = payloads.ToList();
|
||||
((List<SurfaceManifestPayload>)payloads).Add(new SurfaceManifestPayload(
|
||||
ArtifactDocumentType.CompositionRecipe,
|
||||
ArtifactDocumentFormat.CompositionRecipeJson,
|
||||
Kind: "composition.recipe",
|
||||
MediaType: "application/vnd.stellaops.composition.recipe+json",
|
||||
Content: recipeBytes,
|
||||
Metadata: new Dictionary<string, string>
|
||||
{
|
||||
["schema"] = "stellaops.composition.recipe@1",
|
||||
["merkleRoot"] = recipeSha256,
|
||||
}));
|
||||
|
||||
var json = JsonSerializer.Serialize(report, JsonOptions);
|
||||
return new SurfaceManifestPayload(
|
||||
ArtifactDocumentType.SurfaceObservation,
|
||||
@@ -293,9 +307,9 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor
|
||||
View: "replay");
|
||||
}
|
||||
|
||||
private static (Dictionary<string, string> Hashes, string MerkleRoot) ComputeDeterminismHashes(IEnumerable<SurfaceManifestPayload> payloads)
|
||||
private static (Dictionary<string, string> Hashes, byte[] RecipeBytes, string RecipeSha256) BuildCompositionRecipe(IEnumerable<SurfaceManifestPayload> payloads)
|
||||
{
|
||||
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
var map = new SortedDictionary<string, string>(StringComparer.Ordinal);
|
||||
using var sha = SHA256.Create();
|
||||
|
||||
foreach (var payload in payloads.OrderBy(p => p.Kind, StringComparer.Ordinal))
|
||||
@@ -304,18 +318,18 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor
|
||||
map[payload.Kind] = digest;
|
||||
}
|
||||
|
||||
// Build Merkle-like root by hashing the ordered list of kind:digest lines.
|
||||
var builder = new StringBuilder();
|
||||
foreach (var kvp in map.OrderBy(kv => kv.Key, StringComparer.Ordinal))
|
||||
var recipe = new
|
||||
{
|
||||
builder.Append(kvp.Key).Append(':').Append(kvp.Value).Append('\n');
|
||||
}
|
||||
schema = "stellaops.composition.recipe@1",
|
||||
artifacts = map, // already sorted
|
||||
};
|
||||
|
||||
var rootBytes = Encoding.UTF8.GetBytes(builder.ToString());
|
||||
var rootHash = sha.ComputeHash(rootBytes);
|
||||
var recipeJson = JsonSerializer.Serialize(recipe, JsonOptions);
|
||||
var recipeBytes = Encoding.UTF8.GetBytes(recipeJson);
|
||||
var rootHash = sha.ComputeHash(recipeBytes);
|
||||
var merkleRoot = Convert.ToHexString(rootHash).ToLowerInvariant();
|
||||
|
||||
return (map, merkleRoot);
|
||||
return (new Dictionary<string, string>(map, StringComparer.OrdinalIgnoreCase), recipeBytes, merkleRoot);
|
||||
}
|
||||
|
||||
private static string? GetReplayBundleUri(ScanJobContext context)
|
||||
|
||||
Reference in New Issue
Block a user