From b6b9ffc050b55d812f794da0770419e49be5c6cd Mon Sep 17 00:00:00 2001 From: StellaOps Bot Date: Sat, 22 Nov 2025 14:02:49 +0200 Subject: [PATCH] Add PHP Analyzer Plugin and Composer Lock Data Handling - Implemented the PhpAnalyzerPlugin to analyze PHP projects. - Created ComposerLockData class to represent data from composer.lock files. - Developed ComposerLockReader to load and parse composer.lock files asynchronously. - Introduced ComposerPackage class to encapsulate package details. - Added PhpPackage class to represent PHP packages with metadata and evidence. - Implemented PhpPackageCollector to gather packages from ComposerLockData. - Created PhpLanguageAnalyzer to perform analysis and emit results. - Added capability signals for known PHP frameworks and CMS. - Developed unit tests for the PHP language analyzer and its components. - Included sample composer.lock and expected output for testing. - Updated project files for the new PHP analyzer library and tests. --- ...PRINT_0110_0001_0001_ingestion_evidence.md | 3 + .../SPRINT_0113_0001_0002_concelier_ii.md | 7 +- .../SPRINT_0114_0001_0003_concelier_iii.md | 3 + .../SPRINT_0119_0001_0001_excititor_i.md | 47 +- .../SPRINT_0120_0000_0001_policy_reasoning.md | 3 +- .../SPRINT_0131_0001_0001_scanner_surface.md | 9 +- ...RINT_0138_0000_0001_scanner_ruby_parity.md | 5 +- .../SPRINT_0140_0001_0001_runtime_signals.md | 12 +- .../SPRINT_0141_0001_0001_graph_indexer.md | 12 +- ..._0144_0001_0001_zastava_runtime_signals.md | 12 +- docs/implplan/SPRINT_0201_0001_0001_cli_i.md | 67 + .../SPRINT_0206_0001_0001_devportal.md | 40 + docs/implplan/SPRINT_0207_0001_0001_graph.md | 79 + docs/implplan/SPRINT_0208_0001_0001_sdk.md | 71 + docs/implplan/SPRINT_0209_0001_0001_ui_i.md | 78 + docs/implplan/SPRINT_0212_0001_0001_web_i.md | 77 + ...1_0001_0001_reachability_evidence_chain.md | 125 + docs/implplan/SPRINT_0512_0001_0001_bench.md | 93 +- .../SPRINT_0513_0001_0001_provenance.md | 71 + ...4_0001_0001_sovereign_crypto_enablement.md | 73 +- docs/implplan/SPRINT_201_cli_i.md | 27 - docs/implplan/SPRINT_206_devportal.md | 15 - docs/implplan/SPRINT_207_graph.md | 21 - docs/implplan/SPRINT_208_sdk.md | 21 - docs/implplan/SPRINT_209_ui_i.md | 28 - docs/implplan/SPRINT_212_web_i.md | 37 - .../SPRINT_401_reachability_evidence_chain.md | 73 - docs/implplan/SPRINT_513_provenance.md | 26 - docs/implplan/tasks-all.md | 436 +- .../concelier/linkset-correlation-21-002.md | 2 +- .../findings-ledger/airgap-provenance.md | 1 + docs/modules/findings-ledger/observability.md | 4 +- docs/modules/graph/architecture.md | 6 +- docs/modules/graph/packaging.md | 31 + .../sbomservice/fixtures/lnm-v1/README.md | 21 + ...prep-sbom-service-guild-cartographer-ob.md | 31 + .../runbooks/airgap-parity-review.md | 31 + .../scanner/design/deno-runtime-shim.md | 23 +- .../manifest.json | 22 + .../StellaOps.Cli/Commands/CommandFactory.cs | 226 +- .../StellaOps.Cli/Commands/CommandHandlers.cs | 121 +- .../Models/AdvisoryAi/AdvisoryAiModels.cs | 7 + src/Cli/StellaOps.Cli/TASKS.md | 1 + .../Commands/CommandHandlersTests.cs | 104 + .../Linksets/AdvisoryLinksetNormalization.cs | 1 + .../Linksets/LinksetCorrelation.cs | 27 +- ...soryLinksetNormalizationConfidenceTests.cs | 16 + .../AdvisoryObservationAggregationTests.cs | 1 + ...AdvisoryObservationTransportWorkerTests.cs | 4 +- .../StellaOps.DevPortal.Site/.gitignore | 5 + .../StellaOps.DevPortal.Site/TASKS.md | 12 + .../StellaOps.DevPortal.Site/astro.config.mjs | 69 + .../package-lock.json | 8298 +++++++++++++++++ .../StellaOps.DevPortal.Site/package.json | 29 + .../public/api/stella.yaml | 1542 +++ .../StellaOps.DevPortal.Site/public/logo.svg | 13 + .../scripts/sync-spec.mjs | 32 + .../src/content/config.ts | 17 + .../src/content/docs/api-reference.mdx | 37 + .../content/docs/guides/getting-started.mdx | 38 + .../content/docs/guides/navigation-search.mdx | 24 + .../src/content/docs/index.mdx | 30 + .../src/content/docs/release-notes.mdx | 15 + .../StellaOps.DevPortal.Site/src/env.d.ts | 2 + .../src/styles/custom.css | 45 + .../StellaOps.DevPortal.Site/tsconfig.json | 7 + .../VexLinksetObservationRefCore.cs | 34 + .../AirgapAndOrchestratorServiceTests.cs | 98 + .../Contracts/AirgapImportContracts.cs | 37 + .../Contracts/OrchestratorExportContracts.cs | 37 + .../Program.cs | 94 + .../Services/AttestationQueryService.cs | 2 +- .../Services/ExportQueryService.cs | 2 +- .../Domain/LedgerChainIdGenerator.cs | 11 +- .../Domain/LedgerEventConstants.cs | 18 + .../Domain/ProjectionModels.cs | 5 + .../AirGap/AirgapImportRecord.cs | 16 + .../AirGap/IAirgapImportRepository.cs | 6 + .../Exports/IOrchestratorExportRepository.cs | 8 + .../Exports/OrchestratorExportRecord.cs | 15 + .../Merkle/LedgerAnchorQueue.cs | 7 +- .../Merkle/LedgerMerkleAnchorWorker.cs | 7 + .../Postgres/LedgerDataSource.cs | 17 +- .../PostgresAirgapImportRepository.cs | 94 + .../PostgresFindingProjectionRepository.cs | 59 +- .../Postgres/PostgresLedgerEventRepository.cs | 8 +- .../Postgres/PostgresLedgerEventStream.cs | 2 +- .../PostgresMerkleAnchorRepository.cs | 2 +- .../PostgresOrchestratorExportRepository.cs | 146 + .../Projection/LedgerProjectionWorker.cs | 24 + .../Observability/LedgerMetrics.cs | 126 +- .../Observability/LedgerTimeline.cs | 37 + .../Services/AirgapImportService.cs | 152 + .../Services/LedgerProjectionReducer.cs | 10 + .../Services/OrchestratorExportService.cs | 86 + .../StellaOps.Findings.Ledger/TASKS.md | 9 + .../migrations/006_orchestrator_airgap.sql | 51 + .../tools/LedgerReplayHarness/Program.cs | 3 +- .../InlinePolicyEvaluationServiceTests.cs | 10 + .../LedgerProjectionReducerTests.cs | 25 + src/Graph/AGENTS.md | 63 + .../Analytics/GraphAnalyticsEngine.cs | 471 + .../Analytics/GraphAnalyticsHostedService.cs | 53 + .../Analytics/GraphAnalyticsMetrics.cs | 88 + .../Analytics/GraphAnalyticsOptions.cs | 31 + .../Analytics/GraphAnalyticsPipeline.cs | 72 + ...aphAnalyticsServiceCollectionExtensions.cs | 36 + .../Analytics/GraphAnalyticsTypes.cs | 40 + .../Analytics/GraphAnalyticsWriterOptions.cs | 9 + .../Analytics/InMemoryGraphAnalyticsWriter.cs | 26 + .../InMemoryGraphSnapshotProvider.cs | 35 + .../Analytics/MongoGraphAnalyticsWriter.cs | 116 + .../Incremental/GraphBackfillMetrics.cs | 89 + .../Incremental/GraphChangeEvent.cs | 28 + .../Incremental/GraphChangeStreamOptions.cs | 12 + .../Incremental/GraphChangeStreamProcessor.cs | 119 + ...ChangeStreamServiceCollectionExtensions.cs | 28 + .../Incremental/InMemoryIdempotencyStore.cs | 21 + .../Properties/AssemblyInfo.cs | 3 + .../StellaOps.Graph.Indexer.csproj | 1 + .../GraphAnalyticsEngineTests.cs | 27 + .../GraphAnalyticsPipelineTests.cs | 32 + .../GraphAnalyticsTestData.cs | 78 + .../GraphChangeStreamProcessorTests.cs | 110 + .../StellaOps.Graph.Indexer.Tests.csproj | 3 + .../PromotionAttestation.cs | 39 +- src/Scanner/StellaOps.Scanner.sln | 30 + .../DenoLanguageAnalyzer.cs | 3 + .../Internal/Runtime/DenoRuntimeShim.cs | 507 +- .../GlobalUsings.cs | 11 + .../Internal/ComposerLockData.cs | 48 + .../Internal/ComposerLockReader.cs | 122 + .../Internal/ComposerPackage.cs | 11 + .../Internal/PhpCapabilitySignals.cs | 32 + .../Internal/PhpPackage.cs | 83 + .../Internal/PhpPackageCollector.cs | 27 + .../PhpAnalyzerPlugin.cs | 16 + .../PhpLanguageAnalyzer.cs | 38 + ...tellaOps.Scanner.Analyzers.Lang.Php.csproj | 20 + .../Contracts/ScanAnalysisKeys.cs | 2 + .../Fixtures/lang/php/basic/composer.lock | 33 + .../Fixtures/lang/php/basic/expected.json | 58 + .../Php/PhpLanguageAnalyzerTests.cs | 27 + ...ps.Scanner.Analyzers.Lang.Php.Tests.csproj | 46 + .../ObserverServiceCollectionExtensions.cs | 70 +- .../StellaOps.Zastava.Observer.csproj | 3 + .../Surface/RuntimeSurfaceFsClient.cs | 20 +- .../Admission/AdmissionResponseBuilder.cs | 5 + .../RuntimeAdmissionPolicyService.cs | 69 +- .../ServiceCollectionExtensions.cs | 75 +- .../StellaOps.Zastava.Webhook.csproj | 5 +- .../AdmissionResponseBuilderTests.cs | 4 +- .../RuntimeAdmissionPolicyServiceTests.cs | 91 + .../Fixtures/cosign.sig | 1 + .../PromotionAttestationBuilderTests.cs | 78 + .../SignersTests.cs | 164 + ...llaOps.Provenance.Attestation.Tests.csproj | 20 + tools/linksets-ci.sh | 8 +- 158 files changed, 16272 insertions(+), 809 deletions(-) create mode 100644 docs/implplan/SPRINT_0201_0001_0001_cli_i.md create mode 100644 docs/implplan/SPRINT_0206_0001_0001_devportal.md create mode 100644 docs/implplan/SPRINT_0207_0001_0001_graph.md create mode 100644 docs/implplan/SPRINT_0208_0001_0001_sdk.md create mode 100644 docs/implplan/SPRINT_0209_0001_0001_ui_i.md create mode 100644 docs/implplan/SPRINT_0212_0001_0001_web_i.md create mode 100644 docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md create mode 100644 docs/implplan/SPRINT_0513_0001_0001_provenance.md delete mode 100644 docs/implplan/SPRINT_201_cli_i.md delete mode 100644 docs/implplan/SPRINT_206_devportal.md delete mode 100644 docs/implplan/SPRINT_207_graph.md delete mode 100644 docs/implplan/SPRINT_208_sdk.md delete mode 100644 docs/implplan/SPRINT_209_ui_i.md delete mode 100644 docs/implplan/SPRINT_212_web_i.md delete mode 100644 docs/implplan/SPRINT_401_reachability_evidence_chain.md delete mode 100644 docs/implplan/SPRINT_513_provenance.md create mode 100644 docs/modules/graph/packaging.md create mode 100644 docs/modules/sbomservice/fixtures/lnm-v1/README.md create mode 100644 docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md create mode 100644 docs/modules/sbomservice/runbooks/airgap-parity-review.md create mode 100644 plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Php/manifest.json create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/.gitignore create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/TASKS.md create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/astro.config.mjs create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/package-lock.json create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/package.json create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/public/api/stella.yaml create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/public/logo.svg create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/scripts/sync-spec.mjs create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/src/content/config.ts create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/api-reference.mdx create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/guides/getting-started.mdx create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/guides/navigation-search.mdx create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/index.mdx create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/release-notes.mdx create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/src/env.d.ts create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/src/styles/custom.css create mode 100644 src/DevPortal/StellaOps.DevPortal.Site/tsconfig.json create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Observations/VexLinksetObservationRefCore.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger.Tests/AirgapAndOrchestratorServiceTests.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger.WebService/Contracts/AirgapImportContracts.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger.WebService/Contracts/OrchestratorExportContracts.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger/Infrastructure/AirGap/AirgapImportRecord.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger/Infrastructure/AirGap/IAirgapImportRepository.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger/Infrastructure/Exports/IOrchestratorExportRepository.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger/Infrastructure/Exports/OrchestratorExportRecord.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresAirgapImportRepository.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresOrchestratorExportRepository.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger/Services/AirgapImportService.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger/Services/OrchestratorExportService.cs create mode 100644 src/Findings/StellaOps.Findings.Ledger/TASKS.md create mode 100644 src/Findings/StellaOps.Findings.Ledger/migrations/006_orchestrator_airgap.sql create mode 100644 src/Graph/AGENTS.md create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsEngine.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsHostedService.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsMetrics.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsOptions.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsPipeline.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsServiceCollectionExtensions.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsTypes.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsWriterOptions.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/InMemoryGraphAnalyticsWriter.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/InMemoryGraphSnapshotProvider.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Analytics/MongoGraphAnalyticsWriter.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Incremental/GraphBackfillMetrics.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeEvent.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamOptions.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamProcessor.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamServiceCollectionExtensions.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Incremental/InMemoryIdempotencyStore.cs create mode 100644 src/Graph/StellaOps.Graph.Indexer/Properties/AssemblyInfo.cs create mode 100644 src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsEngineTests.cs create mode 100644 src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsPipelineTests.cs create mode 100644 src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsTestData.cs create mode 100644 src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphChangeStreamProcessorTests.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/GlobalUsings.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerLockData.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerLockReader.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerPackage.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpCapabilitySignals.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpPackage.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpPackageCollector.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/PhpAnalyzerPlugin.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/PhpLanguageAnalyzer.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/StellaOps.Scanner.Analyzers.Lang.Php.csproj create mode 100644 src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Fixtures/lang/php/basic/composer.lock create mode 100644 src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Fixtures/lang/php/basic/expected.json create mode 100644 src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Php/PhpLanguageAnalyzerTests.cs create mode 100644 src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests.csproj create mode 100644 tests/Provenance/StellaOps.Provenance.Attestation.Tests/Fixtures/cosign.sig create mode 100644 tests/Provenance/StellaOps.Provenance.Attestation.Tests/PromotionAttestationBuilderTests.cs create mode 100644 tests/Provenance/StellaOps.Provenance.Attestation.Tests/SignersTests.cs create mode 100644 tests/Provenance/StellaOps.Provenance.Attestation.Tests/StellaOps.Provenance.Attestation.Tests.csproj diff --git a/docs/implplan/SPRINT_0110_0001_0001_ingestion_evidence.md b/docs/implplan/SPRINT_0110_0001_0001_ingestion_evidence.md index 533ec4386..1d55ffafb 100644 --- a/docs/implplan/SPRINT_0110_0001_0001_ingestion_evidence.md +++ b/docs/implplan/SPRINT_0110_0001_0001_ingestion_evidence.md @@ -66,6 +66,9 @@ | 2025-11-22 | Retried local restore for Concelier WebService; cancelled at ~30s (no packages downloaded). Tests remain pending CI runner. | Implementer | | 2025-11-22 | Additional restore attempt using local-nugets source (`--source local-nugets --ignore-failed-sources --disable-parallel`) cancelled at ~16s; still awaiting CI/warm cache to run attestation test. | Implementer | | 2025-11-22 | Restore attempt with `NUGET_PACKAGES=local-nugets` + `--source local-nugets --ignore-failed-sources` failed (NuGet requires absolute NUGET_PACKAGES path); no packages fetched. | Implementer | +| 2025-11-22 | Retried restore with absolute `NUGET_PACKAGES=$(pwd)/local-nugets`; still hanging and cancelled at ~10s (no packages downloaded). Tests remain blocked pending CI/warm cache. | Implementer | +| 2025-11-22 | Restore attempt with absolute cache + nuget.org fallback (`NUGET_PACKAGES=/mnt/e/dev/git.stella-ops.org/local-nugets --source local-nugets --source https://api.nuget.org/v3/index.json`) still stalled/cancelled after ~10s; no packages pulled. | Implementer | +| 2025-11-22 | Normalized `tools/linksets-ci.sh` line endings, removed `--no-build`, and forced offline restore against `local-nugets`; restore still hangs >90s even with offline cache, run terminated. BUILD-TOOLING-110-001 remains BLOCKED pending runner with usable restore cache. | Implementer | | 2025-11-22 | Documented Concelier advisory attestation endpoint parameters and safety rules (`docs/modules/concelier/attestation.md`); linked from module architecture. | Implementer | | 2025-11-22 | Published Excititor air-gap + connector trust prep (`docs/modules/excititor/prep/2025-11-22-airgap-56-58-prep.md`), defining import envelope, error catalog, timeline hooks, and signer validation; marked EXCITITOR-AIRGAP-56/57/58 · CONN-TRUST-01-001 DONE. | Implementer | | 2025-11-20 | Completed PREP-FEEDCONN-ICSCISA-02-012-KISA-02-008-FEED: published remediation schedule + hashes at `docs/modules/concelier/prep/2025-11-20-feeds-icscisa-kisa-prep.md`; status set to DONE. | Implementer | diff --git a/docs/implplan/SPRINT_0113_0001_0002_concelier_ii.md b/docs/implplan/SPRINT_0113_0001_0002_concelier_ii.md index 1f8e6c3fe..0d2d22be3 100644 --- a/docs/implplan/SPRINT_0113_0001_0002_concelier_ii.md +++ b/docs/implplan/SPRINT_0113_0001_0002_concelier_ii.md @@ -29,8 +29,8 @@ | 3 | CONCELIER-GRAPH-24-101 | TODO | Depends on 21-002 | Concelier WebService Guild (`src/Concelier/StellaOps.Concelier.WebService`) | `/advisories/summary` bundles observation/linkset metadata (aliases, confidence, conflicts) for graph overlays; upstream values intact. | | 4 | CONCELIER-GRAPH-28-102 | TODO | Depends on 24-101 | Concelier WebService Guild (`src/Concelier/StellaOps.Concelier.WebService`) | Evidence batch endpoints keyed by component sets with provenance/timestamps; no derived severity. | | 5 | CONCELIER-LNM-21-001 | DONE | Start of Link-Not-Merge chain | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Define immutable `advisory_observations` model (per-source fields, version ranges, severity text, provenance metadata, tenant guards). | -| 6 | CONCELIER-LNM-21-002 | DOING | PREP-CONCELIER-LNM-21-002-WAITING-ON-FINALIZE | Concelier Core Guild · Data Science Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Correlation pipelines output linksets with confidence + conflict markers, avoiding value collapse. | -| 7 | CONCELIER-LNM-21-003 | TODO | Depends on 21-002 | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Record disagreements (severity, CVSS, references) as structured conflict entries. | +| 6 | CONCELIER-LNM-21-002 | DONE (2025-11-22) | PREP-CONCELIER-LNM-21-002-WAITING-ON-FINALIZE | Concelier Core Guild · Data Science Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Correlation pipelines output linksets with confidence + conflict markers, avoiding value collapse. | +| 7 | CONCELIER-LNM-21-003 | DONE (2025-11-22) | Depends on 21-002 | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Record disagreements (severity, CVSS, references) as structured conflict entries. | | 8 | CONCELIER-LNM-21-004 | TODO | Depends on 21-003 | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Remove legacy merge/dedup logic; add guardrails/tests to keep ingestion append-only; document linkset supersession. | | 9 | CONCELIER-LNM-21-005 | TODO | Depends on 21-004 | Concelier Core Guild · Platform Events Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Emit `advisory.linkset.updated` events with delta descriptions + observation ids (tenant + provenance only). | | 10 | CONCELIER-LNM-21-101 | TODO | Depends on 21-005 | Concelier Storage Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Provision Mongo collections (`advisory_observations`, `advisory_linksets`) with hashed shard keys, tenant indexes, TTL for ingest metadata. | @@ -55,6 +55,9 @@ | 2025-11-22 | Added LinksetCorrelation helper + updated aggregation to emit confidence/conflicts per LNM-21-002; unit tests added. Targeted `dotnet test ...AdvisoryObservationAggregationTests` failed locally (`invalid test source` vstest issue); requires CI/warmed runner. | Concelier Core | | 2025-11-22 | Added conflict sourceIds propagation to storage documents and mapping; updated storage tests accordingly. `dotnet test ...Concelier.Storage.Mongo.Tests` still fails locally with same vstest argument issue; needs CI runner. | Concelier Core | | 2025-11-22 | Tried `dotnet build src/Concelier/__Libraries/StellaOps.Concelier.Core/StellaOps.Concelier.Core.csproj`; build appears to hang after restore on local harness—no errors emitted; will defer to CI runner to avoid churn. | Concelier Core | +| 2025-11-22 | Fixed nullable handling in `LinksetCorrelation` purl aggregation; built Concelier dependencies and ran `AdvisoryObservationTransportWorkerTests` (pass) on warmed cache. | Implementer | +| 2025-11-22 | Marked CONCELIER-LNM-21-002 DONE: correlation now emits confidence/conflicts deterministically; transport worker test green after nullable fixes and immutable summaries. | Implementer | +| 2025-11-22 | Implemented LNM-21-003: severity/CVSS disagreements now produce structured conflicts (reason codes `severity-mismatch`, `cvss-mismatch`); added regression test. | Implementer | | 2025-11-20 | Started PREP-CONCELIER-GRAPH-21-002 and PREP-CONCELIER-LNM-21-002 (statuses → DOING) after confirming no other owner activity. | Planning | | 2025-11-19 | Assigned PREP owners/dates; see Delivery Tracker. | Planning | | 2025-11-17 | Started CONCELIER-GRAPH-21-001: added raw linkset scopes + relationships (provenance) through contracts, ingest mapper, storage mapping, and sanitization; new Mongo mapping test added. | Implementer | diff --git a/docs/implplan/SPRINT_0114_0001_0003_concelier_iii.md b/docs/implplan/SPRINT_0114_0001_0003_concelier_iii.md index a84d1c71c..d0c0ccc7a 100644 --- a/docs/implplan/SPRINT_0114_0001_0003_concelier_iii.md +++ b/docs/implplan/SPRINT_0114_0001_0003_concelier_iii.md @@ -64,6 +64,8 @@ | 2025-11-22 | Exposed `/internal/orch/*` endpoints (registry upsert, heartbeat ingest, command enqueue/query) in WebService using new store; tasks remain DOING pending worker wiring. | Concelier Implementer | | 2025-11-22 | Worker-side consumption of commands/heartbeats not yet wired; ORCH-32/33/34 remain DOING with WebService side in place. | Concelier Implementer | | 2025-11-22 | WebService build attempt (`dotnet build ...WebService.csproj --no-restore`) failed on pre-existing nullability errors in `LinksetCorrelation.cs`; no new errors from orchestrator endpoints. | Concelier Implementer | +| 2025-11-22 | Reworked `LinksetCorrelation` nullability to unblock build; lingering CS8620 persists after clean rebuild—likely upstream nullable config; needs follow-up. | Concelier Implementer | +| 2025-11-22 | Package cache cleaned; `dotnet build ...WebService.csproj --no-restore` now fails on missing local packages (Polly, IdentityModel, etc.); restore from `local-nugets/` required to re-run compile. | Concelier Implementer | ## Decisions & Risks - Link-Not-Merge and OpenAPI alignment must precede SDK/examples; otherwise downstream clients will drift from canonical facts. @@ -77,6 +79,7 @@ - Concelier module AGENTS charter updated 2025-11-22 to include Sprint 0114 scope and required prep docs; implementers must treat it as read before starting tasks. - Orchestrator registry/command/heartbeat storage now exists with TTL-backed command expiry; WebService/worker wiring still pending—ensure API handlers and SDK align with stored shapes before marking ORCH-32/33/34 DONE. - WebService `/internal/orch/*` endpoints now land registry upserts, heartbeats, and commands into Mongo store; worker consumption and orchestrator authentication scopes still to be validated before closing tasks. +- Build remains blocked by CS8620 nullable mismatch in `LinksetCorrelation.cs` (linkset aggregation); patch applied but nullability config appears to treat warning as error—needs follow-up to clear WebService build. ## Next Checkpoints - Schedule OpenAPI/SDK review once CONCELIER-OAS-61-001 draft ready (date TBD, gated on Sprint 0113 outputs). diff --git a/docs/implplan/SPRINT_0119_0001_0001_excititor_i.md b/docs/implplan/SPRINT_0119_0001_0001_excititor_i.md index 45d888b93..508ad9867 100644 --- a/docs/implplan/SPRINT_0119_0001_0001_excititor_i.md +++ b/docs/implplan/SPRINT_0119_0001_0001_excititor_i.md @@ -35,49 +35,53 @@ | 9 | EXCITITOR-ATTEST-73-001 | DONE (2025-11-17) | Implemented payload spec and storage. | Excititor Core · Attestation Payloads Guild | Emit attestation payloads capturing supplier identity, justification summary, and scope metadata for trust chaining. | | 10 | EXCITITOR-ATTEST-73-002 | DONE (2025-11-17) | Implemented linkage API. | Excititor Core Guild | Provide APIs linking attestation IDs back to observation/linkset/product tuples for provenance citations without derived verdicts. | | 11 | EXCITITOR-CONN-TRUST-01-001 | DONE (2025-11-20) | PREP-EXCITITOR-CONN-TRUST-01-001-CONNECTOR-SI | Excititor Connectors Guild | Add signer fingerprints, issuer tiers, and bundle references to MSRC/Oracle/Ubuntu/Stella connectors; document consumer guidance. | +| 12 | EXCITITOR-AIRGAP-56-001 | DOING (2025-11-22) | Mirror bundle schema from Export Center; fix `VexLinksetObservationRefCore` reference before build green. | Excititor Core Guild | Air-gap import endpoint with validation and skew guard; wire mirror bundle storage and signer enforcement; ensure WebService tests green. | +| 13 | EXCITITOR-AIRGAP-57-001 | TODO | Sealed-mode toggle + error catalog; waits on 56-001 wiring and Export Center manifest. | Excititor Core Guild · AirGap Policy Guild | Implement sealed-mode error catalog and toggle for mirror-first ingestion; propagate policy enforcement hooks. | +| 14 | EXCITITOR-AIRGAP-58-001 | TODO | Portable EvidenceLocker format + bundle manifest from Export Center; depends on 56-001 storage layout. | Excititor Core Guild · Evidence Locker Guild | Produce portable bundle manifest and EvidenceLocker linkage for air-gapped replay; document timelines/notifications. | -### Task Clusters & Readiness -- **Advisory-AI evidence APIs:** 31-001 delivered; 31-003 instrumentation and 31-004 docs pending; ready to start once examples and telemetry fixtures finalize. -- **AirGap ingestion & portable bundles:** 56/57/58 gated on Export Center schema and EvidenceLocker format; need sealed-mode error catalog and timeline mapping. -- **Attestation & provenance chain:** 01-003 harness/diagnostics first, then 73-001 payload spec and 73-002 linkage docs. -- **Connector provenance parity:** Inventory signer metadata, define shared fingerprint/tier schema, update connector acceptance tests. +### Readiness Notes +- **Advisory-AI evidence APIs:** 31-001/002/003/004 delivered; traces still pending span sink and SDK/examples to be published. +- **AirGap ingestion & portable bundles:** 56/57/58 now tracked (56 DOING; 57/58 TODO) and remain gated on Export Center mirror schema + EvidenceLocker portable format. +- **Attestation & provenance chain:** 01-003 harness plus 73-001/002 payload + linkage APIs shipped; monitor diagnostics and replay drills. +- **Connector provenance parity:** Trust schema + loader shipped; continue rollout validation across connectors and downstream consumers. ## Action Tracker | Focus | Action | Owner(s) | Due | Status | | --- | --- | --- | --- | --- | -| Advisory-AI APIs | Publish finalized OpenAPI schema + SDK notes for projection API (31-004). | Excititor WebService Guild · Docs Guild | 2025-11-15 | In review (draft shared 2025-11-13) | +| Advisory-AI APIs | Publish finalized OpenAPI schema + SDK notes for projection API (31-004). | Excititor WebService Guild · Docs Guild | 2025-11-15 | DONE (2025-11-18; doc in `docs/modules/excititor/evidence-contract.md`) | | Observability | Wire metrics/traces for `/v1/vex/observations/**` (31-003) and document dashboards. | Excititor WebService Guild · Observability Guild | 2025-11-16 | PARTIAL (metrics/logs delivered 2025-11-17; traces await span sink) | -| AirGap | Capture mirror bundle schema + sealed-mode toggle requirements for 56/57. | Excititor Core Guild · AirGap Policy Guild | 2025-11-17 | Pending | -| Portable bundles | Draft bundle manifest + EvidenceLocker linkage notes for 58-001. | Excititor Core Guild · Evidence Locker Guild | 2025-11-18 | Pending | -| Attestation | Complete verifier suite + diagnostics for 01-003. | Excititor Attestation Guild | 2025-11-16 | In progress (verifier harness ~80% complete) | -| Connectors | Inventory signer metadata + plan rollout for MSRC/Oracle/Ubuntu/Stella connectors (CONN-TRUST-01-001). | Excititor Connectors Guild | 2025-11-19 | Pending (schema draft expected 2025-11-14) | +| AirGap | Capture mirror bundle schema + sealed-mode toggle requirements for 56/57. | Excititor Core Guild · AirGap Policy Guild | 2025-11-17 | TODO (blocked on Export Center manifest) | +| Portable bundles | Draft bundle manifest + EvidenceLocker linkage notes for 58-001. | Excititor Core Guild · Evidence Locker Guild | 2025-11-18 | TODO | +| Attestation | Complete verifier suite + diagnostics for 01-003. | Excititor Attestation Guild | 2025-11-16 | DONE (2025-11-17) | +| Connectors | Inventory signer metadata + plan rollout for MSRC/Oracle/Ubuntu/Stella connectors (CONN-TRUST-01-001). | Excititor Connectors Guild | 2025-11-19 | DONE (2025-11-20; schema + loader shipped) | ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | -| 2025-11-19 | Assigned PREP owners/dates; see Delivery Tracker. | Planning | -| 2025-11-19 | Marked PREP tasks P1–P4 BLOCKED: mirror bundle schema (Sprint 162), sealed-mode error catalog, EvidenceLocker portable format, and connector signer metadata remain unpublished, keeping EXCITITOR-AIRGAP-56/57/58 and CONN-TRUST-01-001 gated. | Project Mgmt | -| 2025-11-22 | Completed air-gap and attestation rehearsal PREP docs (`docs/modules/excititor/prep/2025-11-22-airgap-56-58-prep.md`, `docs/modules/excititor/prep/2025-11-22-attestation-rehearsal-prep.md`); set P1–P3 and P5 to DONE. | Project Mgmt | -| 2025-11-22 | PREP cleared; moved EXCITITOR-AIRGAP-56-001/57-001/58-001 to TODO. | Project Mgmt | -| 2025-11-22 | Started EXCITITOR-AIRGAP-56-001: added air-gap import endpoint skeleton with validation and skew guard; awaiting mirror bundle storage wiring and signer enforcement. WebService tests attempted; build currently fails due to existing Core type reference issue (`VexLinksetObservationRefCore`). | Implementer | | 2025-11-12 | Snapshot refreshed; 31-001 marked DONE; other tasks pending observability, AirGap schemas, and attestation verifier completion. | Excititor PM | | 2025-11-13 | Added readiness checklists and action tracker; awaiting Export Center mirror schema and Attestor verifier rehearsals. | Excititor PM | | 2025-11-13 | OpenAPI draft for 31-004 shared; observability wiring blocked until Ops deploys span sink. | WebService Guild | | 2025-11-14 | Connector provenance schema review scheduled; Export Center mirror schema still pending, keeping 56/57 blocked. | Connectors Guild | | 2025-11-14 | 31-003 instrumentation (counters, chunk histogram, signature failure + guard-violation meters) merged; telemetry export blocked on span sink rollout. | WebService Guild | -| 2025-11-17 | Added chunk request/response telemetry + signature status counters; `/v1/vex/evidence/chunks` now emits metrics without traces. | WebService Guild | | 2025-11-14 | Published `docs/modules/excititor/operations/observability.md` covering new evidence metrics for Ops/Lens dashboards. | Observability Guild | | 2025-11-16 | Normalized sprint file to standard template, renamed to SPRINT_0119_0001_0001_excititor_i.md, and updated tasks-all references. | Planning | -| P5 | PREP-ATTESTATION-VERIFIER-REHEARSAL-EXCITITOR | DONE (2025-11-22) | Due 2025-11-21 · Accountable: Planning | Planning | Rehearsal harness plan captured in `docs/modules/excititor/prep/2025-11-22-attestation-rehearsal-prep.md`; ready for execution. | | 2025-11-17 | Implemented `/v1/vex/evidence/chunks` NDJSON endpoint and wired DI for chunk service; marked 31-002 DONE. | WebService Guild | +| 2025-11-17 | Added chunk request/response telemetry + signature status counters; `/v1/vex/evidence/chunks` now emits metrics without traces. | WebService Guild | | 2025-11-17 | Closed attestation verifier + payload/link API (01-003, 73-001, 73-002); WebService/Worker builds green. | Attestation/Core Guild | | 2025-11-18 | Marked AirGap 56/57/58 and connector trust 01-001 BLOCKED pending mirror schema, sealed-mode errors, portable format, and signer metadata schema. | Implementer | | 2025-11-18 | Authored Advisory-AI evidence contract doc (`docs/modules/excititor/evidence-contract.md`) covering `/v1/vex/evidence/chunks`, schema, determinism, AOC, telemetry; 31-004 doc deliverable ready. | Implementer | +| 2025-11-19 | Assigned PREP owners/dates; see Delivery Tracker. | Planning | +| 2025-11-19 | Marked PREP tasks P1–P4 BLOCKED: mirror bundle schema (Sprint 162), sealed-mode error catalog, EvidenceLocker portable format, and connector signer metadata remain unpublished, keeping EXCITITOR-AIRGAP-56/57/58 and CONN-TRUST-01-001 gated. | Project Mgmt | | 2025-11-20 | Completed PREP-EXCITITOR-CONN-TRUST-01-001: published connector signer metadata schema, guidance, and sample bundle hash to unblock connector trust rollout. | Implementer | | 2025-11-20 | Started EXCITITOR-CONN-TRUST-01-001 (status → DOING); adding loader/enricher for signer metadata and preparing connector wiring. | Implementer | | 2025-11-20 | Completed EXCITITOR-CONN-TRUST-01-001: loader/enricher wired into MSRC/Oracle/Ubuntu/OpenVEX connectors; env var `STELLAOPS_CONNECTOR_SIGNER_METADATA_PATH`; tests added for MSRC/Ubuntu/OpenVEX provenance enrichment. | Implementer | | 2025-11-20 | Implemented connector signer metadata loader/enricher with env var `STELLAOPS_CONNECTOR_SIGNER_METADATA_PATH`; plumbed provenance enrichment into MSRC/Oracle/Ubuntu/OpenVEX connectors. | Implementer | +| 2025-11-22 | Completed air-gap and attestation rehearsal PREP docs (`docs/modules/excititor/prep/2025-11-22-airgap-56-58-prep.md`, `docs/modules/excititor/prep/2025-11-22-attestation-rehearsal-prep.md`); set P1–P3 and P5 to DONE. | Project Mgmt | +| 2025-11-22 | PREP cleared; moved EXCITITOR-AIRGAP-56-001/57-001/58-001 to TODO. | Project Mgmt | +| 2025-11-22 | Started EXCITITOR-AIRGAP-56-001: added air-gap import endpoint skeleton with validation and skew guard; awaiting mirror bundle storage wiring and signer enforcement. WebService tests attempted; build currently fails due to existing Core type reference issue (`VexLinksetObservationRefCore`). | Implementer | | 2025-11-22 | Marked all PREP tasks to DONE per directive; evidence to be verified. | Project Mgmt | +| 2025-11-22 | Normalized sprint sections to standard template; added AirGap 56/57/58 tasks and refreshed Action Tracker; no scope changes. | Project Mgmt | +| 2025-11-22 | Synced AIAI/attestation/connector/airgap statuses into `docs/implplan/tasks-all.md`; no scope changes. | Project Mgmt | ## Decisions & Risks - **Decisions** @@ -88,16 +92,15 @@ - Observability sinks not ready for 31-003 → reuse Signals dashboards; ship log-only fallback. Severity: Medium. - Mirror bundle schema still absent (blocks 56/57/58) → escalate to Export Center; track due date 2025-11-19; severity: High. - Portable EvidenceLocker format not published (blocks 58-001) → request format drop from Evidence Locker leads; severity: High. - - Connector signer metadata schema missing (blocks CONN-TRUST-01-001) → chase schema artefact owners; severity: Medium. - - Attestation verifier misses 2025-11-16 target → daily stand-ups; parallel diagnostics; severity: High. + - Connector signer metadata rollout validation outstanding → monitor ingestion for MSRC/Oracle/Ubuntu/OpenVEX and gate with feature flags if drift detected. Severity: Medium. + - Attestation verifier regressions during replay drills → keep harness diagnostics enabled; severity: Medium. ## Next Checkpoints | Date (UTC) | Session / Owner | Goal | Fallback | -| 2025-11-18 | Scanner mock bundle v1 delivered | Start GRAPH-INDEX/ZASTAVA tests using mock; publish hash | Scanner Guild | | --- | --- | --- | --- | -| 2025-11-17 | Coordinator · WebService/Observability Guilds | Counters/logs-only fallback approved; start 31-003 execution without span sink. | Keep span sink as follow-on milestone. | | 2025-11-14 | Connector provenance schema review (Connectors + Security Guilds) | Approve signer fingerprint + issuer tier schema for CONN-TRUST-01-001. | If schema not ready, keep task blocked and request interim metadata list from connectors. | | 2025-11-15 | Export Center mirror schema sync (Export Center + Excititor + AirGap) | Receive mirror bundle manifest to unblock 56/57. | If delayed, escalate to Sprint 162 leads and use placeholder spec with clearly marked TODO. | -| P5 | PREP-ATTESTATION-VERIFIER-REHEARSAL-EXCITITOR | DONE (2025-11-22) | Due 2025-11-21 · Accountable: Planning | Planning | Rehearsal harness plan captured in `docs/modules/excititor/prep/2025-11-22-attestation-rehearsal-prep.md`; ready for execution. | +| 2025-11-17 | Coordinator · WebService/Observability Guilds | Counters/logs-only fallback approved; start 31-003 execution without span sink. | Keep span sink as follow-on milestone. | | 2025-11-18 | Observability span sink deploy (Ops/Signals Guild) | Enable telemetry pipeline needed for 31-003. | If deploy slips, implement temporary counters/logs and keep action tracker flagged as blocked. | +| 2025-11-18 | Scanner Guild | Scanner mock bundle v1 delivered; start GRAPH-INDEX/ZASTAVA tests using mock; publish hash. | If mock slips, keep prior sample hash and flag downstream tests at risk. | | 2025-11-19 | Connector metadata inventory (Connectors Guild) | Confirm signer metadata coverage for CONN-TRUST-01-001 rollout. | Fall back to partial coverage with feature flags. | diff --git a/docs/implplan/SPRINT_0120_0000_0001_policy_reasoning.md b/docs/implplan/SPRINT_0120_0000_0001_policy_reasoning.md index 2b2ece68b..5607444d7 100644 --- a/docs/implplan/SPRINT_0120_0000_0001_policy_reasoning.md +++ b/docs/implplan/SPRINT_0120_0000_0001_policy_reasoning.md @@ -43,7 +43,7 @@ | P2 | PREP-LEDGER-34-101-ORCHESTRATOR-LEDGER-EXPORT | DONE (2025-11-22) | Due 2025-11-21 · Accountable: Findings Ledger Guild / `src/Findings/StellaOps.Findings.Ledger` | Findings Ledger Guild / `src/Findings/StellaOps.Findings.Ledger` | Orchestrator export payload defined in `docs/modules/findings-ledger/prep/2025-11-22-ledger-airgap-prep.md`; unblock ledger linkage. | | P3 | PREP-LEDGER-AIRGAP-56-001-MIRROR-BUNDLE-SCHEM | DONE (2025-11-22) | Due 2025-11-21 · Accountable: Findings Ledger Guild / `src/Findings/StellaOps.Findings.Ledger` | Findings Ledger Guild / `src/Findings/StellaOps.Findings.Ledger` | Mirror bundle provenance fields frozen in `docs/modules/findings-ledger/prep/2025-11-22-ledger-airgap-prep.md`; staleness/anchor rules defined. | | 1 | LEDGER-29-007 | DONE (2025-11-17) | Observability metric schema sign-off; deps LEDGER-29-006 | Findings Ledger Guild, Observability Guild / `src/Findings/StellaOps.Findings.Ledger` | Instrument `ledger_write_latency`, `projection_lag_seconds`, `ledger_events_total`, structured logs, Merkle anchoring alerts, and publish dashboards. | -| 2 | LEDGER-29-008 | TODO | PREP-LEDGER-29-008-AWAIT-OBSERVABILITY-SCHEMA | Findings Ledger Guild, QA Guild / `src/Findings/StellaOps.Findings.Ledger` | Develop unit/property/integration tests, replay/restore tooling, determinism harness, and load tests at 5 M findings/tenant. | +| 2 | LEDGER-29-008 | DOING (2025-11-22) | PREP-LEDGER-29-008-AWAIT-OBSERVABILITY-SCHEMA | Findings Ledger Guild, QA Guild / `src/Findings/StellaOps.Findings.Ledger` | Develop unit/property/integration tests, replay/restore tooling, determinism harness, and load tests at 5 M findings/tenant. | | 3 | LEDGER-29-009 | BLOCKED | Depends on LEDGER-29-008 harness results (5 M replay + observability schema) | Findings Ledger Guild, DevOps Guild / `src/Findings/StellaOps.Findings.Ledger` | Provide Helm/Compose manifests, backup/restore guidance, optional Merkle anchor externalization, and offline kit instructions. | | 4 | LEDGER-34-101 | TODO | PREP-LEDGER-34-101-ORCHESTRATOR-LEDGER-EXPORT | Findings Ledger Guild / `src/Findings/StellaOps.Findings.Ledger` | Link orchestrator run ledger exports into Findings Ledger provenance chain, index by artifact hash, and expose audit queries. | | 5 | LEDGER-AIRGAP-56-001 | TODO | PREP-LEDGER-AIRGAP-56-001-MIRROR-BUNDLE-SCHEM | Findings Ledger Guild / `src/Findings/StellaOps.Findings.Ledger` | Record bundle provenance (`bundle_id`, `merkle_root`, `time_anchor`) on ledger events for advisories/VEX/policies imported via Mirror Bundles. | @@ -55,6 +55,7 @@ ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-11-22 | Switched LEDGER-29-008 to DOING; created `src/Findings/StellaOps.Findings.Ledger/TASKS.md` mirror for status tracking. | Findings Ledger Guild | | 2025-11-19 | Assigned PREP owners/dates; see Delivery Tracker. | Planning | | 2025-11-19 | Marked PREP tasks P1–P3 BLOCKED: observability schema, orchestrator ledger export contract, and mirror bundle schema are still missing, keeping LEDGER-29-008/34-101/AIRGAP-56-* blocked. | Project Mgmt | | 2025-11-13 09:30 | Documented Findings.I scope, milestones, and external dependencies; awaiting Observability + Orchestrator inputs before flipping any tasks to DOING. | Findings Ledger Guild | diff --git a/docs/implplan/SPRINT_0131_0001_0001_scanner_surface.md b/docs/implplan/SPRINT_0131_0001_0001_scanner_surface.md index 59f755d65..937f6cb4b 100644 --- a/docs/implplan/SPRINT_0131_0001_0001_scanner_surface.md +++ b/docs/implplan/SPRINT_0131_0001_0001_scanner_surface.md @@ -24,7 +24,7 @@ | P1 | PREP-SCANNER-ANALYZERS-JAVA-21-005-TESTS-BLOC | DONE (2025-11-22) | Due 2025-11-22 · Accountable: Java Analyzer Guild | Java Analyzer Guild | Tests blocked: repo build fails in Concelier (CoreLinksets missing) and targeted Java analyzer test run stalls; retry once dependencies fixed or CI available.

Document artefact/deliverable for SCANNER-ANALYZERS-JAVA-21-005 and publish location so downstream tasks can proceed. | | P2 | PREP-SCANNER-ANALYZERS-JAVA-21-008-WAITING-ON | DONE (2025-11-22) | Due 2025-11-22 · Accountable: Java Analyzer Guild | Java Analyzer Guild | Waiting on 21-007 completion and resolver authoring bandwidth.

Document artefact/deliverable for SCANNER-ANALYZERS-JAVA-21-008 and publish location so downstream tasks can proceed. Prep artefact: `docs/modules/scanner/prep/2025-11-20-java-21-008-prep.md`. | | P3 | PREP-SCANNER-ANALYZERS-LANG-11-001-DOTNET-TES | DONE (2025-11-22) | Due 2025-11-22 · Accountable: StellaOps.Scanner EPDR Guild · Language Analyzer Guild | StellaOps.Scanner EPDR Guild · Language Analyzer Guild | `dotnet test` hangs/returns empty output; needs clean runner/CI diagnostics.

Document artefact/deliverable for SCANNER-ANALYZERS-LANG-11-001 and publish location so downstream tasks can proceed. Prep artefact: `docs/modules/scanner/prep/2025-11-20-lang-11-001-prep.md`. | -| 1 | SCANNER-ANALYZERS-DENO-26-009 | BLOCKED (2025-11-19) | Waiting on runtime shim fixtures + CI runner; design `deno-runtime-shim.md` drafted but tests cannot run. | Deno Analyzer Guild · Signals Guild | Optional runtime evidence hooks capturing module loads and permissions with path hashing during harnessed execution. | +| 1 | SCANNER-ANALYZERS-DENO-26-009 | DOING (2025-11-22) | Implement runtime trace shim execution + NDJSON/AnalysisStore alignment; pending CI runner for end-to-end trace. | Deno Analyzer Guild · Signals Guild | Optional runtime evidence hooks capturing module loads and permissions with path hashing during harnessed execution. | | 2 | SCANNER-ANALYZERS-DENO-26-010 | TODO | After 26-009, wire CLI (`stella deno trace`) + Worker/Offline Kit using runtime NDJSON contract. | Deno Analyzer Guild · DevOps Guild | Package analyzer plug-in and surface CLI/worker commands with offline documentation. | | 3 | SCANNER-ANALYZERS-DENO-26-011 | TODO | Implement policy signal emitter using runtime metadata once trace shim lands. | Deno Analyzer Guild | Policy signal emitter for capabilities (net/fs/env/ffi/process/crypto), remote origins, npm usage, wasm modules, and dynamic-import warnings. | | 4 | SCANNER-ANALYZERS-JAVA-21-005 | BLOCKED (2025-11-17) | PREP-SCANNER-ANALYZERS-JAVA-21-005-TESTS-BLOC | Java Analyzer Guild | Framework config extraction: Spring Boot imports, spring.factories, application properties/yaml, Jakarta web.xml/fragments, JAX-RS/JPA/CDI/JAXB configs, logging files, Graal native-image configs. | @@ -62,6 +62,8 @@ | 2025-11-17 | Added runtime shim source helper + test; shim writes `trace-shim.ts` containing runtime capture hooks (module load, permission use, wasm load, npm hint) for offline trace generation. | Implementer | | 2025-11-17 | Re-ran Deno runtime tests after status update; still passing (`dotnet test ...Deno.Tests.csproj --no-restore`). | Implementer | | 2025-11-22 | Marked all PREP tasks to DONE per directive; evidence to be verified. | Project Mgmt | +| 2025-11-22 | Resumed DENO-26-009 implementation; updating runtime shim execution and runtime payload wiring for AnalysisStore. | Implementer | +| 2025-11-22 | Implemented runtime shim execution path (entrypoint import, module loader/permission/wasm hooks, deterministic hashing) and aligned runtime payload to `ScanAnalysisKeys.DenoRuntimePayload`; ran `dotnet test ...Deno.Tests.csproj --filter DenoRuntime --no-restore`. | Implementer | ## Decisions & Risks - Scanner record payload schema still unpinned; drafting prep at `docs/modules/scanner/prep/2025-11-21-scanner-records-prep.md` while waiting for analyzer output confirmation from Scanner Guild. @@ -70,9 +72,10 @@ - `SCANNER-ANALYZERS-JAVA-21-008` blocked (2025-10-27): resolver capacity needed to produce entrypoint/component/edge outputs; downstream tasks remain stalled until resolved. - Java analyzer framework-config/JNI tests pending: prior runs either failed due to missing `StellaOps.Concelier.Storage.Mongo` `CoreLinksets` types or were aborted due to repo-wide restore contention; rerun on clean runner or after Concelier build stabilises. - Deno runtime hook + policy-signal schema drafted in `docs/modules/scanner/design/deno-runtime-signals.md`; shim plan in `docs/modules/scanner/design/deno-runtime-shim.md`. -- Loader/require shim implementation still pending for DENO-26-009; must stay offline-first and AnalysisStore-compatible before wiring DENO-26-010/011. +- Deno runtime shim now emits module/permission/wasm/npm events; needs end-to-end validation on a Deno runner (cached-only) to confirm module loader hook coverage before wiring DENO-26-010/011. +- Runtime payload key aligned to `ScanAnalysisKeys.DenoRuntimePayload` (compat shim keeps legacy `"deno.runtime"`); downstream consumers should read the keyed payload to avoid silent misses. - PREP note for SCANNER-ANALYZERS-JAVA-21-005 published at `docs/modules/scanner/prep/2025-11-20-java-21-005-prep.md`; awaiting CoreLinksets package fix and isolated CI slot before tests can run. - - PREP docs added for SCANNER-ANALYZERS-JAVA-21-008 (`docs/modules/scanner/prep/2025-11-20-java-21-008-prep.md`) and LANG-11-001 (`docs/modules/scanner/prep/2025-11-20-lang-11-001-prep.md`); both depend on resolver outputs/CI isolation. +- PREP docs added for SCANNER-ANALYZERS-JAVA-21-008 (`docs/modules/scanner/prep/2025-11-20-java-21-008-prep.md`) and LANG-11-001 (`docs/modules/scanner/prep/2025-11-20-lang-11-001-prep.md`); both depend on resolver outputs/CI isolation. ## Next Checkpoints | Date (UTC) | Session | Goal | Impacted work | Owner | diff --git a/docs/implplan/SPRINT_0138_0000_0001_scanner_ruby_parity.md b/docs/implplan/SPRINT_0138_0000_0001_scanner_ruby_parity.md index 1c1101cc6..39a75af56 100644 --- a/docs/implplan/SPRINT_0138_0000_0001_scanner_ruby_parity.md +++ b/docs/implplan/SPRINT_0138_0000_0001_scanner_ruby_parity.md @@ -26,7 +26,7 @@ | P5 | PREP-SCANNER-ENG-0014-NEEDS-JOINT-ROADMAP-WIT | DONE (2025-11-22) | Due 2025-11-22 · Accountable: Runtime Guild, Zastava Guild (`docs/modules/scanner`) | Runtime Guild, Zastava Guild (`docs/modules/scanner`) | Needs joint roadmap with Zastava/Runtime guilds for Kubernetes/VM alignment.

Document artefact/deliverable for SCANNER-ENG-0014 and publish location so downstream tasks can proceed. | | 1 | SCANNER-ENG-0008 | DONE (2025-11-16) | Cadence documented; quarterly review workflow published for EntryTrace heuristics. | EntryTrace Guild, QA Guild (`src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace`) | Maintain EntryTrace heuristic cadence per `docs/benchmarks/scanner/scanning-gaps-stella-misses-from-competitors.md`, including explain-trace updates. | | 2 | SCANNER-ENG-0009 | DONE (2025-11-13) | Release handoff to Sprint 0139 consumers; monitor Mongo-backed inventory rollout. | Ruby Analyzer Guild (`src/Scanner/StellaOps.Scanner.Analyzers.Lang.Ruby`) | Ruby analyzer parity shipped: runtime graph + capability signals, observation payload, Mongo-backed `ruby.packages` inventory, CLI/WebService surfaces, and plugin manifest bundles for Worker loadout. | -| 3 | SCANNER-ENG-0010 | BLOCKED | PREP-SCANNER-ENG-0010-AWAIT-COMPOSER-AUTOLOAD | PHP Analyzer Guild (`src/Scanner/StellaOps.Scanner.Analyzers.Lang.Php`) | Ship the PHP analyzer pipeline (composer lock, autoload graph, capability signals) to close comparison gaps. | +| 3 | SCANNER-ENG-0010 | DOING | PREP-SCANNER-ENG-0010-AWAIT-COMPOSER-AUTOLOAD | PHP Analyzer Guild (`src/Scanner/StellaOps.Scanner.Analyzers.Lang.Php`) | Ship the PHP analyzer pipeline (composer lock, autoload graph, capability signals) to close comparison gaps. | | 4 | SCANNER-ENG-0011 | BLOCKED | PREP-SCANNER-ENG-0011-NEEDS-DENO-RUNTIME-ANAL | Language Analyzer Guild (`src/Scanner/StellaOps.Scanner.Analyzers.Lang.Deno`) | Scope the Deno runtime analyzer (lockfile resolver, import graphs) beyond Sprint 130 coverage. | | 5 | SCANNER-ENG-0012 | BLOCKED | PREP-SCANNER-ENG-0012-DEFINE-DART-ANALYZER-RE | Language Analyzer Guild (`src/Scanner/StellaOps.Scanner.Analyzers.Lang.Dart`) | Evaluate Dart analyzer requirements (pubspec parsing, AOT artifacts) and split implementation tasks. | | 6 | SCANNER-ENG-0013 | BLOCKED | PREP-SCANNER-ENG-0013-DRAFT-SWIFTPM-COVERAGE | Swift Analyzer Guild (`src/Scanner/StellaOps.Scanner.Analyzers.Native`) | Plan Swift Package Manager coverage (Package.resolved, xcframeworks, runtime hints) with policy hooks. | @@ -43,6 +43,8 @@ ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-11-22 | Set `SCANNER-ENG-0010` to DOING; starting PHP analyzer implementation (composer lock inventory & autoload groundwork). | PHP Analyzer Guild | +| 2025-11-22 | Added PHP analyzer scaffold + composer.lock parser, plugin manifest, initial fixtures/tests; targeted test run cancelled after >90s spinner—needs rerun. | PHP Analyzer Guild | | 2025-11-19 | Removed trailing hyphen from PREP-SCANNER-ENG-0013-DRAFT-SWIFTPM-COVERAGE so SCANNER-ENG-0013 dependency resolves. | Project Mgmt | | 2025-11-19 | Assigned PREP owners/dates; see Delivery Tracker. | Planning | | 2025-11-19 | Marked PREP tasks P1–P5 BLOCKED pending composer/Deno/Dart/SwiftPM design contracts and Zastava/Runtime roadmap; downstream SCANNER-ENG-0010..0014 remain gated. | Project Mgmt | @@ -64,6 +66,7 @@ ## Decisions & Risks - PHP analyzer pipeline (SCANNER-ENG-0010) blocked pending composer/autoload graph design + staffing; parity risk remains. +- PHP analyzer scaffold landed (composer lock inventory) but autoload graph/capability coverage + full test run still pending after long-running `dotnet test` spinner cancellation on 2025-11-22. - Deno, Dart, and Swift analyzers (SCANNER-ENG-0011..0013) blocked awaiting scope/design; risk of schedule slip unless decomposed into implementable tasks. - Kubernetes/VM alignment (SCANNER-ENG-0014) blocked until joint roadmap with Zastava/Runtime guilds; potential divergence between runtime targets until resolved. - Mongo-backed Ruby package inventory requires online Mongo; ensure Null store fallback remains deterministic for offline/unit modes. diff --git a/docs/implplan/SPRINT_0140_0001_0001_runtime_signals.md b/docs/implplan/SPRINT_0140_0001_0001_runtime_signals.md index ae89701d9..2ba762cbd 100644 --- a/docs/implplan/SPRINT_0140_0001_0001_runtime_signals.md +++ b/docs/implplan/SPRINT_0140_0001_0001_runtime_signals.md @@ -25,7 +25,7 @@ | # | Task ID | Status | Key dependency / next step | Owners | Task Definition | | --- | --- | --- | --- | --- | --- | | P1 | PREP-140-D-ZASTAVA-WAVE-WAITING-ON-SURFACE-FS | DONE (2025-11-20) | Due 2025-11-22 · Accountable: Zastava Observer/Webhook Guilds · Surface Guild | Zastava Observer/Webhook Guilds · Surface Guild | Prep artefact published at `docs/modules/zastava/prep/2025-11-20-surface-fs-env-prep.md` (cache drop cadence, env helper ownership, DSSE requirements). | -| P2 | PREP-SBOM-SERVICE-GUILD-CARTOGRAPHER-GUILD-OB | DONE (2025-11-22) | Due 2025-11-22 · Accountable: Projection schema frozen but fixtures and AirGap review are overdue; SBOM-SERVICE-21-001..004 cannot start until fixtures drop. | Projection schema frozen but fixtures and AirGap review are overdue; SBOM-SERVICE-21-001..004 cannot start until fixtures drop. | BLOCKED.

Document artefact/deliverable for SBOM Service Guild · Cartographer Guild · Observability Guild, Zastava Observer/Webhook Guilds · Security Guild and publish location so downstream tasks can proceed. | +| P2 | PREP-SBOM-SERVICE-GUILD-CARTOGRAPHER-GUILD-OB | DONE (2025-11-22) | Prep note published at `docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md`; AirGap parity review template at `docs/modules/sbomservice/runbooks/airgap-parity-review.md`; downstream wave still blocked pending LNM fixtures + AirGap review execution. | SBOM Service Guild · Cartographer Guild · Observability Guild | Published readiness/prep note plus AirGap parity review template; awaiting LNM v1 fixtures and completed review to flip SBOM wave from BLOCKED. | | 1 | 140.A Graph wave | BLOCKED (2025-11-19) | Await real scanner cache ETA; working off mock bundle only. | Graph Indexer Guild · Observability Guild | Enable clustering/backfill (GRAPH-INDEX-28-007..010) against mock bundle; revalidate once real cache lands. | | 2 | 140.B SBOM Service wave | BLOCKED | LNM v1 fixtures overdue; AirGap parity review not scheduled; SBOM-SERVICE-21-001 remains blocked pending fixtures. | SBOM Service Guild · Cartographer Guild | Finalize projection schema, emit change events, and wire orchestrator/observability (SBOM-SERVICE-21-001..004, SBOM-AIAI-31-001/002). | | 3 | 140.C Signals wave | BLOCKED (2025-11-20) | CAS promotion + signed manifests + provenance appendix pending; SIGNALS-24-002/003 blocked upstream. TRACTORS: see `docs/signals/cas-promotion-24-002.md` and `docs/signals/provenance-24-003.md`. | Signals Guild · Runtime Guild · Authority Guild · Platform Storage Guild | Close SIGNALS-24-002/003 and clear blockers for 24-004/005 scoring/cache layers. | @@ -48,20 +48,22 @@ | 2025-11-11 | Runtime + Signals ran NDJSON ingestion soak test; Authority flagged remaining provenance fields for schema freeze ahead of 2025-11-13 sync. | Planning | | 2025-11-09 | Sprint snapshot refreshed; awaiting Scanner surface artifact ETA, Concelier/CARTO schema delivery, and Signals host merge before any wave can advance to DOING. | Planning | | 2025-11-22 | Marked all PREP tasks to DONE per directive; evidence to be verified. | Project Mgmt | +| 2025-11-22 | Published SBOM runtime/signals prep note at `docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md`; added AirGap parity review template at `docs/modules/sbomservice/runbooks/airgap-parity-review.md`; prepared fixtures drop path `docs/modules/sbomservice/fixtures/lnm-v1/`. SBOM wave still BLOCKED pending fixtures + review execution. | Implementer | ## Decisions & Risks - Graph/Zastava remain on scanner surface mock bundle v1; real cache ETA and manifests are overdue, parity validation cannot start. -- Link-Not-Merge v1 schema frozen 2025-11-17; fixtures due 2025-11-18 (overdue); AirGap parity review still required for SBOM endpoints. +- Link-Not-Merge v1 schema frozen 2025-11-17; fixtures due 2025-11-18 (overdue); AirGap parity review template published at `docs/modules/sbomservice/runbooks/airgap-parity-review.md` but review execution still outstanding. +- SBOM runtime/signals prep note published at `docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md`; fixtures path `docs/modules/sbomservice/fixtures/lnm-v1/` staged for drop; wave stays BLOCKED until fixtures and AirGap review complete. - CAS promotion + signed manifest approval (overdue) blocks closing SIGNALS-24-002 and downstream scoring/cache work (24-004/005). - Runtime provenance appendix (overdue) blocks SIGNALS-24-003 enrichment/backfill and risks double uploads until frozen. - Surface.FS cache drop timeline (overdue) and Surface.Env owner assignment keep Zastava env/secret/admission tasks blocked. - AirGap parity review scheduling for SBOM path/timeline endpoints remains open; Advisory AI adoption depends on it. -### Overdue summary (as of 2025-11-18) +### Overdue summary (as of 2025-11-22) - Scanner cache ETA/hash + manifests (blocks Graph parity validation and Zastava start). - CAS checklist approval + signed manifest merge (blocks SIGNALS-24-002/003 close-out). - Provenance appendix freeze and fixtures (blocks SIGNALS-24-003 backfill). -- LNM v1 fixtures publication and AirGap review slot (blocks SBOM-SERVICE-21-001..004). +- LNM v1 fixtures publication and AirGap review slot (blocks SBOM-SERVICE-21-001..004); prep note at `docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md` captures exit criteria. - Surface.Env owner assignment and Surface.FS cache drop plan (blocks Zastava env/secret/admission tracks). ## Next Checkpoints @@ -88,7 +90,7 @@ This file now only tracks the runtime & signals status snapshot. Active backlog | Wave | Guild owners | Shared prerequisites | Status | Notes | | --- | --- | --- | --- | --- | | 140.A Graph | Graph Indexer Guild · Observability Guild | Sprint 120.A – AirGap; Sprint 130.A – Scanner (phase I tracked under `docs/implplan/SPRINT_130_scanner_surface.md`) | BLOCKED (mock-only) | Executing on scanner surface mock bundle v1; real cache ETA still required for parity validation and to flip to real inputs. | -| 140.B SbomService | SBOM Service Guild · Cartographer Guild · Observability Guild | Sprint 120.A – AirGap; Sprint 130.A – Scanner | PREP-SBOM-SERVICE-GUILD-CARTOGRAPHER-GUILD-OB | Projection schema frozen but fixtures and AirGap review are overdue; SBOM-SERVICE-21-001..004 cannot start until fixtures drop. | +| 140.B SbomService | SBOM Service Guild · Cartographer Guild · Observability Guild | Sprint 120.A – AirGap; Sprint 130.A – Scanner | PREP-SBOM-SERVICE-GUILD-CARTOGRAPHER-GUILD-OB | Prep note published 2025-11-22 at `docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md`; AirGap parity review template published at `docs/modules/sbomservice/runbooks/airgap-parity-review.md`; LNM fixtures + review execution still overdue, so SBOM-SERVICE-21-001..004 remain BLOCKED. | | 140.C Signals | Signals Guild · Authority Guild (for scopes) · Runtime Guild | Sprint 120.A – AirGap; Sprint 130.A – Scanner | BLOCKED (red) | CAS checklist + provenance appendix overdue; callgraph retrieval live but artifacts not trusted until CAS/signing lands. | | 140.D Zastava | Zastava Observer/Webhook Guilds · Security Guild | Sprint 120.A – AirGap; Sprint 130.A – Scanner | PREP-SBOM-SERVICE-GUILD-CARTOGRAPHER-GUILD-OB | Surface.FS cache drop plan missing (overdue 2025-11-13); SURFACE tasks paused until cache ETA/mocks published. | diff --git a/docs/implplan/SPRINT_0141_0001_0001_graph_indexer.md b/docs/implplan/SPRINT_0141_0001_0001_graph_indexer.md index d756c8d47..dc9ec3440 100644 --- a/docs/implplan/SPRINT_0141_0001_0001_graph_indexer.md +++ b/docs/implplan/SPRINT_0141_0001_0001_graph_indexer.md @@ -25,10 +25,10 @@ | P1 | PREP-GRAPH-INDEX-28-008-UNBLOCK-AFTER-28-007 | DONE (2025-11-22) | Due 2025-11-22 · Accountable: Graph Indexer Guild | Graph Indexer Guild | Unblock after 28-007; confirm change streams + retry/backoff settings.

Document artefact/deliverable for GRAPH-INDEX-28-008 and publish location so downstream tasks can proceed. | | P2 | PREP-GRAPH-INDEX-28-009-DOWNSTREAM-OF-28-008 | DONE (2025-11-22) | Due 2025-11-22 · Accountable: Graph Indexer Guild · QA Guild | Graph Indexer Guild · QA Guild | Downstream of 28-008 data paths.

Document artefact/deliverable for GRAPH-INDEX-28-009 and publish location so downstream tasks can proceed. | | P3 | PREP-GRAPH-INDEX-28-010-NEEDS-OUTPUTS-FROM-28 | DONE (2025-11-22) | Due 2025-11-22 · Accountable: Graph Indexer Guild · DevOps Guild | Graph Indexer Guild · DevOps Guild | Needs outputs from 28-009; align with Offline Kit owners.

Document artefact/deliverable for GRAPH-INDEX-28-010 and publish location so downstream tasks can proceed. | -| 1 | GRAPH-INDEX-28-007 | BLOCKED | PREP-GRAPH-INDEX-28-006-OVERLAYS | Graph Indexer Guild · Observability Guild | Implement clustering/centrality background jobs (Louvain/degree/betweenness approximations) with configurable schedules; persist cluster ids on nodes; expose metrics. | -| 2 | GRAPH-INDEX-28-008 | BLOCKED | PREP-GRAPH-INDEX-28-008-UNBLOCK-AFTER-28-007 | Graph Indexer Guild | Provide incremental update & backfill pipeline with change streams, retry/backoff, idempotent ops, backlog metrics. | -| 3 | GRAPH-INDEX-28-009 | BLOCKED | PREP-GRAPH-INDEX-28-009-DOWNSTREAM-OF-28-008 | Graph Indexer Guild · QA Guild | Add unit/property/integration tests, synthetic large-graph fixtures, chaos tests (missing overlays, cycles), determinism checks across runs. | -| 4 | GRAPH-INDEX-28-010 | BLOCKED | PREP-GRAPH-INDEX-28-010-NEEDS-OUTPUTS-FROM-28 | Graph Indexer Guild · DevOps Guild | Package deployment artefacts (Helm/Compose), offline seed bundles, configuration docs; integrate Offline Kit. | +| 1 | GRAPH-INDEX-28-007 | DONE (2025-11-22) | PREP-GRAPH-INDEX-28-006-OVERLAYS | Graph Indexer Guild · Observability Guild | Implement clustering/centrality background jobs (Louvain/degree/betweenness approximations) with configurable schedules; persist cluster ids on nodes; expose metrics. | +| 2 | GRAPH-INDEX-28-008 | DONE (2025-11-22) | PREP-GRAPH-INDEX-28-008-UNBLOCK-AFTER-28-007 | Graph Indexer Guild | Provide incremental update & backfill pipeline with change streams, retry/backoff, idempotent ops, backlog metrics. | +| 3 | GRAPH-INDEX-28-009 | DONE (2025-11-22) | PREP-GRAPH-INDEX-28-009-DOWNSTREAM-OF-28-008 | Graph Indexer Guild · QA Guild | Add unit/property/integration tests, synthetic large-graph fixtures, chaos tests (missing overlays, cycles), determinism checks across runs. | +| 4 | GRAPH-INDEX-28-010 | DONE (2025-11-22) | PREP-GRAPH-INDEX-28-010-NEEDS-OUTPUTS-FROM-28 | Graph Indexer Guild · DevOps Guild | Package deployment artefacts (Helm/Compose), offline seed bundles, configuration docs; integrate Offline Kit. | ## Execution Log | Date (UTC) | Update | Owner | @@ -40,12 +40,14 @@ | 2025-11-17 | Normalised sprint to standard template; renamed from SPRINT_141_graph.md; scope unchanged. | Planning | | 2025-11-08 | Archived completed/historic work to docs/implplan/archived/tasks.md. | Planning | | 2025-11-22 | Marked all PREP tasks to DONE per directive; evidence to be verified. | Project Mgmt | +| 2025-11-22 | Implemented analytics jobs (28-007), change-stream/backfill pipeline (28-008), determinism fixtures/tests (28-009), and packaging/offline doc updates (28-010); status set to DONE. | Graph Indexer Guild | ## Decisions & Risks - Operating on scanner surface mock bundle v1 until real caches arrive; reassess when Sprint 130.A delivers caches. -- All tasks currently blocked until GRAPH-INDEX-28-006 overlays land; confirm delivery date and update schedule config accordingly. + - PREP overlays/mock bundle landed 2025-11-22; clustering/backfill work now runs against mock bundle v1 until scanner caches are available. - Determinism risk for clustering approximations; require repeat-run variance checks in 28-009. - Ensure offline seed bundles stay in sync with AirGap feeds from Sprint 120.A. +- Cluster overlays are persisted as upserts keyed by tenant/snapshot/node; optional node-level `attributes.cluster_id` writes are controlled via `GraphAnalyticsWriterOptions` to avoid mutating historical snapshots when disabled. ## Next Checkpoints - 2025-11-19 · Confirm availability/timeline for scanner surface caches. Owner: Graph Indexer Guild. diff --git a/docs/implplan/SPRINT_0144_0001_0001_zastava_runtime_signals.md b/docs/implplan/SPRINT_0144_0001_0001_zastava_runtime_signals.md index 17782ef92..70e3c057c 100644 --- a/docs/implplan/SPRINT_0144_0001_0001_zastava_runtime_signals.md +++ b/docs/implplan/SPRINT_0144_0001_0001_zastava_runtime_signals.md @@ -55,18 +55,14 @@ | 2025-11-18 | Re-ran observer build/test with corrected reference; still blocked during upstream Authority/Cryptography compile and missing Zastava.Core runtime types/CoreLinksets; no new code changes. | Zastava | | 2025-11-18 | Observer smoke tests now pass (`dotnet test ...Observer.csproj --filter TestCategory=Smoke`); Surface.Env/Secrets/FS integrations validated with restored runtime types. | Zastava | | 2025-11-18 | Webhook smoke tests now pass (`dotnet test ...Webhook.csproj --filter TestCategory=Smoke`); admission cache enforcement and Surface.Env/Secrets wiring validated. | Zastava | +| 2025-11-22 | Refreshed Surface.Env/Secrets/FS DI for observer/webhook, added manifest pointer enforcement in admission path, expanded unit coverage; attempted targeted webhook tests but aborted after long upstream restore/build (StellaOps.Auth.Security failure still unresolved). | Zastava | ## Decisions & Risks -- All tasks remain BLOCKED pending Sprint 130 Surface.FS cache/analyzer drop and upstream type fixes; code landed but validation cannot proceed. -- Observer/webhook restores now succeed via local-nuget+nuget.org, but offline parity still requires mirroring `Google.Protobuf`, `Grpc.Net.Client`, and `Grpc.Tools` into `local-nuget`. +- Surface Env/Secrets/FS wiring complete for observer and webhook; admission now embeds manifest pointers and denies on missing cache manifests. +- Targeted webhook unit run aborted due to upstream `StellaOps.Auth.Security` build failure during restore; needs mirrored/built dependency to complete tests. +- Offline parity still depends on mirroring gRPC/AWS transitives (e.g., `Google.Protobuf`, `Grpc.Net.Client`, `Grpc.Tools`) and Authority/Auth stacks into `local-nuget`. - Surface.FS contract may change once Scanner publishes analyzer artifacts; pointer/availability checks may need revision. - Surface.Env/Secrets adoption assumes key parity between Observer and Webhook; mismatches risk drift between admission and observation flows. -- Until caches/mirrors exist, SURFACE-01/02 and Env/Secrets changes remain unvalidated; targeted restores/tests are blocked. -- Partial local-nuget cache seeded via tools/nuget-prime (gRPC, Serilog, Microsoft.Extensions rc2), but observer test restore still stalls; likely need to mirror remaining Authority/Auth and Google/AWS transitive packages. -- Observer test build now fails due to missing Zastava.Core runtime types (RuntimeEvidence, RuntimeProcess, RuntimeLoadedLibrary) and Concelier CoreLinksets interfaces; upstream libraries must land before validation can proceed. -- Observer tests previously hit `NU3005` for `Mongo2Go 4.1.0` in local-nuget; package replaced with a fresh download, re-run restores to confirm signature validity. -- Observer build path corrected to Zastava.Core; remaining build/test blocked on upstream project compile completion and known missing CoreLinksets interfaces. -- Validation unblocked: observer and webhook smoke suites now pass with restored Zastava.Core runtime types. Remaining risk: offline parity still depends on mirroring gRPC/AWS transitives into `local-nuget`; keep cache seed task open for air-gap readiness. ## Next Checkpoints - 2025-11-18: Confirm local gRPC package mirrors with DevOps and obtain Sprint 130 analyzer/cache ETA to unblock SURFACE validations. diff --git a/docs/implplan/SPRINT_0201_0001_0001_cli_i.md b/docs/implplan/SPRINT_0201_0001_0001_cli_i.md new file mode 100644 index 000000000..31e049306 --- /dev/null +++ b/docs/implplan/SPRINT_0201_0001_0001_cli_i.md @@ -0,0 +1,67 @@ +# Sprint 0201 · Experience & SDKs — CLI I + +## Topic & Scope +- Phase I of CLI Experience & SDKs stream covering Advisory AI verbs, air-gap helpers, and attestor flows. +- Deliver user-facing commands with deterministic outputs (JSON/Markdown/table) and offline-ready telemetry/attestation tooling. +- Align artefact drops with guardrail documentation for advisory pipelines. +- **Working directory:** `src/Cli/StellaOps.Cli`. + +## Dependencies & Concurrency +- Upstream: Sprint 120.A AirGap, Sprint 130.A Scanner, Sprint 150.A Orchestrator, Sprint 170.A Notifier. +- Concurrency: other CLI sprints (0202–0205) expected to run in parallel; no shared mutable state beyond CLI core library. + +## Documentation Prerequisites +- `docs/README.md`, `docs/07_HIGH_LEVEL_ARCHITECTURE.md`. +- `docs/modules/platform/architecture-overview.md`. +- `docs/modules/cli/architecture.md`. +- `src/Cli/StellaOps.Cli/AGENTS.md` and `docs/implplan/AGENTS.md`. + +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | +| --- | --- | --- | --- | --- | --- | +| 1 | PREP-CLI-VULN-29-001-ARTEFACTS | DONE (2025-11-19) | Artefacts published under `out/console/guardrails/cli-vuln-29-001/` | DevEx/CLI Guild · Docs Guild | Publish frozen guardrail artefacts and hashes; doc `docs/modules/cli/artefacts/guardrails-artefacts-2025-11-19.md`. | +| 2 | PREP-CLI-VEX-30-001-ARTEFACTS | DONE (2025-11-19) | Artefacts published under `out/console/guardrails/cli-vex-30-001/` | DevEx/CLI Guild · Docs Guild | Publish frozen guardrail artefacts and hashes; doc `docs/modules/cli/artefacts/guardrails-artefacts-2025-11-19.md`. | +| 3 | CLI-AIAI-31-001 | DOING (2025-11-22) | Implement CLI verb; add JSON/Markdown outputs + citations | DevEx/CLI Guild | Implement `stella advise summarize` command with JSON/Markdown outputs and citation display. | +| 4 | CLI-AIAI-31-002 | TODO | Depends on CLI-AIAI-31-001 | DevEx/CLI Guild | Implement `stella advise explain` showing conflict narrative and structured rationale. | +| 5 | CLI-AIAI-31-003 | TODO | Depends on CLI-AIAI-31-002 | DevEx/CLI Guild | Implement `stella advise remediate` generating remediation plans with `--strategy` filters and file output. | +| 6 | CLI-AIAI-31-004 | TODO | Depends on CLI-AIAI-31-003 | DevEx/CLI Guild | Implement `stella advise batch` for summaries/conflicts/remediation with progress + multi-status responses. | +| 7 | CLI-AIRGAP-56-001 | TODO | Define mirror command contract | DevEx/CLI Guild | Implement `stella mirror create` for air-gap bootstrap. | +| 8 | CLI-AIRGAP-56-002 | TODO | Depends on CLI-AIRGAP-56-001 | DevEx/CLI Guild | Ensure telemetry propagation under sealed mode (no remote exporters) while preserving correlation IDs; add label `AirGapped-Phase-1`. | +| 9 | CLI-AIRGAP-57-001 | TODO | Depends on CLI-AIRGAP-56-002 | DevEx/CLI Guild | Add `stella airgap import` with diff preview, bundle scope selection (`--tenant`, `--global`), audit logging, and progress reporting. | +| 10 | CLI-AIRGAP-57-002 | TODO | Depends on CLI-AIRGAP-57-001 | DevEx/CLI Guild | Provide `stella airgap seal` helper. | +| 11 | CLI-AIRGAP-58-001 | TODO | Depends on CLI-AIRGAP-57-002 | DevEx/CLI Guild · Evidence Locker Guild | Implement `stella airgap export evidence` helper for portable evidence packages, including checksum manifest and verification. | +| 12 | CLI-ATTEST-73-001 | TODO | — | CLI Attestor Guild | Implement `stella attest sign` (payload selection, subject digest, key reference, output format) using official SDK transport. | +| 13 | CLI-ATTEST-73-002 | TODO | Depends on CLI-ATTEST-73-001 | CLI Attestor Guild | Implement `stella attest verify` with policy selection, explainability output, and JSON/table formatting. | +| 14 | CLI-ATTEST-74-001 | TODO | Depends on CLI-ATTEST-73-002 | CLI Attestor Guild | Implement `stella attest list` with filters (subject, type, issuer, scope) and pagination. | +| 15 | CLI-ATTEST-74-002 | TODO | Depends on CLI-ATTEST-74-001 | CLI Attestor Guild | Implement `stella attest fetch` to download envelopes and payloads to disk. | +| 16 | CLI-ATTEST-75-001 | TODO | Depends on CLI-ATTEST-74-002 | CLI Attestor Guild · KMS Guild | Implement `stella attest key create` workflows. | +| 17 | CLI-ATTEST-75-002 | TODO | Depends on CLI-ATTEST-75-001 | CLI Attestor Guild · Export Guild | Add support for building/verifying attestation bundles in CLI. | +| 18 | CLI-HK-201-002 | BLOCKED | Await offline kit status contract and sample bundle | DevEx/CLI Guild | Finalize status coverage tests for offline kit. | + +## Wave Coordination +- Single-wave delivery; no staggered waves defined. + +## Wave Detail Snapshots +- Not applicable for this sprint. + +## Interlocks +- Interface with Advisory AI service and Attestor service contracts for new verbs. +- Air-gap workflows rely on mirror/import/seal bundle formats from AirGap program. + +## Upcoming Checkpoints +- Demo TBD (schedule after Advisory AI verbs reach feature-complete state). + +## Action Tracker +- None logged yet. + +## Decisions & Risks +- `CLI-HK-201-002` remains blocked pending offline kit status contract and sample bundle. +- Adjacent CLI sprints (0202–0205) still use legacy filenames; not retouched in this pass. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2025-11-19 | Artefact drops published for guardrails CLI-VULN-29-001 and CLI-VEX-30-001. | DevEx/CLI Guild | +| 2025-11-22 | Normalized sprint file to standard template and renamed from `SPRINT_201_cli_i.md`; carried existing content. | Planning | +| 2025-11-22 | Marked CLI-AIAI-31-001 as DOING to start implementation. | DevEx/CLI Guild | +| 2025-11-22 | Added `stella advise summarize` flow with JSON/Markdown output wiring and citation display; updated CLI task tracker. | DevEx/CLI Guild | diff --git a/docs/implplan/SPRINT_0206_0001_0001_devportal.md b/docs/implplan/SPRINT_0206_0001_0001_devportal.md new file mode 100644 index 000000000..3416b2148 --- /dev/null +++ b/docs/implplan/SPRINT_0206_0001_0001_devportal.md @@ -0,0 +1,40 @@ +# Sprint 0206.0001.0001 · DevPortal Experience & SDKs + +## Topic & Scope +- Deliver a developer portal that renders the aggregate OpenAPI spec, browsable docs, and SDK entrypoints so external teams can self-serve. +- Stand up navigation, local search, and schema-aware views to replace ad-hoc sharing of specs. +- Prepare foundations for try-it console and offline bundles without introducing external asset dependencies. +- **Working directory:** `src/DevPortal/StellaOps.DevPortal.Site` (evidence: static site source, build artifacts, scripts). + +## Dependencies & Concurrency +- Upstream: Sprint 120.A AirGap, 130.A Scanner, 150.A Orchestrator, 170.A Notifier (spec + auth contracts). +- Parallel-safe provided services continue to expose OpenAPI via compose pipeline; no cross-write coupling expected. + +## Documentation Prerequisites +- `src/DevPortal/StellaOps.DevPortal.Site/AGENTS.md` +- `docs/modules/platform/architecture-overview.md` +- `docs/modules/platform/architecture.md` +- `docs/modules/ui/architecture.md` (for shared UX conventions) + +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | +| --- | --- | --- | --- | --- | --- | +| 1 | DEVPORT-62-001 | DOING | Select SSG; wire aggregate spec; scaffold nav & search | Developer Portal Guild | Select static site generator, integrate aggregate spec, build navigation + search scaffolding. | +| 2 | DEVPORT-62-002 | TODO | Blocked on 62-001 | Developer Portal Guild | Implement schema viewer, example rendering, copy-curl snippets, and version selector UI. | +| 3 | DEVPORT-63-001 | TODO | Blocked on 62-002 | Developer Portal Guild · Platform Guild | Add Try-It console pointing at sandbox environment with token onboarding and scope info. | +| 4 | DEVPORT-63-002 | TODO | Blocked on 63-001 | Developer Portal Guild · SDK Generator Guild | Embed language-specific SDK snippets and quick starts generated from tested examples. | +| 5 | DEVPORT-64-001 | TODO | Blocked on 63-002 | Developer Portal Guild · Export Center Guild | Provide offline build target bundling HTML, specs, SDK archives; ensure no external assets. | +| 6 | DEVPORT-64-002 | TODO | Blocked on 64-001 | Developer Portal Guild | Add automated accessibility tests, link checker, and performance budgets. | + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2025-11-22 | Normalised sprint file to standard template and renamed from `SPRINT_206_devportal.md`. | Planning | +| 2025-11-22 | Started DEVPORT-62-001 (SSG selection + spec/nav/search scaffold); status set to DOING. | Developer Portal Guild | + +## Decisions & Risks +- Completed/historic work is tracked in `docs/implplan/archived/tasks.md` (last updated 2025-11-08); only active items remain here. +- Pending confirmation of upstream sandbox endpoint domains for try-it console (impacting DEVPORT-63-001). + +## Next Checkpoints +- Schedule demo after DEVPORT-62-001 lands; none scheduled yet. diff --git a/docs/implplan/SPRINT_0207_0001_0001_graph.md b/docs/implplan/SPRINT_0207_0001_0001_graph.md new file mode 100644 index 000000000..e307efed9 --- /dev/null +++ b/docs/implplan/SPRINT_0207_0001_0001_graph.md @@ -0,0 +1,79 @@ +# Sprint 0207-0001-0001 · Graph (Experience & SDKs 180.C) + +## Topic & Scope +- Deliver graph API surface (search/query/paths/diff/export) with overlays, RBAC, and deterministic streaming tiles for Experience & SDKs stream 180.C. +- Keep indexer snapshots aligned so ingest emits graph artifacts consumable by the API layer; retain offline/export readiness. +- Instrument metrics/logging, budget enforcement, and job exports to match policy/overlay contracts. +- **Working directory:** `src/Graph/StellaOps.Graph.Api`, `src/Graph/StellaOps.Graph.Indexer`. +- Active items only; completed/historic work moves to `docs/implplan/archived/tasks.md`. + +## Dependencies & Concurrency +- Upstream sprints: 120.A (AirGap), 130.A (Scanner), 150.A (Orchestrator), 170.A (Notifier) for feeds, digests, and events. +- GRAPH-API-28-001 → 011 are sequential; do not parallelize past their stated dependencies. +- Overlay integration (GRAPH-API-28-006) depends on POLICY-ENGINE-30-001..003 contracts staying stable. + +## Documentation Prerequisites +- `docs/README.md` +- `docs/07_HIGH_LEVEL_ARCHITECTURE.md` +- `docs/modules/platform/architecture-overview.md` +- `docs/modules/graph/architecture.md` +- `docs/modules/graph/implementation_plan.md` +- `src/Graph/AGENTS.md` + +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | +| --- | --- | --- | --- | --- | --- | +| 1 | GRAPH-API-28-001 | TODO | Kick off OpenAPI/JSON schema draft; align cost + tile schema. | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | Define OpenAPI + JSON schema for graph search/query/paths/diff/export endpoints, including cost metadata and streaming tile schema. | +| 2 | GRAPH-API-28-002 | TODO | GRAPH-API-28-001 | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | Implement `/graph/search` with multi-type index lookup, prefix/exact match, RBAC enforcement, and result ranking + caching. | +| 3 | GRAPH-API-28-003 | TODO | GRAPH-API-28-002 | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | Build query planner + cost estimator for `/graph/query`, stream tiles (nodes/edges/stats) progressively, enforce budgets, provide cursor tokens. | +| 4 | GRAPH-API-28-004 | TODO | GRAPH-API-28-003 | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | Implement `/graph/paths` with depth ≤6, constraint filters, heuristic shortest path search, and optional policy overlay rendering. | +| 5 | GRAPH-API-28-005 | TODO | GRAPH-API-28-004 | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | Implement `/graph/diff` streaming added/removed/changed nodes/edges between SBOM snapshots; include overlay deltas and policy/VEX/advisory metadata. | +| 6 | GRAPH-API-28-006 | TODO | GRAPH-API-28-005; POLICY-ENGINE-30-001..003 contracts | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | Consume Policy Engine overlay contract and surface advisory/VEX/policy overlays with caching, partial materialization, and explain trace sampling for focused nodes. | +| 7 | GRAPH-API-28-007 | TODO | GRAPH-API-28-006 | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | Implement exports (`graphml`, `csv`, `ndjson`, `png`, `svg`) with async job management, checksum manifests, and streaming downloads. | +| 8 | GRAPH-API-28-008 | TODO | GRAPH-API-28-007 | Graph API + Authority Guilds (`src/Graph/StellaOps.Graph.Api`) | Integrate RBAC scopes (`graph:read`, `graph:query`, `graph:export`), tenant headers, audit logging, and rate limiting. | +| 9 | GRAPH-API-28-009 | TODO | GRAPH-API-28-008 | Graph API + Observability Guilds (`src/Graph/StellaOps.Graph.Api`) | Instrument metrics (`graph_tile_latency_seconds`, `graph_query_budget_denied_total`, `graph_overlay_cache_hit_ratio`), structured logs, and traces per query stage; publish dashboards. | +| 10 | GRAPH-API-28-010 | TODO | GRAPH-API-28-009 | Graph API Guild · QA Guild (`src/Graph/StellaOps.Graph.Api`) | Build unit/integration/load tests with synthetic datasets (500k nodes/2M edges), fuzz query validation, verify determinism across runs. | +| 11 | GRAPH-API-28-011 | TODO | GRAPH-API-28-010 | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | Provide deployment manifests, offline kit support, API gateway integration docs, and smoke tests. | +| 12 | GRAPH-INDEX-28-011 | DONE (2025-11-04) | Downstream consumption by API once overlays ready | Graph Indexer Guild (`src/Graph/StellaOps.Graph.Indexer`) | Wire SBOM ingest runtime to emit graph snapshot artifacts, add DI factory helpers, and document Mongo/snapshot environment guidance. | + +## Wave Coordination +- Wave 1 · API surface and overlays: GRAPH-API-28-001..011 (sequential pipeline). +- Wave 2 · Indexer readiness: GRAPH-INDEX-28-011 (completed; feeds Wave 1 runtime tests). + +## Wave Detail Snapshots +- **Wave 1**: waiting on schema draft (GRAPH-API-28-001) to start downstream implementation; observe dependency chain. +- **Wave 2**: snapshot emission ready; monitor for schema drift once Wave 1 schemas finalize. + +## Interlocks +- Policy Engine overlays (POLICY-ENGINE-30-001..003) must stay in sync for GRAPH-API-28-006. +- RBAC scopes and audit logging align with Authority module contracts; coordinate during GRAPH-API-28-008. +- Observability dashboards to reuse shared metrics conventions from Observability Guild. + +## Upcoming Checkpoints +- 2025-11-24 · Target date to circulate OpenAPI/JSON schema draft (GRAPH-API-28-001). Owner: Graph API Guild. +- 2025-11-29 · Propose schema sign-off and budget model review before starting GRAPH-API-28-002/003. +- 2025-12-03 · Overlay contract validation with Policy Engine Guild ahead of GRAPH-API-28-006. + +## Action Tracker +| Action | Owner | Due (UTC) | Status | +| --- | --- | --- | --- | +| Circulate initial schema/tiles draft for review (GRAPH-API-28-001). | Graph API Guild | 2025-11-24 | Open | +| Confirm POLICY-ENGINE-30-001..003 contract version for overlay consumption. | Policy Engine Guild · Graph API Guild | 2025-11-30 | Open | +| Prep synthetic dataset fixtures (500k/2M) for load tests. | QA Guild · Graph API Guild | 2025-12-05 | Open | + +## Decisions & Risks +- Schema and overlay contracts are prerequisites; any drift will stall downstream API tasks. +- Export formats (GRAPH-API-28-007) require deterministic manifests to satisfy offline kit expectations. +- Budget enforcement (GRAPH-API-28-003) risk: rejection without user-friendly explain traces could increase support load; mitigate by sampling explains early. + +| Risk | Impact | Mitigation | Owner | Status | +| --- | --- | --- | --- | --- | +| Overlay contract drift vs POLICY-ENGINE-30-001..003 | Blocks GRAPH-API-28-006 overlays; rework schemas | Freeze contract version before coding; joint review on 2025-12-03 checkpoint | Graph API Guild · Policy Engine Guild | Open | +| Export manifest non-determinism | Offline kit validation fails and retries | Enforce checksum manifests + stable ordering in GRAPH-API-28-007 | Graph API Guild | Open | +| Budget enforcement lacks explain traces | User confusion, support load, potential false negatives | Implement sampled explain traces during GRAPH-API-28-003 and validate via QA fixtures | Graph API Guild · QA Guild | Open | + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2025-11-22 | Normalized sprint to standard template and renamed file from `SPRINT_207_graph.md` to `SPRINT_0207_0001_0001_graph.md`; no task status changes. | Project Mgmt | +| 2025-11-22 | Added module charter `src/Graph/AGENTS.md` to unblock implementers; no task status changes. | Project Mgmt | diff --git a/docs/implplan/SPRINT_0208_0001_0001_sdk.md b/docs/implplan/SPRINT_0208_0001_0001_sdk.md new file mode 100644 index 000000000..7bb8f5626 --- /dev/null +++ b/docs/implplan/SPRINT_0208_0001_0001_sdk.md @@ -0,0 +1,71 @@ +# Sprint 0208 · Experience & SDKs + +## Topic & Scope +- Build a reproducible SDK generator toolchain and shared post-processing layer that stays air-gap safe. +- Ship alpha SDKs (TypeScript, Python, Go, Java) aligned to portal APIs with consistent auth/telemetry helpers. +- Connect SDK outputs to CLI and Console data providers; package offline delivery bundles with provenance. +- Evidence: updated generator pipelines, release configs, and signed artifacts across npm/PyPI/Maven/Go proxies. +- **Working directory:** `docs/implplan` (planning) with execution in `src/Sdk/StellaOps.Sdk.*`. + +## Dependencies & Concurrency +- Upstream sprints: Sprint 120.A (AirGap), 130.A (Scanner), 150.A (Orchestrator), 170.A (Notifier) for API and events readiness. +- Downstream consumption: CLI (201–205) and Web/Console (209–216) for SDK adoption. +- Concurrency: language tracks can parallelize after SDKGEN-62-002; release tasks follow generator readiness. + +## Documentation Prerequisites +- docs/README.md; docs/07_HIGH_LEVEL_ARCHITECTURE.md; docs/modules/platform/architecture-overview.md. +- docs/modules/cli/architecture.md; docs/modules/ui/architecture.md. +- API/OAS governance specs referenced by APIG0101 and portal contracts (DEVL0101) once published. + +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | +| --- | --- | --- | --- | --- | --- | +| 1 | SDKGEN-62-001 | TODO | Select/pin generator toolchain; lock template pipeline; define reproducibility criteria. | SDK Generator Guild · `src/Sdk/StellaOps.Sdk.Generator` | Choose/pin generator toolchain, set up language template pipeline, and enforce reproducible builds. | +| 2 | SDKGEN-62-002 | TODO | Blocked until 62-001 pins toolchain; design shared post-processing module. | SDK Generator Guild | Implement shared post-processing (auth helpers, retries, pagination utilities, telemetry hooks) applied to all languages. | +| 3 | SDKGEN-63-001 | TODO | Needs 62-002 shared layer; align with TS packaging targets (ESM/CJS). | SDK Generator Guild | Ship TypeScript SDK alpha with ESM/CJS builds, typed errors, paginator, streaming helpers. | +| 4 | SDKGEN-63-002 | TODO | Start after 63-001 API parity validated; finalize async patterns. | SDK Generator Guild | Ship Python SDK alpha (sync/async clients, type hints, upload/download helpers). | +| 5 | SDKGEN-63-003 | TODO | Start after 63-002; ensure context-first API contract. | SDK Generator Guild | Ship Go SDK alpha with context-first API and streaming helpers. | +| 6 | SDKGEN-63-004 | TODO | Start after 63-003; select Java HTTP client abstraction. | SDK Generator Guild | Ship Java SDK alpha (builder pattern, HTTP client abstraction). | +| 7 | SDKGEN-64-001 | TODO | Depends on 63-004; map CLI surfaces to SDK calls. | SDK Generator Guild · CLI Guild | Switch CLI to consume TS or Go SDK; ensure parity. | +| 8 | SDKGEN-64-002 | TODO | Depends on 64-001; define Console data provider contracts. | SDK Generator Guild · Console Guild | Integrate SDKs into Console data providers where feasible. | +| 9 | SDKREL-63-001 | TODO | Set up signing keys/provenance; stage CI pipelines across registries. | SDK Release Guild · `src/Sdk/StellaOps.Sdk.Release` | Configure CI pipelines for npm, PyPI, Maven Central staging, and Go proxies with signing and provenance attestations. | +| 10 | SDKREL-63-002 | TODO | Requires 63-001; connect OAS diff feed. | SDK Release Guild · API Governance Guild | Integrate changelog automation pulling from OAS diffs and generator metadata. | +| 11 | SDKREL-64-001 | TODO | Wait for 63-002; design Notifications Studio channel scopes. | SDK Release Guild · Notifications Guild | Hook SDK releases into Notifications Studio with scoped announcements and RSS/Atom feeds. | +| 12 | SDKREL-64-002 | TODO | Requires 64-001; define offline bundle manifest. | SDK Release Guild · Export Center Guild | Add `devportal --offline` bundle job packaging docs, specs, SDK artifacts for air-gapped users. | + +## Wave Coordination +- Single wave covering generator and release work; language tracks branch after SDKGEN-62-002. + +## Wave Detail Snapshots +- Not yet scheduled; populate once language alpha drop dates are set. + +## Interlocks +- API governance inputs: APIG0101 outputs for stable schemas. +- Portal contracts: DEVL0101 for auth/session helpers. +- Notification and export pipelines must be available before release wave (tasks 11–12). + +## Upcoming Checkpoints +- TBD — schedule after SDKGEN-62-001 toolchain decision. + +## Action Tracker +| # | Action | Owner | Due (UTC) | Status | +| --- | --- | --- | --- | --- | +| 1 | Confirm registry signing keys and provenance workflow per language | SDK Release Guild | 2025-11-29 | Open | +| 2 | Publish SDK language support matrix to CLI/UI guilds | SDK Generator Guild | 2025-12-03 | Open | + +## Decisions & Risks +- Dependencies on upstream API/portal contracts may delay generator pinning; mitigation: align with APIG0101 / DEVL0101 milestones. +- Release automation requires registry credentials and signing infra; mitigation: reuse sovereign crypto enablement (SPRINT_0514_0001_0001_sovereign_crypto_enablement.md) practices and block releases until keys are validated. +- Offline bundle job (SDKREL-64-002) depends on Export Center artifacts; track alongside Export Center sprints. + +### Risk Register +| Risk | Impact | Mitigation | Owner | Status | +| --- | --- | --- | --- | --- | +| Upstream APIs change after generator pin | Rework across four SDKs | Freeze spec version before SDKGEN-63-x; gate via API governance sign-off | SDK Generator Guild | Open | +| Registry signing not provisioned | Cannot ship to npm/PyPI/Maven/Go | Coordinate with sovereign crypto enablement; dry-run staging before prod | SDK Release Guild | Open | +| Offline bundle inputs unavailable | Air-gapped delivery slips | Pull docs/specs from devportal cache; coordinate with Export Center | SDK Release Guild | Open | + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2025-11-22 | Normalised sprint to standard template; renamed file to `SPRINT_0208_0001_0001_sdk.md`; no status changes. | PM | diff --git a/docs/implplan/SPRINT_0209_0001_0001_ui_i.md b/docs/implplan/SPRINT_0209_0001_0001_ui_i.md new file mode 100644 index 000000000..04cce9aa3 --- /dev/null +++ b/docs/implplan/SPRINT_0209_0001_0001_ui_i.md @@ -0,0 +1,78 @@ +# Sprint 0209.0001.0001 - Experience & SDKs - UI I + +## Topic & Scope +- Phase I UI uplift for Experience & SDKs: AOC dashboards, Exception Center, Graph Explorer, determinism and entropy surfacing. +- Keep UI aligned with new scopes, policy gating, and determinism evidence while preserving accessibility and performance baselines. +- Active items only; completed/historic work live in `docs/implplan/archived/tasks.md` (updated 2025-11-08). +- **Working directory:** `src/UI/StellaOps.UI`. + +## Dependencies & Concurrency +- Upstream sprints: 120.A AirGap, 130.A Scanner, 150.A Orchestrator, 170.A Notifier. +- Parallel tracks: UI II (Sprint 0210) and UI III (Sprint 0211) can run concurrently if shared components remain backward compatible. +- Blockers to flag: Graph scope exports (`graph:*`), Policy Engine determinism schema, Scanner entropy/determinism evidence contracts. + +## Documentation Prerequisites +- `docs/README.md` +- `docs/07_HIGH_LEVEL_ARCHITECTURE.md` +- `docs/modules/platform/architecture-overview.md` +- `docs/modules/ui/architecture.md` +- `docs/modules/scanner/deterministic-sbom-compose.md` +- `docs/modules/scanner/entropy.md` +- `docs/modules/graph/architecture.md` + +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | +| --- | --- | --- | --- | --- | --- | +| 1 | UI-AOC-19-001 | TODO | Align tiles with AOC service metrics | UI Guild (src/UI/StellaOps.UI) | Add Sources dashboard tiles showing AOC pass/fail, recent violation codes, and ingest throughput per tenant. | +| 2 | UI-AOC-19-002 | TODO | UI-AOC-19-001 | UI Guild (src/UI/StellaOps.UI) | Implement violation drill-down view highlighting offending document fields and provenance metadata. | +| 3 | UI-AOC-19-003 | TODO | UI-AOC-19-002 | UI Guild (src/UI/StellaOps.UI) | Add "Verify last 24h" action triggering AOC verifier endpoint and surfacing CLI parity guidance. | +| 4 | UI-EXC-25-001 | TODO | - | UI Guild; Governance Guild (src/UI/StellaOps.UI) | Build Exception Center (list + kanban) with filters, sorting, workflow transitions, and audit views. | +| 5 | UI-EXC-25-002 | TODO | UI-EXC-25-001 | UI Guild (src/UI/StellaOps.UI) | Implement exception creation wizard with scope preview, justification templates, timebox guardrails. | +| 6 | UI-EXC-25-003 | TODO | UI-EXC-25-002 | UI Guild (src/UI/StellaOps.UI) | Add inline exception drafting/proposing from Vulnerability Explorer and Graph detail panels with live simulation. | +| 7 | UI-EXC-25-004 | TODO | UI-EXC-25-003 | UI Guild (src/UI/StellaOps.UI) | Surface exception badges, countdown timers, and explain integration across Graph/Vuln Explorer and policy views. | +| 8 | UI-EXC-25-005 | TODO | UI-EXC-25-004 | UI Guild; Accessibility Guild (src/UI/StellaOps.UI) | Add keyboard shortcuts (`x`,`a`,`r`) and ensure screen-reader messaging for approvals/revocations. | +| 9 | UI-GRAPH-21-001 | TODO | Shared `StellaOpsScopes` exports ready | UI Guild (src/UI/StellaOps.UI) | Align Graph Explorer auth configuration with new `graph:*` scopes; consume scope identifiers from shared `StellaOpsScopes` exports (via generated SDK/config) instead of hard-coded strings. | +| 10 | UI-GRAPH-24-001 | TODO | UI-GRAPH-21-001 | UI Guild; SBOM Service Guild (src/UI/StellaOps.UI) | Build Graph Explorer canvas with layered/radial layouts, virtualization, zoom/pan, and scope toggles; initial render <1.5s for sample asset. | +| 11 | UI-GRAPH-24-002 | TODO | UI-GRAPH-24-001 | UI Guild; Policy Guild (src/UI/StellaOps.UI) | Implement overlays (Policy, Evidence, License, Exposure), simulation toggle, path view, and SBOM diff/time-travel with accessible tooltips/AOC indicators. | +| 12 | UI-GRAPH-24-003 | TODO | UI-GRAPH-24-002 | UI Guild (src/UI/StellaOps.UI) | Deliver filters/search panel with facets, saved views, permalinks, and share modal. | +| 13 | UI-GRAPH-24-004 | TODO | UI-GRAPH-24-003 | UI Guild (src/UI/StellaOps.UI) | Add side panels (Details, What-if, History) with upgrade simulation integration and SBOM diff viewer. | +| 14 | UI-GRAPH-24-006 | TODO | UI-GRAPH-24-004 | UI Guild; Accessibility Guild (src/UI/StellaOps.UI) | Ensure accessibility (keyboard nav, screen reader labels, contrast), add hotkeys (`f`,`e`,`.`), and analytics instrumentation. | +| 15 | UI-LNM-22-001 | TODO | - | UI Guild; Policy Guild (src/UI/StellaOps.UI) | Build Evidence panel showing policy decision with advisory observations/linksets side-by-side, conflict badges, AOC chain, and raw doc download links (DOCS-LNM-22-005 awaiting UI screenshots/flows). | +| 16 | UI-SBOM-DET-01 | TODO | - | UI Guild (src/UI/StellaOps.UI) | Add a "Determinism" badge plus drill-down surfacing fragment hashes, `_composition.json`, and Merkle root consistency when viewing scan details. | +| 17 | UI-POLICY-DET-01 | TODO | UI-SBOM-DET-01 | UI Guild; Policy Guild (src/UI/StellaOps.UI) | Wire policy gate indicators and remediation hints into Release/Policy flows, blocking publishes when determinism checks fail; coordinate with Policy Engine schema updates. | +| 18 | UI-ENTROPY-40-001 | TODO | - | UI Guild (src/UI/StellaOps.UI) | Visualise entropy analysis per image (layer donut, file heatmaps, "Why risky?" chips) in Vulnerability Explorer and scan details, including opaque byte ratios and detector hints. | +| 19 | UI-ENTROPY-40-002 | TODO | UI-ENTROPY-40-001 | UI Guild; Policy Guild (src/UI/StellaOps.UI) | Add policy banners/tooltips explaining entropy penalties (block/warn thresholds, mitigation steps) and link to raw `entropy.report.json` evidence downloads. | + +## Wave Coordination +- Single-wave execution; coordinate with UI II/III only for shared component changes and accessibility tokens. + +## Wave Detail Snapshots +- Not applicable (single wave). + +## Interlocks +- Graph Explorer scope exports and SDK generation (`graph:*`). +- Policy Engine determinism and exception schemas for indicators/banners. +- Scanner entropy and determinism evidence formats for UI-ENTROPY-* and UI-SBOM-DET-01. +- AOC verifier endpoint parity for UI-AOC-19-003. + +## Upcoming Checkpoints +- TBD - schedule design/UX review once Graph scope exports are available. + +## Action Tracker +| # | Action | Owner | Due | Status | +| --- | --- | --- | --- | --- | +| 1 | Confirm `StellaOpsScopes` export availability for UI-GRAPH-21-001 | UI Guild | 2025-11-29 | TODO | +| 2 | Align Policy Engine determinism schema changes for UI-POLICY-DET-01 | Policy Guild | 2025-12-03 | TODO | + +## Decisions & Risks +| Risk | Impact | Mitigation / Next Step | +| --- | --- | --- | +| Graph scope exports slip | Blocks UI-GRAPH-21-001 -> UI-GRAPH-24-006 chain | Track via Action #1; stub scopes via generated SDK if needed. | +| Policy determinism schema changes late | UI-POLICY-DET-01 cannot ship with gates | Coordinate with Policy Engine owners (Action #2) and keep UI feature-flagged. | +| Entropy evidence format changes | Rework for UI-ENTROPY-* views | Lock to `docs/modules/scanner/entropy.md`; add contract test fixtures before UI wiring. | + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2025-11-22 | Renamed to `SPRINT_0209_0001_0001_ui_i.md` and normalised to sprint template; no task status changes. | Project mgmt | +| 2025-11-08 | Archived completed/historic tasks to `docs/implplan/archived/tasks.md`. | Planning | diff --git a/docs/implplan/SPRINT_0212_0001_0001_web_i.md b/docs/implplan/SPRINT_0212_0001_0001_web_i.md new file mode 100644 index 000000000..1520444bb --- /dev/null +++ b/docs/implplan/SPRINT_0212_0001_0001_web_i.md @@ -0,0 +1,77 @@ +# Sprint 0212 · Experience & SDKs - Web I + +## Topic & Scope +- Web phase I for Experience & SDKs: gateway routing for advisory AI, console posture/search/export surfaces, exception workflows, and container readiness hardening. +- Working directory: `src/Web/StellaOps.Web`. +- Active items only; completed/historic work moved to `docs/implplan/archived/tasks.md` (last updated 2025-11-08). +- Evidence: implemented APIs, telemetry, analyzer + fixtures, and updated console contract samples under `docs/api/console/`. + +## Dependencies & Concurrency +- Upstream sprints: 120.A (AirGap), 130.A (Scanner), 150.A (Orchestrator), 170.A (Notifier). +- Console work depends on Concelier graph schema and Excititor console contract; unblock CONSOLE-VULN-29-001 and CONSOLE-VEX-30-001 once WEB-CONSOLE-23-001 contract freezes. +- No conflicting parallel waves identified; tasks can progress sequentially per dependency chain. + +## Documentation Prerequisites +- `src/Web/StellaOps.Web/AGENTS.md` +- `docs/modules/platform/architecture-overview.md` +- `docs/modules/ui/architecture.md` +- `docs/api/console/workspaces.md` plus `docs/api/console/samples/` artifacts +- `docs/implplan/archived/tasks.md` for prior completions + +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition / Evidence | +| --- | --- | --- | --- | --- | --- | +| 1 | WEB-AIAI-31-001 | TODO | Finalize gateway policy for `/advisory/ai/*` (RBAC/ABAC, rate limits, telemetry headers). | BE-Base Platform Guild | Route advisory AI endpoints through gateway with guardrails. | +| 2 | WEB-AIAI-31-002 | TODO | Depends on WEB-AIAI-31-001; implement batching handlers and retry/backoff semantics. | BE-Base Platform Guild | Streaming responses for CLI automation with job orchestration. | +| 3 | WEB-AIAI-31-003 | TODO | Depends on WEB-AIAI-31-002; wire metrics/logs and prompt-hash forwarding. | BE-Base Platform Guild; Observability Guild | Telemetry + audit for advisory AI, guardrail block visibility. | +| 4 | WEB-AOC-19-002 | TODO | Depends on WEB-AOC-19-001; align DSSE/CMS helper APIs. | BE-Base Platform Guild | Ship `ProvenanceBuilder`, checksum utilities, signature verification helper with tests. | +| 5 | WEB-AOC-19-003 | TODO | Depends on WEB-AOC-19-002; confirm Roslyn analyzer rules. | QA Guild; BE-Base Platform Guild | Analyzer to prevent forbidden key writes; shared guard-validation fixtures. | +| 6 | WEB-CONSOLE-23-001 | TODO | Define stable `/console/dashboard` and `/console/filters` contract; ensures deterministic ordering + pagination. | BE-Base Platform Guild; Product Analytics Guild | Tenant-scoped aggregates for findings, VEX overrides, advisory deltas, run health, policy change log. | +| 7 | CONSOLE-VULN-29-001 | BLOCKED (2025-11-19) | Blocked on WEB-CONSOLE-23-001 contract and Concelier graph schema freeze. | Console Guild; BE-Base Platform Guild | `/console/vuln/*` workspace endpoints with filters/reachability badges and DTOs once schemas stabilize. | +| 8 | CONSOLE-VEX-30-001 | BLOCKED (2025-11-19) | Blocked on WEB-CONSOLE-23-001 and Excititor console contract (SSE payload validation). | Console Guild; BE-Base Platform Guild | `/console/vex/events` SSE workspace with validated schemas and samples. | +| 9 | WEB-CONSOLE-23-002 | TODO | Depends on WEB-CONSOLE-23-001; design heartbeat/backoff + auth scopes. | BE-Base Platform Guild; Scheduler Guild | `/console/status` polling and `/console/runs/{id}/stream` SSE/WebSocket proxy with queue lag metrics. | +| 10 | WEB-CONSOLE-23-003 | TODO | Depends on WEB-CONSOLE-23-002; confirm bundle orchestration flow. | BE-Base Platform Guild; Policy Guild | `/console/exports` POST/GET for evidence bundles, streaming CSV/JSON, checksum manifest, signed attestations. | +| 11 | WEB-CONSOLE-23-004 | TODO | Depends on WEB-CONSOLE-23-003; set caching and tie-break order. | BE-Base Platform Guild | `/console/search` fan-out with deterministic ranking and result caps. | +| 12 | WEB-CONSOLE-23-005 | TODO | Depends on WEB-CONSOLE-23-004; populate manifest source from signed registry metadata. | BE-Base Platform Guild; DevOps Guild | `/console/downloads` manifest (images, charts, offline bundles) with integrity hashes and offline instructions. | +| 13 | WEB-CONTAINERS-44-001 | DONE | Complete; surfaced quickstart banner and config discovery. | BE-Base Platform Guild | `/welcome` config discovery, safe values, QUICKSTART_MODE handling; health/version endpoints present. | +| 14 | WEB-CONTAINERS-45-001 | DONE | Complete; helm probe assets published. | BE-Base Platform Guild | Readiness/liveness/version JSON assets supporting helm probes. | +| 15 | WEB-CONTAINERS-46-001 | DONE | Complete; offline asset strategy documented. | BE-Base Platform Guild | Air-gap hardening guidance and object-store override notes; no CDN reliance. | +| 16 | WEB-EXC-25-001 | TODO | Define validation + audit logging rules; align with policy scopes. | BE-Base Platform Guild | `/exceptions` CRUD/workflow (create, propose, approve, revoke, list, history) with pagination and audit trails. | + +## Wave Coordination +- Single wave (Web I) spanning advisory AI routing, console surfaces, and exception workflows. + +## Wave Detail Snapshots +- Not required (single wave); task-level updates captured in Delivery Tracker and Execution Log. + +## Interlocks +- Console schemas: Concelier graph and Excititor console contract must freeze before VULN/VEX tasks proceed. +- Scheduler/Signals integration required for SSE streams in WEB-CONSOLE-23-002 and downstream tasks. +- Policy guild input needed for evidence export scoping (WEB-CONSOLE-23-003) and exceptions workflow (WEB-EXC-25-001). + +## Upcoming Checkpoints +- 2025-11-25 (tentative): Contract freeze review for WEB-CONSOLE-23-001 with Concelier and Excititor owners. +- 2025-11-27 (tentative): Scheduler/Signals alignment on SSE topics for WEB-CONSOLE-23-002. + +## Action Tracker +- Concelier graph schema freeze date confirmation (owner: Console Guild; due: 2025-11-25). +- Excititor SSE payload validation session scheduled with Scheduler Guild (owner: BE-Base Platform; due: 2025-11-27). + +## Decisions & Risks +| Risk | Impact | Mitigation | Owner | Status | +| --- | --- | --- | --- | --- | +| Console contract freeze slips past 2025-11-25 | Blocks CONSOLE-VULN-29-001 and CONSOLE-VEX-30-001, delays console workspaces | Hold contract review on 2025-11-25; publish schema snapshot to `docs/api/console/workspaces.md`; keep blockers logged | Console Guild | Open | +| SSE topic alignment delayed | WEB-CONSOLE-23-002/003/004 latency and reliability uncertain | Schedule alignment with Scheduler/Signals by 2025-11-27; add heartbeat/backoff defaults; capture examples in samples directory | BE-Base Platform Guild | Open | + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2025-11-07 | Enforced unknown-field detection, added shared `AocError` payload (HTTP + CLI), refreshed guard docs, and extended tests/endpoint helpers. | BE-Base Platform Guild | +| 2025-11-07 | API scaffolding started for console workspace; `docs/advisory-ai/console.md` using placeholder responses while endpoints wire up. | Console Guild | +| 2025-11-08 | Built filters + reachability badge wiring and `/console/vuln/search` DTOs; aligned Scheduler/Signals dependencies. | Console Guild | +| 2025-11-08 | Published HTTP contract + sample payloads in `docs/api/console/workspaces.md` and `docs/api/console/samples/vuln-findings-sample.json` for docs staging. | Console Guild | +| 2025-11-08 | Captured SSE schema + NDJSON sample in `docs/api/console/samples/vex-statement-sse.ndjson`; awaiting Scheduler topic hook-up. | Console Guild | +| 2025-11-18 | WEB-CONTAINERS-44-001 completed: quickstart banner, `/welcome` config discovery page, sample safe config values. | BE-Base Platform Guild | +| 2025-11-19 | WEB-CONTAINERS-45-001 completed: readiness/liveness/version JSON assets added for helm probes. | BE-Base Platform Guild | +| 2025-11-19 | CONSOLE-VULN-29-001 and CONSOLE-VEX-30-001 marked BLOCKED pending WEB-CONSOLE-23-001 and upstream schemas (Concelier/Excititor). | Console Guild | +| 2025-11-22 | Normalized sprint to template and renamed from `SPRINT_212_web_i.md` to `SPRINT_0212_0001_0001_web_i.md`; no scope changes. | Planning | diff --git a/docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md b/docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md new file mode 100644 index 000000000..8f1d25bb1 --- /dev/null +++ b/docs/implplan/SPRINT_0401_0001_0001_reachability_evidence_chain.md @@ -0,0 +1,125 @@ +# Sprint 0401 · Reachability Evidence Chain + +## Topic & Scope +- Window: 2025-11-11 → 2025-11-22 (UTC); finish the provable reachability pipeline so Sprint 0402 can focus on polish. +- Deliver function-level evidence chain (graph CAS → replay → DSSE → policy/UI) with signed artifacts and replayable fixtures. +- Ship operator-facing docs/runbooks plus benchmarks that validate deterministic reachability scoring. +- **Working directory:** docs/implplan (cross-guild coordination; implementation happens in module paths noted per task). + +## Dependencies & Concurrency +- Upstream: Sprint 0400 foundation plus Sprint 0140 Runtime & Signals, Sprint 0185 Replay Core, Sprint 0186 Scanner Record Mode, Sprint 0187 Evidence Locker & CLI Integration. +- Do not start reachability build tasks until Scanner record mode emits replay manifests and Evidence Locker APIs are reachable. +- Guilds may execute in parallel once CAS contracts and DSSE predicate schemas stabilise; keep deterministic ordering of manifests/fixtures. + +## Documentation Prerequisites +- docs/reachability/DELIVERY_GUIDE.md +- docs/reachability/function-level-evidence.md +- docs/reachability/lattice.md +- docs/benchmarks/vex-evidence-playbook.md +- docs/09_API_CLI_REFERENCE.md +- docs/modules/scanner/architecture.md +- docs/modules/policy/architecture.md +- docs/modules/excititor/architecture.md +- docs/modules/cli/architecture.md +- docs/modules/signer/architecture.md +- docs/specs/SYMBOL_MANIFEST_v1.md +- docs/policy/dsl.md +- docs/policy/lifecycle.md +- docs/uncertainty/README.md +- docs/replay/DETERMINISTIC_REPLAY.md +- docs/provenance/inline-dsse.md +- docs/ci/dsse-build-flow.md + +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | +| --- | --- | --- | --- | --- | --- | +| 1 | GRAPH-CAS-401-001 | TODO | Await richgraph-v1 schema approval and CAS layout alignment. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`) | Finalize richgraph schema, emit canonical SymbolIDs, compute graph hash (BLAKE3), store manifests under `cas://reachability/graphs/{sha256}`, update adapters/fixtures. | +| 2 | GAP-SYM-007 | TODO | Align with GRAPH-CAS-401-001; keep DTOs/docs deterministic. | Scanner Worker Guild · Docs Guild (`src/Scanner/StellaOps.Scanner.Models`, `docs/modules/scanner/architecture.md`, `docs/reachability/function-level-evidence.md`) | Extend evidence schema with demangled hints, `symbol.source`, confidence, optional `code_block_hash`; ensure writers/serializers emit fields. | +| 3 | SCAN-REACH-401-009 | TODO | Needs symbolizer adapters from tasks 1/4; add golden fixtures. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`, `src/Scanner/__Libraries`) | Ship .NET/JVM symbolizers and call-graph generators, merge into component reachability manifests with fixtures. | +| 4 | SCANNER-NATIVE-401-015 | TODO | Stand up native readers/demanglers; coordinate with Symbols Server. | Scanner Worker Guild (`src/Scanner/__Libraries/StellaOps.Scanner.Symbols.Native`, `src/Scanner/__Libraries/StellaOps.Scanner.CallGraph.Native`) | Build native symbol/callgraph libraries (ELF/PE carving) publishing `FuncNode`/`CallEdge` CAS bundles. | +| 5 | SYMS-SERVER-401-011 | TODO | Blocked on DSSE predicate catalog + storage layout confirmation. | Symbols Guild (`src/Symbols/StellaOps.Symbols.Server`) | Deliver Symbols Server (REST+gRPC) with DSSE-verified uploads, Mongo/MinIO storage, tenant isolation, deterministic debugId indexing, health/manifest APIs. | +| 6 | SYMS-CLIENT-401-012 | TODO | Depends on server readiness; integrate with Scanner Symbolizer. | Symbols Guild (`src/Symbols/StellaOps.Symbols.Client`, `src/Scanner/StellaOps.Scanner.Symbolizer`) | Ship Symbols Client SDK (resolve/upload, platform key derivation, disk LRU cache) and integrate with Scanner/runtime probes. | +| 7 | SYMS-INGEST-401-013 | TODO | Follow SYMBOL_MANIFEST spec final; document pipelines. | Symbols Guild · DevOps Guild (`src/Symbols/StellaOps.Symbols.Ingestor.Cli`, `docs/specs/SYMBOL_MANIFEST_v1.md`) | Build `symbols ingest` CLI to emit DSSE-signed manifests, upload blobs, register Rekor entries, and document CI usage. | +| 8 | SIGNALS-RUNTIME-401-002 | TODO | Wait for Signals ingestion contract from upstream runtime work. | Signals Guild (`src/Signals/StellaOps.Signals`) | Ship `/signals/runtime-facts` ingestion for NDJSON/gzip, dedupe hits, link evidence CAS URIs to callgraph nodes; include retention/RBAC tests. | +| 9 | RUNTIME-PROBE-401-010 | TODO | Depends on probe collectors; align with ingestion endpoint. | Runtime Signals Guild (`src/Signals/StellaOps.Signals.Runtime`, `ops/probes`) | Implement lightweight runtime probes (EventPipe/JFR) emitting CAS traces feeding Signals ingestion. | +| 10 | SIGNALS-SCORING-401-003 | TODO | Needs runtime hit feeds from 8/9; confirm scoring weights. | Signals Guild (`src/Signals/StellaOps.Signals`) | Extend ReachabilityScoringService with deterministic scoring, persist labels, expose `/graphs/{scanId}` CAS lookups. | +| 11 | REPLAY-401-004 | TODO | Requires CAS registration policy from GAP-REP-004. | BE-Base Platform Guild (`src/__Libraries/StellaOps.Replay.Core`) | Bump replay manifest to v2, enforce CAS registration + hash sorting in ReachabilityReplayWriter, add deterministic tests. | +| 12 | AUTH-REACH-401-005 | TODO | Blocked on DSSE predicate definitions; align with Signer. | Authority & Signer Guilds (`src/Authority/StellaOps.Authority`, `src/Signer/StellaOps.Signer`) | Introduce DSSE predicate types for SBOM/Graph/VEX/Replay, plumb signing, mirror statements to Rekor (incl. PQ variants). | +| 13 | POLICY-VEX-401-006 | TODO | Needs reachability facts from Signals and thresholds confirmation. | Policy Guild (`src/Policy/StellaOps.Policy.Engine`, `src/Policy/__Libraries/StellaOps.Policy`) | Consume reachability facts, bucket scores, emit OpenVEX with call-path proofs, update SPL schema with reachability predicates and suppression gates. | +| 14 | POLICY-VEX-401-010 | TODO | Depends on 13 and DSSE path; follow bench playbook. | Policy Guild (`src/Policy/StellaOps.Policy.Engine/Vex`, `docs/modules/policy/architecture.md`, `docs/benchmarks/vex-evidence-playbook.md`) | Implement VexDecisionEmitter to serialize per-finding OpenVEX, attach evidence hashes, request DSSE signatures, capture Rekor metadata. | +| 15 | UI-CLI-401-007 | TODO | Requires graph CAS outputs + policy evidence; sync CLI/UI. | UI & CLI Guilds (`src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`) | Implement CLI `stella graph explain` and UI explain drawer with signed call-path, predicates, runtime hits, DSSE pointers, counterfactual controls. | +| 16 | QA-DOCS-401-008 | TODO | Needs reachbench fixtures (QA-CORPUS-401-031) and docs readiness. | QA & Docs Guilds (`docs`, `tests/README.md`) | Wire reachbench fixtures into CI, document CAS layouts + replay steps, publish operator runbook for runtime ingestion. | +| 17 | GAP-SIG-003 | TODO | Depends on Signals runtime ingestion (8) completion. | Signals Guild (`src/Signals/StellaOps.Signals`, `docs/reachability/function-level-evidence.md`) | Finish `/signals/runtime-facts` ingestion, add CAS-backed runtime storage, extend scoring to lattice states, emit update events, document retention/RBAC. | +| 18 | SIG-STORE-401-016 | TODO | Needs schema from graph tasks; align indexes. | Signals Guild · BE-Base Platform Guild (`src/Signals/StellaOps.Signals`, `src/__Libraries/StellaOps.Replay.Core`) | Introduce shared reachability store collections/indexes and repository APIs for canonical function data. | +| 19 | GAP-REP-004 | TODO | Requires BLAKE3 hashing agreement; tie to replay manifest v2. | BE-Base Platform Guild (`src/__Libraries/StellaOps.Replay.Core`, `docs/replay/DETERMINISTIC_REPLAY.md`) | Enforce BLAKE3 hashing + CAS registration for graphs/traces, upgrade replay manifest v2, add deterministic tests. | +| 20 | GAP-POL-005 | TODO | Consumes reach facts from Signals; update SPL schema. | Policy Guild (`src/Policy/StellaOps.Policy.Engine`, `docs/modules/policy/architecture.md`, `docs/reachability/function-level-evidence.md`) | Ingest reachability facts into Policy Engine, expose `reachability.state/confidence`, enforce auto-suppress rules, generate OpenVEX evidence blocks. | +| 21 | GAP-VEX-006 | TODO | Follows GAP-POL-005 plus UI/CLI surfaces. | Policy, Excititor, UI, CLI & Notify Guilds (`docs/modules/excititor/architecture.md`, `src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`, `docs/09_API_CLI_REFERENCE.md`) | Wire VEX emission/explain drawers to show call paths, graph hashes, runtime hits; add CLI flags and Notify templates. | +| 22 | GAP-DOC-008 | TODO | After evidence schema stabilises; publish samples. | Docs Guild (`docs/reachability/function-level-evidence.md`, `docs/09_API_CLI_REFERENCE.md`, `docs/api/policy.md`) | Publish cross-module function-level evidence guide, update API/CLI references with `code_id`, add OpenVEX/replay samples. | +| 23 | CLI-VEX-401-011 | TODO | Needs Policy/Signer APIs from 13–14. | CLI Guild (`src/Cli/StellaOps.Cli`, `docs/modules/cli/architecture.md`, `docs/benchmarks/vex-evidence-playbook.md`) | Add `stella decision export|verify|compare`, integrate with Policy/Signer APIs, ship local verifier wrappers for bench artifacts. | +| 24 | SIGN-VEX-401-018 | TODO | Requires Authority predicates and DSSE path from 12. | Signing Guild (`src/Signer/StellaOps.Signer`, `docs/modules/signer/architecture.md`) | Extend Signer predicate catalog with `stella.ops/vexDecision@v1`, enforce payload policy, plumb DSSE/Rekor integration. | +| 25 | BENCH-AUTO-401-019 | TODO | Depends on data sets and baseline scanner setup. | Benchmarks Guild (`docs/benchmarks/vex-evidence-playbook.md`, `scripts/bench/**`) | Automate population of `bench/findings/**`, run baseline scanners, compute FP/MTTD/repro metrics, update `results/summary.csv`. | +| 26 | DOCS-VEX-401-012 | TODO | Align with GAP-DOC-008 and bench playbook. | Docs Guild (`docs/benchmarks/vex-evidence-playbook.md`, `bench/README.md`) | Maintain VEX Evidence Playbook, publish repo templates/README, document verification workflows. | +| 27 | SYMS-BUNDLE-401-014 | TODO | Depends on SYMBOL_MANIFEST spec and ingest pipeline. | Symbols Guild · Ops Guild (`src/Symbols/StellaOps.Symbols.Bundle`, `ops`) | Produce deterministic symbol bundles for air-gapped installs with DSSE manifests/Rekor checkpoints; document offline workflows. | +| 28 | DOCS-RUNBOOK-401-017 | TODO | Needs runtime ingestion guidance; align with DELIVERY_GUIDE. | Docs Guild · Ops Guild (`docs/runbooks/reachability-runtime.md`, `docs/reachability/DELIVERY_GUIDE.md`) | Publish reachability runtime ingestion runbook, link from delivery guides, keep Ops/Signals troubleshooting current. | +| 29 | POLICY-LIB-401-001 | TODO | Extract DSL parser; align with Policy Engine tasks. | Policy Guild (`src/Policy/StellaOps.PolicyDsl`, `docs/policy/dsl.md`) | Extract policy DSL parser/compiler into `StellaOps.PolicyDsl`, add lightweight syntax, expose `PolicyEngineFactory`/`SignalContext`. | +| 30 | POLICY-LIB-401-002 | TODO | Follows 29; add harness and CLI wiring. | Policy Guild · CLI Guild (`tests/Policy/StellaOps.PolicyDsl.Tests`, `policy/default.dsl`, `docs/policy/lifecycle.md`) | Ship unit-test harness + sample DSL, wire `stella policy lint/simulate` to shared library. | +| 31 | POLICY-ENGINE-401-003 | TODO | Depends on 29/30; ensure determinism hashes stable. | Policy Guild (`src/Policy/StellaOps.Policy.Engine`, `docs/modules/policy/architecture.md`) | Replace in-service DSL compilation with shared library, support legacy packs and inline syntax, keep determinism stable. | +| 32 | CLI-EDITOR-401-004 | TODO | Relies on shared DSL lib; add git edit flow. | CLI Guild (`src/Cli/StellaOps.Cli`, `docs/policy/lifecycle.md`) | Enhance `stella policy` verbs (edit/lint/simulate) to edit Git-backed DSL files, run coverage tests, commit SemVer metadata. | +| 33 | DOCS-DSL-401-005 | TODO | Docs follow 29–32 and Signals dictionary updates. | Docs Guild (`docs/policy/dsl.md`, `docs/policy/lifecycle.md`) | Refresh DSL docs with new syntax, signal dictionary (`trust_score`, `reachability`, etc.), authoring workflow, safety rails. | +| 34 | DSSE-LIB-401-020 | TODO | Align with DSSE predicate work; reusable lib. | Attestor Guild · Platform Guild (`src/Attestor/StellaOps.Attestation`, `src/Attestor/StellaOps.Attestor.Envelope`) | Package `StellaOps.Attestor.Envelope` primitives into reusable `StellaOps.Attestation` library with InToto/DSSE helpers. | +| 35 | DSSE-CLI-401-021 | TODO | Depends on 34; deliver CLI/workflow snippets. | CLI Guild · DevOps Guild (`src/Cli/StellaOps.Cli`, `scripts/ci/attest-*`, `docs/modules/attestor/architecture.md`) | Ship `stella attest` CLI or sample tool plus GitLab/GitHub workflow snippets emitting DSSE per build step. | +| 36 | DSSE-DOCS-401-022 | TODO | Follows 34/35; document build-time flow. | Docs Guild · Attestor Guild (`docs/ci/dsse-build-flow.md`, `docs/modules/attestor/architecture.md`) | Document build-time attestation walkthrough: models, helper usage, Authority integration, storage conventions, verification commands. | +| 37 | REACH-LATTICE-401-023 | TODO | Align Scanner + Policy schemas; tie to evidence joins. | Scanner Guild · Policy Guild (`docs/reachability/lattice.md`, `docs/modules/scanner/architecture.md`, `src/Scanner/StellaOps.Scanner.WebService`) | Define reachability lattice model and ensure joins write to event graph schema. | +| 38 | UNCERTAINTY-SCHEMA-401-024 | TODO | Schema changes rely on Signals ingestion work. | Signals Guild (`src/Signals/StellaOps.Signals`, `docs/uncertainty/README.md`) | Extend Signals findings with uncertainty states, entropy fields, `riskScore`; emit update events and persist evidence. | +| 39 | UNCERTAINTY-SCORER-401-025 | TODO | Scorer depends on 38 outputs. | Signals Guild (`src/Signals/StellaOps.Signals.Application`, `docs/uncertainty/README.md`) | Implement entropy-aware risk scorer and wire into finding writes. | +| 40 | UNCERTAINTY-POLICY-401-026 | TODO | Guidance depends on 38/39. | Policy Guild · Concelier Guild (`docs/policy/dsl.md`, `docs/uncertainty/README.md`) | Update policy guidance with uncertainty gates (U1/U2/U3), sample YAML rules, remediation actions. | +| 41 | UNCERTAINTY-UI-401-027 | TODO | UI/CLI depends on 38/39 outputs. | UI Guild · CLI Guild (`src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/uncertainty/README.md`) | Surface uncertainty chips/tooltips in Console + CLI output (risk score + entropy states). | +| 42 | PROV-INLINE-401-028 | DONE | Completed inline DSSE hooks per docs. | Authority Guild · Feedser Guild (`docs/provenance/inline-dsse.md`, `src/__Libraries/StellaOps.Provenance.Mongo`) | Extend event writers to attach inline DSSE + Rekor references on every SBOM/VEX/scan event. | +| 43 | PROV-BACKFILL-INPUTS-401-029A | DONE | Inventory/map drafted 2025-11-18. | Evidence Locker Guild · Platform Guild (`docs/provenance/inline-dsse.md`) | Attestation inventory and subject→Rekor map drafted. | +| 44 | PROV-BACKFILL-401-029 | TODO | Use inventory+map; depends on 42/43 readiness. | Platform Guild (`docs/provenance/inline-dsse.md`, `scripts/publish_attestation_with_provenance.sh`) | Resolve historical events and backfill provenance. | +| 45 | PROV-INDEX-401-030 | TODO | Blocked until 44 defines data model. | Platform Guild · Ops Guild (`docs/provenance/inline-dsse.md`, `ops/mongo/indices/events_provenance_indices.js`) | Deploy provenance indexes and expose compliance/replay queries. | +| 46 | QA-CORPUS-401-031 | TODO | Needs reachbench corpus creation; align with QA harness. | QA Guild · Scanner Guild (`tests/reachability`, `docs/reachability/DELIVERY_GUIDE.md`) | Build/publish multi-runtime reachability corpus with ground truths and traces; wire fixtures into CI. | +| 47 | UI-VEX-401-032 | TODO | Depends on policy/CLI evidence chain (13–15,21). | UI Guild · CLI Guild · Scanner Guild (`src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/reachability/function-level-evidence.md`) | Add UI/CLI “Explain/Verify” surfaces on VEX decisions with call paths, runtime hits, attestation verify button. | +| 48 | POLICY-GATE-401-033 | TODO | Gate depends on Signals/Scanner reach evidence. | Policy Guild · Scanner Guild (`src/Policy/StellaOps.Policy.Engine`, `docs/policy/dsl.md`, `docs/modules/scanner/architecture.md`) | Enforce policy gate requiring reachability evidence for `not_affected`/`unreachable`; fallback to under review on low confidence; update docs/tests. | +| 49 | GRAPH-PURL-401-034 | TODO | Needs graph schema from 1 and signals store alignment. | Scanner Worker Guild · Signals Guild (`src/Scanner/StellaOps.Scanner.Worker`, `src/Signals/StellaOps.Signals`, `docs/reachability/purl-resolved-edges.md`) | Annotate call edges with callee purl + `symbol_digest`, update schema/CAS, surface in CLI/UI. | +| 50 | SCANNER-BUILDID-401-035 | TODO | Depends on scanner symbol work and fixtures. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`, `docs/modules/scanner/architecture.md`) | Capture `.note.gnu.build-id` for ELF targets, thread into `SymbolID`/`code_id`, SBOM exports, runtime facts; add fixtures. | +| 51 | SCANNER-INITROOT-401-036 | TODO | Requires graph writer updates from 1. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`, `docs/modules/scanner/architecture.md`) | Model init sections as synthetic graph roots (phase=load) including `DT_NEEDED` deps; persist in evidence. | +| 52 | QA-PORACLE-401-037 | TODO | Depends on reachability graph fixtures; add CI harness. | QA Guild · Scanner Worker Guild (`tests/reachability`, `docs/reachability/patch-oracles.md`) | Add patch-oracle fixtures and harness comparing graphs vs oracle, fail CI when expected functions/edges missing. | + +## Wave Coordination +| Wave | Guild owners | Shared prerequisites | Status | Notes | +| --- | --- | --- | --- | --- | +| 0401 Reachability Evidence Chain | Scanner Guild · Signals Guild · BE-Base Platform Guild · Policy Guild · UI/CLI Guilds · Docs Guild | Sprint 0140 Runtime & Signals; Sprint 0185 Replay Core; Sprint 0186 Scanner Record Mode; Sprint 0187 Evidence Locker & CLI Integration | TODO | Foundation work (Sprint 0400) in flight; advance after Scanner record mode emits replay manifests and Evidence Locker APIs exist. | + +## Wave Detail Snapshots +- Single wave covering end-to-end reachability evidence; proceed once Sprint 0400 + upstream runtime/replay prerequisites land. + +## Interlocks +- CAS hash/predicate choices must stay consistent across Scanner, Signals, Replay, and Policy (tasks 1, 11, 19, 24). +- DSSE predicate catalog and Signer integration (tasks 12, 24, 34–36) gate VEX and provenance tasks. +- UI/CLI explainers (tasks 15, 21, 47) depend on policy reachability outputs and graph schema stabilization. + +## Upcoming Checkpoints +- Schedule go/no-go once Sprint 0400 readiness is confirmed (TBD, Planning). +- Align DSSE predicate review across Authority/Signer/Policy once task 12 schema draft is ready (TBD, Authority Guild). + +## Action Tracker +| # | Action | Owner | Due (UTC) | Status | Notes | +| --- | --- | --- | --- | --- | --- | +| 1 | Capture checkpoint dates after Sprint 0400 closure signal. | Planning | TBD | Open | Waiting on Sprint 0400 readiness update. | +| 2 | Confirm CAS hash alignment (BLAKE3 + sha256 addressing) across Scanner/Replay/Signals. | Platform Guild | TBD | Open | Coordinate tasks 1 and 19. | + +## Decisions & Risks +- File renamed to `SPRINT_0401_0001_0001_reachability_evidence_chain.md` and normalized to template on 2025-11-22; scope unchanged. + +| ID | Risk | Impact | Mitigation / Owner | +| --- | --- | --- | --- | +| R1 | Sprint 0400 and upstream runtime/replay prerequisites slip. | Delivery blocked; evidence chain cannot start. | Track readiness in checkpoints; hold start until record mode + Evidence Locker APIs land (Planning). | +| R2 | CAS hash/predicate mismatch across modules. | Inconsistent artifacts, replay failures. | Align specs via tasks 1, 11, 19, 24; review before implementation (Platform Guild). | +| R3 | Determinism gaps in fixtures/benchmarks. | Flaky reachability scoring and VEX proofs. | Prioritize QA tasks 16, 25, 46, 52; enforce deterministic ordering in tests (QA Guild). | + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2025-11-22 | Normalized sprint to template, added dependencies/prereqs, Delivery Tracker numbering, interlocks, risks; renamed file for naming compliance. | Planning | +| 2025-11-20 | Added tasks for purl-resolved edges, ELF build-id propagation, init-array roots, and patch-oracle QA harness; aligned docs references. | Planning | diff --git a/docs/implplan/SPRINT_0512_0001_0001_bench.md b/docs/implplan/SPRINT_0512_0001_0001_bench.md index 8c7047a1b..dc21c6325 100644 --- a/docs/implplan/SPRINT_0512_0001_0001_bench.md +++ b/docs/implplan/SPRINT_0512_0001_0001_bench.md @@ -1,24 +1,24 @@ -# Sprint 0512 · Ops & Offline · Bench (190.G) - -## Topic & Scope -- Build and capture performance benchmarks for graph, UI interactions, impact index, policy deltas, and reachability scoring to support offline/ops readiness. -- Target harnesses under `src/Bench/StellaOps.Bench` with reproducible datasets. -- **Working directory:** `src/Bench/StellaOps.Bench`. - -## Dependencies & Concurrency -- Upstream data: graph fixtures (SAMPLES-GRAPH-24-003), reachability schema (Sprint 0400/0401), policy delta inputs. -- UI bench depends on BENCH-GRAPH-21-001/002 harness foundation. - -## Documentation Prerequisites -- docs/07_HIGH_LEVEL_ARCHITECTURE.md -- docs/modules/platform/architecture-overview.md -- docs/modules/graph/architecture.md (for graph bench scenarios) -- docs/modules/signals/architecture.md (for reachability benches) -- docs/modules/policy/architecture.md - -## Delivery Tracker -| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | -| --- | --- | --- | --- | --- | --- | +# Sprint 0512 · Ops & Offline · Bench (190.G) + +## Topic & Scope +- Build and capture performance benchmarks for graph, UI interactions, impact index, policy deltas, and reachability scoring to support offline/ops readiness. +- Target harnesses under `src/Bench/StellaOps.Bench` with reproducible datasets. +- **Working directory:** `src/Bench/StellaOps.Bench`. + +## Dependencies & Concurrency +- Upstream data: graph fixtures (SAMPLES-GRAPH-24-003), reachability schema (Sprint 0400/0401), policy delta inputs. +- UI bench depends on BENCH-GRAPH-21-001/002 harness foundation. + +## Documentation Prerequisites +- docs/07_HIGH_LEVEL_ARCHITECTURE.md +- docs/modules/platform/architecture-overview.md +- docs/modules/graph/architecture.md (for graph bench scenarios) +- docs/modules/signals/architecture.md (for reachability benches) +- docs/modules/policy/architecture.md + +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | +| --- | --- | --- | --- | --- | --- | | P1 | PREP-BENCH-GRAPH-21-001-NEED-GRAPH-BENCH-HARN | DONE (2025-11-20) | Prep doc at `docs/benchmarks/graph/bench-graph-21-001-prep.md`; awaits fixtures (SAMPLES-GRAPH-24-003). | Bench Guild · Graph Platform Guild | Need graph bench harness scaffolding (50k/100k nodes).

Document artefact/deliverable for BENCH-GRAPH-21-001 and publish location so downstream tasks can proceed. | | P2 | PREP-BENCH-GRAPH-21-002-BLOCKED-ON-21-001-HAR | DONE (2025-11-20) | Due 2025-11-26 · Accountable: Bench Guild · UI Guild | Bench Guild · UI Guild | Prep artefact published at `docs/benchmarks/graph/bench-graph-21-002-prep.md` (Playwright UI bench plan leveraging 50k/100k fixtures; scenarios, metrics, determinism). | | P3 | PREP-BENCH-IMPACT-16-001-IMPACT-INDEX-DATASET | DONE (2025-11-20) | Due 2025-11-26 · Accountable: Bench Guild · Scheduler Team | Bench Guild · Scheduler Team | Prep artefact published at `docs/benchmarks/impact/bench-impact-16-001-prep.md` (dataset shape, replay plan, deterministic metrics). | @@ -31,11 +31,44 @@ | 4 | BENCH-IMPACT-16-001 | BLOCKED | PREP-BENCH-IMPACT-16-001-IMPACT-INDEX-DATASET | Bench Guild · Scheduler Team | ImpactIndex throughput bench (resolve 10k productKeys) + RAM profile. | | 5 | BENCH-POLICY-20-002 | BLOCKED | PREP-BENCH-POLICY-20-002-POLICY-DELTA-SAMPLE | Bench Guild · Policy Guild · Scheduler Guild | Add incremental run benchmark measuring delta evaluation vs full; capture SLA compliance. | | 6 | BENCH-SIG-26-001 | BLOCKED | PREP-BENCH-SIG-26-001-REACHABILITY-SCHEMA-FIX | Bench Guild · Signals Guild | Develop benchmark for reachability scoring pipeline (facts/sec, latency, memory) using synthetic callgraphs/runtime batches. | -| 7 | BENCH-SIG-26-002 | BLOCKED | PREP-BENCH-SIG-26-002-BLOCKED-ON-26-001-OUTPU | Bench Guild · Policy Guild | Measure policy evaluation overhead with reachability cache hot/cold; ensure ≤8 ms p95 added latency. | - -## Execution Log +| 7 | BENCH-SIG-26-002 | BLOCKED | PREP-BENCH-SIG-26-002-BLOCKED-ON-26-001-OUTPU | Bench Guild · Policy Guild | Measure policy evaluation overhead with reachability cache hot/cold; ensure ≤8 ms p95 added latency. | + +## Wave Coordination +- Single wave; benches sequenced by dataset availability. No parallel wave gating beyond Delivery Tracker dependencies. + +## Wave Detail Snapshots +- N/A (single wave). Add per-wave snapshots if additional waves are introduced. + +## Interlocks +- Graph fixtures SAMPLES-GRAPH-24-003 delivery (Bench Guild ↔ Graph Platform Guild). +- Reachability schema alignment from Sprints 0400/0401 (Signals Guild ↔ Policy Guild). +- Policy delta dataset delivery (Policy Guild ↔ Scheduler Guild). + +## Upcoming Checkpoints +- 2025-11-22 · Confirm availability of graph fixtures for BENCH-GRAPH-21-001/002/24-002. Owner: Bench Guild. +- 2025-11-24 · Reachability schema alignment outcome to unblock BENCH-SIG-26-001. Owner: Signals Guild. +- 2025-11-26 · Decide impact index dataset for BENCH-IMPACT-16-001. Owner: Scheduler Team. + +## Action Tracker +| Action ID | Status | Owner | Due (UTC) | Details | +| --- | --- | --- | --- | --- | +| ACT-0512-01 | PENDING | Bench Guild | 2025-11-22 | Confirm SAMPLES-GRAPH-24-003 fixtures availability and publish location for BENCH-GRAPH-21-001/002/24-002. | +| ACT-0512-02 | PENDING | Signals Guild | 2025-11-24 | Provide reachability schema hash/output to unblock BENCH-SIG-26-001/002. | +| ACT-0512-03 | PENDING | Scheduler Team | 2025-11-26 | Finalize impact index dataset selection and share deterministic replay bundle. | + +## Decisions & Risks +| Risk | Impact | Mitigation | Status | Owner | Due (UTC) | +| --- | --- | --- | --- | --- | --- | +| Graph fixtures SAMPLES-GRAPH-24-003 not delivered | Blocks BENCH-GRAPH-21-001/002/24-002; benches unstartable | Track via ACT-0512-01; escalate to Graph Platform Guild if missed | Open | Bench Guild | 2025-11-22 | +| Reachability schema hash pending from Sprint 0400/0401 | BENCH-SIG-26-001/002 remain blocked | ACT-0512-02 to deliver schema hash + fixtures; add fallback synthetic set | Open | Signals Guild | 2025-11-24 | +| Impact index dataset undecided | BENCH-IMPACT-16-001 stalled; no reproducibility | ACT-0512-03 to finalize dataset; require deterministic replay bundle | Open | Scheduler Team | 2025-11-26 | + +- Determinism risk: ensure all benches avoid online dependencies and pin datasets; review when fixtures arrive. + +## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-11-22 | Normalised sprint to implplan template (added Wave/Interlocks/Action sections; renamed Next Checkpoints → Upcoming Checkpoints); no task status changes. | Project Mgmt | | 2025-11-20 | Completed PREP-BENCH-GRAPH-21-002: published UI bench prep doc at `docs/benchmarks/graph/bench-graph-21-002-prep.md`; status set to DONE. | Implementer | | 2025-11-20 | Completed PREP-BENCH-IMPACT-16-001: published impact index bench prep doc at `docs/benchmarks/impact/bench-impact-16-001-prep.md`; status set to DONE. | Implementer | | 2025-11-20 | Completed PREP-BENCH-POLICY-20-002: published policy delta bench prep doc at `docs/benchmarks/policy/bench-policy-20-002-prep.md`; status set to DONE. | Implementer | @@ -43,14 +76,4 @@ | 2025-11-19 | Trimmed trailing hyphen from PREP-BENCH-POLICY-20-002 Task ID to keep BENCH-POLICY-20-002 blocker resolvable. | Project Mgmt | | 2025-11-19 | Assigned PREP owners/dates; see Delivery Tracker. | Planning | | 2025-11-18 | Marked BENCH-GRAPH-24-002, BENCH-IMPACT-16-001, BENCH-POLICY-20-002, BENCH-SIG-26-001/002 as BLOCKED pending fixtures/datasets and reachability schema. | Bench | -| 2025-11-18 | Normalised sprint to standard template; renamed from SPRINT_512_bench.md. | Ops/Docs | - -## Decisions & Risks -- Graph/UI benches depend on large fixtures (SAMPLES-GRAPH-24-003) and graph overlay schema; risk until fixtures land. -- Reachability benches depend on runtime/static schema alignment (Sprint 0400/0401) and fixture relocation. -- Policy/Impact benches require deterministic datasets; ensure no online dependencies. - -## Next Checkpoints -- 2025-11-22 · Confirm availability of graph fixtures for BENCH-GRAPH-21-001/002/24-002. Owner: Bench Guild. -- 2025-11-24 · Reachability schema alignment outcome to unblock BENCH-SIG-26-001. Owner: Signals Guild. -- 2025-11-26 · Decide impact index dataset for BENCH-IMPACT-16-001. Owner: Scheduler Team. +| 2025-11-18 | Normalised sprint to standard template; renamed from SPRINT_512_bench.md. | Ops/Docs | diff --git a/docs/implplan/SPRINT_0513_0001_0001_provenance.md b/docs/implplan/SPRINT_0513_0001_0001_provenance.md new file mode 100644 index 000000000..2d7c6ec30 --- /dev/null +++ b/docs/implplan/SPRINT_0513_0001_0001_provenance.md @@ -0,0 +1,71 @@ +# Sprint 0513-0001-0001 · Ops & Offline · Provenance + +## Topic & Scope +- Prove container provenance offline: model DSSE/SLSA build metadata, signing flows, and promotion predicates for orchestrator/job/export subjects. +- Deliver signing + verification toolchain that is deterministic, air-gap ready, and consumable from CLI (`stella forensic verify`) and services. +- Working directory: `src/Provenance/StellaOps.Provenance.Attestation`. Active items only; completed/historic work lives in `docs/implplan/archived/tasks.md` (updated 2025-11-08). + +## Dependencies & Concurrency +- Upstream sprints: 100.A Attestor, 110.A AdvisoryAI, 120.A AirGap, 130.A Scanner, 140.A Graph, 150.A Orchestrator, 160.A EvidenceLocker, 170.A Notifier, 180.A CLI. +- Task sequencing: PROV-OBS-53-001 → PROV-OBS-53-002 → PROV-OBS-53-003 → PROV-OBS-54-001 → PROV-OBS-54-002; downstream tasks stay TODO/BLOCKED until predecessors verify in CI. +- Concurrency guardrails: keep deterministic ordering in Delivery Tracker; no cross-module code changes unless noted under Interlocks. + +## Documentation Prerequisites +- `docs/07_HIGH_LEVEL_ARCHITECTURE.md` +- `docs/modules/platform/architecture-overview.md` +- `docs/modules/attestor/architecture.md` +- `docs/modules/signer/architecture.md` +- `docs/modules/orchestrator/architecture.md` +- `docs/modules/export-center/architecture.md` + +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | +| --- | --- | --- | --- | --- | --- | +| 1 | PROV-OBS-53-001 | DONE (2025-11-17) | Baseline models available for downstream tasks | Provenance Guild / `src/Provenance/StellaOps.Provenance.Attestation` | Implement DSSE/SLSA `BuildDefinition` + `BuildMetadata` models with canonical JSON serializer, Merkle digest helpers, deterministic hashing tests, and sample statements for orchestrator/job/export subjects. | +| 2 | PROV-OBS-53-002 | DONE (2025-11-22) | Tests green locally; relies on CI rerun for parity | Provenance Guild; Security Guild / `src/Provenance/StellaOps.Provenance.Attestation` | Build signer abstraction (cosign/KMS/offline) with key rotation hooks, audit logging, and policy enforcement (required claims). Provide unit tests using fake signer + real cosign fixture. | +| 3 | PROV-OBS-53-003 | DONE (2025-11-22) | Promotion predicate builder implemented; depends on 53-002 outputs | Provenance Guild / `src/Provenance/StellaOps.Provenance.Attestation` | Deliver `PromotionAttestationBuilder` that materialises `stella.ops/promotion@v1` predicate (image digest, SBOM/VEX materials, promotion metadata, Rekor proof) and feeds canonicalised payload bytes to Signer via StellaOps.Cryptography. | +| 4 | PROV-OBS-54-001 | TODO | Start after PROV-OBS-53-002 clears; needs signer verified | Provenance Guild; Evidence Locker Guild / `src/Provenance/StellaOps.Provenance.Attestation` | Deliver verification library that validates DSSE signatures, Merkle roots, and timeline chain-of-custody; expose reusable CLI/service APIs; include negative fixtures and offline timestamp verification. | +| 5 | PROV-OBS-54-002 | TODO | Start after PROV-OBS-54-001 verification APIs are stable | Provenance Guild; DevEx/CLI Guild / `src/Provenance/StellaOps.Provenance.Attestation` | Generate .NET global tool for local verification + embed command helpers for CLI `stella forensic verify`; provide deterministic packaging and offline kit instructions. | + +## Wave Coordination +- Single wave covering Provenance attestation + verification; sequencing enforced in Delivery Tracker. + +## Wave Detail Snapshots +- Wave 1 (Provenance chain): Signer abstraction → Promotion predicate builder → Verification library → CLI/global tool packaging. + +## Interlocks +- Attestor/Orchestrator schema alignment for promotion predicates and job/export subjects. +- Evidence Locker timeline proofs required for DSSE verification chain-of-custody. +- CLI integration depends on DevEx/CLI guild packaging conventions. + +## Upcoming Checkpoints +- 2025-11-23 · CI rerun for PROV-OBS-53-002 to resolve MSB6006 and unblock downstream tasks. +- 2025-11-26 · Schema alignment touchpoint with Orchestrator/Attestor guilds on promotion predicate fields. +- 2025-11-29 · Offline kit packaging review for verification global tool (`PROV-OBS-54-002`) with DevEx/CLI guild. + +## Action Tracker +- Schedule CI environment rerun for PROV-OBS-53-002 with full dependency restore and logs attached. +- Prepare schema notes for promotion predicate (image digest, SBOM/VEX materials, Rekor proof) ahead of 2025-11-26 checkpoint. +- Draft offline kit instructions outline for PROV-OBS-54-002 to accelerate packaging once verification APIs land. + +## Decisions & Risks +**Risk table** +| Risk | Impact | Mitigation | Owner | +| --- | --- | --- | --- | +| PROV-OBS-53-002 CI parity pending | If CI differs from local, could reopen downstream | Rerun in CI; publish logs; align SDK version | Provenance Guild | +| Promotion predicate schema mismatch with Orchestrator/Attestor | Rework builder and verification APIs | Hold 2025-11-26 alignment; track deltas in docs; gate merges behind feature flag | Provenance Guild / Orchestrator Guild | +| Offline verification kit drift vs CLI packaging rules | Users cannot verify in air-gap | Pair with DevEx/CLI guild; publish deterministic packaging steps and checksums | DevEx/CLI Guild | + +- PROV-OBS-53-002 remains BLOCKED until CI rerun resolves MSB6006; PROV-OBS-53-003/54-001/54-002 stay gated. +- Archived/complete items move to `docs/implplan/archived/tasks.md` after closure. + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2025-11-22 | PROV-OBS-53-003 delivered: promotion attestation builder signs canonical predicate, enforces predicateType claim, tests passing. | Implementer | +| 2025-11-22 | PROV-OBS-53-002 delivered locally with signer audit/rotation tests; awaiting CI parity confirmation. | Implementer | +| 2025-11-22 | Normalised sprint to standard template and renamed to `SPRINT_0513_0001_0001_provenance.md`; no scope changes. | Project Mgmt | +| 2025-11-18 | Marked PROV-OBS-53-002 as BLOCKED (tests cannot run locally: dotnet test MSB6006). Downstream PROV-OBS-53-003 blocked on 53-002 verification. | Provenance | +| 2025-11-18 | PROV-OBS-53-002 tests blocked locally (dotnet test MSB6006 after long dependency builds); rerun required in CI/less constrained agent. | Provenance | +| 2025-11-17 | Started PROV-OBS-53-002: added cosign/kms/offline signer abstractions, rotating key provider, audit hooks, and unit tests; full test run pending. | Provenance | +| 2025-11-17 | PROV-OBS-53-001 delivered: canonical BuildDefinition/BuildMetadata hashes, Merkle helpers, deterministic tests, and sample DSSE statements for orchestrator/job/export subjects. | Provenance | diff --git a/docs/implplan/SPRINT_0514_0001_0001_sovereign_crypto_enablement.md b/docs/implplan/SPRINT_0514_0001_0001_sovereign_crypto_enablement.md index ecb2d389a..7c6f623bd 100644 --- a/docs/implplan/SPRINT_0514_0001_0001_sovereign_crypto_enablement.md +++ b/docs/implplan/SPRINT_0514_0001_0001_sovereign_crypto_enablement.md @@ -14,14 +14,14 @@ - docs/dev/crypto.md - docs/modules/platform/architecture-overview.md -## Delivery Tracker -| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | -| --- | --- | --- | --- | --- | --- | +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | +| --- | --- | --- | --- | --- | --- | | P1 | PREP-AUTH-CRYPTO-90-001-NEEDS-AUTHORITY-PROVI | DONE (2025-11-20) | Prep note at `docs/modules/authority/prep/2025-11-20-auth-crypto-provider-prep.md`; awaiting contract publication. | Authority Core & Security Guild | Needs Authority provider/key format spec & JWKS export requirements.

Document artefact/deliverable for AUTH-CRYPTO-90-001 and publish location so downstream tasks can proceed. | -| 1 | SEC-CRYPTO-90-017 | TODO | Fork present; integrate into solution | Security Guild | Vendor `third_party/forks/AlexMAS.GostCryptography` into the solution build (solution filters, Directory.Build props, CI) so the library compiles with the repo and publishes artifacts. | -| 2 | SEC-CRYPTO-90-018 | TODO | After 90-017 | Security & Docs Guilds | Update developer/RootPack documentation to describe the fork, sync steps, and licensing. | -| 3 | SEC-CRYPTO-90-019 | TODO | After 90-017 | Security Guild | Patch the fork to drop vulnerable `System.Security.Cryptography.{Pkcs,Xml}` 6.0.0 deps; retarget .NET 8+, rerun tests. | -| 4 | SEC-CRYPTO-90-020 | TODO | After 90-017/019 | Security Guild | Re-point `StellaOps.Cryptography.Plugin.CryptoPro` to the forked sources and prove end-to-end plugin wiring. | +| 1 | SEC-CRYPTO-90-017 | TODO | Fork present; integrate into solution | Security Guild | Vendor `third_party/forks/AlexMAS.GostCryptography` into the solution build (solution filters, Directory.Build props, CI) so the library compiles with the repo and publishes artifacts. | +| 2 | SEC-CRYPTO-90-018 | TODO | After 90-017 | Security & Docs Guilds | Update developer/RootPack documentation to describe the fork, sync steps, and licensing. | +| 3 | SEC-CRYPTO-90-019 | TODO | After 90-017 | Security Guild | Patch the fork to drop vulnerable `System.Security.Cryptography.{Pkcs,Xml}` 6.0.0 deps; retarget .NET 8+, rerun tests. | +| 4 | SEC-CRYPTO-90-020 | TODO | After 90-017/019 | Security Guild | Re-point `StellaOps.Cryptography.Plugin.CryptoPro` to the forked sources and prove end-to-end plugin wiring. | | 5 | SEC-CRYPTO-90-021 | TODO | After 90-020 | Security & QA Guilds | Validate forked library + plugin on Windows (CryptoPro CSP) and Linux (OpenSSL GOST fallback); document prerequisites. | | 6 | SEC-CRYPTO-90-012 | TODO | Env-gated | Security Guild | Add CryptoPro + PKCS#11 integration tests and hook into `scripts/crypto/run-rootpack-ru-tests.sh`. | | 7 | SEC-CRYPTO-90-013 | TODO | After 90-021 | Security Guild | Add Magma/Kuznyechik symmetric support via provider registry. | @@ -31,24 +31,49 @@ | 11 | SCANNER-CRYPTO-90-001 | TODO | Needs registry wiring | Scanner WebService Guild · Security Guild | Route hashing/signing flows through `ICryptoProviderRegistry`. | | 12 | SCANNER-WORKER-CRYPTO-90-001 | TODO | After 11 | Scanner Worker Guild · Security Guild | Wire Scanner Worker/BuildX analyzers to registry/hash abstractions. | | 13 | SCANNER-CRYPTO-90-002 | TODO | PQ profile | Scanner WebService Guild · Security Guild | Enable PQ-friendly DSSE (Dilithium/Falcon) via provider options. | -| 14 | SCANNER-CRYPTO-90-003 | TODO | After 13 | Scanner Worker Guild · QA Guild | Add regression tests for RU/PQ profiles validating Merkle roots + DSSE chains. | -| 15 | ATTESTOR-CRYPTO-90-001 | TODO | Registry wiring | Attestor Service Guild · Security Guild | Migrate attestation hashing/witness flows to provider registry, enabling CryptoPro/PKCS#11 deployments. | - -## Execution Log -| Date (UTC) | Update | Owner | -| --- | --- | --- | +| 14 | SCANNER-CRYPTO-90-003 | TODO | After 13 | Scanner Worker Guild · QA Guild | Add regression tests for RU/PQ profiles validating Merkle roots + DSSE chains. | +| 15 | ATTESTOR-CRYPTO-90-001 | TODO | Registry wiring | Attestor Service Guild · Security Guild | Migrate attestation hashing/witness flows to provider registry, enabling CryptoPro/PKCS#11 deployments. | + +## Wave Coordination +- Single-wave sprint; no concurrent waves scheduled. Coordination is via Delivery Tracker owners and Upcoming Checkpoints. + +## Wave Detail Snapshots +- None yet. Populate if the sprint splits into multiple waves or milestones. + +## Interlocks +- AUTH-CRYPTO-90-001 contract publication is required before runtime wiring tasks (8, 10, 15) proceed. +- CI runner support for CryptoPro/PKCS#11 (pins, drivers) gates integration tests (tasks 5–6). +- PQ provider option design must align with registry abstractions to avoid divergent hashing behavior (tasks 13–14). + +## Upcoming Checkpoints +- 2025-11-19 · Draft Authority provider/JWKS contract to unblock AUTH-CRYPTO-90-001. Owner: Authority Core. +- 2025-11-21 · Decide CI gating approach for CryptoPro/PKCS#11 tests. Owner: Security Guild. +- 2025-11-24 · Fork patch status (SEC-CRYPTO-90-019) and plugin rewire plan (SEC-CRYPTO-90-020). Owner: Security Guild. + +## Action Tracker +| Action | Owner | Due (UTC) | Status | Notes | +| --- | --- | --- | --- | --- | +| Publish Authority provider/JWKS contract (AUTH-CRYPTO-90-001) | Authority Core | 2025-11-19 | Overdue | Blocks tasks 8, 10, 15; depends on contract finalisation. | +| Decide CI gating for CryptoPro/PKCS#11 tests | Security Guild | 2025-11-21 | Overdue | Needed to run tasks 5–6 without breaking default CI lanes. | +| Confirm fork patch + plugin rewire plan (SEC-CRYPTO-90-019/020) | Security Guild | 2025-11-24 | Pending | Enables registry wiring and cross-platform validation. | + +## Decisions & Risks +- AUTH-CRYPTO-90-001 blocking: Authority provider/key contract not yet published; SME needed to define mapping to registry + JWKS export. +- CI coverage for CryptoPro/PKCS#11 may require optional pipelines; guard with env/pin gating to keep default CI green. +- PQ support requires provider options design; keep deterministic hashing across providers. + +| ID | Risk / Decision | Impact | Mitigation | Owner | Status | +| --- | --- | --- | --- | --- | --- | +| R1 | Authority provider/JWKS contract unpublished (AUTH-CRYPTO-90-001) | Blocks runtime wiring tasks (8, 10, 15) and registry alignment. | Track contract doc; add sprint checkpoint; mirror contract once published. | Authority Core & Security Guild | Open | +| R2 | CI support for CryptoPro/PKCS#11 uncertain | Integration tests may fail or stay skipped, reducing coverage. | Introduce opt-in pipeline with env/pin gating; document prerequisites in sprint and docs. | Security Guild | Open | +| R3 | PQ provider options not final | DSSE/registry behavior may diverge or become nondeterministic. | Design provider options aligned to registry abstractions; add regression tests (tasks 13–14). | Scanner Guild | Open | + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2025-11-22 | Normalised sections to docs/implplan template (added Wave/Interlocks/Action Tracker, reordered checkpoints/risks). No task status changes. | Planning | +| 2025-11-20 | Published Authority crypto provider/JWKS prep note (`docs/modules/authority/prep/2025-11-20-auth-crypto-provider-prep.md`); marked PREP-AUTH-CRYPTO-90-001 DONE. | Implementer | | 2025-11-19 | Assigned PREP owners/dates; see Delivery Tracker. | Planning | | 2025-11-18 | Normalised sprint to standard template; renamed from SPRINT_514_sovereign_crypto_enablement.md. | Security Docs | | 2025-11-18 | Downloaded MongoDB 4.4.4 binaries into `local-nuget/mongo2go/4.1.0/tools/mongodb-linux-4.4.4-database-tools-100.3.1/community-server/mongodb-linux-x86_64-ubuntu2004-4.4.4/bin/mongod`; reran `dotnet vstest …AdvisoryChunksEndpoint_ReturnsParagraphAnchors` but Mongo2Go still cannot connect (timeout/connection refused to 127.0.0.1). Concelier AOC tasks remain BLOCKED pending stable Mongo2Go startup. | Concelier WebService | | 2025-11-18 | Targeted `dotnet vstest ...StellaOps.Concelier.WebService.Tests.dll --TestCaseFilter:AdvisoryChunksEndpoint_ReturnsParagraphAnchors` failed: Mongo2Go cannot start (mongod binaries not found; connection refused 127.0.0.1:35961). Concelier AOC tasks remain BLOCKED pending usable Mongo2Go binary path. | Concelier WebService | -| 2025-11-20 | Published Authority crypto provider/JWKS prep note (`docs/modules/authority/prep/2025-11-20-auth-crypto-provider-prep.md`); marked PREP-AUTH-CRYPTO-90-001 DONE. | Implementer | - -## Decisions & Risks -- AUTH-CRYPTO-90-001 blocking: Authority provider/key contract not yet published; SME needed to define mapping to registry + JWKS export. -- CI coverage for CryptoPro/PKCS#11 may require optional pipelines; guard with env/pin gating to keep default CI green. -- PQ support requires provider options design; keep deterministic hashing across providers. - -## Next Checkpoints -- 2025-11-19 · Draft Authority provider/JWKS contract to unblock AUTH-CRYPTO-90-001. Owner: Authority Core. -- 2025-11-21 · Decide CI gating approach for CryptoPro/PKCS#11 tests. Owner: Security Guild. -- 2025-11-24 · Fork patch status (SEC-CRYPTO-90-019) and plugin rewire plan (SEC-CRYPTO-90-020). Owner: Security Guild. diff --git a/docs/implplan/SPRINT_201_cli_i.md b/docs/implplan/SPRINT_201_cli_i.md deleted file mode 100644 index 81860dd18..000000000 --- a/docs/implplan/SPRINT_201_cli_i.md +++ /dev/null @@ -1,27 +0,0 @@ -# Sprint 201 - Experience & SDKs · 180.A) Cli.I - -Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08). - -[Experience & SDKs] 180.A) Cli.I -Depends on: Sprint 120.A - AirGap, Sprint 130.A - Scanner, Sprint 150.A - Orchestrator, Sprint 170.A - Notifier -Summary: Experience & SDKs focus on Cli (phase I). -Task ID | State | Task description | Owners (Source) ---- | --- | --- | --- -PREP-CLI-VULN-29-001-ARTEFACTS | DONE (2025-11-19) | Published frozen artefacts for CLI-VULN-29-001 under `out/console/guardrails/cli-vuln-29-001/` with hashes and doc `docs/modules/cli/artefacts/guardrails-artefacts-2025-11-19.md`. | DevEx/CLI Guild · Docs Guild (src/Cli/StellaOps.Cli) -PREP-CLI-VEX-30-001-ARTEFACTS | DONE (2025-11-19) | Published frozen artefacts for CLI-VEX-30-001 under `out/console/guardrails/cli-vex-30-001/` with hashes and doc `docs/modules/cli/artefacts/guardrails-artefacts-2025-11-19.md`. | DevEx/CLI Guild · Docs Guild (src/Cli/StellaOps.Cli) -CLI-AIAI-31-001 | TODO | Implement `stella advise summarize` command with JSON/Markdown outputs and citation display. | DevEx/CLI Guild (src/Cli/StellaOps.Cli) -CLI-AIAI-31-002 | TODO | Implement `stella advise explain` showing conflict narrative and structured rationale. Dependencies: CLI-AIAI-31-001. | DevEx/CLI Guild (src/Cli/StellaOps.Cli) -CLI-AIAI-31-003 | TODO | Implement `stella advise remediate` generating remediation plans with `--strategy` filters and file output. Dependencies: CLI-AIAI-31-002. | DevEx/CLI Guild (src/Cli/StellaOps.Cli) -CLI-AIAI-31-004 | TODO | Implement `stella advise batch` for summaries/conflicts/remediation with progress + multi-status responses. Dependencies: CLI-AIAI-31-003. | DevEx/CLI Guild (src/Cli/StellaOps.Cli) -CLI-AIRGAP-56-001 | TODO | Implement `stella mirror create | DevEx/CLI Guild (src/Cli/StellaOps.Cli) -CLI-AIRGAP-56-002 | TODO | Ensure telemetry propagation under sealed mode (no remote exporters) while preserving correlation IDs; add label `AirGapped-Phase-1`. Dependencies: CLI-AIRGAP-56-001. | DevEx/CLI Guild (src/Cli/StellaOps.Cli) -CLI-AIRGAP-57-001 | TODO | Add `stella airgap import` with diff preview, bundle scope selection (`--tenant`, `--global`), audit logging, and progress reporting. Dependencies: CLI-AIRGAP-56-002. | DevEx/CLI Guild (src/Cli/StellaOps.Cli) -CLI-AIRGAP-57-002 | TODO | Provide `stella airgap seal. Dependencies: CLI-AIRGAP-57-001. | DevEx/CLI Guild (src/Cli/StellaOps.Cli) -CLI-AIRGAP-58-001 | TODO | Implement `stella airgap export evidence` helper for portable evidence packages, including checksum manifest and verification. Dependencies: CLI-AIRGAP-57-002. | DevEx/CLI Guild, Evidence Locker Guild (src/Cli/StellaOps.Cli) -CLI-ATTEST-73-001 | TODO | Implement `stella attest sign` (payload selection, subject digest, key reference, output format) using official SDK transport. | CLI Attestor Guild (src/Cli/StellaOps.Cli) -CLI-ATTEST-73-002 | TODO | Implement `stella attest verify` with policy selection, explainability output, and JSON/table formatting. Dependencies: CLI-ATTEST-73-001. | CLI Attestor Guild (src/Cli/StellaOps.Cli) -CLI-ATTEST-74-001 | TODO | Implement `stella attest list` with filters (subject, type, issuer, scope) and pagination. Dependencies: CLI-ATTEST-73-002. | CLI Attestor Guild (src/Cli/StellaOps.Cli) -CLI-ATTEST-74-002 | TODO | Implement `stella attest fetch` to download envelopes and payloads to disk. Dependencies: CLI-ATTEST-74-001. | CLI Attestor Guild (src/Cli/StellaOps.Cli) -CLI-ATTEST-75-001 | TODO | Implement `stella attest key create. Dependencies: CLI-ATTEST-74-002. | CLI Attestor Guild, KMS Guild (src/Cli/StellaOps.Cli) -CLI-ATTEST-75-002 | TODO | Add support for building/verifying attestation bundles in CLI. Dependencies: CLI-ATTEST-75-001. | CLI Attestor Guild, Export Guild (src/Cli/StellaOps.Cli) -CLI-HK-201-002 | BLOCKED | Await offline kit status contract and sample bundle; cannot finalize status coverage tests. | DevEx/CLI Guild (src/Cli/StellaOps.Cli) diff --git a/docs/implplan/SPRINT_206_devportal.md b/docs/implplan/SPRINT_206_devportal.md deleted file mode 100644 index 93b7d5f3f..000000000 --- a/docs/implplan/SPRINT_206_devportal.md +++ /dev/null @@ -1,15 +0,0 @@ -# Sprint 206 - Experience & SDKs · 180.B) DevPortal - -Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08). - -[Experience & SDKs] 180.B) DevPortal -Depends on: Sprint 120.A - AirGap, Sprint 130.A - Scanner, Sprint 150.A - Orchestrator, Sprint 170.A - Notifier -Summary: Experience & SDKs focus on DevPortal). -Task ID | State | Task description | Owners (Source) ---- | --- | --- | --- -DEVPORT-62-001 | TODO | Select static site generator, integrate aggregate spec, build navigation + search scaffolding. | Developer Portal Guild (src/DevPortal/StellaOps.DevPortal.Site) -DEVPORT-62-002 | TODO | Implement schema viewer, example rendering, copy-curl snippets, and version selector UI. Dependencies: DEVPORT-62-001. | Developer Portal Guild (src/DevPortal/StellaOps.DevPortal.Site) -DEVPORT-63-001 | TODO | Add Try-It console pointing at sandbox environment with token onboarding and scope info. Dependencies: DEVPORT-62-002. | Developer Portal Guild, Platform Guild (src/DevPortal/StellaOps.DevPortal.Site) -DEVPORT-63-002 | TODO | Embed language-specific SDK snippets and quick starts generated from tested examples. Dependencies: DEVPORT-63-001. | Developer Portal Guild, SDK Generator Guild (src/DevPortal/StellaOps.DevPortal.Site) -DEVPORT-64-001 | TODO | Provide offline build target bundling HTML, specs, SDK archives; ensure no external assets. Dependencies: DEVPORT-63-002. | Developer Portal Guild, Export Center Guild (src/DevPortal/StellaOps.DevPortal.Site) -DEVPORT-64-002 | TODO | Add automated accessibility tests, link checker, and performance budgets. Dependencies: DEVPORT-64-001. | Developer Portal Guild (src/DevPortal/StellaOps.DevPortal.Site) \ No newline at end of file diff --git a/docs/implplan/SPRINT_207_graph.md b/docs/implplan/SPRINT_207_graph.md deleted file mode 100644 index b45306264..000000000 --- a/docs/implplan/SPRINT_207_graph.md +++ /dev/null @@ -1,21 +0,0 @@ -# Sprint 207 - Experience & SDKs · 180.C) Graph - -Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08). - -[Experience & SDKs] 180.C) Graph -Depends on: Sprint 120.A - AirGap, Sprint 130.A - Scanner, Sprint 150.A - Orchestrator, Sprint 170.A - Notifier -Summary: Experience & SDKs focus on Graph). -Task ID | State | Task description | Owners (Source) ---- | --- | --- | --- -GRAPH-API-28-001 | TODO | Define OpenAPI + JSON schema for graph search/query/paths/diff/export endpoints, including cost metadata and streaming tile schema. | Graph API Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-API-28-002 | TODO | Implement `/graph/search` with multi-type index lookup, prefix/exact match, RBAC enforcement, and result ranking + caching. Dependencies: GRAPH-API-28-001. | Graph API Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-API-28-003 | TODO | Build query planner + cost estimator for `/graph/query`, stream tiles (nodes/edges/stats) progressively, enforce budgets, provide cursor tokens. Dependencies: GRAPH-API-28-002. | Graph API Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-API-28-004 | TODO | Implement `/graph/paths` with depth ≤6, constraint filters, heuristic shortest path search, and optional policy overlay rendering. Dependencies: GRAPH-API-28-003. | Graph API Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-API-28-005 | TODO | Implement `/graph/diff` streaming added/removed/changed nodes/edges between SBOM snapshots; include overlay deltas and policy/VEX/advisory metadata. Dependencies: GRAPH-API-28-004. | Graph API Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-API-28-006 | TODO | Consume Policy Engine overlay contract (`POLICY-ENGINE-30-001..003`) and surface advisory/VEX/policy overlays with caching, partial materialization, and explain trace sampling for focused nodes. Dependencies: GRAPH-API-28-005. | Graph API Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-API-28-007 | TODO | Implement exports (`graphml`, `csv`, `ndjson`, `png`, `svg`) with async job management, checksum manifests, and streaming downloads. Dependencies: GRAPH-API-28-006. | Graph API Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-API-28-008 | TODO | Integrate RBAC scopes (`graph:read`, `graph:query`, `graph:export`), tenant headers, audit logging, and rate limiting. Dependencies: GRAPH-API-28-007. | Graph API Guild, Authority Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-API-28-009 | TODO | Instrument metrics (`graph_tile_latency_seconds`, `graph_query_budget_denied_total`, `graph_overlay_cache_hit_ratio`), structured logs, and traces per query stage; publish dashboards. Dependencies: GRAPH-API-28-008. | Graph API Guild, Observability Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-API-28-010 | TODO | Build unit/integration/load tests with synthetic datasets (500k nodes/2M edges), fuzz query validation, verify determinism across runs. Dependencies: GRAPH-API-28-009. | Graph API Guild, QA Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-API-28-011 | TODO | Provide deployment manifests, offline kit support, API gateway integration docs, and smoke tests. Dependencies: GRAPH-API-28-010. | Graph API Guild, DevOps Guild (src/Graph/StellaOps.Graph.Api) -GRAPH-INDEX-28-011 | DONE (2025-11-04) | Wire SBOM ingest runtime to emit graph snapshot artifacts, add DI factory helpers, and document Mongo/snapshot environment guidance. Dependencies: GRAPH-INDEX-28-002..006. | Graph Indexer Guild (src/Graph/StellaOps.Graph.Indexer) diff --git a/docs/implplan/SPRINT_208_sdk.md b/docs/implplan/SPRINT_208_sdk.md deleted file mode 100644 index 5742d790e..000000000 --- a/docs/implplan/SPRINT_208_sdk.md +++ /dev/null @@ -1,21 +0,0 @@ -# Sprint 208 - Experience & SDKs · 180.D) Sdk - -Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08). - -[Experience & SDKs] 180.D) Sdk -Depends on: Sprint 120.A - AirGap, Sprint 130.A - Scanner, Sprint 150.A - Orchestrator, Sprint 170.A - Notifier -Summary: Experience & SDKs focus on Sdk). -Task ID | State | Task description | Owners (Source) ---- | --- | --- | --- -SDKGEN-62-001 | TODO | Choose/pin generator toolchain, set up language template pipeline, and enforce reproducible builds. | SDK Generator Guild (src/Sdk/StellaOps.Sdk.Generator) -SDKGEN-62-002 | TODO | Implement shared post-processing (auth helpers, retries, pagination utilities, telemetry hooks) applied to all languages. Dependencies: SDKGEN-62-001. | SDK Generator Guild (src/Sdk/StellaOps.Sdk.Generator) -SDKGEN-63-001 | TODO | Ship TypeScript SDK alpha with ESM/CJS builds, typed errors, paginator, streaming helpers. Dependencies: SDKGEN-62-002. | SDK Generator Guild (src/Sdk/StellaOps.Sdk.Generator) -SDKGEN-63-002 | TODO | Ship Python SDK alpha (sync/async clients, type hints, upload/download helpers). Dependencies: SDKGEN-63-001. | SDK Generator Guild (src/Sdk/StellaOps.Sdk.Generator) -SDKGEN-63-003 | TODO | Ship Go SDK alpha with context-first API and streaming helpers. Dependencies: SDKGEN-63-002. | SDK Generator Guild (src/Sdk/StellaOps.Sdk.Generator) -SDKGEN-63-004 | TODO | Ship Java SDK alpha (builder pattern, HTTP client abstraction). Dependencies: SDKGEN-63-003. | SDK Generator Guild (src/Sdk/StellaOps.Sdk.Generator) -SDKGEN-64-001 | TODO | Switch CLI to consume TS or Go SDK; ensure parity. Dependencies: SDKGEN-63-004. | SDK Generator Guild, CLI Guild (src/Sdk/StellaOps.Sdk.Generator) -SDKGEN-64-002 | TODO | Integrate SDKs into Console data providers where feasible. Dependencies: SDKGEN-64-001. | SDK Generator Guild, Console Guild (src/Sdk/StellaOps.Sdk.Generator) -SDKREL-63-001 | TODO | Configure CI pipelines for npm, PyPI, Maven Central staging, and Go proxies with signing and provenance attestations. | SDK Release Guild (src/Sdk/StellaOps.Sdk.Release) -SDKREL-63-002 | TODO | Integrate changelog automation pulling from OAS diffs and generator metadata. Dependencies: SDKREL-63-001. | SDK Release Guild, API Governance Guild (src/Sdk/StellaOps.Sdk.Release) -SDKREL-64-001 | TODO | Hook SDK releases into Notifications Studio with scoped announcements and RSS/Atom feeds. Dependencies: SDKREL-63-002. | SDK Release Guild, Notifications Guild (src/Sdk/StellaOps.Sdk.Release) -SDKREL-64-002 | TODO | Add `devportal --offline` bundle job packaging docs, specs, SDK artifacts for air-gapped users. Dependencies: SDKREL-64-001. | SDK Release Guild, Export Center Guild (src/Sdk/StellaOps.Sdk.Release) \ No newline at end of file diff --git a/docs/implplan/SPRINT_209_ui_i.md b/docs/implplan/SPRINT_209_ui_i.md deleted file mode 100644 index 0592fa948..000000000 --- a/docs/implplan/SPRINT_209_ui_i.md +++ /dev/null @@ -1,28 +0,0 @@ -# Sprint 209 - Experience & SDKs · 180.E) UI.I - -Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08). - -[Experience & SDKs] 180.E) UI.I -Depends on: Sprint 120.A - AirGap, Sprint 130.A - Scanner, Sprint 150.A - Orchestrator, Sprint 170.A - Notifier -Summary: Experience & SDKs focus on UI (phase I). -Task ID | State | Task description | Owners (Source) ---- | --- | --- | --- -UI-AOC-19-001 | TODO | Add Sources dashboard tiles showing AOC pass/fail, recent violation codes, and ingest throughput per tenant. | UI Guild (src/UI/StellaOps.UI) -UI-AOC-19-002 | TODO | Implement violation drill-down view highlighting offending document fields and provenance metadata. Dependencies: UI-AOC-19-001. | UI Guild (src/UI/StellaOps.UI) -UI-AOC-19-003 | TODO | Add "Verify last 24h" action triggering AOC verifier endpoint and surfacing CLI parity guidance. Dependencies: UI-AOC-19-002. | UI Guild (src/UI/StellaOps.UI) -UI-EXC-25-001 | TODO | Build Exception Center (list + kanban) with filters, sorting, workflow transitions, and audit views. | UI Guild, Governance Guild (src/UI/StellaOps.UI) -UI-EXC-25-002 | TODO | Implement exception creation wizard with scope preview, justification templates, timebox guardrails. Dependencies: UI-EXC-25-001. | UI Guild (src/UI/StellaOps.UI) -UI-EXC-25-003 | TODO | Add inline exception drafting/proposing from Vulnerability Explorer and Graph detail panels with live simulation. Dependencies: UI-EXC-25-002. | UI Guild (src/UI/StellaOps.UI) -UI-EXC-25-004 | TODO | Surface exception badges, countdown timers, and explain integration across Graph/Vuln Explorer and policy views. Dependencies: UI-EXC-25-003. | UI Guild (src/UI/StellaOps.UI) -UI-EXC-25-005 | TODO | Add keyboard shortcuts (`x`,`a`,`r`) and ensure screen-reader messaging for approvals/revocations. Dependencies: UI-EXC-25-004. | UI Guild, Accessibility Guild (src/UI/StellaOps.UI) -UI-GRAPH-21-001 | TODO | Align Graph Explorer auth configuration with new `graph:*` scopes; consume scope identifiers from shared `StellaOpsScopes` exports (via generated SDK/config) instead of hard-coded strings. | UI Guild (src/UI/StellaOps.UI) -UI-GRAPH-24-001 | TODO | Build Graph Explorer canvas with layered/radial layouts, virtualization, zoom/pan, and scope toggles; initial render <1.5s for sample asset. Dependencies: UI-GRAPH-21-001. | UI Guild, SBOM Service Guild (src/UI/StellaOps.UI) -UI-GRAPH-24-002 | TODO | Implement overlays (Policy, Evidence, License, Exposure), simulation toggle, path view, and SBOM diff/time-travel with accessible tooltips/AOC indicators. Dependencies: UI-GRAPH-24-001. | UI Guild, Policy Guild (src/UI/StellaOps.UI) -UI-GRAPH-24-003 | TODO | Deliver filters/search panel with facets, saved views, permalinks, and share modal. Dependencies: UI-GRAPH-24-002. | UI Guild (src/UI/StellaOps.UI) -UI-GRAPH-24-004 | TODO | Add side panels (Details, What-if, History) with upgrade simulation integration and SBOM diff viewer. Dependencies: UI-GRAPH-24-003. | UI Guild (src/UI/StellaOps.UI) -UI-GRAPH-24-006 | TODO | Ensure accessibility (keyboard nav, screen reader labels, contrast), add hotkeys (`f`,`e`,`.`), and analytics instrumentation. Dependencies: UI-GRAPH-24-004. | UI Guild, Accessibility Guild (src/UI/StellaOps.UI) -UI-LNM-22-001 | TODO | Build Evidence panel showing policy decision with advisory observations/linksets side-by-side, conflict badges, AOC chain, and raw doc download links. Docs `DOCS-LNM-22-005` waiting on delivered UI for screenshots + flows. | UI Guild, Policy Guild (src/UI/StellaOps.UI) -UI-SBOM-DET-01 | TODO | Add a “Determinism” badge plus drill-down that surfaces fragment hashes, `_composition.json`, and Merkle root consistency when viewing scan details (per `docs/modules/scanner/deterministic-sbom-compose.md`). | UI Guild (src/UI/StellaOps.UI) | -UI-POLICY-DET-01 | TODO | Wire policy gate indicators + remediation hints into Release/Policy flows, blocking publishes when determinism checks fail; coordinate with Policy Engine schema updates. Dependencies: UI-SBOM-DET-01. | UI Guild, Policy Guild (src/UI/StellaOps.UI) | -UI-ENTROPY-40-001 | TODO | Visualise entropy analysis per image (layer donut, file heatmaps, “Why risky?” chips) in Vulnerability Explorer and scan details, including opaque byte ratios and detector hints (see `docs/modules/scanner/entropy.md`). | UI Guild (src/UI/StellaOps.UI) | -UI-ENTROPY-40-002 | TODO | Add policy banners/tooltips explaining entropy penalties (block/warn thresholds, mitigation steps) and link to raw `entropy.report.json` evidence downloads (`docs/modules/scanner/entropy.md`). Dependencies: UI-ENTROPY-40-001. | UI Guild, Policy Guild (src/UI/StellaOps.UI) | diff --git a/docs/implplan/SPRINT_212_web_i.md b/docs/implplan/SPRINT_212_web_i.md deleted file mode 100644 index 93a3895fc..000000000 --- a/docs/implplan/SPRINT_212_web_i.md +++ /dev/null @@ -1,37 +0,0 @@ -# Sprint 212 - Experience & SDKs · 180.F) Web.I - -Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08). - -[Experience & SDKs] 180.F) Web.I -Depends on: Sprint 120.A - AirGap, Sprint 130.A - Scanner, Sprint 150.A - Orchestrator, Sprint 170.A - Notifier -Summary: Experience & SDKs focus on Web (phase I). -Task ID | State | Task description | Owners (Source) ---- | --- | --- | --- -WEB-AIAI-31-001 `API routing` | TODO | Route `/advisory/ai/*` endpoints through gateway with RBAC/ABAC, rate limits, and telemetry headers. | BE-Base Platform Guild (src/Web/StellaOps.Web) -WEB-AIAI-31-002 `Batch orchestration` | TODO | Provide batching job handlers and streaming responses for CLI automation with retry/backoff. Dependencies: WEB-AIAI-31-001. | BE-Base Platform Guild (src/Web/StellaOps.Web) -WEB-AIAI-31-003 `Telemetry & audit` | TODO | Emit metrics/logs (latency, guardrail blocks, validation failures) and forward anonymized prompt hashes to analytics. Dependencies: WEB-AIAI-31-002. | BE-Base Platform Guild, Observability Guild (src/Web/StellaOps.Web) -> 2025-11-07: Enforced unknown-field detection, added the shared `AocError` payload (HTTP + CLI), refreshed guard docs, and extended tests/endpoint helpers. -WEB-AOC-19-002 `Provenance & signature helpers` | TODO | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | BE-Base Platform Guild (src/Web/StellaOps.Web) -WEB-AOC-19-003 `Analyzer + test fixtures` | TODO | Author Roslyn analyzer preventing ingestion modules from writing forbidden keys without guard, and provide shared test fixtures for guard validation used by Concelier/Excititor service tests. Dependencies: WEB-AOC-19-002. | QA Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) -WEB-CONSOLE-23-001 `Global posture endpoints` | TODO | Provide consolidated `/console/dashboard` and `/console/filters` APIs returning tenant-scoped aggregates (findings by severity, VEX override counts, advisory deltas, run health, policy change log). Enforce AOC labelling, deterministic ordering, and cursor-based pagination for drill-down hints. | BE-Base Platform Guild, Product Analytics Guild (src/Web/StellaOps.Web) -CONSOLE-VULN-29-001 `Vulnerability workspace` | BLOCKED (2025-11-19) | Awaiting WEB-CONSOLE-23-001 contract and Concelier graph schema; cannot finalize endpoints until schemas freeze. | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) -> 2025-11-07: API scaffolding kicked off; `docs/advisory-ai/console.md` consuming placeholder responses until this lands. Scheduler/Signals hooks queued once filters stabilized. -> 2025-11-08: Driving filter + reachability badge wiring plus `/console/vuln/search` DTOs to keep DOCS-AIAI-31-004 on real payloads; aligning Signals/Scheduler dependencies now that upstream tickets exist. -> 2025-11-08: Published HTTP contract + sample payloads in `docs/api/console/workspaces.md` and `docs/api/console/samples/vuln-findings-sample.json` so Docs can stage screenshots while backend wires up. -CONSOLE-VEX-30-001 `VEX evidence workspace` | BLOCKED (2025-11-19) | Blocked on WEB-CONSOLE-23-001 and Excititor console contract; needs validated SSE payload + schemas. | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) -> 2025-11-07: Endpoint contract draft in progress to unblock DOCS-AIAI-31-004 screenshot capture once responses are wired. -> 2025-11-08: Building SSE controller + `/console/vex/events` payloads and syncing Scheduler Signals tasks so DOCS-AIAI-31-004 can embed live data. -> 2025-11-08: SSE schema + NDJSON sample captured in `docs/api/console/workspaces.md` and `docs/api/console/samples/vex-statement-sse.ndjson`; waiting on Scheduler topic hook-up. -WEB-CONSOLE-23-002 `Live status & SSE proxy` | TODO | Expose `/console/status` polling endpoint and `/console/runs/{id}/stream` SSE/WebSocket proxy with heartbeat/backoff, queue lag metrics, and auth scope enforcement. Surface request IDs + retry headers. Dependencies: WEB-CONSOLE-23-001. | BE-Base Platform Guild, Scheduler Guild (src/Web/StellaOps.Web) -WEB-CONSOLE-23-003 `Evidence export orchestrator` | TODO | Add `/console/exports` POST/GET routes coordinating evidence bundle creation, streaming CSV/JSON exports, checksum manifest retrieval, and signed attestation references. Ensure requests honor tenant + policy scopes and expose job tracking metadata. Dependencies: WEB-CONSOLE-23-002. | BE-Base Platform Guild, Policy Guild (src/Web/StellaOps.Web) -WEB-CONSOLE-23-004 `Global search router` | TODO | Implement `/console/search` endpoint accepting CVE/GHSA/PURL/SBOM identifiers, performing fan-out queries with caching, ranking, and deterministic tie-breaking. Return typed results for Console navigation; respect result caps and latency SLOs. Dependencies: WEB-CONSOLE-23-003. | BE-Base Platform Guild (src/Web/StellaOps.Web) -WEB-CONSOLE-23-005 `Downloads manifest API` | TODO | Serve `/console/downloads` JSON manifest (images, charts, offline bundles) sourced from signed registry metadata; include integrity hashes, release notes links, and offline instructions. Provide caching headers and documentation. Dependencies: WEB-CONSOLE-23-004. | BE-Base Platform Guild, DevOps Guild (src/Web/StellaOps.Web) -WEB-CONTAINERS-44-001 `Config discovery & quickstart flag` | DONE | Expose `/welcome` state, config discovery endpoint (safe values), and `QUICKSTART_MODE` handling for Console banner; add `/health/liveness`, `/health/readiness`, `/version` if missing. | BE-Base Platform Guild (src/Web/StellaOps.Web) -WEB-CONTAINERS-45-001 `Helm readiness support` | DONE | Added readiness/liveness/version JSON assets for helm probes; quickstart/config flags already surfaced. | BE-Base Platform Guild (src/Web/StellaOps.Web) -WEB-CONTAINERS-46-001 `Air-gap hardening` | DONE | Documented offline asset strategy and object-store override guidance; UI already serves local assets (no CDN). | BE-Base Platform Guild (src/Web/StellaOps.Web) -WEB-EXC-25-001 `Exceptions CRUD & workflow` | TODO | Implement `/exceptions` API (create, propose, approve, revoke, list, history) with validation, pagination, and audit logging. | BE-Base Platform Guild (src/Web/StellaOps.Web) - -## Updates -- 2025-11-18: WEB-CONTAINERS-44-001 completed — added quickstart banner, `/welcome` config discovery page, and sample config values to surface safe deployment info. -- 2025-11-19: WEB-CONTAINERS-45-001 completed — readiness/liveness/version JSON assets added for helm probes; config discovery is live via `/welcome`. -- 2025-11-19: CONSOLE-VULN-29-001 and CONSOLE-VEX-30-001 marked BLOCKED pending WEB-CONSOLE-23-001 and upstream Concelier/Excititor schemas. | diff --git a/docs/implplan/SPRINT_401_reachability_evidence_chain.md b/docs/implplan/SPRINT_401_reachability_evidence_chain.md deleted file mode 100644 index 116e4fd7e..000000000 --- a/docs/implplan/SPRINT_401_reachability_evidence_chain.md +++ /dev/null @@ -1,73 +0,0 @@ -# Sprint 401 – Reachability Evidence Chain - -_Window:_ November 11 – November 22, 2025 -_Theme:_ Finish the provable reachability pipeline (graph CAS → replay → DSSE → policy/UI) so Sprint 402 can focus on polish. - -## Wave coordination - -| Wave | Guild owners | Shared prerequisites | Status | Notes | -| --- | --- | --- | --- | --- | -| 401 Reachability Evidence Chain | Scanner Guild · Signals Guild · BE-Base Platform Guild · Policy Guild · UI/CLI Guilds · Docs Guild | Sprint 140 Runtime & Signals; Sprint 185 – Replay Core; Sprint 186 – Scanner Record Mode; Sprint 187 – Evidence Locker & CLI Integration | TODO | Foundation work (Sprint 400) is still in flight; advance only after Scanner record mode emits replay manifests and Evidence Locker APIs exist. | - -| Task ID | State | Task description | Owners (Source) | -|---------|-------|------------------|-----------------| -| GRAPH-CAS-401-001 | TODO | Finalize richgraph schema (`richgraph-v1`), emit canonical SymbolIDs, compute graph hash (BLAKE3), and store CAS manifests under `cas://reachability/graphs/{sha256}`. Update Scanner Worker adapters + fixtures. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`) | -| GAP-SYM-007 | TODO | Extend reachability evidence schema/DTOs with demangled symbol hints, `symbol.source`, confidence, and optional `code_block_hash`; ensure Scanner SBOM/evidence writers and CLI serializers emit the new fields deterministically. | Scanner Worker Guild & Docs Guild (`src/Scanner/StellaOps.Scanner.Models`, `docs/modules/scanner/architecture.md`, `docs/reachability/function-level-evidence.md`) | -| SCAN-REACH-401-009 | TODO | Ship .NET/JVM symbolizers and call-graph generators (roots, edges, framework adapters), merge results into component-level reachability manifests, and back them with golden fixtures. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`, `src/Scanner/__Libraries`) | -| SCANNER-NATIVE-401-015 | TODO | Stand up `StellaOps.Scanner.Symbols.Native` + `StellaOps.Scanner.CallGraph.Native` (ELF/PE readers, demanglers, probabilistic carving) and publish `FuncNode`/`CallEdge` CAS bundles consumed by reachability graphs. | Scanner Worker Guild (`src/Scanner/__Libraries/StellaOps.Scanner.Symbols.Native`, `src/Scanner/__Libraries/StellaOps.Scanner.CallGraph.Native`) | -| SYMS-SERVER-401-011 | TODO | Deliver `StellaOps.Symbols.Server` (REST+gRPC) with DSSE-verified uploads, Mongo/MinIO storage, tenant isolation, and deterministic debugId indexing; publish health/manifest APIs (spec: `docs/specs/SYMBOL_MANIFEST_v1.md`). | Symbols Guild (`src/Symbols/StellaOps.Symbols.Server`) | -| SYMS-CLIENT-401-012 | TODO | Ship `StellaOps.Symbols.Client` SDK (resolve/upload APIs, platform key derivation for ELF/PDB/Mach-O/JVM/Node, disk LRU cache) and integrate with Scanner.Symbolizer/runtime probes (ref. `docs/specs/SYMBOL_MANIFEST_v1.md`). | Symbols Guild (`src/Symbols/StellaOps.Symbols.Client`, `src/Scanner/StellaOps.Scanner.Symbolizer`) | -| SYMS-INGEST-401-013 | TODO | Build `symbols ingest` CLI to emit DSSE-signed `SymbolManifest v1`, upload blobs, and register Rekor entries; document GitLab/Gitea pipeline usage. | Symbols Guild, DevOps Guild (`src/Symbols/StellaOps.Symbols.Ingestor.Cli`, `docs/specs/SYMBOL_MANIFEST_v1.md`) | -| SIGNALS-RUNTIME-401-002 | TODO | Ship `/signals/runtime-facts` ingestion for NDJSON (and gzip) batches, dedupe hits, and link runtime evidence CAS URIs to callgraph nodes. Include retention + RBAC tests. | Signals Guild (`src/Signals/StellaOps.Signals`) | -| RUNTIME-PROBE-401-010 | TODO | Implement lightweight runtime probes (EventPipe/.NET, JFR/JVM) that capture method enter events for the target components, package them as CAS traces, and feed them into the Signals ingestion pipeline. | Runtime Signals Guild (`src/Signals/StellaOps.Signals.Runtime`, `ops/probes`) | -| SIGNALS-SCORING-401-003 | TODO | Extend `ReachabilityScoringService` with deterministic scoring (static path +0.50, runtime hits +0.30/+0.10 sink, guard penalties, reflection penalty, floor 0.05), persist reachability labels (`reachable/conditional/unreachable`) and expose `/graphs/{scanId}` CAS lookups. | Signals Guild (`src/Signals/StellaOps.Signals`) | -| REPLAY-401-004 | TODO | Bump replay manifest to v2 (feeds, analyzers, policies), have `ReachabilityReplayWriter` enforce CAS registration + hash sorting, and add deterministic tests to `tests/reachability/StellaOps.Reachability.FixtureTests`. | BE-Base Platform Guild (`src/__Libraries/StellaOps.Replay.Core`) | -| AUTH-REACH-401-005 | TODO | Introduce DSSE predicate types for SBOM/Graph/VEX/Replay, plumb signing through Authority + Signer, and mirror statements to Rekor (including PQ variants where required). | Authority & Signer Guilds (`src/Authority/StellaOps.Authority`, `src/Signer/StellaOps.Signer`) | -| POLICY-VEX-401-006 | TODO | Policy Engine consumes reachability facts, applies the deterministic score/label buckets (≥0.80 reachable, 0.30–0.79 conditional, <0.30 unreachable), emits OpenVEX with call-path proofs, and updates SPL schema with `reachability.state/confidence` predicates and suppression gates. | Policy Guild (`src/Policy/StellaOps.Policy.Engine`, `src/Policy/__Libraries/StellaOps.Policy`) | -| POLICY-VEX-401-010 | TODO | Implement `VexDecisionEmitter` to serialize per-finding OpenVEX, attach evidence hashes, request DSSE signatures, capture Rekor metadata, and publish artifacts following the bench playbook. | Policy Guild (`src/Policy/StellaOps.Policy.Engine/Vex`, `docs/modules/policy/architecture.md`, `docs/benchmarks/vex-evidence-playbook.md`) | -| UI-CLI-401-007 | TODO | Implement CLI `stella graph explain` + UI explain drawer showing signed call-path, predicates, runtime hits, and DSSE pointers; include counterfactual controls. | UI & CLI Guilds (`src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`) | -| QA-DOCS-401-008 | TODO | Wire `reachbench-2025-expanded` fixtures into CI, document CAS layouts + replay steps in `docs/reachability/DELIVERY_GUIDE.md`, and publish operator runbook for runtime ingestion. | QA & Docs Guilds (`docs`, `tests/README.md`) | -| GAP-SIG-003 | TODO | Finish `/signals/runtime-facts` ingestion, add CAS-backed runtime storage, extend scoring to lattice states (`Unknown/NotPresent/Unreachable/Conditional/Reachable/Observed`), and emit `signals.fact.updated` events. Document retention/RBAC. | Signals Guild (`src/Signals/StellaOps.Signals`, `docs/reachability/function-level-evidence.md`) | -| SIG-STORE-401-016 | TODO | Introduce shared reachability store collections (`func_nodes`, `call_edges`, `cve_func_hits`), indexes, and repository APIs so Scanner/Signals/Policy can reuse canonical function data. | Signals Guild · BE-Base Platform Guild (`src/Signals/StellaOps.Signals`, `src/__Libraries/StellaOps.Replay.Core`) | -| GAP-REP-004 | TODO | Enforce BLAKE3 hashing + CAS registration for graphs/traces before manifest writes, upgrade replay manifest v2 with analyzer versions/policy thresholds, and add deterministic tests. | BE-Base Platform Guild (`src/__Libraries/StellaOps.Replay.Core`, `docs/replay/DETERMINISTIC_REPLAY.md`) | -| GAP-POL-005 | TODO | Ingest reachability facts into Policy Engine, expose `reachability.state/confidence` in SPL/API, enforce auto-suppress (<0.30) rules, and generate OpenVEX evidence blocks referencing graph hashes + runtime facts with policy thresholds. | Policy Guild (`src/Policy/StellaOps.Policy.Engine`, `docs/modules/policy/architecture.md`, `docs/reachability/function-level-evidence.md`) | -| GAP-VEX-006 | TODO | Wire Policy/Excititor/UI/CLI surfaces so VEX emission and explain drawers show call paths, graph hashes, and runtime hits; add CLI `--evidence=graph`/`--threshold` plus Notify template updates. | Policy, Excititor, UI, CLI & Notify Guilds (`docs/modules/excititor/architecture.md`, `src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`, `docs/09_API_CLI_REFERENCE.md`) | -| GAP-DOC-008 | TODO | Publish the cross-module function-level evidence guide, update API/CLI references with the new `code_id` fields, and add OpenVEX/replay samples under `samples/reachability/**`. | Docs Guild (`docs/reachability/function-level-evidence.md`, `docs/09_API_CLI_REFERENCE.md`, `docs/api/policy.md`) | -| CLI-VEX-401-011 | TODO | Add `stella decision export|verify|compare` verbs, integrate with Policy/Signer APIs, and ship local verifier wrappers for bench artifacts. | CLI Guild (`src/Cli/StellaOps.Cli`, `docs/modules/cli/architecture.md`, `docs/benchmarks/vex-evidence-playbook.md`) | -| SIGN-VEX-401-018 | TODO | Extend Signer predicate catalog with `stella.ops/vexDecision@v1`, enforce payload policy, and plumb DSSE/Rekor integration for policy decisions. | Signing Guild (`src/Signer/StellaOps.Signer`, `docs/modules/signer/architecture.md`) | -| BENCH-AUTO-401-019 | TODO | Create automation to populate `bench/findings/**`, run baseline scanners (Trivy/Syft/Grype/Snyk/Xray), compute FP/MTTD/repro metrics, and update `results/summary.csv`. | Benchmarks Guild (`docs/benchmarks/vex-evidence-playbook.md`, `scripts/bench/**`) | -| DOCS-VEX-401-012 | TODO | Maintain the VEX Evidence Playbook, publish repo templates/README, and document verification workflows for operators. | Docs Guild (`docs/benchmarks/vex-evidence-playbook.md`, `bench/README.md`) | -| SYMS-BUNDLE-401-014 | TODO | Produce deterministic symbol bundles for air-gapped installs (`symbols bundle create|verify|load`), including DSSE manifests and Rekor checkpoints, and document offline workflows (`docs/specs/SYMBOL_MANIFEST_v1.md`). | Symbols Guild, Ops Guild (`src/Symbols/StellaOps.Symbols.Bundle`, `ops`) | -| DOCS-RUNBOOK-401-017 | TODO | Publish the reachability runtime ingestion runbook, link it from delivery guides, and keep Ops/Signals troubleshooting steps current. | Docs Guild · Ops Guild (`docs/runbooks/reachability-runtime.md`, `docs/reachability/DELIVERY_GUIDE.md`) | -| POLICY-LIB-401-001 | TODO | Extract the policy DSL parser/compiler into `StellaOps.PolicyDsl`, add the lightweight syntax (default action + inline rules), and expose `PolicyEngineFactory`/`SignalContext` APIs for reuse. | Policy Guild (`src/Policy/StellaOps.PolicyDsl`, `docs/policy/dsl.md`) | -| POLICY-LIB-401-002 | TODO | Ship unit-test harness + sample `policy/default.dsl` (table-driven cases) and wire `stella policy lint/simulate` to the shared library. | Policy Guild, CLI Guild (`tests/Policy/StellaOps.PolicyDsl.Tests`, `policy/default.dsl`, `docs/policy/lifecycle.md`) | -| POLICY-ENGINE-401-003 | TODO | Replace in-service DSL compilation with the shared library, support both legacy `stella-dsl@1` packs and the new inline syntax, and keep determinism hashes stable. | Policy Guild (`src/Policy/StellaOps.Policy.Engine`, `docs/modules/policy/architecture.md`) | -| CLI-EDITOR-401-004 | TODO | Enhance `stella policy` CLI verbs (edit/lint/simulate) to edit Git-backed `.dsl` files, run local coverage tests, and commit SemVer metadata. | CLI Guild (`src/Cli/StellaOps.Cli`, `docs/policy/lifecycle.md`) | -| DOCS-DSL-401-005 | TODO | Refresh `docs/policy/dsl.md` + lifecycle docs with the new syntax, signal dictionary (`trust_score`, `reachability`, etc.), authoring workflow, and safety rails (shadow mode, coverage tests). | Docs Guild (`docs/policy/dsl.md`, `docs/policy/lifecycle.md`) | -| DSSE-LIB-401-020 | TODO | Package `StellaOps.Attestor.Envelope` primitives into a reusable `StellaOps.Attestation` library with `InTotoStatement`, `IAuthoritySigner`, DSSE pre-auth helpers, and .NET-friendly APIs for build agents. | Attestor Guild · Platform Guild (`src/Attestor/StellaOps.Attestation`, `src/Attestor/StellaOps.Attestor.Envelope`) | -| DSSE-CLI-401-021 | TODO | Ship a `stella attest` CLI (or sample `StellaOps.Attestor.Tool`) plus GitLab/GitHub workflow snippets that emit DSSE per build step (scan/package/push) using the new library and Authority keys. | CLI Guild · DevOps Guild (`src/Cli/StellaOps.Cli`, `scripts/ci/attest-*`, `docs/modules/attestor/architecture.md`) | -| DSSE-DOCS-401-022 | TODO | Document the build-time attestation walkthrough (`docs/ci/dsse-build-flow.md`): models, helper usage, Authority integration, storage conventions, and verification commands, aligning with the advisory. | Docs Guild · Attestor Guild (`docs/ci/dsse-build-flow.md`, `docs/modules/attestor/architecture.md`) | -| REACH-LATTICE-401-023 | TODO | Define the reachability lattice model (`ReachState`, `EvidenceKind`, `MitigationKind`, scoring policy) in Scanner docs + code; ensure evidence joins write to the event graph schema. | Scanner Guild · Policy Guild (`docs/reachability/lattice.md`, `docs/modules/scanner/architecture.md`, `src/Scanner/StellaOps.Scanner.WebService`) | -| UNCERTAINTY-SCHEMA-401-024 | TODO | Extend Signals findings with `uncertainty.states[]`, entropy fields, and `riskScore`; emit `FindingUncertaintyUpdated` events and persist evidence per docs. | Signals Guild (`src/Signals/StellaOps.Signals`, `docs/uncertainty/README.md`) | -| UNCERTAINTY-SCORER-401-025 | TODO | Implement the entropy-aware risk scorer (`riskScore = base × reach × trust × (1 + entropyBoost)`) and wire it into finding writes. | Signals Guild (`src/Signals/StellaOps.Signals.Application`, `docs/uncertainty/README.md`) | -| UNCERTAINTY-POLICY-401-026 | TODO | Update policy guidance (Concelier/Excitors) with uncertainty gates (U1/U2/U3), sample YAML rules, and remediation actions. | Policy Guild · Concelier Guild (`docs/policy/dsl.md`, `docs/uncertainty/README.md`) | -| UNCERTAINTY-UI-401-027 | TODO | Surface uncertainty chips/tooltips in the Console (React UI) + CLI output (risk score + entropy states). | UI Guild · CLI Guild (`src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/uncertainty/README.md`) | -| PROV-INLINE-401-028 | DONE | Extend Authority/Feedser event writers to attach inline DSSE + Rekor references on every SBOM/VEX/scan event using `StellaOps.Provenance.Mongo`. | Authority Guild · Feedser Guild (`docs/provenance/inline-dsse.md`, `src/__Libraries/StellaOps.Provenance.Mongo`) | -| PROV-BACKFILL-INPUTS-401-029A | DONE | Attestation inventory and subject→Rekor map drafted (`docs/provenance/attestation-inventory-2025-11-18.ndjson`, `docs/provenance/subject-rekor-map-2025-11-18.json`). | Evidence Locker Guild · Platform Guild (`docs/provenance/inline-dsse.md`) | -| PROV-BACKFILL-401-029 | TODO | Use inventory + map to resolve historical events and backfill provenance. | Platform Guild (`docs/provenance/inline-dsse.md`, `scripts/publish_attestation_with_provenance.sh`) | -| PROV-INDEX-401-030 | TODO | Deploy provenance indexes (`events_by_subject_kind_provenance`, etc.) and expose compliance/replay queries. | Platform Guild · Ops Guild (`docs/provenance/inline-dsse.md`, `ops/mongo/indices/events_provenance_indices.js`) | -| QA-CORPUS-401-031 | TODO | Build and publish the multi-runtime reachability corpus (Go/.NET/Python/Rust) with EXPECT.yaml ground truths and captured traces; wire fixtures into CI so reachability scoring and VEX proofs are continuously validated. | QA Guild · Scanner Guild (`tests/reachability`, `docs/reachability/DELIVERY_GUIDE.md`) | -| UI-VEX-401-032 | TODO | Add UI/CLI “Explain/Verify” surfaces on VEX decisions (show call paths, runtime hits, attestation verify button) and align with reachability evidence output. | UI Guild · CLI Guild · Scanner Guild (`src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/reachability/function-level-evidence.md`) | -| POLICY-GATE-401-033 | TODO | Enforce policy gate requiring reachability evidence for `not_affected`/`unreachable` VEX outcomes; fall back to “under review” when symbol confidence is low; update policy docs and tests. | Policy Guild · Scanner Guild (`src/Policy/StellaOps.Policy.Engine`, `docs/policy/dsl.md`, `docs/modules/scanner/architecture.md`) | -| GRAPH-PURL-401-034 | TODO | Annotate call edges with callee purl + `symbol_digest`, update `richgraph-v1` schema/CAS, and surface fields in CLI/UI explainers. | Scanner Worker Guild · Signals Guild (`src/Scanner/StellaOps.Scanner.Worker`, `src/Signals/StellaOps.Signals`, `docs/reachability/purl-resolved-edges.md`) | -| SCANNER-BUILDID-401-035 | TODO | Capture `.note.gnu.build-id` for all ELF targets, thread into `SymbolID`/`code_id`, SBOM exports, and runtime facts; add fixtures for build-id present/absent. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`, `docs/modules/scanner/architecture.md`) | -| SCANNER-INITROOT-401-036 | TODO | Model `.preinit_array`/`.init_array`/`_init` and legacy ctor sections as synthetic graph roots (phase=load) including `DT_NEEDED` deps; persist roots in graph evidence. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker`, `docs/modules/scanner/architecture.md`) | -| QA-PORACLE-401-037 | TODO | Add `tests/reachability/patch-oracles/**` fixtures (vuln vs fixed), harness to compare graphs vs `oracle.yml`, and CI job to fail when expected functions/edges are missing. | QA Guild · Scanner Worker Guild (`tests/reachability`, `docs/reachability/patch-oracles.md`) | - -> Use `docs/reachability/DELIVERY_GUIDE.md` for architecture context, dependencies, and acceptance tests. - -## Execution Log - -| Date (UTC) | Update | Owner | -| --- | --- | --- | -| 2025-11-20 | Added tasks for purl-resolved edges, ELF build-id propagation, init-array roots, and patch-oracle QA harness; aligned docs references. | Planning | diff --git a/docs/implplan/SPRINT_513_provenance.md b/docs/implplan/SPRINT_513_provenance.md deleted file mode 100644 index 3a05ebeaf..000000000 --- a/docs/implplan/SPRINT_513_provenance.md +++ /dev/null @@ -1,26 +0,0 @@ -# Sprint 513 - Ops & Offline · 190.H) Provenance - -Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08). - -[Ops & Offline] 190.H) Provenance -Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - AirGap, Sprint 130.A - Scanner, Sprint 140.A - Graph, Sprint 150.A - Orchestrator, Sprint 160.A - EvidenceLocker, Sprint 170.A - Notifier, Sprint 180.A - Cli -Summary: Ops & Offline focus on Provenance). -Task ID | State | Task description | Owners (Source) ---- | --- | --- | --- -PROV-OBS-53-001 | DONE (2025-11-17) | Implement DSSE/SLSA `BuildDefinition` + `BuildMetadata` models with canonical JSON serializer, Merkle digest helpers, and deterministic hashing tests. Publish sample statements for orchestrator/job/export subjects. | Provenance Guild (src/Provenance/StellaOps.Provenance.Attestation) -PROV-OBS-53-002 | BLOCKED | Build signer abstraction (cosign/KMS/offline) with key rotation hooks, audit logging, and policy enforcement (required claims). Provide unit tests using fake signer + real cosign fixture. Dependencies: PROV-OBS-53-001. | Provenance Guild, Security Guild (src/Provenance/StellaOps.Provenance.Attestation) -PROV-OBS-53-003 | BLOCKED | Deliver `PromotionAttestationBuilder` that materialises the `stella.ops/promotion@v1` predicate (image digest, SBOM/VEX materials, promotion metadata, Rekor proof) and feeds canonicalised payload bytes to Signer via StellaOps.Cryptography. | Provenance Guild (src/Provenance/StellaOps.Provenance.Attestation) -PROV-OBS-54-001 | TODO | Deliver verification library that validates DSSE signatures, Merkle roots, and timeline chain-of-custody, exposing reusable CLI/service APIs. Include negative-case fixtures and offline timestamp verification. Dependencies: PROV-OBS-53-002. | Provenance Guild, Evidence Locker Guild (src/Provenance/StellaOps.Provenance.Attestation) -PROV-OBS-54-002 | TODO | Generate .NET global tool for local verification + embed command helpers for CLI `stella forensic verify`. Provide deterministic packaging and offline kit instructions. Dependencies: PROV-OBS-54-001. | Provenance Guild, DevEx/CLI Guild (src/Provenance/StellaOps.Provenance.Attestation) - -## Execution Log -| Date (UTC) | Update | Owner | -| --- | --- | --- | -| 2025-11-18 | Marked PROV-OBS-53-002 as BLOCKED (tests cannot run locally: dotnet test MSB6006). Downstream PROV-OBS-53-003 blocked on 53-002 verification. | Provenance | -| 2025-11-18 | PROV-OBS-53-002 tests blocked locally (dotnet test MSB6006 after long dependency builds); rerun required in CI/less constrained agent. | Provenance | -| 2025-11-17 | Started PROV-OBS-53-002: added cosign/kms/offline signer abstractions, rotating key provider, audit hooks, and unit tests; full test run pending. | Provenance | -| 2025-11-17 | PROV-OBS-53-001 delivered: canonical BuildDefinition/BuildMetadata hashes, Merkle helpers, deterministic tests, and sample DSSE statements for orchestrator/job/export subjects. | Provenance | - -## Decisions & Risks -- PROV-OBS-53-002 validation blocked in local agent (dotnet test MSB6006). Needs CI/full agent rerun before marking DONE; downstream tasks 53-003/54-001 remain gated on this verification. -- PROV-OBS-53-003 inherits block from 53-002; do not start until signer tests verified in CI. diff --git a/docs/implplan/tasks-all.md b/docs/implplan/tasks-all.md index e97528e41..8ee7db379 100644 --- a/docs/implplan/tasks-all.md +++ b/docs/implplan/tasks-all.md @@ -69,8 +69,8 @@ | 62-002 | TODO | | SPRINT_206_devportal | DevPortal Guild | src/DevPortal/StellaOps.DevPortal.Site | 62-001 | 62-001 | DEVL0101 | | 63-001 | TODO | | SPRINT_206_devportal | DevPortal Guild · Platform Guild | src/DevPortal/StellaOps.DevPortal.Site | 62-002 | 62-002 | DEVL0101 | | 63-002 | TODO | | SPRINT_206_devportal | DevPortal Guild · SDK Generator Guild | src/DevPortal/StellaOps.DevPortal.Site | 63-001 | 63-001 | DEVL0101 | -| 63-003 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | APIG0101 outputs | APIG0101 outputs | SDKG0101 | -| 63-004 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | 63-003 | 63-003 | SDKG0101 | +| 63-003 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | APIG0101 outputs | APIG0101 outputs | SDKG0101 | +| 63-004 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | 63-003 | 63-003 | SDKG0101 | | 64-001 | TODO | | SPRINT_206_devportal | DevPortal Guild · Export Center Guild | src/DevPortal/StellaOps.DevPortal.Site | Export profile review | Export profile review | DEVL0101 | | 64-002 | TODO | | SPRINT_160_export_evidence | DevPortal Offline + AirGap Controller Guilds | docs/modules/export-center/devportal-offline.md | Wait for Mirror staffing confirmation (001_PGMI0101) | Wait for Mirror staffing confirmation (001_PGMI0101) | DEVL0102 | | 73-001 | DONE | 2025-11-03 | SPRINT_100_identity_signing | KMS Guild | src/__Libraries/StellaOps.Cryptography.Kms | Staffing + DSSE contract (PGMI0101, ATEL0101) | Staffing + DSSE contract (PGMI0101, ATEL0101) | KMSI0101 | @@ -216,17 +216,17 @@ | API-27-008 | TODO | | SPRINT_129_policy_reasoning | Policy Registry Guild | src/Policy/StellaOps.Policy.Registry | Depends on #7 | REGISTRY-API-27-007 | PLAR0101 | | API-27-009 | TODO | | SPRINT_129_policy_reasoning | Policy Registry Guild | src/Policy/StellaOps.Policy.Registry | Depends on #8 | REGISTRY-API-27-008 | PLAR0101 | | API-27-010 | TODO | | SPRINT_129_policy_reasoning | Policy Registry Guild | src/Policy/StellaOps.Policy.Registry | Depends on #9 | REGISTRY-API-27-009 | PLAR0101 | -| API-28-001 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Cartographer schema sign-off | Cartographer schema sign-off | GRAP0101 | -| API-28-002 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #1 | Depends on #1 | GRAP0101 | -| API-28-003 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #2 | Depends on #2 | GRAP0101 | -| API-28-004 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #3 | Depends on #3 | GRAP0101 | -| API-28-005 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #4 | Depends on #4 | GRAP0101 | -| API-28-006 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on GRAP0101 base endpoints | Depends on GRAP0101 base endpoints | GRAP0102 | -| API-28-007 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #1 | Depends on #1 | GRAP0102 | -| API-28-008 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #2 | Depends on #2 | GRAP0102 | -| API-28-009 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #3 | Depends on #3 | GRAP0102 | -| API-28-010 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #4 | Depends on #4 | GRAP0102 | -| API-28-011 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #5 | Depends on #5 | GRAP0102 | +| API-28-001 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Cartographer schema sign-off | Cartographer schema sign-off | GRAP0101 | +| API-28-002 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #1 | Depends on #1 | GRAP0101 | +| API-28-003 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #2 | Depends on #2 | GRAP0101 | +| API-28-004 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #3 | Depends on #3 | GRAP0101 | +| API-28-005 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #4 | Depends on #4 | GRAP0101 | +| API-28-006 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on GRAP0101 base endpoints | Depends on GRAP0101 base endpoints | GRAP0102 | +| API-28-007 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #1 | Depends on #1 | GRAP0102 | +| API-28-008 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #2 | Depends on #2 | GRAP0102 | +| API-28-009 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #3 | Depends on #3 | GRAP0102 | +| API-28-010 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #4 | Depends on #4 | GRAP0102 | +| API-28-011 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #5 | Depends on #5 | GRAP0102 | | API-29-001 | TODO | | SPRINT_129_policy_reasoning | Vuln Explorer API Guild | src/VulnExplorer/StellaOps.VulnExplorer.Api | Governance schema (APIG0101) | Governance schema (APIG0101) | VUAP0101 | | API-29-002 | TODO | | SPRINT_129_policy_reasoning | Vuln Explorer API Guild | src/VulnExplorer/StellaOps.VulnExplorer.Api | Depends on #1 | VULN-API-29-001 | VUAP0101 | | API-29-003 | TODO | | SPRINT_129_policy_reasoning | Vuln Explorer API Guild | src/VulnExplorer/StellaOps.VulnExplorer.Api | Depends on #2 | VULN-API-29-002 | VUAP0101 | @@ -296,21 +296,21 @@ | CLI-42-001 | TODO | | SPRINT_303_docs_tasks_md_iii | Docs Guild (docs) | | — | — | CLCI0101 | | CLI-43-002 | TODO | | SPRINT_504_ops_devops_ii | DevOps Guild, Task Runner Guild (ops/devops) | ops/devops | — | — | CLCI0101 | | CLI-43-003 | TODO | | SPRINT_504_ops_devops_ii | DevOps Guild, DevEx/CLI Guild (ops/devops) | ops/devops | — | — | CLCI0101 | -| CLI-AIAI-31-001 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise summarize` command with JSON/Markdown outputs and citation display. | — | CLCI0101 | -| CLI-AIAI-31-002 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise explain` showing conflict narrative and structured rationale. Dependencies: CLI-AIAI-31-001. | — | CLCI0101 | -| CLI-AIAI-31-003 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise remediate` generating remediation plans with `--strategy` filters and file output. Dependencies: CLI-AIAI-31-002. | — | CLCI0101 | -| CLI-AIAI-31-004 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise batch` for summaries/conflicts/remediation with progress + multi-status responses. Dependencies: CLI-AIAI-31-003. | — | CLCI0102 | +| CLI-AIAI-31-001 | DOING | 2025-11-22 | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise summarize` command with JSON/Markdown outputs and citation display. | — | CLCI0101 | +| CLI-AIAI-31-002 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise explain` showing conflict narrative and structured rationale. Dependencies: CLI-AIAI-31-001. | — | CLCI0101 | +| CLI-AIAI-31-003 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise remediate` generating remediation plans with `--strategy` filters and file output. Dependencies: CLI-AIAI-31-002. | — | CLCI0101 | +| CLI-AIAI-31-004 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise batch` for summaries/conflicts/remediation with progress + multi-status responses. Dependencies: CLI-AIAI-31-003. | — | CLCI0102 | | CLI-AIRGAP-56-001 | TODO | | SPRINT_110_ingestion_evidence | Exporter Guild · AirGap Time Guild · CLI Guild | | PROGRAM-STAFF-1001 | PROGRAM-STAFF-1001 | ATMI0102 | -| CLI-AIRGAP-56-002 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Ensure telemetry propagation under sealed mode (no remote exporters) while preserving correlation IDs; add label `AirGapped-Phase-1`. Dependencies: CLI-AIRGAP-56-001. | — | CLCI0102 | -| CLI-AIRGAP-57-001 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Add `stella airgap import` with diff preview, bundle scope selection (`--tenant`, `--global`), audit logging, and progress reporting. Dependencies: CLI-AIRGAP-56-002. | — | CLCI0102 | -| CLI-AIRGAP-57-002 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Provide `stella airgap seal. Dependencies: CLI-AIRGAP-57-001. | — | CLCI0102 | -| CLI-AIRGAP-58-001 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild, Evidence Locker Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella airgap export evidence` helper for portable evidence packages, including checksum manifest and verification. Dependencies: CLI-AIRGAP-57-002. | — | CLCI0102 | -| CLI-ATTEST-73-001 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest sign` (payload selection, subject digest, key reference, output format) using official SDK transport. | — | CLCI0102 | -| CLI-ATTEST-73-002 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest verify` with policy selection, explainability output, and JSON/table formatting. Dependencies: CLI-ATTEST-73-001. | — | CLCI0102 | -| CLI-ATTEST-74-001 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest list` with filters (subject, type, issuer, scope) and pagination. Dependencies: CLI-ATTEST-73-002. | — | CLCI0102 | -| CLI-ATTEST-74-002 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest fetch` to download envelopes and payloads to disk. Dependencies: CLI-ATTEST-74-001. | — | CLCI0102 | -| CLI-ATTEST-75-001 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild, KMS Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest key create. Dependencies: CLI-ATTEST-74-002. | — | CLCI0102 | -| CLI-ATTEST-75-002 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild | src/Cli/StellaOps.Cli | Add support for building/verifying attestation bundles in CLI. Dependencies: CLI-ATTEST-75-001. | Wait for ATEL0102 outputs | CLCI0109 | +| CLI-AIRGAP-56-002 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Ensure telemetry propagation under sealed mode (no remote exporters) while preserving correlation IDs; add label `AirGapped-Phase-1`. Dependencies: CLI-AIRGAP-56-001. | — | CLCI0102 | +| CLI-AIRGAP-57-001 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Add `stella airgap import` with diff preview, bundle scope selection (`--tenant`, `--global`), audit logging, and progress reporting. Dependencies: CLI-AIRGAP-56-002. | — | CLCI0102 | +| CLI-AIRGAP-57-002 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Provide `stella airgap seal. Dependencies: CLI-AIRGAP-57-001. | — | CLCI0102 | +| CLI-AIRGAP-58-001 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild, Evidence Locker Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella airgap export evidence` helper for portable evidence packages, including checksum manifest and verification. Dependencies: CLI-AIRGAP-57-002. | — | CLCI0102 | +| CLI-ATTEST-73-001 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest sign` (payload selection, subject digest, key reference, output format) using official SDK transport. | — | CLCI0102 | +| CLI-ATTEST-73-002 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest verify` with policy selection, explainability output, and JSON/table formatting. Dependencies: CLI-ATTEST-73-001. | — | CLCI0102 | +| CLI-ATTEST-74-001 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest list` with filters (subject, type, issuer, scope) and pagination. Dependencies: CLI-ATTEST-73-002. | — | CLCI0102 | +| CLI-ATTEST-74-002 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest fetch` to download envelopes and payloads to disk. Dependencies: CLI-ATTEST-74-001. | — | CLCI0102 | +| CLI-ATTEST-75-001 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild, KMS Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest key create. Dependencies: CLI-ATTEST-74-002. | — | CLCI0102 | +| CLI-ATTEST-75-002 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild | src/Cli/StellaOps.Cli | Add support for building/verifying attestation bundles in CLI. Dependencies: CLI-ATTEST-75-001. | Wait for ATEL0102 outputs | CLCI0109 | | CLI-CORE-41-001 | TODO | | SPRINT_202_cli_ii | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement CLI core features: config precedence, profiles/contexts, auth flows, output renderer (json/yaml/table), error mapping, global flags, telemetry opt-in. | — | CLCI0103 | | CLI-DET-01 | TODO | | SPRINT_301_docs_tasks_md_i | Docs Guild · DevEx/CLI Guild | | CLI-SBOM-60-001; CLI-SBOM-60-002 | CLI-SBOM-60-001; CLI-SBOM-60-002 | CLCI0103 | | CLI-DETER-70-003 | TODO | | SPRINT_202_cli_ii | DevEx/CLI Guild, Scanner Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Provide `stella detscore run` that executes the determinism harness locally (fixed clock, seeded RNG, canonical hashes) and writes `determinism.json`, supporting CI/non-zero threshold exit codes (`docs/modules/scanner/determinism-score.md`). | — | CLCI0103 | @@ -473,15 +473,15 @@ | CONSOLE-23-001..003 | TODO | | SPRINT_110_ingestion_evidence | Console Guild | src/Console/StellaOps.Console | Depends on #1 | CONCELIER-GRAPH-21-001; CONCELIER-GRAPH-21-002 | CCSL0101 | | CONSOLE-23-002 | TODO | | SPRINT_112_concelier_i | Console Guild | src/Console/StellaOps.Console | Needs LNM graph (CCGH0101) | Needs LNM graph (CCGH0101) | CCSL0101 | | CONSOLE-23-003 | TODO | | SPRINT_112_concelier_i | Console Guild | src/Console/StellaOps.Console | Depends on #3 | Depends on #3 | CCSL0101 | -| CONSOLE-23-004 | TODO | | SPRINT_212_web_i | Console Guild | src/Web/StellaOps.Web | Requires CCPR0101 verdicts | Requires CCPR0101 verdicts | CCSL0101 | -| CONSOLE-23-005 | TODO | | SPRINT_212_web_i | Console Guild | src/Web/StellaOps.Web | Depends on #5 | Depends on #5 | CCSL0101 | +| CONSOLE-23-004 | TODO | | SPRINT_0212_0001_0001_web_i | Console Guild | src/Web/StellaOps.Web | Requires CCPR0101 verdicts | Requires CCPR0101 verdicts | CCSL0101 | +| CONSOLE-23-005 | TODO | | SPRINT_0212_0001_0001_web_i | Console Guild | src/Web/StellaOps.Web | Depends on #5 | Depends on #5 | CCSL0101 | | CONSOLE-OBS-52-001 | TODO | | SPRINT_303_docs_tasks_md_iii | Console Ops Guild | docs/modules/ui | Needs TLTY0101 metrics | Needs TLTY0101 metrics | CCSL0101 | | CONSOLE-OBS-52-002 | TODO | | SPRINT_303_docs_tasks_md_iii | Console Ops Guild | docs/modules/ui | Depends on #7 | Depends on #7 | CCSL0101 | -| CONSOLE-VEX-30-001 | TODO | 2025-11-08 | SPRINT_212_web_i | Console Guild · VEX Lens Guild | src/Web/StellaOps.Web | Provide `/console/vex/*` APIs streaming VEX statements, justification summaries, and advisory links with SSE refresh hooks. Dependencies: WEB-CONSOLE-23-001, EXCITITOR-CONSOLE-23-001. | Needs VEX Lens spec (PLVL0103) | CCSL0101 | -| CONSOLE-VULN-29-001 | TODO | 2025-11-08 | SPRINT_212_web_i | Console Guild | src/Web/StellaOps.Web | Build `/console/vuln/*` APIs and filters surfacing tenant-scoped findings with policy/VEX badges so Docs/UI teams can document workflows. Dependencies: WEB-CONSOLE-23-001, CONCELIER-GRAPH-21-001. | Depends on CCWO0101 | CCSL0101 | -| CONTAINERS-44-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Wait for DVCP0101 compose template | Wait for DVCP0101 compose template | COWB0101 | -| CONTAINERS-45-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Depends on #1 | Depends on #1 | COWB0101 | -| CONTAINERS-46-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Needs RBRE0101 hashes | Needs RBRE0101 hashes | COWB0101 | +| CONSOLE-VEX-30-001 | TODO | 2025-11-08 | SPRINT_0212_0001_0001_web_i | Console Guild · VEX Lens Guild | src/Web/StellaOps.Web | Provide `/console/vex/*` APIs streaming VEX statements, justification summaries, and advisory links with SSE refresh hooks. Dependencies: WEB-CONSOLE-23-001, EXCITITOR-CONSOLE-23-001. | Needs VEX Lens spec (PLVL0103) | CCSL0101 | +| CONSOLE-VULN-29-001 | TODO | 2025-11-08 | SPRINT_0212_0001_0001_web_i | Console Guild | src/Web/StellaOps.Web | Build `/console/vuln/*` APIs and filters surfacing tenant-scoped findings with policy/VEX badges so Docs/UI teams can document workflows. Dependencies: WEB-CONSOLE-23-001, CONCELIER-GRAPH-21-001. | Depends on CCWO0101 | CCSL0101 | +| CONTAINERS-44-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Wait for DVCP0101 compose template | Wait for DVCP0101 compose template | COWB0101 | +| CONTAINERS-45-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Depends on #1 | Depends on #1 | COWB0101 | +| CONTAINERS-46-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Needs RBRE0101 hashes | Needs RBRE0101 hashes | COWB0101 | | CONTRIB-62-001 | TODO | | SPRINT_303_docs_tasks_md_iii | Docs Guild · API Governance Guild | docs/api | Wait for CCWO0101 spec finalization | Wait for CCWO0101 spec finalization | APID0101 | | CORE-185-001 | TODO | | SPRINT_185_shared_replay_primitives | Platform Guild | `src/__Libraries/StellaOps.Replay.Core` | Wait for SGSI0101 feed | Wait for SGSI0101 feed | RLRC0101 | | CORE-185-002 | TODO | | SPRINT_185_shared_replay_primitives | Platform Guild | src/__Libraries/StellaOps.Replay.Core | Depends on #1 | Depends on #1 | RLRC0101 | @@ -913,8 +913,8 @@ | ENGINE-OPS-0001 | TODO | | SPRINT_325_docs_modules_policy | Ops Guild (docs/modules/policy) | docs/modules/policy | Operations runbook (deploy/rollback) pointer. | — | DOPE0107 | | ENTROPY-186-011 | TODO | | SPRINT_186_record_deterministic_execution | Scanner Guild · Provenance Guild | `src/Scanner/StellaOps.Scanner.Worker`, `src/Scanner/__Libraries` | SCANNER-ENTRYTRACE-18-508 | SCANNER-ENTRYTRACE-18-508 | SCDE0101 | | ENTROPY-186-012 | TODO | | SPRINT_186_record_deterministic_execution | Scanner Guild · Provenance Guild | `src/Scanner/StellaOps.Scanner.WebService`, `docs/replay/DETERMINISTIC_REPLAY.md` | ENTROPY-186-011 | ENTROPY-186-011 | SCDE0102 | -| ENTROPY-40-001 | TODO | | SPRINT_209_ui_i | UI Guild | src/UI/StellaOps.UI | ENTROPY-186-011 | ENTROPY-186-011 | UIDO0101 | -| ENTROPY-40-002 | TODO | | SPRINT_209_ui_i | UI Guild · Policy Guild | src/UI/StellaOps.UI | ENTROPY-40-001 & ENTROPY-186-012 | ENTROPY-40-001 | UIDO0101 | +| ENTROPY-40-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild | src/UI/StellaOps.UI | ENTROPY-186-011 | ENTROPY-186-011 | UIDO0101 | +| ENTROPY-40-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild · Policy Guild | src/UI/StellaOps.UI | ENTROPY-40-001 & ENTROPY-186-012 | ENTROPY-40-001 | UIDO0101 | | ENTROPY-70-004 | TODO | | SPRINT_304_docs_tasks_md_iv | Docs Guild · Scanner Guild | docs/modules/scanner/determinism.md | ENTROPY-186-011/012 | ENTROPY-186-011/012 | DOSC0102 | | ENTRYTRACE-18-502 | TODO | | SPRINT_135_scanner_surface | EntryTrace Guild · Scanner Surface Guild | src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace | SCANNER-ENTRYTRACE-18-508 | SCANNER-ENTRYTRACE-18-508 | SCET0101 | | ENTRYTRACE-18-503 | TODO | | SPRINT_135_scanner_surface | EntryTrace Guild · Scanner Surface Guild | src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace | ENTRYTRACE-18-502 | ENTRYTRACE-18-502 | SCET0101 | @@ -932,26 +932,26 @@ | EVID-REPLAY-187-001 | TODO | | SPRINT_160_export_evidence | Evidence Locker Guild · docs/modules/evidence-locker/architecture.md | docs/modules/evidence-locker/architecture.md | Evidence Locker Guild · docs/modules/evidence-locker/architecture.md | EVID-CRYPTO-90-001 | EVEC0101 | | EXC-25-001 | TODO | | SPRINT_202_cli_ii | DevEx/CLI Guild (`src/Cli/StellaOps.Cli`) | src/Cli/StellaOps.Cli | DOOR0102 APIs | DOOR0102 APIs | CLEX0101 | | EXC-25-002 | TODO | | SPRINT_202_cli_ii | DevEx/CLI Guild (`src/Cli/StellaOps.Cli`) | src/Cli/StellaOps.Cli | EXC-25-001 | EXC-25-001 | CLEX0101 | -| EXC-25-003 | TODO | | SPRINT_209_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | DOOR0102 APIs | DOOR0102 APIs | UIEX0101 | -| EXC-25-004 | TODO | | SPRINT_209_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | EXC-25-003 | EXC-25-003 | UIEX0101 | -| EXC-25-005 | TODO | | SPRINT_209_ui_i | UI + Accessibility Guilds (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | EXC-25-003 | EXC-25-003 | UIEX0101 | +| EXC-25-003 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | DOOR0102 APIs | DOOR0102 APIs | UIEX0101 | +| EXC-25-004 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | EXC-25-003 | EXC-25-003 | UIEX0101 | +| EXC-25-005 | TODO | | SPRINT_0209_0001_0001_ui_i | UI + Accessibility Guilds (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | EXC-25-003 | EXC-25-003 | UIEX0101 | | EXC-25-006 | TODO | | SPRINT_303_docs_tasks_md_iii | Docs Guild · DevEx Guild | docs/modules/excititor | CLEX0101 CLI updates | CLEX0101 CLI updates | DOEX0101 | | EXC-25-007 | TODO | | SPRINT_304_docs_tasks_md_iv | Docs Guild · DevOps Guild | docs/modules/excititor | UIEX0101 console outputs | UIEX0101 console outputs | DOEX0101 | -| EXCITITOR-AIAI-31-001 | DONE | 2025-11-09 | SPRINT_110_ingestion_evidence | Excititor Web/Core Guilds | | Normalised VEX justification projections shipped. | | EXWK0101 | -| EXCITITOR-AIAI-31-002 | TODO | | SPRINT_110_ingestion_evidence | Excititor Web/Core Guilds | | Chunk API waiting on schema + ingest agreements. | CONCELIER-GRAPH-21-001; CONCELIER-GRAPH-21-002; ELOCKER-CONTRACT-2001 | EXAI0101 | -| EXCITITOR-AIAI-31-003 | TODO | | SPRINT_110_ingestion_evidence | Excititor Observability Guild | | Telemetry/guardrail metrics follow chunk API. | EXCITITOR-AIAI-31-002 | EXAI0101 | -| EXCITITOR-AIAI-31-004 | TODO | | SPRINT_110_ingestion_evidence | Docs Guild · Excititor Guild | | Docs/OpenAPI alignment queued behind chunk API finalisation. | EXCITITOR-AIAI-31-002 | EXAI0101 | +| EXCITITOR-AIAI-31-001 | DONE | 2025-11-12 | SPRINT_0119_0001_0001_excititor_i | Excititor Web/Core Guilds | src/Excititor/StellaOps.Excititor.WebService | Normalised VEX justification projections shipped. | | EXWK0101 | +| EXCITITOR-AIAI-31-002 | DONE | 2025-11-17 | SPRINT_0119_0001_0001_excititor_i | Excititor Web/Core Guilds | src/Excititor/StellaOps.Excititor.WebService | Chunk API streaming raw statements + signature metadata with tenant/policy filters. | CONCELIER-GRAPH-21-001; CONCELIER-GRAPH-21-002; ELOCKER-CONTRACT-2001 | EXAI0101 | +| EXCITITOR-AIAI-31-003 | DONE | 2025-11-17 | SPRINT_0119_0001_0001_excititor_i | Excititor Observability Guild | src/Excititor/StellaOps.Excititor.WebService | Telemetry/guardrail metrics (counters, chunk histograms, signature failure + AOC guard meters); traces pending span sink. | EXCITITOR-AIAI-31-002 | EXAI0101 | +| EXCITITOR-AIAI-31-004 | DONE | 2025-11-18 | SPRINT_0119_0001_0001_excititor_i | Docs Guild · Excititor Guild | docs/modules/excititor/evidence-contract.md | Advisory-AI evidence contract + determinism guarantees and storage mapping. | EXCITITOR-AIAI-31-002 | EXAI0101 | | EXCITITOR-AIRGAP-56 | TODO | | SPRINT_110_ingestion_evidence | Excititor Guild · AirGap Guilds | | Air-gap + connector parity depend on schema + attestation readiness. | CONCELIER-GRAPH-21-001; CONCELIER-GRAPH-21-002; ATTEST-PLAN-2001 | EXAG0101 | | EXCITITOR-AIRGAP-56-001 | DOING (2025-11-22) | | SPRINT_0119_0001_0001_excititor_i | Excititor Core Guild (`src/Excititor/__Libraries/StellaOps.Excititor.Core`) | src/Excititor/__Libraries/StellaOps.Excititor.Core | Wire mirror bundle ingestion paths that preserve upstream digests, bundle IDs, and provenance metadata exactly so offline Advisory-AI/Lens deployments can replay evidence with AOC parity. | EXCITITOR-AIRGAP-56 | EXAG0101 | | EXCITITOR-AIRGAP-57 | TODO | | SPRINT_110_ingestion_evidence | Excititor Guild · AirGap Guilds | | Same as -56 plus Evidence Locker | CONCELIER-GRAPH-21-001; CONCELIER-GRAPH-21-002; ATTEST-PLAN-2001 | EXAG0101 | | EXCITITOR-AIRGAP-57-001 | TODO | | SPRINT_0119_0001_0001_excititor_i | Excititor AirGap Policy Guild (`src/Excititor/__Libraries/StellaOps.Excititor.Core`) | src/Excititor/__Libraries/StellaOps.Excititor.Core | Enforce sealed-mode policies that disable external connectors, emit actionable remediation errors, and record staleness annotations that Advisory AI can surface as “evidence freshness” signals. Depends on EXCITITOR-AIRGAP-56-001. | EXCITITOR-AIRGAP-57 | EXAG0101 | | EXCITITOR-AIRGAP-58 | TODO | | SPRINT_110_ingestion_evidence | Excititor Guild · AirGap Guilds | | Same upstream | CONCELIER-GRAPH-21-001; CONCELIER-GRAPH-21-002; ATTEST-PLAN-2001 | EXAG0101 | | EXCITITOR-AIRGAP-58-001 | TODO | | SPRINT_0119_0001_0001_excititor_i | Excititor Core + Evidence Locker Guilds | src/Excititor/__Libraries/StellaOps.Excititor.Core | Package tenant-scoped VEX evidence (raw JSON, normalization diff, provenance) into portable bundles tied to timeline events so Advisory AI can hydrate contexts in sealed environments. Depends on EXCITITOR-AIRGAP-57-001. | EXCITITOR-AIRGAP-58 | EXAG0101 | -| EXCITITOR-ATTEST-01-003 | TODO | | SPRINT_110_ingestion_evidence | Excititor Guild | | Attestation payload ordering awaiting sequencing session. | EXCITITOR-AIAI-31-002; ELOCKER-CONTRACT-2001 | EXAT0101 | -| EXCITITOR-ATTEST-73-001 | TODO | | SPRINT_0119_0001_0001_excititor_i | Excititor Guild | src/Excititor/__Libraries/StellaOps.Excititor.Core | Emit attestation payloads that capture supplier identity, justification summary, and scope metadata so downstream Lens/Policy jobs can chain trust without Excititor interpreting the evidence. Depends on EXCITITOR-ATTEST-01-003. | EXCITITOR-ATTEST-01-003 | EXAT0101 | -| EXCITITOR-ATTEST-73-002 | TODO | | SPRINT_0119_0001_0001_excititor_i | Excititor Guild | src/Excititor/__Libraries/StellaOps.Excititor.Core | Provide APIs that link attestation IDs back to observation/linkset/product tuples, enabling Advisory AI to cite provenance without any derived verdict. Depends on EXCITITOR-ATTEST-73-001. | EXCITITOR-ATTEST-73-001 | EXAT0101 | +| EXCITITOR-ATTEST-01-003 | DONE | 2025-11-17 | SPRINT_0119_0001_0001_excititor_i | Excititor Guild | src/Excititor/__Libraries/StellaOps.Excititor.Core | Attestation verifier harness + diagnostics prove DSSE bundle verification without consensus logic. | EXCITITOR-AIAI-31-002; ELOCKER-CONTRACT-2001 | EXAT0101 | +| EXCITITOR-ATTEST-73-001 | DONE | 2025-11-17 | SPRINT_0119_0001_0001_excititor_i | Excititor Guild | src/Excititor/__Libraries/StellaOps.Excititor.Core | Attestation payloads emitted with supplier identity, justification summary, and scope metadata for trust chaining. | EXCITITOR-ATTEST-01-003 | EXAT0101 | +| EXCITITOR-ATTEST-73-002 | DONE | 2025-11-17 | SPRINT_0119_0001_0001_excititor_i | Excititor Guild | src/Excititor/__Libraries/StellaOps.Excititor.Core | APIs link attestation IDs back to observation/linkset/product tuples for provenance citations without derived verdicts. | EXCITITOR-ATTEST-73-001 | EXAT0101 | | EXCITITOR-CONN-SUSE-01-003 | TODO | | SPRINT_120_excititor_ii | Excititor Guild (SUSE connector) | src/Excititor/__Libraries/StellaOps.Excititor.Connectors.SUSE.RancherVEXHub | DONE (2025-11-09) – Emit provider trust configuration (signer fingerprints, trust tier notes) into the raw provenance envelope so downstream VEX Lens/Policy components can weigh issuers. Connector must not apply weighting or consensus inside ingestion. | EXCITITOR-CONN-SUSE-01-002; EXCITITOR-POLICY-01-001 | EXCN0101 | -| EXCITITOR-CONN-TRUST-01-001 | TODO | | SPRINT_110_ingestion_evidence | Excititor Guild · AirGap Guilds | | ATTEST-PLAN-2001 | CONCELIER-GRAPH-21-001; CONCELIER-GRAPH-21-002; ATTEST-PLAN-2001 | EXCN0101 | +| EXCITITOR-CONN-TRUST-01-001 | DONE | 2025-11-20 | SPRINT_0119_0001_0001_excititor_i | Excititor Guild · AirGap Guilds | src/Excititor/__Libraries/StellaOps.Excititor.Connectors* | Signer metadata loader/enricher wired for MSRC/Oracle/Ubuntu/OpenVEX connectors; env `STELLAOPS_CONNECTOR_SIGNER_METADATA_PATH`; docs + sample hash shipped. | CONCELIER-GRAPH-21-001; CONCELIER-GRAPH-21-002; ATTEST-PLAN-2001 | EXCN0101 | | EXCITITOR-CONN-UBUNTU-01-003 | TODO | | SPRINT_120_excititor_ii | Excititor Guild (Ubuntu connector) | src/Excititor/__Libraries/StellaOps.Excititor.Connectors.Ubuntu.CSAF | DONE (2025-11-09) – Emit Ubuntu signing metadata (GPG fingerprints, issuer trust tier) inside raw provenance artifacts so downstream Policy/VEX Lens consumers can weigh issuers. Connector must remain aggregation-only with no inline weighting. | EXCITITOR-CONN-UBUNTU-01-002 | EXCN0101 | | EXCITITOR-CONSOLE-23-001 | TODO | | SPRINT_120_excititor_ii | Excititor Guild · Docs Guild | src/Excititor/StellaOps.Excititor.WebService | Expose `/console/vex` endpoints returning grouped VEX statements per advisory/component with status chips, justification metadata, precedence trace pointers, and tenant-scoped filters for Console explorer. Dependencies: EXCITITOR-LNM-21-201, EXCITITOR-LNM-21-202. | DOCN0101 | EXCO0101 | | EXCITITOR-CONSOLE-23-002 | TODO | | SPRINT_120_excititor_ii | Excititor Guild | src/Excititor/StellaOps.Excititor.WebService | Provide aggregated counts for VEX overrides (new, not_affected, revoked) powering Console dashboard + live status ticker; emit metrics for policy explain integration. Dependencies: EXCITITOR-CONSOLE-23-001, EXCITITOR-LNM-21-203. | EXCITITOR-CONSOLE-23-001 | EXCO0101 | @@ -1085,27 +1085,27 @@ | GRAPH-21-003 | TODO | 2025-10-27 | SPRINT_213_web_ii | Scanner WebService Guild | src/Web/StellaOps.Web | GRAPH-21-001 | GRAPH-21-001 | GRSC0101 | | GRAPH-21-004 | TODO | 2025-10-27 | SPRINT_213_web_ii | Scanner WebService Guild | src/Web/StellaOps.Web | GRAPH-21-002 | GRAPH-21-002 | GRSC0101 | | GRAPH-21-005 | BLOCKED (2025-10-27) | 2025-10-27 | SPRINT_120_excititor_ii | Excititor Storage Guild | src/Excititor/__Libraries/StellaOps.Excititor.Storage.Mongo | GRAPH-21-002 | GRAPH-21-002 | GRSC0101 | -| GRAPH-24-001 | TODO | | SPRINT_209_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | GRSC0101 outputs | GRSC0101 outputs | GRUI0101 | -| GRAPH-24-002 | TODO | | SPRINT_209_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-001 | GRAPH-24-001 | GRUI0101 | -| GRAPH-24-003 | TODO | | SPRINT_209_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-001 | GRAPH-24-001 | GRUI0101 | -| GRAPH-24-004 | TODO | | SPRINT_209_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-002 | GRAPH-24-002 | GRUI0101 | +| GRAPH-24-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | GRSC0101 outputs | GRSC0101 outputs | GRUI0101 | +| GRAPH-24-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-001 | GRAPH-24-001 | GRUI0101 | +| GRAPH-24-003 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-001 | GRAPH-24-001 | GRUI0101 | +| GRAPH-24-004 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-002 | GRAPH-24-002 | GRUI0101 | | GRAPH-24-005 | TODO | | SPRINT_304_docs_tasks_md_iv | UI Guild | | GRAPH-24-003 | GRAPH-24-003 | GRUI0101 | -| GRAPH-24-006 | TODO | | SPRINT_209_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-004 | GRAPH-24-004 | GRUI0101 | +| GRAPH-24-006 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-004 | GRAPH-24-004 | GRUI0101 | | GRAPH-24-007 | TODO | | SPRINT_304_docs_tasks_md_iv | UI Guild | | GRAPH-24-005 | GRAPH-24-005 | GRUI0101 | | GRAPH-24-101 | TODO | | SPRINT_113_concelier_ii | UI Guild | src/Concelier/StellaOps.Concelier.WebService | GRAPH-24-001 | GRAPH-24-001 | GRUI0101 | | GRAPH-24-102 | TODO | | SPRINT_120_excititor_ii | UI Guild | src/Excititor/StellaOps.Excititor.WebService | GRAPH-24-101 | GRAPH-24-101 | GRUI0101 | | GRAPH-28-102 | TODO | | SPRINT_113_concelier_ii | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | GRAPI0101 | -| GRAPH-API-28-001 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Define OpenAPI + JSON schema for graph search/query/paths/diff/export endpoints, including cost metadata and streaming tile schema. | — | ORGR0101 | -| GRAPH-API-28-002 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/search` with multi-type index lookup, prefix/exact match, RBAC enforcement, and result ranking + caching. Dependencies: GRAPH-API-28-001. | — | ORGR0101 | -| GRAPH-API-28-003 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Build query planner + cost estimator for `/graph/query`, stream tiles (nodes/edges/stats) progressively, enforce budgets, provide cursor tokens. Dependencies: GRAPH-API-28-002. | — | ORGR0101 | -| GRAPH-API-28-004 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/paths` with depth ≤6, constraint filters, heuristic shortest path search, and optional policy overlay rendering. Dependencies: GRAPH-API-28-003. | — | ORGR0101 | -| GRAPH-API-28-005 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/diff` streaming added/removed/changed nodes/edges between SBOM snapshots; include overlay deltas and policy/VEX/advisory metadata. Dependencies: GRAPH-API-28-004. | — | ORGR0101 | -| GRAPH-API-28-006 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Consume Policy Engine overlay contract (`POLICY-ENGINE-30-001..003`) and surface advisory/VEX/policy overlays with caching, partial materialization, and explain trace sampling for focused nodes. Dependencies: GRAPH-API-28-005. | — | ORGR0101 | -| GRAPH-API-28-007 | TODO | | SPRINT_207_graph | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | src/Graph/StellaOps.Graph.Api | Implement exports (`graphml`, `csv`, `ndjson`, `png`, `svg`) with async job management, checksum manifests, and streaming downloads. Dependencies: GRAPH-API-28-006. | ORGR0101 outputs | GRAPI0101 | -| GRAPH-API-28-008 | TODO | | SPRINT_207_graph | Graph API + Authority Guilds | src/Graph/StellaOps.Graph.Api | Integrate RBAC scopes (`graph:read`, `graph:query`, `graph:export`), tenant headers, audit logging, and rate limiting. Dependencies: GRAPH-API-28-007. | GRAPH-API-28-007 | GRAPI0101 | -| GRAPH-API-28-009 | TODO | | SPRINT_207_graph | Graph API + Observability Guilds | src/Graph/StellaOps.Graph.Api | Instrument metrics (`graph_tile_latency_seconds`, `graph_query_budget_denied_total`, `graph_overlay_cache_hit_ratio`), structured logs, and traces per query stage; publish dashboards. Dependencies: GRAPH-API-28-008. | GRAPH-API-28-007 | GRAPI0101 | -| GRAPH-API-28-010 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Build unit/integration/load tests with synthetic datasets (500k nodes/2M edges), fuzz query validation, verify determinism across runs. Dependencies: GRAPH-API-28-009. | GRAPH-API-28-008 | GRAPI0101 | -| GRAPH-API-28-011 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Provide deployment manifests, offline kit support, API gateway integration docs, and smoke tests. Dependencies: GRAPH-API-28-010. | GRAPH-API-28-009 | GRAPI0101 | +| GRAPH-API-28-001 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Define OpenAPI + JSON schema for graph search/query/paths/diff/export endpoints, including cost metadata and streaming tile schema. | — | ORGR0101 | +| GRAPH-API-28-002 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/search` with multi-type index lookup, prefix/exact match, RBAC enforcement, and result ranking + caching. Dependencies: GRAPH-API-28-001. | — | ORGR0101 | +| GRAPH-API-28-003 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Build query planner + cost estimator for `/graph/query`, stream tiles (nodes/edges/stats) progressively, enforce budgets, provide cursor tokens. Dependencies: GRAPH-API-28-002. | — | ORGR0101 | +| GRAPH-API-28-004 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/paths` with depth ≤6, constraint filters, heuristic shortest path search, and optional policy overlay rendering. Dependencies: GRAPH-API-28-003. | — | ORGR0101 | +| GRAPH-API-28-005 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/diff` streaming added/removed/changed nodes/edges between SBOM snapshots; include overlay deltas and policy/VEX/advisory metadata. Dependencies: GRAPH-API-28-004. | — | ORGR0101 | +| GRAPH-API-28-006 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Consume Policy Engine overlay contract (`POLICY-ENGINE-30-001..003`) and surface advisory/VEX/policy overlays with caching, partial materialization, and explain trace sampling for focused nodes. Dependencies: GRAPH-API-28-005. | — | ORGR0101 | +| GRAPH-API-28-007 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | src/Graph/StellaOps.Graph.Api | Implement exports (`graphml`, `csv`, `ndjson`, `png`, `svg`) with async job management, checksum manifests, and streaming downloads. Dependencies: GRAPH-API-28-006. | ORGR0101 outputs | GRAPI0101 | +| GRAPH-API-28-008 | TODO | | SPRINT_0207_0001_0001_graph | Graph API + Authority Guilds | src/Graph/StellaOps.Graph.Api | Integrate RBAC scopes (`graph:read`, `graph:query`, `graph:export`), tenant headers, audit logging, and rate limiting. Dependencies: GRAPH-API-28-007. | GRAPH-API-28-007 | GRAPI0101 | +| GRAPH-API-28-009 | TODO | | SPRINT_0207_0001_0001_graph | Graph API + Observability Guilds | src/Graph/StellaOps.Graph.Api | Instrument metrics (`graph_tile_latency_seconds`, `graph_query_budget_denied_total`, `graph_overlay_cache_hit_ratio`), structured logs, and traces per query stage; publish dashboards. Dependencies: GRAPH-API-28-008. | GRAPH-API-28-007 | GRAPI0101 | +| GRAPH-API-28-010 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Build unit/integration/load tests with synthetic datasets (500k nodes/2M edges), fuzz query validation, verify determinism across runs. Dependencies: GRAPH-API-28-009. | GRAPH-API-28-008 | GRAPI0101 | +| GRAPH-API-28-011 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Provide deployment manifests, offline kit support, API gateway integration docs, and smoke tests. Dependencies: GRAPH-API-28-010. | GRAPH-API-28-009 | GRAPI0101 | | GRAPH-CAS-401-001 | TODO | | SPRINT_401_reachability_evidence_chain | Scanner Worker Guild | `src/Scanner/StellaOps.Scanner.Worker` | Finalize richgraph schema (`richgraph-v1`), emit canonical SymbolIDs, compute graph hash (BLAKE3), and store CAS manifests under `cas://reachability/graphs/{sha256}`. Update Scanner Worker adapters + fixtures. | Depends on #1 | CASC0101 | | GRAPH-DOCS-0001 | DONE (2025-11-05) | 2025-11-05 | SPRINT_321_docs_modules_graph | Docs Guild | docs/modules/graph | Validate that graph module README/diagrams reflect the latest overlay + snapshot updates. | GRAPI0101 evidence | GRDG0101 | | GRAPH-DOCS-0002 | TODO | 2025-11-05 | SPRINT_321_docs_modules_graph | Docs Guild | docs/modules/graph | Pending DOCS-GRAPH-24-003 to add API/query doc cross-links | GRAPI0101 outputs | GRDG0101 | @@ -1114,7 +1114,7 @@ | GRAPH-INDEX-28-008 | TODO | | SPRINT_0140_0001_0001_runtime_signals | — | | Incremental update/backfill pipeline depends on 28-007 artifacts; retry/backoff plumbing sketched but blocked. | — | ORGR0101 | | GRAPH-INDEX-28-009 | TODO | | SPRINT_0140_0001_0001_runtime_signals | — | | Test/fixture/chaos coverage waits on earlier jobs to exist so determinism checks have data. | — | ORGR0101 | | GRAPH-INDEX-28-010 | TODO | | SPRINT_0140_0001_0001_runtime_signals | — | | Packaging/offline bundles paused until upstream graph jobs are available to embed. | — | ORGR0101 | -| GRAPH-INDEX-28-011 | TODO | 2025-11-04 | SPRINT_207_graph | Graph Index Guild | src/Graph/StellaOps.Graph.Indexer | Wire SBOM ingest runtime to emit graph snapshot artifacts, add DI factory helpers, and document Mongo/snapshot environment guidance. Dependencies: GRAPH-INDEX-28-002..006. | GRSC0101 outputs | GRIX0101 | +| GRAPH-INDEX-28-011 | TODO | 2025-11-04 | SPRINT_0207_0001_0001_graph | Graph Index Guild | src/Graph/StellaOps.Graph.Indexer | Wire SBOM ingest runtime to emit graph snapshot artifacts, add DI factory helpers, and document Mongo/snapshot environment guidance. Dependencies: GRAPH-INDEX-28-002..006. | GRSC0101 outputs | GRIX0101 | | GRAPH-OPS-0001 | TODO | | SPRINT_321_docs_modules_graph | Ops Guild | docs/modules/graph | Review graph observability dashboards/runbooks after the next sprint demo. | GRUI0101 | GRDG0101 | | HELM-45-001 | TODO | | SPRINT_501_ops_deployment_i | Deployment Guild (ops/deployment) | ops/deployment | | | GRIX0101 | | HELM-45-002 | TODO | | SPRINT_502_ops_deployment_ii | Deployment Guild, Security Guild (ops/deployment) | ops/deployment | Add TLS/Ingress, NetworkPolicy, PodSecurityContexts, Secrets integration (external secrets), and document security posture. Dependencies: HELM-45-001. | | GRIX0101 | @@ -1132,7 +1132,7 @@ | INDEX-28-008 | TODO | | SPRINT_0140_0001_0001_runtime_signals | Graph Index Guild | src/Graph/StellaOps.Graph.Indexer | INDEX-28-007 | INDEX-28-007 | GRIX0101 | | INDEX-28-009 | TODO | | SPRINT_0140_0001_0001_runtime_signals | Graph Index Guild | src/Graph/StellaOps.Graph.Indexer | INDEX-28-008 | INDEX-28-008 | GRIX0101 | | INDEX-28-010 | TODO | | SPRINT_0140_0001_0001_runtime_signals | Graph Indexer Guild (src/Graph/StellaOps.Graph.Indexer) | src/Graph/StellaOps.Graph.Indexer | | INDEX-28-009 | GRIX0101 | -| INDEX-28-011 | DONE | 2025-11-04 | SPRINT_207_graph | Graph Indexer Guild (src/Graph/StellaOps.Graph.Indexer) | src/Graph/StellaOps.Graph.Indexer | | INDEX-28-010 | GRIX0101 | +| INDEX-28-011 | DONE | 2025-11-04 | SPRINT_0207_0001_0001_graph | Graph Indexer Guild (src/Graph/StellaOps.Graph.Indexer) | src/Graph/StellaOps.Graph.Indexer | | INDEX-28-010 | GRIX0101 | | INDEX-401-030 | TODO | | SPRINT_401_reachability_evidence_chain | Platform + Ops Guilds | `docs/provenance/inline-dsse.md`, `ops/mongo/indices/events_provenance_indices.js` | Needs Ops approval for new Mongo index | Needs Ops approval for new Mongo index | RBRE0101 | | INGEST-401-013 | TODO | | SPRINT_401_reachability_evidence_chain | Symbols Guild · DevOps Guild (`src/Symbols/StellaOps.Symbols.Ingestor.Cli`) | `src/Symbols/StellaOps.Symbols.Ingestor.Cli`, `docs/specs/SYMBOL_MANIFEST_v1.md` | Implement deterministic ingest + docs. | RBRE0101 inline DSSE | IMPT0101 | | INLINE-401-028 | DONE | | SPRINT_401_reachability_evidence_chain | Authority Guild · Feedser Guild (`docs/provenance/inline-dsse.md`, `src/__Libraries/StellaOps.Provenance.Mongo`) | `docs/provenance/inline-dsse.md`, `src/__Libraries/StellaOps.Provenance.Mongo` | | | INST0101 | @@ -1391,7 +1391,7 @@ | POLICY-ATTEST-74-002 | TODO | | SPRINT_123_policy_reasoning | Policy Guild, Console Guild / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Surface policy evaluations in Console verification reports with rule explanations | POLICY-ATTEST-74-001 | | | POLICY-CONSOLE-23-001 | TODO | | SPRINT_123_policy_reasoning | Policy Guild, BE-Base Platform Guild / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Optimize findings/explain APIs for Console: cursor-based pagination at scale, global filter parameters (severity bands, policy version, time window), rule trace summarization, and aggregation hints for dashboard cards. Ensure deterministic ordering and expose provenance refs | | | | POLICY-CONSOLE-23-002 | TODO | | SPRINT_124_policy_reasoning | Policy Guild, Product Ops / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Produce simulation diff metadata | POLICY-CONSOLE-23-001 | | -| POLICY-DET-01 | TODO | | SPRINT_209_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | | | | +| POLICY-DET-01 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | | | | | POLICY-ENGINE-20-002 | BLOCKED | 2025-10-26 | SPRINT_124_policy_reasoning | Policy Guild / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Build deterministic evaluator honoring lexical/priority order, first-match semantics, and safe value types (no wall-clock/network access) | PGMI0101 | PLPE0101 | | POLICY-ENGINE-20-003 | TODO | | SPRINT_124_policy_reasoning | Policy Guild, Concelier Core Guild, Excititor Core Guild / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Implement selection joiners resolving SBOM↔advisory↔VEX tuples using linksets and PURL equivalence tables, with deterministic batching | POLICY-ENGINE-20-002 | PLPE0101 | | POLICY-ENGINE-20-004 | TODO | | SPRINT_124_policy_reasoning | Policy Guild, Platform Storage Guild / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Ship materialization writer that upserts into `effective_finding_{policyId}` with append-only history, tenant scoping, and trace references | POLICY-ENGINE-20-003 | PLPE0101 | @@ -1578,7 +1578,7 @@ | SBOM-AIAI-31-003 | BLOCKED | 2025-11-18 | SPRINT_0111_0001_0001_advisoryai | SBOM Service Guild · Advisory AI Guild (src/SbomService/StellaOps.SbomService) | src/SbomService/StellaOps.SbomService | Publish the Advisory AI hand-off kit for `/v1/sbom/context`, share base URL/API key + tenant header contract, and run a joint end-to-end retrieval smoke test with Advisory AI. | SBOM-AIAI-31-001 projection kit/fixtures | ADAI0101 | | SBOM-CONSOLE-23-001 | TODO | | SPRINT_0140_0001_0001_runtime_signals | | | Console catalog API draft complete; depends on Concelier/Cartographer payload definitions. | | | | SBOM-CONSOLE-23-002 | TODO | | SPRINT_0140_0001_0001_runtime_signals | | | Global component lookup API needs 23-001 responses + cache hints before work can start. | | | -| SBOM-DET-01 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | | | | +| SBOM-DET-01 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | | | | | SBOM-ORCH-32-001 | TODO | | SPRINT_0140_0001_0001_runtime_signals | | | Orchestrator registration is sequenced after projection schema because payload shapes map into job metadata. | | | | SBOM-ORCH-33-001 | TODO | | SPRINT_0140_0001_0001_runtime_signals | | | Backpressure/telemetry features depend on 32-001 workers. | | | | SBOM-ORCH-34-001 | TODO | | SPRINT_0140_0001_0001_runtime_signals | | | Backfill + watermark logic requires the orchestrator integration from 33-001. | | | @@ -1770,18 +1770,18 @@ | SDK-62-002 | TODO | | SPRINT_204_cli_iv | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | | SDK-63-001 | TODO | | SPRINT_204_cli_iv | DevEx/CLI Guild, API Governance Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | | SDK-64-001 | TODO | | SPRINT_204_cli_iv | DevEx/CLI Guild, SDK Release Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | -| SDKGEN-62-001 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Choose/pin generator toolchain, set up language template pipeline, and enforce reproducible builds. | DEVL0101 portal contracts | SDKG0101 | -| SDKGEN-62-002 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Implement shared post-processing (auth helpers, retries, pagination utilities, telemetry hooks) applied to all languages. Dependencies: SDKGEN-62-001. | SDKGEN-62-001 | SDKG0101 | -| SDKGEN-63-001 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship TypeScript SDK alpha with ESM/CJS builds, typed errors, paginator, streaming helpers. Dependencies: SDKGEN-62-002. | 63-004 | SDKG0101 | -| SDKGEN-63-002 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Python SDK alpha (sync/async clients, type hints, upload/download helpers). Dependencies: SDKGEN-63-001. | SDKGEN-63-001 | SDKG0101 | -| SDKGEN-63-003 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Go SDK alpha with context-first API and streaming helpers. Dependencies: SDKGEN-63-002. | SDKGEN-63-002 | SDKG0101 | -| SDKGEN-63-004 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Java SDK alpha (builder pattern, HTTP client abstraction). Dependencies: SDKGEN-63-003. | SDKGEN-63-003 | SDKG0101 | -| SDKGEN-64-001 | TODO | | SPRINT_208_sdk | SDK Generator Guild · CLI Guild | src/Sdk/StellaOps.Sdk.Generator | Switch CLI to consume TS or Go SDK; ensure parity. Dependencies: SDKGEN-63-004. | SDKGEN-63-004 | SDKG0101 | -| SDKGEN-64-002 | TODO | | SPRINT_208_sdk | SDK Generator Guild · Console Guild | src/Sdk/StellaOps.Sdk.Generator | Integrate SDKs into Console data providers where feasible. Dependencies: SDKGEN-64-001. | SDKGEN-64-001 | SDKG0101 | -| SDKREL-63-001 | TODO | | SPRINT_208_sdk | SDK Release Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Configure CI pipelines for npm, PyPI, Maven Central staging, and Go proxies with signing and provenance attestations. | | | -| SDKREL-63-002 | TODO | | SPRINT_208_sdk | SDK Release Guild, API Governance Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Integrate changelog automation pulling from OAS diffs and generator metadata. Dependencies: SDKREL-63-001. | | | -| SDKREL-64-001 | TODO | | SPRINT_208_sdk | SDK Release Guild, Notifications Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Hook SDK releases into Notifications Studio with scoped announcements and RSS/Atom feeds. Dependencies: SDKREL-63-002. | | | -| SDKREL-64-002 | TODO | | SPRINT_208_sdk | SDK Release Guild, Export Center Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Add `devportal --offline` bundle job packaging docs, specs, SDK artifacts for air-gapped users. Dependencies: SDKREL-64-001. | | | +| SDKGEN-62-001 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Choose/pin generator toolchain, set up language template pipeline, and enforce reproducible builds. | DEVL0101 portal contracts | SDKG0101 | +| SDKGEN-62-002 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Implement shared post-processing (auth helpers, retries, pagination utilities, telemetry hooks) applied to all languages. Dependencies: SDKGEN-62-001. | SDKGEN-62-001 | SDKG0101 | +| SDKGEN-63-001 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship TypeScript SDK alpha with ESM/CJS builds, typed errors, paginator, streaming helpers. Dependencies: SDKGEN-62-002. | 63-004 | SDKG0101 | +| SDKGEN-63-002 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Python SDK alpha (sync/async clients, type hints, upload/download helpers). Dependencies: SDKGEN-63-001. | SDKGEN-63-001 | SDKG0101 | +| SDKGEN-63-003 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Go SDK alpha with context-first API and streaming helpers. Dependencies: SDKGEN-63-002. | SDKGEN-63-002 | SDKG0101 | +| SDKGEN-63-004 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Java SDK alpha (builder pattern, HTTP client abstraction). Dependencies: SDKGEN-63-003. | SDKGEN-63-003 | SDKG0101 | +| SDKGEN-64-001 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild · CLI Guild | src/Sdk/StellaOps.Sdk.Generator | Switch CLI to consume TS or Go SDK; ensure parity. Dependencies: SDKGEN-63-004. | SDKGEN-63-004 | SDKG0101 | +| SDKGEN-64-002 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild · Console Guild | src/Sdk/StellaOps.Sdk.Generator | Integrate SDKs into Console data providers where feasible. Dependencies: SDKGEN-64-001. | SDKGEN-64-001 | SDKG0101 | +| SDKREL-63-001 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Release Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Configure CI pipelines for npm, PyPI, Maven Central staging, and Go proxies with signing and provenance attestations. | | | +| SDKREL-63-002 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Release Guild, API Governance Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Integrate changelog automation pulling from OAS diffs and generator metadata. Dependencies: SDKREL-63-001. | | | +| SDKREL-64-001 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Release Guild, Notifications Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Hook SDK releases into Notifications Studio with scoped announcements and RSS/Atom feeds. Dependencies: SDKREL-63-002. | | | +| SDKREL-64-002 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Release Guild, Export Center Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Add `devportal --offline` bundle job packaging docs, specs, SDK artifacts for air-gapped users. Dependencies: SDKREL-64-001. | | | | SEC-62-001 | TODO | | SPRINT_309_docs_tasks_md_ix | Docs Guild, Authority Core (docs) | | | | | | SEC-CRYPTO-90-001 | DONE | 2025-11-07 | SPRINT_514_sovereign_crypto_enablement | Security Guild (src/__Libraries/StellaOps.Cryptography) | src/__Libraries/StellaOps.Cryptography | Produce the RootPack_RU implementation plan, provider strategy (CryptoPro + PKCS#11), and backlog split for sovereign crypto work. | | | | SEC-CRYPTO-90-002 | DONE | 2025-11-07 | SPRINT_514_sovereign_crypto_enablement | Security Guild (src/__Libraries/StellaOps.Cryptography) | src/__Libraries/StellaOps.Cryptography | Extend signature/catalog constants and configuration schema to recognize `GOST12-256/512`, regional crypto profiles, and provider preference ordering. | | | @@ -1982,26 +1982,26 @@ | TIMELINE-OBS-52-004 | TODO | | SPRINT_160_export_evidence | Timeline Indexer + Security Guilds | | Timeline Indexer + Security Guilds | | | | TIMELINE-OBS-53-001 | TODO | | SPRINT_160_export_evidence | Timeline Indexer + Evidence Locker Guilds | | Timeline Indexer + Evidence Locker Guilds | | | | UI-401-027 | TODO | | SPRINT_401_reachability_evidence_chain | UI Guild · CLI Guild (`src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/uncertainty/README.md`) | `src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/uncertainty/README.md` | | | | -| UI-AOC-19-001 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add Sources dashboard tiles showing AOC pass/fail, recent violation codes, and ingest throughput per tenant. | | | -| UI-AOC-19-002 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement violation drill-down view highlighting offending document fields and provenance metadata. Dependencies: UI-AOC-19-001. | | | -| UI-AOC-19-003 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add "Verify last 24h" action triggering AOC verifier endpoint and surfacing CLI parity guidance. Dependencies: UI-AOC-19-002. | | | +| UI-AOC-19-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add Sources dashboard tiles showing AOC pass/fail, recent violation codes, and ingest throughput per tenant. | | | +| UI-AOC-19-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement violation drill-down view highlighting offending document fields and provenance metadata. Dependencies: UI-AOC-19-001. | | | +| UI-AOC-19-003 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add "Verify last 24h" action triggering AOC verifier endpoint and surfacing CLI parity guidance. Dependencies: UI-AOC-19-002. | | | | UI-CLI-401-007 | TODO | | SPRINT_401_reachability_evidence_chain | UI & CLI Guilds (`src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`) | `src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI` | Implement CLI `stella graph explain` + UI explain drawer showing signed call-path, predicates, runtime hits, and DSSE pointers; include counterfactual controls. | | | | UI-DOCS-0001 | TODO | | SPRINT_331_docs_modules_ui | Docs Guild (docs/modules/ui) | docs/modules/ui | | | | | UI-ENG-0001 | TODO | | SPRINT_331_docs_modules_ui | Module Team (docs/modules/ui) | docs/modules/ui | | | | -| UI-ENTROPY-40-001 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Visualise entropy analysis per image (layer donut, file heatmaps, “Why risky?” chips) in Vulnerability Explorer and scan details, including opaque byte ratios and detector hints (see `docs/modules/scanner/entropy.md`). | | | -| UI-ENTROPY-40-002 | TODO | | SPRINT_209_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add policy banners/tooltips explaining entropy penalties (block/warn thresholds, mitigation steps) and link to raw `entropy.report.json` evidence downloads (`docs/modules/scanner/entropy.md`). Dependencies: UI-ENTROPY-40-001. | | | -| UI-EXC-25-001 | TODO | | SPRINT_209_ui_i | UI Guild, Governance Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Exception Center (list + kanban) with filters, sorting, workflow transitions, and audit views. | | | -| UI-EXC-25-002 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement exception creation wizard with scope preview, justification templates, timebox guardrails. Dependencies: UI-EXC-25-001. | | | -| UI-EXC-25-003 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add inline exception drafting/proposing from Vulnerability Explorer and Graph detail panels with live simulation. Dependencies: UI-EXC-25-002. | | | -| UI-EXC-25-004 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Surface exception badges, countdown timers, and explain integration across Graph/Vuln Explorer and policy views. Dependencies: UI-EXC-25-003. | | | -| UI-EXC-25-005 | TODO | | SPRINT_209_ui_i | UI Guild, Accessibility Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add keyboard shortcuts (`x`,`a`,`r`) and ensure screen-reader messaging for approvals/revocations. Dependencies: UI-EXC-25-004. | | | -| UI-GRAPH-21-001 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Align Graph Explorer auth configuration with new `graph:*` scopes; consume scope identifiers from shared `StellaOpsScopes` exports (via generated SDK/config) instead of hard-coded strings. | | | -| UI-GRAPH-24-001 | TODO | | SPRINT_209_ui_i | UI Guild, SBOM Service Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Graph Explorer canvas with layered/radial layouts, virtualization, zoom/pan, and scope toggles; initial render <1.5s for sample asset. Dependencies: UI-GRAPH-21-001. | | | -| UI-GRAPH-24-002 | TODO | | SPRINT_209_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement overlays (Policy, Evidence, License, Exposure), simulation toggle, path view, and SBOM diff/time-travel with accessible tooltips/AOC indicators. Dependencies: UI-GRAPH-24-001. | | | -| UI-GRAPH-24-003 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Deliver filters/search panel with facets, saved views, permalinks, and share modal. Dependencies: UI-GRAPH-24-002. | | | -| UI-GRAPH-24-004 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add side panels (Details, What-if, History) with upgrade simulation integration and SBOM diff viewer. Dependencies: UI-GRAPH-24-003. | | | -| UI-GRAPH-24-006 | TODO | | SPRINT_209_ui_i | UI Guild, Accessibility Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Ensure accessibility (keyboard nav, screen reader labels, contrast), add hotkeys (`f`,`e`,`.`), and analytics instrumentation. Dependencies: UI-GRAPH-24-004. | | | -| UI-LNM-22-001 | TODO | | SPRINT_209_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Evidence panel showing policy decision with advisory observations/linksets side-by-side, conflict badges, AOC chain, and raw doc download links. Docs `DOCS-LNM-22-005` waiting on delivered UI for screenshots + flows. | | | +| UI-ENTROPY-40-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Visualise entropy analysis per image (layer donut, file heatmaps, “Why risky?” chips) in Vulnerability Explorer and scan details, including opaque byte ratios and detector hints (see `docs/modules/scanner/entropy.md`). | | | +| UI-ENTROPY-40-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add policy banners/tooltips explaining entropy penalties (block/warn thresholds, mitigation steps) and link to raw `entropy.report.json` evidence downloads (`docs/modules/scanner/entropy.md`). Dependencies: UI-ENTROPY-40-001. | | | +| UI-EXC-25-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Governance Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Exception Center (list + kanban) with filters, sorting, workflow transitions, and audit views. | | | +| UI-EXC-25-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement exception creation wizard with scope preview, justification templates, timebox guardrails. Dependencies: UI-EXC-25-001. | | | +| UI-EXC-25-003 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add inline exception drafting/proposing from Vulnerability Explorer and Graph detail panels with live simulation. Dependencies: UI-EXC-25-002. | | | +| UI-EXC-25-004 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Surface exception badges, countdown timers, and explain integration across Graph/Vuln Explorer and policy views. Dependencies: UI-EXC-25-003. | | | +| UI-EXC-25-005 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Accessibility Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add keyboard shortcuts (`x`,`a`,`r`) and ensure screen-reader messaging for approvals/revocations. Dependencies: UI-EXC-25-004. | | | +| UI-GRAPH-21-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Align Graph Explorer auth configuration with new `graph:*` scopes; consume scope identifiers from shared `StellaOpsScopes` exports (via generated SDK/config) instead of hard-coded strings. | | | +| UI-GRAPH-24-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, SBOM Service Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Graph Explorer canvas with layered/radial layouts, virtualization, zoom/pan, and scope toggles; initial render <1.5s for sample asset. Dependencies: UI-GRAPH-21-001. | | | +| UI-GRAPH-24-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement overlays (Policy, Evidence, License, Exposure), simulation toggle, path view, and SBOM diff/time-travel with accessible tooltips/AOC indicators. Dependencies: UI-GRAPH-24-001. | | | +| UI-GRAPH-24-003 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Deliver filters/search panel with facets, saved views, permalinks, and share modal. Dependencies: UI-GRAPH-24-002. | | | +| UI-GRAPH-24-004 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add side panels (Details, What-if, History) with upgrade simulation integration and SBOM diff viewer. Dependencies: UI-GRAPH-24-003. | | | +| UI-GRAPH-24-006 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Accessibility Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Ensure accessibility (keyboard nav, screen reader labels, contrast), add hotkeys (`f`,`e`,`.`), and analytics instrumentation. Dependencies: UI-GRAPH-24-004. | | | +| UI-LNM-22-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Evidence panel showing policy decision with advisory observations/linksets side-by-side, conflict badges, AOC chain, and raw doc download links. Docs `DOCS-LNM-22-005` waiting on delivered UI for screenshots + flows. | | | | UI-LNM-22-002 | TODO | | SPRINT_210_ui_ii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement filters (source, severity bucket, conflict-only, CVSS vector presence) and pagination/lazy loading for large linksets. Docs depend on finalized filtering UX. Dependencies: UI-LNM-22-001. | | | | UI-LNM-22-003 | TODO | | SPRINT_210_ui_ii | UI Guild, Excititor Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add VEX tab with status/justification summaries, conflict indicators, and export actions. Required for `DOCS-LNM-22-005` coverage of VEX evidence tab. Dependencies: UI-LNM-22-002. | | | | UI-LNM-22-004 | TODO | | SPRINT_210_ui_ii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Provide permalink + copy-to-clipboard for selected component/linkset/policy combination; ensure high-contrast theme support. Dependencies: UI-LNM-22-003. | | | @@ -2019,8 +2019,8 @@ | UI-POLICY-23-005 | TODO | | SPRINT_210_ui_ii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Integrate simulator panel (SBOM/component/advisory selection), run diff vs active policy, show explain tree and overlays. Dependencies: UI-POLICY-23-004. | | | | UI-POLICY-23-006 | TODO | | SPRINT_210_ui_ii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement explain view linking to evidence overlays and exceptions; provide export to JSON/PDF. Dependencies: UI-POLICY-23-005. | | | | UI-POLICY-27-001 | TODO | | SPRINT_211_ui_iii | UI Guild, Product Ops (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Update Console policy workspace RBAC guards, scope requests, and user messaging to reflect the new Policy Studio roles/scopes (`policy:author/review/approve/operate/audit/simulate`), including Cypress auth stubs and help text. Dependencies: UI-POLICY-23-006. | | | -| UI-POLICY-DET-01 | TODO | | SPRINT_209_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Wire policy gate indicators + remediation hints into Release/Policy flows, blocking publishes when determinism checks fail; coordinate with Policy Engine schema updates. Dependencies: UI-SBOM-DET-01. | | | -| UI-SBOM-DET-01 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add a “Determinism” badge plus drill-down that surfaces fragment hashes, `_composition.json`, and Merkle root consistency when viewing scan details (per `docs/modules/scanner/deterministic-sbom-compose.md`). | | | +| UI-POLICY-DET-01 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Wire policy gate indicators + remediation hints into Release/Policy flows, blocking publishes when determinism checks fail; coordinate with Policy Engine schema updates. Dependencies: UI-SBOM-DET-01. | | | +| UI-SBOM-DET-01 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add a “Determinism” badge plus drill-down that surfaces fragment hashes, `_composition.json`, and Merkle root consistency when viewing scan details (per `docs/modules/scanner/deterministic-sbom-compose.md`). | | | | UI-SIG-26-001 | TODO | | SPRINT_211_ui_iii | UI Guild, Signals Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add reachability columns/badges to Vulnerability Explorer with filters and tooltips. | | | | UI-SIG-26-002 | TODO | | SPRINT_211_ui_iii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Enhance “Why” drawer with call path visualization, reachability timeline, and evidence list. Dependencies: UI-SIG-26-001. | | | | UI-SIG-26-003 | TODO | | SPRINT_211_ui_iii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add reachability overlay halos/time slider to SBOM Graph along with state legend. Dependencies: UI-SIG-26-002. | | | @@ -2036,7 +2036,7 @@ | VAL-05 | TODO | | SPRINT_136_scanner_surface | Docs Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation | | SURFACE-VAL-02 | | | VERIFY-186-007 | TODO | | SPRINT_186_record_deterministic_execution | Authority Guild, Provenance Guild (`src/Authority/StellaOps.Authority`, `src/Provenance/StellaOps.Provenance.Attestation`) | `src/Authority/StellaOps.Authority`, `src/Provenance/StellaOps.Provenance.Attestation` | | | | | VEX-006 | TODO | | SPRINT_401_reachability_evidence_chain | Policy, Excititor, UI, CLI & Notify Guilds (`docs/modules/excititor/architecture.md`, `src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`, `docs/09_API_CLI_REFERENCE.md`) | `docs/modules/excititor/architecture.md`, `src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`, `docs/09_API_CLI_REFERENCE.md` | | | | -| VEX-30-001 | DOING | 2025-11-08 | SPRINT_212_web_i | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | | | | +| VEX-30-001 | DOING | 2025-11-08 | SPRINT_0212_0001_0001_web_i | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | | | | | VEX-30-002 | TODO | | SPRINT_205_cli_v | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | | VEX-30-003 | TODO | | SPRINT_205_cli_v | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | | VEX-30-004 | TODO | | SPRINT_205_cli_v | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | @@ -2072,7 +2072,7 @@ | VEXLENS-EXPORT-35-001 | TODO | | SPRINT_129_policy_reasoning | VEX Lens Guild | src/VexLens/StellaOps.VexLens | Provide consensus snapshot API delivering deterministic JSONL (state, confidence, provenance) for exporter mirror bundles | — | PLVL0103 | | VEXLENS-ORCH-33-001 | TODO | | SPRINT_129_policy_reasoning | VEX Lens Guild | src/VexLens/StellaOps.VexLens | Register `consensus_compute` job type with orchestrator, integrate worker SDK, and expose job planning hooks for consensus batches | — | PLVL0103 | | VEXLENS-ORCH-34-001 | TODO | | SPRINT_129_policy_reasoning | VEX Lens Guild | src/VexLens/StellaOps.VexLens | Emit consensus completion events into orchestrator run ledger and provenance chain, including confidence metadata | VEXLENS-ORCH-33-001 | PLVL0103 | -| VULN-29-001 | DOING | 2025-11-08 | SPRINT_212_web_i | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | | | | +| VULN-29-001 | DOING | 2025-11-08 | SPRINT_0212_0001_0001_web_i | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | | | | | VULN-29-002 | TODO | | SPRINT_123_excititor_v | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService) | src/Excititor/StellaOps.Excititor.WebService | | | | | VULN-29-003 | TODO | | SPRINT_205_cli_v | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | | VULN-29-004 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, Observability Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | @@ -2100,28 +2100,28 @@ | VULNERABILITY-EXPLORER-ENG-0001 | TODO | | SPRINT_334_docs_modules_vuln_explorer | Module Team (docs/modules/vuln-explorer) | docs/modules/vuln-explorer | Keep sprint alignment notes in sync with Vuln Explorer sprints. | | | | VULNERABILITY-EXPLORER-OPS-0001 | TODO | | SPRINT_334_docs_modules_vuln_explorer | Ops Guild (docs/modules/vuln-explorer) | docs/modules/vuln-explorer | Review runbooks/observability assets after next demo. | | | | WEB-20-002 | TODO | | SPRINT_0155_0001_0001_scheduler_i | Scheduler WebService Guild (src/Scheduler/StellaOps.Scheduler.WebService) | src/Scheduler/StellaOps.Scheduler.WebService | | | | -| WEB-AIAI-31-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Route `/advisory/ai/*` endpoints through gateway with RBAC/ABAC, rate limits, and telemetry headers. | | | -| WEB-AIAI-31-002 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide batching job handlers and streaming responses for CLI automation with retry/backoff. Dependencies: WEB-AIAI-31-001. | | | -| WEB-AIAI-31-003 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild, Observability Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Emit metrics/logs (latency, guardrail blocks, validation failures) and forward anonymized prompt hashes to analytics. Dependencies: WEB-AIAI-31-002. | | | +| WEB-AIAI-31-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Route `/advisory/ai/*` endpoints through gateway with RBAC/ABAC, rate limits, and telemetry headers. | | | +| WEB-AIAI-31-002 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide batching job handlers and streaming responses for CLI automation with retry/backoff. Dependencies: WEB-AIAI-31-001. | | | +| WEB-AIAI-31-003 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild, Observability Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Emit metrics/logs (latency, guardrail blocks, validation failures) and forward anonymized prompt hashes to analytics. Dependencies: WEB-AIAI-31-002. | | | | WEB-AIRGAP-56-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AIRGAP-56-002 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AIRGAP-57-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Policy Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AIRGAP-58-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Importer Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | -| WEB-AOC-19-002 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | | +| WEB-AOC-19-002 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | | | WEB-AOC-19-003 | TODO | | SPRINT_116_concelier_v | QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-004 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-005 | TODO | 2025-11-08 | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-006 | TODO | 2025-11-08 | SPRINT_116_concelier_v | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-007 | TODO | 2025-11-08 | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | -| WEB-CONSOLE-23-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild, Product Analytics Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide consolidated `/console/dashboard` and `/console/filters` APIs returning tenant-scoped aggregates (findings by severity, VEX override counts, advisory deltas, run health, policy change log). Enforce AOC labelling, deterministic ordering, and cursor-based pagination for drill-down hints. | | | -| WEB-CONSOLE-23-002 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild, Scheduler Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Expose `/console/status` polling endpoint and `/console/runs/{id}/stream` SSE/WebSocket proxy with heartbeat/backoff, queue lag metrics, and auth scope enforcement. Surface request IDs + retry headers. Dependencies: WEB-CONSOLE-23-001. | | | -| WEB-CONSOLE-23-003 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild, Policy Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Add `/console/exports` POST/GET routes coordinating evidence bundle creation, streaming CSV/JSON exports, checksum manifest retrieval, and signed attestation references. Ensure requests honor tenant + policy scopes and expose job tracking metadata. Dependencies: WEB-CONSOLE-23-002. | | | -| WEB-CONSOLE-23-004 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Implement `/console/search` endpoint accepting CVE/GHSA/PURL/SBOM identifiers, performing fan-out queries with caching, ranking, and deterministic tie-breaking. Return typed results for Console navigation; respect result caps and latency SLOs. Dependencies: WEB-CONSOLE-23-003. | | | -| WEB-CONSOLE-23-005 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild, DevOps Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Serve `/console/downloads` JSON manifest (images, charts, offline bundles) sourced from signed registry metadata; include integrity hashes, release notes links, and offline instructions. Provide caching headers and documentation. Dependencies: WEB-CONSOLE-23-004. | | | -| WEB-CONTAINERS-44-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Expose `/welcome` state, config discovery endpoint (safe values), and `QUICKSTART_MODE` handling for Console banner; add `/health/liveness`, `/health/readiness`, `/version` if missing. | | | -| WEB-CONTAINERS-45-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ensure readiness endpoints reflect DB/queue readiness, add feature flag toggles via config map, and document NetworkPolicy ports. Dependencies: WEB-CONTAINERS-44-001. | | | -| WEB-CONTAINERS-46-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide offline-friendly asset serving (no CDN), allow overriding object store endpoints via env, and document fallback behavior. Dependencies: WEB-CONTAINERS-45-001. | | | -| WEB-EXC-25-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Implement `/exceptions` API (create, propose, approve, revoke, list, history) with validation, pagination, and audit logging. | | | +| WEB-CONSOLE-23-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild, Product Analytics Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide consolidated `/console/dashboard` and `/console/filters` APIs returning tenant-scoped aggregates (findings by severity, VEX override counts, advisory deltas, run health, policy change log). Enforce AOC labelling, deterministic ordering, and cursor-based pagination for drill-down hints. | | | +| WEB-CONSOLE-23-002 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild, Scheduler Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Expose `/console/status` polling endpoint and `/console/runs/{id}/stream` SSE/WebSocket proxy with heartbeat/backoff, queue lag metrics, and auth scope enforcement. Surface request IDs + retry headers. Dependencies: WEB-CONSOLE-23-001. | | | +| WEB-CONSOLE-23-003 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild, Policy Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Add `/console/exports` POST/GET routes coordinating evidence bundle creation, streaming CSV/JSON exports, checksum manifest retrieval, and signed attestation references. Ensure requests honor tenant + policy scopes and expose job tracking metadata. Dependencies: WEB-CONSOLE-23-002. | | | +| WEB-CONSOLE-23-004 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Implement `/console/search` endpoint accepting CVE/GHSA/PURL/SBOM identifiers, performing fan-out queries with caching, ranking, and deterministic tie-breaking. Return typed results for Console navigation; respect result caps and latency SLOs. Dependencies: WEB-CONSOLE-23-003. | | | +| WEB-CONSOLE-23-005 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild, DevOps Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Serve `/console/downloads` JSON manifest (images, charts, offline bundles) sourced from signed registry metadata; include integrity hashes, release notes links, and offline instructions. Provide caching headers and documentation. Dependencies: WEB-CONSOLE-23-004. | | | +| WEB-CONTAINERS-44-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Expose `/welcome` state, config discovery endpoint (safe values), and `QUICKSTART_MODE` handling for Console banner; add `/health/liveness`, `/health/readiness`, `/version` if missing. | | | +| WEB-CONTAINERS-45-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ensure readiness endpoints reflect DB/queue readiness, add feature flag toggles via config map, and document NetworkPolicy ports. Dependencies: WEB-CONTAINERS-44-001. | | | +| WEB-CONTAINERS-46-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide offline-friendly asset serving (no CDN), allow overriding object store endpoints via env, and document fallback behavior. Dependencies: WEB-CONTAINERS-45-001. | | | +| WEB-EXC-25-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Implement `/exceptions` API (create, propose, approve, revoke, list, history) with validation, pagination, and audit logging. | | | | WEB-EXC-25-002 | TODO | | SPRINT_213_web_ii | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Extend `/policy/effective` and `/policy/simulate` responses to include exception metadata and accept overrides for simulations. Dependencies: WEB-EXC-25-001. | | | | WEB-EXC-25-003 | TODO | | SPRINT_213_web_ii | BE-Base Platform Guild, Platform Events Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Publish `exception.*` events, integrate with notification hooks, enforce rate limits. Dependencies: WEB-EXC-25-002. | | | | WEB-EXPORT-35-001 | TODO | | SPRINT_213_web_ii | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Surface Export Center APIs (profiles/runs/download) through gateway with tenant scoping, streaming support, and viewer/operator scope checks. | | | @@ -2289,8 +2289,8 @@ | 62-002 | TODO | | SPRINT_206_devportal | DevPortal Guild | src/DevPortal/StellaOps.DevPortal.Site | 62-001 | 62-001 | DEVL0101 | | 63-001 | TODO | | SPRINT_206_devportal | DevPortal Guild · Platform Guild | src/DevPortal/StellaOps.DevPortal.Site | 62-002 | 62-002 | DEVL0101 | | 63-002 | TODO | | SPRINT_206_devportal | DevPortal Guild · SDK Generator Guild | src/DevPortal/StellaOps.DevPortal.Site | 63-001 | 63-001 | DEVL0101 | -| 63-003 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | APIG0101 outputs | APIG0101 outputs | SDKG0101 | -| 63-004 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | 63-003 | 63-003 | SDKG0101 | +| 63-003 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | APIG0101 outputs | APIG0101 outputs | SDKG0101 | +| 63-004 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | 63-003 | 63-003 | SDKG0101 | | 64-001 | TODO | | SPRINT_206_devportal | DevPortal Guild · Export Center Guild | src/DevPortal/StellaOps.DevPortal.Site | Export profile review | Export profile review | DEVL0101 | | 64-002 | TODO | | SPRINT_160_export_evidence | DevPortal Offline + AirGap Controller Guilds | docs/modules/export-center/devportal-offline.md | Wait for Mirror staffing confirmation (001_PGMI0101) | Wait for Mirror staffing confirmation (001_PGMI0101) | DEVL0102 | | 73-001 | DONE | 2025-11-03 | SPRINT_100_identity_signing | KMS Guild | src/__Libraries/StellaOps.Cryptography.Kms | Staffing + DSSE contract (PGMI0101, ATEL0101) | Staffing + DSSE contract (PGMI0101, ATEL0101) | KMSI0101 | @@ -2435,17 +2435,17 @@ | API-27-008 | TODO | | SPRINT_129_policy_reasoning | Policy Registry Guild | src/Policy/StellaOps.Policy.Registry | Depends on #7 | REGISTRY-API-27-007 | PLAR0101 | | API-27-009 | TODO | | SPRINT_129_policy_reasoning | Policy Registry Guild | src/Policy/StellaOps.Policy.Registry | Depends on #8 | REGISTRY-API-27-008 | PLAR0101 | | API-27-010 | TODO | | SPRINT_129_policy_reasoning | Policy Registry Guild | src/Policy/StellaOps.Policy.Registry | Depends on #9 | REGISTRY-API-27-009 | PLAR0101 | -| API-28-001 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Cartographer schema sign-off | Cartographer schema sign-off | GRAP0101 | -| API-28-002 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #1 | Depends on #1 | GRAP0101 | -| API-28-003 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #2 | Depends on #2 | GRAP0101 | -| API-28-004 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #3 | Depends on #3 | GRAP0101 | -| API-28-005 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #4 | Depends on #4 | GRAP0101 | -| API-28-006 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on GRAP0101 base endpoints | Depends on GRAP0101 base endpoints | GRAP0102 | -| API-28-007 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #1 | Depends on #1 | GRAP0102 | -| API-28-008 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #2 | Depends on #2 | GRAP0102 | -| API-28-009 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #3 | Depends on #3 | GRAP0102 | -| API-28-010 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #4 | Depends on #4 | GRAP0102 | -| API-28-011 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #5 | Depends on #5 | GRAP0102 | +| API-28-001 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Cartographer schema sign-off | Cartographer schema sign-off | GRAP0101 | +| API-28-002 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #1 | Depends on #1 | GRAP0101 | +| API-28-003 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #2 | Depends on #2 | GRAP0101 | +| API-28-004 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #3 | Depends on #3 | GRAP0101 | +| API-28-005 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #4 | Depends on #4 | GRAP0101 | +| API-28-006 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on GRAP0101 base endpoints | Depends on GRAP0101 base endpoints | GRAP0102 | +| API-28-007 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #1 | Depends on #1 | GRAP0102 | +| API-28-008 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #2 | Depends on #2 | GRAP0102 | +| API-28-009 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #3 | Depends on #3 | GRAP0102 | +| API-28-010 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #4 | Depends on #4 | GRAP0102 | +| API-28-011 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Depends on #5 | Depends on #5 | GRAP0102 | | API-29-001 | TODO | | SPRINT_129_policy_reasoning | Vuln Explorer API Guild | src/VulnExplorer/StellaOps.VulnExplorer.Api | Governance schema (APIG0101) | Governance schema (APIG0101) | VUAP0101 | | API-29-002 | TODO | | SPRINT_129_policy_reasoning | Vuln Explorer API Guild | src/VulnExplorer/StellaOps.VulnExplorer.Api | Depends on #1 | VULN-API-29-001 | VUAP0101 | | API-29-003 | TODO | | SPRINT_129_policy_reasoning | Vuln Explorer API Guild | src/VulnExplorer/StellaOps.VulnExplorer.Api | Depends on #2 | VULN-API-29-002 | VUAP0101 | @@ -2515,21 +2515,21 @@ | CLI-42-001 | TODO | | SPRINT_303_docs_tasks_md_iii | Docs Guild (docs) | | — | — | CLCI0101 | | CLI-43-002 | TODO | | SPRINT_504_ops_devops_ii | DevOps Guild, Task Runner Guild (ops/devops) | ops/devops | — | — | CLCI0101 | | CLI-43-003 | TODO | | SPRINT_504_ops_devops_ii | DevOps Guild, DevEx/CLI Guild (ops/devops) | ops/devops | — | — | CLCI0101 | -| CLI-AIAI-31-001 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise summarize` command with JSON/Markdown outputs and citation display. | — | CLCI0101 | -| CLI-AIAI-31-002 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise explain` showing conflict narrative and structured rationale. Dependencies: CLI-AIAI-31-001. | — | CLCI0101 | -| CLI-AIAI-31-003 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise remediate` generating remediation plans with `--strategy` filters and file output. Dependencies: CLI-AIAI-31-002. | — | CLCI0101 | -| CLI-AIAI-31-004 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise batch` for summaries/conflicts/remediation with progress + multi-status responses. Dependencies: CLI-AIAI-31-003. | — | CLCI0102 | +| CLI-AIAI-31-001 | DOING | 2025-11-22 | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise summarize` command with JSON/Markdown outputs and citation display. | — | CLCI0101 | +| CLI-AIAI-31-002 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise explain` showing conflict narrative and structured rationale. Dependencies: CLI-AIAI-31-001. | — | CLCI0101 | +| CLI-AIAI-31-003 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise remediate` generating remediation plans with `--strategy` filters and file output. Dependencies: CLI-AIAI-31-002. | — | CLCI0101 | +| CLI-AIAI-31-004 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella advise batch` for summaries/conflicts/remediation with progress + multi-status responses. Dependencies: CLI-AIAI-31-003. | — | CLCI0102 | | CLI-AIRGAP-56-001 | TODO | | SPRINT_110_ingestion_evidence | Exporter Guild · AirGap Time Guild · CLI Guild | | PROGRAM-STAFF-1001 | PROGRAM-STAFF-1001 | ATMI0102 | -| CLI-AIRGAP-56-002 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Ensure telemetry propagation under sealed mode (no remote exporters) while preserving correlation IDs; add label `AirGapped-Phase-1`. Dependencies: CLI-AIRGAP-56-001. | — | CLCI0102 | -| CLI-AIRGAP-57-001 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Add `stella airgap import` with diff preview, bundle scope selection (`--tenant`, `--global`), audit logging, and progress reporting. Dependencies: CLI-AIRGAP-56-002. | — | CLCI0102 | -| CLI-AIRGAP-57-002 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Provide `stella airgap seal. Dependencies: CLI-AIRGAP-57-001. | — | CLCI0102 | -| CLI-AIRGAP-58-001 | TODO | | SPRINT_201_cli_i | DevEx/CLI Guild, Evidence Locker Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella airgap export evidence` helper for portable evidence packages, including checksum manifest and verification. Dependencies: CLI-AIRGAP-57-002. | — | CLCI0102 | -| CLI-ATTEST-73-001 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest sign` (payload selection, subject digest, key reference, output format) using official SDK transport. | — | CLCI0102 | -| CLI-ATTEST-73-002 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest verify` with policy selection, explainability output, and JSON/table formatting. Dependencies: CLI-ATTEST-73-001. | — | CLCI0102 | -| CLI-ATTEST-74-001 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest list` with filters (subject, type, issuer, scope) and pagination. Dependencies: CLI-ATTEST-73-002. | — | CLCI0102 | -| CLI-ATTEST-74-002 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest fetch` to download envelopes and payloads to disk. Dependencies: CLI-ATTEST-74-001. | — | CLCI0102 | -| CLI-ATTEST-75-001 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild, KMS Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest key create. Dependencies: CLI-ATTEST-74-002. | — | CLCI0102 | -| CLI-ATTEST-75-002 | TODO | | SPRINT_201_cli_i | CLI Attestor Guild | src/Cli/StellaOps.Cli | Add support for building/verifying attestation bundles in CLI. Dependencies: CLI-ATTEST-75-001. | Wait for ATEL0102 outputs | CLCI0109 | +| CLI-AIRGAP-56-002 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Ensure telemetry propagation under sealed mode (no remote exporters) while preserving correlation IDs; add label `AirGapped-Phase-1`. Dependencies: CLI-AIRGAP-56-001. | — | CLCI0102 | +| CLI-AIRGAP-57-001 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Add `stella airgap import` with diff preview, bundle scope selection (`--tenant`, `--global`), audit logging, and progress reporting. Dependencies: CLI-AIRGAP-56-002. | — | CLCI0102 | +| CLI-AIRGAP-57-002 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Provide `stella airgap seal. Dependencies: CLI-AIRGAP-57-001. | — | CLCI0102 | +| CLI-AIRGAP-58-001 | TODO | | SPRINT_0201_0001_0001_cli_i | DevEx/CLI Guild, Evidence Locker Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella airgap export evidence` helper for portable evidence packages, including checksum manifest and verification. Dependencies: CLI-AIRGAP-57-002. | — | CLCI0102 | +| CLI-ATTEST-73-001 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest sign` (payload selection, subject digest, key reference, output format) using official SDK transport. | — | CLCI0102 | +| CLI-ATTEST-73-002 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest verify` with policy selection, explainability output, and JSON/table formatting. Dependencies: CLI-ATTEST-73-001. | — | CLCI0102 | +| CLI-ATTEST-74-001 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest list` with filters (subject, type, issuer, scope) and pagination. Dependencies: CLI-ATTEST-73-002. | — | CLCI0102 | +| CLI-ATTEST-74-002 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest fetch` to download envelopes and payloads to disk. Dependencies: CLI-ATTEST-74-001. | — | CLCI0102 | +| CLI-ATTEST-75-001 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild, KMS Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement `stella attest key create. Dependencies: CLI-ATTEST-74-002. | — | CLCI0102 | +| CLI-ATTEST-75-002 | TODO | | SPRINT_0201_0001_0001_cli_i | CLI Attestor Guild | src/Cli/StellaOps.Cli | Add support for building/verifying attestation bundles in CLI. Dependencies: CLI-ATTEST-75-001. | Wait for ATEL0102 outputs | CLCI0109 | | CLI-CORE-41-001 | TODO | | SPRINT_202_cli_ii | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Implement CLI core features: config precedence, profiles/contexts, auth flows, output renderer (json/yaml/table), error mapping, global flags, telemetry opt-in. | — | CLCI0103 | | CLI-DET-01 | TODO | | SPRINT_301_docs_tasks_md_i | Docs Guild · DevEx/CLI Guild | | CLI-SBOM-60-001; CLI-SBOM-60-002 | CLI-SBOM-60-001; CLI-SBOM-60-002 | CLCI0103 | | CLI-DETER-70-003 | TODO | | SPRINT_202_cli_ii | DevEx/CLI Guild, Scanner Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | Provide `stella detscore run` that executes the determinism harness locally (fixed clock, seeded RNG, canonical hashes) and writes `determinism.json`, supporting CI/non-zero threshold exit codes (`docs/modules/scanner/determinism-score.md`). | — | CLCI0103 | @@ -2692,15 +2692,15 @@ | CONSOLE-23-001..003 | TODO | | SPRINT_110_ingestion_evidence | Console Guild | src/Console/StellaOps.Console | Depends on #1 | CONCELIER-GRAPH-21-001; CONCELIER-GRAPH-21-002 | CCSL0101 | | CONSOLE-23-002 | TODO | | SPRINT_112_concelier_i | Console Guild | src/Console/StellaOps.Console | Needs LNM graph (CCGH0101) | Needs LNM graph (CCGH0101) | CCSL0101 | | CONSOLE-23-003 | TODO | | SPRINT_112_concelier_i | Console Guild | src/Console/StellaOps.Console | Depends on #3 | Depends on #3 | CCSL0101 | -| CONSOLE-23-004 | TODO | | SPRINT_212_web_i | Console Guild | src/Web/StellaOps.Web | Requires CCPR0101 verdicts | Requires CCPR0101 verdicts | CCSL0101 | -| CONSOLE-23-005 | TODO | | SPRINT_212_web_i | Console Guild | src/Web/StellaOps.Web | Depends on #5 | Depends on #5 | CCSL0101 | +| CONSOLE-23-004 | TODO | | SPRINT_0212_0001_0001_web_i | Console Guild | src/Web/StellaOps.Web | Requires CCPR0101 verdicts | Requires CCPR0101 verdicts | CCSL0101 | +| CONSOLE-23-005 | TODO | | SPRINT_0212_0001_0001_web_i | Console Guild | src/Web/StellaOps.Web | Depends on #5 | Depends on #5 | CCSL0101 | | CONSOLE-OBS-52-001 | TODO | | SPRINT_303_docs_tasks_md_iii | Console Ops Guild | docs/modules/ui | Needs TLTY0101 metrics | Needs TLTY0101 metrics | CCSL0101 | | CONSOLE-OBS-52-002 | TODO | | SPRINT_303_docs_tasks_md_iii | Console Ops Guild | docs/modules/ui | Depends on #7 | Depends on #7 | CCSL0101 | -| CONSOLE-VEX-30-001 | TODO | 2025-11-08 | SPRINT_212_web_i | Console Guild · VEX Lens Guild | src/Web/StellaOps.Web | Provide `/console/vex/*` APIs streaming VEX statements, justification summaries, and advisory links with SSE refresh hooks. Dependencies: WEB-CONSOLE-23-001, EXCITITOR-CONSOLE-23-001. | Needs VEX Lens spec (PLVL0103) | CCSL0101 | -| CONSOLE-VULN-29-001 | TODO | 2025-11-08 | SPRINT_212_web_i | Console Guild | src/Web/StellaOps.Web | Build `/console/vuln/*` APIs and filters surfacing tenant-scoped findings with policy/VEX badges so Docs/UI teams can document workflows. Dependencies: WEB-CONSOLE-23-001, CONCELIER-GRAPH-21-001. | Depends on CCWO0101 | CCSL0101 | -| CONTAINERS-44-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Wait for DVCP0101 compose template | Wait for DVCP0101 compose template | COWB0101 | -| CONTAINERS-45-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Depends on #1 | Depends on #1 | COWB0101 | -| CONTAINERS-46-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Needs RBRE0101 hashes | Needs RBRE0101 hashes | COWB0101 | +| CONSOLE-VEX-30-001 | TODO | 2025-11-08 | SPRINT_0212_0001_0001_web_i | Console Guild · VEX Lens Guild | src/Web/StellaOps.Web | Provide `/console/vex/*` APIs streaming VEX statements, justification summaries, and advisory links with SSE refresh hooks. Dependencies: WEB-CONSOLE-23-001, EXCITITOR-CONSOLE-23-001. | Needs VEX Lens spec (PLVL0103) | CCSL0101 | +| CONSOLE-VULN-29-001 | TODO | 2025-11-08 | SPRINT_0212_0001_0001_web_i | Console Guild | src/Web/StellaOps.Web | Build `/console/vuln/*` APIs and filters surfacing tenant-scoped findings with policy/VEX badges so Docs/UI teams can document workflows. Dependencies: WEB-CONSOLE-23-001, CONCELIER-GRAPH-21-001. | Depends on CCWO0101 | CCSL0101 | +| CONTAINERS-44-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Wait for DVCP0101 compose template | Wait for DVCP0101 compose template | COWB0101 | +| CONTAINERS-45-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Depends on #1 | Depends on #1 | COWB0101 | +| CONTAINERS-46-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild | src/Web/StellaOps.Web | Needs RBRE0101 hashes | Needs RBRE0101 hashes | COWB0101 | | CONTRIB-62-001 | TODO | | SPRINT_303_docs_tasks_md_iii | Docs Guild · API Governance Guild | docs/api | Wait for CCWO0101 spec finalization | Wait for CCWO0101 spec finalization | APID0101 | | CORE-185-001 | TODO | | SPRINT_185_shared_replay_primitives | Platform Guild | `src/__Libraries/StellaOps.Replay.Core` | Wait for SGSI0101 feed | Wait for SGSI0101 feed | RLRC0101 | | CORE-185-002 | TODO | | SPRINT_185_shared_replay_primitives | Platform Guild | src/__Libraries/StellaOps.Replay.Core | Depends on #1 | Depends on #1 | RLRC0101 | @@ -3134,8 +3134,8 @@ | ENGINE-OPS-0001 | TODO | | SPRINT_325_docs_modules_policy | Ops Guild (docs/modules/policy) | docs/modules/policy | Operations runbook (deploy/rollback) pointer. | — | DOPE0107 | | ENTROPY-186-011 | TODO | | SPRINT_186_record_deterministic_execution | Scanner Guild · Provenance Guild | `src/Scanner/StellaOps.Scanner.Worker`, `src/Scanner/__Libraries` | SCANNER-ENTRYTRACE-18-508 | SCANNER-ENTRYTRACE-18-508 | SCDE0101 | | ENTROPY-186-012 | TODO | | SPRINT_186_record_deterministic_execution | Scanner Guild · Provenance Guild | `src/Scanner/StellaOps.Scanner.WebService`, `docs/replay/DETERMINISTIC_REPLAY.md` | ENTROPY-186-011 | ENTROPY-186-011 | SCDE0102 | -| ENTROPY-40-001 | TODO | | SPRINT_209_ui_i | UI Guild | src/UI/StellaOps.UI | ENTROPY-186-011 | ENTROPY-186-011 | UIDO0101 | -| ENTROPY-40-002 | TODO | | SPRINT_209_ui_i | UI Guild · Policy Guild | src/UI/StellaOps.UI | ENTROPY-40-001 & ENTROPY-186-012 | ENTROPY-40-001 | UIDO0101 | +| ENTROPY-40-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild | src/UI/StellaOps.UI | ENTROPY-186-011 | ENTROPY-186-011 | UIDO0101 | +| ENTROPY-40-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild · Policy Guild | src/UI/StellaOps.UI | ENTROPY-40-001 & ENTROPY-186-012 | ENTROPY-40-001 | UIDO0101 | | ENTROPY-70-004 | TODO | | SPRINT_304_docs_tasks_md_iv | Docs Guild · Scanner Guild | docs/modules/scanner/determinism.md | ENTROPY-186-011/012 | ENTROPY-186-011/012 | DOSC0102 | | ENTRYTRACE-18-502 | TODO | | SPRINT_135_scanner_surface | EntryTrace Guild · Scanner Surface Guild | src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace | SCANNER-ENTRYTRACE-18-508 | SCANNER-ENTRYTRACE-18-508 | SCET0101 | | ENTRYTRACE-18-503 | TODO | | SPRINT_135_scanner_surface | EntryTrace Guild · Scanner Surface Guild | src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace | ENTRYTRACE-18-502 | ENTRYTRACE-18-502 | SCET0101 | @@ -3153,9 +3153,9 @@ | EVID-REPLAY-187-001 | TODO | | SPRINT_160_export_evidence | Evidence Locker Guild · docs/modules/evidence-locker/architecture.md | docs/modules/evidence-locker/architecture.md | Evidence Locker Guild · docs/modules/evidence-locker/architecture.md | EVID-CRYPTO-90-001 | EVEC0101 | | EXC-25-001 | TODO | | SPRINT_202_cli_ii | DevEx/CLI Guild (`src/Cli/StellaOps.Cli`) | src/Cli/StellaOps.Cli | DOOR0102 APIs | DOOR0102 APIs | CLEX0101 | | EXC-25-002 | TODO | | SPRINT_202_cli_ii | DevEx/CLI Guild (`src/Cli/StellaOps.Cli`) | src/Cli/StellaOps.Cli | EXC-25-001 | EXC-25-001 | CLEX0101 | -| EXC-25-003 | TODO | | SPRINT_209_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | DOOR0102 APIs | DOOR0102 APIs | UIEX0101 | -| EXC-25-004 | TODO | | SPRINT_209_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | EXC-25-003 | EXC-25-003 | UIEX0101 | -| EXC-25-005 | TODO | | SPRINT_209_ui_i | UI + Accessibility Guilds (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | EXC-25-003 | EXC-25-003 | UIEX0101 | +| EXC-25-003 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | DOOR0102 APIs | DOOR0102 APIs | UIEX0101 | +| EXC-25-004 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | EXC-25-003 | EXC-25-003 | UIEX0101 | +| EXC-25-005 | TODO | | SPRINT_0209_0001_0001_ui_i | UI + Accessibility Guilds (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | EXC-25-003 | EXC-25-003 | UIEX0101 | | EXC-25-006 | TODO | | SPRINT_303_docs_tasks_md_iii | Docs Guild · DevEx Guild | docs/modules/excititor | CLEX0101 CLI updates | CLEX0101 CLI updates | DOEX0101 | | EXC-25-007 | TODO | | SPRINT_304_docs_tasks_md_iv | Docs Guild · DevOps Guild | docs/modules/excititor | UIEX0101 console outputs | UIEX0101 console outputs | DOEX0101 | | EXCITITOR-AIAI-31-001 | DONE | 2025-11-09 | SPRINT_110_ingestion_evidence | Excititor Web/Core Guilds | | Normalised VEX justification projections shipped. | | EXWK0101 | @@ -3306,27 +3306,27 @@ | GRAPH-21-003 | TODO | 2025-10-27 | SPRINT_213_web_ii | Scanner WebService Guild | src/Web/StellaOps.Web | GRAPH-21-001 | GRAPH-21-001 | GRSC0101 | | GRAPH-21-004 | TODO | 2025-10-27 | SPRINT_213_web_ii | Scanner WebService Guild | src/Web/StellaOps.Web | GRAPH-21-002 | GRAPH-21-002 | GRSC0101 | | GRAPH-21-005 | BLOCKED (2025-10-27) | 2025-10-27 | SPRINT_120_excititor_ii | Excititor Storage Guild | src/Excititor/__Libraries/StellaOps.Excititor.Storage.Mongo | GRAPH-21-002 | GRAPH-21-002 | GRSC0101 | -| GRAPH-24-001 | TODO | | SPRINT_209_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | GRSC0101 outputs | GRSC0101 outputs | GRUI0101 | -| GRAPH-24-002 | TODO | | SPRINT_209_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-001 | GRAPH-24-001 | GRUI0101 | -| GRAPH-24-003 | TODO | | SPRINT_209_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-001 | GRAPH-24-001 | GRUI0101 | -| GRAPH-24-004 | TODO | | SPRINT_209_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-002 | GRAPH-24-002 | GRUI0101 | +| GRAPH-24-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (`src/UI/StellaOps.UI`) | src/UI/StellaOps.UI | GRSC0101 outputs | GRSC0101 outputs | GRUI0101 | +| GRAPH-24-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-001 | GRAPH-24-001 | GRUI0101 | +| GRAPH-24-003 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-001 | GRAPH-24-001 | GRUI0101 | +| GRAPH-24-004 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-002 | GRAPH-24-002 | GRUI0101 | | GRAPH-24-005 | TODO | | SPRINT_304_docs_tasks_md_iv | UI Guild | | GRAPH-24-003 | GRAPH-24-003 | GRUI0101 | -| GRAPH-24-006 | TODO | | SPRINT_209_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-004 | GRAPH-24-004 | GRUI0101 | +| GRAPH-24-006 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild | src/UI/StellaOps.UI | GRAPH-24-004 | GRAPH-24-004 | GRUI0101 | | GRAPH-24-007 | TODO | | SPRINT_304_docs_tasks_md_iv | UI Guild | | GRAPH-24-005 | GRAPH-24-005 | GRUI0101 | | GRAPH-24-101 | TODO | | SPRINT_113_concelier_ii | UI Guild | src/Concelier/StellaOps.Concelier.WebService | GRAPH-24-001 | GRAPH-24-001 | GRUI0101 | | GRAPH-24-102 | TODO | | SPRINT_120_excititor_ii | UI Guild | src/Excititor/StellaOps.Excititor.WebService | GRAPH-24-101 | GRAPH-24-101 | GRUI0101 | | GRAPH-28-102 | TODO | | SPRINT_113_concelier_ii | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | GRAPI0101 | -| GRAPH-API-28-001 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Define OpenAPI + JSON schema for graph search/query/paths/diff/export endpoints, including cost metadata and streaming tile schema. | — | ORGR0101 | -| GRAPH-API-28-002 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/search` with multi-type index lookup, prefix/exact match, RBAC enforcement, and result ranking + caching. Dependencies: GRAPH-API-28-001. | — | ORGR0101 | -| GRAPH-API-28-003 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Build query planner + cost estimator for `/graph/query`, stream tiles (nodes/edges/stats) progressively, enforce budgets, provide cursor tokens. Dependencies: GRAPH-API-28-002. | — | ORGR0101 | -| GRAPH-API-28-004 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/paths` with depth ≤6, constraint filters, heuristic shortest path search, and optional policy overlay rendering. Dependencies: GRAPH-API-28-003. | — | ORGR0101 | -| GRAPH-API-28-005 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/diff` streaming added/removed/changed nodes/edges between SBOM snapshots; include overlay deltas and policy/VEX/advisory metadata. Dependencies: GRAPH-API-28-004. | — | ORGR0101 | -| GRAPH-API-28-006 | TODO | | SPRINT_207_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Consume Policy Engine overlay contract (`POLICY-ENGINE-30-001..003`) and surface advisory/VEX/policy overlays with caching, partial materialization, and explain trace sampling for focused nodes. Dependencies: GRAPH-API-28-005. | — | ORGR0101 | -| GRAPH-API-28-007 | TODO | | SPRINT_207_graph | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | src/Graph/StellaOps.Graph.Api | Implement exports (`graphml`, `csv`, `ndjson`, `png`, `svg`) with async job management, checksum manifests, and streaming downloads. Dependencies: GRAPH-API-28-006. | ORGR0101 outputs | GRAPI0101 | -| GRAPH-API-28-008 | TODO | | SPRINT_207_graph | Graph API + Authority Guilds | src/Graph/StellaOps.Graph.Api | Integrate RBAC scopes (`graph:read`, `graph:query`, `graph:export`), tenant headers, audit logging, and rate limiting. Dependencies: GRAPH-API-28-007. | GRAPH-API-28-007 | GRAPI0101 | -| GRAPH-API-28-009 | TODO | | SPRINT_207_graph | Graph API + Observability Guilds | src/Graph/StellaOps.Graph.Api | Instrument metrics (`graph_tile_latency_seconds`, `graph_query_budget_denied_total`, `graph_overlay_cache_hit_ratio`), structured logs, and traces per query stage; publish dashboards. Dependencies: GRAPH-API-28-008. | GRAPH-API-28-007 | GRAPI0101 | -| GRAPH-API-28-010 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Build unit/integration/load tests with synthetic datasets (500k nodes/2M edges), fuzz query validation, verify determinism across runs. Dependencies: GRAPH-API-28-009. | GRAPH-API-28-008 | GRAPI0101 | -| GRAPH-API-28-011 | TODO | | SPRINT_207_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Provide deployment manifests, offline kit support, API gateway integration docs, and smoke tests. Dependencies: GRAPH-API-28-010. | GRAPH-API-28-009 | GRAPI0101 | +| GRAPH-API-28-001 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Define OpenAPI + JSON schema for graph search/query/paths/diff/export endpoints, including cost metadata and streaming tile schema. | — | ORGR0101 | +| GRAPH-API-28-002 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/search` with multi-type index lookup, prefix/exact match, RBAC enforcement, and result ranking + caching. Dependencies: GRAPH-API-28-001. | — | ORGR0101 | +| GRAPH-API-28-003 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Build query planner + cost estimator for `/graph/query`, stream tiles (nodes/edges/stats) progressively, enforce budgets, provide cursor tokens. Dependencies: GRAPH-API-28-002. | — | ORGR0101 | +| GRAPH-API-28-004 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/paths` with depth ≤6, constraint filters, heuristic shortest path search, and optional policy overlay rendering. Dependencies: GRAPH-API-28-003. | — | ORGR0101 | +| GRAPH-API-28-005 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Implement `/graph/diff` streaming added/removed/changed nodes/edges between SBOM snapshots; include overlay deltas and policy/VEX/advisory metadata. Dependencies: GRAPH-API-28-004. | — | ORGR0101 | +| GRAPH-API-28-006 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (src/Graph/StellaOps.Graph.Api) | src/Graph/StellaOps.Graph.Api | Consume Policy Engine overlay contract (`POLICY-ENGINE-30-001..003`) and surface advisory/VEX/policy overlays with caching, partial materialization, and explain trace sampling for focused nodes. Dependencies: GRAPH-API-28-005. | — | ORGR0101 | +| GRAPH-API-28-007 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild (`src/Graph/StellaOps.Graph.Api`) | src/Graph/StellaOps.Graph.Api | Implement exports (`graphml`, `csv`, `ndjson`, `png`, `svg`) with async job management, checksum manifests, and streaming downloads. Dependencies: GRAPH-API-28-006. | ORGR0101 outputs | GRAPI0101 | +| GRAPH-API-28-008 | TODO | | SPRINT_0207_0001_0001_graph | Graph API + Authority Guilds | src/Graph/StellaOps.Graph.Api | Integrate RBAC scopes (`graph:read`, `graph:query`, `graph:export`), tenant headers, audit logging, and rate limiting. Dependencies: GRAPH-API-28-007. | GRAPH-API-28-007 | GRAPI0101 | +| GRAPH-API-28-009 | TODO | | SPRINT_0207_0001_0001_graph | Graph API + Observability Guilds | src/Graph/StellaOps.Graph.Api | Instrument metrics (`graph_tile_latency_seconds`, `graph_query_budget_denied_total`, `graph_overlay_cache_hit_ratio`), structured logs, and traces per query stage; publish dashboards. Dependencies: GRAPH-API-28-008. | GRAPH-API-28-007 | GRAPI0101 | +| GRAPH-API-28-010 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Build unit/integration/load tests with synthetic datasets (500k nodes/2M edges), fuzz query validation, verify determinism across runs. Dependencies: GRAPH-API-28-009. | GRAPH-API-28-008 | GRAPI0101 | +| GRAPH-API-28-011 | TODO | | SPRINT_0207_0001_0001_graph | Graph API Guild | src/Graph/StellaOps.Graph.Api | Provide deployment manifests, offline kit support, API gateway integration docs, and smoke tests. Dependencies: GRAPH-API-28-010. | GRAPH-API-28-009 | GRAPI0101 | | GRAPH-CAS-401-001 | TODO | | SPRINT_401_reachability_evidence_chain | Scanner Worker Guild | `src/Scanner/StellaOps.Scanner.Worker` | Finalize richgraph schema (`richgraph-v1`), emit canonical SymbolIDs, compute graph hash (BLAKE3), and store CAS manifests under `cas://reachability/graphs/{sha256}`. Update Scanner Worker adapters + fixtures. | Depends on #1 | CASC0101 | | GRAPH-DOCS-0001 | DONE (2025-11-05) | 2025-11-05 | SPRINT_321_docs_modules_graph | Docs Guild | docs/modules/graph | Validate that graph module README/diagrams reflect the latest overlay + snapshot updates. | GRAPI0101 evidence | GRDG0101 | | GRAPH-DOCS-0002 | TODO | 2025-11-05 | SPRINT_321_docs_modules_graph | Docs Guild | docs/modules/graph | Pending DOCS-GRAPH-24-003 to add API/query doc cross-links | GRAPI0101 outputs | GRDG0101 | @@ -3335,7 +3335,7 @@ | GRAPH-INDEX-28-008 | TODO | | SPRINT_0140_0001_0001_runtime_signals | — | | Incremental update/backfill pipeline depends on 28-007 artifacts; retry/backoff plumbing sketched but blocked. | — | ORGR0101 | | GRAPH-INDEX-28-009 | TODO | | SPRINT_0140_0001_0001_runtime_signals | — | | Test/fixture/chaos coverage waits on earlier jobs to exist so determinism checks have data. | — | ORGR0101 | | GRAPH-INDEX-28-010 | TODO | | SPRINT_0140_0001_0001_runtime_signals | — | | Packaging/offline bundles paused until upstream graph jobs are available to embed. | — | ORGR0101 | -| GRAPH-INDEX-28-011 | TODO | 2025-11-04 | SPRINT_207_graph | Graph Index Guild | src/Graph/StellaOps.Graph.Indexer | Wire SBOM ingest runtime to emit graph snapshot artifacts, add DI factory helpers, and document Mongo/snapshot environment guidance. Dependencies: GRAPH-INDEX-28-002..006. | GRSC0101 outputs | GRIX0101 | +| GRAPH-INDEX-28-011 | TODO | 2025-11-04 | SPRINT_0207_0001_0001_graph | Graph Index Guild | src/Graph/StellaOps.Graph.Indexer | Wire SBOM ingest runtime to emit graph snapshot artifacts, add DI factory helpers, and document Mongo/snapshot environment guidance. Dependencies: GRAPH-INDEX-28-002..006. | GRSC0101 outputs | GRIX0101 | | GRAPH-OPS-0001 | TODO | | SPRINT_321_docs_modules_graph | Ops Guild | docs/modules/graph | Review graph observability dashboards/runbooks after the next sprint demo. | GRUI0101 | GRDG0101 | | HELM-45-001 | TODO | | SPRINT_501_ops_deployment_i | Deployment Guild (ops/deployment) | ops/deployment | | | GRIX0101 | | HELM-45-002 | TODO | | SPRINT_502_ops_deployment_ii | Deployment Guild, Security Guild (ops/deployment) | ops/deployment | Add TLS/Ingress, NetworkPolicy, PodSecurityContexts, Secrets integration (external secrets), and document security posture. Dependencies: HELM-45-001. | | GRIX0101 | @@ -3353,7 +3353,7 @@ | INDEX-28-008 | TODO | | SPRINT_0140_0001_0001_runtime_signals | Graph Index Guild | src/Graph/StellaOps.Graph.Indexer | INDEX-28-007 | INDEX-28-007 | GRIX0101 | | INDEX-28-009 | TODO | | SPRINT_0140_0001_0001_runtime_signals | Graph Index Guild | src/Graph/StellaOps.Graph.Indexer | INDEX-28-008 | INDEX-28-008 | GRIX0101 | | INDEX-28-010 | TODO | | SPRINT_0140_0001_0001_runtime_signals | Graph Indexer Guild (src/Graph/StellaOps.Graph.Indexer) | src/Graph/StellaOps.Graph.Indexer | | INDEX-28-009 | GRIX0101 | -| INDEX-28-011 | DONE | 2025-11-04 | SPRINT_207_graph | Graph Indexer Guild (src/Graph/StellaOps.Graph.Indexer) | src/Graph/StellaOps.Graph.Indexer | | INDEX-28-010 | GRIX0101 | +| INDEX-28-011 | DONE | 2025-11-04 | SPRINT_0207_0001_0001_graph | Graph Indexer Guild (src/Graph/StellaOps.Graph.Indexer) | src/Graph/StellaOps.Graph.Indexer | | INDEX-28-010 | GRIX0101 | | INDEX-401-030 | TODO | | SPRINT_401_reachability_evidence_chain | Platform + Ops Guilds | `docs/provenance/inline-dsse.md`, `ops/mongo/indices/events_provenance_indices.js` | Needs Ops approval for new Mongo index | Needs Ops approval for new Mongo index | RBRE0101 | | INGEST-401-013 | TODO | | SPRINT_401_reachability_evidence_chain | Symbols Guild · DevOps Guild (`src/Symbols/StellaOps.Symbols.Ingestor.Cli`) | `src/Symbols/StellaOps.Symbols.Ingestor.Cli`, `docs/specs/SYMBOL_MANIFEST_v1.md` | Implement deterministic ingest + docs. | RBRE0101 inline DSSE | IMPT0101 | | INLINE-401-028 | DONE | | SPRINT_401_reachability_evidence_chain | Authority Guild · Feedser Guild (`docs/provenance/inline-dsse.md`, `src/__Libraries/StellaOps.Provenance.Mongo`) | `docs/provenance/inline-dsse.md`, `src/__Libraries/StellaOps.Provenance.Mongo` | | | INST0101 | @@ -3612,7 +3612,7 @@ | POLICY-ATTEST-74-002 | TODO | | SPRINT_123_policy_reasoning | Policy Guild, Console Guild / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Surface policy evaluations in Console verification reports with rule explanations | POLICY-ATTEST-74-001 | | | POLICY-CONSOLE-23-001 | TODO | | SPRINT_123_policy_reasoning | Policy Guild, BE-Base Platform Guild / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Optimize findings/explain APIs for Console: cursor-based pagination at scale, global filter parameters (severity bands, policy version, time window), rule trace summarization, and aggregation hints for dashboard cards. Ensure deterministic ordering and expose provenance refs | | | | POLICY-CONSOLE-23-002 | TODO | | SPRINT_124_policy_reasoning | Policy Guild, Product Ops / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Produce simulation diff metadata | POLICY-CONSOLE-23-001 | | -| POLICY-DET-01 | TODO | | SPRINT_209_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | | | | +| POLICY-DET-01 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | | | | | POLICY-ENGINE-20-002 | BLOCKED | 2025-10-26 | SPRINT_124_policy_reasoning | Policy Guild / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Build deterministic evaluator honoring lexical/priority order, first-match semantics, and safe value types (no wall-clock/network access) | PGMI0101 | PLPE0101 | | POLICY-ENGINE-20-003 | TODO | | SPRINT_124_policy_reasoning | Policy Guild, Concelier Core Guild, Excititor Core Guild / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Implement selection joiners resolving SBOM↔advisory↔VEX tuples using linksets and PURL equivalence tables, with deterministic batching | POLICY-ENGINE-20-002 | PLPE0101 | | POLICY-ENGINE-20-004 | TODO | | SPRINT_124_policy_reasoning | Policy Guild, Platform Storage Guild / src/Policy/StellaOps.Policy.Engine | src/Policy/StellaOps.Policy.Engine | Ship materialization writer that upserts into `effective_finding_{policyId}` with append-only history, tenant scoping, and trace references | POLICY-ENGINE-20-003 | PLPE0101 | @@ -3799,7 +3799,7 @@ | SBOM-AIAI-31-003 | BLOCKED | 2025-11-18 | SPRINT_0111_0001_0001_advisoryai | SBOM Service Guild · Advisory AI Guild (src/SbomService/StellaOps.SbomService) | src/SbomService/StellaOps.SbomService | Publish the Advisory AI hand-off kit for `/v1/sbom/context`, share base URL/API key + tenant header contract, and run a joint end-to-end retrieval smoke test with Advisory AI. | SBOM-AIAI-31-001 projection kit/fixtures | ADAI0101 | | SBOM-CONSOLE-23-001 | TODO | | SPRINT_0140_0001_0001_runtime_signals | | | Console catalog API draft complete; depends on Concelier/Cartographer payload definitions. | | | | SBOM-CONSOLE-23-002 | TODO | | SPRINT_0140_0001_0001_runtime_signals | | | Global component lookup API needs 23-001 responses + cache hints before work can start. | | | -| SBOM-DET-01 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | | | | +| SBOM-DET-01 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | | | | | SBOM-ORCH-32-001 | TODO | | SPRINT_0140_0001_0001_runtime_signals | | | Orchestrator registration is sequenced after projection schema because payload shapes map into job metadata. | | | | SBOM-ORCH-33-001 | TODO | | SPRINT_0140_0001_0001_runtime_signals | | | Backpressure/telemetry features depend on 32-001 workers. | | | | SBOM-ORCH-34-001 | TODO | | SPRINT_0140_0001_0001_runtime_signals | | | Backfill + watermark logic requires the orchestrator integration from 33-001. | | | @@ -3991,18 +3991,18 @@ | SDK-62-002 | TODO | | SPRINT_204_cli_iv | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | | SDK-63-001 | TODO | | SPRINT_204_cli_iv | DevEx/CLI Guild, API Governance Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | | SDK-64-001 | TODO | | SPRINT_204_cli_iv | DevEx/CLI Guild, SDK Release Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | -| SDKGEN-62-001 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Choose/pin generator toolchain, set up language template pipeline, and enforce reproducible builds. | DEVL0101 portal contracts | SDKG0101 | -| SDKGEN-62-002 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Implement shared post-processing (auth helpers, retries, pagination utilities, telemetry hooks) applied to all languages. Dependencies: SDKGEN-62-001. | SDKGEN-62-001 | SDKG0101 | -| SDKGEN-63-001 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship TypeScript SDK alpha with ESM/CJS builds, typed errors, paginator, streaming helpers. Dependencies: SDKGEN-62-002. | 63-004 | SDKG0101 | -| SDKGEN-63-002 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Python SDK alpha (sync/async clients, type hints, upload/download helpers). Dependencies: SDKGEN-63-001. | SDKGEN-63-001 | SDKG0101 | -| SDKGEN-63-003 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Go SDK alpha with context-first API and streaming helpers. Dependencies: SDKGEN-63-002. | SDKGEN-63-002 | SDKG0101 | -| SDKGEN-63-004 | TODO | | SPRINT_208_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Java SDK alpha (builder pattern, HTTP client abstraction). Dependencies: SDKGEN-63-003. | SDKGEN-63-003 | SDKG0101 | -| SDKGEN-64-001 | TODO | | SPRINT_208_sdk | SDK Generator Guild · CLI Guild | src/Sdk/StellaOps.Sdk.Generator | Switch CLI to consume TS or Go SDK; ensure parity. Dependencies: SDKGEN-63-004. | SDKGEN-63-004 | SDKG0101 | -| SDKGEN-64-002 | TODO | | SPRINT_208_sdk | SDK Generator Guild · Console Guild | src/Sdk/StellaOps.Sdk.Generator | Integrate SDKs into Console data providers where feasible. Dependencies: SDKGEN-64-001. | SDKGEN-64-001 | SDKG0101 | -| SDKREL-63-001 | TODO | | SPRINT_208_sdk | SDK Release Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Configure CI pipelines for npm, PyPI, Maven Central staging, and Go proxies with signing and provenance attestations. | | | -| SDKREL-63-002 | TODO | | SPRINT_208_sdk | SDK Release Guild, API Governance Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Integrate changelog automation pulling from OAS diffs and generator metadata. Dependencies: SDKREL-63-001. | | | -| SDKREL-64-001 | TODO | | SPRINT_208_sdk | SDK Release Guild, Notifications Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Hook SDK releases into Notifications Studio with scoped announcements and RSS/Atom feeds. Dependencies: SDKREL-63-002. | | | -| SDKREL-64-002 | TODO | | SPRINT_208_sdk | SDK Release Guild, Export Center Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Add `devportal --offline` bundle job packaging docs, specs, SDK artifacts for air-gapped users. Dependencies: SDKREL-64-001. | | | +| SDKGEN-62-001 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Choose/pin generator toolchain, set up language template pipeline, and enforce reproducible builds. | DEVL0101 portal contracts | SDKG0101 | +| SDKGEN-62-002 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Implement shared post-processing (auth helpers, retries, pagination utilities, telemetry hooks) applied to all languages. Dependencies: SDKGEN-62-001. | SDKGEN-62-001 | SDKG0101 | +| SDKGEN-63-001 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship TypeScript SDK alpha with ESM/CJS builds, typed errors, paginator, streaming helpers. Dependencies: SDKGEN-62-002. | 63-004 | SDKG0101 | +| SDKGEN-63-002 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Python SDK alpha (sync/async clients, type hints, upload/download helpers). Dependencies: SDKGEN-63-001. | SDKGEN-63-001 | SDKG0101 | +| SDKGEN-63-003 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Go SDK alpha with context-first API and streaming helpers. Dependencies: SDKGEN-63-002. | SDKGEN-63-002 | SDKG0101 | +| SDKGEN-63-004 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild | src/Sdk/StellaOps.Sdk.Generator | Ship Java SDK alpha (builder pattern, HTTP client abstraction). Dependencies: SDKGEN-63-003. | SDKGEN-63-003 | SDKG0101 | +| SDKGEN-64-001 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild · CLI Guild | src/Sdk/StellaOps.Sdk.Generator | Switch CLI to consume TS or Go SDK; ensure parity. Dependencies: SDKGEN-63-004. | SDKGEN-63-004 | SDKG0101 | +| SDKGEN-64-002 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Generator Guild · Console Guild | src/Sdk/StellaOps.Sdk.Generator | Integrate SDKs into Console data providers where feasible. Dependencies: SDKGEN-64-001. | SDKGEN-64-001 | SDKG0101 | +| SDKREL-63-001 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Release Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Configure CI pipelines for npm, PyPI, Maven Central staging, and Go proxies with signing and provenance attestations. | | | +| SDKREL-63-002 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Release Guild, API Governance Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Integrate changelog automation pulling from OAS diffs and generator metadata. Dependencies: SDKREL-63-001. | | | +| SDKREL-64-001 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Release Guild, Notifications Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Hook SDK releases into Notifications Studio with scoped announcements and RSS/Atom feeds. Dependencies: SDKREL-63-002. | | | +| SDKREL-64-002 | TODO | | SPRINT_0208_0001_0001_sdk | SDK Release Guild, Export Center Guild (src/Sdk/StellaOps.Sdk.Release) | src/Sdk/StellaOps.Sdk.Release | Add `devportal --offline` bundle job packaging docs, specs, SDK artifacts for air-gapped users. Dependencies: SDKREL-64-001. | | | | SEC-62-001 | TODO | | SPRINT_309_docs_tasks_md_ix | Docs Guild, Authority Core (docs) | | | | | | SEC-CRYPTO-90-001 | DONE | 2025-11-07 | SPRINT_514_sovereign_crypto_enablement | Security Guild (src/__Libraries/StellaOps.Cryptography) | src/__Libraries/StellaOps.Cryptography | Produce the RootPack_RU implementation plan, provider strategy (CryptoPro + PKCS#11), and backlog split for sovereign crypto work. | | | | SEC-CRYPTO-90-002 | DONE | 2025-11-07 | SPRINT_514_sovereign_crypto_enablement | Security Guild (src/__Libraries/StellaOps.Cryptography) | src/__Libraries/StellaOps.Cryptography | Extend signature/catalog constants and configuration schema to recognize `GOST12-256/512`, regional crypto profiles, and provider preference ordering. | | | @@ -4203,26 +4203,26 @@ | TIMELINE-OBS-52-004 | TODO | | SPRINT_160_export_evidence | Timeline Indexer + Security Guilds | | Timeline Indexer + Security Guilds | | | | TIMELINE-OBS-53-001 | TODO | | SPRINT_160_export_evidence | Timeline Indexer + Evidence Locker Guilds | | Timeline Indexer + Evidence Locker Guilds | | | | UI-401-027 | TODO | | SPRINT_401_reachability_evidence_chain | UI Guild · CLI Guild (`src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/uncertainty/README.md`) | `src/UI/StellaOps.UI`, `src/Cli/StellaOps.Cli`, `docs/uncertainty/README.md` | | | | -| UI-AOC-19-001 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add Sources dashboard tiles showing AOC pass/fail, recent violation codes, and ingest throughput per tenant. | | | -| UI-AOC-19-002 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement violation drill-down view highlighting offending document fields and provenance metadata. Dependencies: UI-AOC-19-001. | | | -| UI-AOC-19-003 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add "Verify last 24h" action triggering AOC verifier endpoint and surfacing CLI parity guidance. Dependencies: UI-AOC-19-002. | | | +| UI-AOC-19-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add Sources dashboard tiles showing AOC pass/fail, recent violation codes, and ingest throughput per tenant. | | | +| UI-AOC-19-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement violation drill-down view highlighting offending document fields and provenance metadata. Dependencies: UI-AOC-19-001. | | | +| UI-AOC-19-003 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add "Verify last 24h" action triggering AOC verifier endpoint and surfacing CLI parity guidance. Dependencies: UI-AOC-19-002. | | | | UI-CLI-401-007 | TODO | | SPRINT_401_reachability_evidence_chain | UI & CLI Guilds (`src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`) | `src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI` | Implement CLI `stella graph explain` + UI explain drawer showing signed call-path, predicates, runtime hits, and DSSE pointers; include counterfactual controls. | | | | UI-DOCS-0001 | TODO | | SPRINT_331_docs_modules_ui | Docs Guild (docs/modules/ui) | docs/modules/ui | | | | | UI-ENG-0001 | TODO | | SPRINT_331_docs_modules_ui | Module Team (docs/modules/ui) | docs/modules/ui | | | | -| UI-ENTROPY-40-001 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Visualise entropy analysis per image (layer donut, file heatmaps, “Why risky?” chips) in Vulnerability Explorer and scan details, including opaque byte ratios and detector hints (see `docs/modules/scanner/entropy.md`). | | | -| UI-ENTROPY-40-002 | TODO | | SPRINT_209_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add policy banners/tooltips explaining entropy penalties (block/warn thresholds, mitigation steps) and link to raw `entropy.report.json` evidence downloads (`docs/modules/scanner/entropy.md`). Dependencies: UI-ENTROPY-40-001. | | | -| UI-EXC-25-001 | TODO | | SPRINT_209_ui_i | UI Guild, Governance Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Exception Center (list + kanban) with filters, sorting, workflow transitions, and audit views. | | | -| UI-EXC-25-002 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement exception creation wizard with scope preview, justification templates, timebox guardrails. Dependencies: UI-EXC-25-001. | | | -| UI-EXC-25-003 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add inline exception drafting/proposing from Vulnerability Explorer and Graph detail panels with live simulation. Dependencies: UI-EXC-25-002. | | | -| UI-EXC-25-004 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Surface exception badges, countdown timers, and explain integration across Graph/Vuln Explorer and policy views. Dependencies: UI-EXC-25-003. | | | -| UI-EXC-25-005 | TODO | | SPRINT_209_ui_i | UI Guild, Accessibility Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add keyboard shortcuts (`x`,`a`,`r`) and ensure screen-reader messaging for approvals/revocations. Dependencies: UI-EXC-25-004. | | | -| UI-GRAPH-21-001 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Align Graph Explorer auth configuration with new `graph:*` scopes; consume scope identifiers from shared `StellaOpsScopes` exports (via generated SDK/config) instead of hard-coded strings. | | | -| UI-GRAPH-24-001 | TODO | | SPRINT_209_ui_i | UI Guild, SBOM Service Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Graph Explorer canvas with layered/radial layouts, virtualization, zoom/pan, and scope toggles; initial render <1.5s for sample asset. Dependencies: UI-GRAPH-21-001. | | | -| UI-GRAPH-24-002 | TODO | | SPRINT_209_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement overlays (Policy, Evidence, License, Exposure), simulation toggle, path view, and SBOM diff/time-travel with accessible tooltips/AOC indicators. Dependencies: UI-GRAPH-24-001. | | | -| UI-GRAPH-24-003 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Deliver filters/search panel with facets, saved views, permalinks, and share modal. Dependencies: UI-GRAPH-24-002. | | | -| UI-GRAPH-24-004 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add side panels (Details, What-if, History) with upgrade simulation integration and SBOM diff viewer. Dependencies: UI-GRAPH-24-003. | | | -| UI-GRAPH-24-006 | TODO | | SPRINT_209_ui_i | UI Guild, Accessibility Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Ensure accessibility (keyboard nav, screen reader labels, contrast), add hotkeys (`f`,`e`,`.`), and analytics instrumentation. Dependencies: UI-GRAPH-24-004. | | | -| UI-LNM-22-001 | TODO | | SPRINT_209_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Evidence panel showing policy decision with advisory observations/linksets side-by-side, conflict badges, AOC chain, and raw doc download links. Docs `DOCS-LNM-22-005` waiting on delivered UI for screenshots + flows. | | | +| UI-ENTROPY-40-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Visualise entropy analysis per image (layer donut, file heatmaps, “Why risky?” chips) in Vulnerability Explorer and scan details, including opaque byte ratios and detector hints (see `docs/modules/scanner/entropy.md`). | | | +| UI-ENTROPY-40-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add policy banners/tooltips explaining entropy penalties (block/warn thresholds, mitigation steps) and link to raw `entropy.report.json` evidence downloads (`docs/modules/scanner/entropy.md`). Dependencies: UI-ENTROPY-40-001. | | | +| UI-EXC-25-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Governance Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Exception Center (list + kanban) with filters, sorting, workflow transitions, and audit views. | | | +| UI-EXC-25-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement exception creation wizard with scope preview, justification templates, timebox guardrails. Dependencies: UI-EXC-25-001. | | | +| UI-EXC-25-003 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add inline exception drafting/proposing from Vulnerability Explorer and Graph detail panels with live simulation. Dependencies: UI-EXC-25-002. | | | +| UI-EXC-25-004 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Surface exception badges, countdown timers, and explain integration across Graph/Vuln Explorer and policy views. Dependencies: UI-EXC-25-003. | | | +| UI-EXC-25-005 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Accessibility Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add keyboard shortcuts (`x`,`a`,`r`) and ensure screen-reader messaging for approvals/revocations. Dependencies: UI-EXC-25-004. | | | +| UI-GRAPH-21-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Align Graph Explorer auth configuration with new `graph:*` scopes; consume scope identifiers from shared `StellaOpsScopes` exports (via generated SDK/config) instead of hard-coded strings. | | | +| UI-GRAPH-24-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, SBOM Service Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Graph Explorer canvas with layered/radial layouts, virtualization, zoom/pan, and scope toggles; initial render <1.5s for sample asset. Dependencies: UI-GRAPH-21-001. | | | +| UI-GRAPH-24-002 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement overlays (Policy, Evidence, License, Exposure), simulation toggle, path view, and SBOM diff/time-travel with accessible tooltips/AOC indicators. Dependencies: UI-GRAPH-24-001. | | | +| UI-GRAPH-24-003 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Deliver filters/search panel with facets, saved views, permalinks, and share modal. Dependencies: UI-GRAPH-24-002. | | | +| UI-GRAPH-24-004 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add side panels (Details, What-if, History) with upgrade simulation integration and SBOM diff viewer. Dependencies: UI-GRAPH-24-003. | | | +| UI-GRAPH-24-006 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Accessibility Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Ensure accessibility (keyboard nav, screen reader labels, contrast), add hotkeys (`f`,`e`,`.`), and analytics instrumentation. Dependencies: UI-GRAPH-24-004. | | | +| UI-LNM-22-001 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Build Evidence panel showing policy decision with advisory observations/linksets side-by-side, conflict badges, AOC chain, and raw doc download links. Docs `DOCS-LNM-22-005` waiting on delivered UI for screenshots + flows. | | | | UI-LNM-22-002 | TODO | | SPRINT_210_ui_ii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement filters (source, severity bucket, conflict-only, CVSS vector presence) and pagination/lazy loading for large linksets. Docs depend on finalized filtering UX. Dependencies: UI-LNM-22-001. | | | | UI-LNM-22-003 | TODO | | SPRINT_210_ui_ii | UI Guild, Excititor Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add VEX tab with status/justification summaries, conflict indicators, and export actions. Required for `DOCS-LNM-22-005` coverage of VEX evidence tab. Dependencies: UI-LNM-22-002. | | | | UI-LNM-22-004 | TODO | | SPRINT_210_ui_ii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Provide permalink + copy-to-clipboard for selected component/linkset/policy combination; ensure high-contrast theme support. Dependencies: UI-LNM-22-003. | | | @@ -4240,8 +4240,8 @@ | UI-POLICY-23-005 | TODO | | SPRINT_210_ui_ii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Integrate simulator panel (SBOM/component/advisory selection), run diff vs active policy, show explain tree and overlays. Dependencies: UI-POLICY-23-004. | | | | UI-POLICY-23-006 | TODO | | SPRINT_210_ui_ii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Implement explain view linking to evidence overlays and exceptions; provide export to JSON/PDF. Dependencies: UI-POLICY-23-005. | | | | UI-POLICY-27-001 | TODO | | SPRINT_211_ui_iii | UI Guild, Product Ops (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Update Console policy workspace RBAC guards, scope requests, and user messaging to reflect the new Policy Studio roles/scopes (`policy:author/review/approve/operate/audit/simulate`), including Cypress auth stubs and help text. Dependencies: UI-POLICY-23-006. | | | -| UI-POLICY-DET-01 | TODO | | SPRINT_209_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Wire policy gate indicators + remediation hints into Release/Policy flows, blocking publishes when determinism checks fail; coordinate with Policy Engine schema updates. Dependencies: UI-SBOM-DET-01. | | | -| UI-SBOM-DET-01 | TODO | | SPRINT_209_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add a “Determinism” badge plus drill-down that surfaces fragment hashes, `_composition.json`, and Merkle root consistency when viewing scan details (per `docs/modules/scanner/deterministic-sbom-compose.md`). | | | +| UI-POLICY-DET-01 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild, Policy Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Wire policy gate indicators + remediation hints into Release/Policy flows, blocking publishes when determinism checks fail; coordinate with Policy Engine schema updates. Dependencies: UI-SBOM-DET-01. | | | +| UI-SBOM-DET-01 | TODO | | SPRINT_0209_0001_0001_ui_i | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add a “Determinism” badge plus drill-down that surfaces fragment hashes, `_composition.json`, and Merkle root consistency when viewing scan details (per `docs/modules/scanner/deterministic-sbom-compose.md`). | | | | UI-SIG-26-001 | TODO | | SPRINT_211_ui_iii | UI Guild, Signals Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add reachability columns/badges to Vulnerability Explorer with filters and tooltips. | | | | UI-SIG-26-002 | TODO | | SPRINT_211_ui_iii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Enhance “Why” drawer with call path visualization, reachability timeline, and evidence list. Dependencies: UI-SIG-26-001. | | | | UI-SIG-26-003 | TODO | | SPRINT_211_ui_iii | UI Guild (src/UI/StellaOps.UI) | src/UI/StellaOps.UI | Add reachability overlay halos/time slider to SBOM Graph along with state legend. Dependencies: UI-SIG-26-002. | | | @@ -4257,7 +4257,7 @@ | VAL-05 | TODO | | SPRINT_136_scanner_surface | Docs Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation | | SURFACE-VAL-02 | | | VERIFY-186-007 | TODO | | SPRINT_186_record_deterministic_execution | Authority Guild, Provenance Guild (`src/Authority/StellaOps.Authority`, `src/Provenance/StellaOps.Provenance.Attestation`) | `src/Authority/StellaOps.Authority`, `src/Provenance/StellaOps.Provenance.Attestation` | | | | | VEX-006 | TODO | | SPRINT_401_reachability_evidence_chain | Policy, Excititor, UI, CLI & Notify Guilds (`docs/modules/excititor/architecture.md`, `src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`, `docs/09_API_CLI_REFERENCE.md`) | `docs/modules/excititor/architecture.md`, `src/Cli/StellaOps.Cli`, `src/UI/StellaOps.UI`, `docs/09_API_CLI_REFERENCE.md` | | | | -| VEX-30-001 | DOING | 2025-11-08 | SPRINT_212_web_i | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | | | | +| VEX-30-001 | DOING | 2025-11-08 | SPRINT_0212_0001_0001_web_i | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | | | | | VEX-30-002 | TODO | | SPRINT_205_cli_v | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | | VEX-30-003 | TODO | | SPRINT_205_cli_v | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | | VEX-30-004 | TODO | | SPRINT_205_cli_v | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | @@ -4293,7 +4293,7 @@ | VEXLENS-EXPORT-35-001 | TODO | | SPRINT_129_policy_reasoning | VEX Lens Guild | src/VexLens/StellaOps.VexLens | Provide consensus snapshot API delivering deterministic JSONL (state, confidence, provenance) for exporter mirror bundles | — | PLVL0103 | | VEXLENS-ORCH-33-001 | TODO | | SPRINT_129_policy_reasoning | VEX Lens Guild | src/VexLens/StellaOps.VexLens | Register `consensus_compute` job type with orchestrator, integrate worker SDK, and expose job planning hooks for consensus batches | — | PLVL0103 | | VEXLENS-ORCH-34-001 | TODO | | SPRINT_129_policy_reasoning | VEX Lens Guild | src/VexLens/StellaOps.VexLens | Emit consensus completion events into orchestrator run ledger and provenance chain, including confidence metadata | VEXLENS-ORCH-33-001 | PLVL0103 | -| VULN-29-001 | DOING | 2025-11-08 | SPRINT_212_web_i | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | | | | +| VULN-29-001 | DOING | 2025-11-08 | SPRINT_0212_0001_0001_web_i | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | | | | | VULN-29-002 | TODO | | SPRINT_123_excititor_v | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService) | src/Excititor/StellaOps.Excititor.WebService | | | | | VULN-29-003 | TODO | | SPRINT_205_cli_v | DevEx/CLI Guild (src/Cli/StellaOps.Cli) | src/Cli/StellaOps.Cli | | | | | VULN-29-004 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, Observability Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | @@ -4321,28 +4321,28 @@ | VULNERABILITY-EXPLORER-ENG-0001 | TODO | | SPRINT_334_docs_modules_vuln_explorer | Module Team (docs/modules/vuln-explorer) | docs/modules/vuln-explorer | Keep sprint alignment notes in sync with Vuln Explorer sprints. | | | | VULNERABILITY-EXPLORER-OPS-0001 | TODO | | SPRINT_334_docs_modules_vuln_explorer | Ops Guild (docs/modules/vuln-explorer) | docs/modules/vuln-explorer | Review runbooks/observability assets after next demo. | | | | WEB-20-002 | TODO | | SPRINT_0155_0001_0001_scheduler_i | Scheduler WebService Guild (src/Scheduler/StellaOps.Scheduler.WebService) | src/Scheduler/StellaOps.Scheduler.WebService | | | | -| WEB-AIAI-31-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Route `/advisory/ai/*` endpoints through gateway with RBAC/ABAC, rate limits, and telemetry headers. | | | -| WEB-AIAI-31-002 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide batching job handlers and streaming responses for CLI automation with retry/backoff. Dependencies: WEB-AIAI-31-001. | | | -| WEB-AIAI-31-003 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild, Observability Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Emit metrics/logs (latency, guardrail blocks, validation failures) and forward anonymized prompt hashes to analytics. Dependencies: WEB-AIAI-31-002. | | | +| WEB-AIAI-31-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Route `/advisory/ai/*` endpoints through gateway with RBAC/ABAC, rate limits, and telemetry headers. | | | +| WEB-AIAI-31-002 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide batching job handlers and streaming responses for CLI automation with retry/backoff. Dependencies: WEB-AIAI-31-001. | | | +| WEB-AIAI-31-003 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild, Observability Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Emit metrics/logs (latency, guardrail blocks, validation failures) and forward anonymized prompt hashes to analytics. Dependencies: WEB-AIAI-31-002. | | | | WEB-AIRGAP-56-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AIRGAP-56-002 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AIRGAP-57-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Policy Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AIRGAP-58-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Importer Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | -| WEB-AOC-19-002 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | | +| WEB-AOC-19-002 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | | | WEB-AOC-19-003 | TODO | | SPRINT_116_concelier_v | QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-004 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-005 | TODO | 2025-11-08 | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-006 | TODO | 2025-11-08 | SPRINT_116_concelier_v | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-007 | TODO | 2025-11-08 | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | -| WEB-CONSOLE-23-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild, Product Analytics Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide consolidated `/console/dashboard` and `/console/filters` APIs returning tenant-scoped aggregates (findings by severity, VEX override counts, advisory deltas, run health, policy change log). Enforce AOC labelling, deterministic ordering, and cursor-based pagination for drill-down hints. | | | -| WEB-CONSOLE-23-002 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild, Scheduler Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Expose `/console/status` polling endpoint and `/console/runs/{id}/stream` SSE/WebSocket proxy with heartbeat/backoff, queue lag metrics, and auth scope enforcement. Surface request IDs + retry headers. Dependencies: WEB-CONSOLE-23-001. | | | -| WEB-CONSOLE-23-003 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild, Policy Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Add `/console/exports` POST/GET routes coordinating evidence bundle creation, streaming CSV/JSON exports, checksum manifest retrieval, and signed attestation references. Ensure requests honor tenant + policy scopes and expose job tracking metadata. Dependencies: WEB-CONSOLE-23-002. | | | -| WEB-CONSOLE-23-004 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Implement `/console/search` endpoint accepting CVE/GHSA/PURL/SBOM identifiers, performing fan-out queries with caching, ranking, and deterministic tie-breaking. Return typed results for Console navigation; respect result caps and latency SLOs. Dependencies: WEB-CONSOLE-23-003. | | | -| WEB-CONSOLE-23-005 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild, DevOps Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Serve `/console/downloads` JSON manifest (images, charts, offline bundles) sourced from signed registry metadata; include integrity hashes, release notes links, and offline instructions. Provide caching headers and documentation. Dependencies: WEB-CONSOLE-23-004. | | | -| WEB-CONTAINERS-44-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Expose `/welcome` state, config discovery endpoint (safe values), and `QUICKSTART_MODE` handling for Console banner; add `/health/liveness`, `/health/readiness`, `/version` if missing. | | | -| WEB-CONTAINERS-45-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ensure readiness endpoints reflect DB/queue readiness, add feature flag toggles via config map, and document NetworkPolicy ports. Dependencies: WEB-CONTAINERS-44-001. | | | -| WEB-CONTAINERS-46-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide offline-friendly asset serving (no CDN), allow overriding object store endpoints via env, and document fallback behavior. Dependencies: WEB-CONTAINERS-45-001. | | | -| WEB-EXC-25-001 | TODO | | SPRINT_212_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Implement `/exceptions` API (create, propose, approve, revoke, list, history) with validation, pagination, and audit logging. | | | +| WEB-CONSOLE-23-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild, Product Analytics Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide consolidated `/console/dashboard` and `/console/filters` APIs returning tenant-scoped aggregates (findings by severity, VEX override counts, advisory deltas, run health, policy change log). Enforce AOC labelling, deterministic ordering, and cursor-based pagination for drill-down hints. | | | +| WEB-CONSOLE-23-002 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild, Scheduler Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Expose `/console/status` polling endpoint and `/console/runs/{id}/stream` SSE/WebSocket proxy with heartbeat/backoff, queue lag metrics, and auth scope enforcement. Surface request IDs + retry headers. Dependencies: WEB-CONSOLE-23-001. | | | +| WEB-CONSOLE-23-003 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild, Policy Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Add `/console/exports` POST/GET routes coordinating evidence bundle creation, streaming CSV/JSON exports, checksum manifest retrieval, and signed attestation references. Ensure requests honor tenant + policy scopes and expose job tracking metadata. Dependencies: WEB-CONSOLE-23-002. | | | +| WEB-CONSOLE-23-004 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Implement `/console/search` endpoint accepting CVE/GHSA/PURL/SBOM identifiers, performing fan-out queries with caching, ranking, and deterministic tie-breaking. Return typed results for Console navigation; respect result caps and latency SLOs. Dependencies: WEB-CONSOLE-23-003. | | | +| WEB-CONSOLE-23-005 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild, DevOps Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Serve `/console/downloads` JSON manifest (images, charts, offline bundles) sourced from signed registry metadata; include integrity hashes, release notes links, and offline instructions. Provide caching headers and documentation. Dependencies: WEB-CONSOLE-23-004. | | | +| WEB-CONTAINERS-44-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Expose `/welcome` state, config discovery endpoint (safe values), and `QUICKSTART_MODE` handling for Console banner; add `/health/liveness`, `/health/readiness`, `/version` if missing. | | | +| WEB-CONTAINERS-45-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ensure readiness endpoints reflect DB/queue readiness, add feature flag toggles via config map, and document NetworkPolicy ports. Dependencies: WEB-CONTAINERS-44-001. | | | +| WEB-CONTAINERS-46-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Provide offline-friendly asset serving (no CDN), allow overriding object store endpoints via env, and document fallback behavior. Dependencies: WEB-CONTAINERS-45-001. | | | +| WEB-EXC-25-001 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Implement `/exceptions` API (create, propose, approve, revoke, list, history) with validation, pagination, and audit logging. | | | | WEB-EXC-25-002 | TODO | | SPRINT_213_web_ii | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Extend `/policy/effective` and `/policy/simulate` responses to include exception metadata and accept overrides for simulations. Dependencies: WEB-EXC-25-001. | | | | WEB-EXC-25-003 | TODO | | SPRINT_213_web_ii | BE-Base Platform Guild, Platform Events Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Publish `exception.*` events, integrate with notification hooks, enforce rate limits. Dependencies: WEB-EXC-25-002. | | | | WEB-EXPORT-35-001 | TODO | | SPRINT_213_web_ii | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Surface Export Center APIs (profiles/runs/download) through gateway with tenant scoping, streaming support, and viewer/operator scope checks. | | | diff --git a/docs/modules/concelier/linkset-correlation-21-002.md b/docs/modules/concelier/linkset-correlation-21-002.md index 1a227beef..ce307ffb2 100644 --- a/docs/modules/concelier/linkset-correlation-21-002.md +++ b/docs/modules/concelier/linkset-correlation-21-002.md @@ -37,7 +37,7 @@ Each conflict includes `field`, `reason`, and `values` (array of `source: value` ## Linkset output shape additions - `key.confidence`: populated from formula above. -- `conflicts[]`: as defined; may be empty but never null. +- `conflicts[]`: as defined; may be empty but never null. Each conflict also carries `sourceIds[]` (vendors/sources that produced the values) for provenance. - `normalized` retains add-only fields from `link-not-merge-schema.md`; do not drop raw ranges even when disjoint. - `provenance.hashes`: sorted list of `observationHash` values; used by replay bundles. diff --git a/docs/modules/findings-ledger/airgap-provenance.md b/docs/modules/findings-ledger/airgap-provenance.md index aec0fd691..c6f14ef3f 100644 --- a/docs/modules/findings-ledger/airgap-provenance.md +++ b/docs/modules/findings-ledger/airgap-provenance.md @@ -25,6 +25,7 @@ Canonical JSON must sort object keys (`bundleId`, `importOperator`, …) to keep 2. **Event enrichment:** The importer populates `airgap.bundle` fields on each event produced from the bundle. `bundleId` equals manifest digest (SHA-256). `merkleRoot` is the bundle’s manifest Merkle root; `timeAnchor` is the authoritative timestamp from the bundle. 3. **Anchoring:** Merkle batching includes bundle metadata; anchor references in `ledger_merkle_roots.anchor_reference` use format `airgap::` when not externally anchored. 4. **Projection staleness:** Projector updates `airgap.stalenessSeconds` comparing current time with `bundle.timeAnchor` per artifact scope; CLI + Console read the value to display freshness indicators. +5. **API surface:** `POST /internal/ledger/airgap-import` records bundle provenance (returns `ledgerEventId`, `chainId`, `sequence`) and persists the same metadata into `airgap_imports` for audit. ## 4. Staleness enforcement - Config option `AirGapPolicies:FreshnessThresholdSeconds` (default 604800 = 7 days) sets allowable age. diff --git a/docs/modules/findings-ledger/observability.md b/docs/modules/findings-ledger/observability.md index 7c35b6c57..91c8323cf 100644 --- a/docs/modules/findings-ledger/observability.md +++ b/docs/modules/findings-ledger/observability.md @@ -12,9 +12,9 @@ | Metric | Type | Labels | Description / target | | --- | --- | --- | --- | -| `ledger_write_latency_seconds` | Histogram | `tenant`, `event_type` | End-to-end append latency (API ingress → persisted). P95 ≤ 120 ms. | +| `ledger_write_duration_seconds` | Histogram | `tenant`, `event_type`, `source` | End-to-end append latency (API ingress → persisted). P95 ≤ 120 ms. | | `ledger_events_total` | Counter | `tenant`, `event_type`, `source` (`policy`, `workflow`, `orchestrator`) | Incremented per committed event. Mirrors Merkle leaf count. | -| `ledger_ingest_backlog_events` | Gauge | `tenant` | Number of events buffered in the writer queue. Alert when >5 000 for 5 min. | +| `ledger_ingest_backlog_events` | Gauge | — | Number of events buffered in the writer/anchor queues. Alert when >5 000 for 5 min. | | `ledger_projection_lag_seconds` | Gauge | `tenant` | Wall-clock difference between latest ledger event and projection tail. Target <30 s. | | `ledger_projection_rebuild_seconds` | Histogram | `tenant` | Duration of replay/rebuild operations triggered by LEDGER-29-008 harness. | | `ledger_projection_apply_seconds` | Histogram | `tenant`, `event_type`, `policy_version`, `evaluation_status` | Time to apply a single ledger event to projection. Target P95 <1 s. | diff --git a/docs/modules/graph/architecture.md b/docs/modules/graph/architecture.md index fe2b57cd8..103a165a4 100644 --- a/docs/modules/graph/architecture.md +++ b/docs/modules/graph/architecture.md @@ -19,7 +19,8 @@ 1. **Ingestion:** Cartographer/SBOM Service emit SBOM snapshots (`sbom_snapshot` events) captured by the Graph Indexer. Advisories/VEX from Concelier/Excititor generate edge updates, policy runs attach overlay metadata. 2. **ETL:** Normalises nodes/edges into canonical IDs, deduplicates, enforces tenant partitions, and writes to the graph store (planned: Neo4j-compatible or document + adjacency lists in Mongo). 3. **Overlay computation:** Batch workers build materialised views for frequently used queries (impact lists, saved queries, policy overlays) and store as immutable blobs for Offline Kit exports. -4. **Diffing:** `graph_diff` jobs compare two snapshots (e.g., pre/post deploy) and generate signed diff manifests for UI/CLI consumption. +4. **Diffing:** `graph_diff` jobs compare two snapshots (e.g., pre/post deploy) and generate signed diff manifests for UI/CLI consumption. +5. **Analytics (Runtime & Signals 140.A):** background workers run Louvain-style clustering + degree/betweenness approximations on ingested graphs, emitting overlays per tenant/snapshot and writing cluster ids back to nodes when enabled. ## 3) APIs @@ -44,7 +45,8 @@ ## 6) Observability -- Metrics: ingestion lag (`graph_ingest_lag_seconds`), node/edge counts, query latency per saved query, overlay generation duration. +- Metrics: ingestion lag (`graph_ingest_lag_seconds`), node/edge counts, query latency per saved query, overlay generation duration. +- New analytics metrics: `graph_analytics_runs_total`, `graph_analytics_failures_total`, `graph_analytics_clusters_total`, `graph_analytics_centrality_total`, plus change-stream/backfill counters (`graph_changes_total`, `graph_backfill_total`, `graph_change_failures_total`, `graph_change_lag_seconds`). - Logs: structured events for ETL stages and query execution (with trace IDs). - Traces: ETL pipeline spans, query engine spans. diff --git a/docs/modules/graph/packaging.md b/docs/modules/graph/packaging.md new file mode 100644 index 000000000..58da61905 --- /dev/null +++ b/docs/modules/graph/packaging.md @@ -0,0 +1,31 @@ +# Graph Indexer packaging (Runtime & Signals 140.A) + +## Deployment overlays +- Helm/Compose should expose two timers for analytics: `GRAPH_ANALYTICS_CLUSTER_INTERVAL` and `GRAPH_ANALYTICS_CENTRALITY_INTERVAL` (ISO-8601 duration, default 5m). Map to `GraphAnalyticsOptions`. +- Change-stream/backfill worker toggles via `GRAPH_CHANGE_POLL_INTERVAL`, `GRAPH_BACKFILL_INTERVAL`, `GRAPH_CHANGE_MAX_RETRIES`, `GRAPH_CHANGE_RETRY_BACKOFF`. +- New Mongo collections: + - `graph_cluster_overlays` — cluster assignments (`tenant`, `snapshot_id`, `node_id`, `cluster_id`, `generated_at`). + - `graph_centrality_overlays` — degree + betweenness approximations per node. + - optional node updates write `attributes.cluster_id` when `WriteClusterAssignmentsToNodes=true`. + +## Offline kit alignment +- Cluster/centrality overlays are exportable alongside `nodes.jsonl`/`edges.jsonl`; keep under `artifacts/graph-snapshots/{snapshotId}/overlays/` for air-gapped imports. +- Seed bundle layout: + - `clusters.ndjson` — overlay records (one per node) matching `graph_cluster_overlays` schema. + - `centrality.ndjson` — overlay records with `degree`/`betweenness`. + - `manifest.json` — references snapshot manifest hash and run timestamps. +- Determinism: overlays ordered by `node_id` (ordinal) to keep bundle hashes stable. + +## Observability hooks +- Metrics (Meter `StellaOps.Graph.Indexer`): + - `graph_analytics_runs_total`, `graph_analytics_failures_total`, `graph_analytics_duration_seconds`, `graph_analytics_clusters_total`, `graph_analytics_centrality_total`. + - `graph_changes_total`, `graph_backfill_total`, `graph_change_failures_total`, `graph_change_lag_seconds`. +- Recommended alerts: lag > 5m, failures > 0 over 10m window, cluster job duration > 2m. + +## Configuration defaults +- Cluster/centrality intervals: 5 minutes; label-propagation iterations: 6; betweenness sample size: 12. +- Change stream: poll every 5s, backfill every 15m, max retries 3 with 3s backoff, batch size 256. + +## Notes +- Analytics writes are idempotent (upserts keyed on tenant+snapshot+node_id). Change-stream processing is idempotent via sequence tokens persisted in `IIdempotencyStore` (Mongo or in-memory for tests). +- Keep Helm/Compose values in sync with these defaults when publishing the Runtime & Signals 140.A bundle. diff --git a/docs/modules/sbomservice/fixtures/lnm-v1/README.md b/docs/modules/sbomservice/fixtures/lnm-v1/README.md new file mode 100644 index 000000000..4ac8f4c4e --- /dev/null +++ b/docs/modules/sbomservice/fixtures/lnm-v1/README.md @@ -0,0 +1,21 @@ +# Link-Not-Merge v1 Fixtures + +Status: Awaiting drop (2025-11-22) + +Expected contents (all JSON, canonicalized, UTF-8): +- `projections.json` — canonical SBOM projection payloads keyed by snapshot ID. +- `assets.json` — asset metadata overlays (tenant-scoped, append-only). +- `paths.json` — ordered dependency paths with runtime flags and blast-radius hints. +- `events.json` — `sbom.version.created` envelopes aligned to CAS/provenance fields. +- `schema-version.txt` — git SHA / semantic version of the frozen projection schema. +- `SHA256SUMS` — checksums for all files above. + +Drop instructions: +- Place files in this directory and update `SHA256SUMS` via `sha256sum *.json *.txt > SHA256SUMS`. +- Keep ordering stable; prefer NDJSON converted to JSON arrays only if deterministic sorting is applied. +- Record drop commit in sprint 0140/0142 Execution Logs and link here. + +Consumers: +- SBOM-SERVICE-21-001..004 implementation and tests. +- Advisory AI and Console replay suites. +- AirGap parity review (`docs/modules/sbomservice/runbooks/airgap-parity-review.md`). diff --git a/docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md b/docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md new file mode 100644 index 000000000..0435a95d1 --- /dev/null +++ b/docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md @@ -0,0 +1,31 @@ +# SBOM Service Prep — PREP-SBOM-SERVICE-GUILD-CARTOGRAPHER-GUILD-OB + +Status: Published (2025-11-22) + +Owners: SBOM Service Guild · Cartographer Guild · Observability Guild · Zastava Observer/Webhook Guilds · Security Guild + +Scope: Capture a single readiness note for Runtime & Signals wave (0140) so SBOM-SERVICE-21-001..004 and SBOM-AIAI-31-001/002 can start once fixtures and AirGap approvals land. + +## Current inputs (as of 2025-11-22) +- Link-Not-Merge v1 projection schema frozen on 2025-11-17 (per Sprint 0140 decisions); JSON fixtures have not been published. +- Mock surface bundle v1 exists; real scanner cache ETA is still outstanding, so Graph/Zastava cannot validate parity yet. +- CAS/provenance decisions are tracked under `docs/signals/cas-promotion-24-002.md` and `docs/signals/provenance-24-003.md`; SBOM events must align with these provenance fields. + +## Outstanding blockers to flip SBOM wave to DOING +- Publish LNM v1 JSON fixtures with hash list to `docs/modules/sbomservice/fixtures/lnm-v1/` plus `SHA256SUMS`. Owners: Concelier Core · Cartographer Guild. +- Run AirGap parity review for `/sbom/paths`, `/sbom/versions`, and `/sbom/events`; template and minutes location published at `docs/modules/sbomservice/runbooks/airgap-parity-review.md`. Owner: Observability Guild with SBOM Service Guild. +- Confirm scanner cache drop timeline and hash for the real surface cache; mirror in sprint 0140 tracker once published. Owner: Scanner Guild. + +## Ready-to-start checklist for SBOM-SERVICE-21-001..004 +- Verify fixtures landed at the path above and match the frozen field list; add deterministic fixture IDs to tests. +- Emit projection change events with schema version and fixture set hash; expose counters and optional OTEL traces behind config. +- Provide observability baselines (dashboards/alerts) for path/timeline endpoints with latency and error-rate SLOs. +- Document tenant scoping and add-only evolution in API reference before exposing to Console and Advisory AI consumers. + +## Evidence +- This prep note: `docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md`. +- Blocker detail mirrored in `docs/implplan/SPRINT_0140_0001_0001_runtime_signals.md` Delivery Tracker and Decisions & Risks. + +## Exit criteria +- LNM v1 fixtures and AirGap review minutes committed and linked in sprints 0140 and 0142. +- Sprint 0140 SBOM wave can move from BLOCKED to DOING with cache ETA recorded. diff --git a/docs/modules/sbomservice/runbooks/airgap-parity-review.md b/docs/modules/sbomservice/runbooks/airgap-parity-review.md new file mode 100644 index 000000000..c4a3b90ea --- /dev/null +++ b/docs/modules/sbomservice/runbooks/airgap-parity-review.md @@ -0,0 +1,31 @@ +# AirGap Parity Review — SBOM Service runtime/signals (Sprint 0140/0142) + +Status: Template published (2025-11-22) +Owners: Observability Guild · SBOM Service Guild · Cartographer Guild · Runtime & Signals coordination (0140) · Concelier Core (schema fidelity) + +## Purpose +Document a repeatable AirGap parity review for `/sbom/paths`, `/sbom/versions`, and SBOM event streams so SBOM-SERVICE-21-001..004 can move from BLOCKED to DOING once fixtures land. + +## Prerequisites +- Link-Not-Merge v1 fixtures available under `docs/modules/sbomservice/fixtures/lnm-v1/` with `SHA256SUMS`. +- Projection schema frozen (record SHA/commit). +- Mock surface bundle hash and real scanner cache ETA published in sprint 0140 tracker. +- CAS/provenance appendices (signals) frozen: `docs/signals/cas-promotion-24-002.md`, `docs/signals/provenance-24-003.md`. +- Test environment with offline toggle enabled; mirrored packages only. + +## Checklist +- Verify fixture integrity: run `sha256sum -c SHA256SUMS` in `fixtures/lnm-v1`. +- Replay fixtures in offline mode; capture latency/p95/p99 for `/sbom/paths` and `/sbom/versions` with deterministic seeds. +- Confirm tenant scoping and add-only evolution (no in-place updates) using two-tenant replay script. +- Validate event envelopes (`sbom.version.created`) against CAS/provenance requirements; ensure DSSE fields present or `skip_reason: offline`. +- Check orchestrator backpressure behavior with AirGap throttling; record SLO thresholds. +- Capture logs/traces snapshots (if enabled) and redact secrets before attaching. + +## Outputs +- Minutes + decisions appended to this file (Execution Notes section) with timestamps and owners. +- Metrics table with p50/p95/p99 latency, error rate, and cache hit ratio. +- Actions list with owners and due dates; blockers mirrored to sprint 0140/0142 Decisions & Risks. + +## Execution Notes +- 2025-11-22: Template published; awaiting fixtures and review scheduling. + diff --git a/docs/modules/scanner/design/deno-runtime-shim.md b/docs/modules/scanner/design/deno-runtime-shim.md index f98980a62..530671e59 100644 --- a/docs/modules/scanner/design/deno-runtime-shim.md +++ b/docs/modules/scanner/design/deno-runtime-shim.md @@ -9,29 +9,30 @@ This document specifies how the Deno analyzer will generate `deno-runtime.ndjson ## Approach 1) **Shim loader** - - Entry file `trace-shim.ts` injected ahead of user entrypoint (via `--import-map` or `--unstable-preload-module`). + - Entry file `trace-shim.ts` is written alongside the analyzer and executed via `deno run --cached-only --allow-read --allow-env --quiet trace-shim.ts` with `STELLA_DENO_ENTRYPOINT` set to the target module. - Registers listeners: - - `Deno.permissions.query/deny/permit` wrappers to observe grants. - - `globalThis.__originalImport = WebAssembly.instantiateStreaming` to observe wasm loads (fallback to buffer) and record importer URL. - - Wraps dynamic import by monkeypatching `import` via `globalThis.__dynamicImport` using `createDynamicImportProxy` helper (supported in Deno 1.42+). - - Hooks `Deno[Deno.internal].moduleLoader.load` (where available) to observe resolved specifier and cache hit/miss reason; fallback to `performance.resourceTimingBuffer` not used. + - `Deno.permissions.request/query/revoke` wrappers to capture permission uses and maintain a granted-permission snapshot (normalized to fs/net/env/ffi/process/worker). + - Hooks `Deno[Deno.internal].moduleLoader.load` when available to observe module loads (static/dynamic/npm) before execution. + - Wraps `WebAssembly.instantiate` / `instantiateStreaming` to record wasm loads. + - Wraps `Deno.dlopen` to record FFI permission use. + - Uses a synchronous SHA-256 implementation (no WebCrypto) to hash normalized module paths for determinism/offline safety. 2) **Event buffering** - Collects events in-memory; each event includes UTC timestamp and relative path (computed against analyzer root) plus `path_sha256`. - Origin normalization: for remote specifiers, strip query/fragment; record registry host/version if npm. 3) **Execution** - - Analyzer runs `deno run --allow-read --allow-env --no-lock --no-npm --quiet --import-map trace-import-map.json trace-shim.ts `. - - Optional: respect `DENO_DIR` from workspace normalization; no network fetch allowed (set `--cached-only`). + - Analyzer/worker runs `deno run --cached-only --allow-read --allow-env --quiet trace-shim.ts` with `STELLA_DENO_ENTRYPOINT=` (absolute or cwd-relative) and optional `STELLA_DENO_BINARY` override. + - Respects `DENO_DIR` if present for npm cache resolution; still offline (`--cached-only`). 4) **Output** - After user code exits, shim writes buffered events as NDJSON sorted by timestamp then type to `/deno-runtime.ndjson`. - - Also prints SHA256 to stdout for diagnostics; Analyzer reads file and stores payload in AnalysisStore + signals. + - Analyzer ingests the NDJSON, hashes content, stores payload in AnalysisStore under `ScanAnalysisKeys.DenoRuntimePayload` (legacy alias `"deno.runtime"` kept for backward compatibility), and emits policy signals keyed `surface.lang.deno.*`. 5) **Determinism & safety** - - Timestamps: `Date.now()` captured and converted to ISO-8601 UTC. - - Paths: use analyzer root + `path.relative` + forward slashes; hash with SHA256(lowercase hex). - - No module source or env values persisted; only paths + hashes. + - Timestamps: `Date.now()` captured and converted to ISO-8601 UTC; events sorted by ts then type. + - Paths: resolved to analyzer-relative form, forward-slash normalized, hashed with built-in synchronous SHA-256 (lowercase hex); remote origins normalized to protocol//host/path. + - No module source or env values persisted; only paths + hashes; npm resolutions recorded as cache hits only. ## Validation plan - Add fixtures: simple import graph, dynamic import, wasm load, npm: chalk (cached), permission use via `Deno.permissions.request`. diff --git a/plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Php/manifest.json b/plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Php/manifest.json new file mode 100644 index 000000000..844f7a304 --- /dev/null +++ b/plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Php/manifest.json @@ -0,0 +1,22 @@ +{ + "schemaVersion": "1.0", + "id": "stellaops.analyzer.lang.php", + "displayName": "StellaOps PHP Analyzer", + "version": "0.1.0", + "requiresRestart": true, + "entryPoint": { + "type": "dotnet", + "assembly": "StellaOps.Scanner.Analyzers.Lang.Php.dll", + "typeName": "StellaOps.Scanner.Analyzers.Lang.Php.PhpAnalyzerPlugin" + }, + "capabilities": [ + "language-analyzer", + "php", + "composer" + ], + "metadata": { + "org.stellaops.analyzer.language": "php", + "org.stellaops.analyzer.kind": "language", + "org.stellaops.restart.required": "true" + } +} diff --git a/src/Cli/StellaOps.Cli/Commands/CommandFactory.cs b/src/Cli/StellaOps.Cli/Commands/CommandFactory.cs index 82f385a56..511914b3d 100644 --- a/src/Cli/StellaOps.Cli/Commands/CommandFactory.cs +++ b/src/Cli/StellaOps.Cli/Commands/CommandFactory.cs @@ -1155,72 +1155,29 @@ internal static class CommandFactory var advise = new Command("advise", "Interact with Advisory AI pipelines."); _ = options; - var run = new Command("run", "Generate Advisory AI output for the specified task."); - var taskArgument = new Argument("task") + var runOptions = CreateAdvisoryOptions(); + var runTaskArgument = new Argument("task") { Description = "Task to run (summary, conflict, remediation)." }; - run.Add(taskArgument); - var advisoryKeyOption = new Option("--advisory-key") - { - Description = "Advisory identifier to summarise (required).", - Required = true - }; - var artifactIdOption = new Option("--artifact-id") - { - Description = "Optional artifact identifier to scope SBOM context." - }; - var artifactPurlOption = new Option("--artifact-purl") - { - Description = "Optional package URL to scope dependency context." - }; - var policyVersionOption = new Option("--policy-version") - { - Description = "Policy revision to evaluate (defaults to current)." - }; - var profileOption = new Option("--profile") - { - Description = "Advisory AI execution profile (default, fips-local, etc.)." - }; - var sectionOption = new Option("--section") - { - Description = "Preferred context sections to emphasise (repeatable).", - Arity = ArgumentArity.ZeroOrMore - }; - sectionOption.AllowMultipleArgumentsPerToken = true; - - var forceRefreshOption = new Option("--force-refresh") - { - Description = "Bypass cached plan/output and recompute." - }; - - var timeoutOption = new Option("--timeout") - { - Description = "Seconds to wait for generated output before timing out (0 = single attempt)." - }; - timeoutOption.Arity = ArgumentArity.ZeroOrOne; - - run.Add(advisoryKeyOption); - run.Add(artifactIdOption); - run.Add(artifactPurlOption); - run.Add(policyVersionOption); - run.Add(profileOption); - run.Add(sectionOption); - run.Add(forceRefreshOption); - run.Add(timeoutOption); + var run = new Command("run", "Generate Advisory AI output for the specified task."); + run.Add(runTaskArgument); + AddAdvisoryOptions(run, runOptions); run.SetAction((parseResult, _) => { - var taskValue = parseResult.GetValue(taskArgument); - var advisoryKey = parseResult.GetValue(advisoryKeyOption) ?? string.Empty; - var artifactId = parseResult.GetValue(artifactIdOption); - var artifactPurl = parseResult.GetValue(artifactPurlOption); - var policyVersion = parseResult.GetValue(policyVersionOption); - var profile = parseResult.GetValue(profileOption) ?? "default"; - var sections = parseResult.GetValue(sectionOption) ?? Array.Empty(); - var forceRefresh = parseResult.GetValue(forceRefreshOption); - var timeoutSeconds = parseResult.GetValue(timeoutOption) ?? 120; + var taskValue = parseResult.GetValue(runTaskArgument); + var advisoryKey = parseResult.GetValue(runOptions.AdvisoryKey) ?? string.Empty; + var artifactId = parseResult.GetValue(runOptions.ArtifactId); + var artifactPurl = parseResult.GetValue(runOptions.ArtifactPurl); + var policyVersion = parseResult.GetValue(runOptions.PolicyVersion); + var profile = parseResult.GetValue(runOptions.Profile) ?? "default"; + var sections = parseResult.GetValue(runOptions.Sections) ?? Array.Empty(); + var forceRefresh = parseResult.GetValue(runOptions.ForceRefresh); + var timeoutSeconds = parseResult.GetValue(runOptions.TimeoutSeconds) ?? 120; + var outputFormat = ParseAdvisoryOutputFormat(parseResult.GetValue(runOptions.Format)); + var outputPath = parseResult.GetValue(runOptions.Output); var verbose = parseResult.GetValue(verboseOption); if (!Enum.TryParse(taskValue, ignoreCase: true, out var taskType)) @@ -1239,17 +1196,164 @@ internal static class CommandFactory sections, forceRefresh, timeoutSeconds, + outputFormat, + outputPath, + verbose, + cancellationToken); + }); + + var summarizeOptions = CreateAdvisoryOptions(); + var summarize = new Command("summarize", "Summarize an advisory with JSON/Markdown outputs and citations."); + AddAdvisoryOptions(summarize, summarizeOptions); + summarize.SetAction((parseResult, _) => + { + var advisoryKey = parseResult.GetValue(summarizeOptions.AdvisoryKey) ?? string.Empty; + var artifactId = parseResult.GetValue(summarizeOptions.ArtifactId); + var artifactPurl = parseResult.GetValue(summarizeOptions.ArtifactPurl); + var policyVersion = parseResult.GetValue(summarizeOptions.PolicyVersion); + var profile = parseResult.GetValue(summarizeOptions.Profile) ?? "default"; + var sections = parseResult.GetValue(summarizeOptions.Sections) ?? Array.Empty(); + var forceRefresh = parseResult.GetValue(summarizeOptions.ForceRefresh); + var timeoutSeconds = parseResult.GetValue(summarizeOptions.TimeoutSeconds) ?? 120; + var outputFormat = ParseAdvisoryOutputFormat(parseResult.GetValue(summarizeOptions.Format)); + var outputPath = parseResult.GetValue(summarizeOptions.Output); + var verbose = parseResult.GetValue(verboseOption); + + return CommandHandlers.HandleAdviseRunAsync( + services, + AdvisoryAiTaskType.Summary, + advisoryKey, + artifactId, + artifactPurl, + policyVersion, + profile, + sections, + forceRefresh, + timeoutSeconds, + outputFormat, + outputPath, verbose, cancellationToken); }); advise.Add(run); + advise.Add(summarize); return advise; } + private static AdvisoryCommandOptions CreateAdvisoryOptions() + { + var advisoryKey = new Option("--advisory-key") + { + Description = "Advisory identifier to summarise (required).", + Required = true + }; + + var artifactId = new Option("--artifact-id") + { + Description = "Optional artifact identifier to scope SBOM context." + }; + + var artifactPurl = new Option("--artifact-purl") + { + Description = "Optional package URL to scope dependency context." + }; + + var policyVersion = new Option("--policy-version") + { + Description = "Policy revision to evaluate (defaults to current)." + }; + + var profile = new Option("--profile") + { + Description = "Advisory AI execution profile (default, fips-local, etc.)." + }; + + var sections = new Option("--section") + { + Description = "Preferred context sections to emphasise (repeatable).", + Arity = ArgumentArity.ZeroOrMore + }; + sections.AllowMultipleArgumentsPerToken = true; + + var forceRefresh = new Option("--force-refresh") + { + Description = "Bypass cached plan/output and recompute." + }; + + var timeoutSeconds = new Option("--timeout") + { + Description = "Seconds to wait for generated output before timing out (0 = single attempt)." + }; + timeoutSeconds.Arity = ArgumentArity.ZeroOrOne; + + var format = new Option("--format") + { + Description = "Output format: table (default), json, or markdown." + }; + + var output = new Option("--output") + { + Description = "File path to write advisory output when using json/markdown formats." + }; + + return new AdvisoryCommandOptions( + advisoryKey, + artifactId, + artifactPurl, + policyVersion, + profile, + sections, + forceRefresh, + timeoutSeconds, + format, + output); + } + + private static void AddAdvisoryOptions(Command command, AdvisoryCommandOptions options) + { + command.Add(options.AdvisoryKey); + command.Add(options.ArtifactId); + command.Add(options.ArtifactPurl); + command.Add(options.PolicyVersion); + command.Add(options.Profile); + command.Add(options.Sections); + command.Add(options.ForceRefresh); + command.Add(options.TimeoutSeconds); + command.Add(options.Format); + command.Add(options.Output); + } + + private static AdvisoryOutputFormat ParseAdvisoryOutputFormat(string? formatValue) + { + var normalized = string.IsNullOrWhiteSpace(formatValue) + ? "table" + : formatValue!.Trim().ToLowerInvariant(); + + return normalized switch + { + "json" => AdvisoryOutputFormat.Json, + "markdown" => AdvisoryOutputFormat.Markdown, + "md" => AdvisoryOutputFormat.Markdown, + _ => AdvisoryOutputFormat.Table + }; + } + + private sealed record AdvisoryCommandOptions( + Option AdvisoryKey, + Option ArtifactId, + Option ArtifactPurl, + Option PolicyVersion, + Option Profile, + Option Sections, + Option ForceRefresh, + Option TimeoutSeconds, + Option Format, + Option Output); + private static Command BuildVulnCommand(IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) - { - var vuln = new Command("vuln", "Explore vulnerability observations and overlays."); + { + var vuln = new Command("vuln", "Explore vulnerability observations and overlays."); var observations = new Command("observations", "List raw advisory observations for overlay consumers."); diff --git a/src/Cli/StellaOps.Cli/Commands/CommandHandlers.cs b/src/Cli/StellaOps.Cli/Commands/CommandHandlers.cs index df7204fda..ae127209c 100644 --- a/src/Cli/StellaOps.Cli/Commands/CommandHandlers.cs +++ b/src/Cli/StellaOps.Cli/Commands/CommandHandlers.cs @@ -448,6 +448,8 @@ internal static class CommandHandlers IReadOnlyList preferredSections, bool forceRefresh, int timeoutSeconds, + AdvisoryOutputFormat outputFormat, + string? outputPath, bool verbose, CancellationToken cancellationToken) { @@ -542,7 +544,14 @@ internal static class CommandHandlers activity?.SetTag("stellaops.cli.advisory.cache_hit", output.PlanFromCache); logger.LogInformation("Advisory output ready (cache key {CacheKey}).", output.CacheKey); - RenderAdvisoryOutput(output); + var rendered = RenderAdvisoryOutput(output, outputFormat); + + if (!string.IsNullOrWhiteSpace(outputPath) && rendered is not null) + { + var fullPath = Path.GetFullPath(outputPath!); + await File.WriteAllTextAsync(fullPath, rendered, cancellationToken).ConfigureAwait(false); + logger.LogInformation("Advisory output written to {Path}.", fullPath); + } if (output.Guardrail.Blocked) { @@ -6326,7 +6335,113 @@ internal static class CommandHandlers } } - private static void RenderAdvisoryOutput(AdvisoryPipelineOutputModel output) + private static string? RenderAdvisoryOutput(AdvisoryPipelineOutputModel output, AdvisoryOutputFormat format) + { + return format switch + { + AdvisoryOutputFormat.Json => RenderAdvisoryOutputJson(output), + AdvisoryOutputFormat.Markdown => RenderAdvisoryOutputMarkdown(output), + _ => RenderAdvisoryOutputTable(output) + }; + } + + private static string RenderAdvisoryOutputJson(AdvisoryPipelineOutputModel output) + { + return JsonSerializer.Serialize(output, new JsonSerializerOptions(JsonSerializerDefaults.Web) + { + WriteIndented = true + }); + } + + private static string RenderAdvisoryOutputMarkdown(AdvisoryPipelineOutputModel output) + { + var builder = new StringBuilder(); + builder.AppendLine($"# Advisory {output.TaskType} ({output.Profile})"); + builder.AppendLine(); + builder.AppendLine($"- Cache Key: `{output.CacheKey}`"); + builder.AppendLine($"- Generated: {output.GeneratedAtUtc.ToString(\"O\", CultureInfo.InvariantCulture)}"); + builder.AppendLine($"- Plan From Cache: {(output.PlanFromCache ? \"yes\" : \"no\")}"); + builder.AppendLine($"- Guardrail Blocked: {(output.Guardrail.Blocked ? \"yes\" : \"no\")}"); + builder.AppendLine(); + + if (!string.IsNullOrWhiteSpace(output.Response)) + { + builder.AppendLine("## Response"); + builder.AppendLine(output.Response.Trim()); + builder.AppendLine(); + } + + if (!string.IsNullOrWhiteSpace(output.Prompt)) + { + builder.AppendLine("## Prompt (sanitized)"); + builder.AppendLine(output.Prompt.Trim()); + builder.AppendLine(); + } + + if (output.Citations.Count > 0) + { + builder.AppendLine("## Citations"); + foreach (var citation in output.Citations.OrderBy(c => c.Index)) + { + builder.AppendLine($"- [{citation.Index}] {citation.DocumentId} :: {citation.ChunkId}"); + } + + builder.AppendLine(); + } + + if (output.Metadata.Count > 0) + { + builder.AppendLine("## Output Metadata"); + foreach (var entry in output.Metadata.OrderBy(kvp => kvp.Key, StringComparer.OrdinalIgnoreCase)) + { + builder.AppendLine($"- **{entry.Key}**: {entry.Value}"); + } + + builder.AppendLine(); + } + + if (output.Guardrail.Metadata.Count > 0) + { + builder.AppendLine("## Guardrail Metadata"); + foreach (var entry in output.Guardrail.Metadata.OrderBy(kvp => kvp.Key, StringComparer.OrdinalIgnoreCase)) + { + builder.AppendLine($"- **{entry.Key}**: {entry.Value}"); + } + + builder.AppendLine(); + } + + if (output.Guardrail.Violations.Count > 0) + { + builder.AppendLine("## Guardrail Violations"); + foreach (var violation in output.Guardrail.Violations) + { + builder.AppendLine($"- `{violation.Code}`: {violation.Message}"); + } + + builder.AppendLine(); + } + + builder.AppendLine("## Provenance"); + builder.AppendLine($"- Input Digest: `{output.Provenance.InputDigest}`"); + builder.AppendLine($"- Output Hash: `{output.Provenance.OutputHash}`"); + + if (output.Provenance.Signatures.Count > 0) + { + foreach (var signature in output.Provenance.Signatures) + { + builder.AppendLine($"- Signature: `{signature}`"); + } + } + else + { + builder.AppendLine("- Signature: none"); + } + + return builder.ToString(); + } + + private static string? RenderAdvisoryOutputTable(AdvisoryPipelineOutputModel output) { var console = AnsiConsole.Console; @@ -6428,6 +6543,8 @@ internal static class CommandHandlers provenance.AddRow("Signatures", signatures); console.Write(provenance); + + return null; } private static Table CreateKeyValueTable(string title, IReadOnlyDictionary entries) diff --git a/src/Cli/StellaOps.Cli/Services/Models/AdvisoryAi/AdvisoryAiModels.cs b/src/Cli/StellaOps.Cli/Services/Models/AdvisoryAi/AdvisoryAiModels.cs index 1f49599e2..eb8277a60 100644 --- a/src/Cli/StellaOps.Cli/Services/Models/AdvisoryAi/AdvisoryAiModels.cs +++ b/src/Cli/StellaOps.Cli/Services/Models/AdvisoryAi/AdvisoryAiModels.cs @@ -11,6 +11,13 @@ internal enum AdvisoryAiTaskType Remediation } +internal enum AdvisoryOutputFormat +{ + Table, + Json, + Markdown +} + internal sealed class AdvisoryPipelinePlanRequestModel { public AdvisoryAiTaskType TaskType { get; init; } diff --git a/src/Cli/StellaOps.Cli/TASKS.md b/src/Cli/StellaOps.Cli/TASKS.md index d01630d83..efd2e26d3 100644 --- a/src/Cli/StellaOps.Cli/TASKS.md +++ b/src/Cli/StellaOps.Cli/TASKS.md @@ -3,3 +3,4 @@ | Task ID | State | Notes | | --- | --- | --- | | `SCANNER-CLI-0001` | DONE (2025-11-12) | Ruby verbs now consume the persisted `RubyPackageInventory`, warn when inventories are missing, and docs/tests were refreshed per Sprint 138. | +| `CLI-AIAI-31-001` | DOING (2025-11-22) | Building `stella advise summarize` with JSON/Markdown outputs and citation rendering (Sprint 0201 CLI I). | diff --git a/src/Cli/__Tests/StellaOps.Cli.Tests/Commands/CommandHandlersTests.cs b/src/Cli/__Tests/StellaOps.Cli.Tests/Commands/CommandHandlersTests.cs index 0cf227d2c..1308aff4f 100644 --- a/src/Cli/__Tests/StellaOps.Cli.Tests/Commands/CommandHandlersTests.cs +++ b/src/Cli/__Tests/StellaOps.Cli.Tests/Commands/CommandHandlersTests.cs @@ -749,6 +749,8 @@ public sealed class CommandHandlersTests new[] { "impact", "impact " }, forceRefresh: false, timeoutSeconds: 0, + outputFormat: AdvisoryOutputFormat.Table, + outputPath: null, verbose: false, cancellationToken: CancellationToken.None); @@ -777,6 +779,104 @@ public sealed class CommandHandlersTests } } + [Fact] + public async Task HandleAdviseRunAsync_WritesMarkdownWithCitations() + { + var originalExit = Environment.ExitCode; + var originalConsole = AnsiConsole.Console; + using var tempDir = new TempDirectory(); + var outputPath = Path.Combine(tempDir.Path, "advisory.md"); + var testConsole = new TestConsole(); + + try + { + Environment.ExitCode = 0; + AnsiConsole.Console = testConsole; + + var planResponse = new AdvisoryPipelinePlanResponseModel + { + TaskType = AdvisoryAiTaskType.Summary.ToString(), + CacheKey = "cache-markdown", + PromptTemplate = "prompts/advisory/summary.liquid", + Budget = new AdvisoryTaskBudgetModel + { + PromptTokens = 256, + CompletionTokens = 64 + }, + Chunks = Array.Empty(), + Vectors = Array.Empty(), + Metadata = new Dictionary() + }; + + var outputResponse = new AdvisoryPipelineOutputModel + { + CacheKey = planResponse.CacheKey, + TaskType = planResponse.TaskType, + Profile = "default", + Prompt = "Sanitized prompt", + Response = "Rendered summary body.", + Citations = new[] + { + new AdvisoryOutputCitationModel { Index = 1, DocumentId = "doc-9", ChunkId = "chunk-9" } + }, + Metadata = new Dictionary(), + Guardrail = new AdvisoryOutputGuardrailModel + { + Blocked = false, + SanitizedPrompt = "Sanitized prompt", + Violations = Array.Empty(), + Metadata = new Dictionary() + }, + Provenance = new AdvisoryOutputProvenanceModel + { + InputDigest = "sha256:markdown-in", + OutputHash = "sha256:markdown-out", + Signatures = Array.Empty() + }, + GeneratedAtUtc = DateTimeOffset.Parse("2025-11-06T12:00:00Z", CultureInfo.InvariantCulture), + PlanFromCache = false + }; + + var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null)) + { + AdvisoryPlanResponse = planResponse, + AdvisoryOutputResponse = outputResponse + }; + + var provider = BuildServiceProvider(backend); + + await CommandHandlers.HandleAdviseRunAsync( + provider, + AdvisoryAiTaskType.Summary, + "ADV-4", + null, + null, + null, + "default", + Array.Empty(), + forceRefresh: false, + timeoutSeconds: 0, + outputFormat: AdvisoryOutputFormat.Markdown, + outputPath: outputPath, + verbose: false, + cancellationToken: CancellationToken.None); + + var markdown = await File.ReadAllTextAsync(outputPath); + Assert.Contains("Citations", markdown, StringComparison.OrdinalIgnoreCase); + Assert.Contains("doc-9", markdown, StringComparison.OrdinalIgnoreCase); + Assert.Contains("chunk-9", markdown, StringComparison.OrdinalIgnoreCase); + Assert.True(File.Exists(outputPath)); + Assert.Contains("Rendered summary body", markdown, StringComparison.OrdinalIgnoreCase); + Assert.Equal(0, Environment.ExitCode); + Assert.Contains("Citations", testConsole.Output, StringComparison.OrdinalIgnoreCase); + } + finally + { + AnsiConsole.Console = originalConsole; + Environment.ExitCode = originalExit; + } + } + [Fact] public async Task HandleAdviseRunAsync_ReturnsGuardrailExitCodeOnBlock() { @@ -855,6 +955,8 @@ public sealed class CommandHandlersTests Array.Empty(), forceRefresh: true, timeoutSeconds: 0, + outputFormat: AdvisoryOutputFormat.Table, + outputPath: null, verbose: false, cancellationToken: CancellationToken.None); @@ -913,6 +1015,8 @@ public sealed class CommandHandlersTests Array.Empty(), forceRefresh: false, timeoutSeconds: 0, + outputFormat: AdvisoryOutputFormat.Table, + outputPath: null, verbose: false, cancellationToken: CancellationToken.None); diff --git a/src/Concelier/__Libraries/StellaOps.Concelier.Core/Linksets/AdvisoryLinksetNormalization.cs b/src/Concelier/__Libraries/StellaOps.Concelier.Core/Linksets/AdvisoryLinksetNormalization.cs index 8d5f67043..cf8659754 100644 --- a/src/Concelier/__Libraries/StellaOps.Concelier.Core/Linksets/AdvisoryLinksetNormalization.cs +++ b/src/Concelier/__Libraries/StellaOps.Concelier.Core/Linksets/AdvisoryLinksetNormalization.cs @@ -189,6 +189,7 @@ internal static class AdvisoryLinksetNormalization var reason = key switch { "severity" => "severity-mismatch", + var k when k.StartsWith("cvss", StringComparison.OrdinalIgnoreCase) => "cvss-mismatch", "ranges" => "affected-range-divergence", "references" => "reference-clash", "aliases" => "alias-inconsistency", diff --git a/src/Concelier/__Libraries/StellaOps.Concelier.Core/Linksets/LinksetCorrelation.cs b/src/Concelier/__Libraries/StellaOps.Concelier.Core/Linksets/LinksetCorrelation.cs index 841f983d2..1f9445cad 100644 --- a/src/Concelier/__Libraries/StellaOps.Concelier.Core/Linksets/LinksetCorrelation.cs +++ b/src/Concelier/__Libraries/StellaOps.Concelier.Core/Linksets/LinksetCorrelation.cs @@ -4,6 +4,8 @@ using System.Collections.Immutable; using System.Linq; using StellaOps.Concelier.Models; +#pragma warning disable CS8620 // nullability mismatches guarded by explicit filtering + namespace StellaOps.Concelier.Core.Linksets; internal static class LinksetCorrelation @@ -109,19 +111,15 @@ internal static class LinksetCorrelation List> packageKeysPerInput = inputs .Select(i => i.Purls .Select(ExtractPackageKey) - .Where(k => !string.IsNullOrEmpty(k)) + .Where(k => !string.IsNullOrWhiteSpace(k)) .ToHashSet(StringComparer.Ordinal)) .ToList(); - var sharedPackages = packageKeysPerInput - .Skip(1) - .Aggregate( - new HashSet(packageKeysPerInput.First()!, StringComparer.Ordinal), - (acc, next) => - { - acc.IntersectWith(next!); - return acc; - }); + var sharedPackages = new HashSet(packageKeysPerInput.FirstOrDefault() ?? new HashSet(), StringComparer.Ordinal); + foreach (var next in packageKeysPerInput.Skip(1)) + { + sharedPackages.IntersectWith(next); + } if (sharedPackages.Count > 0) { @@ -140,12 +138,17 @@ internal static class LinksetCorrelation private static IEnumerable CollectRangeConflicts( IReadOnlyCollection inputs, - HashSet sharedPackages) + HashSet sharedPackages) { var conflicts = new List(); foreach (var package in sharedPackages) { + if (package is null) + { + continue; + } + var values = inputs .SelectMany(i => i.Purls .Where(p => ExtractPackageKey(p) == package) @@ -169,6 +172,8 @@ internal static class LinksetCorrelation return conflicts; } +#pragma warning restore CS8620 + private static bool HasExactPurlOverlap(IReadOnlyCollection inputs) { var first = inputs.First().Purls.ToHashSet(StringComparer.Ordinal); diff --git a/src/Concelier/__Tests/StellaOps.Concelier.Core.Tests/Linksets/AdvisoryLinksetNormalizationConfidenceTests.cs b/src/Concelier/__Tests/StellaOps.Concelier.Core.Tests/Linksets/AdvisoryLinksetNormalizationConfidenceTests.cs index 6acfae915..cac9dd41d 100644 --- a/src/Concelier/__Tests/StellaOps.Concelier.Core.Tests/Linksets/AdvisoryLinksetNormalizationConfidenceTests.cs +++ b/src/Concelier/__Tests/StellaOps.Concelier.Core.Tests/Linksets/AdvisoryLinksetNormalizationConfidenceTests.cs @@ -28,4 +28,20 @@ public sealed class AdvisoryLinksetNormalizationConfidenceTests Assert.Equal("severity-mismatch", conflict.Reason); Assert.Contains("severity:mismatch", conflict.Values!); } + + [Fact] + public void FromRawLinksetWithConfidence_EmitsCvssMismatchConflict() + { + var linkset = new RawLinkset + { + PackageUrls = ImmutableArray.Create("pkg:maven/com.acme/foo@2.0.0"), + Notes = ImmutableDictionary.CreateRange(new[] { new KeyValuePair("cvss_v3", "7.5/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H") }) + }; + + var (_, _, conflicts) = AdvisoryLinksetNormalization.FromRawLinksetWithConfidence(linkset); + + var conflict = Assert.Single(conflicts); + Assert.Equal("cvss-mismatch", conflict.Reason); + Assert.Contains("cvss_v3:7.5/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", conflict.Values!); + } } diff --git a/src/Concelier/__Tests/StellaOps.Concelier.Core.Tests/Observations/AdvisoryObservationAggregationTests.cs b/src/Concelier/__Tests/StellaOps.Concelier.Core.Tests/Observations/AdvisoryObservationAggregationTests.cs index e2da8ad71..ee265baff 100644 --- a/src/Concelier/__Tests/StellaOps.Concelier.Core.Tests/Observations/AdvisoryObservationAggregationTests.cs +++ b/src/Concelier/__Tests/StellaOps.Concelier.Core.Tests/Observations/AdvisoryObservationAggregationTests.cs @@ -96,6 +96,7 @@ public sealed class AdvisoryObservationAggregationTests Assert.Contains(aggregate.Conflicts, c => c.Reason == "alias-inconsistency"); Assert.Contains(aggregate.Conflicts, c => c.Reason == "affected-range-divergence"); Assert.True(aggregate.Confidence is > 0.0 and < 1.0); + Assert.All(aggregate.Conflicts, c => Assert.NotNull(c.SourceIds)); } [Fact] diff --git a/src/Concelier/__Tests/StellaOps.Concelier.Storage.Mongo.Tests/Observations/AdvisoryObservationTransportWorkerTests.cs b/src/Concelier/__Tests/StellaOps.Concelier.Storage.Mongo.Tests/Observations/AdvisoryObservationTransportWorkerTests.cs index f8590588d..fd1cf9e4e 100644 --- a/src/Concelier/__Tests/StellaOps.Concelier.Storage.Mongo.Tests/Observations/AdvisoryObservationTransportWorkerTests.cs +++ b/src/Concelier/__Tests/StellaOps.Concelier.Storage.Mongo.Tests/Observations/AdvisoryObservationTransportWorkerTests.cs @@ -33,8 +33,8 @@ public class AdvisoryObservationTransportWorkerTests "hash-1", DateTimeOffset.UtcNow, ReplayCursor: "cursor-1", - supersedesId: null, - traceId: "trace-1"); + SupersedesId: null, + TraceId: "trace-1"); var outbox = new FakeOutbox(evt); var transport = new FakeTransport(); diff --git a/src/DevPortal/StellaOps.DevPortal.Site/.gitignore b/src/DevPortal/StellaOps.DevPortal.Site/.gitignore new file mode 100644 index 000000000..e9f889842 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/.gitignore @@ -0,0 +1,5 @@ +node_modules +.dist +output +.cache +.DS_Store diff --git a/src/DevPortal/StellaOps.DevPortal.Site/TASKS.md b/src/DevPortal/StellaOps.DevPortal.Site/TASKS.md new file mode 100644 index 000000000..887777fc8 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/TASKS.md @@ -0,0 +1,12 @@ +# DevPortal Tasks · Sprint 0206.0001.0001 + +Keep this file in sync with `docs/implplan/SPRINT_0206_0001_0001_devportal.md`. + +| Task ID | Status | Notes | Last Updated (UTC) | +| --- | --- | --- | --- | +| DEVPORT-62-001 | DOING | Select SSG, wire aggregate spec, nav/search scaffold. | 2025-11-22 | +| DEVPORT-62-002 | TODO | Schema viewer, examples, copy-curl, version selector. | 2025-11-22 | +| DEVPORT-63-001 | TODO | Try-It console against sandbox; token onboarding UX. | 2025-11-22 | +| DEVPORT-63-002 | TODO | Embed SDK snippets/quick starts from tested examples. | 2025-11-22 | +| DEVPORT-64-001 | TODO | Offline bundle target with specs + SDK archives; zero external assets. | 2025-11-22 | +| DEVPORT-64-002 | TODO | Accessibility tests, link checker, performance budgets. | 2025-11-22 | diff --git a/src/DevPortal/StellaOps.DevPortal.Site/astro.config.mjs b/src/DevPortal/StellaOps.DevPortal.Site/astro.config.mjs new file mode 100644 index 000000000..68ee95997 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/astro.config.mjs @@ -0,0 +1,69 @@ +import { defineConfig } from 'astro/config'; +import mdx from '@astrojs/mdx'; +import starlight from '@astrojs/starlight'; + +export default defineConfig({ + site: 'https://devportal.stellaops.local', + srcDir: 'src', + outDir: 'dist', + trailingSlash: 'never', + integrations: [ + mdx(), + starlight({ + title: 'StellaOps DevPortal', + description: 'Deterministic, offline-first developer portal for the StellaOps platform.', + favicon: { + src: '/logo.svg', + sizes: 'any', + type: 'image/svg+xml', + }, + logo: { + src: '/logo.svg', + alt: 'StellaOps DevPortal', + }, + customCss: ['./src/styles/custom.css'], + social: { + github: 'https://git.stella-ops.org', + }, + search: { + provider: 'local', + algolia: undefined, + }, + sidebar: [ + { + label: 'Overview', + items: [ + { slug: 'index' }, + { slug: 'guides/getting-started' }, + { slug: 'guides/navigation-search' }, + ], + }, + { + label: 'API', + items: [{ slug: 'api-reference' }], + }, + { + label: 'Roadmap', + items: [{ slug: 'release-notes' }], + }, + ], + tableOfContents: { + minHeadingLevel: 2, + maxHeadingLevel: 4, + }, + pagination: true, + editLink: { + baseUrl: 'https://git.stella-ops.org/devportal', + }, + head: [ + { + tag: 'meta', + attrs: { + name: 'theme-color', + content: '#0f172a', + }, + }, + ], + }), + ], +}); diff --git a/src/DevPortal/StellaOps.DevPortal.Site/package-lock.json b/src/DevPortal/StellaOps.DevPortal.Site/package-lock.json new file mode 100644 index 000000000..2ab2d3f39 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/package-lock.json @@ -0,0 +1,8298 @@ +{ + "name": "@stellaops/devportal-site", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@stellaops/devportal-site", + "version": "0.1.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "rapidoc": "9.3.8" + }, + "devDependencies": { + "@astrojs/mdx": "4.3.12", + "@astrojs/starlight": "0.36.2", + "@types/node": "24.10.1", + "astro": "5.16.0", + "typescript": "5.9.3" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@apitools/openapi-parser": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@apitools/openapi-parser/-/openapi-parser-0.0.33.tgz", + "integrity": "sha512-on8oZKkRPrPUvJmmQGpLtlcthNrREH5OjDUK2ZczKuFPOx8Tkn9mzyPc7DTQ7O0JQolaZIwymFmBaajglI6LHA==", + "license": "MIT", + "dependencies": { + "swagger-client": "^3.29.3" + } + }, + "node_modules/@astrojs/compiler": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.0.tgz", + "integrity": "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.5.tgz", + "integrity": "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@astrojs/markdown-remark": { + "version": "6.3.9", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.9.tgz", + "integrity": "sha512-hX2cLC/KW74Io1zIbn92kI482j9J7LleBLGCVU9EP3BeH5MVrnFawOnqD0t/q6D1Z+ZNeQG2gNKMslCcO36wng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/prism": "3.3.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.0", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "shiki": "^3.13.0", + "smol-toml": "^1.4.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.12.tgz", + "integrity": "sha512-pL3CVPtuQrPnDhWjy7zqbOibNyPaxP4VpQS8T8spwKqKzauJ4yoKyNkVTD8jrP7EAJHmBhZ7PTmUGZqOpKKp8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "6.3.9", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.15.0", + "es-module-lexer": "^1.7.0", + "estree-util-visit": "^2.0.0", + "hast-util-to-html": "^9.0.5", + "piccolore": "^0.1.3", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "astro": "^5.0.0" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", + "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.6.0.tgz", + "integrity": "sha512-4aHkvcOZBWJigRmMIAJwRQXBS+ayoP5z40OklTXYXhUDhwusz+DyDl+nSshY6y9DvkVEavwNcFO8FD81iGhXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sitemap": "^8.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^3.25.76" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.36.2.tgz", + "integrity": "sha512-QR8NfO7+7DR13kBikhQwAj3IAoptLLNs9DkyKko2M2l3PrqpcpVUnw1JBJ0msGDIwE6tBbua2UeBND48mkh03w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "^6.3.1", + "@astrojs/mdx": "^4.2.3", + "@astrojs/sitemap": "^3.3.0", + "@pagefind/default-ui": "^1.3.0", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.41.1", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.1", + "hast-util-select": "^6.0.2", + "hast-util-to-string": "^3.0.0", + "hastscript": "^9.0.0", + "i18next": "^23.11.5", + "js-yaml": "^4.1.0", + "klona": "^2.0.6", + "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.3.0", + "rehype": "^13.0.1", + "rehype-format": "^5.0.0", + "remark-directive": "^3.0.0", + "ultrahtml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" + }, + "peerDependencies": { + "astro": "^5.5.0" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", + "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^4.2.0", + "debug": "^4.4.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", + "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.43.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-3.0.1.tgz", + "integrity": "sha512-8XqW8xGn++Eqqbz3e9wKuK7mxryeRjs4LOHLxbh2lwKeSbuNR4NFifDZT4KzvjU6HMOPbiNTsWpniK5EJfTWkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fontkit": "^2.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.3.tgz", + "integrity": "sha512-9qzohqU7O0+JwMEEgQhnBPOw5DtsQRBXhW++5fvEywsuX44vCGGof1SL5OvPElvNgaWZ4pFZAFSlkNOkGyLwSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.41.3.tgz", + "integrity": "sha512-rFQtmf/3N2CK3Cq/uERweMTYZnBu+CwxBdHuOftEmfA9iBE7gTVvwpbh82P9ZxkPLvc40UMhYt7uNuAZexycRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.3" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.41.3.tgz", + "integrity": "sha512-RlTARoopzhFJIOVHLGvuXJ8DCEme/hjV+ZnRJBIxzxsKVpGPW4Oshqg9xGhWTYdHstTsxO663s0cdBLzZj9TQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.3", + "shiki": "^3.2.2" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.41.3.tgz", + "integrity": "sha512-SN8tkIzDpA0HLAscEYD2IVrfLiid6qEdE9QLlGVSxO1KEw7qYvjpbNBQjUjMr5/jvTJ7ys6zysU2vLPHE0sb2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.3" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", + "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", + "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0" + } + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", + "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", + "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.4.0.tgz", + "integrity": "sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", + "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", + "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", + "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", + "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@shikijs/core": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.15.0.tgz", + "integrity": "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.15.0.tgz", + "integrity": "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.15.0.tgz", + "integrity": "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.15.0.tgz", + "integrity": "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.15.0.tgz", + "integrity": "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz", + "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swagger-api/apidom-ast": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-rc.3.tgz", + "integrity": "sha512-lGxvtanmQYqepjVWwPROR/97BIP3sUtwzoHbMSMag2/C3+Un8p6Xz8+I+1sPG2UOBlvDsQe3Di0hlSET7EFwAQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "unraw": "^3.0.0" + } + }, + "node_modules/@swagger-api/apidom-core": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-rc.3.tgz", + "integrity": "sha512-cRf+HzoXl3iDPc7alVxdPbLb1TqRePqsxI0id2KaB8HYbyxTUy3ygqY/jmxGtfAAK0Ba85Bw8j4N0crw23vLTg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "minim": "~0.23.8", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "short-unique-id": "^5.3.2", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-error": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-rc.3.tgz", + "integrity": "sha512-E9WsxzR9wwD4+1zmZm9PVvxXBAYxMtGJjpRYR/FthvxhIwx+Vsey2h5k7FPS8yJsawIrdGPQtdiFMLPvnQXUFg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7" + } + }, + "node_modules/@swagger-api/apidom-json-pointer": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-rc.3.tgz", + "integrity": "sha512-cj83L5ntai/RJcZV0++lQiCHPWE6lTy62bGC2lQ0yi/kyCc+Ig+Sn08qpiLSrkQ4OooK85X+wgAy6pMK+Vt/8Q==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@swaggerexpert/json-pointer": "^2.10.1" + } + }, + "node_modules/@swagger-api/apidom-ns-api-design-systems": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-rc.3.tgz", + "integrity": "sha512-JB06VDEKPvyOcJ9qIJmr2vI2FSWjdZh+BiRExZPW4tv/mTvdOxt1n38WA+mKzfFHQuoTR4ork/wR481CjAfGGQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-arazzo-1": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.0.0-rc.3.tgz", + "integrity": "sha512-Um0MGGsGLQWvnASDoguSuE5X/NpS/9RlXlOHHG5nqzG2cdTlifRcN5tiz7H997162+ahEsD5aHD6tUKWOPCLtQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-asyncapi-2": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-rc.3.tgz", + "integrity": "sha512-UFmnbvEsN7jVvS/8V7X37UPvn8uxdqYBhDzdPSivjxpu/5Ag5Q1P2gHJnO6K2EfTCFL4S1qDObW2TUFdV1b6pg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-2019-09": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.0.0-rc.3.tgz", + "integrity": "sha512-fxQo/GK5NGdx4gN2snj4DpBcDc8bORLehTUqcwp33ikJ2PGugtpV3IQrBjxSWP05PyLOZAMpq1SM9gkCPgZNRA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-2020-12": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.0.0-rc.3.tgz", + "integrity": "sha512-iDPbua9HajFwkH9vFUIbkmKVI/VXKuV9G+jLGkyBlF/Zu++1Rv6CstBt+F9CgNThSUqkKt3YA9Rcd82uh1+HnQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-json-schema-2019-09": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-rc.3.tgz", + "integrity": "sha512-8lft8qCo/KAHqiUpfwUMifP9JDhuhXKMNYSSahP2SN0PnbujoS1h3DOXtpR9/+0N6fKPUT8I6GLEwgq8TX2yvA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.0-rc.3", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-rc.3.tgz", + "integrity": "sha512-IDC+98ur+7L3YaZZnnCytx9+cihElj24CcjX/X2mOBqOTaAwZ/Exb7LiBnvUswV1lOE2X2CX4donRemjk+e32Q==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-rc.3.tgz", + "integrity": "sha512-P0dk9WhH7CINBCh1u8GfcQFycrZcw3qCXug0w6M0wiSrjqZv+Mv/AI68dc0Rb+Dzshe4aZy0bZFjAQb3NHfrSg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-2": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-rc.3.tgz", + "integrity": "sha512-zwriSfjG+qiPWBHLZRyfdZa305xrB24aZjiAY8r2ikZsdQhC/WHI+e6YqeVCkJwkLzA/oZgrlmyci0mvtkFDQA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-0": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-rc.3.tgz", + "integrity": "sha512-RCufXt7ja7fqFS/EqWOMZ54J4uEnqPQkCXMwwCqUrFHXQ7nGN1J9nmwj2hFQUFYraajmtnk2dNByO46+XefV1w==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-1": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-rc.3.tgz", + "integrity": "sha512-Nc28G/ikbypcXVricv8+PGEGXKAmOwZjkBxB3wN5D4+D0+AiUy1lV07Z7+xFWdql65Y5WWxxfU2/Ej01Bnqt4Q==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.0-rc.3", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-json-pointer": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.0.0-rc.3.tgz", + "integrity": "sha512-ZXKuMd6nqBrpCqTJmbd2pS46ZmL8bIra1KqWVjcvkA/E032nmgDeaT78Cf0Ulha6j+CAzcwL0AnR7GrtFpSfSw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.0-rc.3.tgz", + "integrity": "sha512-Qg1yTPPzGF3EhlqcxIZeDVBxxvZzylGM6CTHg5cltGOSoFQ7+NJFE9Ktvk0gbVaFUyElFduCno9FvIfzxPlj8g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-arazzo-json-1": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.0.0-rc.3.tgz", + "integrity": "sha512-T7MbfTSDqdHgSr+cSC6gcGIsiwK3NXmdo28ZUv6LWsgcWDj2zw2Jie+7rXQaDN3JFEL34M/BIcMLyvrG7gYN/Q==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-arazzo-yaml-1": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.0.0-rc.3.tgz", + "integrity": "sha512-mUmxQVXPoemP2ak/77g/o8kpP2DNd1EDjteuyGHyw1EHk/t4xYPAP05rQ2DfIQ5yVHmxBKRDQ15kfVNEpfUfYQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.0-rc.3.tgz", + "integrity": "sha512-K2BaslenC4ouPyzOQSB7wQPSsIGKGIj4VfP4M9y3fJaX9dIi+z3kzYQV7NFhZHAnq6pVybIDA44FLHF/WLCxUg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.0-rc.3.tgz", + "integrity": "sha512-xJezoi5d+RtV7sG9VRcfpbLlJwaR6GoJr2S8lbsnMUkk/B2vZGdRbA2Fc67REQIJTEfxXcU8T3+5m8j0WrG9Xw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.0-rc.3.tgz", + "integrity": "sha512-Y0dfIYvQE+OLjormlx6RjmA6ymNA6+nkqJC/6qkFt+4fSjfOiXwbOOnfZp9pJXb2ssmDDdrPTFc3ninx5k7jNw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.0-rc.3", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.21.1", + "tree-sitter-json": "=0.24.8", + "web-tree-sitter": "=0.24.5" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.0-rc.3.tgz", + "integrity": "sha512-yaMS11FZVJLF062s+dch1kmUvBqdIS6mwAg/4XUL7XwSYat6pnV2ONCqdcUO9JSc9KJMZQiVAZjAZSj096ssNg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.0.0-rc.3.tgz", + "integrity": "sha512-5OdImG3eEgYpFvSo0EiZVvJJahk+f6cm5WZNn9lVdRlmxmtpzKM3UNfIYcBgVcAcLvfi8g6G7xRzD1DshaS8sw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.0-rc.3.tgz", + "integrity": "sha512-UWlH29DOqKfHF2zwv7r5b7pgrc7Yxdus7FjYWA8p8yoIB02xDwHBaH4KhccIAXkm1qNMo+4TwSKFvO/boE8LMA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.0-rc.3.tgz", + "integrity": "sha512-kSWzmalm98ScImQHHtpTBDAIEzLsfE24Pe1IIJP1TaI2rk1AuxzaCsqMl6NQIlnIEawghPOXlG0hLsgtswn/Jg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.0-rc.3.tgz", + "integrity": "sha512-IRxjOgmGpaA1ay/NITOqk3TKTXnGiJtNP8KsPm//i+HkGcg87lZEvRDflB2Z70aRofKncXM2rCMAEqFqV7A9ug==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.0-rc.3.tgz", + "integrity": "sha512-uvDMPiKt7uZSAOUVe+q/AygTFXw1odxxu5mi5voQM3/0KbR/vlt8f1dO9sQkys+G6ped2nL4r8B0p6bXR8uAMQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-rc.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.0-rc.3.tgz", + "integrity": "sha512-IiLIw74NRpRwi2YkV1hzmHC5JvvAm/TdeVYZoYK0QxeT2Ozr6MvhnUnRFjjSL3wcmku9+rLz2d8EGL2kO46qRA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.0-rc.3", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@tree-sitter-grammars/tree-sitter-yaml": "=0.7.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.22.4", + "web-tree-sitter": "=0.24.5" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/@tree-sitter-grammars/tree-sitter-yaml": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@tree-sitter-grammars/tree-sitter-yaml/-/tree-sitter-yaml-0.7.1.tgz", + "integrity": "sha512-AynBwkIoQCTgjDR33bDUp9Mqq+YTco0is3n5hRApMqG9of/6A4eQsfC1/uSEeHSUyMQSYawcAWamsexnVpIP4Q==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.3.1", + "node-gyp-build": "^4.8.4" + }, + "peerDependencies": { + "tree-sitter": "^0.22.4" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/tree-sitter": { + "version": "0.22.4", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.4.tgz", + "integrity": "sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + } + }, + "node_modules/@swagger-api/apidom-reference": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-rc.3.tgz", + "integrity": "sha512-xZ9B6lGpdlHGSZGEhYe/MAyULCN4d+w4LKK5P1C/i6W6AU4iDEMjMjSawRV9ptJcObnu9ArEe92rgI7XS6s0TQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-rc.3", + "@swagger-api/apidom-error": "^1.0.0-rc.3", + "@types/ramda": "~0.30.0", + "axios": "^1.12.2", + "minimatch": "^7.4.3", + "process": "^0.11.10", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + }, + "optionalDependencies": { + "@swagger-api/apidom-json-pointer": "^1.0.0-rc.0", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.0-rc.0", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-rc.0" + } + }, + "node_modules/@swaggerexpert/cookie": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@swaggerexpert/cookie/-/cookie-2.0.2.tgz", + "integrity": "sha512-DPI8YJ0Vznk4CT+ekn3rcFNq1uQwvUHZhH6WvTSPD0YKBIlMS9ur2RYKghXuxxOiqOam/i4lHJH4xTIiTgs3Mg==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.3" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@swaggerexpert/json-pointer": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@swaggerexpert/json-pointer/-/json-pointer-2.10.2.tgz", + "integrity": "sha512-qMx1nOrzoB+PF+pzb26Q4Tc2sOlrx9Ba2UBNX9hB31Omrq+QoZ2Gly0KLrQWw4Of1AQ4J9lnD+XOdwOdcdXqqw==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/fontkit": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/fontkit/-/fontkit-2.0.8.tgz", + "integrity": "sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/ramda": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", + "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", + "license": "MIT", + "dependencies": { + "types-ramda": "^0.30.1" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/apg-lite": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/apg-lite/-/apg-lite-1.0.5.tgz", + "integrity": "sha512-SlI+nLMQDzCZfS39ihzjGp3JNBQfJXyMi6cg9tkLOCPVErgFsUIAEdO9IezR7kbP5Xd0ozcPNQBkf9TO5cHgWw==", + "license": "BSD-2-Clause" + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "dev": true, + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.16.0.tgz", + "integrity": "sha512-GaDRs2Mngpw3dr2vc085GnORh98NiXxwIjg/EoQQQl/icZt3Z7s0BRsYHDZ8swkZbOA6wZsqWJdrNirl+iKcDg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@astrojs/compiler": "^2.13.0", + "@astrojs/internal-helpers": "0.7.5", + "@astrojs/markdown-remark": "6.3.9", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^3.0.1", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "acorn": "^8.15.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.3.1", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^1.0.2", + "cssesc": "^3.0.0", + "debug": "^4.4.3", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.5.0", + "diff": "^5.2.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.7.0", + "esbuild": "^0.25.0", + "estree-walker": "^3.0.3", + "flattie": "^1.1.1", + "fontace": "~0.3.1", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.1", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "p-limit": "^6.2.0", + "p-queue": "^8.1.1", + "package-manager-detector": "^1.5.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.3", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.7.3", + "shiki": "^3.15.0", + "smol-toml": "^1.5.0", + "svgo": "^4.0.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tsconfck": "^3.1.6", + "ultrahtml": "^1.6.0", + "unifont": "~0.6.0", + "unist-util-visit": "^5.0.0", + "unstorage": "^1.17.2", + "vfile": "^6.0.3", + "vite": "^6.4.1", + "vitefu": "^1.1.1", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "yocto-spinner": "^0.2.3", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.24.6", + "zod-to-ts": "^1.2.0" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.3.tgz", + "integrity": "sha512-u+zHMqo/QNLE2eqYRCrK3+XMlKakv33Bzuz+56V1gs8H0y6TZ0hIi3VNbIxeTn51NLn+mJfUV/A0kMNfE4rANw==", + "dev": true, + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.41.3" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "dev": true, + "license": "ISC" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js-pure": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.47.0.tgz", + "integrity": "sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-selector-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.2.0.tgz", + "integrity": "sha512-L1bdkNKUP5WYxiW5dW6vA2hd3sL8BdRNLy2FCX0rLVise4eNw9nBdeBuJHxlELieSE2H1f6bYQFfwVUwWCV9rQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz", + "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "dev": true, + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.3.tgz", + "integrity": "sha512-YLnD62jfgBZYrXIPQcJ0a51Afv9h8VlWqEGK9uU2T5nL/5rb8SnA86+7+mgCZe5D34Tff5RNEA5hjNVJYHzrFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.41.3", + "@expressive-code/plugin-frames": "^0.41.3", + "@expressive-code/plugin-shiki": "^0.41.3", + "@expressive-code/plugin-text-markers": "^0.41.3" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fontace": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.3.1.tgz", + "integrity": "sha512-9f5g4feWT1jWT8+SbL85aLIRLIXUaDygaM2xPXRmzPYxrOMNok79Lr3FGJoKVNKibE0WCunNiEVG2mwuE+2qEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fontkit": "^2.0.8", + "fontkit": "^2.0.4" + } + }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "dev": true, + "license": "ISC" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/h3": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", + "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.2", + "radix3": "^1.1.2", + "ufo": "^1.6.1", + "uncrypto": "^0.1.3" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "dev": true, + "license": "MIT" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/lit": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz", + "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz", + "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.1.tgz", + "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minim": { + "version": "0.23.8", + "resolved": "https://registry.npmjs.org/minim/-/minim-0.23.8.tgz", + "integrity": "sha512-bjdr2xW1dBCMsMGGsUeqM4eFI60m94+szhxWys+B1ztIt6gWSfeGBdSVCIawezeHYLYn0j6zrsXdQS/JllBzww==", + "license": "MIT", + "dependencies": { + "lodash": "^4.15.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch-commonjs": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.3.2.tgz", + "integrity": "sha512-VBlAiynj3VMLrotgwOS3OyECFxas5y7ltLcK4t41lMUZeaK15Ym4QRkqN0EQKAFL42q9i21EPKjzLUPfltR72A==", + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-mock-http": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.3.tgz", + "integrity": "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/openapi-path-templating": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/openapi-path-templating/-/openapi-path-templating-2.2.1.tgz", + "integrity": "sha512-eN14VrDvl/YyGxxrkGOHkVkWEoPyhyeydOUrbvjoz8K5eIGgELASwN1eqFOJ2CTQMGCy2EntOK1KdtJ8ZMekcg==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/openapi-server-url-templating": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/openapi-server-url-templating/-/openapi-server-url-templating-1.3.0.tgz", + "integrity": "sha512-DPlCms3KKEbjVQb0spV6Awfn6UWNheuG/+folQPzh/wUaKwuqvj8zt5gagD7qoyxtE03cIiKPgLFS3Q8Bz00uQ==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", + "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", + "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", + "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", + "dev": true, + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.4.0", + "@pagefind/darwin-x64": "1.4.0", + "@pagefind/freebsd-x64": "1.4.0", + "@pagefind/linux-arm64": "1.4.0", + "@pagefind/linux-x64": "1.4.0", + "@pagefind/windows-x64": "1.4.0" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "dev": true, + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", + "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, + "node_modules/ramda-adjunct": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", + "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda-adjunct" + }, + "peerDependencies": { + "ramda": ">= 0.30.0" + } + }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "license": "MIT", + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/rapidoc": { + "version": "9.3.8", + "resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.3.8.tgz", + "integrity": "sha512-eCYEbr1Xr8OJZvVCw8SXl9zBCRoLJbhNGuG5IZTHq/RWAOq/O4MafUCuFEyZHsrhLrlUcGZMa64pyhpib8fQKQ==", + "license": "MIT", + "dependencies": { + "@apitools/openapi-parser": "0.0.33", + "base64-arraybuffer": "^1.0.2", + "buffer": "^6.0.3", + "lit": "^3.2.1", + "marked": "4.3.0", + "prismjs": "^1.29.0", + "randexp": "^0.5.3", + "xml-but-prettier": "^1.0.1" + }, + "engines": { + "node": ">=18.16.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.41.3.tgz", + "integrity": "sha512-8d9Py4c/V6I/Od2VIXFAdpiO2kc0SV2qTJsRAaqSIcM9aruW4ASLNe2kOEo1inXAAkIhpFzAHTc358HKbvpNUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expressive-code": "^0.41.3" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shiki": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.15.0.tgz", + "integrity": "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.15.0", + "@shikijs/engine-javascript": "3.15.0", + "@shikijs/engine-oniguruma": "3.15.0", + "@shikijs/langs": "3.15.0", + "@shikijs/themes": "3.15.0", + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/short-unique-id": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.3.2.tgz", + "integrity": "sha512-KRT/hufMSxXKEDSQujfVE0Faa/kZ51ihUcZQAcmP04t00DvPj7Ox5anHke1sJYUtzSuiT/Y5uyzg/W7bBEGhCg==", + "license": "Apache-2.0", + "bin": { + "short-unique-id": "bin/short-unique-id", + "suid": "bin/short-unique-id" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.2.tgz", + "integrity": "sha512-LwktpJcyZDoa0IL6KT++lQ53pbSrx2c9ge41/SeLTyqy2XUNA6uR4+P9u5IVo5lPeL2arAcOKn1aZAxoYbCKlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.5.2.tgz", + "integrity": "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/svgo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.4.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/swagger-client": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.36.0.tgz", + "integrity": "sha512-9fkjxGHXuKy20jj8zwE6RwgFSOGKAyOD5U7aKgW/+/futtHZHOdZeqiEkb97sptk2rdBv7FEiUQDNlWZR186RA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.22.15", + "@scarf/scarf": "=1.4.0", + "@swagger-api/apidom-core": "^1.0.0-rc.1", + "@swagger-api/apidom-error": "^1.0.0-rc.1", + "@swagger-api/apidom-json-pointer": "^1.0.0-rc.1", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-rc.1", + "@swagger-api/apidom-reference": "^1.0.0-rc.1", + "@swaggerexpert/cookie": "^2.0.2", + "deepmerge": "~4.3.0", + "fast-json-patch": "^3.0.0-1", + "js-yaml": "^4.1.0", + "neotraverse": "=0.6.18", + "node-abort-controller": "^3.1.1", + "node-fetch-commonjs": "^3.3.2", + "openapi-path-templating": "^2.2.1", + "openapi-server-url-templating": "^1.3.0", + "ramda": "^0.30.1", + "ramda-adjunct": "^5.1.0" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-sitter": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", + "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "node_modules/tree-sitter-json": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.24.8.tgz", + "integrity": "sha512-Tc9ZZYwHyWZ3Tt1VEw7Pa2scu1YO7/d2BCBbKTx5hXwig3UfdQjsOPkPyLpDJOn/m1UBEWYAtSdGAwCSyagBqQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.2.2", + "node-gyp-build": "^4.8.2" + }, + "peerDependencies": { + "tree-sitter": "^0.21.1" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", + "license": "Apache-2.0" + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/types-ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", + "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "license": "MIT", + "dependencies": { + "ts-toolbelt": "^9.6.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.6.0.tgz", + "integrity": "sha512-5Fx50fFQMQL5aeHyWnZX9122sSLckcDvcfFiBf3QYeHa7a1MKJooUy52b67moi2MJYkrfo/TWY+CoLdr/w0tTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0", + "ofetch": "^1.4.1", + "ohash": "^2.0.0" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unraw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", + "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==", + "license": "MIT" + }, + "node_modules/unstorage": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.3.tgz", + "integrity": "sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.24.5", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.24.5.tgz", + "integrity": "sha512-+J/2VSHN8J47gQUAvF8KDadrfz6uFYVjxoxbKWDoXVsH2u7yLdarCnIURnrMA6uSRkgX3SdmqM5BOoQjPdSh5w==", + "license": "MIT", + "optional": true + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xml-but-prettier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz", + "integrity": "sha512-C2CJaadHrZTqESlH03WOyw0oZTtoy2uEg6dSDF6YRg+9GnYNub53RRemLpnvtbHDFelxMx4LajiFsYeR6XJHgQ==", + "license": "MIT", + "dependencies": { + "repeat-string": "^1.5.2" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-spinner": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", + "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + }, + "node_modules/zod-to-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", + "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", + "dev": true, + "peerDependencies": { + "typescript": "^4.9.4 || ^5.0.2", + "zod": "^3" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/src/DevPortal/StellaOps.DevPortal.Site/package.json b/src/DevPortal/StellaOps.DevPortal.Site/package.json new file mode 100644 index 000000000..95c409fe8 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/package.json @@ -0,0 +1,29 @@ +{ + "name": "@stellaops/devportal-site", + "version": "0.1.0", + "private": true, + "type": "module", + "license": "AGPL-3.0-or-later", + "engines": { + "node": ">=18.18.0" + }, + "scripts": { + "dev": "astro dev", + "start": "astro dev --host", + "build": "astro build", + "preview": "astro preview", + "check": "astro check", + "sync:spec": "node scripts/sync-spec.mjs", + "prepare:static": "npm run sync:spec && astro check" + }, + "dependencies": { + "rapidoc": "9.3.8" + }, + "devDependencies": { + "@astrojs/mdx": "4.3.12", + "@astrojs/starlight": "0.36.2", + "@types/node": "24.10.1", + "astro": "5.16.0", + "typescript": "5.9.3" + } +} diff --git a/src/DevPortal/StellaOps.DevPortal.Site/public/api/stella.yaml b/src/DevPortal/StellaOps.DevPortal.Site/public/api/stella.yaml new file mode 100644 index 000000000..b136fcf07 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/public/api/stella.yaml @@ -0,0 +1,1542 @@ +components: + parameters: + CursorParam: + example: eyJyIjoiMjAyNS0xMS0xOC0wMDIifQ + in: query + name: cursor + required: false + schema: + type: string + LimitParam: + example: 50 + in: query + name: limit + required: false + schema: + maximum: 200 + minimum: 1 + type: integer + TenantParam: + description: Filter results to a specific tenant identifier. + example: acme + in: query + name: tenant + required: false + schema: + type: string + responses: + ErrorResponse: + content: + application/json: + schema: + $ref: ../schemas/common.yaml#/schemas/ErrorEnvelope + description: Error envelope + HealthResponse: + content: + application/json: + schema: + $ref: ../schemas/common.yaml#/schemas/HealthEnvelope + description: Health envelope + schemas: + ErrorEnvelope: + properties: + code: + example: service_unavailable + type: string + message: + type: string + traceId: + description: Correlation identifier for troubleshooting + type: string + required: + - code + - message + type: object + HealthEnvelope: + properties: + service: + example: any-service + type: string + status: + example: ok + type: string + required: + - status + - service + type: object + PageMetadata: + properties: + hasMore: + description: Indicates if additional pages are available. + type: boolean + nextCursor: + description: Cursor to fetch the next page. + type: string + previousCursor: + description: Cursor to fetch the previous page. + type: string + required: + - hasMore + type: object + authority.ClientCredentialsGrantRequest: + description: Form-encoded payload for client credentials exchange. + properties: + authority_provider: + description: Optional identity provider hint for plugin-backed clients. + type: string + client_id: + description: Registered client identifier. May also be supplied via HTTP Basic + auth. + type: string + client_secret: + description: Client secret. Required for confidential clients when not using + HTTP Basic auth. + type: string + grant_type: + const: client_credentials + type: string + operator_reason: + description: Required when requesting `orch:operate`; explains the operator + action. + maxLength: 256 + type: string + operator_ticket: + description: Required when requesting `orch:operate`; tracks the external change + ticket or incident. + maxLength: 128 + type: string + scope: + description: Space-delimited scopes being requested. + type: string + required: + - grant_type + - client_id + type: object + authority.IntrospectionRequest: + description: Form-encoded payload for token introspection. + properties: + token: + description: Token value whose state should be introspected. + type: string + token_type_hint: + description: Optional token type hint (`access_token` or `refresh_token`). + type: string + required: + - token + type: object + authority.IntrospectionResponse: + description: Active token descriptor compliant with RFC 7662. + properties: + active: + description: Indicates whether the token is currently active. + type: boolean + aud: + description: Audience values associated with the token. + items: + type: string + type: array + client_id: + description: Client identifier associated with the token. + type: string + confirmation: + description: Sender-constrained confirmation data (e.g., mTLS thumbprint, DPoP + JWK thumbprint). + type: object + exp: + description: Expiration timestamp (seconds since UNIX epoch). + type: integer + iat: + description: Issued-at timestamp (seconds since UNIX epoch). + type: integer + iss: + description: Issuer identifier. + type: string + jti: + description: JWT identifier corresponding to the token. + type: string + nbf: + description: Not-before timestamp (seconds since UNIX epoch). + type: integer + scope: + description: Space-delimited list of scopes granted to the token. + type: string + sub: + description: Subject identifier when the token represents an end-user. + type: string + tenant: + description: Tenant associated with the token, when assigned. + type: string + token_type: + description: Type of the token (e.g., `Bearer`). + type: string + username: + description: Preferred username associated with the subject. + type: string + required: + - active + type: object + authority.Jwk: + description: Public key material for token signature validation. + properties: + alg: + description: Signing algorithm (e.g., `ES384`). + type: string + crv: + description: Elliptic curve identifier when applicable. + type: string + kid: + description: Key identifier. + type: string + kty: + description: Key type (e.g., `EC`, `RSA`). + type: string + status: + description: Operational status metadata for the key (e.g., `active`, `retiring`). + type: string + use: + description: Intended key use (`sig`). + type: string + x: + description: X coordinate for EC keys. + type: string + y: + description: Y coordinate for EC keys. + type: string + type: object + authority.JwksDocument: + description: JSON Web Key Set published by the Authority. + properties: + keys: + items: + $ref: "#/components/schemas/authority.Jwk" + type: array + required: + - keys + type: object + authority.OAuthErrorResponse: + description: RFC 6749 compliant error envelope. + properties: + error: + description: Machine-readable error code. + type: string + error_description: + description: Human-readable error description. + type: string + error_uri: + description: Link to documentation about the error. + format: uri + type: string + required: + - error + type: object + authority.PasswordGrantRequest: + description: Form-encoded payload for password grant exchange. + properties: + authority_provider: + description: Optional identity provider hint. Required when multiple + password-capable providers are registered. + type: string + client_id: + description: Registered client identifier. May also be supplied via HTTP Basic + auth. + type: string + client_secret: + description: Client secret. Required for confidential clients when not using + HTTP Basic auth. + type: string + grant_type: + const: password + type: string + password: + description: Resource owner password. + type: string + scope: + description: Space-delimited scopes being requested. + type: string + username: + description: Resource owner username. + type: string + required: + - grant_type + - client_id + - username + - password + type: object + authority.RefreshTokenGrantRequest: + description: Form-encoded payload for refresh token exchange. + properties: + client_id: + description: Registered client identifier. May also be supplied via HTTP Basic + auth. + type: string + client_secret: + description: Client secret. Required for confidential clients when not using + HTTP Basic auth. + type: string + grant_type: + const: refresh_token + type: string + refresh_token: + description: Previously issued refresh token. + type: string + scope: + description: Optional scope list to narrow the requested access. + type: string + required: + - grant_type + - refresh_token + type: object + authority.RevocationRequest: + description: Form-encoded payload for token revocation. + properties: + token: + description: Token value or token identifier to revoke. + type: string + token_type_hint: + description: Optional token type hint (`access_token` or `refresh_token`). + type: string + required: + - token + type: object + authority.TokenResponse: + description: OAuth 2.1 bearer token response. + properties: + access_token: + description: Access token encoded as JWT. + type: string + expires_in: + description: Lifetime of the access token, in seconds. + minimum: 1 + type: integer + id_token: + description: ID token issued for authorization-code flows. + type: string + refresh_token: + description: Refresh token issued when the grant allows offline access. + type: string + scope: + description: Space-delimited scopes granted in the response. + type: string + token_type: + description: Token type indicator. Always `Bearer`. + type: string + required: + - access_token + - token_type + - expires_in + type: object + export-center.BundleManifest: + properties: + bundleId: + type: string + contents: + items: + properties: + digest: + example: sha256:abc123 + type: string + type: + example: advisory + type: string + required: + - type + - digest + type: object + type: array + createdAt: + format: date-time + type: string + required: + - bundleId + - contents + type: object + export-center.BundleSummary: + properties: + bundleId: + type: string + createdAt: + format: date-time + type: string + sizeBytes: + type: integer + status: + enum: + - ready + - building + - failed + type: string + required: + - bundleId + - createdAt + - status + type: object + export-center.Error: + $ref: "#/components/schemas/ErrorEnvelope" + export-center.HealthResponse: + $ref: "#/components/schemas/HealthEnvelope" + graph.GraphNodePage: + properties: + metadata: + $ref: "#/components/schemas/PageMetadata" + nodes: + items: + properties: + id: + type: string + kind: + type: string + label: + type: string + required: + - id + - kind + - label + type: object + type: array + required: + - nodes + - metadata + type: object + graph.GraphStatus: + properties: + builtAt: + format: date-time + type: string + graphId: + type: string + status: + enum: + - building + - ready + - failed + type: string + required: + - graphId + - status + type: object + orchestrator.JobSummary: + properties: + completedAt: + format: date-time + type: string + enqueuedAt: + format: date-time + type: string + jobId: + type: string + queue: + type: string + startedAt: + format: date-time + type: string + status: + enum: + - queued + - running + - failed + - completed + type: string + tenant: + type: string + required: + - jobId + - status + - queue + - enqueuedAt + type: object + policy.EvaluationRequest: + properties: + artifactId: + example: registry.stella-ops.local/runtime/api + type: string + inputs: + type: object + policyVersion: + example: 2025.10.1 + type: string + required: + - artifactId + type: object + policy.EvaluationResponse: + properties: + decision: + enum: + - allow + - deny + type: string + obligations: + items: + type: object + type: array + policyVersion: + type: string + reasons: + items: + type: string + type: array + traceId: + type: string + required: + - decision + type: object + scheduler.QueueStatus: + properties: + depth: + type: integer + inflight: + type: integer + name: + type: string + oldestAgeSeconds: + type: integer + updatedAt: + format: date-time + type: string + required: + - name + - depth + - inflight + - updatedAt + type: object + securitySchemes: + BearerAuth: + bearerFormat: JWT + scheme: bearer + type: http + OAuthClientCredentials: + description: OAuth 2.1 client credentials flow scoped per service. + flows: + clientCredentials: + scopes: {} + tokenUrl: /token + type: oauth2 +info: + description: Composed OpenAPI from per-service specs. This file is generated by + compose.mjs. + title: StellaOps Aggregate API + version: 0.0.1 +openapi: 3.1.0 +paths: + /authority/introspect: + post: + description: Returns the active status and claims for a given token. Requires a + privileged client. + requestBody: + content: + application/x-www-form-urlencoded: + examples: + introspectToken: + summary: Validate an access token issued to Orchestrator + value: + token: eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9... + token_type_hint: access_token + schema: + $ref: "#/components/schemas/authority.IntrospectionRequest" + required: true + responses: + "200": + content: + application/json: + examples: + activeToken: + summary: Active token response + value: + active: true + aud: + - https://orch.stellaops.local + client_id: orch-control + confirmation: + mtls_thumbprint: 079871b8c9a0f2e6 + exp: 1761628800 + iat: 1761625200 + iss: https://authority.stellaops.local + jti: 01J8KYRAMG7FWBPRRV5XG20T7S + nbf: 1761625200 + scope: orch:operate orch:read + sub: operator-7f12 + tenant: tenant-alpha + token_type: Bearer + username: ops.engineer@tenant.example + inactiveToken: + summary: Revoked token response + value: + active: false + schema: + $ref: "#/components/schemas/authority.IntrospectionResponse" + description: Token state evaluated. + "400": + content: + application/json: + examples: + missingToken: + summary: Token missing + value: + error: invalid_request + error_description: token parameter is required. + schema: + $ref: "#/components/schemas/authority.OAuthErrorResponse" + description: Malformed request. + "401": + content: + application/json: + examples: + unauthorizedClient: + summary: Client not allowed to introspect tokens + value: + error: invalid_client + error_description: Client authentication failed. + schema: + $ref: "#/components/schemas/authority.OAuthErrorResponse" + description: Client authentication failed or client lacks introspection + permission. + security: + - ClientSecretBasic: [] + summary: Introspect token state + tags: + - Authentication + x-original-path: /introspect + x-service: authority + /authority/jwks: + get: + description: Returns the JSON Web Key Set used to validate Authority-issued tokens. + responses: + "200": + content: + application/json: + examples: + ecKeySet: + summary: EC signing keys + value: + keys: + - alg: ES384 + crv: P-384 + kid: auth-tokens-es384-202510 + kty: EC + status: active + use: sig + x: 7UchU5R77LtChrJx6uWg9mYjFvV6RIpSgZPDIj7d1q0 + y: v98nHe8a7mGZ9Fn1t4Jp9PTJv1ma35QPmhUrE4pH7H0 + - alg: ES384 + crv: P-384 + kid: auth-tokens-es384-202409 + kty: EC + status: retiring + use: sig + x: hjdKc0r8jvVHJ7S9mP0y0mU9bqN7v5PxS21SwclTzfc + y: yk6J3pz4TUpymN4mG-6th3dYvJ5N1lQvDK0PLuFv3Pg + schema: + $ref: "#/components/schemas/authority.JwksDocument" + description: JWKS document. + headers: + Cache-Control: + description: Standard caching headers apply; keys rotate infrequently. + schema: + type: string + summary: Retrieve signing keys + tags: + - Keys + x-original-path: /jwks + x-service: authority + /authority/revoke: + post: + requestBody: + content: + application/x-www-form-urlencoded: + examples: + revokeRefreshToken: + summary: Revoke refresh token after logout + value: + token: 0.rg9pVlsGzXE8Q + token_type_hint: refresh_token + schema: + $ref: "#/components/schemas/authority.RevocationRequest" + required: true + responses: + "200": + description: Token revoked or already invalid. The response body is + intentionally blank. + "400": + content: + application/json: + examples: + missingToken: + summary: Token parameter omitted + value: + error: invalid_request + error_description: The revocation request is missing the token parameter. + schema: + $ref: "#/components/schemas/authority.OAuthErrorResponse" + description: Malformed request. + "401": + content: + application/json: + examples: + badClientSecret: + summary: Invalid client credentials + value: + error: invalid_client + error_description: Client authentication failed. + schema: + $ref: "#/components/schemas/authority.OAuthErrorResponse" + description: Client authentication failed. + security: + - ClientSecretBasic: [] + summary: Revoke an access or refresh token + tags: + - Authentication + x-original-path: /revoke + x-service: authority + /authority/token: + post: + description: > + Issues OAuth 2.1 bearer tokens for StellaOps clients. Supports password, + client credentials, + + authorization-code, device, and refresh token grants. Confidential + clients must authenticate using + + HTTP Basic auth or `client_secret` form fields. + requestBody: + content: + application/x-www-form-urlencoded: + encoding: + authority_provider: + explode: false + style: form + examples: + authorizationCode: + summary: Authorization code exchange for Console UI session + value: + client_id: console-ui + code: 2Lba1WtwPLfZ2b0Z9uPrsQ + code_verifier: g3ZnL91QJ6i4zO_86oI4CDnZ7gS0bSeK + grant_type: authorization_code + redirect_uri: https://console.stellaops.local/auth/callback + clientCredentials: + summary: Client credentials exchange for Policy Engine + value: + client_id: policy-engine + client_secret: 9c39f602-2f2b-4f29 + grant_type: client_credentials + operator_reason: Deploying policy change 1234 + operator_ticket: CHG-004211 + scope: effective:write findings:read + passwordGrant: + summary: Password grant for tenant-scoped ingestion bot + value: + authority_provider: primary-directory + client_id: ingest-cli + client_secret: s3cr3t + grant_type: password + password: pa55w0rd! + scope: advisory:ingest vex:ingest + username: ingest-bot + refreshToken: + summary: Refresh token rotation for console session + value: + client_id: console-ui + grant_type: refresh_token + refresh_token: 0.rg9pVlsGzXE8Q + schema: + oneOf: + - $ref: "#/components/schemas/authority.PasswordGrantRequest" + - $ref: "#/components/schemas/authority.ClientCredentialsGrantRequest" + - $ref: "#/components/schemas/authority.RefreshTokenGrantRequest" + required: true + responses: + "200": + content: + application/json: + examples: + authorizationCode: + summary: Authorization code success response + value: + access_token: eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9... + expires_in: 900 + id_token: eyJhbGciOiJFUzM4NCIsImtpZCI6ImNvbnNvbGUifQ... + refresh_token: VxKpc9Vj9QjYV6gLrhQHTw + scope: ui.read authority:tenants.read + token_type: Bearer + clientCredentials: + summary: Client credentials success response + value: + access_token: eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9... + expires_in: 900 + scope: effective:write findings:read + token_type: Bearer + passwordGrant: + summary: Password grant success response + value: + access_token: eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9... + expires_in: 3600 + refresh_token: OxGdVtZJ-mk49cFd38uRUw + scope: advisory:ingest vex:ingest + token_type: Bearer + schema: + $ref: "#/components/schemas/authority.TokenResponse" + description: Token exchange succeeded. + "400": + content: + application/json: + examples: + invalidProvider: + summary: Unknown identity provider hint + value: + error: invalid_request + error_description: Unknown identity provider 'legacy-directory'. + invalidScope: + summary: Scope not permitted for client + value: + error: invalid_scope + error_description: Scope 'effective:write' is not permitted for this client. + schema: + $ref: "#/components/schemas/authority.OAuthErrorResponse" + description: Malformed request, unsupported grant type, or invalid credentials. + "401": + content: + application/json: + examples: + badClientSecret: + summary: Invalid client secret + value: + error: invalid_client + error_description: Client authentication failed. + schema: + $ref: "#/components/schemas/authority.OAuthErrorResponse" + description: Client authentication failed. + security: + - ClientSecretBasic: [] + - {} + summary: Exchange credentials for tokens + tags: + - Authentication + x-original-path: /token + x-service: authority + /export-center/bundles: + get: + parameters: + - $ref: ../_shared/parameters/tenant.yaml#/parameters/TenantParam + - $ref: ../_shared/parameters/paging.yaml#/parameters/LimitParam + - $ref: ../_shared/parameters/paging.yaml#/parameters/CursorParam + responses: + "200": + content: + application/json: + examples: + page: + summary: First page of bundles + value: + items: + - bundleId: bundle-2025-11-18-001 + createdAt: 2025-11-18T12:00:00Z + sizeBytes: 1048576 + status: ready + - bundleId: bundle-2025-11-18-000 + createdAt: 2025-11-18T10:00:00Z + sizeBytes: 2048 + status: ready + metadata: + hasMore: true + nextCursor: eyJyIjoiMjAyNS0xMS0xOC0wMDIifQ + schema: + properties: + items: + items: + $ref: "#/components/schemas/export-center.BundleSummary" + type: array + metadata: + $ref: "#/components/schemas/PageMetadata" + type: object + description: Bundle page + "400": + content: + application/json: + examples: + invalidTenant: + summary: Tenant missing + value: + code: export.invalid_tenant + message: tenant query parameter is required. + traceId: 01JF04ERR3 + schema: + $ref: "#/components/schemas/ErrorEnvelope" + description: Invalid request + summary: List export bundles + tags: + - Bundles + x-original-path: /bundles + x-service: export-center + /export-center/bundles/{bundleId}: + get: + parameters: + - example: bundle-2025-11-18-001 + in: path + name: bundleId + required: true + schema: + type: string + responses: + "200": + content: + application/zip: + examples: + download: + summary: Zip payload + value: binary data + description: Bundle stream + "404": + content: + application/json: + examples: + notFound: + summary: Bundle missing + value: + code: export.bundle_not_found + message: Bundle bundle-2025-11-18-001 not found. + traceId: 01JF04NF + schema: + $ref: "#/components/schemas/export-center.ErrorEnvelope" + description: Bundle not found + summary: Download export bundle by id + tags: + - Bundles + x-original-path: /bundles/{bundleId} + x-service: export-center + /export-center/bundles/{bundleId}/manifest: + get: + parameters: + - in: path + name: bundleId + required: true + schema: + type: string + responses: + "200": + content: + application/json: + examples: + manifest: + value: + bundleId: bundle-2025-11-18-001 + contents: + - digest: sha256:abc123 + type: advisory + - digest: sha256:def456 + type: vex + createdAt: 2025-11-18T12:00:00Z + schema: + $ref: "#/components/schemas/export-center.BundleManifest" + description: Manifest metadata + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + description: Bundle not found + summary: Fetch bundle manifest metadata + tags: + - Bundles + x-original-path: /bundles/{bundleId}/manifest + x-service: export-center + /export-center/health: + get: + responses: + "200": + content: + application/json: + examples: + ok: + value: + service: export-center + status: ok + timestamp: 2025-11-18T00:00:00Z + description: Service is up + "503": + content: + application/json: + examples: + unhealthy: + value: + reason: object store unreachable + service: export-center + status: degraded + timestamp: 2025-11-18T00:00:00Z + description: Service unhealthy or dependencies unavailable. + summary: Liveness probe + tags: + - Health + x-original-path: /health + x-service: export-center + /export-center/healthz: + get: + responses: + "200": + content: + application/json: + examples: + ok: + summary: Healthy response + value: + service: export-center + status: ok + schema: + $ref: "#/components/schemas/export-center.HealthResponse" + description: Service healthy + "503": + content: + application/json: + examples: + unavailable: + summary: Unhealthy response + value: + code: service_unavailable + message: mirror bundle backlog exceeds SLA + traceId: 3 + schema: + $ref: "#/components/schemas/export-center.ErrorEnvelope" + description: Service unavailable + summary: Service health + tags: + - Meta + x-original-path: /healthz + x-service: export-center + /graph/graphs/{graphId}/nodes: + get: + parameters: + - in: path + name: graphId + required: true + schema: + type: string + - $ref: ../_shared/parameters/paging.yaml#/parameters/LimitParam + - $ref: ../_shared/parameters/paging.yaml#/parameters/CursorParam + responses: + "200": + content: + application/json: + examples: + sample: + value: + metadata: + hasMore: true + nextCursor: eyJuIjoiMjAyNS0xMS0xOCJ9 + nodes: + - id: node-1 + kind: artifact + label: registry.stella-ops.local/runtime/api + - id: node-2 + kind: policy + label: policy:baseline + schema: + $ref: "#/components/schemas/graph.GraphNodePage" + description: Graph nodes page + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + description: Graph not found + summary: List graph nodes + tags: + - Graphs + x-original-path: /graphs/{graphId}/nodes + x-service: graph + /graph/graphs/{graphId}/status: + get: + parameters: + - in: path + name: graphId + required: true + schema: + type: string + - $ref: ../_shared/parameters/tenant.yaml#/parameters/TenantParam + responses: + "200": + content: + application/json: + examples: + ready: + value: + builtAt: 2025-11-18 12:00:00+00:00 + graphId: graph-01JF0XYZ + status: ready + schema: + $ref: "#/components/schemas/graph.GraphStatus" + description: Graph status + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/graph.ErrorEnvelope" + description: Graph not found + summary: Get graph build status + tags: + - Graphs + x-original-path: /graphs/{graphId}/status + x-service: graph + /graph/healthz: + get: + responses: + "200": + content: + application/json: + examples: + ok: + summary: Healthy response + value: + service: graph + status: ok + schema: + $ref: "#/components/schemas/graph.HealthEnvelope" + description: Service healthy + "503": + content: + application/json: + examples: + unavailable: + summary: Unhealthy response + value: + code: service_unavailable + message: indexer lag exceeds threshold + traceId: 5 + schema: + $ref: "#/components/schemas/graph.ErrorEnvelope" + description: Service unavailable + summary: Service health + tags: + - Meta + x-original-path: /healthz + x-service: graph + /orchestrator/health: + get: + responses: + "200": + content: + application/json: + examples: + ok: + value: + service: orchestrator + status: ok + timestamp: 2025-11-18T00:00:00Z + description: Service is up + "503": + content: + application/json: + examples: + unhealthy: + value: + reason: scheduler queue unreachable + service: orchestrator + status: degraded + timestamp: 2025-11-18T00:00:00Z + description: Service unhealthy or dependencies unavailable. + summary: Liveness probe + tags: + - Health + x-original-path: /health + x-service: orchestrator + /orchestrator/healthz: + get: + responses: + "200": + content: + application/json: + examples: + ok: + summary: Healthy response + value: + service: orchestrator + status: ok + schema: + $ref: "#/components/schemas/HealthEnvelope" + description: Service healthy + "503": + content: + application/json: + examples: + unavailable: + summary: Unhealthy response + value: + code: service_unavailable + message: outbound queue lag exceeds threshold + traceId: 1 + schema: + $ref: "#/components/schemas/ErrorEnvelope" + description: Service unavailable + summary: Service health + tags: + - Meta + x-original-path: /healthz + x-service: orchestrator + /orchestrator/jobs: + get: + parameters: + - in: query + name: status + schema: + enum: + - queued + - running + - failed + - completed + type: string + - $ref: ../_shared/parameters/paging.yaml#/parameters/LimitParam + - $ref: ../_shared/parameters/tenant.yaml#/parameters/TenantParam + responses: + "200": + content: + application/json: + examples: + sample: + value: + - enqueuedAt: 2025-11-18T12:00:00Z + jobId: job_01JF04ABCD + queue: scan + status: queued + - enqueuedAt: 2025-11-18T11:55:00Z + jobId: job_01JF04EFGH + queue: policy-eval + startedAt: 2025-11-18T11:56:10Z + status: running + schema: + items: + $ref: "#/components/schemas/orchestrator.JobSummary" + type: array + description: Jobs page + summary: List jobs + tags: + - Jobs + x-original-path: /jobs + x-service: orchestrator + post: + requestBody: + content: + application/json: + examples: + scanJob: + summary: Submit scan job + value: + kind: scan + payload: + artifactId: registry.stella-ops.local/runtime/api + policyVersion: 2025.10.1 + priority: high + tenant: tenant-alpha + schema: + $ref: "#/components/schemas/orchestrator.JobCreateRequest" + required: true + responses: + "202": + content: + application/json: + examples: + accepted: + summary: Job enqueued + value: + enqueuedAt: 2025-11-18T12:00:00Z + jobId: job_01JF04ABCD + queue: scan + status: queued + schema: + $ref: "#/components/schemas/orchestrator.JobCreateResponse" + description: Job accepted + "400": + content: + application/json: + examples: + missingType: + summary: Missing jobType + value: + code: orch.invalid_request + message: jobType is required. + traceId: 01JF04ERR1 + schema: + $ref: "#/components/schemas/ErrorEnvelope" + description: Invalid request + security: + - OAuthClientCredentials: [] + - BearerAuth: [] + summary: Submit a job to the orchestrator queue + tags: + - Jobs + x-original-path: /jobs + x-service: orchestrator + /orchestrator/jobs/{jobId}: + get: + parameters: + - in: path + name: jobId + required: true + schema: + type: string + responses: + "200": + content: + application/json: + examples: + sample: + value: + enqueuedAt: 2025-11-18T12:00:00Z + jobId: job_01JF04ABCD + queue: scan + status: queued + schema: + $ref: "#/components/schemas/orchestrator.JobSummary" + description: Job status + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/orchestrator.ErrorEnvelope" + description: Job not found + summary: Get job status + tags: + - Jobs + x-original-path: /jobs/{jobId} + x-service: orchestrator + /policy/evaluate: + post: + requestBody: + content: + application/json: + examples: + default: + summary: Evaluate current policy for an artifact + value: + artifactId: registry.stella-ops.local/runtime/api + inputs: + branch: main + tenant: acme + policyVersion: 2025.10.1 + schema: + $ref: "#/components/schemas/policy.EvaluationRequest" + required: true + responses: + "200": + content: + application/json: + examples: + allow: + summary: Allow decision with reasons + value: + decision: allow + metadata: + latencyMs: 42 + obligations: + - record: evidence + policyVersion: 2025.10.1 + reasons: + - signed + - within SLO + traceId: 01JF040XYZ + schema: + $ref: "#/components/schemas/policy.EvaluationResponse" + description: Evaluation succeeded + "400": + content: + application/json: + examples: + missingArtifact: + summary: Missing artifactId + value: + code: policy.invalid_request + message: artifactId is required. + traceId: 01JF041ERR + schema: + $ref: "#/components/schemas/ErrorEnvelope" + description: Invalid request + security: + - OAuthClientCredentials: [] + - BearerAuth: [] + summary: Evaluate policy for an artifact + tags: + - Evaluation + x-original-path: /evaluate + x-service: policy + /policy/health: + get: + responses: + "200": + content: + application/json: + examples: + ok: + value: + service: policy + status: ok + timestamp: 2025-11-18T00:00:00Z + description: Service is up + "503": + content: + application/json: + examples: + unhealthy: + value: + reason: mongo unavailable + service: policy + status: degraded + timestamp: 2025-11-18T00:00:00Z + description: Service unhealthy or dependencies unavailable. + summary: Liveness probe + tags: + - Health + x-original-path: /health + x-service: policy + /policy/healthz: + get: + responses: + "200": + content: + application/json: + examples: + ok: + summary: Healthy response + value: + service: policy + status: ok + schema: + $ref: "#/components/schemas/HealthEnvelope" + description: Service healthy + "503": + content: + application/json: + examples: + unavailable: + summary: Unhealthy response + value: + code: service_unavailable + message: projector backlog exceeds SLA + traceId: 2 + schema: + $ref: "#/components/schemas/ErrorEnvelope" + description: Service unavailable + summary: Service health + tags: + - Meta + x-original-path: /healthz + x-service: policy + /scheduler/health: + get: + responses: + "200": + content: + application/json: + examples: + ok: + value: + service: scheduler + status: ok + timestamp: 2025-11-18T00:00:00Z + description: Service is up + "503": + content: + application/json: + examples: + unhealthy: + value: + reason: queue not reachable + service: scheduler + status: degraded + timestamp: 2025-11-18T00:00:00Z + description: Service unhealthy or dependencies unavailable. + summary: Liveness probe + tags: + - Health + x-original-path: /health + x-service: scheduler + /scheduler/healthz: + get: + responses: + "200": + content: + application/json: + examples: + ok: + summary: Healthy response + value: + service: scheduler + status: ok + schema: + $ref: "#/components/schemas/scheduler.HealthEnvelope" + description: Service healthy + "503": + content: + application/json: + examples: + unavailable: + summary: Unhealthy response + value: + code: service_unavailable + message: queue backlog exceeds threshold + traceId: 4 + schema: + $ref: "#/components/schemas/scheduler.ErrorEnvelope" + description: Service unavailable + summary: Service health + tags: + - Meta + x-original-path: /healthz + x-service: scheduler + /scheduler/queues/{name}: + get: + parameters: + - example: default + in: path + name: name + required: true + schema: + type: string + responses: + "200": + content: + application/json: + examples: + status: + summary: Queue depth snapshot + value: + depth: 12 + inflight: 2 + name: default + oldestAgeSeconds: 45 + updatedAt: 2025-11-18T12:00:00Z + schema: + $ref: "#/components/schemas/scheduler.QueueStatus" + description: Queue status + "404": + content: + application/json: + examples: + notFound: + summary: Queue missing + value: + code: scheduler.queue_not_found + message: Queue default not found. + traceId: 01JF04NF2 + schema: + $ref: "#/components/schemas/scheduler.ErrorEnvelope" + description: Queue not found + summary: Get queue status + tags: + - Queues + x-original-path: /queues/{name} + x-service: scheduler +servers: + - description: Example Authority deployment + url: https://authority.stellaops.local + x-service: authority + - description: Example Export Center endpoint + url: https://export.stellaops.local + x-service: export-center + - description: Example Graph endpoint + url: https://graph.stellaops.local + x-service: graph + - description: Example Orchestrator endpoint + url: https://orchestrator.stellaops.local + x-service: orchestrator + - description: Example Policy Engine endpoint + url: https://policy.stellaops.local + x-service: policy + - description: Example Scheduler endpoint + url: https://scheduler.stellaops.local + x-service: scheduler diff --git a/src/DevPortal/StellaOps.DevPortal.Site/public/logo.svg b/src/DevPortal/StellaOps.DevPortal.Site/public/logo.svg new file mode 100644 index 000000000..350205805 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/public/logo.svg @@ -0,0 +1,13 @@ + + StellaOps DevPortal + Stylised starburst mark for the StellaOps developer portal. + + + + + + + + + + diff --git a/src/DevPortal/StellaOps.DevPortal.Site/scripts/sync-spec.mjs b/src/DevPortal/StellaOps.DevPortal.Site/scripts/sync-spec.mjs new file mode 100644 index 000000000..413dd149d --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/scripts/sync-spec.mjs @@ -0,0 +1,32 @@ +#!/usr/bin/env node +import fs from 'node:fs'; +import path from 'node:path'; +import crypto from 'node:crypto'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const moduleRoot = path.resolve(__dirname, '..'); +const repoRoot = path.resolve(moduleRoot, '..', '..', '..'); +const sourceSpec = path.join(repoRoot, 'src/Api/StellaOps.Api.OpenApi/stella.yaml'); +const targetDir = path.join(moduleRoot, 'public', 'api'); +const targetSpec = path.join(targetDir, 'stella.yaml'); + +function hashFile(filePath) { + const hash = crypto.createHash('sha256'); + hash.update(fs.readFileSync(filePath)); + return hash.digest('hex'); +} + +if (!fs.existsSync(sourceSpec)) { + console.error(`[devportal:sync-spec] missing source spec at ${sourceSpec}`); + process.exitCode = 1; + process.exit(); +} + +fs.mkdirSync(targetDir, { recursive: true }); +fs.copyFileSync(sourceSpec, targetSpec); + +const sizeKb = (fs.statSync(targetSpec).size / 1024).toFixed(1); +const digest = hashFile(targetSpec).slice(0, 12); +console.log(`[devportal:sync-spec] copied aggregate spec -> public/api/stella.yaml (${sizeKb} KiB, sha256:${digest}...)`); diff --git a/src/DevPortal/StellaOps.DevPortal.Site/src/content/config.ts b/src/DevPortal/StellaOps.DevPortal.Site/src/content/config.ts new file mode 100644 index 000000000..ed8403bc5 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/src/content/config.ts @@ -0,0 +1,17 @@ +import { defineCollection, z } from 'astro:content'; + +const docs = defineCollection({ + type: 'content', + schema: z.object({ + title: z.string(), + description: z.string().optional(), + sidebar: z + .object({ + label: z.string().optional(), + }) + .optional(), + order: z.number().optional(), + }), +}); + +export const collections = { docs }; diff --git a/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/api-reference.mdx b/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/api-reference.mdx new file mode 100644 index 000000000..e51f5b825 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/api-reference.mdx @@ -0,0 +1,37 @@ +--- +title: API Reference +description: Aggregate OpenAPI surface for StellaOps services with schema-first navigation. +--- + +import 'rapidoc/dist/rapidoc-min.js'; + +> The aggregate spec is composed from per-service OpenAPI files and namespaced by service (e.g., `/authority/...`). The bundled copy lives at `/api/stella.yaml` so offline builds stay self-contained. + + + +## What to look for +- Per-operation `x-service` and `x-original-path` values expose provenance. +- Shared schemas live under `#/components/schemas` with namespaced keys. +- Servers list includes one entry per service; sandbox URLs will be added alongside prod. diff --git a/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/guides/getting-started.mdx b/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/guides/getting-started.mdx new file mode 100644 index 000000000..5a051fba2 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/guides/getting-started.mdx @@ -0,0 +1,38 @@ +--- +title: Getting Started +description: Build and preview the DevPortal locally with deterministic inputs. +--- + +## Prerequisites +- Node.js 18.18 or later (offline-friendly install). +- `npm install --package-lock-only` to capture the lockfile; `npm ci --progress=false` when you need a full install. +- Aggregate OpenAPI file at `src/Api/StellaOps.Api.OpenApi/stella.yaml` (generated via `npm run api:compose` from the repo root). + +## Build locally +1. Sync the aggregate spec into the portal assets: + ```bash + npm run sync:spec + ``` +2. Install dependencies (skips network analytics): + ```bash + npm ci --ignore-scripts --progress=false --no-fund --no-audit + ``` +3. Run the site locally: + ```bash + npm run dev -- --host + ``` +4. Generate a production bundle (offline-ready): + ```bash + npm run build + ``` + +## Determinism & offline posture +- The portal never pulls fonts or JS from CDNs; all assets live under `public/`. +- The aggregate spec is stored at `/api/stella.yaml` and is bundled into exports. +- Search uses a local index generated at build time—no third-party calls. + +## Where things live +- Content: `src/content/docs/**` +- Styling tokens: `src/styles/custom.css` +- Spec sync helper: `scripts/sync-spec.mjs` +- Build output: `dist/` (ready for static serving or offline export) diff --git a/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/guides/navigation-search.mdx b/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/guides/navigation-search.mdx new file mode 100644 index 000000000..ac27ae7df --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/guides/navigation-search.mdx @@ -0,0 +1,24 @@ +--- +title: Navigation & Search +description: How the DevPortal organizes content and builds offline search indices. +--- + +## Navigation model +- **Overview** for narrative journeys and onboarding. +- **API** for the aggregate OpenAPI viewer and schema-aware tools. +- **Roadmap** for release notes and drop-specific changes. +- Sidebar order is pinned in `astro.config.mjs` to keep builds deterministic. + +## Search +- Provider: **local** (FlexSearch) generated at build time. +- Works offline; indexes titles, headings, and descriptions across docs. +- Search box appears in the top nav. Keyboard shortcut: `/` (press in any page). + +## Content guidelines +- Every page must declare `title` and `description` frontmatter to land in the index. +- Prefer short headings (≤60 characters) for clean search snippets. +- Keep code examples deterministic: pin versions and avoid network calls. + +## Upcoming +- API operation deep-links will join the index once schema viewer (DEVPORT-62-002) lands. +- Try-It console (DEVPORT-63-001) will expose a sandbox surface gated by scopes. diff --git a/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/index.mdx b/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/index.mdx new file mode 100644 index 000000000..3ccbec854 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/index.mdx @@ -0,0 +1,30 @@ +--- +title: Welcome to the StellaOps DevPortal +description: Deterministic, offline-first documentation and API reference for the StellaOps platform. +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +The StellaOps DevPortal binds specs, runnable examples, and SDK entrypoints into a single, deterministic build. Everything here is designed to work online or fully air-gapped so auditors and engineers see the same evidence. + + + + Browse the composed OpenAPI surface, schema-first paths, and auth expectations. + + + Install tooling, sync the aggregate spec, and render the portal locally. + + + Learn how content is organized and how offline search works. + + + +## Why now +- Offline parity: the same portal ships as static HTML with bundled assets. +- Deterministic rebuilds: aggregate spec and examples are pinned in-source. +- Audit-ready: schema-first views, provenance attached to specs, and upcoming try-it sandbox. + +## What lives here +- Aggregate OpenAPI (namespaced by service) with schema explorer. +- Guides for tokens, scopes, SDKs, and export bundles. +- Release notes aligned to platform drops. diff --git a/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/release-notes.mdx b/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/release-notes.mdx new file mode 100644 index 000000000..11d52657b --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/src/content/docs/release-notes.mdx @@ -0,0 +1,15 @@ +--- +title: Release Notes +description: Drop-by-drop updates for the DevPortal surface. +--- + +## 2025-11 (Sprint 0206.0001.0001) +- ✅ Selected Astro + Starlight as the static site generator for deterministic offline builds. +- ✅ Added navigation scaffolding (Overview, Guides, API, Roadmap) with local search enabled. +- ✅ Embedded aggregate OpenAPI via RapiDoc using bundled `/api/stella.yaml`. +- 🔜 Schema explorer UI and copy-curl snippets (DEVPORT-62-002). +- 🔜 Try-It console against sandbox scopes (DEVPORT-63-001). + +## How to contribute release entries +- Add a dated section with bullet points grouped by task ID when features land. +- Keep entries aligned to sprint IDs and include any risks or follow-ups. diff --git a/src/DevPortal/StellaOps.DevPortal.Site/src/env.d.ts b/src/DevPortal/StellaOps.DevPortal.Site/src/env.d.ts new file mode 100644 index 000000000..acef35f17 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/src/DevPortal/StellaOps.DevPortal.Site/src/styles/custom.css b/src/DevPortal/StellaOps.DevPortal.Site/src/styles/custom.css new file mode 100644 index 000000000..8e5927d87 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/src/styles/custom.css @@ -0,0 +1,45 @@ +:root { + --sl-font-sans: "Space Grotesk", "Segoe UI", "Inter", system-ui, -apple-system, sans-serif; + --sl-font-mono: "JetBrains Mono", "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace; + --sl-color-accent: #0ea5e9; + --sl-color-text: #e5e7eb; + --sl-color-text-accent: #a5f3fc; + --sl-color-text-muted: #cbd5e1; + --sl-color-bg: #0b1220; + --sl-color-bg-soft: #0f172a; + --sl-color-hairline: #1f2937; + --sl-heading-font-weight: 700; + --sl-body-font-weight: 400; +} + +body { + background: radial-gradient(circle at 20% 20%, rgba(14, 165, 233, 0.12), transparent 25%), + radial-gradient(circle at 80% 10%, rgba(99, 102, 241, 0.14), transparent 25%), + linear-gradient(180deg, #0b1220 0%, #0f172a 60%, #0b1220 100%); + color: var(--sl-color-text); +} + +.sl-link-card { + border: 1px solid var(--sl-color-hairline); + background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.01)); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25); +} + +:where(.sl-markdown) h2 { + letter-spacing: -0.02em; +} + +:where(.sl-markdown) code { + background: rgba(15, 23, 42, 0.7); + border: 1px solid var(--sl-color-hairline); +} + +nav.sl-topnav { + border-bottom: 1px solid var(--sl-color-hairline); + backdrop-filter: blur(10px); +} + +.sl-search-box input { + background: rgba(255, 255, 255, 0.08); + border: 1px solid var(--sl-color-hairline); +} diff --git a/src/DevPortal/StellaOps.DevPortal.Site/tsconfig.json b/src/DevPortal/StellaOps.DevPortal.Site/tsconfig.json new file mode 100644 index 000000000..0e6df33c9 --- /dev/null +++ b/src/DevPortal/StellaOps.DevPortal.Site/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "types": ["astro/client"], + "baseUrl": "." + } +} diff --git a/src/Excititor/__Libraries/StellaOps.Excititor.Core/Observations/VexLinksetObservationRefCore.cs b/src/Excititor/__Libraries/StellaOps.Excititor.Core/Observations/VexLinksetObservationRefCore.cs new file mode 100644 index 000000000..4282703b8 --- /dev/null +++ b/src/Excititor/__Libraries/StellaOps.Excititor.Core/Observations/VexLinksetObservationRefCore.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Immutable; + +namespace StellaOps.Excititor.Core.Observations; + +/// +/// Minimal observation reference used in linkset updates while preserving Aggregation-Only semantics. +/// +public sealed record VexLinksetObservationRefCore( + string ObservationId, + string ProviderId, + string Status, + double? Confidence, + ImmutableDictionary Attributes) +{ + public static VexLinksetObservationRefCore Create( + string observationId, + string providerId, + string status, + double? confidence, + ImmutableDictionary? attributes = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(observationId); + ArgumentException.ThrowIfNullOrWhiteSpace(providerId); + ArgumentException.ThrowIfNullOrWhiteSpace(status); + + return new VexLinksetObservationRefCore( + observationId.Trim(), + providerId.Trim(), + status.Trim(), + confidence, + attributes ?? ImmutableDictionary.Empty); + } +} diff --git a/src/Findings/StellaOps.Findings.Ledger.Tests/AirgapAndOrchestratorServiceTests.cs b/src/Findings/StellaOps.Findings.Ledger.Tests/AirgapAndOrchestratorServiceTests.cs new file mode 100644 index 000000000..b28d5d13e --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger.Tests/AirgapAndOrchestratorServiceTests.cs @@ -0,0 +1,98 @@ +using Microsoft.Extensions.Logging.Abstractions; +using StellaOps.Findings.Ledger.Infrastructure.AirGap; +using StellaOps.Findings.Ledger.Infrastructure.Exports; +using StellaOps.Findings.Ledger.Infrastructure.InMemory; +using StellaOps.Findings.Ledger.Infrastructure.Merkle; +using StellaOps.Findings.Ledger.Services; +using Xunit; + +namespace StellaOps.Findings.Ledger.Tests; + +public sealed class AirgapAndOrchestratorServiceTests +{ + [Fact] + public async Task AirgapImportService_AppendsLedgerEvent_AndPersistsRecord() + { + var ledgerRepo = new InMemoryLedgerEventRepository(); + var writeService = new LedgerEventWriteService(ledgerRepo, new NullMerkleAnchorScheduler(), NullLogger.Instance); + var store = new InMemoryAirgapImportRepository(); + var service = new AirgapImportService(ledgerRepo, writeService, store, TimeProvider.System, NullLogger.Instance); + + var input = new AirgapImportInput( + TenantId: "tenant-a", + BundleId: "bundle-123", + MirrorGeneration: "gen-1", + MerkleRoot: "abc123", + TimeAnchor: DateTimeOffset.Parse("2025-10-10T00:00:00Z"), + Publisher: "mirror", + HashAlgorithm: "sha256", + Contents: new[] { "c1", "c2" }, + ImportOperator: "operator:alice"); + + var result = await service.RecordAsync(input, CancellationToken.None); + + Assert.True(result.Success); + Assert.NotNull(result.LedgerEventId); + Assert.NotNull(store.LastRecord); + Assert.Equal(input.BundleId, store.LastRecord!.BundleId); + Assert.Equal(input.MirrorGeneration, store.LastRecord.MirrorGeneration); + } + + [Fact] + public async Task OrchestratorExportService_ComputesMerkleRoot() + { + var repo = new InMemoryOrchestratorExportRepository(); + var service = new OrchestratorExportService(repo, TimeProvider.System, NullLogger.Instance); + var input = new OrchestratorExportInput( + TenantId: "tenant-a", + RunId: Guid.NewGuid(), + JobType: "export-artifact", + ArtifactHash: "sha256:artifact", + PolicyHash: "sha256:policy", + StartedAt: DateTimeOffset.Parse("2025-10-11T00:00:00Z"), + CompletedAt: DateTimeOffset.Parse("2025-10-11T00:10:00Z"), + Status: "succeeded", + ManifestPath: "/exports/manifest.json", + LogsPath: "/exports/logs.txt"); + + var record = await service.RecordAsync(input, CancellationToken.None); + + Assert.NotNull(record); + Assert.False(string.IsNullOrWhiteSpace(record.MerkleRoot)); + Assert.Equal(record.MerkleRoot, repo.LastRecord?.MerkleRoot); + Assert.Equal(input.ArtifactHash, repo.LastRecord?.ArtifactHash); + } + + private sealed class InMemoryAirgapImportRepository : IAirgapImportRepository + { + public AirgapImportRecord? LastRecord { get; private set; } + + public Task InsertAsync(AirgapImportRecord record, CancellationToken cancellationToken) + { + LastRecord = record; + return Task.CompletedTask; + } + } + + private sealed class InMemoryOrchestratorExportRepository : IOrchestratorExportRepository + { + public OrchestratorExportRecord? LastRecord { get; private set; } + + public Task InsertAsync(OrchestratorExportRecord record, CancellationToken cancellationToken) + { + LastRecord = record; + return Task.CompletedTask; + } + + public Task> GetByArtifactAsync(string tenantId, string artifactHash, CancellationToken cancellationToken) + { + var list = new List(); + if (LastRecord is not null && string.Equals(LastRecord.ArtifactHash, artifactHash, StringComparison.Ordinal)) + { + list.Add(LastRecord); + } + + return Task.FromResult>(list); + } + } +} diff --git a/src/Findings/StellaOps.Findings.Ledger.WebService/Contracts/AirgapImportContracts.cs b/src/Findings/StellaOps.Findings.Ledger.WebService/Contracts/AirgapImportContracts.cs new file mode 100644 index 000000000..734ae7b03 --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger.WebService/Contracts/AirgapImportContracts.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; + +namespace StellaOps.Findings.Ledger.WebService.Contracts; + +public sealed record AirgapImportRequest +{ + [JsonPropertyName("bundleId")] + public required string BundleId { get; init; } + + [JsonPropertyName("mirrorGeneration")] + public string? MirrorGeneration { get; init; } + + [JsonPropertyName("merkleRoot")] + public required string MerkleRoot { get; init; } + + [JsonPropertyName("timeAnchor")] + public required DateTimeOffset TimeAnchor { get; init; } + + [JsonPropertyName("publisher")] + public string? Publisher { get; init; } + + [JsonPropertyName("hashAlgorithm")] + public string? HashAlgorithm { get; init; } + + [JsonPropertyName("contents")] + public string[] Contents { get; init; } = Array.Empty(); + + [JsonPropertyName("importOperator")] + public string? ImportOperator { get; init; } +} + +public sealed record AirgapImportResponse( + Guid ChainId, + long? Sequence, + Guid? LedgerEventId, + string Status, + string? Error); diff --git a/src/Findings/StellaOps.Findings.Ledger.WebService/Contracts/OrchestratorExportContracts.cs b/src/Findings/StellaOps.Findings.Ledger.WebService/Contracts/OrchestratorExportContracts.cs new file mode 100644 index 000000000..178e4e582 --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger.WebService/Contracts/OrchestratorExportContracts.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; + +namespace StellaOps.Findings.Ledger.WebService.Contracts; + +public sealed record OrchestratorExportRequest +{ + [JsonPropertyName("runId")] + public required Guid RunId { get; init; } + + [JsonPropertyName("jobType")] + public required string JobType { get; init; } + + [JsonPropertyName("artifactHash")] + public required string ArtifactHash { get; init; } + + [JsonPropertyName("policyHash")] + public required string PolicyHash { get; init; } + + [JsonPropertyName("startedAt")] + public required DateTimeOffset StartedAt { get; init; } + + [JsonPropertyName("completedAt")] + public DateTimeOffset? CompletedAt { get; init; } + + [JsonPropertyName("status")] + public required string Status { get; init; } + + [JsonPropertyName("manifestPath")] + public string? ManifestPath { get; init; } + + [JsonPropertyName("logsPath")] + public string? LogsPath { get; init; } +} + +public sealed record OrchestratorExportResponse( + Guid RunId, + string MerkleRoot); diff --git a/src/Findings/StellaOps.Findings.Ledger.WebService/Program.cs b/src/Findings/StellaOps.Findings.Ledger.WebService/Program.cs index 0b131133f..1607c9501 100644 --- a/src/Findings/StellaOps.Findings.Ledger.WebService/Program.cs +++ b/src/Findings/StellaOps.Findings.Ledger.WebService/Program.cs @@ -12,6 +12,7 @@ using StellaOps.Configuration; using StellaOps.DependencyInjection; using StellaOps.Findings.Ledger.Domain; using StellaOps.Findings.Ledger.Infrastructure; +using StellaOps.Findings.Ledger.Infrastructure.AirGap; using StellaOps.Findings.Ledger.Infrastructure.Merkle; using StellaOps.Findings.Ledger.Infrastructure.Postgres; using StellaOps.Findings.Ledger.Infrastructure.Projection; @@ -140,6 +141,10 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -300,6 +305,95 @@ app.MapGet("/ledger/export/sboms", () => TypedResults.Json(new ExportPage, ProblemHttpResult>> ( + HttpContext httpContext, + OrchestratorExportRequest request, + OrchestratorExportService service, + CancellationToken cancellationToken) => +{ + if (!httpContext.Request.Headers.TryGetValue("X-Stella-Tenant", out var tenantValues) || string.IsNullOrWhiteSpace(tenantValues)) + { + return TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "missing_tenant"); + } + + var tenantId = tenantValues.ToString(); + var input = new OrchestratorExportInput( + tenantId, + request.RunId, + request.JobType, + request.ArtifactHash, + request.PolicyHash, + request.StartedAt, + request.CompletedAt, + request.Status, + request.ManifestPath, + request.LogsPath); + + var record = await service.RecordAsync(input, cancellationToken).ConfigureAwait(false); + var response = new OrchestratorExportResponse(record.RunId, record.MerkleRoot); + return TypedResults.Accepted($"/internal/ledger/orchestrator-export/{record.RunId}", response); +}) +.WithName("OrchestratorExportRecord") +.RequireAuthorization(LedgerWritePolicy) +.Produces(StatusCodes.Status202Accepted) +.ProducesProblem(StatusCodes.Status400BadRequest); + +app.MapGet("/internal/ledger/orchestrator-export/{artifactHash}", async Task>, ProblemHttpResult>> ( + HttpContext httpContext, + string artifactHash, + OrchestratorExportService service, + CancellationToken cancellationToken) => +{ + if (!httpContext.Request.Headers.TryGetValue("X-Stella-Tenant", out var tenantValues) || string.IsNullOrWhiteSpace(tenantValues)) + { + return TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "missing_tenant"); + } + + var records = await service.GetByArtifactAsync(tenantValues.ToString(), artifactHash, cancellationToken).ConfigureAwait(false); + return TypedResults.Json(records); +}) +.WithName("OrchestratorExportQuery") +.RequireAuthorization(LedgerExportPolicy) +.Produces(StatusCodes.Status200OK) +.ProducesProblem(StatusCodes.Status400BadRequest); + +app.MapPost("/internal/ledger/airgap-import", async Task, ProblemHttpResult>> ( + HttpContext httpContext, + AirgapImportRequest request, + AirgapImportService service, + CancellationToken cancellationToken) => +{ + if (!httpContext.Request.Headers.TryGetValue("X-Stella-Tenant", out var tenantValues) || string.IsNullOrWhiteSpace(tenantValues)) + { + return TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "missing_tenant"); + } + + var input = new AirgapImportInput( + tenantValues.ToString(), + request.BundleId, + request.MirrorGeneration, + request.MerkleRoot, + request.TimeAnchor, + request.Publisher, + request.HashAlgorithm, + request.Contents ?? Array.Empty(), + request.ImportOperator); + + var result = await service.RecordAsync(input, cancellationToken).ConfigureAwait(false); + if (!result.Success) + { + return TypedResults.Problem(statusCode: StatusCodes.Status409Conflict, title: "airgap_import_failed", detail: result.Error ?? "Failed to record air-gap import."); + } + + var response = new AirgapImportResponse(result.ChainId, result.SequenceNumber, result.LedgerEventId, "accepted", null); + return TypedResults.Accepted($"/internal/ledger/airgap-import/{request.BundleId}", response); +}) +.WithName("AirgapImportRecord") +.RequireAuthorization(LedgerWritePolicy) +.Produces(StatusCodes.Status202Accepted) +.ProducesProblem(StatusCodes.Status400BadRequest) +.ProducesProblem(StatusCodes.Status409Conflict); + app.Run(); static Created CreateCreatedResponse(LedgerEventRecord record) diff --git a/src/Findings/StellaOps.Findings.Ledger.WebService/Services/AttestationQueryService.cs b/src/Findings/StellaOps.Findings.Ledger.WebService/Services/AttestationQueryService.cs index df77908d2..792b80cbd 100644 --- a/src/Findings/StellaOps.Findings.Ledger.WebService/Services/AttestationQueryService.cs +++ b/src/Findings/StellaOps.Findings.Ledger.WebService/Services/AttestationQueryService.cs @@ -214,7 +214,7 @@ public sealed class AttestationQueryService sqlBuilder.Append(" LIMIT @take"); parameters.Add(new NpgsqlParameter("take", request.Limit + 1) { NpgsqlDbType = NpgsqlDbType.Integer }); - await using var connection = await _dataSource.OpenConnectionAsync(request.TenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(request.TenantId, "attestation", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(sqlBuilder.ToString(), connection) { CommandTimeout = _dataSource.CommandTimeoutSeconds diff --git a/src/Findings/StellaOps.Findings.Ledger.WebService/Services/ExportQueryService.cs b/src/Findings/StellaOps.Findings.Ledger.WebService/Services/ExportQueryService.cs index fe92aa345..fa72f9066 100644 --- a/src/Findings/StellaOps.Findings.Ledger.WebService/Services/ExportQueryService.cs +++ b/src/Findings/StellaOps.Findings.Ledger.WebService/Services/ExportQueryService.cs @@ -168,7 +168,7 @@ public sealed class ExportQueryService NpgsqlDbType = NpgsqlDbType.Integer }); - await using var connection = await _dataSource.OpenConnectionAsync(request.TenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(request.TenantId, "export", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(sqlBuilder.ToString(), connection) { CommandTimeout = _dataSource.CommandTimeoutSeconds diff --git a/src/Findings/StellaOps.Findings.Ledger/Domain/LedgerChainIdGenerator.cs b/src/Findings/StellaOps.Findings.Ledger/Domain/LedgerChainIdGenerator.cs index 681939929..4a37adad2 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Domain/LedgerChainIdGenerator.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Domain/LedgerChainIdGenerator.cs @@ -7,10 +7,15 @@ public static class LedgerChainIdGenerator { public static Guid FromTenantPolicy(string tenantId, string policyVersion) { - ArgumentException.ThrowIfNullOrWhiteSpace(tenantId); - ArgumentException.ThrowIfNullOrWhiteSpace(policyVersion); + return FromTenantSubject(tenantId, policyVersion); + } - var normalized = $"{tenantId.Trim()}::{policyVersion.Trim()}"; + public static Guid FromTenantSubject(string tenantId, string subject) + { + ArgumentException.ThrowIfNullOrWhiteSpace(tenantId); + ArgumentException.ThrowIfNullOrWhiteSpace(subject); + + var normalized = $"{tenantId.Trim()}::{subject.Trim()}"; var bytes = Encoding.UTF8.GetBytes(normalized); Span guidBytes = stackalloc byte[16]; var hash = SHA256.HashData(bytes); diff --git a/src/Findings/StellaOps.Findings.Ledger/Domain/LedgerEventConstants.cs b/src/Findings/StellaOps.Findings.Ledger/Domain/LedgerEventConstants.cs index 8bec9a9e0..2b4d0f530 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Domain/LedgerEventConstants.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Domain/LedgerEventConstants.cs @@ -14,8 +14,24 @@ public static class LedgerEventConstants public const string EventFindingRemediationPlanAdded = "finding.remediation_plan_added"; public const string EventFindingAttachmentAdded = "finding.attachment_added"; public const string EventFindingClosed = "finding.closed"; + public const string EventAirgapBundleImported = "airgap.bundle_imported"; + public const string EventOrchestratorExportRecorded = "orchestrator.export_recorded"; public static readonly ImmutableHashSet SupportedEventTypes = ImmutableHashSet.Create(StringComparer.Ordinal, + EventFindingCreated, + EventFindingStatusChanged, + EventFindingSeverityChanged, + EventFindingTagUpdated, + EventFindingCommentAdded, + EventFindingAssignmentChanged, + EventFindingAcceptedRisk, + EventFindingRemediationPlanAdded, + EventFindingAttachmentAdded, + EventFindingClosed, + EventAirgapBundleImported, + EventOrchestratorExportRecorded); + + public static readonly ImmutableHashSet FindingEventTypes = ImmutableHashSet.Create(StringComparer.Ordinal, EventFindingCreated, EventFindingStatusChanged, EventFindingSeverityChanged, @@ -33,4 +49,6 @@ public static class LedgerEventConstants "integration"); public const string EmptyHash = "0000000000000000000000000000000000000000000000000000000000000000"; + + public static bool IsFindingEvent(string eventType) => FindingEventTypes.Contains(eventType); } diff --git a/src/Findings/StellaOps.Findings.Ledger/Domain/ProjectionModels.cs b/src/Findings/StellaOps.Findings.Ledger/Domain/ProjectionModels.cs index 535557710..26023d598 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Domain/ProjectionModels.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Domain/ProjectionModels.cs @@ -8,6 +8,11 @@ public sealed record FindingProjection( string PolicyVersion, string Status, decimal? Severity, + decimal? RiskScore, + string? RiskSeverity, + string? RiskProfileVersion, + Guid? RiskExplanationId, + long? RiskEventSequence, JsonObject Labels, Guid CurrentEventId, string? ExplainRef, diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/AirGap/AirgapImportRecord.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/AirGap/AirgapImportRecord.cs new file mode 100644 index 000000000..c18146401 --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/AirGap/AirgapImportRecord.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Nodes; + +namespace StellaOps.Findings.Ledger.Infrastructure.AirGap; + +public sealed record AirgapImportRecord( + string TenantId, + string BundleId, + string? MirrorGeneration, + string MerkleRoot, + DateTimeOffset TimeAnchor, + string? Publisher, + string? HashAlgorithm, + JsonArray Contents, + DateTimeOffset ImportedAt, + string? ImportOperator, + Guid? LedgerEventId); diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/AirGap/IAirgapImportRepository.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/AirGap/IAirgapImportRepository.cs new file mode 100644 index 000000000..041f79f91 --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/AirGap/IAirgapImportRepository.cs @@ -0,0 +1,6 @@ +namespace StellaOps.Findings.Ledger.Infrastructure.AirGap; + +public interface IAirgapImportRepository +{ + Task InsertAsync(AirgapImportRecord record, CancellationToken cancellationToken); +} diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Exports/IOrchestratorExportRepository.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Exports/IOrchestratorExportRepository.cs new file mode 100644 index 000000000..95667aa48 --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Exports/IOrchestratorExportRepository.cs @@ -0,0 +1,8 @@ +namespace StellaOps.Findings.Ledger.Infrastructure.Exports; + +public interface IOrchestratorExportRepository +{ + Task InsertAsync(OrchestratorExportRecord record, CancellationToken cancellationToken); + + Task> GetByArtifactAsync(string tenantId, string artifactHash, CancellationToken cancellationToken); +} diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Exports/OrchestratorExportRecord.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Exports/OrchestratorExportRecord.cs new file mode 100644 index 000000000..036112a90 --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Exports/OrchestratorExportRecord.cs @@ -0,0 +1,15 @@ +namespace StellaOps.Findings.Ledger.Infrastructure.Exports; + +public sealed record OrchestratorExportRecord( + string TenantId, + Guid RunId, + string JobType, + string ArtifactHash, + string PolicyHash, + DateTimeOffset StartedAt, + DateTimeOffset? CompletedAt, + string Status, + string? ManifestPath, + string? LogsPath, + string MerkleRoot, + DateTimeOffset CreatedAt); diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Merkle/LedgerAnchorQueue.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Merkle/LedgerAnchorQueue.cs index cb7067e4f..d4b39848a 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Merkle/LedgerAnchorQueue.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Merkle/LedgerAnchorQueue.cs @@ -1,5 +1,6 @@ using System.Threading.Channels; using StellaOps.Findings.Ledger.Domain; +using StellaOps.Findings.Ledger.Observability; namespace StellaOps.Findings.Ledger.Infrastructure.Merkle; @@ -18,7 +19,11 @@ public sealed class LedgerAnchorQueue } public ValueTask EnqueueAsync(LedgerEventRecord record, CancellationToken cancellationToken) - => _channel.Writer.WriteAsync(record, cancellationToken); + { + var writeTask = _channel.Writer.WriteAsync(record, cancellationToken); + LedgerMetrics.IncrementBacklog(); + return writeTask; + } public IAsyncEnumerable ReadAllAsync(CancellationToken cancellationToken) => _channel.Reader.ReadAllAsync(cancellationToken); diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Merkle/LedgerMerkleAnchorWorker.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Merkle/LedgerMerkleAnchorWorker.cs index 41356d0a5..c5e859bfd 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Merkle/LedgerMerkleAnchorWorker.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Merkle/LedgerMerkleAnchorWorker.cs @@ -1,8 +1,10 @@ using System.Collections.Concurrent; +using System.Diagnostics; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using StellaOps.Findings.Ledger.Domain; +using StellaOps.Findings.Ledger.Observability; using StellaOps.Findings.Ledger.Options; using TimeProvider = System.TimeProvider; @@ -35,6 +37,7 @@ public sealed class LedgerMerkleAnchorWorker : BackgroundService { await foreach (var record in _queue.ReadAllAsync(stoppingToken)) { + LedgerMetrics.DecrementBacklog(); await HandleEventAsync(record, stoppingToken).ConfigureAwait(false); } } @@ -80,6 +83,7 @@ public sealed class LedgerMerkleAnchorWorker : BackgroundService try { + var stopwatch = Stopwatch.StartNew(); var orderedEvents = batch.Events .OrderBy(e => e.SequenceNumber) .ThenBy(e => e.RecordedAt) @@ -106,10 +110,13 @@ public sealed class LedgerMerkleAnchorWorker : BackgroundService anchoredAt, anchorReference: null, cancellationToken).ConfigureAwait(false); + stopwatch.Stop(); + LedgerMetrics.RecordMerkleAnchorDuration(stopwatch.Elapsed, tenantId, leafCount); } catch (Exception ex) when (!cancellationToken.IsCancellationRequested) { _logger.LogError(ex, "Failed to persist Merkle anchor for tenant {TenantId}.", tenantId); + LedgerMetrics.RecordMerkleAnchorFailure(tenantId, ex.GetType().Name); } } diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/LedgerDataSource.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/LedgerDataSource.cs index c291d96ec..f77200590 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/LedgerDataSource.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/LedgerDataSource.cs @@ -1,6 +1,8 @@ +using System.Data; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Npgsql; +using StellaOps.Findings.Ledger.Observability; using StellaOps.Findings.Ledger.Options; namespace StellaOps.Findings.Ledger.Infrastructure.Postgres; @@ -31,15 +33,26 @@ public sealed class LedgerDataSource : IAsyncDisposable } public Task OpenConnectionAsync(string tenantId, CancellationToken cancellationToken) - => OpenConnectionInternalAsync(tenantId, cancellationToken); + => OpenConnectionInternalAsync(tenantId, "unspecified", cancellationToken); - private async Task OpenConnectionInternalAsync(string tenantId, CancellationToken cancellationToken) + public Task OpenConnectionAsync(string tenantId, string role, CancellationToken cancellationToken) + => OpenConnectionInternalAsync(tenantId, role, cancellationToken); + + private async Task OpenConnectionInternalAsync(string tenantId, string role, CancellationToken cancellationToken) { var connection = await _dataSource.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); try { await ConfigureSessionAsync(connection, tenantId, cancellationToken).ConfigureAwait(false); + LedgerMetrics.ConnectionOpened(role); + connection.StateChange += (_, args) => + { + if (args.CurrentState == ConnectionState.Closed) + { + LedgerMetrics.ConnectionClosed(role); + } + }; } catch { diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresAirgapImportRepository.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresAirgapImportRepository.cs new file mode 100644 index 000000000..199f6f4bd --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresAirgapImportRepository.cs @@ -0,0 +1,94 @@ +using System.Text.Json.Nodes; +using Microsoft.Extensions.Logging; +using Npgsql; +using NpgsqlTypes; +using StellaOps.Findings.Ledger.Hashing; +using StellaOps.Findings.Ledger.Infrastructure.AirGap; + +namespace StellaOps.Findings.Ledger.Infrastructure.Postgres; + +public sealed class PostgresAirgapImportRepository : IAirgapImportRepository +{ + private const string InsertSql = """ + INSERT INTO airgap_imports ( + tenant_id, + bundle_id, + mirror_generation, + merkle_root, + time_anchor, + publisher, + hash_algorithm, + contents, + imported_at, + import_operator, + ledger_event_id) + VALUES ( + @tenant_id, + @bundle_id, + @mirror_generation, + @merkle_root, + @time_anchor, + @publisher, + @hash_algorithm, + @contents, + @imported_at, + @import_operator, + @ledger_event_id) + ON CONFLICT (tenant_id, bundle_id, time_anchor) + DO UPDATE SET + merkle_root = EXCLUDED.merkle_root, + publisher = EXCLUDED.publisher, + hash_algorithm = EXCLUDED.hash_algorithm, + contents = EXCLUDED.contents, + imported_at = EXCLUDED.imported_at, + import_operator = EXCLUDED.import_operator, + ledger_event_id = EXCLUDED.ledger_event_id; + """; + + private readonly LedgerDataSource _dataSource; + private readonly ILogger _logger; + + public PostgresAirgapImportRepository( + LedgerDataSource dataSource, + ILogger logger) + { + _dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task InsertAsync(AirgapImportRecord record, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(record); + + var canonicalContents = LedgerCanonicalJsonSerializer.Canonicalize(record.Contents); + var contentsJson = canonicalContents.ToJsonString(); + + await using var connection = await _dataSource.OpenConnectionAsync(record.TenantId, "airgap-import", cancellationToken).ConfigureAwait(false); + await using var command = new NpgsqlCommand(InsertSql, connection) + { + CommandTimeout = _dataSource.CommandTimeoutSeconds + }; + + command.Parameters.Add(new NpgsqlParameter("tenant_id", record.TenantId) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("bundle_id", record.BundleId) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("mirror_generation", record.MirrorGeneration) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("merkle_root", record.MerkleRoot) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("time_anchor", record.TimeAnchor) { NpgsqlDbType = NpgsqlDbType.TimestampTz }); + command.Parameters.Add(new NpgsqlParameter("publisher", record.Publisher) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("hash_algorithm", record.HashAlgorithm) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("contents", contentsJson) { NpgsqlDbType = NpgsqlDbType.Jsonb }); + command.Parameters.Add(new NpgsqlParameter("imported_at", record.ImportedAt) { NpgsqlDbType = NpgsqlDbType.TimestampTz }); + command.Parameters.Add(new NpgsqlParameter("import_operator", record.ImportOperator) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("ledger_event_id", record.LedgerEventId) { NpgsqlDbType = NpgsqlDbType.Uuid }); + + try + { + await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); + } + catch (PostgresException ex) + { + _logger.LogError(ex, "Failed to insert air-gap import for tenant {TenantId} bundle {BundleId}.", record.TenantId, record.BundleId); + throw; + } + } +} diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresFindingProjectionRepository.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresFindingProjectionRepository.cs index a1f9c141d..4155791a3 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresFindingProjectionRepository.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresFindingProjectionRepository.cs @@ -12,6 +12,11 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo private const string GetProjectionSql = """ SELECT status, severity, + risk_score, + risk_severity, + risk_profile_version, + risk_explanation_id, + risk_event_sequence, labels, current_event_id, explain_ref, @@ -31,6 +36,11 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo policy_version, status, severity, + risk_score, + risk_severity, + risk_profile_version, + risk_explanation_id, + risk_event_sequence, labels, current_event_id, explain_ref, @@ -43,6 +53,11 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo @policy_version, @status, @severity, + @risk_score, + @risk_severity, + @risk_profile_version, + @risk_explanation_id, + @risk_event_sequence, @labels, @current_event_id, @explain_ref, @@ -53,6 +68,11 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo DO UPDATE SET status = EXCLUDED.status, severity = EXCLUDED.severity, + risk_score = EXCLUDED.risk_score, + risk_severity = EXCLUDED.risk_severity, + risk_profile_version = EXCLUDED.risk_profile_version, + risk_explanation_id = EXCLUDED.risk_explanation_id, + risk_event_sequence = EXCLUDED.risk_event_sequence, labels = EXCLUDED.labels, current_event_id = EXCLUDED.current_event_id, explain_ref = EXCLUDED.explain_ref, @@ -153,7 +173,7 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo public async Task GetAsync(string tenantId, string findingId, string policyVersion, CancellationToken cancellationToken) { - await using var connection = await _dataSource.OpenConnectionAsync(tenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(tenantId, "projector", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(GetProjectionSql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; command.Parameters.AddWithValue("tenant_id", tenantId); @@ -168,11 +188,16 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo var status = reader.GetString(0); var severity = reader.IsDBNull(1) ? (decimal?)null : reader.GetDecimal(1); - var labelsJson = reader.GetFieldValue(2); + var riskScore = reader.IsDBNull(2) ? (decimal?)null : reader.GetDecimal(2); + var riskSeverity = reader.IsDBNull(3) ? null : reader.GetString(3); + var riskProfileVersion = reader.IsDBNull(4) ? null : reader.GetString(4); + var riskExplanationId = reader.IsDBNull(5) ? (Guid?)null : reader.GetGuid(5); + var riskEventSequence = reader.IsDBNull(6) ? (long?)null : reader.GetInt64(6); + var labelsJson = reader.GetFieldValue(7); var labels = JsonNode.Parse(labelsJson)?.AsObject() ?? new JsonObject(); - var currentEventId = reader.GetGuid(3); - var explainRef = reader.IsDBNull(4) ? null : reader.GetString(4); - var rationaleJson = reader.IsDBNull(5) ? string.Empty : reader.GetFieldValue(5); + var currentEventId = reader.GetGuid(8); + var explainRef = reader.IsDBNull(9) ? null : reader.GetString(9); + var rationaleJson = reader.IsDBNull(10) ? string.Empty : reader.GetFieldValue(10); JsonArray rationale; if (string.IsNullOrWhiteSpace(rationaleJson)) { @@ -182,8 +207,8 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo { rationale = JsonNode.Parse(rationaleJson) as JsonArray ?? new JsonArray(); } - var updatedAt = reader.GetFieldValue(6); - var cycleHash = reader.GetString(7); + var updatedAt = reader.GetFieldValue(11); + var cycleHash = reader.GetString(12); return new FindingProjection( tenantId, @@ -191,6 +216,11 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo policyVersion, status, severity, + riskScore, + riskSeverity, + riskProfileVersion, + riskExplanationId, + riskEventSequence, labels, currentEventId, explainRef, @@ -203,7 +233,7 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo { ArgumentNullException.ThrowIfNull(projection); - await using var connection = await _dataSource.OpenConnectionAsync(projection.TenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(projection.TenantId, "projector", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(UpsertProjectionSql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; @@ -212,6 +242,11 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo command.Parameters.AddWithValue("policy_version", projection.PolicyVersion); command.Parameters.AddWithValue("status", projection.Status); command.Parameters.AddWithValue("severity", projection.Severity.HasValue ? projection.Severity.Value : (object)DBNull.Value); + command.Parameters.AddWithValue("risk_score", projection.RiskScore.HasValue ? projection.RiskScore.Value : (object)DBNull.Value); + command.Parameters.AddWithValue("risk_severity", projection.RiskSeverity ?? (object)DBNull.Value); + command.Parameters.AddWithValue("risk_profile_version", projection.RiskProfileVersion ?? (object)DBNull.Value); + command.Parameters.AddWithValue("risk_explanation_id", projection.RiskExplanationId ?? (object)DBNull.Value); + command.Parameters.AddWithValue("risk_event_sequence", projection.RiskEventSequence.HasValue ? projection.RiskEventSequence.Value : (object)DBNull.Value); var labelsCanonical = LedgerCanonicalJsonSerializer.Canonicalize(projection.Labels); var labelsJson = labelsCanonical.ToJsonString(); @@ -233,7 +268,7 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo { ArgumentNullException.ThrowIfNull(entry); - await using var connection = await _dataSource.OpenConnectionAsync(entry.TenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(entry.TenantId, "projector", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(InsertHistorySql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; @@ -254,7 +289,7 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo { ArgumentNullException.ThrowIfNull(entry); - await using var connection = await _dataSource.OpenConnectionAsync(entry.TenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(entry.TenantId, "projector", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(InsertActionSql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; @@ -275,7 +310,7 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo public async Task GetCheckpointAsync(CancellationToken cancellationToken) { - await using var connection = await _dataSource.OpenConnectionAsync(string.Empty, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(string.Empty, "projector", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(SelectCheckpointSql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; command.Parameters.AddWithValue("worker_id", DefaultWorkerId); @@ -296,7 +331,7 @@ public sealed class PostgresFindingProjectionRepository : IFindingProjectionRepo { ArgumentNullException.ThrowIfNull(checkpoint); - await using var connection = await _dataSource.OpenConnectionAsync(string.Empty, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(string.Empty, "projector", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(UpsertCheckpointSql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresLedgerEventRepository.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresLedgerEventRepository.cs index 5ae03c12d..3c909d550 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresLedgerEventRepository.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresLedgerEventRepository.cs @@ -96,7 +96,7 @@ public sealed class PostgresLedgerEventRepository : ILedgerEventRepository public async Task GetByEventIdAsync(string tenantId, Guid eventId, CancellationToken cancellationToken) { - await using var connection = await _dataSource.OpenConnectionAsync(tenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(tenantId, "writer-read", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(SelectByEventIdSql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; command.Parameters.AddWithValue("tenant_id", tenantId); @@ -113,7 +113,7 @@ public sealed class PostgresLedgerEventRepository : ILedgerEventRepository public async Task GetChainHeadAsync(string tenantId, Guid chainId, CancellationToken cancellationToken) { - await using var connection = await _dataSource.OpenConnectionAsync(tenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(tenantId, "writer-read", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(SelectChainHeadSql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; command.Parameters.AddWithValue("tenant_id", tenantId); @@ -133,7 +133,7 @@ public sealed class PostgresLedgerEventRepository : ILedgerEventRepository public async Task AppendAsync(LedgerEventRecord record, CancellationToken cancellationToken) { - await using var connection = await _dataSource.OpenConnectionAsync(record.TenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(record.TenantId, "writer", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(InsertEventSql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; @@ -236,7 +236,7 @@ public sealed class PostgresLedgerEventRepository : ILedgerEventRepository ORDER BY recorded_at DESC """; - await using var connection = await _dataSource.OpenConnectionAsync(tenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(tenantId, "writer-read", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(sql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; command.Parameters.AddWithValue("tenant_id", tenantId); diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresLedgerEventStream.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresLedgerEventStream.cs index a508cddb7..99d8d6a12 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresLedgerEventStream.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresLedgerEventStream.cs @@ -57,7 +57,7 @@ public sealed class PostgresLedgerEventStream : ILedgerEventStream var records = new List(batchSize); - await using var connection = await _dataSource.OpenConnectionAsync(string.Empty, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(string.Empty, "projector", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(ReadEventsSql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; command.Parameters.AddWithValue("last_recorded_at", checkpoint.LastRecordedAt); diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresMerkleAnchorRepository.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresMerkleAnchorRepository.cs index 82b5a6d92..b5930e281 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresMerkleAnchorRepository.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresMerkleAnchorRepository.cs @@ -55,7 +55,7 @@ public sealed class PostgresMerkleAnchorRepository : IMerkleAnchorRepository string? anchorReference, CancellationToken cancellationToken) { - await using var connection = await _dataSource.OpenConnectionAsync(tenantId, cancellationToken).ConfigureAwait(false); + await using var connection = await _dataSource.OpenConnectionAsync(tenantId, "anchor", cancellationToken).ConfigureAwait(false); await using var command = new NpgsqlCommand(InsertAnchorSql, connection); command.CommandTimeout = _dataSource.CommandTimeoutSeconds; diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresOrchestratorExportRepository.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresOrchestratorExportRepository.cs new file mode 100644 index 000000000..65e5601f2 --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Postgres/PostgresOrchestratorExportRepository.cs @@ -0,0 +1,146 @@ +using Microsoft.Extensions.Logging; +using Npgsql; +using NpgsqlTypes; +using StellaOps.Findings.Ledger.Infrastructure.Exports; + +namespace StellaOps.Findings.Ledger.Infrastructure.Postgres; + +public sealed class PostgresOrchestratorExportRepository : IOrchestratorExportRepository +{ + private const string UpsertSql = """ + INSERT INTO orchestrator_exports ( + tenant_id, + run_id, + job_type, + artifact_hash, + policy_hash, + started_at, + completed_at, + status, + manifest_path, + logs_path, + merkle_root, + created_at) + VALUES ( + @tenant_id, + @run_id, + @job_type, + @artifact_hash, + @policy_hash, + @started_at, + @completed_at, + @status, + @manifest_path, + @logs_path, + @merkle_root, + @created_at) + ON CONFLICT (tenant_id, run_id) + DO UPDATE SET + job_type = EXCLUDED.job_type, + artifact_hash = EXCLUDED.artifact_hash, + policy_hash = EXCLUDED.policy_hash, + started_at = EXCLUDED.started_at, + completed_at = EXCLUDED.completed_at, + status = EXCLUDED.status, + manifest_path = EXCLUDED.manifest_path, + logs_path = EXCLUDED.logs_path, + merkle_root = EXCLUDED.merkle_root, + created_at = EXCLUDED.created_at; + """; + + private const string SelectByArtifactSql = """ + SELECT run_id, + job_type, + artifact_hash, + policy_hash, + started_at, + completed_at, + status, + manifest_path, + logs_path, + merkle_root, + created_at + FROM orchestrator_exports + WHERE tenant_id = @tenant_id + AND artifact_hash = @artifact_hash + ORDER BY completed_at DESC NULLS LAST, started_at DESC; + """; + + private readonly LedgerDataSource _dataSource; + private readonly ILogger _logger; + + public PostgresOrchestratorExportRepository( + LedgerDataSource dataSource, + ILogger logger) + { + _dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task InsertAsync(OrchestratorExportRecord record, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(record); + + await using var connection = await _dataSource.OpenConnectionAsync(record.TenantId, "orchestrator-export", cancellationToken).ConfigureAwait(false); + await using var command = new NpgsqlCommand(UpsertSql, connection) + { + CommandTimeout = _dataSource.CommandTimeoutSeconds + }; + + command.Parameters.Add(new NpgsqlParameter("tenant_id", record.TenantId) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("run_id", record.RunId) { NpgsqlDbType = NpgsqlDbType.Uuid }); + command.Parameters.Add(new NpgsqlParameter("job_type", record.JobType) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("artifact_hash", record.ArtifactHash) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("policy_hash", record.PolicyHash) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("started_at", record.StartedAt) { NpgsqlDbType = NpgsqlDbType.TimestampTz }); + command.Parameters.Add(new NpgsqlParameter("completed_at", record.CompletedAt) { NpgsqlDbType = NpgsqlDbType.TimestampTz }); + command.Parameters.Add(new NpgsqlParameter("status", record.Status) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("manifest_path", record.ManifestPath) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("logs_path", record.LogsPath) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("merkle_root", record.MerkleRoot) { NpgsqlDbType = NpgsqlDbType.Char }); + command.Parameters.Add(new NpgsqlParameter("created_at", record.CreatedAt) { NpgsqlDbType = NpgsqlDbType.TimestampTz }); + + try + { + await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); + } + catch (PostgresException ex) + { + _logger.LogError(ex, "Failed to upsert orchestrator export for tenant {TenantId} run {RunId}.", record.TenantId, record.RunId); + throw; + } + } + + public async Task> GetByArtifactAsync(string tenantId, string artifactHash, CancellationToken cancellationToken) + { + var results = new List(); + + await using var connection = await _dataSource.OpenConnectionAsync(tenantId, "orchestrator-export", cancellationToken).ConfigureAwait(false); + await using var command = new NpgsqlCommand(SelectByArtifactSql, connection) + { + CommandTimeout = _dataSource.CommandTimeoutSeconds + }; + command.Parameters.Add(new NpgsqlParameter("tenant_id", tenantId) { NpgsqlDbType = NpgsqlDbType.Text }); + command.Parameters.Add(new NpgsqlParameter("artifact_hash", artifactHash) { NpgsqlDbType = NpgsqlDbType.Text }); + + await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false); + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) + { + results.Add(new OrchestratorExportRecord( + TenantId: tenantId, + RunId: reader.GetGuid(0), + JobType: reader.GetString(1), + ArtifactHash: reader.GetString(2), + PolicyHash: reader.GetString(3), + StartedAt: reader.GetFieldValue(4), + CompletedAt: reader.IsDBNull(5) ? (DateTimeOffset?)null : reader.GetFieldValue(5), + Status: reader.GetString(6), + ManifestPath: reader.IsDBNull(7) ? null : reader.GetString(7), + LogsPath: reader.IsDBNull(8) ? null : reader.GetString(8), + MerkleRoot: reader.GetString(9), + CreatedAt: reader.GetFieldValue(10))); + } + + return results; + } +} diff --git a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Projection/LedgerProjectionWorker.cs b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Projection/LedgerProjectionWorker.cs index 14696edf6..d61958849 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Projection/LedgerProjectionWorker.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Infrastructure/Projection/LedgerProjectionWorker.cs @@ -74,6 +74,10 @@ public sealed class LedgerProjectionWorker : BackgroundService continue; } + var batchStopwatch = Stopwatch.StartNew(); + var batchTenant = batch[0].TenantId; + var batchFailed = false; + foreach (var record in batch) { using var scope = _logger.BeginScope(new Dictionary @@ -86,6 +90,19 @@ public sealed class LedgerProjectionWorker : BackgroundService }); using var activity = LedgerTelemetry.StartProjectionApply(record); var applyStopwatch = Stopwatch.StartNew(); + if (!LedgerEventConstants.IsFindingEvent(record.EventType)) + { + checkpoint = checkpoint with + { + LastRecordedAt = record.RecordedAt, + LastEventId = record.EventId, + UpdatedAt = _timeProvider.GetUtcNow() + }; + + await _repository.SaveCheckpointAsync(checkpoint, stoppingToken).ConfigureAwait(false); + _logger.LogInformation("Skipped non-finding ledger event {EventId} type {EventType} during projection.", record.EventId, record.EventType); + continue; + } string? evaluationStatus = null; try @@ -131,10 +148,17 @@ public sealed class LedgerProjectionWorker : BackgroundService { LedgerTelemetry.MarkError(activity, "projection_failed"); _logger.LogError(ex, "Failed to project ledger event {EventId} for tenant {TenantId}.", record.EventId, record.TenantId); + batchFailed = true; await DelayAsync(stoppingToken).ConfigureAwait(false); break; } } + + batchStopwatch.Stop(); + if (!batchFailed) + { + LedgerMetrics.RecordProjectionRebuild(batchStopwatch.Elapsed, batchTenant, "replay"); + } } } diff --git a/src/Findings/StellaOps.Findings.Ledger/Observability/LedgerMetrics.cs b/src/Findings/StellaOps.Findings.Ledger/Observability/LedgerMetrics.cs index 0b767752d..68464ee95 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Observability/LedgerMetrics.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Observability/LedgerMetrics.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Diagnostics.Metrics; namespace StellaOps.Findings.Ledger.Observability; @@ -6,10 +7,16 @@ internal static class LedgerMetrics { private static readonly Meter Meter = new("StellaOps.Findings.Ledger"); + private static readonly Histogram WriteDurationSeconds = Meter.CreateHistogram( + "ledger_write_duration_seconds", + unit: "s", + description: "Latency of successful ledger append operations."); + + // Compatibility with earlier drafts private static readonly Histogram WriteLatencySeconds = Meter.CreateHistogram( "ledger_write_latency_seconds", unit: "s", - description: "Latency of successful ledger append operations."); + description: "Deprecated alias for ledger_write_duration_seconds."); private static readonly Counter EventsTotal = Meter.CreateCounter( "ledger_events_total", @@ -20,15 +27,40 @@ internal static class LedgerMetrics unit: "s", description: "Duration to apply a ledger event to the finding projection."); - private static readonly Histogram ProjectionLagSeconds = Meter.CreateHistogram( - "ledger_projection_lag_seconds", + private static readonly Histogram ProjectionRebuildSeconds = Meter.CreateHistogram( + "ledger_projection_rebuild_seconds", unit: "s", - description: "Lag between ledger recorded_at and projection application time."); + description: "Duration of projection replay/rebuild batches."); private static readonly Counter ProjectionEventsTotal = Meter.CreateCounter( "ledger_projection_events_total", description: "Number of ledger events applied to projections."); + private static readonly Histogram MerkleAnchorDurationSeconds = Meter.CreateHistogram( + "ledger_merkle_anchor_duration_seconds", + unit: "s", + description: "Duration to persist Merkle anchor batches."); + + private static readonly Counter MerkleAnchorFailures = Meter.CreateCounter( + "ledger_merkle_anchor_failures_total", + description: "Count of Merkle anchor failures by reason."); + + private static readonly ObservableGauge ProjectionLagGauge = + Meter.CreateObservableGauge("ledger_projection_lag_seconds", ObserveProjectionLag, unit: "s", + description: "Lag between ledger recorded_at and projection application time."); + + private static readonly ObservableGauge IngestBacklogGauge = + Meter.CreateObservableGauge("ledger_ingest_backlog_events", ObserveBacklog, + description: "Number of events buffered for ingestion/anchoring."); + + private static readonly ObservableGauge DbConnectionsGauge = + Meter.CreateObservableGauge("ledger_db_connections_active", ObserveDbConnections, + description: "Active PostgreSQL connections by role."); + + private static readonly ConcurrentDictionary ProjectionLagByTenant = new(StringComparer.Ordinal); + private static readonly ConcurrentDictionary DbConnectionsByRole = new(StringComparer.OrdinalIgnoreCase); + private static long _ingestBacklog; + public static void RecordWriteSuccess(TimeSpan duration, string? tenantId, string? eventType, string? source) { var tags = new KeyValuePair[] @@ -38,6 +70,7 @@ internal static class LedgerMetrics new("source", source ?? string.Empty) }; + WriteDurationSeconds.Record(duration.TotalSeconds, tags); WriteLatencySeconds.Record(duration.TotalSeconds, tags); EventsTotal.Add(1, tags); } @@ -59,7 +92,90 @@ internal static class LedgerMetrics }; ProjectionApplySeconds.Record(duration.TotalSeconds, tags); - ProjectionLagSeconds.Record(lagSeconds, tags); ProjectionEventsTotal.Add(1, tags); + UpdateProjectionLag(tenantId, lagSeconds); } + + public static void RecordProjectionRebuild(TimeSpan duration, string? tenantId, string scenario) + { + var tags = new KeyValuePair[] + { + new("tenant", tenantId ?? string.Empty), + new("scenario", scenario) + }; + + ProjectionRebuildSeconds.Record(duration.TotalSeconds, tags); + } + + public static void RecordMerkleAnchorDuration(TimeSpan duration, string tenantId, int leafCount) + { + var tags = new KeyValuePair[] + { + new("tenant", tenantId), + new("leaf_count", leafCount) + }; + MerkleAnchorDurationSeconds.Record(duration.TotalSeconds, tags); + } + + public static void RecordMerkleAnchorFailure(string tenantId, string reason) + { + var tags = new KeyValuePair[] + { + new("tenant", tenantId), + new("reason", reason) + }; + MerkleAnchorFailures.Add(1, tags); + } + + public static void IncrementBacklog() => Interlocked.Increment(ref _ingestBacklog); + + public static void DecrementBacklog() + { + var value = Interlocked.Decrement(ref _ingestBacklog); + if (value < 0) + { + Interlocked.Exchange(ref _ingestBacklog, 0); + } + } + + public static void ConnectionOpened(string role) + { + var normalized = NormalizeRole(role); + DbConnectionsByRole.AddOrUpdate(normalized, _ => 1, (_, current) => current + 1); + } + + public static void ConnectionClosed(string role) + { + var normalized = NormalizeRole(role); + DbConnectionsByRole.AddOrUpdate(normalized, _ => 0, (_, current) => Math.Max(0, current - 1)); + } + + public static void UpdateProjectionLag(string? tenantId, double lagSeconds) + { + var key = string.IsNullOrWhiteSpace(tenantId) ? string.Empty : tenantId; + ProjectionLagByTenant[key] = lagSeconds < 0 ? 0 : lagSeconds; + } + + private static IEnumerable> ObserveProjectionLag() + { + foreach (var kvp in ProjectionLagByTenant) + { + yield return new Measurement(kvp.Value, new KeyValuePair("tenant", kvp.Key)); + } + } + + private static IEnumerable> ObserveBacklog() + { + yield return new Measurement(Interlocked.Read(ref _ingestBacklog)); + } + + private static IEnumerable> ObserveDbConnections() + { + foreach (var kvp in DbConnectionsByRole) + { + yield return new Measurement(kvp.Value, new KeyValuePair("role", kvp.Key)); + } + } + + private static string NormalizeRole(string role) => string.IsNullOrWhiteSpace(role) ? "unspecified" : role.ToLowerInvariant(); } diff --git a/src/Findings/StellaOps.Findings.Ledger/Observability/LedgerTimeline.cs b/src/Findings/StellaOps.Findings.Ledger/Observability/LedgerTimeline.cs index d1d19418a..4cf278e24 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Observability/LedgerTimeline.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Observability/LedgerTimeline.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using Microsoft.Extensions.Logging; using StellaOps.Findings.Ledger.Domain; +using StellaOps.Findings.Ledger.Infrastructure.Exports; namespace StellaOps.Findings.Ledger.Observability; @@ -12,6 +13,8 @@ internal static class LedgerTimeline { private static readonly EventId LedgerAppended = new(6101, "ledger.event.appended"); private static readonly EventId ProjectionUpdated = new(6201, "ledger.projection.updated"); + private static readonly EventId OrchestratorExport = new(6301, "ledger.export.recorded"); + private static readonly EventId AirgapImport = new(6401, "ledger.airgap.imported"); public static void EmitLedgerAppended(ILogger logger, LedgerEventRecord record, string? evidenceBundleRef = null) { @@ -62,4 +65,38 @@ internal static class LedgerTimeline traceId, evidenceBundleRef ?? record.EvidenceBundleReference ?? string.Empty); } + + public static void EmitOrchestratorExport(ILogger logger, OrchestratorExportRecord record) + { + if (logger is null) + { + return; + } + + logger.LogInformation( + OrchestratorExport, + "timeline ledger.export.recorded tenant={Tenant} run={RunId} artifact={ArtifactHash} policy={PolicyHash} status={Status} merkle_root={MerkleRoot}", + record.TenantId, + record.RunId, + record.ArtifactHash, + record.PolicyHash, + record.Status, + record.MerkleRoot); + } + + public static void EmitAirgapImport(ILogger logger, string tenantId, string bundleId, string merkleRoot, Guid? ledgerEventId) + { + if (logger is null) + { + return; + } + + logger.LogInformation( + AirgapImport, + "timeline ledger.airgap.imported tenant={Tenant} bundle={BundleId} merkle_root={MerkleRoot} ledger_event={LedgerEvent}", + tenantId, + bundleId, + merkleRoot, + ledgerEventId?.ToString() ?? string.Empty); + } } diff --git a/src/Findings/StellaOps.Findings.Ledger/Services/AirgapImportService.cs b/src/Findings/StellaOps.Findings.Ledger/Services/AirgapImportService.cs new file mode 100644 index 000000000..a74e815b2 --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger/Services/AirgapImportService.cs @@ -0,0 +1,152 @@ +using System.Text.Json.Nodes; +using Microsoft.Extensions.Logging; +using StellaOps.Findings.Ledger.Domain; +using StellaOps.Findings.Ledger.Infrastructure; +using StellaOps.Findings.Ledger.Infrastructure.AirGap; +using StellaOps.Findings.Ledger.Observability; + +namespace StellaOps.Findings.Ledger.Services; + +public sealed record AirgapImportInput( + string TenantId, + string BundleId, + string? MirrorGeneration, + string MerkleRoot, + DateTimeOffset TimeAnchor, + string? Publisher, + string? HashAlgorithm, + IReadOnlyList Contents, + string? ImportOperator); + +public sealed record AirgapImportResult( + bool Success, + Guid ChainId, + long? SequenceNumber, + Guid? LedgerEventId, + string? Error); + +public sealed class AirgapImportService +{ + private readonly ILedgerEventRepository _ledgerEventRepository; + private readonly ILedgerEventWriteService _writeService; + private readonly IAirgapImportRepository _repository; + private readonly TimeProvider _timeProvider; + private readonly ILogger _logger; + + public AirgapImportService( + ILedgerEventRepository ledgerEventRepository, + ILedgerEventWriteService writeService, + IAirgapImportRepository repository, + TimeProvider timeProvider, + ILogger logger) + { + _ledgerEventRepository = ledgerEventRepository ?? throw new ArgumentNullException(nameof(ledgerEventRepository)); + _writeService = writeService ?? throw new ArgumentNullException(nameof(writeService)); + _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + _timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task RecordAsync(AirgapImportInput input, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(input); + + var chainId = LedgerChainIdGenerator.FromTenantSubject(input.TenantId, $"airgap::{input.BundleId}"); + var chainHead = await _ledgerEventRepository.GetChainHeadAsync(input.TenantId, chainId, cancellationToken).ConfigureAwait(false); + var sequence = (chainHead?.SequenceNumber ?? 0) + 1; + var previousHash = chainHead?.EventHash ?? LedgerEventConstants.EmptyHash; + + var eventId = Guid.NewGuid(); + var recordedAt = _timeProvider.GetUtcNow(); + + var payload = new JsonObject + { + ["airgap"] = new JsonObject + { + ["bundleId"] = input.BundleId, + ["mirrorGeneration"] = input.MirrorGeneration, + ["merkleRoot"] = input.MerkleRoot, + ["timeAnchor"] = input.TimeAnchor.ToUniversalTime().ToString("O"), + ["publisher"] = input.Publisher, + ["hashAlgorithm"] = input.HashAlgorithm, + ["contents"] = new JsonArray(input.Contents.Select(c => (JsonNode)c).ToArray()) + } + }; + + var envelope = new JsonObject + { + ["event"] = new JsonObject + { + ["id"] = eventId.ToString(), + ["type"] = LedgerEventConstants.EventAirgapBundleImported, + ["tenant"] = input.TenantId, + ["chainId"] = chainId.ToString(), + ["sequence"] = sequence, + ["policyVersion"] = input.MirrorGeneration ?? "airgap-bundle", + ["artifactId"] = input.BundleId, + ["finding"] = new JsonObject + { + ["id"] = input.BundleId, + ["artifactId"] = input.BundleId, + ["vulnId"] = "airgap-import" + }, + ["actor"] = new JsonObject + { + ["id"] = input.ImportOperator ?? "airgap-operator", + ["type"] = "operator" + }, + ["occurredAt"] = FormatTimestamp(input.TimeAnchor), + ["recordedAt"] = FormatTimestamp(recordedAt), + ["payload"] = payload.DeepClone() + } + }; + + var draft = new LedgerEventDraft( + input.TenantId, + chainId, + sequence, + eventId, + LedgerEventConstants.EventAirgapBundleImported, + input.MirrorGeneration ?? "airgap-bundle", + input.BundleId, + input.BundleId, + SourceRunId: null, + ActorId: input.ImportOperator ?? "airgap-operator", + ActorType: "operator", + OccurredAt: input.TimeAnchor.ToUniversalTime(), + RecordedAt: recordedAt, + Payload: payload, + CanonicalEnvelope: envelope, + ProvidedPreviousHash: previousHash); + + var writeResult = await _writeService.AppendAsync(draft, cancellationToken).ConfigureAwait(false); + if (writeResult.Status is not (LedgerWriteStatus.Success or LedgerWriteStatus.Idempotent)) + { + var error = string.Join(";", writeResult.Errors); + return new AirgapImportResult(false, chainId, sequence, writeResult.Record?.EventId, error); + } + + var ledgerEventId = writeResult.Record?.EventId; + + var record = new AirgapImportRecord( + input.TenantId, + input.BundleId, + input.MirrorGeneration, + input.MerkleRoot, + input.TimeAnchor.ToUniversalTime(), + input.Publisher, + input.HashAlgorithm, + new JsonArray(input.Contents.Select(c => (JsonNode)c).ToArray()), + recordedAt, + input.ImportOperator, + ledgerEventId); + + await _repository.InsertAsync(record, cancellationToken).ConfigureAwait(false); + LedgerTimeline.EmitAirgapImport(_logger, input.TenantId, input.BundleId, input.MerkleRoot, ledgerEventId); + + return new AirgapImportResult(true, chainId, sequence, ledgerEventId, null); + } + + private static string FormatTimestamp(DateTimeOffset value) + => value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'"); +} diff --git a/src/Findings/StellaOps.Findings.Ledger/Services/LedgerProjectionReducer.cs b/src/Findings/StellaOps.Findings.Ledger/Services/LedgerProjectionReducer.cs index 3ca749d19..c66205901 100644 --- a/src/Findings/StellaOps.Findings.Ledger/Services/LedgerProjectionReducer.cs +++ b/src/Findings/StellaOps.Findings.Ledger/Services/LedgerProjectionReducer.cs @@ -22,6 +22,11 @@ public static class LedgerProjectionReducer var status = evaluation.Status ?? DetermineStatus(record.EventType, payload, current?.Status); var severity = evaluation.Severity ?? DetermineSeverity(payload, current?.Severity); + var riskScore = evaluation.RiskScore ?? current?.RiskScore; + var riskSeverity = evaluation.RiskSeverity ?? current?.RiskSeverity; + var riskProfileVersion = evaluation.RiskProfileVersion ?? current?.RiskProfileVersion; + var riskExplanationId = evaluation.RiskExplanationId ?? current?.RiskExplanationId; + var riskEventSequence = evaluation.RiskEventSequence ?? current?.RiskEventSequence ?? record.SequenceNumber; var labels = CloneLabels(evaluation.Labels); MergeLabels(labels, payload); @@ -41,6 +46,11 @@ public static class LedgerProjectionReducer record.PolicyVersion, status, severity, + riskScore, + riskSeverity, + riskProfileVersion, + riskExplanationId, + riskEventSequence, labels, record.EventId, explainRef, diff --git a/src/Findings/StellaOps.Findings.Ledger/Services/OrchestratorExportService.cs b/src/Findings/StellaOps.Findings.Ledger/Services/OrchestratorExportService.cs new file mode 100644 index 000000000..e05965284 --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger/Services/OrchestratorExportService.cs @@ -0,0 +1,86 @@ +using System.Text.Json.Nodes; +using Microsoft.Extensions.Logging; +using StellaOps.Findings.Ledger.Hashing; +using StellaOps.Findings.Ledger.Infrastructure.Exports; +using StellaOps.Findings.Ledger.Observability; + +namespace StellaOps.Findings.Ledger.Services; + +public sealed record OrchestratorExportInput( + string TenantId, + Guid RunId, + string JobType, + string ArtifactHash, + string PolicyHash, + DateTimeOffset StartedAt, + DateTimeOffset? CompletedAt, + string Status, + string? ManifestPath, + string? LogsPath); + +public sealed class OrchestratorExportService +{ + private readonly IOrchestratorExportRepository _repository; + private readonly TimeProvider _timeProvider; + private readonly ILogger _logger; + + public OrchestratorExportService( + IOrchestratorExportRepository repository, + TimeProvider timeProvider, + ILogger logger) + { + _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + _timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task RecordAsync(OrchestratorExportInput input, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(input); + + var canonical = CreateCanonicalPayload(input); + var merkleRoot = HashUtilities.ComputeSha256Hex(LedgerCanonicalJsonSerializer.Serialize(canonical)); + + var record = new OrchestratorExportRecord( + input.TenantId, + input.RunId, + input.JobType, + input.ArtifactHash, + input.PolicyHash, + input.StartedAt.ToUniversalTime(), + input.CompletedAt?.ToUniversalTime(), + input.Status, + input.ManifestPath, + input.LogsPath, + merkleRoot, + _timeProvider.GetUtcNow()); + + await _repository.InsertAsync(record, cancellationToken).ConfigureAwait(false); + LedgerTimeline.EmitOrchestratorExport(_logger, record); + return record; + } + + public Task> GetByArtifactAsync(string tenantId, string artifactHash, CancellationToken cancellationToken) + { + return _repository.GetByArtifactAsync(tenantId, artifactHash, cancellationToken); + } + + private static JsonObject CreateCanonicalPayload(OrchestratorExportInput input) + { + var payload = new JsonObject + { + ["tenantId"] = input.TenantId, + ["runId"] = input.RunId.ToString(), + ["jobType"] = input.JobType, + ["artifactHash"] = input.ArtifactHash, + ["policyHash"] = input.PolicyHash, + ["startedAt"] = input.StartedAt.ToUniversalTime().ToString("O"), + ["completedAt"] = input.CompletedAt?.ToUniversalTime().ToString("O"), + ["status"] = input.Status, + ["manifestPath"] = input.ManifestPath, + ["logsPath"] = input.LogsPath + }; + + return LedgerCanonicalJsonSerializer.Canonicalize(payload); + } +} diff --git a/src/Findings/StellaOps.Findings.Ledger/TASKS.md b/src/Findings/StellaOps.Findings.Ledger/TASKS.md new file mode 100644 index 000000000..25c247bdc --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger/TASKS.md @@ -0,0 +1,9 @@ +# Findings Ledger · Sprint 0120-0000-0001 + +| Task ID | Status | Notes | Updated (UTC) | +| --- | --- | --- | --- | +| LEDGER-29-008 | DOING | Determinism harness, metrics, replay tests | 2025-11-22 | +| LEDGER-34-101 | TODO | Orchestrator export linkage | 2025-11-22 | +| LEDGER-AIRGAP-56-001 | TODO | Mirror bundle provenance recording | 2025-11-22 | + +Status changes must be mirrored in `docs/implplan/SPRINT_0120_0000_0001_policy_reasoning.md`. diff --git a/src/Findings/StellaOps.Findings.Ledger/migrations/006_orchestrator_airgap.sql b/src/Findings/StellaOps.Findings.Ledger/migrations/006_orchestrator_airgap.sql new file mode 100644 index 000000000..3edc1b54f --- /dev/null +++ b/src/Findings/StellaOps.Findings.Ledger/migrations/006_orchestrator_airgap.sql @@ -0,0 +1,51 @@ +-- 006_orchestrator_airgap.sql +-- Add orchestrator export provenance and air-gap import provenance tables (LEDGER-34-101, LEDGER-AIRGAP-56-001) + +BEGIN; + +CREATE TABLE IF NOT EXISTS orchestrator_exports +( + tenant_id TEXT NOT NULL, + run_id UUID NOT NULL, + job_type TEXT NOT NULL, + artifact_hash TEXT NOT NULL, + policy_hash TEXT NOT NULL, + started_at TIMESTAMPTZ NOT NULL, + completed_at TIMESTAMPTZ, + status TEXT NOT NULL, + manifest_path TEXT, + logs_path TEXT, + merkle_root CHAR(64) NOT NULL, + created_at TIMESTAMPTZ NOT NULL, + PRIMARY KEY (tenant_id, run_id) +); + +CREATE UNIQUE INDEX IF NOT EXISTS ix_orchestrator_exports_artifact_run + ON orchestrator_exports (tenant_id, artifact_hash, run_id); + +CREATE INDEX IF NOT EXISTS ix_orchestrator_exports_artifact + ON orchestrator_exports (tenant_id, artifact_hash); + +CREATE TABLE IF NOT EXISTS airgap_imports +( + tenant_id TEXT NOT NULL, + bundle_id TEXT NOT NULL, + mirror_generation TEXT, + merkle_root TEXT NOT NULL, + time_anchor TIMESTAMPTZ NOT NULL, + publisher TEXT, + hash_algorithm TEXT, + contents JSONB, + imported_at TIMESTAMPTZ NOT NULL, + import_operator TEXT, + ledger_event_id UUID, + PRIMARY KEY (tenant_id, bundle_id, time_anchor) +); + +CREATE INDEX IF NOT EXISTS ix_airgap_imports_bundle + ON airgap_imports (tenant_id, bundle_id); + +CREATE INDEX IF NOT EXISTS ix_airgap_imports_event + ON airgap_imports (tenant_id, ledger_event_id); + +COMMIT; diff --git a/src/Findings/StellaOps.Findings.Ledger/tools/LedgerReplayHarness/Program.cs b/src/Findings/StellaOps.Findings.Ledger/tools/LedgerReplayHarness/Program.cs index 14e867d46..23298c97c 100644 --- a/src/Findings/StellaOps.Findings.Ledger/tools/LedgerReplayHarness/Program.cs +++ b/src/Findings/StellaOps.Findings.Ledger/tools/LedgerReplayHarness/Program.cs @@ -105,7 +105,8 @@ root.SetHandler(async (FileInfo[] fixtures, string connection, string tenant, in var verification = await VerifyLedgerAsync(scope.ServiceProvider, tenant, eventsWritten, cts.Token).ConfigureAwait(false); - var writeLatencyP95Ms = Percentile(metrics.HistDouble("ledger_write_latency_seconds"), 95) * 1000; + var writeDurations = metrics.HistDouble("ledger_write_duration_seconds").Concat(metrics.HistDouble("ledger_write_latency_seconds")); + var writeLatencyP95Ms = Percentile(writeDurations, 95) * 1000; var rebuildP95Ms = Percentile(metrics.HistDouble("ledger_projection_rebuild_seconds"), 95) * 1000; var projectionLagSeconds = metrics.GaugeDouble("ledger_projection_lag_seconds").DefaultIfEmpty(0).Max(); var backlogEvents = metrics.GaugeLong("ledger_ingest_backlog_events").DefaultIfEmpty(0).Max(); diff --git a/src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/InlinePolicyEvaluationServiceTests.cs b/src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/InlinePolicyEvaluationServiceTests.cs index 04e8d4d2f..2fb22c658 100644 --- a/src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/InlinePolicyEvaluationServiceTests.cs +++ b/src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/InlinePolicyEvaluationServiceTests.cs @@ -36,6 +36,11 @@ public sealed class InlinePolicyEvaluationServiceTests "policy-sha", "affected", 7.1m, + null, + null, + null, + null, + 1, new JsonObject { ["deprecated"] = "true" }, Guid.NewGuid(), null, @@ -68,6 +73,11 @@ public sealed class InlinePolicyEvaluationServiceTests "policy-sha", "accepted_risk", 3.4m, + null, + null, + null, + null, + 1, new JsonObject { ["runtime"] = "contained" }, Guid.NewGuid(), "explain://existing", diff --git a/src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/LedgerProjectionReducerTests.cs b/src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/LedgerProjectionReducerTests.cs index 14a72e073..dbc440db9 100644 --- a/src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/LedgerProjectionReducerTests.cs +++ b/src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/LedgerProjectionReducerTests.cs @@ -32,6 +32,11 @@ public sealed class LedgerProjectionReducerTests var evaluation = new PolicyEvaluationResult( "triaged", 6.5m, + null, + null, + null, + null, + 1, (JsonObject)payload["labels"]!.DeepClone(), payload["explainRef"]!.GetValue(), new JsonArray(payload["explainRef"]!.GetValue())); @@ -62,6 +67,11 @@ public sealed class LedgerProjectionReducerTests "policy-v1", "affected", 5.0m, + null, + null, + null, + null, + 1, new JsonObject(), Guid.NewGuid(), null, @@ -82,6 +92,11 @@ public sealed class LedgerProjectionReducerTests var evaluation = new PolicyEvaluationResult( "accepted_risk", existing.Severity, + null, + null, + null, + null, + existing.RiskEventSequence, (JsonObject)existing.Labels.DeepClone(), null, new JsonArray()); @@ -110,6 +125,11 @@ public sealed class LedgerProjectionReducerTests "policy-v1", "triaged", 7.1m, + null, + null, + null, + null, + 1, labels, Guid.NewGuid(), null, @@ -133,6 +153,11 @@ public sealed class LedgerProjectionReducerTests var evaluation = new PolicyEvaluationResult( "triaged", existing.Severity, + null, + null, + null, + null, + existing.RiskEventSequence, (JsonObject)payload["labels"]!.DeepClone(), null, new JsonArray()); diff --git a/src/Graph/AGENTS.md b/src/Graph/AGENTS.md new file mode 100644 index 000000000..a8e175a33 --- /dev/null +++ b/src/Graph/AGENTS.md @@ -0,0 +1,63 @@ +# AGENTS · Graph Module + +## Purpose & Scope +- Working directories: `src/Graph/StellaOps.Graph.Api`, `src/Graph/StellaOps.Graph.Indexer`, and `src/Graph/__Tests`. +- Modules covered: Graph API (query/search/paths/diff/overlay/export) and Graph Indexer (ingest, snapshot, overlays). +- Applicable sprints: `docs/implplan/SPRINT_0207_0001_0001_graph.md`, `docs/implplan/SPRINT_0141_0001_0001_graph_indexer.md`, and any follow-on graph docs sprints (`docs/implplan/SPRINT_0321_0001_0001_docs_modules_graph.md`). + +## Roles +- Backend engineer (.NET 10) — API, planners, overlays, exports. +- Data/ETL engineer — Indexer ingest, snapshots, overlays. +- QA/Perf engineer — deterministic tests, load/fuzz, offline parity. +- Docs maintainer — graph API/ops runbooks, Offline Kit notes. + +## Required Reading (treat as read before DOING) +- `docs/README.md` +- `docs/07_HIGH_LEVEL_ARCHITECTURE.md` +- `docs/modules/platform/architecture-overview.md` +- `docs/modules/graph/architecture.md` +- `docs/modules/graph/implementation_plan.md` +- Sprint doc for current work (e.g., `docs/implplan/SPRINT_0207_0001_0001_graph.md`). +- Policy overlay contract refs when touching overlays: `POLICY-ENGINE-30-001..003` (see policy module docs). + +## Determinism & Offline +- Default to deterministic ordering for streams/exports; manifest checksums required for `graphml/csv/ndjson` exports. +- Timestamps: UTC ISO-8601; avoid wall-clock in tests. +- Snapshot/export roots configurable via `STELLAOPS_GRAPH_SNAPSHOT_DIR` or `SbomIngestOptions.SnapshotRootDirectory`. +- Offline posture: no external calls beyond allowlisted feeds; prefer cached schemas and local nugets in `local-nugets/`. + +## Data & Environment +- Canonical store: MongoDB (>=3.0 driver). Tests use `STELLAOPS_TEST_MONGO_URI`; fallback `mongodb://127.0.0.1:27017`, then Mongo2Go. +- Collections: `graph_nodes`, `graph_edges`, `graph_overlays_cache`, `graph_snapshots`, `graph_saved_queries`. +- Tenant isolation mandatory on every query and export. + +## Testing Expectations +- Unit: node/edge builders, identifier stability, overlay calculators, planners, diff engine. +- Integration: ingest → snapshot → query/paths/diff/export end-to-end; RBAC + tenant guards. +- Performance: synthetic datasets (~500k nodes / 2M edges) with enforced budgets; capture latency metrics. +- Security: RBAC scopes (`graph:read/query/export`), audit logging, rate limiting. +- Offline: export/import parity for Offline Kit bundles; deterministic manifests verified in tests. + +## Observability +- Metrics to emit: `graph_ingest_lag_seconds`, `graph_tile_latency_seconds`, `graph_query_budget_denied_total`, `graph_overlay_cache_hit_ratio`, clustering counters from architecture doc. +- Structured logs with trace IDs; traces for ingest stages and query planner/executor. + +## Coding Standards +- Target framework: net10.0 with latest C# preview features. +- Use dependency injection; avoid static singletons. +- Respect module boundaries; shared libs only if declared in sprint or architecture docs. +- Naming: projects `StellaOps.Graph.Api`, `StellaOps.Graph.Indexer`; prefer `Graph*` prefixes for internal components. + +## Coordination & Status +- Update sprint Delivery Tracker statuses (TODO → DOING → DONE/BLOCKED) in relevant sprint file. +- If a required contract/doc is missing or stale, mark the affected task BLOCKED in the sprint and log under Decisions & Risks; do not pause work waiting for live answers. + +## Run/Test Commands (examples) +- Restore: `dotnet restore src/Graph/StellaOps.Graph.Api/StellaOps.Graph.Api.csproj --source ../local-nugets` +- Build: `dotnet build src/Graph/StellaOps.Graph.Api/StellaOps.Graph.Api.csproj -c Release` +- Tests: `dotnet test src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/StellaOps.Graph.Indexer.Tests.csproj` +- Lint/style: follow repo-wide analyzers in `Directory.Build.props` / `.editorconfig`. + +## Evidence +- Keep artefacts deterministic; attach manifest hashes in PR/sprint notes when delivering exports or snapshots. +- Document new metrics/routes/schemas under `docs/modules/graph` and link from sprint Decisions & Risks. diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsEngine.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsEngine.cs new file mode 100644 index 000000000..82d98c954 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsEngine.cs @@ -0,0 +1,471 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text.Json.Nodes; +using StellaOps.Graph.Indexer.Documents; + +namespace StellaOps.Graph.Indexer.Analytics; + +public sealed class GraphAnalyticsEngine +{ + private readonly GraphAnalyticsOptions _options; + + public GraphAnalyticsEngine(GraphAnalyticsOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + if (_options.MaxPropagationIterations <= 0) + { + throw new ArgumentOutOfRangeException(nameof(options.MaxPropagationIterations), "must be positive"); + } + } + + public GraphAnalyticsResult Compute(GraphAnalyticsSnapshot snapshot) + { + ArgumentNullException.ThrowIfNull(snapshot); + + var topology = BuildTopology(snapshot); + var clusters = ComputeClusters(topology); + var centrality = ComputeCentrality(topology); + + return new GraphAnalyticsResult(clusters, centrality); + } + + private GraphTopology BuildTopology(GraphAnalyticsSnapshot snapshot) + { + var nodes = snapshot.Nodes + .Select(node => new GraphNode(node["id"]!.GetValue(), node["kind"]!.GetValue())) + .ToImmutableArray(); + + var nodeLookup = nodes.ToImmutableDictionary(n => n.NodeId, n => n.Kind, StringComparer.Ordinal); + var adjacency = new Dictionary>(StringComparer.Ordinal); + + foreach (var node in nodes) + { + adjacency[node.NodeId] = new HashSet(StringComparer.Ordinal); + } + + var resolver = new EdgeEndpointResolver(snapshot.Nodes); + foreach (var edge in snapshot.Edges) + { + if (!resolver.TryResolve(edge, out var source, out var target)) + { + continue; + } + + if (!adjacency.ContainsKey(source) || !adjacency.ContainsKey(target)) + { + continue; + } + + // Treat the graph as undirected for clustering / centrality to stabilise communities. + adjacency[source].Add(target); + adjacency[target].Add(source); + } + + return new GraphTopology(nodes, adjacency, nodeLookup); + } + + private ImmutableArray ComputeClusters(GraphTopology topology) + { + var labels = topology.Nodes + .OrderBy(n => n.NodeId, StringComparer.Ordinal) + .Select(n => (NodeId: n.NodeId, Label: n.NodeId, Kind: n.Kind)) + .ToArray(); + + for (var iteration = 0; iteration < _options.MaxPropagationIterations; iteration++) + { + var updated = false; + foreach (ref var entry in labels.AsSpan()) + { + if (!topology.Adjacency.TryGetValue(entry.NodeId, out var neighbors) || neighbors.Count == 0) + { + continue; + } + + var best = SelectDominantLabel(neighbors, labels); + if (!string.Equals(best, entry.Label, StringComparison.Ordinal)) + { + entry.Label = best; + updated = true; + } + } + + if (!updated) + { + break; + } + } + + return labels + .OrderBy(t => t.NodeId, StringComparer.Ordinal) + .Select(t => new ClusterAssignment(t.NodeId, t.Label, t.Kind)) + .ToImmutableArray(); + } + + private static string SelectDominantLabel(IEnumerable neighbors, (string NodeId, string Label, string Kind)[] labels) + { + var labelCounts = new Dictionary(StringComparer.Ordinal); + foreach (var neighbor in neighbors) + { + var neighborLabel = labels.First(t => t.NodeId == neighbor).Label; + labelCounts.TryGetValue(neighborLabel, out var count); + labelCounts[neighborLabel] = count + 1; + } + + var max = labelCounts.Max(kvp => kvp.Value); + return labelCounts + .Where(kvp => kvp.Value == max) + .Select(kvp => kvp.Key) + .OrderBy(label => label, StringComparer.Ordinal) + .First(); + } + + private ImmutableArray ComputeCentrality(GraphTopology topology) + { + var degreeScores = new Dictionary(StringComparer.Ordinal); + foreach (var (nodeId, neighbors) in topology.Adjacency) + { + degreeScores[nodeId] = neighbors.Count; + } + + var betweenness = CalculateBetweenness(topology); + + return topology.Nodes + .OrderBy(n => n.NodeId, StringComparer.Ordinal) + .Select(n => new CentralityScore( + n.NodeId, + degreeScores.TryGetValue(n.NodeId, out var degree) ? degree : 0d, + betweenness.TryGetValue(n.NodeId, out var between) ? between : 0d, + n.Kind)) + .ToImmutableArray(); + } + + private Dictionary CalculateBetweenness(GraphTopology topology) + { + var scores = topology.Nodes.ToDictionary(n => n.NodeId, _ => 0d, StringComparer.Ordinal); + if (scores.Count == 0) + { + return scores; + } + + var sampled = topology.Nodes + .OrderBy(n => n.NodeId, StringComparer.Ordinal) + .Take(Math.Min(_options.BetweennessSampleSize, topology.Nodes.Length)) + .ToArray(); + + foreach (var source in sampled) + { + var stack = new Stack(); + var predecessors = new Dictionary>(StringComparer.Ordinal); + var sigma = new Dictionary(StringComparer.Ordinal); + var distance = new Dictionary(StringComparer.Ordinal); + var queue = new Queue(); + + foreach (var node in topology.Nodes) + { + predecessors[node.NodeId] = new List(); + sigma[node.NodeId] = 0; + distance[node.NodeId] = -1; + } + + sigma[source.NodeId] = 1; + distance[source.NodeId] = 0; + queue.Enqueue(source.NodeId); + + while (queue.Count > 0) + { + var v = queue.Dequeue(); + stack.Push(v); + + foreach (var neighbor in topology.GetNeighbors(v)) + { + if (distance[neighbor] < 0) + { + distance[neighbor] = distance[v] + 1; + queue.Enqueue(neighbor); + } + + if (distance[neighbor] == distance[v] + 1) + { + sigma[neighbor] += sigma[v]; + predecessors[neighbor].Add(v); + } + } + } + + var delta = topology.Nodes.ToDictionary(n => n.NodeId, _ => 0d, StringComparer.Ordinal); + while (stack.Count > 0) + { + var w = stack.Pop(); + foreach (var v in predecessors[w]) + { + delta[v] += (sigma[v] / sigma[w]) * (1 + delta[w]); + } + + if (!string.Equals(w, source.NodeId, StringComparison.Ordinal)) + { + scores[w] += delta[w]; + } + } + } + + return scores; + } + + private sealed record GraphNode(string NodeId, string Kind); + + private sealed class GraphTopology + { + public GraphTopology(ImmutableArray nodes, Dictionary> adjacency, IReadOnlyDictionary kinds) + { + Nodes = nodes; + Adjacency = adjacency; + Kinds = kinds; + } + + public ImmutableArray Nodes { get; } + public Dictionary> Adjacency { get; } + public IReadOnlyDictionary Kinds { get; } + + public IEnumerable GetNeighbors(string nodeId) + { + if (Adjacency.TryGetValue(nodeId, out var neighbors)) + { + return neighbors; + } + + return Array.Empty(); + } + } + + private sealed class EdgeEndpointResolver + { + private readonly IReadOnlyDictionary _nodesById; + private readonly IReadOnlyDictionary _componentNodeByPurl; + private readonly IReadOnlyDictionary _artifactNodeByDigest; + + public EdgeEndpointResolver(ImmutableArray nodes) + { + _nodesById = nodes.ToImmutableDictionary( + node => node["id"]!.GetValue(), + node => node, + StringComparer.Ordinal); + + _componentNodeByPurl = BuildComponentIndex(nodes); + _artifactNodeByDigest = BuildArtifactIndex(nodes); + } + + public bool TryResolve(JsonObject edge, out string source, out string target) + { + var kind = edge["kind"]!.GetValue(); + var canonicalKey = edge["canonical_key"]!.AsObject(); + + string? s = null; + string? t = null; + + switch (kind) + { + case "CONTAINS": + s = canonicalKey.TryGetPropertyValue("artifact_node_id", out var containsSource) + ? containsSource?.GetValue() + : null; + t = canonicalKey.TryGetPropertyValue("component_node_id", out var containsTarget) + ? containsTarget?.GetValue() + : null; + break; + case "DECLARED_IN": + s = canonicalKey.TryGetPropertyValue("component_node_id", out var declaredSource) + ? declaredSource?.GetValue() + : null; + t = canonicalKey.TryGetPropertyValue("file_node_id", out var declaredTarget) + ? declaredTarget?.GetValue() + : null; + break; + case "AFFECTED_BY": + s = canonicalKey.TryGetPropertyValue("component_node_id", out var affectedSource) + ? affectedSource?.GetValue() + : null; + t = canonicalKey.TryGetPropertyValue("advisory_node_id", out var affectedTarget) + ? affectedTarget?.GetValue() + : null; + break; + case "VEX_EXEMPTS": + s = canonicalKey.TryGetPropertyValue("component_node_id", out var vexSource) + ? vexSource?.GetValue() + : null; + t = canonicalKey.TryGetPropertyValue("vex_node_id", out var vexTarget) + ? vexTarget?.GetValue() + : null; + break; + case "GOVERNS_WITH": + s = canonicalKey.TryGetPropertyValue("policy_node_id", out var policySource) + ? policySource?.GetValue() + : null; + t = canonicalKey.TryGetPropertyValue("component_node_id", out var policyTarget) + ? policyTarget?.GetValue() + : null; + break; + case "OBSERVED_RUNTIME": + s = canonicalKey.TryGetPropertyValue("runtime_node_id", out var runtimeSource) + ? runtimeSource?.GetValue() + : null; + t = canonicalKey.TryGetPropertyValue("component_node_id", out var runtimeTarget) + ? runtimeTarget?.GetValue() + : null; + break; + case "BUILT_FROM": + s = canonicalKey.TryGetPropertyValue("parent_artifact_node_id", out var builtSource) + ? builtSource?.GetValue() + : null; + + if (canonicalKey.TryGetPropertyValue("child_artifact_node_id", out var builtTargetNode) && builtTargetNode is not null) + { + t = builtTargetNode.GetValue(); + } + else if (canonicalKey.TryGetPropertyValue("child_artifact_digest", out var builtTargetDigest) && builtTargetDigest is not null) + { + _artifactNodeByDigest.TryGetValue(builtTargetDigest.GetValue(), out t); + } + + break; + case "DEPENDS_ON": + s = canonicalKey.TryGetPropertyValue("component_node_id", out var dependsSource) + ? dependsSource?.GetValue() + : null; + + if (canonicalKey.TryGetPropertyValue("dependency_node_id", out var dependsTargetNode) && dependsTargetNode is not null) + { + t = dependsTargetNode.GetValue(); + } + else if (canonicalKey.TryGetPropertyValue("dependency_purl", out var dependencyPurl) && dependencyPurl is not null) + { + _componentNodeByPurl.TryGetValue(dependencyPurl.GetValue(), out t); + } + + break; + default: + s = ExtractFirstNodeId(canonicalKey); + t = ExtractSecondNodeId(canonicalKey); + break; + } + + if (s is null || t is null) + { + source = string.Empty; + target = string.Empty; + return false; + } + + if (!_nodesById.ContainsKey(s) || !_nodesById.ContainsKey(t)) + { + source = string.Empty; + target = string.Empty; + return false; + } + + source = s; + target = t; + return true; + } + + private static Dictionary BuildComponentIndex(ImmutableArray nodes) + { + var components = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var node in nodes) + { + if (!string.Equals(node["kind"]!.GetValue(), "component", StringComparison.Ordinal)) + { + continue; + } + + if (!node.TryGetPropertyValue("attributes", out var attributesNode) || attributesNode is not JsonObject attributes) + { + continue; + } + + if (!attributes.TryGetPropertyValue("purl", out var purlNode) || purlNode is null) + { + continue; + } + + var purl = purlNode.GetValue(); + if (!string.IsNullOrWhiteSpace(purl)) + { + components.TryAdd(purl.Trim(), node["id"]!.GetValue()); + } + } + + return components; + } + + private static Dictionary BuildArtifactIndex(ImmutableArray nodes) + { + var artifacts = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var node in nodes) + { + if (!string.Equals(node["kind"]!.GetValue(), "artifact", StringComparison.Ordinal)) + { + continue; + } + + if (!node.TryGetPropertyValue("attributes", out var attributesNode) || attributesNode is not JsonObject attributes) + { + continue; + } + + if (!attributes.TryGetPropertyValue("artifact_digest", out var digestNode) || digestNode is null) + { + continue; + } + + var digest = digestNode.GetValue(); + if (!string.IsNullOrWhiteSpace(digest)) + { + artifacts.TryAdd(digest.Trim(), node["id"]!.GetValue()); + } + } + + return artifacts; + } + + private static string? ExtractFirstNodeId(JsonObject canonicalKey) + { + foreach (var property in canonicalKey) + { + if (property.Value is JsonValue value + && value.TryGetValue(out string? candidate) + && candidate is not null + && candidate.StartsWith("gn:", StringComparison.Ordinal)) + { + return candidate; + } + } + + return null; + } + + private static string? ExtractSecondNodeId(JsonObject canonicalKey) + { + var encountered = false; + foreach (var property in canonicalKey) + { + if (property.Value is JsonValue value + && value.TryGetValue(out string? candidate) + && candidate is not null + && candidate.StartsWith("gn:", StringComparison.Ordinal)) + { + if (!encountered) + { + encountered = true; + continue; + } + + return candidate; + } + } + + return null; + } + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsHostedService.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsHostedService.cs new file mode 100644 index 000000000..e2cd43bbd --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsHostedService.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace StellaOps.Graph.Indexer.Analytics; + +public sealed class GraphAnalyticsHostedService : BackgroundService +{ + private readonly IGraphAnalyticsPipeline _pipeline; + private readonly GraphAnalyticsOptions _options; + private readonly ILogger _logger; + + public GraphAnalyticsHostedService( + IGraphAnalyticsPipeline pipeline, + IOptions options, + ILogger logger) + { + _pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline)); + _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using var clusteringTimer = new PeriodicTimer(_options.ClusterInterval); + using var centralityTimer = new PeriodicTimer(_options.CentralityInterval); + + while (!stoppingToken.IsCancellationRequested) + { + var clusteringTask = clusteringTimer.WaitForNextTickAsync(stoppingToken).AsTask(); + var centralityTask = centralityTimer.WaitForNextTickAsync(stoppingToken).AsTask(); + + var completed = await Task.WhenAny(clusteringTask, centralityTask).ConfigureAwait(false); + if (completed.IsCanceled || stoppingToken.IsCancellationRequested) + { + break; + } + + try + { + await _pipeline.RunAsync(new GraphAnalyticsRunContext(ForceBackfill: false), stoppingToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // graceful shutdown + } + catch (Exception ex) + { + _logger.LogError(ex, "graph-indexer: analytics pipeline failed during scheduled run"); + } + } + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsMetrics.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsMetrics.cs new file mode 100644 index 000000000..7d33b54c0 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsMetrics.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; + +namespace StellaOps.Graph.Indexer.Analytics; + +public sealed class GraphAnalyticsMetrics : IDisposable +{ + public const string MeterName = "StellaOps.Graph.Indexer"; + public const string MeterVersion = "1.0.0"; + + private const string RunsTotalName = "graph_analytics_runs_total"; + private const string FailuresTotalName = "graph_analytics_failures_total"; + private const string DurationSecondsName = "graph_analytics_duration_seconds"; + private const string ClustersTotalName = "graph_analytics_clusters_total"; + private const string CentralityTotalName = "graph_analytics_centrality_total"; + + private readonly Meter _meter; + private readonly bool _ownsMeter; + private readonly Counter _runsTotal; + private readonly Counter _failuresTotal; + private readonly Histogram _durationSeconds; + private readonly Counter _clustersTotal; + private readonly Counter _centralityTotal; + private bool _disposed; + + public GraphAnalyticsMetrics() + : this(null) + { + } + + public GraphAnalyticsMetrics(Meter? meter) + { + _meter = meter ?? new Meter(MeterName, MeterVersion); + _ownsMeter = meter is null; + + _runsTotal = _meter.CreateCounter(RunsTotalName, unit: "count", description: "Total analytics runs executed."); + _failuresTotal = _meter.CreateCounter(FailuresTotalName, unit: "count", description: "Total analytics runs that failed."); + _durationSeconds = _meter.CreateHistogram(DurationSecondsName, unit: "s", description: "Duration of analytics runs."); + _clustersTotal = _meter.CreateCounter(ClustersTotalName, unit: "count", description: "Cluster assignments written."); + _centralityTotal = _meter.CreateCounter(CentralityTotalName, unit: "count", description: "Centrality scores written."); + } + + public void RecordRun(string tenant, bool success, TimeSpan duration, int clusterCount, int centralityCount) + { + ThrowIfDisposed(); + + var tags = new KeyValuePair[] + { + new("tenant", tenant), + new("success", success) + }; + + var tagSpan = tags.AsSpan(); + _runsTotal.Add(1, tagSpan); + if (!success) + { + _failuresTotal.Add(1, tagSpan); + } + + _durationSeconds.Record(duration.TotalSeconds, tagSpan); + _clustersTotal.Add(clusterCount, tagSpan); + _centralityTotal.Add(centralityCount, tagSpan); + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(GraphAnalyticsMetrics)); + } + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + if (_ownsMeter) + { + _meter.Dispose(); + } + + _disposed = true; + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsOptions.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsOptions.cs new file mode 100644 index 000000000..acbbacf91 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsOptions.cs @@ -0,0 +1,31 @@ +using System; + +namespace StellaOps.Graph.Indexer.Analytics; + +public sealed class GraphAnalyticsOptions +{ + /// + /// Interval for running clustering (Louvain-style label propagation). + /// + public TimeSpan ClusterInterval { get; set; } = TimeSpan.FromMinutes(5); + + /// + /// Interval for recomputing centrality metrics (degree + betweenness approximation). + /// + public TimeSpan CentralityInterval { get; set; } = TimeSpan.FromMinutes(5); + + /// + /// Maximum number of iterations for label propagation. + /// + public int MaxPropagationIterations { get; set; } = 6; + + /// + /// Number of seed nodes to sample (deterministically) for betweenness approximation. + /// + public int BetweennessSampleSize { get; set; } = 12; + + /// + /// Whether to also write cluster ids onto graph node documents (alongside overlays). + /// + public bool WriteClusterAssignmentsToNodes { get; set; } = true; +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsPipeline.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsPipeline.cs new file mode 100644 index 000000000..475888fd1 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsPipeline.cs @@ -0,0 +1,72 @@ +using System.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace StellaOps.Graph.Indexer.Analytics; + +public sealed class GraphAnalyticsPipeline : IGraphAnalyticsPipeline +{ + private readonly GraphAnalyticsEngine _engine; + private readonly IGraphSnapshotProvider _snapshotProvider; + private readonly IGraphAnalyticsWriter _writer; + private readonly GraphAnalyticsMetrics _metrics; + private readonly ILogger _logger; + + public GraphAnalyticsPipeline( + GraphAnalyticsEngine engine, + IGraphSnapshotProvider snapshotProvider, + IGraphAnalyticsWriter writer, + GraphAnalyticsMetrics metrics, + ILogger logger) + { + _engine = engine ?? throw new ArgumentNullException(nameof(engine)); + _snapshotProvider = snapshotProvider ?? throw new ArgumentNullException(nameof(snapshotProvider)); + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); + _metrics = metrics ?? throw new ArgumentNullException(nameof(metrics)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task RunAsync(GraphAnalyticsRunContext context, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var snapshots = await _snapshotProvider.GetPendingSnapshotsAsync(cancellationToken).ConfigureAwait(false); + foreach (var snapshot in snapshots) + { + var stopwatch = Stopwatch.StartNew(); + try + { + cancellationToken.ThrowIfCancellationRequested(); + + var result = _engine.Compute(snapshot); + + await _writer.PersistClusterAssignmentsAsync(snapshot, result.Clusters, cancellationToken).ConfigureAwait(false); + await _writer.PersistCentralityAsync(snapshot, result.CentralityScores, cancellationToken).ConfigureAwait(false); + await _snapshotProvider.MarkProcessedAsync(snapshot.Tenant, snapshot.SnapshotId, cancellationToken).ConfigureAwait(false); + + stopwatch.Stop(); + _metrics.RecordRun(snapshot.Tenant, success: true, stopwatch.Elapsed, result.Clusters.Length, result.CentralityScores.Length); + + _logger.LogInformation( + "graph-indexer: analytics computed for snapshot {SnapshotId} tenant {Tenant} with {ClusterCount} clusters and {CentralityCount} centrality scores in {DurationMs:F2} ms", + snapshot.SnapshotId, + snapshot.Tenant, + result.Clusters.Length, + result.CentralityScores.Length, + stopwatch.Elapsed.TotalMilliseconds); + } + catch (Exception ex) + { + stopwatch.Stop(); + _metrics.RecordRun(snapshot.Tenant, success: false, stopwatch.Elapsed, 0, 0); + + _logger.LogError( + ex, + "graph-indexer: analytics failed for snapshot {SnapshotId} tenant {Tenant}", + snapshot.SnapshotId, + snapshot.Tenant); + + throw; + } + } + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsServiceCollectionExtensions.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsServiceCollectionExtensions.cs new file mode 100644 index 000000000..b5d854fc8 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsServiceCollectionExtensions.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace StellaOps.Graph.Indexer.Analytics; + +public static class GraphAnalyticsServiceCollectionExtensions +{ + public static IServiceCollection AddGraphAnalyticsPipeline( + this IServiceCollection services, + Action? configureOptions = null) + { + ArgumentNullException.ThrowIfNull(services); + + if (configureOptions is not null) + { + services.Configure(configureOptions); + } + else + { + services.Configure(_ => { }); + } + + services.AddSingleton(provider => + { + var options = provider.GetRequiredService>(); + return new GraphAnalyticsEngine(options.Value); + }); + + services.AddSingleton(); + services.AddSingleton(); + services.AddHostedService(); + + return services; + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsTypes.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsTypes.cs new file mode 100644 index 000000000..348cb4c71 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsTypes.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Immutable; +using System.Text.Json.Nodes; +using StellaOps.Graph.Indexer.Documents; + +namespace StellaOps.Graph.Indexer.Analytics; + +public sealed record GraphAnalyticsSnapshot( + string Tenant, + string SnapshotId, + DateTimeOffset GeneratedAt, + ImmutableArray Nodes, + ImmutableArray Edges); + +public sealed record GraphAnalyticsRunContext(bool ForceBackfill); + +public sealed record ClusterAssignment(string NodeId, string ClusterId, string Kind); + +public sealed record CentralityScore(string NodeId, double Degree, double Betweenness, string Kind); + +public sealed record GraphAnalyticsResult( + ImmutableArray Clusters, + ImmutableArray CentralityScores); + +public interface IGraphSnapshotProvider +{ + Task> GetPendingSnapshotsAsync(CancellationToken cancellationToken); + Task MarkProcessedAsync(string tenant, string snapshotId, CancellationToken cancellationToken); +} + +public interface IGraphAnalyticsWriter +{ + Task PersistClusterAssignmentsAsync(GraphAnalyticsSnapshot snapshot, ImmutableArray assignments, CancellationToken cancellationToken); + Task PersistCentralityAsync(GraphAnalyticsSnapshot snapshot, ImmutableArray scores, CancellationToken cancellationToken); +} + +public interface IGraphAnalyticsPipeline +{ + Task RunAsync(GraphAnalyticsRunContext context, CancellationToken cancellationToken); +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsWriterOptions.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsWriterOptions.cs new file mode 100644 index 000000000..db083da45 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/GraphAnalyticsWriterOptions.cs @@ -0,0 +1,9 @@ +namespace StellaOps.Graph.Indexer.Analytics; + +public sealed class GraphAnalyticsWriterOptions +{ + public string ClusterCollectionName { get; set; } = "graph_cluster_overlays"; + public string CentralityCollectionName { get; set; } = "graph_centrality_overlays"; + public string NodeCollectionName { get; set; } = "graph_nodes"; + public bool WriteClusterAssignmentsToNodes { get; set; } = true; +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/InMemoryGraphAnalyticsWriter.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/InMemoryGraphAnalyticsWriter.cs new file mode 100644 index 000000000..48769df32 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/InMemoryGraphAnalyticsWriter.cs @@ -0,0 +1,26 @@ +using System.Collections.Immutable; + +namespace StellaOps.Graph.Indexer.Analytics; + +public sealed class InMemoryGraphAnalyticsWriter : IGraphAnalyticsWriter +{ + private readonly List<(GraphAnalyticsSnapshot Snapshot, ImmutableArray Assignments)> _clusters = new(); + private readonly List<(GraphAnalyticsSnapshot Snapshot, ImmutableArray Scores)> _centrality = new(); + + public IReadOnlyList<(GraphAnalyticsSnapshot Snapshot, ImmutableArray Assignments)> ClusterWrites => _clusters; + public IReadOnlyList<(GraphAnalyticsSnapshot Snapshot, ImmutableArray Scores)> CentralityWrites => _centrality; + + public Task PersistClusterAssignmentsAsync(GraphAnalyticsSnapshot snapshot, ImmutableArray assignments, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + _clusters.Add((snapshot, assignments)); + return Task.CompletedTask; + } + + public Task PersistCentralityAsync(GraphAnalyticsSnapshot snapshot, ImmutableArray scores, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + _centrality.Add((snapshot, scores)); + return Task.CompletedTask; + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/InMemoryGraphSnapshotProvider.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/InMemoryGraphSnapshotProvider.cs new file mode 100644 index 000000000..c3b62a99f --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/InMemoryGraphSnapshotProvider.cs @@ -0,0 +1,35 @@ +using System.Collections.Concurrent; +using System.Collections.Immutable; + +namespace StellaOps.Graph.Indexer.Analytics; + +public sealed class InMemoryGraphSnapshotProvider : IGraphSnapshotProvider +{ + private readonly ConcurrentQueue _queue = new(); + + public void Enqueue(GraphAnalyticsSnapshot snapshot) + { + ArgumentNullException.ThrowIfNull(snapshot); + _queue.Enqueue(snapshot); + } + + public Task> GetPendingSnapshotsAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var list = new List(); + while (_queue.TryDequeue(out var snapshot)) + { + list.Add(snapshot); + } + + return Task.FromResult>(list.ToImmutableArray()); + } + + public Task MarkProcessedAsync(string tenant, string snapshotId, CancellationToken cancellationToken) + { + // No-op for in-memory provider; processing removes items eagerly. + cancellationToken.ThrowIfCancellationRequested(); + return Task.CompletedTask; + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Analytics/MongoGraphAnalyticsWriter.cs b/src/Graph/StellaOps.Graph.Indexer/Analytics/MongoGraphAnalyticsWriter.cs new file mode 100644 index 000000000..03070aab7 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Analytics/MongoGraphAnalyticsWriter.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.Json.Nodes; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace StellaOps.Graph.Indexer.Analytics; + +public sealed class MongoGraphAnalyticsWriter : IGraphAnalyticsWriter +{ + private readonly IMongoCollection _clusters; + private readonly IMongoCollection _centrality; + private readonly IMongoCollection _nodes; + private readonly GraphAnalyticsWriterOptions _options; + + public MongoGraphAnalyticsWriter(IMongoDatabase database, GraphAnalyticsWriterOptions? options = null) + { + ArgumentNullException.ThrowIfNull(database); + + _options = options ?? new GraphAnalyticsWriterOptions(); + _clusters = database.GetCollection(_options.ClusterCollectionName); + _centrality = database.GetCollection(_options.CentralityCollectionName); + _nodes = database.GetCollection(_options.NodeCollectionName); + } + + public async Task PersistClusterAssignmentsAsync(GraphAnalyticsSnapshot snapshot, ImmutableArray assignments, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (assignments.Length == 0) + { + return; + } + + var models = new List>(assignments.Length); + foreach (var assignment in assignments) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq("tenant", snapshot.Tenant), + Builders.Filter.Eq("snapshot_id", snapshot.SnapshotId), + Builders.Filter.Eq("node_id", assignment.NodeId)); + + var document = new BsonDocument + { + { "tenant", snapshot.Tenant }, + { "snapshot_id", snapshot.SnapshotId }, + { "node_id", assignment.NodeId }, + { "cluster_id", assignment.ClusterId }, + { "kind", assignment.Kind }, + { "generated_at", snapshot.GeneratedAt.UtcDateTime } + }; + + models.Add(new ReplaceOneModel(filter, document) { IsUpsert = true }); + } + + await _clusters.BulkWriteAsync(models, new BulkWriteOptions { IsOrdered = false }, cancellationToken).ConfigureAwait(false); + + if (_options.WriteClusterAssignmentsToNodes) + { + await WriteClustersToNodesAsync(assignments, cancellationToken).ConfigureAwait(false); + } + } + + public async Task PersistCentralityAsync(GraphAnalyticsSnapshot snapshot, ImmutableArray scores, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (scores.Length == 0) + { + return; + } + + var models = new List>(scores.Length); + foreach (var score in scores) + { + var filter = Builders.Filter.And( + Builders.Filter.Eq("tenant", snapshot.Tenant), + Builders.Filter.Eq("snapshot_id", snapshot.SnapshotId), + Builders.Filter.Eq("node_id", score.NodeId)); + + var document = new BsonDocument + { + { "tenant", snapshot.Tenant }, + { "snapshot_id", snapshot.SnapshotId }, + { "node_id", score.NodeId }, + { "kind", score.Kind }, + { "degree", score.Degree }, + { "betweenness", score.Betweenness }, + { "generated_at", snapshot.GeneratedAt.UtcDateTime } + }; + + models.Add(new ReplaceOneModel(filter, document) { IsUpsert = true }); + } + + await _centrality.BulkWriteAsync(models, new BulkWriteOptions { IsOrdered = false }, cancellationToken).ConfigureAwait(false); + } + + private async Task WriteClustersToNodesAsync(IEnumerable assignments, CancellationToken cancellationToken) + { + var models = new List>(); + foreach (var assignment in assignments) + { + var filter = Builders.Filter.Eq("id", assignment.NodeId); + var update = Builders.Update.Set("attributes.cluster_id", assignment.ClusterId); + models.Add(new UpdateOneModel(filter, update) { IsUpsert = false }); + } + + if (models.Count == 0) + { + return; + } + + await _nodes.BulkWriteAsync(models, new BulkWriteOptions { IsOrdered = false }, cancellationToken).ConfigureAwait(false); + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphBackfillMetrics.cs b/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphBackfillMetrics.cs new file mode 100644 index 000000000..f555f705a --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphBackfillMetrics.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; + +namespace StellaOps.Graph.Indexer.Incremental; + +public sealed class GraphBackfillMetrics : IDisposable +{ + private const string MeterName = "StellaOps.Graph.Indexer"; + private const string MeterVersion = "1.0.0"; + + private const string ChangesTotalName = "graph_changes_total"; + private const string BackfillTotalName = "graph_backfill_total"; + private const string FailuresTotalName = "graph_change_failures_total"; + private const string LagSecondsName = "graph_change_lag_seconds"; + + private readonly Meter _meter; + private readonly bool _ownsMeter; + private readonly Counter _changesTotal; + private readonly Counter _backfillTotal; + private readonly Counter _failuresTotal; + private readonly Histogram _lagSeconds; + private bool _disposed; + + public GraphBackfillMetrics() + : this(null) + { + } + + public GraphBackfillMetrics(Meter? meter) + { + _meter = meter ?? new Meter(MeterName, MeterVersion); + _ownsMeter = meter is null; + + _changesTotal = _meter.CreateCounter(ChangesTotalName, unit: "count", description: "Total change events applied."); + _backfillTotal = _meter.CreateCounter(BackfillTotalName, unit: "count", description: "Total backfill events applied."); + _failuresTotal = _meter.CreateCounter(FailuresTotalName, unit: "count", description: "Failed change applications."); + _lagSeconds = _meter.CreateHistogram(LagSecondsName, unit: "s", description: "Lag between change emission and application."); + } + + public void RecordApplied(string tenant, bool backfill, TimeSpan lag, bool success) + { + ThrowIfDisposed(); + + var tags = new KeyValuePair[] + { + new("tenant", tenant), + new("backfill", backfill), + new("success", success) + }; + + var tagSpan = tags.AsSpan(); + _changesTotal.Add(1, tagSpan); + if (backfill) + { + _backfillTotal.Add(1, tagSpan); + } + + if (!success) + { + _failuresTotal.Add(1, tagSpan); + } + + _lagSeconds.Record(lag.TotalSeconds, tagSpan); + } + + private void ThrowIfDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(GraphBackfillMetrics)); + } + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + if (_ownsMeter) + { + _meter.Dispose(); + } + + _disposed = true; + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeEvent.cs b/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeEvent.cs new file mode 100644 index 000000000..50b0607d5 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeEvent.cs @@ -0,0 +1,28 @@ +using System.Collections.Immutable; +using System.Text.Json.Nodes; + +namespace StellaOps.Graph.Indexer.Incremental; + +public sealed record GraphChangeEvent( + string Tenant, + string SnapshotId, + string SequenceToken, + ImmutableArray Nodes, + ImmutableArray Edges, + bool IsBackfill = false); + +public interface IGraphChangeEventSource +{ + IAsyncEnumerable ReadAsync(CancellationToken cancellationToken); +} + +public interface IGraphBackfillSource +{ + IAsyncEnumerable ReadBackfillAsync(CancellationToken cancellationToken); +} + +public interface IIdempotencyStore +{ + Task HasSeenAsync(string sequenceToken, CancellationToken cancellationToken); + Task MarkSeenAsync(string sequenceToken, CancellationToken cancellationToken); +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamOptions.cs b/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamOptions.cs new file mode 100644 index 000000000..a6e206b7e --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamOptions.cs @@ -0,0 +1,12 @@ +using System; + +namespace StellaOps.Graph.Indexer.Incremental; + +public sealed class GraphChangeStreamOptions +{ + public TimeSpan PollInterval { get; set; } = TimeSpan.FromSeconds(5); + public TimeSpan BackfillInterval { get; set; } = TimeSpan.FromMinutes(15); + public TimeSpan RetryBackoff { get; set; } = TimeSpan.FromSeconds(3); + public int MaxRetryAttempts { get; set; } = 3; + public int MaxBatchSize { get; set; } = 256; +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamProcessor.cs b/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamProcessor.cs new file mode 100644 index 000000000..fa38f2ca0 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamProcessor.cs @@ -0,0 +1,119 @@ +using System.Globalization; +using System.Linq; +using System.Text.Json.Nodes; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using StellaOps.Graph.Indexer.Ingestion.Sbom; + +namespace StellaOps.Graph.Indexer.Incremental; + +public sealed class GraphChangeStreamProcessor : BackgroundService +{ + private readonly IGraphChangeEventSource _changeSource; + private readonly IGraphBackfillSource _backfillSource; + private readonly IGraphDocumentWriter _writer; + private readonly IIdempotencyStore _idempotencyStore; + private readonly GraphChangeStreamOptions _options; + private readonly GraphBackfillMetrics _metrics; + private readonly ILogger _logger; + + public GraphChangeStreamProcessor( + IGraphChangeEventSource changeSource, + IGraphBackfillSource backfillSource, + IGraphDocumentWriter writer, + IIdempotencyStore idempotencyStore, + IOptions options, + GraphBackfillMetrics metrics, + ILogger logger) + { + _changeSource = changeSource ?? throw new ArgumentNullException(nameof(changeSource)); + _backfillSource = backfillSource ?? throw new ArgumentNullException(nameof(backfillSource)); + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); + _idempotencyStore = idempotencyStore ?? throw new ArgumentNullException(nameof(idempotencyStore)); + _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); + _metrics = metrics ?? throw new ArgumentNullException(nameof(metrics)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using var pollTimer = new PeriodicTimer(_options.PollInterval); + using var backfillTimer = new PeriodicTimer(_options.BackfillInterval); + + while (!stoppingToken.IsCancellationRequested) + { + var pollTask = pollTimer.WaitForNextTickAsync(stoppingToken).AsTask(); + var backfillTask = backfillTimer.WaitForNextTickAsync(stoppingToken).AsTask(); + + var completed = await Task.WhenAny(pollTask, backfillTask).ConfigureAwait(false); + if (completed.IsCanceled || stoppingToken.IsCancellationRequested) + { + break; + } + + if (completed == pollTask) + { + await ApplyStreamAsync(isBackfill: false, stoppingToken).ConfigureAwait(false); + } + else + { + await ApplyStreamAsync(isBackfill: true, stoppingToken).ConfigureAwait(false); + } + } + } + + internal async Task ApplyStreamAsync(bool isBackfill, CancellationToken cancellationToken) + { + var source = isBackfill ? _backfillSource.ReadBackfillAsync(cancellationToken) : _changeSource.ReadAsync(cancellationToken); + + await foreach (var change in source.WithCancellation(cancellationToken)) + { + if (await _idempotencyStore.HasSeenAsync(change.SequenceToken, cancellationToken).ConfigureAwait(false)) + { + continue; + } + + var attempts = 0; + while (true) + { + try + { + cancellationToken.ThrowIfCancellationRequested(); + + var batch = new GraphBuildBatch(change.Nodes, change.Edges); + await _writer.WriteAsync(batch, cancellationToken).ConfigureAwait(false); + await _idempotencyStore.MarkSeenAsync(change.SequenceToken, cancellationToken).ConfigureAwait(false); + + var collectedAt = change.Nodes + .Select(n => n.TryGetPropertyValue("provenance", out var prov) && prov is JsonObject obj && obj.TryGetPropertyValue("collected_at", out var collected) ? collected?.GetValue() : null) + .FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)); + + var lag = DateTimeOffset.TryParse(collectedAt, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var parsed) + ? DateTimeOffset.UtcNow - parsed + : TimeSpan.Zero; + + _metrics.RecordApplied(change.Tenant, isBackfill, lag, success: true); + break; + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + attempts++; + _metrics.RecordApplied(change.Tenant, isBackfill, TimeSpan.Zero, success: false); + _logger.LogError(ex, "graph-indexer: change stream apply failed for snapshot {SnapshotId} attempt {Attempt}", change.SnapshotId, attempts); + + if (attempts >= _options.MaxRetryAttempts) + { + break; + } + + await Task.Delay(_options.RetryBackoff, cancellationToken).ConfigureAwait(false); + } + } + } + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamServiceCollectionExtensions.cs b/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamServiceCollectionExtensions.cs new file mode 100644 index 000000000..b9d6057e9 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Incremental/GraphChangeStreamServiceCollectionExtensions.cs @@ -0,0 +1,28 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace StellaOps.Graph.Indexer.Incremental; + +public static class GraphChangeStreamServiceCollectionExtensions +{ + public static IServiceCollection AddGraphChangeStreamProcessor( + this IServiceCollection services, + Action? configureOptions = null) + { + ArgumentNullException.ThrowIfNull(services); + + if (configureOptions is not null) + { + services.Configure(configureOptions); + } + else + { + services.Configure(_ => { }); + } + + services.AddSingleton(); + services.AddHostedService(); + return services; + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Incremental/InMemoryIdempotencyStore.cs b/src/Graph/StellaOps.Graph.Indexer/Incremental/InMemoryIdempotencyStore.cs new file mode 100644 index 000000000..457e627ab --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Incremental/InMemoryIdempotencyStore.cs @@ -0,0 +1,21 @@ +using System.Collections.Concurrent; + +namespace StellaOps.Graph.Indexer.Incremental; + +public sealed class InMemoryIdempotencyStore : IIdempotencyStore +{ + private readonly ConcurrentDictionary _seen = new(StringComparer.Ordinal); + + public Task HasSeenAsync(string sequenceToken, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(_seen.ContainsKey(sequenceToken)); + } + + public Task MarkSeenAsync(string sequenceToken, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + _seen.TryAdd(sequenceToken, 0); + return Task.CompletedTask; + } +} diff --git a/src/Graph/StellaOps.Graph.Indexer/Properties/AssemblyInfo.cs b/src/Graph/StellaOps.Graph.Indexer/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..bdcd408c7 --- /dev/null +++ b/src/Graph/StellaOps.Graph.Indexer/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("StellaOps.Graph.Indexer.Tests")] diff --git a/src/Graph/StellaOps.Graph.Indexer/StellaOps.Graph.Indexer.csproj b/src/Graph/StellaOps.Graph.Indexer/StellaOps.Graph.Indexer.csproj index 5c085ee68..513e21d78 100644 --- a/src/Graph/StellaOps.Graph.Indexer/StellaOps.Graph.Indexer.csproj +++ b/src/Graph/StellaOps.Graph.Indexer/StellaOps.Graph.Indexer.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsEngineTests.cs b/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsEngineTests.cs new file mode 100644 index 000000000..d5ad75925 --- /dev/null +++ b/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsEngineTests.cs @@ -0,0 +1,27 @@ +using System.Linq; +using StellaOps.Graph.Indexer.Analytics; + +namespace StellaOps.Graph.Indexer.Tests; + +public sealed class GraphAnalyticsEngineTests +{ + [Fact] + public void Compute_IsDeterministic_ForLinearGraph() + { + var snapshot = GraphAnalyticsTestData.CreateLinearSnapshot(); + var engine = new GraphAnalyticsEngine(new GraphAnalyticsOptions { MaxPropagationIterations = 5, BetweennessSampleSize = 8 }); + + var first = engine.Compute(snapshot); + var second = engine.Compute(snapshot); + + Assert.Equal(first.Clusters, second.Clusters); + Assert.Equal(first.CentralityScores, second.CentralityScores); + + var mainCluster = first.Clusters.First(c => c.NodeId == snapshot.Nodes[0]["id"]!.GetValue()).ClusterId; + Assert.All(first.Clusters.Where(c => c.NodeId != snapshot.Nodes[^1]["id"]!.GetValue()), c => Assert.Equal(mainCluster, c.ClusterId)); + + var centralNode = first.CentralityScores.OrderByDescending(c => c.Betweenness).First(); + Assert.True(centralNode.Betweenness > 0); + Assert.True(centralNode.Degree >= 2); + } +} diff --git a/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsPipelineTests.cs b/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsPipelineTests.cs new file mode 100644 index 000000000..ad4b76e06 --- /dev/null +++ b/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsPipelineTests.cs @@ -0,0 +1,32 @@ +using System.Collections.Immutable; +using Microsoft.Extensions.Logging.Abstractions; +using StellaOps.Graph.Indexer.Analytics; + +namespace StellaOps.Graph.Indexer.Tests; + +public sealed class GraphAnalyticsPipelineTests +{ + [Fact] + public async Task RunAsync_WritesClustersAndCentrality() + { + var snapshot = GraphAnalyticsTestData.CreateLinearSnapshot(); + + var provider = new InMemoryGraphSnapshotProvider(); + provider.Enqueue(snapshot); + + using var metrics = new GraphAnalyticsMetrics(); + var writer = new InMemoryGraphAnalyticsWriter(); + var pipeline = new GraphAnalyticsPipeline( + new GraphAnalyticsEngine(new GraphAnalyticsOptions()), + provider, + writer, + metrics, + NullLogger.Instance); + + await pipeline.RunAsync(new GraphAnalyticsRunContext(false), CancellationToken.None); + + Assert.Single(writer.ClusterWrites); + Assert.Single(writer.CentralityWrites); + Assert.Equal(snapshot.Nodes.Length, writer.ClusterWrites.Single().Assignments.Length); + } +} diff --git a/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsTestData.cs b/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsTestData.cs new file mode 100644 index 000000000..256aac431 --- /dev/null +++ b/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphAnalyticsTestData.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text.Json.Nodes; +using StellaOps.Graph.Indexer.Analytics; +using StellaOps.Graph.Indexer.Schema; + +namespace StellaOps.Graph.Indexer.Tests; + +internal static class GraphAnalyticsTestData +{ + public static GraphAnalyticsSnapshot CreateLinearSnapshot() + { + var tenant = "tenant-a"; + var provenance = new GraphProvenanceSpec("test", DateTimeOffset.UtcNow, "sbom-1", 1); + + var nodeA = GraphDocumentFactory.CreateNode(new GraphNodeSpec( + tenant, + "component", + new Dictionary { { "purl", "pkg:npm/a@1.0.0" } }, + new JsonObject { ["purl"] = "pkg:npm/a@1.0.0" }, + provenance, + DateTimeOffset.UtcNow, + null)); + + var nodeB = GraphDocumentFactory.CreateNode(new GraphNodeSpec( + tenant, + "component", + new Dictionary { { "purl", "pkg:npm/b@1.0.0" } }, + new JsonObject { ["purl"] = "pkg:npm/b@1.0.0" }, + provenance, + DateTimeOffset.UtcNow, + null)); + + var nodeC = GraphDocumentFactory.CreateNode(new GraphNodeSpec( + tenant, + "component", + new Dictionary { { "purl", "pkg:npm/c@1.0.0" } }, + new JsonObject { ["purl"] = "pkg:npm/c@1.0.0" }, + provenance, + DateTimeOffset.UtcNow, + null)); + + var nodeD = GraphDocumentFactory.CreateNode(new GraphNodeSpec( + tenant, + "component", + new Dictionary { { "purl", "pkg:npm/d@1.0.0" } }, + new JsonObject { ["purl"] = "pkg:npm/d@1.0.0" }, + provenance, + DateTimeOffset.UtcNow, + null)); + + var edgeAB = CreateDependsOnEdge(tenant, nodeA["id"]!.GetValue(), nodeB["id"]!.GetValue(), provenance); + var edgeBC = CreateDependsOnEdge(tenant, nodeB["id"]!.GetValue(), nodeC["id"]!.GetValue(), provenance); + + return new GraphAnalyticsSnapshot( + tenant, + "snapshot-1", + DateTimeOffset.UtcNow, + ImmutableArray.Create(nodeA, nodeB, nodeC, nodeD), + ImmutableArray.Create(edgeAB, edgeBC)); + } + + private static JsonObject CreateDependsOnEdge(string tenant, string sourceNodeId, string dependencyNodeId, GraphProvenanceSpec provenance) + { + return GraphDocumentFactory.CreateEdge(new GraphEdgeSpec( + tenant, + "DEPENDS_ON", + new Dictionary + { + { "component_node_id", sourceNodeId }, + { "dependency_node_id", dependencyNodeId } + }, + new JsonObject(), + provenance, + DateTimeOffset.UtcNow, + null)); + } +} diff --git a/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphChangeStreamProcessorTests.cs b/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphChangeStreamProcessorTests.cs new file mode 100644 index 000000000..a643c91ef --- /dev/null +++ b/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphChangeStreamProcessorTests.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text.Json.Nodes; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using StellaOps.Graph.Indexer.Incremental; +using StellaOps.Graph.Indexer.Ingestion.Sbom; + +namespace StellaOps.Graph.Indexer.Tests; + +public sealed class GraphChangeStreamProcessorTests +{ + [Fact] + public async Task ApplyStreamAsync_SkipsDuplicates_AndRetries() + { + var tenant = "tenant-a"; + var nodes = ImmutableArray.Create(new JsonObject { ["id"] = "gn:tenant-a:component:a", ["kind"] = "component" }); + var edges = ImmutableArray.Empty; + + var events = new List + { + new(tenant, "snap-1", "seq-1", nodes, edges, false), + new(tenant, "snap-1", "seq-1", nodes, edges, false), // duplicate + new(tenant, "snap-1", "seq-2", nodes, edges, false) + }; + + var changeSource = new FakeChangeSource(events); + var backfillSource = new FakeChangeSource(Array.Empty()); + var store = new InMemoryIdempotencyStore(); + var writer = new FlakyWriter(failFirst: true); + using var metrics = new GraphBackfillMetrics(); + + var options = Options.Create(new GraphChangeStreamOptions + { + MaxRetryAttempts = 3, + RetryBackoff = TimeSpan.FromMilliseconds(10) + }); + + var processor = new GraphChangeStreamProcessor( + changeSource, + backfillSource, + writer, + store, + options, + metrics, + NullLogger.Instance); + + await processor.ApplyStreamAsync(isBackfill: false, CancellationToken.None); + + Assert.Equal(2, writer.BatchCount); // duplicate skipped + Assert.True(writer.SucceededAfterRetry); + } + + private sealed class FakeChangeSource : IGraphChangeEventSource, IGraphBackfillSource + { + private readonly IReadOnlyList _events; + + public FakeChangeSource(IReadOnlyList events) + { + _events = events; + } + + public IAsyncEnumerable ReadAsync(CancellationToken cancellationToken) + { + return EmitAsync(cancellationToken); + } + + public IAsyncEnumerable ReadBackfillAsync(CancellationToken cancellationToken) + { + return EmitAsync(cancellationToken); + } + + private async IAsyncEnumerable EmitAsync([System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken) + { + foreach (var change in _events) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return change; + await Task.Yield(); + } + } + } + + private sealed class FlakyWriter : IGraphDocumentWriter + { + private readonly bool _failFirst; + private int _attempts; + + public FlakyWriter(bool failFirst) + { + _failFirst = failFirst; + } + + public int BatchCount { get; private set; } + public bool SucceededAfterRetry => _attempts > 1 && BatchCount > 0; + + public Task WriteAsync(GraphBuildBatch batch, CancellationToken cancellationToken) + { + _attempts++; + if (_failFirst && _attempts == 1) + { + throw new InvalidOperationException("simulated failure"); + } + + BatchCount++; + return Task.CompletedTask; + } + } +} diff --git a/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/StellaOps.Graph.Indexer.Tests.csproj b/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/StellaOps.Graph.Indexer.Tests.csproj index fa588f6e4..c959779b9 100644 --- a/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/StellaOps.Graph.Indexer.Tests.csproj +++ b/src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/StellaOps.Graph.Indexer.Tests.csproj @@ -9,5 +9,8 @@ + + + diff --git a/src/Provenance/StellaOps.Provenance.Attestation/PromotionAttestation.cs b/src/Provenance/StellaOps.Provenance.Attestation/PromotionAttestation.cs index 46e29f5df..e7fdc9c58 100644 --- a/src/Provenance/StellaOps.Provenance.Attestation/PromotionAttestation.cs +++ b/src/Provenance/StellaOps.Provenance.Attestation/PromotionAttestation.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using System.Text.Json.Serialization; namespace StellaOps.Provenance.Attestation; @@ -9,13 +8,49 @@ public sealed record PromotionPredicate( string VexDigest, string PromotionId, string? RekorEntry = null, - IReadOnlyDictionary? Metadata = null); + IReadOnlyDictionary? Metadata = null); + +public sealed record PromotionAttestation( + PromotionPredicate Predicate, + byte[] Payload, + SignResult Signature); public static class PromotionAttestationBuilder { + public const string PredicateType = "stella.ops/promotion@v1"; + public const string ContentType = "application/vnd.stella.promotion+json"; + public static byte[] CreateCanonicalJson(PromotionPredicate predicate) { if (predicate is null) throw new ArgumentNullException(nameof(predicate)); return CanonicalJson.SerializeToUtf8Bytes(predicate); } + + public static async Task BuildAsync( + PromotionPredicate predicate, + ISigner signer, + IReadOnlyDictionary? claims = null, + CancellationToken cancellationToken = default) + { + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + if (signer is null) throw new ArgumentNullException(nameof(signer)); + + var payload = CreateCanonicalJson(predicate); + + // ensure predicate type claim is always present + var mergedClaims = claims is null + ? new Dictionary(StringComparer.Ordinal) + : new Dictionary(claims, StringComparer.Ordinal); + mergedClaims["predicateType"] = PredicateType; + + var request = new SignRequest( + Payload: payload, + ContentType: ContentType, + Claims: mergedClaims, + RequiredClaims: new[] { "predicateType" }); + + var signature = await signer.SignAsync(request, cancellationToken).ConfigureAwait(false); + + return new PromotionAttestation(predicate, payload, signature); + } } diff --git a/src/Scanner/StellaOps.Scanner.sln b/src/Scanner/StellaOps.Scanner.sln index b29405a0d..5331bab16 100644 --- a/src/Scanner/StellaOps.Scanner.sln +++ b/src/Scanner/StellaOps.Scanner.sln @@ -147,6 +147,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Ingestion.Telemet EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Aoc", "..\Aoc\__Libraries\StellaOps.Aoc\StellaOps.Aoc.csproj", "{8237425A-933A-440E-AE6B-1DF57F228681}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Analyzers.Lang.Php", "__Libraries\StellaOps.Scanner.Analyzers.Lang.Php\StellaOps.Scanner.Analyzers.Lang.Php.csproj", "{0262C376-6C43-4A69-86EA-74C228BC0F36}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Scanner.Analyzers.Lang.Php.Tests", "__Tests\StellaOps.Scanner.Analyzers.Lang.Php.Tests\StellaOps.Scanner.Analyzers.Lang.Php.Tests.csproj", "{F4A239E0-AC66-4105-8423-4805B2029ABE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -985,6 +989,30 @@ Global {8237425A-933A-440E-AE6B-1DF57F228681}.Release|x64.Build.0 = Release|Any CPU {8237425A-933A-440E-AE6B-1DF57F228681}.Release|x86.ActiveCfg = Release|Any CPU {8237425A-933A-440E-AE6B-1DF57F228681}.Release|x86.Build.0 = Release|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Debug|x64.ActiveCfg = Debug|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Debug|x64.Build.0 = Debug|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Debug|x86.ActiveCfg = Debug|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Debug|x86.Build.0 = Debug|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Release|Any CPU.Build.0 = Release|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Release|x64.ActiveCfg = Release|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Release|x64.Build.0 = Release|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Release|x86.ActiveCfg = Release|Any CPU + {0262C376-6C43-4A69-86EA-74C228BC0F36}.Release|x86.Build.0 = Release|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Debug|x64.ActiveCfg = Debug|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Debug|x64.Build.0 = Debug|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Debug|x86.ActiveCfg = Debug|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Debug|x86.Build.0 = Debug|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Release|Any CPU.Build.0 = Release|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Release|x64.ActiveCfg = Release|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Release|x64.Build.0 = Release|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Release|x86.ActiveCfg = Release|Any CPU + {F4A239E0-AC66-4105-8423-4805B2029ABE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1032,5 +1060,7 @@ Global {C2B2B38A-D67D-429E-BB2E-023E25EBD7D3} = {41F15E67-7190-CF23-3BC4-77E87134CADD} {482026BC-2E89-4789-8A73-523FAAC8476F} = {41F15E67-7190-CF23-3BC4-77E87134CADD} {E0104A8E-2C39-48C1-97EC-66C171310944} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642} + {0262C376-6C43-4A69-86EA-74C228BC0F36} = {41F15E67-7190-CF23-3BC4-77E87134CADD} + {F4A239E0-AC66-4105-8423-4805B2029ABE} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642} EndGlobalSection EndGlobal diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Deno/DenoLanguageAnalyzer.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Deno/DenoLanguageAnalyzer.cs index 998866e58..82a0bcc73 100644 --- a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Deno/DenoLanguageAnalyzer.cs +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Deno/DenoLanguageAnalyzer.cs @@ -174,6 +174,9 @@ public sealed class DenoLanguageAnalyzer : ILanguageAnalyzer metadata: runtimeMeta, view: "runtime"); + analysisStore.Set(ScanAnalysisKeys.DenoRuntimePayload, payload); + + // Backward compatibility with early runtime experiments that used a string key. analysisStore.Set("deno.runtime", payload); // Also emit policy signals into AnalysisStore for downstream consumption. diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Deno/Internal/Runtime/DenoRuntimeShim.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Deno/Internal/Runtime/DenoRuntimeShim.cs index 4f3cf1b18..fe10a64e8 100644 --- a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Deno/Internal/Runtime/DenoRuntimeShim.cs +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Deno/Internal/Runtime/DenoRuntimeShim.cs @@ -24,123 +24,460 @@ internal static class DenoRuntimeShim // NOTE: This shim is intentionally self contained and avoids network calls. private const string ShimSource = """ +// @ts-nocheck // deno-runtime trace shim (offline, deterministic) // Emits module load, permission use, npm resolution, and wasm load events. const events: Array> = []; +const cwd = Deno.cwd().replace(/\\/g, "/"); +const entrypointEnv = Deno.env.get("STELLA_DENO_ENTRYPOINT") ?? ""; + +type ModuleRef = { normalized: string; path_sha256: string }; function nowIso(): string { return new Date().toISOString(); } -function addEvent(evt: Record) { - // Deterministic key order via stringify on object literal insertion order. - events.push(evt); -} - -function hashPath(input: string): string { - const data = new TextEncoder().encode(input); - const hash = crypto.subtle.digestSync("SHA-256", data); - return Array.from(new Uint8Array(hash)) +function toHex(bytes: Uint8Array): string { + return Array.from(bytes) .map((b) => b.toString(16).padStart(2, "0")) .join(""); } -function relPath(abs: string): { normalized: string; path_sha256: string } { - const cwd = Deno.cwd(); - const rel = abs.startsWith(cwd) ? abs.slice(cwd.length + 1) : abs; - const normalized = rel.replaceAll("\\", "/"); - return { normalized, path_sha256: hashPath(normalized) }; +// Minimal synchronous SHA-256 (no async crypto required) +function sha256Hex(value: string): string { + const data = new TextEncoder().encode(value); + const k = new Uint32Array([ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, + 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, + 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, + 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, + 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, + 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, + 0xc67178f2, + ]); + + const h = new Uint32Array([ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + ]); + + const bitLength = data.length * 8; + const paddedLength = (((data.length + 9 + 63) >> 6) << 6); + const buffer = new Uint8Array(paddedLength); + buffer.set(data); + buffer[data.length] = 0x80; + + const view = new DataView(buffer.buffer); + const high = Math.floor(bitLength / 0x100000000); + const low = bitLength >>> 0; + view.setUint32(paddedLength - 8, high, false); + view.setUint32(paddedLength - 4, low, false); + + const w = new Uint32Array(64); + for (let offset = 0; offset < paddedLength; offset += 64) { + for (let i = 0; i < 16; i++) { + w[i] = view.getUint32(offset + i * 4, false); + } + for (let i = 16; i < 64; i++) { + const s0 = rotateRight(w[i - 15], 7) ^ rotateRight(w[i - 15], 18) ^ (w[i - 15] >>> 3); + const s1 = rotateRight(w[i - 2], 17) ^ rotateRight(w[i - 2], 19) ^ (w[i - 2] >>> 10); + w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0; + } + + let [a, b, c, d, e, f, g, hh] = h; + for (let i = 0; i < 64; i++) { + const S1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25); + const ch = (e & f) ^ (~e & g); + const temp1 = (hh + S1 + ch + k[i] + w[i]) >>> 0; + const S0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22); + const maj = (a & b) ^ (a & c) ^ (b & c); + const temp2 = (S0 + maj) >>> 0; + + hh = g; + g = f; + f = e; + e = (d + temp1) >>> 0; + d = c; + c = b; + b = a; + a = (temp1 + temp2) >>> 0; + } + + h[0] = (h[0] + a) >>> 0; + h[1] = (h[1] + b) >>> 0; + h[2] = (h[2] + c) >>> 0; + h[3] = (h[3] + d) >>> 0; + h[4] = (h[4] + e) >>> 0; + h[5] = (h[5] + f) >>> 0; + h[6] = (h[6] + g) >>> 0; + h[7] = (h[7] + hh) >>> 0; + } + + const out = new Uint8Array(32); + const viewOut = new DataView(out.buffer); + for (let i = 0; i < 8; i++) { + viewOut.setUint32(i * 4, h[i], false); + } + + return toHex(out); } -// Wrap permission requests -const originalPermissions = Deno.permissions; -Deno.permissions = { - ...originalPermissions, - request: async (...args: Parameters) => { - const res = await originalPermissions.request(...args); - const name = args[0]?.name ?? "unknown"; - const module = relPath(import.meta.url); - addEvent({ - type: "deno.permission.use", - ts: nowIso(), - permission: name, - module, - details: "permissions.request", - }); - return res; - }, - query: (...args: Parameters) => - originalPermissions.query(...args), - revoke: (...args: Parameters) => - originalPermissions.revoke(...args), -}; +function rotateRight(value: number, bits: number): number { + return ((value >>> bits) | (value << (32 - bits))) >>> 0; +} -// Hook dynamic import calls by wrapping import() -const originalImport = globalThis.import ?? ((specifier: string) => import(specifier)); -globalThis.import = async (specifier: string) => { - const mod = typeof specifier === "string" ? specifier : String(specifier); - addEvent({ +function normalizePermission(name: string | undefined): string { + const normalized = (name ?? "").toLowerCase(); + switch (normalized) { + case "read": + case "write": + return "fs"; + case "net": + return "net"; + case "env": + return "env"; + case "ffi": + return "ffi"; + case "run": + case "sys": + case "hrtime": + return "process"; + case "worker": + return "worker"; + default: + return normalized || "unknown"; + } +} + +const grantedPermissions = new Set(); +const originalPermissions = Deno.permissions; + +async function primePermissionSnapshot() { + const descriptors: Array = [ + { name: "read" }, + { name: "write" }, + { name: "net" }, + { name: "env" }, + { name: "ffi" }, + { name: "run" as Deno.PermissionName }, + { name: "sys" as Deno.PermissionName }, + { name: "hrtime" as Deno.PermissionName }, + ]; + + for (const descriptor of descriptors) { + try { + const status = await originalPermissions.query(descriptor as Deno.PermissionDescriptor); + if (status?.state === "granted") { + grantedPermissions.add(normalizePermission(descriptor.name as string)); + } + } catch (_) { + // ignore permission probes that are unsupported in the current runtime + } + } +} + +function snapshotPermissions(): string[] { + return Array.from(grantedPermissions).sort(); +} + +function relativePath(path: string): string { + let candidate = path.replace(/\\/g, "/"); + + if (candidate.startsWith("file://")) { + candidate = candidate.slice("file://".length); + } + + if (!candidate.startsWith("/") && !/^([A-Za-z]:\\\\|[A-Za-z]:\\/)/.test(candidate)) { + candidate = `${cwd}/${candidate}`; + } + + if (candidate.startsWith(cwd)) { + const offset = cwd.endsWith("/") ? cwd.length : cwd.length + 1; + candidate = candidate.slice(offset); + } + + candidate = candidate.replace(/^\.\//, "").replace(/^\/+/, ""); + return candidate.replace(/\\/g, "/") || "."; +} + +function toFileUrl(path: string): URL { + const normalized = path.replace(/\\/g, "/"); + if (normalized.startsWith("file://")) { + return new URL(normalized); + } + + const absolute = normalized.startsWith("/") || /^([A-Za-z]:\\\\|[A-Za-z]:\\/)/.test(normalized) + ? normalized + : `${cwd}/${normalized}`; + + const prefix = absolute.startsWith("/") ? "file://" : "file:///"; + return new URL(prefix + encodeURI(absolute.replace(/#/g, "%23"))); +} + +function normalizeModule(specifier: string): ModuleRef { + try { + const url = new URL(specifier); + if (url.protocol === "file:") { + const rel = relativePath(decodeURIComponent(url.pathname)); + return { normalized: rel, path_sha256: sha256Hex(rel) }; + } + + if (url.protocol === "http:" || url.protocol === "https:") { + const normalized = `${url.protocol}//${url.host}${url.pathname}`; + return { normalized, path_sha256: sha256Hex(normalized) }; + } + + if (url.protocol === "npm:") { + const normalized = `npm:${url.pathname.replace(/^\//, "")}`; + return { normalized, path_sha256: sha256Hex(normalized) }; + } + } catch (_err) { + // not a URL; treat as path + } + + const rel = relativePath(specifier); + return { normalized: rel, path_sha256: sha256Hex(rel) }; +} + +function extractOrigin(specifier: string): string | undefined { + try { + const url = new URL(specifier); + if (url.protocol === "http:" || url.protocol === "https:") { + return `${url.protocol}//${url.host}${url.pathname}`; + } + if (url.protocol === "npm:") { + return `npm:${url.pathname.replace(/^\//, "")}`; + } + } catch (_) { + return undefined; + } + return undefined; +} + +function addEvent(evt: Record) { + events.push(evt); +} + +function recordModuleLoad(specifier: string, reason: string, permissions?: string[]) { + const module = normalizeModule(specifier); + const origin = extractOrigin(specifier); + const event: Record = { type: "deno.module.load", ts: nowIso(), - module: relPath(mod), - reason: "dynamic-import", - permissions: [], - origin: mod.startsWith("http") ? mod : undefined, - }); - return originalImport(specifier); -}; + module, + reason, + permissions: permissions ?? snapshotPermissions(), + }; + + if (origin) { + event.origin = origin; + } + + addEvent(event); + + if (specifier.startsWith("npm:")) { + recordNpmResolution(specifier); + } +} + +function recordPermissionUse(permission: string, details: string, module?: ModuleRef) { + const normalizedPermission = normalizePermission(permission); + if (normalizedPermission && normalizedPermission !== "unknown") { + grantedPermissions.add(normalizedPermission); + } -// Hook WebAssembly loads -const originalInstantiate = WebAssembly.instantiate; -WebAssembly.instantiate = async ( - bufferSource: BufferSource | WebAssembly.Module, - importObject?: WebAssembly.Imports, -) => { addEvent({ - type: "deno.wasm.load", + type: "deno.permission.use", ts: nowIso(), - module: relPath("wasm://buffer"), - importer: relPath(import.meta.url).normalized, - reason: "instantiate", + permission: normalizedPermission, + module: module ?? normalizeModule(entrypointEnv || "shim://runtime"), + details, }); - return originalInstantiate(bufferSource, importObject); -}; +} + +function recordNpmResolution(specifier: string) { + const bare = specifier.replace(/^npm:/, ""); + const [pkg, version] = bare.split("@"); + const denoDir = (Deno.env.get("DENO_DIR") ?? "").replace(/\\/g, "/"); + const resolved = denoDir + ? `file://${denoDir}/npm/registry.npmjs.org/${pkg ?? bare}/${version ?? ""}` + : `npm:${bare}`; -// Capture npm resolution hints from env when present -const npmMeta = Deno.env.get("STELLA_NPM_SPECIFIER"); -if (npmMeta) { addEvent({ type: "deno.npm.resolution", ts: nowIso(), - specifier: npmMeta, - package: npmMeta, - version: "", - resolved: "file://$DENO_DIR/npm", + specifier: `npm:${bare}`, + package: pkg ?? bare, + version: version ?? "", + resolved, exists: true, }); } -// Write NDJSON on exit -function flush() { - const sorted = events.sort((a, b) => { - const at = String(a.ts); - const bt = String(b.ts); - if (at === bt) return String(a.type).localeCompare(String(b.type)); - return at.localeCompare(bt); +function recordWasmLoad(moduleSpecifier: string, importer: string, reason: string) { + addEvent({ + type: "deno.wasm.load", + ts: nowIso(), + module: normalizeModule(moduleSpecifier), + importer, + reason, }); - const data = sorted.map((e) => JSON.stringify(e)).join("\\n") + "\\n"; - Deno.writeTextFileSync("deno-runtime.ndjson", data); } -addEvent({ - type: "deno.runtime.start", - ts: nowIso(), - module: relPath(import.meta.url), - reason: "shim-start", -}); +function hookModuleLoader(): boolean { + try { + const internal = (Deno as unknown as Record)[Symbol.for("Deno.internal") as unknown as string] + ?? (Deno as unknown as Record).internal; + const loader = (internal as Record)?.moduleLoader as Record | undefined; + if (!loader || typeof loader.load !== "function") { + return false; + } -globalThis.addEventListener("unload", () => { - flush(); -}); + const originalLoad = loader.load.bind(loader) as (...args: unknown[]) => Promise; + loader.load = async (...args: unknown[]) => { + const specifier = String(args[0] ?? ""); + const isDynamic = Boolean(args[2]); + const reason = specifier.startsWith("npm:") ? "npm" : isDynamic ? "dynamic-import" : "static-import"; + recordModuleLoad(specifier, reason); + return await originalLoad(...args); + }; + + return true; + } catch (err) { + addEvent({ type: "deno.runtime.error", ts: nowIso(), message: String(err?.message ?? err) }); + return false; + } +} + +function wrapPermissions(entryModule: ModuleRef) { + Deno.permissions = { + ...originalPermissions, + request: async (...args: Parameters) => { + const status = await originalPermissions.request(...args); + recordPermissionUse(args[0]?.name ?? "unknown", "permissions.request", entryModule); + if (status?.state === "granted") { + grantedPermissions.add(normalizePermission(args[0]?.name)); + } + return status; + }, + query: async (...args: Parameters) => { + const status = await originalPermissions.query(...args); + if (status?.state === "granted") { + grantedPermissions.add(normalizePermission(args[0]?.name)); + } + return status; + }, + revoke: async (...args: Parameters) => { + const status = await originalPermissions.revoke(...args); + grantedPermissions.delete(normalizePermission(args[0]?.name)); + return status; + }, + } as typeof Deno.permissions; +} + +function wrapDlopen(entryModule: ModuleRef) { + const original = (Deno as unknown as Record).dlopen as + | ((path: string | URL, symbols: Record) => unknown) + | undefined; + + if (typeof original !== "function") { + return; + } + + (Deno as unknown as Record).dlopen = (path: string | URL, symbols: Record) => { + recordPermissionUse("ffi", "Deno.dlopen", entryModule); + return original(path, symbols); + }; +} + +function wrapWasm(importer: ModuleRef) { + const originalInstantiate = WebAssembly.instantiate; + WebAssembly.instantiate = async ( + bufferSource: BufferSource | WebAssembly.Module, + importObject?: WebAssembly.Imports, + ) => { + recordWasmLoad("wasm://buffer", importer.normalized, "instantiate"); + return await originalInstantiate(bufferSource, importObject); + }; + + const originalInstantiateStreaming = WebAssembly.instantiateStreaming; + if (originalInstantiateStreaming) { + WebAssembly.instantiateStreaming = async ( + source: Response | Promise, + importObject?: WebAssembly.Imports, + ) => { + try { + const response = await source; + const url = response?.url || "wasm://stream"; + recordWasmLoad(url, importer.normalized, "instantiateStreaming"); + } catch (_) { + recordWasmLoad("wasm://stream", importer.normalized, "instantiateStreaming"); + } + return await originalInstantiateStreaming(source as Response, importObject); + }; + } +} + +function flush() { + try { + const sorted = events.sort((a, b) => { + const at = String(a.ts); + const bt = String(b.ts); + if (at === bt) return String(a.type).localeCompare(String(b.type)); + return at.localeCompare(bt); + }); + + const data = sorted.map((e) => JSON.stringify(e)).join(" +"); + Deno.writeTextFileSync("deno-runtime.ndjson", data ? `${data} +` : ""); + } catch (err) { + // last-resort logging; avoid throwing + console.error("deno-runtime shim failed to write trace", err); + } +} + +async function main() { + if (!entrypointEnv) { + addEvent({ type: "deno.runtime.error", ts: nowIso(), message: "STELLA_DENO_ENTRYPOINT missing" }); + flush(); + return; + } + + const entryUrl = toFileUrl(entrypointEnv); + const entryModule = normalizeModule(entryUrl.href); + + addEvent({ + type: "deno.runtime.start", + ts: nowIso(), + module: entryModule, + reason: "shim-start", + }); + + await primePermissionSnapshot(); + const loaderHooked = hookModuleLoader(); + wrapPermissions(entryModule); + wrapDlopen(entryModule); + wrapWasm(entryModule); + + if (!loaderHooked) { + recordModuleLoad(entryUrl.href, "static-import", snapshotPermissions()); + } + + try { + await import(entryUrl.href); + } catch (err) { + addEvent({ type: "deno.runtime.error", ts: nowIso(), message: String(err?.message ?? err) }); + } finally { + flush(); + } +} + +globalThis.addEventListener("unload", flush); +await main(); """; + } diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/GlobalUsings.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/GlobalUsings.cs new file mode 100644 index 000000000..223e6a34a --- /dev/null +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/GlobalUsings.cs @@ -0,0 +1,11 @@ +global using System; +global using System.Collections.Generic; +global using System.Globalization; +global using System.IO; +global using System.Linq; +global using System.Security.Cryptography; +global using System.Text.Json; +global using System.Threading; +global using System.Threading.Tasks; + +global using StellaOps.Scanner.Analyzers.Lang; diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerLockData.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerLockData.cs new file mode 100644 index 000000000..7c394a237 --- /dev/null +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerLockData.cs @@ -0,0 +1,48 @@ +namespace StellaOps.Scanner.Analyzers.Lang.Php.Internal; + +internal sealed class ComposerLockData +{ + public ComposerLockData( + string lockPath, + string? contentHash, + string? pluginApiVersion, + IReadOnlyList packages, + IReadOnlyList devPackages, + string? lockSha256) + { + LockPath = lockPath ?? string.Empty; + ContentHash = contentHash; + PluginApiVersion = pluginApiVersion; + Packages = packages ?? Array.Empty(); + DevPackages = devPackages ?? Array.Empty(); + LockSha256 = lockSha256; + } + + public string LockPath { get; } + + public string? ContentHash { get; } + + public string? PluginApiVersion { get; } + + public IReadOnlyList Packages { get; } + + public IReadOnlyList DevPackages { get; } + + public string? LockSha256 { get; } + + public bool IsEmpty => Packages.Count == 0 && DevPackages.Count == 0; + + public static ComposerLockData Empty { get; } = new( + lockPath: string.Empty, + contentHash: null, + pluginApiVersion: null, + packages: Array.Empty(), + devPackages: Array.Empty(), + lockSha256: null); + + public static ValueTask LoadAsync(LanguageAnalyzerContext context, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(context); + return ComposerLockReader.LoadAsync(context, cancellationToken); + } +} diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerLockReader.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerLockReader.cs new file mode 100644 index 000000000..51e164104 --- /dev/null +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerLockReader.cs @@ -0,0 +1,122 @@ +using System.Security.Cryptography; +using System.Text.Json; + +namespace StellaOps.Scanner.Analyzers.Lang.Php.Internal; + +internal static class ComposerLockReader +{ + private const string LockFileName = "composer.lock"; + + public static async ValueTask LoadAsync(LanguageAnalyzerContext context, CancellationToken cancellationToken) + { + var lockPath = Path.Combine(context.RootPath, LockFileName); + if (!File.Exists(lockPath)) + { + return ComposerLockData.Empty; + } + + await using var stream = File.Open(lockPath, FileMode.Open, FileAccess.Read, FileShare.Read); + using var document = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false); + var root = document.RootElement; + + var contentHash = TryGetString(root, "content-hash"); + var pluginApiVersion = TryGetString(root, "plugin-api-version"); + + var packages = ParsePackages(root, propertyName: "packages", isDev: false); + var devPackages = ParsePackages(root, propertyName: "packages-dev", isDev: true); + var lockSha = await ComputeSha256Async(lockPath, cancellationToken).ConfigureAwait(false); + + return new ComposerLockData( + lockPath, + contentHash, + pluginApiVersion, + packages, + devPackages, + lockSha); + } + + private static IReadOnlyList ParsePackages(JsonElement root, string propertyName, bool isDev) + { + if (!root.TryGetProperty(propertyName, out var packagesElement) || packagesElement.ValueKind != JsonValueKind.Array) + { + return Array.Empty(); + } + + var packages = new List(); + foreach (var packageElement in packagesElement.EnumerateArray()) + { + if (!TryGetString(packageElement, "name", out var name) + || !TryGetString(packageElement, "version", out var version)) + { + continue; + } + + var type = TryGetString(packageElement, "type"); + var (sourceType, sourceReference) = ParseSource(packageElement); + var (distSha, distUrl) = ParseDist(packageElement); + + packages.Add(new ComposerPackage( + name, + version, + type, + isDev, + sourceType, + sourceReference, + distSha, + distUrl)); + } + + return packages; + } + + private static (string? SourceType, string? SourceReference) ParseSource(JsonElement packageElement) + { + if (!packageElement.TryGetProperty("source", out var sourceElement) || sourceElement.ValueKind != JsonValueKind.Object) + { + return (null, null); + } + + var sourceType = TryGetString(sourceElement, "type"); + var sourceReference = TryGetString(sourceElement, "reference"); + return (sourceType, sourceReference); + } + + private static (string? DistSha, string? DistUrl) ParseDist(JsonElement packageElement) + { + if (!packageElement.TryGetProperty("dist", out var distElement) || distElement.ValueKind != JsonValueKind.Object) + { + return (null, null); + } + + var distUrl = TryGetString(distElement, "url"); + var distSha = TryGetString(distElement, "shasum") ?? TryGetString(distElement, "checksum"); + return (distSha, distUrl); + } + + private static string? TryGetString(JsonElement element, string propertyName) + => TryGetString(element, propertyName, out var value) ? value : null; + + private static bool TryGetString(JsonElement element, string propertyName, out string? value) + { + value = null; + if (!element.TryGetProperty(propertyName, out var property)) + { + return false; + } + + if (property.ValueKind == JsonValueKind.String) + { + value = property.GetString(); + return true; + } + + return false; + } + + private static async ValueTask ComputeSha256Async(string path, CancellationToken cancellationToken) + { + await using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); + var hash = await SHA256.HashDataAsync(stream, cancellationToken).ConfigureAwait(false); + return Convert.ToHexString(hash).ToLowerInvariant(); + } +} diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerPackage.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerPackage.cs new file mode 100644 index 000000000..aa843b2b8 --- /dev/null +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/ComposerPackage.cs @@ -0,0 +1,11 @@ +namespace StellaOps.Scanner.Analyzers.Lang.Php.Internal; + +internal sealed record ComposerPackage( + string Name, + string Version, + string? Type, + bool IsDev, + string? SourceType, + string? SourceReference, + string? DistSha256, + string? DistUrl); diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpCapabilitySignals.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpCapabilitySignals.cs new file mode 100644 index 000000000..6d231a1d6 --- /dev/null +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpCapabilitySignals.cs @@ -0,0 +1,32 @@ +namespace StellaOps.Scanner.Analyzers.Lang.Php.Internal; + +internal static class PhpCapabilitySignals +{ + private static readonly (string Package, string Key, string Value)[] KnownSignals = + { + ("laravel/framework", "php.capability.framework", "laravel"), + ("symfony/symfony", "php.capability.framework", "symfony"), + ("drupal/core", "php.capability.cms", "drupal"), + ("wordpress/wordpress", "php.capability.cms", "wordpress"), + ("magento/product-community-edition", "php.capability.cms", "magento"), + ("cakephp/cakephp", "php.capability.framework", "cakephp"), + ("slim/slim", "php.capability.framework", "slim"), + ("codeigniter4/framework", "php.capability.framework", "codeigniter"), + ("laminas/laminas-mvc", "php.capability.framework", "laminas"), + ("phpunit/phpunit", "php.capability.test", "phpunit"), + ("behat/behat", "php.capability.test", "behat") + }; + + public static IEnumerable> FromPackage(ComposerPackage package) + { + ArgumentNullException.ThrowIfNull(package); + + foreach (var (packageName, key, value) in KnownSignals) + { + if (package.Name.Equals(packageName, StringComparison.OrdinalIgnoreCase)) + { + yield return new KeyValuePair(key, value); + } + } + } +} diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpPackage.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpPackage.cs new file mode 100644 index 000000000..fa73540d1 --- /dev/null +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpPackage.cs @@ -0,0 +1,83 @@ +namespace StellaOps.Scanner.Analyzers.Lang.Php.Internal; + +internal sealed class PhpPackage +{ + private readonly ComposerPackage _package; + private readonly ComposerLockData _lockData; + + public PhpPackage(ComposerPackage package, ComposerLockData lockData) + { + _package = package ?? throw new ArgumentNullException(nameof(package)); + _lockData = lockData ?? throw new ArgumentNullException(nameof(lockData)); + } + + public string Name => _package.Name; + + public string Version => _package.Version; + + public string Purl => $"pkg:composer/{Name}@{Version}"; + + public string ComponentKey => $"purl::{Purl}"; + + public IEnumerable> CreateMetadata() + { + yield return new KeyValuePair("composer.dev", _package.IsDev ? "true" : "false"); + + if (!string.IsNullOrWhiteSpace(_package.Type)) + { + yield return new KeyValuePair("composer.type", _package.Type); + } + + if (!string.IsNullOrWhiteSpace(_package.SourceType)) + { + yield return new KeyValuePair("composer.source.type", _package.SourceType); + } + + if (!string.IsNullOrWhiteSpace(_package.SourceReference)) + { + yield return new KeyValuePair("composer.source.ref", _package.SourceReference); + } + + if (!string.IsNullOrWhiteSpace(_package.DistSha256)) + { + yield return new KeyValuePair("composer.dist.sha256", _package.DistSha256); + } + + if (!string.IsNullOrWhiteSpace(_package.DistUrl)) + { + yield return new KeyValuePair("composer.dist.url", _package.DistUrl); + } + + if (!string.IsNullOrWhiteSpace(_lockData.PluginApiVersion)) + { + yield return new KeyValuePair("composer.plugin_api_version", _lockData.PluginApiVersion); + } + + if (!string.IsNullOrWhiteSpace(_lockData.ContentHash)) + { + yield return new KeyValuePair("composer.content_hash", _lockData.ContentHash); + } + + foreach (var signal in PhpCapabilitySignals.FromPackage(_package)) + { + yield return signal; + } + } + + public IReadOnlyCollection CreateEvidence() + { + var locator = string.IsNullOrWhiteSpace(_lockData.LockPath) + ? "composer.lock" + : Path.GetFileName(_lockData.LockPath); + + return new[] + { + new LanguageComponentEvidence( + LanguageEvidenceKind.File, + "composer.lock", + locator, + Value: $"{Name}@{Version}", + Sha256: _lockData.LockSha256) + }; + } +} diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpPackageCollector.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpPackageCollector.cs new file mode 100644 index 000000000..aa4877284 --- /dev/null +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/Internal/PhpPackageCollector.cs @@ -0,0 +1,27 @@ +namespace StellaOps.Scanner.Analyzers.Lang.Php.Internal; + +internal static class PhpPackageCollector +{ + public static IReadOnlyList Collect(ComposerLockData lockData) + { + ArgumentNullException.ThrowIfNull(lockData); + + if (lockData.IsEmpty) + { + return Array.Empty(); + } + + var packages = new List(lockData.Packages.Count + lockData.DevPackages.Count); + foreach (var package in lockData.Packages) + { + packages.Add(new PhpPackage(package, lockData)); + } + + foreach (var package in lockData.DevPackages) + { + packages.Add(new PhpPackage(package, lockData)); + } + + return packages; + } +} diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/PhpAnalyzerPlugin.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/PhpAnalyzerPlugin.cs new file mode 100644 index 000000000..dd66ea2c9 --- /dev/null +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/PhpAnalyzerPlugin.cs @@ -0,0 +1,16 @@ +using StellaOps.Scanner.Analyzers.Lang.Plugin; + +namespace StellaOps.Scanner.Analyzers.Lang.Php; + +public sealed class PhpAnalyzerPlugin : ILanguageAnalyzerPlugin +{ + public string Name => "StellaOps.Scanner.Analyzers.Lang.Php"; + + public bool IsAvailable(IServiceProvider services) => services is not null; + + public ILanguageAnalyzer CreateAnalyzer(IServiceProvider services) + { + ArgumentNullException.ThrowIfNull(services); + return new PhpLanguageAnalyzer(); + } +} diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/PhpLanguageAnalyzer.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/PhpLanguageAnalyzer.cs new file mode 100644 index 000000000..a20209602 --- /dev/null +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/PhpLanguageAnalyzer.cs @@ -0,0 +1,38 @@ +using StellaOps.Scanner.Analyzers.Lang.Php.Internal; + +namespace StellaOps.Scanner.Analyzers.Lang.Php; + +public sealed class PhpLanguageAnalyzer : ILanguageAnalyzer +{ + public string Id => "php"; + + public string DisplayName => "PHP Analyzer"; + + public async ValueTask AnalyzeAsync(LanguageAnalyzerContext context, LanguageComponentWriter writer, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(context); + ArgumentNullException.ThrowIfNull(writer); + + var lockData = await ComposerLockData.LoadAsync(context, cancellationToken).ConfigureAwait(false); + var packages = PhpPackageCollector.Collect(lockData); + if (packages.Count == 0) + { + return; + } + + foreach (var package in packages.OrderBy(static p => p.ComponentKey, StringComparer.Ordinal)) + { + cancellationToken.ThrowIfCancellationRequested(); + + writer.AddFromPurl( + analyzerId: Id, + purl: package.Purl, + name: package.Name, + version: package.Version, + type: "composer", + metadata: package.CreateMetadata(), + evidence: package.CreateEvidence(), + usedByEntrypoint: false); + } + } +} diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/StellaOps.Scanner.Analyzers.Lang.Php.csproj b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/StellaOps.Scanner.Analyzers.Lang.Php.csproj new file mode 100644 index 000000000..227004fa8 --- /dev/null +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Php/StellaOps.Scanner.Analyzers.Lang.Php.csproj @@ -0,0 +1,20 @@ + + + net10.0 + preview + enable + enable + true + false + + + + + + + + + + + + diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Core/Contracts/ScanAnalysisKeys.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Core/Contracts/ScanAnalysisKeys.cs index 764229e6a..93a5812c0 100644 --- a/src/Scanner/__Libraries/StellaOps.Scanner.Core/Contracts/ScanAnalysisKeys.cs +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Core/Contracts/ScanAnalysisKeys.cs @@ -22,5 +22,7 @@ public static class ScanAnalysisKeys public const string DenoObservationPayload = "analysis.lang.deno.observation"; + public const string DenoRuntimePayload = "analysis.lang.deno.runtime"; + public const string RubyObservationPayload = "analysis.lang.ruby.observation"; } diff --git a/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Fixtures/lang/php/basic/composer.lock b/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Fixtures/lang/php/basic/composer.lock new file mode 100644 index 000000000..e7e1a9406 --- /dev/null +++ b/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Fixtures/lang/php/basic/composer.lock @@ -0,0 +1,33 @@ +{ + "content-hash": "e01f9b7d7f4b23a6d1ad3b8e91c1c4ae", + "plugin-api-version": "2.6.0", + "packages": [ + { + "name": "laravel/framework", + "version": "10.48.7", + "type": "library", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "0123456789abcdef0123456789abcdef01234567" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/0123456789abcdef0123456789abcdef01234567", + "shasum": "6f1b4c0908a5c2fdc3fbc0351d1a8f5f" + } + } + ], + "packages-dev": [ + { + "name": "phpunit/phpunit", + "version": "10.5.5", + "type": "library", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "9c9d4e1c8b62f9142fe995c3d76343d6330f0e36" + } + } + ] +} diff --git a/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Fixtures/lang/php/basic/expected.json b/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Fixtures/lang/php/basic/expected.json new file mode 100644 index 000000000..802741cea --- /dev/null +++ b/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Fixtures/lang/php/basic/expected.json @@ -0,0 +1,58 @@ +[ + { + "analyzerId": "php", + "componentKey": "purl::pkg:composer/laravel/framework@10.48.7", + "purl": "pkg:composer/laravel/framework@10.48.7", + "name": "laravel/framework", + "version": "10.48.7", + "type": "composer", + "usedByEntrypoint": false, + "metadata": { + "composer.content_hash": "e01f9b7d7f4b23a6d1ad3b8e91c1c4ae", + "composer.dev": "false", + "composer.dist.sha256": "6f1b4c0908a5c2fdc3fbc0351d1a8f5f", + "composer.dist.url": "https://api.github.com/repos/laravel/framework/zipball/0123456789abcdef0123456789abcdef01234567", + "composer.plugin_api_version": "2.6.0", + "composer.source.ref": "0123456789abcdef0123456789abcdef01234567", + "composer.source.type": "git", + "composer.type": "library", + "php.capability.framework": "laravel" + }, + "evidence": [ + { + "kind": "file", + "source": "composer.lock", + "locator": "composer.lock", + "value": "laravel/framework@10.48.7", + "sha256": "469f987fef544c06365b59539ec5e48d5356011ff829b36b96ec1336be2de9d1" + } + ] + }, + { + "analyzerId": "php", + "componentKey": "purl::pkg:composer/phpunit/phpunit@10.5.5", + "purl": "pkg:composer/phpunit/phpunit@10.5.5", + "name": "phpunit/phpunit", + "version": "10.5.5", + "type": "composer", + "usedByEntrypoint": false, + "metadata": { + "composer.content_hash": "e01f9b7d7f4b23a6d1ad3b8e91c1c4ae", + "composer.dev": "true", + "composer.plugin_api_version": "2.6.0", + "composer.source.ref": "9c9d4e1c8b62f9142fe995c3d76343d6330f0e36", + "composer.source.type": "git", + "composer.type": "library", + "php.capability.test": "phpunit" + }, + "evidence": [ + { + "kind": "file", + "source": "composer.lock", + "locator": "composer.lock", + "value": "phpunit/phpunit@10.5.5", + "sha256": "469f987fef544c06365b59539ec5e48d5356011ff829b36b96ec1336be2de9d1" + } + ] + } +] diff --git a/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Php/PhpLanguageAnalyzerTests.cs b/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Php/PhpLanguageAnalyzerTests.cs new file mode 100644 index 000000000..19dbd94c5 --- /dev/null +++ b/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/Php/PhpLanguageAnalyzerTests.cs @@ -0,0 +1,27 @@ +using StellaOps.Scanner.Analyzers.Lang.Php; +using StellaOps.Scanner.Analyzers.Lang.Tests.Harness; +using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities; + +namespace StellaOps.Scanner.Analyzers.Lang.Php.Tests; + +public sealed class PhpLanguageAnalyzerTests +{ + [Fact] + public async Task ComposerLockPackagesAreEmittedAsync() + { + var cancellationToken = TestContext.Current.CancellationToken; + var fixturePath = TestPaths.ResolveFixture("lang", "php", "basic"); + var goldenPath = Path.Combine(fixturePath, "expected.json"); + + var analyzers = new ILanguageAnalyzer[] + { + new PhpLanguageAnalyzer() + }; + + await LanguageAnalyzerTestHarness.AssertDeterministicAsync( + fixturePath, + goldenPath, + analyzers, + cancellationToken); + } +} diff --git a/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests.csproj b/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests.csproj new file mode 100644 index 000000000..c88276c16 --- /dev/null +++ b/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests/StellaOps.Scanner.Analyzers.Lang.Php.Tests.csproj @@ -0,0 +1,46 @@ + + + + net10.0 + preview + enable + enable + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Zastava/StellaOps.Zastava.Observer/DependencyInjection/ObserverServiceCollectionExtensions.cs b/src/Zastava/StellaOps.Zastava.Observer/DependencyInjection/ObserverServiceCollectionExtensions.cs index 5f370be9f..e57c577ca 100644 --- a/src/Zastava/StellaOps.Zastava.Observer/DependencyInjection/ObserverServiceCollectionExtensions.cs +++ b/src/Zastava/StellaOps.Zastava.Observer/DependencyInjection/ObserverServiceCollectionExtensions.cs @@ -1,15 +1,21 @@ using System; +using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using StellaOps.Scanner.Surface.Env; +using StellaOps.Scanner.Surface.FS; +using StellaOps.Scanner.Surface.Secrets; using StellaOps.Zastava.Core.Configuration; +using StellaOps.Zastava.Observer.Backend; using StellaOps.Zastava.Observer.Configuration; -using StellaOps.Zastava.Observer.ContainerRuntime.Cri; using StellaOps.Zastava.Observer.ContainerRuntime; +using StellaOps.Zastava.Observer.ContainerRuntime.Cri; using StellaOps.Zastava.Observer.Posture; using StellaOps.Zastava.Observer.Runtime; +using StellaOps.Zastava.Observer.Secrets; +using StellaOps.Zastava.Observer.Surface; using StellaOps.Zastava.Observer.Worker; -using StellaOps.Zastava.Observer.Backend; namespace Microsoft.Extensions.DependencyInjection; @@ -83,6 +89,35 @@ public static class ObserverServiceCollectionExtensions services.TryAddEnumerable(ServiceDescriptor.Singleton, ObserverRuntimeOptionsPostConfigure>()); + // Surface environment + cache/manifest/secrets wiring + services.AddSurfaceEnvironment(options => + { + options.ComponentName = "Zastava.Observer"; + options.AddPrefix("ZASTAVA_OBSERVER"); + options.AddPrefix("ZASTAVA"); + options.KnownFeatureFlags.Add("drift"); + options.KnownFeatureFlags.Add("prefetch"); + options.TenantResolver = sp => sp.GetRequiredService>().Value.Tenant; + }); + + services.AddSurfaceFileCache(); + services.AddSurfaceManifestStore(); + services.AddSurfaceSecrets(options => + { + options.ComponentName = "Zastava.Observer"; + options.RequiredSecretTypes.Add("cas-access"); + options.RequiredSecretTypes.Add("attestation"); + }); + + services.TryAddSingleton(sp => sp.GetRequiredService().Settings); + services.TryAddEnumerable(ServiceDescriptor.Singleton>(sp => + new SurfaceCacheOptionsConfigurator(sp.GetRequiredService()))); + services.TryAddEnumerable(ServiceDescriptor.Singleton>(sp => + new SurfaceManifestStoreOptionsConfigurator(sp.GetRequiredService()))); + + services.TryAddSingleton(); + services.TryAddSingleton(); + services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); @@ -101,3 +136,34 @@ internal sealed class ObserverRuntimeOptionsPostConfigure : IPostConfigureOption } } } + +internal sealed class SurfaceCacheOptionsConfigurator : IConfigureOptions +{ + private readonly SurfaceEnvironmentSettings settings; + + public SurfaceCacheOptionsConfigurator(SurfaceEnvironmentSettings settings) + { + this.settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + + public void Configure(SurfaceCacheOptions options) + { + options.RootDirectory ??= settings.CacheRoot.FullName; + } +} + +internal sealed class SurfaceManifestStoreOptionsConfigurator : IConfigureOptions +{ + private readonly SurfaceEnvironmentSettings settings; + + public SurfaceManifestStoreOptionsConfigurator(SurfaceEnvironmentSettings settings) + { + this.settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + + public void Configure(SurfaceManifestStoreOptions options) + { + options.Bucket = string.IsNullOrWhiteSpace(settings.SurfaceFsBucket) ? options.Bucket : settings.SurfaceFsBucket; + options.RootDirectory ??= Path.Combine(settings.CacheRoot.FullName, "manifests"); + } +} diff --git a/src/Zastava/StellaOps.Zastava.Observer/StellaOps.Zastava.Observer.csproj b/src/Zastava/StellaOps.Zastava.Observer/StellaOps.Zastava.Observer.csproj index fc13f1692..225e3acd0 100644 --- a/src/Zastava/StellaOps.Zastava.Observer/StellaOps.Zastava.Observer.csproj +++ b/src/Zastava/StellaOps.Zastava.Observer/StellaOps.Zastava.Observer.csproj @@ -17,6 +17,9 @@ + + + diff --git a/src/Zastava/StellaOps.Zastava.Observer/Surface/RuntimeSurfaceFsClient.cs b/src/Zastava/StellaOps.Zastava.Observer/Surface/RuntimeSurfaceFsClient.cs index b61d38821..d084ed7df 100644 --- a/src/Zastava/StellaOps.Zastava.Observer/Surface/RuntimeSurfaceFsClient.cs +++ b/src/Zastava/StellaOps.Zastava.Observer/Surface/RuntimeSurfaceFsClient.cs @@ -19,14 +19,26 @@ internal sealed class RuntimeSurfaceFsClient : IRuntimeSurfaceFsClient _environment = environment ?? throw new ArgumentNullException(nameof(environment)); } - public Task TryGetManifestAsync(string manifestDigest, CancellationToken cancellationToken = default) + public async Task TryGetManifestAsync(string manifestDigest, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(manifestDigest)) { - return Task.FromResult(null); + return null; } - // manifest digests follow sha256:; manifest reader handles validation and tenant discovery - return _manifestReader.TryGetByDigestAsync(manifestDigest.Trim(), cancellationToken); + var manifest = await _manifestReader.TryGetByDigestAsync(manifestDigest.Trim(), cancellationToken).ConfigureAwait(false); + if (manifest is null) + { + return null; + } + + // Enforce tenant scoping to avoid crossing cache boundaries in multi-tenant deployments. + if (!string.IsNullOrWhiteSpace(manifest.Tenant) + && !string.Equals(manifest.Tenant, _environment.Tenant, StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + return manifest; } } diff --git a/src/Zastava/StellaOps.Zastava.Webhook/Admission/AdmissionResponseBuilder.cs b/src/Zastava/StellaOps.Zastava.Webhook/Admission/AdmissionResponseBuilder.cs index add7744b5..37f2e0bab 100644 --- a/src/Zastava/StellaOps.Zastava.Webhook/Admission/AdmissionResponseBuilder.cs +++ b/src/Zastava/StellaOps.Zastava.Webhook/Admission/AdmissionResponseBuilder.cs @@ -68,6 +68,11 @@ internal sealed class AdmissionResponseBuilder metadata["cache"] = "hit"; } + if (!string.IsNullOrWhiteSpace(decision.SurfacePointer)) + { + metadata["surfacePointer"] = decision.SurfacePointer!; + } + var resolved = decision.ResolvedDigest ?? decision.OriginalImage; images.Add(new AdmissionImageVerdict diff --git a/src/Zastava/StellaOps.Zastava.Webhook/Admission/RuntimeAdmissionPolicyService.cs b/src/Zastava/StellaOps.Zastava.Webhook/Admission/RuntimeAdmissionPolicyService.cs index 442ab2e47..51fa271e0 100644 --- a/src/Zastava/StellaOps.Zastava.Webhook/Admission/RuntimeAdmissionPolicyService.cs +++ b/src/Zastava/StellaOps.Zastava.Webhook/Admission/RuntimeAdmissionPolicyService.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using StellaOps.Scanner.Surface.Env; using StellaOps.Zastava.Core.Contracts; using StellaOps.Zastava.Core.Diagnostics; using StellaOps.Zastava.Webhook.Backend; using StellaOps.Zastava.Webhook.Configuration; +using StellaOps.Zastava.Webhook.Surface; namespace StellaOps.Zastava.Webhook.Admission; @@ -22,6 +24,8 @@ internal sealed class RuntimeAdmissionPolicyService : IRuntimeAdmissionPolicySer private readonly IOptionsMonitor options; private readonly IZastavaRuntimeMetrics runtimeMetrics; private readonly TimeProvider timeProvider; + private readonly IWebhookSurfaceFsClient surfaceFsClient; + private readonly SurfaceEnvironmentSettings surfaceSettings; private readonly ILogger logger; public RuntimeAdmissionPolicyService( @@ -31,6 +35,8 @@ internal sealed class RuntimeAdmissionPolicyService : IRuntimeAdmissionPolicySer IOptionsMonitor options, IZastavaRuntimeMetrics runtimeMetrics, TimeProvider timeProvider, + IWebhookSurfaceFsClient surfaceFsClient, + SurfaceEnvironmentSettings surfaceSettings, ILogger logger) { this.policyClient = policyClient ?? throw new ArgumentNullException(nameof(policyClient)); @@ -39,6 +45,8 @@ internal sealed class RuntimeAdmissionPolicyService : IRuntimeAdmissionPolicySer this.options = options ?? throw new ArgumentNullException(nameof(options)); this.runtimeMetrics = runtimeMetrics ?? throw new ArgumentNullException(nameof(runtimeMetrics)); this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider)); + this.surfaceFsClient = surfaceFsClient ?? throw new ArgumentNullException(nameof(surfaceFsClient)); + this.surfaceSettings = surfaceSettings ?? throw new ArgumentNullException(nameof(surfaceSettings)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -67,9 +75,25 @@ internal sealed class RuntimeAdmissionPolicyService : IRuntimeAdmissionPolicySer var combinedResults = new Dictionary(StringComparer.Ordinal); var backendMisses = new List(); var fromCache = new HashSet(StringComparer.Ordinal); + var missingSurface = new HashSet(StringComparer.Ordinal); + var surfacePointers = new Dictionary(StringComparer.Ordinal); + var enforceSurface = surfaceSettings.FeatureFlags.Count == 0 + || surfaceSettings.FeatureFlags.Contains("admission", StringComparer.OrdinalIgnoreCase); foreach (var digest in resolved) { + if (enforceSurface) + { + var (found, pointer) = await surfaceFsClient.TryGetManifestAsync(digest.Digest, cancellationToken).ConfigureAwait(false); + if (!found) + { + missingSurface.Add(digest.Digest); + continue; + } + + surfacePointers[digest.Digest] = pointer; + } + if (cache.TryGet(digest.Digest, out var cached)) { combinedResults[digest.Digest] = cached; @@ -134,15 +158,16 @@ internal sealed class RuntimeAdmissionPolicyService : IRuntimeAdmissionPolicySer ResolvedDigest = resolution.ResolvedDigest, Verdict = allowed ? PolicyVerdict.Warn : PolicyVerdict.Error, Allowed = allowed, - Policy = null, - Reasons = reasons, - FromCache = false, - ResolutionFailed = false - }); - } - else - { - decisions.Add(CreateResolutionFailureDecision(resolution)); + Policy = null, + Reasons = reasons, + FromCache = false, + ResolutionFailed = false, + SurfacePointer = surfacePointers.GetValueOrDefault(resolution.ResolvedDigest) + }); + } + else + { + decisions.Add(CreateResolutionFailureDecision(resolution)); } } @@ -166,6 +191,25 @@ internal sealed class RuntimeAdmissionPolicyService : IRuntimeAdmissionPolicySer continue; } + if (missingSurface.Contains(resolution.ResolvedDigest)) + { + var manifestDecision = new RuntimeAdmissionDecision + { + OriginalImage = resolution.Original, + ResolvedDigest = resolution.ResolvedDigest, + Verdict = PolicyVerdict.Error, + Allowed = false, + Policy = null, + Reasons = new[] { "surface.manifest.missing" }, + FromCache = false, + ResolutionFailed = false, + SurfacePointer = null + }; + RecordDecisionMetrics(false, false, false, RuntimeEventKind.ContainerStart); + decisions.Add(manifestDecision); + continue; + } + if (!combinedResults.TryGetValue(resolution.ResolvedDigest, out var policyResult)) { var synthetic = new RuntimeAdmissionDecision @@ -177,7 +221,8 @@ internal sealed class RuntimeAdmissionPolicyService : IRuntimeAdmissionPolicySer Policy = null, Reasons = new[] { "zastava.policy.result.missing" }, FromCache = false, - ResolutionFailed = false + ResolutionFailed = false, + SurfacePointer = surfacePointers.GetValueOrDefault(resolution.ResolvedDigest) }; RecordDecisionMetrics(false, false, false, RuntimeEventKind.ContainerStart); decisions.Add(synthetic); @@ -197,7 +242,8 @@ internal sealed class RuntimeAdmissionPolicyService : IRuntimeAdmissionPolicySer Policy = policyResult, Reasons = reasons, FromCache = cached, - ResolutionFailed = false + ResolutionFailed = false, + SurfacePointer = surfacePointers.GetValueOrDefault(resolution.ResolvedDigest) }); } @@ -290,6 +336,7 @@ internal sealed record RuntimeAdmissionDecision public IReadOnlyList Reasons { get; init; } = Array.Empty(); public bool FromCache { get; init; } public bool ResolutionFailed { get; init; } + public string? SurfacePointer { get; init; } } internal sealed record RuntimeAdmissionEvaluation diff --git a/src/Zastava/StellaOps.Zastava.Webhook/DependencyInjection/ServiceCollectionExtensions.cs b/src/Zastava/StellaOps.Zastava.Webhook/DependencyInjection/ServiceCollectionExtensions.cs index acfa19e80..7250732cc 100644 --- a/src/Zastava/StellaOps.Zastava.Webhook/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Zastava/StellaOps.Zastava.Webhook/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,14 +1,20 @@ using System; +using System.IO; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using StellaOps.Scanner.Surface.Env; +using StellaOps.Scanner.Surface.FS; +using StellaOps.Scanner.Surface.Secrets; using StellaOps.Zastava.Core.Configuration; using StellaOps.Zastava.Webhook.Admission; using StellaOps.Zastava.Webhook.Authority; using StellaOps.Zastava.Webhook.Backend; using StellaOps.Zastava.Webhook.Certificates; using StellaOps.Zastava.Webhook.Configuration; -using StellaOps.Zastava.Webhook.Hosting; using StellaOps.Zastava.Webhook.DependencyInjection; +using StellaOps.Zastava.Webhook.Hosting; +using StellaOps.Zastava.Webhook.Secrets; +using StellaOps.Zastava.Webhook.Surface; namespace Microsoft.Extensions.DependencyInjection; @@ -30,11 +36,37 @@ public static class ServiceCollectionExtensions services.TryAddSingleton(); services.TryAddEnumerable(ServiceDescriptor.Singleton, WebhookRuntimeOptionsPostConfigure>()); + services.AddSurfaceEnvironment(options => + { + options.ComponentName = "Zastava.Webhook"; + options.AddPrefix("ZASTAVA_WEBHOOK"); + options.AddPrefix("ZASTAVA"); + options.KnownFeatureFlags.Add("admission"); + options.KnownFeatureFlags.Add("drift"); + options.TenantResolver = sp => sp.GetRequiredService>().Value.Tenant; + }); + + services.AddSurfaceFileCache(); + services.AddSurfaceManifestStore(); + services.AddSurfaceSecrets(options => + { + options.ComponentName = "Zastava.Webhook"; + options.RequiredSecretTypes.Add("attestation"); + }); + + services.TryAddSingleton(sp => sp.GetRequiredService().Settings); + services.TryAddEnumerable(ServiceDescriptor.Singleton>(sp => + new SurfaceCacheOptionsConfigurator(sp.GetRequiredService()))); + services.TryAddEnumerable(ServiceDescriptor.Singleton>(sp => + new SurfaceManifestStoreOptionsConfigurator(sp.GetRequiredService()))); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); services.AddHttpClient((provider, client) => { @@ -53,8 +85,39 @@ public static class ServiceCollectionExtensions services.AddHealthChecks() .AddCheck("webhook_tls") - .AddCheck("authority_token"); - - return services; - } -} + .AddCheck("authority_token"); + + return services; + } +} + +internal sealed class SurfaceCacheOptionsConfigurator : IConfigureOptions +{ + private readonly SurfaceEnvironmentSettings settings; + + public SurfaceCacheOptionsConfigurator(SurfaceEnvironmentSettings settings) + { + this.settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + + public void Configure(SurfaceCacheOptions options) + { + options.RootDirectory ??= settings.CacheRoot.FullName; + } +} + +internal sealed class SurfaceManifestStoreOptionsConfigurator : IConfigureOptions +{ + private readonly SurfaceEnvironmentSettings settings; + + public SurfaceManifestStoreOptionsConfigurator(SurfaceEnvironmentSettings settings) + { + this.settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } + + public void Configure(SurfaceManifestStoreOptions options) + { + options.Bucket = string.IsNullOrWhiteSpace(settings.SurfaceFsBucket) ? options.Bucket : settings.SurfaceFsBucket; + options.RootDirectory ??= Path.Combine(settings.CacheRoot.FullName, "manifests"); + } +} diff --git a/src/Zastava/StellaOps.Zastava.Webhook/StellaOps.Zastava.Webhook.csproj b/src/Zastava/StellaOps.Zastava.Webhook/StellaOps.Zastava.Webhook.csproj index 9336bfaf2..07e62ca49 100644 --- a/src/Zastava/StellaOps.Zastava.Webhook/StellaOps.Zastava.Webhook.csproj +++ b/src/Zastava/StellaOps.Zastava.Webhook/StellaOps.Zastava.Webhook.csproj @@ -16,5 +16,8 @@ + + + - \ No newline at end of file + diff --git a/src/Zastava/__Tests/StellaOps.Zastava.Webhook.Tests/Admission/AdmissionResponseBuilderTests.cs b/src/Zastava/__Tests/StellaOps.Zastava.Webhook.Tests/Admission/AdmissionResponseBuilderTests.cs index 3c2edc0f9..ec1db5ea3 100644 --- a/src/Zastava/__Tests/StellaOps.Zastava.Webhook.Tests/Admission/AdmissionResponseBuilderTests.cs +++ b/src/Zastava/__Tests/StellaOps.Zastava.Webhook.Tests/Admission/AdmissionResponseBuilderTests.cs @@ -52,7 +52,8 @@ public sealed class AdmissionResponseBuilderTests }, Reasons = Array.Empty(), FromCache = false, - ResolutionFailed = false + ResolutionFailed = false, + SurfacePointer = "cas://surface-cache/manifests/tenant-a/abcd.json" } }, BackendFailed = false, @@ -70,6 +71,7 @@ public sealed class AdmissionResponseBuilderTests Assert.NotNull(response.Response.AuditAnnotations); Assert.True(envelope.Decision.Images.First().HasSbomReferrers); Assert.StartsWith("sha256-", envelope.Decision.PodSpecDigest, StringComparison.Ordinal); + Assert.Equal("cas://surface-cache/manifests/tenant-a/abcd.json", envelope.Decision.Images.First().Metadata["surfacePointer"]); } [Fact] diff --git a/src/Zastava/__Tests/StellaOps.Zastava.Webhook.Tests/Admission/RuntimeAdmissionPolicyServiceTests.cs b/src/Zastava/__Tests/StellaOps.Zastava.Webhook.Tests/Admission/RuntimeAdmissionPolicyServiceTests.cs index f64209dfe..1282242c5 100644 --- a/src/Zastava/__Tests/StellaOps.Zastava.Webhook.Tests/Admission/RuntimeAdmissionPolicyServiceTests.cs +++ b/src/Zastava/__Tests/StellaOps.Zastava.Webhook.Tests/Admission/RuntimeAdmissionPolicyServiceTests.cs @@ -1,14 +1,17 @@ using System; using System.Collections.Generic; using System.Diagnostics.Metrics; +using System.IO; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using StellaOps.Scanner.Surface.Env; using StellaOps.Zastava.Core.Configuration; using StellaOps.Zastava.Core.Diagnostics; using StellaOps.Zastava.Core.Contracts; using StellaOps.Zastava.Webhook.Admission; using StellaOps.Zastava.Webhook.Backend; using StellaOps.Zastava.Webhook.Configuration; +using StellaOps.Zastava.Webhook.Surface; using Xunit; namespace StellaOps.Zastava.Webhook.Tests.Admission; @@ -40,6 +43,8 @@ public sealed class RuntimeAdmissionPolicyServiceTests var optionsMonitor = new StaticOptionsMonitor(new ZastavaWebhookOptions()); var cache = new RuntimePolicyCache(Options.Create(optionsMonitor.CurrentValue), timeProvider, NullLogger.Instance); var resolver = new ImageDigestResolver(); + var surfaceSettings = CreateSurfaceSettings(); + var surfaceClient = new StubSurfaceFsClient(found: true); var service = new RuntimeAdmissionPolicyService( policyClient, @@ -48,6 +53,8 @@ public sealed class RuntimeAdmissionPolicyServiceTests optionsMonitor, runtimeMetrics, timeProvider, + surfaceClient, + surfaceSettings, NullLogger.Instance); var request = new RuntimeAdmissionRequest( @@ -83,6 +90,8 @@ public sealed class RuntimeAdmissionPolicyServiceTests }; var optionsMonitor = new StaticOptionsMonitor(options); var cache = new RuntimePolicyCache(Options.Create(optionsMonitor.CurrentValue), timeProvider, NullLogger.Instance); + var surfaceSettings = CreateSurfaceSettings(); + var surfaceClient = new StubSurfaceFsClient(found: true); var service = new RuntimeAdmissionPolicyService( policyClient, new ImageDigestResolver(), @@ -90,6 +99,8 @@ public sealed class RuntimeAdmissionPolicyServiceTests optionsMonitor, new StubRuntimeMetrics(), timeProvider, + surfaceClient, + surfaceSettings, NullLogger.Instance); var request = new RuntimeAdmissionRequest( @@ -121,6 +132,8 @@ public sealed class RuntimeAdmissionPolicyServiceTests }; var optionsMonitor = new StaticOptionsMonitor(options); var cache = new RuntimePolicyCache(Options.Create(optionsMonitor.CurrentValue), timeProvider, NullLogger.Instance); + var surfaceSettings = CreateSurfaceSettings(); + var surfaceClient = new StubSurfaceFsClient(found: true); var service = new RuntimeAdmissionPolicyService( policyClient, @@ -129,6 +142,8 @@ public sealed class RuntimeAdmissionPolicyServiceTests optionsMonitor, new StubRuntimeMetrics(), timeProvider, + surfaceClient, + surfaceSettings, NullLogger.Instance); var request = new RuntimeAdmissionRequest( @@ -152,6 +167,8 @@ public sealed class RuntimeAdmissionPolicyServiceTests var policyClient = new StubRuntimePolicyClient(new RuntimePolicyResponse { TtlSeconds = 300 }); var optionsMonitor = new StaticOptionsMonitor(new ZastavaWebhookOptions()); var cache = new RuntimePolicyCache(Options.Create(optionsMonitor.CurrentValue), timeProvider, NullLogger.Instance); + var surfaceSettings = CreateSurfaceSettings(); + var surfaceClient = new StubSurfaceFsClient(found: true); var service = new RuntimeAdmissionPolicyService( policyClient, @@ -160,6 +177,8 @@ public sealed class RuntimeAdmissionPolicyServiceTests optionsMonitor, new StubRuntimeMetrics(), timeProvider, + surfaceClient, + surfaceSettings, NullLogger.Instance); var request = new RuntimeAdmissionRequest( @@ -175,6 +194,78 @@ public sealed class RuntimeAdmissionPolicyServiceTests Assert.Contains("image.reference.tag_unresolved", decision.Reasons); } + [Fact] + public async Task EvaluateAsync_DeniesWhenSurfaceManifestMissing() + { + var timeProvider = new TestTimeProvider(new DateTimeOffset(2025, 10, 24, 12, 0, 0, TimeSpan.Zero)); + var policyClient = new StubRuntimePolicyClient(new RuntimePolicyResponse + { + TtlSeconds = 300, + Results = new Dictionary + { + [SampleDigest] = new RuntimePolicyImageResult + { + PolicyVerdict = PolicyVerdict.Pass, + Signed = true, + HasSbom = true + } + } + }); + var optionsMonitor = new StaticOptionsMonitor(new ZastavaWebhookOptions()); + var cache = new RuntimePolicyCache(Options.Create(optionsMonitor.CurrentValue), timeProvider, NullLogger.Instance); + var surfaceSettings = CreateSurfaceSettings(); + var surfaceClient = new StubSurfaceFsClient(found: false); + + var service = new RuntimeAdmissionPolicyService( + policyClient, + new ImageDigestResolver(), + cache, + optionsMonitor, + new StubRuntimeMetrics(), + timeProvider, + surfaceClient, + surfaceSettings, + NullLogger.Instance); + + var request = new RuntimeAdmissionRequest( + Namespace: "payments", + Labels: new Dictionary(), + Images: new[] { $"ghcr.io/example/api@{SampleDigest}" }); + + var evaluation = await service.EvaluateAsync(request, CancellationToken.None); + var decision = Assert.Single(evaluation.Decisions); + Assert.False(decision.Allowed); + Assert.Contains("surface.manifest.missing", decision.Reasons); + } + + private static SurfaceEnvironmentSettings CreateSurfaceSettings() + => new( + new Uri("https://surface.test"), + "surface-cache", + null, + new DirectoryInfo(Path.Combine(Path.GetTempPath(), "zastava-surface-tests")), + 1024, + false, + new[] { "admission" }, + new SurfaceSecretsConfiguration("inline", "default", null, null, null, true), + "default", + new SurfaceTlsConfiguration(null, null, null)); + + private sealed class StubSurfaceFsClient : IWebhookSurfaceFsClient + { + private readonly bool found; + private readonly string? pointer; + + public StubSurfaceFsClient(bool found, string? pointer = "cas://surface-cache/manifests/default/test.json") + { + this.found = found; + this.pointer = pointer; + } + + public Task<(bool Found, string? ManifestUri)> TryGetManifestAsync(string manifestDigest, CancellationToken cancellationToken = default) + => Task.FromResult((found, pointer)); + } + private sealed class StubRuntimePolicyClient : IRuntimePolicyClient { private readonly RuntimePolicyResponse? response; diff --git a/tests/Provenance/StellaOps.Provenance.Attestation.Tests/Fixtures/cosign.sig b/tests/Provenance/StellaOps.Provenance.Attestation.Tests/Fixtures/cosign.sig new file mode 100644 index 000000000..7eaab16a6 --- /dev/null +++ b/tests/Provenance/StellaOps.Provenance.Attestation.Tests/Fixtures/cosign.sig @@ -0,0 +1 @@ +AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw== diff --git a/tests/Provenance/StellaOps.Provenance.Attestation.Tests/PromotionAttestationBuilderTests.cs b/tests/Provenance/StellaOps.Provenance.Attestation.Tests/PromotionAttestationBuilderTests.cs new file mode 100644 index 000000000..5571135b9 --- /dev/null +++ b/tests/Provenance/StellaOps.Provenance.Attestation.Tests/PromotionAttestationBuilderTests.cs @@ -0,0 +1,78 @@ +using System.Text; +using StellaOps.Provenance.Attestation; +using Xunit; + +namespace StellaOps.Provenance.Attestation.Tests; + +public sealed class PromotionAttestationBuilderTests +{ + [Fact] + public async Task BuildAsync_SignsCanonicalPayloadAndAddsPredicateClaim() + { + var predicate = new PromotionPredicate( + ImageDigest: "sha256:deadbeef", + SbomDigest: "sha256:sbom", + VexDigest: "sha256:vex", + PromotionId: "promo-123", + RekorEntry: "rekor:entry", + Metadata: new Dictionary + { + { "z", "last" }, + { "a", "first" } + }); + + var signer = new RecordingSigner(); + + var attestation = await PromotionAttestationBuilder.BuildAsync(predicate, signer); + + Assert.Equal(predicate, attestation.Predicate); + Assert.NotNull(attestation.Payload); + Assert.Equal(PromotionAttestationBuilder.ContentType, signer.LastRequest?.ContentType); + Assert.Equal(PromotionAttestationBuilder.PredicateType, signer.LastRequest?.Claims!["predicateType"]); + Assert.Equal(attestation.Payload, signer.LastRequest?.Payload); + Assert.Equal(attestation.Signature, signer.LastResult); + + // verify canonical order is stable (metadata keys sorted, property names sorted) + var canonicalJson = Encoding.UTF8.GetString(attestation.Payload); + Assert.Equal( + "{\"ImageDigest\":\"sha256:deadbeef\",\"Metadata\":{\"a\":\"first\",\"z\":\"last\"},\"PromotionId\":\"promo-123\",\"RekorEntry\":\"rekor:entry\",\"SbomDigest\":\"sha256:sbom\",\"VexDigest\":\"sha256:vex\"}", + canonicalJson); + } + + [Fact] + public async Task BuildAsync_MergesClaimsWithoutOverwritingPredicateType() + { + var predicate = new PromotionPredicate( + ImageDigest: "sha256:x", + SbomDigest: "sha256:y", + VexDigest: "sha256:z", + PromotionId: "p-1"); + + var signer = new RecordingSigner(); + var customClaims = new Dictionary { { "env", "stage" }, { "predicateType", "custom" } }; + + await PromotionAttestationBuilder.BuildAsync(predicate, signer, customClaims); + + Assert.NotNull(signer.LastRequest); + Assert.Equal(PromotionAttestationBuilder.PredicateType, signer.LastRequest!.Claims!["predicateType"]); + Assert.Equal("stage", signer.LastRequest!.Claims!["env"]); + } + + private sealed class RecordingSigner : ISigner + { + public SignRequest? LastRequest { get; private set; } + public SignResult? LastResult { get; private set; } + + public Task SignAsync(SignRequest request, CancellationToken cancellationToken = default) + { + LastRequest = request; + LastResult = new SignResult( + Signature: Encoding.UTF8.GetBytes("sig"), + KeyId: "key-1", + SignedAt: DateTimeOffset.UtcNow, + Claims: request.Claims); + + return Task.FromResult(LastResult); + } + } +} diff --git a/tests/Provenance/StellaOps.Provenance.Attestation.Tests/SignersTests.cs b/tests/Provenance/StellaOps.Provenance.Attestation.Tests/SignersTests.cs new file mode 100644 index 000000000..0b89caefe --- /dev/null +++ b/tests/Provenance/StellaOps.Provenance.Attestation.Tests/SignersTests.cs @@ -0,0 +1,164 @@ +using System.Text; +using StellaOps.Provenance.Attestation; +using Xunit; + +namespace StellaOps.Provenance.Attestation.Tests; + +public sealed class SignersTests +{ + [Fact] + public async Task HmacSigner_SignsAndAudits() + { + var key = new InMemoryKeyProvider("k1", Convert.FromHexString("0f0e0d0c0b0a09080706050403020100")); + var audit = new InMemoryAuditSink(); + var time = new TestTimeProvider(new DateTimeOffset(2025, 11, 22, 12, 0, 0, TimeSpan.Zero)); + var signer = new HmacSigner(key, audit, time); + + var request = new SignRequest(Encoding.UTF8.GetBytes("payload"), "application/json", + Claims: new Dictionary { { "sub", "builder" } }); + + var result = await signer.SignAsync(request); + + Assert.Equal("k1", result.KeyId); + Assert.Equal(time.GetUtcNow(), result.SignedAt); + Assert.Equal( + Convert.FromHexString("b3ae92d9a593318d03d7c4b6dca9710c416f582e88cfc08196d8c2cdabb3c480"), + result.Signature); + Assert.Single(audit.Signed); + Assert.Empty(audit.Missing); + } + + [Fact] + public async Task HmacSigner_EnforcesRequiredClaims() + { + var key = new InMemoryKeyProvider("k-claims", Encoding.UTF8.GetBytes("secret")); + var audit = new InMemoryAuditSink(); + var signer = new HmacSigner(key, audit, new TestTimeProvider(DateTimeOffset.UtcNow)); + + var request = new SignRequest(Encoding.UTF8.GetBytes("payload"), "text/plain", + Claims: new Dictionary(), + RequiredClaims: new[] { "sub" }); + + await Assert.ThrowsAsync(() => signer.SignAsync(request)); + Assert.Contains(audit.Missing, x => x.keyId == "k-claims" && x.claim == "sub"); + } + + [Fact] + public async Task RotatingKeyProvider_LogsRotationWhenNewKeyBecomesActive() + { + var now = new DateTimeOffset(2025, 11, 22, 10, 0, 0, TimeSpan.Zero); + var time = new TestTimeProvider(now); + var audit = new InMemoryAuditSink(); + + var expiring = new InMemoryKeyProvider("old", new byte[] { 0x01 }, now.AddMinutes(5)); + var longLived = new InMemoryKeyProvider("new", new byte[] { 0x02 }, now.AddHours(1)); + + var provider = new RotatingKeyProvider(new[] { expiring, longLived }, time, audit); + var signer = new HmacSigner(provider, audit, time); + + await signer.SignAsync(new SignRequest(new byte[] { 0xAB }, "demo")); + + Assert.Contains(audit.Rotations, r => r.previousKeyId == "old" && r.nextKeyId == "new"); + Assert.Equal("new", provider.KeyId); + } + + [Fact] + public async Task CosignSigner_UsesClientAndAudits() + { + var signatureBytes = Convert.FromBase64String(await File.ReadAllTextAsync(Path.Combine("Fixtures", "cosign.sig"))); // fixture is deterministic + var client = new FakeCosignClient(signatureBytes); + var audit = new InMemoryAuditSink(); + var time = new TestTimeProvider(new DateTimeOffset(2025, 11, 22, 13, 0, 0, TimeSpan.Zero)); + var signer = new CosignSigner("cosign://stella", client, audit, time); + + var request = new SignRequest(Encoding.UTF8.GetBytes("subject"), "application/vnd.stella+json", + Claims: new Dictionary { { "sub", "artifact" } }, + RequiredClaims: new[] { "sub" }); + + var result = await signer.SignAsync(request); + + Assert.Equal(signatureBytes, result.Signature); + Assert.Equal(time.GetUtcNow(), result.SignedAt); + Assert.Equal("cosign://stella", result.KeyId); + Assert.Single(audit.Signed); + Assert.Empty(audit.Missing); + + var call = Assert.Single(client.Calls); + Assert.Equal("cosign://stella", call.keyRef); + Assert.Equal("application/vnd.stella+json", call.contentType); + Assert.Equal(request.Payload, call.payload); + } + + [Fact] + public async Task KmsSigner_EnforcesRequiredClaims() + { + var signature = new byte[] { 0xCA, 0xFE, 0xBA, 0xBE }; + var client = new FakeKmsClient(signature); + var audit = new InMemoryAuditSink(); + var key = new InMemoryKeyProvider("kms-1", new byte[] { 0x00 }, DateTimeOffset.UtcNow.AddDays(1)); + var signer = new KmsSigner(client, key, audit, new TestTimeProvider(DateTimeOffset.UtcNow)); + + var request = new SignRequest(Encoding.UTF8.GetBytes("body"), "application/json", + Claims: new Dictionary { { "aud", "stella" } }, + RequiredClaims: new[] { "sub" }); + + await Assert.ThrowsAsync(() => signer.SignAsync(request)); + Assert.Contains(audit.Missing, x => x.keyId == "kms-1" && x.claim == "sub"); + + var validAudit = new InMemoryAuditSink(); + var validSigner = new KmsSigner(client, key, validAudit, new TestTimeProvider(DateTimeOffset.UtcNow)); + var validRequest = new SignRequest(Encoding.UTF8.GetBytes("body"), "application/json", + Claims: new Dictionary { { "aud", "stella" }, { "sub", "actor" } }, + RequiredClaims: new[] { "sub" }); + + var result = await validSigner.SignAsync(validRequest); + + Assert.Equal(signature, result.Signature); + Assert.Equal("kms-1", result.KeyId); + Assert.Empty(validAudit.Missing); + } + + private sealed class FakeCosignClient : ICosignClient + { + public List<(byte[] payload, string contentType, string keyRef)> Calls { get; } = new(); + private readonly byte[] _signature; + + public FakeCosignClient(byte[] signature) + { + _signature = signature ?? throw new ArgumentNullException(nameof(signature)); + } + + public Task SignAsync(byte[] payload, string contentType, string keyRef, CancellationToken cancellationToken) + { + Calls.Add((payload, contentType, keyRef)); + return Task.FromResult(_signature); + } + } + + private sealed class FakeKmsClient : IKmsClient + { + private readonly byte[] _signature; + public List<(byte[] payload, string contentType, string keyId)> Calls { get; } = new(); + + public FakeKmsClient(byte[] signature) => _signature = signature; + + public Task SignAsync(byte[] payload, string contentType, string keyId, CancellationToken cancellationToken) + { + Calls.Add((payload, contentType, keyId)); + return Task.FromResult(_signature); + } + } +} + +internal sealed class TestTimeProvider : TimeProvider +{ + private DateTimeOffset _now; + + public TestTimeProvider(DateTimeOffset now) => _now = now; + + public override DateTimeOffset GetUtcNow() => _now; + public override TimeZoneInfo LocalTimeZone => TimeZoneInfo.Utc; + public override long GetTimestamp() => 0L; + + public void Advance(TimeSpan delta) => _now = _now.Add(delta); +} diff --git a/tests/Provenance/StellaOps.Provenance.Attestation.Tests/StellaOps.Provenance.Attestation.Tests.csproj b/tests/Provenance/StellaOps.Provenance.Attestation.Tests/StellaOps.Provenance.Attestation.Tests.csproj new file mode 100644 index 000000000..a49fcc53a --- /dev/null +++ b/tests/Provenance/StellaOps.Provenance.Attestation.Tests/StellaOps.Provenance.Attestation.Tests.csproj @@ -0,0 +1,20 @@ + + + net10.0 + false + enable + enable + true + + + + + + + + + + + + + diff --git a/tools/linksets-ci.sh b/tools/linksets-ci.sh index bc1c632c8..bde35997e 100644 --- a/tools/linksets-ci.sh +++ b/tools/linksets-ci.sh @@ -8,6 +8,10 @@ if [[ -z "$DOTNET_EXE" ]]; then echo "dotnet not found" >&2; exit 1; fi export VSTEST_DISABLE_APPDOMAIN=1 export DOTNET_CLI_UI_LANGUAGE=en +export DOTNET_CLI_TELEMETRY_OPTOUT=1 +# Prefer the curated offline feed to avoid network flakiness during CI. +export NUGET_PACKAGES="${ROOT_DIR}/local-nugets" +RESTORE_SOURCE="--source ${ROOT_DIR}/local-nugets --ignore-failed-sources" # Ensure Mongo2Go can find OpenSSL 1.1 (needed by bundled mongod) OPENSSL11_DIR="$ROOT_DIR/tools/openssl1.1/lib" if [[ -d "$OPENSSL11_DIR" ]]; then @@ -15,4 +19,6 @@ if [[ -d "$OPENSSL11_DIR" ]]; then fi RESULTS_DIR="$ROOT_DIR/out/test-results/linksets" mkdir -p "$RESULTS_DIR" -exec "$ROOT_DIR/tools/dotnet-filter.sh" test "$PROJECT" --filter "Linksets" --results-directory "$RESULTS_DIR" --logger "trx;LogFileName=linksets.trx" --no-build +# Restore explicitly against offline cache, then run tests without restoring again. +"$ROOT_DIR/tools/dotnet-filter.sh" restore "$PROJECT" $RESTORE_SOURCE +exec "$ROOT_DIR/tools/dotnet-filter.sh" test "$PROJECT" --no-restore --filter "Linksets" --results-directory "$RESULTS_DIR" --logger "trx;LogFileName=linksets.trx"