feat: Implement PackRunApprovalDecisionService for handling approval decisions
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:
master
2025-11-06 11:08:52 +02:00
157 changed files with 6386 additions and 3296 deletions

View File

@@ -23,13 +23,27 @@ This directory contains deterministic deployment bundles for the core Stella Ops
`python ./ops/devops/telemetry/smoke_otel_collector.py` to verify the OTLP endpoints. `python ./ops/devops/telemetry/smoke_otel_collector.py` to verify the OTLP endpoints.
5. Commit the change alongside any documentation updates (e.g. install guide cross-links). 5. Commit the change alongside any documentation updates (e.g. install guide cross-links).
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.
### Additional tooling ### Surface.Env rollout warnings
- `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. - 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`.
- `ops/devops/telemetry/generate_dev_tls.sh` produces local CA/server/client certificates for Compose-based collector testing. - 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`.
- `ops/devops/telemetry/smoke_otel_collector.py` sends OTLP traffic and asserts the collector accepted traces, metrics, and logs. - 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
- `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.
- `ops/devops/telemetry/generate_dev_tls.sh` produces local CA/server/client certificates for Compose-based collector testing.
- `ops/devops/telemetry/smoke_otel_collector.py` sends OTLP traffic and asserts the collector accepted traces, metrics, and logs.
- `ops/devops/telemetry/package_offline_bundle.py` packages telemetry assets (config/Helm/Compose) into a signed tarball for air-gapped installs. - `ops/devops/telemetry/package_offline_bundle.py` packages telemetry assets (config/Helm/Compose) into a signed tarball for air-gapped installs.
- `docs/modules/devops/runbooks/deployment-upgrade.md` end-to-end instructions for upgrade, rollback, and channel promotion workflows (Helm + Compose). - `docs/modules/devops/runbooks/deployment-upgrade.md` end-to-end instructions for upgrade, rollback, and channel promotion workflows (Helm + Compose).

View File

@@ -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

View File

@@ -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

View File

@@ -6,10 +6,16 @@ MONGO_INITDB_ROOT_PASSWORD=mirror-password
MINIO_ROOT_USER=stellaops-mirror 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
# Mirror HTTP listeners # Scanner surface integration
MIRROR_GATEWAY_HTTP_PORT=8080 SCANNER_SURFACE_FS_ENDPOINT=http://rustfs:8080/api/v1
MIRROR_GATEWAY_HTTPS_PORT=9443 SCANNER_SURFACE_CACHE_ROOT=/var/lib/stellaops/surface
SCANNER_SURFACE_SECRETS_PROVIDER=file
SCANNER_SURFACE_SECRETS_ROOT=/etc/stellaops/secrets
# Mirror HTTP listeners
MIRROR_GATEWAY_HTTP_PORT=8080
MIRROR_GATEWAY_HTTPS_PORT=9443
# Concelier mirror configuration # Concelier mirror configuration
CONCELIER_MIRROR_LATEST_SEGMENT=latest CONCELIER_MIRROR_LATEST_SEGMENT=latest

View File

@@ -26,9 +26,13 @@ SCANNER_EVENTS_ENABLED=true
SCANNER_EVENTS_DRIVER=redis SCANNER_EVENTS_DRIVER=redis
# Leave SCANNER_EVENTS_DSN empty to inherit the Redis queue DSN when SCANNER_QUEUE_BROKER uses redis://. # Leave SCANNER_EVENTS_DSN empty to inherit the Redis queue DSN when SCANNER_QUEUE_BROKER uses redis://.
SCANNER_EVENTS_DSN= 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

View File

@@ -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

View File

@@ -1,102 +1,102 @@
global: global:
profile: airgap profile: airgap
release: release:
version: "2025.09.2-airgap" version: "2025.09.2-airgap"
channel: airgap channel: airgap
manifestSha256: "b787b833dddd73960c31338279daa0b0a0dce2ef32bd32ef1aaf953d66135f94" manifestSha256: "b787b833dddd73960c31338279daa0b0a0dce2ef32bd32ef1aaf953d66135f94"
image: image:
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
labels: labels:
stellaops.io/channel: airgap stellaops.io/channel: airgap
configMaps: configMaps:
notify-config: notify-config:
data: data:
notify.yaml: | notify.yaml: |
storage: storage:
driver: mongo driver: mongo
connectionString: "mongodb://notify-mongo.prod.svc.cluster.local:27017" connectionString: "mongodb://notify-mongo.prod.svc.cluster.local:27017"
database: "stellaops_notify" database: "stellaops_notify"
commandTimeoutSeconds: 60 commandTimeoutSeconds: 60
authority: authority:
enabled: true enabled: true
issuer: "https://authority.stella-ops.org" issuer: "https://authority.stella-ops.org"
metadataAddress: "https://authority.stella-ops.org/.well-known/openid-configuration" metadataAddress: "https://authority.stella-ops.org/.well-known/openid-configuration"
requireHttpsMetadata: true requireHttpsMetadata: true
allowAnonymousFallback: false allowAnonymousFallback: false
backchannelTimeoutSeconds: 30 backchannelTimeoutSeconds: 30
tokenClockSkewSeconds: 60 tokenClockSkewSeconds: 60
audiences: audiences:
- notify - notify
readScope: notify.read readScope: notify.read
adminScope: notify.admin adminScope: notify.admin
api: api:
basePath: "/api/v1/notify" basePath: "/api/v1/notify"
internalBasePath: "/internal/notify" internalBasePath: "/internal/notify"
tenantHeader: "X-StellaOps-Tenant" tenantHeader: "X-StellaOps-Tenant"
plugins: plugins:
baseDirectory: "/var/opt/stellaops" baseDirectory: "/var/opt/stellaops"
directory: "plugins/notify" directory: "plugins/notify"
searchPatterns: searchPatterns:
- "StellaOps.Notify.Connectors.*.dll" - "StellaOps.Notify.Connectors.*.dll"
orderedPlugins: orderedPlugins:
- StellaOps.Notify.Connectors.Slack - StellaOps.Notify.Connectors.Slack
- StellaOps.Notify.Connectors.Teams - StellaOps.Notify.Connectors.Teams
- StellaOps.Notify.Connectors.Email - StellaOps.Notify.Connectors.Email
- StellaOps.Notify.Connectors.Webhook - StellaOps.Notify.Connectors.Webhook
telemetry: telemetry:
enableRequestLogging: true enableRequestLogging: true
minimumLogLevel: Warning minimumLogLevel: Warning
services: services:
authority: authority:
image: registry.stella-ops.org/stellaops/authority@sha256:5551a3269b7008cd5aceecf45df018c67459ed519557ccbe48b093b926a39bcc image: registry.stella-ops.org/stellaops/authority@sha256:5551a3269b7008cd5aceecf45df018c67459ed519557ccbe48b093b926a39bcc
service: service:
port: 8440 port: 8440
env: env:
STELLAOPS_AUTHORITY__ISSUER: "https://stellaops-authority:8440" STELLAOPS_AUTHORITY__ISSUER: "https://stellaops-authority:8440"
STELLAOPS_AUTHORITY__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017" STELLAOPS_AUTHORITY__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017"
STELLAOPS_AUTHORITY__ALLOWANONYMOUSFALLBACK: "false" STELLAOPS_AUTHORITY__ALLOWANONYMOUSFALLBACK: "false"
signer: signer:
image: registry.stella-ops.org/stellaops/signer@sha256:ddbbd664a42846cea6b40fca6465bc679b30f72851158f300d01a8571c5478fc image: registry.stella-ops.org/stellaops/signer@sha256:ddbbd664a42846cea6b40fca6465bc679b30f72851158f300d01a8571c5478fc
service: service:
port: 8441 port: 8441
env: env:
SIGNER__AUTHORITY__BASEURL: "https://stellaops-authority:8440" SIGNER__AUTHORITY__BASEURL: "https://stellaops-authority:8440"
SIGNER__POE__INTROSPECTURL: "file:///offline/poe/introspect.json" SIGNER__POE__INTROSPECTURL: "file:///offline/poe/introspect.json"
SIGNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017" SIGNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017"
attestor: attestor:
image: registry.stella-ops.org/stellaops/attestor@sha256:1ff0a3124d66d3a2702d8e421df40fbd98cc75cb605d95510598ebbae1433c50 image: registry.stella-ops.org/stellaops/attestor@sha256:1ff0a3124d66d3a2702d8e421df40fbd98cc75cb605d95510598ebbae1433c50
service: service:
port: 8442 port: 8442
env: env:
ATTESTOR__SIGNER__BASEURL: "https://stellaops-signer:8441" ATTESTOR__SIGNER__BASEURL: "https://stellaops-signer:8441"
ATTESTOR__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017" ATTESTOR__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017"
concelier: concelier:
image: registry.stella-ops.org/stellaops/concelier@sha256:29e2e1a0972707e092cbd3d370701341f9fec2aa9316fb5d8100480f2a1c76b5 image: registry.stella-ops.org/stellaops/concelier@sha256:29e2e1a0972707e092cbd3d370701341f9fec2aa9316fb5d8100480f2a1c76b5
service: service:
port: 8445 port: 8445
env: env:
CONCELIER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017" CONCELIER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017"
CONCELIER__STORAGE__S3__ENDPOINT: "http://stellaops-minio:9000" CONCELIER__STORAGE__S3__ENDPOINT: "http://stellaops-minio:9000"
CONCELIER__STORAGE__S3__ACCESSKEYID: "stellaops-airgap" CONCELIER__STORAGE__S3__ACCESSKEYID: "stellaops-airgap"
CONCELIER__STORAGE__S3__SECRETACCESSKEY: "airgap-minio-secret" CONCELIER__STORAGE__S3__SECRETACCESSKEY: "airgap-minio-secret"
CONCELIER__AUTHORITY__BASEURL: "https://stellaops-authority:8440" CONCELIER__AUTHORITY__BASEURL: "https://stellaops-authority:8440"
CONCELIER__AUTHORITY__RESILIENCE__ALLOWOFFLINECACHEFALLBACK: "true" CONCELIER__AUTHORITY__RESILIENCE__ALLOWOFFLINECACHEFALLBACK: "true"
CONCELIER__AUTHORITY__RESILIENCE__OFFLINECACHETOLERANCE: "00:45:00" CONCELIER__AUTHORITY__RESILIENCE__OFFLINECACHETOLERANCE: "00:45:00"
volumeMounts: volumeMounts:
- name: concelier-jobs - name: concelier-jobs
mountPath: /var/lib/concelier/jobs mountPath: /var/lib/concelier/jobs
volumeClaims: volumeClaims:
- name: concelier-jobs - name: concelier-jobs
claimName: stellaops-concelier-jobs claimName: stellaops-concelier-jobs
scanner-web: scanner-web:
image: registry.stella-ops.org/stellaops/scanner-web@sha256:3df8ca21878126758203c1a0444e39fd97f77ddacf04a69685cda9f1e5e94718 image: registry.stella-ops.org/stellaops/scanner-web@sha256:3df8ca21878126758203c1a0444e39fd97f77ddacf04a69685cda9f1e5e94718
service: service:
port: 8444 port: 8444
env: env:
SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017" SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017"
SCANNER__ARTIFACTSTORE__DRIVER: "rustfs" SCANNER__ARTIFACTSTORE__DRIVER: "rustfs"
@@ -110,8 +110,12 @@ 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:
SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017" SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017"
SCANNER__ARTIFACTSTORE__DRIVER: "rustfs" SCANNER__ARTIFACTSTORE__DRIVER: "rustfs"
@@ -125,62 +129,66 @@ 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"
notify-web: SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
image: registry.stella-ops.org/stellaops/notify-web:2025.09.2 SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
service: SCANNER_SURFACE_SECRETS_PROVIDER: "file"
port: 8446 SCANNER_SURFACE_SECRETS_ROOT: "/etc/stellaops/secrets"
env: notify-web:
DOTNET_ENVIRONMENT: Production image: registry.stella-ops.org/stellaops/notify-web:2025.09.2
configMounts: service:
- name: notify-config port: 8446
mountPath: /app/etc/notify.yaml env:
subPath: notify.yaml DOTNET_ENVIRONMENT: Production
configMap: notify-config configMounts:
excititor: - name: notify-config
image: registry.stella-ops.org/stellaops/excititor@sha256:65c0ee13f773efe920d7181512349a09d363ab3f3e177d276136bd2742325a68 mountPath: /app/etc/notify.yaml
env: subPath: notify.yaml
EXCITITOR__CONCELIER__BASEURL: "https://stellaops-concelier:8445" configMap: notify-config
EXCITITOR__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017" excititor:
web-ui: image: registry.stella-ops.org/stellaops/excititor@sha256:65c0ee13f773efe920d7181512349a09d363ab3f3e177d276136bd2742325a68
image: registry.stella-ops.org/stellaops/web-ui@sha256:bee9668011ff414572131dc777faab4da24473fe12c230893f161cabee092a1d env:
service: EXCITITOR__CONCELIER__BASEURL: "https://stellaops-concelier:8445"
port: 9443 EXCITITOR__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-airgap:stellaops-airgap@stellaops-mongo:27017"
targetPort: 8443 web-ui:
env: image: registry.stella-ops.org/stellaops/web-ui@sha256:bee9668011ff414572131dc777faab4da24473fe12c230893f161cabee092a1d
STELLAOPS_UI__BACKEND__BASEURL: "https://stellaops-scanner-web:8444" service:
mongo: port: 9443
class: infrastructure targetPort: 8443
image: docker.io/library/mongo@sha256:c258b26dbb7774f97f52aff52231ca5f228273a84329c5f5e451c3739457db49 env:
service: STELLAOPS_UI__BACKEND__BASEURL: "https://stellaops-scanner-web:8444"
port: 27017 mongo:
command: class: infrastructure
- mongod image: docker.io/library/mongo@sha256:c258b26dbb7774f97f52aff52231ca5f228273a84329c5f5e451c3739457db49
- --bind_ip_all service:
env: port: 27017
MONGO_INITDB_ROOT_USERNAME: stellaops-airgap command:
MONGO_INITDB_ROOT_PASSWORD: stellaops-airgap - mongod
volumeMounts: - --bind_ip_all
- name: mongo-data env:
mountPath: /data/db MONGO_INITDB_ROOT_USERNAME: stellaops-airgap
volumeClaims: MONGO_INITDB_ROOT_PASSWORD: stellaops-airgap
- name: mongo-data volumeMounts:
claimName: stellaops-mongo-data - name: mongo-data
mountPath: /data/db
volumeClaims:
- name: mongo-data
claimName: stellaops-mongo-data
minio: minio:
class: infrastructure class: infrastructure
image: docker.io/minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e image: docker.io/minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e
service: service:
port: 9000 port: 9000
command: command:
- server - server
- /data - /data
- --console-address - --console-address
- :9001 - :9001
env: env:
MINIO_ROOT_USER: stellaops-airgap MINIO_ROOT_USER: stellaops-airgap
MINIO_ROOT_PASSWORD: airgap-minio-secret MINIO_ROOT_PASSWORD: airgap-minio-secret
volumeMounts: volumeMounts:
- name: minio-data - name: minio-data
mountPath: /data mountPath: /data
volumeClaims: volumeClaims:
- name: minio-data - name: minio-data
claimName: stellaops-minio-data claimName: stellaops-minio-data
@@ -204,18 +212,18 @@ services:
volumeClaims: volumeClaims:
- name: rustfs-data - name: rustfs-data
claimName: stellaops-rustfs-data claimName: stellaops-rustfs-data
nats: nats:
class: infrastructure class: infrastructure
image: docker.io/library/nats@sha256:c82559e4476289481a8a5196e675ebfe67eea81d95e5161e3e78eccfe766608e image: docker.io/library/nats@sha256:c82559e4476289481a8a5196e675ebfe67eea81d95e5161e3e78eccfe766608e
service: service:
port: 4222 port: 4222
command: command:
- -js - -js
- -sd - -sd
- /data - /data
volumeMounts: volumeMounts:
- name: nats-data - name: nats-data
mountPath: /data mountPath: /data
volumeClaims: volumeClaims:
- name: nats-data - name: nats-data
claimName: stellaops-nats-data claimName: stellaops-nats-data

View File

@@ -17,92 +17,92 @@ telemetry:
secretName: stellaops-otel-tls secretName: stellaops-otel-tls
configMaps: configMaps:
notify-config: notify-config:
data: data:
notify.yaml: | notify.yaml: |
storage: storage:
driver: mongo driver: mongo
connectionString: "mongodb://notify-mongo.dev.svc.cluster.local:27017" connectionString: "mongodb://notify-mongo.dev.svc.cluster.local:27017"
database: "stellaops_notify_dev" database: "stellaops_notify_dev"
commandTimeoutSeconds: 30 commandTimeoutSeconds: 30
authority: authority:
enabled: true enabled: true
issuer: "https://authority.dev.stella-ops.local" issuer: "https://authority.dev.stella-ops.local"
metadataAddress: "https://authority.dev.stella-ops.local/.well-known/openid-configuration" metadataAddress: "https://authority.dev.stella-ops.local/.well-known/openid-configuration"
requireHttpsMetadata: false requireHttpsMetadata: false
allowAnonymousFallback: false allowAnonymousFallback: false
backchannelTimeoutSeconds: 30 backchannelTimeoutSeconds: 30
tokenClockSkewSeconds: 60 tokenClockSkewSeconds: 60
audiences: audiences:
- notify.dev - notify.dev
readScope: notify.read readScope: notify.read
adminScope: notify.admin adminScope: notify.admin
api: api:
basePath: "/api/v1/notify" basePath: "/api/v1/notify"
internalBasePath: "/internal/notify" internalBasePath: "/internal/notify"
tenantHeader: "X-StellaOps-Tenant" tenantHeader: "X-StellaOps-Tenant"
plugins: plugins:
baseDirectory: "../" baseDirectory: "../"
directory: "plugins/notify" directory: "plugins/notify"
searchPatterns: searchPatterns:
- "StellaOps.Notify.Connectors.*.dll" - "StellaOps.Notify.Connectors.*.dll"
orderedPlugins: orderedPlugins:
- StellaOps.Notify.Connectors.Slack - StellaOps.Notify.Connectors.Slack
- StellaOps.Notify.Connectors.Teams - StellaOps.Notify.Connectors.Teams
- StellaOps.Notify.Connectors.Email - StellaOps.Notify.Connectors.Email
- StellaOps.Notify.Connectors.Webhook - StellaOps.Notify.Connectors.Webhook
telemetry: telemetry:
enableRequestLogging: true enableRequestLogging: true
minimumLogLevel: Debug minimumLogLevel: Debug
services: services:
authority: authority:
image: registry.stella-ops.org/stellaops/authority@sha256:a8e8faec44a579aa5714e58be835f25575710430b1ad2ccd1282a018cd9ffcdd image: registry.stella-ops.org/stellaops/authority@sha256:a8e8faec44a579aa5714e58be835f25575710430b1ad2ccd1282a018cd9ffcdd
service: service:
port: 8440 port: 8440
env: env:
STELLAOPS_AUTHORITY__ISSUER: "https://stellaops-authority:8440" STELLAOPS_AUTHORITY__ISSUER: "https://stellaops-authority:8440"
STELLAOPS_AUTHORITY__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017" STELLAOPS_AUTHORITY__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017"
STELLAOPS_AUTHORITY__PLUGINDIRECTORIES__0: "/app/plugins" STELLAOPS_AUTHORITY__PLUGINDIRECTORIES__0: "/app/plugins"
STELLAOPS_AUTHORITY__PLUGINS__CONFIGURATIONDIRECTORY: "/app/etc/authority.plugins" STELLAOPS_AUTHORITY__PLUGINS__CONFIGURATIONDIRECTORY: "/app/etc/authority.plugins"
signer: signer:
image: registry.stella-ops.org/stellaops/signer@sha256:8bfef9a75783883d49fc18e3566553934e970b00ee090abee9cb110d2d5c3298 image: registry.stella-ops.org/stellaops/signer@sha256:8bfef9a75783883d49fc18e3566553934e970b00ee090abee9cb110d2d5c3298
service: service:
port: 8441 port: 8441
env: env:
SIGNER__AUTHORITY__BASEURL: "https://stellaops-authority:8440" SIGNER__AUTHORITY__BASEURL: "https://stellaops-authority:8440"
SIGNER__POE__INTROSPECTURL: "https://licensing.svc.local/introspect" SIGNER__POE__INTROSPECTURL: "https://licensing.svc.local/introspect"
SIGNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017" SIGNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017"
attestor: attestor:
image: registry.stella-ops.org/stellaops/attestor@sha256:5cc417948c029da01dccf36e4645d961a3f6d8de7e62fe98d845f07cd2282114 image: registry.stella-ops.org/stellaops/attestor@sha256:5cc417948c029da01dccf36e4645d961a3f6d8de7e62fe98d845f07cd2282114
service: service:
port: 8442 port: 8442
env: env:
ATTESTOR__SIGNER__BASEURL: "https://stellaops-signer:8441" ATTESTOR__SIGNER__BASEURL: "https://stellaops-signer:8441"
ATTESTOR__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017" ATTESTOR__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017"
concelier: concelier:
image: registry.stella-ops.org/stellaops/concelier@sha256:dafef3954eb4b837e2c424dd2d23e1e4d60fa83794840fac9cd3dea1d43bd085 image: registry.stella-ops.org/stellaops/concelier@sha256:dafef3954eb4b837e2c424dd2d23e1e4d60fa83794840fac9cd3dea1d43bd085
service: service:
port: 8445 port: 8445
env: env:
CONCELIER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017" CONCELIER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017"
CONCELIER__STORAGE__S3__ENDPOINT: "http://stellaops-minio:9000" CONCELIER__STORAGE__S3__ENDPOINT: "http://stellaops-minio:9000"
CONCELIER__STORAGE__S3__ACCESSKEYID: "stellaops" CONCELIER__STORAGE__S3__ACCESSKEYID: "stellaops"
CONCELIER__STORAGE__S3__SECRETACCESSKEY: "dev-minio-secret" CONCELIER__STORAGE__S3__SECRETACCESSKEY: "dev-minio-secret"
CONCELIER__AUTHORITY__BASEURL: "https://stellaops-authority:8440" CONCELIER__AUTHORITY__BASEURL: "https://stellaops-authority:8440"
volumeMounts: volumeMounts:
- name: concelier-jobs - name: concelier-jobs
mountPath: /var/lib/concelier/jobs mountPath: /var/lib/concelier/jobs
volumes: volumes:
- name: concelier-jobs - name: concelier-jobs
emptyDir: {} emptyDir: {}
scanner-web: scanner-web:
image: registry.stella-ops.org/stellaops/scanner-web@sha256:e0dfdb087e330585a5953029fb4757f5abdf7610820a085bd61b457dbead9a11 image: registry.stella-ops.org/stellaops/scanner-web@sha256:e0dfdb087e330585a5953029fb4757f5abdf7610820a085bd61b457dbead9a11
service: service:
port: 8444 port: 8444
env: env:
SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017" SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017"
SCANNER__ARTIFACTSTORE__DRIVER: "rustfs" SCANNER__ARTIFACTSTORE__DRIVER: "rustfs"
@@ -116,8 +116,12 @@ 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:
SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017" SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017"
SCANNER__ARTIFACTSTORE__DRIVER: "rustfs" SCANNER__ARTIFACTSTORE__DRIVER: "rustfs"
@@ -131,61 +135,65 @@ 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"
notify-web: SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
image: registry.stella-ops.org/stellaops/notify-web:2025.10.0-edge SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
service: SCANNER_SURFACE_SECRETS_PROVIDER: "inline"
port: 8446 SCANNER_SURFACE_SECRETS_ROOT: ""
env: notify-web:
DOTNET_ENVIRONMENT: Development image: registry.stella-ops.org/stellaops/notify-web:2025.10.0-edge
configMounts: service:
- name: notify-config port: 8446
mountPath: /app/etc/notify.yaml env:
subPath: notify.yaml DOTNET_ENVIRONMENT: Development
configMap: notify-config configMounts:
excititor: - name: notify-config
image: registry.stella-ops.org/stellaops/excititor@sha256:d9bd5cadf1eab427447ce3df7302c30ded837239771cc6433b9befb895054285 mountPath: /app/etc/notify.yaml
env: subPath: notify.yaml
EXCITITOR__CONCELIER__BASEURL: "https://stellaops-concelier:8445" configMap: notify-config
EXCITITOR__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017" excititor:
web-ui: image: registry.stella-ops.org/stellaops/excititor@sha256:d9bd5cadf1eab427447ce3df7302c30ded837239771cc6433b9befb895054285
image: registry.stella-ops.org/stellaops/web-ui@sha256:38b225fa7767a5b94ebae4dae8696044126aac429415e93de514d5dd95748dcf env:
service: EXCITITOR__CONCELIER__BASEURL: "https://stellaops-concelier:8445"
port: 8443 EXCITITOR__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops:stellaops@stellaops-mongo:27017"
env: web-ui:
STELLAOPS_UI__BACKEND__BASEURL: "https://stellaops-scanner-web:8444" image: registry.stella-ops.org/stellaops/web-ui@sha256:38b225fa7767a5b94ebae4dae8696044126aac429415e93de514d5dd95748dcf
mongo: service:
class: infrastructure port: 8443
image: docker.io/library/mongo@sha256:c258b26dbb7774f97f52aff52231ca5f228273a84329c5f5e451c3739457db49 env:
service: STELLAOPS_UI__BACKEND__BASEURL: "https://stellaops-scanner-web:8444"
port: 27017 mongo:
command: class: infrastructure
- mongod image: docker.io/library/mongo@sha256:c258b26dbb7774f97f52aff52231ca5f228273a84329c5f5e451c3739457db49
- --bind_ip_all service:
env: port: 27017
MONGO_INITDB_ROOT_USERNAME: stellaops command:
MONGO_INITDB_ROOT_PASSWORD: stellaops - mongod
volumeMounts: - --bind_ip_all
- name: mongo-data env:
mountPath: /data/db MONGO_INITDB_ROOT_USERNAME: stellaops
volumes: MONGO_INITDB_ROOT_PASSWORD: stellaops
- name: mongo-data volumeMounts:
emptyDir: {} - name: mongo-data
mountPath: /data/db
volumes:
- name: mongo-data
emptyDir: {}
minio: minio:
class: infrastructure class: infrastructure
image: docker.io/minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e image: docker.io/minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e
service: service:
port: 9000 port: 9000
command: command:
- server - server
- /data - /data
- --console-address - --console-address
- :9001 - :9001
env: env:
MINIO_ROOT_USER: stellaops MINIO_ROOT_USER: stellaops
MINIO_ROOT_PASSWORD: dev-minio-secret MINIO_ROOT_PASSWORD: dev-minio-secret
volumeMounts: volumeMounts:
- name: minio-data - name: minio-data
mountPath: /data mountPath: /data
volumes: volumes:
- name: minio-data - name: minio-data
emptyDir: {} emptyDir: {}
@@ -203,18 +211,18 @@ services:
volumes: volumes:
- name: rustfs-data - name: rustfs-data
emptyDir: {} emptyDir: {}
nats: nats:
class: infrastructure class: infrastructure
image: docker.io/library/nats@sha256:c82559e4476289481a8a5196e675ebfe67eea81d95e5161e3e78eccfe766608e image: docker.io/library/nats@sha256:c82559e4476289481a8a5196e675ebfe67eea81d95e5161e3e78eccfe766608e
service: service:
port: 4222 port: 4222
command: command:
- -js - -js
- -sd - -sd
- /data - /data
volumeMounts: volumeMounts:
- name: nats-data - name: nats-data
mountPath: /data mountPath: /data
volumes: volumes:
- name: nats-data - name: nats-data
emptyDir: {} emptyDir: {}

View File

@@ -1,221 +1,229 @@
global: global:
profile: prod profile: prod
release: release:
version: "2025.09.2" version: "2025.09.2"
channel: stable channel: stable
manifestSha256: "dc3c8fe1ab83941c838ccc5a8a5862f7ddfa38c2078e580b5649db26554565b7" manifestSha256: "dc3c8fe1ab83941c838ccc5a8a5862f7ddfa38c2078e580b5649db26554565b7"
image: image:
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
labels: labels:
stellaops.io/channel: stable stellaops.io/channel: stable
stellaops.io/profile: prod stellaops.io/profile: prod
configMaps: configMaps:
notify-config: notify-config:
data: data:
notify.yaml: | notify.yaml: |
storage: storage:
driver: mongo driver: mongo
connectionString: "mongodb://stellaops-mongo:27017" connectionString: "mongodb://stellaops-mongo:27017"
database: "stellaops_notify_prod" database: "stellaops_notify_prod"
commandTimeoutSeconds: 45 commandTimeoutSeconds: 45
authority: authority:
enabled: true enabled: true
issuer: "https://authority.prod.stella-ops.org" issuer: "https://authority.prod.stella-ops.org"
metadataAddress: "https://authority.prod.stella-ops.org/.well-known/openid-configuration" metadataAddress: "https://authority.prod.stella-ops.org/.well-known/openid-configuration"
requireHttpsMetadata: true requireHttpsMetadata: true
allowAnonymousFallback: false allowAnonymousFallback: false
backchannelTimeoutSeconds: 30 backchannelTimeoutSeconds: 30
tokenClockSkewSeconds: 60 tokenClockSkewSeconds: 60
audiences: audiences:
- notify - notify
readScope: notify.read readScope: notify.read
adminScope: notify.admin adminScope: notify.admin
api: api:
basePath: "/api/v1/notify" basePath: "/api/v1/notify"
internalBasePath: "/internal/notify" internalBasePath: "/internal/notify"
tenantHeader: "X-StellaOps-Tenant" tenantHeader: "X-StellaOps-Tenant"
plugins: plugins:
baseDirectory: "/opt/stellaops" baseDirectory: "/opt/stellaops"
directory: "plugins/notify" directory: "plugins/notify"
searchPatterns: searchPatterns:
- "StellaOps.Notify.Connectors.*.dll" - "StellaOps.Notify.Connectors.*.dll"
orderedPlugins: orderedPlugins:
- StellaOps.Notify.Connectors.Slack - StellaOps.Notify.Connectors.Slack
- StellaOps.Notify.Connectors.Teams - StellaOps.Notify.Connectors.Teams
- StellaOps.Notify.Connectors.Email - StellaOps.Notify.Connectors.Email
- StellaOps.Notify.Connectors.Webhook - StellaOps.Notify.Connectors.Webhook
telemetry: telemetry:
enableRequestLogging: true enableRequestLogging: true
minimumLogLevel: Information minimumLogLevel: Information
services: services:
authority: authority:
image: registry.stella-ops.org/stellaops/authority@sha256:b0348bad1d0b401cc3c71cb40ba034c8043b6c8874546f90d4783c9dbfcc0bf5 image: registry.stella-ops.org/stellaops/authority@sha256:b0348bad1d0b401cc3c71cb40ba034c8043b6c8874546f90d4783c9dbfcc0bf5
service: service:
port: 8440 port: 8440
env: env:
STELLAOPS_AUTHORITY__ISSUER: "https://authority.prod.stella-ops.org" STELLAOPS_AUTHORITY__ISSUER: "https://authority.prod.stella-ops.org"
STELLAOPS_AUTHORITY__PLUGINDIRECTORIES__0: "/app/plugins" STELLAOPS_AUTHORITY__PLUGINDIRECTORIES__0: "/app/plugins"
STELLAOPS_AUTHORITY__PLUGINS__CONFIGURATIONDIRECTORY: "/app/etc/authority.plugins" STELLAOPS_AUTHORITY__PLUGINS__CONFIGURATIONDIRECTORY: "/app/etc/authority.plugins"
envFrom: envFrom:
- secretRef: - secretRef:
name: stellaops-prod-core name: stellaops-prod-core
signer: signer:
image: registry.stella-ops.org/stellaops/signer@sha256:8ad574e61f3a9e9bda8a58eb2700ae46813284e35a150b1137bc7c2b92ac0f2e image: registry.stella-ops.org/stellaops/signer@sha256:8ad574e61f3a9e9bda8a58eb2700ae46813284e35a150b1137bc7c2b92ac0f2e
service: service:
port: 8441 port: 8441
env: env:
SIGNER__AUTHORITY__BASEURL: "https://stellaops-authority:8440" SIGNER__AUTHORITY__BASEURL: "https://stellaops-authority:8440"
SIGNER__POE__INTROSPECTURL: "https://licensing.prod.stella-ops.org/introspect" SIGNER__POE__INTROSPECTURL: "https://licensing.prod.stella-ops.org/introspect"
envFrom: envFrom:
- secretRef: - secretRef:
name: stellaops-prod-core name: stellaops-prod-core
attestor: attestor:
image: registry.stella-ops.org/stellaops/attestor@sha256:0534985f978b0b5d220d73c96fddd962cd9135f616811cbe3bff4666c5af568f image: registry.stella-ops.org/stellaops/attestor@sha256:0534985f978b0b5d220d73c96fddd962cd9135f616811cbe3bff4666c5af568f
service: service:
port: 8442 port: 8442
env: env:
ATTESTOR__SIGNER__BASEURL: "https://stellaops-signer:8441" ATTESTOR__SIGNER__BASEURL: "https://stellaops-signer:8441"
envFrom: envFrom:
- secretRef: - secretRef:
name: stellaops-prod-core name: stellaops-prod-core
concelier: concelier:
image: registry.stella-ops.org/stellaops/concelier@sha256:c58cdcaee1d266d68d498e41110a589dd204b487d37381096bd61ab345a867c5 image: registry.stella-ops.org/stellaops/concelier@sha256:c58cdcaee1d266d68d498e41110a589dd204b487d37381096bd61ab345a867c5
service: service:
port: 8445 port: 8445
env: env:
CONCELIER__STORAGE__S3__ENDPOINT: "http://stellaops-minio:9000" CONCELIER__STORAGE__S3__ENDPOINT: "http://stellaops-minio:9000"
CONCELIER__AUTHORITY__BASEURL: "https://stellaops-authority:8440" CONCELIER__AUTHORITY__BASEURL: "https://stellaops-authority:8440"
envFrom: envFrom:
- secretRef: - secretRef:
name: stellaops-prod-core name: stellaops-prod-core
volumeMounts: volumeMounts:
- name: concelier-jobs - name: concelier-jobs
mountPath: /var/lib/concelier/jobs mountPath: /var/lib/concelier/jobs
volumeClaims: volumeClaims:
- name: concelier-jobs - name: concelier-jobs
claimName: stellaops-concelier-jobs claimName: stellaops-concelier-jobs
scanner-web: scanner-web:
image: registry.stella-ops.org/stellaops/scanner-web@sha256:14b23448c3f9586a9156370b3e8c1991b61907efa666ca37dd3aaed1e79fe3b7 image: registry.stella-ops.org/stellaops/scanner-web@sha256:14b23448c3f9586a9156370b3e8c1991b61907efa666ca37dd3aaed1e79fe3b7
service: service:
port: 8444 port: 8444
env: env:
SCANNER__ARTIFACTSTORE__DRIVER: "rustfs" SCANNER__ARTIFACTSTORE__DRIVER: "rustfs"
SCANNER__ARTIFACTSTORE__ENDPOINT: "http://stellaops-rustfs:8080/api/v1" SCANNER__ARTIFACTSTORE__ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
SCANNER__ARTIFACTSTORE__BUCKET: "scanner-artifacts" SCANNER__ARTIFACTSTORE__BUCKET: "scanner-artifacts"
SCANNER__ARTIFACTSTORE__TIMEOUTSECONDS: "30" SCANNER__ARTIFACTSTORE__TIMEOUTSECONDS: "30"
SCANNER__QUEUE__BROKER: "nats://stellaops-nats:4222" SCANNER__QUEUE__BROKER: "nats://stellaops-nats:4222"
SCANNER__EVENTS__ENABLED: "true" SCANNER__EVENTS__ENABLED: "true"
SCANNER__EVENTS__DRIVER: "redis" SCANNER__EVENTS__DRIVER: "redis"
SCANNER__EVENTS__DSN: "" SCANNER__EVENTS__DSN: ""
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"
envFrom: SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
- secretRef: SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
name: stellaops-prod-core SCANNER_SURFACE_SECRETS_PROVIDER: "kubernetes"
scanner-worker: SCANNER_SURFACE_SECRETS_ROOT: "stellaops/scanner"
image: registry.stella-ops.org/stellaops/scanner-worker@sha256:32e25e76386eb9ea8bee0a1ad546775db9a2df989fab61ac877e351881960dab envFrom:
replicas: 3 - secretRef:
env: name: stellaops-prod-core
SCANNER__ARTIFACTSTORE__DRIVER: "rustfs" scanner-worker:
SCANNER__ARTIFACTSTORE__ENDPOINT: "http://stellaops-rustfs:8080/api/v1" image: registry.stella-ops.org/stellaops/scanner-worker@sha256:32e25e76386eb9ea8bee0a1ad546775db9a2df989fab61ac877e351881960dab
SCANNER__ARTIFACTSTORE__BUCKET: "scanner-artifacts" replicas: 3
SCANNER__ARTIFACTSTORE__TIMEOUTSECONDS: "30" env:
SCANNER__QUEUE__BROKER: "nats://stellaops-nats:4222" SCANNER__ARTIFACTSTORE__DRIVER: "rustfs"
SCANNER__EVENTS__ENABLED: "true" SCANNER__ARTIFACTSTORE__ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
SCANNER__EVENTS__DRIVER: "redis" SCANNER__ARTIFACTSTORE__BUCKET: "scanner-artifacts"
SCANNER__EVENTS__DSN: "" SCANNER__ARTIFACTSTORE__TIMEOUTSECONDS: "30"
SCANNER__EVENTS__STREAM: "stella.events" SCANNER__QUEUE__BROKER: "nats://stellaops-nats:4222"
SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5" SCANNER__EVENTS__ENABLED: "true"
SCANNER__EVENTS__MAXSTREAMLENGTH: "10000" SCANNER__EVENTS__DRIVER: "redis"
envFrom: SCANNER__EVENTS__DSN: ""
- secretRef: SCANNER__EVENTS__STREAM: "stella.events"
name: stellaops-prod-core SCANNER__EVENTS__PUBLISHTIMEOUTSECONDS: "5"
notify-web: SCANNER__EVENTS__MAXSTREAMLENGTH: "10000"
image: registry.stella-ops.org/stellaops/notify-web:2025.09.2 SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
service: SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
port: 8446 SCANNER_SURFACE_SECRETS_PROVIDER: "kubernetes"
env: SCANNER_SURFACE_SECRETS_ROOT: "stellaops/scanner"
DOTNET_ENVIRONMENT: Production envFrom:
envFrom: - secretRef:
- secretRef: name: stellaops-prod-core
name: stellaops-prod-notify notify-web:
configMounts: image: registry.stella-ops.org/stellaops/notify-web:2025.09.2
- name: notify-config service:
mountPath: /app/etc/notify.yaml port: 8446
subPath: notify.yaml env:
configMap: notify-config DOTNET_ENVIRONMENT: Production
excititor: envFrom:
image: registry.stella-ops.org/stellaops/excititor@sha256:59022e2016aebcef5c856d163ae705755d3f81949d41195256e935ef40a627fa - secretRef:
env: name: stellaops-prod-notify
EXCITITOR__CONCELIER__BASEURL: "https://stellaops-concelier:8445" configMounts:
envFrom: - name: notify-config
- secretRef: mountPath: /app/etc/notify.yaml
name: stellaops-prod-core subPath: notify.yaml
web-ui: configMap: notify-config
image: registry.stella-ops.org/stellaops/web-ui@sha256:10d924808c48e4353e3a241da62eb7aefe727a1d6dc830eb23a8e181013b3a23 excititor:
service: image: registry.stella-ops.org/stellaops/excititor@sha256:59022e2016aebcef5c856d163ae705755d3f81949d41195256e935ef40a627fa
port: 8443 env:
env: EXCITITOR__CONCELIER__BASEURL: "https://stellaops-concelier:8445"
STELLAOPS_UI__BACKEND__BASEURL: "https://stellaops-scanner-web:8444" envFrom:
mongo: - secretRef:
class: infrastructure name: stellaops-prod-core
image: docker.io/library/mongo@sha256:c258b26dbb7774f97f52aff52231ca5f228273a84329c5f5e451c3739457db49 web-ui:
service: image: registry.stella-ops.org/stellaops/web-ui@sha256:10d924808c48e4353e3a241da62eb7aefe727a1d6dc830eb23a8e181013b3a23
port: 27017 service:
command: port: 8443
- mongod env:
- --bind_ip_all STELLAOPS_UI__BACKEND__BASEURL: "https://stellaops-scanner-web:8444"
envFrom: mongo:
- secretRef: class: infrastructure
name: stellaops-prod-mongo image: docker.io/library/mongo@sha256:c258b26dbb7774f97f52aff52231ca5f228273a84329c5f5e451c3739457db49
volumeMounts: service:
- name: mongo-data port: 27017
mountPath: /data/db command:
volumeClaims: - mongod
- name: mongo-data - --bind_ip_all
claimName: stellaops-mongo-data envFrom:
minio: - secretRef:
class: infrastructure name: stellaops-prod-mongo
image: docker.io/minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e volumeMounts:
service: - name: mongo-data
port: 9000 mountPath: /data/db
command: volumeClaims:
- server - name: mongo-data
- /data claimName: stellaops-mongo-data
- --console-address minio:
- :9001 class: infrastructure
envFrom: image: docker.io/minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e
- secretRef: service:
name: stellaops-prod-minio port: 9000
volumeMounts: command:
- name: minio-data - server
mountPath: /data - /data
volumeClaims: - --console-address
- name: minio-data - :9001
claimName: stellaops-minio-data envFrom:
rustfs: - secretRef:
class: infrastructure name: stellaops-prod-minio
image: registry.stella-ops.org/stellaops/rustfs:2025.10.0-edge volumeMounts:
service: - name: minio-data
port: 8080 mountPath: /data
command: volumeClaims:
- serve - name: minio-data
- --listen claimName: stellaops-minio-data
- 0.0.0.0:8080 rustfs:
- --root class: infrastructure
- /data image: registry.stella-ops.org/stellaops/rustfs:2025.10.0-edge
env: service:
RUSTFS__LOG__LEVEL: info port: 8080
RUSTFS__STORAGE__PATH: /data command:
volumeMounts: - serve
- name: rustfs-data - --listen
mountPath: /data - 0.0.0.0:8080
volumeClaims: - --root
- name: rustfs-data - /data
claimName: stellaops-rustfs-data env:
RUSTFS__LOG__LEVEL: info
RUSTFS__STORAGE__PATH: /data
volumeMounts:
- name: rustfs-data
mountPath: /data
volumeClaims:
- name: rustfs-data
claimName: stellaops-rustfs-data

View File

@@ -2,10 +2,10 @@ global:
profile: stage profile: stage
release: release:
version: "2025.09.2" version: "2025.09.2"
channel: stable channel: stable
manifestSha256: "dc3c8fe1ab83941c838ccc5a8a5862f7ddfa38c2078e580b5649db26554565b7" manifestSha256: "dc3c8fe1ab83941c838ccc5a8a5862f7ddfa38c2078e580b5649db26554565b7"
image: image:
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
labels: labels:
stellaops.io/channel: stable stellaops.io/channel: stable
@@ -15,94 +15,94 @@ telemetry:
defaultTenant: stage defaultTenant: stage
tls: tls:
secretName: stellaops-otel-tls-stage secretName: stellaops-otel-tls-stage
configMaps: configMaps:
notify-config: notify-config:
data: data:
notify.yaml: | notify.yaml: |
storage: storage:
driver: mongo driver: mongo
connectionString: "mongodb://notify-mongo.stage.svc.cluster.local:27017" connectionString: "mongodb://notify-mongo.stage.svc.cluster.local:27017"
database: "stellaops_notify_stage" database: "stellaops_notify_stage"
commandTimeoutSeconds: 45 commandTimeoutSeconds: 45
authority: authority:
enabled: true enabled: true
issuer: "https://authority.stage.stella-ops.org" issuer: "https://authority.stage.stella-ops.org"
metadataAddress: "https://authority.stage.stella-ops.org/.well-known/openid-configuration" metadataAddress: "https://authority.stage.stella-ops.org/.well-known/openid-configuration"
requireHttpsMetadata: true requireHttpsMetadata: true
allowAnonymousFallback: false allowAnonymousFallback: false
backchannelTimeoutSeconds: 30 backchannelTimeoutSeconds: 30
tokenClockSkewSeconds: 60 tokenClockSkewSeconds: 60
audiences: audiences:
- notify - notify
readScope: notify.read readScope: notify.read
adminScope: notify.admin adminScope: notify.admin
api: api:
basePath: "/api/v1/notify" basePath: "/api/v1/notify"
internalBasePath: "/internal/notify" internalBasePath: "/internal/notify"
tenantHeader: "X-StellaOps-Tenant" tenantHeader: "X-StellaOps-Tenant"
plugins: plugins:
baseDirectory: "/opt/stellaops" baseDirectory: "/opt/stellaops"
directory: "plugins/notify" directory: "plugins/notify"
searchPatterns: searchPatterns:
- "StellaOps.Notify.Connectors.*.dll" - "StellaOps.Notify.Connectors.*.dll"
orderedPlugins: orderedPlugins:
- StellaOps.Notify.Connectors.Slack - StellaOps.Notify.Connectors.Slack
- StellaOps.Notify.Connectors.Teams - StellaOps.Notify.Connectors.Teams
- StellaOps.Notify.Connectors.Email - StellaOps.Notify.Connectors.Email
- StellaOps.Notify.Connectors.Webhook - StellaOps.Notify.Connectors.Webhook
telemetry: telemetry:
enableRequestLogging: true enableRequestLogging: true
minimumLogLevel: Information minimumLogLevel: Information
services: services:
authority: authority:
image: registry.stella-ops.org/stellaops/authority@sha256:b0348bad1d0b401cc3c71cb40ba034c8043b6c8874546f90d4783c9dbfcc0bf5 image: registry.stella-ops.org/stellaops/authority@sha256:b0348bad1d0b401cc3c71cb40ba034c8043b6c8874546f90d4783c9dbfcc0bf5
service: service:
port: 8440 port: 8440
env: env:
STELLAOPS_AUTHORITY__ISSUER: "https://stellaops-authority:8440" STELLAOPS_AUTHORITY__ISSUER: "https://stellaops-authority:8440"
STELLAOPS_AUTHORITY__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017" STELLAOPS_AUTHORITY__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017"
STELLAOPS_AUTHORITY__PLUGINDIRECTORIES__0: "/app/plugins" STELLAOPS_AUTHORITY__PLUGINDIRECTORIES__0: "/app/plugins"
STELLAOPS_AUTHORITY__PLUGINS__CONFIGURATIONDIRECTORY: "/app/etc/authority.plugins" STELLAOPS_AUTHORITY__PLUGINS__CONFIGURATIONDIRECTORY: "/app/etc/authority.plugins"
signer: signer:
image: registry.stella-ops.org/stellaops/signer@sha256:8ad574e61f3a9e9bda8a58eb2700ae46813284e35a150b1137bc7c2b92ac0f2e image: registry.stella-ops.org/stellaops/signer@sha256:8ad574e61f3a9e9bda8a58eb2700ae46813284e35a150b1137bc7c2b92ac0f2e
service: service:
port: 8441 port: 8441
env: env:
SIGNER__AUTHORITY__BASEURL: "https://stellaops-authority:8440" SIGNER__AUTHORITY__BASEURL: "https://stellaops-authority:8440"
SIGNER__POE__INTROSPECTURL: "https://licensing.stage.stella-ops.internal/introspect" SIGNER__POE__INTROSPECTURL: "https://licensing.stage.stella-ops.internal/introspect"
SIGNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017" SIGNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017"
attestor: attestor:
image: registry.stella-ops.org/stellaops/attestor@sha256:0534985f978b0b5d220d73c96fddd962cd9135f616811cbe3bff4666c5af568f image: registry.stella-ops.org/stellaops/attestor@sha256:0534985f978b0b5d220d73c96fddd962cd9135f616811cbe3bff4666c5af568f
service: service:
port: 8442 port: 8442
env: env:
ATTESTOR__SIGNER__BASEURL: "https://stellaops-signer:8441" ATTESTOR__SIGNER__BASEURL: "https://stellaops-signer:8441"
ATTESTOR__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017" ATTESTOR__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017"
concelier: concelier:
image: registry.stella-ops.org/stellaops/concelier@sha256:c58cdcaee1d266d68d498e41110a589dd204b487d37381096bd61ab345a867c5 image: registry.stella-ops.org/stellaops/concelier@sha256:c58cdcaee1d266d68d498e41110a589dd204b487d37381096bd61ab345a867c5
service: service:
port: 8445 port: 8445
env: env:
CONCELIER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017" CONCELIER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017"
CONCELIER__STORAGE__S3__ENDPOINT: "http://stellaops-minio:9000" CONCELIER__STORAGE__S3__ENDPOINT: "http://stellaops-minio:9000"
CONCELIER__STORAGE__S3__ACCESSKEYID: "stellaops-stage" CONCELIER__STORAGE__S3__ACCESSKEYID: "stellaops-stage"
CONCELIER__STORAGE__S3__SECRETACCESSKEY: "stage-minio-secret" CONCELIER__STORAGE__S3__SECRETACCESSKEY: "stage-minio-secret"
CONCELIER__AUTHORITY__BASEURL: "https://stellaops-authority:8440" CONCELIER__AUTHORITY__BASEURL: "https://stellaops-authority:8440"
volumeMounts: volumeMounts:
- name: concelier-jobs - name: concelier-jobs
mountPath: /var/lib/concelier/jobs mountPath: /var/lib/concelier/jobs
volumeClaims: volumeClaims:
- name: concelier-jobs - name: concelier-jobs
claimName: stellaops-concelier-jobs claimName: stellaops-concelier-jobs
scanner-web: scanner-web:
image: registry.stella-ops.org/stellaops/scanner-web@sha256:14b23448c3f9586a9156370b3e8c1991b61907efa666ca37dd3aaed1e79fe3b7 image: registry.stella-ops.org/stellaops/scanner-web@sha256:14b23448c3f9586a9156370b3e8c1991b61907efa666ca37dd3aaed1e79fe3b7
service: service:
port: 8444 port: 8444
env: env:
SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017" SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017"
SCANNER__ARTIFACTSTORE__DRIVER: "rustfs" SCANNER__ARTIFACTSTORE__DRIVER: "rustfs"
@@ -116,9 +116,13 @@ 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
env: env:
SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017" SCANNER__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017"
SCANNER__ARTIFACTSTORE__DRIVER: "rustfs" SCANNER__ARTIFACTSTORE__DRIVER: "rustfs"
@@ -132,61 +136,65 @@ 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"
notify-web: SCANNER_SURFACE_FS_ENDPOINT: "http://stellaops-rustfs:8080/api/v1"
image: registry.stella-ops.org/stellaops/notify-web:2025.09.2 SCANNER_SURFACE_CACHE_ROOT: "/var/lib/stellaops/surface"
service: SCANNER_SURFACE_SECRETS_PROVIDER: "kubernetes"
port: 8446 SCANNER_SURFACE_SECRETS_ROOT: "stellaops/scanner"
env: notify-web:
DOTNET_ENVIRONMENT: Production image: registry.stella-ops.org/stellaops/notify-web:2025.09.2
configMounts: service:
- name: notify-config port: 8446
mountPath: /app/etc/notify.yaml env:
subPath: notify.yaml DOTNET_ENVIRONMENT: Production
configMap: notify-config configMounts:
excititor: - name: notify-config
image: registry.stella-ops.org/stellaops/excititor@sha256:59022e2016aebcef5c856d163ae705755d3f81949d41195256e935ef40a627fa mountPath: /app/etc/notify.yaml
env: subPath: notify.yaml
EXCITITOR__CONCELIER__BASEURL: "https://stellaops-concelier:8445" configMap: notify-config
EXCITITOR__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017" excititor:
web-ui: image: registry.stella-ops.org/stellaops/excititor@sha256:59022e2016aebcef5c856d163ae705755d3f81949d41195256e935ef40a627fa
image: registry.stella-ops.org/stellaops/web-ui@sha256:10d924808c48e4353e3a241da62eb7aefe727a1d6dc830eb23a8e181013b3a23 env:
service: EXCITITOR__CONCELIER__BASEURL: "https://stellaops-concelier:8445"
port: 8443 EXCITITOR__STORAGE__MONGO__CONNECTIONSTRING: "mongodb://stellaops-stage:stellaops-stage@stellaops-mongo:27017"
env: web-ui:
STELLAOPS_UI__BACKEND__BASEURL: "https://stellaops-scanner-web:8444" image: registry.stella-ops.org/stellaops/web-ui@sha256:10d924808c48e4353e3a241da62eb7aefe727a1d6dc830eb23a8e181013b3a23
mongo: service:
class: infrastructure port: 8443
image: docker.io/library/mongo@sha256:c258b26dbb7774f97f52aff52231ca5f228273a84329c5f5e451c3739457db49 env:
service: STELLAOPS_UI__BACKEND__BASEURL: "https://stellaops-scanner-web:8444"
port: 27017 mongo:
command: class: infrastructure
- mongod image: docker.io/library/mongo@sha256:c258b26dbb7774f97f52aff52231ca5f228273a84329c5f5e451c3739457db49
- --bind_ip_all service:
env: port: 27017
MONGO_INITDB_ROOT_USERNAME: stellaops-stage command:
MONGO_INITDB_ROOT_PASSWORD: stellaops-stage - mongod
volumeMounts: - --bind_ip_all
- name: mongo-data env:
mountPath: /data/db MONGO_INITDB_ROOT_USERNAME: stellaops-stage
volumeClaims: MONGO_INITDB_ROOT_PASSWORD: stellaops-stage
- name: mongo-data volumeMounts:
claimName: stellaops-mongo-data - name: mongo-data
mountPath: /data/db
volumeClaims:
- name: mongo-data
claimName: stellaops-mongo-data
minio: minio:
class: infrastructure class: infrastructure
image: docker.io/minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e image: docker.io/minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e
service: service:
port: 9000 port: 9000
command: command:
- server - server
- /data - /data
- --console-address - --console-address
- :9001 - :9001
env: env:
MINIO_ROOT_USER: stellaops-stage MINIO_ROOT_USER: stellaops-stage
MINIO_ROOT_PASSWORD: stage-minio-secret MINIO_ROOT_PASSWORD: stage-minio-secret
volumeMounts: volumeMounts:
- name: minio-data - name: minio-data
mountPath: /data mountPath: /data
volumeClaims: volumeClaims:
- name: minio-data - name: minio-data
claimName: stellaops-minio-data claimName: stellaops-minio-data
@@ -210,18 +218,18 @@ services:
volumeClaims: volumeClaims:
- name: rustfs-data - name: rustfs-data
claimName: stellaops-rustfs-data claimName: stellaops-rustfs-data
nats: nats:
class: infrastructure class: infrastructure
image: docker.io/library/nats@sha256:c82559e4476289481a8a5196e675ebfe67eea81d95e5161e3e78eccfe766608e image: docker.io/library/nats@sha256:c82559e4476289481a8a5196e675ebfe67eea81d95e5161e3e78eccfe766608e
service: service:
port: 4222 port: 4222
command: command:
- -js - -js
- -sd - -sd
- /data - /data
volumeMounts: volumeMounts:
- name: nats-data - name: nats-data
mountPath: /data mountPath: /data
volumeClaims: volumeClaims:
- name: nats-data - name: nats-data
claimName: stellaops-nats-data claimName: stellaops-nats-data

View File

@@ -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.

View File

@@ -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. |

View File

@@ -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)

View File

@@ -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)

View File

@@ -81,11 +81,12 @@ Summary: Scheduling & Automation focus on Scheduler (phase I).
Task ID | State | Task description | Owners (Source) Task ID | State | Task description | Owners (Source)
--- | --- | --- | --- --- | --- | --- | ---
SCHED-CONSOLE-23-001 | DONE (2025-11-03) | Extend runs APIs with live progress SSE endpoints (`/console/runs/{id}/stream`), queue lag summaries, diff metadata fetch, retry/cancel hooks with RBAC enforcement, and deterministic pagination for history views consumed by Console. | Scheduler WebService Guild, BE-Base Platform Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md) SCHED-CONSOLE-23-001 | DONE (2025-11-03) | Extend runs APIs with live progress SSE endpoints (`/console/runs/{id}/stream`), queue lag summaries, diff metadata fetch, retry/cancel hooks with RBAC enforcement, and deterministic pagination for history views consumed by Console. | Scheduler WebService Guild, BE-Base Platform Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
SCHED-CONSOLE-27-001 | DONE (2025-11-03) | Provide policy batch simulation orchestration endpoints (`/policies/simulations` POST/GET) exposing run creation, shard status, SSE progress, cancellation, and retries with RBAC enforcement. Dependencies: SCHED-CONSOLE-23-001. | Scheduler WebService Guild, Policy Registry Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md) SCHED-CONSOLE-27-001 | DONE (2025-11-03) | Provide policy batch simulation orchestration endpoints (`/policies/simulations` POST/GET) exposing run creation, shard status, SSE progress, cancellation, and retries with RBAC enforcement. Dependencies: SCHED-CONSOLE-23-001. | Scheduler WebService Guild, Policy Registry 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) 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`).
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) > 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-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)
SCHED-VULN-29-002 | TODO | Provide projector lag metrics endpoint and webhook notifications for backlog breaches consumed by DevOps dashboards. Dependencies: SCHED-VULN-29-001. | Scheduler WebService Guild, Observability Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md) SCHED-VULN-29-002 | TODO | Provide projector lag metrics endpoint and webhook notifications for backlog breaches consumed by DevOps dashboards. Dependencies: SCHED-VULN-29-001. | Scheduler WebService Guild, Observability Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -1,181 +1,181 @@
# Aggregation-Only Contract Reference # Aggregation-Only Contract Reference
> The Aggregation-Only Contract (AOC) is the governing rule set that keeps StellaOps ingestion services deterministic, policy-neutral, and auditable. It applies to Concelier, Excititor, and any future collectors that write raw advisory or VEX documents. > The Aggregation-Only Contract (AOC) is the governing rule set that keeps StellaOps ingestion services deterministic, policy-neutral, and auditable. It applies to Concelier, Excititor, and any future collectors that write raw advisory or VEX documents.
## 1. Purpose and Scope ## 1. Purpose and Scope
- Defines the canonical behaviour for `advisory_raw` and `vex_raw` collections and the linkset hints they may emit. - Defines the canonical behaviour for `advisory_raw` and `vex_raw` collections and the linkset hints they may emit.
- Applies to every ingestion runtime (`StellaOps.Concelier.*`, `StellaOps.Excititor.*`), the Authority scopes that guard them, and the DevOps/QA surfaces that verify compliance. - Applies to every ingestion runtime (`StellaOps.Concelier.*`, `StellaOps.Excititor.*`), the Authority scopes that guard them, and the DevOps/QA surfaces that verify compliance.
- Complements the high-level architecture in [Concelier](../modules/concelier/architecture.md) and Authority enforcement documented in [Authority Architecture](../modules/authority/architecture.md). - Complements the high-level architecture in [Concelier](../modules/concelier/architecture.md) and Authority enforcement documented in [Authority Architecture](../modules/authority/architecture.md).
- Paired guidance: see the guard-rail checkpoints in [AOC Guardrails](../aoc/aoc-guardrails.md), the implementation reference in [AOC Guard Library](../aoc/guard-library.md), and CLI usage that will land in `/docs/modules/cli/guides/` as part of Sprint 19 follow-up. - Paired guidance: see the guard-rail checkpoints in [AOC Guardrails](../aoc/aoc-guardrails.md), the implementation reference in [AOC Guard Library](../aoc/guard-library.md), and CLI usage that will land in `/docs/modules/cli/guides/` as part of Sprint 19 follow-up.
## 2. Philosophy and Goals ## 2. Philosophy and Goals
- Preserve upstream truth: ingestion only captures immutable raw facts plus provenance, never derived severity or policy decisions. - Preserve upstream truth: ingestion only captures immutable raw facts plus provenance, never derived severity or policy decisions.
- Defer interpretation: Policy Engine and downstream overlays remain the sole writers of materialised findings, severity, consensus, or risk scores. - Defer interpretation: Policy Engine and downstream overlays remain the sole writers of materialised findings, severity, consensus, or risk scores.
- Make every write explainable: provenance, signatures, and content hashes are required so operators can prove where each fact originated. - Make every write explainable: provenance, signatures, and content hashes are required so operators can prove where each fact originated.
- Keep outputs reproducible: identical inputs must yield identical documents, hashes, and linksets across replays and air-gapped installs. - Keep outputs reproducible: identical inputs must yield identical documents, hashes, and linksets across replays and air-gapped installs.
## 3. Contract Invariants ## 3. Contract Invariants
| # | Invariant | What it forbids or requires | Enforcement surfaces | | # | Invariant | What it forbids or requires | Enforcement surfaces |
|---|-----------|-----------------------------|----------------------| |---|-----------|-----------------------------|----------------------|
| 1 | No derived severity at ingest | Reject top-level keys such as `severity`, `cvss`, `effective_status`, `consensus_provider`, `risk_score`. Raw upstream CVSS remains inside `content.raw`. | Mongo schema validator, `AOCWriteGuard`, Roslyn analyzer, `stella aoc verify`. | | 1 | No derived severity at ingest | Reject top-level keys such as `severity`, `cvss`, `effective_status`, `consensus_provider`, `risk_score`. Raw upstream CVSS remains inside `content.raw`. | Mongo schema validator, `AOCWriteGuard`, Roslyn analyzer, `stella aoc verify`. |
| 2 | No merges or opinionated dedupe | Each upstream document persists on its own; ingestion never collapses multiple vendors into one document. | Repository interceptors, unit/fixture suites. | | 2 | No merges or opinionated dedupe | Each upstream document persists on its own; ingestion never collapses multiple vendors into one document. | Repository interceptors, unit/fixture suites. |
| 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. |
## 4. Raw Schemas ## 4. Raw Schemas
### 4.1 `advisory_raw` ### 4.1 `advisory_raw`
| Field | Type | Notes | | Field | Type | Notes |
|-------|------|-------| |-------|------|-------|
| `_id` | string | `advisory_raw:{source}:{upstream_id}:{revision}`; deterministic and tenant-scoped. | | `_id` | string | `advisory_raw:{source}:{upstream_id}:{revision}`; deterministic and tenant-scoped. |
| `tenant` | string | Required; injected by Authority middleware and asserted by schema validator. | | `tenant` | string | Required; injected by Authority middleware and asserted by schema validator. |
| `source.vendor` | string | Provider identifier (e.g., `redhat`, `osv`, `ghsa`). | | `source.vendor` | string | Provider identifier (e.g., `redhat`, `osv`, `ghsa`). |
| `source.stream` | string | Connector stream name (`csaf`, `osv`, etc.). | | `source.stream` | string | Connector stream name (`csaf`, `osv`, etc.). |
| `source.api` | string | Absolute URI of upstream document; stored for traceability. | | `source.api` | string | Absolute URI of upstream document; stored for traceability. |
| `source.collector_version` | string | Semantic version of the collector. | | `source.collector_version` | string | Semantic version of the collector. |
| `upstream.upstream_id` | string | Vendor- or ecosystem-provided identifier (CVE, GHSA, vendor ID). | | `upstream.upstream_id` | string | Vendor- or ecosystem-provided identifier (CVE, GHSA, vendor ID). |
| `upstream.document_version` | string | Upstream issued timestamp or revision string. | | `upstream.document_version` | string | Upstream issued timestamp or revision string. |
| `upstream.fetched_at` / `received_at` | string | ISO 8601 UTC timestamps recorded by the collector. | | `upstream.fetched_at` / `received_at` | string | ISO 8601 UTC timestamps recorded by the collector. |
| `upstream.content_hash` | string | `sha256:` digest of the raw payload used for idempotency. | | `upstream.content_hash` | string | `sha256:` digest of the raw payload used for idempotency. |
| `upstream.signature` | object | Required structure storing `present`, `format`, `key_id`, `sig`; even unsigned payloads set `present: false`. | | `upstream.signature` | object | Required structure storing `present`, `format`, `key_id`, `sig`; even unsigned payloads set `present: false`. |
| `content.format` | string | Source format (`CSAF`, `OSV`, etc.). | | `content.format` | string | Source format (`CSAF`, `OSV`, etc.). |
| `content.spec_version` | string | Upstream spec version when known. | | `content.spec_version` | string | Upstream spec version when known. |
| `content.raw` | object | Full upstream payload, untouched except for transport normalisation. | | `content.raw` | object | Full upstream payload, untouched except for transport normalisation. |
| `identifiers` | object | Upstream identifiers (`cve`, `ghsa`, `aliases`, etc.) captured as provided (trimmed, order preserved, duplicates allowed). | | `identifiers` | object | Upstream identifiers (`cve`, `ghsa`, `aliases`, etc.) captured as provided (trimmed, order preserved, duplicates allowed). |
| `linkset` | object | Join hints (see section 4.3). | | `linkset` | object | Join hints (see section 4.3). |
| `supersedes` | string or null | Points to previous revision of same upstream doc when content hash changes. | | `supersedes` | string or null | Points to previous revision of same upstream doc when content hash changes. |
### 4.2 `vex_raw` ### 4.2 `vex_raw`
| Field | Type | Notes | | Field | Type | Notes |
|-------|------|-------| |-------|------|-------|
| `_id` | string | `vex_raw:{source}:{upstream_id}:{revision}`. | | `_id` | string | `vex_raw:{source}:{upstream_id}:{revision}`. |
| `tenant` | string | Required; matches advisory collection requirements. | | `tenant` | string | Required; matches advisory collection requirements. |
| `source.*` | object | Same shape and requirements as `advisory_raw`. | | `source.*` | object | Same shape and requirements as `advisory_raw`. |
| `upstream.*` | object | Includes `document_version`, timestamps, `content_hash`, and `signature`. | | `upstream.*` | object | Includes `document_version`, timestamps, `content_hash`, and `signature`. |
| `content.format` | string | Typically `CycloneDX-VEX` or `CSAF-VEX`. | | `content.format` | string | Typically `CycloneDX-VEX` or `CSAF-VEX`. |
| `content.raw` | object | Entire upstream VEX payload. | | `content.raw` | object | Entire upstream VEX payload. |
| `identifiers.statements` | array | Normalised statement summaries (IDs, PURLs, status, justification) to accelerate policy joins. | | `identifiers.statements` | array | Normalised statement summaries (IDs, PURLs, status, justification) to accelerate policy joins. |
| `linkset` | object | CVEs, GHSA IDs, and PURLs referenced in the document. | | `linkset` | object | CVEs, GHSA IDs, and PURLs referenced in the document. |
| `supersedes` | string or null | Same convention as advisory documents. | | `supersedes` | string or null | Same convention as advisory documents. |
### 4.3 Linkset Fields ### 4.3 Linkset Fields
- `purls`: fully qualified Package URLs extracted from raw ranges or product nodes. - `purls`: fully qualified Package URLs extracted from raw ranges or product nodes.
- `cpes`: Common Platform Enumerations when upstream docs provide them. - `cpes`: Common Platform Enumerations when upstream docs provide them.
- `aliases`: Any alternate advisory identifiers present in the payload. - `aliases`: Any alternate advisory identifiers present in the payload.
- `references`: Array of `{ type, url }` pairs pointing back to vendor advisories, patches, or exploits. - `references`: Array of `{ type, url }` pairs pointing back to vendor advisories, patches, or exploits.
- `reconciled_from`: Provenance of linkset entries (JSON Pointer or field origin) to make automated checks auditable. - `reconciled_from`: Provenance of linkset entries (JSON Pointer or field origin) to make automated checks auditable.
Canonicalisation rules: Canonicalisation rules:
- Package URLs are rendered in canonical form without qualifiers/subpaths (`pkg:type/namespace/name@version`). - Package URLs are rendered in canonical form without qualifiers/subpaths (`pkg:type/namespace/name@version`).
- CPE values are normalised to the 2.3 binding (`cpe:2.3:part:vendor:product:version:*:*:*:*:*:*:*`). - CPE values are normalised to the 2.3 binding (`cpe:2.3:part:vendor:product:version:*:*:*:*:*:*:*`).
- Connector mapping stages are responsible for the canonical form; ingestion trims whitespace but otherwise preserves the original order and duplicate entries so downstream policy can reason about upstream intent. - Connector mapping stages are responsible for the canonical form; ingestion trims whitespace but otherwise preserves the original order and duplicate entries so downstream policy can reason about upstream intent.
### 4.4 `advisory_observations` ### 4.4 `advisory_observations`
`advisory_observations` is an immutable projection of the validated raw document used by LinkNotMerge overlays. Fields mirror the JSON contract surfaced by `StellaOps.Concelier.Models.Observations.AdvisoryObservation`. `advisory_observations` is an immutable projection of the validated raw document used by LinkNotMerge overlays. Fields mirror the JSON contract surfaced by `StellaOps.Concelier.Models.Observations.AdvisoryObservation`.
| Field | Type | Notes | | Field | Type | Notes |
|-------|------|-------| |-------|------|-------|
| `_id` | string | Deterministic observation id — `{tenant}:{source.vendor}:{upstreamId}:{revision}`. | | `_id` | string | Deterministic observation id — `{tenant}:{source.vendor}:{upstreamId}:{revision}`. |
| `tenant` | string | Lower-case tenant identifier. | | `tenant` | string | Lower-case tenant identifier. |
| `source.vendor` / `source.stream` | string | Connector identity (e.g., `vendor/redhat`, `ecosystem/osv`). | | `source.vendor` / `source.stream` | string | Connector identity (e.g., `vendor/redhat`, `ecosystem/osv`). |
| `source.api` | string | Absolute URI the connector fetched from. | | `source.api` | string | Absolute URI the connector fetched from. |
| `source.collectorVersion` | string | Optional semantic version of the connector build. | | `source.collectorVersion` | string | Optional semantic version of the connector build. |
| `upstream.upstream_id` | string | Advisory identifier as issued by the provider (CVE, vendor ID, etc.). | | `upstream.upstream_id` | string | Advisory identifier as issued by the provider (CVE, vendor ID, etc.). |
| `upstream.document_version` | string | Upstream revision/version string. | | `upstream.document_version` | string | Upstream revision/version string. |
| `upstream.fetchedAt` / `upstream.receivedAt` | datetime | UTC timestamps recorded by the connector. | | `upstream.fetchedAt` / `upstream.receivedAt` | datetime | UTC timestamps recorded by the connector. |
| `upstream.contentHash` | string | `sha256:` digest used for idempotency. | | `upstream.contentHash` | string | `sha256:` digest used for idempotency. |
| `upstream.signature` | object | `{present, format?, keyId?, signature?}` describing upstream signature material. | | `upstream.signature` | object | `{present, format?, keyId?, signature?}` describing upstream signature material. |
| `content.format` / `content.specVersion` | string | Raw payload format metadata (CSAF, OSV, JSON, etc.). | | `content.format` / `content.specVersion` | string | Raw payload format metadata (CSAF, OSV, JSON, etc.). |
| `content.raw` | object | Full upstream document stored losslessly (Relaxed Extended JSON). | | `content.raw` | object | Full upstream document stored losslessly (Relaxed Extended JSON). |
| `content.metadata` | object | Optional connector-specific metadata (batch ids, hints). | | `content.metadata` | object | Optional connector-specific metadata (batch ids, hints). |
| `linkset.aliases` | array | Connector-supplied aliases (trimmed, order preserved, duplicates allowed). | | `linkset.aliases` | array | Connector-supplied aliases (trimmed, order preserved, duplicates allowed). |
| `linkset.purls` | array | Connector-supplied PURLs (ingestion preserves order and duplicates). | | `linkset.purls` | array | Connector-supplied PURLs (ingestion preserves order and duplicates). |
| `linkset.cpes` | array | Connector-supplied CPE URIs (trimmed, order preserved). | | `linkset.cpes` | array | Connector-supplied CPE URIs (trimmed, order preserved). |
| `linkset.references` | array | `{ type, url }` pairs (trimmed; ingestion preserves order). | | `linkset.references` | array | `{ type, url }` pairs (trimmed; ingestion preserves order). |
| `createdAt` | datetime | Timestamp when Concelier persisted the observation. | | `createdAt` | datetime | Timestamp when Concelier persisted the observation. |
| `attributes` | object | Optional provenance attributes keyed by connector. | | `attributes` | object | Optional provenance attributes keyed by connector. |
## 5. Error Model ## 5. Error Model
| Code | Description | HTTP status | Surfaces | | Code | Description | HTTP status | Surfaces |
|------|-------------|-------------|----------| |------|-------------|-------------|----------|
| `ERR_AOC_001` | Forbidden field detected (severity, cvss, effective data). | 400 | Ingestion APIs, CLI verifier, CI guard. | | `ERR_AOC_001` | Forbidden field detected (severity, cvss, effective data). | 400 | Ingestion APIs, CLI verifier, CI guard. |
| `ERR_AOC_002` | Merge attempt detected (multiple upstream sources fused into one document). | 400 | Ingestion APIs, CLI verifier. | | `ERR_AOC_002` | Merge attempt detected (multiple upstream sources fused into one document). | 400 | Ingestion APIs, CLI verifier. |
| `ERR_AOC_003` | Idempotency violation (duplicate without supersedes pointer). | 409 | Repository guard, Mongo unique index, CLI verifier. | | `ERR_AOC_003` | Idempotency violation (duplicate without supersedes pointer). | 409 | Repository guard, Mongo unique index, CLI verifier. |
| `ERR_AOC_004` | Missing provenance metadata (`source`, `upstream`, `signature`). | 422 | Schema validator, ingestion endpoints. | | `ERR_AOC_004` | Missing provenance metadata (`source`, `upstream`, `signature`). | 422 | Schema validator, ingestion endpoints. |
| `ERR_AOC_005` | Signature or checksum mismatch. | 422 | Collector validation, CLI verifier. | | `ERR_AOC_005` | Signature or checksum mismatch. | 422 | Collector validation, CLI verifier. |
| `ERR_AOC_006` | Attempt to persist derived findings from ingestion context. | 403 | Policy engine guard, Authority scopes. | | `ERR_AOC_006` | Attempt to persist derived findings from ingestion context. | 403 | Policy engine guard, Authority scopes. |
| `ERR_AOC_007` | Unknown top-level fields (schema violation). | 400 | Mongo validator, CLI verifier. | | `ERR_AOC_007` | Unknown top-level fields (schema violation). | 400 | Mongo validator, CLI verifier. |
Consumers should map these codes to CLI exit codes and structured log events so automation can fail fast and produce actionable guidance. Consumers should map these codes to CLI exit codes and structured log events so automation can fail fast and produce actionable guidance.
## 6. API and Tooling Interfaces ## 6. API and Tooling Interfaces
- **Concelier ingestion** (`StellaOps.Concelier.WebService`) - **Concelier ingestion** (`StellaOps.Concelier.WebService`)
- `POST /ingest/advisory`: accepts upstream payload metadata; server-side guard constructs and persists raw document. - `POST /ingest/advisory`: accepts upstream payload metadata; server-side guard constructs and persists raw document.
- `GET /advisories/raw/{id}` and filterable list endpoints expose raw documents for debugging and offline analysis. - `GET /advisories/raw/{id}` and filterable list endpoints expose raw documents for debugging and offline analysis.
- `POST /aoc/verify`: runs guard checks over recent documents and returns summary totals plus first violations. - `POST /aoc/verify`: runs guard checks over recent documents and returns summary totals plus first violations.
- **Excititor ingestion** (`StellaOps.Excititor.WebService`) mirrors the same surface for VEX documents. - **Excititor ingestion** (`StellaOps.Excititor.WebService`) mirrors the same surface for VEX documents.
- **CLI workflows** (`stella aoc verify`, `stella sources ingest --dry-run`) surface pre-flight verification; documentation will live in `/docs/modules/cli/guides/` alongside Sprint 19 CLI updates. - **CLI workflows** (`stella aoc verify`, `stella sources ingest --dry-run`) surface pre-flight verification; documentation will live in `/docs/modules/cli/guides/` alongside Sprint 19 CLI updates.
- **Authority scopes**: new `advisory:ingest`, `advisory:read`, `vex:ingest`, and `vex:read` scopes enforce least privilege; see [Authority Architecture](../modules/authority/architecture.md) for scope grammar. - **Authority scopes**: new `advisory:ingest`, `advisory:read`, `vex:ingest`, and `vex:read` scopes enforce least privilege; see [Authority Architecture](../modules/authority/architecture.md) for scope grammar.
## 7. Idempotency and Supersedes Rules ## 7. Idempotency and Supersedes Rules
1. Compute `content_hash` before any transformation; use it with `(source.vendor, upstream.upstream_id)` to detect duplicates. 1. Compute `content_hash` before any transformation; use it with `(source.vendor, upstream.upstream_id)` to detect duplicates.
2. If a document with the same hash already exists, skip the write and log a no-op. 2. If a document with the same hash already exists, skip the write and log a no-op.
3. When a new hash arrives for an existing upstream document, insert a new record and set `supersedes` to the previous `_id`. 3. When a new hash arrives for an existing upstream document, insert a new record and set `supersedes` to the previous `_id`.
4. Keep supersedes chains acyclic; collectors must resolve conflicts by rewinding before they insert. 4. Keep supersedes chains acyclic; collectors must resolve conflicts by rewinding before they insert.
5. Expose idempotency counters via metrics (`ingestion_write_total{result=ok|noop}`) to catch regressions early. 5. Expose idempotency counters via metrics (`ingestion_write_total{result=ok|noop}`) to catch regressions early.
## 8. Migration Playbook ## 8. Migration Playbook
1. Freeze ingestion writes except for raw pass-through paths while deploying schema validators. 1. Freeze ingestion writes except for raw pass-through paths while deploying schema validators.
2. Snapshot existing collections to `_backup_*` for rollback safety. 2. Snapshot existing collections to `_backup_*` for rollback safety.
3. Strip forbidden fields from historical documents into a temporary `advisory_view_legacy` used only during transition. 3. Strip forbidden fields from historical documents into a temporary `advisory_view_legacy` used only during transition.
4. Enable Mongo JSON schema validators for `advisory_raw` and `vex_raw`. 4. Enable Mongo JSON schema validators for `advisory_raw` and `vex_raw`.
5. Run collectors in `--dry-run` to confirm only allowed keys appear; fix violations before lifting the freeze. 5. Run collectors in `--dry-run` to confirm only allowed keys appear; fix violations before lifting the freeze.
6. Point Policy Engine to consume exclusively from raw collections and compute derived outputs downstream. 6. Point Policy Engine to consume exclusively from raw collections and compute derived outputs downstream.
7. Delete legacy normalisation paths from ingestion code and enable runtime guards plus CI linting. 7. Delete legacy normalisation paths from ingestion code and enable runtime guards plus CI linting.
8. Roll forward CLI, Console, and dashboards so operators can monitor AOC status end-to-end. 8. Roll forward CLI, Console, and dashboards so operators can monitor AOC status end-to-end.
## 9. Observability and Diagnostics ## 9. Observability and Diagnostics
- **Metrics**: `ingestion_write_total{result=ok|reject}`, `aoc_violation_total{code}`, `ingestion_signature_verified_total{result}`, `ingestion_latency_seconds`, `advisory_revision_count`. - **Metrics**: `ingestion_write_total{result=ok|reject}`, `aoc_violation_total{code}`, `ingestion_signature_verified_total{result}`, `ingestion_latency_seconds`, `advisory_revision_count`.
- **Traces**: spans `ingest.fetch`, `ingest.transform`, `ingest.write`, and `aoc.guard` with correlation IDs shared across workers. - **Traces**: spans `ingest.fetch`, `ingest.transform`, `ingest.write`, and `aoc.guard` with correlation IDs shared across workers.
- **Logs**: structured entries must include `tenant`, `source.vendor`, `upstream.upstream_id`, `content_hash`, and `violation_code` when applicable. - **Logs**: structured entries must include `tenant`, `source.vendor`, `upstream.upstream_id`, `content_hash`, and `violation_code` when applicable.
- **Dashboards**: DevOps should add panels for violation counts, signature failures, supersedes growth, and CLI verifier outcomes for each tenant. - **Dashboards**: DevOps should add panels for violation counts, signature failures, supersedes growth, and CLI verifier outcomes for each tenant.
## 10. Security and Tenancy Checklist ## 10. Security and Tenancy Checklist
- Enforce Authority scopes (`advisory:ingest`, `vex:ingest`, `advisory:read`, `vex:read`) and require tenant claims on every request. - Enforce Authority scopes (`advisory:ingest`, `vex:ingest`, `advisory:read`, `vex:read`) and require tenant claims on every request.
- Maintain pinned trust stores for signature verification; capture verification result in metrics and logs. - Maintain pinned trust stores for signature verification; capture verification result in metrics and logs.
- Ensure collectors never log secrets or raw authentication headers; redact tokens before persistence. - Ensure collectors never log secrets or raw authentication headers; redact tokens before persistence.
- Validate that Policy Engine remains the only identity with permission to write `effective_finding_*` documents. - Validate that Policy Engine remains the only identity with permission to write `effective_finding_*` documents.
- Verify offline bundles include the raw collections, guard configuration, and verifier binaries so air-gapped installs can audit parity. - Verify offline bundles include the raw collections, guard configuration, and verifier binaries so air-gapped installs can audit parity.
- Document operator steps for recovering from violations, including rollback to superseded revisions and re-running policy evaluation. - Document operator steps for recovering from violations, including rollback to superseded revisions and re-running policy evaluation.
## 11. Compliance Checklist ## 11. Compliance Checklist
- [ ] Deterministic guard enabled in Concelier and Excititor repositories. - [ ] Deterministic guard enabled in Concelier and Excititor repositories.
- [ ] Mongo validators deployed for `advisory_raw` and `vex_raw`. - [ ] Mongo validators deployed for `advisory_raw` and `vex_raw`.
- [ ] Authority scopes and tenant enforcement verified via integration tests. - [ ] Authority scopes and tenant enforcement verified via integration tests.
- [ ] CLI and CI pipelines run `stella aoc verify` against seeded snapshots. - [ ] CLI and CI pipelines run `stella aoc verify` against seeded snapshots.
- [ ] Observability feeds (metrics, logs, traces) wired into dashboards with alerts. - [ ] Observability feeds (metrics, logs, traces) wired into dashboards with alerts.
- [ ] Offline kit instructions updated to bundle validators and verifier tooling. - [ ] Offline kit instructions updated to bundle validators and verifier tooling.
- [ ] Security review recorded covering ingestion, tenancy, and rollback procedures. - [ ] Security review recorded covering ingestion, tenancy, and rollback procedures.
--- ---
*Last updated: 2025-10-27 (Sprint 19).* *Last updated: 2025-10-27 (Sprint 19).*

View File

@@ -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 Sprint110 (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 Sprint110 (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 Phase1 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 Phase2+ to prevent accidental restarts. | | `concelier:jobs:merge:allowlist` | `[]` | Explicit allowlist for Merge jobs when noMergeEnabled is `false`. | Set to empty during Phase2+ to prevent accidental restarts. |
| `policy:overlays:requireLinksetEvidence` | `false` | Policy engine safety net to require linkset-backed findings. | Flip to `true` only after cutover (Phase2). | | `policy:overlays:requireLinksetEvidence` | `false` | Policy engine safety net to require linkset-backed findings. | Flip to `true` only after cutover (Phase2). |
> 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

View File

@@ -1,11 +1,14 @@
# StellaOps Attestor # StellaOps Attestor
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.
## Why it exists ## Latest updates (2025-10-19)
- **Evidence first:** organisations need portable, verifiable attestations that prove build provenance, SBOM availability, policy verdicts, and VEX statements. - 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.
- **Policy enforcement:** verification policies ensure only approved issuers, key types, witnesses, and freshness windows are accepted.
- **Sovereign/offline-ready:** Attestor archives envelopes, signatures, and proofs so air-gapped deployments can replay verification without contacting external services. ## Why it exists
- **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.
- **Sovereign/offline-ready:** Attestor archives envelopes, signatures, and proofs so air-gapped deployments can replay verification without contacting external services.
## Roles & surfaces ## Roles & surfaces
- **Subjects:** immutable digests for container images, SBOMs, reports, and policy bundles. - **Subjects:** immutable digests for container images, SBOMs, reports, and policy bundles.

View File

@@ -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 |

View File

@@ -18,19 +18,22 @@ Concelier ingests signed advisories from dozens of sources and converts them int
- Policy Engine / Export Center / CLI for evidence consumption. - Policy Engine / Export Center / CLI for evidence consumption.
- Notify and UI for advisory deltas. - Notify and UI for advisory deltas.
## Operational notes ## Operational notes
- 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
- ./operations/conflict-resolution.md ## Related resources
- ./operations/mirror.md - ./operations/conflict-resolution.md
- ./operations/mirror.md
## Backlog references - ./operations/authority-audit-runbook.md
- DOCS-LNM-22-001, DOCS-LNM-22-007 in ../../TASKS.md. - ../../10_CONCELIER_CLI_QUICKSTART.md (authority integration timeline & smoke tests)
- Connector-specific TODOs in `src/Concelier/**/TASKS.md`.
## Backlog references
- DOCS-LNM-22-001, DOCS-LNM-22-007 in ../../TASKS.md.
- Connector-specific TODOs in `src/Concelier/**/TASKS.md`.
## Epic alignment ## Epic alignment
- **Epic 1 AOC enforcement:** uphold raw observation invariants, provenance requirements, linkset-only enrichment, and AOC verifier guardrails across every connector. - **Epic 1 AOC enforcement:** uphold raw observation invariants, provenance requirements, linkset-only enrichment, and AOC verifier guardrails across every connector.
- **Epic 10 Export Center:** expose deterministic advisory exports and metadata required by JSON/Trivy/mirror bundles. - **Epic 10 Export Center:** expose deterministic advisory exports and metadata required by JSON/Trivy/mirror bundles.

View File

@@ -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 |

View File

@@ -27,10 +27,12 @@
3. **Mandatory provenance.** Collectors record `source`, `upstream` metadata (`document_version`, `fetched_at`, `received_at`, `content_hash`), and signature presence before writing. 3. **Mandatory provenance.** Collectors record `source`, `upstream` metadata (`document_version`, `fetched_at`, `received_at`, `content_hash`), and signature presence before writing.
4. **Linkset only.** Derived joins (aliases, PURLs, CPEs, references) are stored inside `linkset` and never mutate `content.raw`. 4. **Linkset only.** Derived joins (aliases, PURLs, CPEs, references) are stored inside `linkset` and never mutate `content.raw`.
5. **Deterministic canonicalisation.** Writers use canonical JSON (sorted object keys, lexicographic arrays) ensuring identical inputs yield the same hashes/diff-friendly outputs. 5. **Deterministic canonicalisation.** Writers use canonical JSON (sorted object keys, lexicographic arrays) ensuring identical inputs yield the same hashes/diff-friendly outputs.
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.
### 1.1 Advisory raw document shape > 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
```json ```json
{ {

View File

@@ -1,33 +1,37 @@
# StellaOps Excititor # StellaOps Excititor
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.
## Responsibilities ## Latest updates (2025-11-05)
- Fetch OpenVEX/CSAF/CycloneDX statements via restart-only connectors. - 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)).
- Store immutable VEX observations with full provenance. - README now points policy/UI teams to the upcoming consensus integration work.
- Publish linksets and events that drive policy suppression decisions.
- Provide deterministic exports for Offline Kit and downstream tooling. ## Responsibilities
- Fetch OpenVEX/CSAF/CycloneDX statements via restart-only connectors.
## Key components - Store immutable VEX observations with full provenance.
- `StellaOps.Excititor.WebService` scheduler/API host. - Publish linksets and events that drive policy suppression decisions.
- Connector libraries under `StellaOps.Excititor.Connector.*`. - Provide deterministic exports for Offline Kit and downstream tooling.
- Normalization helpers and exporters in `StellaOps.Excititor.*`.
## Key components
## Integrations & dependencies - `StellaOps.Excititor.WebService` scheduler/API host.
- Policy Engine for evidence queries. - Connector libraries under `StellaOps.Excititor.Connector.*`.
- UI/CLI for conflict visibility and explanation. - Normalization helpers and exporters in `StellaOps.Excititor.*`.
- Notify for VEX-driven alerts.
## Integrations & dependencies
## Operational notes - Policy Engine for evidence queries.
- MongoDB for observation storage and job metadata. - UI/CLI for conflict visibility and explanation.
- Offline kit packaging aligned with Concelier merges. - Notify for VEX-driven alerts.
- Connector-specific runbooks (see `docs/modules/concelier/operations/connectors`).
## Operational notes
## Backlog references - MongoDB for observation storage and job metadata.
- DOCS-LNM-22-006 / DOCS-LNM-22-007 (shared with Concelier). - Offline kit packaging aligned with Concelier merges.
- CLI-EXC-25-001..002 follow-up for CLI parity. - Connector-specific runbooks (see `docs/modules/concelier/operations/connectors`).
## Epic alignment ## Backlog references
- **Epic 1 AOC enforcement:** maintain immutable VEX observations, provenance, and AOC verifier coverage. - DOCS-LNM-22-006 / DOCS-LNM-22-007 (shared with Concelier).
- **Epic 7 VEX Consensus Lens:** supply trustworthy raw inputs, trust metadata, and consensus hooks for the lens computations. - CLI-EXC-25-001..002 follow-up for CLI parity.
- **Epic 8 Advisory AI:** expose citation-ready VEX payloads for the advisory assistant pipeline.
## Epic alignment
- **Epic 1 AOC enforcement:** maintain immutable VEX observations, provenance, and AOC verifier coverage.
- **Epic 7 VEX Consensus Lens:** supply trustworthy raw inputs, trust metadata, and consensus hooks for the lens computations.
- **Epic 8 Advisory AI:** expose citation-ready VEX payloads for the advisory assistant pipeline.

View File

@@ -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 |

View File

@@ -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.

View File

@@ -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 |

View File

@@ -8,27 +8,35 @@ Export Center packages reproducible evidence bundles (JSON, Trivy DB, mirror) wi
- Stream bundles via HTTP/OCI and stage them for Offline Kit uses. - Stream bundles via HTTP/OCI and stage them for Offline Kit uses.
- Expose CLI/API surfaces for automation. - Expose CLI/API surfaces for automation.
## Key components ## Key components
- `StellaOps.ExportCenter.WebService` planner. - `StellaOps.ExportCenter.WebService` planner.
- `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.
## Integrations & dependencies ## Profiles at a glance
- Concelier/Excititor/Policy data stores for evidence. - **json:raw / json:policy** — Evidence bundles with raw ingestion facts or policy overlays.
- Signer/Attestor for provenance signing. - **trivy:db / trivy:java-db** — Trivy-compatible vulnerability feeds with deterministic manifests.
- CLI for operator-managed exports. - **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
- Concelier/Excititor/Policy data stores for evidence.
- Signer/Attestor for provenance signing.
- CLI for operator-managed exports.
## Operational notes ## Operational notes
- Runbooks in ./operations/ for deployment and monitoring. - Runbooks in ./operations/ for deployment and monitoring.
- Mirror bundle instructions and validation notes. - Mirror bundle instructions and validation notes.
- Telemetry dashboards for export latency and retry rates. - Telemetry dashboards for export latency and retry rates.
## Related resources ## Related resources
- ./operations/runbook.md - ./operations/runbook.md
- ./devportal-offline.md (bundle structure, verification workflow, DSSE signature details)
## Backlog references - ./provenance-and-signing.md (manifest/provenance schema, signing pipeline, verification)
- DOCS-EXPORT-35-001 … DOCS-EXPORT-37-002 in ../../TASKS.md.
- EXPORT-ATTEST-75-002 cross-team deliverable. ## Backlog references
- DOCS-EXPORT-35-001 … DOCS-EXPORT-37-002 in ../../TASKS.md.
- EXPORT-ATTEST-75-002 cross-team deliverable.
## Epic alignment ## Epic alignment
- **Epic 10 Export Center:** deliver canonical JSON, Trivy DB, and mirror bundle workflows with provenance, signatures, and offline parity. - **Epic 10 Export Center:** deliver canonical JSON, Trivy DB, and mirror bundle workflows with provenance, signatures, and offline parity.

View File

@@ -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 |

View File

@@ -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.

View File

@@ -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`.

View File

@@ -1,12 +1,15 @@
# StellaOps Source & Job Orchestrator # StellaOps Source & Job Orchestrator
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.
## Responsibilities ## Latest updates (2025-11-01)
- Track job state, throughput, and errors for Concelier, Excititor, Scheduler, and export pipelines. - 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.
- Expose dashboards and APIs for throttling, replays, and failover.
- Enforce rate-limits, concurrency and dependency chains across queues. ## Responsibilities
- Stream structured events and audit logs for incident response. - Track job state, throughput, and errors for Concelier, Excititor, Scheduler, and export pipelines.
- Expose dashboards and APIs for throttling, replays, and failover.
- Enforce rate-limits, concurrency and dependency chains across queues.
- Stream structured events and audit logs for incident response.
## Key components ## Key components
- Orchestrator WebService (control plane). - Orchestrator WebService (control plane).
@@ -19,10 +22,11 @@ The Orchestrator schedules, observes, and recovers ingestion and analysis jobs a
- Scheduler/Concelier/Excititor workers for job lifecycle. - Scheduler/Concelier/Excititor workers for job lifecycle.
- Offline Kit for state export/import during air-gap refreshes. - Offline Kit for state export/import during air-gap refreshes.
## Operational notes ## Operational notes
- 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.

View File

@@ -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 |

View File

@@ -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.

View File

@@ -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 |

View File

@@ -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

View File

@@ -1,21 +1,32 @@
# StellaOps Signer # StellaOps Signer
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 Sprint11 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.
- Export Center and CLI for artifact signing flows. - OCI registries (Referrers API) for scanner release verification.
- Attestor for transparency logging and Rekor ingestion.
- 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.

View File

@@ -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 |

View File

@@ -18,9 +18,10 @@ Telemetry module captures deployment and operations guidance for the shared obse
- Module-specific dashboards (scheduler, scanner, etc.). - Module-specific dashboards (scheduler, scanner, etc.).
- Security/Compliance for retention policies. - Security/Compliance for retention policies.
## 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

View File

@@ -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 |

View File

@@ -1,11 +1,14 @@
# StellaOps Vulnerability Explorer # StellaOps Vulnerability Explorer
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.
## Responsibilities ## Latest updates (2025-11-03)
- Present policy-evaluated findings with advisory, VEX, SBOM, and runtime context. - 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.
- Capture triage workflow in an immutable findings ledger with role-based access.
- Provide pivots, exports, and reports for auditors and operations teams. ## Responsibilities
- Present policy-evaluated findings with advisory, VEX, SBOM, and runtime context.
- Capture triage workflow in an immutable findings ledger with role-based access.
- Provide pivots, exports, and reports for auditors and operations teams.
- Integrate explain traces, remediation notes, and offline bundles. - Integrate explain traces, remediation notes, and offline bundles.
## Key components ## Key components

View File

@@ -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 |

View 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`).

View 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`.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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. |

View File

@@ -730,10 +730,11 @@ internal static class CommandFactory
}; };
activate.Add(activatePolicyIdArgument); activate.Add(activatePolicyIdArgument);
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")
{ {

View File

@@ -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

View File

@@ -1811,11 +1811,11 @@ spec:
} }
[Fact] [Fact]
public async Task HandlePolicyActivateAsync_PendingSecondApprovalSetsExitCode() public async Task HandlePolicyActivateAsync_PendingSecondApprovalSetsExitCode()
{ {
var originalExit = Environment.ExitCode; var originalExit = Environment.ExitCode;
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null)); var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
backend.ActivationResult = new PolicyActivationResult( backend.ActivationResult = new PolicyActivationResult(
"pending_second_approval", "pending_second_approval",
new PolicyActivationRevision( new PolicyActivationRevision(
@@ -1852,15 +1852,65 @@ spec:
finally finally
{ {
Environment.ExitCode = originalExit; Environment.ExitCode = originalExit;
} }
} }
[Fact] [Fact]
public async Task HandlePolicyActivateAsync_MapsErrorCodes() public async Task HandlePolicyActivateAsync_ParsesScheduledTimestamp()
{ {
var originalExit = Environment.ExitCode; var originalExit = Environment.ExitCode;
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
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]
public async Task HandlePolicyActivateAsync_MapsErrorCodes()
{
var originalExit = Environment.ExitCode;
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null))
{ {
ActivationException = new PolicyApiException("Revision not approved", HttpStatusCode.BadRequest, "ERR_POL_002") ActivationException = new PolicyApiException("Revision not approved", HttpStatusCode.BadRequest, "ERR_POL_002")
}; };

View File

@@ -52,9 +52,11 @@ internal static class JobRegistrationExtensions
new("source:vndr-oracle:parse", "StellaOps.Concelier.Connector.Vndr.Oracle.OracleParseJob", "StellaOps.Concelier.Connector.Vndr.Oracle", TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(5)), new("source:vndr-oracle:parse", "StellaOps.Concelier.Connector.Vndr.Oracle.OracleParseJob", "StellaOps.Concelier.Connector.Vndr.Oracle", TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(5)),
new("source:vndr-oracle:map", "StellaOps.Concelier.Connector.Vndr.Oracle.OracleMapJob", "StellaOps.Concelier.Connector.Vndr.Oracle", TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(5)), new("source:vndr-oracle:map", "StellaOps.Concelier.Connector.Vndr.Oracle.OracleMapJob", "StellaOps.Concelier.Connector.Vndr.Oracle", TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(5)),
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)

View File

@@ -15,6 +15,8 @@ public sealed class ConcelierOptions
public AuthorityOptions Authority { get; set; } = new(); public AuthorityOptions Authority { get; set; } = new();
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
{ {
@@ -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>();
}
} }

View File

@@ -17,7 +17,8 @@ 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)

View File

@@ -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;

View File

@@ -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>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
; Shipped analyzer releases

View File

@@ -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`.

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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,124 +170,91 @@ 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 note in linkset.Notes)
foreach (var alias in identifiers.Aliases) {
{ if (string.IsNullOrWhiteSpace(note.Value))
if (LinksetNormalization.TryNormalizeAlias(alias, out var normalized)) {
{ continue;
aliases.Add(normalized); }
}
} results.Add(note.Value.Trim());
}
foreach (var alias in linkset.Aliases)
{ return results;
if (LinksetNormalization.TryNormalizeAlias(alias, out var normalized))
{ static void AddAlias(ICollection<string> target, string? value)
aliases.Add(normalized); {
} if (string.IsNullOrWhiteSpace(value))
} {
return;
foreach (var note in linkset.Notes) }
{
if (!string.IsNullOrWhiteSpace(note.Value) target.Add(value.Trim());
&& LinksetNormalization.TryNormalizeAlias(note.Value, out var normalized)) }
{
aliases.Add(normalized); static void AddRange(ICollection<string> target, ImmutableArray<string> values)
} {
} if (values.IsDefaultOrEmpty)
{
return aliases return;
.OrderBy(static value => value, StringComparer.OrdinalIgnoreCase) }
.ToImmutableArray();
} foreach (var value in values)
{
private static IEnumerable<string> NormalizePackageUrls(ImmutableArray<string> packageUrls) AddAlias(target, value);
{ }
if (packageUrls.IsDefaultOrEmpty) }
{ }
return ImmutableArray<string>.Empty;
} private static IEnumerable<string> CollectValues(ImmutableArray<string> values)
{
var set = new HashSet<string>(StringComparer.Ordinal); if (values.IsDefaultOrEmpty)
{
foreach (var candidate in packageUrls) return ImmutableArray<string>.Empty;
{ }
if (!LinksetNormalization.TryNormalizePackageUrl(candidate, out var normalized) || string.IsNullOrEmpty(normalized))
{ var list = new List<string>(values.Length);
continue; foreach (var value in values)
} {
if (string.IsNullOrWhiteSpace(value))
set.Add(normalized); {
} continue;
}
return set
.OrderBy(static value => value, StringComparer.Ordinal) list.Add(value.Trim());
.ToImmutableArray(); }
}
return list;
private static IEnumerable<string> NormalizeCpes(ImmutableArray<string> cpes) }
{
if (cpes.IsDefaultOrEmpty) private static IEnumerable<AdvisoryObservationReference> CollectReferences(ImmutableArray<RawReference> references)
{ {
return ImmutableArray<string>.Empty; if (references.IsDefaultOrEmpty)
} {
return ImmutableArray<AdvisoryObservationReference>.Empty;
var set = new HashSet<string>(StringComparer.Ordinal); }
foreach (var cpe in cpes) var list = new List<AdvisoryObservationReference>(references.Length);
{ foreach (var reference in references)
if (!LinksetNormalization.TryNormalizeCpe(cpe, out var normalized) || string.IsNullOrEmpty(normalized)) {
{ if (string.IsNullOrWhiteSpace(reference.Type) || string.IsNullOrWhiteSpace(reference.Url))
continue; {
} continue;
}
set.Add(normalized);
} list.Add(new AdvisoryObservationReference(reference.Type.Trim(), reference.Url.Trim()));
}
return set
.OrderBy(static value => value, StringComparer.Ordinal) return list;
.ToImmutableArray(); }
}
private static IEnumerable<AdvisoryObservationReference> NormalizeReferences(ImmutableArray<RawReference> references)
{
if (references.IsDefaultOrEmpty)
{
return ImmutableArray<AdvisoryObservationReference>.Empty;
}
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var list = new List<AdvisoryObservationReference>();
foreach (var reference in references)
{
var normalized = LinksetNormalization.TryCreateReference(reference.Type, reference.Url);
if (normalized is null)
{
continue;
}
if (!seen.Add(normalized.Url))
{
continue;
}
list.Add(normalized);
}
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)
{ {

View File

@@ -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. |

View File

@@ -5,9 +5,10 @@ using Microsoft.Extensions.Logging;
using StellaOps.Concelier.Core.Jobs; using StellaOps.Concelier.Core.Jobs;
using StellaOps.Concelier.Merge.Services; using StellaOps.Concelier.Merge.Services;
namespace StellaOps.Concelier.Merge.Jobs; namespace StellaOps.Concelier.Merge.Jobs;
public sealed class MergeReconcileJob : IJob [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
{ {
private readonly AdvisoryMergeService _mergeService; private readonly AdvisoryMergeService _mergeService;
private readonly ILogger<MergeReconcileJob> _logger; private readonly ILogger<MergeReconcileJob> _logger;

View File

@@ -1,15 +1,17 @@
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;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using StellaOps.Concelier.Core; using StellaOps.Concelier.Core;
using StellaOps.Concelier.Merge.Jobs; using StellaOps.Concelier.Merge.Jobs;
using StellaOps.Concelier.Merge.Options; using StellaOps.Concelier.Merge.Options;
using StellaOps.Concelier.Merge.Services; using StellaOps.Concelier.Merge.Services;
namespace StellaOps.Concelier.Merge; namespace StellaOps.Concelier.Merge;
public static class MergeServiceCollectionExtensions [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 IServiceCollection AddMergeModule(this IServiceCollection services, IConfiguration configuration) public static IServiceCollection AddMergeModule(this IServiceCollection services, IConfiguration configuration)
{ {
@@ -34,10 +36,12 @@ public static class MergeServiceCollectionExtensions
return new AdvisoryPrecedenceMerger(resolver, options, timeProvider, logger); return new AdvisoryPrecedenceMerger(resolver, options, timeProvider, logger);
}); });
services.TryAddSingleton<MergeEventWriter>(); #pragma warning disable CS0618 // Legacy merge services are marked obsolete.
services.TryAddSingleton<AdvisoryMergeService>(); services.TryAddSingleton<MergeEventWriter>();
services.AddTransient<MergeReconcileJob>(); services.TryAddSingleton<AdvisoryMergeService>();
services.AddTransient<MergeReconcileJob>();
return services; #pragma warning restore CS0618
}
} return services;
}
}

View File

@@ -14,9 +14,10 @@ using StellaOps.Concelier.Storage.Mongo.Aliases;
using StellaOps.Concelier.Storage.Mongo.MergeEvents; using StellaOps.Concelier.Storage.Mongo.MergeEvents;
using System.Text.Json; using System.Text.Json;
namespace StellaOps.Concelier.Merge.Services; namespace StellaOps.Concelier.Merge.Services;
public sealed class AdvisoryMergeService [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
{ {
private static readonly Meter MergeMeter = new("StellaOps.Concelier.Merge"); private static readonly Meter MergeMeter = new("StellaOps.Concelier.Merge");
private static readonly Counter<long> AliasCollisionCounter = MergeMeter.CreateCounter<long>( private static readonly Counter<long> AliasCollisionCounter = MergeMeter.CreateCounter<long>(

View File

@@ -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.|

View File

@@ -280,57 +280,60 @@ 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> Purls { get; }
public ImmutableArray<string> Aliases { get; } public ImmutableArray<string> Cpes { get; }
public ImmutableArray<string> Purls { get; } public ImmutableArray<AdvisoryObservationReference> References { get; }
public ImmutableArray<string> Cpes { get; } private static ImmutableArray<string> ToImmutableArray(IEnumerable<string>? values)
{
public ImmutableArray<AdvisoryObservationReference> References { get; } if (values is null)
{
private static ImmutableArray<string> NormalizeStringSet(IEnumerable<string>? values, bool toLower = false) return ImmutableArray<string>.Empty;
{ }
if (values is null)
{ var builder = ImmutableArray.CreateBuilder<string>();
return ImmutableArray<string>.Empty; foreach (var value in values)
} {
var trimmed = Validation.TrimToNull(value);
var list = new List<string>(); if (trimmed is null)
foreach (var value in values) {
{ continue;
var trimmed = Validation.TrimToNull(value); }
if (trimmed is null)
{ builder.Add(trimmed);
continue; }
}
return builder.Count == 0 ? ImmutableArray<string>.Empty : builder.ToImmutable();
list.Add(toLower ? trimmed.ToLowerInvariant() : trimmed); }
}
private static ImmutableArray<AdvisoryObservationReference> ToImmutableReferences(IEnumerable<AdvisoryObservationReference>? references)
return list {
.Distinct(StringComparer.Ordinal) if (references is null)
.OrderBy(static v => v, StringComparer.Ordinal) {
.ToImmutableArray(); return ImmutableArray<AdvisoryObservationReference>.Empty;
} }
private static ImmutableArray<AdvisoryObservationReference> NormalizeReferences(IEnumerable<AdvisoryObservationReference>? references) var builder = ImmutableArray.CreateBuilder<AdvisoryObservationReference>();
{ foreach (var reference in references)
if (references is null) {
{ if (reference is null)
return ImmutableArray<AdvisoryObservationReference>.Empty; {
} continue;
}
return references
.Where(static reference => reference is not null) builder.Add(reference);
.Distinct() }
.OrderBy(static reference => reference.Type, StringComparer.Ordinal)
.ThenBy(static reference => reference.Url, StringComparer.Ordinal) return builder.Count == 0 ? ImmutableArray<AdvisoryObservationReference>.Empty : builder.ToImmutable();
.ToImmutableArray(); }
} }
}

View File

@@ -13,11 +13,11 @@ 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(
identifiers: new RawIdentifiers( identifiers: new RawIdentifiers(
Aliases: ImmutableArray.Create(" CVE-2025-0001 ", "ghsa-XXXX-YYYY"), Aliases: ImmutableArray.Create(" CVE-2025-0001 ", "ghsa-XXXX-YYYY"),
PrimaryId: "GHSA-XXXX-YYYY"), PrimaryId: "GHSA-XXXX-YYYY"),
linkset: new RawLinkset linkset: new RawLinkset
@@ -29,16 +29,27 @@ public sealed class AdvisoryObservationFactoryTests
new RawReference("Advisory", " https://example.test/advisory "), new RawReference("Advisory", " https://example.test/advisory "),
new RawReference("ADVISORY", "https://example.test/advisory")) new RawReference("ADVISORY", "https://example.test/advisory"))
}); });
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 " },

View File

@@ -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;
@@ -52,9 +53,9 @@ public sealed class AdvisoryObservationQueryServiceTests
Assert.Equal("tenant-a:osv:beta:1", result.Observations[0].ObservationId); Assert.Equal("tenant-a:osv:beta:1", result.Observations[0].ObservationId);
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(
new[] { "pkg:npm/package-a@1.0.0", "pkg:pypi/package-b@2.0.0" }, new[] { "pkg:npm/package-a@1.0.0", "pkg:pypi/package-b@2.0.0" },
@@ -103,8 +104,11 @@ public sealed class AdvisoryObservationQueryServiceTests
CancellationToken.None); CancellationToken.None);
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);

View File

@@ -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>

View File

@@ -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;
@@ -886,15 +886,61 @@ public sealed class WebServiceEndpointsTests : IAsyncLifetime
var limitedResponse = await client.GetAsync("/concelier/exports/index.json"); var limitedResponse = await client.GetAsync("/concelier/exports/index.json");
Assert.Equal((HttpStatusCode)429, limitedResponse.StatusCode); Assert.Equal((HttpStatusCode)429, limitedResponse.StatusCode);
Assert.NotNull(limitedResponse.Headers.RetryAfter); Assert.NotNull(limitedResponse.Headers.RetryAfter);
Assert.True(limitedResponse.Headers.RetryAfter!.Delta.HasValue); Assert.True(limitedResponse.Headers.RetryAfter!.Delta.HasValue);
Assert.True(limitedResponse.Headers.RetryAfter!.Delta!.Value.TotalSeconds > 0); Assert.True(limitedResponse.Headers.RetryAfter!.Delta!.Value.TotalSeconds > 0);
} }
[Fact]
[Fact] public void MergeModuleDisabledWhenFeatureFlagEnabled()
public async Task JobsEndpointsAllowBypassWhenAuthorityEnabled() {
{ var environment = new Dictionary<string, string?>
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]
public async Task JobsEndpointsAllowBypassWhenAuthorityEnabled()
{
var environment = new Dictionary<string, string?>
{ {
["CONCELIER_AUTHORITY__ENABLED"] = "true", ["CONCELIER_AUTHORITY__ENABLED"] = "true",
["CONCELIER_AUTHORITY__ALLOWANONYMOUSFALLBACK"] = "false", ["CONCELIER_AUTHORITY__ALLOWANONYMOUSFALLBACK"] = "false",

View File

@@ -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);

View File

@@ -84,10 +84,13 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation })); throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
} }
VexSignatureMetadata? signatureMetadata = null; VexSignatureMetadata? signatureMetadata = null;
if (document.Format == VexDocumentFormat.OciAttestation && _attestationVerifier is not null) VexAttestationDiagnostics? attestationDiagnostics = 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,31 +99,40 @@ 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)
{
if (resultLabel == "skipped") resultLabel = attestationDiagnostics.Result ?? resultLabel;
{ }
if (attestationDiagnostics is null)
{
RecordVerification(document.ProviderId, metadata, resultLabel);
}
if (resultLabel == "skipped")
{
_logger.LogDebug( _logger.LogDebug(
"Signature verification skipped for provider {ProviderId} (no signature metadata).", "Signature verification skipped for provider {ProviderId} (no signature metadata).",
document.ProviderId); document.ProviderId);
} }
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(
VexRawDocument document, private async ValueTask<(VexSignatureMetadata Metadata, VexAttestationDiagnostics Diagnostics)> VerifyAttestationAsync(
ImmutableDictionary<string, string> metadata, VexRawDocument document,
CancellationToken cancellationToken) ImmutableDictionary<string, string> metadata,
CancellationToken cancellationToken)
{ {
try try
{ {
@@ -146,37 +158,48 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
attestationMetadata, attestationMetadata,
envelopeJson); envelopeJson);
var verification = await _attestationVerifier! var verification = await _attestationVerifier!
.VerifyAsync(verificationRequest, cancellationToken) .VerifyAsync(verificationRequest, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
if (!verification.IsValid) var diagnosticsSnapshot = verification.Diagnostics;
{
var diagnostics = string.Join(", ", verification.Diagnostics.Select(kvp => $"{kvp.Key}={kvp.Value}")); if (!verification.IsValid)
_logger.LogError( {
"Attestation verification failed for provider {ProviderId} (uri={SourceUri}) diagnostics={Diagnostics}", var failureReason = diagnosticsSnapshot.FailureReason ?? "verification_failed";
document.ProviderId, var resultTag = diagnosticsSnapshot.Result ?? "invalid";
document.SourceUri,
diagnostics); RecordVerification(document.ProviderId, metadata, resultTag);
_logger.LogError(
var violation = AocViolation.Create( "Attestation verification failed for provider {ProviderId} (uri={SourceUri}) result={Result} failure={FailureReason} diagnostics={@Diagnostics}",
AocViolationCode.SignatureInvalid, document.ProviderId,
"/upstream/signature", document.SourceUri,
"Attestation verification failed."); resultTag,
failureReason,
RecordVerification(document.ProviderId, metadata, "fail"); diagnosticsSnapshot);
throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
} var violation = AocViolation.Create(
AocViolationCode.SignatureInvalid,
_logger.LogInformation( "/upstream/signature",
"Attestation verification succeeded for provider {ProviderId} (predicate={PredicateType}, subject={Subject}).", "Attestation verification failed.");
document.ProviderId,
attestationMetadata.PredicateType, throw new ExcititorAocGuardException(AocGuardResult.FromViolations(new[] { violation }));
statement.Subject[0].Name ?? "<unknown>"); }
return BuildSignatureMetadata(statement, metadata, attestationMetadata, verification.Diagnostics); var successResult = diagnosticsSnapshot.Result ?? "valid";
} RecordVerification(document.ProviderId, metadata, successResult);
catch (ExcititorAocGuardException)
{ _logger.LogInformation(
"Attestation verification succeeded for provider {ProviderId} (predicate={PredicateType}, subject={Subject}, result={Result}).",
document.ProviderId,
attestationMetadata.PredicateType,
statement.Subject[0].Name ?? "<unknown>",
successResult);
var signatureMetadata = BuildSignatureMetadata(statement, metadata, attestationMetadata, diagnosticsSnapshot);
return (signatureMetadata, diagnosticsSnapshot);
}
catch (ExcititorAocGuardException)
{
throw; throw;
} }
catch (Exception ex) catch (Exception ex)
@@ -192,10 +215,10 @@ 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 }));
} }
} }
private VexAttestationRequest BuildAttestationRequest(VexInTotoStatement statement, VexAttestationPredicate predicate) private VexAttestationRequest BuildAttestationRequest(VexInTotoStatement statement, VexAttestationPredicate predicate)
{ {
@@ -252,11 +275,11 @@ internal sealed class WorkerSignatureVerifier : IVexSignatureVerifier
signedAt); signedAt);
} }
private VexSignatureMetadata BuildSignatureMetadata( private VexSignatureMetadata BuildSignatureMetadata(
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);

View File

@@ -13,16 +13,18 @@
3. Emit observability signals (logs, metrics, optional tracing) that can run offline and degrade gracefully when transparency services are unreachable. 3. Emit observability signals (logs, metrics, optional tracing) that can run offline and degrade gracefully when transparency services are unreachable.
4. Add regression tests (unit + integration) covering positive path, negative path, and offline fallback scenarios. 4. Add regression tests (unit + integration) covering positive path, negative path, and offline fallback scenarios.
## 2. Deliverables ## 2. Deliverables
- `IVexAttestationVerifier` abstraction + `VexAttestationVerifier` implementation inside `StellaOps.Excititor.Attestation`, encapsulating DSSE validation, predicate checks, artifact digest confirmation, Rekor inclusion verification, and deterministic diagnostics. - `IVexAttestationVerifier` abstraction + `VexAttestationVerifier` implementation inside `StellaOps.Excititor.Attestation`, encapsulating DSSE validation, predicate checks, artifact digest confirmation, Rekor inclusion verification, and deterministic diagnostics.
- DI wiring (extension method) for registering verifier + instrumentation dependencies alongside the existing signer/rekor client. - DI wiring (extension method) for registering verifier + instrumentation dependencies alongside the existing signer/rekor client.
- 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.
- Documentation updates (`EXCITITOR-ATTEST-01-003-plan.md`, `TASKS.md` notes) describing instrumentation + test expectations. - 2025-11-05: Implemented `VexAttestationDiagnostics`, activity tagging via `VexAttestationActivitySource`, and updated verifier/tests to emit structured failure reasons.
- Test coverage in `StellaOps.Excititor.Attestation.Tests` (unit) and scaffolding notes for WebService/Worker integration tests. - 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.
- Test coverage in `StellaOps.Excititor.Attestation.Tests` (unit) and scaffolding notes for WebService/Worker integration tests.
## 3. Verification Flow ## 3. Verification Flow
### 3.1 Inputs ### 3.1 Inputs

View File

@@ -1,6 +1,9 @@
If you are working on this file you need to read docs/modules/excititor/ARCHITECTURE.md and ./AGENTS.md). If you are working on this file you need to read docs/modules/excititor/ARCHITECTURE.md and ./AGENTS.md).
# 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.

View File

@@ -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);
}

View File

@@ -1,8 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@@ -59,104 +59,120 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
public async ValueTask<VexAttestationVerification> VerifyAsync( public async ValueTask<VexAttestationVerification> VerifyAsync(
VexAttestationVerificationRequest request, VexAttestationVerificationRequest request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(request);
var stopwatch = Stopwatch.StartNew(); var stopwatch = Stopwatch.StartNew();
var diagnostics = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.Ordinal); var diagnostics = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.Ordinal);
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";
_logger.LogWarning("Attestation envelope is missing for export {ExportId}", request.Attestation.ExportId); SetFailure("missing_envelope");
resultLabel = "invalid"; _logger.LogWarning("Attestation envelope is missing for export {ExportId}", request.Attestation.ExportId);
return BuildResult(false); resultLabel = "invalid";
} return BuildResult(false);
}
if (!TryDeserializeEnvelope(request.Envelope, out var envelope, diagnostics))
{ if (!TryDeserializeEnvelope(request.Envelope, out var envelope, diagnostics))
_logger.LogWarning("Failed to deserialize attestation envelope for export {ExportId}", request.Attestation.ExportId); {
resultLabel = "invalid"; SetFailure("invalid_envelope");
return BuildResult(false); _logger.LogWarning("Failed to deserialize attestation envelope for export {ExportId}", request.Attestation.ExportId);
} resultLabel = "invalid";
return BuildResult(false);
if (!string.Equals(envelope.PayloadType, VexDsseBuilder.PayloadType, StringComparison.OrdinalIgnoreCase)) }
{
diagnostics["payload.type"] = envelope.PayloadType ?? string.Empty; if (!string.Equals(envelope.PayloadType, VexDsseBuilder.PayloadType, StringComparison.OrdinalIgnoreCase))
_logger.LogWarning( {
"Unexpected DSSE payload type {PayloadType} for export {ExportId}", diagnostics["payload.type"] = envelope.PayloadType ?? string.Empty;
envelope.PayloadType, SetFailure("unexpected_payload_type");
request.Attestation.ExportId); _logger.LogWarning(
resultLabel = "invalid"; "Unexpected DSSE payload type {PayloadType} for export {ExportId}",
return BuildResult(false); envelope.PayloadType,
} request.Attestation.ExportId);
resultLabel = "invalid";
if (envelope.Signatures is null || envelope.Signatures.Count == 0) return BuildResult(false);
{ }
diagnostics["signature.state"] = "missing";
_logger.LogWarning("Attestation envelope for export {ExportId} does not contain signatures.", request.Attestation.ExportId); if (envelope.Signatures is null || envelope.Signatures.Count == 0)
resultLabel = "invalid"; {
return BuildResult(false); diagnostics["signature.state"] = "missing";
} SetFailure("missing_signature");
_logger.LogWarning("Attestation envelope for export {ExportId} does not contain signatures.", request.Attestation.ExportId);
var payloadBase64 = envelope.Payload ?? string.Empty; resultLabel = "invalid";
if (!TryDecodePayload(payloadBase64, out var payloadBytes, diagnostics)) return BuildResult(false);
{ }
_logger.LogWarning("Failed to decode attestation payload for export {ExportId}", request.Attestation.ExportId);
resultLabel = "invalid"; var payloadBase64 = envelope.Payload ?? string.Empty;
return BuildResult(false); if (!TryDecodePayload(payloadBase64, out var payloadBytes, diagnostics))
} {
SetFailure("payload_decode_failed");
if (!TryDeserializeStatement(payloadBytes, out var statement, diagnostics)) _logger.LogWarning("Failed to decode attestation payload for export {ExportId}", request.Attestation.ExportId);
{ resultLabel = "invalid";
_logger.LogWarning("Failed to deserialize DSSE statement for export {ExportId}", request.Attestation.ExportId); return BuildResult(false);
resultLabel = "invalid"; }
return BuildResult(false);
} if (!TryDeserializeStatement(payloadBytes, out var statement, diagnostics))
{
if (!ValidatePredicateType(statement, request, diagnostics)) SetFailure("invalid_statement");
{ _logger.LogWarning("Failed to deserialize DSSE statement 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); }
}
if (!ValidatePredicateType(statement, request, diagnostics))
if (!ValidateSubject(statement, request, diagnostics)) {
{ SetFailure("predicate_type_mismatch");
_logger.LogWarning("Subject 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);
} }
if (!ValidatePredicate(statement, request, diagnostics)) if (!ValidateSubject(statement, request, diagnostics))
{ {
_logger.LogWarning("Predicate payload mismatch for export {ExportId}", request.Attestation.ExportId); SetFailure("subject_mismatch");
resultLabel = "invalid"; _logger.LogWarning("Subject mismatch for export {ExportId}", request.Attestation.ExportId);
return BuildResult(false); resultLabel = "invalid";
} return BuildResult(false);
}
if (!ValidateMetadataDigest(envelope, request.Metadata, diagnostics))
{ if (!ValidatePredicate(statement, request, diagnostics))
_logger.LogWarning("Attestation digest mismatch for export {ExportId}", request.Attestation.ExportId); {
resultLabel = "invalid"; SetFailure("predicate_mismatch");
return BuildResult(false); _logger.LogWarning("Predicate payload mismatch for export {ExportId}", request.Attestation.ExportId);
} resultLabel = "invalid";
return BuildResult(false);
if (!ValidateSignedAt(request.Metadata, request.Attestation.CreatedAt, diagnostics)) }
{
_logger.LogWarning("SignedAt validation failed for export {ExportId}", request.Attestation.ExportId); if (!ValidateMetadataDigest(envelope, request.Metadata, diagnostics))
resultLabel = "invalid"; {
return BuildResult(false); SetFailure("envelope_digest_mismatch");
} _logger.LogWarning("Attestation digest mismatch for export {ExportId}", request.Attestation.ExportId);
resultLabel = "invalid";
return BuildResult(false);
}
if (!ValidateSignedAt(request.Metadata, request.Attestation.CreatedAt, diagnostics))
{
SetFailure("signedat_out_of_range");
_logger.LogWarning("SignedAt validation failed for export {ExportId}", request.Attestation.ExportId);
resultLabel = "invalid";
return BuildResult(false);
}
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";
@@ -183,13 +202,16 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
catch (Exception ex) catch (Exception ex)
{ {
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);
return BuildResult(false); diagnostics["failure_reason"] = diagnostics.TryGetValue("error", out var errorCode)
} ? errorCode
finally : ex.GetType().Name;
{ return BuildResult(false);
stopwatch.Stop(); }
finally
{
stopwatch.Stop();
var tags = new KeyValuePair<string, object?>[] var tags = new KeyValuePair<string, object?>[]
{ {
new("result", resultLabel), new("result", resultLabel),
@@ -200,12 +222,32 @@ internal sealed class VexAttestationVerifier : IVexAttestationVerifier
_metrics.VerifyDuration.Record(stopwatch.Elapsed.TotalSeconds, tags); _metrics.VerifyDuration.Record(stopwatch.Elapsed.TotalSeconds, tags);
} }
VexAttestationVerification BuildResult(bool isValid) VexAttestationVerification BuildResult(bool isValid)
{ {
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);
} }
} }

View File

@@ -14,4 +14,4 @@
<ProjectReference Include="../../../Aoc/__Libraries/StellaOps.Aoc/StellaOps.Aoc.csproj" /> <ProjectReference Include="../../../Aoc/__Libraries/StellaOps.Aoc/StellaOps.Aoc.csproj" />
<ProjectReference Include="../../../Concelier/__Libraries/StellaOps.Concelier.RawModels/StellaOps.Concelier.RawModels.csproj" /> <ProjectReference Include="../../../Concelier/__Libraries/StellaOps.Concelier.RawModels/StellaOps.Concelier.RawModels.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,7 +1,8 @@
using System; 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);

View File

@@ -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();
}

View File

@@ -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);
} }

View File

@@ -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,

View File

@@ -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);

View File

@@ -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));
} }
} }

View File

@@ -16,42 +16,44 @@ public sealed class VexAttestationVerifierTests : IDisposable
{ {
private readonly VexAttestationMetrics _metrics = new(); private readonly VexAttestationMetrics _metrics = new();
[Fact] [Fact]
public async Task VerifyAsync_ReturnsValid_WhenEnvelopeMatches() public async Task VerifyAsync_ReturnsValid_WhenEnvelopeMatches()
{ {
var (request, metadata, envelope) = await CreateSignedAttestationAsync(); var (request, metadata, envelope) = await CreateSignedAttestationAsync();
var verifier = CreateVerifier(options => options.RequireTransparencyLog = false); var verifier = CreateVerifier(options => options.RequireTransparencyLog = false);
var verification = await verifier.VerifyAsync(
new VexAttestationVerificationRequest(request, metadata, envelope),
CancellationToken.None);
Assert.True(verification.IsValid);
Assert.Equal("valid", verification.Diagnostics.Result);
Assert.Null(verification.Diagnostics.FailureReason);
}
var verification = await verifier.VerifyAsync( [Fact]
new VexAttestationVerificationRequest(request, metadata, envelope), public async Task VerifyAsync_ReturnsInvalid_WhenDigestMismatch()
CancellationToken.None); {
var (request, metadata, envelope) = await CreateSignedAttestationAsync();
var verifier = CreateVerifier(options => options.RequireTransparencyLog = false);
var tamperedMetadata = new VexAttestationMetadata(
metadata.PredicateType,
metadata.Rekor,
"sha256:deadbeef",
metadata.SignedAt);
var verification = await verifier.VerifyAsync(
new VexAttestationVerificationRequest(request, tamperedMetadata, envelope),
CancellationToken.None);
Assert.False(verification.IsValid);
Assert.Equal("invalid", verification.Diagnostics.Result);
Assert.Equal("sha256:deadbeef", verification.Diagnostics["metadata.envelopeDigest"]);
Assert.Equal("envelope_digest_mismatch", verification.Diagnostics.FailureReason);
}
Assert.True(verification.IsValid); [Fact]
Assert.Equal("valid", verification.Diagnostics["result"]);
}
[Fact]
public async Task VerifyAsync_ReturnsInvalid_WhenDigestMismatch()
{
var (request, metadata, envelope) = await CreateSignedAttestationAsync();
var verifier = CreateVerifier(options => options.RequireTransparencyLog = false);
var tamperedMetadata = new VexAttestationMetadata(
metadata.PredicateType,
metadata.Rekor,
"sha256:deadbeef",
metadata.SignedAt);
var verification = await verifier.VerifyAsync(
new VexAttestationVerificationRequest(request, tamperedMetadata, envelope),
CancellationToken.None);
Assert.False(verification.IsValid);
Assert.Equal("invalid", verification.Diagnostics["result"]);
Assert.Equal("sha256:deadbeef", verification.Diagnostics["metadata.envelopeDigest"]);
}
[Fact]
public async Task VerifyAsync_AllowsOfflineTransparency_WhenConfigured() public async Task VerifyAsync_AllowsOfflineTransparency_WhenConfigured()
{ {
var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: true); var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: true);
@@ -67,47 +69,50 @@ 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]
public async Task VerifyAsync_ReturnsInvalid_WhenTransparencyRequiredAndMissing() public async Task VerifyAsync_ReturnsInvalid_WhenTransparencyRequiredAndMissing()
{ {
var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: false); var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: false);
var verifier = CreateVerifier(options => var verifier = CreateVerifier(options =>
{ {
options.RequireTransparencyLog = true; options.RequireTransparencyLog = true;
options.AllowOfflineTransparency = false; options.AllowOfflineTransparency = false;
}); });
var verification = await verifier.VerifyAsync( var verification = await verifier.VerifyAsync(
new VexAttestationVerificationRequest(request, metadata, envelope), new VexAttestationVerificationRequest(request, metadata, envelope),
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]
public async Task VerifyAsync_ReturnsInvalid_WhenTransparencyUnavailableAndOfflineDisallowed() [Fact]
{ public async Task VerifyAsync_ReturnsInvalid_WhenTransparencyUnavailableAndOfflineDisallowed()
var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: true); {
var transparency = new ThrowingTransparencyLogClient(); var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: true);
var verifier = CreateVerifier(options => var transparency = new ThrowingTransparencyLogClient();
{ var verifier = CreateVerifier(options =>
options.RequireTransparencyLog = true; {
options.AllowOfflineTransparency = false; options.RequireTransparencyLog = true;
}, transparency); options.AllowOfflineTransparency = false;
}, transparency);
var verification = await verifier.VerifyAsync(
new VexAttestationVerificationRequest(request, metadata, envelope), var verification = await verifier.VerifyAsync(
CancellationToken.None); new VexAttestationVerificationRequest(request, metadata, envelope),
CancellationToken.None);
Assert.False(verification.IsValid);
Assert.Equal("unreachable", verification.Diagnostics["rekor.state"]); Assert.False(verification.IsValid);
Assert.Equal("invalid", verification.Diagnostics["result"]); Assert.Equal("unreachable", verification.Diagnostics.RekorState);
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(

View File

@@ -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

View File

@@ -4,13 +4,14 @@ using System.Collections.Immutable;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; 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.Export; using StellaOps.Excititor.Attestation.Verification;
using StellaOps.Excititor.Storage.Mongo; using StellaOps.Excititor.Export;
using StellaOps.Excititor.WebService.Services; using StellaOps.Excititor.Storage.Mongo;
using StellaOps.Excititor.WebService.Services;
using MongoDB.Driver; using MongoDB.Driver;
using StellaOps.Excititor.Attestation.Dsse; using StellaOps.Excititor.Attestation.Dsse;
@@ -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);
} }
} }

View File

@@ -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
@@ -655,25 +670,25 @@ 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; }
public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationVerificationRequest request, CancellationToken cancellationToken) public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationVerificationRequest request, CancellationToken cancellationToken)
{ {
Invocations++; Invocations++;
return ValueTask.FromResult(new VexAttestationVerification(_isValid, _diagnostics)); return ValueTask.FromResult(new VexAttestationVerification(_isValid, _diagnostics));
} }
} }
private static VexRawDocument CreateAttestationRawDocument(DateTimeOffset observedAt) private static VexRawDocument CreateAttestationRawDocument(DateTimeOffset observedAt)
{ {

View File

@@ -249,19 +249,21 @@ 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 ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationVerificationRequest request, CancellationToken cancellationToken) public int Invocations { get; private set; }
{
Invocations++; public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationVerificationRequest request, CancellationToken cancellationToken)
{
Invocations++;
return ValueTask.FromResult(new VexAttestationVerification(_isValid, _diagnostics)); return ValueTask.FromResult(new VexAttestationVerification(_isValid, _diagnostics));
} }
} }
@@ -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

View File

@@ -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. |

Some files were not shown because too many files have changed in this diff Show More