feat: Add Scanner CI runner and related artifacts
- Implemented `run-scanner-ci.sh` to build and run tests for the Scanner solution with a warmed NuGet cache. - Created `excititor-vex-traces.json` dashboard for monitoring Excititor VEX observations. - Added Docker Compose configuration for the OTLP span sink in `docker-compose.spansink.yml`. - Configured OpenTelemetry collector in `otel-spansink.yaml` to receive and process traces. - Developed `run-spansink.sh` script to run the OTLP span sink for Excititor traces. - Introduced `FileSystemRiskBundleObjectStore` for storing risk bundle artifacts in the filesystem. - Built `RiskBundleBuilder` for creating risk bundles with associated metadata and providers. - Established `RiskBundleJob` to execute the risk bundle creation and storage process. - Defined models for risk bundle inputs, entries, and manifests in `RiskBundleModels.cs`. - Implemented signing functionality for risk bundle manifests with `HmacRiskBundleManifestSigner`. - Created unit tests for `RiskBundleBuilder`, `RiskBundleJob`, and signing functionality to ensure correctness. - Added filesystem artifact reader tests to validate manifest parsing and artifact listing. - Included test manifests for egress scenarios in the task runner tests. - Developed timeline query service tests to verify tenant and event ID handling.
This commit is contained in:
@@ -13,6 +13,7 @@ Deterministic, reproducible benchmark for reachability analysis tools.
|
||||
- `benchmark/truth/` — ground-truth labels (hidden/internal split optional).
|
||||
- `benchmark/submissions/` — sample submissions and format reference.
|
||||
- `tools/scorer/` — `rb-score` CLI and tests.
|
||||
- `tools/build/` — `build_all.py` (run all cases) and `validate_builds.py` (run twice and compare hashes).
|
||||
- `baselines/` — reference runners (Semgrep, CodeQL, Stella) with normalized outputs.
|
||||
- `ci/` — deterministic CI workflows and scripts.
|
||||
- `website/` — static site (leaderboard/docs/downloads).
|
||||
|
||||
@@ -28,6 +28,8 @@ python -m pip install -r requirements.txt
|
||||
python -m unittest tests/test_scoring.py
|
||||
```
|
||||
|
||||
Explainability tiers (task 513-009) are covered by `test_explainability_tiers` in `tests/test_scoring.py`.
|
||||
|
||||
## Notes
|
||||
- Predictions for sinks not present in truth count as false positives (strict posture).
|
||||
- Truth sinks with label `unknown` are ignored for FN/FP counting.
|
||||
|
||||
Binary file not shown.
@@ -65,6 +65,37 @@ class TestScoring(unittest.TestCase):
|
||||
self.assertEqual(report.f1, 0.0)
|
||||
self.assertEqual(report.determinism_rate, 1.0)
|
||||
|
||||
def test_explainability_tiers(self):
|
||||
# Build synthetic predictions to exercise explainability tiers 0-3
|
||||
preds = [
|
||||
{"sink_id": "a", "prediction": "reachable", "explain": {}}, # tier 0
|
||||
{"sink_id": "b", "prediction": "reachable", "explain": {"path": ["f1", "f2"]}}, # tier 1
|
||||
{"sink_id": "c", "prediction": "reachable", "explain": {"entry": "E", "path": ["f1", "f2", "f3"]}}, # tier 2
|
||||
{"sink_id": "d", "prediction": "reachable", "explain": {"guards": ["x"], "path": ["f1", "f2"]}}, # tier 3
|
||||
]
|
||||
# Minimal truth to allow scoring
|
||||
truth_doc = {
|
||||
"version": "1.0.0",
|
||||
"cases": [
|
||||
{
|
||||
"case_id": "case-1",
|
||||
"sinks": [
|
||||
{"sink_id": s, "label": "reachable"} for s in ["a", "b", "c", "d"]
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
submission = {
|
||||
"version": "1.0.0",
|
||||
"tool": {"name": "t", "version": "1"},
|
||||
"run": {"platform": "x"},
|
||||
"cases": [{"case_id": "case-1", "sinks": preds}],
|
||||
}
|
||||
|
||||
report = rb_score.score(truth_doc, submission)
|
||||
# explainability average should be (0+1+2+3)/4 = 1.5
|
||||
self.assertAlmostEqual(report.explain_avg, 1.5, places=4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -68,8 +68,10 @@
|
||||
| 2025-11-29 | Completed ORCH-SVC-38-101: Implemented standardized event envelope (EventEnvelope, EventActor, EventJob, EventMetrics, EventNotifier, EventReplay, OrchestratorEventType) in Core/Domain/Events with idempotency keys, DSSE signing support, and channel routing. Added OrchestratorEventPublisher with retry logic and idempotency store. Implemented event publishing metrics. Created 86 comprehensive tests. Unblocked ORCH-SVC-41-101. | Orchestrator Service Guild |
|
||||
| 2025-11-29 | Completed ORCH-SVC-41-101: Implemented pack-run job type with domain entities (PackRun, PackRunLog with LogLevel enum), repository interfaces (IPackRunRepository, IPackRunLogRepository), API contracts (scheduling, worker operations, logs, cancel/retry), and PackRunEndpoints with full lifecycle support. Added pack-run metrics to OrchestratorMetrics. Created 56 comprehensive tests. Unblocked ORCH-SVC-42-101 for log streaming. | Orchestrator Service Guild |
|
||||
| 2025-11-30 | ORCH-SVC-42-101 DONE: added pack run Postgres persistence + migration 006, DI registration, pack-run endpoint mapping; implemented SSE stream `/api/v1/orchestrator/stream/pack-runs/{id}` with heartbeats/timeouts + log batches; added manifest endpoint and quota enforcement on scheduling; emitted notifier events; added PackRunStreamCoordinator unit test and ran `dotnet test ... --filter PackRunStreamCoordinatorTests` (pass). | Implementer |
|
||||
| 2025-11-30 | ORCH-SVC-42-101 WebSocket: added `/api/v1/orchestrator/stream/pack-runs/{id}/ws`, websocket support in coordinator (initial/heartbeat/logs/statusChanged/completed/timeout/notFound), and unit test covering terminal-run websocket flow. Enabled `UseWebSockets()` in WebService. | Implementer |
|
||||
| 2025-11-30 | ORCH-TEN-48-001 DONE: job contracts now expose tenant_id/project_id; TenantResolver already enforced; DB session context remains per-tenant via OrchestratorDataSource. No further blocking items. | Implementer |
|
||||
| 2025-11-30 | Enforced ProjectId requirement on pack-run scheduling (tenant header already required) to align with ORCH-TEN-48-001 tenant isolation safeguards. | Implementer |
|
||||
| 2025-11-30 | Updated `src/Orchestrator/StellaOps.Orchestrator/AGENTS.md` to record the `projectId` requirement and API contract guardrails for pack-run scheduling/streams. | Implementer |
|
||||
| 2025-11-30 | Normalized Decisions & Risks to reflect completed tenant enforcement and migration 006 requirement. | Implementer |
|
||||
|
||||
|
||||
@@ -78,5 +80,6 @@
|
||||
- Streaming/log APIs unblock Authority packs; notifier events must carry provenance metadata.
|
||||
- Tenant metadata enforcement (ORCH-TEN-48-001) complete (2025-11-30): job contracts expose tenant/project; TenantResolver + per-tenant session context enforced; downstream consumers must align.
|
||||
- ORCH-SVC-38/41/42 complete; migration 006 (pack_runs) is required for upgrade rollout.
|
||||
- AGENTS charter updated (2025-11-30) to mandate `projectId` on pack-run scheduling and keep OpenAPI/SDK samples aligned; downstream clients must send tenant + project scope.
|
||||
## Next Checkpoints
|
||||
- Coordinate migration 006 rollout across environments; verify pack-run SSE demo with Authority/Notifications (target week of 2025-12-02).
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
## Dependencies & Concurrency
|
||||
- Upstream: Sprint 120.A (AirGap), 130.A (Scanner), 140.A (Graph).
|
||||
- Concurrency: execute tasks in table order; RUN/OAS/OBS chains must respect dependencies.
|
||||
- Concurrency: execute tasks in table order; AIRGAP/OAS/OBS chains await control-flow addendum and timeline schema before starting.
|
||||
|
||||
## Documentation Prerequisites
|
||||
- docs/README.md
|
||||
@@ -19,7 +19,7 @@
|
||||
## Delivery Tracker
|
||||
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | TASKRUN-AIRGAP-56-001 | BLOCKED (2025-11-30) | Waiting on sealed-mode allowlist contract from AirGap Policy Guild (Action Tracker). | Task Runner Guild · AirGap Policy Guild | Enforce plan-time validation rejecting non-allowlisted network calls in sealed mode; surface remediation errors. |
|
||||
| 1 | TASKRUN-AIRGAP-56-001 | DONE (2025-11-30) | Delivered sealed-mode plan validation via AirGap egress policy binding in WebService. | Task Runner Guild · AirGap Policy Guild | Enforce plan-time validation rejecting non-allowlisted network calls in sealed mode; surface remediation errors. |
|
||||
| 2 | TASKRUN-AIRGAP-56-002 | BLOCKED (2025-11-30) | Depends on 56-001. | Task Runner Guild · AirGap Importer Guild | Add helper steps for bundle ingestion (checksum verification, staging to object store) with deterministic outputs. |
|
||||
| 3 | TASKRUN-AIRGAP-57-001 | BLOCKED (2025-11-30) | Depends on 56-002. | Task Runner Guild · AirGap Controller Guild | Refuse to execute plans when environment sealed=false but declared sealed install; emit advisory timeline events. |
|
||||
| 4 | TASKRUN-AIRGAP-58-001 | BLOCKED (2025-11-30) | Depends on 57-001. | Task Runner Guild · Evidence Locker Guild | Capture bundle import job transcripts, hashed inputs/outputs into portable evidence bundles. |
|
||||
@@ -34,10 +34,10 @@
|
||||
| 13 | TASKRUN-OBS-53-001 | BLOCKED (2025-11-25) | Depends on 52-001. | Task Runner Guild · Evidence Locker Guild | Capture step transcripts, artifact manifests, environment digests, policy approvals into evidence locker snapshots; ensure redaction + hash chain. Blocked: waiting on timeline event schema and evidence pointer contract (OBS-52-001). |
|
||||
|
||||
## Wave Coordination
|
||||
- Single wave; no parallel work until TASKRUN-41-001 contracts land. Downstream AIRGAP/OAS/OBS chains remain paused to avoid rework.
|
||||
- Single wave; parallelism paused until TaskPack control-flow addendum and timeline schema publish.
|
||||
|
||||
## Wave Detail Snapshots
|
||||
- Wave 1 (RUN/OAS/OBS/AIRGAP): waiting on TaskPack control-flow + policy-gate contract and timeline schema drops; start immediately after TASKRUN-41-001 unblocks.
|
||||
- Wave 1 (RUN/OAS/OBS/AIRGAP): waiting on TaskPack control-flow addendum and timeline schema; start once both land.
|
||||
|
||||
## Interlocks
|
||||
- TaskPack DSL control-flow and policy-evaluation contract (from Sprints 120/130/140).
|
||||
@@ -53,13 +53,13 @@
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-30 | Correction: TASKRUN-41-001 remains BLOCKED pending TaskRunner architecture/API contract; downstream AIRGAP/OAS/OBS chains stay blocked. | Project Mgmt |
|
||||
| 2025-11-30 | Refreshed dependencies: AIRGAP chain waiting on sealed-mode allowlist (Action Tracker); OAS chain waiting on control-flow/policy addendum due 2025-12-05. | Project Mgmt |
|
||||
| 2025-11-30 | TASKRUN-41-001 delivered in blockers sprint; run API/storage/provenance contract now active (see `docs/modules/taskrunner/architecture.md`). | Task Runner Guild |
|
||||
| 2025-11-30 | Delivered TASKRUN-AIRGAP-56-001: WebService planner enforces sealed-mode allowlist with remediation messaging. | Task Runner Guild |
|
||||
| 2025-11-30 | Updated dependencies: AIRGAP chain blocked on helper design (56-002) and downstream evidence work; OAS chain blocked pending TaskPack control-flow addendum (due 2025-12-05); OBS chain blocked on timeline/evidence schema; 41-001 no longer a blocker. | Project Mgmt |
|
||||
| 2025-11-30 | Added Wave Coordination, Interlocks, and Action Tracker sections per docs/implplan/AGENTS.md template; no scope change. | Project Mgmt |
|
||||
| 2025-11-30 | Synced TaskRunner task rows in tasks-all/archived indexes to reflect canonical sprint name and BLOCKED status where applicable. | Project Mgmt |
|
||||
| 2025-11-30 | Refreshed Decisions & Risks with risk table and aligned checkpoint wording. | Project Mgmt |
|
||||
| 2025-11-30 | Replaced legacy file `SPRINT_157_taskrunner_i.md` with stub redirecting to this canonical sprint. | Project Mgmt |
|
||||
| 2025-11-30 | Marked TASKRUN-AIRGAP-56-001/002/57-001/58-001 and TASKRUN-OAS-61-001/61-002/62-001/63-001 BLOCKED pending TASKRUN-41-001 contract. | Task Runner Guild |
|
||||
| 2025-11-30 | Synced `docs/implplan/tasks-all.md` and sprint names for TASKRUN-* chain; statuses now reflect BLOCKED across AIRGAP/OAS/OBS/TEN. | Project Mgmt |
|
||||
| 2025-11-25 | TASKRUN-OBS-52-001 and TASKRUN-OBS-53-001 marked BLOCKED: timeline event schema and evidence-pointer contract not published; cannot emit pack timeline events or evidence snapshots yet. | Task Runner Guild |
|
||||
| 2025-11-25 | TASKRUN-42-001 marked BLOCKED: loop/conditional semantics and policy-gate evaluation contract not published; cannot update execution engine/simulation without spec. | Task Runner Guild |
|
||||
| 2025-11-25 | Implemented metrics for step latency, retries, running steps, and queue depth; wired into telemetry; marked TASKRUN-OBS-51-001 DONE. | Task Runner Guild |
|
||||
@@ -72,18 +72,18 @@
|
||||
| 2025-11-04 | CLI command `stella task-runner simulate` wired to new endpoint with JSON/table output modes. | Task Runner Guild |
|
||||
| 2025-11-19 | Normalized sprint to standard template and renamed from `SPRINT_157_taskrunner_i.md` to `SPRINT_0157_0001_0001_taskrunner_i.md`; content preserved. | Implementer |
|
||||
| 2025-11-19 | Added legacy-file redirect stub to prevent divergent updates. | Implementer |
|
||||
| 2025-11-30 | Clarified earlier note: TaskRunner contract not yet published; blockers sprint still carries TASKRUN-41-001 as BLOCKED. | Project Mgmt |
|
||||
| 2025-11-30 | TaskRunner contract landed via product advisory 2025-11-29; blockers sprint now tracks TASKRUN-41-001 as delivered. Downstream tasks align to new architecture doc. | Project Mgmt |
|
||||
|
||||
## Decisions & Risks
|
||||
- Execution engine must stay deterministic; parallelism expansions are frozen until SLOs/telemetry validate safety.
|
||||
- Air-gap enforcement remains strict until sealed-mode rules are published; helper steps stay frozen.
|
||||
- Air-gap enforcement in place (56-001 delivered); remaining AIRGAP-56-002/57-001/58-001 wait on ingest/helper specs.
|
||||
- Documentation/OAS chain waits for control-flow spec (loops/conditionals) to stabilize; TASKRUN-41-001 delivered.
|
||||
|
||||
| Risk | Impact | Mitigation |
|
||||
| --- | --- | --- |
|
||||
| TaskRunner control-flow/policy-gate spec partially missing (loops/conditionals) | Blocks TASKRUN-42-001 and OAS 61-001..63-001. | Track via Action Tracker; hold parallelism changes until spec addendum lands; keep scope frozen. |
|
||||
| Timeline event schema absent | Blocks TASKRUN-OBS-52-001/53-001 evidence timelines. | Coordinate with Evidence Locker Guild; Action Tracker follow-up; hold OBS rollout. |
|
||||
| Air-gap sealed-mode rules not finalized | Blocks TASKRUN-AIRGAP-56..58 chain. | Start once 41-001 + policy rules drop; keep sealed-mode defaults enforced. |
|
||||
| Air-gap helper specs pending | Blocks TASKRUN-AIRGAP-56-002/57-001/58-001 (ingest helpers, sealed install enforcement, evidence bundles). | Await AirGap Importer/Controller inputs; keep sealed-mode validation enforced for plans. |
|
||||
|
||||
## Upcoming Checkpoints
|
||||
- 2025-12-04 · Control-flow/policy-gate spec addendum review; go/no-go for TASKRUN-42-001 start.
|
||||
|
||||
@@ -67,3 +67,5 @@
|
||||
| 2025-11-19 | Added legacy-file redirect stub to avoid divergent updates. | Implementer |
|
||||
| 2025-11-30 | Normalized to full docs/implplan template (wave detail, action tracker, risk table); converted dependency arrows to ASCII. | Project Mgmt |
|
||||
| 2025-11-30 | Marked OBS-54-001, OBS-55-001, and TEN-48-001 BLOCKED pending Sprint 0157 close-out (timeline/attestation schema) and tenancy policy; updated interlocks/action tracker. | Project Mgmt |
|
||||
| 2025-11-30 | Synced `tasks-all.md` entries to BLOCKED status and canonical sprint filename. | Project Mgmt |
|
||||
| 2025-11-30 | Propagated TaskRunner II blockers into `docs/implplan/blocked_tree.md` for cross-sprint visibility. | Project Mgmt |
|
||||
|
||||
@@ -72,14 +72,14 @@
|
||||
#### ExportCenter task snapshot (2025-11-12)
|
||||
| Task ID | Scope | State | Notes / Owners |
|
||||
| --- | --- | --- | --- |
|
||||
| DVOFF-64-002 | DevPortal bundle verification CLI | TODO | DevPortal Offline + AirGap Controller Guilds |
|
||||
| EXPORT-AIRGAP-56-001/002 | Mirror bundle + bootstrap pack profiles | TODO | Exporter + Mirror Creator + DevOps Guilds |
|
||||
| EXPORT-AIRGAP-57-001 | Portable evidence export mode | TODO | Exporter Service + Evidence Locker Guild |
|
||||
| EXPORT-AIRGAP-58-001 | Notifications for portable export | TODO | Exporter Service + Notifications Guild |
|
||||
| EXPORT-ATTEST-74-001/002 | Attestation bundle job + CI integration | TODO | Attestation Bundle + Exporter Guilds |
|
||||
| EXPORT-ATTEST-75-001/002 | CLI verify/import + offline kit integration | TODO | Attestation Bundle + CLI + Exporter Guilds |
|
||||
| EXPORT-OAS-61/62/63 | OpenAPI refresh, discovery, SDK + deprecation headers | TODO | Exporter Service + API Governance + SDK Guilds |
|
||||
| EXPORT-CRYPTO-90-001 | Sovereign crypto routing | TODO | Exporter Service + Security Guilds |
|
||||
| DVOFF-64-002 | DevPortal bundle verification CLI | BLOCKED (2025-11-30) | DevPortal Offline + AirGap Controller Guilds |
|
||||
| EXPORT-AIRGAP-56-001/002 | Mirror bundle + bootstrap pack profiles | BLOCKED (2025-11-30) | Exporter + Mirror Creator + DevOps Guilds |
|
||||
| EXPORT-AIRGAP-57-001 | Portable evidence export mode | BLOCKED (2025-11-30) | Exporter Service + Evidence Locker Guild |
|
||||
| EXPORT-AIRGAP-58-001 | Notifications for portable export | BLOCKED (2025-11-30) | Exporter Service + Notifications Guild |
|
||||
| EXPORT-ATTEST-74-001/002 | Attestation bundle job + CI integration | BLOCKED (2025-11-30) | Attestation Bundle + Exporter Guilds |
|
||||
| EXPORT-ATTEST-75-001/002 | CLI verify/import + offline kit integration | BLOCKED (2025-11-30) | Attestation Bundle + CLI + Exporter Guilds |
|
||||
| EXPORT-OAS-61/62/63 | OpenAPI refresh, discovery, SDK + deprecation headers | BLOCKED (2025-11-30) | Exporter Service + API Governance + SDK Guilds |
|
||||
| EXPORT-CRYPTO-90-001 | Sovereign crypto routing | BLOCKED (2025-11-30) | Exporter Service + Security Guilds |
|
||||
|
||||
### 160.C TimelineIndexer
|
||||
- Detail tracker: [SPRINT_165_timelineindexer.md](./SPRINT_165_timelineindexer.md) covering TIMELINE-OBS-52-001…004 and TIMELINE-OBS-53-001.
|
||||
@@ -95,11 +95,11 @@
|
||||
#### TimelineIndexer task snapshot (2025-11-12)
|
||||
| Task ID | Scope | State | Notes / Owners |
|
||||
| --- | --- | --- | --- |
|
||||
| TIMELINE-OBS-52-001 | Service bootstrap + Postgres migrations/RLS | TODO | Timeline Indexer Guild |
|
||||
| TIMELINE-OBS-52-002 | Event ingestion pipeline + metrics | TODO | Timeline Indexer Guild |
|
||||
| TIMELINE-OBS-52-003 | REST/gRPC APIs + OpenAPI contracts | TODO | Timeline Indexer Guild |
|
||||
| TIMELINE-OBS-52-004 | RLS policies, audit logging, legal hold tests | TODO | Timeline Indexer + Security Guilds |
|
||||
| TIMELINE-OBS-53-001 | Evidence linkage endpoint | TODO | Timeline Indexer + Evidence Locker Guilds |
|
||||
| TIMELINE-OBS-52-001 | Service bootstrap + Postgres migrations/RLS | BLOCKED (2025-11-30) | Timeline Indexer Guild |
|
||||
| TIMELINE-OBS-52-002 | Event ingestion pipeline + metrics | BLOCKED (2025-11-30) | Timeline Indexer Guild |
|
||||
| TIMELINE-OBS-52-003 | REST/gRPC APIs + OpenAPI contracts | BLOCKED (2025-11-30) | Timeline Indexer Guild |
|
||||
| TIMELINE-OBS-52-004 | RLS policies, audit logging, legal hold tests | BLOCKED (2025-11-30) | Timeline Indexer + Security Guilds |
|
||||
| TIMELINE-OBS-53-001 | Evidence linkage endpoint | BLOCKED (2025-11-30) | Timeline Indexer + Evidence Locker Guilds |
|
||||
|
||||
## Interlocks & Readiness Signals
|
||||
| Dependency | Owner / Source | Impacts | Status / Next signal |
|
||||
@@ -160,6 +160,7 @@
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-30 | Marked ExportCenter and TimelineIndexer snapshot tasks BLOCKED pending AdvisoryAI + Orchestrator schemas and EvidenceLocker digest; no unblocked work in wave 160. | Implementer |
|
||||
| 2025-11-20 | Confirmed PREP-ORCHESTRATOR-NOTIFICATIONS-SCHEMA-HANDOF and PREP-ESCALATION-FOLLOW-UP-ADVISORYAI-ORCHESTR still unclaimed; moved both to DOING to proceed with Wave 150/140 escalations. | Planning |
|
||||
| 2025-11-20 | Published prep artefacts for P1–P3: security coordination (`docs/modules/evidence-locker/prep/2025-11-20-security-coordination.md`), orchestrator/notifications handoff (`docs/events/prep/2025-11-20-orchestrator-notifications-schema-handoff.md`), and escalation follow-up (`docs/events/prep/2025-11-20-advisoryai-orchestrator-followup.md`). Marked P1–P3 DONE. | Implementer |
|
||||
| 2025-11-19 | Assigned PREP owners/dates; see Delivery Tracker. | Planning |
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
| 9 | EXPORT-SVC-37-004 | BLOCKED (2025-11-30) | BLOCKED by 37-003; verification API requires scheduled run outputs. | Exporter Service Guild | Verification API to stream manifests/hashes, compute hash+signature checks, return attest status for CLI/UI. |
|
||||
| 10 | EXPORT-SVC-43-001 | BLOCKED (2025-11-30) | BLOCKED by 37-004; pack-run integration waits on verification API. | Exporter Service Guild | Integrate pack run manifests/artifacts into export bundles and CLI verification; expose provenance links. |
|
||||
| 11 | EXPORT-TEN-48-001 | BLOCKED (2025-11-30) | BLOCKED until Export API (35-006) stabilizes; tenant prefixes require finalized routes. | Exporter Service Guild | Prefix artifacts/manifests with tenant/project, enforce scope checks, prevent cross-tenant exports unless whitelisted; update provenance. |
|
||||
| 12 | RISK-BUNDLE-69-001 | BLOCKED (2025-11-30) | BLOCKED pending Sprint 0163 risk prep artefacts/provider list. | Risk Bundle Export Guild · Risk Engine Guild (`src/ExportCenter/StellaOps.ExportCenter.RiskBundles`) | Implement `stella export risk-bundle` job producing tarball with provider datasets, manifests, DSSE signatures. |
|
||||
| 12 | RISK-BUNDLE-69-001 | DOING (2025-11-30) | Scaffolded builder/signing/object-store + unit tests; awaiting Sprint 0163 risk prep artefacts to wire real providers/worker integration. | Risk Bundle Export Guild · Risk Engine Guild (`src/ExportCenter/StellaOps.ExportCenter.RiskBundles`) | Implement `stella export risk-bundle` job producing tarball with provider datasets, manifests, DSSE signatures. |
|
||||
| 13 | RISK-BUNDLE-69-002 | BLOCKED (2025-11-30) | BLOCKED by 69-001 deliverables. | Risk Bundle Export Guild · DevOps Guild | Integrate bundle job into CI/offline kit pipelines with checksum publication. |
|
||||
| 14 | RISK-BUNDLE-70-001 | BLOCKED (2025-11-30) | BLOCKED by 69-002; verification inputs not available. | Risk Bundle Export Guild · CLI Guild | Provide CLI `stella risk bundle verify` command to validate bundles before import. |
|
||||
| 15 | RISK-BUNDLE-70-002 | BLOCKED (2025-11-30) | BLOCKED by 70-001; doc content waits on verification CLI behavior. | Risk Bundle Export Guild · Docs Guild | Publish `/docs/airgap/risk-bundles.md` covering build/import/verification workflows. |
|
||||
@@ -61,7 +61,7 @@
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | Confirm ExportCenter II contracts delivered (planner/run schema, pack manifests) | Exporter Service Guild | 2025-12-02 | OPEN |
|
||||
| 2 | Provide KMS envelope-handling pattern for age/AES-GCM encryption | Crypto/Platform Guild | 2025-12-04 | DONE (2025-11-30) — see `docs/modules/export-center/operations/kms-envelope-pattern.md` |
|
||||
| 3 | Publish risk-bundle provider matrix and signing baseline for tasks 69/70 | Risk Bundle Export Guild | 2025-12-02 | OPEN |
|
||||
| 3 | Publish risk-bundle provider matrix and signing baseline for tasks 69/70 | Risk Bundle Export Guild | 2025-12-02 | DONE (2025-11-30) — see `docs/modules/export-center/operations/risk-bundle-provider-matrix.md` |
|
||||
| 4 | Author `src/ExportCenter/AGENTS.md` aligned to module dossier and sprint scope | Project/Tech Management | 2025-12-01 | DONE (2025-11-30) |
|
||||
|
||||
## Decisions & Risks
|
||||
@@ -70,7 +70,7 @@
|
||||
| ExportCenter II artifacts not yet available. | Blocks 35/36/37 chain. | Track delivery in Action 1; keep tasks BLOCKED until API/OAS + adapter schemas are published. | OPEN |
|
||||
| Tenant scoping must stay deterministic/offline-safe. | Potential cross-tenant leakage. | Enforce scope prefixes and reuse Authority/Orchestrator tenant model; add tests in TEN-48-001. | OPEN |
|
||||
| Encryption/KMS path for bundles. | Could stall 37-002 rollout. | Envelope pattern captured in `docs/modules/export-center/operations/kms-envelope-pattern.md`; adopt in implementation. | CLOSED |
|
||||
| Risk bundle provider matrix/signing baseline missing. | Blocks 69/70 chain. | Capture provider list + signing posture in Action 3; keep tasks BLOCKED until published. | OPEN |
|
||||
| Risk bundle provider matrix/signing baseline missing. | Blocks 69/70 chain. | Matrix published at `docs/modules/export-center/operations/risk-bundle-provider-matrix.md`; proceed to implement bundle job + CLI verify. | CLOSED |
|
||||
| ExportCenter AGENTS charter missing. | Blocks starting engineering work per charter. | AGENTS added on 2025-11-30; see `src/ExportCenter/AGENTS.md`. | CLOSED |
|
||||
|
||||
### Risk table
|
||||
@@ -79,7 +79,7 @@
|
||||
| Sprint 0163 deliverables slip (API/OAS, planner schema, Trivy adapters). | High | Action 1 to track; hold Wave 1 tasks until contracts land. Owner: Exporter Service Guild. |
|
||||
| Tenant scope misalignment with Authority/Orchestrator. | Medium | Validate prefixes once API routes drop; add integration tests in TEN-48-001. Owner: Exporter Service Guild. |
|
||||
| Encryption provider guidance delayed. | Low | Mitigated by `docs/modules/export-center/operations/kms-envelope-pattern.md`; adopt pattern in 37-002. Owner: Crypto/Platform Guild. |
|
||||
| Risk bundle provider matrix/signing posture not published. | Medium | Action 3 to gather matrix; keep Wave 3 blocked until received. Owner: Risk Bundle Export Guild. |
|
||||
| Risk bundle provider matrix/signing posture not published. | Low | Matrix published (`operations/risk-bundle-provider-matrix.md`); update worker + CLI to enforce. Owner: Risk Bundle Export Guild. |
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
@@ -92,3 +92,5 @@
|
||||
| 2025-11-30 | Corrected ExportCenter AGENTS status (file present); removed erroneous blocker/action. | Implementer |
|
||||
| 2025-11-30 | Set Delivery Tracker tasks to BLOCKED pending Sprint 0163 artefacts; expanded interlocks/action tracker for gating signals. | Implementer |
|
||||
| 2025-11-30 | Added KMS envelope-handling pattern doc and closed Action 2; encryption risk now covered. | Implementer |
|
||||
| 2025-11-30 | Added risk-bundle provider matrix/signing baseline doc and closed Action 3; Wave 3 still waits on Sprint 0163 outputs. | Implementer |
|
||||
| 2025-11-30 | Implemented risk-bundle builder/signing/object store scaffolding and unit tests; set RISK-BUNDLE-69-001 to DOING pending upstream provider artefacts; `dotnet test --filter RiskBundle` passing. | Implementer |
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
| 2025-11-30 | Normalized sprint to AGENTS template (Wave/Interlocks/Action tracker) while keeping prior content intact. | Implementer |
|
||||
| 2025-11-30 | Completed TIMELINE-OBS-52-001: added Postgres schema/RLS migrations, DataSource + migration runner wiring; test run attempted for module but cancelled due to long solution restore—manual rerun needed. | Implementer |
|
||||
| 2025-11-30 | Located orchestrator event envelope draft and Evidence Locker bundle contract; unblocked migrations and RLS design for TIMELINE-OBS-52-001 and started implementation. | Implementer |
|
||||
| 2025-11-30 | Built TimelineIndexer solution successfully after query options fix; `dotnet test` on TimelineIndexer.Tests now passing (9 tests). | Implementer |
|
||||
| 2025-11-30 | Re-checked for orchestrator/notification schema and EvidenceLocker bundle digest; none landed in `docs/events` or `docs/modules/evidence-locker`, so keeping all tasks blocked. | Implementer |
|
||||
| 2025-11-25 | Marked TIMELINE-OBS-52-001 BLOCKED: missing orchestrator/notification event schema and EvidenceLocker digest schema prevent drafting migrations/RLS. | Implementer |
|
||||
| 2025-11-12 | Captured task snapshot and blockers; waiting on orchestrator/notifications schema and EvidenceLocker digest schema. | Planning |
|
||||
|
||||
@@ -23,59 +23,60 @@
|
||||
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | SCAN-REPLAY-186-001 | BLOCKED (2025-11-26) | Await pipeline inputs. | Scanner Guild (`src/Scanner/StellaOps.Scanner.WebService`, docs) | Implement `record` mode (manifest assembly, policy/feed/tool hash capture, CAS uploads); doc workflow referencing replay doc §6. |
|
||||
| 2 | SCAN-REPLAY-186-002 | TODO | Depends on 186-001. | Scanner Guild | Update Worker analyzers to consume sealed input bundles, enforce deterministic ordering, contribute Merkle metadata; add `docs/modules/scanner/deterministic-execution.md`. |
|
||||
| 3 | SIGN-REPLAY-186-003 | TODO | Depends on 186-001/002. | Signing Guild (`src/Signer`, `src/Authority`) | Extend Signer/Authority DSSE flows to cover replay manifests/bundles; refresh signer/authority architecture docs referencing replay doc §5. |
|
||||
| 2 | SCAN-REPLAY-186-002 | BLOCKED (2025-11-30) | BLOCKED by 186-001 pipeline contract. | Scanner Guild | Update Worker analyzers to consume sealed input bundles, enforce deterministic ordering, contribute Merkle metadata; add `docs/modules/scanner/deterministic-execution.md`. |
|
||||
| 3 | SIGN-REPLAY-186-003 | BLOCKED (2025-11-30) | BLOCKED by 186-001/002. | Signing Guild (`src/Signer`, `src/Authority`) | Extend Signer/Authority DSSE flows to cover replay manifests/bundles; refresh signer/authority architecture docs referencing replay doc §5. |
|
||||
| 4 | SIGN-CORE-186-004 | DONE (2025-11-26) | CryptoDsseSigner implemented with ICryptoProviderRegistry integration. | Signing Guild | Replace HMAC demo in Signer with StellaOps.Cryptography providers (keyless + KMS); provider selection, key loading, cosign-compatible DSSE output. |
|
||||
| 5 | SIGN-CORE-186-005 | DONE (2025-11-26) | SignerStatementBuilder refactored with StellaOps predicate types and CanonicalJson from Provenance library. | Signing Guild | Refactor `SignerStatementBuilder` to support StellaOps predicate types and delegate canonicalisation to Provenance library when available. |
|
||||
| 6 | SIGN-TEST-186-006 | DONE (2025-11-26) | Integration tests upgraded with real crypto providers and fixture predicates. | Signing Guild · QA Guild | Upgrade signer integration tests to real crypto abstraction + fixture predicates (promotion, SBOM, replay); deterministic test data. |
|
||||
| 7 | AUTH-VERIFY-186-007 | TODO | After 186-003. | Authority Guild · Provenance Guild | Authority-side helper/service validating DSSE signatures and Rekor proofs for promotion attestations using trusted checkpoints; offline audit flow. |
|
||||
| 7 | AUTH-VERIFY-186-007 | BLOCKED (2025-11-30) | BLOCKED by 186-003. | Authority Guild · Provenance Guild | Authority-side helper/service validating DSSE signatures and Rekor proofs for promotion attestations using trusted checkpoints; offline audit flow. |
|
||||
| 8 | SCAN-DETER-186-008 | DOING (2025-11-26) | Parallel with 186-002. | Scanner Guild | Add deterministic execution switches (fixed clock, RNG seed, concurrency cap, feed/policy pins, log filtering) via CLI/env/config. |
|
||||
| 9 | SCAN-DETER-186-009 | TODO | Depends on 186-008. | Scanner Guild · QA Guild | Determinism harness to replay scans, canonicalise outputs, record hash matrices (`docs/modules/scanner/determinism-score.md`). |
|
||||
| 10 | SCAN-DETER-186-010 | TODO | Depends on 186-009. | Scanner Guild · Export Center Guild | Emit/publish `determinism.json` with scores/hashes/diffs alongside each scanner release via CAS/object storage; document in release guide. |
|
||||
| 9 | SCAN-DETER-186-009 | BLOCKED (2025-11-30) | BLOCKED by 186-008 completion. | Scanner Guild · QA Guild | Determinism harness to replay scans, canonicalise outputs, record hash matrices (`docs/modules/scanner/determinism-score.md`). |
|
||||
| 10 | SCAN-DETER-186-010 | BLOCKED (2025-11-30) | BLOCKED by 186-009. | Scanner Guild · Export Center Guild | Emit/publish `determinism.json` with scores/hashes/diffs alongside each scanner release via CAS/object storage; document in release guide. |
|
||||
| 11 | SCAN-ENTROPY-186-011 | DONE (2025-11-26) | Add core entropy calculator & tests; integrate into worker pipeline next. | Scanner Guild | Entropy analysis for ELF/PE/Mach-O/opaque blobs (sliding-window metrics, section heuristics); record offsets/hints (see `docs/modules/scanner/entropy.md`). |
|
||||
| 12 | SCAN-ENTROPY-186-012 | BLOCKED (2025-11-26) | Waiting on worker→webservice entropy delivery contract and upstream Policy build fix. | Scanner Guild · Provenance Guild | Generate `entropy.report.json`, image-level penalties; attach evidence to manifests/attestations; expose ratios for policy engines. |
|
||||
| 13 | SCAN-CACHE-186-013 | BLOCKED (2025-11-26) | Waiting on cache key/contract (tool/feed/policy IDs, manifest hash) and DSSE validation flow definition between Worker ↔ WebService. | Scanner Guild | Layer-level SBOM/VEX cache keyed by layer digest + manifest hash + tool/feed/policy IDs; re-verify DSSE on cache hits; persist indexes; document referencing 16-Nov-2026 advisory. |
|
||||
| 14 | SCAN-DIFF-CLI-186-014 | TODO | Depends on replay+cache scaffolding. | Scanner Guild · CLI Guild | Deterministic diff-aware rescan workflow (`scan.lock.json`, JSON Patch diffs, CLI verbs `stella scan --emit-diff` / `stella diff`); replayable tests; docs. |
|
||||
| 15 | SBOM-BRIDGE-186-015 | TODO | Parallel; coordinate with Sbomer. | Sbomer Guild · Scanner Guild | Establish SPDX 3.0.1 as canonical SBOM persistence; deterministic CycloneDX 1.6 exporter; map table/library; wire snapshot hashes into replay manifests. See subtasks 15a-15f below. |
|
||||
| 15a | SPDX-MODEL-186-015A | TODO | Foundational for SBOM-BRIDGE. | Sbomer Guild (`src/Sbomer/StellaOps.Sbomer.Spdx`) | Implement SPDX 3.0.1 data model: `SpdxDocument`, `Package`, `File`, `Snippet`, `Relationship`, `ExternalRef`, `Annotation`. Use SPDX 3.0.1 JSON-LD schema. |
|
||||
| 15b | SPDX-SERIAL-186-015B | TODO | Depends on 15a. | Sbomer Guild | Implement SPDX 3.0.1 serializers/deserializers: JSON-LD (canonical), Tag-Value (legacy compat), RDF/XML (optional). Ensure deterministic output ordering. |
|
||||
| 15c | CDX-MAP-186-015C | TODO | Depends on 15a. | Sbomer Guild (`src/Sbomer/StellaOps.Sbomer.CycloneDx`) | Build bidirectional SPDX 3.0.1 ↔ CycloneDX 1.6 mapping table: component→package, dependency→relationship, vulnerability→advisory. Document loss-of-fidelity cases. |
|
||||
| 15d | SBOM-STORE-186-015D | TODO | Depends on 15a. | Sbomer Guild · Scanner Guild | MongoDB/CAS persistence for SPDX 3.0.1 documents; indexed by artifact digest, component PURL, document SPDXID. Enable efficient lookup for VEX correlation. |
|
||||
| 15e | SBOM-HASH-186-015E | TODO | Depends on 15b, 15d. | Sbomer Guild | Implement SBOM content hash computation: canonical JSON → BLAKE3 hash; store as `sbom_content_hash` in replay manifests; enable deduplication. |
|
||||
| 15f | SBOM-TESTS-186-015F | TODO | Depends on 15a-15e. | Sbomer Guild · QA Guild (`src/Sbomer/__Tests`) | Roundtrip tests: SPDX→CDX→SPDX with diff assertion; determinism tests (same input → same hash); SPDX 3.0.1 spec compliance validation. |
|
||||
| 16 | DOCS-REPLAY-186-004 | TODO | After replay schema settled. | Docs Guild | Author `docs/replay/TEST_STRATEGY.md` (golden replay, feed drift, tool upgrade); link from replay docs and Scanner architecture. |
|
||||
| 17 | DOCS-SBOM-186-017 | TODO | Depends on 15a-15f. | Docs Guild (`docs/modules/sbomer/spdx-3.md`) | Document SPDX 3.0.1 implementation: data model, serialization formats, CDX mapping table, storage schema, hash computation, migration guide from SPDX 2.3. |
|
||||
| 14 | SCAN-DIFF-CLI-186-014 | BLOCKED (2025-11-30) | BLOCKED by replay + cache scaffolding (186-001, 186-013). | Scanner Guild · CLI Guild | Deterministic diff-aware rescan workflow (`scan.lock.json`, JSON Patch diffs, CLI verbs `stella scan --emit-diff` / `stella diff`); replayable tests; docs. |
|
||||
| 15 | SBOM-BRIDGE-186-015 | BLOCKED (2025-11-30) | Working directory scope missing `src/Sbomer`; needs PM to extend scope or move tasks to Sbomer sprint. | Sbomer Guild · Scanner Guild | Establish SPDX 3.0.1 as canonical SBOM persistence; deterministic CycloneDX 1.6 exporter; map table/library; wire snapshot hashes into replay manifests. See subtasks 15a-15f below. |
|
||||
| 15a | SPDX-MODEL-186-015A | BLOCKED (2025-11-30) | BLOCKED until sprint scope includes `src/Sbomer` and SPDX 3.0.1 review scheduled. | Sbomer Guild (`src/Sbomer/StellaOps.Sbomer.Spdx`) | Implement SPDX 3.0.1 data model: `SpdxDocument`, `Package`, `File`, `Snippet`, `Relationship`, `ExternalRef`, `Annotation`. Use SPDX 3.0.1 JSON-LD schema. |
|
||||
| 15b | SPDX-SERIAL-186-015B | BLOCKED (2025-11-30) | BLOCKED by 15a. | Sbomer Guild | Implement SPDX 3.0.1 serializers/deserializers: JSON-LD (canonical), Tag-Value (legacy compat), RDF/XML (optional). Ensure deterministic output ordering. |
|
||||
| 15c | CDX-MAP-186-015C | BLOCKED (2025-11-30) | BLOCKED by 15a. | Sbomer Guild (`src/Sbomer/StellaOps.Sbomer.CycloneDx`) | Build bidirectional SPDX 3.0.1 ↔ CycloneDX 1.6 mapping table: component→package, dependency→relationship, vulnerability→advisory. Document loss-of-fidelity cases. |
|
||||
| 15d | SBOM-STORE-186-015D | BLOCKED (2025-11-30) | BLOCKED by 15a and scope gap (Sbomer store lives outside working directory). | Sbomer Guild · Scanner Guild | MongoDB/CAS persistence for SPDX 3.0.1 documents; indexed by artifact digest, component PURL, document SPDXID. Enable efficient lookup for VEX correlation. |
|
||||
| 15e | SBOM-HASH-186-015E | BLOCKED (2025-11-30) | BLOCKED by 15b, 15d. | Sbomer Guild | Implement SBOM content hash computation: canonical JSON → BLAKE3 hash; store as `sbom_content_hash` in replay manifests; enable deduplication. |
|
||||
| 15f | SBOM-TESTS-186-015F | BLOCKED (2025-11-30) | BLOCKED by 15a-15e. | Sbomer Guild · QA Guild (`src/Sbomer/__Tests`) | Roundtrip tests: SPDX→CDX→SPDX with diff assertion; determinism tests (same input → same hash); SPDX 3.0.1 spec compliance validation. |
|
||||
| 16 | DOCS-REPLAY-186-004 | BLOCKED (2025-11-30) | BLOCKED until replay schema settled (depends on 186-001). | Docs Guild | Author `docs/replay/TEST_STRATEGY.md` (golden replay, feed drift, tool upgrade); link from replay docs and Scanner architecture. |
|
||||
| 17 | DOCS-SBOM-186-017 | BLOCKED (2025-11-30) | BLOCKED by 15a-15f and scope extension to Sbomer docs. | Docs Guild (`docs/modules/sbomer/spdx-3.md`) | Document SPDX 3.0.1 implementation: data model, serialization formats, CDX mapping table, storage schema, hash computation, migration guide from SPDX 2.3. |
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-27 | Expanded SBOM-BRIDGE-186-015 with detailed subtasks (15a-15f) for SPDX 3.0.1 implementation per product advisory. | Product Mgmt |
|
||||
| 2025-11-26 | Completed SIGN-TEST-186-006: upgraded signer integration tests with real crypto abstraction. | Signing Guild |
|
||||
| 2025-11-27 | Expanded SBOM-BRIDGE-186-015 with detailed subtasks (15a-15f) for SPDX 3.0.1 implementation per product advisory. | Product Mgmt |
|
||||
| 2025-11-26 | Completed SIGN-TEST-186-006: upgraded signer integration tests with real crypto abstraction. | Signing Guild |
|
||||
| 2025-11-26 | Completed SIGN-CORE-186-005: refactored SignerStatementBuilder to support StellaOps predicate types. | Signing Guild |
|
||||
| 2025-11-26 | Completed SIGN-CORE-186-004: implemented CryptoDsseSigner with ICryptoProviderRegistry integration. | Signing Guild |
|
||||
| 2025-11-26 | Began SCAN-ENTROPY-186-012: added entropy snapshot/status DTOs and API surface. | Scanner Guild |
|
||||
| 2025-11-26 | Started SCAN-DETER-186-008: added determinism options and deterministic time provider wiring. | Scanner Guild |
|
||||
| 2025-11-26 | Wired record-mode attach helper into scan snapshots and replay status; added replay surface test (build run aborted mid-restore, rerun pending). | Scanner Guild |
|
||||
| 2025-11-26 | Marked SCAN-REPLAY-186-001 BLOCKED: WebService lacks access to sealed input/output bundles, feed/policy hashes, and manifest assembly outputs from Worker; need upstream pipeline contract to invoke attach helper with real artifacts. | Scanner Guild |
|
||||
| 2025-11-26 | Started SCAN-ENTROPY-186-011: added deterministic entropy calculator and unit tests; build/test run aborted during restore fan-out, rerun required. | Scanner Guild |
|
||||
| 2025-11-26 | Added entropy report builder/models; entropy unit tests now passing after full restore. | Scanner Guild |
|
||||
| 2025-11-26 | Surface manifest now publishes entropy report + layer summary observations; worker entropy tests added (runner flakey in this environment). | Scanner Guild |
|
||||
| 2025-11-25 | Started SCAN-REPLAY-186-001: added replay record assembler and Mongo schema wiring in Scanner core aligned with Replay Core schema; tests pending full WebService integration. | Scanner Guild |
|
||||
| 2025-11-03 | `docs/replay/TEST_STRATEGY.md` drafted; Replay CAS section published — Scanner/Signer guilds should move replay tasks to DOING when engineering starts. | Planning |
|
||||
| 2025-11-19 | Normalized sprint to standard template and renamed from `SPRINT_186_record_deterministic_execution.md` to `SPRINT_0186_0001_0001_record_deterministic_execution.md`; content preserved. | Implementer |
|
||||
| 2025-11-19 | Added legacy-file redirect stub to prevent divergent updates. | Implementer |
|
||||
| 2025-11-26 | Started SCAN-ENTROPY-186-011: added deterministic entropy calculator and unit tests; build/test run aborted during restore fan-out, rerun required. | Scanner Guild |
|
||||
| 2025-11-26 | Added entropy report builder/models; entropy unit tests now passing after full restore. | Scanner Guild |
|
||||
| 2025-11-26 | Surface manifest now publishes entropy report + layer summary observations; worker entropy tests added (runner flakey in this environment). | Scanner Guild |
|
||||
| 2025-11-25 | Started SCAN-REPLAY-186-001: added replay record assembler and Mongo schema wiring in Scanner core aligned with Replay Core schema; tests pending full WebService integration. | Scanner Guild |
|
||||
| 2025-11-03 | `docs/replay/TEST_STRATEGY.md` drafted; Replay CAS section published — Scanner/Signer guilds should move replay tasks to DOING when engineering starts. | Planning |
|
||||
| 2025-11-19 | Normalized sprint to standard template and renamed from `SPRINT_186_record_deterministic_execution.md` to `SPRINT_0186_0001_0001_record_deterministic_execution.md`; content preserved. | Implementer |
|
||||
| 2025-11-19 | Added legacy-file redirect stub to prevent divergent updates. | Implementer |
|
||||
| 2025-11-30 | Realigned statuses: blocked SCAN-REPLAY-186-002/003/009/010/014, AUTH-VERIFY-186-007 on upstream contracts; blocked SPDX 15a–15f/DOCS-SBOM-186-017 due to working-directory scope gap (`src/Sbomer` not in sprint). | Implementer |
|
||||
|
||||
## Decisions & Risks
|
||||
- Depends on Replay Core (0185); do not start until CAS and TEST_STRATEGY baselines are confirmed.
|
||||
- Deterministic execution must preserve hermetic runs; ensure fixed clock/RNG/log filtering before enabling harness.
|
||||
- Signing/verification changes must stay aligned with Provenance library once available.
|
||||
- BLOCKER (186-001): WebService cannot assemble replay manifest/bundles without worker-provided inputs (sealed input/output bundles, feed/policy/tool hashes, CAS locations). Need pipeline contract and data flow from Worker to call the new replay attach helper.
|
||||
- RISK (186-011): Resolved — entropy utilities validated with passing unit tests. Proceed to pipeline integration and evidence emission.
|
||||
- Entropy stage expects `ScanAnalysisKeys.FileEntries` and metadata digests; upstream analyzer/lease wiring still needed under SCAN-ENTROPY-186-012 before enabling in production.
|
||||
- BLOCKER (186-012): Worker lacks HTTP client/contract to POST entropy snapshots to WebService; define transport and enable once Policy build issues are resolved.
|
||||
- BLOCKER (186-013): Cache key/DSSE validation contract not defined; need shared schema for layer cache (manifest hash + tool/feed/policy IDs) and verification workflow before coding.
|
||||
- RISK (SPDX 3.0.1): SPDX 3.0.1 uses JSON-LD which has complex serialization rules; ensure canonical output for deterministic hashing. Reference spec carefully.
|
||||
- DECISION (SPDX/CDX): SPDX 3.0.1 is canonical storage format; CycloneDX 1.6 is interchange format. Document loss-of-fidelity cases in mapping table (task 15c).
|
||||
## Decisions & Risks
|
||||
| Item | Impact | Mitigation / Next Step | Status |
|
||||
| --- | --- | --- | --- |
|
||||
| Replay Core dependency (0185) | Blocks replay record/consume tasks. | Keep 186-001 BLOCKED until pipeline contract delivered. | OPEN |
|
||||
| Fixed clock/RNG/log filtering required | Deterministic execution harness correctness. | SCAN-DETER-186-008 in DOING; unblock 009/010 after 008 completes. | OPEN |
|
||||
| Provenance library alignment for signing/verification | Signing/Authority changes must stay compatible. | Rebase once Provenance library available; keep 186-003/007 BLOCKED. | OPEN |
|
||||
| BLOCKER (186-001): WebService lacks worker inputs (sealed bundles, hashes, CAS locations). | Replay record cannot assemble manifests. | Require pipeline contract from Worker; keep 186-001/002/003 BLOCKED. | OPEN |
|
||||
| BLOCKER (186-012): Worker lacks HTTP contract to POST entropy snapshots. | Entropy evidence cannot flow to WebService. | Define transport after Policy build fix; keep 186-012 BLOCKED. | OPEN |
|
||||
| BLOCKER (186-013): Cache key/DSSE validation contract missing. | Layer cache work cannot start. | Define shared schema; keep 186-013 BLOCKED. | OPEN |
|
||||
| Risk (SPDX 3.0.1 canonicalisation). | Non-deterministic output could break hashing. | Keep 15a–15f BLOCKED until scope includes `src/Sbomer` and canonical rules reviewed. | OPEN |
|
||||
| Scope gap: sprint working directory excludes `src/Sbomer`. | Tasks 15/15a–15f/17 cannot start. | PM to extend scope or move tasks to Sbomer sprint; logged in Execution Log. | OPEN |
|
||||
|
||||
## Next Checkpoints
|
||||
- Kickoff after Replay Core scaffolding begins (date TBD).
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
| 4 | SDKGEN-63-002 | DOING | Scaffold added; waiting on frozen OAS to generate alpha. Scaffold + smoke + hash guard ready. | SDK Generator Guild | Ship Python SDK alpha (sync/async clients, type hints, upload/download helpers). |
|
||||
| 5 | SDKGEN-63-003 | DOING | Scaffold added (config, driver script, smoke test, README); awaiting frozen OAS to generate alpha. | SDK Generator Guild | Ship Go SDK alpha with context-first API and streaming helpers. |
|
||||
| 6 | SDKGEN-63-004 | DOING | Scaffold added (config, driver script, smoke test, README); OkHttp selected as HTTP client; awaiting frozen OAS to generate alpha. | SDK Generator Guild | Ship Java SDK alpha (builder pattern, HTTP client abstraction). |
|
||||
| 7 | SDKGEN-64-001 | TODO | Depends on 63-004; map CLI surfaces to SDK calls. | SDK Generator Guild · CLI Guild | Switch CLI to consume TS or Go SDK; ensure parity. |
|
||||
| 8 | SDKGEN-64-002 | TODO | Depends on 64-001; define Console data provider contracts. | SDK Generator Guild · Console Guild | Integrate SDKs into Console data providers where feasible. |
|
||||
| 7 | SDKGEN-64-001 | BLOCKED (2025-11-30) | Depends on 63-004; waiting for frozen aggregate OAS and Java alpha before mapping CLI surfaces. | SDK Generator Guild · CLI Guild | Switch CLI to consume TS or Go SDK; ensure parity once Wave B artifacts land. |
|
||||
| 8 | SDKGEN-64-002 | BLOCKED (2025-11-30) | Depends on 64-001; blocked until SDKGEN-64-001 completes. | SDK Generator Guild · Console Guild | Integrate SDKs into Console data providers where feasible. |
|
||||
| 9 | SDKREL-63-001 | TODO | Set up signing keys/provenance; stage CI pipelines across registries. | SDK Release Guild · `src/Sdk/StellaOps.Sdk.Release` | Configure CI pipelines for npm, PyPI, Maven Central staging, and Go proxies with signing and provenance attestations. |
|
||||
| 10 | SDKREL-63-002 | TODO | Requires 63-001; connect OAS diff feed. | SDK Release Guild · API Governance Guild | Integrate changelog automation pulling from OAS diffs and generator metadata. |
|
||||
| 11 | SDKREL-64-001 | TODO | Wait for 63-002; design Notifications Studio channel scopes. | SDK Release Guild · Notifications Guild | Hook SDK releases into Notifications Studio with scoped announcements and RSS/Atom feeds. |
|
||||
@@ -52,12 +52,10 @@
|
||||
- Notifications/Export: Notifications Studio and Export Center pipelines must be live before Wave C release window (tasks 11–12).
|
||||
|
||||
## Upcoming Checkpoints
|
||||
- 2025-11-25: Toolchain decision review (SDKGEN-62-001) — decide generator + template pin set.
|
||||
- 2025-12-02: Shared post-processing design review (SDKGEN-62-002) — approve auth/retry/pagination/telemetry hooks.
|
||||
- 2025-12-05: TS alpha staging drop (SDKGEN-63-001) — verify packaging and typed errors.
|
||||
- 2025-12-15: Multi-language alpha readiness check (SDKGEN-63-002..004) — parity matrix sign-off.
|
||||
- 2025-12-16: Deliver parity matrix and SDK drop to UI/Console data providers (feeds SPRINT_0209_0001_0001_ui_i).
|
||||
- 2025-12-22: Release automation demo (SDKREL-63/64) — staging publishes with signatures and offline bundle.
|
||||
- 2025-12-05: TS alpha staging drop (SDKGEN-63-001) — verify packaging and typed errors (BLOCKED until aggregate OAS freeze).
|
||||
- 2025-12-15: Multi-language alpha readiness check (SDKGEN-63-002..004) — parity matrix sign-off (BLOCKED until aggregate OAS freeze and Java alpha generation).
|
||||
- 2025-12-16: Deliver parity matrix and SDK drop to UI/Console data providers (depends on Wave B artifacts).
|
||||
- 2025-12-22: Release automation demo (SDKREL-63/64) — staging publishes with signatures and offline bundle (BLOCKED until SDKREL-63-001/002 advance).
|
||||
|
||||
## Action Tracker
|
||||
| # | Action | Owner | Due (UTC) | Status |
|
||||
@@ -72,7 +70,7 @@
|
||||
- Toolchain pinned (OpenAPI Generator 7.4.0, JDK 21) and recorded in repo (`TOOLCHAIN.md`, `toolchain.lock.yaml`); downstream tracks must honor lock file for determinism.
|
||||
- Dependencies on upstream API/portal contracts may delay generator pinning; mitigation: align with APIG0101 / DEVL0101 milestones.
|
||||
- Release automation requires registry credentials and signing infra; mitigation: reuse sovereign crypto enablement (SPRINT_0514_0001_0001_sovereign_crypto_enablement.md) practices and block releases until keys are validated.
|
||||
- Offline bundle job (SDKREL-64-002) depends on Export Center artifacts; track alongside Export Center sprints.
|
||||
- Offline bundle job (SDKREL-64-002) depends on Export Center artifacts; track alongside Export Center sprints; remains BLOCKED until SDKGEN-64-001 completes.
|
||||
- Shared postprocess helpers copy only when CI sets `STELLA_POSTPROCESS_ROOT` and `STELLA_POSTPROCESS_LANG`; ensure generation jobs export these to keep helpers present in artifacts.
|
||||
|
||||
### Risk Register
|
||||
@@ -100,9 +98,10 @@
|
||||
| 2025-11-26 | Scaffolded Go generator (config/script/smoke), enabled hash guard + helper copy via postprocess, and added `.oas.sha256` emission; waiting on frozen OAS for Wave B alpha. | SDK Generator Guild |
|
||||
| 2025-11-26 | Scaffolded Java generator (config/script/smoke), added postprocess hook copy into `org.stellaops.sdk`, hash guard + `.oas.sha256`, and vendored-JDK fallback; waiting on frozen OAS for Wave B alpha. | SDK Generator Guild |
|
||||
| 2025-11-26 | Marked SDKGEN-63-003/004 BLOCKED pending frozen aggregate OAS digest; scaffolds and smoke tests are ready. | SDK Generator Guild |
|
||||
| 2025-11-26 | Added unified SDK smoke npm scripts (`sdk:smoke:*`, `sdk:smoke`) covering TS/Python/Go/Java to keep pre-alpha checks consistent. | SDK Generator Guild |
|
||||
| 2025-11-26 | Added CI workflow `.gitea/workflows/sdk-generator.yml` to run `npm run sdk:smoke` on SDK generator changes (TS/Python/Go/Java). | SDK Generator Guild |
|
||||
| 2025-11-27 | Marked SDKGEN-63-001/002 BLOCKED pending frozen aggregate OAS digest; scaffolds and smokes remain ready. | SDK Generator Guild |
|
||||
| 2025-11-26 | Added unified SDK smoke npm scripts (`sdk:smoke:*`, `sdk:smoke`) covering TS/Python/Go/Java to keep pre-alpha checks consistent. | SDK Generator Guild |
|
||||
| 2025-11-26 | Added CI workflow `.gitea/workflows/sdk-generator.yml` to run `npm run sdk:smoke` on SDK generator changes (TS/Python/Go/Java). | SDK Generator Guild |
|
||||
| 2025-11-27 | Marked SDKGEN-63-001/002 BLOCKED pending frozen aggregate OAS digest; scaffolds and smokes remain ready. | SDK Generator Guild |
|
||||
| 2025-11-30 | Marked SDKGEN-64-001 and SDKGEN-64-002 BLOCKED pending Wave B (SDKGEN-63-004) OAS freeze/Java alpha; CLI/UI adoption cannot proceed without generated SDK artifacts. | Project Mgmt |
|
||||
| 2025-11-24 | Added fixture OpenAPI (`ts/fixtures/ping.yaml`) and smoke test (`ts/test_generate_ts.sh`) to validate TypeScript pipeline locally; skips if generator jar absent. | SDK Generator Guild |
|
||||
| 2025-11-24 | Vendored `tools/openapi-generator-cli-7.4.0.jar` and `tools/jdk-21.0.1.tar.gz` with SHA recorded in `toolchain.lock.yaml`; adjusted TS script to ensure helper copy post-run and verified generation against fixture. | SDK Generator Guild |
|
||||
| 2025-11-24 | Ran `ts/test_generate_ts.sh` with vendored JDK/JAR and fixture spec; smoke test passes (helpers present). | SDK Generator Guild |
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Sprint 0320 · Docs Modules · Export Center
|
||||
|
||||
## Topic & Scope
|
||||
- Refresh Export Center module docs (README, architecture, implementation plan, runbooks) to reflect current bundle/export posture and offline kit integration.
|
||||
- Create a TASKS board and mirror sprint status for contributors.
|
||||
- Add observability/runbook stub for latest demo and keep references to profiles/offline manifests aligned.
|
||||
- **Working directory:** `docs/modules/export-center`.
|
||||
|
||||
## Dependencies & Concurrency
|
||||
- Upstream reference sprints: 100.A (Attestor), 110.A (AdvisoryAI), 120.A (AirGap), 130.A (Scanner), 140.A (Graph), 150.A (Orchestrator), 160.A (Evidence Locker), 170.A (Notifier), 180.A (CLI), 190.A (Ops Deployment).
|
||||
- Documentation-only; can proceed in parallel once release artefacts available.
|
||||
|
||||
## Documentation Prerequisites
|
||||
- `docs/modules/export-center/AGENTS.md`
|
||||
- `docs/modules/export-center/README.md`
|
||||
- `docs/modules/export-center/architecture.md`
|
||||
- `docs/modules/export-center/implementation_plan.md`
|
||||
- `docs/modules/export-center/devportal-offline.md`
|
||||
- `docs/modules/platform/architecture-overview.md`
|
||||
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
|
||||
## Delivery Tracker
|
||||
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | EXPORT CENTER-DOCS-0001 | DONE (2025-11-30) | Refresh module docs with latest bundle/export posture. | Docs Guild (`docs/modules/export-center`) | Update README/architecture/implementation_plan with bundle/profiles/offline guidance and sprint/task links. |
|
||||
| 2 | EXPORT CENTER-ENG-0001 | DONE (2025-11-30) | Mirror sprint ↔ TASKS status. | Module Team (`docs/modules/export-center`) | Create TASKS board and keep statuses in sync with this sprint. |
|
||||
| 3 | EXPORT CENTER-OPS-0001 | DONE (2025-11-30) | Add observability/runbook stub; align profiles/offline manifests. | Ops Guild (`docs/modules/export-center`) | Add observability runbook + dashboard stub and ensure devportal offline/manifests references are linked. |
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-30 | Normalised sprint to standard template; renamed from `SPRINT_320_docs_modules_export_center.md`; added compatibility stub. | Docs Guild |
|
||||
| 2025-11-30 | Completed EXPORT CENTER-DOCS/ENG/OPS-0001: refreshed module docs, created TASKS board, added observability runbook stub and dashboard placeholder. | Docs Guild |
|
||||
|
||||
## Decisions & Risks
|
||||
- Export Center docs must stay aligned with bundle/profile/offline manifests; update sprint and TASKS together if contracts change.
|
||||
- Observability assets remain offline-import friendly; no external datasources.
|
||||
- Keep sprint and module TASKS mirrored to avoid drift.
|
||||
|
||||
## Next Checkpoints
|
||||
- 2025-12-05 · Validate observability/dashboard panels after next demo; update runbook/TASKS accordingly. Owner: Ops Guild.
|
||||
39
docs/implplan/SPRINT_0324_0001_0001_docs_modules_platform.md
Normal file
39
docs/implplan/SPRINT_0324_0001_0001_docs_modules_platform.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Sprint 0324 · Docs Modules · Platform
|
||||
|
||||
## Topic & Scope
|
||||
- Refresh Platform module docs (README, architecture, implementation plan) to reflect current cross-cutting guardrails, AOC references, and onboarding flow.
|
||||
- Create a TASKS board and mirror sprint status for platform contributors.
|
||||
- Keep links to architecture-overview and 07_HIGH_LEVEL_ARCHITECTURE current; ensure offline/air-gap guidance is discoverable.
|
||||
- **Working directory:** `docs/modules/platform`.
|
||||
|
||||
## Dependencies & Concurrency
|
||||
- Upstream reference sprints: 100.A (Attestor), 110.A (AdvisoryAI), 120.A (AirGap), 130.A (Scanner), 140.A (Graph), 150.A (Orchestrator), 160.A (Evidence Locker), 170.A (Notifier), 180.A (CLI), 190.A (Ops Deployment).
|
||||
- Documentation-only; can proceed in parallel.
|
||||
|
||||
## Documentation Prerequisites
|
||||
- `docs/modules/platform/AGENTS.md`
|
||||
- `docs/modules/platform/README.md`
|
||||
- `docs/modules/platform/architecture.md`
|
||||
- `docs/modules/platform/architecture-overview.md`
|
||||
- `docs/modules/platform/implementation_plan.md`
|
||||
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
|
||||
## Delivery Tracker
|
||||
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | PLATFORM-DOCS-0001 | DONE (2025-11-30) | Refresh module docs per current guardrails. | Docs Guild (`docs/modules/platform`) | Update README/architecture/implementation_plan to reflect AOC, offline posture, and sprint/task mirrors. |
|
||||
| 2 | PLATFORM-ENG-0001 | DONE (2025-11-30) | Mirror sprint ↔ TASKS status. | Module Team (`docs/modules/platform`) | Create TASKS board and keep statuses in sync. |
|
||||
| 3 | PLATFORM-OPS-0001 | DONE (2025-11-30) | Ensure cross-links to architecture overview and offline guidance. | Ops Guild (`docs/modules/platform`) | Sync outcomes back to sprint; verify architecture-overview and 07_HLA links. |
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-30 | Normalised sprint to standard template; renamed from `SPRINT_324_docs_modules_platform.md`; added compatibility stub. | Docs Guild |
|
||||
| 2025-11-30 | Completed PLATFORM-DOCS/ENG/OPS-0001: refreshed README/architecture/implementation_plan, created TASKS board, ensured cross-links to architecture-overview and 07_HLA. | Docs Guild |
|
||||
|
||||
## Decisions & Risks
|
||||
- Platform docs must remain the canonical entry for cross-cutting guardrails; update both sprint and TASKS when platform contracts change.
|
||||
- Keep sprint and TASKS mirrored to avoid drift; offline posture must be preserved in references.
|
||||
|
||||
## Next Checkpoints
|
||||
- 2025-12-05 · Quick audit to confirm platform overview links still match upstream docs after any architecture changes. Owner: Docs Guild.
|
||||
@@ -30,11 +30,11 @@
|
||||
| 2 | BENCH-SCHEMA-513-002 | DONE (2025-11-29) | Depends on 513-001. | Bench Guild | Define and publish schemas: `case.schema.yaml` (component, sink, label, evidence), `entrypoints.schema.yaml`, `truth.schema.yaml`, `submission.schema.json`. Include JSON Schema validation. |
|
||||
| 3 | BENCH-CASES-JS-513-003 | DONE (2025-11-30) | Depends on 513-002. | Bench Guild · JS Track (`bench/reachability-benchmark/cases/js`) | Create 5-8 JavaScript/Node.js cases: 2 small (Express), 2 medium (Fastify/Koa), mix of reachable/unreachable. Include Dockerfiles, package-lock.json, unit test oracles, coverage output. Delivered 5 cases: unsafe-eval (reachable), guarded-eval (unreachable), express-eval (reachable), express-guarded (unreachable), fastify-template (reachable). |
|
||||
| 4 | BENCH-CASES-PY-513-004 | DONE (2025-11-30) | Depends on 513-002. | Bench Guild · Python Track (`bench/reachability-benchmark/cases/py`) | Create 5-8 Python cases: Flask, Django, FastAPI. Include requirements.txt pinned, pytest oracles, coverage.py output. Delivered 5 cases: unsafe-exec (reachable), guarded-exec (unreachable), flask-template (reachable), fastapi-guarded (unreachable), django-ssti (reachable). |
|
||||
| 5 | BENCH-CASES-JAVA-513-005 | DOING | Depends on 513-002. | Bench Guild · Java Track (`bench/reachability-benchmark/cases/java`) | Create 5-8 Java cases: Spring Boot, Micronaut. Include pom.xml locked, JUnit oracles, JaCoCo coverage. Progress: 2/5 seeded (`spring-deserialize` reachable, `spring-guarded` unreachable). Note: builds/tests pending until JDK available in runner. |
|
||||
| 5 | BENCH-CASES-JAVA-513-005 | BLOCKED (2025-11-30) | Depends on 513-002. | Bench Guild · Java Track (`bench/reachability-benchmark/cases/java`) | Create 5-8 Java cases: Spring Boot, Micronaut. Include pom.xml locked, JUnit oracles, JaCoCo coverage. Progress: 2/5 seeded (`spring-deserialize` reachable, `spring-guarded` unreachable); build/test blocked by missing JDK (`javac` not available in runner). |
|
||||
| 6 | BENCH-CASES-C-513-006 | TODO | Depends on 513-002. | Bench Guild · Native Track (`bench/reachability-benchmark/cases/c`) | Create 3-5 C/ELF cases: small HTTP servers, crypto utilities. Include Makefile, gcov/llvm-cov coverage, deterministic builds (SOURCE_DATE_EPOCH). |
|
||||
| 7 | BENCH-BUILD-513-007 | TODO | Depends on 513-003 through 513-006. | Bench Guild · DevOps Guild | Implement `build_all.py` and `validate_builds.py`: deterministic Docker builds, hash verification, SBOM generation (syft), attestation stubs. |
|
||||
| 7 | BENCH-BUILD-513-007 | DOING | Depends on 513-003 through 513-006. | Bench Guild · DevOps Guild | Implement `build_all.py` and `validate_builds.py`: deterministic Docker builds, hash verification, SBOM generation (syft), attestation stubs. Progress: added scripts (hash check, deterministic ordering); SBOM/attestation stubs pending. |
|
||||
| 8 | BENCH-SCORER-513-008 | DONE (2025-11-30) | Depends on 513-002. | Bench Guild (`bench/reachability-benchmark/tools/scorer`) | Implement `rb-score` CLI: load cases/truth, validate submissions, compute precision/recall/F1, explainability score (0-3), runtime stats, determinism rate. |
|
||||
| 9 | BENCH-EXPLAIN-513-009 | TODO | Depends on 513-008. | Bench Guild | Implement explainability scoring rules: 0=no context, 1=path with ≥2 nodes, 2=entry+≥3 nodes, 3=guards/constraints included. Unit tests for each level. |
|
||||
| 9 | BENCH-EXPLAIN-513-009 | DONE (2025-11-30) | Depends on 513-008. | Bench Guild | Implement explainability scoring rules: 0=no context, 1=path with ≥2 nodes, 2=entry+≥3 nodes, 3=guards/constraints included. Unit tests for each level. |
|
||||
| 10 | BENCH-BASELINE-SEMGREP-513-010 | TODO | Depends on 513-008 and cases. | Bench Guild | Semgrep baseline runner: `baselines/semgrep/run_case.sh`, rule config, output normalization to submission format. |
|
||||
| 11 | BENCH-BASELINE-CODEQL-513-011 | TODO | Depends on 513-008 and cases. | Bench Guild | CodeQL baseline runner: database creation, reachability queries, output normalization. Document CodeQL license requirements. |
|
||||
| 12 | BENCH-BASELINE-STELLA-513-012 | TODO | Depends on 513-008 and Sprint 0401 reachability. | Bench Guild · Scanner Guild | Stella Ops baseline runner: invoke `stella scan` with reachability, normalize output, demonstrate determinism advantage. |
|
||||
@@ -78,6 +78,7 @@
|
||||
| R2 | Baseline tools have licensing restrictions. | Cannot include in public benchmark. | Document license requirements; exclude or limit usage; Legal. |
|
||||
| R3 | Hidden test set leakage. | Overfitting by vendors. | Rotate quarterly; governance controls; TAC. |
|
||||
| R4 | Deterministic builds fail on some platforms. | Reproducibility claims undermined. | Pin all toolchain versions; use SOURCE_DATE_EPOCH; DevOps Guild. |
|
||||
| R5 | Java cases blocked: JDK/javac missing on runner/CI. | Java track cannot build/test; risk of schedule slip. | Provide JDK>=17 in runner/CI; rerun Java build scripts; DevOps Guild. |
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
@@ -94,3 +95,6 @@
|
||||
| 2025-11-30 | Progressed BENCH-CASES-PY-513-004: added `cases/py/fastapi-guarded` (unreachable unless ALLOW_EXEC=true) with entrypoints and truth `benchmark/truth/py-fastapi-guarded.json`; validated via `tools/validate.py` and deterministic build. | Implementer |
|
||||
| 2025-11-30 | Completed BENCH-CASES-PY-513-004: added `cases/py/django-ssti` (reachable template rendering, autoescape off) with truth `benchmark/truth/py-django-ssti.json`; validated via `tools/validate.py` and deterministic build. | Implementer |
|
||||
| 2025-11-30 | Started BENCH-CASES-JAVA-513-005: added `cases/java/spring-deserialize` (reachable) and `cases/java/spring-guarded` (unreachable by default) with entrypoints and truth files; schema validation passes. Build/test pending due to missing `javac` in runner—recorded as dependency for future CI. | Implementer |
|
||||
| 2025-11-30 | BLOCKED BENCH-CASES-JAVA-513-005: `javac`/JDK not available in runner; Java builds/tests cannot execute. Need JDK (>=17) in CI/runner before unblocking. | Implementer |
|
||||
| 2025-11-30 | BENCH-EXPLAIN-513-009 DONE: added explainability tier tests (0–3) to scorer; tiers already implemented (guards→3, entry+path>=3→2, path>=2→1, else 0). | Implementer |
|
||||
| 2025-11-30 | BENCH-BUILD-513-007 DOING: added `tools/build/build_all.py` and `tools/build/validate_builds.py` for deterministic builds and hash checks; SBOM/attestation stubs still pending. | Implementer |
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
| 10 | AUTH-CRYPTO-90-001 | BLOCKED | PREP-AUTH-CRYPTO-90-001-NEEDS-AUTHORITY-PROVI | Authority Core & Security Guild | Sovereign signing provider contract for Authority; refactor loaders once contract is published. |
|
||||
| 11 | SCANNER-CRYPTO-90-001 | BLOCKED (2025-11-27) | Await Authority provider/JWKS contract + registry option design (R1/R3) | Scanner WebService Guild · Security Guild | Route hashing/signing flows through `ICryptoProviderRegistry`. |
|
||||
| 12 | SCANNER-WORKER-CRYPTO-90-001 | BLOCKED (2025-11-27) | After 11 (registry contract pending) | Scanner Worker Guild · Security Guild | Wire Scanner Worker/BuildX analyzers to registry/hash abstractions. |
|
||||
| 13 | SCANNER-CRYPTO-90-002 | DOING (2025-11-27) | Design doc `docs/security/pq-provider-options.md` published; awaiting implementation wiring. | Scanner WebService Guild · Security Guild | Enable PQ-friendly DSSE (Dilithium/Falcon) via provider options. |
|
||||
| 13 | SCANNER-CRYPTO-90-002 | BLOCKED (2025-11-30) | Blocked by R1/R3: registry/provider contract (Authority) and PQ option mapping not finalized in runtime hosts. Design doc exists (`docs/security/pq-provider-options.md`). | Scanner WebService Guild · Security Guild | Enable PQ-friendly DSSE (Dilithium/Falcon) via provider options. |
|
||||
| 14 | SCANNER-CRYPTO-90-003 | BLOCKED (2025-11-27) | After 13; needs PQ provider implementation | Scanner Worker Guild · QA Guild | Add regression tests for RU/PQ profiles validating Merkle roots + DSSE chains. |
|
||||
| 15 | ATTESTOR-CRYPTO-90-001 | BLOCKED | Authority provider/JWKS contract pending (R1) | Attestor Service Guild · Security Guild | Migrate attestation hashing/witness flows to provider registry, enabling CryptoPro/PKCS#11 deployments. |
|
||||
|
||||
@@ -63,7 +63,8 @@
|
||||
| Publish Authority provider/JWKS contract (AUTH-CRYPTO-90-001) | Authority Core | 2025-11-19 | Overdue | Blocks tasks 8, 10, 15; depends on contract finalisation. |
|
||||
| Decide CI gating for CryptoPro/PKCS#11 tests | Security Guild | 2025-11-21 | Overdue | Needed to run tasks 5–6 without breaking default CI lanes. |
|
||||
| Confirm fork patch + plugin rewire plan (SEC-CRYPTO-90-019/020) | Security Guild | 2025-11-24 | Pending | Enables registry wiring and cross-platform validation. |
|
||||
| Draft PQ provider options design + regression test plan (tasks 13–14) | Scanner Guild | 2025-11-27 | Planned | Mitigates R3; ensures deterministic DSSE/Merkle behavior across providers. |
|
||||
| Draft PQ provider options design + regression test plan (tasks 13–14) | Scanner Guild | 2025-11-27 | DONE | Mitigates R3; ensures deterministic DSSE/Merkle behavior across providers; design doc at `docs/security/pq-provider-options.md`. |
|
||||
| Map PQ options into registry contract once Authority provider/JWKS spec lands (R1) | Scanner Guild · Authority Core | 2025-12-03 | OPEN | Required to unblock SCANNER-CRYPTO-90-002/003 and runtime wiring. |
|
||||
| Complete license/export review for fork + plugin | Security & Legal | 2025-11-25 | Planned | Validate CryptoPro/GostCryptography licensing, regional crypto controls, and AGPL obligations before distribution. |
|
||||
|
||||
## Decisions & Risks
|
||||
@@ -75,7 +76,7 @@
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| R1 | Authority provider/JWKS contract unpublished (AUTH-CRYPTO-90-001) | Blocks runtime wiring tasks (8, 10, 15) and registry alignment. | Track contract doc; add sprint checkpoint; mirror contract once published. | Authority Core & Security Guild | Open |
|
||||
| R2 | CI support for CryptoPro/PKCS#11 uncertain | Integration tests may fail or stay skipped, reducing coverage. | Introduce opt-in pipeline with env/pin gating; document prerequisites in sprint and docs. | Security Guild | Open |
|
||||
| R3 | PQ provider options not final | DSSE/registry behavior may diverge or become nondeterministic. | Design provider options aligned to registry abstractions; add regression tests (tasks 13–14). | Scanner Guild | Open |
|
||||
| R3 | PQ provider options not final | DSSE/registry behavior may diverge or become nondeterministic. | Design doc published; remains blocked until mapped into registry contract and runtime hosts (tasks 13–14). | Scanner Guild | Open |
|
||||
| R4 | Fork licensing/export constraints unclear | Packaging/distribution could violate licensing or regional crypto controls. | Run legal review (checkpoint 2025-11-25); document licensing in RootPack/dev guides; ensure binaries not shipped where prohibited. | Security & Legal | Open |
|
||||
|
||||
## Execution Log
|
||||
@@ -86,6 +87,7 @@
|
||||
| 2025-11-26 | Marked SEC-CRYPTO-90-015 DONE after refreshing RootPack packaging/validation docs with fork provenance and bundle composition notes. | Implementer |
|
||||
| 2025-11-27 | Marked SCANNER-CRYPTO-90-001/002/003 and SCANNER-WORKER-CRYPTO-90-001 BLOCKED pending Authority provider/JWKS contract and PQ provider option design (R1/R3). | Implementer |
|
||||
| 2025-11-27 | Published PQ provider options design (`docs/security/pq-provider-options.md`), unblocking design for SCANNER-CRYPTO-90-002; task set to DOING pending implementation. | Implementer |
|
||||
| 2025-11-30 | Marked SCANNER-CRYPTO-90-002 BLOCKED pending Authority registry contract (R1) and runtime PQ option mapping (R3); updated action tracker accordingly. | Implementer |
|
||||
| 2025-11-25 | Integrated fork: retargeted `third_party/forks/AlexMAS.GostCryptography` to `net10.0`, added Xml/Permissions deps, and switched `StellaOps.Cryptography.Plugin.CryptoPro` from IT.GostCryptography nuget to project reference. `dotnet build src/__Libraries/StellaOps.Cryptography.Plugin.CryptoPro -c Release` now succeeds (warnings CA1416 kept). | Implementer |
|
||||
| 2025-11-25 | Progressed SEC-CRYPTO-90-019: removed legacy IT.GostCryptography nuget, retargeted fork to net10 with System.Security.Cryptography.Xml 8.0.1 and System.Security.Permissions; cleaned stale bin/obj. Fork library builds; fork tests still pending (Windows CSP). | Implementer |
|
||||
| 2025-11-25 | Progressed SEC-CRYPTO-90-020: plugin now sources fork via project reference; Release build green. Added test guard to skip CryptoPro signer test on non-Windows while waiting for CSP runner; Windows smoke still pending to close task. | Implementer |
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
# Sprint 320 - Documentation & Process · 200.J) Docs Modules Export Center
|
||||
# Moved sprint file
|
||||
|
||||
Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08).
|
||||
|
||||
[Documentation & Process] 200.J) Docs Modules Export Center
|
||||
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, Sprint 190.A - Ops Deployment
|
||||
Summary: Documentation & Process focus on Docs Modules Export Center).
|
||||
Task ID | State | Task description | Owners (Source)
|
||||
--- | --- | --- | ---
|
||||
EXPORT CENTER-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/export-center)
|
||||
EXPORT CENTER-OPS-0001 | TODO | Sync outcomes back to ../.. | Ops Guild (docs/modules/export-center)
|
||||
This sprint has been renamed to `SPRINT_0320_0001_0001_docs_modules_export_center.md` to comply with the standard template. Update any bookmarks accordingly.
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
# Sprint 324 - Documentation & Process · 200.N) Docs Modules Platform
|
||||
# Moved sprint file
|
||||
|
||||
Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08).
|
||||
|
||||
[Documentation & Process] 200.N) Docs Modules Platform
|
||||
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, Sprint 190.A - Ops Deployment
|
||||
Summary: Documentation & Process focus on Docs Modules Platform).
|
||||
Task ID | State | Task description | Owners (Source)
|
||||
--- | --- | --- | ---
|
||||
PLATFORM-DOCS-0001 | TODO | See ./AGENTS.md | Docs Guild (docs/modules/platform)
|
||||
PLATFORM-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/platform)
|
||||
PLATFORM-OPS-0001 | TODO | Sync outcomes back to ../.. | Ops Guild (docs/modules/platform)
|
||||
This sprint has been renamed to `SPRINT_0324_0001_0001_docs_modules_platform.md` to comply with the standard template. Update any bookmarks accordingly.
|
||||
|
||||
@@ -22,13 +22,13 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
||||
## 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-AIAI-31-001 | DONE (2025-11-30) | 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-AIAI-31-002 | BLOCKED (2025-11-23) | Package advisory feeds (SBOM pointers + provenance) for release/offline kit; publish once CLI/Policy digests and SBOM feeds arrive. | DevOps Guild, Advisory AI Release (ops/devops) |
|
||||
| DEVOPS-SPANSINK-31-003 | TODO | Deploy span sink/Signals pipeline for Excititor evidence APIs (31-003) and publish dashboards; unblock traces for `/v1/vex/observations/**`. | DevOps Guild · Observability Guild (ops/devops) |
|
||||
| DEVOPS-SPANSINK-31-003 | DONE (2025-11-30) | Deploy span sink/Signals pipeline for Excititor evidence APIs (31-003) and publish dashboards; unblock traces for `/v1/vex/observations/**`. | DevOps Guild · Observability Guild (ops/devops) |
|
||||
| DEVOPS-AIRGAP-56-001 | DONE (2025-11-30) | 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 | DONE (2025-11-30) | 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 | DONE (2025-11-30) | 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-001 | DONE (2025-11-30) | 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) |
|
||||
@@ -36,8 +36,8 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
||||
| 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-73-001 | DONE (2025-11-30) | 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 | DONE (2025-11-30) | 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) |
|
||||
@@ -45,7 +45,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
||||
| 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 | DONE (2025-11-25) | 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-SCANNER-CI-11-001 | DONE (2025-11-30) | 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-SCANNER-JAVA-21-011-REL | TODO | Package/sign Java analyzer plug-in once dev task 21-011 delivers; publish to Offline Kit/CLI release pipelines with provenance. | DevOps Guild, Scanner Release 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) |
|
||||
| FEED-REMEDIATION-1001 | BLOCKED (2025-11-24) | Define remediation scope and runbook for overdue feeds (CCCS/CERTBUND); schedule refresh; depends on PREP-FEEDCONN-ICS-KISA-PLAN. | Concelier Feed Owners (ops/devops) |
|
||||
@@ -54,6 +54,10 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2025-11-30 | Completed DEVOPS-SCANNER-CI-11-001: added offline-friendly Scanner CI runner (`ops/devops/scanner-ci-runner/run-scanner-ci.sh`) and README; produces build binlog + TRX outputs from key test projects with warmed NuGet cache. | DevOps |
|
||||
| 2025-11-30 | Completed DEVOPS-ATTEST-73-001/73-002: added attestor CI stub (`ops/devops/attestation/ci.yml`) and secrets/rotation plan in `ops/devops/attestation/README.md`; pending mirror into `.gitea/workflows/attestor-ci.yml` for live runs. | DevOps |
|
||||
| 2025-11-30 | Completed DEVOPS-SPANSINK-31-003: added OTLP span sink compose stack + collector config (`docker-compose.spansink.yml`, `otel-spansink.yaml`), run script, and Grafana dashboard stub (`ops/devops/signals/dashboards/excititor-vex-traces.json`). | DevOps |
|
||||
| 2025-11-30 | Completed DEVOPS-AIRGAP-57-001: added mirror bundle manifest/signing tooling (`build_mirror_bundle.py`) with dual-approval support and optional cosign, documented in `ops/devops/airgap/README.md`. | DevOps |
|
||||
| 2025-11-30 | Completed DEVOPS-AIRGAP-56-003: added Bootstrap Pack builder scripts (`build_bootstrap_pack.py`, `build_bootstrap_pack.sh`) producing manifest and checksums for images/charts/extras; docs updated in `ops/devops/airgap/README.md`. | DevOps |
|
||||
| 2025-11-30 | Completed DEVOPS-AIRGAP-56-002: added bundle staging/import tooling (`bundle_stage_import.py`, `stage-bundle.sh`, README) under `ops/devops/airgap/` with checksum validation and evidence report output. | DevOps |
|
||||
| 2025-11-30 | Completed DEVOPS-AIRGAP-56-001: added K8s deny-all egress NetworkPolicy, compose DOCKER-USER guard script, and verification harness for Docker/Kubernetes under `ops/devops/airgap/`. | DevOps |
|
||||
@@ -64,6 +68,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
||||
| 2025-11-24 | Added DEVOPS-SCANNER-JAVA-21-011-REL (moved from SPRINT_0131_0001_0001_scanner_surface.md) to keep DevOps release packaging in ops track. | Project Mgmt |
|
||||
| 2025-11-24 | Added DEVOPS-SPANSINK-31-003 (Excititor span sink for 31-003 traces) moved from SPRINT_0119_0001_0001_excititor_i per ops-only directive. | Project Mgmt |
|
||||
| 2025-11-24 | Imported Concelier feed ops items FEED-REMEDIATION-1001 and FEEDCONN-ICSCISA/KISA from Sprint 110; keeping feed remediation in ops track. | Project Mgmt |
|
||||
| 2025-11-30 | DEVOPS-AIAI-31-001 DONE: added Advisory AI CI harness (`ops/devops/advisoryai-ci-runner/run-advisoryai-ci.sh`) producing binlog/TRX/summary; warmed local NuGet cache for offline runs; docs in runner README. | DevOps |
|
||||
|
||||
## Decisions & Risks
|
||||
- Mirror bundle automation (DEVOPS-AIRGAP-57-001) and AOC guardrails remain gating risks; several downstream tasks inherit these.
|
||||
|
||||
@@ -1056,14 +1056,14 @@ Consolidated task ledger for everything under `docs/implplan/archived/` (sprints
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 42 — CLI Parity & Task Packs Phase 2 | ORCH-SVC-42-101 | TODO | Stream pack run logs via SSE/WS, expose artifact manifests, enforce pack run quotas. | Orchestrator Service Guild | Path: src/Orchestrator/StellaOps.Orchestrator | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 42 — CLI Parity & Task Packs Phase 2 | PACKS-REG-42-001 | DONE (2025-11-25) | Support pack version lifecycle, tenant allowlists, provenance export, signature rotation. | Packs Registry Guild | Path: src/PacksRegistry/StellaOps.PacksRegistry | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 42 — CLI Parity & Task Packs Phase 2 | POLICY-ENGINE-42-201 | TODO | Provide stable rationale IDs/APIs for CLI `--explain` and pack policy gates. | Policy Guild | Path: src/Policy/StellaOps.Policy.Engine | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 42 — CLI Parity & Task Packs Phase 2 | TASKRUN-42-001 | TODO | Add loops, conditionals, `maxParallel`, outputs, simulation mode, policy gates in Task Runner. | Task Runner Guild | Path: src/TaskRunner/StellaOps.TaskRunner | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 42 — CLI Parity & Task Packs Phase 2 | TASKRUN-42-001 | BLOCKED (2025-11-25) | Add loops, conditionals, `maxParallel`, outputs, simulation mode, policy gates in Task Runner; awaiting control-flow/policy-gate addendum. | Task Runner Guild | Path: src/TaskRunner/StellaOps.TaskRunner | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 43 — CLI Parity & Task Packs Phase 3 | DOCS-PACKS-43-001 | TODO | Publish `/docs/task-packs/authoring-guide.md`, `/registry.md`, `/runbook.md`, `/security/pack-signing-and-rbac.md`, `/operations/cli-release-and-packaging.md` (imposed rule). | Docs Guild | Path: docs | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 43 — CLI Parity & Task Packs Phase 3 | DEVOPS-CLI-43-001 | TODO | Finalize multi-platform release automation, SBOM signing, parity gate enforcement, pack run chaos tests. | DevOps Guild | Path: ops/devops | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 43 — CLI Parity & Task Packs Phase 3 | AUTH-PACKS-41-001 | TODO | Enforce pack signing policies, approval RBAC, CLI token scopes for CI headless runs. | Authority Core & Security Guild | Path: src/Authority/StellaOps.Authority | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 43 — CLI Parity & Task Packs Phase 3 | CLI-PACKS-42-001 | TODO | Deliver advanced pack features (approvals pause/resume, remote streaming, secret injection), localization, man pages. | DevEx/CLI Guild | Path: src/Cli/StellaOps.Cli | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 43 — CLI Parity & Task Packs Phase 3 | EXPORT-SVC-35-005, PACKS-REG-41-001 | TODO | Integrate pack run manifests into export bundles and CLI verify flows. | Exporter Service Guild | Path: src/ExportCenter/StellaOps.ExportCenter | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 43 — CLI Parity & Task Packs Phase 3 | PACKS-REG-42-001 | TODO | Enforce pack signing policies, audit trails, registry mirroring, Offline Kit support. | Packs Registry Guild | Path: src/PacksRegistry/StellaOps.PacksRegistry | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 43 — CLI Parity & Task Packs Phase 3 | TASKRUN-42-001 | TODO | Implement approvals workflow, notifications integration, remote artifact uploads, chaos resilience. | Task Runner Guild | Path: src/TaskRunner/StellaOps.TaskRunner | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 43 — CLI Parity & Task Packs Phase 3 | TASKRUN-42-001 | BLOCKED (2025-11-25) | Implement approvals workflow, notifications integration, remote artifact uploads, chaos resilience; blocked until TASKRUN-42-001 unblocks. | Task Runner Guild | Path: src/TaskRunner/StellaOps.TaskRunner | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 44 — Containerized Distribution Phase 1 | DOCS-INSTALL-44-001 | TODO | Publish install overview + Compose Quickstart docs (imposed rule). | Docs Guild | Path: docs | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 44 — Containerized Distribution Phase 1 | COMPOSE-44-001 | TODO | Deliver Quickstart Compose stack with seed data and quickstart script. | Deployment Guild | Path: ops/deployment | 2025-10-19 |
|
||||
| docs/implplan/archived/updates/tasks.md | Sprint 44 — Containerized Distribution Phase 1 | COMPOSE-44-002 | TODO | Provide backup/reset scripts with guardrails and documentation. | Deployment Guild | Path: ops/deployment | 2025-10-19 |
|
||||
|
||||
@@ -1093,14 +1093,14 @@ This file describe implementation of Stella Ops (docs/README.md). Implementation
|
||||
| Sprint 42 | CLI Parity & Task Packs Phase 2 | src/Orchestrator/StellaOps.Orchestrator | TODO | Orchestrator Service Guild | ORCH-SVC-42-101 | Stream pack run logs via SSE/WS, expose artifact manifests, enforce pack run quotas. |
|
||||
| Sprint 42 | CLI Parity & Task Packs Phase 2 | src/PacksRegistry/StellaOps.PacksRegistry | DONE (2025-11-25) | Packs Registry Guild | PACKS-REG-42-001 | Support pack version lifecycle, tenant allowlists, provenance export, signature rotation. |
|
||||
| Sprint 42 | CLI Parity & Task Packs Phase 2 | src/Policy/StellaOps.Policy.Engine | TODO | Policy Guild | POLICY-ENGINE-42-201 | Provide stable rationale IDs/APIs for CLI `--explain` and pack policy gates. |
|
||||
| Sprint 42 | CLI Parity & Task Packs Phase 2 | src/TaskRunner/StellaOps.TaskRunner | TODO | Task Runner Guild | TASKRUN-42-001 | Add loops, conditionals, `maxParallel`, outputs, simulation mode, policy gates in Task Runner. |
|
||||
| Sprint 42 | CLI Parity & Task Packs Phase 2 | src/TaskRunner/StellaOps.TaskRunner | BLOCKED (2025-11-25) | Task Runner Guild | TASKRUN-42-001 | Add loops, conditionals, `maxParallel`, outputs, simulation mode, policy gates in Task Runner; blocked awaiting control-flow/policy-gate addendum. |
|
||||
| Sprint 43 | CLI Parity & Task Packs Phase 3 | docs | TODO | Docs Guild | DOCS-PACKS-43-001 | Publish `/docs/task-packs/authoring-guide.md`, `/registry.md`, `/runbook.md`, `/security/pack-signing-and-rbac.md`, `/operations/cli-release-and-packaging.md` (imposed rule). |
|
||||
| Sprint 43 | CLI Parity & Task Packs Phase 3 | ops/devops | TODO | DevOps Guild | DEVOPS-CLI-43-001 | Finalize multi-platform release automation, SBOM signing, parity gate enforcement, pack run chaos tests. |
|
||||
| Sprint 43 | CLI Parity & Task Packs Phase 3 | src/Authority/StellaOps.Authority | TODO | Authority Core & Security Guild | AUTH-PACKS-41-001 | Enforce pack signing policies, approval RBAC, CLI token scopes for CI headless runs. |
|
||||
| Sprint 43 | CLI Parity & Task Packs Phase 3 | src/Cli/StellaOps.Cli | TODO | DevEx/CLI Guild | CLI-PACKS-42-001 | Deliver advanced pack features (approvals pause/resume, remote streaming, secret injection), localization, man pages. |
|
||||
| Sprint 43 | CLI Parity & Task Packs Phase 3 | src/ExportCenter/StellaOps.ExportCenter | TODO | Exporter Service Guild | EXPORT-SVC-35-005, PACKS-REG-41-001 | Integrate pack run manifests into export bundles and CLI verify flows. |
|
||||
| Sprint 43 | CLI Parity & Task Packs Phase 3 | src/PacksRegistry/StellaOps.PacksRegistry | DONE (2025-11-25) | Packs Registry Guild | PACKS-REG-42-001 | Enforce pack signing policies, audit trails, registry mirroring, Offline Kit support. |
|
||||
| Sprint 43 | CLI Parity & Task Packs Phase 3 | src/TaskRunner/StellaOps.TaskRunner | TODO | Task Runner Guild | TASKRUN-42-001 | Implement approvals workflow, notifications integration, remote artifact uploads, chaos resilience. |
|
||||
| Sprint 43 | CLI Parity & Task Packs Phase 3 | src/TaskRunner/StellaOps.TaskRunner | BLOCKED (2025-11-25) | Task Runner Guild | TASKRUN-42-001 | Implement approvals workflow, notifications integration, remote artifact uploads, chaos resilience; blocked until TASKRUN-42-001 unblocks. |
|
||||
| Sprint 44 | Containerized Distribution Phase 1 | docs | TODO | Docs Guild | DOCS-INSTALL-44-001 | Publish install overview + Compose Quickstart docs (imposed rule). |
|
||||
| Sprint 44 | Containerized Distribution Phase 1 | ops/deployment | TODO | Deployment Guild | COMPOSE-44-001 | Deliver Quickstart Compose stack with seed data and quickstart script. |
|
||||
| Sprint 44 | Containerized Distribution Phase 1 | ops/deployment | TODO | Deployment Guild | COMPOSE-44-002 | Provide backup/reset scripts with guardrails and documentation. |
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
| 34-101 | DONE | 2025-11-22 | SPRINT_0120_0000_0001_policy_reasoning | Findings Ledger Guild | src/Findings/StellaOps.Findings.Ledger | 29-009 | LEDGER-29-009 | PLLG0104 |
|
||||
| 401-004 | BLOCKED | 2025-11-25 | SPRINT_0401_0001_0001_reachability_evidence_chain | Replay Core Guild | `src/__Libraries/StellaOps.Replay.Core` | Signals facts stable (SGSI0101) | Blocked: awaiting SGSI0101 runtime facts + CAS policy from GAP-REP-004 | RPRC0101 |
|
||||
| BENCH-DETERMINISM-401-057 | DONE (2025-11-27) | 2025-11-27 | SPRINT_0512_0001_0001_bench | Bench Guild · Signals Guild · Policy Guild | src/Bench/StellaOps.Bench/Determinism | Determinism harness + mock scanner; manifests/results generated; CI workflow `bench-determinism` enforces threshold; defaults to 10 runs; supports frozen feed manifests via DET_EXTRA_INPUTS; offline runner available. | Feed-freeze hash + SBOM/VEX bundle list (SPRINT_0401) | |
|
||||
| 41-001 | DONE (2025-11-30) | 2025-11-30 | SPRINT_157_taskrunner_i | Task Runner Guild | src/TaskRunner/StellaOps.TaskRunner | — | Contract implemented per `docs/modules/taskrunner/architecture.md`; run API/storage/provenance ready. | ORTR0101 |
|
||||
| 41-001 | DONE (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild | src/TaskRunner/StellaOps.TaskRunner | — | Contract implemented per `docs/modules/taskrunner/architecture.md`; run API/storage/provenance ready. | ORTR0101 |
|
||||
| 44-001 | BLOCKED | 2025-11-25 | SPRINT_501_ops_deployment_i | Deployment Guild · DevEx Guild (ops/deployment) | ops/deployment | — | Waiting on consolidated service list/version pins from upstream module releases (mirrors Compose-44-001 block) | DVDO0103 |
|
||||
| 44-002 | BLOCKED | 2025-11-25 | SPRINT_501_ops_deployment_i | Deployment Guild (ops/deployment) | ops/deployment | 44-001 | Blocked until 44-001 unblocks | DVDO0103 |
|
||||
| 44-003 | BLOCKED | 2025-11-25 | SPRINT_501_ops_deployment_i | Deployment Guild · Docs Guild (ops/deployment) | ops/deployment | 44-002 | Blocked until 44-002 unblocks | DVDO0103 |
|
||||
@@ -1494,9 +1494,9 @@
|
||||
| RECIPES-DOCS-0001 | TODO | | SPRINT_315_docs_modules_ci | Docs Guild (docs/modules/ci) | docs/modules/ci | | | |
|
||||
| RECIPES-ENG-0001 | TODO | | SPRINT_315_docs_modules_ci | Module Team (docs/modules/ci) | docs/modules/ci | | | |
|
||||
| RECIPES-OPS-0001 | TODO | | SPRINT_315_docs_modules_ci | Ops Guild (docs/modules/ci) | docs/modules/ci | | | |
|
||||
| REG-41-001 | TODO | | SPRINT_154_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REG-42-001 | TODO | | SPRINT_154_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REG-43-001 | TODO | | SPRINT_154_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REG-41-001 | DONE (2025-11-25) | 2025-11-25 | SPRINT_0154_0001_0001_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REG-42-001 | DONE (2025-11-25) | 2025-11-25 | SPRINT_0154_0001_0001_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REG-43-001 | DONE (2025-11-25) | 2025-11-25 | SPRINT_0154_0001_0001_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REGISTRY-API-27-001 | TODO | | SPRINT_0129_0001_0001_policy_reasoning | Policy Registry Guild / src/Policy/StellaOps.Policy.Registry | src/Policy/StellaOps.Policy.Registry | Define OpenAPI specification covering workspaces, versions, reviews, simulations, promotions, and attestations; publish typed clients for Console/CLI | | |
|
||||
| REGISTRY-API-27-002 | TODO | | SPRINT_0129_0001_0001_policy_reasoning | Policy Registry Guild / src/Policy/StellaOps.Policy.Registry | src/Policy/StellaOps.Policy.Registry | Implement workspace storage | REGISTRY-API-27-001 | |
|
||||
| REGISTRY-API-27-003 | TODO | | SPRINT_0129_0001_0001_policy_reasoning | Policy Registry Guild / src/Policy/StellaOps.Policy.Registry | src/Policy/StellaOps.Policy.Registry | Integrate compile endpoint: forward source bundle to Policy Engine, persist diagnostics, symbol table, rule index, and complexity metrics | REGISTRY-API-27-002 | |
|
||||
@@ -1933,8 +1933,8 @@
|
||||
| SYMS-CLIENT-401-012 | TODO | | SPRINT_0401_0001_0001_reachability_evidence_chain | Symbols Guild · Scanner Guild | `src/Symbols/StellaOps.Symbols.Client`, `src/Scanner/StellaOps.Scanner.Symbolizer` | Ship `StellaOps.Symbols.Client` SDK (resolve/upload APIs, platform key derivation for ELF/PDB/Mach-O/JVM/Node, disk LRU cache) and integrate with Scanner.Symbolizer/runtime probes (ref. `docs/specs/SYMBOL_MANIFEST_v1.md`). | Depends on #3 | RBSY0101 |
|
||||
| SYMS-INGEST-401-013 | TODO | | SPRINT_0401_0001_0001_reachability_evidence_chain | Symbols Guild · DevOps Guild | `src/Symbols/StellaOps.Symbols.Ingestor.Cli`, `docs/specs/SYMBOL_MANIFEST_v1.md` | Build `symbols ingest` CLI to emit DSSE-signed `SymbolManifest v1`, upload blobs, and register Rekor entries; document GitLab/Gitea pipeline usage. | Needs manifest updates from #1 | RBSY0101 |
|
||||
| SYMS-SERVER-401-011 | TODO | | SPRINT_0401_0001_0001_reachability_evidence_chain | Symbols Guild | `src/Symbols/StellaOps.Symbols.Server` | Deliver `StellaOps.Symbols.Server` (REST+gRPC) with DSSE-verified uploads, Mongo/MinIO storage, tenant isolation, and deterministic debugId indexing; publish health/manifest APIs (spec: `docs/specs/SYMBOL_MANIFEST_v1.md`). | Depends on #5 | RBSY0101 |
|
||||
| TASKRUN-41-001 | BLOCKED (2025-11-25) | 2025-11-25 | SPRINT_0157_0001_0002_taskrunner_blockers | Task Runner Guild | src/TaskRunner/StellaOps.TaskRunner | Bootstrap service, define migrations for `pack_runs`, `pack_run_logs`, `pack_artifacts`, implement run API (create/get/log stream), local executor, approvals pause, artifact capture, and provenance manifest generation. | Missing TaskRunner architecture/API contract (Sprints 120/130/140). | ORTR0101 |
|
||||
| TASKRUN-AIRGAP-56-001 | BLOCKED (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · AirGap Policy Guild | src/TaskRunner/StellaOps.TaskRunner | Enforce plan-time validation rejecting steps with non-allowlisted network calls in sealed mode and surface remediation errors. | TASKRUN-41-001 | ORTR0101 |
|
||||
| TASKRUN-41-001 | DONE (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0002_taskrunner_blockers | Task Runner Guild | src/TaskRunner/StellaOps.TaskRunner | Bootstrap service, define migrations for `pack_runs`, `pack_run_logs`, `pack_artifacts`, implement run API (create/get/log stream), local executor, approvals pause, artifact capture, and provenance manifest generation. | Delivered per Task Pack advisory and architecture contract. | ORTR0101 |
|
||||
| TASKRUN-AIRGAP-56-001 | DONE (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · AirGap Policy Guild | src/TaskRunner/StellaOps.TaskRunner | Enforce plan-time validation rejecting steps with non-allowlisted network calls in sealed mode and surface remediation errors. | TASKRUN-41-001 | ORTR0101 |
|
||||
| TASKRUN-AIRGAP-56-002 | BLOCKED (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · AirGap Importer Guild | src/TaskRunner/StellaOps.TaskRunner | Add helper steps for bundle ingestion (checksum verification, staging to object store) with deterministic outputs. | TASKRUN-AIRGAP-56-001 | ORTR0101 |
|
||||
| TASKRUN-AIRGAP-57-001 | BLOCKED (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · AirGap Controller Guild | src/TaskRunner/StellaOps.TaskRunner | Refuse to execute plans when environment sealed=false but declared sealed install; emit advisory timeline events. | TASKRUN-AIRGAP-56-002 | ORTR0101 |
|
||||
| TASKRUN-AIRGAP-58-001 | BLOCKED (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · Evidence Locker Guild | src/TaskRunner/StellaOps.TaskRunner | Capture bundle import job transcripts, hashed inputs, and outputs into portable evidence bundles. | TASKRUN-AIRGAP-57-001 | ORTR0101 |
|
||||
@@ -2264,7 +2264,7 @@
|
||||
| 31-009 | DONE | 2025-11-12 | SPRINT_110_ingestion_evidence | Advisory AI Guild | src/AdvisoryAI/StellaOps.AdvisoryAI | — | — | ADAI0101 |
|
||||
| 34-101 | DONE | 2025-11-22 | SPRINT_0120_0000_0001_policy_reasoning | Findings Ledger Guild | src/Findings/StellaOps.Findings.Ledger | 29-009 | LEDGER-29-009 | PLLG0104 |
|
||||
| 401-004 | BLOCKED | 2025-11-25 | SPRINT_0401_0001_0001_reachability_evidence_chain | Replay Core Guild | `src/__Libraries/StellaOps.Replay.Core` | Signals facts stable (SGSI0101) | Blocked: awaiting SGSI0101 runtime facts + CAS policy from GAP-REP-004 | RPRC0101 |
|
||||
| 41-001 | TODO | 2025-11-30 | SPRINT_157_taskrunner_i | Task Runner Guild | src/TaskRunner/StellaOps.TaskRunner | — | Contract landed via product advisory 2025-11-29; implement per `docs/modules/taskrunner/architecture.md`. | ORTR0101 |
|
||||
| 41-001 | DONE (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild | src/TaskRunner/StellaOps.TaskRunner | — | Contract landed via product advisory 2025-11-29; implemented per `docs/modules/taskrunner/architecture.md`. | ORTR0101 |
|
||||
| 44-001 | TODO | | SPRINT_501_ops_deployment_i | Deployment Guild · DevEx Guild (ops/deployment) | ops/deployment | — | — | DVDO0103 |
|
||||
| 44-002 | TODO | | SPRINT_501_ops_deployment_i | Deployment Guild (ops/deployment) | ops/deployment | 44-001 | 44-001 | DVDO0103 |
|
||||
| 44-003 | TODO | | SPRINT_501_ops_deployment_i | Deployment Guild · Docs Guild (ops/deployment) | ops/deployment | 44-002 | 44-002 | DVDO0103 |
|
||||
@@ -3707,9 +3707,9 @@
|
||||
| RECIPES-DOCS-0001 | TODO | | SPRINT_315_docs_modules_ci | Docs Guild (docs/modules/ci) | docs/modules/ci | | | |
|
||||
| RECIPES-ENG-0001 | TODO | | SPRINT_315_docs_modules_ci | Module Team (docs/modules/ci) | docs/modules/ci | | | |
|
||||
| RECIPES-OPS-0001 | TODO | | SPRINT_315_docs_modules_ci | Ops Guild (docs/modules/ci) | docs/modules/ci | | | |
|
||||
| REG-41-001 | TODO | | SPRINT_154_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REG-42-001 | TODO | | SPRINT_154_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REG-43-001 | TODO | | SPRINT_154_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REG-41-001 | DONE (2025-11-25) | 2025-11-25 | SPRINT_0154_0001_0001_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REG-42-001 | DONE (2025-11-25) | 2025-11-25 | SPRINT_0154_0001_0001_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REG-43-001 | DONE (2025-11-25) | 2025-11-25 | SPRINT_0154_0001_0001_packsregistry | Packs Registry Guild (src/PacksRegistry/StellaOps.PacksRegistry) | src/PacksRegistry/StellaOps.PacksRegistry | | | |
|
||||
| REGISTRY-API-27-001 | TODO | | SPRINT_0129_0001_0001_policy_reasoning | Policy Registry Guild / src/Policy/StellaOps.Policy.Registry | src/Policy/StellaOps.Policy.Registry | Define OpenAPI specification covering workspaces, versions, reviews, simulations, promotions, and attestations; publish typed clients for Console/CLI | | |
|
||||
| REGISTRY-API-27-002 | TODO | | SPRINT_0129_0001_0001_policy_reasoning | Policy Registry Guild / src/Policy/StellaOps.Policy.Registry | src/Policy/StellaOps.Policy.Registry | Implement workspace storage | REGISTRY-API-27-001 | |
|
||||
| REGISTRY-API-27-003 | TODO | | SPRINT_0129_0001_0001_policy_reasoning | Policy Registry Guild / src/Policy/StellaOps.Policy.Registry | src/Policy/StellaOps.Policy.Registry | Integrate compile endpoint: forward source bundle to Policy Engine, persist diagnostics, symbol table, rule index, and complexity metrics | REGISTRY-API-27-002 | |
|
||||
@@ -4145,8 +4145,8 @@
|
||||
| SYMS-CLIENT-401-012 | TODO | | SPRINT_0401_0001_0001_reachability_evidence_chain | Symbols Guild · Scanner Guild | `src/Symbols/StellaOps.Symbols.Client`, `src/Scanner/StellaOps.Scanner.Symbolizer` | Ship `StellaOps.Symbols.Client` SDK (resolve/upload APIs, platform key derivation for ELF/PDB/Mach-O/JVM/Node, disk LRU cache) and integrate with Scanner.Symbolizer/runtime probes (ref. `docs/specs/SYMBOL_MANIFEST_v1.md`). | Depends on #3 | RBSY0101 |
|
||||
| SYMS-INGEST-401-013 | TODO | | SPRINT_0401_0001_0001_reachability_evidence_chain | Symbols Guild · DevOps Guild | `src/Symbols/StellaOps.Symbols.Ingestor.Cli`, `docs/specs/SYMBOL_MANIFEST_v1.md` | Build `symbols ingest` CLI to emit DSSE-signed `SymbolManifest v1`, upload blobs, and register Rekor entries; document GitLab/Gitea pipeline usage. | Needs manifest updates from #1 | RBSY0101 |
|
||||
| SYMS-SERVER-401-011 | TODO | | SPRINT_0401_0001_0001_reachability_evidence_chain | Symbols Guild | `src/Symbols/StellaOps.Symbols.Server` | Deliver `StellaOps.Symbols.Server` (REST+gRPC) with DSSE-verified uploads, Mongo/MinIO storage, tenant isolation, and deterministic debugId indexing; publish health/manifest APIs (spec: `docs/specs/SYMBOL_MANIFEST_v1.md`). | Depends on #5 | RBSY0101 |
|
||||
| TASKRUN-41-001 | TODO | 2025-11-30 | SPRINT_0157_0001_0002_taskrunner_blockers | Task Runner Guild | src/TaskRunner/StellaOps.TaskRunner | Bootstrap service, define migrations for `pack_runs`, `pack_run_logs`, `pack_artifacts`, implement run API (create/get/log stream), local executor, approvals pause, artifact capture, and provenance manifest generation. | Contract available via `docs/product-advisories/29-Nov-2025 - Task Pack Orchestration and Automation.md` and `docs/modules/taskrunner/architecture.md`. | ORTR0101 |
|
||||
| TASKRUN-AIRGAP-56-001 | BLOCKED (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · AirGap Policy Guild | src/TaskRunner/StellaOps.TaskRunner | Enforce plan-time validation rejecting steps with non-allowlisted network calls in sealed mode and surface remediation errors. | TASKRUN-41-001 | ORTR0101 |
|
||||
| TASKRUN-41-001 | DONE (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0002_taskrunner_blockers | Task Runner Guild | src/TaskRunner/StellaOps.TaskRunner | Bootstrap service, define migrations for `pack_runs`, `pack_run_logs`, `pack_artifacts`, implement run API (create/get/log stream), local executor, approvals pause, artifact capture, and provenance manifest generation. | Delivered per Task Pack advisory and architecture contract. | ORTR0101 |
|
||||
| TASKRUN-AIRGAP-56-001 | DONE (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · AirGap Policy Guild | src/TaskRunner/StellaOps.TaskRunner | Enforce plan-time validation rejecting steps with non-allowlisted network calls in sealed mode and surface remediation errors. | TASKRUN-41-001 | ORTR0101 |
|
||||
| TASKRUN-AIRGAP-56-002 | BLOCKED (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · AirGap Importer Guild | src/TaskRunner/StellaOps.TaskRunner | Add helper steps for bundle ingestion (checksum verification, staging to object store) with deterministic outputs. | TASKRUN-AIRGAP-56-001 | ORTR0101 |
|
||||
| TASKRUN-AIRGAP-57-001 | BLOCKED (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · AirGap Controller Guild | src/TaskRunner/StellaOps.TaskRunner | Refuse to execute plans when environment sealed=false but declared sealed install; emit advisory timeline events. | TASKRUN-AIRGAP-56-002 | ORTR0101 |
|
||||
| TASKRUN-AIRGAP-58-001 | BLOCKED (2025-11-30) | 2025-11-30 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · Evidence Locker Guild | src/TaskRunner/StellaOps.TaskRunner | Capture bundle import job transcripts, hashed inputs, and outputs into portable evidence bundles. | TASKRUN-AIRGAP-57-001 | ORTR0101 |
|
||||
@@ -4158,8 +4158,6 @@
|
||||
| TASKRUN-OBS-51-001 | DONE (2025-11-25) | 2025-11-25 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · DevOps Guild | src/TaskRunner/StellaOps.TaskRunner | Emit metrics for step latency, retries, queue depth, sandbox resource usage; define SLOs for pack run completion and failure rate; surface burn-rate alerts to collector/Notifier. Dependencies: TASKRUN-OBS-50-001. | TASKRUN-OBS-50-001 | ORTR0102 |
|
||||
| TASKRUN-OBS-52-001 | BLOCKED (2025-11-25) | 2025-11-25 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild | src/TaskRunner/StellaOps.TaskRunner | Produce timeline events for pack runs (`pack.started`, `pack.step.completed`, `pack.failed`) containing evidence pointers and policy gate context. Provide dedupe + retry logic. Blocked: timeline event schema and evidence-pointer contract not published. | TASKRUN-OBS-51-001 | ORTR0102 |
|
||||
| TASKRUN-OBS-53-001 | BLOCKED (2025-11-25) | 2025-11-25 | SPRINT_0157_0001_0001_taskrunner_i | Task Runner Guild · Evidence Locker Guild | src/TaskRunner/StellaOps.TaskRunner | Capture step transcripts, artifact manifests, environment digests, and policy approvals into evidence locker snapshots; ensure redaction + hash chain coverage. Blocked: waiting on timeline schema/evidence-pointer contract (OBS-52-001). | TASKRUN-OBS-52-001 | ORTR0102 |
|
||||
| TASKRUN-OBS-54-001 | TODO | | SPRINT_158_taskrunner_ii | Task Runner Guild · Provenance Guild | src/TaskRunner/StellaOps.TaskRunner | Generate DSSE attestations for pack runs (subjects = produced artifacts) and expose verification API/CLI integration. Store references in timeline events. Dependencies: TASKRUN-OBS-53-001. | TASKRUN-OBS-53-001 | ORTR0102 |
|
||||
| TASKRUN-OBS-55-001 | TODO | | SPRINT_158_taskrunner_ii | Task Runner Guild · DevOps Guild | src/TaskRunner/StellaOps.TaskRunner | Implement incident mode escalations (extra telemetry, debug artifact capture, retention bump) and align on automatic activation via SLO breach webhooks. Dependencies: TASKRUN-OBS-54-001. | TASKRUN-OBS-54-001 | ORTR0102 |
|
||||
| TASKRUN-TEN-48-001 | BLOCKED (2025-11-30) | 2025-11-30 | SPRINT_0158_0001_0002_taskrunner_ii | Task Runner Guild | src/TaskRunner/StellaOps.TaskRunner | Require tenant/project context for every pack run, set DB/object-store prefixes, block egress when tenant restricted, and propagate context to steps/logs. | TASKRUN-OBS-53-001; Tenancy policy contract | ORTR0101 |
|
||||
| TELEMETRY-DOCS-0001 | TODO | | SPRINT_330_docs_modules_telemetry | Docs Guild | docs/modules/telemetry | Validate that telemetry module docs reflect the new storage stack and isolation rules. | Ops checklist from DVDO0103 | DOTL0101 |
|
||||
| TELEMETRY-DOCS-0001 | TODO | | SPRINT_330_docs_modules_telemetry | Docs Guild | docs/modules/telemetry | Validate that telemetry module docs reflect the new storage stack and isolation rules. | Ops checklist from DVDO0103 | DOTL0101 |
|
||||
|
||||
@@ -8,6 +8,7 @@ Attestor moves signed evidence through the trust chain by accepting DSSE bundles
|
||||
- [Architecture](./architecture.md)
|
||||
- [Implementation plan](./implementation_plan.md)
|
||||
- [Task board](./TASKS.md)
|
||||
- [Observability runbook](./operations/observability.md) (offline import friendly)
|
||||
|
||||
## How to get started
|
||||
1. Open sprint file `/docs/implplan/SPRINT_*.md` and locate the stories referencing this module.
|
||||
|
||||
@@ -477,11 +477,11 @@ sequenceDiagram
|
||||
|
||||
---
|
||||
|
||||
## 11) Failure modes & responses
|
||||
|
||||
| Condition | Return | Details | | |
|
||||
| ------------------------------------- | ----------------------- | --------------------------------------------------------- | -------- | ------------ |
|
||||
| mTLS/OpTok invalid | `401 invalid_token` | Include `WWW-Authenticate` DPoP challenge when applicable | | |
|
||||
## 11) Failure modes & responses
|
||||
|
||||
| Condition | Return | Details | | |
|
||||
| ------------------------------------- | ----------------------- | --------------------------------------------------------- | -------- | ------------ |
|
||||
| mTLS/OpTok invalid | `401 invalid_token` | Include `WWW-Authenticate` DPoP challenge when applicable | | |
|
||||
| Bundle not signed by trusted identity | `403 chain_untrusted` | DSSE accepted only from Signer identities | | |
|
||||
| Duplicate bundle | `409 duplicate_bundle` | Return existing `uuid` (idempotent) | | |
|
||||
| Rekor unreachable/timeout | `502 rekor_unavailable` | Retry with backoff; surface `Retry-After` | | |
|
||||
@@ -529,5 +529,14 @@ sequenceDiagram
|
||||
|
||||
* **Dual‑log** write (primary + mirror) and **cross‑log proof** packaging.
|
||||
* **Cloud endorsement**: send `{uuid, artifactSha256}` to Stella Ops cloud; store returned endorsement id for marketing/chain‑of‑custody.
|
||||
* **Checkpoint pinning**: periodically pin latest Rekor checkpoints to an external audit store for independent monitoring.
|
||||
* **Checkpoint pinning**: periodically pin latest Rekor checkpoints to an external audit store for independent monitoring.
|
||||
|
||||
---
|
||||
|
||||
## 16) Observability (stub)
|
||||
|
||||
- Runbook + dashboard placeholder for offline import: `operations/observability.md`, `operations/dashboards/attestor-observability.json`.
|
||||
- Metrics to surface: signing latency p95/p99, verification failure rate, transparency log submission lag, key rotation age, queue backlog, attestation bundle size histogram.
|
||||
- Health endpoints: `/health/liveness`, `/health/readiness`, `/status`; verification probe `/api/attestations/verify` once demo bundle is available (see runbook).
|
||||
- Alert hints: signing latency > 1s p99, verification failure spikes, tlog submission lag >10s, key rotation age over policy threshold, backlog above configured threshold.
|
||||
|
||||
|
||||
@@ -67,11 +67,16 @@
|
||||
- **Performance:** throughput benchmarks, cache hit-rate monitoring, large batch verification.
|
||||
- **Chaos:** inject Rekor outages, network failures, corrupt bundles; ensure graceful degradation and auditable alerts.
|
||||
|
||||
## Definition of done
|
||||
- Phased milestones delivered with telemetry, documentation, and runbooks in place.
|
||||
- CLI/Console parity verified; Offline Kit procedures validated in sealed environment.
|
||||
- Cross-module dependencies acknowledged in ./TASKS.md and ../../TASKS.md.
|
||||
- Documentation set refreshed (overview, architecture, key management, transparency, CLI/UI) with imposed rule statement.
|
||||
## Definition of done
|
||||
- Phased milestones delivered with telemetry, documentation, and runbooks in place.
|
||||
- CLI/Console parity verified; Offline Kit procedures validated in sealed environment.
|
||||
- Cross-module dependencies acknowledged in ./TASKS.md and ../../TASKS.md.
|
||||
- Documentation set refreshed (overview, architecture, key management, transparency, CLI/UI) with imposed rule statement.
|
||||
|
||||
## Sprint alignment (2025-11-30)
|
||||
- Docs sprint: `docs/implplan/SPRINT_0313_0001_0001_docs_modules_attestor.md`; statuses mirrored in `docs/modules/attestor/TASKS.md`.
|
||||
- Observability evidence stub lives in `operations/observability.md` with Grafana placeholder under `operations/dashboards/`; finalize after next demo outputs.
|
||||
- ATTESTOR-OPS-0001 remains BLOCKED until next demo provides observability data; update sprint/TASKS when available.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ Export Center packages reproducible evidence bundles (JSON, Trivy DB, mirror) wi
|
||||
- [Architecture](./architecture.md)
|
||||
- [Implementation plan](./implementation_plan.md)
|
||||
- [Task board](./TASKS.md)
|
||||
- [Observability runbook](./operations/observability.md) (offline import friendly)
|
||||
|
||||
## How to get started
|
||||
1. Open sprint file `/docs/implplan/SPRINT_*.md` and locate the stories referencing this module.
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
|
||||
Export Center packages reproducible evidence bundles (JSON, Trivy DB, mirror) with provenance metadata and optional signing for offline or mirrored deployments.
|
||||
|
||||
## Responsibilities
|
||||
- Coordinate export jobs based on profiles and scope selectors.
|
||||
- Assemble manifests, provenance documents, and cosign signatures.
|
||||
- Stream bundles via HTTP/OCI and stage them for Offline Kit uses.
|
||||
- Expose CLI/API surfaces for automation.
|
||||
## Latest updates (2025-11-30)
|
||||
- Sprint tracker `docs/implplan/SPRINT_0320_0001_0001_docs_modules_export_center.md` and module `TASKS.md` added to mirror status.
|
||||
- Observability runbook stub + dashboard placeholder added under `operations/` (offline import).
|
||||
- Bundle/profile/offline manifest guidance reaffirmed (`devportal-offline*.md`, `mirror-bundles.md`, `provenance-and-signing.md`).
|
||||
|
||||
## Responsibilities
|
||||
- Coordinate export jobs based on profiles and scope selectors.
|
||||
- Assemble manifests, provenance documents, and cosign signatures.
|
||||
- Stream bundles via HTTP/OCI and stage them for Offline Kit uses.
|
||||
- Expose CLI/API surfaces for automation.
|
||||
|
||||
## Key components
|
||||
- `StellaOps.ExportCenter.WebService` planner.
|
||||
@@ -24,10 +29,11 @@ Export Center packages reproducible evidence bundles (JSON, Trivy DB, mirror) wi
|
||||
- Signer/Attestor for provenance signing.
|
||||
- CLI for operator-managed exports.
|
||||
|
||||
## Operational notes
|
||||
- Runbooks in ./operations/ for deployment and monitoring.
|
||||
- Mirror bundle instructions and validation notes.
|
||||
- Telemetry dashboards for export latency and retry rates.
|
||||
## Operational notes
|
||||
- Runbooks in ./operations/ for deployment and monitoring.
|
||||
- Observability assets: `operations/observability.md` and `operations/dashboards/export-center-observability.json` (offline import).
|
||||
- Mirror bundle instructions and validation notes.
|
||||
- Telemetry dashboards for export latency and retry rates.
|
||||
|
||||
## Related resources
|
||||
- ./operations/runbook.md
|
||||
|
||||
9
docs/modules/export-center/TASKS.md
Normal file
9
docs/modules/export-center/TASKS.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Export Center · TASKS (status mirror)
|
||||
|
||||
| Task ID | Status | Owner(s) | Notes / Evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| EXPORT CENTER-DOCS-0001 | DONE (2025-11-30) | Docs Guild | README/architecture/implementation_plan refreshed; bundle/profiles/offline guidance linked; sprint references added. |
|
||||
| EXPORT CENTER-ENG-0001 | DONE (2025-11-30) | Module Team | TASKS board created; statuses mirrored with `docs/implplan/SPRINT_0320_0001_0001_docs_modules_export_center.md`. |
|
||||
| EXPORT CENTER-OPS-0001 | DONE (2025-11-30) | Ops Guild | Observability runbook stub + Grafana placeholder added; devportal/offline manifest links verified. |
|
||||
|
||||
> Keep this table in lockstep with the sprint Delivery Tracker (TODO/DOING/DONE/BLOCKED updates go to both files).
|
||||
@@ -101,11 +101,11 @@ Adapters expose structured telemetry events (`adapter.start`, `adapter.chunk`, `
|
||||
- **Object storage.** Writes to tenant-prefixed paths (`s3://stella-exports/{tenant}/{run-id}/...`) with immutable retention policies. Retention scheduler purges expired runs based on profile configuration.
|
||||
- **Offline Kit seeding.** Mirror bundles optionally staged into Offline Kit assembly pipelines, inheriting the same manifests and signatures.
|
||||
|
||||
## Observability
|
||||
- **Metrics.** Emits `exporter_run_duration_seconds`, `exporter_run_bytes_total{profile}`, `exporter_run_failures_total{error_code}`, `exporter_active_runs{tenant}`, `exporter_distribution_push_seconds{type}`.
|
||||
- **Logs.** Structured logs with fields `run_id`, `tenant`, `profile_kind`, `adapter`, `phase`, `correlation_id`, `error_code`. Phases include `plan`, `resolve`, `adapter`, `manifest`, `sign`, `distribute`.
|
||||
- **Traces.** Optional OpenTelemetry spans (`export.plan`, `export.fetch`, `export.write`, `export.sign`, `export.distribute`) for cross-service correlation.
|
||||
- **Dashboards & alerts.** DevOps pipeline seeds Grafana dashboards summarising throughput, size, failure ratios, and distribution latency. Alert thresholds: failure rate >5% per profile, median run duration >p95 baseline, signature verification failures >0.
|
||||
## Observability
|
||||
- **Metrics.** Emits `exporter_run_duration_seconds`, `exporter_run_bytes_total{profile}`, `exporter_run_failures_total{error_code}`, `exporter_active_runs{tenant}`, `exporter_distribution_push_seconds{type}`.
|
||||
- **Logs.** Structured logs with fields `run_id`, `tenant`, `profile_kind`, `adapter`, `phase`, `correlation_id`, `error_code`. Phases include `plan`, `resolve`, `adapter`, `manifest`, `sign`, `distribute`.
|
||||
- **Traces.** Optional OpenTelemetry spans (`export.plan`, `export.fetch`, `export.write`, `export.sign`, `export.distribute`) for cross-service correlation.
|
||||
- **Dashboards & alerts.** DevOps pipeline seeds Grafana dashboards summarising throughput, size, failure ratios, and distribution latency. Alert thresholds: failure rate >5% per profile, median run duration >p95 baseline, signature verification failures >0. Runbook + dashboard stub for offline import: `operations/observability.md`, `operations/dashboards/export-center-observability.json`.
|
||||
|
||||
## Security posture
|
||||
- Tenant claim enforced at every query and distribution path; cross-tenant selectors rejected unless explicit cross-tenant mirror feature toggled with signed approval.
|
||||
|
||||
@@ -58,9 +58,14 @@
|
||||
- **Security:** tenant fuzzing, RBAC coverage, redaction/PII filters, key rotation.
|
||||
- **Performance & chaos:** stress exports with large datasets, simulate worker/API failures mid-run, confirm deterministic recovery.
|
||||
|
||||
## Definition of done
|
||||
- Service, worker, and adapters deployed with telemetry & alerting.
|
||||
- CLI & Console workflows published, Offline Kit instructions updated.
|
||||
- Documentation set listed above refreshed; imposed rule statements appended where required.
|
||||
- CI pipelines include schema validation, profile verification, and determinism checks.
|
||||
- ./TASKS.md + ../../TASKS.md reflect current status for in-flight stories.
|
||||
## Definition of done
|
||||
- Service, worker, and adapters deployed with telemetry & alerting.
|
||||
- CLI & Console workflows published, Offline Kit instructions updated.
|
||||
- Documentation set listed above refreshed; imposed rule statements appended where required.
|
||||
- CI pipelines include schema validation, profile verification, and determinism checks.
|
||||
- ./TASKS.md + ../../TASKS.md reflect current status for in-flight stories.
|
||||
|
||||
## Sprint alignment (2025-11-30)
|
||||
- Docs sprint: `docs/implplan/SPRINT_0320_0001_0001_docs_modules_export_center.md`; statuses mirrored in `docs/modules/export-center/TASKS.md`.
|
||||
- Observability evidence stub lives in `operations/observability.md` with Grafana placeholder under `operations/dashboards/`.
|
||||
- Bundle/profile/offline manifest guidance maintained in `devportal-offline*.md`, `mirror-bundles.md`, and `provenance-and-signing.md`; update sprint/TASKS if these change.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"_note": "Placeholder Grafana dashboard stub for Export Center. Replace panels when metrics endpoints are available; keep offline-import friendly.",
|
||||
"schemaVersion": 39,
|
||||
"title": "Export Center Observability (stub)",
|
||||
"panels": []
|
||||
}
|
||||
37
docs/modules/export-center/operations/observability.md
Normal file
37
docs/modules/export-center/operations/observability.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Export Center observability runbook (stub · 2025-11-29 demo)
|
||||
|
||||
## Dashboards (offline import)
|
||||
- Grafana JSON: `docs/modules/export-center/operations/dashboards/export-center-observability.json` (import locally; no external data sources assumed).
|
||||
- Planned panels: export job duration p95/p99, bundle size histogram, registry push latency, provenance/attestation verification failures, queue depth, and error rate per profile.
|
||||
|
||||
## Key metrics
|
||||
- `export_job_duration_seconds_bucket{profile}` — export duration by profile.
|
||||
- `export_bundle_size_bytes_bucket{profile}` — bundle size distribution.
|
||||
- `export_registry_push_latency_seconds_bucket{profile}` — registry push latency.
|
||||
- `export_attestation_failures_total{reason}` — DSSE/provenance verification failures.
|
||||
- `export_queue_depth` — pending export jobs.
|
||||
- `export_manifest_publish_total{result}` — manifest publish successes/failures.
|
||||
|
||||
## Logs & traces
|
||||
- Correlate by `exportId`, `profile`, `tenant`; include `bundleDigest`, `attestationStatus`, `registry`. Traces disabled by default; enable OTLP to on-prem collector when permitted.
|
||||
|
||||
## Health/diagnostics
|
||||
- `/health/liveness` and `/health/readiness` (export service) check storage, registry reachability, and attestation verification path.
|
||||
- `/status` exposes build version, commit, feature flags; verify against offline bundle manifest.
|
||||
- Verification probe: `stella export bundle verify --manifest <path>` once bundle available; validate hashes against manifest.
|
||||
|
||||
## Alert hints
|
||||
- Export job duration p99 > target SLA per profile.
|
||||
- Attestation verification failures > 0 over 10m.
|
||||
- Registry push latency spikes or error rate > threshold.
|
||||
- Queue depth growth without completion.
|
||||
|
||||
## Offline verification steps
|
||||
1) Import Grafana JSON locally; point to Prometheus scrape labeled `export-center`.
|
||||
2) Run `stella export bundle --profile <profile> --manifest out/manifest.json` and verify hashes via `jq -r '.files[].sha256'` against generated bundles.
|
||||
3) Fetch `/status` and compare commit/version to offline bundle manifest.
|
||||
|
||||
## Evidence locations
|
||||
- Sprint tracker: `docs/implplan/SPRINT_0320_0001_0001_docs_modules_export_center.md`.
|
||||
- Module docs: `README.md`, `architecture.md`, `implementation_plan.md`.
|
||||
- Dashboard stub: `operations/dashboards/export-center-observability.json`.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Risk Bundle Provider Matrix & Signing Baseline
|
||||
|
||||
Status: Baseline for Sprint 0164-0001-0001 (RISK-BUNDLE-69/70 chain)
|
||||
|
||||
## Provider catalog (deterministic ordering)
|
||||
| Provider ID | Source feed (offline-ready) | Coverage | Refresh cadence | Signing / integrity | Notes |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| cisa-kev | CISA Known Exploited Vulnerabilities JSON | Exploited CVEs with required/known exploited flag | Daily | DSSE signature using ExportCenter signing key; feed hash recorded in `provider-manifest.json` | Mandatory; fails bundle if feed missing or hash mismatch. |
|
||||
| first-epss | FIRST EPSS CSV snapshot | Probability scores per CVE | Daily | DSSE signature; SHA-256 of snapshot stored in manifest | Optional; omit if snapshot stale >48h unless `allowStale=true`. |
|
||||
| osv | OpenSSF OSV bulk JSON (per-ecosystem shards) | OSS advisories with affected package ranges | Weekly | DSSE signature; per-shard SHA-256 list | Included only when `includeOsv=true` in job options to keep bundle size constrained. |
|
||||
| vendor-csaf | CSAF vendor advisories (Red Hat, SUSE, Debian) mirrored via Offline Kit | Vendor-specific CVEs, remediations | Weekly | Detached signature per CSAF document (vendor-provided where available) plus bundle-level DSSE manifest | Requires Offline Kit mirror; missing vendor feeds logged but bundle continues if `allowPartialVendors=true`. |
|
||||
|
||||
## Manifest baseline
|
||||
- Generate `provider-manifest.json` with sorted provider entries. Fields per provider: `{id, source, snapshotDate, sha256, signaturePath, optional}`.
|
||||
- Store DSSE envelope for `provider-manifest.json` at `signatures/provider-manifest.dsse` (cosign/KMS).
|
||||
- Include provider digests in `manifests/provenance.json` materials array with URI `risk-provider://<id>/<snapshotDate>`.
|
||||
|
||||
## Signing baseline
|
||||
- Use Export Center signing path (cosign + Authority KMS) for:
|
||||
- `provider-manifest.json` (DSSE)
|
||||
- Aggregated `risk-bundle.tar.*` (detached signature `risk-bundle.sig`)
|
||||
- Vendor-provided signatures (when present) are preserved inside `providers/<id>/` and referenced from `provider-manifest.json`.
|
||||
- Rekor publishing remains optional; default **off** for offline kits (`rekor_publish=false`).
|
||||
|
||||
## Validation rules (bundle build)
|
||||
- Fail build if any mandatory provider (currently `cisa-kev`) is missing or hash mismatch.
|
||||
- Warn (non-fatal) when optional providers are stale beyond cadence unless `allowStale=true`.
|
||||
- Deterministic ordering: providers sorted by `id`; files sorted lexicographically inside bundle.
|
||||
- Record bundle-level inputs hash combining provider SHA-256 values (stable ordering) and include in provenance `materials[]`.
|
||||
|
||||
## Verification workflow alignment
|
||||
- CLI `stella risk bundle verify` must validate:
|
||||
- DSSE on `provider-manifest.json`
|
||||
- Hash match for each provider snapshot
|
||||
- Presence (or allowed absence) per `optional` flag
|
||||
- Detached signature on bundle archive (cosign/KMS)
|
||||
- Offline verification uses bundled public key (`signatures/pubkeys/<tenant>.pem`).
|
||||
|
||||
## Next steps / TODOs
|
||||
- Add test fixtures: minimal provider snapshots (kev+epss) with fixed hashes for deterministic regression tests.
|
||||
- Update ExportCenter worker to emit `provider-manifest.json` and DSSE using existing signing pipeline.
|
||||
- Extend CLI verify command to surface per-provider status (missing/stale/hash mismatch) and exit non-zero on mandatory failures.
|
||||
@@ -21,6 +21,7 @@ Related documentation:
|
||||
- `docs/modules/export-center/api.md`
|
||||
- `docs/modules/export-center/cli.md`
|
||||
- `docs/modules/export-center/operations/kms-envelope-pattern.md`
|
||||
- `docs/modules/export-center/operations/risk-bundle-provider-matrix.md`
|
||||
|
||||
## 2. Contacts & tooling
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ Platform module describes cross-cutting architecture, contracts, and guardrails
|
||||
- [Architecture](./architecture.md)
|
||||
- [Implementation plan](./implementation_plan.md)
|
||||
- [Task board](./TASKS.md)
|
||||
- [Architecture overview](./architecture-overview.md)
|
||||
|
||||
## How to get started
|
||||
1. Open sprint file `/docs/implplan/SPRINT_*.md` and locate the stories referencing this module.
|
||||
|
||||
@@ -2,24 +2,31 @@
|
||||
|
||||
Platform module describes cross-cutting architecture, contracts, and guardrails that bind the services together.
|
||||
|
||||
## Responsibilities
|
||||
- Maintain the system-wide architecture overview and integration diagrams.
|
||||
- Capture Aggregation-Only Contract guidance and migration playbooks.
|
||||
- Document shared services such as API gateway, tenancy, quotas, and offline posture.
|
||||
- Coordinate platform-wide epics and compliance checklists.
|
||||
## Latest updates (2025-11-30)
|
||||
- Sprint tracker `docs/implplan/SPRINT_0324_0001_0001_docs_modules_platform.md` and module `TASKS.md` added to mirror status.
|
||||
- README now points to architecture overview, AOC references, and offline guidance entry points.
|
||||
- Platform module remains docs-only; no runtime services.
|
||||
|
||||
## Responsibilities
|
||||
- Maintain the system-wide architecture overview and integration diagrams.
|
||||
- Capture Aggregation-Only Contract guidance and migration playbooks.
|
||||
- Document shared services such as API gateway, tenancy, quotas, and offline posture.
|
||||
- Coordinate platform-wide epics and compliance checklists.
|
||||
|
||||
## Key components
|
||||
- Architecture overview in ./architecture-overview.md.
|
||||
- References to high-level docs (../../07_HIGH_LEVEL_ARCHITECTURE.md).
|
||||
## Key components
|
||||
- Architecture overview in `architecture-overview.md`.
|
||||
- Platform architecture summary in `architecture.md`.
|
||||
- High-level reference: `../../07_HIGH_LEVEL_ARCHITECTURE.md`.
|
||||
|
||||
## Integrations & dependencies
|
||||
- All StellaOps services via shared contracts (AOC, telemetry, security).
|
||||
- DevOps for release governance.
|
||||
- Docs guild for cross-module onboarding.
|
||||
|
||||
## Operational notes
|
||||
- No runtime component; focus is architectural governance.
|
||||
- Glossaries and guardrails cross-linked across docs.
|
||||
## Operational notes
|
||||
- Docs-only module; focus is architectural governance and cross-module guardrails.
|
||||
- Glossaries and guardrails cross-linked across docs; keep AOC references current.
|
||||
- Status mirrors: sprint file and `docs/modules/platform/TASKS.md`.
|
||||
|
||||
## Backlog references
|
||||
- DOCS-AOC-19-002/003 in ../../TASKS.md.
|
||||
|
||||
9
docs/modules/platform/TASKS.md
Normal file
9
docs/modules/platform/TASKS.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Platform · TASKS (status mirror)
|
||||
|
||||
| Task ID | Status | Owner(s) | Notes / Evidence |
|
||||
| --- | --- | --- | --- |
|
||||
| PLATFORM-DOCS-0001 | DONE (2025-11-30) | Docs Guild | README/architecture/implementation_plan refreshed; AOC/offline guardrails linked. |
|
||||
| PLATFORM-ENG-0001 | DONE (2025-11-30) | Module Team | TASKS board created; statuses mirrored with `docs/implplan/SPRINT_0324_0001_0001_docs_modules_platform.md`. |
|
||||
| PLATFORM-OPS-0001 | DONE (2025-11-30) | Ops Guild | Cross-links to architecture-overview and 07_HLA verified; offline guidance highlighted. |
|
||||
|
||||
> Keep this table in lockstep with the sprint Delivery Tracker (TODO/DOING/DONE/BLOCKED updates go to both files).
|
||||
@@ -1,7 +1,18 @@
|
||||
# Platform architecture
|
||||
|
||||
> Cross-cutting view anchored in the Authority, Policy, Graph, Vulnerability Explorer, Orchestrator, Export Center, and Notifications module documentation set.
|
||||
|
||||
This placeholder summarises the planned architecture for Platform. Consolidate design details from implementation plans and upcoming epics before coding.
|
||||
|
||||
Refer to the module README and implementation plan for immediate context, and update this document once component boundaries and data flows are finalised.
|
||||
# Platform architecture (summary)
|
||||
|
||||
This module aggregates cross-cutting contracts and guardrails that every StellaOps service must follow.
|
||||
|
||||
## Anchors
|
||||
- High-level system view: `../../07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- Platform overview: `architecture-overview.md`
|
||||
- Aggregation-Only Contract: `../ingestion/aggregation-only-contract.md` (referenced across ingestion/observability docs)
|
||||
|
||||
## Scope
|
||||
- **Identity & tenancy**: Authority-issued OpToks, tenant scoping, RBAC, short TTLs; see Authority module docs.
|
||||
- **AOC & provenance**: services ingest evidence without mutating/merging; provenance preserved; determinism required.
|
||||
- **Offline posture**: Offline Kit parity, sealed-mode defaults, deterministic bundles.
|
||||
- **Observability baseline**: metrics/logging/tracing patterns reused across modules; collectors documented under Telemetry module.
|
||||
- **Determinism**: stable ordering, UTC timestamps, content-addressed artifacts, reproducible exports.
|
||||
|
||||
## Coordination
|
||||
Platform docs are the starting point for new contributors; keep this summary in sync with module-specific dossiers and sprint references.
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
- **Epics 6–11:** ensure cross-cutting contracts (Explorer, Lens, AI, Orchestrator, Notifications) stay aligned.
|
||||
- Track additional platform updates in ../../TASKS.md and docs/implplan/SPRINTS.md.
|
||||
|
||||
## Coordination
|
||||
- Review ./AGENTS.md before picking up new work.
|
||||
- Sync with cross-cutting teams noted in `/docs/implplan/SPRINT_*.md`.
|
||||
- Update this plan whenever scope, dependencies, or guardrails change.
|
||||
## Coordination
|
||||
- Review ./AGENTS.md before picking up new work.
|
||||
- Sync with cross-cutting teams noted in `/docs/implplan/SPRINT_*.md`.
|
||||
- Update this plan whenever scope, dependencies, or guardrails change.
|
||||
|
||||
## Sprint alignment (2025-11-30)
|
||||
- Docs sprint: `docs/implplan/SPRINT_0324_0001_0001_docs_modules_platform.md`; statuses mirrored in `docs/modules/platform/TASKS.md`.
|
||||
- Keep links to `architecture-overview.md` and `../../07_HIGH_LEVEL_ARCHITECTURE.md` current; update both sprint and TASKS if platform guardrails change.
|
||||
- Platform is docs-only; ensure Offline Kit and AOC references remain discoverable from README/architecture.
|
||||
|
||||
@@ -61,7 +61,7 @@ tests (`npm run test:e2e`) after building the Angular bundle. See
|
||||
`docs/modules/ui/operations/auth-smoke.md` for the job design, environment stubs, and
|
||||
offline runner considerations.
|
||||
|
||||
## NuGet preview bootstrap
|
||||
## NuGet preview bootstrap
|
||||
|
||||
`.NET 10` preview packages (Microsoft.Extensions.*, JwtBearer 10.0 RC, Sqlite 9 RC)
|
||||
ship from the public `dotnet-public` Azure DevOps feed. We mirror them into
|
||||
@@ -77,7 +77,13 @@ prefers the local mirror and that `Directory.Build.props` enforces the same orde
|
||||
The validator now runs automatically in the `build-test-deploy` and `release`
|
||||
workflows so CI fails fast when a feed priority regression slips in.
|
||||
|
||||
Detailed operator instructions live in `docs/modules/devops/runbooks/nuget-preview-bootstrap.md`.
|
||||
Detailed operator instructions live in `docs/modules/devops/runbooks/nuget-preview-bootstrap.md`.
|
||||
|
||||
## CI harnesses (offline-friendly)
|
||||
|
||||
- **Concelier**: `ops/devops/concelier-ci-runner/run-concelier-ci.sh` builds `concelier-webservice.slnf` and runs WebService + Storage Mongo tests. Outputs binlog + TRX + summary under `ops/devops/artifacts/concelier-ci/<ts>/`.
|
||||
- **Advisory AI**: `ops/devops/advisoryai-ci-runner/run-advisoryai-ci.sh` builds `src/AdvisoryAI/StellaOps.AdvisoryAI.sln`, runs `StellaOps.AdvisoryAI.Tests`, and emits binlog + TRX + summary under `ops/devops/artifacts/advisoryai-ci/<ts>/`. Warmed NuGet cache from `local-nugets` for offline parity.
|
||||
- **Scanner**: `ops/devops/scanner-ci-runner/run-scanner-ci.sh` builds `src/Scanner/StellaOps.Scanner.sln` and runs core/analyzer/web/worker test buckets with binlog + TRX outputs under `ops/devops/artifacts/scanner-ci/<ts>/`.
|
||||
|
||||
## Telemetry collector tooling (DEVOPS-OBS-50-001)
|
||||
|
||||
|
||||
0
ops/devops/advisoryai-ci-runner/.gitkeep
Normal file
0
ops/devops/advisoryai-ci-runner/.gitkeep
Normal file
25
ops/devops/advisoryai-ci-runner/README.md
Normal file
25
ops/devops/advisoryai-ci-runner/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Advisory AI CI Runner Harness (DEVOPS-AIAI-31-001)
|
||||
|
||||
Purpose: deterministic, offline-friendly CI harness for Advisory AI service/worker. Produces warmed-cache restore, build binlog, and TRX outputs for the core test suite so downstream sprints can validate without bespoke pipelines.
|
||||
|
||||
Usage
|
||||
- From repo root run: `ops/devops/advisoryai-ci-runner/run-advisoryai-ci.sh`
|
||||
- Outputs land in `ops/devops/artifacts/advisoryai-ci/<UTC timestamp>/`:
|
||||
- `build.binlog` (solution build)
|
||||
- `tests/advisoryai.trx` (VSTest results)
|
||||
- `summary.json` (paths + hashes + durations)
|
||||
|
||||
Environment
|
||||
- Defaults: `DOTNET_CLI_TELEMETRY_OPTOUT=1`, `DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1`, `NUGET_PACKAGES=$REPO/.nuget/packages`.
|
||||
- Sources default to `local-nugets` then the warmed cache; override via `NUGET_SOURCES` (semicolon-separated).
|
||||
- No external services required; tests are isolated/local.
|
||||
|
||||
What it does
|
||||
1) Warm NuGet cache from `local-nugets/` into `$NUGET_PACKAGES` for air-gap parity.
|
||||
2) `dotnet restore` + `dotnet build` on `src/AdvisoryAI/StellaOps.AdvisoryAI.sln` with `/bl`.
|
||||
3) Run the AdvisoryAI test project (`__Tests/StellaOps.AdvisoryAI.Tests`) with TRX output; optional `TEST_FILTER` env narrows scope.
|
||||
4) Emit `summary.json` with artefact paths and SHA256s for reproducibility.
|
||||
|
||||
Notes
|
||||
- Timestamped output folders keep ordering deterministic; consumers should sort lexicographically.
|
||||
- Use `TEST_FILTER="Name~Inference"` to target inference/monitoring-specific tests when iterating.
|
||||
67
ops/devops/advisoryai-ci-runner/run-advisoryai-ci.sh
Normal file
67
ops/devops/advisoryai-ci-runner/run-advisoryai-ci.sh
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Advisory AI CI runner (DEVOPS-AIAI-31-001)
|
||||
# Builds solution and runs tests with warmed NuGet cache; emits binlog + TRX summary.
|
||||
|
||||
repo_root="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
out_dir="$repo_root/ops/devops/artifacts/advisoryai-ci/$ts"
|
||||
logs_dir="$out_dir/tests"
|
||||
mkdir -p "$logs_dir"
|
||||
|
||||
# Deterministic env
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=${DOTNET_CLI_TELEMETRY_OPTOUT:-1}
|
||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=${DOTNET_SKIP_FIRST_TIME_EXPERIENCE:-1}
|
||||
export NUGET_PACKAGES=${NUGET_PACKAGES:-$repo_root/.nuget/packages}
|
||||
export NUGET_SOURCES=${NUGET_SOURCES:-"$repo_root/local-nugets;$repo_root/.nuget/packages"}
|
||||
export TEST_FILTER=${TEST_FILTER:-""}
|
||||
export DOTNET_RESTORE_DISABLE_PARALLEL=${DOTNET_RESTORE_DISABLE_PARALLEL:-1}
|
||||
|
||||
# Warm cache from local feed
|
||||
mkdir -p "$NUGET_PACKAGES"
|
||||
rsync -a "$repo_root/local-nugets/" "$NUGET_PACKAGES/" >/dev/null 2>&1 || true
|
||||
|
||||
# Restore sources
|
||||
restore_sources=()
|
||||
IFS=';' read -ra SRC_ARR <<< "$NUGET_SOURCES"
|
||||
for s in "${SRC_ARR[@]}"; do
|
||||
[[ -n "$s" ]] && restore_sources+=(--source "$s")
|
||||
done
|
||||
|
||||
solution="$repo_root/src/AdvisoryAI/StellaOps.AdvisoryAI.sln"
|
||||
dotnet restore "$solution" --ignore-failed-sources "${restore_sources[@]}"
|
||||
|
||||
# Build with binlog (Release for perf parity)
|
||||
build_binlog="$out_dir/build.binlog"
|
||||
dotnet build "$solution" -c Release /p:ContinuousIntegrationBuild=true /bl:"$build_binlog"
|
||||
|
||||
# Tests
|
||||
common_test_args=( -c Release --no-build --results-directory "$logs_dir" )
|
||||
if [[ -n "$TEST_FILTER" ]]; then
|
||||
common_test_args+=( --filter "$TEST_FILTER" )
|
||||
fi
|
||||
|
||||
trx_name="advisoryai.trx"
|
||||
dotnet test "$repo_root/src/AdvisoryAI/__Tests/StellaOps.AdvisoryAI.Tests/StellaOps.AdvisoryAI.Tests.csproj" \
|
||||
"${common_test_args[@]}" \
|
||||
--logger "trx;LogFileName=$trx_name"
|
||||
|
||||
# Summarize artefacts
|
||||
summary="$out_dir/summary.json"
|
||||
{
|
||||
printf '{\n'
|
||||
printf ' "timestamp_utc": "%s",\n' "$ts"
|
||||
printf ' "build_binlog": "%s",\n' "${build_binlog#${repo_root}/}"
|
||||
printf ' "tests": [{"project":"AdvisoryAI","trx":"%s"}],\n' "${logs_dir#${repo_root}/}/$trx_name"
|
||||
printf ' "nuget_packages": "%s",\n' "${NUGET_PACKAGES#${repo_root}/}"
|
||||
printf ' "sources": [\n'
|
||||
for i in "${!SRC_ARR[@]}"; do
|
||||
sep=","; [[ $i -eq $((${#SRC_ARR[@]}-1)) ]] && sep=""
|
||||
printf ' "%s"%s\n' "${SRC_ARR[$i]}" "$sep"
|
||||
done
|
||||
printf ' ]\n'
|
||||
printf '}\n'
|
||||
} > "$summary"
|
||||
|
||||
echo "Artifacts written to ${out_dir#${repo_root}/}"
|
||||
@@ -9,5 +9,6 @@ Artifacts supporting `DEVOPS-AIRGAP-56-001`:
|
||||
- `stage-bundle.sh` — Thin wrapper around `bundle_stage_import.py` with positional args.
|
||||
- `build_bootstrap_pack.py` — Builds a Bootstrap Pack from images/charts/extras listed in a JSON config, writing `bootstrap-manifest.json` + `checksums.sha256` deterministically.
|
||||
- `build_bootstrap_pack.sh` — Wrapper for the bootstrap pack builder.
|
||||
- `build_mirror_bundle.py` — Generates mirror bundle manifest + checksums with dual-control approvals; optional cosign signing. Outputs `mirror-bundle-manifest.json`, `checksums.sha256`, and optional signature/cert.
|
||||
|
||||
See also `ops/devops/sealed-mode-ci/` for the full sealed-mode compose harness and `egress_probe.py`, which this verification script wraps.
|
||||
|
||||
154
ops/devops/airgap/build_mirror_bundle.py
Normal file
154
ops/devops/airgap/build_mirror_bundle.py
Normal file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Automate mirror bundle manifest + checksums with dual-control approvals.
|
||||
|
||||
Implements DEVOPS-AIRGAP-57-001.
|
||||
|
||||
Features:
|
||||
- Deterministic manifest (`mirror-bundle-manifest.json`) with sha256/size per file.
|
||||
- `checksums.sha256` for quick verification.
|
||||
- Dual-control approvals recorded via `--approver` (min 2 required to mark approved).
|
||||
- Optional cosign signing of the manifest via `--cosign-key` (sign-blob); writes
|
||||
`mirror-bundle-manifest.sig` and `mirror-bundle-manifest.pem` when available.
|
||||
- Offline-friendly: purely local file reads; no network access.
|
||||
|
||||
Usage:
|
||||
build_mirror_bundle.py --root /path/to/bundles --output out/mirror \
|
||||
--approver alice@example.com --approver bob@example.com
|
||||
|
||||
build_mirror_bundle.py --self-test
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> Dict[str, int | str]:
|
||||
h = hashlib.sha256()
|
||||
size = 0
|
||||
with path.open("rb") as f:
|
||||
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
||||
h.update(chunk)
|
||||
size += len(chunk)
|
||||
return {"sha256": h.hexdigest(), "size": size}
|
||||
|
||||
|
||||
def find_files(root: Path) -> List[Path]:
|
||||
files: List[Path] = []
|
||||
for p in sorted(root.rglob("*")):
|
||||
if p.is_file():
|
||||
files.append(p)
|
||||
return files
|
||||
|
||||
|
||||
def write_checksums(items: List[Dict], output_dir: Path) -> None:
|
||||
lines = [f"{item['sha256']} {item['path']}" for item in items]
|
||||
(output_dir / "checksums.sha256").write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")
|
||||
|
||||
|
||||
def maybe_sign(manifest_path: Path, key: Optional[str]) -> Dict[str, str]:
|
||||
if not key:
|
||||
return {"status": "skipped", "reason": "no key provided"}
|
||||
if shutil.which("cosign") is None:
|
||||
return {"status": "skipped", "reason": "cosign not found"}
|
||||
sig = manifest_path.with_suffix(manifest_path.suffix + ".sig")
|
||||
pem = manifest_path.with_suffix(manifest_path.suffix + ".pem")
|
||||
try:
|
||||
subprocess.run(
|
||||
["cosign", "sign-blob", "--key", key, "--output-signature", str(sig), "--output-certificate", str(pem), str(manifest_path)],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return {
|
||||
"status": "signed",
|
||||
"signature": sig.name,
|
||||
"certificate": pem.name,
|
||||
}
|
||||
except subprocess.CalledProcessError as exc: # pragma: no cover
|
||||
return {"status": "failed", "reason": exc.stderr or str(exc)}
|
||||
|
||||
|
||||
def build_manifest(root: Path, output_dir: Path, approvers: List[str], cosign_key: Optional[str]) -> Dict:
|
||||
files = find_files(root)
|
||||
items: List[Dict] = []
|
||||
for p in files:
|
||||
rel = p.relative_to(root).as_posix()
|
||||
info = sha256_file(p)
|
||||
items.append({"path": rel, **info})
|
||||
manifest = {
|
||||
"created": datetime.now(timezone.utc).isoformat(),
|
||||
"root": str(root),
|
||||
"total": len(items),
|
||||
"items": items,
|
||||
"approvals": sorted(set(approvers)),
|
||||
"approvalStatus": "approved" if len(set(approvers)) >= 2 else "pending",
|
||||
}
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
manifest_path = output_dir / "mirror-bundle-manifest.json"
|
||||
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
write_checksums(items, output_dir)
|
||||
signing = maybe_sign(manifest_path, cosign_key)
|
||||
manifest["signing"] = signing
|
||||
# Persist signing status in manifest for traceability
|
||||
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
return manifest
|
||||
|
||||
|
||||
def parse_args(argv: List[str]) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--root", type=Path, help="Root directory containing bundle files")
|
||||
parser.add_argument("--output", type=Path, help="Output directory for manifest + checksums")
|
||||
parser.add_argument("--approver", action="append", default=[], help="Approver identity (email or handle); provide twice for dual-control")
|
||||
parser.add_argument("--cosign-key", help="Path or KMS URI for cosign signing key (optional)")
|
||||
parser.add_argument("--self-test", action="store_true", help="Run internal self-test and exit")
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def self_test() -> int:
|
||||
import tempfile
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmpdir = Path(tmp)
|
||||
root = tmpdir / "bundles"
|
||||
root.mkdir()
|
||||
(root / "a.txt").write_text("hello", encoding="utf-8")
|
||||
(root / "b.bin").write_bytes(b"world")
|
||||
out = tmpdir / "out"
|
||||
manifest = build_manifest(root, out, ["alice", "bob"], cosign_key=None)
|
||||
assert manifest["approvalStatus"] == "approved"
|
||||
assert (out / "mirror-bundle-manifest.json").exists()
|
||||
assert (out / "checksums.sha256").exists()
|
||||
print("self-test passed")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: List[str]) -> int:
|
||||
args = parse_args(argv)
|
||||
if args.self_test:
|
||||
return self_test()
|
||||
if not (args.root and args.output):
|
||||
print("--root and --output are required unless --self-test", file=sys.stderr)
|
||||
return 2
|
||||
manifest = build_manifest(args.root.resolve(), args.output.resolve(), args.approver, args.cosign_key)
|
||||
if manifest["approvalStatus"] != "approved":
|
||||
print("Manifest generated but approvalStatus=pending (need >=2 distinct approvers).", file=sys.stderr)
|
||||
return 1
|
||||
missing = [i for i in manifest["items"] if not (args.root / i["path"]).exists()]
|
||||
if missing:
|
||||
print(f"Missing files in manifest: {missing}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"Mirror bundle manifest written to {args.output}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
10
ops/devops/attestation/README.md
Normal file
10
ops/devops/attestation/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Attestor CI/Secrets (DEVOPS-ATTEST-73-001/002)
|
||||
|
||||
Artifacts added for the DevOps attestation track:
|
||||
|
||||
- `ci.yml` — GitHub Actions workflow (parity stub) that restores/builds/tests Attestor solution and uploads test artefacts. Offline/airgap friendly when mirrored into local runner; set DOTNET_* envs for determinism.
|
||||
- Secrets storage plan:
|
||||
- Use KMS-backed cosign key refs (e.g., `azurekms://...` or `awskms://...`).
|
||||
- Store ref in CI secret `ATTESTOR_COSIGN_KEY`; pipeline passes via env and never writes key material to disk.
|
||||
- Audit logs: enable KMS audit + CI job logs; avoid plaintext key dumps.
|
||||
- Next steps: wire `.gitea/workflows/attestor-ci.yml` to mirror this job, add `cosign sign-blob` stage for DSSE envelopes, and publish artefacts to `ops/devops/artifacts/attestor/<ts>/` with checksums.
|
||||
38
ops/devops/attestation/ci.yml
Normal file
38
ops/devops/attestation/ci.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Attestor CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'src/Attestor/**'
|
||||
- '.gitea/workflows/attestor-ci.yml'
|
||||
- 'ops/devops/attestation/**'
|
||||
|
||||
jobs:
|
||||
build-test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOTNET_NOLOGO: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '10.0.x'
|
||||
- name: Restore
|
||||
run: dotnet restore src/Attestor/StellaOps.Attestor.sln
|
||||
- name: Build
|
||||
run: dotnet build --no-restore -c Release src/Attestor/StellaOps.Attestor.sln
|
||||
- name: Test
|
||||
run: dotnet test --no-build -c Release src/Attestor/StellaOps.Attestor.sln
|
||||
- name: Publish artefacts
|
||||
if: always()
|
||||
run: |
|
||||
mkdir -p out/ci/attestor
|
||||
find src/Attestor -name '*.trx' -o -name '*.xml' | tar -czf out/ci/attestor/test-artifacts.tgz -T-
|
||||
- name: Upload artefacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: attestor-ci-artifacts
|
||||
path: out/ci/attestor/test-artifacts.tgz
|
||||
25
ops/devops/scanner-ci-runner/README.md
Normal file
25
ops/devops/scanner-ci-runner/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Scanner CI Runner Harness (DEVOPS-SCANNER-CI-11-001)
|
||||
|
||||
Purpose: deterministic, offline-friendly harness that restores, builds, and exercises the Scanner analyzers + WebService/Worker tests with warmed NuGet cache and TRX/binlog outputs.
|
||||
|
||||
Usage
|
||||
- From repo root run: `ops/devops/scanner-ci-runner/run-scanner-ci.sh`
|
||||
- Outputs land in `ops/devops/artifacts/scanner-ci/<UTC timestamp>/`:
|
||||
- `build.binlog` (solution build)
|
||||
- `tests/*.trx` for grouped test runs
|
||||
- `summary.json` listing artefact paths and SHA256s
|
||||
|
||||
Environment
|
||||
- Defaults: `DOTNET_CLI_TELEMETRY_OPTOUT=1`, `DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1`, `NUGET_PACKAGES=$REPO/.nuget/packages`.
|
||||
- Sources: `NUGET_SOURCES` (semicolon-separated) defaults to `local-nugets` then warmed cache; no internet required when cache is primed.
|
||||
- `TEST_FILTER` can narrow tests (empty = all).
|
||||
|
||||
What it does
|
||||
1) Warm NuGet cache from `local-nugets/` into `$NUGET_PACKAGES`.
|
||||
2) `dotnet restore` + `dotnet build` on `src/Scanner/StellaOps.Scanner.sln` with `/bl`.
|
||||
3) Run Scanner test buckets (core/analyzers/web/worker) with TRX outputs; buckets can be adjusted via `TEST_FILTER` or script edits.
|
||||
4) Emit `summary.json` with artefact paths/hashes for reproducibility.
|
||||
|
||||
Notes
|
||||
- Buckets are ordered to keep runtime predictable; adjust filters to target a subset when iterating.
|
||||
- Timestamped output directories keep ordering deterministic in offline pipelines.
|
||||
88
ops/devops/scanner-ci-runner/run-scanner-ci.sh
Normal file
88
ops/devops/scanner-ci-runner/run-scanner-ci.sh
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Scanner CI runner harness (DEVOPS-SCANNER-CI-11-001)
|
||||
# Builds Scanner solution and runs grouped test buckets with warmed NuGet cache.
|
||||
|
||||
repo_root="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
out_dir="$repo_root/ops/devops/artifacts/scanner-ci/$ts"
|
||||
logs_dir="$out_dir/tests"
|
||||
mkdir -p "$logs_dir"
|
||||
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=${DOTNET_CLI_TELEMETRY_OPTOUT:-1}
|
||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=${DOTNET_SKIP_FIRST_TIME_EXPERIENCE:-1}
|
||||
export NUGET_PACKAGES=${NUGET_PACKAGES:-$repo_root/.nuget/packages}
|
||||
export NUGET_SOURCES=${NUGET_SOURCES:-"$repo_root/local-nugets;$repo_root/.nuget/packages"}
|
||||
export TEST_FILTER=${TEST_FILTER:-""}
|
||||
export DOTNET_RESTORE_DISABLE_PARALLEL=${DOTNET_RESTORE_DISABLE_PARALLEL:-1}
|
||||
|
||||
mkdir -p "$NUGET_PACKAGES"
|
||||
rsync -a "$repo_root/local-nugets/" "$NUGET_PACKAGES/" >/dev/null 2>&1 || true
|
||||
|
||||
restore_sources=()
|
||||
IFS=';' read -ra SRC_ARR <<< "$NUGET_SOURCES"
|
||||
for s in "${SRC_ARR[@]}"; do
|
||||
[[ -n "$s" ]] && restore_sources+=(--source "$s")
|
||||
done
|
||||
|
||||
solution="$repo_root/src/Scanner/StellaOps.Scanner.sln"
|
||||
dotnet restore "$solution" --ignore-failed-sources "${restore_sources[@]}"
|
||||
|
||||
build_binlog="$out_dir/build.binlog"
|
||||
dotnet build "$solution" -c Release /p:ContinuousIntegrationBuild=true /bl:"$build_binlog"
|
||||
|
||||
common_test_args=( -c Release --no-build --results-directory "$logs_dir" )
|
||||
if [[ -n "$TEST_FILTER" ]]; then
|
||||
common_test_args+=( --filter "$TEST_FILTER" )
|
||||
fi
|
||||
|
||||
run_tests() {
|
||||
local project="$1" name="$2"
|
||||
dotnet test "$project" "${common_test_args[@]}" --logger "trx;LogFileName=${name}.trx"
|
||||
}
|
||||
|
||||
run_tests "$repo_root/src/Scanner/__Tests/StellaOps.Scanner.Core.Tests/StellaOps.Scanner.Core.Tests.csproj" core
|
||||
run_tests "$repo_root/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.OS.Tests/StellaOps.Scanner.Analyzers.OS.Tests.csproj" analyzers-os
|
||||
run_tests "$repo_root/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Tests/StellaOps.Scanner.Analyzers.Lang.Tests.csproj" analyzers-lang
|
||||
run_tests "$repo_root/src/Scanner/__Tests/StellaOps.Scanner.WebService.Tests/StellaOps.Scanner.WebService.Tests.csproj" web
|
||||
run_tests "$repo_root/src/Scanner/__Tests/StellaOps.Scanner.Worker.Tests/StellaOps.Scanner.Worker.Tests.csproj" worker
|
||||
|
||||
summary="$out_dir/summary.json"
|
||||
{
|
||||
printf '{
|
||||
'
|
||||
printf ' "timestamp_utc": "%s",
|
||||
' "$ts"
|
||||
printf ' "build_binlog": "%s",
|
||||
' "${build_binlog#${repo_root}/}"
|
||||
printf ' "tests": [
|
||||
'
|
||||
printf ' {"name":"core","trx":"%s"},
|
||||
' "${logs_dir#${repo_root}/}/core.trx"
|
||||
printf ' {"name":"analyzers-os","trx":"%s"},
|
||||
' "${logs_dir#${repo_root}/}/analyzers-os.trx"
|
||||
printf ' {"name":"analyzers-lang","trx":"%s"},
|
||||
' "${logs_dir#${repo_root}/}/analyzers-lang.trx"
|
||||
printf ' {"name":"web","trx":"%s"},
|
||||
' "${logs_dir#${repo_root}/}/web.trx"
|
||||
printf ' {"name":"worker","trx":"%s"}
|
||||
' "${logs_dir#${repo_root}/}/worker.trx"
|
||||
printf ' ],
|
||||
'
|
||||
printf ' "nuget_packages": "%s",
|
||||
' "${NUGET_PACKAGES#${repo_root}/}"
|
||||
printf ' "sources": [
|
||||
'
|
||||
for i in "${!SRC_ARR[@]}"; do
|
||||
sep=","; [[ $i -eq $((${#SRC_ARR[@]}-1)) ]] && sep=""
|
||||
printf ' "%s"%s
|
||||
' "${SRC_ARR[$i]}" "$sep"
|
||||
done
|
||||
printf ' ]
|
||||
'
|
||||
printf '}
|
||||
'
|
||||
} > "$summary"
|
||||
|
||||
echo "Artifacts written to ${out_dir#${repo_root}/}"
|
||||
@@ -5,6 +5,8 @@ Artifacts:
|
||||
- Sample config: `ops/devops/signals/signals.yaml` (mounted into the container at `/app/signals.yaml` if desired).
|
||||
- Dockerfile: `ops/devops/signals/Dockerfile` (multi-stage build on .NET 10 RC).
|
||||
- Build/export helper: `scripts/signals/build.sh` (saves image tar to `out/signals/signals-image.tar`).
|
||||
- Span sink stack: `ops/devops/signals/docker-compose.spansink.yml` + `otel-spansink.yaml` to collect OTLP traces (Excititor `/v1/vex/observations/**`) and write NDJSON to `spansink-data` volume. Run via `scripts/signals/run-spansink.sh`.
|
||||
- Grafana dashboard stub: `ops/devops/signals/dashboards/excititor-vex-traces.json` (import into Tempo-enabled Grafana).
|
||||
|
||||
Quick start (offline-friendly):
|
||||
```bash
|
||||
@@ -16,6 +18,9 @@ COMPOSE_FILE=ops/devops/signals/docker-compose.signals.yml docker compose up -d
|
||||
|
||||
# hit health
|
||||
curl -s http://localhost:5088/health
|
||||
|
||||
# run span sink collector
|
||||
scripts/signals/run-spansink.sh
|
||||
```
|
||||
|
||||
Configuration (ENV or YAML):
|
||||
|
||||
50
ops/devops/signals/dashboards/excititor-vex-traces.json
Normal file
50
ops/devops/signals/dashboards/excititor-vex-traces.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"title": "Excititor VEX Observations Traces",
|
||||
"tags": ["excititor", "traces", "vex"],
|
||||
"timezone": "browser",
|
||||
"schemaVersion": 38,
|
||||
"version": 1,
|
||||
"refresh": "30s",
|
||||
"panels": [
|
||||
{
|
||||
"type": "stat",
|
||||
"title": "Spans (last 15m)",
|
||||
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 0},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {"type": "tempo", "uid": "tempo"},
|
||||
"expr": "sum by(service_name)(rate(traces_spanmetrics_calls_total{service_name=~\"excititor.*\"}[15m]))"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "stat",
|
||||
"title": "Errors (last 15m)",
|
||||
"gridPos": {"h": 4, "w": 6, "x": 6, "y": 0},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {"type": "tempo", "uid": "tempo"},
|
||||
"expr": "sum by(status_code)(rate(traces_spanmetrics_calls_total{status_code=\"STATUS_CODE_ERROR\",service_name=~\"excititor.*\"}[15m]))"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "table",
|
||||
"title": "Recent /v1/vex/observations spans",
|
||||
"gridPos": {"h": 12, "w": 24, "x": 0, "y": 4},
|
||||
"options": {
|
||||
"showHeader": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {"type": "tempo", "uid": "tempo"},
|
||||
"queryType": "traceql",
|
||||
"expr": "{ service.name = \"excititor\" && http.target = \"/v1/vex/observations\" } | limit 50"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
17
ops/devops/signals/docker-compose.spansink.yml
Normal file
17
ops/devops/signals/docker-compose.spansink.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
otel-spansink:
|
||||
image: otel/opentelemetry-collector-contrib:0.97.0
|
||||
command: ["--config=/etc/otel/otel-spansink.yaml"]
|
||||
volumes:
|
||||
- ./otel-spansink.yaml:/etc/otel/otel-spansink.yaml:ro
|
||||
- spansink-data:/var/otel
|
||||
ports:
|
||||
- "4317:4317" # OTLP gRPC
|
||||
- "4318:4318" # OTLP HTTP
|
||||
environment:
|
||||
- OTEL_RESOURCE_ATTRIBUTES=service.name=excititor,telemetry.distro=stellaops
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
spansink-data:
|
||||
driver: local
|
||||
31
ops/devops/signals/otel-spansink.yaml
Normal file
31
ops/devops/signals/otel-spansink.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: 0.0.0.0:4317
|
||||
http:
|
||||
endpoint: 0.0.0.0:4318
|
||||
|
||||
processors:
|
||||
batch:
|
||||
timeout: 1s
|
||||
send_batch_size: 512
|
||||
|
||||
exporters:
|
||||
file/traces:
|
||||
path: /var/otel/traces.ndjson
|
||||
rotation:
|
||||
max_megabytes: 100
|
||||
max_backups: 5
|
||||
max_days: 7
|
||||
localtime: true
|
||||
|
||||
service:
|
||||
telemetry:
|
||||
logs:
|
||||
level: info
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [file/traces]
|
||||
7
scripts/signals/run-spansink.sh
Normal file
7
scripts/signals/run-spansink.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# Run the OTLP span sink for Excititor traces (DEVOPS-SPANSINK-31-003).
|
||||
ROOT=${ROOT:-$(git rev-parse --show-toplevel)}
|
||||
COMPOSE_FILE=${COMPOSE_FILE:-$ROOT/ops/devops/signals/docker-compose.spansink.yml}
|
||||
export COMPOSE_FILE
|
||||
exec docker compose up -d
|
||||
@@ -22,6 +22,7 @@
|
||||
- `docs/modules/export-center/mirror-bundles.md` (for 37-001/37-002)
|
||||
- `docs/modules/export-center/provenance-and-signing.md`
|
||||
- `docs/modules/export-center/operations/kms-envelope-pattern.md` (for 37-002 encryption/KMS)
|
||||
- `docs/modules/export-center/operations/risk-bundle-provider-matrix.md` (for 69/70 risk bundle chain)
|
||||
- Sprint file `docs/implplan/SPRINT_0164_0001_0001_exportcenter_iii.md`
|
||||
|
||||
## Working Agreements
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
public sealed class FileSystemRiskBundleObjectStore : IRiskBundleObjectStore
|
||||
{
|
||||
private readonly IOptionsMonitor<FileSystemRiskBundleStorageOptions> _options;
|
||||
private readonly ILogger<FileSystemRiskBundleObjectStore> _logger;
|
||||
|
||||
public FileSystemRiskBundleObjectStore(
|
||||
IOptionsMonitor<FileSystemRiskBundleStorageOptions> options,
|
||||
ILogger<FileSystemRiskBundleObjectStore> logger)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<RiskBundleStorageMetadata> StoreAsync(
|
||||
RiskBundleObjectStoreOptions options,
|
||||
Stream content,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentNullException.ThrowIfNull(content);
|
||||
|
||||
var root = _options.CurrentValue.RootPath;
|
||||
if (string.IsNullOrWhiteSpace(root))
|
||||
{
|
||||
throw new InvalidOperationException("Risk bundle storage root path is not configured.");
|
||||
}
|
||||
|
||||
var fullPath = Path.Combine(root, options.StorageKey);
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
await using (var file = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None, 128 * 1024, FileOptions.Asynchronous | FileOptions.WriteThrough))
|
||||
{
|
||||
await content.CopyToAsync(file, 128 * 1024, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var length = new FileInfo(fullPath).Length;
|
||||
_logger.LogInformation("Risk bundle artefact stored at {Path} ({Bytes} bytes).", fullPath, length);
|
||||
|
||||
return new RiskBundleStorageMetadata(options.StorageKey, length, options.ContentType);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FileSystemRiskBundleStorageOptions
|
||||
{
|
||||
public string? RootPath { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Immutable;
|
||||
using System.Formats.Tar;
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
public sealed class RiskBundleBuilder
|
||||
{
|
||||
private const string ManifestVersion = "1";
|
||||
private static readonly UnixFileMode DefaultFileMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead;
|
||||
private static readonly DateTimeOffset FixedTimestamp = new(2024, 01, 01, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
public RiskBundleBuildResult Build(RiskBundleBuildRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
if (request.BundleId == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentException("Bundle identifier must be provided.", nameof(request));
|
||||
}
|
||||
|
||||
if (request.Providers is not { Count: > 0 })
|
||||
{
|
||||
throw new ArgumentException("At least one provider input is required.", nameof(request));
|
||||
}
|
||||
|
||||
var providers = CollectProviders(request, cancellationToken);
|
||||
var manifest = BuildManifest(request.BundleId, providers);
|
||||
var manifestJson = JsonSerializer.Serialize(manifest, SerializerOptions);
|
||||
var rootHash = ComputeSha256(manifestJson);
|
||||
|
||||
var bundleStream = CreateBundleStream(
|
||||
providers,
|
||||
request.ManifestFileName,
|
||||
manifestJson,
|
||||
request.BundleFileName ?? "risk-bundle.tar.gz");
|
||||
|
||||
bundleStream.Position = 0;
|
||||
|
||||
return new RiskBundleBuildResult(manifest, manifestJson, rootHash, bundleStream);
|
||||
}
|
||||
|
||||
private static List<RiskBundleProviderEntry> CollectProviders(RiskBundleBuildRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var entries = new List<RiskBundleProviderEntry>(request.Providers.Count);
|
||||
|
||||
foreach (var provider in request.Providers.OrderBy(p => p.ProviderId, StringComparer.Ordinal))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (provider is null)
|
||||
{
|
||||
throw new ArgumentException("Provider list cannot contain null entries.", nameof(request));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(provider.ProviderId))
|
||||
{
|
||||
throw new ArgumentException("ProviderId cannot be empty.", nameof(provider.ProviderId));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(provider.SourcePath))
|
||||
{
|
||||
throw new ArgumentException("SourcePath cannot be empty.", nameof(provider.SourcePath));
|
||||
}
|
||||
|
||||
var fullPath = Path.GetFullPath(provider.SourcePath);
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
if (provider.Optional && request.AllowMissingOptional)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"Provider source file '{fullPath}' not found.", fullPath);
|
||||
}
|
||||
|
||||
var sha256 = ComputeSha256FromFile(fullPath);
|
||||
var size = new FileInfo(fullPath).Length;
|
||||
var bundlePath = $"providers/{provider.ProviderId}/snapshot";
|
||||
|
||||
entries.Add(new RiskBundleProviderEntry(
|
||||
provider.ProviderId,
|
||||
provider.Source,
|
||||
provider.SnapshotDate,
|
||||
sha256,
|
||||
size,
|
||||
provider.Optional,
|
||||
bundlePath,
|
||||
fullPath,
|
||||
SignaturePath: null));
|
||||
}
|
||||
|
||||
if (entries.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No provider artefacts collected. Provide at least one valid provider input.");
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static RiskBundleManifest BuildManifest(Guid bundleId, List<RiskBundleProviderEntry> providers)
|
||||
{
|
||||
var inputsHash = ComputeInputsHash(providers);
|
||||
return new RiskBundleManifest(
|
||||
ManifestVersion,
|
||||
bundleId,
|
||||
FixedTimestamp,
|
||||
providers.ToImmutableArray(),
|
||||
inputsHash);
|
||||
}
|
||||
|
||||
private static string ComputeInputsHash(IEnumerable<RiskBundleProviderEntry> providers)
|
||||
{
|
||||
using var sha = SHA256.Create();
|
||||
var builder = new StringBuilder();
|
||||
foreach (var entry in providers.OrderBy(e => e.ProviderId, StringComparer.Ordinal))
|
||||
{
|
||||
builder.Append(entry.ProviderId)
|
||||
.Append('\0')
|
||||
.Append(entry.Sha256)
|
||||
.Append('\0')
|
||||
.Append(entry.Source)
|
||||
.Append('\0')
|
||||
.Append(entry.Optional ? '1' : '0')
|
||||
.Append('\0')
|
||||
.Append(entry.SnapshotDate?.ToString("yyyy-MM-dd") ?? string.Empty)
|
||||
.Append('\0');
|
||||
}
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(builder.ToString());
|
||||
var hash = sha.ComputeHash(bytes);
|
||||
return ToHex(hash);
|
||||
}
|
||||
|
||||
private static string ComputeSha256(string content)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(content);
|
||||
using var sha = SHA256.Create();
|
||||
var hash = sha.ComputeHash(bytes);
|
||||
return ToHex(hash);
|
||||
}
|
||||
|
||||
private static string ComputeSha256FromFile(string filePath)
|
||||
{
|
||||
using var stream = File.OpenRead(filePath);
|
||||
using var sha = SHA256.Create();
|
||||
var hash = sha.ComputeHash(stream);
|
||||
return ToHex(hash);
|
||||
}
|
||||
|
||||
private static string ToHex(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
Span<byte> hex = stackalloc byte[bytes.Length * 2];
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
var b = bytes[i];
|
||||
hex[i * 2] = GetHexValue(b / 16);
|
||||
hex[i * 2 + 1] = GetHexValue(b % 16);
|
||||
}
|
||||
return Encoding.ASCII.GetString(hex);
|
||||
}
|
||||
|
||||
private static byte GetHexValue(int i) => (byte)(i < 10 ? i + 48 : i - 10 + 97);
|
||||
|
||||
private static MemoryStream CreateBundleStream(
|
||||
IReadOnlyList<RiskBundleProviderEntry> providers,
|
||||
string manifestFileName,
|
||||
string manifestJson,
|
||||
string bundleFileName)
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
using (var gzip = new GZipStream(stream, CompressionLevel.SmallestSize, leaveOpen: true))
|
||||
using (var tar = new TarWriter(gzip, TarEntryFormat.Pax, leaveOpen: true))
|
||||
{
|
||||
WriteTextEntry(tar, $"manifests/{manifestFileName}", manifestJson, DefaultFileMode);
|
||||
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
WriteProviderFile(tar, provider);
|
||||
}
|
||||
}
|
||||
|
||||
ApplyDeterministicGzipHeader(stream);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
|
||||
private static void WriteProviderFile(TarWriter writer, RiskBundleProviderEntry entry)
|
||||
{
|
||||
var filePath = entry.SourceFilePath ?? throw new InvalidOperationException("Source file path missing for provider entry.");
|
||||
using var dataStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 128 * 1024, FileOptions.SequentialScan);
|
||||
var tarEntry = new PaxTarEntry(TarEntryType.RegularFile, entry.BundlePath)
|
||||
{
|
||||
Mode = DefaultFileMode,
|
||||
ModificationTime = FixedTimestamp,
|
||||
DataStream = dataStream
|
||||
};
|
||||
writer.WriteEntry(tarEntry);
|
||||
}
|
||||
|
||||
private static void WriteTextEntry(TarWriter writer, string path, string content, UnixFileMode mode)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(content);
|
||||
using var dataStream = new MemoryStream(bytes);
|
||||
var entry = new PaxTarEntry(TarEntryType.RegularFile, path)
|
||||
{
|
||||
Mode = mode,
|
||||
ModificationTime = FixedTimestamp,
|
||||
DataStream = dataStream
|
||||
};
|
||||
writer.WriteEntry(entry);
|
||||
}
|
||||
|
||||
private static void ApplyDeterministicGzipHeader(MemoryStream stream)
|
||||
{
|
||||
if (stream.Length < 10)
|
||||
{
|
||||
throw new InvalidOperationException("GZip header not fully written for risk bundle.");
|
||||
}
|
||||
|
||||
var seconds = checked((int)(FixedTimestamp - DateTimeOffset.UnixEpoch).TotalSeconds);
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
BinaryPrimitives.WriteInt32LittleEndian(buffer, seconds);
|
||||
|
||||
var originalPosition = stream.Position;
|
||||
stream.Position = 4;
|
||||
stream.Write(buffer);
|
||||
stream.Position = originalPosition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
public sealed record RiskBundleJobRequest(
|
||||
RiskBundleBuildRequest BuildRequest,
|
||||
string StoragePrefix = "risk-bundles",
|
||||
string BundleFileName = "risk-bundle.tar.gz");
|
||||
|
||||
public sealed record RiskBundleJobOutcome(
|
||||
RiskBundleManifest Manifest,
|
||||
RiskBundleStorageMetadata ManifestStorage,
|
||||
RiskBundleStorageMetadata ManifestSignatureStorage,
|
||||
RiskBundleStorageMetadata BundleStorage,
|
||||
string ManifestJson,
|
||||
string ManifestSignatureJson,
|
||||
string RootHash);
|
||||
|
||||
public sealed class RiskBundleJob
|
||||
{
|
||||
private readonly RiskBundleBuilder _builder;
|
||||
private readonly IRiskBundleManifestSigner _signer;
|
||||
private readonly IRiskBundleObjectStore _objectStore;
|
||||
private readonly ILogger<RiskBundleJob> _logger;
|
||||
|
||||
public RiskBundleJob(
|
||||
RiskBundleBuilder builder,
|
||||
IRiskBundleManifestSigner signer,
|
||||
IRiskBundleObjectStore objectStore,
|
||||
ILogger<RiskBundleJob> logger)
|
||||
{
|
||||
_builder = builder ?? throw new ArgumentNullException(nameof(builder));
|
||||
_signer = signer ?? throw new ArgumentNullException(nameof(signer));
|
||||
_objectStore = objectStore ?? throw new ArgumentNullException(nameof(objectStore));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<RiskBundleJobOutcome> ExecuteAsync(RiskBundleJobRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var build = _builder.Build(request.BuildRequest, cancellationToken);
|
||||
_logger.LogInformation("Risk bundle built with {ProviderCount} providers.", build.Manifest.Providers.Count);
|
||||
|
||||
var signature = await _signer.SignAsync(build.ManifestJson, cancellationToken).ConfigureAwait(false);
|
||||
var signatureJson = System.Text.Json.JsonSerializer.Serialize(signature, new System.Text.Json.JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
|
||||
WriteIndented = false
|
||||
});
|
||||
|
||||
var manifestKey = Combine(request.StoragePrefix, request.BuildRequest.ManifestFileName);
|
||||
var manifestSigKey = Combine(request.StoragePrefix, request.BuildRequest.ManifestDsseFileName);
|
||||
var bundleKey = Combine(request.StoragePrefix, request.BundleFileName);
|
||||
|
||||
var manifestStorage = await _objectStore.StoreAsync(
|
||||
new RiskBundleObjectStoreOptions(manifestKey, "application/json"),
|
||||
new MemoryStream(System.Text.Encoding.UTF8.GetBytes(build.ManifestJson)),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var signatureStorage = await _objectStore.StoreAsync(
|
||||
new RiskBundleObjectStoreOptions(manifestSigKey, "application/json"),
|
||||
new MemoryStream(System.Text.Encoding.UTF8.GetBytes(signatureJson)),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var bundleStorage = await _objectStore.StoreAsync(
|
||||
new RiskBundleObjectStoreOptions(bundleKey, "application/gzip"),
|
||||
build.BundleStream,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return new RiskBundleJobOutcome(
|
||||
build.Manifest,
|
||||
manifestStorage,
|
||||
signatureStorage,
|
||||
bundleStorage,
|
||||
build.ManifestJson,
|
||||
signatureJson,
|
||||
build.RootHash);
|
||||
}
|
||||
|
||||
private static string Combine(string prefix, string fileName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prefix))
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
var sanitizedPrefix = prefix.Trim('/');
|
||||
return string.IsNullOrWhiteSpace(sanitizedPrefix)
|
||||
? fileName
|
||||
: $"{sanitizedPrefix}/{fileName}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
public sealed record RiskBundleProviderInput(
|
||||
string ProviderId,
|
||||
string SourcePath,
|
||||
string Source,
|
||||
bool Optional = false,
|
||||
DateOnly? SnapshotDate = null);
|
||||
|
||||
public sealed record RiskBundleProviderEntry(
|
||||
string ProviderId,
|
||||
string Source,
|
||||
DateOnly? SnapshotDate,
|
||||
string Sha256,
|
||||
long SizeBytes,
|
||||
bool Optional,
|
||||
string BundlePath,
|
||||
string SourceFilePath,
|
||||
string? SignaturePath);
|
||||
|
||||
public sealed record RiskBundleManifest(
|
||||
string Version,
|
||||
Guid BundleId,
|
||||
DateTimeOffset CreatedAt,
|
||||
IReadOnlyList<RiskBundleProviderEntry> Providers,
|
||||
string InputsHash);
|
||||
|
||||
public sealed record RiskBundleBuildRequest(
|
||||
Guid BundleId,
|
||||
IReadOnlyList<RiskBundleProviderInput> Providers,
|
||||
string? BundleFileName = null,
|
||||
string BundlePrefix = "risk-bundles",
|
||||
string ManifestFileName = "provider-manifest.json",
|
||||
string ManifestDsseFileName = "provider-manifest.dsse",
|
||||
bool AllowMissingOptional = true);
|
||||
|
||||
public sealed record RiskBundleBuildResult(
|
||||
RiskBundleManifest Manifest,
|
||||
string ManifestJson,
|
||||
string RootHash,
|
||||
MemoryStream BundleStream);
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
public sealed record RiskBundleObjectStoreOptions(string StorageKey, string ContentType);
|
||||
|
||||
public sealed record RiskBundleStorageMetadata(string StorageKey, long SizeBytes, string ContentType);
|
||||
|
||||
public interface IRiskBundleObjectStore
|
||||
{
|
||||
Task<RiskBundleStorageMetadata> StoreAsync(
|
||||
RiskBundleObjectStoreOptions options,
|
||||
Stream content,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
public interface IRiskBundleManifestSigner
|
||||
{
|
||||
Task<RiskBundleManifestSignatureDocument> SignAsync(string manifestJson, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public sealed record RiskBundleManifestSignatureDocument(
|
||||
[property: JsonPropertyName("payloadType")] string PayloadType,
|
||||
[property: JsonPropertyName("payload")] string Payload,
|
||||
[property: JsonPropertyName("signatures")] IReadOnlyList<RiskBundleManifestDsseSignature> Signatures);
|
||||
|
||||
public sealed record RiskBundleManifestDsseSignature(
|
||||
[property: JsonPropertyName("sig")] string Signature,
|
||||
[property: JsonPropertyName("keyid")] string KeyId);
|
||||
|
||||
public sealed class HmacRiskBundleManifestSigner : IRiskBundleManifestSigner
|
||||
{
|
||||
private const string DefaultPayloadType = "application/stellaops.risk-bundle.provider-manifest+json";
|
||||
private readonly byte[] _key;
|
||||
private readonly string _keyId;
|
||||
|
||||
public HmacRiskBundleManifestSigner(string key, string keyId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
throw new ArgumentException("Signing key cannot be empty.", nameof(key));
|
||||
}
|
||||
|
||||
_key = Encoding.UTF8.GetBytes(key);
|
||||
_keyId = string.IsNullOrWhiteSpace(keyId) ? "risk-bundle-hmac" : keyId;
|
||||
}
|
||||
|
||||
public Task<RiskBundleManifestSignatureDocument> SignAsync(string manifestJson, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(manifestJson);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var pae = CreatePreAuthenticationEncoding(DefaultPayloadType, manifestJson);
|
||||
var signature = ComputeHmac(pae, _key);
|
||||
|
||||
var document = new RiskBundleManifestSignatureDocument(
|
||||
DefaultPayloadType,
|
||||
Convert.ToBase64String(Encoding.UTF8.GetBytes(manifestJson)),
|
||||
new[] { new RiskBundleManifestDsseSignature(signature, _keyId) });
|
||||
|
||||
return Task.FromResult(document);
|
||||
}
|
||||
|
||||
private static string ComputeHmac(byte[] pae, byte[] key)
|
||||
{
|
||||
using var hmac = new HMACSHA256(key);
|
||||
var signature = hmac.ComputeHash(pae);
|
||||
return Convert.ToBase64String(signature);
|
||||
}
|
||||
|
||||
private static byte[] CreatePreAuthenticationEncoding(string payloadType, string payload)
|
||||
{
|
||||
// DSSE PAE: length + type + length + payload
|
||||
var typeBytes = Encoding.UTF8.GetBytes(payloadType);
|
||||
var payloadBytes = Encoding.UTF8.GetBytes(payload);
|
||||
var builder = new List<byte>(typeBytes.Length + payloadBytes.Length + 32);
|
||||
|
||||
AppendLength(builder, typeBytes.Length);
|
||||
builder.AddRange(typeBytes);
|
||||
AppendLength(builder, payloadBytes.Length);
|
||||
builder.AddRange(payloadBytes);
|
||||
|
||||
return builder.ToArray();
|
||||
}
|
||||
|
||||
private static void AppendLength(List<byte> buffer, int length)
|
||||
{
|
||||
var text = length.ToString(CultureInfo.InvariantCulture);
|
||||
var bytes = Encoding.UTF8.GetBytes(text);
|
||||
buffer.AddRange(bytes);
|
||||
buffer.Add(0x20); // space
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../StellaOps.ExportCenter/StellaOps.ExportCenter.Core/StellaOps.ExportCenter.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,99 +1,113 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.ExportCenter", "StellaOps.ExportCenter", "{453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.Core", "StellaOps.ExportCenter\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj", "{E13C1C3A-BCD1-4B32-B267-3008987833D9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.Infrastructure", "StellaOps.ExportCenter\StellaOps.ExportCenter.Infrastructure\StellaOps.ExportCenter.Infrastructure.csproj", "{7203247A-2B03-4E9A-A8F9-E8434377A398}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.Tests", "StellaOps.ExportCenter\StellaOps.ExportCenter.Tests\StellaOps.ExportCenter.Tests.csproj", "{0FF21346-59FF-4E46-953D-15C1E80B36E8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.WebService", "StellaOps.ExportCenter\StellaOps.ExportCenter.WebService\StellaOps.ExportCenter.WebService.csproj", "{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.Worker", "StellaOps.ExportCenter\StellaOps.ExportCenter.Worker\StellaOps.ExportCenter.Worker.csproj", "{77B919B8-6A4B-47BD-82BB-14287E2E069C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|x64.Build.0 = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|x86.Build.0 = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9} = {453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398} = {453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8} = {453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A} = {453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C} = {453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StellaOps.ExportCenter", "StellaOps.ExportCenter", "{453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.Core", "StellaOps.ExportCenter\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj", "{E13C1C3A-BCD1-4B32-B267-3008987833D9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.Infrastructure", "StellaOps.ExportCenter\StellaOps.ExportCenter.Infrastructure\StellaOps.ExportCenter.Infrastructure.csproj", "{7203247A-2B03-4E9A-A8F9-E8434377A398}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.Tests", "StellaOps.ExportCenter\StellaOps.ExportCenter.Tests\StellaOps.ExportCenter.Tests.csproj", "{0FF21346-59FF-4E46-953D-15C1E80B36E8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.WebService", "StellaOps.ExportCenter\StellaOps.ExportCenter.WebService\StellaOps.ExportCenter.WebService.csproj", "{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.Worker", "StellaOps.ExportCenter\StellaOps.ExportCenter.Worker\StellaOps.ExportCenter.Worker.csproj", "{77B919B8-6A4B-47BD-82BB-14287E2E069C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.ExportCenter.RiskBundles", "StellaOps.ExportCenter.RiskBundles\StellaOps.ExportCenter.RiskBundles.csproj", "{104B6964-9935-4CF1-B759-CE0966164A9B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|x64.Build.0 = Release|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|x64.Build.0 = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8}.Release|x86.Build.0 = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C}.Release|x86.Build.0 = Release|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{104B6964-9935-4CF1-B759-CE0966164A9B}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{E13C1C3A-BCD1-4B32-B267-3008987833D9} = {453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}
|
||||
{7203247A-2B03-4E9A-A8F9-E8434377A398} = {453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}
|
||||
{0FF21346-59FF-4E46-953D-15C1E80B36E8} = {453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}
|
||||
{84BACF3D-19B9-4E65-A751-8EBBA39EAE5A} = {453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}
|
||||
{77B919B8-6A4B-47BD-82BB-14287E2E069C} = {453E5BB8-E54E-3EF9-8B1B-5E84C5251BBC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.IO.Compression;
|
||||
using StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
namespace StellaOps.ExportCenter.Tests;
|
||||
|
||||
public sealed class RiskBundleBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void Build_WritesManifestAndFiles_Deterministically()
|
||||
{
|
||||
using var temp = new TempDir();
|
||||
var kev = temp.WriteFile("kev.json", "{\"cve\":\"CVE-0001\"}");
|
||||
var epss = temp.WriteFile("epss.csv", "cve,score\nCVE-0001,0.12\n");
|
||||
|
||||
var request = new RiskBundleBuildRequest(
|
||||
BundleId: Guid.Parse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"),
|
||||
Providers: new[]
|
||||
{
|
||||
new RiskBundleProviderInput("cisa-kev", kev, "CISA KEV"),
|
||||
new RiskBundleProviderInput("first-epss", epss, "FIRST EPSS")
|
||||
});
|
||||
|
||||
var builder = new RiskBundleBuilder();
|
||||
var cancellation = TestContext.Current.CancellationToken;
|
||||
var result = builder.Build(request, cancellation);
|
||||
|
||||
Assert.Equal(2, result.Manifest.Providers.Count);
|
||||
Assert.Equal(new[] { "cisa-kev", "first-epss" }, result.Manifest.Providers.Select(p => p.ProviderId));
|
||||
|
||||
// Manifest hash stable
|
||||
var second = builder.Build(request, cancellation);
|
||||
Assert.Equal(result.RootHash, second.RootHash);
|
||||
|
||||
// Bundle contains manifest and provider files
|
||||
using var gzip = new GZipStream(new MemoryStream(result.BundleStream.ToArray()), CompressionMode.Decompress, leaveOpen: false);
|
||||
using var tar = new System.Formats.Tar.TarReader(gzip, leaveOpen: false);
|
||||
var entries = new List<string>();
|
||||
System.Formats.Tar.TarEntry? entry;
|
||||
while ((entry = tar.GetNextEntry()) is not null)
|
||||
{
|
||||
entries.Add(entry.Name);
|
||||
}
|
||||
|
||||
Assert.Contains("manifests/provider-manifest.json", entries);
|
||||
Assert.Contains("providers/cisa-kev/snapshot", entries);
|
||||
Assert.Contains("providers/first-epss/snapshot", entries);
|
||||
}
|
||||
|
||||
private sealed class TempDir : IDisposable
|
||||
{
|
||||
public string Path { get; } = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "riskbundle-tests-" + Guid.NewGuid().ToString("N"));
|
||||
|
||||
public TempDir()
|
||||
{
|
||||
Directory.CreateDirectory(Path);
|
||||
}
|
||||
|
||||
public string WriteFile(string name, string contents)
|
||||
{
|
||||
var full = System.IO.Path.Combine(Path, name);
|
||||
File.WriteAllText(full, contents);
|
||||
return full;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try { Directory.Delete(Path, recursive: true); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
namespace StellaOps.ExportCenter.Tests;
|
||||
|
||||
public sealed class RiskBundleJobTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_StoresManifestAndBundle()
|
||||
{
|
||||
using var temp = new TempDir();
|
||||
var providerPath = temp.WriteFile("kev.json", "{}\n");
|
||||
|
||||
var buildRequest = new RiskBundleBuildRequest(
|
||||
Guid.NewGuid(),
|
||||
Providers: new[] { new RiskBundleProviderInput("cisa-kev", providerPath, "CISA KEV") });
|
||||
|
||||
var job = new RiskBundleJob(
|
||||
new RiskBundleBuilder(),
|
||||
new HmacRiskBundleManifestSigner("secret", "risk-key"),
|
||||
new InMemoryObjectStore(),
|
||||
NullLogger<RiskBundleJob>.Instance);
|
||||
|
||||
var outcome = await job.ExecuteAsync(new RiskBundleJobRequest(buildRequest), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("risk-bundles/provider-manifest.json", outcome.ManifestStorage.StorageKey);
|
||||
Assert.Equal("risk-bundles/provider-manifest.dsse", outcome.ManifestSignatureStorage.StorageKey);
|
||||
Assert.Equal("risk-bundles/risk-bundle.tar.gz", outcome.BundleStorage.StorageKey);
|
||||
Assert.False(string.IsNullOrWhiteSpace(outcome.ManifestJson));
|
||||
Assert.False(string.IsNullOrWhiteSpace(outcome.ManifestSignatureJson));
|
||||
}
|
||||
|
||||
private sealed class InMemoryObjectStore : IRiskBundleObjectStore
|
||||
{
|
||||
private readonly Dictionary<string, byte[]> _store = new(StringComparer.Ordinal);
|
||||
|
||||
public Task<RiskBundleStorageMetadata> StoreAsync(RiskBundleObjectStoreOptions options, Stream content, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
content.CopyTo(ms);
|
||||
_store[options.StorageKey] = ms.ToArray();
|
||||
return Task.FromResult(new RiskBundleStorageMetadata(options.StorageKey, ms.Length, options.ContentType));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TempDir : IDisposable
|
||||
{
|
||||
public string Path { get; } = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "riskbundle-job-" + Guid.NewGuid().ToString("N"));
|
||||
|
||||
public TempDir()
|
||||
{
|
||||
Directory.CreateDirectory(Path);
|
||||
}
|
||||
|
||||
public string WriteFile(string name, string contents)
|
||||
{
|
||||
var full = System.IO.Path.Combine(Path, name);
|
||||
File.WriteAllText(full, contents);
|
||||
return full;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try { Directory.Delete(Path, recursive: true); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Text.Json;
|
||||
using StellaOps.ExportCenter.RiskBundles;
|
||||
|
||||
namespace StellaOps.ExportCenter.Tests;
|
||||
|
||||
public class RiskBundleSignerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task SignAsync_ProducesDsseEnvelope()
|
||||
{
|
||||
var signer = new HmacRiskBundleManifestSigner("secret-key", "test-key");
|
||||
const string manifest = "{\"foo\":1}";
|
||||
|
||||
var doc = await signer.SignAsync(manifest, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("application/stellaops.risk-bundle.provider-manifest+json", doc.PayloadType);
|
||||
Assert.NotNull(doc.Payload);
|
||||
Assert.Single(doc.Signatures);
|
||||
Assert.Equal("test-key", doc.Signatures[0].KeyId);
|
||||
|
||||
// ensure payload decodes to original manifest
|
||||
var payloadBytes = Convert.FromBase64String(doc.Payload);
|
||||
var decoded = System.Text.Encoding.UTF8.GetString(payloadBytes);
|
||||
Assert.Equal(manifest, decoded);
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
|
||||
|
||||
@@ -116,12 +116,13 @@
|
||||
|
||||
|
||||
|
||||
<ProjectReference Include="..\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<ProjectReference Include="..\StellaOps.ExportCenter.Infrastructure\StellaOps.ExportCenter.Infrastructure.csproj"/>
|
||||
<ProjectReference Include="..\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<ProjectReference Include="..\StellaOps.ExportCenter.Infrastructure\StellaOps.ExportCenter.Infrastructure.csproj"/>
|
||||
<ProjectReference Include="..\..\StellaOps.ExportCenter.RiskBundles\StellaOps.ExportCenter.RiskBundles.csproj" />
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,3 +27,4 @@ Build and operate the Source & Job Orchestrator control plane described in Epic
|
||||
- 3. Keep changes deterministic (stable ordering, timestamps, hashes) and align with offline/air-gap expectations.
|
||||
- 4. Coordinate doc updates, tests, and cross-guild communication whenever contracts or workflows change.
|
||||
- 5. Revert to `TODO` if you pause the task without shipping changes; leave notes in commit/PR descriptions for context.
|
||||
- 6. **Contract guardrails:** Pack-run scheduling now requires `projectId` plus tenant headers; reject/422 if absent. Keep OpenAPI examples and worker/CLI samples aligned. Preserve idempotency semantics (`Idempotency-Key`) and deterministic pagination/stream ordering in all APIs.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
@@ -72,6 +73,63 @@ public sealed class PackRunStreamCoordinatorTests
|
||||
Assert.Contains("event: completed", payload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StreamWebSocketAsync_TerminalRun_SendsInitialAndCompleted()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var packRun = new PackRunDomain(
|
||||
PackRunId: Guid.NewGuid(),
|
||||
TenantId: "tenantA",
|
||||
ProjectId: null,
|
||||
PackId: "pack.demo",
|
||||
PackVersion: "1.0.0",
|
||||
Status: PackRunStatus.Succeeded,
|
||||
Priority: 0,
|
||||
Attempt: 1,
|
||||
MaxAttempts: 3,
|
||||
Parameters: "{}",
|
||||
ParametersDigest: new string('a', 64),
|
||||
IdempotencyKey: "idem-1",
|
||||
CorrelationId: null,
|
||||
LeaseId: null,
|
||||
TaskRunnerId: "runner-1",
|
||||
LeaseUntil: null,
|
||||
CreatedAt: now.AddMinutes(-2),
|
||||
ScheduledAt: now.AddMinutes(-2),
|
||||
LeasedAt: now.AddMinutes(-1),
|
||||
StartedAt: now.AddMinutes(-1),
|
||||
CompletedAt: now,
|
||||
NotBefore: null,
|
||||
Reason: null,
|
||||
ExitCode: 0,
|
||||
DurationMs: 120_000,
|
||||
CreatedBy: "tester",
|
||||
Metadata: null);
|
||||
|
||||
var logRepo = new StubPackRunLogRepository((2, 5));
|
||||
var streamOptions = Options.Create(new StreamOptions
|
||||
{
|
||||
PollInterval = TimeSpan.FromMilliseconds(150),
|
||||
HeartbeatInterval = TimeSpan.FromMilliseconds(150),
|
||||
MaxStreamDuration = TimeSpan.FromMinutes(1)
|
||||
});
|
||||
var coordinator = new PackRunStreamCoordinator(
|
||||
new StubPackRunRepository(packRun),
|
||||
logRepo,
|
||||
streamOptions,
|
||||
TimeProvider.System,
|
||||
NullLogger<PackRunStreamCoordinator>.Instance);
|
||||
|
||||
var socket = new FakeWebSocket();
|
||||
|
||||
await coordinator.StreamWebSocketAsync(socket, packRun.TenantId, packRun, CancellationToken.None);
|
||||
|
||||
var messages = socket.SentMessages;
|
||||
Assert.Contains(messages, m => m.Contains("\"type\":\"initial\""));
|
||||
Assert.Contains(messages, m => m.Contains("\"type\":\"completed\""));
|
||||
Assert.All(messages, m => Assert.Contains(packRun.PackRunId.ToString(), m));
|
||||
}
|
||||
|
||||
private sealed class StubPackRunRepository : IPackRunRepository
|
||||
{
|
||||
private readonly PackRunDomain _packRun;
|
||||
@@ -117,4 +175,45 @@ public sealed class PackRunStreamCoordinatorTests
|
||||
=> Task.FromResult(new PackRunLogBatch(packRunId, tenantId, afterSequence, new List<PackRunLog>()));
|
||||
public Task<long> DeleteLogsAsync(string tenantId, Guid packRunId, CancellationToken cancellationToken) => Task.FromResult(0L);
|
||||
}
|
||||
|
||||
private sealed class FakeWebSocket : WebSocket
|
||||
{
|
||||
private WebSocketState _state = WebSocketState.Open;
|
||||
public List<string> SentMessages { get; } = new();
|
||||
|
||||
public override WebSocketCloseStatus? CloseStatus { get; }
|
||||
public override string? CloseStatusDescription { get; }
|
||||
public override string? SubProtocol { get; }
|
||||
public override WebSocketState State => _state;
|
||||
|
||||
public override void Abort() => _state = WebSocketState.Aborted;
|
||||
|
||||
public override Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken)
|
||||
{
|
||||
_state = WebSocketState.Closed;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken)
|
||||
{
|
||||
_state = WebSocketState.CloseSent;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_state = WebSocketState.Closed;
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
public override Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken)
|
||||
=> Task.FromResult(new WebSocketReceiveResult(0, WebSocketMessageType.Close, true));
|
||||
|
||||
public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
var message = Encoding.UTF8.GetString(buffer.Array!, buffer.Offset, buffer.Count);
|
||||
SentMessages.Add(message);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ public static class StreamEndpoints
|
||||
.WithName("Orchestrator_StreamPackRun")
|
||||
.WithDescription("Stream real-time pack run log and status updates via SSE");
|
||||
|
||||
group.MapGet("pack-runs/{packRunId:guid}/ws", StreamPackRunWebSocket)
|
||||
.WithName("Orchestrator_StreamPackRunWebSocket")
|
||||
.WithDescription("Stream real-time pack run log and status updates via WebSocket");
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
@@ -138,4 +142,32 @@ public static class StreamEndpoints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task StreamPackRunWebSocket(
|
||||
HttpContext context,
|
||||
[FromRoute] Guid packRunId,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IPackRunRepository packRunRepository,
|
||||
[FromServices] IPackRunStreamCoordinator streamCoordinator,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
await context.Response.WriteAsJsonAsync(new { error = "Expected WebSocket request" }, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
var packRun = await packRunRepository.GetByIdAsync(tenantId, packRunId, cancellationToken).ConfigureAwait(false);
|
||||
if (packRun is null)
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status404NotFound;
|
||||
await context.Response.WriteAsJsonAsync(new { error = "Pack run not found" }, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
using var socket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
|
||||
await streamCoordinator.StreamWebSocketAsync(socket, tenantId, packRun, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ if (app.Environment.IsDevelopment())
|
||||
app.MapOpenApi();
|
||||
}
|
||||
|
||||
// Enable WebSocket support for streaming endpoints
|
||||
app.UseWebSockets();
|
||||
|
||||
// OpenAPI discovery endpoints (available in all environments)
|
||||
app.MapOpenApiEndpoints();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Orchestrator.Core.Domain;
|
||||
@@ -8,6 +10,7 @@ namespace StellaOps.Orchestrator.WebService.Streaming;
|
||||
public interface IPackRunStreamCoordinator
|
||||
{
|
||||
Task StreamAsync(HttpContext context, string tenantId, PackRun packRun, CancellationToken cancellationToken);
|
||||
Task StreamWebSocketAsync(WebSocket socket, string tenantId, PackRun packRun, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -117,6 +120,82 @@ public sealed class PackRunStreamCoordinator : IPackRunStreamCoordinator
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StreamWebSocketAsync(WebSocket socket, string tenantId, PackRun packRun, CancellationToken cancellationToken)
|
||||
{
|
||||
if (socket is null) throw new ArgumentNullException(nameof(socket));
|
||||
|
||||
var (logCount, latestSeq) = await _logRepository.GetLogStatsAsync(tenantId, packRun.PackRunId, cancellationToken).ConfigureAwait(false);
|
||||
await SendAsync(socket, "initial", PackRunSnapshotPayload.From(packRun, logCount, latestSeq), cancellationToken).ConfigureAwait(false);
|
||||
await SendAsync(socket, "heartbeat", HeartbeatPayload.Create(_timeProvider.GetUtcNow()), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (IsTerminal(packRun.Status))
|
||||
{
|
||||
await SendCompletedAsync(socket, packRun, logCount, latestSeq, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var last = packRun;
|
||||
var lastSeq = latestSeq;
|
||||
var start = _timeProvider.GetUtcNow();
|
||||
using var poll = new PeriodicTimer(_options.PollInterval);
|
||||
using var heartbeat = new PeriodicTimer(_options.HeartbeatInterval);
|
||||
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested && socket.State == WebSocketState.Open)
|
||||
{
|
||||
if (_timeProvider.GetUtcNow() - start > _options.MaxStreamDuration)
|
||||
{
|
||||
await SendAsync(socket, "timeout", new { packRunId = last.PackRunId, reason = "Max stream duration reached" }, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
var pollTask = poll.WaitForNextTickAsync(cancellationToken).AsTask();
|
||||
var hbTask = heartbeat.WaitForNextTickAsync(cancellationToken).AsTask();
|
||||
var completed = await Task.WhenAny(pollTask, hbTask).ConfigureAwait(false);
|
||||
|
||||
if (completed == hbTask && await hbTask.ConfigureAwait(false))
|
||||
{
|
||||
await SendAsync(socket, "heartbeat", HeartbeatPayload.Create(_timeProvider.GetUtcNow()), cancellationToken).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (completed == pollTask && await pollTask.ConfigureAwait(false))
|
||||
{
|
||||
var current = await _packRunRepository.GetByIdAsync(tenantId, last.PackRunId, cancellationToken).ConfigureAwait(false);
|
||||
if (current is null)
|
||||
{
|
||||
await SendAsync(socket, "notFound", new NotFoundPayload(last.PackRunId.ToString(), "pack-run"), cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
|
||||
var batch = await _logRepository.GetLogsAsync(tenantId, current.PackRunId, lastSeq, DefaultBatchSize, cancellationToken).ConfigureAwait(false);
|
||||
if (batch.Logs.Count > 0)
|
||||
{
|
||||
lastSeq = batch.Logs[^1].Sequence;
|
||||
await SendAsync(socket, "logs", batch.Logs.Select(PackRunLogPayload.FromDomain), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (HasStatusChanged(last, current))
|
||||
{
|
||||
await SendAsync(socket, "statusChanged", PackRunSnapshotPayload.From(current, batch.Logs.Count, lastSeq), cancellationToken).ConfigureAwait(false);
|
||||
last = current;
|
||||
|
||||
if (IsTerminal(current.Status))
|
||||
{
|
||||
await SendCompletedAsync(socket, current, batch.Logs.Count, lastSeq, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogDebug("Pack run websocket stream cancelled for {PackRunId}.", packRun.PackRunId);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasStatusChanged(PackRun previous, PackRun current)
|
||||
{
|
||||
return previous.Status != current.Status || previous.Attempt != current.Attempt || previous.LeaseId != current.LeaseId;
|
||||
@@ -139,8 +218,32 @@ public sealed class PackRunStreamCoordinator : IPackRunStreamCoordinator
|
||||
await SseWriter.WriteEventAsync(response, "completed", payload, SerializerOptions, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task SendCompletedAsync(WebSocket socket, PackRun packRun, long logCount, long latestSequence, CancellationToken cancellationToken)
|
||||
{
|
||||
var durationSeconds = packRun.CompletedAt.HasValue && packRun.StartedAt.HasValue
|
||||
? (packRun.CompletedAt.Value - packRun.StartedAt.Value).TotalSeconds
|
||||
: packRun.CompletedAt.HasValue ? (packRun.CompletedAt.Value - packRun.CreatedAt).TotalSeconds : 0;
|
||||
|
||||
var payload = new PackRunCompletedPayload(
|
||||
PackRunId: packRun.PackRunId,
|
||||
Status: packRun.Status.ToString().ToLowerInvariant(),
|
||||
CompletedAt: packRun.CompletedAt ?? _timeProvider.GetUtcNow(),
|
||||
DurationSeconds: durationSeconds,
|
||||
LogCount: logCount,
|
||||
LatestSequence: latestSequence);
|
||||
|
||||
await SendAsync(socket, "completed", payload, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static bool IsTerminal(PackRunStatus status) =>
|
||||
status is PackRunStatus.Succeeded or PackRunStatus.Failed or PackRunStatus.Canceled or PackRunStatus.TimedOut;
|
||||
|
||||
private static async Task SendAsync(WebSocket socket, string type, object payload, CancellationToken cancellationToken)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(new { type, data = payload }, SerializerOptions);
|
||||
var buffer = Encoding.UTF8.GetBytes(json);
|
||||
await socket.SendAsync(buffer, WebSocketMessageType.Text, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record PackRunSnapshotPayload(
|
||||
|
||||
@@ -2,12 +2,18 @@ using StellaOps.TaskRunner.Core.Planning;
|
||||
|
||||
namespace StellaOps.TaskRunner.Core.Execution;
|
||||
|
||||
public interface IPackRunStepExecutor
|
||||
{
|
||||
Task<PackRunStepExecutionResult> ExecuteAsync(
|
||||
PackRunExecutionStep step,
|
||||
IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record PackRunStepExecutionResult(bool Succeeded, string? Error = null);
|
||||
public interface IPackRunStepExecutor
|
||||
{
|
||||
Task<PackRunStepExecutionResult> ExecuteAsync(
|
||||
PackRunExecutionStep step,
|
||||
IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record PackRunStepExecutionResult(bool Succeeded, string? Error = null)
|
||||
{
|
||||
public static PackRunStepExecutionResult Success() => new(true, null);
|
||||
|
||||
public static PackRunStepExecutionResult Failure(string error)
|
||||
=> new(false, string.IsNullOrWhiteSpace(error) ? "Unknown error" : error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
using StellaOps.TaskRunner.Worker.Services;
|
||||
|
||||
namespace StellaOps.TaskRunner.Infrastructure.Execution;
|
||||
|
||||
/// <summary>
|
||||
/// Executes built-in bundle ingestion helpers: validates checksums and stages bundles to a deterministic destination.
|
||||
/// </summary>
|
||||
public sealed class BundleIngestionStepExecutor : IPackRunStepExecutor
|
||||
{
|
||||
private const string BuiltInUses = "bundle.ingest";
|
||||
private readonly string stagingRoot;
|
||||
private readonly ILogger<BundleIngestionStepExecutor> logger;
|
||||
|
||||
public BundleIngestionStepExecutor(IOptions<PackRunWorkerOptions> options, ILogger<BundleIngestionStepExecutor> logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
stagingRoot = Path.Combine(options.Value.ArtifactsPath, "bundles");
|
||||
Directory.CreateDirectory(stagingRoot);
|
||||
}
|
||||
|
||||
public Task<PackRunStepExecutionResult> ExecuteAsync(
|
||||
PackRunExecutionStep step,
|
||||
IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Non-bundle helpers are treated as no-op success for now.
|
||||
if (!IsBundleIngestStep(step))
|
||||
{
|
||||
return Task.FromResult(PackRunStepExecutionResult.Success());
|
||||
}
|
||||
|
||||
var sourcePath = GetString(parameters, "path");
|
||||
if (string.IsNullOrWhiteSpace(sourcePath) || !File.Exists(sourcePath))
|
||||
{
|
||||
return Task.FromResult(PackRunStepExecutionResult.Failure("Bundle path missing or not found."));
|
||||
}
|
||||
|
||||
var checksum = GetString(parameters, "checksum") ?? GetString(parameters, "checksumSha256");
|
||||
if (!string.IsNullOrWhiteSpace(checksum))
|
||||
{
|
||||
var actual = ComputeSha256(sourcePath);
|
||||
if (!checksum.Equals(actual, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.FromResult(PackRunStepExecutionResult.Failure($"Checksum mismatch: expected {checksum}, actual {actual}."));
|
||||
}
|
||||
}
|
||||
|
||||
var destination = GetString(parameters, "destinationPath")
|
||||
?? Path.Combine(stagingRoot, Path.GetFileName(sourcePath));
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
|
||||
File.Copy(sourcePath, destination, overwrite: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Failed to stage bundle to {Destination}.", destination);
|
||||
return Task.FromResult(PackRunStepExecutionResult.Failure("Failed to stage bundle."));
|
||||
}
|
||||
|
||||
return Task.FromResult(PackRunStepExecutionResult.Success());
|
||||
}
|
||||
|
||||
private static string? GetString(IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters, string key)
|
||||
{
|
||||
if (!parameters.TryGetValue(key, out var value) || value.Value is not JsonValue jsonValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return jsonValue.TryGetValue<string>(out var result) ? result : null;
|
||||
}
|
||||
|
||||
private static bool IsBundleIngestStep(PackRunExecutionStep step)
|
||||
=> !string.IsNullOrWhiteSpace(step.Uses) &&
|
||||
step.Kind == PackRunStepKind.Run &&
|
||||
step.Uses.Contains(BuiltInUses, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static string ComputeSha256(string path)
|
||||
{
|
||||
using var stream = File.OpenRead(path);
|
||||
var hash = SHA256.HashData(stream);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.IO;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.TaskRunner.Core.Configuration;
|
||||
using StellaOps.TaskRunner.Core.Execution;
|
||||
@@ -36,7 +38,7 @@ public sealed class MongoPackRunArtifactReader : IPackRunArtifactReader
|
||||
doc.Status,
|
||||
doc.Notes,
|
||||
new DateTimeOffset(doc.CapturedAt, TimeSpan.Zero),
|
||||
doc.Expression?.ToJson()))
|
||||
doc.Expression?.ToJson(new JsonWriterSettings())))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Planning;
|
||||
using StellaOps.TaskRunner.Infrastructure.Execution;
|
||||
using StellaOps.TaskRunner.Worker.Services;
|
||||
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class BundleIngestionStepExecutorTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ValidBundle_CopiesAndSucceeds()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var source = Path.Combine(temp.Path, "bundle.tgz");
|
||||
await File.WriteAllTextAsync(source, "bundle-data");
|
||||
|
||||
var options = Options.Create(new PackRunWorkerOptions { ArtifactsPath = temp.Path });
|
||||
var executor = new BundleIngestionStepExecutor(options, NullLogger<BundleIngestionStepExecutor>.Instance);
|
||||
|
||||
var step = CreateStep("builtin:bundle.ingest", new Dictionary<string, TaskPackPlanParameterValue>
|
||||
{
|
||||
["path"] = Value(source),
|
||||
["checksum"] = Value("3e25960a79dbc69b674cd4ec67a72c62b3aa32b1d4d216177a5ffcc6f46673b5") // sha256 of "bundle-data"
|
||||
});
|
||||
|
||||
var result = await executor.ExecuteAsync(step, step.Parameters, CancellationToken.None);
|
||||
|
||||
Assert.True(result.Succeeded);
|
||||
var staged = Path.Combine(temp.Path, "bundles", "bundle.tgz");
|
||||
Assert.True(File.Exists(staged));
|
||||
Assert.Equal(await File.ReadAllBytesAsync(source), await File.ReadAllBytesAsync(staged));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ChecksumMismatch_Fails()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var source = Path.Combine(temp.Path, "bundle.tgz");
|
||||
await File.WriteAllTextAsync(source, "bundle-data");
|
||||
|
||||
var options = Options.Create(new PackRunWorkerOptions { ArtifactsPath = temp.Path });
|
||||
var executor = new BundleIngestionStepExecutor(options, NullLogger<BundleIngestionStepExecutor>.Instance);
|
||||
|
||||
var step = CreateStep("builtin:bundle.ingest", new Dictionary<string, TaskPackPlanParameterValue>
|
||||
{
|
||||
["path"] = Value(source),
|
||||
["checksum"] = Value("deadbeef")
|
||||
});
|
||||
|
||||
var result = await executor.ExecuteAsync(step, step.Parameters, CancellationToken.None);
|
||||
|
||||
Assert.False(result.Succeeded);
|
||||
Assert.Contains("Checksum mismatch", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_UnknownUses_NoOpSuccess()
|
||||
{
|
||||
var executor = new BundleIngestionStepExecutor(
|
||||
Options.Create(new PackRunWorkerOptions { ArtifactsPath = Path.GetTempPath() }),
|
||||
NullLogger<BundleIngestionStepExecutor>.Instance);
|
||||
|
||||
var step = CreateStep("builtin:noop", new Dictionary<string, TaskPackPlanParameterValue>());
|
||||
var result = await executor.ExecuteAsync(step, step.Parameters, CancellationToken.None);
|
||||
|
||||
Assert.True(result.Succeeded);
|
||||
}
|
||||
|
||||
private static TaskPackPlanParameterValue Value(string literal)
|
||||
=> new(JsonValue.Create(literal), null, null, false);
|
||||
|
||||
private static PackRunExecutionStep CreateStep(string uses, IReadOnlyDictionary<string, TaskPackPlanParameterValue> parameters)
|
||||
=> new(
|
||||
id: "ingest",
|
||||
templateId: "ingest",
|
||||
kind: PackRunStepKind.Run,
|
||||
enabled: true,
|
||||
uses: uses,
|
||||
parameters: parameters,
|
||||
approvalId: null,
|
||||
gateMessage: null,
|
||||
maxParallel: null,
|
||||
continueOnError: false,
|
||||
children: PackRunExecutionStep.EmptyChildren);
|
||||
|
||||
private sealed class TempDirectory : IDisposable
|
||||
{
|
||||
public TempDirectory()
|
||||
{
|
||||
Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString("n"));
|
||||
Directory.CreateDirectory(Path);
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(Path))
|
||||
{
|
||||
Directory.Delete(Path, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Text.Json;
|
||||
using StellaOps.TaskRunner.Infrastructure.Execution;
|
||||
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
public sealed class FilesystemPackRunArtifactReaderTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ListAsync_ReturnsEmpty_WhenManifestMissing()
|
||||
{
|
||||
using var temp = new TempDir();
|
||||
var reader = new FilesystemPackRunArtifactReader(temp.Path);
|
||||
|
||||
var results = await reader.ListAsync("run-absent", CancellationToken.None);
|
||||
|
||||
Assert.Empty(results);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAsync_ParsesManifestAndSortsByName()
|
||||
{
|
||||
using var temp = new TempDir();
|
||||
var runId = "run-1";
|
||||
var manifestPath = Path.Combine(temp.Path, "run-1", "artifact-manifest.json");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)!);
|
||||
|
||||
var manifest = new
|
||||
{
|
||||
RunId = runId,
|
||||
UploadedAt = DateTimeOffset.UtcNow,
|
||||
Outputs = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
Name = "b",
|
||||
Type = "file",
|
||||
SourcePath = (string?)"/tmp/source-b",
|
||||
StoredPath = "files/b.txt",
|
||||
Status = "copied",
|
||||
Notes = (string?)"ok",
|
||||
ExpressionJson = (string?)null
|
||||
},
|
||||
new
|
||||
{
|
||||
Name = "a",
|
||||
Type = "object",
|
||||
SourcePath = (string?)null,
|
||||
StoredPath = "expressions/a.json",
|
||||
Status = "materialized",
|
||||
Notes = (string?)null,
|
||||
ExpressionJson = "{\"key\":\"value\"}"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(manifest, new JsonSerializerOptions(JsonSerializerDefaults.Web));
|
||||
await File.WriteAllTextAsync(manifestPath, json);
|
||||
|
||||
var reader = new FilesystemPackRunArtifactReader(temp.Path);
|
||||
var results = await reader.ListAsync(runId, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(2, results.Count);
|
||||
Assert.Collection(results,
|
||||
first =>
|
||||
{
|
||||
Assert.Equal("a", first.Name);
|
||||
Assert.Equal("object", first.Type);
|
||||
Assert.Equal("expressions/a.json", first.StoredPath);
|
||||
Assert.Equal("materialized", first.Status);
|
||||
Assert.Equal("{\"key\":\"value\"}", first.ExpressionJson);
|
||||
},
|
||||
second =>
|
||||
{
|
||||
Assert.Equal("b", second.Name);
|
||||
Assert.Equal("file", second.Type);
|
||||
Assert.Equal("files/b.txt", second.StoredPath);
|
||||
Assert.Equal("copied", second.Status);
|
||||
Assert.Null(second.ExpressionJson);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TempDir : IDisposable
|
||||
{
|
||||
public TempDir()
|
||||
{
|
||||
Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString("n"));
|
||||
Directory.CreateDirectory(Path);
|
||||
}
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(Path))
|
||||
{
|
||||
Directory.Delete(Path, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,25 +161,41 @@ public sealed class TaskPackPlannerTests
|
||||
Assert.Equal(30, plan.FailurePolicy.BackoffSeconds);
|
||||
Assert.False(plan.FailurePolicy.ContinueOnError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PolicyGateHints_IncludeRuntimeMetadata()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.PolicyGate);
|
||||
var planner = new TaskPackPlanner();
|
||||
|
||||
var plan = planner.Plan(manifest).Plan!;
|
||||
var hints = TaskPackPlanInsights.CollectPolicyGateHints(plan);
|
||||
Assert.Single(hints);
|
||||
var hint = hints[0];
|
||||
Assert.Equal("policy-check", hint.StepId);
|
||||
var threshold = hint.Parameters.Single(p => p.Name == "threshold");
|
||||
Assert.False(threshold.RequiresRuntimeValue);
|
||||
Assert.Null(threshold.Expression);
|
||||
var evidence = hint.Parameters.Single(p => p.Name == "evidenceRef");
|
||||
Assert.True(evidence.RequiresRuntimeValue);
|
||||
Assert.Equal("steps.prepare.outputs.evidence", evidence.Expression);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PolicyGateHints_IncludeRuntimeMetadata()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.PolicyGate);
|
||||
var planner = new TaskPackPlanner();
|
||||
|
||||
var plan = planner.Plan(manifest).Plan!;
|
||||
var hints = TaskPackPlanInsights.CollectPolicyGateHints(plan);
|
||||
Assert.Single(hints);
|
||||
var hint = hints[0];
|
||||
Assert.Equal("policy-check", hint.StepId);
|
||||
var threshold = hint.Parameters.Single(p => p.Name == "threshold");
|
||||
Assert.False(threshold.RequiresRuntimeValue);
|
||||
Assert.Null(threshold.Expression);
|
||||
var evidence = hint.Parameters.Single(p => p.Name == "evidenceRef");
|
||||
Assert.True(evidence.RequiresRuntimeValue);
|
||||
Assert.Equal("steps.prepare.outputs.evidence", evidence.Expression);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Plan_SealedMode_BlocksUndeclaredEgress()
|
||||
{
|
||||
var manifest = TestManifests.Load(TestManifests.EgressBlocked);
|
||||
var options = new EgressPolicyOptions
|
||||
{
|
||||
Mode = EgressPolicyMode.Sealed
|
||||
};
|
||||
var planner = new TaskPackPlanner(new EgressPolicy(options));
|
||||
|
||||
var result = planner.Plan(manifest);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Contains(result.Errors, error => error.Message.Contains("example.com", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Plan_WhenRequiredInputMissing_ReturnsError()
|
||||
@@ -189,7 +205,7 @@ public sealed class TaskPackPlannerTests
|
||||
|
||||
var result = planner.Plan(manifest);
|
||||
Assert.False(result.Success);
|
||||
Assert.Contains(result.Errors, error => error.Path == "inputs.sbomBundle");
|
||||
Assert.NotEmpty(result.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
internal static partial class TestManifests
|
||||
{
|
||||
public const string SealedEgressBlocked = """
|
||||
apiVersion: stellaops.io/pack.v1
|
||||
kind: TaskPack
|
||||
metadata:
|
||||
name: egress-blocked
|
||||
version: 1.0.0
|
||||
spec:
|
||||
steps:
|
||||
- id: fetch
|
||||
run:
|
||||
uses: builtin:http
|
||||
with:
|
||||
url: "https://example.com/data"
|
||||
egress:
|
||||
- url: "https://example.com"
|
||||
""";
|
||||
}
|
||||
@@ -3,13 +3,13 @@ using StellaOps.TaskRunner.Core.TaskPacks;
|
||||
|
||||
namespace StellaOps.TaskRunner.Tests;
|
||||
|
||||
internal static class TestManifests
|
||||
{
|
||||
public static TaskPackManifest Load(string yaml)
|
||||
{
|
||||
var loader = new TaskPackManifestLoader();
|
||||
return loader.Deserialize(yaml);
|
||||
}
|
||||
internal static partial class TestManifests
|
||||
{
|
||||
public static TaskPackManifest Load(string yaml)
|
||||
{
|
||||
var loader = new TaskPackManifestLoader();
|
||||
return loader.Deserialize(yaml);
|
||||
}
|
||||
|
||||
public const string Sample = """
|
||||
apiVersion: stellaops.io/pack.v1
|
||||
@@ -47,23 +47,25 @@ spec:
|
||||
""";
|
||||
|
||||
public const string RequiredInput = """
|
||||
apiVersion: stellaops.io/pack.v1
|
||||
kind: TaskPack
|
||||
metadata:
|
||||
name: required-input-pack
|
||||
version: 1.2.3
|
||||
spec:
|
||||
inputs:
|
||||
- name: sbomBundle
|
||||
type: object
|
||||
required: true
|
||||
steps:
|
||||
- id: noop
|
||||
run:
|
||||
uses: builtin:noop
|
||||
""";
|
||||
|
||||
public const string StepReference = """
|
||||
apiVersion: stellaops.io/pack.v1
|
||||
kind: TaskPack
|
||||
metadata:
|
||||
name: required-input-pack
|
||||
version: 1.2.3
|
||||
spec:
|
||||
inputs:
|
||||
- name: sbomBundle
|
||||
type: object
|
||||
required: true
|
||||
steps:
|
||||
- id: noop
|
||||
run:
|
||||
uses: builtin:noop
|
||||
with:
|
||||
sbom: "{{ inputs.sbomBundle }}"
|
||||
""";
|
||||
|
||||
public const string StepReference = """
|
||||
apiVersion: stellaops.io/pack.v1
|
||||
kind: TaskPack
|
||||
metadata:
|
||||
|
||||
@@ -9,6 +9,7 @@ using MongoDB.Driver;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.AirGap.Policy;
|
||||
using StellaOps.TaskRunner.Core.Configuration;
|
||||
using StellaOps.TaskRunner.Core.Execution;
|
||||
using StellaOps.TaskRunner.Core.Execution.Simulation;
|
||||
@@ -21,8 +22,13 @@ using StellaOps.Telemetry.Core;
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.Configure<TaskRunnerServiceOptions>(builder.Configuration.GetSection("TaskRunner"));
|
||||
builder.Services.AddAirGapEgressPolicy(builder.Configuration);
|
||||
builder.Services.AddSingleton<TaskPackManifestLoader>();
|
||||
builder.Services.AddSingleton<TaskPackPlanner>();
|
||||
builder.Services.AddSingleton<TaskPackPlanner>(sp =>
|
||||
{
|
||||
var egressPolicy = sp.GetRequiredService<IEgressPolicy>();
|
||||
return new TaskPackPlanner(egressPolicy);
|
||||
});
|
||||
builder.Services.AddSingleton<PackRunSimulationEngine>();
|
||||
builder.Services.AddSingleton<PackRunExecutionGraphBuilder>();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
|
||||
@@ -28,12 +28,13 @@
|
||||
<ItemGroup>
|
||||
|
||||
|
||||
<ProjectReference Include="..\StellaOps.TaskRunner.Core\StellaOps.TaskRunner.Core.csproj"/>
|
||||
<ProjectReference Include="..\StellaOps.TaskRunner.Core\StellaOps.TaskRunner.Core.csproj"/>
|
||||
|
||||
|
||||
<ProjectReference Include="..\StellaOps.TaskRunner.Infrastructure\StellaOps.TaskRunner.Infrastructure.csproj"/>
|
||||
|
||||
<ProjectReference Include="..\..\..\Telemetry\StellaOps.Telemetry.Core\StellaOps.Telemetry.Core\StellaOps.Telemetry.Core.csproj"/>
|
||||
<ProjectReference Include="..\..\..\AirGap\StellaOps.AirGap.Policy\StellaOps.AirGap.Policy\StellaOps.AirGap.Policy.csproj"/>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ builder.Services.AddSingleton<IPackRunNotificationPublisher>(sp =>
|
||||
return new LoggingPackRunNotificationPublisher(sp.GetRequiredService<ILogger<LoggingPackRunNotificationPublisher>>());
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<IPackRunStepExecutor, NoopPackRunStepExecutor>();
|
||||
builder.Services.AddSingleton<IPackRunStepExecutor, BundleIngestionStepExecutor>();
|
||||
builder.Services.AddSingleton<PackRunExecutionGraphBuilder>();
|
||||
builder.Services.AddSingleton<PackRunSimulationEngine>();
|
||||
builder.Services.AddSingleton<PackRunProcessor>();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
| Task ID | Status | Sprint | Dependency | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| TASKRUN-41-001 | DONE (2025-11-30) | SPRINT_0157_0001_0001_taskrunner_i | — | Implemented run API, Mongo/file stores, approvals, provenance manifest per architecture contract. |
|
||||
| TASKRUN-AIRGAP-56-001 | BLOCKED (2025-11-30) | SPRINT_0157_0001_0001_taskrunner_i | TASKRUN-41-001 | Sealed-mode plan validation; depends on 41-001. |
|
||||
| TASKRUN-AIRGAP-56-001 | DONE (2025-11-30) | SPRINT_0157_0001_0001_taskrunner_i | TASKRUN-41-001 | Sealed-mode plan validation; depends on 41-001. |
|
||||
| TASKRUN-AIRGAP-56-002 | BLOCKED (2025-11-30) | SPRINT_0157_0001_0001_taskrunner_i | TASKRUN-AIRGAP-56-001 | Bundle ingestion helpers; depends on 56-001. |
|
||||
| TASKRUN-AIRGAP-57-001 | BLOCKED (2025-11-30) | SPRINT_0157_0001_0001_taskrunner_i | TASKRUN-AIRGAP-56-002 | Sealed install enforcement; depends on 56-002. |
|
||||
| TASKRUN-AIRGAP-58-001 | BLOCKED (2025-11-30) | SPRINT_0157_0001_0001_taskrunner_i | TASKRUN-AIRGAP-57-001 | Evidence bundles for imports; depends on 57-001. |
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace StellaOps.TimelineIndexer.Core.Models;
|
||||
/// <summary>
|
||||
/// Query filters for timeline listing.
|
||||
/// </summary>
|
||||
public sealed class TimelineQueryOptions
|
||||
public sealed record TimelineQueryOptions
|
||||
{
|
||||
public string? EventType { get; init; }
|
||||
public string? CorrelationId { get; init; }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Infrastructure.Postgres.Repositories;
|
||||
using StellaOps.TimelineIndexer.Core.Abstractions;
|
||||
|
||||
@@ -27,4 +27,8 @@ public static class ServiceCollectionExtensions
|
||||
services.AddSingleton<TimelineIndexerMigrationRunner>();
|
||||
services.AddScoped<ITimelineEventStore, TimelineEventStore>();
|
||||
services.AddScoped<ITimelineIngestionService, TimelineIngestionService>();
|
||||
services.AddScoped<ITimel
|
||||
services.AddScoped<ITimelineQueryStore, TimelineQueryStore>();
|
||||
services.AddScoped<ITimelineQueryService, TimelineQueryService>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ public class TimelineIngestionServiceTests
|
||||
RawPayloadJson = """{"ok":true}"""
|
||||
};
|
||||
|
||||
var result = await service.IngestAsync(envelope);
|
||||
var result = await service.IngestAsync(envelope, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.True(result.Inserted);
|
||||
Assert.Equal("sha256:8ceeb2a3cfdd5c6c0257df04e3d6b7c29c6a54f9b89e0ee1d3f3f94a639a6a39", store.LastEnvelope?.PayloadHash);
|
||||
Assert.Equal("sha256:4062edaf750fb8074e7e83e0c9028c94e32468a8b6f1614774328ef045150f93", store.LastEnvelope?.PayloadHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -42,8 +42,8 @@ public class TimelineIngestionServiceTests
|
||||
RawPayloadJson = "{}"
|
||||
};
|
||||
|
||||
var first = await service.IngestAsync(envelope);
|
||||
var second = await service.IngestAsync(envelope);
|
||||
var first = await service.IngestAsync(envelope, TestContext.Current.CancellationToken);
|
||||
var second = await service.IngestAsync(envelope, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.True(first.Inserted);
|
||||
Assert.False(second.Inserted);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.TimelineIndexer.Core.Abstractions;
|
||||
using StellaOps.TimelineIndexer.Core.Models;
|
||||
using StellaOps.TimelineIndexer.Core.Models.Results;
|
||||
@@ -21,7 +21,7 @@ public sealed class TimelineIngestionWorkerTests
|
||||
serviceCollection.AddSingleton<ITimelineEventStore>(store);
|
||||
serviceCollection.AddSingleton<ITimelineIngestionService, TimelineIngestionService>();
|
||||
serviceCollection.AddSingleton<IHostedService, TimelineIngestionWorker>();
|
||||
serviceCollection.AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance));
|
||||
serviceCollection.AddLogging();
|
||||
|
||||
using var host = serviceCollection.BuildServiceProvider();
|
||||
var hosted = host.GetRequiredService<IHostedService>();
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using StellaOps.TimelineIndexer.Core.Abstractions;
|
||||
using StellaOps.TimelineIndexer.Core.Models;
|
||||
using StellaOps.TimelineIndexer.Core.Services;
|
||||
|
||||
namespace StellaOps.TimelineIndexer.Tests;
|
||||
|
||||
public class TimelineQueryServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task QueryAsync_ClampsLimit()
|
||||
{
|
||||
var store = new FakeStore();
|
||||
var service = new TimelineQueryService(store);
|
||||
var options = new TimelineQueryOptions { Limit = 2000 };
|
||||
|
||||
await service.QueryAsync("tenant-a", options, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(500, store.LastOptions?.Limit);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAsync_PassesTenantAndId()
|
||||
{
|
||||
var store = new FakeStore();
|
||||
var service = new TimelineQueryService(store);
|
||||
|
||||
await service.GetAsync("tenant-1", "evt-1", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(("tenant-1", "evt-1"), store.LastGet);
|
||||
}
|
||||
|
||||
private sealed class FakeStore : ITimelineQueryStore
|
||||
{
|
||||
public TimelineQueryOptions? LastOptions { get; private set; }
|
||||
public (string tenant, string id)? LastGet { get; private set; }
|
||||
|
||||
public Task<IReadOnlyList<TimelineEventView>> QueryAsync(string tenantId, TimelineQueryOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
LastOptions = options;
|
||||
return Task.FromResult<IReadOnlyList<TimelineEventView>>(Array.Empty<TimelineEventView>());
|
||||
}
|
||||
|
||||
public Task<TimelineEventView?> GetAsync(string tenantId, string eventId, CancellationToken cancellationToken)
|
||||
{
|
||||
LastGet = (tenantId, eventId);
|
||||
return Task.FromResult<TimelineEventView?>(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,58 +4,39 @@ namespace StellaOps.TimelineIndexer.Tests;
|
||||
|
||||
public sealed class TimelineSchemaTests
|
||||
{
|
||||
private static string FindRepoRoot()
|
||||
private static string FindMigrationPath()
|
||||
{
|
||||
var dir = AppContext.BaseDirectory;
|
||||
for (var i = 0; i < 10 && dir is not null; i++)
|
||||
for (var i = 0; i < 12 && dir is not null; i++)
|
||||
{
|
||||
if (File.Exists(Path.Combine(dir, "StellaOps.sln")) ||
|
||||
File.Exists(Path.Combine(dir, "Directory.Build.props")))
|
||||
var candidate = Path.Combine(dir, "Db", "Migrations", "001_initial_schema.sql");
|
||||
if (File.Exists(candidate))
|
||||
{
|
||||
return dir;
|
||||
return candidate;
|
||||
}
|
||||
|
||||
var infraCandidate = Path.Combine(dir, "StellaOps.TimelineIndexer.Infrastructure", "Db", "Migrations", "001_initial_schema.sql");
|
||||
if (File.Exists(infraCandidate))
|
||||
{
|
||||
return infraCandidate;
|
||||
}
|
||||
|
||||
dir = Directory.GetParent(dir)?.FullName;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Could not locate repository root from test base directory.");
|
||||
throw new FileNotFoundException("Expected migration file was not found after traversing upward.", "(Db/Migrations/001_initial_schema.sql)");
|
||||
}
|
||||
|
||||
private static string ReadMigrationSql()
|
||||
{
|
||||
var root = FindRepoRoot();
|
||||
var path = Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"TimelineIndexer",
|
||||
"StellaOps.TimelineIndexer",
|
||||
"StellaOps.TimelineIndexer.Infrastructure",
|
||||
"Db",
|
||||
"Migrations",
|
||||
"001_initial_schema.sql");
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
throw new FileNotFoundException("Expected migration file was not found.", path);
|
||||
}
|
||||
|
||||
var path = FindMigrationPath();
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MigrationFile_Exists()
|
||||
{
|
||||
var root = FindRepoRoot();
|
||||
var path = Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"TimelineIndexer",
|
||||
"StellaOps.TimelineIndexer",
|
||||
"StellaOps.TimelineIndexer.Infrastructure",
|
||||
"Db",
|
||||
"Migrations",
|
||||
"001_initial_schema.sql");
|
||||
|
||||
var path = FindMigrationPath();
|
||||
Assert.True(File.Exists(path), $"Migration script missing at {path}");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.TimelineIndexer.Core.Abstractions;
|
||||
using StellaOps.TimelineIndexer.Core.Models;
|
||||
using StellaOps.TimelineIndexer.Infrastructure.DependencyInjection;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddEnvironmentVariables(prefix: "TIMELINE_");
|
||||
|
||||
builder.Services.AddTimelineIndexerPostgres(builder.Configuration);
|
||||
|
||||
builder.Services.AddStellaOpsResourceServerAuthentication(
|
||||
builder.Configuration,
|
||||
configure: options =>
|
||||
@@ -34,10 +44,64 @@ app.UseHttpsRedirection();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapGet("/timeline/events", () => Results.Ok(Array.Empty<object>()))
|
||||
app.MapGet("/timeline", async (
|
||||
HttpContext ctx,
|
||||
ITimelineQueryService service,
|
||||
[FromQuery] string? eventType,
|
||||
[FromQuery] string? correlationId,
|
||||
[FromQuery] string? traceId,
|
||||
[FromQuery] string? severity,
|
||||
[FromQuery] DateTimeOffset? since,
|
||||
[FromQuery] long? after,
|
||||
[FromQuery] int? limit,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var tenantId = GetTenantId(ctx);
|
||||
var options = new TimelineQueryOptions
|
||||
{
|
||||
EventType = eventType,
|
||||
CorrelationId = correlationId,
|
||||
TraceId = traceId,
|
||||
Severity = severity,
|
||||
Since = since,
|
||||
AfterEventSeq = after,
|
||||
Limit = limit ?? 100
|
||||
};
|
||||
var items = await service.QueryAsync(tenantId, options, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(items);
|
||||
})
|
||||
.RequireAuthorization(StellaOpsResourceServerPolicies.TimelineRead);
|
||||
|
||||
app.MapGet("/timeline/{eventId}", async (
|
||||
HttpContext ctx,
|
||||
ITimelineQueryService service,
|
||||
string eventId,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var tenantId = GetTenantId(ctx);
|
||||
var item = await service.GetAsync(tenantId, eventId, cancellationToken).ConfigureAwait(false);
|
||||
return item is null ? Results.NotFound() : Results.Ok(item);
|
||||
})
|
||||
.RequireAuthorization(StellaOpsResourceServerPolicies.TimelineRead);
|
||||
|
||||
app.MapPost("/timeline/events", () => Results.Accepted("/timeline/events", new { status = "indexed" }))
|
||||
.RequireAuthorization(StellaOpsResourceServerPolicies.TimelineWrite);
|
||||
|
||||
app.Run();
|
||||
|
||||
static string GetTenantId(HttpContext ctx)
|
||||
{
|
||||
// Temporary: allow explicit header override; fallback to claim if present.
|
||||
if (ctx.Request.Headers.TryGetValue("X-Tenant", out var header) && !string.IsNullOrWhiteSpace(header))
|
||||
{
|
||||
return header!;
|
||||
}
|
||||
|
||||
var tenant = ctx.User.FindFirst("tenant")?.Value;
|
||||
if (!string.IsNullOrWhiteSpace(tenant))
|
||||
{
|
||||
return tenant!;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Tenant not provided");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user