feat: Implement PackRunApprovalDecisionService for handling approval decisions
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added PackRunApprovalDecisionService to manage approval workflows for pack runs. - Introduced PackRunApprovalDecisionRequest and PackRunApprovalDecisionResult records. - Implemented logic to apply approval decisions and schedule run resumes based on approvals. - Updated related tests to validate approval decision functionality. test: Enhance tests for PackRunApprovalDecisionService - Created PackRunApprovalDecisionServiceTests to cover various approval scenarios. - Added in-memory stores for approvals and states to facilitate testing. - Validated behavior for applying approvals, including handling missing states. test: Add FilesystemPackRunArtifactUploaderTests for artifact uploads - Implemented tests for FilesystemPackRunArtifactUploader to ensure correct file handling. - Verified that missing files are recorded without exceptions and outputs are written as expected. fix: Update PackRunState creation to include plan reference - Modified PackRunState creation logic to include the plan in the state. chore: Refactor service registration in Program.cs - Updated service registrations in Program.cs to include new approval store and dispatcher services. - Ensured proper dependency injection for PackRunApprovalDecisionService. chore: Enhance TaskRunnerServiceOptions for approval store paths - Added ApprovalStorePath and other paths to TaskRunnerServiceOptions for better configuration. chore: Update PackRunWorkerService to handle artifact uploads - Integrated artifact uploading into PackRunWorkerService upon successful run completion. docs: Update TASKS.md for sprint progress - Documented progress on approvals workflow and related tasks in TASKS.md.
This commit is contained in:
@@ -25,6 +25,20 @@ This directory contains deterministic deployment bundles for the core Stella Ops
|
|||||||
|
|
||||||
Maintaining the digest linkage keeps offline/air-gapped installs reproducible and avoids tag drift between environments.
|
Maintaining the digest linkage keeps offline/air-gapped installs reproducible and avoids tag drift between environments.
|
||||||
|
|
||||||
|
### Surface.Env rollout warnings
|
||||||
|
|
||||||
|
- Compose (`deploy/compose/env/*.env.example`) and Helm (`deploy/helm/stellaops/values-*.yaml`) now seed `SCANNER_SURFACE_*` variables so the worker and web service resolve cache roots, Surface.FS endpoints, and secrets providers through `StellaOps.Scanner.Surface.Env`.
|
||||||
|
- During rollout, watch for structured log messages (and readiness output) prefixed with `surface.env.`—for example, `surface.env.cache_root_missing`, `surface.env.endpoint_unreachable`, or `surface.env.secrets_provider_invalid`.
|
||||||
|
- Treat these warnings as deployment blockers: update the endpoint/cache/secrets values or permissions before promoting the environment, otherwise workers will fail fast at startup.
|
||||||
|
- Air-gapped bundles default the secrets provider to `file` with `/etc/stellaops/secrets`; connected clusters default to `kubernetes`. Adjust the provider/root pair if your secrets manager differs.
|
||||||
|
|
||||||
|
### Mongo2Go OpenSSL prerequisites
|
||||||
|
|
||||||
|
- Linux runners that execute Mongo2Go-backed suites (Excititor, Scheduler, Graph, etc.) must expose OpenSSL 1.1 (`libcrypto.so.1.1`, `libssl.so.1.1`). The canonical copies live under `tests/native/openssl-1.1/linux-x64`.
|
||||||
|
- Export `LD_LIBRARY_PATH="$(git rev-parse --show-toplevel)/tests/native/openssl-1.1/linux-x64:${LD_LIBRARY_PATH:-}"` before invoking `dotnet test`. Example:\
|
||||||
|
`LD_LIBRARY_PATH="$(pwd)/tests/native/openssl-1.1/linux-x64" dotnet test src/Excititor/__Tests/StellaOps.Excititor.WebService.Tests/StellaOps.Excititor.WebService.Tests.csproj --nologo`.
|
||||||
|
- CI agents or Dockerfiles that host these tests should either mount the directory into the container or copy the two `.so` files into a directory that is already on the runtime library path.
|
||||||
|
|
||||||
### Additional tooling
|
### Additional tooling
|
||||||
|
|
||||||
- `deploy/tools/check-channel-alignment.py` – verifies that Helm/Compose profiles reference the exact images listed in a release manifest. Run it for each channel before promoting a release.
|
- `deploy/tools/check-channel-alignment.py` – verifies that Helm/Compose profiles reference the exact images listed in a release manifest. Run it for each channel before promoting a release.
|
||||||
|
|||||||
4
deploy/compose/env/airgap.env.example
vendored
4
deploy/compose/env/airgap.env.example
vendored
@@ -27,6 +27,10 @@ SCANNER_EVENTS_DSN=
|
|||||||
SCANNER_EVENTS_STREAM=stella.events
|
SCANNER_EVENTS_STREAM=stella.events
|
||||||
SCANNER_EVENTS_PUBLISH_TIMEOUT_SECONDS=5
|
SCANNER_EVENTS_PUBLISH_TIMEOUT_SECONDS=5
|
||||||
SCANNER_EVENTS_MAX_STREAM_LENGTH=10000
|
SCANNER_EVENTS_MAX_STREAM_LENGTH=10000
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT=http://rustfs:8080/api/v1
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT=/var/lib/stellaops/surface
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER=file
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT=/etc/stellaops/secrets
|
||||||
SCHEDULER_QUEUE_KIND=Nats
|
SCHEDULER_QUEUE_KIND=Nats
|
||||||
SCHEDULER_QUEUE_NATS_URL=nats://nats:4222
|
SCHEDULER_QUEUE_NATS_URL=nats://nats:4222
|
||||||
SCHEDULER_STORAGE_DATABASE=stellaops_scheduler
|
SCHEDULER_STORAGE_DATABASE=stellaops_scheduler
|
||||||
|
|||||||
5
deploy/compose/env/dev.env.example
vendored
5
deploy/compose/env/dev.env.example
vendored
@@ -26,6 +26,11 @@ SCANNER_EVENTS_DSN=
|
|||||||
SCANNER_EVENTS_STREAM=stella.events
|
SCANNER_EVENTS_STREAM=stella.events
|
||||||
SCANNER_EVENTS_PUBLISH_TIMEOUT_SECONDS=5
|
SCANNER_EVENTS_PUBLISH_TIMEOUT_SECONDS=5
|
||||||
SCANNER_EVENTS_MAX_STREAM_LENGTH=10000
|
SCANNER_EVENTS_MAX_STREAM_LENGTH=10000
|
||||||
|
# Surface.Env defaults keep worker/web service aligned with local RustFS and inline secrets.
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT=http://rustfs:8080/api/v1
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT=/var/lib/stellaops/surface
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER=inline
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT=
|
||||||
SCHEDULER_QUEUE_KIND=Nats
|
SCHEDULER_QUEUE_KIND=Nats
|
||||||
SCHEDULER_QUEUE_NATS_URL=nats://nats:4222
|
SCHEDULER_QUEUE_NATS_URL=nats://nats:4222
|
||||||
SCHEDULER_STORAGE_DATABASE=stellaops_scheduler
|
SCHEDULER_STORAGE_DATABASE=stellaops_scheduler
|
||||||
|
|||||||
6
deploy/compose/env/mirror.env.example
vendored
6
deploy/compose/env/mirror.env.example
vendored
@@ -7,6 +7,12 @@ MINIO_ROOT_USER=stellaops-mirror
|
|||||||
MINIO_ROOT_PASSWORD=mirror-minio-secret
|
MINIO_ROOT_PASSWORD=mirror-minio-secret
|
||||||
RUSTFS_HTTP_PORT=8080
|
RUSTFS_HTTP_PORT=8080
|
||||||
|
|
||||||
|
# Scanner surface integration
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT=http://rustfs:8080/api/v1
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT=/var/lib/stellaops/surface
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER=file
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT=/etc/stellaops/secrets
|
||||||
|
|
||||||
# Mirror HTTP listeners
|
# Mirror HTTP listeners
|
||||||
MIRROR_GATEWAY_HTTP_PORT=8080
|
MIRROR_GATEWAY_HTTP_PORT=8080
|
||||||
MIRROR_GATEWAY_HTTPS_PORT=9443
|
MIRROR_GATEWAY_HTTPS_PORT=9443
|
||||||
|
|||||||
4
deploy/compose/env/prod.env.example
vendored
4
deploy/compose/env/prod.env.example
vendored
@@ -29,6 +29,10 @@ SCANNER_EVENTS_DSN=
|
|||||||
SCANNER_EVENTS_STREAM=stella.events
|
SCANNER_EVENTS_STREAM=stella.events
|
||||||
SCANNER_EVENTS_PUBLISH_TIMEOUT_SECONDS=5
|
SCANNER_EVENTS_PUBLISH_TIMEOUT_SECONDS=5
|
||||||
SCANNER_EVENTS_MAX_STREAM_LENGTH=10000
|
SCANNER_EVENTS_MAX_STREAM_LENGTH=10000
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT=https://surfacefs.prod.stella-ops.org/api/v1
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT=/var/lib/stellaops/surface
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER=kubernetes
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT=stellaops/scanner
|
||||||
SCHEDULER_QUEUE_KIND=Nats
|
SCHEDULER_QUEUE_KIND=Nats
|
||||||
SCHEDULER_QUEUE_NATS_URL=nats://nats:4222
|
SCHEDULER_QUEUE_NATS_URL=nats://nats:4222
|
||||||
SCHEDULER_STORAGE_DATABASE=stellaops_scheduler
|
SCHEDULER_STORAGE_DATABASE=stellaops_scheduler
|
||||||
|
|||||||
4
deploy/compose/env/stage.env.example
vendored
4
deploy/compose/env/stage.env.example
vendored
@@ -26,6 +26,10 @@ SCANNER_EVENTS_DSN=
|
|||||||
SCANNER_EVENTS_STREAM=stella.events
|
SCANNER_EVENTS_STREAM=stella.events
|
||||||
SCANNER_EVENTS_PUBLISH_TIMEOUT_SECONDS=5
|
SCANNER_EVENTS_PUBLISH_TIMEOUT_SECONDS=5
|
||||||
SCANNER_EVENTS_MAX_STREAM_LENGTH=10000
|
SCANNER_EVENTS_MAX_STREAM_LENGTH=10000
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT=http://rustfs:8080/api/v1
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT=/var/lib/stellaops/surface
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER=kubernetes
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT=stellaops/scanner
|
||||||
SCHEDULER_QUEUE_KIND=Nats
|
SCHEDULER_QUEUE_KIND=Nats
|
||||||
SCHEDULER_QUEUE_NATS_URL=nats://nats:4222
|
SCHEDULER_QUEUE_NATS_URL=nats://nats:4222
|
||||||
SCHEDULER_STORAGE_DATABASE=stellaops_scheduler
|
SCHEDULER_STORAGE_DATABASE=stellaops_scheduler
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ services:
|
|||||||
SCANNER__EVENTS__STREAM: "stella.events"
|
SCANNER__EVENTS__STREAM: "stella.events"
|
||||||
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
||||||
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER: "file"
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT: "/etc/stellaops/secrets"
|
||||||
scanner-worker:
|
scanner-worker:
|
||||||
image: registry.stella-ops.org/stellaops/scanner-worker@sha256:eea5d6cfe7835950c5ec7a735a651f2f0d727d3e470cf9027a4a402ea89c4fb5
|
image: registry.stella-ops.org/stellaops/scanner-worker@sha256:eea5d6cfe7835950c5ec7a735a651f2f0d727d3e470cf9027a4a402ea89c4fb5
|
||||||
env:
|
env:
|
||||||
@@ -125,6 +129,10 @@ services:
|
|||||||
SCANNER__EVENTS__STREAM: "stella.events"
|
SCANNER__EVENTS__STREAM: "stella.events"
|
||||||
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
||||||
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER: "file"
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT: "/etc/stellaops/secrets"
|
||||||
notify-web:
|
notify-web:
|
||||||
image: registry.stella-ops.org/stellaops/notify-web:2025.09.2
|
image: registry.stella-ops.org/stellaops/notify-web:2025.09.2
|
||||||
service:
|
service:
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ services:
|
|||||||
SCANNER__EVENTS__STREAM: "stella.events"
|
SCANNER__EVENTS__STREAM: "stella.events"
|
||||||
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
||||||
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER: "inline"
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT: ""
|
||||||
scanner-worker:
|
scanner-worker:
|
||||||
image: registry.stella-ops.org/stellaops/scanner-worker@sha256:92dda42f6f64b2d9522104a5c9ffb61d37b34dd193132b68457a259748008f37
|
image: registry.stella-ops.org/stellaops/scanner-worker@sha256:92dda42f6f64b2d9522104a5c9ffb61d37b34dd193132b68457a259748008f37
|
||||||
env:
|
env:
|
||||||
@@ -131,6 +135,10 @@ services:
|
|||||||
SCANNER__EVENTS__STREAM: "stella.events"
|
SCANNER__EVENTS__STREAM: "stella.events"
|
||||||
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
||||||
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER: "inline"
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT: ""
|
||||||
notify-web:
|
notify-web:
|
||||||
image: registry.stella-ops.org/stellaops/notify-web:2025.10.0-edge
|
image: registry.stella-ops.org/stellaops/notify-web:2025.10.0-edge
|
||||||
service:
|
service:
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ services:
|
|||||||
SCANNER__EVENTS__STREAM: "stella.events"
|
SCANNER__EVENTS__STREAM: "stella.events"
|
||||||
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
||||||
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER: "kubernetes"
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT: "stellaops/scanner"
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: stellaops-prod-core
|
name: stellaops-prod-core
|
||||||
@@ -133,6 +137,10 @@ services:
|
|||||||
SCANNER__EVENTS__STREAM: "stella.events"
|
SCANNER__EVENTS__STREAM: "stella.events"
|
||||||
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
||||||
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER: "kubernetes"
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT: "stellaops/scanner"
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: stellaops-prod-core
|
name: stellaops-prod-core
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ services:
|
|||||||
SCANNER__EVENTS__STREAM: "stella.events"
|
SCANNER__EVENTS__STREAM: "stella.events"
|
||||||
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
||||||
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER: "kubernetes"
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT: "stellaops/scanner"
|
||||||
scanner-worker:
|
scanner-worker:
|
||||||
image: registry.stella-ops.org/stellaops/scanner-worker@sha256:32e25e76386eb9ea8bee0a1ad546775db9a2df989fab61ac877e351881960dab
|
image: registry.stella-ops.org/stellaops/scanner-worker@sha256:32e25e76386eb9ea8bee0a1ad546775db9a2df989fab61ac877e351881960dab
|
||||||
replicas: 2
|
replicas: 2
|
||||||
@@ -132,6 +136,10 @@ services:
|
|||||||
SCANNER__EVENTS__STREAM: "stella.events"
|
SCANNER__EVENTS__STREAM: "stella.events"
|
||||||
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
|
||||||
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
|
||||||
|
SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
|
||||||
|
SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
|
||||||
|
SCANNER_SURFACE_SECRETS_PROVIDER: "kubernetes"
|
||||||
|
SCANNER_SURFACE_SECRETS_ROOT: "stellaops/scanner"
|
||||||
notify-web:
|
notify-web:
|
||||||
image: registry.stella-ops.org/stellaops/notify-web:2025.09.2
|
image: registry.stella-ops.org/stellaops/notify-web:2025.09.2
|
||||||
service:
|
service:
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ Document follow-up actions for CONCELIER-CORE-AOC-19-004 as we unwind the final
|
|||||||
|
|
||||||
1. Introduce a raw linkset projection alongside the existing canonical mapper so Policy Engine can choose which flavour to consume. ✅ 2025-10-31: `AdvisoryObservation` now surfaces `RawLinkset`; Mongo documents store both canonical & raw shapes; tests/goldens updated.
|
1. Introduce a raw linkset projection alongside the existing canonical mapper so Policy Engine can choose which flavour to consume. ✅ 2025-10-31: `AdvisoryObservation` now surfaces `RawLinkset`; Mongo documents store both canonical & raw shapes; tests/goldens updated.
|
||||||
2. Update observation factory/query tests to assert duplicate handling and ordering with the relaxed projection. ✅ 2025-10-31.
|
2. Update observation factory/query tests to assert duplicate handling and ordering with the relaxed projection. ✅ 2025-10-31.
|
||||||
3. Refresh docs (`docs/ingestion/aggregation-only-contract.md`) once behaviour lands to explain the “raw vs canonical linkset” split.
|
3. Refresh docs (`docs/ingestion/aggregation-only-contract.md`) once behaviour lands to explain the “raw vs canonical linkset” split. ✅ 2025-11-06: Added invariant notes and rollout guidance, linked to `docs/migration/no-merge.md` and `docs/dev/raw-linkset-backfill-plan.md`.
|
||||||
4. Coordinate with Policy Guild to validate consumers against the new raw projection before flipping defaults. ↺ Ongoing — see action items in `docs/dev/raw-linkset-backfill-plan.md` (2025-10-31 handshake with POLICY-ENGINE-20-003 owners).
|
4. Coordinate with Policy Guild to validate consumers against the new raw projection before flipping defaults. ↺ Ongoing — see action items in `docs/dev/raw-linkset-backfill-plan.md` (2025-10-31 handshake with POLICY-ENGINE-20-003 owners).
|
||||||
|
|
||||||
|
- 2025-11-05: Catalogued residual normalization paths tied to the legacy Merge service and outlined `noMergeEnabled` feature-toggle work to keep AOC ingestion fully merge-free.
|
||||||
|
- 2025-11-05 19:20Z: Observation factory/linkset now preserve upstream ordering and duplicates; canonicalisation shifts to downstream services.
|
||||||
|
- 2025-11-06: Documented post-merge rollout plan and annotated sprint trackers with analyzer gating updates.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Raw Linkset Backfill & Adoption Plan
|
# Raw Linkset Backfill & Adoption Plan
|
||||||
|
|
||||||
_Last updated: 2025-10-31_
|
_Last updated: 2025-11-06_
|
||||||
Owners: Concelier Storage Guild, DevOps Guild, Policy Guild
|
Owners: Concelier Storage Guild, DevOps Guild, Policy Guild
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
@@ -39,7 +39,7 @@ Owners: Concelier Storage Guild, DevOps Guild, Policy Guild
|
|||||||
| 2025-10-31 | Handshake w/ Policy | Agreement to consume `rawLinkset`; this document created. |
|
| 2025-10-31 | Handshake w/ Policy | Agreement to consume `rawLinkset`; this document created. |
|
||||||
| 2025-11-01 | Draft migration script | Validate against staging dataset snapshots. |
|
| 2025-11-01 | Draft migration script | Validate against staging dataset snapshots. |
|
||||||
| 2025-11-04 | Storage task CONCELIER-STORE-AOC-19-005 due | Deliver script + runbook for review. |
|
| 2025-11-04 | Storage task CONCELIER-STORE-AOC-19-005 due | Deliver script + runbook for review. |
|
||||||
| 2025-11-06 | Staging backfill rehearsal | Target < 30 min runtime on 5M observations. |
|
| 2025-11-06 | Staging backfill rehearsal | Target < 30 min runtime on 5M observations; docs refreshed to highlight raw vs canonical invariants and analyzer guardrails. |
|
||||||
| 2025-11-08 | Policy fixtures updated | POL engine branch consumes `rawLinkset`. |
|
| 2025-11-08 | Policy fixtures updated | POL engine branch consumes `rawLinkset`. |
|
||||||
| 2025-11-11 | Production rollout window | Pending DevOps sign-off after rehearsals. |
|
| 2025-11-11 | Production rollout window | Pending DevOps sign-off after rehearsals. |
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ CONCELIER-ATTEST-73-002 `Transparency metadata` | TODO | Ensure Conseiller expos
|
|||||||
CONCELIER-CONSOLE-23-001 `Advisory aggregation views` | TODO | Expose `/console/advisories` endpoints returning aggregation groups (per linkset) with source chips, provider-reported severity columns (no local consensus), and provenance metadata for Console list + dashboard cards. Support filters by source, ecosystem, published/modified window, tenant enforcement. Dependencies: CONCELIER-LNM-21-201, CONCELIER-LNM-21-202. | Concelier WebService Guild, BE-Base Platform Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-CONSOLE-23-001 `Advisory aggregation views` | TODO | Expose `/console/advisories` endpoints returning aggregation groups (per linkset) with source chips, provider-reported severity columns (no local consensus), and provenance metadata for Console list + dashboard cards. Support filters by source, ecosystem, published/modified window, tenant enforcement. Dependencies: CONCELIER-LNM-21-201, CONCELIER-LNM-21-202. | Concelier WebService Guild, BE-Base Platform Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-CONSOLE-23-002 `Dashboard deltas API` | TODO | Provide aggregated advisory delta counts (new, modified, conflicting) for Console dashboard + live status ticker; emit structured events for queue lag metrics. Ensure deterministic counts across repeated queries. Dependencies: CONCELIER-CONSOLE-23-001, CONCELIER-LNM-21-203. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-CONSOLE-23-002 `Dashboard deltas API` | TODO | Provide aggregated advisory delta counts (new, modified, conflicting) for Console dashboard + live status ticker; emit structured events for queue lag metrics. Ensure deterministic counts across repeated queries. Dependencies: CONCELIER-CONSOLE-23-001, CONCELIER-LNM-21-203. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-CONSOLE-23-003 `Search fan-out helpers` | TODO | Deliver fast lookup endpoints for CVE/GHSA/purl search (linksets, observations) returning evidence fragments for Console global search; implement caching + scope guards. Dependencies: CONCELIER-CONSOLE-23-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-CONSOLE-23-003 `Search fan-out helpers` | TODO | Deliver fast lookup endpoints for CVE/GHSA/purl search (linksets, observations) returning evidence fragments for Console global search; implement caching + scope guards. Dependencies: CONCELIER-CONSOLE-23-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-CORE-AOC-19-004 `Remove ingestion normalization` | DOING (2025-10-28) | Strip normalization/dedup/severity logic from ingestion pipelines, delegate derived computations to Policy Engine, and update exporters/tests to consume raw documents only.<br>2025-10-29 19:05Z: Audit completed for `AdvisoryRawService`/Mongo repo to confirm alias order/dedup removal persists; identified remaining normalization in observation/linkset factory that will be revised to surface raw duplicates for Policy ingestion. Change sketch + regression matrix drafted under `docs/dev/aoc-normalization-removal-notes.md` (pending commit).<br>2025-10-31 20:45Z: Added raw linkset projection to observations/storage, exposing canonical+raw views, refreshed fixtures/tests, and documented behaviour in models/doc factory.<br>2025-10-31 21:10Z: Coordinated with Policy Engine (POLICY-ENGINE-20-003) on adoption timeline; backfill + consumer readiness tracked in `docs/dev/raw-linkset-backfill-plan.md`. Dependencies: CONCELIER-CORE-AOC-19-002, POLICY-AOC-19-003. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
CONCELIER-CORE-AOC-19-004 `Remove ingestion normalization` | DOING (2025-10-28) | Strip normalization/dedup/severity logic from ingestion pipelines, delegate derived computations to Policy Engine, and update exporters/tests to consume raw documents only.<br>2025-10-29 19:05Z: Audit completed for `AdvisoryRawService`/Mongo repo to confirm alias order/dedup removal persists; identified remaining normalization in observation/linkset factory that will be revised to surface raw duplicates for Policy ingestion. Change sketch + regression matrix drafted under `docs/dev/aoc-normalization-removal-notes.md` (pending commit).<br>2025-10-31 20:45Z: Added raw linkset projection to observations/storage, exposing canonical+raw views, refreshed fixtures/tests, and documented behaviour in models/doc factory.<br>2025-10-31 21:10Z: Coordinated with Policy Engine (POLICY-ENGINE-20-003) on adoption timeline; backfill + consumer readiness tracked in `docs/dev/raw-linkset-backfill-plan.md`.<br>2025-11-05 14:20Z: Resumed work to map remaining normalization hooks tied to Merge service and capture requirements for the upcoming `noMergeEnabled` feature toggle.<br>2025-11-05 19:05Z: Hardened no-merge feature flag wiring by suppressing obsolete diagnostics and extending gating tests.<br>2025-11-06 16:10Z: Updated AOC references/backfill plan with raw-vs-canonical guidance and noted analyzer guardrails introduced under MERGE-LNM-21-002. Dependencies: CONCELIER-CORE-AOC-19-002, POLICY-AOC-19-003. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
||||||
CONCELIER-CORE-AOC-19-013 `Authority tenant scope smoke coverage` | TODO | Extend Concelier smoke/e2e fixtures to configure `requiredTenants` and assert cross-tenant rejection with updated Authority tokens. Dependencies: AUTH-AOC-19-002. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
CONCELIER-CORE-AOC-19-013 `Authority tenant scope smoke coverage` | TODO | Extend Concelier smoke/e2e fixtures to configure `requiredTenants` and assert cross-tenant rejection with updated Authority tokens. Dependencies: AUTH-AOC-19-002. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
||||||
|
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ Depends on: Sprint 110.B - Concelier.VI
|
|||||||
Summary: Ingestion & Evidence focus on Concelier (phase VII).
|
Summary: Ingestion & Evidence focus on Concelier (phase VII).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
MERGE-LNM-21-002 | DOING (2025-11-03) | Refactor or retire `AdvisoryMergeService` and related pipelines, ensuring callers transition to observation/linkset APIs; add compile-time analyzer preventing merge service usage.<br>2025-11-03: Began dependency audit and call-site inventory ahead of deprecation plan; cataloging service registrations/tests referencing merge APIs. | BE-Merge (src/Concelier/__Libraries/StellaOps.Concelier.Merge/TASKS.md)
|
MERGE-LNM-21-002 | DOING (2025-11-03) | Refactor or retire `AdvisoryMergeService` and related pipelines, ensuring callers transition to observation/linkset APIs; add compile-time analyzer preventing merge service usage.<br>2025-11-03: Began dependency audit and call-site inventory ahead of deprecation plan; cataloging service registrations/tests referencing merge APIs.<br>2025-11-05 14:42Z: Drafting `concelier:features:noMergeEnabled` gating, merge job allowlist handling, and deprecation/telemetry changes prior to analyzer rollout.<br>2025-11-06 16:10Z: Landed analyzer project (`CONCELIER0002`), wired into Concelier WebService/tests, and updated docs to direct suppressions through explicit migration notes. | BE-Merge (src/Concelier/__Libraries/StellaOps.Concelier.Merge/TASKS.md)
|
||||||
MERGE-LNM-21-003 Determinism/test updates | QA Guild, BE-Merge | Replace merge determinism suites with observation/linkset regression tests verifying no data mutation and conflicts remain visible. Dependencies: MERGE-LNM-21-002. | MERGE-LNM-21-002 (src/Concelier/__Libraries/StellaOps.Concelier.Merge/TASKS.md)
|
MERGE-LNM-21-003 Determinism/test updates | QA Guild, BE-Merge | Replace merge determinism suites with observation/linkset regression tests verifying no data mutation and conflicts remain visible. Dependencies: MERGE-LNM-21-002. | MERGE-LNM-21-002 (src/Concelier/__Libraries/StellaOps.Concelier.Merge/TASKS.md)
|
||||||
|
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ EXCITITOR-AIRGAP-56-002 `Bundle provenance` | TODO | Persist bundle metadata on
|
|||||||
EXCITITOR-AIRGAP-57-001 `Sealed-mode enforcement` | TODO | Block non-mirror connectors in sealed mode and surface remediation errors. Dependencies: EXCITITOR-AIRGAP-56-002. | Excititor Core Guild, AirGap Policy Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
EXCITITOR-AIRGAP-57-001 `Sealed-mode enforcement` | TODO | Block non-mirror connectors in sealed mode and surface remediation errors. Dependencies: EXCITITOR-AIRGAP-56-002. | Excititor Core Guild, AirGap Policy Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
||||||
EXCITITOR-AIRGAP-57-002 `Staleness annotations` | TODO | Annotate VEX statements with staleness metrics and expose via API. Dependencies: EXCITITOR-AIRGAP-57-001. | Excititor Core Guild, AirGap Time Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
EXCITITOR-AIRGAP-57-002 `Staleness annotations` | TODO | Annotate VEX statements with staleness metrics and expose via API. Dependencies: EXCITITOR-AIRGAP-57-001. | Excititor Core Guild, AirGap Time Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
||||||
EXCITITOR-AIRGAP-58-001 `Portable VEX evidence` | TODO | Package VEX evidence segments into portable evidence bundles linked to timeline. Dependencies: EXCITITOR-AIRGAP-57-002. | Excititor Core Guild, Evidence Locker Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
EXCITITOR-AIRGAP-58-001 `Portable VEX evidence` | TODO | Package VEX evidence segments into portable evidence bundles linked to timeline. Dependencies: EXCITITOR-AIRGAP-57-002. | Excititor Core Guild, Evidence Locker Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
||||||
EXCITITOR-ATTEST-01-003 – Verification suite & observability | Team Excititor Attestation | DOING (2025-10-22) – Continuing implementation: build `IVexAttestationVerifier`, wire metrics/logging, and add regression tests. Draft plan in `EXCITITOR-ATTEST-01-003-plan.md` (2025-10-19) guides scope; updating with worknotes as progress lands.<br>2025-10-31: Verifier now tolerates duplicate source providers from AOC raw projections, downgrades offline Rekor verification to a degraded result, and enforces trusted signer registry checks with detailed diagnostics/tests. | EXCITITOR-ATTEST-01-002 (src/Excititor/__Libraries/StellaOps.Excititor.Attestation/TASKS.md)
|
EXCITITOR-ATTEST-01-003 – Verification suite & observability | Team Excititor Attestation | TODO (2025-11-06) – Continuing implementation: build `IVexAttestationVerifier`, wire metrics/logging, and add regression tests. Draft plan in `EXCITITOR-ATTEST-01-003-plan.md` (2025-10-19) guides scope; updating with worknotes as progress lands.<br>2025-10-31: Verifier now tolerates duplicate source providers from AOC raw projections, downgrades offline Rekor verification to a degraded result, and enforces trusted signer registry checks with detailed diagnostics/tests.<br>2025-11-05 14:35Z: Resuming with diagnostics/observability deliverables (typed diagnostics record, ActivitySource wiring, metrics dimensions) before WebService/Worker integration.<br>2025-11-06 07:12Z: Worker & web service suites pass with new diagnostics (`dotnet test` via staged libssl1.1); export envelope context exposed publicly for mirror bundle publishing.<br>2025-11-06 07:55Z: Paused—automation for OpenSSL shim tracked under `DEVOPS-OPENSSL-11-001/002`. | EXCITITOR-ATTEST-01-002 (src/Excititor/__Libraries/StellaOps.Excititor.Attestation/TASKS.md)
|
||||||
EXCITITOR-ATTEST-73-001 `VEX attestation payloads` | TODO | Provide VEX statement metadata (supplier identity, justification, scope) required for VEXAttestation payloads. Dependencies: EXCITITOR-ATTEST-01-003. | Excititor Core Guild, Attestation Payloads Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
EXCITITOR-ATTEST-73-001 `VEX attestation payloads` | TODO | Provide VEX statement metadata (supplier identity, justification, scope) required for VEXAttestation payloads. Dependencies: EXCITITOR-ATTEST-01-003. | Excititor Core Guild, Attestation Payloads Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
||||||
EXCITITOR-ATTEST-73-002 `Chain provenance` | TODO | Expose linkage from VEX statements to subject/product for chain of custody graph. Dependencies: EXCITITOR-ATTEST-73-001. | Excititor Core Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
EXCITITOR-ATTEST-73-002 `Chain provenance` | TODO | Expose linkage from VEX statements to subject/product for chain of custody graph. Dependencies: EXCITITOR-ATTEST-73-001. | Excititor Core Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
||||||
EXCITITOR-CONN-MS-01-003 – Trust metadata & provenance hints | Team Excititor Connectors – MSRC | TODO – Emit cosign/AAD issuer metadata, attach provenance details, and document policy integration. | EXCITITOR-CONN-MS-01-002, EXCITITOR-POLICY-01-001 (src/Excititor/__Libraries/StellaOps.Excititor.Connectors.MSRC.CSAF/TASKS.md)
|
EXCITITOR-CONN-MS-01-003 – Trust metadata & provenance hints | Team Excititor Connectors – MSRC | TODO – Emit cosign/AAD issuer metadata, attach provenance details, and document policy integration. | EXCITITOR-CONN-MS-01-002, EXCITITOR-POLICY-01-001 (src/Excititor/__Libraries/StellaOps.Excititor.Connectors.MSRC.CSAF/TASKS.md)
|
||||||
|
|||||||
@@ -134,8 +134,8 @@ Summary: Scanner & Surface focus on Scanner (phase VII).
|
|||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
SCANNER-ENTRYTRACE-18-504 | TODO | Emit EntryTrace AOC NDJSON (`entrytrace.entry/node/edge/target/warning/capability`) and wire CLI/service streaming outputs. Dependencies: SCANNER-ENTRYTRACE-18-503. | EntryTrace Guild (src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace/TASKS.md)
|
SCANNER-ENTRYTRACE-18-504 | TODO | Emit EntryTrace AOC NDJSON (`entrytrace.entry/node/edge/target/warning/capability`) and wire CLI/service streaming outputs. Dependencies: SCANNER-ENTRYTRACE-18-503. | EntryTrace Guild (src/Scanner/__Libraries/StellaOps.Scanner.EntryTrace/TASKS.md)
|
||||||
SCANNER-ENV-01 | DOING (2025-11-02) | Replace ad-hoc environment reads with `StellaOps.Scanner.Surface.Env` helpers for cache roots and CAS endpoints.<br>2025-11-02: Env helper wiring drafted for Worker startup; initial tests validate cache root resolution. | Scanner Worker Guild (src/Scanner/StellaOps.Scanner.Worker/TASKS.md)
|
SCANNER-ENV-01 | TODO (2025-11-06) | Replace ad-hoc environment reads with `StellaOps.Scanner.Surface.Env` helpers for cache roots and CAS endpoints.<br>2025-11-02: Env helper wiring drafted for Worker startup; initial tests validate cache root resolution.<br>2025-11-05 14:55Z: Continuing integration by propagating resolved settings into cache/secret services and prepping worker smoke tests + docs updates.<br>2025-11-05 19:18Z: Bound `SurfaceCacheOptions` root to Surface.Env settings and added configurator unit coverage.<br>2025-11-06 17:05Z: Documented misconfiguration warnings and updated module README to highlight Surface.Env usage.<br>2025-11-06 07:45Z: Helm/Compose env profiles (dev/stage/prod/airgap/mirror) now emit `SCANNER_SURFACE_*` defaults and ops README covers rollout warnings.<br>2025-11-06 07:55Z: Paused pending automation tracked under `DEVOPS-OPENSSL-11-001/002` and additional Surface.Env fixtures. | Scanner Worker Guild (src/Scanner/StellaOps.Scanner.Worker/TASKS.md)
|
||||||
SCANNER-ENV-02 | DOING (2025-11-02) | Wire Surface.Env helpers into WebService hosting (cache roots, feature flags) and document configuration. Dependencies: SCANNER-ENV-01.<br>2025-11-02: WebService bootstrap now consumes Surface.Env helpers for cache roots and feature flag toggles; configuration doc draft pending. | Scanner WebService Guild, Ops Guild (src/Scanner/StellaOps.Scanner.WebService/TASKS.md)
|
SCANNER-ENV-02 | TODO (2025-11-06) | Wire Surface.Env helpers into WebService hosting (cache roots, feature flags) and document configuration. Dependencies: SCANNER-ENV-01.<br>2025-11-02: WebService bootstrap now consumes Surface.Env helpers for cache roots and feature flag toggles; configuration doc draft pending.<br>2025-11-05 14:55Z: Picking up configuration/documentation work and aligning API readiness checks with Surface.Env validation outputs.<br>2025-11-05 19:18Z: Added unit test for Surface.Env cache root binding and ensured configurator registration.<br>2025-11-06 17:05Z: Surface.Env design doc expanded with warning catalogue and release notes, README refreshed.<br>2025-11-06 07:45Z: Helm/Compose templates ship `SCANNER_SURFACE_*` defaults across dev/stage/prod/airgap/mirror profiles with rollout guidance in deploy docs.<br>2025-11-06 07:55Z: Paused; follow-up automation tracked under `DEVOPS-OPENSSL-11-001/002` and readiness tests outstanding. | Scanner WebService Guild, Ops Guild (src/Scanner/StellaOps.Scanner.WebService/TASKS.md)
|
||||||
SCANNER-ENV-03 | TODO | Adopt Surface.Env helpers for plugin configuration (cache roots, CAS endpoints, feature toggles). Dependencies: SCANNER-ENV-02. | BuildX Plugin Guild (src/Scanner/StellaOps.Scanner.Sbomer.BuildXPlugin/TASKS.md)
|
SCANNER-ENV-03 | TODO | Adopt Surface.Env helpers for plugin configuration (cache roots, CAS endpoints, feature toggles). Dependencies: SCANNER-ENV-02. | BuildX Plugin Guild (src/Scanner/StellaOps.Scanner.Sbomer.BuildXPlugin/TASKS.md)
|
||||||
SCANNER-EVENTS-16-301 | BLOCKED (2025-10-26) | Emit orchestrator-compatible envelopes (`scanner.event.*`) and update integration tests to verify Notifier ingestion (no Redis queue coupling). | Scanner WebService Guild (src/Scanner/StellaOps.Scanner.WebService/TASKS.md)
|
SCANNER-EVENTS-16-301 | BLOCKED (2025-10-26) | Emit orchestrator-compatible envelopes (`scanner.event.*`) and update integration tests to verify Notifier ingestion (no Redis queue coupling). | Scanner WebService Guild (src/Scanner/StellaOps.Scanner.WebService/TASKS.md)
|
||||||
SCANNER-EVENTS-16-302 | DOING (2025-10-26) | Extend orchestrator event links (report/policy/attestation) once endpoints are finalised across gateway + console. Dependencies: SCANNER-EVENTS-16-301. | Scanner WebService Guild (src/Scanner/StellaOps.Scanner.WebService/TASKS.md)
|
SCANNER-EVENTS-16-302 | DOING (2025-10-26) | Extend orchestrator event links (report/policy/attestation) once endpoints are finalised across gateway + console. Dependencies: SCANNER-EVENTS-16-301. | Scanner WebService Guild (src/Scanner/StellaOps.Scanner.WebService/TASKS.md)
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ SCHED-CONSOLE-27-001 | DONE (2025-11-03) | Provide policy batch simulation orche
|
|||||||
SCHED-CONSOLE-27-002 | DONE (2025-11-05) | Emit telemetry endpoints/metrics (`policy_simulation_queue_depth`, `policy_simulation_latency_seconds`) and webhook callbacks for completion/failure consumed by Registry. Dependencies: SCHED-CONSOLE-27-001. | Scheduler WebService Guild, Observability Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
SCHED-CONSOLE-27-002 | DONE (2025-11-05) | Emit telemetry endpoints/metrics (`policy_simulation_queue_depth`, `policy_simulation_latency_seconds`) and webhook callbacks for completion/failure consumed by Registry. Dependencies: SCHED-CONSOLE-27-001. | Scheduler WebService Guild, Observability Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
||||||
> 2025-11-05: Resumed instrumentation work to match `policy_simulation_latency_seconds` naming, add coverage for SSE latency recording, and validate webhook sample alignment before closing.
|
> 2025-11-05: Resumed instrumentation work to match `policy_simulation_latency_seconds` naming, add coverage for SSE latency recording, and validate webhook sample alignment before closing.
|
||||||
> 2025-11-05: Ship telemetry updates + tests; local `dotnet test` blocked by pre-existing GraphJobs accessibility errors (`IGraphJobStore.UpdateAsync`).
|
> 2025-11-05: Ship telemetry updates + tests; local `dotnet test` blocked by pre-existing GraphJobs accessibility errors (`IGraphJobStore.UpdateAsync`).
|
||||||
|
> 2025-11-06: Added tenant-aware tagging to `policy_simulation_queue_depth` gauge samples and extended metrics-provider unit coverage.
|
||||||
SCHED-IMPACT-16-303 | TODO | Snapshot/compaction + invalidation for removed images; persistence to RocksDB/Redis per architecture. | Scheduler ImpactIndex Guild (src/Scheduler/__Libraries/StellaOps.Scheduler.ImpactIndex/TASKS.md)
|
SCHED-IMPACT-16-303 | TODO | Snapshot/compaction + invalidation for removed images; persistence to RocksDB/Redis per architecture. | Scheduler ImpactIndex Guild (src/Scheduler/__Libraries/StellaOps.Scheduler.ImpactIndex/TASKS.md)
|
||||||
SCHED-SURFACE-01 | TODO | Evaluate Surface.FS pointers when planning delta scans to avoid redundant work and prioritise drift-triggered assets. | Scheduler Worker Guild (src/Scheduler/__Libraries/StellaOps.Scheduler.Worker/TASKS.md)
|
SCHED-SURFACE-01 | TODO | Evaluate Surface.FS pointers when planning delta scans to avoid redundant work and prioritise drift-triggered assets. | Scheduler Worker Guild (src/Scheduler/__Libraries/StellaOps.Scheduler.Worker/TASKS.md)
|
||||||
SCHED-VULN-29-001 | TODO | Expose resolver job APIs (`POST /vuln/resolver/jobs`, `GET /vuln/resolver/jobs/{id}`) to trigger candidate recomputation per artifact/policy change with RBAC and rate limits. | Scheduler WebService Guild, Findings Ledger Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
SCHED-VULN-29-001 | TODO | Expose resolver job APIs (`POST /vuln/resolver/jobs`, `GET /vuln/resolver/jobs/{id}`) to trigger candidate recomputation per artifact/policy change with RBAC and rate limits. | Scheduler WebService Guild, Findings Ledger Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Depends on: Sprint 110.A - AdvisoryAI, Sprint 120.A - AirGap, Sprint 130.A - Sca
|
|||||||
Summary: Export & Evidence focus on ExportCenter (phase I).
|
Summary: Export & Evidence focus on ExportCenter (phase I).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
DVOFF-64-001 | DOING (2025-11-04) | Implement Export Center job `devportal --offline` bundling portal HTML, specs, SDK artifacts, changelogs, and verification manifest. | DevPortal Offline Guild, Exporter Guild (src/ExportCenter/StellaOps.ExportCenter.DevPortalOffline/TASKS.md)
|
DVOFF-64-001 | DONE (2025-11-05) | Implement Export Center job `devportal --offline` bundling portal HTML, specs, SDK artifacts, changelogs, and verification manifest.<br>2025-11-05: Worker builds reproducible bundle, persists manifest/checksum/DSSE signature under `<prefix>/<bundleId>/`, and documents verification flow in `devportal-offline.md`. Unit coverage added for job + signer. | DevPortal Offline Guild, Exporter Guild (src/ExportCenter/StellaOps.ExportCenter.DevPortalOffline/TASKS.md)
|
||||||
DVOFF-64-002 | TODO | Provide verification CLI (`stella devportal verify bundle.tgz`) ensuring integrity before import. Dependencies: DVOFF-64-001. | DevPortal Offline Guild, AirGap Controller Guild (src/ExportCenter/StellaOps.ExportCenter.DevPortalOffline/TASKS.md)
|
DVOFF-64-002 | TODO | Provide verification CLI (`stella devportal verify bundle.tgz`) ensuring integrity before import. Dependencies: DVOFF-64-001. | DevPortal Offline Guild, AirGap Controller Guild (src/ExportCenter/StellaOps.ExportCenter.DevPortalOffline/TASKS.md)
|
||||||
EXPORT-AIRGAP-56-001 | TODO | Extend Export Center to build Mirror Bundles as export profiles, including advisories/VEX/policy packs manifesting DSSE/TUF metadata. | Exporter Service Guild, Mirror Creator Guild (src/ExportCenter/StellaOps.ExportCenter/TASKS.md)
|
EXPORT-AIRGAP-56-001 | TODO | Extend Export Center to build Mirror Bundles as export profiles, including advisories/VEX/policy packs manifesting DSSE/TUF metadata. | Exporter Service Guild, Mirror Creator Guild (src/ExportCenter/StellaOps.ExportCenter/TASKS.md)
|
||||||
EXPORT-AIRGAP-56-002 | TODO | Package Bootstrap Pack (images + charts) into OCI archives with signed manifests for air-gapped deployment. Dependencies: EXPORT-AIRGAP-56-001. | Exporter Service Guild, DevOps Guild (src/ExportCenter/StellaOps.ExportCenter/TASKS.md)
|
EXPORT-AIRGAP-56-002 | TODO | Package Bootstrap Pack (images + charts) into OCI archives with signed manifests for air-gapped deployment. Dependencies: EXPORT-AIRGAP-56-001. | Exporter Service Guild, DevOps Guild (src/ExportCenter/StellaOps.ExportCenter/TASKS.md)
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ CLI-PARITY-41-002 | TODO | Implement `notify`, `aoc`, `auth` command groups, ide
|
|||||||
CLI-POLICY-20-001 | TODO | Add `stella policy new | DevEx/CLI Guild (src/Cli/StellaOps.Cli/TASKS.md)
|
CLI-POLICY-20-001 | TODO | Add `stella policy new | DevEx/CLI Guild (src/Cli/StellaOps.Cli/TASKS.md)
|
||||||
CLI-POLICY-23-004 | TODO | Add `stella policy lint` command validating SPL files with compiler diagnostics; support JSON output. Dependencies: CLI-POLICY-20-001. | DevEx/CLI Guild (src/Cli/StellaOps.Cli/TASKS.md)
|
CLI-POLICY-23-004 | TODO | Add `stella policy lint` command validating SPL files with compiler diagnostics; support JSON output. Dependencies: CLI-POLICY-20-001. | DevEx/CLI Guild (src/Cli/StellaOps.Cli/TASKS.md)
|
||||||
CLI-POLICY-23-005 | DOING (2025-10-28) | Implement `stella policy activate` with scheduling window, approval enforcement, and summary output. Dependencies: CLI-POLICY-23-004. | DevEx/CLI Guild (src/Cli/StellaOps.Cli/TASKS.md)
|
CLI-POLICY-23-005 | DOING (2025-10-28) | Implement `stella policy activate` with scheduling window, approval enforcement, and summary output. Dependencies: CLI-POLICY-23-004. | DevEx/CLI Guild (src/Cli/StellaOps.Cli/TASKS.md)
|
||||||
|
> 2025-11-06: CLI enforces `--version` as mandatory and adds scheduled activation timestamp normalization tests while keeping exit codes intact.
|
||||||
CLI-POLICY-23-006 | TODO | Provide `stella policy history` and `stella policy explain` commands to pull run history and explanation trees. Dependencies: CLI-POLICY-23-005. | DevEx/CLI Guild (src/Cli/StellaOps.Cli/TASKS.md)
|
CLI-POLICY-23-006 | TODO | Provide `stella policy history` and `stella policy explain` commands to pull run history and explanation trees. Dependencies: CLI-POLICY-23-005. | DevEx/CLI Guild (src/Cli/StellaOps.Cli/TASKS.md)
|
||||||
CLI-POLICY-27-001 | TODO | Implement policy workspace commands (`stella policy init`, `edit`, `lint`, `compile`, `test`) with template selection, local cache, JSON output, and deterministic temp directories. Dependencies: CLI-POLICY-23-006. | DevEx/CLI Guild (src/Cli/StellaOps.Cli/TASKS.md)
|
CLI-POLICY-27-001 | TODO | Implement policy workspace commands (`stella policy init`, `edit`, `lint`, `compile`, `test`) with template selection, local cache, JSON output, and deterministic temp directories. Dependencies: CLI-POLICY-23-006. | DevEx/CLI Guild (src/Cli/StellaOps.Cli/TASKS.md)
|
||||||
|
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
|||||||
Summary: Documentation & Process focus on Docs Modules Attestor).
|
Summary: Documentation & Process focus on Docs Modules Attestor).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
ATTESTOR-DOCS-0001 | DOING (2025-10-29) | See ./AGENTS.md | Docs Guild (docs/modules/attestor/TASKS.md)
|
ATTESTOR-DOCS-0001 | DONE (2025-11-05) | README updated with platform-events release (attestor.logged@1 canonical samples, schema validation notes). | Docs Guild (docs/modules/attestor/TASKS.md)
|
||||||
ATTESTOR-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/attestor/TASKS.md)
|
ATTESTOR-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/attestor/TASKS.md)
|
||||||
ATTESTOR-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/attestor/TASKS.md)
|
ATTESTOR-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/attestor/TASKS.md)
|
||||||
|
|
||||||
@@ -305,7 +305,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
|||||||
Summary: Documentation & Process focus on Docs Modules Concelier).
|
Summary: Documentation & Process focus on Docs Modules Concelier).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
CONCELIER-DOCS-0001 | DOING (2025-10-29) | See ./AGENTS.md | Docs Guild (docs/modules/concelier/TASKS.md)
|
CONCELIER-DOCS-0001 | DONE (2025-11-05) | README updated to reference 2025-10-22 authority toggle rollout guidance (quickstart + authority audit runbook). | Docs Guild (docs/modules/concelier/TASKS.md)
|
||||||
CONCELIER-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/concelier/TASKS.md)
|
CONCELIER-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/concelier/TASKS.md)
|
||||||
CONCELIER-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/concelier/TASKS.md)
|
CONCELIER-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/concelier/TASKS.md)
|
||||||
|
|
||||||
@@ -325,7 +325,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
|||||||
Summary: Documentation & Process focus on Docs Modules Excititor).
|
Summary: Documentation & Process focus on Docs Modules Excititor).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
EXCITITOR-DOCS-0001 | TODO | See ./AGENTS.md | Docs Guild (docs/modules/excititor/TASKS.md)
|
EXCITITOR-DOCS-0001 | DONE (2025-11-05) | README updated with consensus API beta note ([docs/updates/2025-11-05-excitor-consensus-beta.md](../updates/2025-11-05-excitor-consensus-beta.md)) and consensus JSON sample ([docs/vex/consensus-json.md](../vex/consensus-json.md)). | Docs Guild (docs/modules/excititor/TASKS.md)
|
||||||
EXCITITOR-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/excititor/TASKS.md)
|
EXCITITOR-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/excititor/TASKS.md)
|
||||||
EXCITITOR-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/excititor/TASKS.md)
|
EXCITITOR-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/excititor/TASKS.md)
|
||||||
|
|
||||||
@@ -335,7 +335,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
|||||||
Summary: Documentation & Process focus on Docs Modules Export Center).
|
Summary: Documentation & Process focus on Docs Modules Export Center).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
EXPORT CENTER-DOCS-0001 | DOING (2025-10-29) | See ./AGENTS.md | Docs Guild (docs/modules/export-center/TASKS.md)
|
EXPORT CENTER-DOCS-0001 | DONE (2025-11-05) | README updated to cover devportal offline profile, DSSE manifest signature, and links to provenance docs per 2025-10-29 export-center release update. | Docs Guild (docs/modules/export-center/TASKS.md)
|
||||||
EXPORT CENTER-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/export-center/TASKS.md)
|
EXPORT CENTER-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/export-center/TASKS.md)
|
||||||
EXPORT CENTER-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/export-center/TASKS.md)
|
EXPORT CENTER-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/export-center/TASKS.md)
|
||||||
|
|
||||||
@@ -367,7 +367,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
|||||||
Summary: Documentation & Process focus on Docs Modules Orchestrator).
|
Summary: Documentation & Process focus on Docs Modules Orchestrator).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
SOURCE---JOB-ORCHESTRATOR-DOCS-0001 | DOING (2025-10-29) | Align with ./AGENTS.md | Docs Guild (docs/modules/orchestrator/TASKS.md)
|
SOURCE---JOB-ORCHESTRATOR-DOCS-0001 | DONE (2025-11-05) | README reflects the 2025-11-01 Authority quota/backfill scope release and auditing requirements. | Docs Guild (docs/modules/orchestrator/TASKS.md)
|
||||||
SOURCE---JOB-ORCHESTRATOR-ENG-0001 | TODO | Sync into ../../TASKS.md | Module Team (docs/modules/orchestrator/TASKS.md)
|
SOURCE---JOB-ORCHESTRATOR-ENG-0001 | TODO | Sync into ../../TASKS.md | Module Team (docs/modules/orchestrator/TASKS.md)
|
||||||
SOURCE---JOB-ORCHESTRATOR-OPS-0001 | TODO | Document outputs in ./README.md | Ops Guild (docs/modules/orchestrator/TASKS.md)
|
SOURCE---JOB-ORCHESTRATOR-OPS-0001 | TODO | Document outputs in ./README.md | Ops Guild (docs/modules/orchestrator/TASKS.md)
|
||||||
|
|
||||||
@@ -407,7 +407,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
|||||||
Summary: Documentation & Process focus on Docs Modules Scanner).
|
Summary: Documentation & Process focus on Docs Modules Scanner).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
SCANNER-DOCS-0001 | DOING (2025-10-29) | See ./AGENTS.md | Docs Guild (docs/modules/scanner/TASKS.md)
|
SCANNER-DOCS-0001 | DONE (2025-11-05) | README updated with the 2025-10-19 platform-events release (scanner.report.ready@1 / scan.completed@1 DSSE envelopes + samples). | Docs Guild (docs/modules/scanner/TASKS.md)
|
||||||
SCANNER-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/scanner/TASKS.md)
|
SCANNER-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/scanner/TASKS.md)
|
||||||
SCANNER-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/scanner/TASKS.md)
|
SCANNER-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/scanner/TASKS.md)
|
||||||
|
|
||||||
@@ -427,7 +427,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
|||||||
Summary: Documentation & Process focus on Docs Modules Signer).
|
Summary: Documentation & Process focus on Docs Modules Signer).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
SIGNER-DOCS-0001 | DOING (2025-10-29) | See ./AGENTS.md | Docs Guild (docs/modules/signer/TASKS.md)
|
SIGNER-DOCS-0001 | DONE (2025-11-05) | README updated with Sprint 11 signing-chain release details (sign/dsse, verify/referrers, quota enforcement). | Docs Guild (docs/modules/signer/TASKS.md)
|
||||||
SIGNER-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/signer/TASKS.md)
|
SIGNER-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/signer/TASKS.md)
|
||||||
SIGNER-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/signer/TASKS.md)
|
SIGNER-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/signer/TASKS.md)
|
||||||
|
|
||||||
@@ -437,7 +437,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
|||||||
Summary: Documentation & Process focus on Docs Modules Telemetry).
|
Summary: Documentation & Process focus on Docs Modules Telemetry).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
TELEMETRY-DOCS-0001 | DOING (2025-10-29) | See ./AGENTS.md | Docs Guild (docs/modules/telemetry/TASKS.md)
|
TELEMETRY-DOCS-0001 | DONE (2025-11-05) | README updated with Sprint 23 console security alert pack (console-security Grafana board + burn-rate alert). | Docs Guild (docs/modules/telemetry/TASKS.md)
|
||||||
TELEMETRY-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/telemetry/TASKS.md)
|
TELEMETRY-ENG-0001 | TODO | Update status via ./AGENTS.md workflow | Module Team (docs/modules/telemetry/TASKS.md)
|
||||||
TELEMETRY-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/telemetry/TASKS.md)
|
TELEMETRY-OPS-0001 | TODO | Sync outcomes back to ../../TASKS.md | Ops Guild (docs/modules/telemetry/TASKS.md)
|
||||||
|
|
||||||
@@ -478,7 +478,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
|||||||
Summary: Documentation & Process focus on Docs Modules Vuln Explorer).
|
Summary: Documentation & Process focus on Docs Modules Vuln Explorer).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
VULNERABILITY-EXPLORER-DOCS-0001 | DOING (2025-10-29) | Align with ./AGENTS.md | Docs Guild (docs/modules/vuln-explorer/TASKS.md)
|
VULNERABILITY-EXPLORER-DOCS-0001 | DONE (2025-11-05) | README updated with 2025-11-03 access-controls release (attachment signing tokens + Authority scope guidance). | Docs Guild (docs/modules/vuln-explorer/TASKS.md)
|
||||||
VULNERABILITY-EXPLORER-ENG-0001 | TODO | Sync into ../../TASKS.md | Module Team (docs/modules/vuln-explorer/TASKS.md)
|
VULNERABILITY-EXPLORER-ENG-0001 | TODO | Sync into ../../TASKS.md | Module Team (docs/modules/vuln-explorer/TASKS.md)
|
||||||
VULNERABILITY-EXPLORER-OPS-0001 | TODO | Document outputs in ./README.md | Ops Guild (docs/modules/vuln-explorer/TASKS.md)
|
VULNERABILITY-EXPLORER-OPS-0001 | TODO | Document outputs in ./README.md | Ops Guild (docs/modules/vuln-explorer/TASKS.md)
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
| 3 | Provenance is mandatory | `source.*`, `upstream.*`, and `signature` metadata must be present; missing provenance triggers `ERR_AOC_004`. | Schema validator, guard, CLI verifier. |
|
| 3 | Provenance is mandatory | `source.*`, `upstream.*`, and `signature` metadata must be present; missing provenance triggers `ERR_AOC_004`. | Schema validator, guard, CLI verifier. |
|
||||||
| 4 | Idempotent upserts | Writes keyed by `(vendor, upstream_id, content_hash)` either no-op or insert a new revision with `supersedes`. Duplicate hashes map to the same document. | Repository guard, storage unique index, CI smoke tests. |
|
| 4 | Idempotent upserts | Writes keyed by `(vendor, upstream_id, content_hash)` either no-op or insert a new revision with `supersedes`. Duplicate hashes map to the same document. | Repository guard, storage unique index, CI smoke tests. |
|
||||||
| 5 | Append-only revisions | Updates create a new document with `supersedes` pointer; no in-place mutation of content. | Mongo schema (`supersedes` format), guard, data migration scripts. |
|
| 5 | Append-only revisions | Updates create a new document with `supersedes` pointer; no in-place mutation of content. | Mongo schema (`supersedes` format), guard, data migration scripts. |
|
||||||
| 6 | Linkset only | Ingestion may compute link hints (`purls`, `cpes`, IDs) to accelerate joins, but must not transform or infer severity or policy. Observations now persist both canonical linksets (for indexed queries) and raw linksets (preserving upstream order/duplicates) so downstream policy can decide how to normalise. | Linkset builders reviewed via fixtures/analyzers; raw-vs-canonical parity covered by observation fixtures. |
|
| 6 | Linkset only | Ingestion may compute link hints (`purls`, `cpes`, IDs) to accelerate joins, but must not transform or infer severity or policy. Observations now persist both canonical linksets (for indexed queries) and raw linksets (preserving upstream order/duplicates) so downstream policy can decide how to normalise. When `concelier:features:noMergeEnabled=true`, all merge-derived canonicalisation paths must be disabled. | Linkset builders reviewed via fixtures/analyzers; raw-vs-canonical parity covered by observation fixtures; analyzer `CONCELIER0002` blocks merge API usage. |
|
||||||
| 7 | Policy-only effective findings | Only Policy Engine identities can write `effective_finding_*`; ingestion callers receive `ERR_AOC_006` if they attempt it. | Authority scopes, Policy Engine guard. |
|
| 7 | Policy-only effective findings | Only Policy Engine identities can write `effective_finding_*`; ingestion callers receive `ERR_AOC_006` if they attempt it. | Authority scopes, Policy Engine guard. |
|
||||||
| 8 | Schema safety | Unknown top-level keys reject with `ERR_AOC_007`; timestamps use ISO 8601 UTC strings; tenant is required. | Mongo validator, JSON schema tests. |
|
| 8 | Schema safety | Unknown top-level keys reject with `ERR_AOC_007`; timestamps use ISO 8601 UTC strings; tenant is required. | Mongo validator, JSON schema tests. |
|
||||||
| 9 | Clock discipline | Collectors stamp `fetched_at` and `received_at` monotonically per batch to support reproducibility windows. | Collector contracts, QA fixtures. |
|
| 9 | Clock discipline | Collectors stamp `fetched_at` and `received_at` monotonically per batch to support reproducibility windows. | Collector contracts, QA fixtures. |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# No-Merge Migration Playbook
|
# No-Merge Migration Playbook
|
||||||
|
|
||||||
_Last updated: 2025-11-03_
|
_Last updated: 2025-11-06_
|
||||||
|
|
||||||
This playbook guides the full retirement of the legacy Merge service (`AdvisoryMergeService`) in favour of Link-Not-Merge (LNM) observations plus linksets. It is written for the BE-Merge, Architecture, DevOps, and Docs guilds coordinating Sprint 110 (Ingestion & Evidence) deliverables, and it feeds CONCELIER-LNM-21-101 / MERGE-LNM-21-001 and downstream DOCS-LNM-22-008.
|
This playbook guides the full retirement of the legacy Merge service (`AdvisoryMergeService`) in favour of Link-Not-Merge (LNM) observations plus linksets. It is written for the BE-Merge, Architecture, DevOps, and Docs guilds coordinating Sprint 110 (Ingestion & Evidence) deliverables, and it feeds CONCELIER-LNM-21-101 / MERGE-LNM-21-001 and downstream DOCS-LNM-22-008.
|
||||||
|
|
||||||
@@ -35,6 +35,10 @@ Do not proceed to Phase 1 until all prerequisites are checked or explicitly wa
|
|||||||
| `concelier:jobs:merge:allowlist` | `[]` | Explicit allowlist for Merge jobs when noMergeEnabled is `false`. | Set to empty during Phase 2+ to prevent accidental restarts. |
|
| `concelier:jobs:merge:allowlist` | `[]` | Explicit allowlist for Merge jobs when noMergeEnabled is `false`. | Set to empty during Phase 2+ to prevent accidental restarts. |
|
||||||
| `policy:overlays:requireLinksetEvidence` | `false` | Policy engine safety net to require linkset-backed findings. | Flip to `true` only after cutover (Phase 2). |
|
| `policy:overlays:requireLinksetEvidence` | `false` | Policy engine safety net to require linkset-backed findings. | Flip to `true` only after cutover (Phase 2). |
|
||||||
|
|
||||||
|
> 2025-11-05: WebService honours `concelier:features:noMergeEnabled` by skipping Merge DI registration and removing the `merge:reconcile` job definition (MERGE-LNM-21-002).
|
||||||
|
>
|
||||||
|
> 2025-11-06: Analyzer `CONCELIER0002` ships with Concelier hosts to block new references to `AdvisoryMergeService` / `AddMergeModule`. Suppressions must be paired with an explicit migration note.
|
||||||
|
|
||||||
> **Configuration hygiene:** Document the toggle values per environment in `ops/devops/configuration/staging.md` and `ops/devops/configuration/production.md`. Air-gapped customers receive defaults through the Offline Kit release notes.
|
> **Configuration hygiene:** Document the toggle values per environment in `ops/devops/configuration/staging.md` and `ops/devops/configuration/production.md`. Air-gapped customers receive defaults through the Offline Kit release notes.
|
||||||
|
|
||||||
## 3. Rollout phases
|
## 3. Rollout phases
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
Attestor converts signed DSSE evidence from the Signer into transparency-log proofs and verifiable reports for every downstream surface (Policy Engine, Export Center, CLI, Console, Scheduler). It is the trust backbone that proves SBOM, scan, VEX, and policy artefacts were signed, witnessed, and preserved without tampering.
|
Attestor converts signed DSSE evidence from the Signer into transparency-log proofs and verifiable reports for every downstream surface (Policy Engine, Export Center, CLI, Console, Scheduler). It is the trust backbone that proves SBOM, scan, VEX, and policy artefacts were signed, witnessed, and preserved without tampering.
|
||||||
|
|
||||||
|
## Latest updates (2025-10-19)
|
||||||
|
- Platform Events refresh published canonical `attestor.logged@1` samples under `docs/events/samples/` and validated schemas (`docs/updates/2025-10-18-docs-guild.md`, `docs/updates/2025-10-19-docs-guild.md`). Consumers should align verification workflows and tests with those sample envelopes.
|
||||||
|
|
||||||
## Why it exists
|
## Why it exists
|
||||||
- **Evidence first:** organisations need portable, verifiable attestations that prove build provenance, SBOM availability, policy verdicts, and VEX statements.
|
- **Evidence first:** organisations need portable, verifiable attestations that prove build provenance, SBOM availability, policy verdicts, and VEX statements.
|
||||||
- **Policy enforcement:** verification policies ensure only approved issuers, key types, witnesses, and freshness windows are accepted.
|
- **Policy enforcement:** verification policies ensure only approved issuers, key types, witnesses, and freshness windows are accepted.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Description | Notes |
|
| ID | Status | Owner(s) | Description | Notes |
|
||||||
|----|--------|----------|-------------|-------|
|
|----|--------|----------|-------------|-------|
|
||||||
| ATTESTOR-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | See ./AGENTS.md |
|
| ATTESTOR-DOCS-0001 | DONE (2025-11-05) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | README now references 2025-10-18/19 platform-event updates and the canonical `attestor.logged@1` samples. |
|
||||||
| ATTESTOR-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
| ATTESTOR-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
||||||
| ATTESTOR-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
| ATTESTOR-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
||||||
|
|||||||
@@ -22,10 +22,13 @@ Concelier ingests signed advisories from dozens of sources and converts them int
|
|||||||
- Connector runbooks in ./operations/connectors/.
|
- Connector runbooks in ./operations/connectors/.
|
||||||
- Mirror operations for Offline Kit parity.
|
- Mirror operations for Offline Kit parity.
|
||||||
- Grafana dashboards for connector health.
|
- Grafana dashboards for connector health.
|
||||||
|
- **Authority toggle rollout (2025-10-22 update).** Follow the phased table and audit checklist in `../../10_CONCELIER_CLI_QUICKSTART.md` when enabling `authority.enabled`/`authority.allowAnonymousFallback`, and cross-check the refreshed `./operations/authority-audit-runbook.md` before enforcement.
|
||||||
|
|
||||||
## Related resources
|
## Related resources
|
||||||
- ./operations/conflict-resolution.md
|
- ./operations/conflict-resolution.md
|
||||||
- ./operations/mirror.md
|
- ./operations/mirror.md
|
||||||
|
- ./operations/authority-audit-runbook.md
|
||||||
|
- ../../10_CONCELIER_CLI_QUICKSTART.md (authority integration timeline & smoke tests)
|
||||||
|
|
||||||
## Backlog references
|
## Backlog references
|
||||||
- DOCS-LNM-22-001, DOCS-LNM-22-007 in ../../TASKS.md.
|
- DOCS-LNM-22-001, DOCS-LNM-22-007 in ../../TASKS.md.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Description | Notes |
|
| ID | Status | Owner(s) | Description | Notes |
|
||||||
|----|--------|----------|-------------|-------|
|
|----|--------|----------|-------------|-------|
|
||||||
| CONCELIER-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | See ./AGENTS.md |
|
| CONCELIER-DOCS-0001 | DONE (2025-11-05) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | README now references the 2025-10-22 authority toggle rollout update (quickstart/runbook links). |
|
||||||
| CONCELIER-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
| CONCELIER-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
||||||
| CONCELIER-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
| CONCELIER-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
6. **Idempotent upserts.** `(source.vendor, upstream.upstream_id, upstream.content_hash)` uniquely identify a document. Duplicate hashes short-circuit; new hashes create a new version.
|
6. **Idempotent upserts.** `(source.vendor, upstream.upstream_id, upstream.content_hash)` uniquely identify a document. Duplicate hashes short-circuit; new hashes create a new version.
|
||||||
7. **Verifier & CI.** `StellaOps.AOC.Verifier` processes observation batches in CI and at runtime, rejecting writes lacking provenance, introducing unordered collections, or violating the schema.
|
7. **Verifier & CI.** `StellaOps.AOC.Verifier` processes observation batches in CI and at runtime, rejecting writes lacking provenance, introducing unordered collections, or violating the schema.
|
||||||
|
|
||||||
|
> Feature toggle: set `concelier:features:noMergeEnabled=true` to disable the legacy Merge module and its `merge:reconcile` job once Link-Not-Merge adoption is complete (MERGE-LNM-21-002). Analyzer `CONCELIER0002` prevents new references to Merge DI helpers when this flag is enabled.
|
||||||
|
|
||||||
### 1.1 Advisory raw document shape
|
### 1.1 Advisory raw document shape
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
Excititor converts heterogeneous VEX feeds into raw observations and linksets that honour the Aggregation-Only Contract.
|
Excititor converts heterogeneous VEX feeds into raw observations and linksets that honour the Aggregation-Only Contract.
|
||||||
|
|
||||||
|
## Latest updates (2025-11-05)
|
||||||
|
- Link-Not-Merge readiness: release note [Excitor consensus beta](../../updates/2025-11-05-excitor-consensus-beta.md) captures how Excititor feeds power the Excitor consensus beta (sample payload in [consensus JSON](../../vex/consensus-json.md)).
|
||||||
|
- README now points policy/UI teams to the upcoming consensus integration work.
|
||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
- Fetch OpenVEX/CSAF/CycloneDX statements via restart-only connectors.
|
- Fetch OpenVEX/CSAF/CycloneDX statements via restart-only connectors.
|
||||||
- Store immutable VEX observations with full provenance.
|
- Store immutable VEX observations with full provenance.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Description | Notes |
|
| ID | Status | Owner(s) | Description | Notes |
|
||||||
|----|--------|----------|-------------|-------|
|
|----|--------|----------|-------------|-------|
|
||||||
| EXCITITOR-DOCS-0001 | TODO | Docs Guild | Validate that ./README.md aligns with the latest release notes. | See ./AGENTS.md |
|
| EXCITITOR-DOCS-0001 | DONE (2025-11-05) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | README now links to the [Excitor consensus beta release note](../../updates/2025-11-05-excitor-consensus-beta.md) and [consensus JSON sample](../../vex/consensus-json.md). |
|
||||||
| EXCITITOR-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
| EXCITITOR-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
||||||
| EXCITITOR-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
| EXCITITOR-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
Excitor computes deterministic consensus across VEX claims, preserving conflicts and producing attestable evidence for policy suppression.
|
Excitor computes deterministic consensus across VEX claims, preserving conflicts and producing attestable evidence for policy suppression.
|
||||||
|
|
||||||
|
## Latest updates (2025-11-05)
|
||||||
|
- Consensus API beta documented with canonical JSON samples and DSSE packaging guidance (`docs/updates/2025-11-05-excitor-consensus-beta.md`).
|
||||||
|
- README links to Link-Not-Merge consensus milestone and preview endpoints for downstream integration.
|
||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
- Ingest Excititor observations and compute per-product consensus snapshots.
|
- Ingest Excititor observations and compute per-product consensus snapshots.
|
||||||
- Provide APIs for querying canonical VEX positions and conflict sets.
|
- Provide APIs for querying canonical VEX positions and conflict sets.
|
||||||
@@ -25,6 +29,7 @@ Excitor computes deterministic consensus across VEX claims, preserving conflicts
|
|||||||
|
|
||||||
## Related resources
|
## Related resources
|
||||||
- ./scoring.md
|
- ./scoring.md
|
||||||
|
- ../../vex/consensus-json.md (beta consensus payload sample)
|
||||||
|
|
||||||
## Backlog references
|
## Backlog references
|
||||||
- DOCS-EXCITOR backlog referenced in architecture doc.
|
- DOCS-EXCITOR backlog referenced in architecture doc.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Description | Notes |
|
| ID | Status | Owner(s) | Description | Notes |
|
||||||
|----|--------|----------|-------------|-------|
|
|----|--------|----------|-------------|-------|
|
||||||
| EXCITOR-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | See ./AGENTS.md |
|
| EXCITOR-DOCS-0001 | DONE (2025-11-05) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | README now references the 2025-11-05 consensus API beta release note (`docs/updates/2025-11-05-excitor-consensus-beta.md`). |
|
||||||
| EXCITOR-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
| EXCITOR-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
||||||
| EXCITOR-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
| EXCITOR-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ Export Center packages reproducible evidence bundles (JSON, Trivy DB, mirror) wi
|
|||||||
- `StellaOps.ExportCenter.Worker` bundle builder.
|
- `StellaOps.ExportCenter.Worker` bundle builder.
|
||||||
- Adapters in `StellaOps.ExportCenter.*` for JSON/Trivy/mirror variants.
|
- Adapters in `StellaOps.ExportCenter.*` for JSON/Trivy/mirror variants.
|
||||||
|
|
||||||
|
## Profiles at a glance
|
||||||
|
- **json:raw / json:policy** — Evidence bundles with raw ingestion facts or policy overlays.
|
||||||
|
- **trivy:db / trivy:java-db** — Trivy-compatible vulnerability feeds with deterministic manifests.
|
||||||
|
- **mirror:full / mirror:delta** — OCI-style mirrors with provenance, TUF metadata, and optional encryption.
|
||||||
|
- **devportal:offline** — Developer portal static assets, specs, SDKs, and changelogs packaged with `manifest.json`, `checksums.txt`, helper scripts, and a DSSE-signed manifest (`manifest.dsse.json`) for offline verification.
|
||||||
|
|
||||||
## Integrations & dependencies
|
## Integrations & dependencies
|
||||||
- Concelier/Excititor/Policy data stores for evidence.
|
- Concelier/Excititor/Policy data stores for evidence.
|
||||||
- Signer/Attestor for provenance signing.
|
- Signer/Attestor for provenance signing.
|
||||||
@@ -25,6 +31,8 @@ Export Center packages reproducible evidence bundles (JSON, Trivy DB, mirror) wi
|
|||||||
|
|
||||||
## Related resources
|
## Related resources
|
||||||
- ./operations/runbook.md
|
- ./operations/runbook.md
|
||||||
|
- ./devportal-offline.md (bundle structure, verification workflow, DSSE signature details)
|
||||||
|
- ./provenance-and-signing.md (manifest/provenance schema, signing pipeline, verification)
|
||||||
|
|
||||||
## Backlog references
|
## Backlog references
|
||||||
- DOCS-EXPORT-35-001 … DOCS-EXPORT-37-002 in ../../TASKS.md.
|
- DOCS-EXPORT-35-001 … DOCS-EXPORT-37-002 in ../../TASKS.md.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Description | Notes |
|
| ID | Status | Owner(s) | Description | Notes |
|
||||||
|----|--------|----------|-------------|-------|
|
|----|--------|----------|-------------|-------|
|
||||||
| EXPORT CENTER-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | See ./AGENTS.md |
|
| EXPORT CENTER-DOCS-0001 | DONE (2025-11-05) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | README now documents devportal offline profile, DSSE manifest signature, and links to supporting specs per 2025-10-29 release update. |
|
||||||
| EXPORT CENTER-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
| EXPORT CENTER-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
||||||
| EXPORT CENTER-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
| EXPORT CENTER-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ All endpoints require Authority-issued JWT + DPoP tokens with scopes `export:run
|
|||||||
- Supports optional encryption of `/data` subtree (age/AES-GCM) with key wrapping stored in `provenance.json`.
|
- Supports optional encryption of `/data` subtree (age/AES-GCM) with key wrapping stored in `provenance.json`.
|
||||||
- **DevPortal (`devportal:offline`).**
|
- **DevPortal (`devportal:offline`).**
|
||||||
- Packages developer portal static assets, OpenAPI specs, SDK releases, and changelog content into a reproducible archive with manifest/checksum pairs.
|
- Packages developer portal static assets, OpenAPI specs, SDK releases, and changelog content into a reproducible archive with manifest/checksum pairs.
|
||||||
- Emits `manifest.json`, `checksums.txt`, and helper scripts described in [DevPortal Offline Bundle Specification](devportal-offline.md); signing/DSSE wiring follows the shared Export Center signing service.
|
- Emits `manifest.json`, `checksums.txt`, helper scripts, and a DSSE signature document (`manifest.dsse.json`) as described in [DevPortal Offline Bundle Specification](devportal-offline.md).
|
||||||
|
- Stores artefacts under `<storagePrefix>/<bundleId>/` and signs manifests via the Export Center signing adapter (HMAC-SHA256 v1, tenant scoped).
|
||||||
|
|
||||||
Adapters expose structured telemetry events (`adapter.start`, `adapter.chunk`, `adapter.complete`) with record counts and byte totals per chunk. Failures emit `adapter.error` with reason codes.
|
Adapters expose structured telemetry events (`adapter.start`, `adapter.chunk`, `adapter.complete`) with record counts and byte totals per chunk. Failures emit `adapter.error` with reason codes.
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,14 @@ root <sha256(manifest.json)>
|
|||||||
|
|
||||||
The `root` value is the SHA-256 hash of the serialized manifest and is exposed separately in the result object for downstream signing.
|
The `root` value is the SHA-256 hash of the serialized manifest and is exposed separately in the result object for downstream signing.
|
||||||
|
|
||||||
## 4. Verification script
|
## 4. DSSE signature and storage
|
||||||
|
|
||||||
|
- The export job signs `manifest.json` with an HMAC-SHA256 based DSSE envelope, producing `manifest.dsse.json` alongside the bundle artefacts.
|
||||||
|
- The signature document captures the bundle identifier, manifest root hash, signing timestamp, algorithm metadata, and the DSSE payload/signature.
|
||||||
|
- Operators verify the manifest by validating `manifest.dsse.json`, then cross-checking the `payload` base64 with the downloaded manifest and the `rootHash` in `checksums.txt`.
|
||||||
|
- All artefacts are written under a deterministic storage prefix `<storagePrefix>/<bundleId>/` with the bundle archive stored as `bundle.tgz` (or the configured file name).
|
||||||
|
|
||||||
|
## 5. Verification script
|
||||||
|
|
||||||
`verify-offline.sh` is a POSIX-compatible helper that:
|
`verify-offline.sh` is a POSIX-compatible helper that:
|
||||||
|
|
||||||
@@ -91,7 +98,7 @@ The `root` value is the SHA-256 hash of the serialized manifest and is exposed s
|
|||||||
|
|
||||||
Operators can override the archive name via the first argument (`./verify-offline.sh mybundle.tgz`).
|
Operators can override the archive name via the first argument (`./verify-offline.sh mybundle.tgz`).
|
||||||
|
|
||||||
## 5. Content categories
|
## 6. Content categories
|
||||||
|
|
||||||
| Category | Target prefix | Notes |
|
| Category | Target prefix | Notes |
|
||||||
|-----------|---------------|-------|
|
|-----------|---------------|-------|
|
||||||
@@ -102,14 +109,13 @@ Operators can override the archive name via the first argument (`./verify-offlin
|
|||||||
|
|
||||||
Paths are normalised to forward slashes and guarded against directory traversal.
|
Paths are normalised to forward slashes and guarded against directory traversal.
|
||||||
|
|
||||||
## 6. Determinism and hashing rules
|
## 7. Determinism and hashing rules
|
||||||
|
|
||||||
- Files are enumerated and emitted in ordinal path order.
|
- Files are enumerated and emitted in ordinal path order.
|
||||||
- SHA-256 digests use lowercase hex encoding.
|
- SHA-256 digests use lowercase hex encoding.
|
||||||
- Optional directories (specs, SDKs, changelog) are skipped when absent; at least one category must contain files or the builder fails fast.
|
- Optional directories (specs, SDKs, changelog) are skipped when absent; at least one category must contain files or the builder fails fast.
|
||||||
|
|
||||||
## 7. Next steps
|
## 8. Next steps
|
||||||
|
|
||||||
- Attach DSSE signing + timestamping (`signature.json`) once Export Center signing infrastructure is ready.
|
- Expand `stella devportal verify` (DVOFF-64-002) to validate DSSE signatures and bundle integrity offline.
|
||||||
- Integrate the builder into the Export Center worker profile (`devportal --offline`) and plumb orchestration/persistence.
|
- Document operator workflow under `docs/airgap/devportal-offline.md`.
|
||||||
- Produce CLI validation tooling (`stella devportal verify`) per DVOFF-64-002 and document operator workflows under `docs/airgap/devportal-offline.md`.
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
The Orchestrator schedules, observes, and recovers ingestion and analysis jobs across the StellaOps platform.
|
The Orchestrator schedules, observes, and recovers ingestion and analysis jobs across the StellaOps platform.
|
||||||
|
|
||||||
|
## Latest updates (2025-11-01)
|
||||||
|
- Authority added `orch:quota` and `orch:backfill` scopes for quota/backfill operations, plus token reason/ticket auditing (`docs/updates/2025-11-01-orch-admin-scope.md`). Operators must supply `quota_reason` / `quota_ticket` (or `backfill_reason` / `backfill_ticket`) when requesting elevated tokens and surface those claims in change reviews.
|
||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
- Track job state, throughput, and errors for Concelier, Excititor, Scheduler, and export pipelines.
|
- Track job state, throughput, and errors for Concelier, Excititor, Scheduler, and export pipelines.
|
||||||
- Expose dashboards and APIs for throttling, replays, and failover.
|
- Expose dashboards and APIs for throttling, replays, and failover.
|
||||||
@@ -23,6 +26,7 @@ The Orchestrator schedules, observes, and recovers ingestion and analysis jobs a
|
|||||||
- Job recovery runbooks and dashboard JSON as described in Epic 9.
|
- Job recovery runbooks and dashboard JSON as described in Epic 9.
|
||||||
- Audit retention policies for job history.
|
- Audit retention policies for job history.
|
||||||
- Rate-limit reconfiguration guidelines.
|
- Rate-limit reconfiguration guidelines.
|
||||||
|
- When using the new `orch:quota` / `orch:backfill` scopes, ensure reason/ticket fields are captured in runbooks and audit checklists per the 2025-11-01 Authority update.
|
||||||
|
|
||||||
## Epic alignment
|
## Epic alignment
|
||||||
- Epic 9: Source & Job Orchestrator Dashboard.
|
- Epic 9: Source & Job Orchestrator Dashboard.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Description | Notes |
|
| ID | Status | Owner(s) | Description | Notes |
|
||||||
|----|--------|----------|-------------|-------|
|
|----|--------|----------|-------------|-------|
|
||||||
| SOURCE---JOB-ORCHESTRATOR-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Ensure ./README.md reflects the latest epic deliverables. | Align with ./AGENTS.md |
|
| SOURCE---JOB-ORCHESTRATOR-DOCS-0001 | DONE (2025-11-05) | Docs Guild | Ensure ./README.md reflects the latest epic deliverables. | README now calls out the 2025-11-01 Authority quota/backfill scope update and required audit metadata. |
|
||||||
| SOURCE---JOB-ORCHESTRATOR-ENG-0001 | TODO | Module Team | Break down epic milestones into actionable stories. | Sync into ../../TASKS.md |
|
| SOURCE---JOB-ORCHESTRATOR-ENG-0001 | TODO | Module Team | Break down epic milestones into actionable stories. | Sync into ../../TASKS.md |
|
||||||
| SOURCE---JOB-ORCHESTRATOR-OPS-0001 | TODO | Ops Guild | Prepare runbooks/observability assets once MVP lands. | Document outputs in ./README.md |
|
| SOURCE---JOB-ORCHESTRATOR-OPS-0001 | TODO | Ops Guild | Prepare runbooks/observability assets once MVP lands. | Document outputs in ./README.md |
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
Scanner analyses container images layer-by-layer, producing deterministic SBOM fragments, diffs, and signed reports.
|
Scanner analyses container images layer-by-layer, producing deterministic SBOM fragments, diffs, and signed reports.
|
||||||
|
|
||||||
|
## Latest updates (2025-11-06)
|
||||||
|
- Worker/WebService now resolve cache roots and feature flags via `StellaOps.Scanner.Surface.Env`; misconfiguration warnings are documented in `docs/modules/scanner/design/surface-env.md` and surfaced through startup validation.
|
||||||
|
- Platform events rollout (2025-10-19) continues to publish scanner.report.ready@1 and scanner.scan.completed@1 envelopes with embedded DSSE payloads (see docs/updates/2025-10-19-scanner-policy.md and docs/updates/2025-10-19-platform-events.md). Service and consumer tests should round-trip the canonical samples under docs/events/samples/.
|
||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
- Expose APIs (WebService) for scan orchestration, diffing, and artifact retrieval.
|
- Expose APIs (WebService) for scan orchestration, diffing, and artifact retrieval.
|
||||||
- Run Worker analyzers for OS, language, and native ecosystems with restart-only plug-ins.
|
- Run Worker analyzers for OS, language, and native ecosystems with restart-only plug-ins.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Description | Notes |
|
| ID | Status | Owner(s) | Description | Notes |
|
||||||
|----|--------|----------|-------------|-------|
|
|----|--------|----------|-------------|-------|
|
||||||
| SCANNER-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | See ./AGENTS.md |
|
| SCANNER-DOCS-0001 | DONE (2025-11-05) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | README now highlights the 2025-10-19 platform-events rollout (scanner.report.ready@1 / scanner.scan.completed@1 DSSE envelopes). |
|
||||||
| SCANNER-DOCS-0002 | DONE (2025-11-02) | Docs Guild | Keep scanner benchmark comparisons (Trivy/Grype/Snyk) and deep-dive matrix current with source references. | Coordinate with docs/benchmarks owners |
|
| SCANNER-DOCS-0002 | DONE (2025-11-02) | Docs Guild | Keep scanner benchmark comparisons (Trivy/Grype/Snyk) and deep-dive matrix current with source references. | Coordinate with docs/benchmarks owners |
|
||||||
| SCANNER-DOCS-0003 | TODO | Docs Guild, Product Guild | Gather Windows/macOS analyzer demand signals and record findings in `docs/benchmarks/scanner/windows-macos-demand.md`. | Coordinate with Product Marketing & Sales enablement |
|
| SCANNER-DOCS-0003 | TODO | Docs Guild, Product Guild | Gather Windows/macOS analyzer demand signals and record findings in `docs/benchmarks/scanner/windows-macos-demand.md`. | Coordinate with Product Marketing & Sales enablement |
|
||||||
| SCANNER-ENG-0008 | TODO | EntryTrace Guild, QA Guild | Maintain EntryTrace heuristic cadence per `docs/benchmarks/scanner/scanning-gaps-stella-misses-from-competitors.md`. | Include quarterly pattern review + explain trace updates |
|
| SCANNER-ENG-0008 | TODO | EntryTrace Guild, QA Guild | Maintain EntryTrace heuristic cadence per `docs/benchmarks/scanner/scanning-gaps-stella-misses-from-competitors.md`. | Include quarterly pattern review + explain trace updates |
|
||||||
|
|||||||
@@ -115,6 +115,17 @@ Failures throw `SurfaceEnvironmentException` with error codes (`SURFACE_ENV_MISS
|
|||||||
- **Scanner WebService**: build environment during startup using `AddSurfaceEnvironment`, `AddSurfaceValidation`, `AddSurfaceFileCache`, and `AddSurfaceSecrets`; readiness checks execute the validator runner and scan/report APIs emit Surface CAS pointers derived from the resolved configuration.
|
- **Scanner WebService**: build environment during startup using `AddSurfaceEnvironment`, `AddSurfaceValidation`, `AddSurfaceFileCache`, and `AddSurfaceSecrets`; readiness checks execute the validator runner and scan/report APIs emit Surface CAS pointers derived from the resolved configuration.
|
||||||
- **Zastava Observer/Webhook**: use the same builder; ensure Helm charts set `ZASTAVA_` variables.
|
- **Zastava Observer/Webhook**: use the same builder; ensure Helm charts set `ZASTAVA_` variables.
|
||||||
- **Scheduler Planner (future)**: treat Surface.Env as read-only input; do not mutate settings.
|
- **Scheduler Planner (future)**: treat Surface.Env as read-only input; do not mutate settings.
|
||||||
|
- `Scanner.Worker` and `Scanner.WebService` automatically bind the `SurfaceCacheOptions.RootDirectory` to `SurfaceEnvironment.Settings.CacheRoot` (2025-11-05); both hosts emit structured warnings (`surface.env.misconfiguration`) when the helper detects missing cache roots, endpoints, or secrets provider settings (2025-11-06).
|
||||||
|
|
||||||
|
### 6.1 Misconfiguration warnings
|
||||||
|
|
||||||
|
Surface.Env surfaces actionable warnings that appear in structured logs and readiness responses:
|
||||||
|
|
||||||
|
- `surface.env.cache_root_missing` – emitted when the resolved cache directory does not exist or is not writable. The host attempts to create the directory once; subsequent failures block startup.
|
||||||
|
- `surface.env.endpoint_unreachable` – emitted when `SurfaceFsEndpoint` is missing or not an absolute HTTPS URI.
|
||||||
|
- `surface.env.secrets_provider_invalid` – emitted when the configured secrets provider lacks mandatory fields (e.g., `SCANNER_SURFACE_SECRETS_ROOT` for the `file` provider).
|
||||||
|
|
||||||
|
Each warning includes remediation text and a reference to this design document; operations runbooks should treat these warnings as blockers in production and as validation hints in staging.
|
||||||
|
|
||||||
## 7. Security & Observability
|
## 7. Security & Observability
|
||||||
|
|
||||||
|
|||||||
@@ -2,21 +2,32 @@
|
|||||||
|
|
||||||
Signer validates callers, enforces Proof-of-Entitlement, and produces signed DSSE bundles for SBOMs, reports, and exports.
|
Signer validates callers, enforces Proof-of-Entitlement, and produces signed DSSE bundles for SBOMs, reports, and exports.
|
||||||
|
|
||||||
|
## Latest updates (Sprint 11 · 2025-10-21)
|
||||||
|
- `/sign/dsse` pipeline landed with Authority OpTok + PoE enforcement, Fulcio/KMS signing modes, and deterministic DSSE bundles ready for Attestor logging.
|
||||||
|
- `/verify/referrers` endpoint exposes release-integrity checks against scanner OCI referrers so callers can confirm digests before requesting signatures.
|
||||||
|
- Plan quota enforcement (QPS/concurrency/artifact size) and audit/metrics wiring now align with the Sprint 11 signing-chain release.
|
||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
- Enforce plan quotas and PoE before signing artifacts.
|
- Enforce Proof-of-Entitlement and plan quotas before signing artifacts.
|
||||||
- Support keyless and keyful signing backends.
|
- Support keyless (Fulcio) and keyful (KMS/HSM) signing backends.
|
||||||
- Emit DSSE payloads consumed by Attestor and downstream bundles.
|
- Verify scanner release integrity via OCI referrers prior to issuing signatures.
|
||||||
- Maintain audit trails for all signing operations.
|
- Emit DSSE payloads consumed by Attestor/Export Center and maintain comprehensive audit trails.
|
||||||
|
|
||||||
## Key components
|
## Key components
|
||||||
- `StellaOps.Signer` service host.
|
- `StellaOps.Signer` service host.
|
||||||
- Crypto providers under `StellaOps.Cryptography.*`.
|
- Crypto providers under `StellaOps.Cryptography.*`.
|
||||||
|
|
||||||
## Integrations & dependencies
|
## Integrations & dependencies
|
||||||
- Authority for OpTok validation.
|
- Authority for OpTok + PoE validation.
|
||||||
- Attestor for transparency logging.
|
- Licensing Service for entitlement introspection.
|
||||||
|
- OCI registries (Referrers API) for scanner release verification.
|
||||||
|
- Attestor for transparency logging and Rekor ingestion.
|
||||||
- Export Center and CLI for artifact signing flows.
|
- Export Center and CLI for artifact signing flows.
|
||||||
|
|
||||||
|
## API quick reference
|
||||||
|
- `POST /api/v1/signer/sign/dsse` — validate OpTok/PoE, enforce quotas, return DSSE bundle with signing identity metadata.
|
||||||
|
- `GET /api/v1/signer/verify/referrers` — report scanner release signer and trust verdict for a supplied image digest.
|
||||||
|
|
||||||
## Operational notes
|
## Operational notes
|
||||||
- Key management via Authority/DevOps runbooks.
|
- Key management via Authority/DevOps runbooks.
|
||||||
- Metrics for signing latency/throttle states.
|
- Metrics for signing latency/throttle states.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Description | Notes |
|
| ID | Status | Owner(s) | Description | Notes |
|
||||||
|----|--------|----------|-------------|-------|
|
|----|--------|----------|-------------|-------|
|
||||||
| SIGNER-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | See ./AGENTS.md |
|
| SIGNER-DOCS-0001 | DONE (2025-11-05) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | README now highlights Sprint 11 signing-chain release (sign/dsse, verify/referrers, quota enforcement). |
|
||||||
| SIGNER-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
| SIGNER-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
||||||
| SIGNER-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
| SIGNER-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Telemetry module captures deployment and operations guidance for the shared obse
|
|||||||
## Operational notes
|
## Operational notes
|
||||||
- Smoke script references (../../ops/devops/telemetry).
|
- Smoke script references (../../ops/devops/telemetry).
|
||||||
- Bundle packaging instructions in ops/devops/telemetry.
|
- Bundle packaging instructions in ops/devops/telemetry.
|
||||||
|
- Sprint 23 console security sign-off (2025-10-27) added the `console-security.json` Grafana board and burn-rate alert pack—ensure environments import the updated dashboards/alerts referenced in `docs/updates/2025-10-27-console-security-signoff.md`.
|
||||||
|
|
||||||
## Related resources
|
## Related resources
|
||||||
- ./operations/collector.md
|
- ./operations/collector.md
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Description | Notes |
|
| ID | Status | Owner(s) | Description | Notes |
|
||||||
|----|--------|----------|-------------|-------|
|
|----|--------|----------|-------------|-------|
|
||||||
| TELEMETRY-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | See ./AGENTS.md |
|
| TELEMETRY-DOCS-0001 | DONE (2025-11-05) | Docs Guild | Validate that ./README.md aligns with the latest release notes. | README now captures the 2025-10-27 console security alert pack (console-security.json, burn-rate alert). |
|
||||||
| TELEMETRY-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
| TELEMETRY-OPS-0001 | TODO | Ops Guild | Review runbooks/observability assets after next sprint demo. | Sync outcomes back to ../../TASKS.md |
|
||||||
| TELEMETRY-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
| TELEMETRY-ENG-0001 | TODO | Module Team | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md`. | Update status via ./AGENTS.md workflow |
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
Vulnerability Explorer delivers policy-aware triage, investigation, and reporting surfaces for effective findings.
|
Vulnerability Explorer delivers policy-aware triage, investigation, and reporting surfaces for effective findings.
|
||||||
|
|
||||||
|
## Latest updates (2025-11-03)
|
||||||
|
- Access controls refresh introduced attachment signing tokens and updated scope guidance (`docs/updates/2025-11-03-vuln-explorer-access-controls.md`). Ensure operator runbooks reference the new Authority scopes (`authority-scopes.md`) and security checklist before enabling attachment uploads.
|
||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
- Present policy-evaluated findings with advisory, VEX, SBOM, and runtime context.
|
- Present policy-evaluated findings with advisory, VEX, SBOM, and runtime context.
|
||||||
- Capture triage workflow in an immutable findings ledger with role-based access.
|
- Capture triage workflow in an immutable findings ledger with role-based access.
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Description | Notes |
|
| ID | Status | Owner(s) | Description | Notes |
|
||||||
|----|--------|----------|-------------|-------|
|
|----|--------|----------|-------------|-------|
|
||||||
| VULNERABILITY-EXPLORER-DOCS-0001 | DOING (2025-10-29) | Docs Guild | Ensure ./README.md reflects the latest epic deliverables. | Align with ./AGENTS.md |
|
| VULNERABILITY-EXPLORER-DOCS-0001 | DONE (2025-11-05) | Docs Guild | Ensure ./README.md reflects the latest epic deliverables. | README now includes the 2025-11-03 access-control refresh (attachment signing tokens & scope guidance). |
|
||||||
| VULNERABILITY-EXPLORER-ENG-0001 | TODO | Module Team | Break down epic milestones into actionable stories. | Sync into ../../TASKS.md |
|
| VULNERABILITY-EXPLORER-ENG-0001 | TODO | Module Team | Break down epic milestones into actionable stories. | Sync into ../../TASKS.md |
|
||||||
| VULNERABILITY-EXPLORER-OPS-0001 | TODO | Ops Guild | Prepare runbooks/observability assets once MVP lands. | Document outputs in ./README.md |
|
| VULNERABILITY-EXPLORER-OPS-0001 | TODO | Ops Guild | Prepare runbooks/observability assets once MVP lands. | Document outputs in ./README.md |
|
||||||
|
|||||||
12
docs/updates/2025-11-05-excitor-consensus-beta.md
Normal file
12
docs/updates/2025-11-05-excitor-consensus-beta.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 2025-11-05 – Excitor consensus API beta
|
||||||
|
|
||||||
|
**Subject:** Excitor consensus export/API preview ships \
|
||||||
|
**Audience:** Docs Guild, VEX Lens Guild, Policy Engine Guild, Export Center Guild
|
||||||
|
|
||||||
|
- Published `docs/modules/excitor/README.md` update capturing the Link-Not-Merge consensus milestone and pointing to the beta API surface.
|
||||||
|
- Added `docs/vex/consensus-json.md` sample describing the canonical `consensus_state` payload (`rollupStatus`, `sources[]`, `policyRevisionId`, `consensusDigest`) emitted by the preview endpoint.
|
||||||
|
- Documented DSSE packaging plus Export Center hooks so attested consensus bundles can ride along with devportal/offline exports once Excitor workers emit DSSE manifests (`docs/modules/export-center/devportal-offline.md`).
|
||||||
|
|
||||||
|
**Follow-ups**
|
||||||
|
- [ ] Coordinate with Policy Engine (`POLICY-ENGINE-30-101`) to reference consensus policy weighting knobs before GA.
|
||||||
|
- [ ] Produce CLI quickstart once `stella vex consensus` verbs land (CLI backlog `CLI-VEX-30-002`).
|
||||||
34
docs/vex/consensus-json.md
Normal file
34
docs/vex/consensus-json.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Excitor consensus JSON sample (beta)
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"vulnId": "CVE-2025-12345",
|
||||||
|
"productKey": "pkg:maven/org.apache.commons/commons-text@1.11.0",
|
||||||
|
"rollupStatus": "NOT_AFFECTED",
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"providerId": "redhat",
|
||||||
|
"status": "NOT_AFFECTED",
|
||||||
|
"justification": "component_not_present",
|
||||||
|
"weight": 0.62,
|
||||||
|
"lastObserved": "2025-11-04T18:22:31Z",
|
||||||
|
"accepted": true,
|
||||||
|
"reason": "trust-tier vendor, signed OpenVEX"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"providerId": "github",
|
||||||
|
"status": "AFFECTED",
|
||||||
|
"justification": null,
|
||||||
|
"weight": 0.27,
|
||||||
|
"lastObserved": "2025-11-05T01:12:03Z",
|
||||||
|
"accepted": false,
|
||||||
|
"reason": "lower trust tier and stale statement"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"policyRevisionId": "vex-consensus-policy@2025-11-05",
|
||||||
|
"evaluatedAt": "2025-11-05T02:05:14Z",
|
||||||
|
"consensusDigest": "sha256:41f2d96728b24f7a8b7f1251983b8edccd1e0f5781d4a51e51c8e6b20c1fa31a"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** This payload is generated from the beta consensus endpoint and is subject to change prior to GA. Keys and semantics are documented alongside API previews in `docs/modules/excitor/README.md`.
|
||||||
BIN
local-nuget/Google.Api.Gax.Grpc.4.7.0.nupkg
Normal file
BIN
local-nuget/Google.Api.Gax.Grpc.4.7.0.nupkg
Normal file
Binary file not shown.
BIN
local-nuget/Google.Apis.Core.1.64.0.nupkg
Normal file
BIN
local-nuget/Google.Apis.Core.1.64.0.nupkg
Normal file
Binary file not shown.
BIN
local-nuget/Google.Cloud.Kms.V1.3.19.0.nupkg
Normal file
BIN
local-nuget/Google.Cloud.Kms.V1.3.19.0.nupkg
Normal file
Binary file not shown.
BIN
local-nuget/Google.Protobuf.3.31.1.nupkg
Normal file
BIN
local-nuget/Google.Protobuf.3.31.1.nupkg
Normal file
Binary file not shown.
BIN
local-nuget/Grpc.Auth.2.71.0.nupkg
Normal file
BIN
local-nuget/Grpc.Auth.2.71.0.nupkg
Normal file
Binary file not shown.
BIN
local-nuget/Grpc.Core.Api.2.71.0.nupkg
Normal file
BIN
local-nuget/Grpc.Core.Api.2.71.0.nupkg
Normal file
Binary file not shown.
BIN
local-nuget/Grpc.Net.Client.2.71.0.nupkg
Normal file
BIN
local-nuget/Grpc.Net.Client.2.71.0.nupkg
Normal file
Binary file not shown.
BIN
local-nuget/Grpc.Net.Common.2.71.0.nupkg
Normal file
BIN
local-nuget/Grpc.Net.Common.2.71.0.nupkg
Normal file
Binary file not shown.
BIN
local-nuget/Grpc.Tools.2.71.0.nupkg
Normal file
BIN
local-nuget/Grpc.Tools.2.71.0.nupkg
Normal file
Binary file not shown.
BIN
local-nuget/Pkcs11Interop.5.3.0.nupkg
Normal file
BIN
local-nuget/Pkcs11Interop.5.3.0.nupkg
Normal file
Binary file not shown.
@@ -13,6 +13,9 @@
|
|||||||
> Docs hand-off (2025-10-26): see `docs/ingestion/aggregation-only-contract.md` §5, `docs/modules/platform/architecture-overview.md`, and `docs/modules/cli/guides/cli-reference.md` for guard + verifier expectations.
|
> Docs hand-off (2025-10-26): see `docs/ingestion/aggregation-only-contract.md` §5, `docs/modules/platform/architecture-overview.md`, and `docs/modules/cli/guides/cli-reference.md` for guard + verifier expectations.
|
||||||
| DEVOPS-AOC-19-002 | BLOCKED (2025-10-26) | DevOps Guild | CLI-AOC-19-002, CONCELIER-WEB-AOC-19-004, EXCITITOR-WEB-AOC-19-004 | Add pipeline stage executing `stella aoc verify --since` against seeded Mongo snapshots for Concelier + Excititor, publishing violation report artefacts. | Stage runs on main/nightly, fails on violations, artifacts retained, runbook documented. |
|
| DEVOPS-AOC-19-002 | BLOCKED (2025-10-26) | DevOps Guild | CLI-AOC-19-002, CONCELIER-WEB-AOC-19-004, EXCITITOR-WEB-AOC-19-004 | Add pipeline stage executing `stella aoc verify --since` against seeded Mongo snapshots for Concelier + Excititor, publishing violation report artefacts. | Stage runs on main/nightly, fails on violations, artifacts retained, runbook documented. |
|
||||||
> Blocked: waiting on CLI verifier command and Concelier/Excititor guard endpoints to land (CLI-AOC-19-002, CONCELIER-WEB-AOC-19-004, EXCITITOR-WEB-AOC-19-004).
|
> Blocked: waiting on CLI verifier command and Concelier/Excititor guard endpoints to land (CLI-AOC-19-002, CONCELIER-WEB-AOC-19-004, EXCITITOR-WEB-AOC-19-004).
|
||||||
|
| DEVOPS-OPENSSL-11-001 | TODO (2025-11-06) | DevOps Guild, Build Infra Guild | — | Package the OpenSSL 1.1 shim (`tests/native/openssl-1.1/linux-x64`) into test harness output so Mongo2Go suites discover it automatically. | Shim copied during `dotnet test`, documentation updated, redundant manual extraction removed. |
|
||||||
|
> 2025-11-06: Interim guidance published in `tests/native/openssl-1.1/README.md` and `deploy/README.md`; automation still required.
|
||||||
|
| DEVOPS-OPENSSL-11-002 | TODO (2025-11-06) | DevOps Guild, CI Guild | DEVOPS-OPENSSL-11-001 | Ensure CI runners and Docker images that execute Mongo2Go tests export `LD_LIBRARY_PATH` (or embed the shim) to unblock unattended pipelines. | CI jobs set the variable or bake the libraries; runbook documents the location; smoke builds green without manual exports. |
|
||||||
| DEVOPS-AOC-19-003 | BLOCKED (2025-10-26) | DevOps Guild, QA Guild | CONCELIER-WEB-AOC-19-003, EXCITITOR-WEB-AOC-19-003 | Enforce unit test coverage thresholds for AOC guard suites and ensure coverage exported to dashboards. | Coverage report includes guard projects, threshold gate passes/fails as expected, dashboards refreshed with new metrics. |
|
| DEVOPS-AOC-19-003 | BLOCKED (2025-10-26) | DevOps Guild, QA Guild | CONCELIER-WEB-AOC-19-003, EXCITITOR-WEB-AOC-19-003 | Enforce unit test coverage thresholds for AOC guard suites and ensure coverage exported to dashboards. | Coverage report includes guard projects, threshold gate passes/fails as expected, dashboards refreshed with new metrics. |
|
||||||
> Blocked: guard coverage suites and exporter hooks pending in Concelier/Excititor (CONCELIER-WEB-AOC-19-003, EXCITITOR-WEB-AOC-19-003).
|
> Blocked: guard coverage suites and exporter hooks pending in Concelier/Excititor (CONCELIER-WEB-AOC-19-003, EXCITITOR-WEB-AOC-19-003).
|
||||||
| DEVOPS-AOC-19-101 | TODO (2025-10-28) | DevOps Guild, Concelier Storage Guild | CONCELIER-STORE-AOC-19-002 | Draft supersedes backfill rollout (freeze window, dry-run steps, rollback) once advisory_raw idempotency index passes staging verification. | Runbook committed in `docs/deploy/containers.md` + Offline Kit notes, staging rehearsal scheduled with dependencies captured in SPRINTS. |
|
| DEVOPS-AOC-19-101 | TODO (2025-10-28) | DevOps Guild, Concelier Storage Guild | CONCELIER-STORE-AOC-19-002 | Draft supersedes backfill rollout (freeze window, dry-run steps, rollback) once advisory_raw idempotency index passes staging verification. | Runbook committed in `docs/deploy/containers.md` + Offline Kit notes, staging rehearsal scheduled with dependencies captured in SPRINTS. |
|
||||||
|
|||||||
@@ -732,7 +732,8 @@ internal static class CommandFactory
|
|||||||
|
|
||||||
var activateVersionOption = new Option<int>("--version")
|
var activateVersionOption = new Option<int>("--version")
|
||||||
{
|
{
|
||||||
Description = "Revision version to activate."
|
Description = "Revision version to activate.",
|
||||||
|
IsRequired = true
|
||||||
};
|
};
|
||||||
|
|
||||||
var activationNoteOption = new Option<string?>("--note")
|
var activationNoteOption = new Option<string?>("--note")
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
| CLI-POLICY-23-004 | TODO | DevEx/CLI Guild | WEB-POLICY-23-001 | Add `stella policy lint` command validating SPL files with compiler diagnostics; support JSON output. | Command returns lint diagnostics; exit codes documented; tests cover error scenarios. |
|
| CLI-POLICY-23-004 | TODO | DevEx/CLI Guild | WEB-POLICY-23-001 | Add `stella policy lint` command validating SPL files with compiler diagnostics; support JSON output. | Command returns lint diagnostics; exit codes documented; tests cover error scenarios. |
|
||||||
| CLI-POLICY-23-005 | DOING (2025-10-28) | DevEx/CLI Guild | POLICY-GATEWAY-18-002..003, WEB-POLICY-23-002 | Implement `stella policy activate` with scheduling window, approval enforcement, and summary output. | Activation command integrates with API, handles 2-person rule failures; tests cover success/error. |
|
| CLI-POLICY-23-005 | DOING (2025-10-28) | DevEx/CLI Guild | POLICY-GATEWAY-18-002..003, WEB-POLICY-23-002 | Implement `stella policy activate` with scheduling window, approval enforcement, and summary output. | Activation command integrates with API, handles 2-person rule failures; tests cover success/error. |
|
||||||
> 2025-10-28: CLI command implemented with gateway integration (`policy activate`), interactive summary output, retry-aware metrics, and exit codes (0 success, 75 pending second approval). Tests cover success/pending/error paths.
|
> 2025-10-28: CLI command implemented with gateway integration (`policy activate`), interactive summary output, retry-aware metrics, and exit codes (0 success, 75 pending second approval). Tests cover success/pending/error paths.
|
||||||
|
> 2025-11-06: Tightened required `--version` parsing, added scheduled activation handling coverage, and expanded tests to validate timestamp normalization.
|
||||||
| CLI-POLICY-23-006 | TODO | DevEx/CLI Guild | WEB-POLICY-23-004 | Provide `stella policy history` and `stella policy explain` commands to pull run history and explanation trees. | Commands output JSON/table; integration tests with fixtures; docs updated. |
|
| CLI-POLICY-23-006 | TODO | DevEx/CLI Guild | WEB-POLICY-23-004 | Provide `stella policy history` and `stella policy explain` commands to pull run history and explanation trees. | Commands output JSON/table; integration tests with fixtures; docs updated. |
|
||||||
|
|
||||||
## Graph & Vuln Explorer v1
|
## Graph & Vuln Explorer v1
|
||||||
|
|||||||
@@ -1855,6 +1855,56 @@ spec:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HandlePolicyActivateAsync_ParsesScheduledTimestamp()
|
||||||
|
{
|
||||||
|
var originalExit = Environment.ExitCode;
|
||||||
|
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
|
||||||
|
backend.ActivationResult = new PolicyActivationResult(
|
||||||
|
"scheduled",
|
||||||
|
new PolicyActivationRevision(
|
||||||
|
"P-8",
|
||||||
|
5,
|
||||||
|
"approved",
|
||||||
|
false,
|
||||||
|
DateTimeOffset.Parse("2025-12-01T00:30:00Z", CultureInfo.InvariantCulture),
|
||||||
|
null,
|
||||||
|
new ReadOnlyCollection<PolicyActivationApproval>(Array.Empty<PolicyActivationApproval>())));
|
||||||
|
|
||||||
|
var provider = BuildServiceProvider(backend);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const string scheduledValue = "2025-12-01T03:00:00+02:00";
|
||||||
|
await CommandHandlers.HandlePolicyActivateAsync(
|
||||||
|
provider,
|
||||||
|
policyId: "P-8",
|
||||||
|
version: 5,
|
||||||
|
note: null,
|
||||||
|
runNow: false,
|
||||||
|
scheduledAt: scheduledValue,
|
||||||
|
priority: null,
|
||||||
|
rollback: false,
|
||||||
|
incidentId: null,
|
||||||
|
verbose: false,
|
||||||
|
cancellationToken: CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Equal(0, Environment.ExitCode);
|
||||||
|
Assert.NotNull(backend.LastPolicyActivation);
|
||||||
|
var activation = backend.LastPolicyActivation!.Value;
|
||||||
|
Assert.False(activation.Request.RunNow);
|
||||||
|
var expected = DateTimeOffset.Parse(
|
||||||
|
scheduledValue,
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
|
||||||
|
Assert.Equal(expected, activation.Request.ScheduledAt);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Environment.ExitCode = originalExit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HandlePolicyActivateAsync_MapsErrorCodes()
|
public async Task HandlePolicyActivateAsync_MapsErrorCodes()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ internal static class JobRegistrationExtensions
|
|||||||
|
|
||||||
new("export:json", "StellaOps.Concelier.Exporter.Json.JsonExportJob", "StellaOps.Concelier.Exporter.Json", TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(5)),
|
new("export:json", "StellaOps.Concelier.Exporter.Json.JsonExportJob", "StellaOps.Concelier.Exporter.Json", TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(5)),
|
||||||
new("export:trivy-db", "StellaOps.Concelier.Exporter.TrivyDb.TrivyDbExportJob", "StellaOps.Concelier.Exporter.TrivyDb", TimeSpan.FromMinutes(20), TimeSpan.FromMinutes(10)),
|
new("export:trivy-db", "StellaOps.Concelier.Exporter.TrivyDb.TrivyDbExportJob", "StellaOps.Concelier.Exporter.TrivyDb", TimeSpan.FromMinutes(20), TimeSpan.FromMinutes(10)),
|
||||||
|
#pragma warning disable CS0618, CONCELIER0001 // Legacy merge job remains available until MERGE-LNM-21-002 completes.
|
||||||
new("merge:reconcile", "StellaOps.Concelier.Merge.Jobs.MergeReconcileJob", "StellaOps.Concelier.Merge", TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(5))
|
new("merge:reconcile", "StellaOps.Concelier.Merge.Jobs.MergeReconcileJob", "StellaOps.Concelier.Merge", TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(5))
|
||||||
|
#pragma warning restore CS0618, CONCELIER0001
|
||||||
};
|
};
|
||||||
|
|
||||||
public static IServiceCollection AddBuiltInConcelierJobs(this IServiceCollection services)
|
public static IServiceCollection AddBuiltInConcelierJobs(this IServiceCollection services)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ public sealed class ConcelierOptions
|
|||||||
|
|
||||||
public MirrorOptions Mirror { get; set; } = new();
|
public MirrorOptions Mirror { get; set; } = new();
|
||||||
|
|
||||||
|
public FeaturesOptions Features { get; set; } = new();
|
||||||
|
|
||||||
public sealed class StorageOptions
|
public sealed class StorageOptions
|
||||||
{
|
{
|
||||||
public string Driver { get; set; } = "mongo";
|
public string Driver { get; set; } = "mongo";
|
||||||
@@ -135,4 +137,13 @@ public sealed class ConcelierOptions
|
|||||||
|
|
||||||
public int MaxDownloadRequestsPerHour { get; set; } = 1200;
|
public int MaxDownloadRequestsPerHour { get; set; } = 1200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class FeaturesOptions
|
||||||
|
{
|
||||||
|
public bool NoMergeEnabled { get; set; }
|
||||||
|
|
||||||
|
public bool LnmShadowWrites { get; set; } = true;
|
||||||
|
|
||||||
|
public IList<string> MergeJobAllowlist { get; } = new List<string>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public static class ConcelierOptionsPostConfigure
|
|||||||
ArgumentNullException.ThrowIfNull(options);
|
ArgumentNullException.ThrowIfNull(options);
|
||||||
|
|
||||||
options.Authority ??= new ConcelierOptions.AuthorityOptions();
|
options.Authority ??= new ConcelierOptions.AuthorityOptions();
|
||||||
|
options.Features ??= new ConcelierOptions.FeaturesOptions();
|
||||||
|
|
||||||
var authority = options.Authority;
|
var authority = options.Authority;
|
||||||
if (string.IsNullOrWhiteSpace(authority.ClientSecret)
|
if (string.IsNullOrWhiteSpace(authority.ClientSecret)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -98,9 +99,36 @@ builder.Services.AddConcelierLinksetMappers();
|
|||||||
builder.Services.AddAdvisoryRawServices();
|
builder.Services.AddAdvisoryRawServices();
|
||||||
builder.Services.AddSingleton<IAdvisoryObservationQueryService, AdvisoryObservationQueryService>();
|
builder.Services.AddSingleton<IAdvisoryObservationQueryService, AdvisoryObservationQueryService>();
|
||||||
|
|
||||||
builder.Services.AddMergeModule(builder.Configuration);
|
var features = concelierOptions.Features ?? new ConcelierOptions.FeaturesOptions();
|
||||||
|
|
||||||
|
if (!features.NoMergeEnabled)
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0618, CONCELIER0001, CONCELIER0002 // Legacy merge service is intentionally supported behind a feature toggle.
|
||||||
|
builder.Services.AddMergeModule(builder.Configuration);
|
||||||
|
#pragma warning restore CS0618, CONCELIER0001, CONCELIER0002
|
||||||
|
}
|
||||||
|
|
||||||
builder.Services.AddJobScheduler();
|
builder.Services.AddJobScheduler();
|
||||||
builder.Services.AddBuiltInConcelierJobs();
|
builder.Services.AddBuiltInConcelierJobs();
|
||||||
|
builder.Services.PostConfigure<JobSchedulerOptions>(options =>
|
||||||
|
{
|
||||||
|
if (features.NoMergeEnabled)
|
||||||
|
{
|
||||||
|
options.Definitions.Remove("merge:reconcile");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (features.MergeJobAllowlist is { Count: > 0 })
|
||||||
|
{
|
||||||
|
var allowMergeJob = features.MergeJobAllowlist.Any(value =>
|
||||||
|
string.Equals(value, "merge:reconcile", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (!allowMergeJob)
|
||||||
|
{
|
||||||
|
options.Definitions.Remove("merge:reconcile");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
builder.Services.AddSingleton<OpenApiDiscoveryDocumentProvider>();
|
builder.Services.AddSingleton<OpenApiDiscoveryDocumentProvider>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<ServiceStatus>(sp => new ServiceStatus(sp.GetRequiredService<TimeProvider>()));
|
builder.Services.AddSingleton<ServiceStatus>(sp => new ServiceStatus(sp.GetRequiredService<TimeProvider>()));
|
||||||
@@ -183,7 +211,7 @@ if (authorityConfigured)
|
|||||||
builder.Services.AddAuthorization(options =>
|
builder.Services.AddAuthorization(options =>
|
||||||
{
|
{
|
||||||
options.AddStellaOpsScopePolicy(JobsPolicyName, concelierOptions.Authority.RequiredScopes.ToArray());
|
options.AddStellaOpsScopePolicy(JobsPolicyName, concelierOptions.Authority.RequiredScopes.ToArray());
|
||||||
options.AddStellaOpsScopePolicy(ObservationsPolicyName, StellaOpsScopes.VulnRead);
|
options.AddStellaOpsScopePolicy(ObservationsPolicyName, StellaOpsScopes.VulnView);
|
||||||
options.AddStellaOpsScopePolicy(AdvisoryIngestPolicyName, StellaOpsScopes.AdvisoryIngest);
|
options.AddStellaOpsScopePolicy(AdvisoryIngestPolicyName, StellaOpsScopes.AdvisoryIngest);
|
||||||
options.AddStellaOpsScopePolicy(AdvisoryReadPolicyName, StellaOpsScopes.AdvisoryRead);
|
options.AddStellaOpsScopePolicy(AdvisoryReadPolicyName, StellaOpsScopes.AdvisoryRead);
|
||||||
options.AddStellaOpsScopePolicy(AocVerifyPolicyName, StellaOpsScopes.AdvisoryRead, StellaOpsScopes.AocVerify);
|
options.AddStellaOpsScopePolicy(AocVerifyPolicyName, StellaOpsScopes.AdvisoryRead, StellaOpsScopes.AocVerify);
|
||||||
@@ -197,6 +225,11 @@ builder.Services.AddEndpointsApiExplorer();
|
|||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
if (features.NoMergeEnabled)
|
||||||
|
{
|
||||||
|
app.Logger.LogWarning("Legacy merge module disabled via concelier:features:noMergeEnabled; Link-Not-Merge mode active.");
|
||||||
|
}
|
||||||
|
|
||||||
var resolvedConcelierOptions = app.Services.GetRequiredService<IOptions<ConcelierOptions>>().Value;
|
var resolvedConcelierOptions = app.Services.GetRequiredService<IOptions<ConcelierOptions>>().Value;
|
||||||
var resolvedAuthority = resolvedConcelierOptions.Authority ?? new ConcelierOptions.AuthorityOptions();
|
var resolvedAuthority = resolvedConcelierOptions.Authority ?? new ConcelierOptions.AuthorityOptions();
|
||||||
authorityConfigured = resolvedAuthority.Enabled;
|
authorityConfigured = resolvedAuthority.Enabled;
|
||||||
|
|||||||
@@ -35,5 +35,8 @@
|
|||||||
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj" />
|
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj" />
|
||||||
<ProjectReference Include="../../Aoc/__Libraries/StellaOps.Aoc/StellaOps.Aoc.csproj" />
|
<ProjectReference Include="../../Aoc/__Libraries/StellaOps.Aoc/StellaOps.Aoc.csproj" />
|
||||||
<ProjectReference Include="../../Aoc/__Libraries/StellaOps.Aoc.AspNetCore/StellaOps.Aoc.AspNetCore.csproj" />
|
<ProjectReference Include="../../Aoc/__Libraries/StellaOps.Aoc.AspNetCore/StellaOps.Aoc.AspNetCore.csproj" />
|
||||||
|
<ProjectReference Include="../__Analyzers/StellaOps.Concelier.Analyzers/StellaOps.Concelier.Analyzers.csproj"
|
||||||
|
OutputItemType="Analyzer"
|
||||||
|
ReferenceOutputAssembly="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -183,6 +183,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.Storage
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.WebService.Tests", "__Tests\StellaOps.Concelier.WebService.Tests\StellaOps.Concelier.WebService.Tests.csproj", "{664A2577-6DA1-42DA-A213-3253017FA4BF}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.WebService.Tests", "__Tests\StellaOps.Concelier.WebService.Tests\StellaOps.Concelier.WebService.Tests.csproj", "{664A2577-6DA1-42DA-A213-3253017FA4BF}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Analyzers", "__Analyzers", "{176B5A8A-7857-3ECD-1128-3C721BC7F5C6}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.Analyzers", "__Analyzers\StellaOps.Concelier.Analyzers\StellaOps.Concelier.Analyzers.csproj", "{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -1249,6 +1253,18 @@ Global
|
|||||||
{664A2577-6DA1-42DA-A213-3253017FA4BF}.Release|x64.Build.0 = Release|Any CPU
|
{664A2577-6DA1-42DA-A213-3253017FA4BF}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{664A2577-6DA1-42DA-A213-3253017FA4BF}.Release|x86.ActiveCfg = Release|Any CPU
|
{664A2577-6DA1-42DA-A213-3253017FA4BF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{664A2577-6DA1-42DA-A213-3253017FA4BF}.Release|x86.Build.0 = Release|Any CPU
|
{664A2577-6DA1-42DA-A213-3253017FA4BF}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -1332,5 +1348,6 @@ Global
|
|||||||
{7B995CBB-3D20-4509-9300-EC012C18C4B4} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642}
|
{7B995CBB-3D20-4509-9300-EC012C18C4B4} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642}
|
||||||
{9006A5A2-01D8-4A70-AEA7-B7B1987C4A62} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642}
|
{9006A5A2-01D8-4A70-AEA7-B7B1987C4A62} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642}
|
||||||
{664A2577-6DA1-42DA-A213-3253017FA4BF} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642}
|
{664A2577-6DA1-42DA-A213-3253017FA4BF} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642}
|
||||||
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F} = {176B5A8A-7857-3ECD-1128-3C721BC7F5C6}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
; Shipped analyzer releases
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
## Release History
|
||||||
|
|
||||||
|
### Unreleased
|
||||||
|
|
||||||
|
#### New Rules
|
||||||
|
|
||||||
|
Rule ID | Title | Notes
|
||||||
|
--------|-------|------
|
||||||
|
CONCELIER0002 | Legacy merge pipeline is disabled | Flags usage of `AddMergeModule` and `AdvisoryMergeService`.
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Diagnostics;
|
||||||
|
using Microsoft.CodeAnalysis.Operations;
|
||||||
|
|
||||||
|
namespace StellaOps.Concelier.Analyzers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Analyzer that flags usages of the legacy merge service APIs.
|
||||||
|
/// </summary>
|
||||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||||
|
public sealed class NoMergeUsageAnalyzer : DiagnosticAnalyzer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Diagnostic identifier for legacy merge usage violations.
|
||||||
|
/// </summary>
|
||||||
|
public const string DiagnosticId = "CONCELIER0002";
|
||||||
|
|
||||||
|
private const string Category = "Usage";
|
||||||
|
private const string MergeExtensionType = "StellaOps.Concelier.Merge.MergeServiceCollectionExtensions";
|
||||||
|
private const string MergeServiceType = "StellaOps.Concelier.Merge.Services.AdvisoryMergeService";
|
||||||
|
|
||||||
|
private static readonly LocalizableString Title = "Legacy merge pipeline is disabled";
|
||||||
|
private static readonly LocalizableString MessageFormat = "Do not reference the legacy Concelier merge pipeline (type '{0}')";
|
||||||
|
private static readonly LocalizableString Description =
|
||||||
|
"The legacy Concelier merge service is deprecated under MERGE-LNM-21-002. "
|
||||||
|
+ "Switch to observation/linkset APIs or guard calls behind the concelier:features:noMergeEnabled toggle.";
|
||||||
|
|
||||||
|
private static readonly DiagnosticDescriptor Rule = new(
|
||||||
|
DiagnosticId,
|
||||||
|
Title,
|
||||||
|
MessageFormat,
|
||||||
|
Category,
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true,
|
||||||
|
description: Description,
|
||||||
|
helpLinkUri: "https://stella-ops.org/docs/migration/no-merge");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize(AnalysisContext context)
|
||||||
|
{
|
||||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||||
|
context.EnableConcurrentExecution();
|
||||||
|
|
||||||
|
context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation);
|
||||||
|
context.RegisterOperationAction(AnalyzeObjectCreation, OperationKind.ObjectCreation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AnalyzeInvocation(OperationAnalysisContext context)
|
||||||
|
{
|
||||||
|
if (context.Operation is not IInvocationOperation invocation)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetMethod = invocation.TargetMethod;
|
||||||
|
if (targetMethod is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SymbolEquals(targetMethod.ContainingType, MergeExtensionType))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(targetMethod.Name, "AddMergeModule", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAllowedAssembly(context.ContainingSymbol.ContainingAssembly))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReportDiagnostic(context, invocation.Syntax, $"{MergeExtensionType}.{targetMethod.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AnalyzeObjectCreation(OperationAnalysisContext context)
|
||||||
|
{
|
||||||
|
if (context.Operation is not IObjectCreationOperation creation)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var createdType = creation.Type;
|
||||||
|
if (createdType is null || !SymbolEquals(createdType, MergeServiceType))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAllowedAssembly(context.ContainingSymbol.ContainingAssembly))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReportDiagnostic(context, creation.Syntax, MergeServiceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SymbolEquals(ITypeSymbol? symbol, string fullName)
|
||||||
|
{
|
||||||
|
if (symbol is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var display = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
|
if (display.StartsWith("global::", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
display = display.Substring("global::".Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(display, fullName, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsAllowedAssembly(IAssemblySymbol? assemblySymbol)
|
||||||
|
{
|
||||||
|
if (assemblySymbol is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var assemblyName = assemblySymbol.Name;
|
||||||
|
if (string.IsNullOrWhiteSpace(assemblyName))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assemblyName.StartsWith("StellaOps.Concelier.Merge", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assemblyName.EndsWith(".Analyzers", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReportDiagnostic(OperationAnalysisContext context, SyntaxNode syntax, string targetName)
|
||||||
|
{
|
||||||
|
var diagnostic = Diagnostic.Create(Rule, syntax.GetLocation(), targetName);
|
||||||
|
context.ReportDiagnostic(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<AssemblyName>StellaOps.Concelier.Analyzers</AssemblyName>
|
||||||
|
<RootNamespace>StellaOps.Concelier.Analyzers</RootNamespace>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -114,10 +114,10 @@ internal sealed class AdvisoryObservationFactory : IAdvisoryObservationFactory
|
|||||||
|
|
||||||
private static AdvisoryObservationLinkset CreateLinkset(RawIdentifiers identifiers, RawLinkset linkset)
|
private static AdvisoryObservationLinkset CreateLinkset(RawIdentifiers identifiers, RawLinkset linkset)
|
||||||
{
|
{
|
||||||
var aliases = NormalizeAliases(identifiers, linkset);
|
var aliases = CollectAliases(identifiers, linkset);
|
||||||
var purls = NormalizePackageUrls(linkset.PackageUrls);
|
var purls = CollectValues(linkset.PackageUrls);
|
||||||
var cpes = NormalizeCpes(linkset.Cpes);
|
var cpes = CollectValues(linkset.Cpes);
|
||||||
var references = NormalizeReferences(linkset.References);
|
var references = CollectReferences(linkset.References);
|
||||||
|
|
||||||
return new AdvisoryObservationLinkset(aliases, purls, cpes, references);
|
return new AdvisoryObservationLinkset(aliases, purls, cpes, references);
|
||||||
}
|
}
|
||||||
@@ -170,123 +170,90 @@ internal sealed class AdvisoryObservationFactory : IAdvisoryObservationFactory
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> NormalizeAliases(RawIdentifiers identifiers, RawLinkset linkset)
|
private static IEnumerable<string> CollectAliases(RawIdentifiers identifiers, RawLinkset linkset)
|
||||||
{
|
{
|
||||||
var aliases = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var results = new List<string>();
|
||||||
|
|
||||||
if (LinksetNormalization.TryNormalizeAlias(identifiers.PrimaryId, out var primary))
|
AddAlias(results, identifiers.PrimaryId);
|
||||||
{
|
AddRange(results, identifiers.Aliases);
|
||||||
aliases.Add(primary);
|
AddRange(results, linkset.Aliases);
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var alias in identifiers.Aliases)
|
|
||||||
{
|
|
||||||
if (LinksetNormalization.TryNormalizeAlias(alias, out var normalized))
|
|
||||||
{
|
|
||||||
aliases.Add(normalized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var alias in linkset.Aliases)
|
|
||||||
{
|
|
||||||
if (LinksetNormalization.TryNormalizeAlias(alias, out var normalized))
|
|
||||||
{
|
|
||||||
aliases.Add(normalized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var note in linkset.Notes)
|
foreach (var note in linkset.Notes)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(note.Value)
|
if (string.IsNullOrWhiteSpace(note.Value))
|
||||||
&& LinksetNormalization.TryNormalizeAlias(note.Value, out var normalized))
|
|
||||||
{
|
|
||||||
aliases.Add(normalized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return aliases
|
|
||||||
.OrderBy(static value => value, StringComparer.OrdinalIgnoreCase)
|
|
||||||
.ToImmutableArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<string> NormalizePackageUrls(ImmutableArray<string> packageUrls)
|
|
||||||
{
|
|
||||||
if (packageUrls.IsDefaultOrEmpty)
|
|
||||||
{
|
|
||||||
return ImmutableArray<string>.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var set = new HashSet<string>(StringComparer.Ordinal);
|
|
||||||
|
|
||||||
foreach (var candidate in packageUrls)
|
|
||||||
{
|
|
||||||
if (!LinksetNormalization.TryNormalizePackageUrl(candidate, out var normalized) || string.IsNullOrEmpty(normalized))
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
set.Add(normalized);
|
results.Add(note.Value.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
return set
|
return results;
|
||||||
.OrderBy(static value => value, StringComparer.Ordinal)
|
|
||||||
.ToImmutableArray();
|
static void AddAlias(ICollection<string> target, string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.Add(value.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddRange(ICollection<string> target, ImmutableArray<string> values)
|
||||||
|
{
|
||||||
|
if (values.IsDefaultOrEmpty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
AddAlias(target, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> NormalizeCpes(ImmutableArray<string> cpes)
|
private static IEnumerable<string> CollectValues(ImmutableArray<string> values)
|
||||||
{
|
{
|
||||||
if (cpes.IsDefaultOrEmpty)
|
if (values.IsDefaultOrEmpty)
|
||||||
{
|
{
|
||||||
return ImmutableArray<string>.Empty;
|
return ImmutableArray<string>.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var set = new HashSet<string>(StringComparer.Ordinal);
|
var list = new List<string>(values.Length);
|
||||||
|
foreach (var value in values)
|
||||||
foreach (var cpe in cpes)
|
|
||||||
{
|
{
|
||||||
if (!LinksetNormalization.TryNormalizeCpe(cpe, out var normalized) || string.IsNullOrEmpty(normalized))
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
set.Add(normalized);
|
list.Add(value.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
return set
|
return list;
|
||||||
.OrderBy(static value => value, StringComparer.Ordinal)
|
|
||||||
.ToImmutableArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<AdvisoryObservationReference> NormalizeReferences(ImmutableArray<RawReference> references)
|
private static IEnumerable<AdvisoryObservationReference> CollectReferences(ImmutableArray<RawReference> references)
|
||||||
{
|
{
|
||||||
if (references.IsDefaultOrEmpty)
|
if (references.IsDefaultOrEmpty)
|
||||||
{
|
{
|
||||||
return ImmutableArray<AdvisoryObservationReference>.Empty;
|
return ImmutableArray<AdvisoryObservationReference>.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var list = new List<AdvisoryObservationReference>(references.Length);
|
||||||
var list = new List<AdvisoryObservationReference>();
|
|
||||||
|
|
||||||
foreach (var reference in references)
|
foreach (var reference in references)
|
||||||
{
|
{
|
||||||
var normalized = LinksetNormalization.TryCreateReference(reference.Type, reference.Url);
|
if (string.IsNullOrWhiteSpace(reference.Type) || string.IsNullOrWhiteSpace(reference.Url))
|
||||||
if (normalized is null)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!seen.Add(normalized.Url))
|
list.Add(new AdvisoryObservationReference(reference.Type.Trim(), reference.Url.Trim()));
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.Add(normalized);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return list
|
return list;
|
||||||
.OrderBy(static reference => reference.Type, StringComparer.Ordinal)
|
|
||||||
.ThenBy(static reference => reference.Url, StringComparer.Ordinal)
|
|
||||||
.ToImmutableArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableDictionary<string, string> CreateAttributes(AdvisoryRawDocument rawDocument)
|
private static ImmutableDictionary<string, string> CreateAttributes(AdvisoryRawDocument rawDocument)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
> Docs alignment (2025-10-26): Linkset expectations detailed in AOC reference §4 and policy-engine architecture §2.1.
|
> Docs alignment (2025-10-26): Linkset expectations detailed in AOC reference §4 and policy-engine architecture §2.1.
|
||||||
> 2025-10-28: Advisory raw ingestion now strips client-supplied supersedes hints, logs ignored pointers, and surfaces repository-supplied supersedes identifiers; service tests cover duplicate handling and append-only semantics.
|
> 2025-10-28: Advisory raw ingestion now strips client-supplied supersedes hints, logs ignored pointers, and surfaces repository-supplied supersedes identifiers; service tests cover duplicate handling and append-only semantics.
|
||||||
> Docs alignment (2025-10-26): Deployment guide + observability guide describe supersedes metrics; ensure implementation emits `aoc_violation_total` on failure.
|
> Docs alignment (2025-10-26): Deployment guide + observability guide describe supersedes metrics; ensure implementation emits `aoc_violation_total` on failure.
|
||||||
| CONCELIER-CORE-AOC-19-004 `Remove ingestion normalization` | DOING (2025-10-28) | Concelier Core Guild | CONCELIER-CORE-AOC-19-002, POLICY-AOC-19-003 | Strip normalization/dedup/severity logic from ingestion pipelines, delegate derived computations to Policy Engine, and update exporters/tests to consume raw documents only.<br>2025-10-29 19:05Z: Audit completed for `AdvisoryRawService`/Mongo repo to confirm alias order/dedup removal persists; identified remaining normalization in observation/linkset factory that will be revised to surface raw duplicates for Policy ingestion. Change sketch + regression matrix drafted under `docs/dev/aoc-normalization-removal-notes.md` (pending commit).<br>2025-10-31 20:45Z: Added raw linkset projection to observations/storage, exposing canonical+raw views, refreshed fixtures/tests, and documented behaviour in models/doc factory.<br>2025-10-31 21:10Z: Coordinated with Policy Engine (POLICY-ENGINE-20-003) on adoption timeline; backfill + consumer readiness tracked in `docs/dev/raw-linkset-backfill-plan.md`. |
|
| CONCELIER-CORE-AOC-19-004 `Remove ingestion normalization` | DOING (2025-10-28) | Concelier Core Guild | CONCELIER-CORE-AOC-19-002, POLICY-AOC-19-003 | Strip normalization/dedup/severity logic from ingestion pipelines, delegate derived computations to Policy Engine, and update exporters/tests to consume raw documents only.<br>2025-10-29 19:05Z: Audit completed for `AdvisoryRawService`/Mongo repo to confirm alias order/dedup removal persists; identified remaining normalization in observation/linkset factory that will be revised to surface raw duplicates for Policy ingestion. Change sketch + regression matrix drafted under `docs/dev/aoc-normalization-removal-notes.md` (pending commit).<br>2025-10-31 20:45Z: Added raw linkset projection to observations/storage, exposing canonical+raw views, refreshed fixtures/tests, and documented behaviour in models/doc factory.<br>2025-10-31 21:10Z: Coordinated with Policy Engine (POLICY-ENGINE-20-003) on adoption timeline; backfill + consumer readiness tracked in `docs/dev/raw-linkset-backfill-plan.md`.<br>2025-11-05 14:25Z: Resuming to document merge-dependent normalization paths and prepare implementation notes for `noMergeEnabled` gating before code changes land.<br>2025-11-05 19:20Z: Observation factory/linkset now preserve upstream ordering + duplicates; canonicalisation responsibility shifts to downstream consumers with refreshed unit coverage.<br>2025-11-06 16:10Z: Updated AOC reference/backfill docs with raw vs canonical guidance and cross-linked analyzer guardrails. |
|
||||||
> Docs alignment (2025-10-26): Architecture overview emphasises policy-only derivation; coordinate with Policy Engine guild for rollout.
|
> Docs alignment (2025-10-26): Architecture overview emphasises policy-only derivation; coordinate with Policy Engine guild for rollout.
|
||||||
> 2025-10-29: `AdvisoryRawService` now preserves upstream alias/linkset ordering (trim-only) and updated AOC documentation reflects the behaviour; follow-up to ensure policy consumers handle duplicates remains open.
|
> 2025-10-29: `AdvisoryRawService` now preserves upstream alias/linkset ordering (trim-only) and updated AOC documentation reflects the behaviour; follow-up to ensure policy consumers handle duplicates remains open.
|
||||||
| CONCELIER-CORE-AOC-19-013 `Authority tenant scope smoke coverage` | TODO | Concelier Core Guild | AUTH-AOC-19-002 | Extend Concelier smoke/e2e fixtures to configure `requiredTenants` and assert cross-tenant rejection with updated Authority tokens. | Coordinate deliverable so Authority docs (`AUTH-AOC-19-003`) can close once tests are in place. |
|
| CONCELIER-CORE-AOC-19-013 `Authority tenant scope smoke coverage` | TODO | Concelier Core Guild | AUTH-AOC-19-002 | Extend Concelier smoke/e2e fixtures to configure `requiredTenants` and assert cross-tenant rejection with updated Authority tokens. | Coordinate deliverable so Authority docs (`AUTH-AOC-19-003`) can close once tests are in place. |
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using StellaOps.Concelier.Merge.Services;
|
|||||||
|
|
||||||
namespace StellaOps.Concelier.Merge.Jobs;
|
namespace StellaOps.Concelier.Merge.Jobs;
|
||||||
|
|
||||||
|
[Obsolete("MergeReconcileJob is deprecated; Link-Not-Merge supersedes merge scheduling. Disable via concelier:features:noMergeEnabled. Tracking MERGE-LNM-21-002.", DiagnosticId = "CONCELIER0001", UrlFormat = "https://stella-ops.org/docs/migration/no-merge")]
|
||||||
public sealed class MergeReconcileJob : IJob
|
public sealed class MergeReconcileJob : IJob
|
||||||
{
|
{
|
||||||
private readonly AdvisoryMergeService _mergeService;
|
private readonly AdvisoryMergeService _mergeService;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
@@ -9,6 +10,7 @@ using StellaOps.Concelier.Merge.Services;
|
|||||||
|
|
||||||
namespace StellaOps.Concelier.Merge;
|
namespace StellaOps.Concelier.Merge;
|
||||||
|
|
||||||
|
[Obsolete("Legacy merge module is deprecated; prefer Link-Not-Merge linkset pipelines. Track MERGE-LNM-21-002 and set concelier:features:noMergeEnabled=true to disable registration.", DiagnosticId = "CONCELIER0001", UrlFormat = "https://stella-ops.org/docs/migration/no-merge")]
|
||||||
public static class MergeServiceCollectionExtensions
|
public static class MergeServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddMergeModule(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddMergeModule(this IServiceCollection services, IConfiguration configuration)
|
||||||
@@ -34,9 +36,11 @@ public static class MergeServiceCollectionExtensions
|
|||||||
return new AdvisoryPrecedenceMerger(resolver, options, timeProvider, logger);
|
return new AdvisoryPrecedenceMerger(resolver, options, timeProvider, logger);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#pragma warning disable CS0618 // Legacy merge services are marked obsolete.
|
||||||
services.TryAddSingleton<MergeEventWriter>();
|
services.TryAddSingleton<MergeEventWriter>();
|
||||||
services.TryAddSingleton<AdvisoryMergeService>();
|
services.TryAddSingleton<AdvisoryMergeService>();
|
||||||
services.AddTransient<MergeReconcileJob>();
|
services.AddTransient<MergeReconcileJob>();
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using System.Text.Json;
|
|||||||
|
|
||||||
namespace StellaOps.Concelier.Merge.Services;
|
namespace StellaOps.Concelier.Merge.Services;
|
||||||
|
|
||||||
|
[Obsolete("AdvisoryMergeService is deprecated. Transition callers to Link-Not-Merge observation/linkset APIs (MERGE-LNM-21-002) and enable concelier:features:noMergeEnabled when ready.", DiagnosticId = "CONCELIER0001", UrlFormat = "https://stella-ops.org/docs/migration/no-merge")]
|
||||||
public sealed class AdvisoryMergeService
|
public sealed class AdvisoryMergeService
|
||||||
{
|
{
|
||||||
private static readonly Meter MergeMeter = new("StellaOps.Concelier.Merge");
|
private static readonly Meter MergeMeter = new("StellaOps.Concelier.Merge");
|
||||||
|
|||||||
@@ -10,6 +10,6 @@
|
|||||||
| Task | Owner(s) | Depends on | Notes |
|
| Task | Owner(s) | Depends on | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
|MERGE-LNM-21-001 Migration plan authoring|BE-Merge, Architecture Guild|CONCELIER-LNM-21-101|**DONE (2025-11-03)** – Authored `docs/migration/no-merge.md` with rollout phases, backfill/validation checklists, rollback guidance, and ownership matrix for the Link-Not-Merge cutover.|
|
|MERGE-LNM-21-001 Migration plan authoring|BE-Merge, Architecture Guild|CONCELIER-LNM-21-101|**DONE (2025-11-03)** – Authored `docs/migration/no-merge.md` with rollout phases, backfill/validation checklists, rollback guidance, and ownership matrix for the Link-Not-Merge cutover.|
|
||||||
|MERGE-LNM-21-002 Merge service deprecation|BE-Merge|MERGE-LNM-21-001|**DOING (2025-11-03)** – Auditing service registrations, DI bindings, and tests consuming `AdvisoryMergeService`; drafting deprecation plan and analyzer scope prior to code removal.|
|
|MERGE-LNM-21-002 Merge service deprecation|BE-Merge|MERGE-LNM-21-001|**DOING (2025-11-03)** – Auditing service registrations, DI bindings, and tests consuming `AdvisoryMergeService`; drafting deprecation plan and analyzer scope prior to code removal.<br>2025-11-05 14:42Z: Implementing `concelier:features:noMergeEnabled` gate, merge job allowlist checks, `[Obsolete]` markings, and analyzer scaffolding to steer consumers toward linkset APIs.<br>2025-11-06 16:10Z: Introduced Roslyn analyzer (`CONCELIER0002`) referenced by Concelier WebService + tests, documented suppression guidance, and updated migration playbook.|
|
||||||
> 2025-11-03: Catalogued call sites (WebService Program `AddMergeModule`, built-in job registration `merge:reconcile`, `MergeReconcileJob`) and confirmed unit tests are the only direct `MergeAsync` callers; next step is to define analyzer + replacement observability coverage.
|
> 2025-11-03: Catalogued call sites (WebService Program `AddMergeModule`, built-in job registration `merge:reconcile`, `MergeReconcileJob`) and confirmed unit tests are the only direct `MergeAsync` callers; next step is to define analyzer + replacement observability coverage.
|
||||||
|MERGE-LNM-21-003 Determinism/test updates|QA Guild, BE-Merge|MERGE-LNM-21-002|Replace merge determinism suites with observation/linkset regression tests verifying no data mutation and conflicts remain visible.|
|
|MERGE-LNM-21-003 Determinism/test updates|QA Guild, BE-Merge|MERGE-LNM-21-002|Replace merge determinism suites with observation/linkset regression tests verifying no data mutation and conflicts remain visible.|
|
||||||
|
|||||||
@@ -280,10 +280,10 @@ public sealed record AdvisoryObservationLinkset
|
|||||||
IEnumerable<string>? cpes,
|
IEnumerable<string>? cpes,
|
||||||
IEnumerable<AdvisoryObservationReference>? references)
|
IEnumerable<AdvisoryObservationReference>? references)
|
||||||
{
|
{
|
||||||
Aliases = NormalizeStringSet(aliases, toLower: true);
|
Aliases = ToImmutableArray(aliases);
|
||||||
Purls = NormalizeStringSet(purls);
|
Purls = ToImmutableArray(purls);
|
||||||
Cpes = NormalizeStringSet(cpes);
|
Cpes = ToImmutableArray(cpes);
|
||||||
References = NormalizeReferences(references);
|
References = ToImmutableReferences(references);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImmutableArray<string> Aliases { get; }
|
public ImmutableArray<string> Aliases { get; }
|
||||||
@@ -294,14 +294,14 @@ public sealed record AdvisoryObservationLinkset
|
|||||||
|
|
||||||
public ImmutableArray<AdvisoryObservationReference> References { get; }
|
public ImmutableArray<AdvisoryObservationReference> References { get; }
|
||||||
|
|
||||||
private static ImmutableArray<string> NormalizeStringSet(IEnumerable<string>? values, bool toLower = false)
|
private static ImmutableArray<string> ToImmutableArray(IEnumerable<string>? values)
|
||||||
{
|
{
|
||||||
if (values is null)
|
if (values is null)
|
||||||
{
|
{
|
||||||
return ImmutableArray<string>.Empty;
|
return ImmutableArray<string>.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<string>();
|
var builder = ImmutableArray.CreateBuilder<string>();
|
||||||
foreach (var value in values)
|
foreach (var value in values)
|
||||||
{
|
{
|
||||||
var trimmed = Validation.TrimToNull(value);
|
var trimmed = Validation.TrimToNull(value);
|
||||||
@@ -310,27 +310,30 @@ public sealed record AdvisoryObservationLinkset
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Add(toLower ? trimmed.ToLowerInvariant() : trimmed);
|
builder.Add(trimmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list
|
return builder.Count == 0 ? ImmutableArray<string>.Empty : builder.ToImmutable();
|
||||||
.Distinct(StringComparer.Ordinal)
|
|
||||||
.OrderBy(static v => v, StringComparer.Ordinal)
|
|
||||||
.ToImmutableArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableArray<AdvisoryObservationReference> NormalizeReferences(IEnumerable<AdvisoryObservationReference>? references)
|
private static ImmutableArray<AdvisoryObservationReference> ToImmutableReferences(IEnumerable<AdvisoryObservationReference>? references)
|
||||||
{
|
{
|
||||||
if (references is null)
|
if (references is null)
|
||||||
{
|
{
|
||||||
return ImmutableArray<AdvisoryObservationReference>.Empty;
|
return ImmutableArray<AdvisoryObservationReference>.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
return references
|
var builder = ImmutableArray.CreateBuilder<AdvisoryObservationReference>();
|
||||||
.Where(static reference => reference is not null)
|
foreach (var reference in references)
|
||||||
.Distinct()
|
{
|
||||||
.OrderBy(static reference => reference.Type, StringComparer.Ordinal)
|
if (reference is null)
|
||||||
.ThenBy(static reference => reference.Url, StringComparer.Ordinal)
|
{
|
||||||
.ToImmutableArray();
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Add(reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Count == 0 ? ImmutableArray<AdvisoryObservationReference>.Empty : builder.ToImmutable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public sealed class AdvisoryObservationFactoryTests
|
|||||||
private static readonly DateTimeOffset SampleTimestamp = DateTimeOffset.Parse("2025-10-26T12:34:56Z");
|
private static readonly DateTimeOffset SampleTimestamp = DateTimeOffset.Parse("2025-10-26T12:34:56Z");
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Create_NormalizesLinksetIdentifiersAndReferences()
|
public void Create_PreservesLinksetOrderAndDuplicates()
|
||||||
{
|
{
|
||||||
var factory = new AdvisoryObservationFactory();
|
var factory = new AdvisoryObservationFactory();
|
||||||
var rawDocument = BuildRawDocument(
|
var rawDocument = BuildRawDocument(
|
||||||
@@ -33,12 +33,23 @@ public sealed class AdvisoryObservationFactoryTests
|
|||||||
var observation = factory.Create(rawDocument, SampleTimestamp);
|
var observation = factory.Create(rawDocument, SampleTimestamp);
|
||||||
|
|
||||||
Assert.Equal(SampleTimestamp, observation.CreatedAt);
|
Assert.Equal(SampleTimestamp, observation.CreatedAt);
|
||||||
Assert.Equal(new[] { "cve-2025-0001", "ghsa-xxxx-yyyy" }, observation.Linkset.Aliases);
|
Assert.Equal(
|
||||||
Assert.Equal(new[] { "pkg:npm/left-pad@1.0.0" }, observation.Linkset.Purls);
|
new[] { "GHSA-XXXX-YYYY", "CVE-2025-0001", "ghsa-XXXX-YYYY", "CVE-2025-0001" },
|
||||||
Assert.Equal(new[] { "cpe:2.3:a:example:product:1.0:*:*:*:*:*:*:*" }, observation.Linkset.Cpes);
|
observation.Linkset.Aliases);
|
||||||
var reference = Assert.Single(observation.Linkset.References);
|
Assert.Equal(
|
||||||
Assert.Equal("advisory", reference.Type);
|
new[] { "pkg:NPM/left-pad@1.0.0", "pkg:npm/left-pad@1.0.0?foo=bar" },
|
||||||
Assert.Equal("https://example.test/advisory", reference.Url);
|
observation.Linkset.Purls);
|
||||||
|
Assert.Equal(
|
||||||
|
new[] { "cpe:/a:Example:Product:1.0", "cpe:/a:example:product:1.0" },
|
||||||
|
observation.Linkset.Cpes);
|
||||||
|
Assert.Equal(2, observation.Linkset.References.Length);
|
||||||
|
Assert.All(
|
||||||
|
observation.Linkset.References,
|
||||||
|
reference =>
|
||||||
|
{
|
||||||
|
Assert.Equal("advisory", reference.Type);
|
||||||
|
Assert.Equal("https://example.test/advisory", reference.Url);
|
||||||
|
});
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
new[] { "GHSA-XXXX-YYYY", " CVE-2025-0001 ", "ghsa-XXXX-YYYY", " CVE-2025-0001 " },
|
new[] { "GHSA-XXXX-YYYY", " CVE-2025-0001 ", "ghsa-XXXX-YYYY", " CVE-2025-0001 " },
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
@@ -53,7 +54,7 @@ public sealed class AdvisoryObservationQueryServiceTests
|
|||||||
Assert.Equal("tenant-a:ghsa:alpha:1", result.Observations[1].ObservationId);
|
Assert.Equal("tenant-a:ghsa:alpha:1", result.Observations[1].ObservationId);
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
new[] { "cve-2025-0001", "cve-2025-0002", "ghsa-xyzz" },
|
new[] { "CVE-2025-0001", "CVE-2025-0002", "GHSA-xyzz" },
|
||||||
result.Linkset.Aliases);
|
result.Linkset.Aliases);
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
@@ -104,7 +105,10 @@ public sealed class AdvisoryObservationQueryServiceTests
|
|||||||
|
|
||||||
Assert.Equal(2, result.Observations.Length);
|
Assert.Equal(2, result.Observations.Length);
|
||||||
Assert.All(result.Observations, observation =>
|
Assert.All(result.Observations, observation =>
|
||||||
Assert.Contains(observation.Linkset.Aliases, alias => alias is "cve-2025-0001" or "cve-2025-9999"));
|
Assert.Contains(
|
||||||
|
observation.Linkset.Aliases,
|
||||||
|
alias => alias.Equals("CVE-2025-0001", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| alias.Equals("CVE-2025-9999", StringComparison.OrdinalIgnoreCase)));
|
||||||
|
|
||||||
Assert.False(result.HasMore);
|
Assert.False(result.HasMore);
|
||||||
Assert.Null(result.NextCursor);
|
Assert.Null(result.NextCursor);
|
||||||
|
|||||||
@@ -10,5 +10,8 @@
|
|||||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
|
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
|
||||||
<ProjectReference Include="../../StellaOps.Concelier.WebService/StellaOps.Concelier.WebService.csproj" />
|
<ProjectReference Include="../../StellaOps.Concelier.WebService/StellaOps.Concelier.WebService.csproj" />
|
||||||
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
|
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
|
||||||
|
<ProjectReference Include="../../__Analyzers/StellaOps.Concelier.Analyzers/StellaOps.Concelier.Analyzers.csproj"
|
||||||
|
OutputItemType="Analyzer"
|
||||||
|
ReferenceOutputAssembly="false" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -221,7 +221,7 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
|||||||
Assert.NotNull(ingestResponse.Headers.Location);
|
Assert.NotNull(ingestResponse.Headers.Location);
|
||||||
var locationValue = ingestResponse.Headers.Location!.ToString();
|
var locationValue = ingestResponse.Headers.Location!.ToString();
|
||||||
Assert.False(string.IsNullOrWhiteSpace(locationValue));
|
Assert.False(string.IsNullOrWhiteSpace(locationValue));
|
||||||
var lastSlashIndex = locationValue.LastIndexOf('/', StringComparison.Ordinal);
|
var lastSlashIndex = locationValue.LastIndexOf('/');
|
||||||
var idSegment = lastSlashIndex >= 0
|
var idSegment = lastSlashIndex >= 0
|
||||||
? locationValue[(lastSlashIndex + 1)..]
|
? locationValue[(lastSlashIndex + 1)..]
|
||||||
: locationValue;
|
: locationValue;
|
||||||
@@ -890,6 +890,52 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
|
|||||||
Assert.True(limitedResponse.Headers.RetryAfter!.Delta!.Value.TotalSeconds > 0);
|
Assert.True(limitedResponse.Headers.RetryAfter!.Delta!.Value.TotalSeconds > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MergeModuleDisabledWhenFeatureFlagEnabled()
|
||||||
|
{
|
||||||
|
var environment = new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
["CONCELIER_FEATURES__NOMERGEENABLED"] = "true"
|
||||||
|
};
|
||||||
|
|
||||||
|
using var factory = new ConcelierApplicationFactory(
|
||||||
|
_runner.ConnectionString,
|
||||||
|
authorityConfigure: null,
|
||||||
|
environmentOverrides: environment);
|
||||||
|
using var scope = factory.Services.CreateScope();
|
||||||
|
var provider = scope.ServiceProvider;
|
||||||
|
|
||||||
|
#pragma warning disable CS0618, CONCELIER0001, CONCELIER0002 // Checking deprecated service registration state.
|
||||||
|
Assert.Null(provider.GetService<AdvisoryMergeService>());
|
||||||
|
#pragma warning restore CS0618, CONCELIER0001, CONCELIER0002
|
||||||
|
|
||||||
|
var schedulerOptions = provider.GetRequiredService<IOptions<JobSchedulerOptions>>().Value;
|
||||||
|
Assert.DoesNotContain("merge:reconcile", schedulerOptions.Definitions.Keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MergeJobRemainsWhenAllowlisted()
|
||||||
|
{
|
||||||
|
var environment = new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
["CONCELIER_FEATURES__MERGEJOBALLOWLIST__0"] = "merge:reconcile"
|
||||||
|
};
|
||||||
|
|
||||||
|
using var factory = new ConcelierApplicationFactory(
|
||||||
|
_runner.ConnectionString,
|
||||||
|
authorityConfigure: null,
|
||||||
|
environmentOverrides: environment);
|
||||||
|
using var scope = factory.Services.CreateScope();
|
||||||
|
var provider = scope.ServiceProvider;
|
||||||
|
|
||||||
|
#pragma warning disable CS0618, CONCELIER0001, CONCELIER0002 // Checking deprecated service registration state.
|
||||||
|
Assert.NotNull(provider.GetService<AdvisoryMergeService>());
|
||||||
|
#pragma warning restore CS0618, CONCELIER0001, CONCELIER0002
|
||||||
|
|
||||||
|
var schedulerOptions = provider.GetRequiredService<IOptions<JobSchedulerOptions>>().Value;
|
||||||
|
Assert.Contains("merge:reconcile", schedulerOptions.Definitions.Keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task JobsEndpointsAllowBypassWhenAuthorityEnabled()
|
public async Task JobsEndpointsAllowBypassWhenAuthorityEnabled()
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ using StellaOps.Excititor.Policy;
|
|||||||
using StellaOps.Excititor.Storage.Mongo;
|
using StellaOps.Excititor.Storage.Mongo;
|
||||||
using StellaOps.Excititor.WebService.Endpoints;
|
using StellaOps.Excititor.WebService.Endpoints;
|
||||||
using StellaOps.Excititor.WebService.Services;
|
using StellaOps.Excititor.WebService.Services;
|
||||||
using StellaOps.Excititor.Core;
|
|
||||||
using StellaOps.Excititor.Core.Aoc;
|
using StellaOps.Excititor.Core.Aoc;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|||||||
@@ -85,9 +85,12 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
VexSignatureMetadata? signatureMetadata = null;
|
VexSignatureMetadata? signatureMetadata = null;
|
||||||
|
VexAttestationDiagnostics? attestationDiagnostics = null;
|
||||||
if (document.Format == VexDocumentFormat.OciAttestation && _attestationVerifier is not null)
|
if (document.Format == VexDocumentFormat.OciAttestation && _attestationVerifier is not null)
|
||||||
{
|
{
|
||||||
signatureMetadata = await VerifyAttestationAsync(document, metadata, cancellationToken).ConfigureAwait(false);
|
var attestationResult = await VerifyAttestationAsync(document, metadata, cancellationToken).ConfigureAwait(false);
|
||||||
|
signatureMetadata = attestationResult.Metadata;
|
||||||
|
attestationDiagnostics = attestationResult.Diagnostics;
|
||||||
}
|
}
|
||||||
|
|
||||||
signatureMetadata ??= ExtractSignatureMetadata(metadata);
|
signatureMetadata ??= ExtractSignatureMetadata(metadata);
|
||||||
@@ -96,7 +99,15 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
|||||||
signatureMetadata = await AttachIssuerTrustAsync(signatureMetadata, metadata, cancellationToken).ConfigureAwait(false);
|
signatureMetadata = await AttachIssuerTrustAsync(signatureMetadata, metadata, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
var resultLabel = signatureMetadata is null ? "skipped" : "ok";
|
var resultLabel = signatureMetadata is null ? "skipped" : "ok";
|
||||||
RecordVerification(document.ProviderId, metadata, resultLabel);
|
if (attestationDiagnostics is not null)
|
||||||
|
{
|
||||||
|
resultLabel = attestationDiagnostics.Result ?? resultLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attestationDiagnostics is null)
|
||||||
|
{
|
||||||
|
RecordVerification(document.ProviderId, metadata, resultLabel);
|
||||||
|
}
|
||||||
|
|
||||||
if (resultLabel == "skipped")
|
if (resultLabel == "skipped")
|
||||||
{
|
{
|
||||||
@@ -107,17 +118,18 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Signature metadata recorded for provider {ProviderId} (type={SignatureType}, subject={Subject}, issuer={Issuer}).",
|
"Signature metadata recorded for provider {ProviderId} (type={SignatureType}, subject={Subject}, issuer={Issuer}, result={Result}).",
|
||||||
document.ProviderId,
|
document.ProviderId,
|
||||||
signatureMetadata!.Type,
|
signatureMetadata!.Type,
|
||||||
signatureMetadata.Subject ?? "<unknown>",
|
signatureMetadata.Subject ?? "<unknown>",
|
||||||
signatureMetadata.Issuer ?? "<unknown>");
|
signatureMetadata.Issuer ?? "<unknown>",
|
||||||
|
resultLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
return signatureMetadata;
|
return signatureMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask<VexSignatureMetadata?> VerifyAttestationAsync(
|
private async ValueTask<(VexSignatureMetadata Metadata, VexAttestationDiagnostics Diagnostics)> VerifyAttestationAsync(
|
||||||
VexRawDocument document,
|
VexRawDocument document,
|
||||||
ImmutableDictionary<string, string> metadata,
|
ImmutableDictionary<string, string> metadata,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
@@ -149,31 +161,42 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
|||||||
var verification = await _attestationVerifier!
|
var verification = await _attestationVerifier!
|
||||||
.VerifyAsync(verificationRequest, cancellationToken)
|
.VerifyAsync(verificationRequest, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
var diagnosticsSnapshot = verification.Diagnostics;
|
||||||
|
|
||||||
if (!verification.IsValid)
|
if (!verification.IsValid)
|
||||||
{
|
{
|
||||||
var diagnostics = string.Join(", ", verification.Diagnostics.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
var failureReason = diagnosticsSnapshot.FailureReason ?? "verification_failed";
|
||||||
|
var resultTag = diagnosticsSnapshot.Result ?? "invalid";
|
||||||
|
|
||||||
|
RecordVerification(document.ProviderId, metadata, resultTag);
|
||||||
_logger.LogError(
|
_logger.LogError(
|
||||||
"Attestation verification failed for provider {ProviderId} (uri={SourceUri}) diagnostics={Diagnostics}",
|
"Attestation verification failed for provider {ProviderId} (uri={SourceUri}) result={Result} failure={FailureReason} diagnostics={@Diagnostics}",
|
||||||
document.ProviderId,
|
document.ProviderId,
|
||||||
document.SourceUri,
|
document.SourceUri,
|
||||||
diagnostics);
|
resultTag,
|
||||||
|
failureReason,
|
||||||
|
diagnosticsSnapshot);
|
||||||
|
|
||||||
var violation = AocViolation.Create(
|
var violation = AocViolation.Create(
|
||||||
AocViolationCode.SignatureInvalid,
|
AocViolationCode.SignatureInvalid,
|
||||||
"/upstream/signature",
|
"/upstream/signature",
|
||||||
"Attestation verification failed.");
|
"Attestation verification failed.");
|
||||||
|
|
||||||
RecordVerification(document.ProviderId, metadata, "fail");
|
|
||||||
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
|
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var successResult = diagnosticsSnapshot.Result ?? "valid";
|
||||||
|
RecordVerification(document.ProviderId, metadata, successResult);
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Attestation verification succeeded for provider {ProviderId} (predicate={PredicateType}, subject={Subject}).",
|
"Attestation verification succeeded for provider {ProviderId} (predicate={PredicateType}, subject={Subject}, result={Result}).",
|
||||||
document.ProviderId,
|
document.ProviderId,
|
||||||
attestationMetadata.PredicateType,
|
attestationMetadata.PredicateType,
|
||||||
statement.Subject[0].Name ?? "<unknown>");
|
statement.Subject[0].Name ?? "<unknown>",
|
||||||
|
successResult);
|
||||||
|
|
||||||
return BuildSignatureMetadata(statement, metadata, attestationMetadata, verification.Diagnostics);
|
var signatureMetadata = BuildSignatureMetadata(statement, metadata, attestationMetadata, diagnosticsSnapshot);
|
||||||
|
return (signatureMetadata, diagnosticsSnapshot);
|
||||||
}
|
}
|
||||||
catch (ExcititorAocGuardException)
|
catch (ExcititorAocGuardException)
|
||||||
{
|
{
|
||||||
@@ -192,7 +215,7 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
|||||||
"/upstream/signature",
|
"/upstream/signature",
|
||||||
$"Attestation verification encountered an error: {ex.Message}");
|
$"Attestation verification encountered an error: {ex.Message}");
|
||||||
|
|
||||||
RecordVerification(document.ProviderId, metadata, "fail");
|
RecordVerification(document.ProviderId, metadata, "error");
|
||||||
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
|
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +279,7 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
|
|||||||
VexInTotoStatement statement,
|
VexInTotoStatement statement,
|
||||||
ImmutableDictionary<string, string> metadata,
|
ImmutableDictionary<string, string> metadata,
|
||||||
VexAttestationMetadata attestationMetadata,
|
VexAttestationMetadata attestationMetadata,
|
||||||
ImmutableDictionary<string, string> diagnostics)
|
VexAttestationDiagnostics diagnostics)
|
||||||
{
|
{
|
||||||
metadata.TryGetValue("vex.signature.type", out var type);
|
metadata.TryGetValue("vex.signature.type", out var type);
|
||||||
metadata.TryGetValue("vex.provenance.cosign.subject", out var subject);
|
metadata.TryGetValue("vex.provenance.cosign.subject", out var subject);
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
- Shared `VexAttestationDiagnostics` record describing normalized diagnostic keys consumed by Worker/WebService logging.
|
- Shared `VexAttestationDiagnostics` record describing normalized diagnostic keys consumed by Worker/WebService logging.
|
||||||
- Metrics utility (`AttestationMetrics`) exposing counters/histograms via `System.Diagnostics.Metrics`, exported under `StellaOps.Excititor.Attestation` meter.
|
- Metrics utility (`AttestationMetrics`) exposing counters/histograms via `System.Diagnostics.Metrics`, exported under `StellaOps.Excititor.Attestation` meter.
|
||||||
- Activity source (`AttestationActivitySource`) for optional tracing spans around sign/verify operations.
|
- Activity source (`AttestationActivitySource`) for optional tracing spans around sign/verify operations.
|
||||||
|
- 2025-11-05: Implemented `VexAttestationDiagnostics`, activity tagging via `VexAttestationActivitySource`, and updated verifier/tests to emit structured failure reasons.
|
||||||
|
- 2025-11-05 (pm): Worker attestation verifier now records structured diagnostics/metrics and logs result/failure reasons using `VexAttestationDiagnostics`; attestation success/failure labels propagate to verification counters.
|
||||||
- Documentation updates (`EXCITITOR-ATTEST-01-003-plan.md`, `TASKS.md` notes) describing instrumentation + test expectations.
|
- Documentation updates (`EXCITITOR-ATTEST-01-003-plan.md`, `TASKS.md` notes) describing instrumentation + test expectations.
|
||||||
- Test coverage in `StellaOps.Excititor.Attestation.Tests` (unit) and scaffolding notes for WebService/Worker integration tests.
|
- Test coverage in `StellaOps.Excititor.Attestation.Tests` (unit) and scaffolding notes for WebService/Worker integration tests.
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,8 @@ If you are working on this file you need to read docs/modules/excititor/ARCHITEC
|
|||||||
# TASKS
|
# TASKS
|
||||||
| Task | Owner(s) | Depends on | Notes |
|
| Task | Owner(s) | Depends on | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
|EXCITITOR-ATTEST-01-003 – Verification suite & observability|Team Excititor Attestation|EXCITITOR-ATTEST-01-002|DOING (2025-10-22) – Continuing implementation: build `IVexAttestationVerifier`, wire metrics/logging, and add regression tests. Draft plan in `EXCITITOR-ATTEST-01-003-plan.md` (2025-10-19) guides scope; updating with worknotes as progress lands.<br>2025-10-31: Verifier now tolerates duplicate source providers from AOC raw projections, downgrades offline Rekor verification to a degraded result, and enforces trusted signer registry checks with detailed diagnostics/tests.|
|
|EXCITITOR-ATTEST-01-003 – Verification suite & observability|Team Excititor Attestation|EXCITITOR-ATTEST-01-002|TODO (2025-11-06) – Continuing implementation: build `IVexAttestationVerifier`, wire metrics/logging, and add regression tests. Draft plan in `EXCITITOR-ATTEST-01-003-plan.md` (2025-10-19) guides scope; updating with worknotes as progress lands.<br>2025-10-31: Verifier now tolerates duplicate source providers from AOC raw projections, downgrades offline Rekor verification to a degraded result, and enforces trusted signer registry checks with detailed diagnostics/tests.<br>2025-11-05 14:35Z: Picking up diagnostics record/ActivitySource work and aligning metrics dimensions before wiring verifier into WebService/Worker paths.|
|
||||||
|
> 2025-11-05 19:10Z: Worker signature verifier now emits structured diagnostics/metrics via `VexAttestationDiagnostics`; attestation verification results flow into metric labels and logs.
|
||||||
|
> 2025-11-06 07:12Z: Export verifier builds unblocked; Excititor worker + web service test suites pass with diagnostics wiring (`dotnet test` invocations succeed with staged libssl1.1).
|
||||||
|
> 2025-11-06 07:55Z: Paused after documenting OpenSSL shim usage; follow-up automation tracked under `DEVOPS-OPENSSL-11-001/002`.
|
||||||
> Remark (2025-10-22): Added verifier implementation + metrics/tests; next steps include wiring into WebService/Worker flows and expanding negative-path coverage.
|
> Remark (2025-10-22): Added verifier implementation + metrics/tests; next steps include wiring into WebService/Worker flows and expanding negative-path coverage.
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace StellaOps.Excititor.Attestation.Verification;
|
||||||
|
|
||||||
|
public static class VexAttestationActivitySource
|
||||||
|
{
|
||||||
|
public const string Name = "StellaOps.Excititor.Attestation";
|
||||||
|
|
||||||
|
public static readonly ActivitySource Value = new(Name);
|
||||||
|
}
|
||||||
@@ -67,12 +67,17 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
var resultLabel = "valid";
|
var resultLabel = "valid";
|
||||||
var rekorState = "skipped";
|
var rekorState = "skipped";
|
||||||
var component = request.IsReverify ? "worker" : "webservice";
|
var component = request.IsReverify ? "worker" : "webservice";
|
||||||
|
void SetFailure(string reason) => diagnostics["failure_reason"] = reason;
|
||||||
|
using var activity = VexAttestationActivitySource.Value.StartActivity("Verify", ActivityKind.Internal);
|
||||||
|
activity?.SetTag("attestation.component", component);
|
||||||
|
activity?.SetTag("attestation.export_id", request.Attestation.ExportId);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(request.Envelope))
|
if (string.IsNullOrWhiteSpace(request.Envelope))
|
||||||
{
|
{
|
||||||
diagnostics["envelope.state"] = "missing";
|
diagnostics["envelope.state"] = "missing";
|
||||||
|
SetFailure("missing_envelope");
|
||||||
_logger.LogWarning("Attestation envelope is missing for export {ExportId}", request.Attestation.ExportId);
|
_logger.LogWarning("Attestation envelope is missing for export {ExportId}", request.Attestation.ExportId);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
@@ -80,6 +85,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
|
|
||||||
if (!TryDeserializeEnvelope(request.Envelope, out var envelope, diagnostics))
|
if (!TryDeserializeEnvelope(request.Envelope, out var envelope, diagnostics))
|
||||||
{
|
{
|
||||||
|
SetFailure("invalid_envelope");
|
||||||
_logger.LogWarning("Failed to deserialize attestation envelope for export {ExportId}", request.Attestation.ExportId);
|
_logger.LogWarning("Failed to deserialize attestation envelope for export {ExportId}", request.Attestation.ExportId);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
@@ -88,6 +94,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
if (!string.Equals(envelope.PayloadType, VexDsseBuilder.PayloadType, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(envelope.PayloadType, VexDsseBuilder.PayloadType, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
diagnostics["payload.type"] = envelope.PayloadType ?? string.Empty;
|
diagnostics["payload.type"] = envelope.PayloadType ?? string.Empty;
|
||||||
|
SetFailure("unexpected_payload_type");
|
||||||
_logger.LogWarning(
|
_logger.LogWarning(
|
||||||
"Unexpected DSSE payload type {PayloadType} for export {ExportId}",
|
"Unexpected DSSE payload type {PayloadType} for export {ExportId}",
|
||||||
envelope.PayloadType,
|
envelope.PayloadType,
|
||||||
@@ -99,6 +106,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
if (envelope.Signatures is null || envelope.Signatures.Count == 0)
|
if (envelope.Signatures is null || envelope.Signatures.Count == 0)
|
||||||
{
|
{
|
||||||
diagnostics["signature.state"] = "missing";
|
diagnostics["signature.state"] = "missing";
|
||||||
|
SetFailure("missing_signature");
|
||||||
_logger.LogWarning("Attestation envelope for export {ExportId} does not contain signatures.", request.Attestation.ExportId);
|
_logger.LogWarning("Attestation envelope for export {ExportId} does not contain signatures.", request.Attestation.ExportId);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
@@ -107,6 +115,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
var payloadBase64 = envelope.Payload ?? string.Empty;
|
var payloadBase64 = envelope.Payload ?? string.Empty;
|
||||||
if (!TryDecodePayload(payloadBase64, out var payloadBytes, diagnostics))
|
if (!TryDecodePayload(payloadBase64, out var payloadBytes, diagnostics))
|
||||||
{
|
{
|
||||||
|
SetFailure("payload_decode_failed");
|
||||||
_logger.LogWarning("Failed to decode attestation payload for export {ExportId}", request.Attestation.ExportId);
|
_logger.LogWarning("Failed to decode attestation payload for export {ExportId}", request.Attestation.ExportId);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
@@ -114,6 +123,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
|
|
||||||
if (!TryDeserializeStatement(payloadBytes, out var statement, diagnostics))
|
if (!TryDeserializeStatement(payloadBytes, out var statement, diagnostics))
|
||||||
{
|
{
|
||||||
|
SetFailure("invalid_statement");
|
||||||
_logger.LogWarning("Failed to deserialize DSSE statement for export {ExportId}", request.Attestation.ExportId);
|
_logger.LogWarning("Failed to deserialize DSSE statement for export {ExportId}", request.Attestation.ExportId);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
@@ -121,6 +131,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
|
|
||||||
if (!ValidatePredicateType(statement, request, diagnostics))
|
if (!ValidatePredicateType(statement, request, diagnostics))
|
||||||
{
|
{
|
||||||
|
SetFailure("predicate_type_mismatch");
|
||||||
_logger.LogWarning("Predicate type mismatch for export {ExportId}", request.Attestation.ExportId);
|
_logger.LogWarning("Predicate type mismatch for export {ExportId}", request.Attestation.ExportId);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
@@ -128,6 +139,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
|
|
||||||
if (!ValidateSubject(statement, request, diagnostics))
|
if (!ValidateSubject(statement, request, diagnostics))
|
||||||
{
|
{
|
||||||
|
SetFailure("subject_mismatch");
|
||||||
_logger.LogWarning("Subject mismatch for export {ExportId}", request.Attestation.ExportId);
|
_logger.LogWarning("Subject mismatch for export {ExportId}", request.Attestation.ExportId);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
@@ -135,6 +147,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
|
|
||||||
if (!ValidatePredicate(statement, request, diagnostics))
|
if (!ValidatePredicate(statement, request, diagnostics))
|
||||||
{
|
{
|
||||||
|
SetFailure("predicate_mismatch");
|
||||||
_logger.LogWarning("Predicate payload mismatch for export {ExportId}", request.Attestation.ExportId);
|
_logger.LogWarning("Predicate payload mismatch for export {ExportId}", request.Attestation.ExportId);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
@@ -142,6 +155,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
|
|
||||||
if (!ValidateMetadataDigest(envelope, request.Metadata, diagnostics))
|
if (!ValidateMetadataDigest(envelope, request.Metadata, diagnostics))
|
||||||
{
|
{
|
||||||
|
SetFailure("envelope_digest_mismatch");
|
||||||
_logger.LogWarning("Attestation digest mismatch for export {ExportId}", request.Attestation.ExportId);
|
_logger.LogWarning("Attestation digest mismatch for export {ExportId}", request.Attestation.ExportId);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
@@ -149,6 +163,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
|
|
||||||
if (!ValidateSignedAt(request.Metadata, request.Attestation.CreatedAt, diagnostics))
|
if (!ValidateSignedAt(request.Metadata, request.Attestation.CreatedAt, diagnostics))
|
||||||
{
|
{
|
||||||
|
SetFailure("signedat_out_of_range");
|
||||||
_logger.LogWarning("SignedAt validation failed for export {ExportId}", request.Attestation.ExportId);
|
_logger.LogWarning("SignedAt validation failed for export {ExportId}", request.Attestation.ExportId);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
@@ -157,6 +172,7 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
rekorState = await VerifyTransparencyAsync(request.Metadata, diagnostics, cancellationToken).ConfigureAwait(false);
|
rekorState = await VerifyTransparencyAsync(request.Metadata, diagnostics, cancellationToken).ConfigureAwait(false);
|
||||||
if (rekorState is "missing" or "unverified" or "client_unavailable")
|
if (rekorState is "missing" or "unverified" or "client_unavailable")
|
||||||
{
|
{
|
||||||
|
SetFailure(rekorState);
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
}
|
}
|
||||||
@@ -164,6 +180,9 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
var signaturesVerified = await VerifySignaturesAsync(payloadBytes, envelope.Signatures, diagnostics, cancellationToken).ConfigureAwait(false);
|
var signaturesVerified = await VerifySignaturesAsync(payloadBytes, envelope.Signatures, diagnostics, cancellationToken).ConfigureAwait(false);
|
||||||
if (!signaturesVerified)
|
if (!signaturesVerified)
|
||||||
{
|
{
|
||||||
|
diagnostics["failure_reason"] = diagnostics.TryGetValue("signature.reason", out var reason)
|
||||||
|
? reason
|
||||||
|
: "signature_verification_failed";
|
||||||
if (_options.RequireSignatureVerification)
|
if (_options.RequireSignatureVerification)
|
||||||
{
|
{
|
||||||
resultLabel = "invalid";
|
resultLabel = "invalid";
|
||||||
@@ -185,6 +204,9 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
diagnostics["error"] = ex.GetType().Name;
|
diagnostics["error"] = ex.GetType().Name;
|
||||||
diagnostics["error.message"] = ex.Message; resultLabel = "error";
|
diagnostics["error.message"] = ex.Message; resultLabel = "error";
|
||||||
_logger.LogError(ex, "Unexpected exception verifying attestation for export {ExportId}", request.Attestation.ExportId);
|
_logger.LogError(ex, "Unexpected exception verifying attestation for export {ExportId}", request.Attestation.ExportId);
|
||||||
|
diagnostics["failure_reason"] = diagnostics.TryGetValue("error", out var errorCode)
|
||||||
|
? errorCode
|
||||||
|
: ex.GetType().Name;
|
||||||
return BuildResult(false);
|
return BuildResult(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -205,7 +227,27 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
|
|||||||
diagnostics["result"] = resultLabel;
|
diagnostics["result"] = resultLabel;
|
||||||
diagnostics["component"] = component;
|
diagnostics["component"] = component;
|
||||||
diagnostics["rekor.state"] = rekorState;
|
diagnostics["rekor.state"] = rekorState;
|
||||||
return new VexAttestationVerification(isValid, diagnostics.ToImmutable());
|
var snapshot = VexAttestationDiagnostics.FromBuilder(diagnostics);
|
||||||
|
|
||||||
|
if (activity is { } currentActivity)
|
||||||
|
{
|
||||||
|
currentActivity.SetTag("attestation.result", resultLabel);
|
||||||
|
currentActivity.SetTag("attestation.rekor", rekorState);
|
||||||
|
if (!isValid)
|
||||||
|
{
|
||||||
|
var failure = snapshot.FailureReason ?? "verification_failed";
|
||||||
|
currentActivity.SetStatus(ActivityStatusCode.Error, failure);
|
||||||
|
currentActivity.SetTag("attestation.failure_reason", failure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentActivity.SetStatus(resultLabel is "degraded"
|
||||||
|
? ActivityStatusCode.Ok
|
||||||
|
: ActivityStatusCode.Ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VexAttestationVerification(isValid, snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using StellaOps.Excititor.Attestation.Verification;
|
||||||
|
|
||||||
namespace StellaOps.Excititor.Core;
|
namespace StellaOps.Excititor.Core;
|
||||||
|
|
||||||
@@ -33,4 +34,4 @@ public sealed record VexAttestationVerificationRequest(
|
|||||||
|
|
||||||
public sealed record VexAttestationVerification(
|
public sealed record VexAttestationVerification(
|
||||||
bool IsValid,
|
bool IsValid,
|
||||||
ImmutableDictionary<string, string> Diagnostics);
|
VexAttestationDiagnostics Diagnostics);
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
namespace StellaOps.Excititor.Attestation.Verification;
|
||||||
|
|
||||||
|
public sealed class VexAttestationDiagnostics : IReadOnlyDictionary<string, string>
|
||||||
|
{
|
||||||
|
private readonly ImmutableDictionary<string, string> _values;
|
||||||
|
|
||||||
|
private VexAttestationDiagnostics(ImmutableDictionary<string, string> values)
|
||||||
|
{
|
||||||
|
_values = values ?? ImmutableDictionary<string, string>.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VexAttestationDiagnostics FromBuilder(ImmutableDictionary<string, string>.Builder builder)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(builder);
|
||||||
|
return new(builder.ToImmutable());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VexAttestationDiagnostics Empty { get; } = new(ImmutableDictionary<string, string>.Empty);
|
||||||
|
|
||||||
|
public string? Result => TryGetValue("result", out var value) ? value : null;
|
||||||
|
|
||||||
|
public string? Component => TryGetValue("component", out var value) ? value : null;
|
||||||
|
|
||||||
|
public string? RekorState => TryGetValue("rekor.state", out var value) ? value : null;
|
||||||
|
|
||||||
|
public string? FailureReason => TryGetValue("failure_reason", out var value) ? value : null;
|
||||||
|
|
||||||
|
public string this[string key] => _values[key];
|
||||||
|
|
||||||
|
public IEnumerable<string> Keys => _values.Keys;
|
||||||
|
|
||||||
|
public IEnumerable<string> Values => _values.Values;
|
||||||
|
|
||||||
|
public int Count => _values.Count;
|
||||||
|
|
||||||
|
public bool ContainsKey(string key) => _values.ContainsKey(key);
|
||||||
|
|
||||||
|
public bool TryGetValue(string key, out string value)
|
||||||
|
{
|
||||||
|
if (_values.TryGetValue(key, out var stored))
|
||||||
|
{
|
||||||
|
value = stored;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = string.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<string, string>> GetEnumerator() => _values.GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
@@ -98,6 +98,7 @@ public sealed class VexExportEngine : IExportEngine
|
|||||||
cached.PolicyDigest,
|
cached.PolicyDigest,
|
||||||
cached.ConsensusDigest,
|
cached.ConsensusDigest,
|
||||||
cached.ScoreDigest,
|
cached.ScoreDigest,
|
||||||
|
cached.QuietProvenance,
|
||||||
cached.Attestation,
|
cached.Attestation,
|
||||||
cached.SizeBytes);
|
cached.SizeBytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ internal static class VexExportEnvelopeBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed record VexExportEnvelopeContext(
|
public sealed record VexExportEnvelopeContext(
|
||||||
ImmutableArray<VexConsensus> Consensus,
|
ImmutableArray<VexConsensus> Consensus,
|
||||||
string ConsensusCanonicalJson,
|
string ConsensusCanonicalJson,
|
||||||
VexContentAddress ConsensusDigest,
|
VexContentAddress ConsensusDigest,
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ public sealed class VexMirrorBundlePublisher : IVexMirrorBundlePublisher
|
|||||||
ToRelativePath(mirrorRoot, manifestPath),
|
ToRelativePath(mirrorRoot, manifestPath),
|
||||||
manifestBytes.LongLength,
|
manifestBytes.LongLength,
|
||||||
ComputeDigest(manifestBytes),
|
ComputeDigest(manifestBytes),
|
||||||
signature: null);
|
Signature: null);
|
||||||
|
|
||||||
var bundleDescriptor = manifestDocument.Bundle with
|
var bundleDescriptor = manifestDocument.Bundle with
|
||||||
{
|
{
|
||||||
@@ -298,7 +298,7 @@ public sealed class VexMirrorBundlePublisher : IVexMirrorBundlePublisher
|
|||||||
manifestDocument.DomainId,
|
manifestDocument.DomainId,
|
||||||
manifestDocument.DisplayName,
|
manifestDocument.DisplayName,
|
||||||
manifestDocument.GeneratedAt,
|
manifestDocument.GeneratedAt,
|
||||||
manifestDocument.Exports.Length,
|
manifestDocument.Exports.Count,
|
||||||
manifestDescriptor,
|
manifestDescriptor,
|
||||||
bundleDescriptor,
|
bundleDescriptor,
|
||||||
exportKeys));
|
exportKeys));
|
||||||
@@ -474,6 +474,11 @@ public sealed class VexMirrorBundlePublisher : IVexMirrorBundlePublisher
|
|||||||
|
|
||||||
private JsonMirrorSigningContext PrepareSigningContext(MirrorSigningOptions signingOptions)
|
private JsonMirrorSigningContext PrepareSigningContext(MirrorSigningOptions signingOptions)
|
||||||
{
|
{
|
||||||
|
if (_cryptoRegistry is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Mirror signing requires a crypto provider registry to be configured.");
|
||||||
|
}
|
||||||
|
|
||||||
var algorithm = string.IsNullOrWhiteSpace(signingOptions.Algorithm)
|
var algorithm = string.IsNullOrWhiteSpace(signingOptions.Algorithm)
|
||||||
? SignatureAlgorithms.Es256
|
? SignatureAlgorithms.Es256
|
||||||
: signingOptions.Algorithm.Trim();
|
: signingOptions.Algorithm.Trim();
|
||||||
@@ -496,7 +501,7 @@ public sealed class VexMirrorBundlePublisher : IVexMirrorBundlePublisher
|
|||||||
var provider = ResolveProvider(algorithm, providerHint);
|
var provider = ResolveProvider(algorithm, providerHint);
|
||||||
var signingKey = LoadSigningKey(signingOptions, provider, algorithm);
|
var signingKey = LoadSigningKey(signingOptions, provider, algorithm);
|
||||||
provider.UpsertSigningKey(signingKey);
|
provider.UpsertSigningKey(signingKey);
|
||||||
resolved = _cryptoRegistry.ResolveSigner(CryptoCapability.Signing, algorithm, new CryptoKeyReference(keyId, provider.Name), provider.Name);
|
resolved = _cryptoRegistry!.ResolveSigner(CryptoCapability.Signing, algorithm, new CryptoKeyReference(keyId, provider.Name), provider.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JsonMirrorSigningContext(resolved.Signer, algorithm, resolved.ProviderName, _timeProvider);
|
return new JsonMirrorSigningContext(resolved.Signer, algorithm, resolved.ProviderName, _timeProvider);
|
||||||
|
|||||||
@@ -85,6 +85,6 @@ public sealed class VexAttestationClientTests
|
|||||||
private sealed class FakeVerifier : IVexAttestationVerifier
|
private sealed class FakeVerifier : IVexAttestationVerifier
|
||||||
{
|
{
|
||||||
public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationVerificationRequest request, CancellationToken cancellationToken)
|
public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationVerificationRequest request, CancellationToken cancellationToken)
|
||||||
=> ValueTask.FromResult(new VexAttestationVerification(true, ImmutableDictionary<string, string>.Empty));
|
=> ValueTask.FromResult(new VexAttestationVerification(true, VexAttestationDiagnostics.Empty));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
|||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
Assert.True(verification.IsValid);
|
Assert.True(verification.IsValid);
|
||||||
Assert.Equal("valid", verification.Diagnostics["result"]);
|
Assert.Equal("valid", verification.Diagnostics.Result);
|
||||||
|
Assert.Null(verification.Diagnostics.FailureReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -47,8 +48,9 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
|||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
Assert.False(verification.IsValid);
|
Assert.False(verification.IsValid);
|
||||||
Assert.Equal("invalid", verification.Diagnostics["result"]);
|
Assert.Equal("invalid", verification.Diagnostics.Result);
|
||||||
Assert.Equal("sha256:deadbeef", verification.Diagnostics["metadata.envelopeDigest"]);
|
Assert.Equal("sha256:deadbeef", verification.Diagnostics["metadata.envelopeDigest"]);
|
||||||
|
Assert.Equal("envelope_digest_mismatch", verification.Diagnostics.FailureReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -67,8 +69,9 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
|||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
Assert.True(verification.IsValid);
|
Assert.True(verification.IsValid);
|
||||||
Assert.Equal("offline", verification.Diagnostics["rekor.state"]);
|
Assert.Equal("offline", verification.Diagnostics.RekorState);
|
||||||
Assert.Equal("degraded", verification.Diagnostics["result"]);
|
Assert.Equal("degraded", verification.Diagnostics.Result);
|
||||||
|
Assert.Null(verification.Diagnostics.FailureReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -86,8 +89,9 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
|||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
Assert.False(verification.IsValid);
|
Assert.False(verification.IsValid);
|
||||||
Assert.Equal("missing", verification.Diagnostics["rekor.state"]);
|
Assert.Equal("missing", verification.Diagnostics.RekorState);
|
||||||
Assert.Equal("invalid", verification.Diagnostics["result"]);
|
Assert.Equal("invalid", verification.Diagnostics.Result);
|
||||||
|
Assert.Equal("missing", verification.Diagnostics.FailureReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -106,8 +110,9 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
|||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
Assert.False(verification.IsValid);
|
Assert.False(verification.IsValid);
|
||||||
Assert.Equal("unreachable", verification.Diagnostics["rekor.state"]);
|
Assert.Equal("unreachable", verification.Diagnostics.RekorState);
|
||||||
Assert.Equal("invalid", verification.Diagnostics["result"]);
|
Assert.Equal("invalid", verification.Diagnostics.Result);
|
||||||
|
Assert.Equal("unreachable", verification.Diagnostics.FailureReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -125,7 +130,7 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
|||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
Assert.True(verification.IsValid);
|
Assert.True(verification.IsValid);
|
||||||
Assert.Equal("valid", verification.Diagnostics["result"]);
|
Assert.Equal("valid", verification.Diagnostics.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -152,6 +157,8 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
|||||||
|
|
||||||
Assert.True(verification.IsValid);
|
Assert.True(verification.IsValid);
|
||||||
Assert.Equal("verified", verification.Diagnostics["signature.state"]);
|
Assert.Equal("verified", verification.Diagnostics["signature.state"]);
|
||||||
|
Assert.Equal("valid", verification.Diagnostics.Result);
|
||||||
|
Assert.Null(verification.Diagnostics.FailureReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -179,6 +186,8 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
|||||||
Assert.False(verification.IsValid);
|
Assert.False(verification.IsValid);
|
||||||
Assert.Equal("error", verification.Diagnostics["signature.state"]);
|
Assert.Equal("error", verification.Diagnostics["signature.state"]);
|
||||||
Assert.Equal("verification_failed", verification.Diagnostics["signature.reason"]);
|
Assert.Equal("verification_failed", verification.Diagnostics["signature.reason"]);
|
||||||
|
Assert.Equal("verification_failed", verification.Diagnostics.FailureReason);
|
||||||
|
Assert.Equal("invalid", verification.Diagnostics.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(VexAttestationRequest Request, VexAttestationMetadata Metadata, string Envelope)> CreateSignedAttestationAsync(
|
private async Task<(VexAttestationRequest Request, VexAttestationMetadata Metadata, string Envelope)> CreateSignedAttestationAsync(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Globalization;
|
|||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using MongoDB.Driver;
|
using MongoDB.Driver;
|
||||||
using StellaOps.Excititor.Core;
|
using StellaOps.Excititor.Core;
|
||||||
|
using StellaOps.Excititor.Attestation.Verification;
|
||||||
using StellaOps.Excititor.Export;
|
using StellaOps.Excititor.Export;
|
||||||
using StellaOps.Excititor.Policy;
|
using StellaOps.Excititor.Policy;
|
||||||
using StellaOps.Excititor.Storage.Mongo;
|
using StellaOps.Excititor.Storage.Mongo;
|
||||||
@@ -291,7 +292,7 @@ public sealed class ExportEngineTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationVerificationRequest request, CancellationToken cancellationToken)
|
public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationVerificationRequest request, CancellationToken cancellationToken)
|
||||||
=> ValueTask.FromResult(new VexAttestationVerification(true, ImmutableDictionary<string, string>.Empty));
|
=> ValueTask.FromResult(new VexAttestationVerification(true, VexAttestationDiagnostics.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class RecordingCacheIndex : IVexCacheIndex
|
private sealed class RecordingCacheIndex : IVexCacheIndex
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using StellaOps.Excititor.Core;
|
using StellaOps.Excititor.Core;
|
||||||
|
using StellaOps.Excititor.Attestation.Verification;
|
||||||
using StellaOps.Excititor.Export;
|
using StellaOps.Excititor.Export;
|
||||||
using StellaOps.Excititor.Storage.Mongo;
|
using StellaOps.Excititor.Storage.Mongo;
|
||||||
using StellaOps.Excititor.WebService.Services;
|
using StellaOps.Excititor.WebService.Services;
|
||||||
@@ -162,7 +163,7 @@ internal static class TestServiceOverrides
|
|||||||
|
|
||||||
public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationVerificationRequest request, CancellationToken cancellationToken)
|
public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationVerificationRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var verification = new VexAttestationVerification(true, ImmutableDictionary<string, string>.Empty);
|
var verification = new VexAttestationVerification(true, VexAttestationDiagnostics.Empty);
|
||||||
return ValueTask.FromResult(verification);
|
return ValueTask.FromResult(verification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -504,6 +504,21 @@ public sealed class DefaultVexProviderRunnerTests
|
|||||||
bool includeGlobal,
|
bool includeGlobal,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
=> ValueTask.FromResult(DefaultTrust);
|
=> ValueTask.FromResult(DefaultTrust);
|
||||||
|
|
||||||
|
public ValueTask<IssuerTrustResponseModel> SetIssuerTrustAsync(
|
||||||
|
string tenantId,
|
||||||
|
string issuerId,
|
||||||
|
decimal weight,
|
||||||
|
string? reason,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
=> ValueTask.FromResult(DefaultTrust);
|
||||||
|
|
||||||
|
public ValueTask DeleteIssuerTrustAsync(
|
||||||
|
string tenantId,
|
||||||
|
string issuerId,
|
||||||
|
string? reason,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
=> ValueTask.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class NoopSignatureVerifier : IVexSignatureVerifier
|
private sealed class NoopSignatureVerifier : IVexSignatureVerifier
|
||||||
@@ -658,12 +673,12 @@ public sealed class DefaultVexProviderRunnerTests
|
|||||||
private sealed class StubAttestationVerifier : IVexAttestationVerifier
|
private sealed class StubAttestationVerifier : IVexAttestationVerifier
|
||||||
{
|
{
|
||||||
private readonly bool _isValid;
|
private readonly bool _isValid;
|
||||||
private readonly ImmutableDictionary<string, string> _diagnostics;
|
private readonly VexAttestationDiagnostics _diagnostics;
|
||||||
|
|
||||||
public StubAttestationVerifier(bool isValid, ImmutableDictionary<string, string> diagnostics)
|
public StubAttestationVerifier(bool isValid, ImmutableDictionary<string, string> diagnostics)
|
||||||
{
|
{
|
||||||
_isValid = isValid;
|
_isValid = isValid;
|
||||||
_diagnostics = diagnostics;
|
_diagnostics = VexAttestationDiagnostics.FromBuilder(diagnostics.ToBuilder());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Invocations { get; private set; }
|
public int Invocations { get; private set; }
|
||||||
|
|||||||
@@ -249,12 +249,14 @@ public sealed class WorkerSignatureVerifierTests
|
|||||||
private sealed class StubAttestationVerifier : IVexAttestationVerifier
|
private sealed class StubAttestationVerifier : IVexAttestationVerifier
|
||||||
{
|
{
|
||||||
private readonly bool _isValid;
|
private readonly bool _isValid;
|
||||||
private readonly ImmutableDictionary<string, string> _diagnostics;
|
private readonly VexAttestationDiagnostics _diagnostics;
|
||||||
|
|
||||||
public StubAttestationVerifier(bool isValid, ImmutableDictionary<string, string>? diagnostics = null)
|
public StubAttestationVerifier(bool isValid, ImmutableDictionary<string, string>? diagnostics = null)
|
||||||
{
|
{
|
||||||
_isValid = isValid;
|
_isValid = isValid;
|
||||||
_diagnostics = diagnostics ?? ImmutableDictionary<string, string>.Empty;
|
_diagnostics = diagnostics is null
|
||||||
|
? VexAttestationDiagnostics.Empty
|
||||||
|
: VexAttestationDiagnostics.FromBuilder(diagnostics.ToBuilder());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Invocations { get; private set; }
|
public int Invocations { get; private set; }
|
||||||
@@ -269,7 +271,7 @@ public sealed class WorkerSignatureVerifierTests
|
|||||||
private sealed class StubIssuerDirectoryClient : IIssuerDirectoryClient
|
private sealed class StubIssuerDirectoryClient : IIssuerDirectoryClient
|
||||||
{
|
{
|
||||||
private readonly IReadOnlyList<IssuerKeyModel> _keys;
|
private readonly IReadOnlyList<IssuerKeyModel> _keys;
|
||||||
private readonly IssuerTrustResponseModel _trust;
|
private IssuerTrustResponseModel _trust;
|
||||||
|
|
||||||
private StubIssuerDirectoryClient(
|
private StubIssuerDirectoryClient(
|
||||||
IReadOnlyList<IssuerKeyModel> keys,
|
IReadOnlyList<IssuerKeyModel> keys,
|
||||||
@@ -302,7 +304,7 @@ public sealed class WorkerSignatureVerifierTests
|
|||||||
null,
|
null,
|
||||||
null);
|
null);
|
||||||
|
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UnixEpoch;
|
||||||
var overrideModel = new IssuerTrustOverrideModel(weight, "stub", now, "test", now, "test");
|
var overrideModel = new IssuerTrustOverrideModel(weight, "stub", now, "test", now, "test");
|
||||||
return new StubIssuerDirectoryClient(
|
return new StubIssuerDirectoryClient(
|
||||||
new[] { key },
|
new[] { key },
|
||||||
@@ -322,6 +324,29 @@ public sealed class WorkerSignatureVerifierTests
|
|||||||
bool includeGlobal,
|
bool includeGlobal,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
=> ValueTask.FromResult(_trust);
|
=> ValueTask.FromResult(_trust);
|
||||||
|
|
||||||
|
public ValueTask<IssuerTrustResponseModel> SetIssuerTrustAsync(
|
||||||
|
string tenantId,
|
||||||
|
string issuerId,
|
||||||
|
decimal weight,
|
||||||
|
string? reason,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var now = DateTimeOffset.UnixEpoch;
|
||||||
|
var overrideModel = new IssuerTrustOverrideModel(weight, "stub-set", now, "test", now, "test");
|
||||||
|
_trust = new IssuerTrustResponseModel(overrideModel, null, weight);
|
||||||
|
return ValueTask.FromResult(_trust);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask DeleteIssuerTrustAsync(
|
||||||
|
string tenantId,
|
||||||
|
string issuerId,
|
||||||
|
string? reason,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_trust = new IssuerTrustResponseModel(null, null, 0m);
|
||||||
|
return ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class FixedTimeProvider : TimeProvider
|
private sealed class FixedTimeProvider : TimeProvider
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
## Sprint 64 – Bundle Implementation
|
## Sprint 64 – Bundle Implementation
|
||||||
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
||||||
|----|--------|----------|------------|-------------|---------------|
|
|----|--------|----------|------------|-------------|---------------|
|
||||||
| DVOFF-64-001 | DOING (2025-11-04) | DevPortal Offline Guild, Exporter Guild | DEVPORT-64-001, SDKREL-64-002 | Implement Export Center job `devportal --offline` bundling portal HTML, specs, SDK artifacts, changelogs, and verification manifest. | Job executes in staging; manifest contains checksums + DSSE signatures; docs updated. |
|
| DVOFF-64-001 | DONE (2025-11-05) | DevPortal Offline Guild, Exporter Guild | DEVPORT-64-001, SDKREL-64-002 | Implement Export Center job `devportal --offline` bundling portal HTML, specs, SDK artifacts, changelogs, and verification manifest. | Job executes in staging; manifest contains checksums + DSSE signatures; docs updated. |
|
||||||
| DVOFF-64-002 | TODO | DevPortal Offline Guild, AirGap Controller Guild | DVOFF-64-001 | Provide verification CLI (`stella devportal verify bundle.tgz`) ensuring integrity before import. | CLI command validates signatures; integration test covers corrupted bundle; runbook updated. |
|
| DVOFF-64-002 | TODO | DevPortal Offline Guild, AirGap Controller Guild | DVOFF-64-001 | Provide verification CLI (`stella devportal verify bundle.tgz`) ensuring integrity before import. | CLI command validates signatures; integration test covers corrupted bundle; runbook updated. |
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace StellaOps.ExportCenter.Core.DevPortalOffline;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Coordinates bundle construction, manifest signing, and artefact persistence for the devportal offline export.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DevPortalOfflineJob
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions SignatureSerializerOptions = new(JsonSerializerDefaults.Web)
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly DevPortalOfflineBundleBuilder _builder;
|
||||||
|
private readonly IDevPortalOfflineObjectStore _objectStore;
|
||||||
|
private readonly IDevPortalOfflineManifestSigner _manifestSigner;
|
||||||
|
private readonly ILogger<DevPortalOfflineJob> _logger;
|
||||||
|
|
||||||
|
public DevPortalOfflineJob(
|
||||||
|
DevPortalOfflineBundleBuilder builder,
|
||||||
|
IDevPortalOfflineObjectStore objectStore,
|
||||||
|
IDevPortalOfflineManifestSigner manifestSigner,
|
||||||
|
ILogger<DevPortalOfflineJob> logger)
|
||||||
|
{
|
||||||
|
_builder = builder ?? throw new ArgumentNullException(nameof(builder));
|
||||||
|
_objectStore = objectStore ?? throw new ArgumentNullException(nameof(objectStore));
|
||||||
|
_manifestSigner = manifestSigner ?? throw new ArgumentNullException(nameof(manifestSigner));
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DevPortalOfflineJobOutcome> ExecuteAsync(
|
||||||
|
DevPortalOfflineJobRequest request,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(request);
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var bundleResult = _builder.Build(request.BundleRequest, cancellationToken);
|
||||||
|
_logger.LogInformation("DevPortal offline bundle constructed with {EntryCount} entries.", bundleResult.Manifest.Entries.Count);
|
||||||
|
|
||||||
|
var signature = await _manifestSigner.SignAsync(
|
||||||
|
request.BundleRequest.BundleId,
|
||||||
|
bundleResult.ManifestJson,
|
||||||
|
bundleResult.RootHash,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var storagePrefix = BuildStoragePrefix(request.StoragePrefix, request.BundleRequest.BundleId);
|
||||||
|
var bundleFileName = SanitizeFileName(request.BundleFileName);
|
||||||
|
|
||||||
|
var manifestKey = $"{storagePrefix}/manifest.json";
|
||||||
|
var signatureKey = $"{storagePrefix}/manifest.dsse.json";
|
||||||
|
var checksumsKey = $"{storagePrefix}/checksums.txt";
|
||||||
|
var bundleKey = $"{storagePrefix}/{bundleFileName}";
|
||||||
|
|
||||||
|
var manifestMetadata = await StoreTextAsync(
|
||||||
|
bundleResult.ManifestJson,
|
||||||
|
manifestKey,
|
||||||
|
MediaTypes.Json,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var signatureJson = JsonSerializer.Serialize(signature, SignatureSerializerOptions);
|
||||||
|
var signatureMetadata = await StoreTextAsync(
|
||||||
|
signatureJson,
|
||||||
|
signatureKey,
|
||||||
|
MediaTypes.Json,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var checksumsMetadata = await StoreTextAsync(
|
||||||
|
bundleResult.Checksums,
|
||||||
|
checksumsKey,
|
||||||
|
MediaTypes.Text,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
DevPortalOfflineStorageMetadata bundleMetadata;
|
||||||
|
using (bundleResult.BundleStream)
|
||||||
|
{
|
||||||
|
bundleResult.BundleStream.Position = 0;
|
||||||
|
bundleMetadata = await _objectStore.StoreAsync(
|
||||||
|
bundleResult.BundleStream,
|
||||||
|
new DevPortalOfflineObjectStoreOptions(bundleKey, MediaTypes.GZip),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"DevPortal offline bundle stored at {BundleKey} ({SizeBytes} bytes).",
|
||||||
|
bundleMetadata.StorageKey,
|
||||||
|
bundleMetadata.SizeBytes);
|
||||||
|
|
||||||
|
return new DevPortalOfflineJobOutcome(
|
||||||
|
bundleResult.Manifest,
|
||||||
|
bundleResult.RootHash,
|
||||||
|
signature,
|
||||||
|
manifestMetadata,
|
||||||
|
signatureMetadata,
|
||||||
|
checksumsMetadata,
|
||||||
|
bundleMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<DevPortalOfflineStorageMetadata> StoreTextAsync(
|
||||||
|
string content,
|
||||||
|
string storageKey,
|
||||||
|
string contentType,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
var stream = new MemoryStream(bytes, writable: false);
|
||||||
|
return StoreAsync(stream, storageKey, contentType, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<DevPortalOfflineStorageMetadata> StoreAsync(
|
||||||
|
Stream stream,
|
||||||
|
string storageKey,
|
||||||
|
string contentType,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _objectStore.StoreAsync(
|
||||||
|
stream,
|
||||||
|
new DevPortalOfflineObjectStoreOptions(storageKey, contentType),
|
||||||
|
cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await stream.DisposeAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildStoragePrefix(string? prefix, Guid bundleId)
|
||||||
|
{
|
||||||
|
var trimmed = string.IsNullOrWhiteSpace(prefix)
|
||||||
|
? string.Empty
|
||||||
|
: prefix.Trim().Trim('/').Replace('\\', '/');
|
||||||
|
|
||||||
|
return string.IsNullOrEmpty(trimmed)
|
||||||
|
? bundleId.ToString("D")
|
||||||
|
: $"{trimmed}/{bundleId:D}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SanitizeFileName(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return "devportal-offline-bundle.tgz";
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName = Path.GetFileName(value);
|
||||||
|
if (string.IsNullOrEmpty(fileName))
|
||||||
|
{
|
||||||
|
return "devportal-offline-bundle.tgz";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName.Contains("..", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bundle file name cannot contain path traversal sequences.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MediaTypes
|
||||||
|
{
|
||||||
|
public const string Json = "application/json";
|
||||||
|
public const string Text = "text/plain";
|
||||||
|
public const string GZip = "application/gzip";
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user