work work ... haaaard work
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
| 1 | CONCELIER-LNM-21-001 | DONE (2025-11-22) | Await Cartographer schema. | Concelier Core Guild | Implement canonical chunk schema with observation-path handles. |
|
||||
| 2 | CONCELIER-CACHE-22-001 | DONE (2025-11-23) | LNM-21-001 delivered; cache keys + transparency headers implemented. | Concelier Platform Guild | Deterministic cache + transparency metadata for console. |
|
||||
| 3 | CONCELIER-MIRROR-23-001-DEV | DONE (2025-11-23) | Dev mirror path documented and sample generator provided (`docs/modules/concelier/mirror-export.md`); uses existing endpoints with unsigned dev bundle layout. | Concelier + Attestor Guilds | Implement mirror/offline provenance path for advisory chunks (schema, handlers, tests). |
|
||||
| 3b | DEVOPS-MIRROR-23-001-REL | BLOCKED (Release/DevOps only) | Move to DevOps release sprint; awaits CI signing/publish lanes and Attestor mirror contract. Not a development blocker. | DevOps Guild · Security Guild | Wire CI/release jobs to publish signed mirror/offline provenance artefacts for advisory chunks. |
|
||||
| 3b | DEVOPS-MIRROR-23-001-REL | BLOCKED (Release/DevOps only) | DEPLOY-MIRROR-23-001 (SPRINT_501_ops_deployment_i) — awaits CI signing/publish lanes + Attestor mirror contract; not a development blocker. | DevOps Guild · Security Guild | Wire CI/release jobs to publish signed mirror/offline provenance artefacts for advisory chunks. |
|
||||
|
||||
## Action Tracker
|
||||
| Focus | Action | Owner(s) | Due | Status |
|
||||
@@ -49,6 +49,7 @@
|
||||
| 2025-11-23 | Implemented deterministic chunk cache transparency headers (key hash, hit, ttl) in WebService; CONCELIER-CACHE-22-001 set to DONE. | Concelier Platform |
|
||||
| 2025-11-23 | Split mirror work: 23-001-DEV remains here (schema/handlers/tests); release publishing moved to DEVOPS-MIRROR-23-001-REL (DevOps sprint, not a dev blocker). | Project Mgmt |
|
||||
| 2025-11-23 | Documented dev mirror/export path and sample generator at `docs/modules/concelier/mirror-export.md`; CONCELIER-MIRROR-23-001-DEV marked DONE. | Implementer |
|
||||
| 2025-11-23 | Routed release publishing to ops sprint: DEVOPS-MIRROR-23-001-REL now depends on DEPLOY-MIRROR-23-001 (SPRINT_501_ops_deployment_i); dev sprint stays unblocked. | Project Mgmt |
|
||||
|
||||
## Decisions & Risks
|
||||
- Keep Concelier aggregation-only; no consensus merges.
|
||||
|
||||
@@ -26,19 +26,19 @@
|
||||
| P2 | PREP-CONCELIER-LNM-21-002-WAITING-ON-FINALIZE | DONE (2025-11-20) | Due 2025-11-21 · Accountable: Concelier Core Guild · Data Science Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Concelier Core Guild · Data Science Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Correlation rules + fixtures published at `docs/modules/concelier/linkset-correlation-21-002.md` with samples under `docs/samples/lnm/`. Downstream linkset builder can proceed. |
|
||||
| 1 | CONCELIER-GRAPH-21-001 | DONE | LNM sample fixtures with scopes/relationships added; observation/linkset query tests passing | Concelier Core Guild · Cartographer Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Extend SBOM normalization so relationships/scopes are stored as raw observation metadata with provenance pointers for graph joins. |
|
||||
| 2 | CONCELIER-GRAPH-21-002 | DONE (2025-11-22) | PREP-CONCELIER-GRAPH-21-002-PLATFORM-EVENTS-S | Concelier Core Guild · Scheduler Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Publish `sbom.observation.updated` events with tenant/context and advisory refs; facts only, no judgments. |
|
||||
| 3 | CONCELIER-GRAPH-24-101 | BLOCKED (CI runner required) | Implementation and tests pending due to local vstest build hang; needs CI/clean runner to compile WebService.Tests and run `AdvisorySummary` contract tests. | 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 | BLOCKED (blocked on 24-101 + CI runner) | Awaiting 24-101 completion and CI to execute batch evidence endpoint tests. | Concelier WebService Guild (`src/Concelier/StellaOps.Concelier.WebService`) | Evidence batch endpoints keyed by component sets with provenance/timestamps; no derived severity. |
|
||||
| 3 | CONCELIER-GRAPH-24-101 | BLOCKED (CI runner required) | DEVOPS-CONCELIER-CI-24-101 (SPRINT_503_ops_devops_i) — needs CI/clean runner + vstest harness to compile WebService.Tests and run `AdvisorySummary` contract tests. | 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 | BLOCKED (blocked on 24-101 + CI runner) | Depends on 24-101 and DEVOPS-CONCELIER-CI-24-101 (SPRINT_503_ops_devops_i) to execute batch evidence endpoint tests in CI. | 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 | 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 | BLOCKED (CI runner required) | Depends on 21-003; local test harness blocked (`invalid test source`). Needs CI/clean runner to remove legacy merge logic and run guardrail tests. | 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 | BLOCKED (blocked on 21-004 + CI runner) | Awaiting 21-004 completion and CI to run event emission tests. | 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-DEV | BLOCKED (blocked on 21-005 + CI runner) | Needs CI/clean runner to build Storage.Mongo and validate shard/index migrations. | 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. |
|
||||
| 11 | CONCELIER-LNM-21-102-DEV | BLOCKED (blocked on 21-101-DEV + CI runner) | Backfill/rollback tooling needs CI to validate migrations and Offline Kit assets. | Concelier Storage Guild · DevOps Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Backfill legacy merged advisories; seed tombstones; provide rollback tooling for Offline Kit. |
|
||||
| 12 | CONCELIER-LNM-21-103-DEV | BLOCKED (blocked on 21-102-DEV + CI runner) | Object store move requires CI to validate bootstrapper/offline seeds. | Concelier Storage Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Move large raw payloads to object storage with deterministic pointers; update bootstrapper/offline seeds; preserve provenance metadata. |
|
||||
| 13 | CONCELIER-LNM-21-201 | BLOCKED (blocked on 21-103 + CI runner) | WebService tests need CI to compile/run; awaiting storage/object-store completion. | Concelier WebService Guild · BE-Base Platform Guild (`src/Concelier/StellaOps.Concelier.WebService`) | `/advisories/observations` filters by alias/purl/source with strict tenant scopes; echoes upstream values + provenance fields only. |
|
||||
| 14 | CONCELIER-LNM-21-202 | BLOCKED (blocked on 21-201 + CI runner) | Await upstream and CI to run `/advisories/linksets` export tests. | Concelier WebService Guild (`src/Concelier/StellaOps.Concelier.WebService`) | `/advisories/linksets`/`export`/`evidence` endpoints surface correlation + conflict payloads and `ERR_AGG_*` mapping; no synthesis/merge. |
|
||||
| 15 | CONCELIER-LNM-21-203 | BLOCKED (blocked on 21-202 + CI runner) | Event publishing tests need CI transport harness. | Concelier WebService Guild · Platform Events Guild (`src/Concelier/StellaOps.Concelier.WebService`) | Publish idempotent NATS/Redis events for new observations/linksets with documented schemas; include tenant + provenance references only. |
|
||||
| 8 | CONCELIER-LNM-21-004 | BLOCKED (CI runner required) | Depends on 21-003; waiting on DEVOPS-CONCELIER-CI-24-101 (SPRINT_503_ops_devops_i) for CI/clean runner + vstest harness to execute guardrail tests. | 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 | BLOCKED (blocked on 21-004 + CI runner) | Awaiting 21-004 completion and DEVOPS-CONCELIER-CI-24-101 to run event emission tests in CI. | 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-DEV | BLOCKED (blocked on 21-005 + CI runner) | Needs DEVOPS-CONCELIER-CI-24-101 to provide CI/clean runner for Storage.Mongo build + shard/index migration validation. | 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. |
|
||||
| 11 | CONCELIER-LNM-21-102-DEV | BLOCKED (blocked on 21-101-DEV + CI runner) | Backfill/rollback tooling waits on DEVOPS-CONCELIER-CI-24-101 CI runner to validate migrations and Offline Kit assets. | Concelier Storage Guild · DevOps Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Backfill legacy merged advisories; seed tombstones; provide rollback tooling for Offline Kit. |
|
||||
| 12 | CONCELIER-LNM-21-103-DEV | BLOCKED (blocked on 21-102-DEV + CI runner) | Requires DEVOPS-CONCELIER-CI-24-101 CI runner to validate object-store bootstrapper/offline seeds. | Concelier Storage Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Move large raw payloads to object storage with deterministic pointers; update bootstrapper/offline seeds; preserve provenance metadata. |
|
||||
| 13 | CONCELIER-LNM-21-201 | BLOCKED (blocked on 21-103 + CI runner) | WebService tests await DEVOPS-CONCELIER-CI-24-101 CI runner after storage/object-store completion. | Concelier WebService Guild · BE-Base Platform Guild (`src/Concelier/StellaOps.Concelier.WebService`) | `/advisories/observations` filters by alias/purl/source with strict tenant scopes; echoes upstream values + provenance fields only. |
|
||||
| 14 | CONCELIER-LNM-21-202 | BLOCKED (blocked on 21-201 + CI runner) | Await upstream and DEVOPS-CONCELIER-CI-24-101 to run `/advisories/linksets` export tests in CI. | Concelier WebService Guild (`src/Concelier/StellaOps.Concelier.WebService`) | `/advisories/linksets`/`export`/`evidence` endpoints surface correlation + conflict payloads and `ERR_AGG_*` mapping; no synthesis/merge. |
|
||||
| 15 | CONCELIER-LNM-21-203 | BLOCKED (blocked on 21-202 + CI runner) | Event publishing tests need CI transport harness from DEVOPS-CONCELIER-CI-24-101. | Concelier WebService Guild · Platform Events Guild (`src/Concelier/StellaOps.Concelier.WebService`) | Publish idempotent NATS/Redis events for new observations/linksets with documented schemas; include tenant + provenance references only. |
|
||||
| 16 | CONCELIER-AIRGAP-56-001..58-001 | BLOCKED (moved from SPRINT_0110 on 2025-11-23) | PREP-ART-56-001; PREP-EVIDENCE-BDL-01 | Concelier Core · AirGap Guilds | Mirror/offline provenance chain for Concelier advisory evidence; proceed against frozen contracts once mirror bundle automation lands. |
|
||||
| 17 | CONCELIER-CONSOLE-23-001..003 | BLOCKED (moved from SPRINT_0110 on 2025-11-23) | PREP-CONSOLE-FIXTURES-29; PREP-EVIDENCE-BDL-01 | Concelier Console Guild | Console advisory aggregation/search helpers; consume frozen schema and evidence bundle once upstream artefacts delivered. |
|
||||
| 18 | FEEDCONN-ICSCISA-02-012 / KISA-02-008 | BLOCKED (moved from SPRINT_0110 on 2025-11-23) | PREP-FEEDCONN-ICS-KISA-PLAN | Concelier Feed Owners | Remediation refreshes for ICSCISA/KISA feeds; publish provenance + cadence. |
|
||||
@@ -49,6 +49,7 @@
|
||||
| 2025-11-23 | Local build of `StellaOps.Concelier.WebService.Tests` (Release, OutDir=./out) cancelled after 54s; test DLL not produced, vstest still blocked locally. Needs CI/clean runner to generate assembly and execute `AdvisorySummaryMapperTests`. | Concelier Core |
|
||||
| 2025-11-23 | Retried WebService.Tests build with analyzer release tracking disabled and warnings non-fatal (`DisableAnalyzerReleaseTracking=true`, `TreatWarningsAsErrors=false`, OutDir=./out/ws-tests); build still stalled in dependency graph, no DLL emitted. CI runner still required to produce test assembly. | Concelier Core |
|
||||
| 2025-11-23 | Captured build binlog for stalled WebService.Tests attempt at `out/ws-tests.binlog` for CI triage. | Concelier Core |
|
||||
| 2025-11-23 | Split CI runner blocker into DEVOPS-CONCELIER-CI-24-101 (SPRINT_503_ops_devops_i); all CI/vstest-related blocks now point to that ops task. | Project Mgmt |
|
||||
| 2025-11-23 | Marked downstream tasks (GRAPH-24-101/28-102, LNM-21-004..203) BLOCKED pending CI/clean runner; local harness cannot compile or run tests (`invalid test source` / hang). Development awaiting CI resources. Split storage/backfill/object-store tasks into DEV (here) vs DEVOPS release items (10b/11b/12b) to avoid dev blockage. | Project Mgmt |
|
||||
| 2025-11-23 | Imported CONCELIER-AIRGAP-56-001..58-001, CONCELIER-CONSOLE-23-001..003, FEEDCONN-ICSCISA-02-012/KISA-02-008 from SPRINT_0110; statuses remain BLOCKED pending mirror/console/feed artefacts. | Project Mgmt |
|
||||
| 2025-11-20 | Wired optional NATS transport for `advisory.observation.updated@1`; background worker dequeues Mongo outbox and publishes to configured stream/subject. | Implementer |
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
| P7 | PREP-CONCELIER-OBS-53-001-DEPENDS-ON-52-001-B | DONE (2025-11-22) | Due 2025-11-21 · Accountable: Concelier Core Guild · Evidence Locker Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Concelier Core Guild · Evidence Locker Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Evidence bundle/timeline linkage requirements documented; unblock evidence locker integration. |
|
||||
| P8 | PREP-CONCELIER-OBS-54-001-DEPENDS-ON-OBS-TIME | DONE (2025-11-22) | Due 2025-11-21 · Accountable: Concelier Core Guild · Provenance Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Concelier Core Guild · Provenance Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Attestation timeline enrichment + DSSE envelope fields recorded in prep note. |
|
||||
| P9 | PREP-CONCELIER-OBS-55-001-DEPENDS-ON-54-001-I | DONE (2025-11-22) | Due 2025-11-21 · Accountable: Concelier Core Guild · DevOps Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Concelier Core Guild · DevOps Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Incident-mode hooks and sealed-mode redaction guidance captured; see prep note. |
|
||||
| 10 | CONCELIER-ORCH-32-001 | BLOCKED (2025-11-22) | Build/restore failures on local runner (missing packages, nullable warnings); awaiting CI/clean runner to validate registry wiring. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Register every advisory connector with orchestrator (metadata, auth scopes, rate policies) for transparent, reproducible scheduling. |
|
||||
| 11 | CONCELIER-ORCH-32-002 | BLOCKED (2025-11-22) | Blocked on 32-001 build validation; needs CI runner. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Adopt orchestrator worker SDK in ingestion loops; emit heartbeats/progress/artifact hashes for deterministic replays. |
|
||||
| 12 | CONCELIER-ORCH-33-001 | BLOCKED (2025-11-22) | Blocked on 32-001/002 build validation; needs CI runner. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Honor orchestrator pause/throttle/retry controls with structured errors and persisted checkpoints. |
|
||||
| 13 | CONCELIER-ORCH-34-001 | BLOCKED (2025-11-22) | Blocked on 32-001/002 build validation; needs CI runner. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Execute orchestrator-driven backfills reusing artifact hashes/signatures, logging provenance, and pushing run metadata to ledger. |
|
||||
| 10 | CONCELIER-ORCH-32-001 | BLOCKED (2025-11-22) | DEVOPS-CONCELIER-CI-24-101 (SPRINT_503_ops_devops_i) — build/restore fails locally; needs CI/clean runner to validate registry wiring. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Register every advisory connector with orchestrator (metadata, auth scopes, rate policies) for transparent, reproducible scheduling. |
|
||||
| 11 | CONCELIER-ORCH-32-002 | BLOCKED (2025-11-22) | Blocked on 32-001 build validation; depends on DEVOPS-CONCELIER-CI-24-101 CI runner. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Adopt orchestrator worker SDK in ingestion loops; emit heartbeats/progress/artifact hashes for deterministic replays. |
|
||||
| 12 | CONCELIER-ORCH-33-001 | BLOCKED (2025-11-22) | Blocked on 32-001/002 build validation; needs DEVOPS-CONCELIER-CI-24-101 CI runner. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Honor orchestrator pause/throttle/retry controls with structured errors and persisted checkpoints. |
|
||||
| 13 | CONCELIER-ORCH-34-001 | BLOCKED (2025-11-22) | Blocked on 32-001/002 build validation; needs DEVOPS-CONCELIER-CI-24-101 CI runner. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Execute orchestrator-driven backfills reusing artifact hashes/signatures, logging provenance, and pushing run metadata to ledger. |
|
||||
| 14 | CONCELIER-POLICY-20-001 | DOING (2025-11-23) | OpenAPI source drafted at `src/Concelier/StellaOps.Concelier.WebService/openapi/concelier-lnm.yaml` (published copy: `docs/api/concelier/concelier-lnm.yaml`); list/search/get endpoints exposed, field coverage still partial (no severity/timeline). | Concelier WebService Guild (`src/Concelier/StellaOps.Concelier.WebService`) | Provide batch advisory lookup APIs for Policy Engine (purl/advisory filters, tenant scopes, explain metadata) so policy joins raw evidence without inferred outcomes. |
|
||||
|
||||
## Execution Log
|
||||
@@ -74,6 +74,7 @@
|
||||
| 2025-11-22 | Marked ORCH-32/33/34 BLOCKED pending CI/clean runner build + restore (local runner stuck on missing packages/nullability). | Concelier Core |
|
||||
| 2025-11-22 | Retried `dotnet restore concelier-webservice.slnf -v minimal` with timeout guard; cancelled at ~25s with `NuGet.targets` reporting "Restore canceled!". No packages downloaded; ORCH-32/33/34 remain blocked until CI/warm cache is available. | Concelier Implementer |
|
||||
| 2025-11-22 | Ran `dotnet restore concelier-webservice.slnf -v diag` (60s timeout); aborted after prolonged spinner, no packages fetched, no new diagnostic log produced. Orchestrator tasks stay blocked pending CI/runner with warm cache. | Concelier Implementer |
|
||||
| 2025-11-23 | Routed ORCH-32/33/34 CI dependency to DEVOPS-CONCELIER-CI-24-101 (SPRINT_503_ops_devops_i); dev sprint waits on ops runner deliverable. | Project Mgmt |
|
||||
|
||||
## Decisions & Risks
|
||||
- Link-Not-Merge and OpenAPI alignment must precede SDK/examples; otherwise downstream clients will drift from canonical facts.
|
||||
|
||||
@@ -91,6 +91,8 @@
|
||||
| 2025-11-23 | Enforced air-gap import idempotency with unique indexes on `Id` and `(bundleId,mirrorGeneration)`; duplicate imports now return 409 `AIRGAP_IMPORT_DUPLICATE`. Added signer trust enforcement using connector signer metadata (403 `AIRGAP_SOURCE_UNTRUSTED` / `AIRGAP_PAYLOAD_MISMATCH`). Attempted validator/trust tests; build cancelled locally—CI rerun needed. | Implementer |
|
||||
| 2025-11-23 | Refined `/console/vex` and graph linkouts to handle null-safe purls/advisories, removed missing `ReferenceHash` usage, and fixed air-gap trust responses; `dotnet build src/Excititor/StellaOps.Excititor.WebService -c Release` now succeeds. | Implementer |
|
||||
| 2025-11-23 | Ran `dotnet test -c Release --filter AirgapImportEndpointTests --logger trx`; both air-gap endpoint tests now PASS (TRX at `src/Excititor/__Tests/StellaOps.Excititor.WebService.Tests/TestResults/airgap.trx`). Marked EXCITITOR-AIRGAP-56-001 DONE. | Implementer |
|
||||
| 2025-11-23 | Ran Core unit test `VexEvidenceChunkServiceTests` (`dotnet test -c Release --filter FullyQualifiedName~VexEvidenceChunkServiceTests --logger trx`); PASS (TRX at `src/Excititor/__Tests/StellaOps.Excititor.Core.UnitTests/TestResults/chunks.trx`). | Implementer |
|
||||
| 2025-11-23 | Ran full Core UnitTests (`dotnet test -c Release --results-directory TestResults --logger trx`); 3 tests executed, all PASS (TRX at `src/Excititor/__Tests/StellaOps.Excititor.Core.UnitTests/TestResults/core-all.trx`). | Implementer |
|
||||
|
||||
## Decisions & Risks
|
||||
- **Decisions**
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
| 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 | DONE (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-DEV | BLOCKED | Waiting on DevOps to assign target paths for Helm/Compose/offline-kit assets; backup/restore runbook review pending | Findings Ledger Guild / `src/Findings/StellaOps.Findings.Ledger` | Provide Helm/Compose manifests, backup/restore guidance, optional Merkle anchor externalization, and offline kit instructions (dev/staging artifacts). |
|
||||
| 3 | LEDGER-29-009-DEV | BLOCKED | DEPLOY-LEDGER-29-009 (SPRINT_501_ops_deployment_i) — waiting on DevOps to assign target paths for Helm/Compose/offline-kit assets; backup/restore runbook review pending | Findings Ledger Guild / `src/Findings/StellaOps.Findings.Ledger` | Provide Helm/Compose manifests, backup/restore guidance, optional Merkle anchor externalization, and offline kit instructions (dev/staging artifacts). |
|
||||
| 4 | LEDGER-34-101 | DONE (2025-11-22) | 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 | DONE (2025-11-22) | 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. |
|
||||
| 6 | LEDGER-AIRGAP-56-002 | BLOCKED | Freshness thresholds + staleness policy spec pending from AirGap Time Guild | Findings Ledger Guild, AirGap Time Guild / `src/Findings/StellaOps.Findings.Ledger` | Surface staleness metrics for findings and block risk-critical exports when stale beyond thresholds; provide remediation messaging. |
|
||||
@@ -61,6 +61,7 @@
|
||||
| 2025-11-22 | LEDGER-29-009 remains BLOCKED: DevOps/Offline kit overlays live outside module working dir; awaiting approved path for Helm/Compose assets and backup runbooks. | Findings Ledger Guild |
|
||||
| 2025-11-22 | Marked AIRGAP-56-002 BLOCKED pending freshness threshold spec; downstream AIRGAP-57/58 remain blocked accordingly. | Findings Ledger Guild |
|
||||
| 2025-11-22 | Added backup/restore and restore-replay guidance to `docs/modules/findings-ledger/deployment.md`; noted placeholder until DevOps assigns manifest paths. | Findings Ledger Guild |
|
||||
| 2025-11-23 | Routed deployment assets to DEPLOY-LEDGER-29-009 (SPRINT_501_ops_deployment_i); LEDGER-29-009-DEV remains blocked until ops task delivers target paths. | Project Mgmt |
|
||||
| 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 |
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
| P13 | PREP-POLICY-ENGINE-40-001-DEPENDS-ON-38-201 | DONE (2025-11-22) | Due 2025-11-22 · Accountable: Policy · Concelier Guild / `src/Policy/StellaOps.Policy.Engine` | Policy · Concelier Guild / `src/Policy/StellaOps.Policy.Engine` | Depends on 38-201. <br><br> Document artefact/deliverable for POLICY-ENGINE-40-001 and publish location so downstream tasks can proceed. |
|
||||
| P14 | PREP-POLICY-ENGINE-40-002-DEPENDS-ON-40-001 | DONE (2025-11-22) | Due 2025-11-22 · Accountable: Policy · Excititor Guild / `src/Policy/StellaOps.Policy.Engine` | Policy · Excititor Guild / `src/Policy/StellaOps.Policy.Engine` | Depends on 40-001. <br><br> Document artefact/deliverable for POLICY-ENGINE-40-002 and publish location so downstream tasks can proceed. |
|
||||
| 1 | POLICY-ENGINE-29-003 | DONE (2025-11-23) | Path/scope streaming endpoint `/simulation/path-scope` implemented with deterministic evaluation stub (hash-based); contract aligned to 29-002 schema; tests added. | Policy · SBOM Service Guild / `src/Policy/StellaOps.Policy.Engine` | Path/scope aware evaluation. |
|
||||
| 2 | POLICY-ENGINE-29-004 | TODO | PREP-POLICY-ENGINE-29-004-DEPENDS-ON-29-003 | Policy · Observability Guild / `src/Policy/StellaOps.Policy.Engine` | Metrics/logging for path-aware eval. |
|
||||
| 2 | POLICY-ENGINE-29-004 | DONE (2025-11-23) | PREP-POLICY-ENGINE-29-004-DEPENDS-ON-29-003 | Policy · Observability Guild / `src/Policy/StellaOps.Policy.Engine` | Metrics/logging for path-aware eval. |
|
||||
| 3 | POLICY-ENGINE-30-001 | TODO | PREP-POLICY-ENGINE-30-001-NEEDS-29-004-OUTPUT | Policy · Cartographer Guild / `src/Policy/StellaOps.Policy.Engine` | Overlay projection contract. |
|
||||
| 4 | POLICY-ENGINE-30-002 | TODO | PREP-POLICY-ENGINE-30-002-DEPENDS-ON-30-001 | Policy · Cartographer Guild / `src/Policy/StellaOps.Policy.Engine` | Simulation bridge. |
|
||||
| 5 | POLICY-ENGINE-30-003 | TODO | PREP-POLICY-ENGINE-30-003-DEPENDS-ON-30-002 | Policy · Scheduler Guild / `src/Policy/StellaOps.Policy.Engine` | Change events. |
|
||||
@@ -58,6 +58,7 @@
|
||||
| 2025-11-23 | POLICY-ENGINE-29-002 streaming simulation contract finalized at `docs/modules/policy/contracts/29-002-streaming-simulation.md`; shifted POLICY-ENGINE-29-003..40-002 from BLOCKED to TODO. | Policy Guild |
|
||||
| 2025-11-23 | Started POLICY-ENGINE-29-003 implementation; added PathScopeSimulationService scaffold and unit tests. | Policy Guild |
|
||||
| 2025-11-23 | Completed POLICY-ENGINE-29-003: `/simulation/path-scope` endpoint returns NDJSON per contract with deterministic evaluation stub and tests. | Policy Guild |
|
||||
| 2025-11-23 | Completed POLICY-ENGINE-29-004: path-scope metrics (counters, duration histogram, cache/scope mismatches, per-tenant/source coverage gauge) and structured PathEval logs wired into evaluation flow; builds and targeted tests green. | Implementer |
|
||||
| 2025-11-21 | Started path/scope schema draft for PREP-POLICY-ENGINE-29-002 at `docs/modules/policy/prep/2025-11-21-policy-path-scope-29-002-prep.md`; waiting on SBOM Service coordinate mapping rules. | Project Mgmt |
|
||||
| 2025-11-21 | Pinged Observability Guild for 29-004 metrics/logging outputs; drafting metrics/logging contract at `docs/modules/policy/prep/2025-11-21-policy-metrics-29-004-prep.md` while awaiting path/scope payloads from 29-003. | Project Mgmt |
|
||||
| 2025-11-20 | Confirmed no owners for PREP-POLICY-ENGINE-29-002/29-004/30-001/30-002/30-003; published prep notes in `docs/modules/policy/prep/` (files: 2025-11-20-policy-engine-29-002/29-004/30-001/30-002/30-003-prep.md); set P0–P4 DONE. | Implementer |
|
||||
@@ -72,6 +73,7 @@
|
||||
|
||||
## Decisions & Risks
|
||||
- Downstream implementations must conform to `docs/modules/policy/contracts/29-002-streaming-simulation.md`; any schemaVersion change must be logged here and in affected sprints.
|
||||
- Path-scope metrics/logs implemented; future overlays should reuse the same metric names/tags and log fields to avoid cardinality drift.
|
||||
|
||||
## Next Checkpoints
|
||||
- Kick off POLICY-ENGINE-29-003 implementation using frozen path/scope schema and metrics contracts (week of 2025-11-21).
|
||||
|
||||
@@ -27,15 +27,15 @@
|
||||
| 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. |
|
||||
| 4 | SCANNER-ANALYZERS-JAVA-21-005 | BLOCKED (2025-11-17) | PREP-SCANNER-ANALYZERS-JAVA-21-005-TESTS-BLOC; DEVOPS-SCANNER-CI-11-001 (SPRINT_503_ops_devops_i) for CI runner/binlogs. | 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. |
|
||||
| 5 | SCANNER-ANALYZERS-JAVA-21-006 | TODO | Needs outputs from 21-005. | Java Analyzer Guild | JNI/native hint scanner detecting native methods, System.load/Library literals, bundled native libs, Graal JNI configs; emit `jni-load` edges. |
|
||||
| 6 | SCANNER-ANALYZERS-JAVA-21-007 | TODO | After 21-006; align manifest parsing with resolver. | Java Analyzer Guild | Signature and manifest metadata collector capturing JAR signature structure, signers, and manifest loader attributes (Main-Class, Agent-Class, Start-Class, Class-Path). |
|
||||
| 7 | SCANNER-ANALYZERS-JAVA-21-008 | BLOCKED (2025-10-27) | PREP-SCANNER-ANALYZERS-JAVA-21-008-WAITING-ON | Java Analyzer Guild | Implement resolver + AOC writer emitting entrypoints, components, and edges (jpms, cp, spi, reflect, jni) with reason codes and confidence. |
|
||||
| 7 | SCANNER-ANALYZERS-JAVA-21-008 | BLOCKED (2025-10-27) | PREP-SCANNER-ANALYZERS-JAVA-21-008-WAITING-ON; DEVOPS-SCANNER-CI-11-001 for CI runner/restore logs. | Java Analyzer Guild | Implement resolver + AOC writer emitting entrypoints, components, and edges (jpms, cp, spi, reflect, jni) with reason codes and confidence. |
|
||||
| 8 | SCANNER-ANALYZERS-JAVA-21-009 | TODO | Unblock when 21-008 lands; prepare fixtures in parallel where safe. | Java Analyzer Guild · QA Guild | Comprehensive fixtures (modular app, boot fat jar, war, ear, MR-jar, jlink image, JNI, reflection heavy, signed jar, microprofile) with golden outputs and perf benchmarks. |
|
||||
| 9 | SCANNER-ANALYZERS-JAVA-21-010 | TODO | After 21-009; requires runtime capture design. | Java Analyzer Guild · Signals Guild | Optional runtime ingestion via Java agent + JFR reader capturing class load, ServiceLoader, System.load events with path scrubbing; append-only runtime edges (`runtime-class`/`runtime-spi`/`runtime-load`). |
|
||||
| 10 | SCANNER-ANALYZERS-JAVA-21-011 | TODO | Depends on 21-010; finalize DI/manifest registration and docs. | Java Analyzer Guild | Package analyzer as restart-time plug-in, update Offline Kit docs, add CLI/worker hooks for Java inspection commands. |
|
||||
| 10b | DEVOPS-SCANNER-JAVA-21-011-REL | BLOCKED (DevOps release-only) | Depends on 10 dev; add CI/release packaging/signing for Java analyzer plug-in + Offline Kit docs. | DevOps Guild | Package/sign Java analyzer plug-in, publish to Offline Kit/CLI release pipelines. |
|
||||
| 11 | SCANNER-ANALYZERS-LANG-11-001 | BLOCKED (2025-11-17) | PREP-SCANNER-ANALYZERS-LANG-11-001-DOTNET-TES | StellaOps.Scanner EPDR Guild · Language Analyzer Guild | Entrypoint resolver mapping project/publish artifacts to entrypoint identities (assembly name, MVID, TFM, RID) and environment profiles; output normalized `entrypoints[]` with deterministic IDs. |
|
||||
| 11 | SCANNER-ANALYZERS-LANG-11-001 | BLOCKED (2025-11-17) | PREP-SCANNER-ANALYZERS-LANG-11-001-DOTNET-TES; DEVOPS-SCANNER-CI-11-001 for clean runner + binlogs/TRX. | StellaOps.Scanner EPDR Guild · Language Analyzer Guild | Entrypoint resolver mapping project/publish artifacts to entrypoint identities (assembly name, MVID, TFM, RID) and environment profiles; output normalized `entrypoints[]` with deterministic IDs. |
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
@@ -54,6 +54,7 @@
|
||||
| 2025-11-17 | Reviewed Deno analyzer scope; runtime evidence hook contract and policy-signal keys not defined in docs or code. Marked DENO-26-009/010/011 as BLOCKED pending approved trace/signal schema shared with Surface/Signals. | Implementer |
|
||||
| 2025-11-17 | SCANNER-ANALYZERS-JAVA-21-005: Added JNI/native hint scanning (native libs, Graal jni-config, System.load/Library strings) with component metadata + evidence; targeted tests added. Test run aborted ~80s in due to concurrent repo-wide builds; rerun on clean runner. | Java Analyzer Guild |
|
||||
| 2025-11-17 | Authored `docs/modules/scanner/design/deno-runtime-signals.md` defining NDJSON runtime trace + policy signal keys; unblocked DENO-26-009/010/011 back to TODO. | Implementer |
|
||||
| 2025-11-23 | Pointed Java/Lang analyzer blocks to DEVOPS-SCANNER-CI-11-001 (SPRINT_503_ops_devops_i) to obtain CI runner/binlogs for restore/test hangs. | Project Mgmt |
|
||||
| 2025-11-17 | Implemented Deno runtime NDJSON serializer + metadata (module/permission counts, remote origins, npm/wasm/dynamic import counts) with deterministic ordering and hash; added regression tests for serializer, path hashing, recorder ordering, and policy signal emission. Loader/require shim still pending. | Implementer |
|
||||
| 2025-11-17 | Deno runtime tests passing: `dotnet test src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Deno.Tests/StellaOps.Scanner.Analyzers.Lang.Deno.Tests.csproj --no-restore`. | Implementer |
|
||||
| 2025-11-17 | DenoLanguageAnalyzer now ingests `deno-runtime.ndjson` if present, computes metadata/hash, stores runtime payload in AnalysisStore, and emits policy signals; added runtime probe parser + tests. Loader/require shim that generates the trace remains to be built. | Implementer |
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
| P3 | PREP-BUILD-INFRA-SBOM-SERVICE-GUILD-BLOCKED-M | DONE (2025-11-22) | Due 2025-11-22 · Accountable: Planning | Planning | BLOCKED (multiple restore attempts still hang/fail; need vetted feed/cache). <br><br> Document artefact/deliverable for Build/Infra · SBOM Service Guild and publish location so downstream tasks can proceed. Prep artefact: `docs/modules/sbomservice/prep/2025-11-20-build-infra-prep.md`. |
|
||||
| 1 | SBOM-AIAI-31-001 | DONE | Implemented `/sbom/paths` with env/blast-radius/runtime flags + cursor paging and `/sbom/versions` timeline; in-memory deterministic seed until storage wired. | SBOM Service Guild (src/SbomService/StellaOps.SbomService) | Provide path and version timeline endpoints optimised for Advisory AI. |
|
||||
| 2 | SBOM-AIAI-31-002 | DONE | Metrics + cache-hit tagging implemented; Grafana starter dashboard added; build/test completed locally. | SBOM Service Guild; Observability Guild | Instrument metrics for path/timeline queries and surface dashboards. |
|
||||
| 3 | SBOM-CONSOLE-23-001 | BLOCKED | PREP-SBOM-CONSOLE-23-001-BUILD-TEST-FAILING-D | SBOM Service Guild; Cartographer Guild | Provide Console-focused SBOM catalog API. |
|
||||
| 4 | SBOM-CONSOLE-23-002 | BLOCKED | Stub implemented; blocked on storage wiring and console schema approval. | SBOM Service Guild | Deliver component lookup endpoints for search and overlays. |
|
||||
| 3 | SBOM-CONSOLE-23-001 | BLOCKED | DEVOPS-SBOM-23-001 (SPRINT_503_ops_devops_i) — needs vetted offline feed + CI proof to run restore/tests. | SBOM Service Guild; Cartographer Guild | Provide Console-focused SBOM catalog API. |
|
||||
| 4 | SBOM-CONSOLE-23-002 | BLOCKED | Stub implemented; awaiting DEVOPS-SBOM-23-001 feed + console schema approval before storage wiring. | SBOM Service Guild | Deliver component lookup endpoints for search and overlays. |
|
||||
| 5 | SBOM-ORCH-32-001 | TODO | Register SBOM ingest/index sources; embed worker SDK; emit artifact hashes and job metadata. | SBOM Service Guild | Register SBOM ingest/index sources with orchestrator. |
|
||||
| 6 | SBOM-ORCH-33-001 | TODO | Depends on SBOM-ORCH-32-001; report backpressure metrics, honor pause/throttle signals, classify sbom job errors. | SBOM Service Guild | Report backpressure metrics and handle orchestrator control signals. |
|
||||
| 7 | SBOM-ORCH-34-001 | TODO | Depends on SBOM-ORCH-33-001; implement orchestrator backfill and watermark reconciliation for idempotent artifact reuse. | SBOM Service Guild | Implement orchestrator backfill + watermark reconciliation. |
|
||||
| 8 | SBOM-SERVICE-21-001 | DONE (2025-11-23) | WAF aligned; projection tests pass with fixture-backed in-memory repo; duplicate test PackageReferences removed. | SBOM Service Guild; Cartographer Guild | Projection read API (`/sboms/{snapshotId}/projection`) validated with hash output; ready to proceed to storage-backed wiring/events. |
|
||||
| 9 | SBOM-SERVICE-21-002 | TODO | Depends on SBOM-SERVICE-21-001; emit `sbom.version.created` change events and add replay/backfill tooling. | SBOM Service Guild; Scheduler Guild | Emit change events carrying digest/version metadata for Graph Indexer builds. |
|
||||
| 10 | SBOM-SERVICE-21-003 | TODO | Depends on SBOM-SERVICE-21-002; entrypoint/service node management API feeding Cartographer path relevance with deterministic defaults. | SBOM Service Guild | Provide entrypoint/service node management API. |
|
||||
| 9 | SBOM-SERVICE-21-002 | DONE (2025-11-23) | Emits `sbom.version.created` change events via in-memory publisher; internal `/internal/sbom/events` + backfill endpoint wired; component lookup cursor fixed. | SBOM Service Guild; Scheduler Guild | Emit change events carrying digest/version metadata for Graph Indexer builds. |
|
||||
| 10 | SBOM-SERVICE-21-003 | DONE (2025-11-23) | Depends on SBOM-SERVICE-21-002; entrypoint/service node API delivered (`GET/POST /entrypoints` with tenant guard, deterministic ordering, in-memory seed). | SBOM Service Guild | Provide entrypoint/service node management API. |
|
||||
| 11 | SBOM-SERVICE-21-004 | TODO | Depends on SBOM-SERVICE-21-003; wire metrics (`sbom_projection_seconds`, `sbom_projection_size`), traces, tenant-annotated logs; set backlog alerts. | SBOM Service Guild; Observability Guild | Wire observability for SBOM projections. |
|
||||
| 12 | SBOM-SERVICE-23-001 | TODO | Depends on SBOM-SERVICE-21-004; extend projections with asset metadata (criticality, owner, environment, exposure flags); update schema docs. | SBOM Service Guild; Policy Guild | Extend projections to include asset metadata. |
|
||||
| 13 | SBOM-SERVICE-23-002 | TODO | Depends on SBOM-SERVICE-23-001; emit `sbom.asset.updated` events with idempotent payloads; document envelopes. | SBOM Service Guild; Platform Events Guild | Emit asset metadata change events. |
|
||||
@@ -51,6 +51,9 @@
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-23 | Implemented `sbom.version.created` events (in-memory publisher + `/internal/sbom/events` + backfill); fixed component lookup pagination cursor; SbomService tests now passing (SbomEvent/Sbom/Projection suites). SBOM-SERVICE-21-002 marked DONE. | SBOM Service |
|
||||
| 2025-11-23 | Delivered entrypoint/service node API (`GET/POST /entrypoints` with tenant guard, deterministic ordering, in-memory seed). SBOM-SERVICE-21-003 marked DONE. | SBOM Service |
|
||||
| 2025-11-23 | Split build/feed blocker into DEVOPS-SBOM-23-001 (SPRINT_503_ops_devops_i); SBOM-CONSOLE-23-001/002 remain BLOCKED pending ops feed + CI proof. | Project Mgmt |
|
||||
| 2025-11-23 | ProjectionEndpointTests now pass (400/200 responses); WAF configured with fixture path + in-memory component repo; duplicate test PackageReferences removed. SBOM-SERVICE-21-001 marked DONE. | SBOM Service |
|
||||
| 2025-11-23 | Added Mongo fallback to in-memory component lookup to keep tests/offline runs alive; WebApplicationFactory still returns HTTP 500 for projection endpoints (manual curl against `dotnet run` returns 400/200). Investigation pending; SBOM-SERVICE-21-001 remains DOING. | SBOM Service |
|
||||
| 2025-11-23 | Fixed test package references (`FluentAssertions`, `Microsoft.AspNetCore.Mvc.Testing`, xUnit) and attempted `dotnet test --filter ProjectionEndpointTests`; build runs but projection endpoint responses returned HTTP 500 instead of expected 400/200, leaving SBOM-SERVICE-21-001 in DOING pending investigation. | SBOM Service |
|
||||
@@ -96,6 +99,8 @@
|
||||
## Decisions & Risks
|
||||
- LNM v1 fixtures staged (2025-11-22) and approved; hash recorded in `docs/modules/sbomservice/fixtures/lnm-v1/SHA256SUMS`. SBOM-SERVICE-21-001 DONE (2025-11-23); 21-002..004 remain TODO and now unblocked.
|
||||
- Projection endpoint validated (400 without tenant, 200 with fixture data) via WebApplicationFactory; WAF configured with fixture path + in-memory component repo fallback.
|
||||
- `sbom.version.created` now emitted via in-memory publisher with `/internal/sbom/events` + backfill endpoint; production outbox/queue wiring still required before release.
|
||||
- Component lookup pagination now returns deterministic `nextCursor` for seeded data (fixed null cursor bug).
|
||||
- Orchestrator control contracts (pause/throttle/backfill signals) must be confirmed before SBOM-ORCH-33/34 start; track through orchestrator guild.
|
||||
- Keep `docs/modules/sbomservice/architecture.md` aligned with schema/event decisions made during implementation.
|
||||
- Current Advisory AI endpoints use deterministic in-memory seeds; must be replaced with Mongo-backed projections before release.
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | ZASTAVA-REACH-201-001 | TODO | Need runtime symbol sampling design; align with GAP-ZAS-002 | Zastava Observer Guild | Implement runtime symbol sampling in `StellaOps.Zastava.Observer` (EntryTrace-aware shell AST + build-id capture) and stream ND-JSON batches to Signals `/runtime-facts`, including CAS pointers for traces. Update runbook + config references. |
|
||||
| 2 | SCAN-REACH-201-002 | BLOCKED | Await runtime/static union schema (SymbolID + CAS layout) | Scanner Worker Guild | Ship language-aware static lifters (JVM, .NET/Roslyn+IL, Go SSA, Node/Deno TS AST, Rust MIR, Swift SIL, shell/binary analyzers) in Scanner Worker; emit canonical SymbolIDs, CAS-stored graphs, and attach reachability tags to SBOM components. |
|
||||
| 3 | SIGNALS-REACH-201-003 | BLOCKED | Runtime/static union schema not published | Signals Guild | Extend Signals ingestion to accept the new multi-language graphs + runtime facts, normalize into `reachability_graphs` CAS layout, and expose retrieval APIs for Policy/CLI. |
|
||||
| 4 | SIGNALS-REACH-201-004 | BLOCKED | Depends on 201-003 schema | Signals Guild · Policy Guild | Build the reachability scoring engine (state/score/confidence), wire Redis caches + `signals.fact.updated` events, and integrate reachability weights defined in `docs/11_DATA_SCHEMAS.md`. |
|
||||
| 5 | REPLAY-REACH-201-005 | BLOCKED | Needs finalized graph payload shape | BE-Base Platform Guild | Update `StellaOps.Replay.Core` manifest schema + bundle writer so replay packs capture reachability graphs, runtime traces, analyzer versions, and evidence hashes; document new CAS namespace. |
|
||||
| 2 | SCAN-REACH-201-002 | TODO | Schema published: `docs/reachability/runtime-static-union-schema.md` (v0.1). Implement emitters against CAS layout. | Scanner Worker Guild | Ship language-aware static lifters (JVM, .NET/Roslyn+IL, Go SSA, Node/Deno TS AST, Rust MIR, Swift SIL, shell/binary analyzers) in Scanner Worker; emit canonical SymbolIDs, CAS-stored graphs, and attach reachability tags to SBOM components. |
|
||||
| 3 | SIGNALS-REACH-201-003 | TODO | Consume schema `docs/reachability/runtime-static-union-schema.md`; wire ingestion + CAS storage. | Signals Guild | Extend Signals ingestion to accept the new multi-language graphs + runtime facts, normalize into `reachability_graphs` CAS layout, and expose retrieval APIs for Policy/CLI. |
|
||||
| 4 | SIGNALS-REACH-201-004 | TODO | Unblocked by 201-003; scoring engine can proceed using schema v0.1. | Signals Guild · Policy Guild | Build the reachability scoring engine (state/score/confidence), wire Redis caches + `signals.fact.updated` events, and integrate reachability weights defined in `docs/11_DATA_SCHEMAS.md`. |
|
||||
| 5 | REPLAY-REACH-201-005 | TODO | Schema v0.1 available; update replay manifest/bundle to include CAS namespace + hashes per spec. | BE-Base Platform Guild | Update `StellaOps.Replay.Core` manifest schema + bundle writer so replay packs capture reachability graphs, runtime traces, analyzer versions, and evidence hashes; document new CAS namespace. |
|
||||
| 6 | DOCS-REACH-201-006 | TODO | Requires outputs from 1–5 | Docs Guild | Author the reachability doc set (`docs/signals/reachability.md`, `callgraph-formats.md`, `runtime-facts.md`, CLI/UI appendices) plus update Zastava + Replay guides with the new evidence and operator workflows. |
|
||||
| 7 | QA-REACH-201-007 | TODO | Move fixtures + create evaluator harness | QA Guild | Integrate `reachbench-2025-expanded` fixture pack under `tests/reachability/fixtures/`, add evaluator harness tests that validate reachable vs unreachable cases, and wire CI guidance for deterministic runs. |
|
||||
| 8 | GAP-SCAN-001 | TODO | Align with task 2; binary symbolizers | Scanner Worker Guild | Implement binary/language symbolizers that emit `richgraph-v1` payloads with canonical SymbolIDs and `code_id` anchors, persist graphs to CAS via `StellaOps.Scanner.Reachability`, and refresh analyzer docs/fixtures. |
|
||||
@@ -36,11 +36,11 @@
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-18 | Normalised sprint to standard template; renamed from SPRINT_400_runtime_facts_static_callgraph_union.md. | Docs |
|
||||
| 2025-11-19 | Marked tasks 201-002..201-005 BLOCKED pending runtime/static union schema (SymbolID+CAS layout); no implementation until schema is published. | Implementer |
|
||||
| 2025-11-23 | Published runtime/static union schema v0.1 at `docs/reachability/runtime-static-union-schema.md`; moved 201-002..201-005 to TODO. | Project Mgmt |
|
||||
| 2025-11-20 | Added tasks 201-008 (Unknowns Registry) and 201-009 (purl + symbol-digest edge merge); awaiting schema freeze. | Planning |
|
||||
|
||||
## Decisions & Risks
|
||||
- Runtime/static schema alignment pending (SymbolID, CAS layout, overlay tags); blocks ingestion and scoring finalization.
|
||||
- Schema v0.1 published at `docs/reachability/runtime-static-union-schema.md` (2025-11-23); treat as add-only. Breaking changes require version bump and mirrored updates in Signals/Replay.
|
||||
- reachbench fixtures not yet relocated into tests tree; QA task 201-007 must complete before CI enablement.
|
||||
- Offline posture: ensure reachability pipelines avoid external downloads; rely on sealed/mock bundles.
|
||||
- Unknowns Registry schema and API must align with Signals scoring before 201-008 can start; derive `unknowns_pressure` math from policy team.
|
||||
|
||||
@@ -4,21 +4,50 @@ Active items only. Completed/historic work now resides in docs/implplan/archived
|
||||
|
||||
[Ops & Offline] 190.A) Ops Deployment.I
|
||||
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 Ops Deployment (phase I).
|
||||
Task ID | State | Task description | Owners (Source)
|
||||
--- | --- | --- | ---
|
||||
COMPOSE-44-001 | TODO | Author `docker-compose.yml`, `.env.example`, and `quickstart.sh` with all core services + dependencies (postgres, redis, object-store, queue, otel). | Deployment Guild, DevEx Guild (ops/deployment)
|
||||
COMPOSE-44-002 | TODO | Implement `backup.sh` and `reset.sh` scripts with safety prompts and documentation. Dependencies: COMPOSE-44-001. | Deployment Guild (ops/deployment)
|
||||
COMPOSE-44-003 | TODO | Package seed data container and onboarding wizard toggle (`QUICKSTART_MODE`), ensuring default creds randomized on first run. Dependencies: COMPOSE-44-002. | Deployment Guild, Docs Guild (ops/deployment)
|
||||
DEPLOY-AIAI-31-001 | TODO | Provide Helm/Compose manifests, GPU toggle, scaling/runbook, and offline kit instructions for Advisory AI service + inference container. | Deployment Guild, Advisory AI Guild (ops/deployment)
|
||||
DEPLOY-AIRGAP-46-001 | TODO | Provide instructions and scripts (`load.sh`) for importing air-gap bundle into private registry; update Offline Kit guide. | Deployment Guild, Offline Kit Guild (ops/deployment)
|
||||
DEPLOY-CLI-41-001 | TODO | Package CLI release artifacts (tarballs per OS/arch, checksums, signatures, completions, container image) and publish distribution docs. | Deployment Guild, DevEx/CLI Guild (ops/deployment)
|
||||
DEPLOY-COMPOSE-44-001 | TODO | Finalize Quickstart scripts (`quickstart.sh`, `backup.sh`, `reset.sh`), seed data container, and publish README with imposed rule reminder. | Deployment Guild (ops/deployment)
|
||||
DEPLOY-EXPORT-35-001 | BLOCKED (2025-10-29) | Package exporter service/worker Helm overlays (download-only), document rollout/rollback, and integrate signing KMS secrets. | Deployment Guild, Exporter Service Guild (ops/deployment)
|
||||
DEPLOY-EXPORT-36-001 | TODO | Document OCI/object storage distribution workflows, registry credential automation, and monitoring hooks for exports. Dependencies: DEPLOY-EXPORT-35-001. | Deployment Guild, Exporter Service Guild (ops/deployment)
|
||||
DEPLOY-HELM-45-001 | TODO | Publish Helm install guide and sample values for prod/airgap; integrate with docs site build. | Deployment Guild (ops/deployment)
|
||||
DEPLOY-NOTIFY-38-001 | BLOCKED (2025-10-29) | Package notifier API/worker Helm overlays (email/chat/webhook), secrets templates, rollout guide. | Deployment Guild, DevOps Guild (ops/deployment)
|
||||
DEPLOY-ORCH-34-001 | TODO | Provide orchestrator Helm/Compose manifests, scaling defaults, secret templates, offline kit instructions, and GA rollout/rollback playbook. | Deployment Guild, Orchestrator Service Guild (ops/deployment)
|
||||
DEPLOY-PACKS-42-001 | TODO | Provide deployment manifests for packs-registry and task-runner services, including Helm/Compose overlays, scaling defaults, and secret templates. | Deployment Guild, Packs Registry Guild (ops/deployment)
|
||||
DEPLOY-PACKS-43-001 | TODO | Ship remote Task Runner worker profiles, object storage bootstrap, approval workflow integration, and Offline Kit packaging instructions. Dependencies: DEPLOY-PACKS-42-001. | Deployment Guild, Task Runner Guild (ops/deployment)
|
||||
DEPLOY-POLICY-27-001 | TODO | Produce Helm/Compose overlays for Policy Registry + simulation workers, including Mongo migrations, object storage buckets, signing key secrets, and tenancy defaults. | Deployment Guild, Policy Registry Guild (ops/deployment)
|
||||
|
||||
## Topic & Scope
|
||||
- Ship deployable artefacts (Helm/Compose/offline kits) across modules without leaving deployment work inside dev sprints.
|
||||
- Provide signed mirror/export bundles and backup/restore guidance for regulated environments.
|
||||
|
||||
## Dependencies & Concurrency
|
||||
- Upstream module artefacts must exist before packaging; see task-level dependencies (e.g., MIRROR-KEY-56-002-CI, LEDGER-29-009-DEV).
|
||||
- Can run in parallel to module development; outputs live under `ops/deployment`.
|
||||
|
||||
## Documentation Prerequisites
|
||||
- docs/modules/devops/architecture.md
|
||||
- docs/modules/ci/architecture.md
|
||||
- docs/airgap/** (for mirror/import tasks)
|
||||
|
||||
## Delivery Tracker
|
||||
| Task ID | State | Task description | Owners (Source) |
|
||||
| --- | --- | --- | --- |
|
||||
| COMPOSE-44-001 | TODO | Author `docker-compose.yml`, `.env.example`, and `quickstart.sh` with all core services + dependencies (postgres, redis, object-store, queue, otel). | Deployment Guild, DevEx Guild (ops/deployment) |
|
||||
| COMPOSE-44-002 | TODO | Implement `backup.sh` and `reset.sh` scripts with safety prompts and documentation. Dependencies: COMPOSE-44-001. | Deployment Guild (ops/deployment) |
|
||||
| COMPOSE-44-003 | TODO | Package seed data container and onboarding wizard toggle (`QUICKSTART_MODE`), ensuring default creds randomized on first run. Dependencies: COMPOSE-44-002. | Deployment Guild, Docs Guild (ops/deployment) |
|
||||
| DEPLOY-AIAI-31-001 | TODO | Provide Helm/Compose manifests, GPU toggle, scaling/runbook, and offline kit instructions for Advisory AI service + inference container. | Deployment Guild, Advisory AI Guild (ops/deployment) |
|
||||
| DEPLOY-AIRGAP-46-001 | TODO | Provide instructions and scripts (`load.sh`) for importing air-gap bundle into private registry; update Offline Kit guide. | Deployment Guild, Offline Kit Guild (ops/deployment) |
|
||||
| DEPLOY-CLI-41-001 | TODO | Package CLI release artifacts (tarballs per OS/arch, checksums, signatures, completions, container image) and publish distribution docs. | Deployment Guild, DevEx/CLI Guild (ops/deployment) |
|
||||
| DEPLOY-COMPOSE-44-001 | TODO | Finalize Quickstart scripts (`quickstart.sh`, `backup.sh`, `reset.sh`), seed data container, and publish README with imposed rule reminder. | Deployment Guild (ops/deployment) |
|
||||
| DEPLOY-EXPORT-35-001 | BLOCKED (2025-10-29) | Package exporter service/worker Helm overlays (download-only), document rollout/rollback, and integrate signing KMS secrets. | Deployment Guild, Exporter Service Guild (ops/deployment) |
|
||||
| DEPLOY-EXPORT-36-001 | TODO | Document OCI/object storage distribution workflows, registry credential automation, and monitoring hooks for exports. Dependencies: DEPLOY-EXPORT-35-001. | Deployment Guild, Exporter Service Guild (ops/deployment) |
|
||||
| DEPLOY-HELM-45-001 | TODO | Publish Helm install guide and sample values for prod/airgap; integrate with docs site build. | Deployment Guild (ops/deployment) |
|
||||
| DEPLOY-NOTIFY-38-001 | BLOCKED (2025-10-29) | Package notifier API/worker Helm overlays (email/chat/webhook), secrets templates, rollout guide. | Deployment Guild, DevOps Guild (ops/deployment) |
|
||||
| DEPLOY-ORCH-34-001 | TODO | Provide orchestrator Helm/Compose manifests, scaling defaults, secret templates, offline kit instructions, and GA rollout/rollback playbook. | Deployment Guild, Orchestrator Service Guild (ops/deployment) |
|
||||
| DEPLOY-PACKS-42-001 | TODO | Provide deployment manifests for packs-registry and task-runner services, including Helm/Compose overlays, scaling defaults, and secret templates. | Deployment Guild, Packs Registry Guild (ops/deployment) |
|
||||
| DEPLOY-PACKS-43-001 | TODO | Ship remote Task Runner worker profiles, object storage bootstrap, approval workflow integration, and Offline Kit packaging instructions. Dependencies: DEPLOY-PACKS-42-001. | Deployment Guild, Task Runner Guild (ops/deployment) |
|
||||
| DEPLOY-POLICY-27-001 | TODO | Produce Helm/Compose overlays for Policy Registry + simulation workers, including Mongo migrations, object storage buckets, signing key secrets, and tenancy defaults. | Deployment Guild, Policy Registry Guild (ops/deployment) |
|
||||
| DEPLOY-MIRROR-23-001 | BLOCKED (2025-11-23) | Publish signed mirror/offline artefacts; needs `MIRROR_SIGN_KEY_B64` wired in CI (from MIRROR-KEY-56-002-CI) and Attestor mirror contract. | Deployment Guild, Security Guild (ops/deployment) |
|
||||
| DEPLOY-LEDGER-29-009 | BLOCKED (2025-11-23) | Provide Helm/Compose/offline-kit manifests + backup/restore runbook paths for Findings Ledger; waits on DevOps-approved target directories before committing artefacts. | Deployment Guild, Findings Ledger Guild, DevOps Guild (ops/deployment) |
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-23 | Added DEPLOY-MIRROR-23-001 and DEPLOY-LEDGER-29-009; normalised sprint with template sections. | Project Mgmt |
|
||||
|
||||
## Decisions & Risks
|
||||
- Mirror signing secret (`MIRROR_SIGN_KEY_B64`) and Attestor contract are outstanding; DEPLOY-MIRROR-23-001 remains blocked until provided.
|
||||
- Findings Ledger deployment assets cannot be committed until DevOps assigns target directories to keep module boundaries clean.
|
||||
|
||||
## Next Checkpoints
|
||||
- 2025-11-25: Review mirror signing secret readiness with Security/DevOps.
|
||||
- 2025-11-26: Findings Ledger deployment path/backup runbook review with DevOps Guild.
|
||||
|
||||
@@ -4,29 +4,57 @@ Active items only. Completed/historic work now resides in docs/implplan/archived
|
||||
|
||||
[Ops & Offline] 190.B) Ops Devops.I
|
||||
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 Ops Devops (phase I).
|
||||
Task ID | State | Task description | Owners (Source)
|
||||
--- | --- | --- | ---
|
||||
DEVOPS-AIAI-31-001 | TODO | Stand up CI pipelines, inference monitoring, privacy logging review, and perf dashboards for Advisory AI (summaries/conflicts/remediation). | DevOps Guild, Advisory AI Guild (ops/devops)
|
||||
DEVOPS-AIRGAP-56-001 | TODO | Ship deny-all egress policies for Kubernetes (NetworkPolicy/eBPF) and docker-compose firewall rules; provide verification script for sealed mode. | DevOps Guild (ops/devops)
|
||||
DEVOPS-AIRGAP-56-002 | TODO | Provide import tooling for bundle staging: checksum validation, offline object-store loader scripts, removable media guidance. Dependencies: DEVOPS-AIRGAP-56-001. | DevOps Guild, AirGap Importer Guild (ops/devops)
|
||||
DEVOPS-AIRGAP-56-003 | TODO | Build Bootstrap Pack pipeline bundling images/charts, generating checksums, and publishing manifest for offline transfer. Dependencies: DEVOPS-AIRGAP-56-002. | DevOps Guild, Container Distribution Guild (ops/devops)
|
||||
DEVOPS-AIRGAP-57-001 | TODO | Automate Mirror Bundle creation jobs with dual-control approvals, artifact signing, and checksum publication. Dependencies: DEVOPS-AIRGAP-56-003. | DevOps Guild, Mirror Creator Guild (ops/devops)
|
||||
DEVOPS-AIRGAP-57-002 | BLOCKED (2025-11-18) | Waiting on upstream DEVOPS-AIRGAP-57-001 (mirror bundle automation) to provide artifacts/endpoints for sealed-mode CI; no sealed fixtures available to exercise tests. | DevOps Guild, Authority Guild (ops/devops)
|
||||
> 2025-11-07: Harness scaffolded at `ops/devops/sealed-mode-ci/*` (README + runner script); integrate into CI to unblock AUTH-AIRGAP-57-001.
|
||||
> 2025-11-08: `sealed-mode-compose.yml`, `run-sealed-ci.sh`, and `egress_probe.py` committed plus a `sealed-mode-ci` workflow stage that uploads `artifacts/sealed-mode-ci/<commit>/authority-sealed-ci.json`; Authority can now read the sealed evidence feed.
|
||||
> 2025-11-18: DEVOPS-AIRGAP-57-002 set to BLOCKED; mirror bundle automation (57-001) not delivered, so no sealed fixtures/artifacts exist to exercise egress checks.
|
||||
DEVOPS-AIRGAP-58-001 | TODO | Provide local SMTP/syslog container templates and health checks for sealed environments; integrate into Bootstrap Pack. Dependencies: DEVOPS-AIRGAP-57-002. | DevOps Guild, Notifications Guild (ops/devops)
|
||||
DEVOPS-AIRGAP-58-002 | TODO | Ship sealed-mode observability stack (Prometheus/Grafana/Tempo/Loki) pre-configured with offline dashboards and no remote exporters. Dependencies: DEVOPS-AIRGAP-58-001. | DevOps Guild, Observability Guild (ops/devops)
|
||||
DEVOPS-AOC-19-001 | BLOCKED (2025-10-26) | Integrate the AOC Roslyn analyzer and guard tests into CI, failing builds when ingestion projects attempt banned writes. | DevOps Guild, Platform Guild (ops/devops)
|
||||
DEVOPS-AOC-19-002 | BLOCKED (2025-10-26) | Add pipeline stage executing `stella aoc verify --since` against seeded Mongo snapshots for Concelier + Excititor, publishing violation report artefacts. Dependencies: DEVOPS-AOC-19-001. | DevOps Guild (ops/devops)
|
||||
DEVOPS-AOC-19-003 | BLOCKED (2025-10-26) | Enforce unit test coverage thresholds for AOC guard suites and ensure coverage exported to dashboards. Dependencies: DEVOPS-AOC-19-002. | DevOps Guild, QA Guild (ops/devops)
|
||||
DEVOPS-AOC-19-101 | TODO (2025-10-28) | Draft supersedes backfill rollout (freeze window, dry-run steps, rollback) once advisory_raw idempotency index passes staging verification. Dependencies: DEVOPS-AOC-19-003. | DevOps Guild, Concelier Storage Guild (ops/devops)
|
||||
DEVOPS-ATTEST-73-001 | TODO | Provision CI pipelines for attestor service (lint/test/security scan, seed data) and manage secrets for KMS drivers. | DevOps Guild, Attestor Service Guild (ops/devops)
|
||||
DEVOPS-ATTEST-73-002 | TODO | Establish secure storage for signing keys (vault integration, rotation schedule) and audit logging. Dependencies: DEVOPS-ATTEST-73-001. | DevOps Guild, KMS Guild (ops/devops)
|
||||
DEVOPS-ATTEST-74-001 | TODO | Deploy transparency log witness infrastructure and monitoring. Dependencies: DEVOPS-ATTEST-73-002. | DevOps Guild, Transparency Guild (ops/devops)
|
||||
DEVOPS-GRAPH-INDEX-28-010-REL | TODO | Publish signed Helm/Compose/offline bundles for Graph Indexer; depends on GRAPH-INDEX-28-010 dev artefacts. | DevOps Guild, Graph Indexer Guild (ops/devops)
|
||||
DEVOPS-LNM-21-101-REL | TODO | Run/apply shard/index migrations (Concelier LNM) in release pipelines; capture artefacts and rollback scripts. | DevOps Guild, Concelier Storage Guild (ops/devops)
|
||||
DEVOPS-LNM-21-102-REL | TODO | Package/publish LNM backfill/rollback bundles for release/offline kit; depends on 21-102 dev outputs. | DevOps Guild, Concelier Storage Guild (ops/devops)
|
||||
DEVOPS-LNM-21-103-REL | TODO | Publish/rotate object-store seeds and offline bootstraps with provenance hashes; depends on 21-103 dev outputs. | DevOps Guild, Concelier Storage Guild (ops/devops)
|
||||
DEVOPS-STORE-AOC-19-005-REL | BLOCKED | Release/offline-kit packaging for Concelier backfill; waiting on dataset hash + dev rehearsal. | DevOps Guild, Concelier Storage Guild (ops/devops)
|
||||
|
||||
## Topic & Scope
|
||||
- Stand up CI, signing, and offline pipelines that unblock module sprints without embedding DevOps work in dev backlogs.
|
||||
- Provide sealed/airgap bootstrap artefacts and mirrors required by downstream airgap/attestation tasks.
|
||||
- Ensure AOC/guard rails are enforced in CI across ingestion-heavy modules.
|
||||
|
||||
## Dependencies & Concurrency
|
||||
- Upstream artefacts: mirror bundle automation (DEVOPS-AIRGAP-57-001), AOC analyzers, module-specific prep notes referenced per task.
|
||||
- Runs in parallel with module sprints; deliverables are CI/pipeline assets, not code changes inside module working dirs.
|
||||
|
||||
## Documentation Prerequisites
|
||||
- docs/modules/devops/architecture.md
|
||||
- docs/modules/ci/architecture.md
|
||||
- docs/airgap/** (for sealed-mode tasks)
|
||||
|
||||
## Delivery Tracker
|
||||
| Task ID | State | Task description | Owners (Source) |
|
||||
| --- | --- | --- | --- |
|
||||
| DEVOPS-AIAI-31-001 | TODO | Stand up CI pipelines, inference monitoring, privacy logging review, and perf dashboards for Advisory AI (summaries/conflicts/remediation). | DevOps Guild, Advisory AI Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-56-001 | TODO | Ship deny-all egress policies for Kubernetes (NetworkPolicy/eBPF) and docker-compose firewall rules; provide verification script for sealed mode. | DevOps Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-56-002 | TODO | Provide import tooling for bundle staging: checksum validation, offline object-store loader scripts, removable media guidance. Dependencies: DEVOPS-AIRGAP-56-001. | DevOps Guild, AirGap Importer Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-56-003 | TODO | Build Bootstrap Pack pipeline bundling images/charts, generating checksums, and publishing manifest for offline transfer. Dependencies: DEVOPS-AIRGAP-56-002. | DevOps Guild, Container Distribution Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-57-001 | TODO | Automate Mirror Bundle creation jobs with dual-control approvals, artifact signing, and checksum publication. Dependencies: DEVOPS-AIRGAP-56-003. | DevOps Guild, Mirror Creator Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-57-002 | BLOCKED (2025-11-18) | Waiting on upstream DEVOPS-AIRGAP-57-001 (mirror bundle automation) to provide artifacts/endpoints for sealed-mode CI; no sealed fixtures available to exercise tests. | DevOps Guild, Authority Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-58-001 | TODO | Provide local SMTP/syslog container templates and health checks for sealed environments; integrate into Bootstrap Pack. Dependencies: DEVOPS-AIRGAP-57-002. | DevOps Guild, Notifications Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-58-002 | TODO | Ship sealed-mode observability stack (Prometheus/Grafana/Tempo/Loki) pre-configured with offline dashboards and no remote exporters. Dependencies: DEVOPS-AIRGAP-58-001. | DevOps Guild, Observability Guild (ops/devops) |
|
||||
| DEVOPS-AOC-19-001 | BLOCKED (2025-10-26) | Integrate the AOC Roslyn analyzer and guard tests into CI, failing builds when ingestion projects attempt banned writes. | DevOps Guild, Platform Guild (ops/devops) |
|
||||
| DEVOPS-AOC-19-002 | BLOCKED (2025-10-26) | Add pipeline stage executing `stella aoc verify --since` against seeded Mongo snapshots for Concelier + Excititor, publishing violation report artefacts. Dependencies: DEVOPS-AOC-19-001. | DevOps Guild (ops/devops) |
|
||||
| DEVOPS-AOC-19-003 | BLOCKED (2025-10-26) | Enforce unit test coverage thresholds for AOC guard suites and ensure coverage exported to dashboards. Dependencies: DEVOPS-AOC-19-002. | DevOps Guild, QA Guild (ops/devops) |
|
||||
| DEVOPS-AOC-19-101 | TODO (2025-10-28) | Draft supersedes backfill rollout (freeze window, dry-run steps, rollback) once advisory_raw idempotency index passes staging verification. Dependencies: DEVOPS-AOC-19-003. | DevOps Guild, Concelier Storage Guild (ops/devops) |
|
||||
| DEVOPS-ATTEST-73-001 | TODO | Provision CI pipelines for attestor service (lint/test/security scan, seed data) and manage secrets for KMS drivers. | DevOps Guild, Attestor Service Guild (ops/devops) |
|
||||
| DEVOPS-ATTEST-73-002 | TODO | Establish secure storage for signing keys (vault integration, rotation schedule) and audit logging. Dependencies: DEVOPS-ATTEST-73-001. | DevOps Guild, KMS Guild (ops/devops) |
|
||||
| DEVOPS-ATTEST-74-001 | TODO | Deploy transparency log witness infrastructure and monitoring. Dependencies: DEVOPS-ATTEST-73-002. | DevOps Guild, Transparency Guild (ops/devops) |
|
||||
| DEVOPS-GRAPH-INDEX-28-010-REL | TODO | Publish signed Helm/Compose/offline bundles for Graph Indexer; depends on GRAPH-INDEX-28-010 dev artefacts. | DevOps Guild, Graph Indexer Guild (ops/devops) |
|
||||
| DEVOPS-LNM-21-101-REL | TODO | Run/apply shard/index migrations (Concelier LNM) in release pipelines; capture artefacts and rollback scripts. | DevOps Guild, Concelier Storage Guild (ops/devops) |
|
||||
| DEVOPS-LNM-21-102-REL | TODO | Package/publish LNM backfill/rollback bundles for release/offline kit; depends on 21-102 dev outputs. | DevOps Guild, Concelier Storage Guild (ops/devops) |
|
||||
| DEVOPS-LNM-21-103-REL | TODO | Publish/rotate object-store seeds and offline bootstraps with provenance hashes; depends on 21-103 dev outputs. | DevOps Guild, Concelier Storage Guild (ops/devops) |
|
||||
| DEVOPS-STORE-AOC-19-005-REL | BLOCKED | Release/offline-kit packaging for Concelier backfill; waiting on dataset hash + dev rehearsal. | DevOps Guild, Concelier Storage Guild (ops/devops) |
|
||||
| DEVOPS-CONCELIER-CI-24-101 | TODO | Provide clean CI runner + warmed NuGet cache + vstest harness for Concelier WebService & Storage; deliver TRX/binlogs and unblock CONCELIER-GRAPH-24-101/28-102 and LNM-21-004..203. | DevOps Guild, Concelier Core Guild (ops/devops) |
|
||||
| DEVOPS-SCANNER-CI-11-001 | TODO | Supply warmed cache/diag runner for Scanner analyzers (LANG-11-001, JAVA 21-005/008) with binlogs + TRX; unblock restore/test hangs. | DevOps Guild, Scanner EPDR Guild (ops/devops) |
|
||||
| DEVOPS-SBOM-23-001 | TODO | Publish vetted offline NuGet feed + CI recipe for SbomService; prove with `dotnet test` run and share cache hashes; unblock SBOM-CONSOLE-23-001/002. | DevOps Guild, SBOM Service Guild (ops/devops) |
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-23 | Normalised sprint toward template (sections added); added DEVOPS-CONCELIER-CI-24-101, DEVOPS-SCANNER-CI-11-001, DEVOPS-SBOM-23-001 to absorb CI/restore blockers from module sprints. | Project Mgmt |
|
||||
|
||||
## Decisions & Risks
|
||||
- Mirror bundle automation (DEVOPS-AIRGAP-57-001) and AOC guardrails remain gating risks; several downstream tasks inherit these.
|
||||
- New CI-runner tasks must produce reproducible binlogs/TRX and cache hashes to keep offline posture intact.
|
||||
|
||||
## Next Checkpoints
|
||||
- 2025-11-25: CI runner provisioning check for Concelier/Scanner/SBOM cache jobs.
|
||||
- 2025-11-27: Sealed-mode fixture availability review (DEVOPS-AIRGAP-57-002).
|
||||
|
||||
@@ -44,6 +44,8 @@ Operational rules:
|
||||
- `GET /console/sboms` — Console catalog with filters (artifact, license, scope, asset tags), cursor pagination, evaluation metadata, immutable JSON projection for drawer views.
|
||||
- `GET /components/lookup?purl=...` — component neighborhood for global search/Graph overlays; returns caches hints + tenant enforcement.
|
||||
- `POST /entrypoints` / `GET /entrypoints` — manage entrypoint/service node overrides feeding Cartographer relevance; deterministic defaults when unset.
|
||||
- `GET /internal/sbom/events` — internal diagnostics endpoint returning the in-memory event outbox for validation.
|
||||
- `POST /internal/sbom/events/backfill` — replays existing projections into the event stream; deterministic ordering, clock abstraction for tests.
|
||||
|
||||
## 4) Ingestion & orchestrator integration
|
||||
- Ingest sources: Scanner pipeline (preferred) or uploaded SPDX 3.0.1/CycloneDX 1.6 bundles.
|
||||
@@ -54,6 +56,8 @@ Operational rules:
|
||||
- `sbom.version.created` — emitted per new SBOM snapshot; payload: tenant, artifact digest, sbomVersion, projection hash, source bundle hash, import provenance; replay/backfill via outbox with watermark.
|
||||
- `sbom.asset.updated` — emitted when asset metadata changes; idempotent payload keyed by `(tenant, assetId, version)`.
|
||||
- Inventory/resolver feeds — queue/topic delivering `(artifact, purl, version, paths, runtime_flag, scope, nearest_safe_version)` for Vuln Explorer/Findings Ledger.
|
||||
- Current implementation uses an in-memory event store/publisher (with clock abstraction) plus `/internal/sbom/events` + `/internal/sbom/events/backfill` to validate envelopes until the Mongo-backed outbox is wired.
|
||||
- Entrypoint/service node overrides are exposed via `/entrypoints` (tenant-scoped) and should be mirrored into Cartographer relevance jobs when the outbox lands.
|
||||
|
||||
## 6) Determinism & offline posture
|
||||
- Stable ordering for projections and paths; timestamps in UTC ISO-8601; hash inputs canonicalised.
|
||||
|
||||
129
docs/reachability/runtime-static-union-schema.md
Normal file
129
docs/reachability/runtime-static-union-schema.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Runtime + Static Reachability Union Schema (v0.1, 2025-11-23)
|
||||
|
||||
## Goals
|
||||
- Provide a single, deterministic graph shape that merges static lifter output and runtime traces across languages.
|
||||
- Keep SymbolID stable across hosts (path/location independent) so CAS lookups are reproducible and cacheable.
|
||||
- Make outputs offline-friendly: line-delimited JSON, UTF-8, sorted, with explicit content hashes.
|
||||
|
||||
## File layout (CAS)
|
||||
- Namespace root: `reachability_graphs/<analysis_id>/` (analysis_id is caller-supplied UUID or hash).
|
||||
- Files (all NDJSON, UTF-8, newline terminated, sorted as noted):
|
||||
- `nodes.ndjson` (sorted by `symbol_id`)
|
||||
- `edges.ndjson` (sorted by `from` then `to` then `edge_type`)
|
||||
- `facts_runtime.ndjson` (sorted by `symbol_id`, optional)
|
||||
- `meta.json` (single JSON object; schema version, produced_by, timestamps, tool versions, hashes)
|
||||
- Hashing: SHA-256 of each file recorded in `meta.json` under `files[]` with `path`, `sha256`, `records`.
|
||||
- Compression/packaging is left to the CAS store; files must be valid uncompressed NDJSON first.
|
||||
|
||||
## SymbolID (language-agnostic envelope)
|
||||
```
|
||||
symbol_id = "sym:" + <lang> + ":" + <stable-fragment>
|
||||
```
|
||||
- `lang`: `java|dotnet|go|node|deno|rust|swift|shell|binary`
|
||||
- `stable-fragment`: SHA-256(base64url-no-pad) of the canonical tuple per language:
|
||||
- **java**: (`package`, `class`, `method`, `descriptor`) lowercased, descriptor in JVM format.
|
||||
- **dotnet**: (`assembly_name`, `namespace`, `type`, `member_signature`) using ECMA-335 signature string.
|
||||
- **node/deno**: (`pkg_name_or_path`, `export_path`, `kind`) where `export_path` is slash-joined ESM/CJS path; `pkg_name_or_path` uses npm name or normalized absolute path with drive stripped.
|
||||
- **go**: (`module_path`, `package_path`, `receiver`, `func`), with receiver empty for functions.
|
||||
- **rust**: (`crate`, `module_path`, `item_name`, `mangled`)
|
||||
- **swift**: (`module`, `type`, `member`, `swift-mangled`)
|
||||
- **shell**: (`script_relpath`, `function_or_cmd`)
|
||||
- **binary**: (`binary_build_id`, `section`, `symbol_name`)
|
||||
|
||||
## nodes.ndjson
|
||||
Each line:
|
||||
```
|
||||
{
|
||||
"symbol_id": "sym:lang:...",
|
||||
"lang": "dotnet",
|
||||
"kind": "function|method|type|module|package|binary",
|
||||
"display": "Human readable name",
|
||||
"source": {
|
||||
"file": "relative/or/pkg/path",
|
||||
"line": 123,
|
||||
"col": 1,
|
||||
"digest": "sha256:<hex>"
|
||||
},
|
||||
"attributes": {
|
||||
"visibility": "public|internal|private",
|
||||
"async": true,
|
||||
"static": false,
|
||||
"generic_arity": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
Fields are optional when not applicable; omit rather than null. Additional language-specific fields allowed inside `attributes` (e.g., `jvm_descriptor`, `dotnet_signature`).
|
||||
|
||||
## edges.ndjson
|
||||
Each line (static or runtime-derived; see `source`):
|
||||
```
|
||||
{
|
||||
"from": "sym:...",
|
||||
"to": "sym:...",
|
||||
"edge_type": "call|import|inherits|loads|dynamic|reflects|dlopen|ffi|wasm|spawn",
|
||||
"confidence": "certain|high|medium|low",
|
||||
"source": {
|
||||
"origin": "static|runtime",
|
||||
"provenance": "jvm-bytecode|il|ts-ast|ssa|ebpf|etw|jfr|hook",
|
||||
"evidence": "file:path:line"
|
||||
}
|
||||
}
|
||||
```
|
||||
- Ordering: primary `from`, secondary `to`, tertiary `edge_type`.
|
||||
- Duplicate edges with different provenance are allowed; consumers deduplicate by (`from`,`to`,`edge_type`,`provenance`).
|
||||
|
||||
## facts_runtime.ndjson (optional)
|
||||
Runtime-only observations attached to symbols:
|
||||
```
|
||||
{
|
||||
"symbol_id": "sym:...",
|
||||
"samples": {
|
||||
"call_count": 14,
|
||||
"first_seen_utc": "2025-11-22T18:21:12Z",
|
||||
"last_seen_utc": "2025-11-22T18:23:01Z"
|
||||
},
|
||||
"env": {
|
||||
"pid": 1234,
|
||||
"image": "sha256:...",
|
||||
"entrypoint": "main",
|
||||
"tags": ["sealed","offline"]
|
||||
}
|
||||
}
|
||||
```
|
||||
Sorting by `symbol_id`. Time fields must be UTC ISO-8601 with `Z`.
|
||||
|
||||
## meta.json
|
||||
```
|
||||
{
|
||||
"schema": "reachability-union@0.1",
|
||||
"generated_at": "2025-11-23T00:00:00Z",
|
||||
"produced_by": {
|
||||
"tool": "StellaOps.Scanner.Worker",
|
||||
"version": "0.1.0",
|
||||
"analyzers": ["dotnet-11.1.0","jvm-8.0.0","node-6.2.0"]
|
||||
},
|
||||
"files": [
|
||||
{"path":"nodes.ndjson","sha256":"...","records":1234},
|
||||
{"path":"edges.ndjson","sha256":"...","records":4567},
|
||||
{"path":"facts_runtime.ndjson","sha256":"...","records":89}
|
||||
],
|
||||
"options": {
|
||||
"dedupe_edges": false,
|
||||
"include_runtime": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Determinism rules
|
||||
- Sort order as noted; no nulls; omit empty objects/arrays.
|
||||
- All strings UTF-8 NFC; booleans lower-case; edge_type enumerated list above.
|
||||
- Hash inputs use exact serialized bytes (no trailing spaces, newline `\n` only).
|
||||
|
||||
## Validation
|
||||
- JSON Schema draft 2020-12 available at `docs/reachability/runtime-static-union-schema.json` (to be generated from this spec; allowable values match enumerations above).
|
||||
- Minimal required fields: `symbol_id`, `lang`, `kind` (nodes); `from`, `to`, `edge_type`, `source.origin` (edges).
|
||||
|
||||
## Integration guidance
|
||||
- Static lifters must emit SymbolIDs using the language rules; runtime probes must map call targets to the same SymbolID space (via demangled names + package/module resolution).
|
||||
- CAS writers store each file under the namespace path and return the root manifest path for downstream consumers (Signals, Replay, Policy).
|
||||
- Consumers should treat runtime edges as additive; when both origins exist, prefer `origin=runtime` for exploitability scoring but keep static edges for coverage.
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Policy.Engine.Streaming;
|
||||
@@ -10,8 +11,7 @@ public static class PathScopeSimulationEndpoint
|
||||
public static IEndpointRouteBuilder MapPathScopeSimulation(this IEndpointRouteBuilder routes)
|
||||
{
|
||||
routes.MapPost("/simulation/path-scope", HandleAsync)
|
||||
.WithName("PolicyEngine.PathScopeSimulation")
|
||||
.WithOpenApi();
|
||||
.WithName("PolicyEngine.PathScopeSimulation");
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
@@ -104,13 +104,14 @@ builder.Services.AddOptions<PolicyEngineOptions>()
|
||||
|
||||
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<PolicyEngineOptions>>().Value);
|
||||
builder.Services.AddSingleton(TimeProvider.System);
|
||||
builder.Services.AddSingleton<PolicyEngineStartupDiagnostics>();
|
||||
builder.Services.AddHostedService<PolicyEngineBootstrapWorker>();
|
||||
builder.Services.AddSingleton<PolicyEngineStartupDiagnostics>();
|
||||
builder.Services.AddHostedService<PolicyEngineBootstrapWorker>();
|
||||
builder.Services.AddSingleton<PolicyCompiler>();
|
||||
builder.Services.AddSingleton<PolicyCompilationService>();
|
||||
builder.Services.AddSingleton<StellaOps.Policy.Engine.Services.PathScopeMetrics>();
|
||||
builder.Services.AddSingleton<PolicyEvaluationService>();
|
||||
builder.Services.AddSingleton<PathScopeSimulationService>();
|
||||
builder.Services.AddSingleton<IPolicyPackRepository, InMemoryPolicyPackRepository>();
|
||||
builder.Services.AddSingleton<IPolicyPackRepository, InMemoryPolicyPackRepository>();
|
||||
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddRouting(options => options.LowercaseUrls = true);
|
||||
|
||||
185
src/Policy/StellaOps.Policy.Engine/Services/PathScopeMetrics.cs
Normal file
185
src/Policy/StellaOps.Policy.Engine/Services/PathScopeMetrics.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Metrics sink for path/scope-aware policy evaluation (POLICY-ENGINE-29-004).
|
||||
/// Mirrors the prep contract at docs/modules/policy/prep/2025-11-20-policy-engine-29-004-prep.md.
|
||||
/// </summary>
|
||||
internal sealed class PathScopeMetrics : IDisposable
|
||||
{
|
||||
private readonly Meter _meter;
|
||||
private readonly Counter<long> _evaluations;
|
||||
private readonly Histogram<double> _evaluationDurationMs;
|
||||
private readonly Counter<long> _cacheHit;
|
||||
private readonly Counter<long> _scopeMismatch;
|
||||
private readonly ConcurrentDictionary<(string Tenant, string Source), CoverageState> _coverage = new();
|
||||
private readonly ObservableGauge<double> _coverageGauge;
|
||||
|
||||
public PathScopeMetrics()
|
||||
{
|
||||
_meter = new Meter("StellaOps.Policy.Engine", "1.0.0");
|
||||
|
||||
_evaluations = _meter.CreateCounter<long>(
|
||||
name: "policy.path.eval.total",
|
||||
unit: "count",
|
||||
description: "Total path/scope-aware evaluations processed.");
|
||||
|
||||
_evaluationDurationMs = _meter.CreateHistogram<double>(
|
||||
name: "policy.path.eval.duration.ms",
|
||||
unit: "ms",
|
||||
description: "Latency distribution for path/scope-aware evaluations.");
|
||||
|
||||
_cacheHit = _meter.CreateCounter<long>(
|
||||
name: "policy.path.eval.cache.hit",
|
||||
unit: "count",
|
||||
description: "Cache hit/miss counts for path/scope rule lookups.");
|
||||
|
||||
_scopeMismatch = _meter.CreateCounter<long>(
|
||||
name: "policy.path.eval.scope.mismatch",
|
||||
unit: "count",
|
||||
description: "Counts of scope mismatches (depth/confidence/coverage).");
|
||||
|
||||
Func<IEnumerable<Measurement<double>>> observe = ObserveCoverage;
|
||||
_coverageGauge = _meter.CreateObservableGauge<double>(
|
||||
name: "policy.path.eval.coverage",
|
||||
observeValues: observe,
|
||||
unit: "percent",
|
||||
description: "Share of observations with matching scope.");
|
||||
}
|
||||
|
||||
public void RecordEvaluation(
|
||||
string tenant,
|
||||
string subject,
|
||||
string ruleId,
|
||||
string pathMatch,
|
||||
string result,
|
||||
double durationMs,
|
||||
bool scopeMatched = true)
|
||||
{
|
||||
var evalTags = new[]
|
||||
{
|
||||
new KeyValuePair<string, object?>("tenant", NormalizeTenant(tenant)),
|
||||
new KeyValuePair<string, object?>("subject", NormalizeSubject(subject)),
|
||||
new KeyValuePair<string, object?>("result", NormalizeResult(result)),
|
||||
new KeyValuePair<string, object?>("ruleId", TruncateRule(ruleId)),
|
||||
new KeyValuePair<string, object?>("pathMatch", NormalizePathMatch(pathMatch))
|
||||
};
|
||||
|
||||
_evaluations.Add(1, evalTags);
|
||||
|
||||
var durationTags = new[]
|
||||
{
|
||||
new KeyValuePair<string, object?>("tenant", NormalizeTenant(tenant)),
|
||||
new KeyValuePair<string, object?>("subject", NormalizeSubject(subject)),
|
||||
new KeyValuePair<string, object?>("ruleId", TruncateRule(ruleId))
|
||||
};
|
||||
|
||||
_evaluationDurationMs.Record(durationMs, durationTags);
|
||||
RecordCoverage(tenant, "path-scope", scopeMatched);
|
||||
}
|
||||
|
||||
public void RecordCacheHit(string tenant, string cache, bool hit)
|
||||
{
|
||||
var tags = new[]
|
||||
{
|
||||
new KeyValuePair<string, object?>("tenant", NormalizeTenant(tenant)),
|
||||
new KeyValuePair<string, object?>("cache", NormalizeCache(cache)),
|
||||
new KeyValuePair<string, object?>("hit", hit ? "true" : "false")
|
||||
};
|
||||
|
||||
_cacheHit.Add(1, tags);
|
||||
}
|
||||
|
||||
public void RecordScopeMismatch(string tenant, string reason)
|
||||
{
|
||||
var tags = new[]
|
||||
{
|
||||
new KeyValuePair<string, object?>("tenant", NormalizeTenant(tenant)),
|
||||
new KeyValuePair<string, object?>("reason", NormalizeScopeReason(reason))
|
||||
};
|
||||
|
||||
_scopeMismatch.Add(1, tags);
|
||||
RecordCoverage(tenant, "path-scope", matched: false);
|
||||
}
|
||||
|
||||
private void RecordCoverage(string tenant, string source, bool matched)
|
||||
{
|
||||
var key = (NormalizeTenant(tenant), NormalizeSource(source));
|
||||
|
||||
_coverage.AddOrUpdate(
|
||||
key,
|
||||
_ => matched ? new CoverageState(1, 1) : new CoverageState(0, 1),
|
||||
(_, state) => matched
|
||||
? new CoverageState(state.Matched + 1, state.Total + 1)
|
||||
: new CoverageState(state.Matched, state.Total + 1));
|
||||
}
|
||||
|
||||
private IEnumerable<Measurement<double>> ObserveCoverage()
|
||||
{
|
||||
foreach (var kvp in _coverage)
|
||||
{
|
||||
var state = kvp.Value;
|
||||
if (state.Total == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var percentage = state.Matched * 100.0 / state.Total;
|
||||
yield return new Measurement<double>(
|
||||
percentage,
|
||||
new[]
|
||||
{
|
||||
new KeyValuePair<string, object?>("tenant", kvp.Key.Tenant),
|
||||
new KeyValuePair<string, object?>("source", kvp.Key.Source)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeTenant(string tenant) =>
|
||||
string.IsNullOrWhiteSpace(tenant) ? "unknown" : tenant;
|
||||
|
||||
private static string NormalizeSubject(string subject) =>
|
||||
string.IsNullOrWhiteSpace(subject) ? "unknown" : subject;
|
||||
|
||||
private static string NormalizeResult(string result) =>
|
||||
result switch
|
||||
{
|
||||
"allow" or "deny" or "error" => result,
|
||||
_ => "deny"
|
||||
};
|
||||
|
||||
private static string NormalizePathMatch(string pathMatch) =>
|
||||
pathMatch switch
|
||||
{
|
||||
"exact" or "prefix" or "glob" => pathMatch,
|
||||
_ => "unknown"
|
||||
};
|
||||
|
||||
private static string NormalizeCache(string cache) =>
|
||||
string.IsNullOrWhiteSpace(cache) ? "decision" : cache;
|
||||
|
||||
private static string NormalizeScopeReason(string reason) =>
|
||||
string.IsNullOrWhiteSpace(reason) ? "no-scope" : reason;
|
||||
|
||||
private static string NormalizeSource(string source) =>
|
||||
string.IsNullOrWhiteSpace(source) ? "path-scope" : source;
|
||||
|
||||
private static string TruncateRule(string ruleId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ruleId))
|
||||
{
|
||||
return "unspecified";
|
||||
}
|
||||
|
||||
return ruleId.Length <= 32 ? ruleId : ruleId[..32];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_meter.Dispose();
|
||||
}
|
||||
|
||||
private readonly record struct CoverageState(long Matched, long Total);
|
||||
}
|
||||
@@ -1,10 +1,23 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Policy.Engine.Streaming;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Services;
|
||||
|
||||
public sealed partial class PolicyEvaluationService
|
||||
internal sealed partial class PolicyEvaluationService
|
||||
{
|
||||
private const string StubRuleId = "policy.rules.path-scope.stub";
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
public Task<JsonObject> EvaluatePathScopeAsync(
|
||||
PathScopeSimulationRequest request,
|
||||
PathScopeTarget target,
|
||||
@@ -12,13 +25,16 @@ public sealed partial class PolicyEvaluationService
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var stableKey = string.Create(CultureInfo.InvariantCulture, $"{request.BasePolicyRef}|{request.CandidatePolicyRef}|{target.FilePath}|{target.Pattern}");
|
||||
var verdictDelta = ComputeDelta(stableKey);
|
||||
var result = NormalizeResult(verdictDelta.candidateVerdict);
|
||||
var correlationId = ComputeCorrelationId(stableKey);
|
||||
|
||||
var finding = new JsonObject
|
||||
{
|
||||
["id"] = target.EvidenceHash ?? "stub-ghsa",
|
||||
["ruleId"] = "policy.rules.path-scope.stub",
|
||||
["ruleId"] = StubRuleId,
|
||||
["severity"] = "info",
|
||||
["verdict"] = new JsonObject
|
||||
{
|
||||
@@ -67,6 +83,34 @@ public sealed partial class PolicyEvaluationService
|
||||
}
|
||||
};
|
||||
|
||||
var durationMs = ElapsedMilliseconds(start);
|
||||
((JsonObject)envelope["metrics"]!)[@"durationMs"] = Math.Round(durationMs, 3, MidpointRounding.ToZero);
|
||||
|
||||
_pathMetrics.RecordEvaluation(
|
||||
tenant: request.Tenant,
|
||||
subject: SimplifySubject(request.Subject),
|
||||
ruleId: StubRuleId,
|
||||
pathMatch: target.PathMatch,
|
||||
result: result,
|
||||
durationMs: durationMs,
|
||||
scopeMatched: true);
|
||||
|
||||
_pathMetrics.RecordCacheHit(request.Tenant, cache: "rule", hit: false);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Policy.PathEval {@Tenant} {@RuleId} {@Subject} {@FilePath} {@PathMatch} {@Pattern} {@Confidence} {@Decision} {@DurationMs} {@EvidenceHash} {@CorrelationId}",
|
||||
request.Tenant,
|
||||
StubRuleId,
|
||||
SimplifySubject(request.Subject),
|
||||
target.FilePath,
|
||||
target.PathMatch,
|
||||
target.Pattern,
|
||||
target.Confidence,
|
||||
verdictDelta.candidateVerdict,
|
||||
durationMs,
|
||||
target.EvidenceHash ?? string.Empty,
|
||||
correlationId);
|
||||
|
||||
return Task.FromResult(envelope);
|
||||
}
|
||||
|
||||
@@ -83,4 +127,56 @@ public sealed partial class PolicyEvaluationService
|
||||
var delta = baseVerdict == candidateVerdict ? "unchanged" : "softened";
|
||||
return (baseVerdict, candidateVerdict, delta);
|
||||
}
|
||||
|
||||
private static string NormalizeResult(string candidateVerdict) =>
|
||||
string.Equals(candidateVerdict, "deny", StringComparison.OrdinalIgnoreCase) ? "deny" : "allow";
|
||||
|
||||
private static double ElapsedMilliseconds(long startTimestamp)
|
||||
{
|
||||
var elapsedTicks = Stopwatch.GetTimestamp() - startTimestamp;
|
||||
return elapsedTicks * 1000.0 / Stopwatch.Frequency;
|
||||
}
|
||||
|
||||
private static string ComputeCorrelationId(string stableKey)
|
||||
{
|
||||
Span<byte> hashBytes = stackalloc byte[16];
|
||||
SHA256.HashData(Encoding.UTF8.GetBytes(stableKey), hashBytes);
|
||||
return Convert.ToHexString(hashBytes);
|
||||
}
|
||||
|
||||
private static string SimplifySubject(PathScopeSubject subject)
|
||||
{
|
||||
if (subject is null)
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(subject.Purl))
|
||||
{
|
||||
var purl = subject.Purl;
|
||||
var trimmed = purl.StartsWith("pkg:", StringComparison.OrdinalIgnoreCase) ? purl[4..] : purl;
|
||||
var slashIndex = trimmed.IndexOf('/', StringComparison.Ordinal);
|
||||
if (slashIndex >= 0 && slashIndex + 1 < trimmed.Length)
|
||||
{
|
||||
var remainder = trimmed[(slashIndex + 1)..];
|
||||
var atIndex = remainder.IndexOf('@');
|
||||
var withoutVersion = atIndex >= 0 ? remainder[..atIndex] : remainder;
|
||||
var lastSlash = withoutVersion.LastIndexOf('/');
|
||||
return lastSlash >= 0 ? withoutVersion[(lastSlash + 1)..] : withoutVersion;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(subject.Cpe))
|
||||
{
|
||||
var parts = subject.Cpe.Split(':', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length > 4)
|
||||
{
|
||||
return parts[4];
|
||||
}
|
||||
|
||||
return parts.LastOrDefault() ?? "unknown";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Policy.Engine.Compilation;
|
||||
using StellaOps.Policy.Engine.Evaluation;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Services;
|
||||
|
||||
internal sealed class PolicyEvaluationService
|
||||
internal sealed partial class PolicyEvaluationService
|
||||
{
|
||||
private readonly PolicyEvaluator evaluator = new();
|
||||
private readonly PathScopeMetrics _pathMetrics;
|
||||
private readonly ILogger<PolicyEvaluationService> _logger;
|
||||
|
||||
public PolicyEvaluationService() : this(new PathScopeMetrics())
|
||||
public PolicyEvaluationService()
|
||||
: this(new PathScopeMetrics(), NullLogger<PolicyEvaluationService>.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
public PolicyEvaluationService(PathScopeMetrics pathMetrics)
|
||||
public PolicyEvaluationService(PathScopeMetrics pathMetrics, ILogger<PolicyEvaluationService> logger)
|
||||
{
|
||||
_pathMetrics = pathMetrics ?? throw new ArgumentNullException(nameof(pathMetrics));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public PolicyEvaluationResult Evaluate(PolicyIrDocument document, PolicyEvaluationContext context)
|
||||
internal PolicyEvaluationResult Evaluate(PolicyIrDocument document, PolicyEvaluationContext context)
|
||||
{
|
||||
if (document is null)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using StellaOps.Policy.Engine.Services;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Streaming;
|
||||
@@ -10,7 +11,7 @@ namespace StellaOps.Policy.Engine.Streaming;
|
||||
/// Current behaviour emits no findings but enforces request validation, canonical ordering,
|
||||
/// and NDJSON framing so downstream consumers can integrate without schema drift.
|
||||
/// </summary>
|
||||
public sealed class PathScopeSimulationService
|
||||
internal sealed class PathScopeSimulationService
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
@@ -19,6 +20,10 @@ public sealed class PathScopeSimulationService
|
||||
|
||||
private readonly PolicyEvaluationService _evaluationService;
|
||||
|
||||
public PathScopeSimulationService() : this(new PolicyEvaluationService())
|
||||
{
|
||||
}
|
||||
|
||||
public PathScopeSimulationService(PolicyEvaluationService evaluationService)
|
||||
{
|
||||
_evaluationService = evaluationService ?? throw new ArgumentNullException(nameof(evaluationService));
|
||||
|
||||
@@ -27,8 +27,8 @@ public sealed class PathScopeSimulationServiceTests
|
||||
var lines = await service.StreamAsync(request).ToListAsync();
|
||||
|
||||
Assert.Equal(2, lines.Count);
|
||||
Assert.Contains(lines[0], s => s.Contains("\"filePath\":\"a/file.js\""));
|
||||
Assert.Contains(lines[1], s => s.Contains("\"filePath\":\"b/file.js\""));
|
||||
Assert.Contains("\"filePath\":\"a/file.js\"", lines[0]);
|
||||
Assert.Contains("\"filePath\":\"b/file.js\"", lines[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -44,6 +44,7 @@ public sealed class PathScopeSimulationServiceTests
|
||||
Targets: Array.Empty<PathScopeTarget>(),
|
||||
Options: new SimulationOptions("path,finding,verdict", 100, IncludeTrace: true, Deterministic: true));
|
||||
|
||||
await Assert.ThrowsAsync<PathScopeSimulationException>(() => service.StreamAsync(request).ToListAsync());
|
||||
await Assert.ThrowsAsync<PathScopeSimulationException>(async () =>
|
||||
await service.StreamAsync(request).ToListAsync());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using StellaOps.SbomService.Models;
|
||||
|
||||
namespace StellaOps.SbomService.Tests;
|
||||
|
||||
public class EntrypointEndpointsTests : IClassFixture<SbomServiceWebApplicationFactory>
|
||||
{
|
||||
private readonly SbomServiceWebApplicationFactory _factory;
|
||||
|
||||
public EntrypointEndpointsTests(SbomServiceWebApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_entrypoints_requires_tenant()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var response = await client.GetAsync("/entrypoints");
|
||||
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_entrypoints_returns_seeded_list()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var response = await client.GetAsync("/entrypoints?tenant=tenant-a");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<EntrypointListResponse>();
|
||||
payload.Should().NotBeNull();
|
||||
payload!.Tenant.Should().Be("tenant-a");
|
||||
payload.Items.Should().NotBeEmpty();
|
||||
payload.Items.Select(e => e.Artifact).Should().Contain("ghcr.io/stellaops/sample-api");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Post_entrypoints_upserts_and_returns_ordered_list()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var upsert = new EntrypointUpsertRequest(
|
||||
Tenant: "tenant-a",
|
||||
Artifact: "ghcr.io/stellaops/sample-api",
|
||||
Service: "web",
|
||||
Path: "/api/v2",
|
||||
Scope: "runtime",
|
||||
RuntimeFlag: true);
|
||||
|
||||
var post = await client.PostAsJsonAsync("/entrypoints", upsert);
|
||||
post.EnsureSuccessStatusCode();
|
||||
|
||||
var payload = await post.Content.ReadFromJsonAsync<EntrypointListResponse>();
|
||||
payload.Should().NotBeNull();
|
||||
payload!.Items.First(e => e.Service == "web").Path.Should().Be("/api/v2");
|
||||
payload.Items.Should().BeInAscendingOrder(e => e.Artifact);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Net.Http.Json;
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
@@ -23,7 +23,8 @@ public class SbomEndpointsTests : IClassFixture<WebApplicationFactory<Program>>
|
||||
|
||||
var response = await client.GetAsync("/sbom/paths");
|
||||
|
||||
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
response.StatusCode.Should().Be(HttpStatusCode.BadRequest, body);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -47,7 +48,7 @@ public class SbomEndpointsTests : IClassFixture<WebApplicationFactory<Program>>
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
var response = await client.GetAsync("/sbom/versions?artifact=ghcr.io/stellaops/sample-api");
|
||||
response.EnsureSuccessStatusCode();
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK, await response.Content.ReadAsStringAsync());
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<SbomTimelineResult>();
|
||||
payload.Should().NotBeNull();
|
||||
|
||||
@@ -31,9 +31,10 @@ public class SbomEventEndpointsTests : IClassFixture<WebApplicationFactory<Progr
|
||||
|
||||
var events = await client.GetFromJsonAsync<List<SbomVersionCreatedEvent>>("/internal/sbom/events");
|
||||
events.Should().NotBeNull();
|
||||
events!.Should().HaveCount(1);
|
||||
events[0].SnapshotId.Should().Be("snap-001");
|
||||
events[0].TenantId.Should().Be("tenant-a");
|
||||
var nonNullEvents = events!;
|
||||
nonNullEvents.Should().HaveCount(1);
|
||||
nonNullEvents[0].SnapshotId.Should().Be("snap-001");
|
||||
nonNullEvents[0].TenantId.Should().Be("tenant-a");
|
||||
|
||||
// Requesting the projection should not duplicate events.
|
||||
var projectionResponse = await client.GetAsync("/sboms/snap-001/projection?tenant=tenant-a");
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace StellaOps.SbomService.Models;
|
||||
|
||||
public sealed record Entrypoint(
|
||||
string Artifact,
|
||||
string Service,
|
||||
string Path,
|
||||
string Scope,
|
||||
bool RuntimeFlag);
|
||||
|
||||
public sealed record EntrypointUpsertRequest(
|
||||
string Tenant,
|
||||
string Artifact,
|
||||
string Service,
|
||||
string Path,
|
||||
string Scope,
|
||||
bool RuntimeFlag);
|
||||
|
||||
public sealed record EntrypointListResponse(
|
||||
string Tenant,
|
||||
IReadOnlyList<Entrypoint> Items);
|
||||
@@ -21,4 +21,19 @@ internal static class SbomMetrics
|
||||
public static readonly Counter<long> TimelineQueryTotal =
|
||||
Meter.CreateCounter<long>("sbom_timeline_queries_total",
|
||||
description: "Total SBOM timeline queries");
|
||||
}
|
||||
|
||||
public static readonly Histogram<double> ProjectionLatencySeconds =
|
||||
Meter.CreateHistogram<double>("sbom_projection_seconds", unit: "s",
|
||||
description: "Latency for SBOM projection reads");
|
||||
|
||||
public static readonly Histogram<long> ProjectionSizeBytes =
|
||||
Meter.CreateHistogram<long>("sbom_projection_size_bytes", unit: "By",
|
||||
description: "Payload size of SBOM projections returned");
|
||||
|
||||
public static readonly Counter<long> ProjectionQueryTotal =
|
||||
Meter.CreateCounter<long>("sbom_projection_queries_total",
|
||||
description: "Total SBOM projection queries");
|
||||
|
||||
public static readonly Histogram<long> EventBacklogSize =
|
||||
Meter.CreateHistogram<long>("sbom_events_backlog", unit: "events",
|
||||
description: "Observed size of the SBOM event outbox (in-memory)
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace StellaOps.SbomService.Observability;
|
||||
|
||||
internal static class SbomTracing
|
||||
{
|
||||
public const string SourceName = "StellaOps.SbomService";
|
||||
public static readonly ActivitySource Source = new(SourceName);
|
||||
}
|
||||
@@ -7,22 +7,24 @@ using StellaOps.SbomService.Services;
|
||||
using StellaOps.SbomService.Observability;
|
||||
using StellaOps.SbomService.Repositories;
|
||||
using System.Text.Json;
|
||||
using System.Diagnostics;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Configuration
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||
.AddEnvironmentVariables("SBOM_");
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Configuration
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||
.AddEnvironmentVariables("SBOM_");
|
||||
|
||||
builder.Services.AddOptions();
|
||||
builder.Services.AddLogging();
|
||||
|
||||
builder.Services.AddOptions();
|
||||
builder.Services.AddLogging();
|
||||
|
||||
// Register SBOM query services (InMemory seed; replace with Mongo-backed repository later).
|
||||
builder.Services.AddSingleton<IComponentLookupRepository>(_ => new InMemoryComponentLookupRepository());
|
||||
builder.Services.AddSingleton<IClock, SystemClock>();
|
||||
builder.Services.AddSingleton<ISbomEventStore, InMemorySbomEventStore>();
|
||||
builder.Services.AddSingleton<ISbomEventPublisher>(sp => sp.GetRequiredService<ISbomEventStore>());
|
||||
builder.Services.AddSingleton<ISbomQueryService, InMemorySbomQueryService>();
|
||||
builder.Services.AddSingleton<IEntrypointRepository, InMemoryEntrypointRepository>();
|
||||
|
||||
builder.Services.AddSingleton<IProjectionRepository>(sp =>
|
||||
{
|
||||
@@ -54,16 +56,85 @@ builder.Services.AddSingleton<IProjectionRepository>(sp =>
|
||||
|
||||
return new FileProjectionRepository(string.Empty);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await next();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[dev-exception] {ex}");
|
||||
throw;
|
||||
}
|
||||
});
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.MapGet("/healthz", () => Results.Ok(new { status = "ok" }));
|
||||
app.MapGet("/readyz", () => Results.Ok(new { status = "warming" }));
|
||||
|
||||
app.MapGet("/entrypoints", async Task<IResult> (
|
||||
[FromServices] IEntrypointRepository repo,
|
||||
[FromQuery] string? tenant,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tenant))
|
||||
{
|
||||
return Results.BadRequest(new { error = "tenant is required" });
|
||||
}
|
||||
|
||||
var tenantId = tenant.Trim();
|
||||
using var activity = SbomTracing.Source.StartActivity("entrypoints.list", ActivityKind.Server);
|
||||
activity?.SetTag("tenant", tenantId);
|
||||
|
||||
var items = await repo.ListAsync(tenantId, cancellationToken);
|
||||
return Results.Ok(new EntrypointListResponse(tenantId, items));
|
||||
});
|
||||
|
||||
app.MapPost("/entrypoints", async Task<IResult> (
|
||||
[FromServices] IEntrypointRepository repo,
|
||||
[FromBody] EntrypointUpsertRequest request,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Tenant))
|
||||
{
|
||||
return Results.BadRequest(new { error = "tenant is required" });
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Artifact) || string.IsNullOrWhiteSpace(request.Service) || string.IsNullOrWhiteSpace(request.Path))
|
||||
{
|
||||
return Results.BadRequest(new { error = "artifact, service, and path are required" });
|
||||
}
|
||||
|
||||
var entrypoint = new Entrypoint(
|
||||
request.Artifact.Trim(),
|
||||
request.Service.Trim(),
|
||||
request.Path.Trim(),
|
||||
string.IsNullOrWhiteSpace(request.Scope) ? "runtime" : request.Scope.Trim(),
|
||||
request.RuntimeFlag);
|
||||
|
||||
var tenantId = request.Tenant.Trim();
|
||||
using var activity = SbomTracing.Source.StartActivity("entrypoints.upsert", ActivityKind.Server);
|
||||
activity?.SetTag("tenant", tenantId);
|
||||
activity?.SetTag("artifact", entrypoint.Artifact);
|
||||
activity?.SetTag("service", entrypoint.Service);
|
||||
|
||||
await repo.UpsertAsync(tenantId, entrypoint, cancellationToken);
|
||||
|
||||
var items = await repo.ListAsync(tenantId, cancellationToken);
|
||||
return Results.Ok(new EntrypointListResponse(tenantId, items));
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapGet("/healthz", () => Results.Ok(new { status = "ok" }));
|
||||
app.MapGet("/readyz", () => Results.Ok(new { status = "warming" }));
|
||||
|
||||
app.MapGet("/console/sboms", async Task<IResult> (
|
||||
[FromServices] ISbomQueryService service,
|
||||
[FromQuery] string? artifact,
|
||||
[FromQuery] string? license,
|
||||
app.MapGet("/console/sboms", async Task<IResult> (
|
||||
[FromServices] ISbomQueryService service,
|
||||
[FromQuery] string? artifact,
|
||||
[FromQuery] string? license,
|
||||
[FromQuery] string? scope,
|
||||
[FromQuery(Name = "assetTag")] string? assetTag,
|
||||
[FromQuery] string? cursor,
|
||||
@@ -80,15 +151,17 @@ app.MapGet("/console/sboms", async Task<IResult> (
|
||||
return Results.BadRequest(new { error = "cursor must be an integer offset" });
|
||||
}
|
||||
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetConsoleCatalogAsync(
|
||||
new SbomCatalogQuery(artifact?.Trim(), license?.Trim(), scope?.Trim(), assetTag?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
|
||||
var elapsedSeconds = Stopwatch.GetElapsedTime(start).TotalSeconds;
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
using var activity = SbomTracing.Source.StartActivity("console.sboms", ActivityKind.Server);
|
||||
activity?.SetTag("artifact", artifact);
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetConsoleCatalogAsync(
|
||||
new SbomCatalogQuery(artifact?.Trim(), license?.Trim(), scope?.Trim(), assetTag?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
|
||||
var elapsedSeconds = Stopwatch.GetElapsedTime(start).TotalSeconds;
|
||||
SbomMetrics.PathsLatencySeconds.Record(elapsedSeconds, new TagList
|
||||
{
|
||||
{ "scope", scope ?? string.Empty },
|
||||
@@ -103,10 +176,10 @@ app.MapGet("/console/sboms", async Task<IResult> (
|
||||
return Results.Ok(result.Result);
|
||||
});
|
||||
|
||||
app.MapGet("/components/lookup", async Task<IResult> (
|
||||
[FromServices] ISbomQueryService service,
|
||||
[FromQuery] string? purl,
|
||||
[FromQuery] string? artifact,
|
||||
app.MapGet("/components/lookup", async Task<IResult> (
|
||||
[FromServices] ISbomQueryService service,
|
||||
[FromQuery] string? purl,
|
||||
[FromQuery] string? artifact,
|
||||
[FromQuery] string? cursor,
|
||||
[FromQuery] int? limit,
|
||||
CancellationToken cancellationToken) =>
|
||||
@@ -126,13 +199,16 @@ app.MapGet("/components/lookup", async Task<IResult> (
|
||||
return Results.BadRequest(new { error = "cursor must be an integer offset" });
|
||||
}
|
||||
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetComponentLookupAsync(
|
||||
new ComponentLookupQuery(purl.Trim(), artifact?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
using var activity = SbomTracing.Source.StartActivity("components.lookup", ActivityKind.Server);
|
||||
activity?.SetTag("purl", purl);
|
||||
activity?.SetTag("artifact", artifact);
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetComponentLookupAsync(
|
||||
new ComponentLookupQuery(purl.Trim(), artifact?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
|
||||
var elapsedSeconds = Stopwatch.GetElapsedTime(start).TotalSeconds;
|
||||
SbomMetrics.PathsLatencySeconds.Record(elapsedSeconds, new TagList
|
||||
@@ -149,13 +225,13 @@ app.MapGet("/components/lookup", async Task<IResult> (
|
||||
return Results.Ok(result.Result);
|
||||
});
|
||||
|
||||
app.MapGet("/sbom/paths", async Task<IResult> (
|
||||
[FromServices] ISbomQueryService service,
|
||||
[FromQuery] string? purl,
|
||||
[FromQuery] string? artifact,
|
||||
[FromQuery] string? scope,
|
||||
[FromQuery(Name = "env")] string? environment,
|
||||
[FromQuery] string? cursor,
|
||||
app.MapGet("/sbom/paths", async Task<IResult> (
|
||||
[FromServices] IServiceProvider services,
|
||||
[FromQuery] string? purl,
|
||||
[FromQuery] string? artifact,
|
||||
[FromQuery] string? scope,
|
||||
[FromQuery(Name = "env")] string? environment,
|
||||
[FromQuery] string? cursor,
|
||||
[FromQuery] int? limit,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
@@ -172,15 +248,16 @@ app.MapGet("/sbom/paths", async Task<IResult> (
|
||||
if (cursor is { Length: > 0 } && !int.TryParse(cursor, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
||||
{
|
||||
return Results.BadRequest(new { error = "cursor must be an integer offset" });
|
||||
}
|
||||
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetPathsAsync(
|
||||
new SbomPathQuery(purl.Trim(), artifact?.Trim(), scope?.Trim(), environment?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
var offset = cursor is null ? 0 : int.Parse(cursor, CultureInfo.InvariantCulture);
|
||||
var pageSize = limit ?? 50;
|
||||
|
||||
var service = services.GetRequiredService<ISbomQueryService>();
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var result = await service.GetPathsAsync(
|
||||
new SbomPathQuery(purl.Trim(), artifact?.Trim(), scope?.Trim(), environment?.Trim(), pageSize, offset),
|
||||
cancellationToken);
|
||||
|
||||
var elapsedSeconds = Stopwatch.GetElapsedTime(start).TotalSeconds;
|
||||
SbomMetrics.PathsLatencySeconds.Record(elapsedSeconds, new TagList
|
||||
@@ -250,20 +327,37 @@ app.MapGet("/sboms/{snapshotId}/projection", async Task<IResult> (
|
||||
return Results.BadRequest(new { error = "tenant is required" });
|
||||
}
|
||||
|
||||
var start = Stopwatch.GetTimestamp();
|
||||
var projection = await service.GetProjectionAsync(snapshotId.Trim(), tenantId.Trim(), cancellationToken);
|
||||
if (projection is null)
|
||||
{
|
||||
return Results.NotFound(new { error = "projection not found" });
|
||||
}
|
||||
|
||||
return Results.Ok(new
|
||||
using var activity = SbomTracing.Source.StartActivity("sbom.projection", ActivityKind.Server);
|
||||
activity?.SetTag("tenant", projection.TenantId);
|
||||
activity?.SetTag("snapshotId", projection.SnapshotId);
|
||||
activity?.SetTag("schema", projection.SchemaVersion);
|
||||
|
||||
var payload = new
|
||||
{
|
||||
snapshotId = projection.SnapshotId,
|
||||
tenantId = projection.TenantId,
|
||||
schemaVersion = projection.SchemaVersion,
|
||||
hash = projection.ProjectionHash,
|
||||
projection = projection.Projection
|
||||
});
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
var sizeBytes = System.Text.Encoding.UTF8.GetByteCount(json);
|
||||
SbomMetrics.ProjectionSizeBytes.Record(sizeBytes, new TagList { { "tenant", projection.TenantId } });
|
||||
SbomMetrics.ProjectionLatencySeconds.Record(Stopwatch.GetElapsedTime(start).TotalSeconds,
|
||||
new TagList { { "tenant", projection.TenantId } });
|
||||
SbomMetrics.ProjectionQueryTotal.Add(1, new TagList { { "tenant", projection.TenantId } });
|
||||
|
||||
app.Logger.LogInformation("projection_returned tenant={Tenant} snapshot={Snapshot} size={SizeBytes}", projection.TenantId, projection.SnapshotId, sizeBytes);
|
||||
|
||||
return Results.Ok(payload);
|
||||
});
|
||||
|
||||
app.MapGet("/internal/sbom/events", async Task<IResult> (
|
||||
|
||||
@@ -4,5 +4,9 @@ namespace StellaOps.SbomService.Repositories;
|
||||
|
||||
public interface IComponentLookupRepository
|
||||
{
|
||||
Task<IReadOnlyList<ComponentLookupRecord>> QueryAsync(ComponentLookupQuery query, CancellationToken cancellationToken);
|
||||
/// <summary>
|
||||
/// Returns a page of component neighbors along with the total count that match the query filters.
|
||||
/// The total is required for deterministic pagination cursors.
|
||||
/// </summary>
|
||||
Task<(IReadOnlyList<ComponentLookupRecord> Items, int Total)> QueryAsync(ComponentLookupQuery query, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using StellaOps.SbomService.Models;
|
||||
|
||||
namespace StellaOps.SbomService.Repositories;
|
||||
|
||||
public interface IEntrypointRepository
|
||||
{
|
||||
Task<IReadOnlyList<Entrypoint>> ListAsync(string tenantId, CancellationToken cancellationToken);
|
||||
Task UpsertAsync(string tenantId, Entrypoint entrypoint, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ public sealed class InMemoryComponentLookupRepository : IComponentLookupReposito
|
||||
{
|
||||
private static readonly IReadOnlyList<ComponentLookupRecord> Components = Seed();
|
||||
|
||||
public Task<IReadOnlyList<ComponentLookupRecord>> QueryAsync(ComponentLookupQuery query, CancellationToken cancellationToken)
|
||||
public Task<(IReadOnlyList<ComponentLookupRecord> Items, int Total)> QueryAsync(ComponentLookupQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
var filtered = Components
|
||||
.Where(c => c.Purl.Equals(query.Purl, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -20,7 +20,7 @@ public sealed class InMemoryComponentLookupRepository : IComponentLookupReposito
|
||||
.Take(query.Limit)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult<IReadOnlyList<ComponentLookupRecord>>(page);
|
||||
return Task.FromResult<(IReadOnlyList<ComponentLookupRecord>, int)>((page, filtered.Count));
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ComponentLookupRecord> Seed()
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Collections.Concurrent;
|
||||
using StellaOps.SbomService.Models;
|
||||
|
||||
namespace StellaOps.SbomService.Repositories;
|
||||
|
||||
public sealed class InMemoryEntrypointRepository : IEntrypointRepository
|
||||
{
|
||||
// tenant -> list of entrypoints
|
||||
private readonly ConcurrentDictionary<string, List<Entrypoint>> _store = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public InMemoryEntrypointRepository()
|
||||
{
|
||||
_store["tenant-a"] = new List<Entrypoint>
|
||||
{
|
||||
new("ghcr.io/stellaops/sample-api", "web", "/api", "runtime", true),
|
||||
new("ghcr.io/stellaops/sample-worker", "worker", "queue:jobs", "runtime", true)
|
||||
};
|
||||
|
||||
_store["tenant-b"] = new List<Entrypoint>
|
||||
{
|
||||
new("ghcr.io/stellaops/console", "ui", "/", "runtime", true)
|
||||
};
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<Entrypoint>> ListAsync(string tenantId, CancellationToken cancellationToken)
|
||||
{
|
||||
var items = _store.TryGetValue(tenantId, out var list)
|
||||
? list.OrderBy(e => e.Artifact, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(e => e.Service, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(e => e.Path, StringComparer.Ordinal)
|
||||
.ToList()
|
||||
: new List<Entrypoint>();
|
||||
|
||||
return Task.FromResult<IReadOnlyList<Entrypoint>>(items);
|
||||
}
|
||||
|
||||
public Task UpsertAsync(string tenantId, Entrypoint entrypoint, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = _store.GetOrAdd(tenantId, _ => new List<Entrypoint>());
|
||||
|
||||
var existingIndex = list.FindIndex(e =>
|
||||
e.Artifact.Equals(entrypoint.Artifact, StringComparison.OrdinalIgnoreCase) &&
|
||||
e.Service.Equals(entrypoint.Service, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (existingIndex >= 0)
|
||||
{
|
||||
list[existingIndex] = entrypoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(entrypoint);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.SbomService.Models;
|
||||
|
||||
namespace StellaOps.SbomService.Repositories;
|
||||
|
||||
internal sealed class MongoComponentLookupRepository : IComponentLookupRepository
|
||||
{
|
||||
private readonly IMongoCollection<ComponentLookupRecord> _collection;
|
||||
|
||||
public MongoComponentLookupRepository(IMongoDatabase database)
|
||||
{
|
||||
_collection = database.GetCollection<ComponentLookupRecord>("sbom_components");
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ComponentLookupRecord>> QueryAsync(ComponentLookupQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
var filter = Builders<ComponentLookupRecord>.Filter.Eq(c => c.Purl, query.Purl);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Artifact))
|
||||
{
|
||||
filter &= Builders<ComponentLookupRecord>.Filter.Eq(c => c.Artifact, query.Artifact);
|
||||
}
|
||||
|
||||
var results = await _collection
|
||||
.Find(filter)
|
||||
.Skip(query.Offset)
|
||||
.Limit(query.Limit)
|
||||
.Sort(Builders<ComponentLookupRecord>.Sort.Ascending(c => c.Artifact).Ascending(c => c.NeighborPurl))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
@@ -152,15 +152,15 @@ internal sealed class InMemorySbomQueryService : ISbomQueryService
|
||||
return new QueryResult<ComponentLookupResult>(cachedResult, true);
|
||||
}
|
||||
|
||||
var page = await _componentLookupRepository.QueryAsync(query, cancellationToken);
|
||||
|
||||
string? nextCursor = query.Offset + query.Limit < page.Count
|
||||
? (query.Offset + query.Limit).ToString(CultureInfo.InvariantCulture)
|
||||
: null;
|
||||
|
||||
var neighbors = page
|
||||
.Select(c => new ComponentNeighbor(c.NeighborPurl, c.Relationship, c.License, c.Scope, c.RuntimeFlag))
|
||||
.ToList();
|
||||
var (items, total) = await _componentLookupRepository.QueryAsync(query, cancellationToken);
|
||||
|
||||
string? nextCursor = query.Offset + query.Limit < total
|
||||
? (query.Offset + query.Limit).ToString(CultureInfo.InvariantCulture)
|
||||
: null;
|
||||
|
||||
var neighbors = items
|
||||
.Select(c => new ComponentNeighbor(c.NeighborPurl, c.Relationship, c.License, c.Scope, c.RuntimeFlag))
|
||||
.ToList();
|
||||
|
||||
var result = new ComponentLookupResult(query.Purl, query.Artifact, neighbors, nextCursor, CacheHint: "seeded");
|
||||
_cache[cacheKey] = result;
|
||||
|
||||
@@ -15,6 +15,5 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.24.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -3,3 +3,5 @@
|
||||
| Task ID | Status | Notes | Updated (UTC) |
|
||||
| --- | --- | --- | --- |
|
||||
| PREP-SBOM-CONSOLE-23-001-BUILD-TEST-FAILING-D | DONE | Offline feed cache + script added; see `docs/modules/sbomservice/offline-feed-plan.md`. | 2025-11-20 |
|
||||
| SBOM-SERVICE-21-002 | DONE | `sbom.version.created` events emitted via in-memory publisher; `/internal/sbom/events` + backfill wired; component lookup pagination cursor fixed; tests pass. | 2025-11-23 |
|
||||
| SBOM-SERVICE-21-003 | DONE | Entrypoint/service node API (`GET/POST /entrypoints`) with tenant guard, deterministic ordering, seeded data; tests added. | 2025-11-23 |
|
||||
|
||||
Reference in New Issue
Block a user