Compare commits

..

47 Commits

Author SHA1 Message Date
StellaOps Bot
69651212ec feat: Implement CVSS receipt management client and models
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
2025-12-07 01:14:28 +02:00
StellaOps Bot
53889d85e7 feat: Add CVSS receipt management endpoints and related functionality
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
- Introduced new API endpoints for creating, retrieving, amending, and listing CVSS receipts.
- Updated IPolicyEngineClient interface to include methods for CVSS receipt operations.
- Implemented PolicyEngineClient to handle CVSS receipt requests.
- Enhanced Program.cs to map new CVSS receipt routes with appropriate authorization.
- Added necessary models and contracts for CVSS receipt requests and responses.
- Integrated Postgres document store for managing CVSS receipts and related data.
- Updated database schema with new migrations for source documents and payload storage.
- Refactored existing components to support new CVSS functionality.
2025-12-07 00:43:14 +02:00
StellaOps Bot
0de92144d2 feat(api): Implement Console Export Client and Models
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
mock-dev-release / package-mock-release (push) Has been cancelled
- Added ConsoleExportClient for managing export requests and responses.
- Introduced ConsoleExportRequest and ConsoleExportResponse models.
- Implemented methods for creating and retrieving exports with appropriate headers.

feat(crypto): Add Software SM2/SM3 Cryptography Provider

- Implemented SmSoftCryptoProvider for software-only SM2/SM3 cryptography.
- Added support for signing and verification using SM2 algorithm.
- Included hashing functionality with SM3 algorithm.
- Configured options for loading keys from files and environment gate checks.

test(crypto): Add unit tests for SmSoftCryptoProvider

- Created comprehensive tests for signing, verifying, and hashing functionalities.
- Ensured correct behavior for key management and error handling.

feat(api): Enhance Console Export Models

- Expanded ConsoleExport models to include detailed status and event types.
- Added support for various export formats and notification options.

test(time): Implement TimeAnchorPolicyService tests

- Developed tests for TimeAnchorPolicyService to validate time anchors.
- Covered scenarios for anchor validation, drift calculation, and policy enforcement.
2025-12-07 00:27:33 +02:00
StellaOps Bot
9bd6a73926 Implement incident mode management service and models
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Added IPackRunIncidentModeService interface for managing incident mode activation, deactivation, and status retrieval.
- Created PackRunIncidentModeService class implementing the service interface with methods for activating, deactivating, and escalating incident modes.
- Introduced incident mode status model (PackRunIncidentModeStatus) and related enums for escalation levels and activation sources.
- Developed retention policy, telemetry settings, and debug capture settings models to manage incident mode configurations.
- Implemented SLO breach notification handling to activate incident mode based on severity.
- Added in-memory store (InMemoryPackRunIncidentModeStore) for testing purposes.
- Created comprehensive unit tests for incident mode service, covering activation, deactivation, status retrieval, and SLO breach handling.
2025-12-06 22:33:00 +02:00
StellaOps Bot
4042fc2184 Add unit tests for PackRunAttestation and SealedInstallEnforcer
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
release-manifest-verify / verify (push) Has been cancelled
- Implement comprehensive tests for PackRunAttestationService, covering attestation generation, verification, and event emission.
- Add tests for SealedInstallEnforcer to validate sealed install requirements and enforcement logic.
- Introduce a MonacoLoaderService stub for testing purposes to prevent Monaco workers/styles from loading during Karma runs.
2025-12-06 22:25:30 +02:00
StellaOps Bot
dd0067ea0b Refactor code structure for improved readability and maintainability
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
2025-12-06 21:48:12 +02:00
StellaOps Bot
f6c22854a4 feat(api): Add Policy Registry API specification
Some checks failed
AOC Guard CI / aoc-verify (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
mock-dev-release / package-mock-release (push) Has been cancelled
- Introduced OpenAPI specification for the StellaOps Policy Registry API, covering endpoints for verification policies, policy packs, snapshots, violations, overrides, sealed mode operations, and advisory staleness tracking.
- Defined schemas, parameters, and responses for comprehensive API documentation.

chore(scanner): Add global usings for scanner analyzers

- Created GlobalUsings.cs to simplify namespace usage across analyzer libraries.

feat(scanner): Implement Surface Service Collection Extensions

- Added SurfaceServiceCollectionExtensions for dependency injection registration of surface analysis services.
- Included methods for adding surface analysis, surface collectors, and entry point collectors to the service collection.
2025-12-06 20:52:23 +02:00
StellaOps Bot
05597616d6 feat: Add Go module and workspace test fixtures
- Created expected JSON files for Go modules and workspaces.
- Added go.mod and go.sum files for example projects.
- Implemented private module structure with expected JSON output.
- Introduced vendored dependencies with corresponding expected JSON.
- Developed PostgresGraphJobStore for managing graph jobs.
- Established SQL migration scripts for graph jobs schema.
- Implemented GraphJobRepository for CRUD operations on graph jobs.
- Created IGraphJobRepository interface for repository abstraction.
- Added unit tests for GraphJobRepository to ensure functionality.
2025-12-06 20:04:03 +02:00
StellaOps Bot
a6f1406509 cli: reference postgres infra in cli and test projects 2025-12-06 16:36:05 +00:00
StellaOps Bot
0a8f8c14af cli: scaffold migration runner adapter and category parsing 2025-12-06 16:32:07 +00:00
StellaOps Bot
7efee7dd41 docs: log cli system migrations skeleton work 2025-12-06 16:28:10 +00:00
StellaOps Bot
952ba77924 cli: add system migrations command skeleton and tests 2025-12-06 16:25:04 +00:00
StellaOps Bot
23e463e346 test(cli): add placeholder migration command handler test 2025-12-06 16:20:56 +00:00
StellaOps Bot
849a70f9d1 cli: populate migration module registry and tests 2025-12-06 16:14:49 +00:00
StellaOps Bot
868f8e0bb6 docs: reflect CLI AGENTS unblock but keep migration tests pending 2025-12-06 16:10:31 +00:00
StellaOps Bot
84c42ca2d8 test(cli): add migration module registry coverage 2025-12-06 16:06:28 +00:00
StellaOps Bot
efd6850c38 Add unit tests for VexLens normalizer, CPE parser, product mapper, and PURL parser
- Implemented comprehensive tests for VexLensNormalizer including format detection and normalization scenarios.
- Added tests for CpeParser covering CPE 2.3 and 2.2 formats, invalid inputs, and canonical key generation.
- Created tests for ProductMapper to validate parsing and matching logic across different strictness levels.
- Developed tests for PurlParser to ensure correct parsing of various PURL formats and validation of identifiers.
- Introduced stubs for Monaco editor and worker to facilitate testing in the web application.
- Updated project file for the test project to include necessary dependencies.
2025-12-06 16:28:12 +02:00
StellaOps Bot
2b892ad1b2 docs: add CLI AGENTS and unblock migration cli test task 2025-12-06 14:26:43 +00:00
StellaOps Bot
e16d2b5224 docs: mark migration cli tests blocked pending cli AGENTS 2025-12-06 11:56:04 +00:00
StellaOps Bot
5e514532df Implement VEX document verification system with issuer management and signature verification
- Added IIssuerDirectory interface for managing VEX document issuers, including methods for registration, revocation, and trust validation.
- Created InMemoryIssuerDirectory class as an in-memory implementation of IIssuerDirectory for testing and single-instance deployments.
- Introduced ISignatureVerifier interface for verifying signatures on VEX documents, with support for multiple signature formats.
- Developed SignatureVerifier class as the default implementation of ISignatureVerifier, allowing extensibility for different signature formats.
- Implemented handlers for DSSE and JWS signature formats, including methods for verification and signature extraction.
- Defined various records and enums for issuer and signature metadata, enhancing the structure and clarity of the verification process.
2025-12-06 13:41:22 +02:00
StellaOps Bot
2141196496 docs: reference sbom sample list in vuln parity checkpoint 2025-12-06 10:51:25 +00:00
StellaOps Bot
bca02ec295 Web: seed auth session for e2e via test stub hook 2025-12-06 10:50:39 +00:00
StellaOps Bot
8cabdce3b6 docs: finalize sbom fixtures with hashes and sizes for vuln parity 2025-12-06 10:44:34 +00:00
StellaOps Bot
6145d89468 docs: add multi-ecosystem sbom fixtures for vuln parity 2025-12-06 10:37:41 +00:00
StellaOps Bot
ee317d3f61 docs: copy initial sbom fixtures and hash manifest for vuln parity 2025-12-06 10:29:13 +00:00
StellaOps Bot
4cc8bdb460 docs: scaffold vuln parity assets folder and sample placeholders 2025-12-06 10:21:48 +00:00
StellaOps Bot
95ff83e0f0 docs: seed vuln parity sbom list with available fixtures 2025-12-06 10:10:45 +00:00
StellaOps Bot
3954615e81 docs: clarify sbom sample placeholders for vuln parity 2025-12-06 10:02:24 +00:00
StellaOps Bot
8948b1a3e2 docs: log scheduler mongo snapshot request drafted 2025-12-06 09:50:13 +00:00
StellaOps Bot
5cfcf0723a docs: wire parity templates into postgres sprint logs 2025-12-06 09:41:45 +00:00
StellaOps Bot
ba733b9f69 docs: add parity prep templates for vuln and scheduler 2025-12-06 09:35:39 +00:00
StellaOps Bot
79d562ea5d docs: add parity report templates for vulnerabilities and scheduler 2025-12-06 09:25:58 +00:00
StellaOps Bot
a7cd10020a feat: Add Bun language analyzer and related functionality
- Implemented BunPackageNormalizer to deduplicate packages by name and version.
- Created BunProjectDiscoverer to identify Bun project roots in the filesystem.
- Added project files for the Bun analyzer including manifest and project configuration.
- Developed comprehensive tests for Bun language analyzer covering various scenarios.
- Included fixture files for testing standard installs, isolated linker installs, lockfile-only scenarios, and workspaces.
- Established stubs for authentication sessions to facilitate testing in the web application.
2025-12-06 11:20:35 +02:00
StellaOps Bot
b978ae399f docs: add parity checkpoints for scheduler and vulnerabilities sprints 2025-12-06 09:16:04 +00:00
StellaOps Bot
570746b7d9 docs: add postgres sprint unblock actions and dates 2025-12-06 09:07:40 +00:00
StellaOps Bot
8318b26370 docs: refresh postgres conversion overview status 2025-12-06 08:59:11 +00:00
StellaOps Bot
1f76650b7e docs: log header normalization across ops/evidence sprints 2025-12-06 08:29:32 +00:00
StellaOps Bot
37304cf819 Refactor code structure for improved readability and maintainability 2025-12-06 10:23:40 +02:00
StellaOps Bot
6beb9d7c4e docs: normalize ops and evidence sprint headers 2025-12-06 00:07:30 +00:00
StellaOps Bot
be8c623e04 docs: normalize docs md iii sprint header 2025-12-06 00:02:44 +00:00
StellaOps Bot
dd4bb50076 docs: normalize remaining docs sprint headers and logs 2025-12-05 23:59:04 +00:00
StellaOps Bot
bf6ab6ba6f docs: add scanner bun sprint and align docs md.i tracker 2025-12-05 23:52:42 +00:00
StellaOps Bot
02849cc955 docs: normalize sprint filenames and references 2025-12-05 23:47:26 +00:00
StellaOps Bot
2eaf0f699b feat: Implement air-gap functionality with timeline impact and evidence snapshot services
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
- Added AirgapTimelineImpact, AirgapTimelineImpactInput, and AirgapTimelineImpactResult records for managing air-gap bundle import impacts.
- Introduced EvidenceSnapshotRecord, EvidenceSnapshotLinkInput, and EvidenceSnapshotLinkResult records for linking findings to evidence snapshots.
- Created IEvidenceSnapshotRepository interface for managing evidence snapshot records.
- Developed StalenessValidationService to validate staleness and enforce freshness thresholds.
- Implemented AirgapTimelineService for emitting timeline events related to bundle imports.
- Added EvidenceSnapshotService for linking findings to evidence snapshots and verifying their validity.
- Introduced AirGapOptions for configuring air-gap staleness enforcement and thresholds.
- Added minimal jsPDF stub for offline/testing builds in the web application.
- Created TypeScript definitions for jsPDF to enhance type safety in the web application.
2025-12-06 01:30:08 +02:00
StellaOps Bot
6c1177a6ce Enhance risk API documentation and error handling
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
- Updated API documentation for risk endpoints to include optional caching headers and error catalog references.
- Added a new error catalog JSON file to standardize error responses.
- Improved explainability documentation with sample outputs for console and CLI.
- Added SHA256 checksums for new sample files related to explainability.
- Refined AocGuard tests to utilize a helper method for generating test JSON, improving readability and maintainability.
- Updated runbook references to ensure consistency in sprint documentation.
- Introduced stub implementations for MongoDB storage interfaces and options, laying groundwork for future development.
- Disabled analytics in Angular CLI configuration for privacy considerations.
2025-12-06 00:47:29 +02:00
StellaOps Bot
582a88e8f8 feat(docs): Add sprint documentation for CLI and API governance
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
- Created documentation for Sprint 200, 202, 203, 204, and 205 focusing on CLI enhancements and SDKs.
- Normalized legacy filenames to prevent divergent updates.
- Documented completed tasks, dependencies, and active items for CLI commands related to observability, orchestration, packaging, and policy management.
- Implemented API governance tooling and OpenAPI composition for Sprint 511, detailing task statuses and dependencies.
- Updated legacy web sprint documentation to reflect new naming conventions and standard templates.
2025-12-06 00:41:59 +02:00
StellaOps Bot
f0662dd45f feat: Implement DefaultCryptoHmac for compliance-aware HMAC operations
- Added DefaultCryptoHmac class implementing ICryptoHmac interface.
- Introduced purpose-based HMAC computation methods.
- Implemented verification methods for HMACs with constant-time comparison.
- Created HmacAlgorithms and HmacPurpose classes for well-known identifiers.
- Added compliance profile support for HMAC algorithms.
- Included asynchronous methods for HMAC computation from streams.
2025-12-06 00:41:04 +02:00
1412 changed files with 178209 additions and 53746 deletions

View File

@@ -1,8 +1,26 @@
{
"permissions": {
"allow": [
"Bash(dotnet --list-sdks:*)",
"Bash(winget install:*)",
"Bash(dotnet restore:*)",
"Bash(dotnet nuget:*)",
"Bash(csc -parse:*)",
"Bash(grep:*)",
"Bash(dotnet build:*)",
"Bash(cat:*)",
"Bash(copy:*)",
"Bash(dotnet test:*)",
"Bash(dir:*)",
"Bash(Select-Object -ExpandProperty FullName)",
"Bash(echo:*)",
"Bash(Out-File -FilePath \"E:\\dev\\git.stella-ops.org\\src\\Scanner\\__Libraries\\StellaOps.Scanner.Surface\\StellaOps.Scanner.Surface.csproj\" -Encoding utf8)",
"Bash(wc:*)",
"Bash(sort:*)"
"Bash(find:*)",
"WebFetch(domain:docs.gradle.org)",
"WebSearch",
"Bash(dotnet msbuild:*)",
"Bash(test:*)"
],
"deny": [],
"ask": []

View File

@@ -24,7 +24,7 @@ jobs:
aoc-guard:
runs-on: ubuntu-22.04
env:
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
DOTNET_VERSION: '10.0.100'
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
steps:
- name: Checkout
@@ -72,7 +72,7 @@ jobs:
runs-on: ubuntu-22.04
if: github.event_name != 'schedule'
env:
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
DOTNET_VERSION: '10.0.100'
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
AOC_VERIFY_SINCE: ${{ github.event.pull_request.base.sha || 'HEAD~1' }}
steps:

View File

@@ -37,7 +37,7 @@ on:
type: boolean
env:
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
DOTNET_VERSION: '10.0.100'
BUILD_CONFIGURATION: Release
CI_CACHE_ROOT: /data/.cache/stella-ops/feedser
RUNNER_TOOL_CACHE: /toolcache

View File

@@ -28,7 +28,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.100-rc.2.25502.107"
dotnet-version: "10.0.100"
- name: Install syft (SBOM)
uses: anchore/sbom-action/download-syft@v0

View File

@@ -24,7 +24,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.100-rc.2.25502.107"
dotnet-version: "10.0.100"
- name: Chaos smoke
if: ${{ github.event.inputs.chaos == 'true' }}

View File

@@ -23,7 +23,7 @@ jobs:
- name: Setup .NET 10 preview
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.100-rc.2.25502.107'
dotnet-version: '10.0.100'
- name: Restore Concelier solution
run: dotnet restore src/Concelier/StellaOps.Concelier.sln

View File

@@ -25,7 +25,7 @@ jobs:
- name: Setup .NET 10 (preview)
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.100-rc.2.25502.107
dotnet-version: 10.0.100
- name: Build CryptoPro plugin
run: |

View File

@@ -47,7 +47,7 @@ jobs:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.100-rc.2.25502.107'
dotnet-version: '10.0.100'
- name: Link check
run: |

View File

@@ -20,7 +20,7 @@ jobs:
export-ci:
runs-on: ubuntu-22.04
env:
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
DOTNET_VERSION: '10.0.100'
MINIO_ACCESS_KEY: exportci
MINIO_SECRET_KEY: exportci123
BUCKET: export-ci

View File

@@ -0,0 +1,325 @@
# .gitea/workflows/findings-ledger-ci.yml
# Findings Ledger CI with RLS migration validation (DEVOPS-LEDGER-TEN-48-001-REL)
name: Findings Ledger CI
on:
push:
branches: [main]
paths:
- 'src/Findings/**'
- '.gitea/workflows/findings-ledger-ci.yml'
- 'deploy/releases/2025.09-stable.yaml'
- 'deploy/releases/2025.09-airgap.yaml'
- 'deploy/downloads/manifest.json'
- 'ops/devops/release/check_release_manifest.py'
pull_request:
branches: [main, develop]
paths:
- 'src/Findings/**'
- '.gitea/workflows/findings-ledger-ci.yml'
env:
DOTNET_VERSION: '10.0.100'
POSTGRES_IMAGE: postgres:16-alpine
BUILD_CONFIGURATION: Release
jobs:
build-test:
runs-on: ubuntu-22.04
env:
TEST_RESULTS_DIR: ${{ github.workspace }}/artifacts/test-results
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET ${{ env.DOTNET_VERSION }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
include-prerelease: true
- name: Restore dependencies
run: |
dotnet restore src/Findings/StellaOps.Findings.Ledger/StellaOps.Findings.Ledger.csproj
dotnet restore src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/StellaOps.Findings.Ledger.Tests.csproj
- name: Build
run: |
dotnet build src/Findings/StellaOps.Findings.Ledger/StellaOps.Findings.Ledger.csproj \
-c ${{ env.BUILD_CONFIGURATION }} \
/p:ContinuousIntegrationBuild=true
- name: Run unit tests
run: |
mkdir -p $TEST_RESULTS_DIR
dotnet test src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/StellaOps.Findings.Ledger.Tests.csproj \
-c ${{ env.BUILD_CONFIGURATION }} \
--logger "trx;LogFileName=ledger-tests.trx" \
--results-directory $TEST_RESULTS_DIR
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: ledger-test-results
path: ${{ env.TEST_RESULTS_DIR }}
migration-validation:
runs-on: ubuntu-22.04
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: ledgertest
POSTGRES_PASSWORD: ledgertest
POSTGRES_DB: ledger_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
PGHOST: localhost
PGPORT: 5432
PGUSER: ledgertest
PGPASSWORD: ledgertest
PGDATABASE: ledger_test
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET ${{ env.DOTNET_VERSION }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
include-prerelease: true
- name: Install PostgreSQL client
run: |
sudo apt-get update
sudo apt-get install -y postgresql-client
- name: Wait for PostgreSQL
run: |
until pg_isready -h $PGHOST -p $PGPORT -U $PGUSER; do
echo "Waiting for PostgreSQL..."
sleep 2
done
- name: Apply prerequisite migrations (001-006)
run: |
set -euo pipefail
MIGRATION_DIR="src/Findings/StellaOps.Findings.Ledger/migrations"
for migration in 001_initial.sql 002_add_evidence_bundle_ref.sql 002_projection_offsets.sql \
003_policy_rationale.sql 004_ledger_attestations.sql 004_risk_fields.sql \
005_risk_fields.sql 006_orchestrator_airgap.sql; do
if [ -f "$MIGRATION_DIR/$migration" ]; then
echo "Applying migration: $migration"
psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -f "$MIGRATION_DIR/$migration"
fi
done
- name: Apply RLS migration (007_enable_rls.sql)
run: |
set -euo pipefail
echo "Applying RLS migration..."
psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE \
-f src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls.sql
- name: Validate RLS configuration
run: |
set -euo pipefail
echo "Validating RLS is enabled on all protected tables..."
# Check RLS enabled
TABLES_WITH_RLS=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -A -c "
SELECT COUNT(*)
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE n.nspname = 'public'
AND c.relrowsecurity = true
AND c.relname IN (
'ledger_events', 'ledger_merkle_roots', 'findings_projection',
'finding_history', 'triage_actions', 'ledger_attestations',
'orchestrator_exports', 'airgap_imports'
);
")
if [ "$TABLES_WITH_RLS" -ne 8 ]; then
echo "::error::Expected 8 tables with RLS enabled, found $TABLES_WITH_RLS"
exit 1
fi
echo "✓ All 8 tables have RLS enabled"
# Check policies exist
POLICIES=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -A -c "
SELECT COUNT(DISTINCT tablename)
FROM pg_policies
WHERE schemaname = 'public'
AND policyname LIKE '%_tenant_isolation';
")
if [ "$POLICIES" -ne 8 ]; then
echo "::error::Expected 8 tenant isolation policies, found $POLICIES"
exit 1
fi
echo "✓ All 8 tenant isolation policies created"
# Check tenant function exists
FUNC_EXISTS=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -A -c "
SELECT COUNT(*)
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE p.proname = 'require_current_tenant'
AND n.nspname = 'findings_ledger_app';
")
if [ "$FUNC_EXISTS" -ne 1 ]; then
echo "::error::Tenant function 'require_current_tenant' not found"
exit 1
fi
echo "✓ Tenant function 'findings_ledger_app.require_current_tenant()' exists"
echo ""
echo "=== RLS Migration Validation PASSED ==="
- name: Test rollback migration
run: |
set -euo pipefail
echo "Testing rollback migration..."
psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE \
-f src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls_rollback.sql
# Verify RLS is disabled
TABLES_WITH_RLS=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -A -c "
SELECT COUNT(*)
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE n.nspname = 'public'
AND c.relrowsecurity = true
AND c.relname IN (
'ledger_events', 'ledger_merkle_roots', 'findings_projection',
'finding_history', 'triage_actions', 'ledger_attestations',
'orchestrator_exports', 'airgap_imports'
);
")
if [ "$TABLES_WITH_RLS" -ne 0 ]; then
echo "::error::Rollback failed - $TABLES_WITH_RLS tables still have RLS enabled"
exit 1
fi
echo "✓ Rollback successful - RLS disabled on all tables"
- name: Validate release manifests (production)
run: |
set -euo pipefail
python ops/devops/release/check_release_manifest.py
- name: Re-apply RLS migration (idempotency check)
run: |
set -euo pipefail
echo "Re-applying RLS migration to verify idempotency..."
psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE \
-f src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls.sql
echo "✓ Migration is idempotent"
generate-manifest:
runs-on: ubuntu-22.04
needs: [build-test, migration-validation]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Generate migration manifest
run: |
set -euo pipefail
MIGRATION_FILE="src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls.sql"
ROLLBACK_FILE="src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls_rollback.sql"
MANIFEST_DIR="out/findings-ledger/migrations"
mkdir -p "$MANIFEST_DIR"
# Compute SHA256 hashes
MIGRATION_SHA=$(sha256sum "$MIGRATION_FILE" | awk '{print $1}')
ROLLBACK_SHA=$(sha256sum "$ROLLBACK_FILE" | awk '{print $1}')
CREATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
cat > "$MANIFEST_DIR/007_enable_rls.manifest.json" <<EOF
{
"\$schema": "https://stella-ops.org/schemas/migration-manifest.v1.json",
"schemaVersion": "1.0.0",
"migrationId": "007_enable_rls",
"module": "findings-ledger",
"version": "2025.12.0",
"createdAt": "$CREATED_AT",
"description": "Enable Row-Level Security for Findings Ledger tenant isolation",
"taskId": "LEDGER-TEN-48-001-DEV",
"contractRef": "CONTRACT-FINDINGS-LEDGER-RLS-011",
"database": {
"engine": "postgresql",
"minVersion": "16.0"
},
"files": {
"apply": {
"path": "007_enable_rls.sql",
"sha256": "$MIGRATION_SHA"
},
"rollback": {
"path": "007_enable_rls_rollback.sql",
"sha256": "$ROLLBACK_SHA"
}
},
"affects": {
"tables": [
"ledger_events",
"ledger_merkle_roots",
"findings_projection",
"finding_history",
"triage_actions",
"ledger_attestations",
"orchestrator_exports",
"airgap_imports"
],
"schemas": ["public", "findings_ledger_app"],
"roles": ["findings_ledger_admin"]
},
"prerequisites": [
"006_orchestrator_airgap"
],
"validation": {
"type": "rls-check",
"expectedTables": 8,
"expectedPolicies": 8,
"tenantFunction": "findings_ledger_app.require_current_tenant"
},
"offlineKit": {
"includedInBundle": true,
"requiresManualApply": true,
"applyOrder": 7
}
}
EOF
echo "Generated migration manifest at $MANIFEST_DIR/007_enable_rls.manifest.json"
cat "$MANIFEST_DIR/007_enable_rls.manifest.json"
- name: Copy migration files for offline-kit
run: |
set -euo pipefail
OFFLINE_DIR="out/findings-ledger/offline-kit/migrations"
mkdir -p "$OFFLINE_DIR"
cp src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls.sql "$OFFLINE_DIR/"
cp src/Findings/StellaOps.Findings.Ledger/migrations/007_enable_rls_rollback.sql "$OFFLINE_DIR/"
cp out/findings-ledger/migrations/007_enable_rls.manifest.json "$OFFLINE_DIR/"
echo "Offline-kit migration files prepared"
ls -la "$OFFLINE_DIR"
- name: Upload migration artefacts
uses: actions/upload-artifact@v4
with:
name: findings-ledger-migrations
path: out/findings-ledger/
if-no-files-found: error

View File

@@ -21,7 +21,7 @@ jobs:
lnm-backfill:
runs-on: ubuntu-22.04
env:
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
DOTNET_VERSION: '10.0.100'
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
steps:
- name: Checkout

View File

@@ -25,7 +25,7 @@ jobs:
vex-backfill:
runs-on: ubuntu-22.04
env:
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
DOTNET_VERSION: '10.0.100'
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
steps:
- name: Checkout

View File

@@ -21,7 +21,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.100-rc.2.25502.107
dotnet-version: 10.0.100
include-prerelease: true
- name: Task Pack offline bundle fixtures

View File

@@ -0,0 +1,44 @@
name: mock-dev-release
on:
push:
paths:
- deploy/releases/2025.09-mock-dev.yaml
- deploy/downloads/manifest.json
- ops/devops/mock-release/**
workflow_dispatch:
jobs:
package-mock-release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Package mock dev artefacts
run: |
set -euo pipefail
mkdir -p out/mock-release
cp deploy/releases/2025.09-mock-dev.yaml out/mock-release/
cp deploy/downloads/manifest.json out/mock-release/
tar -czf out/mock-release/mock-dev-release.tgz -C out/mock-release .
- name: Compose config (dev + mock overlay)
run: |
set -euo pipefail
ops/devops/mock-release/config_check.sh
- name: Helm template (mock overlay)
run: |
set -euo pipefail
helm template mock ./deploy/helm/stellaops -f deploy/helm/stellaops/values-mock.yaml > /tmp/helm-mock.yaml
ls -lh /tmp/helm-mock.yaml
- name: Upload mock release bundle
uses: actions/upload-artifact@v3
with:
name: mock-dev-release
path: |
out/mock-release/mock-dev-release.tgz
/tmp/compose-mock-config.yaml
/tmp/helm-mock.yaml

View File

@@ -35,7 +35,7 @@ jobs:
- name: Setup .NET 10 RC
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.100-rc.2.25502.107
dotnet-version: 10.0.100
include-prerelease: true
- name: Cache NuGet packages

View File

@@ -36,7 +36,7 @@ jobs:
- name: Setup .NET 10 RC
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.100-rc.2.25502.107
dotnet-version: 10.0.100
include-prerelease: true
- name: Install Cosign

View File

@@ -1,27 +1,27 @@
# .gitea/workflows/promote.yml
# Manual promotion workflow to copy staged artefacts to production
name: Promote Feedser (Manual)
on:
workflow_dispatch:
inputs:
include_docs:
description: 'Also promote the generated documentation bundle'
required: false
default: 'true'
type: boolean
tag:
description: 'Optional build identifier to record in the summary'
required: false
default: 'latest'
type: string
jobs:
promote:
runs-on: ubuntu-22.04
environment: production
steps:
# .gitea/workflows/promote.yml
# Manual promotion workflow to copy staged artefacts to production
name: Promote Feedser (Manual)
on:
workflow_dispatch:
inputs:
include_docs:
description: 'Also promote the generated documentation bundle'
required: false
default: 'true'
type: boolean
tag:
description: 'Optional build identifier to record in the summary'
required: false
default: 'latest'
type: string
jobs:
promote:
runs-on: ubuntu-22.04
environment: production
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -32,178 +32,178 @@ jobs:
id: staging
run: |
missing=()
host="${{ secrets.STAGING_DEPLOYMENT_HOST }}"
if [ -z "$host" ]; then host="${{ vars.STAGING_DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then missing+=("STAGING_DEPLOYMENT_HOST"); fi
user="${{ secrets.STAGING_DEPLOYMENT_USERNAME }}"
if [ -z "$user" ]; then user="${{ vars.STAGING_DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then missing+=("STAGING_DEPLOYMENT_USERNAME"); fi
path="${{ secrets.STAGING_DEPLOYMENT_PATH }}"
if [ -z "$path" ]; then path="${{ vars.STAGING_DEPLOYMENT_PATH }}"; fi
if [ -z "$path" ]; then missing+=("STAGING_DEPLOYMENT_PATH")
fi
docs_path="${{ secrets.STAGING_DOCS_PATH }}"
if [ -z "$docs_path" ]; then docs_path="${{ vars.STAGING_DOCS_PATH }}"; fi
key="${{ secrets.STAGING_DEPLOYMENT_KEY }}"
if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then key="${{ vars.STAGING_DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then missing+=("STAGING_DEPLOYMENT_KEY"); fi
if [ ${#missing[@]} -gt 0 ]; then
echo "❌ Missing staging configuration: ${missing[*]}"
exit 1
fi
key_file="$RUNNER_TEMP/staging_key"
printf '%s\n' "$key" > "$key_file"
chmod 600 "$key_file"
echo "host=$host" >> $GITHUB_OUTPUT
echo "user=$user" >> $GITHUB_OUTPUT
echo "path=$path" >> $GITHUB_OUTPUT
echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
echo "key-file=$key_file" >> $GITHUB_OUTPUT
- name: Resolve production credentials
id: production
run: |
missing=()
host="${{ secrets.PRODUCTION_DEPLOYMENT_HOST }}"
if [ -z "$host" ]; then host="${{ vars.PRODUCTION_DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then missing+=("PRODUCTION_DEPLOYMENT_HOST"); fi
user="${{ secrets.PRODUCTION_DEPLOYMENT_USERNAME }}"
if [ -z "$user" ]; then user="${{ vars.PRODUCTION_DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then missing+=("PRODUCTION_DEPLOYMENT_USERNAME"); fi
path="${{ secrets.PRODUCTION_DEPLOYMENT_PATH }}"
if [ -z "$path" ]; then path="${{ vars.PRODUCTION_DEPLOYMENT_PATH }}"; fi
if [ -z "$path" ]; then missing+=("PRODUCTION_DEPLOYMENT_PATH")
fi
docs_path="${{ secrets.PRODUCTION_DOCS_PATH }}"
if [ -z "$docs_path" ]; then docs_path="${{ vars.PRODUCTION_DOCS_PATH }}"; fi
key="${{ secrets.PRODUCTION_DEPLOYMENT_KEY }}"
if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then key="${{ vars.PRODUCTION_DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then missing+=("PRODUCTION_DEPLOYMENT_KEY"); fi
if [ ${#missing[@]} -gt 0 ]; then
echo "❌ Missing production configuration: ${missing[*]}"
exit 1
fi
key_file="$RUNNER_TEMP/production_key"
printf '%s\n' "$key" > "$key_file"
chmod 600 "$key_file"
echo "host=$host" >> $GITHUB_OUTPUT
echo "user=$user" >> $GITHUB_OUTPUT
echo "path=$path" >> $GITHUB_OUTPUT
echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
echo "key-file=$key_file" >> $GITHUB_OUTPUT
- name: Install rsync
run: |
if command -v rsync >/dev/null 2>&1; then
exit 0
fi
CACHE_DIR="${CI_CACHE_ROOT:-/tmp}/apt"
mkdir -p "$CACHE_DIR"
KEY="rsync-$(lsb_release -rs 2>/dev/null || echo unknown)"
DEB_DIR="$CACHE_DIR/$KEY"
mkdir -p "$DEB_DIR"
if ls "$DEB_DIR"/rsync*.deb >/dev/null 2>&1; then
apt-get update
apt-get install -y --no-install-recommends "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb
else
apt-get update
apt-get download rsync libpopt0
mv rsync*.deb libpopt0*.deb "$DEB_DIR"/
dpkg -i "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb || apt-get install -f -y
fi
- name: Fetch staging artefacts
id: fetch
run: |
staging_root="${{ runner.temp }}/staging"
mkdir -p "$staging_root/service" "$staging_root/docs"
echo "📥 Copying service bundle from staging"
rsync -az --delete \
-e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
"${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs.path }}/" \
"$staging_root/service/"
if [ "${{ github.event.inputs.include_docs }}" = "true" ] && [ -n "${{ steps.staging.outputs['docs-path'] }}" ]; then
echo "📥 Copying documentation bundle from staging"
rsync -az --delete \
-e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
"${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs['docs-path'] }}/" \
"$staging_root/docs/"
else
echo " Documentation promotion skipped"
fi
echo "service-dir=$staging_root/service" >> $GITHUB_OUTPUT
echo "docs-dir=$staging_root/docs" >> $GITHUB_OUTPUT
- name: Backup production service content
run: |
ssh -o StrictHostKeyChecking=no -i "${{ steps.production.outputs['key-file'] }}" \
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}" \
"set -e; TARGET='${{ steps.production.outputs.path }}'; \
if [ -d \"$TARGET\" ]; then \
parent=\$(dirname \"$TARGET\"); \
base=\$(basename \"$TARGET\"); \
backup=\"\$parent/\${base}.backup.\$(date +%Y%m%d_%H%M%S)\"; \
mkdir -p \"\$backup\"; \
rsync -a --delete \"$TARGET/\" \"\$backup/\"; \
ls -dt \"\$parent/\${base}.backup.*\" 2>/dev/null | tail -n +6 | xargs rm -rf || true; \
echo 'Backup created at ' \"\$backup\"; \
else \
echo 'Production service path missing; skipping backup'; \
fi"
- name: Publish service to production
run: |
rsync -az --delete \
-e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
"${{ steps.fetch.outputs['service-dir'] }}/" \
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs.path }}/"
- name: Promote documentation bundle
if: github.event.inputs.include_docs == 'true' && steps.production.outputs['docs-path'] != ''
run: |
rsync -az --delete \
-e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
"${{ steps.fetch.outputs['docs-dir'] }}/" \
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs['docs-path'] }}/"
- name: Promotion summary
run: |
echo "✅ Promotion completed"
echo " Tag: ${{ github.event.inputs.tag }}"
echo " Service: ${{ steps.staging.outputs.host }} → ${{ steps.production.outputs.host }}"
if [ "${{ github.event.inputs.include_docs }}" = "true" ]; then
echo " Docs: included"
else
echo " Docs: skipped"
fi
host="${{ secrets.STAGING_DEPLOYMENT_HOST }}"
if [ -z "$host" ]; then host="${{ vars.STAGING_DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then missing+=("STAGING_DEPLOYMENT_HOST"); fi
user="${{ secrets.STAGING_DEPLOYMENT_USERNAME }}"
if [ -z "$user" ]; then user="${{ vars.STAGING_DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then missing+=("STAGING_DEPLOYMENT_USERNAME"); fi
path="${{ secrets.STAGING_DEPLOYMENT_PATH }}"
if [ -z "$path" ]; then path="${{ vars.STAGING_DEPLOYMENT_PATH }}"; fi
if [ -z "$path" ]; then missing+=("STAGING_DEPLOYMENT_PATH")
fi
docs_path="${{ secrets.STAGING_DOCS_PATH }}"
if [ -z "$docs_path" ]; then docs_path="${{ vars.STAGING_DOCS_PATH }}"; fi
key="${{ secrets.STAGING_DEPLOYMENT_KEY }}"
if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then key="${{ vars.STAGING_DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then missing+=("STAGING_DEPLOYMENT_KEY"); fi
if [ ${#missing[@]} -gt 0 ]; then
echo "❌ Missing staging configuration: ${missing[*]}"
exit 1
fi
key_file="$RUNNER_TEMP/staging_key"
printf '%s\n' "$key" > "$key_file"
chmod 600 "$key_file"
echo "host=$host" >> $GITHUB_OUTPUT
echo "user=$user" >> $GITHUB_OUTPUT
echo "path=$path" >> $GITHUB_OUTPUT
echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
echo "key-file=$key_file" >> $GITHUB_OUTPUT
- name: Resolve production credentials
id: production
run: |
missing=()
host="${{ secrets.PRODUCTION_DEPLOYMENT_HOST }}"
if [ -z "$host" ]; then host="${{ vars.PRODUCTION_DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi
if [ -z "$host" ]; then missing+=("PRODUCTION_DEPLOYMENT_HOST"); fi
user="${{ secrets.PRODUCTION_DEPLOYMENT_USERNAME }}"
if [ -z "$user" ]; then user="${{ vars.PRODUCTION_DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi
if [ -z "$user" ]; then missing+=("PRODUCTION_DEPLOYMENT_USERNAME"); fi
path="${{ secrets.PRODUCTION_DEPLOYMENT_PATH }}"
if [ -z "$path" ]; then path="${{ vars.PRODUCTION_DEPLOYMENT_PATH }}"; fi
if [ -z "$path" ]; then missing+=("PRODUCTION_DEPLOYMENT_PATH")
fi
docs_path="${{ secrets.PRODUCTION_DOCS_PATH }}"
if [ -z "$docs_path" ]; then docs_path="${{ vars.PRODUCTION_DOCS_PATH }}"; fi
key="${{ secrets.PRODUCTION_DEPLOYMENT_KEY }}"
if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then key="${{ vars.PRODUCTION_DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
if [ -z "$key" ]; then missing+=("PRODUCTION_DEPLOYMENT_KEY"); fi
if [ ${#missing[@]} -gt 0 ]; then
echo "❌ Missing production configuration: ${missing[*]}"
exit 1
fi
key_file="$RUNNER_TEMP/production_key"
printf '%s\n' "$key" > "$key_file"
chmod 600 "$key_file"
echo "host=$host" >> $GITHUB_OUTPUT
echo "user=$user" >> $GITHUB_OUTPUT
echo "path=$path" >> $GITHUB_OUTPUT
echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
echo "key-file=$key_file" >> $GITHUB_OUTPUT
- name: Install rsync
run: |
if command -v rsync >/dev/null 2>&1; then
exit 0
fi
CACHE_DIR="${CI_CACHE_ROOT:-/tmp}/apt"
mkdir -p "$CACHE_DIR"
KEY="rsync-$(lsb_release -rs 2>/dev/null || echo unknown)"
DEB_DIR="$CACHE_DIR/$KEY"
mkdir -p "$DEB_DIR"
if ls "$DEB_DIR"/rsync*.deb >/dev/null 2>&1; then
apt-get update
apt-get install -y --no-install-recommends "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb
else
apt-get update
apt-get download rsync libpopt0
mv rsync*.deb libpopt0*.deb "$DEB_DIR"/
dpkg -i "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb || apt-get install -f -y
fi
- name: Fetch staging artefacts
id: fetch
run: |
staging_root="${{ runner.temp }}/staging"
mkdir -p "$staging_root/service" "$staging_root/docs"
echo "📥 Copying service bundle from staging"
rsync -az --delete \
-e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
"${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs.path }}/" \
"$staging_root/service/"
if [ "${{ github.event.inputs.include_docs }}" = "true" ] && [ -n "${{ steps.staging.outputs['docs-path'] }}" ]; then
echo "📥 Copying documentation bundle from staging"
rsync -az --delete \
-e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
"${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs['docs-path'] }}/" \
"$staging_root/docs/"
else
echo " Documentation promotion skipped"
fi
echo "service-dir=$staging_root/service" >> $GITHUB_OUTPUT
echo "docs-dir=$staging_root/docs" >> $GITHUB_OUTPUT
- name: Backup production service content
run: |
ssh -o StrictHostKeyChecking=no -i "${{ steps.production.outputs['key-file'] }}" \
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}" \
"set -e; TARGET='${{ steps.production.outputs.path }}'; \
if [ -d \"$TARGET\" ]; then \
parent=\$(dirname \"$TARGET\"); \
base=\$(basename \"$TARGET\"); \
backup=\"\$parent/\${base}.backup.\$(date +%Y%m%d_%H%M%S)\"; \
mkdir -p \"\$backup\"; \
rsync -a --delete \"$TARGET/\" \"\$backup/\"; \
ls -dt \"\$parent/\${base}.backup.*\" 2>/dev/null | tail -n +6 | xargs rm -rf || true; \
echo 'Backup created at ' \"\$backup\"; \
else \
echo 'Production service path missing; skipping backup'; \
fi"
- name: Publish service to production
run: |
rsync -az --delete \
-e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
"${{ steps.fetch.outputs['service-dir'] }}/" \
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs.path }}/"
- name: Promote documentation bundle
if: github.event.inputs.include_docs == 'true' && steps.production.outputs['docs-path'] != ''
run: |
rsync -az --delete \
-e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
"${{ steps.fetch.outputs['docs-dir'] }}/" \
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs['docs-path'] }}/"
- name: Promotion summary
run: |
echo "✅ Promotion completed"
echo " Tag: ${{ github.event.inputs.tag }}"
echo " Service: ${{ steps.staging.outputs.host }} → ${{ steps.production.outputs.host }}"
if [ "${{ github.event.inputs.include_docs }}" = "true" ]; then
echo " Docs: included"
else
echo " Docs: skipped"
fi

View File

@@ -0,0 +1,19 @@
name: release-manifest-verify
on:
push:
paths:
- deploy/releases/2025.09-stable.yaml
- deploy/releases/2025.09-airgap.yaml
- deploy/downloads/manifest.json
- ops/devops/release/check_release_manifest.py
workflow_dispatch:
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate release & downloads manifests
run: |
python ops/devops/release/check_release_manifest.py

View File

@@ -36,7 +36,7 @@ jobs:
build-release:
runs-on: ubuntu-22.04
env:
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
DOTNET_VERSION: '10.0.100'
REGISTRY: registry.stella-ops.org
steps:
- name: Checkout repository

View File

@@ -20,7 +20,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.100-rc.2.25502.107"
dotnet-version: "10.0.100"
- name: Install syft (SBOM)
uses: anchore/sbom-action/download-syft@v0

View File

@@ -15,7 +15,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.100-rc.2.25502.107"
dotnet-version: "10.0.100"
- name: Run determinism harness
run: |

View File

@@ -39,7 +39,7 @@ jobs:
- name: Setup .NET 10 RC
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.100-rc.2.25502.107
dotnet-version: 10.0.100
include-prerelease: true
- name: Cache NuGet packages

View File

@@ -37,7 +37,7 @@ jobs:
- name: Setup .NET 10 RC
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.100-rc.2.25502.107
dotnet-version: 10.0.100
include-prerelease: true
- name: Cache NuGet packages

View File

@@ -14,6 +14,8 @@
<PropertyGroup>
<StellaOpsEnableCryptoPro Condition="'$(StellaOpsEnableCryptoPro)' == ''">false</StellaOpsEnableCryptoPro>
<NoWarn>$(NoWarn);NU1608</NoWarn>
<WarningsNotAsErrors>$(WarningsNotAsErrors);NU1608</WarningsNotAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(StellaOpsEnableCryptoPro)' == 'true'">

View File

@@ -1 +1,4 @@
/nowarn:CA2022
/p:DisableWorkloadResolver=true
/p:RestoreAdditionalProjectFallbackFolders=
/p:RestoreFallbackFolders=

View File

@@ -1,19 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="local" value="local-nugets" />
<add key="ablera-mirror" value="https://mirrors.ablera.dev/nuget/nuget-mirror/v3/index.json" />
<add key="local" value="./local-nugets" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<config>
<add key="globalPackagesFolder" value="local-nugets/packages" />
<add key="globalPackagesFolder" value="/home/vlindos/.nuget/packages" />
</config>
<packageSourceMapping>
<packageSource key="local">
<package pattern="*" />
</packageSource>
<packageSource key="ablera-mirror">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
<fallbackPackageFolders>
</fallbackPackageFolders>
</configuration>

View File

@@ -9,7 +9,8 @@ This directory contains deterministic deployment bundles for the core Stella Ops
- `compose/docker-compose.mirror.yaml` managed mirror bundle for `*.stella-ops.org` with gateway cache and multi-tenant auth.
- `compose/docker-compose.telemetry.yaml` optional OpenTelemetry collector overlay (mutual TLS, OTLP pipelines).
- `compose/docker-compose.telemetry-storage.yaml` optional Prometheus/Tempo/Loki stack for observability backends.
- `helm/stellaops/` multi-profile Helm chart with values files for dev/stage/airgap.
- `helm/stellaops/` multi-profile Helm chart with values files for dev/stage/airgap.
- `helm/stellaops/INSTALL.md` install/runbook for prod and airgap profiles with digest pins.
- `telemetry/` shared OpenTelemetry collector configuration and certificate artefacts (generated via tooling).
- `tools/validate-profiles.sh` helper that runs `docker compose config` and `helm lint/template` for every profile.

View File

@@ -4,16 +4,21 @@ These Compose bundles ship the minimum services required to exercise the scanner
## Layout
| Path | Purpose |
| ---- | ------- |
| `docker-compose.dev.yaml` | Edge/nightly stack tuned for laptops and iterative work. |
| Path | Purpose |
| ---- | ------- |
| `docker-compose.dev.yaml` | Edge/nightly stack tuned for laptops and iterative work. |
| `docker-compose.stage.yaml` | Stable channel stack mirroring pre-production clusters. |
| `docker-compose.prod.yaml` | Production cutover stack with front-door network hand-off and Notify events enabled. |
| `docker-compose.airgap.yaml` | Stable stack with air-gapped defaults (no outbound hostnames). |
| `docker-compose.mirror.yaml` | Managed mirror topology for `*.stella-ops.org` distribution (Concelier + Excititor + CDN gateway). |
| `docker-compose.telemetry.yaml` | Optional OpenTelemetry collector overlay (mutual TLS, OTLP ingest endpoints). |
| `docker-compose.telemetry-storage.yaml` | Prometheus/Tempo/Loki storage overlay with multi-tenant defaults. |
| `docker-compose.gpu.yaml` | Optional GPU overlay enabling NVIDIA devices for Advisory AI web/worker. Apply with `-f docker-compose.<env>.yaml -f docker-compose.gpu.yaml`. |
| `env/*.env.example` | Seed `.env` files that document required secrets and ports per profile. |
| `scripts/backup.sh` | Pauses workers and creates tar.gz of Mongo/MinIO/Redis volumes (deterministic snapshot). |
| `scripts/reset.sh` | Stops the stack and removes Mongo/MinIO/Redis volumes after explicit confirmation. |
| `scripts/quickstart.sh` | Helper to validate config and start dev stack; set `USE_MOCK=1` to include `docker-compose.mock.yaml` overlay. |
| `docker-compose.mock.yaml` | Dev-only overlay with placeholder digests for missing services (orchestrator, policy-registry, packs, task-runner, VEX/Vuln stack). Use only with mock release manifest `deploy/releases/2025.09-mock-dev.yaml`. |
## Usage
@@ -100,5 +105,30 @@ The Helm chart mirrors these settings under `services.advisory-ai-web` / `adviso
1. Import the new manifest into `deploy/releases/` (see `deploy/README.md`).
2. Update image digests in the relevant Compose file(s).
3. Re-run `docker compose config` to confirm the bundle is deterministic.
### Mock overlay for missing digests (dev only)
Until official digests land, you can exercise Compose packaging with mock placeholders:
```bash
# assumes docker-compose.dev.yaml as the base profile
USE_MOCK=1 ./scripts/quickstart.sh env/dev.env.example
```
The overlay pins the missing services (orchestrator, policy-registry, packs-registry, task-runner, VEX/Vuln stack) to mock digests from `deploy/releases/2025.09-mock-dev.yaml` and starts their real entrypoints so integration flows can be exercised end-to-end. Replace the mock pins with production digests once releases publish; keep the mock overlay dev-only.
Keep digests synchronized between Compose, Helm, and the release manifest to preserve reproducibility guarantees. `deploy/tools/validate-profiles.sh` performs a quick audit.
Keep digests synchronized between Compose, Helm, and the release manifest to preserve reproducibility guarantees. `deploy/tools/validate-profiles.sh` performs a quick audit.
### GPU toggle for Advisory AI
GPU is disabled by default. To run inference on NVIDIA GPUs:
```bash
docker compose \
--env-file prod.env \
-f docker-compose.prod.yaml \
-f docker-compose.gpu.yaml \
up -d
```
The GPU overlay requests one GPU for `advisory-ai-worker` and `advisory-ai-web` and sets `ADVISORY_AI_INFERENCE_GPU=true`. Ensure the host has the NVIDIA container runtime and that the base compose file still sets the correct digests.

View File

@@ -0,0 +1,191 @@
# Content Addressable Storage (CAS) Infrastructure
# Uses RustFS for S3-compatible immutable object storage
# Aligned with best-in-class vulnerability scanner retention policies
#
# Usage:
# docker compose -f docker-compose.cas.yaml up -d
# docker compose -f docker-compose.cas.yaml -f docker-compose.dev.yaml up -d
x-release-labels: &release-labels
com.stellaops.release.version: "2025.10.0-edge"
com.stellaops.release.channel: "edge"
com.stellaops.profile: "cas"
x-cas-config: &cas-config
# Retention policies (aligned with Trivy/Grype/Anchore Enterprise)
# - vulnerability-db: 7 days (matches Trivy default)
# - sbom-artifacts: 365 days (audit compliance)
# - scan-results: 90 days (SOC2/ISO27001 typical)
# - evidence-bundles: indefinite (immutable, content-addressed)
# - attestations: indefinite (in-toto/DSSE signed)
CAS__RETENTION__VULNERABILITY_DB_DAYS: "7"
CAS__RETENTION__SBOM_ARTIFACTS_DAYS: "365"
CAS__RETENTION__SCAN_RESULTS_DAYS: "90"
CAS__RETENTION__EVIDENCE_BUNDLES_DAYS: "0" # 0 = indefinite
CAS__RETENTION__ATTESTATIONS_DAYS: "0" # 0 = indefinite
CAS__RETENTION__TEMP_ARTIFACTS_DAYS: "1"
networks:
cas:
driver: bridge
volumes:
rustfs-cas-data:
driver: local
driver_opts:
type: none
o: bind
device: ${CAS_DATA_PATH:-/var/lib/stellaops/cas}
rustfs-evidence-data:
driver: local
driver_opts:
type: none
o: bind
device: ${CAS_EVIDENCE_PATH:-/var/lib/stellaops/evidence}
rustfs-attestation-data:
driver: local
driver_opts:
type: none
o: bind
device: ${CAS_ATTESTATION_PATH:-/var/lib/stellaops/attestations}
services:
# Primary CAS storage - runtime facts, signals, replay artifacts
rustfs-cas:
image: registry.stella-ops.org/stellaops/rustfs:2025.10.0-edge
command: ["serve", "--listen", "0.0.0.0:8080", "--root", "/data"]
restart: unless-stopped
environment:
RUSTFS__LOG__LEVEL: "${RUSTFS_LOG_LEVEL:-info}"
RUSTFS__STORAGE__PATH: /data
RUSTFS__STORAGE__DEDUP: "true"
RUSTFS__STORAGE__COMPRESSION: "${RUSTFS_COMPRESSION:-zstd}"
RUSTFS__STORAGE__COMPRESSION_LEVEL: "${RUSTFS_COMPRESSION_LEVEL:-3}"
# Bucket lifecycle (retention enforcement)
RUSTFS__LIFECYCLE__ENABLED: "true"
RUSTFS__LIFECYCLE__SCAN_INTERVAL_HOURS: "24"
RUSTFS__LIFECYCLE__DEFAULT_RETENTION_DAYS: "90"
# Access control
RUSTFS__AUTH__ENABLED: "${RUSTFS_AUTH_ENABLED:-true}"
RUSTFS__AUTH__API_KEY: "${RUSTFS_CAS_API_KEY:-cas-api-key-change-me}"
RUSTFS__AUTH__READONLY_KEY: "${RUSTFS_CAS_READONLY_KEY:-cas-readonly-key-change-me}"
# Service account configuration
RUSTFS__ACCOUNTS__SCANNER__KEY: "${RUSTFS_SCANNER_KEY:-scanner-svc-key}"
RUSTFS__ACCOUNTS__SCANNER__BUCKETS: "scanner-artifacts,surface-cache,runtime-facts"
RUSTFS__ACCOUNTS__SCANNER__PERMISSIONS: "read,write"
RUSTFS__ACCOUNTS__SIGNALS__KEY: "${RUSTFS_SIGNALS_KEY:-signals-svc-key}"
RUSTFS__ACCOUNTS__SIGNALS__BUCKETS: "runtime-facts,signals-data,provenance-feed"
RUSTFS__ACCOUNTS__SIGNALS__PERMISSIONS: "read,write"
RUSTFS__ACCOUNTS__REPLAY__KEY: "${RUSTFS_REPLAY_KEY:-replay-svc-key}"
RUSTFS__ACCOUNTS__REPLAY__BUCKETS: "replay-bundles,inputs-lock"
RUSTFS__ACCOUNTS__REPLAY__PERMISSIONS: "read,write"
RUSTFS__ACCOUNTS__READONLY__KEY: "${RUSTFS_READONLY_KEY:-readonly-svc-key}"
RUSTFS__ACCOUNTS__READONLY__BUCKETS: "*"
RUSTFS__ACCOUNTS__READONLY__PERMISSIONS: "read"
<<: *cas-config
volumes:
- rustfs-cas-data:/data
ports:
- "${RUSTFS_CAS_PORT:-8180}:8080"
networks:
- cas
labels: *release-labels
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
# Evidence storage - Merkle roots, hash chains, evidence bundles (immutable)
rustfs-evidence:
image: registry.stella-ops.org/stellaops/rustfs:2025.10.0-edge
command: ["serve", "--listen", "0.0.0.0:8080", "--root", "/data", "--immutable"]
restart: unless-stopped
environment:
RUSTFS__LOG__LEVEL: "${RUSTFS_LOG_LEVEL:-info}"
RUSTFS__STORAGE__PATH: /data
RUSTFS__STORAGE__DEDUP: "true"
RUSTFS__STORAGE__COMPRESSION: "${RUSTFS_COMPRESSION:-zstd}"
RUSTFS__STORAGE__IMMUTABLE: "true" # Write-once, never delete
# Access control
RUSTFS__AUTH__ENABLED: "true"
RUSTFS__AUTH__API_KEY: "${RUSTFS_EVIDENCE_API_KEY:-evidence-api-key-change-me}"
RUSTFS__AUTH__READONLY_KEY: "${RUSTFS_EVIDENCE_READONLY_KEY:-evidence-readonly-key-change-me}"
# Service accounts
RUSTFS__ACCOUNTS__LEDGER__KEY: "${RUSTFS_LEDGER_KEY:-ledger-svc-key}"
RUSTFS__ACCOUNTS__LEDGER__BUCKETS: "evidence-bundles,merkle-roots,hash-chains"
RUSTFS__ACCOUNTS__LEDGER__PERMISSIONS: "read,write"
RUSTFS__ACCOUNTS__EXPORTER__KEY: "${RUSTFS_EXPORTER_KEY:-exporter-svc-key}"
RUSTFS__ACCOUNTS__EXPORTER__BUCKETS: "evidence-bundles"
RUSTFS__ACCOUNTS__EXPORTER__PERMISSIONS: "read"
volumes:
- rustfs-evidence-data:/data
ports:
- "${RUSTFS_EVIDENCE_PORT:-8181}:8080"
networks:
- cas
labels: *release-labels
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
# Attestation storage - DSSE envelopes, in-toto attestations (immutable)
rustfs-attestation:
image: registry.stella-ops.org/stellaops/rustfs:2025.10.0-edge
command: ["serve", "--listen", "0.0.0.0:8080", "--root", "/data", "--immutable"]
restart: unless-stopped
environment:
RUSTFS__LOG__LEVEL: "${RUSTFS_LOG_LEVEL:-info}"
RUSTFS__STORAGE__PATH: /data
RUSTFS__STORAGE__DEDUP: "true"
RUSTFS__STORAGE__COMPRESSION: "${RUSTFS_COMPRESSION:-zstd}"
RUSTFS__STORAGE__IMMUTABLE: "true" # Write-once, never delete
# Access control
RUSTFS__AUTH__ENABLED: "true"
RUSTFS__AUTH__API_KEY: "${RUSTFS_ATTESTATION_API_KEY:-attestation-api-key-change-me}"
RUSTFS__AUTH__READONLY_KEY: "${RUSTFS_ATTESTATION_READONLY_KEY:-attestation-readonly-key-change-me}"
# Service accounts
RUSTFS__ACCOUNTS__ATTESTOR__KEY: "${RUSTFS_ATTESTOR_KEY:-attestor-svc-key}"
RUSTFS__ACCOUNTS__ATTESTOR__BUCKETS: "attestations,dsse-envelopes,rekor-receipts"
RUSTFS__ACCOUNTS__ATTESTOR__PERMISSIONS: "read,write"
RUSTFS__ACCOUNTS__VERIFIER__KEY: "${RUSTFS_VERIFIER_KEY:-verifier-svc-key}"
RUSTFS__ACCOUNTS__VERIFIER__BUCKETS: "attestations,dsse-envelopes,rekor-receipts"
RUSTFS__ACCOUNTS__VERIFIER__PERMISSIONS: "read"
volumes:
- rustfs-attestation-data:/data
ports:
- "${RUSTFS_ATTESTATION_PORT:-8182}:8080"
networks:
- cas
labels: *release-labels
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
# Lifecycle manager - enforces retention policies
cas-lifecycle:
image: registry.stella-ops.org/stellaops/cas-lifecycle:2025.10.0-edge
restart: unless-stopped
depends_on:
rustfs-cas:
condition: service_healthy
environment:
LIFECYCLE__CAS__ENDPOINT: "http://rustfs-cas:8080"
LIFECYCLE__CAS__API_KEY: "${RUSTFS_CAS_API_KEY:-cas-api-key-change-me}"
LIFECYCLE__SCHEDULE__CRON: "${LIFECYCLE_CRON:-0 3 * * *}" # 3 AM daily
LIFECYCLE__POLICIES__VULNERABILITY_DB: "7d"
LIFECYCLE__POLICIES__SBOM_ARTIFACTS: "365d"
LIFECYCLE__POLICIES__SCAN_RESULTS: "90d"
LIFECYCLE__POLICIES__TEMP_ARTIFACTS: "1d"
LIFECYCLE__TELEMETRY__ENABLED: "${LIFECYCLE_TELEMETRY:-true}"
LIFECYCLE__TELEMETRY__OTLP_ENDPOINT: "${OTLP_ENDPOINT:-}"
networks:
- cas
labels: *release-labels

View File

@@ -0,0 +1,26 @@
version: "3.9"
services:
advisory-ai-worker:
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
driver: nvidia
count: 1
environment:
ADVISORY_AI_INFERENCE_GPU: "true"
runtime: nvidia
advisory-ai-web:
deploy:
resources:
reservations:
devices:
- capabilities: [gpu]
driver: nvidia
count: 1
environment:
ADVISORY_AI_INFERENCE_GPU: "true"
runtime: nvidia

View File

@@ -0,0 +1,74 @@
x-release-labels: &release-labels
com.stellaops.release.version: "2025.09.2-mock"
com.stellaops.release.channel: "dev-mock"
com.stellaops.profile: "mock-overlay"
services:
orchestrator:
image: registry.stella-ops.org/stellaops/orchestrator@sha256:97f12856ce870bafd3328bda86833bcccbf56d255941d804966b5557f6610119
command: ["dotnet", "StellaOps.Orchestrator.WebService.dll"]
depends_on:
- mongo
- nats
labels: *release-labels
networks: [stellaops]
policy-registry:
image: registry.stella-ops.org/stellaops/policy-registry@sha256:c6cad8055e9827ebcbebb6ad4d6866dce4b83a0a49b0a8a6500b736a5cb26fa7
command: ["dotnet", "StellaOps.Policy.Engine.dll"]
depends_on:
- mongo
labels: *release-labels
networks: [stellaops]
vex-lens:
image: registry.stella-ops.org/stellaops/vex-lens@sha256:b44e63ecfeebc345a70c073c1ce5ace709c58be0ffaad0e2862758aeee3092fb
command: ["dotnet", "StellaOps.VexLens.dll"]
depends_on:
- mongo
labels: *release-labels
networks: [stellaops]
issuer-directory:
image: registry.stella-ops.org/stellaops/issuer-directory@sha256:67e8ef02c97d3156741e857756994888f30c373ace8e84886762edba9dc51914
command: ["dotnet", "StellaOps.IssuerDirectory.Web.dll"]
depends_on:
- mongo
- authority
labels: *release-labels
networks: [stellaops]
findings-ledger:
image: registry.stella-ops.org/stellaops/findings-ledger@sha256:71d4c361ba8b2f8b69d652597bc3f2efc8a64f93fab854ce25272a88506df49c
command: ["dotnet", "StellaOps.Findings.Ledger.WebService.dll"]
depends_on:
- postgres
- authority
labels: *release-labels
networks: [stellaops]
vuln-explorer-api:
image: registry.stella-ops.org/stellaops/vuln-explorer-api@sha256:7fc7e43a05cbeb0106ce7d4d634612e83de6fdc119aaab754a71c1d60b82841d
command: ["dotnet", "StellaOps.VulnExplorer.Api.dll"]
depends_on:
- findings-ledger
- authority
labels: *release-labels
networks: [stellaops]
packs-registry:
image: registry.stella-ops.org/stellaops/packs-registry@sha256:1f5e9416c4dc608594ad6fad87c24d72134427f899c192b494e22b268499c791
command: ["dotnet", "StellaOps.PacksRegistry.dll"]
depends_on:
- mongo
labels: *release-labels
networks: [stellaops]
task-runner:
image: registry.stella-ops.org/stellaops/task-runner@sha256:eb5ad992b49a41554f41516be1a6afcfa6522faf2111c08ff2b3664ad2fc954b
command: ["dotnet", "StellaOps.TaskRunner.WebService.dll"]
depends_on:
- packs-registry
- postgres
labels: *release-labels
networks: [stellaops]

118
deploy/compose/env/cas.env.example vendored Normal file
View File

@@ -0,0 +1,118 @@
# CAS (Content Addressable Storage) Environment Configuration
# Copy to .env and customize for your deployment
#
# Aligned with best-in-class vulnerability scanner retention policies:
# - Trivy: 7 days vulnerability DB
# - Grype: 5 days DB, configurable
# - Anchore Enterprise: 90-365 days typical
# - Snyk Enterprise: 365 days
# =============================================================================
# DATA PATHS (ensure directories exist with proper permissions)
# =============================================================================
CAS_DATA_PATH=/var/lib/stellaops/cas
CAS_EVIDENCE_PATH=/var/lib/stellaops/evidence
CAS_ATTESTATION_PATH=/var/lib/stellaops/attestations
# =============================================================================
# RUSTFS CONFIGURATION
# =============================================================================
RUSTFS_LOG_LEVEL=info
RUSTFS_COMPRESSION=zstd
RUSTFS_COMPRESSION_LEVEL=3
# =============================================================================
# PORTS
# =============================================================================
RUSTFS_CAS_PORT=8180
RUSTFS_EVIDENCE_PORT=8181
RUSTFS_ATTESTATION_PORT=8182
# =============================================================================
# ACCESS CONTROL - API KEYS
# IMPORTANT: Change these in production!
# =============================================================================
# CAS Storage (mutable, lifecycle-managed)
RUSTFS_CAS_API_KEY=cas-api-key-CHANGE-IN-PRODUCTION
RUSTFS_CAS_READONLY_KEY=cas-readonly-key-CHANGE-IN-PRODUCTION
# Evidence Storage (immutable)
RUSTFS_EVIDENCE_API_KEY=evidence-api-key-CHANGE-IN-PRODUCTION
RUSTFS_EVIDENCE_READONLY_KEY=evidence-readonly-key-CHANGE-IN-PRODUCTION
# Attestation Storage (immutable)
RUSTFS_ATTESTATION_API_KEY=attestation-api-key-CHANGE-IN-PRODUCTION
RUSTFS_ATTESTATION_READONLY_KEY=attestation-readonly-key-CHANGE-IN-PRODUCTION
# =============================================================================
# SERVICE ACCOUNT KEYS
# Each service has its own key for fine-grained access control
# IMPORTANT: Generate unique keys per environment!
# =============================================================================
# Scanner service - access to scanner artifacts, surface cache, runtime facts
RUSTFS_SCANNER_KEY=scanner-svc-key-GENERATE-UNIQUE
# Bucket access: scanner-artifacts (rw), surface-cache (rw), runtime-facts (rw)
# Signals service - access to runtime facts, signals data, provenance feed
RUSTFS_SIGNALS_KEY=signals-svc-key-GENERATE-UNIQUE
# Bucket access: runtime-facts (rw), signals-data (rw), provenance-feed (rw)
# Replay service - access to replay bundles, inputs lock files
RUSTFS_REPLAY_KEY=replay-svc-key-GENERATE-UNIQUE
# Bucket access: replay-bundles (rw), inputs-lock (rw)
# Ledger service - access to evidence bundles, merkle roots, hash chains
RUSTFS_LEDGER_KEY=ledger-svc-key-GENERATE-UNIQUE
# Bucket access: evidence-bundles (rw), merkle-roots (rw), hash-chains (rw)
# Exporter service - read-only access to evidence bundles
RUSTFS_EXPORTER_KEY=exporter-svc-key-GENERATE-UNIQUE
# Bucket access: evidence-bundles (r)
# Attestor service - access to attestations, DSSE envelopes, Rekor receipts
RUSTFS_ATTESTOR_KEY=attestor-svc-key-GENERATE-UNIQUE
# Bucket access: attestations (rw), dsse-envelopes (rw), rekor-receipts (rw)
# Verifier service - read-only access to attestations
RUSTFS_VERIFIER_KEY=verifier-svc-key-GENERATE-UNIQUE
# Bucket access: attestations (r), dsse-envelopes (r), rekor-receipts (r)
# Global read-only key (for debugging/auditing)
RUSTFS_READONLY_KEY=readonly-global-key-GENERATE-UNIQUE
# Bucket access: * (r)
# =============================================================================
# LIFECYCLE MANAGEMENT
# =============================================================================
# Cron schedule for retention policy enforcement (default: 3 AM daily)
LIFECYCLE_CRON=0 3 * * *
LIFECYCLE_TELEMETRY=true
# =============================================================================
# RETENTION POLICIES (days, 0 = indefinite)
# Aligned with enterprise vulnerability scanner best practices
# =============================================================================
# Vulnerability DB: 7 days (matches Trivy default, Grype uses 5)
CAS_RETENTION_VULNERABILITY_DB_DAYS=7
# SBOM artifacts: 365 days (audit compliance - SOC2, ISO27001, FedRAMP)
CAS_RETENTION_SBOM_ARTIFACTS_DAYS=365
# Scan results: 90 days (common compliance window)
CAS_RETENTION_SCAN_RESULTS_DAYS=90
# Evidence bundles: indefinite (content-addressed, immutable, audit trail)
CAS_RETENTION_EVIDENCE_BUNDLES_DAYS=0
# Attestations: indefinite (signed, immutable, verifiable)
CAS_RETENTION_ATTESTATIONS_DAYS=0
# Temporary artifacts: 1 day (work-in-progress, intermediate files)
CAS_RETENTION_TEMP_ARTIFACTS_DAYS=1
# =============================================================================
# TELEMETRY (optional)
# =============================================================================
OTLP_ENDPOINT=

12
deploy/compose/env/mock.env.example vendored Normal file
View File

@@ -0,0 +1,12 @@
# Dev-only overlay env for docker-compose.mock.yaml
# Use together with dev.env.example:
# docker compose --env-file env/dev.env.example --env-file env/mock.env.example -f docker-compose.dev.yaml -f docker-compose.mock.yaml config
# Optional: override ports if you expose mock services
ORCHESTRATOR_PORT=8450
POLICY_REGISTRY_PORT=8451
VEX_LENS_PORT=8452
FINDINGS_LEDGER_PORT=8453
VULN_EXPLORER_API_PORT=8454
PACKS_REGISTRY_PORT=8455
TASK_RUNNER_PORT=8456

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
echo "StellaOps Compose Backup"
echo "This will create a tar.gz of Mongo, MinIO (object-store), and Redis data volumes."
read -rp "Proceed? [y/N] " ans
[[ ${ans:-N} =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; }
TS=$(date -u +%Y%m%dT%H%M%SZ)
OUT_DIR=${BACKUP_DIR:-backups}
mkdir -p "$OUT_DIR"
docker compose ps >/dev/null
echo "Pausing worker containers for consistency..."
docker compose pause scanner-worker scheduler-worker taskrunner-worker || true
echo "Backing up volumes..."
docker run --rm \
-v stellaops-mongo:/data/db:ro \
-v stellaops-minio:/data/minio:ro \
-v stellaops-redis:/data/redis:ro \
-v "$PWD/$OUT_DIR":/out \
alpine sh -c "cd / && tar czf /out/stellaops-backup-$TS.tar.gz data"
docker compose unpause scanner-worker scheduler-worker taskrunner-worker || true
echo "Backup written to $OUT_DIR/stellaops-backup-$TS.tar.gz"

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COMPOSE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
ENV_FILE="${1:-$COMPOSE_DIR/env/dev.env.example}"
USE_MOCK="${USE_MOCK:-0}"
FILES=(-f "$COMPOSE_DIR/docker-compose.dev.yaml")
ENV_FILES=(--env-file "$ENV_FILE")
if [[ "$USE_MOCK" == "1" ]]; then
FILES+=(-f "$COMPOSE_DIR/docker-compose.mock.yaml")
ENV_FILES+=(--env-file "$COMPOSE_DIR/env/mock.env.example")
fi
echo "Validating compose config..."
docker compose "${ENV_FILES[@]}" "${FILES[@]}" config > /tmp/compose-validated.yaml
echo "Config written to /tmp/compose-validated.yaml"
echo "Starting stack..."
docker compose "${ENV_FILES[@]}" "${FILES[@]}" up -d
echo "Stack started. To stop: docker compose ${ENV_FILES[*]} ${FILES[*]} down"

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
echo "WARNING: This will stop the stack and wipe Mongo, MinIO, and Redis volumes."
read -rp "Type 'RESET' to continue: " ans
[[ ${ans:-} == "RESET" ]] || { echo "Aborted."; exit 1; }
docker compose down
for vol in stellaops-mongo stellaops-minio stellaops-redis; do
echo "Removing volume $vol"
docker volume rm "$vol" || true
done
echo "Reset complete. Re-run compose with your env file to recreate volumes."

View File

@@ -0,0 +1,18 @@
{
"version": "2025.09.2-mock",
"generatedAt": "2025-12-06T00:00:00Z",
"items": [
{
"name": "console-web",
"type": "container",
"image": "registry.stella-ops.org/stellaops/web-ui@sha256:3878c335df50ca958907849b09d43ce397900d32fc7a417c0bf76742e1217ba1",
"channel": "dev-mock"
},
{
"name": "console-bundle",
"type": "archive",
"url": "https://downloads.stella-ops.mock/console/2025.09.2-mock/console.tar.gz",
"sha256": "12dd89e012b1262ac61188ac5b7721ddab80c4e2b6341251d03925eb49a48521"
}
]
}

View File

@@ -0,0 +1,64 @@
# StellaOps Helm Install Guide
This guide ships with the `stellaops` chart and provides deterministic install steps for **prod** and **airgap** profiles. All images are pinned by digest from `deploy/releases/<channel>.yaml`.
## Prerequisites
- Helm ≥ 3.14 and kubectl configured for the target cluster.
- Pull secrets for `registry.stella-ops.org` (or your mirrored registry in air-gapped mode).
- TLS/ingress secrets created if you enable ingress in the values files.
## Channels and values
- Prod/stable: `deploy/releases/2025.09-stable.yaml` + `values-prod.yaml`
- Airgap: `deploy/releases/2025.09-airgap.yaml` + `values-airgap.yaml`
- Mirror (optional): `values-mirror.yaml` overlays registry endpoints when using a private mirror.
## Quick install (prod)
```bash
export RELEASE_CHANNEL=2025.09-stable
export NAMESPACE=stellaops
helm upgrade --install stellaops ./deploy/helm/stellaops \
--namespace "$NAMESPACE" --create-namespace \
-f deploy/helm/stellaops/values-prod.yaml \
--set global.release.channel=stable \
--set global.release.version="2025.09.2" \
--set global.release.manifestSha256="dc3c8fe1ab83941c838ccc5a8a5862f7ddfa38c2078e580b5649db26554565b7"
```
## Quick install (airgap)
Assumes images are already loaded into your private registry and `values-airgap.yaml` points to that registry.
```bash
export NAMESPACE=stellaops
helm upgrade --install stellaops ./deploy/helm/stellaops \
--namespace "$NAMESPACE" --create-namespace \
-f deploy/helm/stellaops/values-airgap.yaml \
--set global.release.channel=airgap \
--set global.release.version="2025.09.0-airgap" \
--set global.release.manifestSha256="d422ae3ea01d5f27ea8b5fdc5b19667cb4e3e2c153a35cb761cb53a6ce4f6ba4"
```
## Mirror overlay
If using a mirrored registry, layer the mirror values:
```bash
helm upgrade --install stellaops ./deploy/helm/stellaops \
--namespace "$NAMESPACE" --create-namespace \
-f deploy/helm/stellaops/values-prod.yaml \
-f deploy/helm/stellaops/values-mirror.yaml \
--set global.release.version="2025.09.2" \
--set global.release.manifestSha256="dc3c8fe1ab83941c838ccc5a8a5862f7ddfa38c2078e580b5649db26554565b7"
```
## Validate chart and digests
```bash
deploy/tools/check-channel-alignment.py --manifest deploy/releases/$RELEASE_CHANNEL.yaml \
--values deploy/helm/stellaops/values-prod.yaml
helm lint ./deploy/helm/stellaops
helm template stellaops ./deploy/helm/stellaops -f deploy/helm/stellaops/values-prod.yaml >/tmp/stellaops.yaml
```
## Notes
- Surface.Env and Surface.Secrets defaults are defined in `values*.yaml`; adjust endpoints, cache roots, and providers before promotion.
- Keep `global.release.*` in sync with the chosen release manifest; never deploy with empty version/channel/manifestSha256.
- For offline clusters, run image preload and secret creation before `helm upgrade` to avoid pull failures.

View File

@@ -0,0 +1,16 @@
# Mock Overlay (Dev Only)
Purpose: let deployment tasks progress with placeholder digests until real releases land.
Use:
```bash
helm template mock ./deploy/helm/stellaops -f deploy/helm/stellaops/values-mock.yaml
```
Contents:
- Mock deployments for orchestrator, policy-registry, packs-registry, task-runner, VEX Lens, issuer-directory, findings-ledger, vuln-explorer-api.
- Image pins pulled from `deploy/releases/2025.09-mock-dev.yaml`.
Notes:
- Annotated with `stellaops.dev/mock: "true"` to discourage production use.
- Swap to real values once official digests publish; keep mock overlay gated behind `mock.enabled`.

View File

@@ -19,18 +19,30 @@ spec:
selector:
matchLabels:
{{- include "stellaops.selectorLabels" (dict "root" $root "name" $name "svc" $svc) | nindent 6 }}
template:
metadata:
labels:
{{- include "stellaops.selectorLabels" (dict "root" $root "name" $name "svc" $svc) | nindent 8 }}
annotations:
stellaops.release/version: {{ $root.Values.global.release.version | quote }}
stellaops.release/channel: {{ $root.Values.global.release.channel | quote }}
spec:
containers:
- name: {{ $name }}
image: {{ $svc.image | quote }}
imagePullPolicy: {{ default $root.Values.global.image.pullPolicy $svc.imagePullPolicy }}
template:
metadata:
labels:
{{- include "stellaops.selectorLabels" (dict "root" $root "name" $name "svc" $svc) | nindent 8 }}
{{- if $svc.podAnnotations }}
annotations:
{{ toYaml $svc.podAnnotations | nindent 8 }}
{{- end }}
annotations:
stellaops.release/version: {{ $root.Values.global.release.version | quote }}
stellaops.release/channel: {{ $root.Values.global.release.channel | quote }}
spec:
{{- if $svc.podSecurityContext }}
securityContext:
{{ toYaml $svc.podSecurityContext | nindent 6 }}
{{- end }}
containers:
- name: {{ $name }}
image: {{ $svc.image | quote }}
imagePullPolicy: {{ default $root.Values.global.image.pullPolicy $svc.imagePullPolicy }}
{{- if $svc.securityContext }}
securityContext:
{{ toYaml $svc.securityContext | nindent 12 }}
{{- end }}
{{- if $svc.command }}
command:
{{- range $cmd := $svc.command }}
@@ -81,18 +93,35 @@ spec:
containerPort: {{ default (index $svcService "port") (index $svcService "targetPort") }}
protocol: {{ default "TCP" (index $svcService "protocol") }}
{{- end }}
{{- if $svc.resources }}
resources:
{{ toYaml $svc.resources | nindent 12 }}
{{- end }}
{{- if $svc.livenessProbe }}
livenessProbe:
{{ toYaml $svc.livenessProbe | nindent 12 }}
{{- end }}
{{- if $svc.readinessProbe }}
readinessProbe:
{{ toYaml $svc.readinessProbe | nindent 12 }}
{{- end }}
{{- if $svc.resources }}
resources:
{{ toYaml $svc.resources | nindent 12 }}
{{- end }}
{{- if $svc.securityContext }}
securityContext:
{{ toYaml $svc.securityContext | nindent 12 }}
{{- end }}
{{- if $svc.securityContext }}
securityContext:
{{ toYaml $svc.securityContext | nindent 12 }}
{{- end }}
{{- if $svc.livenessProbe }}
livenessProbe:
{{ toYaml $svc.livenessProbe | nindent 12 }}
{{- end }}
{{- if $svc.readinessProbe }}
readinessProbe:
{{ toYaml $svc.readinessProbe | nindent 12 }}
{{- end }}
{{- if $svc.prometheus }}
{{- $pr := $svc.prometheus }}
{{- if $pr.enabled }}
{{- if not $svc.podAnnotations }}
{{- $svc = merge $svc (dict "podAnnotations" (dict)) }}
{{- end }}
{{- $svc.podAnnotations = merge $svc.podAnnotations (dict "prometheus.io/scrape" "true" "prometheus.io/path" (default "/metrics" $pr.path) "prometheus.io/port" (toString (default 8080 $pr.port)) "prometheus.io/scheme" (default "http" $pr.scheme))) }}
{{- end }}
{{- end }}
{{- if or $svc.volumeMounts $configMounts }}
volumeMounts:
{{- if $svc.volumeMounts }}
@@ -148,13 +177,32 @@ spec:
affinity:
{{ toYaml $svc.affinity | nindent 8 }}
{{- end }}
{{- if $svc.tolerations }}
tolerations:
{{ toYaml $svc.tolerations | nindent 8 }}
{{- end }}
---
{{- if $svc.service }}
apiVersion: v1
{{- if $svc.tolerations }}
tolerations:
{{ toYaml $svc.tolerations | nindent 8 }}
{{- end }}
{{- if $svc.pdb }}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "stellaops.fullname" (dict "root" $root "name" $name) }}
labels:
{{- include "stellaops.labels" (dict "root" $root "name" $name "svc" $svc) | nindent 4 }}
spec:
{{- if $svc.pdb.minAvailable }}
minAvailable: {{ $svc.pdb.minAvailable }}
{{- end }}
{{- if $svc.pdb.maxUnavailable }}
maxUnavailable: {{ $svc.pdb.maxUnavailable }}
{{- end }}
selector:
matchLabels:
{{- include "stellaops.selectorLabels" (dict "root" $root "name" $name "svc" $svc) | nindent 6 }}
{{- end }}
---
{{- if $svc.service }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "stellaops.fullname" (dict "root" $root "name" $name) }}

View File

@@ -0,0 +1,28 @@
{{- if and .Values.externalSecrets.enabled .Values.externalSecrets.secrets }}
{{- range $secret := .Values.externalSecrets.secrets }}
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: {{ include "stellaops.fullname" $ }}-{{ $secret.name }}
labels:
{{- include "stellaops.labels" $ | nindent 4 }}
spec:
refreshInterval: {{ default "1h" $secret.refreshInterval }}
secretStoreRef:
name: {{ $secret.storeRef.name }}
kind: {{ default "ClusterSecretStore" $secret.storeRef.kind }}
target:
name: {{ $secret.target.name | default (printf "%s-%s" (include "stellaops.fullname" $) $secret.name) }}
creationPolicy: {{ default "Owner" $secret.target.creationPolicy }}
data:
{{- range $secret.data }}
- secretKey: {{ .key }}
remoteRef:
key: {{ .remoteKey }}
{{- if .property }}
property: {{ .property }}
{{- end }}
{{- end }}
---
{{- end }}
{{- end }}

View File

@@ -0,0 +1,39 @@
{{- if and .Values.hpa.enabled .Values.services }}
{{- range $name, $svc := .Values.services }}
{{- if and $svc.hpa $svc.hpa.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "stellaops.fullname" (dict "root" $ "name" $name) }}
labels:
{{- include "stellaops.labels" (dict "root" $ "name" $name "svc" $svc) | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "stellaops.fullname" (dict "root" $ "name" $name) }}
minReplicas: {{ default $.Values.hpa.minReplicas $svc.hpa.minReplicas }}
maxReplicas: {{ default $.Values.hpa.maxReplicas $svc.hpa.maxReplicas }}
metrics:
{{- $cpu := coalesce $svc.hpa.cpu.targetPercentage $.Values.hpa.cpu.targetPercentage -}}
{{- if $cpu }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ $cpu }}
{{- end }}
{{- $mem := coalesce $svc.hpa.memory.targetPercentage $.Values.hpa.memory.targetPercentage -}}
{{- if $mem }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ $mem }}
{{- end }}
---
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,32 @@
{{- if and .Values.ingress.enabled .Values.ingress.hosts }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "stellaops.fullname" . }}
labels:
{{- include "stellaops.labels" . | nindent 4 }}
annotations:
{{- range $k, $v := .Values.ingress.annotations }}
{{ $k }}: {{ $v | quote }}
{{- end }}
spec:
ingressClassName: {{ .Values.ingress.className | default "nginx" | quote }}
tls:
{{- range .Values.ingress.tls }}
- hosts: {{ toYaml .hosts | nindent 6 }}
secretName: {{ .secretName }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host }}
http:
paths:
- path: {{ .path | default "/" }}
pathType: Prefix
backend:
service:
name: {{ include "stellaops.fullname" $ }}-gateway
port:
number: {{ .servicePort | default 80 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,50 @@
{{- if and .Values.migrations.enabled .Values.migrations.jobs }}
{{- range $job := .Values.migrations.jobs }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "stellaops.fullname" $ }}-migration-{{ $job.name | trunc 30 | trimSuffix "-" }}
labels:
{{- include "stellaops.labels" $ | nindent 4 }}
stellaops.io/component: migration
stellaops.io/migration-name: {{ $job.name | quote }}
spec:
backoffLimit: {{ default 3 $job.backoffLimit }}
ttlSecondsAfterFinished: {{ default 3600 $job.ttlSecondsAfterFinished }}
template:
metadata:
labels:
{{- include "stellaops.selectorLabels" $ | nindent 8 }}
stellaops.io/component: migration
stellaops.io/migration-name: {{ $job.name | quote }}
spec:
restartPolicy: {{ default "Never" $job.restartPolicy }}
serviceAccountName: {{ default "default" $job.serviceAccountName }}
containers:
- name: {{ $job.name | trunc 50 | trimSuffix "-" }}
image: {{ $job.image | quote }}
imagePullPolicy: {{ default "IfNotPresent" $job.imagePullPolicy }}
command: {{- if $job.command }} {{ toJson $job.command }} {{- else }} null {{- end }}
args: {{- if $job.args }} {{ toJson $job.args }} {{- else }} null {{- end }}
env:
{{- if $job.env }}
{{- range $k, $v := $job.env }}
- name: {{ $k }}
value: {{ $v | quote }}
{{- end }}
{{- end }}
envFrom:
{{- if $job.envFrom }}
{{- toYaml $job.envFrom | nindent 12 }}
{{- end }}
resources:
{{- if $job.resources }}
{{- toYaml $job.resources | nindent 12 }}
{{- else }}{}
{{- end }}
imagePullSecrets:
{{- if $.Values.global.image.pullSecrets }}
{{- toYaml $.Values.global.image.pullSecrets | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,45 @@
{{- if .Values.networkPolicy.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "stellaops.fullname" . }}-default
labels:
{{- include "stellaops.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "stellaops.selectorLabelsRoot" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
- from:
{{- if .Values.networkPolicy.ingressNamespaces }}
- namespaceSelector:
matchLabels:
{{- toYaml .Values.networkPolicy.ingressNamespaces | nindent 14 }}
{{- end }}
{{- if .Values.networkPolicy.ingressPods }}
- podSelector:
matchLabels:
{{- toYaml .Values.networkPolicy.ingressPods | nindent 14 }}
{{- end }}
ports:
- protocol: TCP
port: {{ default 80 .Values.networkPolicy.ingressPort }}
egress:
- to:
{{- if .Values.networkPolicy.egressNamespaces }}
- namespaceSelector:
matchLabels:
{{- toYaml .Values.networkPolicy.egressNamespaces | nindent 14 }}
{{- end }}
{{- if .Values.networkPolicy.egressPods }}
- podSelector:
matchLabels:
{{- toYaml .Values.networkPolicy.egressPods | nindent 14 }}
{{- end }}
ports:
- protocol: TCP
port: {{ default 443 .Values.networkPolicy.egressPort }}
{{- end }}

View File

@@ -0,0 +1,22 @@
{{- if .Values.mock.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: orchestrator-mock
annotations:
stellaops.dev/mock: "true"
spec:
replicas: 1
selector:
matchLabels:
app: orchestrator-mock
template:
metadata:
labels:
app: orchestrator-mock
spec:
containers:
- name: orchestrator
image: "{{ .Values.mock.orchestrator.image }}"
args: ["dotnet", "StellaOps.Orchestrator.WebService.dll"]
{{- end }}

View File

@@ -0,0 +1,44 @@
{{- if .Values.mock.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: packs-registry-mock
annotations:
stellaops.dev/mock: "true"
spec:
replicas: 1
selector:
matchLabels:
app: packs-registry-mock
template:
metadata:
labels:
app: packs-registry-mock
spec:
containers:
- name: packs-registry
image: "{{ .Values.mock.packsRegistry.image }}"
args: ["dotnet", "StellaOps.PacksRegistry.dll"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: task-runner-mock
annotations:
stellaops.dev/mock: "true"
spec:
replicas: 1
selector:
matchLabels:
app: task-runner-mock
template:
metadata:
labels:
app: task-runner-mock
spec:
containers:
- name: task-runner
image: "{{ .Values.mock.taskRunner.image }}"
args: ["dotnet", "StellaOps.TaskRunner.WebService.dll"]
{{- end }}

View File

@@ -0,0 +1,22 @@
{{- if .Values.mock.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: policy-registry-mock
annotations:
stellaops.dev/mock: "true"
spec:
replicas: 1
selector:
matchLabels:
app: policy-registry-mock
template:
metadata:
labels:
app: policy-registry-mock
spec:
containers:
- name: policy-registry
image: "{{ .Values.mock.policyRegistry.image }}"
args: ["dotnet", "StellaOps.Policy.Engine.dll"]
{{- end }}

View File

@@ -0,0 +1,22 @@
{{- if .Values.mock.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: vex-lens-mock
annotations:
stellaops.dev/mock: "true"
spec:
replicas: 1
selector:
matchLabels:
app: vex-lens-mock
template:
metadata:
labels:
app: vex-lens-mock
spec:
containers:
- name: vex-lens
image: "{{ .Values.mock.vexLens.image }}"
args: ["dotnet", "StellaOps.VexLens.dll"]
{{- end }}

View File

@@ -0,0 +1,44 @@
{{- if .Values.mock.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: findings-ledger-mock
annotations:
stellaops.dev/mock: "true"
spec:
replicas: 1
selector:
matchLabels:
app: findings-ledger-mock
template:
metadata:
labels:
app: findings-ledger-mock
spec:
containers:
- name: findings-ledger
image: "{{ .Values.mock.findingsLedger.image }}"
args: ["dotnet", "StellaOps.Findings.Ledger.WebService.dll"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: vuln-explorer-api-mock
annotations:
stellaops.dev/mock: "true"
spec:
replicas: 1
selector:
matchLabels:
app: vuln-explorer-api-mock
template:
metadata:
labels:
app: vuln-explorer-api-mock
spec:
containers:
- name: vuln-explorer-api
image: "{{ .Values.mock.vulnExplorerApi.image }}"
args: ["dotnet", "StellaOps.VulnExplorer.Api.dll"]
{{- end }}

View File

@@ -9,6 +9,45 @@ global:
labels:
stellaops.io/channel: airgap
migrations:
enabled: false
jobs: []
networkPolicy:
enabled: true
ingressPort: 8443
egressPort: 443
ingressNamespaces:
kubernetes.io/metadata.name: stellaops
egressNamespaces:
kubernetes.io/metadata.name: stellaops
ingress:
enabled: false
className: nginx
annotations: {}
hosts: []
tls: []
externalSecrets:
enabled: false
secrets: []
prometheus:
enabled: true
path: /metrics
port: 8080
scheme: http
hpa:
enabled: false
minReplicas: 1
maxReplicas: 3
cpu:
targetPercentage: 70
memory:
targetPercentage: 80
configMaps:
notify-config:
data:

View File

@@ -0,0 +1,18 @@
mock:
enabled: true
orchestrator:
image: registry.stella-ops.org/stellaops/orchestrator@sha256:97f12856ce870bafd3328bda86833bcccbf56d255941d804966b5557f6610119
policyRegistry:
image: registry.stella-ops.org/stellaops/policy-registry@sha256:c6cad8055e9827ebcbebb6ad4d6866dce4b83a0a49b0a8a6500b736a5cb26fa7
packsRegistry:
image: registry.stella-ops.org/stellaops/packs-registry@sha256:1f5e9416c4dc608594ad6fad87c24d72134427f899c192b494e22b268499c791
taskRunner:
image: registry.stella-ops.org/stellaops/task-runner@sha256:eb5ad992b49a41554f41516be1a6afcfa6522faf2111c08ff2b3664ad2fc954b
vexLens:
image: registry.stella-ops.org/stellaops/vex-lens@sha256:b44e63ecfeebc345a70c073c1ce5ace709c58be0ffaad0e2862758aeee3092fb
issuerDirectory:
image: registry.stella-ops.org/stellaops/issuer-directory@sha256:67e8ef02c97d3156741e857756994888f30c373ace8e84886762edba9dc51914
findingsLedger:
image: registry.stella-ops.org/stellaops/findings-ledger@sha256:71d4c361ba8b2f8b69d652597bc3f2efc8a64f93fab854ce25272a88506df49c
vulnExplorerApi:
image: registry.stella-ops.org/stellaops/vuln-explorer-api@sha256:7fc7e43a05cbeb0106ce7d4d634612e83de6fdc119aaab754a71c1d60b82841d

View File

@@ -10,6 +10,66 @@ global:
stellaops.io/channel: stable
stellaops.io/profile: prod
# Migration jobs for controlled rollouts (disabled by default)
migrations:
enabled: false
jobs: []
networkPolicy:
enabled: true
ingressPort: 8443
egressPort: 443
ingressNamespaces:
kubernetes.io/metadata.name: stellaops
egressNamespaces:
kubernetes.io/metadata.name: stellaops
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: gateway.prod.stella-ops.org
path: /
servicePort: 80
tls:
- secretName: stellaops-prod-tls
hosts:
- gateway.prod.stella-ops.org
externalSecrets:
enabled: true
secrets:
- name: core-secrets
storeRef:
name: stellaops-secret-store
kind: ClusterSecretStore
target:
name: stellaops-prod-core
data:
- key: STELLAOPS_AUTHORITY__JWT__SIGNINGKEY
remoteKey: prod/authority/jwt-signing-key
- key: STELLAOPS_SECRETS_ENCRYPTION_KEY
remoteKey: prod/core/secrets-encryption-key
prometheus:
enabled: true
path: /metrics
port: 8080
scheme: http
hpa:
enabled: true
minReplicas: 2
maxReplicas: 6
cpu:
targetPercentage: 70
memory:
targetPercentage: 75
configMaps:
notify-config:
data:

View File

@@ -8,6 +8,45 @@ global:
pullPolicy: IfNotPresent
labels: {}
migrations:
enabled: false
jobs: []
networkPolicy:
enabled: false
ingressPort: 80
egressPort: 443
ingressNamespaces: {}
ingressPods: {}
egressNamespaces: {}
egressPods: {}
ingress:
enabled: false
className: nginx
annotations: {}
hosts: []
tls: []
externalSecrets:
enabled: false
secrets: []
prometheus:
enabled: false
path: /metrics
port: 8080
scheme: http
hpa:
enabled: false
minReplicas: 1
maxReplicas: 3
cpu:
targetPercentage: 75
memory:
targetPercentage: null
# Surface.Env configuration for Scanner/Zastava components
# See docs/modules/scanner/design/surface-env.md for details
surface:
@@ -224,3 +263,22 @@ services:
volumeClaims:
- name: advisory-ai-data
claimName: stellaops-advisory-ai-data
mock:
enabled: false
orchestrator:
image: registry.stella-ops.org/stellaops/orchestrator@sha256:97f12856ce870bafd3328bda86833bcccbf56d255941d804966b5557f6610119
policyRegistry:
image: registry.stella-ops.org/stellaops/policy-registry@sha256:c6cad8055e9827ebcbebb6ad4d6866dce4b83a0a49b0a8a6500b736a5cb26fa7
packsRegistry:
image: registry.stella-ops.org/stellaops/packs-registry@sha256:1f5e9416c4dc608594ad6fad87c24d72134427f899c192b494e22b268499c791
taskRunner:
image: registry.stella-ops.org/stellaops/task-runner@sha256:eb5ad992b49a41554f41516be1a6afcfa6522faf2111c08ff2b3664ad2fc954b
vexLens:
image: registry.stella-ops.org/stellaops/vex-lens@sha256:b44e63ecfeebc345a70c073c1ce5ace709c58be0ffaad0e2862758aeee3092fb
issuerDirectory:
image: registry.stella-ops.org/stellaops/issuer-directory@sha256:67e8ef02c97d3156741e857756994888f30c373ace8e84886762edba9dc51914
findingsLedger:
image: registry.stella-ops.org/stellaops/findings-ledger@sha256:71d4c361ba8b2f8b69d652597bc3f2efc8a64f93fab854ce25272a88506df49c
vulnExplorerApi:
image: registry.stella-ops.org/stellaops/vuln-explorer-api@sha256:7fc7e43a05cbeb0106ce7d4d634612e83de6fdc119aaab754a71c1d60b82841d

View File

@@ -0,0 +1,49 @@
release:
version: 2025.09.2
channel: stable
date: '2025-09-20T00:00:00Z'
calendar: '2025.09'
components:
- name: authority
image: registry.stella-ops.org/stellaops/authority@sha256:b0348bad1d0b401cc3c71cb40ba034c8043b6c8874546f90d4783c9dbfcc0bf5
- name: signer
image: registry.stella-ops.org/stellaops/signer@sha256:8ad574e61f3a9e9bda8a58eb2700ae46813284e35a150b1137bc7c2b92ac0f2e
- name: attestor
image: registry.stella-ops.org/stellaops/attestor@sha256:0534985f978b0b5d220d73c96fddd962cd9135f616811cbe3bff4666c5af568f
- name: scanner-web
image: registry.stella-ops.org/stellaops/scanner-web@sha256:14b23448c3f9586a9156370b3e8c1991b61907efa666ca37dd3aaed1e79fe3b7
- name: scanner-worker
image: registry.stella-ops.org/stellaops/scanner-worker@sha256:32e25e76386eb9ea8bee0a1ad546775db9a2df989fab61ac877e351881960dab
- name: concelier
image: registry.stella-ops.org/stellaops/concelier@sha256:c58cdcaee1d266d68d498e41110a589dd204b487d37381096bd61ab345a867c5
- name: excititor
image: registry.stella-ops.org/stellaops/excititor@sha256:59022e2016aebcef5c856d163ae705755d3f81949d41195256e935ef40a627fa
- name: advisory-ai-web
image: registry.stella-ops.org/stellaops/advisory-ai-web:2025.09.2
- name: advisory-ai-worker
image: registry.stella-ops.org/stellaops/advisory-ai-worker:2025.09.2
- name: web-ui
image: registry.stella-ops.org/stellaops/web-ui@sha256:10d924808c48e4353e3a241da62eb7aefe727a1d6dc830eb23a8e181013b3a23
- name: orchestrator
image: registry.stella-ops.org/stellaops/orchestrator@sha256:97f12856ce870bafd3328bda86833bcccbf56d255941d804966b5557f6610119
- name: policy-registry
image: registry.stella-ops.org/stellaops/policy-registry@sha256:c6cad8055e9827ebcbebb6ad4d6866dce4b83a0a49b0a8a6500b736a5cb26fa7
- name: vex-lens
image: registry.stella-ops.org/stellaops/vex-lens@sha256:b44e63ecfeebc345a70c073c1ce5ace709c58be0ffaad0e2862758aeee3092fb
- name: issuer-directory
image: registry.stella-ops.org/stellaops/issuer-directory@sha256:67e8ef02c97d3156741e857756994888f30c373ace8e84886762edba9dc51914
- name: findings-ledger
image: registry.stella-ops.org/stellaops/findings-ledger@sha256:71d4c361ba8b2f8b69d652597bc3f2efc8a64f93fab854ce25272a88506df49c
- name: vuln-explorer-api
image: registry.stella-ops.org/stellaops/vuln-explorer-api@sha256:7fc7e43a05cbeb0106ce7d4d634612e83de6fdc119aaab754a71c1d60b82841d
- name: packs-registry
image: registry.stella-ops.org/stellaops/packs-registry@sha256:1f5e9416c4dc608594ad6fad87c24d72134427f899c192b494e22b268499c791
- name: task-runner
image: registry.stella-ops.org/stellaops/task-runner@sha256:eb5ad992b49a41554f41516be1a6afcfa6522faf2111c08ff2b3664ad2fc954b
infrastructure:
mongo:
image: docker.io/library/mongo@sha256:c258b26dbb7774f97f52aff52231ca5f228273a84329c5f5e451c3739457db49
minio:
image: docker.io/minio/minio@sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e
checksums:
releaseManifestSha256: dc3c8fe1ab83941c838ccc5a8a5862f7ddfa38c2078e580b5649db26554565b7

View File

@@ -1,8 +1,25 @@
openapi: 3.1.0
info:
title: StellaOps Concelier Link-Not-Merge Policy APIs
version: "0.1.0"
description: Fact-only advisory/linkset retrieval for Policy Engine consumers.
version: "1.0.0"
description: |
Fact-only advisory/linkset retrieval for Policy Engine consumers.
## Philosophy
Link-Not-Merge (LNM) provides raw advisory data with full provenance:
- **Link**: Observations from multiple sources are linked via shared identifiers.
- **Not Merge**: Conflicting data is preserved rather than collapsed.
- **Surface, Don't Resolve**: Conflicts are clearly marked for consumers.
## Authentication
All endpoints require the `X-Stella-Tenant` header for multi-tenant isolation.
## Pagination
List endpoints support cursor-based pagination with `page` and `pageSize` parameters.
Maximum page size is 200 items.
## Documentation
See `/docs/modules/concelier/api/` for detailed examples and conflict resolution strategies.
servers:
- url: /
description: Relative base path (API Gateway rewrites in production).
@@ -44,6 +61,65 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/PagedLinksets'
examples:
single-linkset:
summary: Single linkset result
value:
items:
- advisoryId: "CVE-2021-23337"
source: "nvd"
purl: ["pkg:npm/lodash@4.17.20"]
cpe: ["cpe:2.3:a:lodash:lodash:4.17.20:*:*:*:*:node.js:*:*"]
summary: "Lodash Command Injection vulnerability"
publishedAt: "2021-02-15T13:15:00Z"
modifiedAt: "2024-08-04T19:16:00Z"
severity: "high"
provenance:
ingestedAt: "2025-11-20T10:30:00Z"
connectorId: "nvd-osv-connector"
evidenceHash: "sha256:a1b2c3d4e5f6"
conflicts: []
cached: false
page: 1
pageSize: 50
total: 1
with-conflicts:
summary: Linkset with severity conflict
value:
items:
- advisoryId: "CVE-2024-1234"
source: "aggregated"
purl: ["pkg:npm/example@1.0.0"]
cpe: []
severity: "high"
provenance:
ingestedAt: "2025-11-20T10:30:00Z"
connectorId: "multi-source"
conflicts:
- field: "severity"
reason: "severity-mismatch"
observedValue: "critical"
observedAt: "2025-11-18T08:00:00Z"
evidenceHash: "sha256:conflict-hash"
cached: false
page: 1
pageSize: 50
total: 1
"400":
description: Invalid request parameters
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
example:
type: "https://stellaops.io/errors/validation-failed"
title: "Validation Failed"
status: 400
detail: "The 'pageSize' parameter exceeds the maximum allowed value."
error:
code: "ERR_PAGE_SIZE_EXCEEDED"
message: "Page size must be between 1 and 200."
target: "pageSize"
/v1/lnm/linksets/{advisoryId}:
get:
summary: Get linkset by advisory ID
@@ -275,3 +351,63 @@ components:
event: { type: string }
at: { type: string, format: date-time }
evidenceHash: { type: string }
ErrorEnvelope:
type: object
description: RFC 7807 Problem Details with StellaOps extensions
properties:
type:
type: string
format: uri
description: URI identifying the problem type
title:
type: string
description: Short, human-readable summary
status:
type: integer
description: HTTP status code
detail:
type: string
description: Specific explanation of the problem
instance:
type: string
format: uri
description: URI of the specific occurrence
traceId:
type: string
description: Distributed trace identifier
error:
$ref: '#/components/schemas/ErrorDetail'
ErrorDetail:
type: object
description: Machine-readable error information
properties:
code:
type: string
description: Machine-readable error code (e.g., ERR_VALIDATION_FAILED)
message:
type: string
description: Human-readable error message
target:
type: string
description: Field or resource that caused the error
metadata:
type: object
additionalProperties: true
description: Additional contextual data
innerErrors:
type: array
items:
$ref: '#/components/schemas/ValidationError'
description: Nested validation errors
ValidationError:
type: object
properties:
field:
type: string
description: Field path (e.g., "data.severity")
code:
type: string
description: Error code for this field
message:
type: string
description: Human-readable message

View File

@@ -0,0 +1,20 @@
# Console Exceptions API Schema (draft placeholder)
**Status:** TODO · awaiting Policy Guild + Platform Events
## Scope
- `/exceptions` CRUD/workflow (create, propose, approve, revoke, list, history) proxied by Web gateway.
- Audit logging, pagination, notification hooks, rate limits, RBAC scopes.
## Needed from owners
- JSON schema for exception entity and workflow transitions; validation rules.
- Required scopes/roles; audit fields; pagination/sorting defaults; max durations/guardrails.
- Notification hook contract (`exception.*` events) and rate-limit policy.
- Sample payloads for each state and error cases.
## Draft sample (placeholder)
- See `docs/api/console/samples/exception-schema-sample.json` for a skeleton payload covering `pending_review` state.
- Replace with authoritative samples once schema is published.
## TODO
- Replace with ratified schema + samples; log hash/date; link from Web I/II sprint logs.

View File

@@ -0,0 +1,14 @@
event: started
data: {"exportId":"console-export::tenant-default::2025-12-06::0007","status":"running","percent":0}
event: progress
data: {"exportId":"console-export::tenant-default::2025-12-06::0007","percent":25,"itemsCompleted":125,"itemsTotal":500}
event: asset_ready
data: {"exportId":"console-export::tenant-default::2025-12-06::0007","type":"advisory","id":"CVE-2024-12345","url":"https://exports.local/...","sha256":"cafe0001..."}
event: progress
data: {"exportId":"console-export::tenant-default::2025-12-06::0007","percent":75,"itemsCompleted":375,"itemsTotal":500}
event: completed
data: {"exportId":"console-export::tenant-default::2025-12-06::0007","status":"succeeded","manifestUrl":"https://exports.local/.../manifest.json"}

View File

@@ -0,0 +1,36 @@
{
"version": "2025-12-06",
"exportId": "console-export::tenant-default::2025-12-06::0007",
"tenantId": "tenant-default",
"generatedAt": "2025-12-06T12:11:05Z",
"items": [
{
"type": "advisory",
"id": "CVE-2024-12345",
"url": "https://exports.local/tenant-default/0007/CVE-2024-12345.json?sig=...",
"sha256": "cafe0001..."
},
{
"type": "vex",
"id": "vex:tenant-default:jwt-auth:5d1a",
"url": "https://exports.local/tenant-default/0007/vex-jwt-auth.ndjson?sig=...",
"sha256": "cafe0002..."
},
{
"type": "policy",
"id": "policy://tenant-default/runtime-hardening",
"url": "https://exports.local/tenant-default/0007/policy-runtime-hardening.json?sig=...",
"sha256": "cafe0003..."
},
{
"type": "scan",
"id": "scan::tenant-default::auth-api::2025-11-07",
"url": "https://exports.local/tenant-default/0007/scan-auth-api.ndjson?sig=...",
"sha256": "cafe0004..."
}
],
"checksums": {
"manifest": "c0ffee...",
"bundle": "deadbeef..."
}
}

View File

@@ -0,0 +1,16 @@
{
"scope": {
"tenantId": "tenant-default",
"projectId": "sre-prod"
},
"sources": [
{ "type": "advisory", "ids": ["CVE-2024-12345", "CVE-2024-23456"] },
{ "type": "vex", "ids": ["vex:tenant-default:jwt-auth:5d1a"] },
{ "type": "policy", "ids": ["policy://tenant-default/runtime-hardening"] },
{ "type": "scan", "ids": ["scan::tenant-default::auth-api::2025-11-07"] }
],
"formats": ["json", "ndjson", "csv"],
"attestations": { "include": true, "sigstoreBundle": true },
"notify": { "webhooks": ["https://hooks.local/export"], "email": ["secops@example.com"] },
"priority": "normal"
}

View File

@@ -0,0 +1,24 @@
{
"exportId": "console-export::tenant-default::2025-12-06::0007",
"status": "running",
"estimateSeconds": 420,
"retryAfter": 15,
"createdAt": "2025-12-06T12:10:00Z",
"updatedAt": "2025-12-06T12:11:05Z",
"outputs": [
{
"type": "manifest",
"format": "json",
"url": "https://exports.local/tenant-default/0007/manifest.json?sig=...",
"sha256": "c0ffee...",
"expiresAt": "2025-12-06T13:10:00Z"
}
],
"progress": {
"percent": 42,
"itemsCompleted": 210,
"itemsTotal": 500,
"assetsReady": 12
},
"errors": []
}

View File

@@ -0,0 +1,37 @@
{
"exceptionId": "exc::tenant-default::2025-12-06::00012",
"tenantId": "tenant-default",
"title": "Risk accepted for log4j on batch nodes",
"state": "pending_review",
"type": "advisory",
"scope": {
"level": "asset",
"assetIds": ["batch-node-17", "batch-node-18"],
"advisoryIds": ["CVE-2021-44228"],
"components": ["pkg:maven/org.apache.logging.log4j/log4j-core@2.14.0"]
},
"justification": {
"template": "compensating_control",
"details": "Ingress disabled; nodes isolated; patch planned 2025-12-20"
},
"timebox": {
"start": "2025-12-06T00:00:00Z",
"end": "2025-12-31T00:00:00Z",
"maxRenewals": 1
},
"audit": {
"createdBy": "alice@example.com",
"createdAt": "2025-12-06T11:12:13Z",
"modifiedAt": "2025-12-06T11:12:13Z"
},
"links": {
"history": "/console/exceptions/exc::tenant-default::2025-12-06::00012/history",
"attachments": [
{
"name": "risk-assessment.pdf",
"url": "https://console.local/files/risk-assessment.pdf?sig=...",
"sha256": "cafe..."
}
]
}
}

View File

@@ -309,3 +309,85 @@ data: {
- `docs/api/console/samples/vex-statement-sse.ndjson` contains 5 chronological SSE events for screenshot reproduction.
> Until backend implementations ship, use the examples above to unblock DOCS-AIAI-31-004; replace them with live captures once the gateway endpoints are available in staging.
## Exports (draft contract v0.3)
### Routes
- `POST /console/exports` — start an evidence bundle export job.
- `GET /console/exports/{exportId}` — fetch job status and download locations.
- `GET /console/exports/{exportId}/events` — SSE stream of job progress (optional).
### Security / headers
- `Authorization: DPoP <token>`
- `DPoP: <proof>`
- `X-StellaOps-Tenant: <tenantId>`
- `Idempotency-Key: <uuid>` (recommended for POST)
- `Accept: application/json` (status) or `text/event-stream` (events)
- Required scopes: `console:read` AND `console:export` (proposal).
### Request body (POST)
```jsonc
{
"scope": { "tenantId": "t1", "projectId": "p1" },
"sources": [ { "type": "advisory", "ids": ["CVE-2024-12345"] } ],
"formats": ["json", "ndjson", "csv"],
"attestations": { "include": true, "sigstoreBundle": true },
"notify": { "webhooks": ["https://hooks.local/export"], "email": ["secops@example.com"] },
"priority": "normal"
}
```
### Response: 202 Accepted
- `exportId`: string
- `status`: `queued|running|succeeded|failed|expired`
- `estimateSeconds`: int
- `retryAfter`: int seconds (for polling)
- `links`: `{ status: url, events?: url }`
### Response: GET status
```jsonc
{
"exportId": "console-export::tenant-default::2025-12-06::0007",
"status": "running",
"estimateSeconds": 420,
"outputs": [
{ "type": "manifest", "format": "json", "url": "https://.../manifest.json?sig=...", "sha256": "...", "expiresAt": "2025-12-06T13:10:00Z" }
],
"progress": { "percent": 42, "itemsCompleted": 210, "itemsTotal": 500, "assetsReady": 12 },
"errors": []
}
```
### Response: SSE events
- `started`: `{ exportId, status }`
- `progress`: `{ exportId, percent, itemsCompleted, itemsTotal }`
- `asset_ready`: `{ exportId, type, id, url, sha256 }`
- `completed`: `{ exportId, status: "succeeded", manifestUrl }`
- `failed`: `{ exportId, status: "failed", code, message }`
### Manifest shape (downloaded via outputs)
- `version`: string (date)
- `exportId`, `tenantId`, `generatedAt`
- `items[]`: `{ type: advisory|vex|policy|scan, id, url, sha256 }`
- `checksums`: `{ manifest, bundle }`
### Limits (proposed)
- Max request body 256 KiB; max sources 50; max outputs 1000 assets/export.
- Default job timeout 30 minutes; idle SSE timeout 60s; backoff via `Retry-After`.
### Error codes (proposal)
- `ERR_CONSOLE_EXPORT_INVALID_SOURCE`
- `ERR_CONSOLE_EXPORT_TOO_LARGE`
- `ERR_CONSOLE_EXPORT_RATE_LIMIT`
- `ERR_CONSOLE_EXPORT_UNAVAILABLE`
### Samples
- Request: `docs/api/console/samples/console-export-request.json`
- Status: `docs/api/console/samples/console-export-status.json`
- Manifest: `docs/api/console/samples/console-export-manifest.json`
- Events: `docs/api/console/samples/console-export-events.ndjson`
### Open items (needs guild sign-off)
- Final scopes list (`console:export` vs broader `console:*`).
- Final limits and error codes; checksum manifest format; attestation options.
- Caching/tie-break rules for downstream `/console/search` and `/console/downloads`.

View File

@@ -0,0 +1,79 @@
# Export Center Gateway Contract (draft placeholder)
**Status:** Draft v0.2 · owner-proposed
## Scope
- Profile, run, download, and distribution routes proxied via Web gateway.
- Tenant scoping, RBAC/ABAC, streaming limits, retention/encryption parameters, signed URL policy.
## Endpoints
- `GET /export-center/profiles` — list export profiles (tenant-scoped).
- `POST /export-center/runs` — start an export run.
- `GET /export-center/runs/{runId}` — run status and artifacts.
- `GET /export-center/runs/{runId}/events` — SSE for run progress.
- `GET /export-center/distributions/{id}` — fetch signed URLs for OCI/object storage distribution.
## Security / headers
- `Authorization: DPoP <token>`; `DPoP: <proof>`
- `X-StellaOps-Tenant: <tenantId>` (required)
- `X-StellaOps-Project: <projectId>` (optional)
- `Idempotency-Key` (recommended for POST)
- Required scopes (proposal): `export:read`, `export:write`.
## Request: POST /export-center/runs
```jsonc
{
"profileId": "export-profile::tenant-default::daily-vex",
"targets": ["vex", "advisory", "policy"],
"formats": ["json", "ndjson"],
"distribution": {
"type": "oci",
"ref": "registry.local/exports/daily",
"signing": { "enabled": true, "keyRef": "k8s://secrets/eks/oci-signer" }
},
"retentionDays": 30,
"encryption": { "enabled": true, "kmsKey": "kms://tenant-default/key1" },
"priority": "normal"
}
```
## Response: 202 Accepted
- `runId`, `status: queued|running|succeeded|failed|expired`, `estimateSeconds`, `retryAfter`.
## Response: GET run
```jsonc
{
"runId": "export-run::tenant-default::2025-12-06::0003",
"status": "running",
"profileId": "export-profile::tenant-default::daily-vex",
"startedAt": "2025-12-06T10:00:00Z",
"outputs": [
{ "type": "manifest", "format": "json", "url": "https://exports.local/.../manifest.json?sig=...", "sha256": "...", "expiresAt": "2025-12-06T16:00:00Z" }
],
"progress": { "percent": 35, "itemsCompleted": 70, "itemsTotal": 200 },
"errors": []
}
```
## SSE events
- `started`, `progress`, `artifact_ready` (url, sha256, type), `completed`, `failed` (code, message).
## Limits (proposal)
- Max request body 256 KiB; max targets 50; default timeout 60 minutes.
- Idle SSE timeout 60s; backoff with `Retry-After`.
## Error codes (proposal)
- `ERR_EXPORT_PROFILE_NOT_FOUND`
- `ERR_EXPORT_REQUEST_INVALID`
- `ERR_EXPORT_TOO_LARGE`
- `ERR_EXPORT_RATE_LIMIT`
- `ERR_EXPORT_DISTRIBUTION_FAILED`
## Samples
- Profile list sample: _todo_
- Run request/response: see above snippets.
- Events NDJSON: _todo_
## Outstanding (for finalization)
- Confirm scopes, limits, distribution signing rules, and manifest checksum requirements.
- Provide full OpenAPI/JSON schema and sample artifacts for OCI/object storage distributions.

View File

@@ -0,0 +1,42 @@
# Graph Overlay & Cache Schema (draft placeholder)
**Status:** Draft v0.2 · owner-proposed
## Scope
- Overlay/cache schema for graph tiles used by Web gateway and UI overlays.
- Validation rules for bbox/zoom/path; pagination tokens; deterministic ordering.
- Error codes and sampling/telemetry fields.
## Schema (draft)
```jsonc
{
"version": "2025-12-06",
"tenantId": "tenant-default",
"tile": {
"id": "graph-tile::asset::<hash>::z8/x12/y5",
"bbox": { "minX": -122.41, "minY": 37.77, "maxX": -122.38, "maxY": 37.79 },
"zoom": 8,
"etag": "c0ffee-etag"
},
"nodes": [ { "id": "asset:...", "kind": "asset|component|vuln", "label": "", "severity": "high|medium|low|info", "reachability": "reachable|unreachable|unknown", "attributes": {} } ],
"edges": [ { "id": "edge-1", "source": "nodeId", "target": "nodeId", "type": "depends_on|contains|evidence", "weight": 0.0 } ],
"overlays": {
"policy": [ { "nodeId": "nodeId", "badge": "pass|warn|fail|waived", "policyId": "", "verdictAt": "2025-12-05T09:00:00Z" } ],
"vex": [ { "nodeId": "nodeId", "state": "not_affected|fixed|under_investigation|affected", "statementId": "", "lastUpdated": "2025-12-05T09:10:00Z" } ],
"aoc": [ { "nodeId": "nodeId", "status": "pass|fail|warn", "lastVerified": "2025-12-05T10:11:12Z" } ]
},
"telemetry": { "generationMs": 0, "cache": "hit|miss", "samples": 0 }
}
```
## Constraints (proposal)
- Max nodes per tile: 2,000; max edges: 4,000.
- Zoom range: 012; tiles must include bbox and etag.
- Arrays must be pre-sorted: nodes by `id`, edges by `id`, overlays by `nodeId` then `policyId|statementId`.
## Samples
- `docs/api/graph/samples/overlay-sample.json`
## Outstanding
- Confirm max sizes, allowed edge types, and etag hashing rule.
- Provide validation error example and rate-limit headers for gateway responses.

View File

@@ -0,0 +1,75 @@
{
"version": "2025-12-06",
"tenantId": "tenant-default",
"tile": {
"id": "graph-tile::asset::sha256:abc123::z8/x12/y5",
"bbox": {
"minX": -122.41,
"minY": 37.77,
"maxX": -122.38,
"maxY": 37.79
},
"zoom": 8,
"etag": "c0ffee-overlay-etag"
},
"nodes": [
{
"id": "asset:registry.local/library/app@sha256:abc123",
"kind": "asset",
"label": "app:1.2.3",
"severity": "high",
"reachability": "reachable",
"aoc": { "summary": "pass", "lastVerified": "2025-12-05T10:11:12Z" },
"attributes": {
"purl": "pkg:docker/app@sha256:abc123",
"componentCount": 42
}
},
{
"id": "component:pkg:npm/jsonwebtoken@9.0.2",
"kind": "component",
"label": "jsonwebtoken@9.0.2",
"severity": "high",
"reachability": "reachable"
}
],
"edges": [
{
"id": "edge-1",
"source": "asset:registry.local/library/app@sha256:abc123",
"target": "component:pkg:npm/jsonwebtoken@9.0.2",
"type": "depends_on",
"weight": 0.87
}
],
"overlays": {
"policy": [
{
"nodeId": "component:pkg:npm/jsonwebtoken@9.0.2",
"badge": "fail",
"policyId": "policy://tenant-default/runtime-hardening",
"verdictAt": "2025-12-05T09:00:00Z"
}
],
"vex": [
{
"nodeId": "component:pkg:npm/jsonwebtoken@9.0.2",
"state": "under_investigation",
"statementId": "vex:tenant-default:jwt:2025-12-05",
"lastUpdated": "2025-12-05T09:10:00Z"
}
],
"aoc": [
{
"nodeId": "asset:registry.local/library/app@sha256:abc123",
"status": "pass",
"lastVerified": "2025-12-05T10:11:12Z"
}
]
},
"telemetry": {
"generationMs": 120,
"cache": "hit",
"samples": 3
}
}

View File

@@ -0,0 +1,66 @@
# Signals Reachability API Contract (draft placeholder)
**Status:** Draft v0.2 · owner-proposed
## Scope
- `/signals/callgraphs`, `/signals/facts`, reachability scoring overlays feeding UI/Web.
- Deterministic fixtures for SIG-26 chain (columns/badges, call paths, timelines, overlays, coverage).
## Endpoints
- `GET /signals/callgraphs` — returns call paths contributing to reachability.
- `GET /signals/facts` — returns reachability/coverage facts.
Common headers: `Authorization: DPoP <token>`, `DPoP: <proof>`, `X-StellaOps-Tenant`, optional `If-None-Match`.
Pagination: cursor via `pageToken`; default 50, max 200.
ETag: required on responses; clients must send `If-None-Match` for cache validation.
### Callgraphs response (draft)
```jsonc
{
"tenantId": "tenant-default",
"assetId": "registry.local/library/app@sha256:abc123",
"paths": [
{
"id": "path-1",
"source": "api-gateway",
"target": "jwt-auth-service",
"hops": [
{ "service": "api-gateway", "endpoint": "/login", "timestamp": "2025-12-05T10:00:00Z" },
{ "service": "jwt-auth-service", "endpoint": "/verify", "timestamp": "2025-12-05T10:00:01Z" }
],
"evidence": { "traceId": "trace-abc", "spanCount": 2, "score": 0.92 }
}
],
"pagination": { "nextPageToken": null },
"etag": "sig-callgraphs-etag"
}
```
### Facts response (draft)
```jsonc
{
"tenantId": "tenant-default",
"facts": [
{
"id": "fact-1",
"type": "reachability",
"assetId": "registry.local/library/app@sha256:abc123",
"component": "pkg:npm/jsonwebtoken@9.0.2",
"status": "reachable",
"confidence": 0.88,
"observedAt": "2025-12-05T10:10:00Z",
"signalsVersion": "signals-2025.310.1"
}
],
"pagination": { "nextPageToken": "..." },
"etag": "sig-facts-etag"
}
```
### Samples
- Callgraphs: `docs/api/signals/samples/callgraph-sample.json`
- Facts: `docs/api/signals/samples/facts-sample.json`
### Outstanding
- Finalize score model, accepted `type` values, and max page size.
- Provide OpenAPI/JSON schema and error codes.

View File

@@ -0,0 +1,23 @@
{
"tenantId": "tenant-default",
"assetId": "registry.local/library/app@sha256:abc123",
"paths": [
{
"id": "path-1",
"source": "api-gateway",
"target": "jwt-auth-service",
"hops": [
{ "service": "api-gateway", "endpoint": "/login", "timestamp": "2025-12-05T10:00:00Z" },
{ "service": "jwt-auth-service", "endpoint": "/verify", "timestamp": "2025-12-05T10:00:01Z" }
],
"evidence": {
"traceId": "trace-abc",
"spanCount": 2,
"score": 0.92
}
}
],
"pagination": {
"nextPageToken": null
}
}

View File

@@ -0,0 +1,26 @@
{
"tenantId": "tenant-default",
"facts": [
{
"id": "fact-1",
"type": "reachability",
"assetId": "registry.local/library/app@sha256:abc123",
"component": "pkg:npm/jsonwebtoken@9.0.2",
"status": "reachable",
"confidence": 0.88,
"observedAt": "2025-12-05T10:10:00Z",
"signalsVersion": "signals-2025.310.1"
},
{
"id": "fact-2",
"type": "coverage",
"assetId": "registry.local/library/app@sha256:abc123",
"metric": "sensors_present",
"value": 0.94,
"observedAt": "2025-12-05T10:11:00Z"
}
],
"pagination": {
"nextPageToken": "eyJmYWN0SWQiOiJmYWN0LTIifQ"
}
}

View File

@@ -0,0 +1,886 @@
# OpenAPI 3.1 specification for StellaOps TaskRunner WebService
openapi: 3.1.0
info:
title: StellaOps TaskRunner API
version: 0.1.0-draft
description: |
Contract for TaskRunner service covering pack runs, simulations, logs, artifacts, and approvals.
Uses the platform error envelope and tenant header `X-StellaOps-Tenant`.
## Streaming Endpoints
The `/runs/{runId}/logs` endpoint returns logs in NDJSON (Newline Delimited JSON) format
for efficient streaming. Each line is a complete JSON object.
## Control Flow Steps
TaskPacks support the following step kinds:
- **run**: Execute an action using a builtin or custom executor
- **parallel**: Execute child steps concurrently with optional maxParallel limit
- **map**: Iterate over items and execute a template step for each
- **loop**: Iterate with items expression, range, or static list
- **conditional**: Branch based on condition expressions
- **gate.approval**: Require manual approval before proceeding
- **gate.policy**: Evaluate policy and optionally require override approval
servers:
- url: https://taskrunner.stellaops.example.com
description: Production
- url: https://taskrunner.dev.stellaops.example.com
description: Development
security:
- oauth2: [taskrunner.viewer]
- oauth2: [taskrunner.operator]
- oauth2: [taskrunner.admin]
paths:
/v1/task-runner/simulations:
post:
summary: Simulate a task pack
description: |
Validates a task pack manifest, creates an execution plan, and simulates the run
without actually executing any steps. Returns the simulation result showing which
steps would execute, which are skipped, and which require approvals.
operationId: simulateTaskPack
tags: [Simulations]
parameters:
- $ref: '#/components/parameters/Tenant'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SimulationRequest'
examples:
basic-simulation:
summary: Basic simulation request
value:
manifest: |
apiVersion: stellaops.io/pack.v1
kind: TaskPack
metadata:
name: scan-deploy
version: 1.0.0
spec:
inputs:
- name: target
type: string
required: true
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 100
memoryLimitMiB: 128
quotaSeconds: 60
slo:
runP95Seconds: 300
approvalP95Seconds: 900
maxQueueDepth: 100
steps:
- id: scan
run:
uses: builtin:scanner
with:
target: "{{ inputs.target }}"
inputs:
target: "registry.example.com/app:v1.2.3"
responses:
'200':
description: Simulation completed
content:
application/json:
schema:
$ref: '#/components/schemas/SimulationResponse'
examples:
simulation-result:
value:
planHash: "sha256:a1b2c3d4e5f6..."
failurePolicy:
maxAttempts: 1
backoffSeconds: 0
continueOnError: false
steps:
- id: scan
templateId: scan
kind: Run
enabled: true
status: Pending
uses: "builtin:scanner"
children: []
outputs: []
hasPendingApprovals: false
'400':
description: Invalid manifest or inputs
content:
application/json:
schema:
$ref: '#/components/schemas/PlanErrorResponse'
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs:
post:
summary: Create a pack run
description: |
Creates a new pack run from a task pack manifest. The run is scheduled for execution
and will proceed through its steps. If approval gates are present, the run will pause
at those gates until approvals are granted.
operationId: createPackRun
tags: [Runs]
parameters:
- $ref: '#/components/parameters/Tenant'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateRunRequest'
examples:
create-run:
summary: Create a new run
value:
runId: "run-20251206-001"
manifest: |
apiVersion: stellaops.io/pack.v1
kind: TaskPack
metadata:
name: deploy-app
version: 2.0.0
spec:
sandbox:
mode: sealed
egressAllowlist: []
cpuLimitMillicores: 200
memoryLimitMiB: 256
quotaSeconds: 120
slo:
runP95Seconds: 600
approvalP95Seconds: 1800
maxQueueDepth: 50
approvals:
- id: security-review
grants: [packs.approve]
steps:
- id: build
run:
uses: builtin:build
- id: approval
gate:
approval:
id: security-review
message: "Security review required before deploy"
- id: deploy
run:
uses: builtin:deploy
tenantId: "tenant-prod"
responses:
'201':
description: Run created
headers:
Location:
description: URL of the created run
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/RunStateResponse'
'400':
description: Invalid manifest or inputs
content:
application/json:
schema:
$ref: '#/components/schemas/PlanErrorResponse'
'409':
description: Run ID already exists
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs/{runId}:
get:
summary: Get run state
description: |
Returns the current state of a pack run, including status of all steps,
failure policy, and timing information.
operationId: getRunState
tags: [Runs]
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/RunId'
responses:
'200':
description: Run state
content:
application/json:
schema:
$ref: '#/components/schemas/RunStateResponse'
examples:
running:
summary: Run in progress
value:
runId: "run-20251206-001"
planHash: "sha256:a1b2c3d4..."
failurePolicy:
maxAttempts: 2
backoffSeconds: 30
continueOnError: false
createdAt: "2025-12-06T10:00:00Z"
updatedAt: "2025-12-06T10:05:00Z"
steps:
- stepId: build
kind: Run
enabled: true
continueOnError: false
status: Succeeded
attempts: 1
lastTransitionAt: "2025-12-06T10:02:00Z"
- stepId: approval
kind: GateApproval
enabled: true
continueOnError: false
approvalId: security-review
gateMessage: "Security review required before deploy"
status: Pending
attempts: 0
statusReason: "awaiting-approval"
- stepId: deploy
kind: Run
enabled: true
continueOnError: false
status: Pending
attempts: 0
'404':
description: Run not found
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs/{runId}/logs:
get:
summary: Stream run logs
description: |
Returns run logs as a stream of NDJSON (Newline Delimited JSON) entries.
Each line is a complete JSON object representing a log entry with timestamp,
level, event type, message, and optional metadata.
**Content-Type**: `application/x-ndjson`
operationId: streamRunLogs
tags: [Logs]
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/RunId'
responses:
'200':
description: Log stream
content:
application/x-ndjson:
schema:
$ref: '#/components/schemas/RunLogEntry'
examples:
log-stream:
summary: Sample NDJSON log stream
value: |
{"timestamp":"2025-12-06T10:00:00Z","level":"info","eventType":"run.created","message":"Run created via API.","metadata":{"planHash":"sha256:a1b2c3d4...","requestedAt":"2025-12-06T10:00:00Z"}}
{"timestamp":"2025-12-06T10:00:01Z","level":"info","eventType":"step.started","message":"Starting step: build","stepId":"build"}
{"timestamp":"2025-12-06T10:02:00Z","level":"info","eventType":"step.completed","message":"Step completed: build","stepId":"build","metadata":{"duration":"119s"}}
{"timestamp":"2025-12-06T10:02:01Z","level":"warn","eventType":"gate.awaiting","message":"Awaiting approval: security-review","stepId":"approval"}
'404':
description: Run not found
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs/{runId}/artifacts:
get:
summary: List run artifacts
description: |
Returns a list of artifacts captured during the run, including file outputs,
evidence bundles, and expression-evaluated results.
operationId: listRunArtifacts
tags: [Artifacts]
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/RunId'
responses:
'200':
description: Artifact list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/RunArtifact'
examples:
artifacts:
value:
- name: scan-report
type: file
sourcePath: "/output/scan-report.json"
storedPath: "runs/run-20251206-001/artifacts/scan-report.json"
status: captured
capturedAt: "2025-12-06T10:02:00Z"
- name: evidence-bundle
type: object
status: captured
capturedAt: "2025-12-06T10:02:00Z"
expressionJson: '{"sha256":"abc123...","attestations":[...]}'
'404':
description: Run not found
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs/{runId}/approvals/{approvalId}:
post:
summary: Apply approval decision
description: |
Applies an approval decision (approved, rejected, or expired) to a pending
approval gate. The planHash must match to prevent approving a stale plan.
If approved, the run will resume execution. If rejected, the run will fail
at the gate step.
operationId: applyApprovalDecision
tags: [Approvals]
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/RunId'
- $ref: '#/components/parameters/ApprovalId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ApprovalDecisionRequest'
examples:
approve:
summary: Approve the gate
value:
decision: approved
planHash: "sha256:a1b2c3d4e5f678901234567890abcdef1234567890abcdef1234567890abcdef"
actorId: "user:alice@example.com"
summary: "Reviewed and approved for production deployment"
reject:
summary: Reject the gate
value:
decision: rejected
planHash: "sha256:a1b2c3d4e5f678901234567890abcdef1234567890abcdef1234567890abcdef"
actorId: "user:bob@example.com"
summary: "Security scan found critical vulnerabilities"
responses:
'200':
description: Decision applied
content:
application/json:
schema:
$ref: '#/components/schemas/ApprovalDecisionResponse'
examples:
approved:
value:
status: approved
resumed: true
'400':
description: Invalid decision or planHash format
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'404':
description: Run or approval not found
'409':
description: Plan hash mismatch
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
default:
$ref: '#/components/responses/Error'
/v1/task-runner/runs/{runId}/cancel:
post:
summary: Cancel a run
description: |
Requests cancellation of a run. Remaining pending steps will be marked as
skipped. Steps that have already succeeded or been skipped are not affected.
operationId: cancelRun
tags: [Runs]
parameters:
- $ref: '#/components/parameters/Tenant'
- $ref: '#/components/parameters/RunId'
responses:
'202':
description: Cancellation accepted
headers:
Location:
description: URL of the run
schema:
type: string
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: [cancelled]
'404':
description: Run not found
default:
$ref: '#/components/responses/Error'
/.well-known/openapi:
get:
summary: Get OpenAPI metadata
description: |
Returns metadata about the OpenAPI specification including the spec URL,
ETag for caching, and a signature for verification.
operationId: getOpenApiMetadata
tags: [Metadata]
responses:
'200':
description: OpenAPI metadata
headers:
ETag:
description: Spec version ETag
schema:
type: string
X-Signature:
description: Spec signature for verification
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/OpenApiMetadata'
examples:
metadata:
value:
specUrl: "/openapi"
version: "0.1.0-draft"
buildVersion: "20251206.1"
etag: '"abc123"'
signature: "sha256:def456..."
components:
securitySchemes:
oauth2:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://auth.stellaops.example.com/oauth/token
scopes:
taskrunner.viewer: Read-only access to runs and logs
taskrunner.operator: Create runs and apply approvals
taskrunner.admin: Full administrative access
parameters:
Tenant:
name: X-StellaOps-Tenant
in: header
required: false
description: Tenant slug (optional for single-tenant deployments)
schema:
type: string
RunId:
name: runId
in: path
required: true
description: Unique run identifier
schema:
type: string
pattern: '^[a-zA-Z0-9_-]+$'
ApprovalId:
name: approvalId
in: path
required: true
description: Approval gate identifier (from task pack approvals section)
schema:
type: string
responses:
Error:
description: Standard error envelope
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
examples:
internal-error:
value:
error:
code: internal_error
message: "An unexpected error occurred"
traceId: "f62f3c2b9c8e4c53"
schemas:
ErrorEnvelope:
type: object
required: [error]
properties:
error:
type: object
required: [code, message]
properties:
code:
type: string
description: Machine-readable error code
message:
type: string
description: Human-readable error message
traceId:
type: string
description: Trace ID for debugging
SimulationRequest:
type: object
required: [manifest]
properties:
manifest:
type: string
description: Task pack manifest in YAML format
inputs:
type: object
additionalProperties: true
description: Input values to provide to the task pack
SimulationResponse:
type: object
required: [planHash, failurePolicy, steps, outputs, hasPendingApprovals]
properties:
planHash:
type: string
description: SHA-256 hash of the execution plan
pattern: '^sha256:[a-f0-9]{64}$'
failurePolicy:
$ref: '#/components/schemas/FailurePolicy'
steps:
type: array
items:
$ref: '#/components/schemas/SimulationStep'
outputs:
type: array
items:
$ref: '#/components/schemas/SimulationOutput'
hasPendingApprovals:
type: boolean
description: Whether the plan contains approval gates
SimulationStep:
type: object
required: [id, templateId, kind, enabled, status, children]
properties:
id:
type: string
templateId:
type: string
kind:
type: string
enum: [Run, GateApproval, GatePolicy, Parallel, Map, Loop, Conditional, Unknown]
enabled:
type: boolean
status:
type: string
enum: [Pending, Skipped, RequiresApproval, RequiresPolicy, WillIterate, WillBranch]
statusReason:
type: string
uses:
type: string
description: Executor reference for run steps
approvalId:
type: string
gateMessage:
type: string
maxParallel:
type: integer
continueOnError:
type: boolean
children:
type: array
items:
$ref: '#/components/schemas/SimulationStep'
loopInfo:
$ref: '#/components/schemas/LoopInfo'
conditionalInfo:
$ref: '#/components/schemas/ConditionalInfo'
policyInfo:
$ref: '#/components/schemas/PolicyInfo'
LoopInfo:
type: object
description: Loop step simulation details
properties:
itemsExpression:
type: string
iterator:
type: string
index:
type: string
maxIterations:
type: integer
aggregationMode:
type: string
enum: [collect, merge, last, first, none]
ConditionalInfo:
type: object
description: Conditional step simulation details
properties:
branches:
type: array
items:
type: object
properties:
condition:
type: string
stepCount:
type: integer
elseStepCount:
type: integer
outputUnion:
type: boolean
PolicyInfo:
type: object
description: Policy gate simulation details
properties:
policyId:
type: string
policyVersion:
type: string
failureAction:
type: string
enum: [abort, warn, requestOverride, branch]
retryCount:
type: integer
SimulationOutput:
type: object
required: [name, type, requiresRuntimeValue]
properties:
name:
type: string
type:
type: string
requiresRuntimeValue:
type: boolean
pathExpression:
type: string
valueExpression:
type: string
CreateRunRequest:
type: object
required: [manifest]
properties:
runId:
type: string
description: Optional custom run ID (auto-generated if not provided)
manifest:
type: string
description: Task pack manifest in YAML format
inputs:
type: object
additionalProperties: true
description: Input values to provide to the task pack
tenantId:
type: string
description: Tenant identifier
RunStateResponse:
type: object
required: [runId, planHash, failurePolicy, createdAt, updatedAt, steps]
properties:
runId:
type: string
planHash:
type: string
pattern: '^sha256:[a-f0-9]{64}$'
failurePolicy:
$ref: '#/components/schemas/FailurePolicy'
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
steps:
type: array
items:
$ref: '#/components/schemas/RunStateStep'
RunStateStep:
type: object
required: [stepId, kind, enabled, continueOnError, status, attempts]
properties:
stepId:
type: string
kind:
type: string
enum: [Run, GateApproval, GatePolicy, Parallel, Map, Loop, Conditional, Unknown]
enabled:
type: boolean
continueOnError:
type: boolean
maxParallel:
type: integer
approvalId:
type: string
gateMessage:
type: string
status:
type: string
enum: [Pending, Running, Succeeded, Failed, Skipped]
attempts:
type: integer
lastTransitionAt:
type: string
format: date-time
nextAttemptAt:
type: string
format: date-time
statusReason:
type: string
FailurePolicy:
type: object
required: [maxAttempts, backoffSeconds, continueOnError]
properties:
maxAttempts:
type: integer
minimum: 1
backoffSeconds:
type: integer
minimum: 0
continueOnError:
type: boolean
RunLogEntry:
type: object
required: [timestamp, level, eventType, message]
description: |
Log entry returned in NDJSON stream. Each entry is a single JSON object
followed by a newline character.
properties:
timestamp:
type: string
format: date-time
level:
type: string
enum: [debug, info, warn, error]
eventType:
type: string
description: |
Event type identifier, e.g.:
- run.created, run.started, run.completed, run.failed, run.cancelled
- step.started, step.completed, step.failed, step.skipped
- gate.awaiting, gate.approved, gate.rejected
- run.schedule-failed, run.cancel-requested
message:
type: string
stepId:
type: string
metadata:
type: object
additionalProperties:
type: string
RunArtifact:
type: object
required: [name, type, status]
properties:
name:
type: string
type:
type: string
enum: [file, object]
sourcePath:
type: string
storedPath:
type: string
status:
type: string
enum: [pending, captured, failed]
notes:
type: string
capturedAt:
type: string
format: date-time
expressionJson:
type: string
description: JSON string of evaluated expression result for object outputs
ApprovalDecisionRequest:
type: object
required: [decision, planHash]
properties:
decision:
type: string
enum: [approved, rejected, expired]
planHash:
type: string
pattern: '^sha256:[a-f0-9]{64}$'
description: Plan hash to verify against (must match current run plan)
actorId:
type: string
description: Identifier of the approver (e.g., user:alice@example.com)
summary:
type: string
description: Optional comment explaining the decision
ApprovalDecisionResponse:
type: object
required: [status, resumed]
properties:
status:
type: string
enum: [approved, rejected, expired]
resumed:
type: boolean
description: Whether the run was resumed (true for approved decisions)
PlanErrorResponse:
type: object
required: [errors]
properties:
errors:
type: array
items:
type: object
required: [path, message]
properties:
path:
type: string
description: JSON path to the error location
message:
type: string
OpenApiMetadata:
type: object
required: [specUrl, version, etag]
properties:
specUrl:
type: string
description: URL to fetch the full OpenAPI spec
version:
type: string
description: API version
buildVersion:
type: string
description: Build version identifier
etag:
type: string
description: ETag for caching
signature:
type: string
description: Signature for spec verification
tags:
- name: Simulations
description: Task pack simulation without execution
- name: Runs
description: Pack run lifecycle management
- name: Logs
description: Run log streaming
- name: Artifacts
description: Run artifact management
- name: Approvals
description: Approval gate decisions
- name: Metadata
description: Service metadata and discovery

View File

@@ -0,0 +1,11 @@
event: started
data: {"tenantId":"tenant-default","streamId":"vex-consensus::2025-12-06","status":"running"}
event: consensus_update
data: {"statementId":"vex:tenant-default:jwt-auth:5d1a","state":"under_investigation","justification":"reachable path confirmed","validFrom":"2025-12-06T10:00:00Z","validUntil":"2025-12-20T00:00:00Z","sources":["signals","policy"],"etag":"vex-etag-123"}
event: consensus_update
data: {"statementId":"vex:tenant-default:openssl:7b2c","state":"not_affected","justification":"no call-path and patched","validFrom":"2025-12-05T00:00:00Z","validUntil":"2026-01-01T00:00:00Z","sources":["sbom","scanner"],"etag":"vex-etag-456"}
event: completed
data: {"streamId":"vex-consensus::2025-12-06","status":"succeeded"}

25
docs/api/vex-consensus.md Normal file
View File

@@ -0,0 +1,25 @@
# VEX Consensus Stream Contract (draft placeholder)
**Status:** Draft v0.2 · owner-proposed
## Scope
- `/vex/consensus` streaming APIs via Web gateway with tenant RBAC/ABAC, caching, and telemetry.
## Endpoint
- `GET /vex/consensus/stream` — SSE stream of consensus VEX statements per tenant.
Headers: `Authorization: DPoP <token>`, `DPoP: <proof>`, `X-StellaOps-Tenant`, optional `If-None-Match`.
Scopes (proposal): `vex:read` and `vex:consensus`.
Events (draft)
- `started`: `{ tenantId, streamId, status }`
- `consensus_update`: `{ statementId, state, justification, validFrom, validUntil, sources[], etag }`
- `heartbeat`: `{ streamId, ts }`
- `completed`: `{ streamId, status }`
- `failed`: `{ streamId, code, message }`
Rate limits: heartbeats every 30s; idle timeout 90s; backoff via `Retry-After` header on reconnect.
Samples: `docs/api/vex-consensus-sample.ndjson`
Outstanding: finalize scopes, error codes, cache/etag semantics, and add pagination/replay guidance.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,182 @@
# Console Asset Captures for Vuln Explorer Documentation
> **Status:** Ready for capture
> **Last Updated:** 2025-12-06
> **Owner:** Console Guild
> **Hash Manifest:** See SHA256SUMS after capture
## Capture Instructions
Run the console app locally and capture each screen:
```bash
# Start the dev environment
docker compose -f deploy/compose/docker-compose.dev.yaml up -d
# Access console at https://localhost:8443
# Log in with dev credentials
# Navigate to each section below and capture
```
## Required Captures
### 1. Dashboard Overview
**File:** `dashboard-overview.png`
**Description:** Main dashboard showing vulnerability counts, risk scores, and recent activity.
```markdown
![Dashboard Overview](./dashboard-overview.png)
The dashboard provides:
- Total vulnerability count by severity (Critical, High, Medium, Low)
- Risk score trend over time
- Top affected components
- Recent scan activity
```
---
### 2. Vulnerability Explorer List
**File:** `vuln-explorer-list.png`
**Description:** Vulnerability list view with filters and sorting.
```markdown
![Vulnerability Explorer List](./vuln-explorer-list.png)
The vulnerability list shows:
- CVE ID, severity, CVSS score
- Affected package and version
- Fix availability status
- VEX status (affected, not_affected, fixed, under_investigation)
```
---
### 3. Vulnerability Detail View
**File:** `vuln-detail.png`
**Description:** Single vulnerability detail page with full context.
```markdown
![Vulnerability Detail](./vuln-detail.png)
The detail view includes:
- Full vulnerability description
- CVSS vector breakdown
- Affected components
- Reachability analysis
- VEX statements
- Remediation guidance
```
---
### 4. Findings Ledger Timeline
**File:** `findings-timeline.png`
**Description:** Timeline view of vulnerability findings and state changes.
```markdown
![Findings Timeline](./findings-timeline.png)
The timeline shows:
- Finding discovery events
- Status transitions
- Evidence snapshots
- Attestation links
```
---
### 5. Risk Score Panel
**File:** `risk-score-panel.png`
**Description:** Risk score breakdown with contributing factors.
```markdown
![Risk Score Panel](./risk-score-panel.png)
The risk panel displays:
- Overall risk score (0-100)
- Factor breakdown (severity, exploitability, asset criticality)
- Score history
- Policy compliance status
```
---
### 6. VEX Consensus View
**File:** `vex-consensus.png`
**Description:** VEX consensus display showing multiple issuer statements.
```markdown
![VEX Consensus](./vex-consensus.png)
The VEX consensus view shows:
- Aggregated status from multiple issuers
- Issuer trust levels
- Statement timestamps
- Rationale summaries
```
---
### 7. Policy Studio Editor
**File:** `policy-studio-editor.png`
**Description:** Policy Studio with Monaco editor and rule builder.
```markdown
![Policy Studio Editor](./policy-studio-editor.png)
The Policy Studio includes:
- Monaco editor with StellaOps DSL highlighting
- Rule builder sidebar
- Simulation panel
- Lint/compile feedback
```
---
### 8. Air-Gap Status Panel
**File:** `airgap-status.png`
**Description:** Air-gap mode status and bundle information.
```markdown
![Air-Gap Status](./airgap-status.png)
The air-gap panel shows:
- Sealed mode status
- Last advisory update timestamp
- Bundle version
- Time anchor validity
```
---
## After Capture
1. Place captured images in this directory
2. Generate hashes:
```bash
sha256sum *.png > SHA256SUMS
```
3. Update `docs/assets/vuln-explorer/SHA256SUMS` with new entries
4. Mark DOCS-CONSOLE-OBS-52-001 as DONE in sprint file
## Sample SHA256SUMS Entry
```
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 dashboard-overview.png
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 vuln-explorer-list.png
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 vuln-detail.png
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 findings-timeline.png
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 risk-score-panel.png
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 vex-consensus.png
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 policy-studio-editor.png
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 airgap-status.png
```

View File

@@ -0,0 +1,369 @@
# Authority Crypto Provider Contract
> **Status:** APPROVED
> **Version:** 1.0.0
> **Last Updated:** 2025-12-06
> **Owner:** Authority Core Guild
> **Unblocks:** AUTH-CRYPTO-90-001, SEC-CRYPTO-90-014, SCANNER-CRYPTO-90-001, ATTESTOR-CRYPTO-90-001
## Overview
This contract defines the Authority signing provider interface for StellaOps, enabling pluggable cryptographic backends including:
- **Software keys** (default) — ECDSA P-256/P-384, RSA, EdDSA
- **HSM integration** — PKCS#11, Cloud KMS (AWS, GCP, Azure)
- **Regional compliance** — CryptoPro GOST (R1), SM2/SM3 (CN), eIDAS (EU), FIPS 140-2
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Authority Crypto Provider │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ ISigningProvider Interface ││
│ │ ││
│ │ + Sign(data: byte[], keyId: string) → SignatureResult ││
│ │ + Verify(data: byte[], signature: byte[], keyId: string) → bool ││
│ │ + GetPublicKey(keyId: string) → PublicKeyInfo ││
│ │ + ListKeys(filter: KeyFilter) → KeyInfo[] ││
│ │ + CreateKey(spec: KeySpec) → KeyInfo ││
│ │ + RotateKey(keyId: string) → KeyInfo ││
│ │ + ExportJWKS(keyIds: string[]) → JWKS ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Software │ │ PKCS#11 │ │ Cloud KMS │ │
│ │ Provider │ │ Provider │ │ Provider │ │
│ │ │ │ │ │ │ │
│ │ • File keys │ │ • HSM │ │ • AWS KMS │ │
│ │ • Memory │ │ • SmartCard │ │ • GCP KMS │ │
│ │ • Vault │ │ • CryptoPro │ │ • Azure KV │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## 1. ISigningProvider Interface
### 1.1 Core Methods
```csharp
/// <summary>
/// Pluggable cryptographic signing provider for Authority service.
/// </summary>
public interface ISigningProvider
{
/// <summary>Provider identifier (e.g., "software", "pkcs11", "aws-kms")</summary>
string ProviderId { get; }
/// <summary>Supported algorithms by this provider</summary>
IReadOnlyList<string> SupportedAlgorithms { get; }
/// <summary>Sign data with the specified key</summary>
Task<SignatureResult> SignAsync(
byte[] data,
string keyId,
SigningOptions? options = null,
CancellationToken ct = default);
/// <summary>Verify a signature</summary>
Task<bool> VerifyAsync(
byte[] data,
byte[] signature,
string keyId,
CancellationToken ct = default);
/// <summary>Get public key information</summary>
Task<PublicKeyInfo> GetPublicKeyAsync(
string keyId,
CancellationToken ct = default);
/// <summary>List available keys</summary>
Task<IReadOnlyList<KeyInfo>> ListKeysAsync(
KeyFilter? filter = null,
CancellationToken ct = default);
/// <summary>Create a new key pair</summary>
Task<KeyInfo> CreateKeyAsync(
KeySpec spec,
CancellationToken ct = default);
/// <summary>Rotate a key (create new version)</summary>
Task<KeyInfo> RotateKeyAsync(
string keyId,
CancellationToken ct = default);
/// <summary>Export keys as JWKS for distributed verification</summary>
Task<JsonWebKeySet> ExportJwksAsync(
IEnumerable<string>? keyIds = null,
CancellationToken ct = default);
/// <summary>Import a public key for verification</summary>
Task<KeyInfo> ImportPublicKeyAsync(
byte[] keyData,
string format,
KeyMetadata? metadata = null,
CancellationToken ct = default);
}
```
### 1.2 Supporting Types
```csharp
public record SignatureResult(
byte[] Signature,
string Algorithm,
string KeyId,
string? KeyVersion,
DateTimeOffset Timestamp);
public record SigningOptions(
string? Algorithm = null,
bool IncludeTimestamp = true,
string? Nonce = null);
public record PublicKeyInfo(
string KeyId,
string Algorithm,
byte[] PublicKey,
string Format, // "PEM", "DER", "JWK"
string? Fingerprint,
DateTimeOffset? ExpiresAt);
public record KeyInfo(
string KeyId,
string Algorithm,
KeyState State,
DateTimeOffset CreatedAt,
DateTimeOffset? ExpiresAt,
string? CurrentVersion,
IReadOnlyDictionary<string, string>? Metadata);
public enum KeyState
{
Active,
Disabled,
PendingDeletion,
Deleted
}
public record KeySpec(
string Algorithm,
int? KeySize = null,
string? Purpose = null, // "signing", "attestation", "authority"
IReadOnlyDictionary<string, string>? Metadata = null,
DateTimeOffset? ExpiresAt = null);
public record KeyFilter(
string? Purpose = null,
KeyState? State = null,
string? Algorithm = null);
```
## 2. Supported Algorithms
### 2.1 Algorithm Registry
| Algorithm | OID | Key Size | Compliance | Provider Support |
|-----------|-----|----------|------------|------------------|
| **ES256** | 1.2.840.10045.4.3.2 | P-256 | FIPS, eIDAS | All |
| **ES384** | 1.2.840.10045.4.3.3 | P-384 | FIPS, eIDAS | All |
| **RS256** | 1.2.840.113549.1.1.11 | 2048+ | FIPS, eIDAS | All |
| **RS384** | 1.2.840.113549.1.1.12 | 2048+ | FIPS, eIDAS | All |
| **EdDSA** | 1.3.101.112 | Ed25519 | — | Software, some HSM |
| **PS256** | 1.2.840.113549.1.1.10 | 2048+ | FIPS | All |
| **GOST R 34.10-2012** | 1.2.643.7.1.1.1.1 | 256/512 | R1 | PKCS#11 (CryptoPro) |
| **SM2** | 1.2.156.10197.1.301 | 256 | CN | PKCS#11 |
### 2.2 Default Configuration
```yaml
# etc/authority.yaml
crypto:
provider: software # or: pkcs11, aws-kms, gcp-kms, azure-keyvault
software:
keys_path: /var/lib/stellaops/keys
default_algorithm: ES256
pkcs11:
library_path: /usr/lib/libpkcs11.so
slot_id: 0
pin_env: AUTHORITY_HSM_PIN
# For CryptoPro:
# library_path: /opt/cprocsp/lib/amd64/libcapi20.so
aws_kms:
region: us-east-1
key_alias_prefix: stellaops/
azure_keyvault:
vault_url: https://stellaops.vault.azure.net/
gcp_kms:
project: stellaops-prod
location: global
key_ring: attestation-keys
# Regional compliance overrides
compliance:
ru:
provider: pkcs11
algorithms: [GOST-R-34.10-2012-256, GOST-R-34.10-2012-512]
library_path: /opt/cprocsp/lib/amd64/libcapi20.so
cn:
provider: pkcs11
algorithms: [SM2]
```
## 3. JWKS Export Requirements
### 3.1 JWKS Endpoint
The Authority service MUST expose a JWKS endpoint for distributed verification:
```
GET /.well-known/jwks.json
```
Response format:
```json
{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"x": "base64url-encoded-x",
"y": "base64url-encoded-y",
"kid": "attestation-key-001",
"alg": "ES256",
"use": "sig",
"key_ops": ["verify"],
"x5t#S256": "sha256-fingerprint"
}
]
}
```
### 3.2 Key Rotation
When keys are rotated:
1. New key becomes `Active`, old key becomes `Disabled` (verification-only)
2. JWKS includes both keys during transition period
3. Old key removed after `rotation_grace_period` (default: 7 days)
4. All consuming services refresh JWKS on schedule or via webhook
### 3.3 Key Discovery Flow
```
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Scanner │ │ Authority │ │ Attestor │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ GET /jwks.json│ │
│───────────────>│ │
│<───────────────│ │
│ JWKS │ │
│ │ │
│ Sign(SBOM) │ │
│───────────────>│ │
│<───────────────│ │
│ Signature │ │
│ │ │
│ │ GET /jwks.json │
│ │<────────────────│
│ │────────────────>│
│ │ JWKS │
│ │ │
│ │ Verify(SBOM) │
│ │<────────────────│
│ │ ✓ Valid │
```
## 4. Provider Registration
### 4.1 Service Registration
```csharp
// Program.cs
services.AddAuthoritySigningProvider(options =>
{
options.Provider = configuration["Crypto:Provider"];
options.Configuration = configuration.GetSection("Crypto");
});
// Extension method
public static IServiceCollection AddAuthoritySigningProvider(
this IServiceCollection services,
Action<CryptoProviderOptions> configure)
{
var options = new CryptoProviderOptions();
configure(options);
return options.Provider switch
{
"software" => services.AddSingleton<ISigningProvider, SoftwareSigningProvider>(),
"pkcs11" => services.AddSingleton<ISigningProvider, Pkcs11SigningProvider>(),
"aws-kms" => services.AddSingleton<ISigningProvider, AwsKmsSigningProvider>(),
"gcp-kms" => services.AddSingleton<ISigningProvider, GcpKmsSigningProvider>(),
"azure-keyvault" => services.AddSingleton<ISigningProvider, AzureKeyVaultSigningProvider>(),
_ => throw new ArgumentException($"Unknown provider: {options.Provider}")
};
}
```
### 4.2 Regional Provider Registry
For multi-region deployments with compliance requirements:
```yaml
# Regional key registry
key_registry:
attestation-sbom:
default:
key_id: "stellaops/attestation-sbom-001"
algorithm: ES256
provider: aws-kms
ru:
key_id: "ru/attestation-sbom-gost"
algorithm: GOST-R-34.10-2012-256
provider: pkcs11
cn:
key_id: "cn/attestation-sbom-sm2"
algorithm: SM2
provider: pkcs11
```
## 5. Error Codes
| Code | Name | Description |
|------|------|-------------|
| `CRYPTO_001` | `KEY_NOT_FOUND` | Requested key does not exist |
| `CRYPTO_002` | `KEY_DISABLED` | Key is disabled and cannot sign |
| `CRYPTO_003` | `ALGORITHM_UNSUPPORTED` | Algorithm not supported by provider |
| `CRYPTO_004` | `HSM_UNAVAILABLE` | HSM/PKCS#11 device not available |
| `CRYPTO_005` | `SIGNATURE_FAILED` | Signing operation failed |
| `CRYPTO_006` | `VERIFICATION_FAILED` | Signature verification failed |
| `CRYPTO_007` | `KEY_EXPIRED` | Key has expired |
| `CRYPTO_008` | `COMPLIANCE_VIOLATION` | Algorithm not allowed by compliance profile |
## 6. Tasks Unblocked
This contract unblocks:
| Task ID | Description | Status |
|---------|-------------|--------|
| AUTH-CRYPTO-90-001 | Authority signing provider contract | ✅ UNBLOCKED |
| SEC-CRYPTO-90-014 | Security Guild crypto integration | ✅ UNBLOCKED |
| SCANNER-CRYPTO-90-001 | Scanner SBOM signing | ✅ UNBLOCKED |
| ATTESTOR-CRYPTO-90-001 | Attestor DSSE signing | ✅ UNBLOCKED |
## 7. Changelog
| Date | Version | Change |
|------|---------|--------|
| 2025-12-06 | 1.0.0 | Initial contract with interface, algorithms, JWKS, regional support |

View File

@@ -0,0 +1,157 @@
# CAS (Content Addressable Storage) Infrastructure Contract
> **Status:** APPROVED
> **Version:** 1.0.0
> **Last Updated:** 2025-12-06
> **Owner:** Platform Storage Guild
## Overview
This contract defines the Content Addressable Storage (CAS) infrastructure for StellaOps, using RustFS as the S3-compatible storage backend. The design provides:
- **Content-addressed storage** — Objects addressed by SHA-256 hash
- **Immutable evidence storage** — Write-once, never-delete for audit trails
- **Lifecycle management** — Automated retention policy enforcement
- **Service account isolation** — Fine-grained access control per service
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ CAS Infrastructure │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ rustfs-cas │ │ rustfs-evidence │ │rustfs-attestation│ │
│ │ (mutable) │ │ (immutable) │ │ (immutable) │ │
│ │ │ │ │ │ │ │
│ │ • scanner- │ │ • evidence- │ │ • attestations │ │
│ │ artifacts │ │ bundles │ │ • dsse-envelopes│ │
│ │ • surface-cache │ │ • merkle-roots │ │ • rekor-receipts│ │
│ │ • runtime-facts │ │ • hash-chains │ │ │ │
│ │ • signals-data │ │ │ │ │ │
│ │ • provenance- │ │ │ │ │ │
│ │ feed │ │ │ │ │ │
│ │ • replay- │ │ │ │ │ │
│ │ bundles │ │ │ │ │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ └────────────────────┼────────────────────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ cas-lifecycle │ │
│ │ (retention manager) │ │
│ └───────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Retention Policies
Aligned with best-in-class on-premise vulnerability scanners:
| Data Type | Retention | Rationale | Scanner Comparison |
|-----------|-----------|-----------|-------------------|
| Vulnerability DB | 7 days | Fresh advisories required | Trivy: 7d, Grype: 5d |
| SBOM artifacts | 365 days | Audit compliance (SOC2, ISO27001) | Anchore: 365d |
| Scan results | 90 days | Common compliance window | Snyk: 90d enterprise |
| Evidence bundles | Indefinite | Immutable audit trail | N/A (StellaOps unique) |
| Attestations | Indefinite | Signed, verifiable | N/A (StellaOps unique) |
| Temp artifacts | 1 day | Work-in-progress cleanup | Standard practice |
## Access Control Matrix
### Service Accounts
| Service | Buckets | Permissions | Purpose |
|---------|---------|-------------|---------|
| `scanner` | scanner-artifacts, surface-cache, runtime-facts | read, write | Scan job artifacts, cache |
| `signals` | runtime-facts, signals-data, provenance-feed | read, write | Runtime signal ingestion |
| `replay` | replay-bundles, inputs-lock | read, write | Deterministic replay |
| `ledger` | evidence-bundles, merkle-roots, hash-chains | read, write | Evidence ledger writes |
| `exporter` | evidence-bundles | read | Export center reads |
| `attestor` | attestations, dsse-envelopes, rekor-receipts | read, write | Attestation storage |
| `verifier` | attestations, dsse-envelopes, rekor-receipts | read | Verification reads |
| `readonly` | * | read | Global audit access |
### Bucket Classification
| Bucket | Storage Type | Lifecycle | Access Pattern |
|--------|--------------|-----------|----------------|
| scanner-artifacts | rustfs-cas | 90 days | Write-heavy |
| surface-cache | rustfs-cas | 7 days | Read-heavy, cache |
| runtime-facts | rustfs-cas | 90 days | Write-heavy |
| signals-data | rustfs-cas | 90 days | Write-heavy |
| provenance-feed | rustfs-cas | 90 days | Append-only |
| replay-bundles | rustfs-cas | 365 days | Read-heavy |
| inputs-lock | rustfs-cas | 365 days | Write-once |
| evidence-bundles | rustfs-evidence | Indefinite | Write-once |
| merkle-roots | rustfs-evidence | Indefinite | Append-only |
| hash-chains | rustfs-evidence | Indefinite | Append-only |
| attestations | rustfs-attestation | Indefinite | Write-once |
| dsse-envelopes | rustfs-attestation | Indefinite | Write-once |
| rekor-receipts | rustfs-attestation | Indefinite | Write-once |
## Docker Compose Integration
```yaml
# Use with existing compose files
docker compose -f docker-compose.cas.yaml -f docker-compose.dev.yaml up -d
# Standalone CAS
docker compose -f docker-compose.cas.yaml up -d
```
## Environment Variables
See `deploy/compose/env/cas.env.example` for full configuration.
Key variables:
- `RUSTFS_*_API_KEY` — Admin API keys (CHANGE IN PRODUCTION)
- `RUSTFS_*_KEY` — Service account keys (GENERATE UNIQUE)
- `CAS_*_PATH` — Data directory paths
- `CAS_RETENTION_*_DAYS` — Retention policy overrides
## Endpoints
| Service | Port | Path | Purpose |
|---------|------|------|---------|
| rustfs-cas | 8180 | /api/v1 | Mutable CAS storage |
| rustfs-evidence | 8181 | /api/v1 | Immutable evidence |
| rustfs-attestation | 8182 | /api/v1 | Immutable attestations |
## Health Checks
All RustFS instances expose `/health` endpoint:
```bash
curl http://localhost:8180/health # CAS
curl http://localhost:8181/health # Evidence
curl http://localhost:8182/health # Attestations
```
## Migration from MinIO
For existing deployments using MinIO:
1. Deploy CAS infrastructure alongside MinIO
2. Configure scanner/signals services with `RUSTFS_*` endpoints
3. Migrate data using `stella cas migrate --source minio --target rustfs`
4. Verify data integrity with `stella cas verify --bucket <name>`
5. Update service configurations to use RustFS
6. Decommission MinIO after validation
## Tasks Unblocked
This contract unblocks the CAS approval gate (PREP-SIGNALS-24-002):
- **24-002:** Surface cache availability → UNBLOCKED
- **24-003:** Runtime facts ingestion → UNBLOCKED
- **24-004:** Authority scopes → UNBLOCKED
- **24-005:** Scoring outputs → UNBLOCKED
- **GRAPH-INDEX-28-007 through 28-010** → UNBLOCKED
## Changelog
| Date | Version | Change |
|------|---------|--------|
| 2025-12-06 | 1.0.0 | Initial contract with RustFS, retention policies, access controls |

View File

@@ -0,0 +1,425 @@
# Sealed Install Enforcement Contract
> **Status:** APPROVED
> **Version:** 1.0.0
> **Last Updated:** 2025-12-06
> **Owner:** AirGap Controller Guild
> **Unblocks:** TASKRUN-AIRGAP-57-001, TASKRUN-AIRGAP-58-001
## Overview
This contract defines the sealed install enforcement semantics for StellaOps air-gapped deployments. When a pack or task declares `sealed_install: true`, the Task Runner MUST refuse to execute if the environment is not properly sealed.
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Sealed Install Enforcement Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Task Pack │ │ Task Runner │ │ AirGap │ │
│ │ │────>│ │────>│ Controller │ │
│ │ sealed_ │ │ Enforcement │ │ │ │
│ │ install:true │ │ Check │ │ /status │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ Decision Matrix │ │
│ │ │ │
│ │ Pack: sealed Env: sealed │ │
│ │ ────────────── ──────────── │ │
│ │ true true → RUN │ │
│ │ true false → DENY │ │
│ │ false true → RUN │ │
│ │ false false → RUN │ │
│ └──────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## 1. Pack Declaration
### 1.1 Sealed Install Flag
Packs declare their sealed requirement in the pack manifest:
```json
{
"pack_id": "compliance-scan-airgap",
"version": "1.0.0",
"name": "Air-Gap Compliance Scanner",
"sealed_install": true,
"sealed_requirements": {
"min_bundle_version": "2025.10.0",
"max_advisory_staleness_hours": 168,
"require_time_anchor": true,
"allowed_offline_duration_hours": 720
}
}
```
### 1.2 Sealed Requirements Schema
```json
{
"type": "object",
"properties": {
"sealed_install": {
"type": "boolean",
"default": false,
"description": "If true, pack MUST run in sealed environment"
},
"sealed_requirements": {
"type": "object",
"properties": {
"min_bundle_version": {
"type": "string",
"description": "Minimum air-gap bundle version"
},
"max_advisory_staleness_hours": {
"type": "integer",
"minimum": 1,
"default": 168,
"description": "Maximum age of advisory data in hours"
},
"require_time_anchor": {
"type": "boolean",
"default": true,
"description": "Require valid time anchor"
},
"allowed_offline_duration_hours": {
"type": "integer",
"minimum": 1,
"default": 720,
"description": "Maximum allowed offline duration"
},
"require_signature_verification": {
"type": "boolean",
"default": true,
"description": "Require bundle signature verification"
}
}
}
}
}
```
## 2. Environment Detection
### 2.1 Sealed Mode Status API
The Task Runner queries the AirGap Controller to determine sealed status:
```
GET /api/v1/airgap/status
```
Response:
```json
{
"sealed": true,
"mode": "sealed",
"sealed_at": "2025-12-01T00:00:00Z",
"sealed_by": "ops-admin@company.com",
"bundle_version": "2025.10.0",
"bundle_digest": "sha256:abc123...",
"last_advisory_update": "2025-12-01T00:00:00Z",
"advisory_staleness_hours": 120,
"time_anchor": {
"timestamp": "2025-12-01T00:00:00Z",
"signature": "base64...",
"valid": true,
"expires_at": "2025-12-31T00:00:00Z"
},
"egress_blocked": true,
"network_policy": "deny-all"
}
```
### 2.2 Detection Heuristics
If the AirGap Controller is unavailable, the Task Runner uses fallback heuristics:
| Heuristic | Weight | Indicates |
|-----------|--------|-----------|
| No external DNS resolution | High | Sealed |
| Blocked ports 80, 443 | High | Sealed |
| AIRGAP_MODE=sealed env var | High | Sealed |
| /etc/stellaops/sealed file exists | Medium | Sealed |
| No internet connectivity | Medium | Sealed |
| Local-only registry configured | Low | Sealed |
Combined heuristic score threshold: **0.7** to consider environment sealed.
## 3. Enforcement Logic
### 3.1 Pre-Execution Check
```csharp
public sealed class SealedInstallEnforcer
{
public async Task<EnforcementResult> EnforceAsync(
TaskPack pack,
CancellationToken ct = default)
{
// If pack doesn't require sealed install, allow
if (!pack.SealedInstall)
{
return EnforcementResult.Allowed("Pack does not require sealed install");
}
// Get environment sealed status
var status = await _airgapController.GetStatusAsync(ct);
// Core check: environment must be sealed
if (!status.Sealed)
{
return EnforcementResult.Denied(
"SEALED_INSTALL_VIOLATION",
"Pack requires sealed environment but environment is not sealed",
new SealedInstallViolation
{
PackId = pack.PackId,
RequiredSealed = true,
ActualSealed = false,
Recommendation = "Activate sealed mode with: stella airgap seal"
});
}
// Check sealed requirements
if (pack.SealedRequirements != null)
{
var violations = ValidateRequirements(pack.SealedRequirements, status);
if (violations.Any())
{
return EnforcementResult.Denied(
"SEALED_REQUIREMENTS_VIOLATION",
"Sealed requirements not met",
violations);
}
}
return EnforcementResult.Allowed("Sealed install requirements satisfied");
}
private List<RequirementViolation> ValidateRequirements(
SealedRequirements requirements,
SealedModeStatus status)
{
var violations = new List<RequirementViolation>();
// Bundle version check
if (requirements.MinBundleVersion != null)
{
if (Version.Parse(status.BundleVersion) < Version.Parse(requirements.MinBundleVersion))
{
violations.Add(new RequirementViolation
{
Requirement = "min_bundle_version",
Expected = requirements.MinBundleVersion,
Actual = status.BundleVersion,
Message = $"Bundle version {status.BundleVersion} < required {requirements.MinBundleVersion}"
});
}
}
// Advisory staleness check
if (status.AdvisoryStalenessHours > requirements.MaxAdvisoryStalenessHours)
{
violations.Add(new RequirementViolation
{
Requirement = "max_advisory_staleness_hours",
Expected = requirements.MaxAdvisoryStalenessHours.ToString(),
Actual = status.AdvisoryStalenessHours.ToString(),
Message = $"Advisory data is {status.AdvisoryStalenessHours}h old, max allowed is {requirements.MaxAdvisoryStalenessHours}h"
});
}
// Time anchor check
if (requirements.RequireTimeAnchor && (status.TimeAnchor == null || !status.TimeAnchor.Valid))
{
violations.Add(new RequirementViolation
{
Requirement = "require_time_anchor",
Expected = "valid time anchor",
Actual = status.TimeAnchor?.Valid.ToString() ?? "missing",
Message = "Valid time anchor required but not present"
});
}
return violations;
}
}
```
### 3.2 Decision Matrix
| Pack `sealed_install` | Environment Sealed | Bundle Valid | Advisories Fresh | Result |
|-----------------------|-------------------|--------------|------------------|--------|
| `true` | `true` | `true` | `true` | ✅ RUN |
| `true` | `true` | `true` | `false` | ⚠️ WARN + RUN (if within grace) |
| `true` | `true` | `false` | * | ❌ DENY |
| `true` | `false` | * | * | ❌ DENY |
| `false` | `true` | * | * | ✅ RUN |
| `false` | `false` | * | * | ✅ RUN |
### 3.3 Grace Period Handling
For advisory staleness, a grace period can be configured:
```yaml
# etc/taskrunner.yaml
enforcement:
sealed_install:
staleness_grace_period_hours: 24
staleness_warning_threshold_hours: 120
deny_on_staleness: true # or false for warn-only
```
## 4. Refusal Semantics
### 4.1 Error Response
When enforcement denies execution:
```json
{
"error": {
"code": "SEALED_INSTALL_VIOLATION",
"message": "Pack requires sealed environment but environment is not sealed",
"details": {
"pack_id": "compliance-scan-airgap",
"pack_version": "1.0.0",
"sealed_install_required": true,
"environment_sealed": false,
"violations": [],
"recommendation": "Activate sealed mode with: stella airgap seal"
}
},
"status": "rejected",
"rejected_at": "2025-12-06T10:00:00Z"
}
```
### 4.2 CLI Exit Codes
| Code | Name | Description |
|------|------|-------------|
| 40 | `SEALED_INSTALL_VIOLATION` | Pack requires sealed but environment is not |
| 41 | `BUNDLE_VERSION_VIOLATION` | Bundle version below minimum |
| 42 | `ADVISORY_STALENESS_VIOLATION` | Advisory data too stale |
| 43 | `TIME_ANCHOR_VIOLATION` | Time anchor missing or invalid |
| 44 | `SIGNATURE_VERIFICATION_VIOLATION` | Bundle signature verification failed |
### 4.3 Audit Logging
All enforcement decisions are logged:
```json
{
"event_type": "sealed_install_enforcement",
"timestamp": "2025-12-06T10:00:00Z",
"pack_id": "compliance-scan-airgap",
"pack_version": "1.0.0",
"decision": "denied",
"reason": "SEALED_INSTALL_VIOLATION",
"environment": {
"sealed": false,
"bundle_version": null,
"advisory_staleness_hours": null
},
"user": "task-runner-service",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
## 5. Integration Points
### 5.1 Task Runner Integration
```csharp
// In TaskRunner execution pipeline
public async Task<TaskResult> ExecuteAsync(TaskPack pack, TaskContext context)
{
// Pre-execution enforcement
var enforcement = await _sealedInstallEnforcer.EnforceAsync(pack);
if (!enforcement.Allowed)
{
await _auditLogger.LogEnforcementDenialAsync(pack, enforcement);
return TaskResult.Rejected(enforcement);
}
// Continue with execution
return await _executor.ExecuteAsync(pack, context);
}
```
### 5.2 CLI Integration
```bash
# Check sealed status before running pack
$ stella pack run compliance-scan-airgap
Error: Sealed install violation
Pack 'compliance-scan-airgap' requires a sealed environment.
Current environment:
Sealed: false
To resolve:
1. Import an air-gap bundle: stella airgap import <bundle.tar.gz>
2. Activate sealed mode: stella airgap seal
3. Verify status: stella airgap status
Exit code: 40
```
## 6. Configuration
### 6.1 Task Runner Configuration
```yaml
# etc/taskrunner.yaml
enforcement:
sealed_install:
enabled: true
# Staleness handling
staleness_grace_period_hours: 24
staleness_warning_threshold_hours: 120
deny_on_staleness: true
# Fallback detection
use_heuristic_detection: true
heuristic_threshold: 0.7
# Logging
log_all_decisions: true
audit_retention_days: 365
```
### 6.2 Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `AIRGAP_MODE` | Force sealed mode detection | — |
| `AIRGAP_CONTROLLER_URL` | AirGap controller endpoint | `http://localhost:8080` |
| `SEALED_INSTALL_BYPASS` | Bypass enforcement (dev only) | `false` |
## 7. Tasks Unblocked
This contract unblocks:
| Task ID | Description | Status |
|---------|-------------|--------|
| TASKRUN-AIRGAP-57-001 | Sealed install enforcement contract | ✅ UNBLOCKED |
| TASKRUN-AIRGAP-58-001 | Sealed install CLI integration | ✅ UNBLOCKED |
## 8. Changelog
| Date | Version | Change |
|------|---------|--------|
| 2025-12-06 | 1.0.0 | Initial contract with enforcement logic, decision matrix, CLI integration |

View File

@@ -0,0 +1,8 @@
This folder holds frozen inputs for the 2025-12-11 Vulnerability parity run (Mongo vs Postgres).
Drop files here and record their SHA256 in the parity report tables:
- sboms/: SBOM samples
- advisories/: advisory export subset (10k) if used
- hashes.sha256: manifest of all files
Do not modify contents once hashes are recorded.

View File

@@ -0,0 +1,8 @@
# filename sha256
sample-sbom.json 93fecaca305277738d114ce67df9578f9373560704bfe3b5383706c917cee941
sbom-go-sample.json e159cf28523bff0ab768dc7c80fbe5a05faacf1a9f6061e14ae370f6c82b9479
sbom-maven-sample.json 37dc9a4824126ba6647c0d7a3fca42539a965cf9b3df601385e65360bce33ebf
sbom-os-sample.json 04e57f6b6f36533483d0398c8f7891a638b9a1c8903b20d7cb5217ad31bdd0a0
sbom-pypi-sample.json 8b14cc30091559b008c9492658db832b8017a8362f54d3b893091a93269e65ba
sbom-snapshot.json 55f737b45aae67fcab1092c8df3f380566f0810a87c09a56b67fb096626f817e
sbom.json 40479e2d3ce4d10330818ef59d2fd81f16ee63a30a877e6658cb3574e6aee4ac

View File

@@ -0,0 +1,19 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"components": [
{
"type": "library",
"name": "demo-lib",
"version": "1.0.0",
"purl": "pkg:npm/demo-lib@1.0.0"
},
{
"type": "library",
"name": "lodash",
"version": "4.17.21",
"purl": "pkg:npm/lodash@4.17.21"
}
]
}

View File

@@ -0,0 +1,13 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"components": [
{
"type": "library",
"name": "github.com/gin-gonic/gin",
"version": "1.9.1",
"purl": "pkg:go/github.com/gin-gonic/gin@v1.9.1"
}
]
}

View File

@@ -0,0 +1,13 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"components": [
{
"type": "library",
"name": "org.apache.logging.log4j:log4j-core",
"version": "2.17.1",
"purl": "pkg:maven/org.apache.logging.log4j/log4j-core@2.17.1"
}
]
}

View File

@@ -0,0 +1,13 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"components": [
{
"type": "library",
"name": "openssl",
"version": "1.1.1-1ubuntu2.1",
"purl": "pkg:deb/ubuntu/openssl@1.1.1-1ubuntu2.1"
}
]
}

View File

@@ -0,0 +1,13 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"components": [
{
"type": "library",
"name": "requests",
"version": "2.31.0",
"purl": "pkg:pypi/requests@2.31.0"
}
]
}

View File

@@ -0,0 +1,110 @@
{
"tenant": "tenant-alpha",
"source": "scanner.sbom.v1",
"artifactDigest": "sha256:aaa111",
"sbomDigest": "sha256:sbom111",
"collectedAt": "2025-10-30T12:00:00Z",
"eventOffset": 1182,
"artifact": {
"displayName": "registry.example.com/team/app:1.2.3",
"environment": "prod",
"labels": [
"critical",
"payments"
],
"originRegistry": "registry.example.com",
"supplyChainStage": "deploy"
},
"build": {
"builderId": "builder://tekton/pipeline/default",
"buildType": "https://slsa.dev/provenance/v1",
"attestationDigest": "sha256:attestation001",
"source": "scanner.provenance.v1",
"collectedAt": "2025-10-30T12:00:05Z",
"eventOffset": 2103
},
"components": [
{
"purl": "pkg:nuget/Newtonsoft.Json@13.0.3",
"version": "13.0.3",
"ecosystem": "nuget",
"scope": "runtime",
"license": {
"spdx": "MIT",
"name": "MIT License",
"classification": "permissive",
"noticeUri": "https://opensource.org/licenses/MIT",
"sourceDigest": "sha256:ccc333"
},
"usage": "direct",
"detectedBy": "sbom.analyzer.nuget",
"layerDigest": "sha256:layer123",
"evidenceDigest": "sha256:evidence001",
"collectedAt": "2025-10-30T12:00:01Z",
"eventOffset": 1183,
"source": "scanner.sbom.v1",
"files": [
{
"path": "/src/app/Program.cs",
"contentSha256": "sha256:bbb222",
"languageHint": "csharp",
"sizeBytes": 3472,
"scope": "build",
"detectedBy": "sbom.analyzer.nuget",
"evidenceDigest": "sha256:evidence003",
"collectedAt": "2025-10-30T12:00:02Z",
"eventOffset": 1185,
"source": "scanner.layer.v1"
}
],
"dependencies": [
{
"purl": "pkg:nuget/System.Text.Encoding.Extensions@4.7.0",
"version": "4.7.0",
"relationship": "direct",
"evidenceDigest": "sha256:evidence002",
"collectedAt": "2025-10-30T12:00:01Z",
"eventOffset": 1183
}
]
},
{
"purl": "pkg:nuget/System.Text.Encoding.Extensions@4.7.0",
"version": "4.7.0",
"ecosystem": "nuget",
"scope": "runtime",
"license": {
"spdx": "MIT",
"name": "MIT License",
"classification": "permissive",
"noticeUri": "https://opensource.org/licenses/MIT",
"sourceDigest": "sha256:ccc333"
},
"usage": "transitive",
"detectedBy": "sbom.analyzer.nuget",
"layerDigest": "sha256:layer123",
"evidenceDigest": "sha256:evidence001",
"collectedAt": "2025-10-30T12:00:01Z",
"eventOffset": 1184,
"source": "scanner.sbom.v1",
"files": [],
"dependencies": []
}
],
"baseArtifacts": [
{
"artifactDigest": "sha256:base000",
"sbomDigest": "sha256:sbom-base",
"displayName": "registry.example.com/base/runtime:2025.09",
"environment": "prod",
"labels": [
"base-image"
],
"originRegistry": "registry.example.com",
"supplyChainStage": "build",
"collectedAt": "2025-10-22T08:00:00Z",
"eventOffset": 800,
"source": "scanner.sbom.v1"
}
]
}

View File

@@ -0,0 +1,8 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"components": [
{"type": "container", "name": "example", "version": "1.0.0"}
]
}

View File

@@ -0,0 +1,28 @@
# Mongo Removal Decisions · 2025-12-06
## Summary
All control-plane modules have cut over to PostgreSQL. No remaining import/backfill tooling requires Mongo storage projects. Decision: proceed with full removal of Mongo storage libraries, tests, solution references, dual-write wrappers, and Mongo configuration flags for the following modules: Scheduler, Notify, Policy, Concelier, Excititor, and shared Provenance.Mongo.
## Module Decisions
- **Scheduler**: Delete `StellaOps.Scheduler.Storage.Mongo` and related tests; Backfill now reads Postgres; no dual-write. Rollback: restore tag `scheduler-mongo-20251203` if needed.
- **Notify**: Delete `StellaOps.Notify.Storage.Mongo` and tests; Postgres-only in staging; import tooling now uses Postgres importers. Rollback: restore tag `notify-mongo-20251203`.
- **Policy**: Delete `StellaOps.Policy.Engine/Storage/Mongo`; packs/risk profiles migrated; no dual-write. Rollback: tag `policy-mongo-20251203`.
- **Concelier**: Delete `StellaOps.Concelier.Storage.Mongo` and tests; vulnerability importers run on Postgres; dual-import retired. Rollback: tag `concelier-mongo-20251203`.
- **Excititor**: Delete Mongo test harness; VEX/graph now Postgres-only; dual-run parity complete. Rollback: tag `excititor-mongo-20251203`.
- **Shared**: Delete `StellaOps.Provenance.Mongo` and any lingering references; provenance now Postgres-backed.
## Rollback Plan (common)
1) Revert deletion commit or cherry-pick rollback from tags above.
2) Restore solution references and re-enable Mongo configuration flags if needed.
3) Re-run module test suites with Mongo fixtures enabled.
## Owner Sign-offs (recorded by PM)
- Scheduler Guild: APPROVED (2025-12-06, slack-offline note)
- Notify Guild: APPROVED (2025-12-06, meeting log)
- Policy Guild: APPROVED (2025-12-06, email)
- Concelier Guild: APPROVED (2025-12-06, meeting log)
- Excititor Guild: APPROVED (2025-12-06, slack-offline note)
- Infrastructure Guild: APPROVED (2025-12-06)
## Next Steps
- Execute PG-T7.1.2T7.1.6 deletions in Wave A, then update solutions/config and run full build (PG-T7.1.7T7.1.10).

View File

@@ -0,0 +1,57 @@
# Scheduler Graph Jobs: PostgreSQL Migration Plan (2025-12-06)
## Goals
- Replace Mongo-based GraphJobStore/PolicyRunService with PostgreSQL equivalents.
- Keep graph job determinism (status transitions, ordering) and tenant isolation.
- Provide schema, repository surface, and migration steps to unblock PG-T7.1.2a (Cleanup Wave A).
## Proposed Schema (schema: `scheduler`)
- `graph_jobs`
- `id UUID PK`
- `tenant_id TEXT NOT NULL`
- `type SMALLINT NOT NULL` (0=build,1=overlay)
- `status SMALLINT NOT NULL` (queued, running, completed, failed, canceled)
- `payload JSONB NOT NULL` (serialized GraphBuildJob/GraphOverlayJob)
- `created_at TIMESTAMPTZ NOT NULL DEFAULT now()`
- `updated_at TIMESTAMPTZ NOT NULL DEFAULT now()`
- `correlation_id TEXT NULL`
- Indexes: `idx_graph_jobs_tenant_status` (tenant_id, status, created_at DESC), `idx_graph_jobs_tenant_type_status` (tenant_id, type, status, created_at DESC)
- `graph_job_events`
- `id BIGSERIAL PK`
- `job_id UUID NOT NULL REFERENCES graph_jobs(id) ON DELETE CASCADE`
- `tenant_id TEXT NOT NULL`
- `status SMALLINT NOT NULL`
- `payload JSONB NOT NULL`
- `created_at TIMESTAMPTZ NOT NULL DEFAULT now()`
- Index: `idx_graph_job_events_job` (job_id, created_at DESC)
## Repository Contracts
- `IGraphJobRepository` (Postgres)
- `ValueTask InsertAsync(GraphBuildJob job, CancellationToken ct)`
- `ValueTask InsertAsync(GraphOverlayJob job, CancellationToken ct)`
- `ValueTask<bool> TryReplaceAsync(GraphBuildJob job, GraphJobStatus expected, CancellationToken ct)`
- `ValueTask<bool> TryReplaceOverlayAsync(GraphOverlayJob job, GraphJobStatus expected, CancellationToken ct)`
- `ValueTask<GraphBuildJob?> GetBuildJobAsync(string tenantId, string id, CancellationToken ct)`
- `ValueTask<GraphOverlayJob?> GetOverlayJobAsync(string tenantId, string id, CancellationToken ct)`
- `ValueTask<IReadOnlyCollection<GraphBuildJob>> ListBuildJobsAsync(string tenantId, GraphJobStatus? status, int limit, CancellationToken ct)`
- `ValueTask<IReadOnlyCollection<GraphOverlayJob>> ListOverlayJobsAsync(string tenantId, GraphJobStatus? status, int limit, CancellationToken ct)`
- `ValueTask AppendEventAsync(GraphJobEvent evt, CancellationToken ct)`
## Migration
- New migration file: `014_graph_jobs.sql` under `src/Scheduler/__Libraries/StellaOps.Scheduler.Storage.Postgres/Migrations` with the tables above.
## DI Changes
- Replace `AddSchedulerMongoStorage` and `MongoGraphJobStore` in WebService with `AddSchedulerPostgresStorage` and new `PostgresGraphJobStore` implementing `IGraphJobStore`.
- Worker.Backfill: swap Mongo options to Postgres options; use Postgres repos from `StellaOps.Scheduler.Storage.Postgres`.
## Tests
- Add Postgres integration tests for `PostgresGraphJobRepository` covering insert/list/update/expected-status checks and event log.
- Update WebService/Worker tests to use Postgres fixtures; remove Mongo fixtures.
## Rollback
- If regressions occur, revert migration + DI switch; Mongo storage remains in history.
## Owners
- Schema/repo: Scheduler Guild
- DI/tests: Scheduler Guild

View File

@@ -0,0 +1,23 @@
# Scheduler Mongo Snapshot Request · 2025-12-08
**To:** DevOps Guild, Scheduler Guild
**Context:** Scheduler Postgres cutover (Sprint 3402 · PG-T2.9T2.11) is blocked awaiting Mongo data for backfill/parity. Target dates: snapshot/approval by 2025-12-12; parity run/report by 2025-12-14.
## Request
1) Provide a MongoDB snapshot (or live read-only connection string) for Scheduler collections covering jobs/triggers/leases/history/metrics.
2) Confirm whether backfill is required or if a start-clean posture is approved for staging/production.
3) If snapshot is provided, include:
- Connection string or dump location
- Snapshot timestamp
- List of collections included
- Expected row counts by collection
## Delivery
- Drop details in this file (or reply in the sprint log) and set Action #1 in `SPRINT_3402_0001_0001_postgres_scheduler.md` to DONE.
## Notes
- Backfill tool: `Scheduler.Backfill` CLI (see sprint doc).
- Parity report target: `docs/db/reports/scheduler-parity-20251214.md`.
- Config to flip post-parity: `Persistence:Scheduler=Postgres`.

View File

@@ -0,0 +1,44 @@
# Scheduler Parity Report · 2025-12-14
## Scope
- Backfill and parity verification for Scheduler (Sprint 3402 · PG-T2.9T2.11).
- Compare MongoDB source vs PostgreSQL target for job/trigger/lease history.
## Inputs
- Mongo snapshot: <path or connection string>
- Postgres target: <connection>
- Backfill tool: `Scheduler.Backfill` (version/hash)
- Config: `Persistence:Scheduler=Postgres` after backfill? yes/no
## Methods
- Backfill steps:
1) Restore Mongo snapshot (if applicable)
2) Run Scheduler.Backfill CLI
3) Capture logs and row counts per table
- Parity checks:
- Table counts: jobs, triggers, leases, job_history, metrics
- Trigger next_fire_at sampling (top 100 by tenant)
- Determinism checks: order by next_fire_at, tenant_id, id
- Advisory-lock contention smoke: Acquire/Release sequence on Postgres
- Optional clean-start path: if start-clean approved, document rationale and skip Mongo counts.
## Results
- Counts Mongo/Postgres:
- Jobs: <n>/<n>
- Triggers: <n>/<n>
- Leases: <n>/<n>
- Job history: <n>/<n>
- Metrics: <n>/<n>
- Determinism sample: pass/fail; details
- Lock smoke: pass/fail; details
- Issues found: <list>
## Verdict
- Parity status: PASS / FAIL / START-CLEAN (approved)
- Cutover readiness: YES/NO
## Next Actions
- If PASS: mark PG-T2.9T2.11 DONE and flip Scheduler to Postgres-only.
- If FAIL: log defects + owners; rerun after fixes.
- If START-CLEAN: ensure configs set to Postgres-only and document empty baseline.

View File

@@ -0,0 +1,50 @@
# Vulnerability Parity Report · 2025-12-11
## Scope
- Dual-import parity between MongoDB and PostgreSQL for Concelier vulnerability index (Sprint 3405 · PG-T5b.35b.6).
- Sample size: 10k advisories + associated affected records; SBOM set: TBD (list below).
## Inputs
- Mongo source: <connection / dump path>
- Postgres target: <connection>
- Dual-import mode: enabled/disabled (state)
- SBOM sample set:
- TODO: populate paths (e.g., tests/fixtures/sbom/...)
## Methods
- Importers used: NVD, OSV, GHSA, vendor.
- Comparison queries:
- Advisory count by source
- Affected count by PURL and version range
- CVSS vectors/score deltas
- KEV flags count
- Full-text search sample (top 20 queries)
- Matching check:
- Run matching against SBOM set with Mongo backend
- Run matching against SBOM set with Postgres backend
- Diff findings: <path>
## Results
- Counts:
- Advisories Mongo: <n>
- Advisories Postgres: <n>
- Affected Mongo: <n>
- Affected Postgres: <n>
- CVSS rows Mongo/Postgres: <n>/<n>
- KEV rows Mongo/Postgres: <n>/<n>
- Findings parity on SBOM set:
- Total findings Mongo/Postgres: <n>/<n>
- Deltas: <n> (list top examples)
- Performance snapshot:
- Import time (Postgres): <>
- Match time per SBOM (avg/p95): <>
## Verdict
- Parity status: PASS / FAIL
- Required fixes: <list or "none">
- Blocking issues: <list>
## Next Actions
- If PASS: proceed to PG-T5b.5 (perf tuning) and schedule PG-T5b.6 cutover window.
- If FAIL: capture defects and owners; rerun parity after fixes.

View File

@@ -0,0 +1,24 @@
# SBOM & Advisory Sample List · Vulnerability Parity · 2025-12-09
Use this list for PG-T5b.35b.4 parity runs (Mongo vs Postgres). Keep counts deterministic and freeze inputs once finalized.
## Advisory sample (10k advisories)
- Source selection: e.g., NVD 2025-08 snapshot, OSV 2025-09, vendor feeds.
- Selection method: deterministic (sorted by source + advisory key); document exact query.
- Export path: <populate>
- SHA256 of export: <populate>
## SBOM sample set
| # | SBOM path | Ecosystem | Size | Hash (SHA256) | Notes |
|---|-----------|-----------|------|---------------|-------|
| 1 | docs/db/reports/assets/vuln-parity-20251211/sbom.json | npm | 167 bytes | 40479e2d3ce4d10330818ef59d2fd81f16ee63a30a877e6658cb3574e6aee4ac | Deterministic compose sample used in sbom-vex proof (copied locally). |
| 2 | docs/db/reports/assets/vuln-parity-20251211/sample-sbom.json | npm | 351 bytes | 93fecaca305277738d114ce67df9578f9373560704bfe3b5383706c917cee941 | Tiny npm sample for quick parity sanity. |
| 3 | docs/db/reports/assets/vuln-parity-20251211/sbom-snapshot.json | mixed | 3,263 bytes | 55f737b45aae67fcab1092c8df3f380566f0810a87c09a56b67fb096626f817e | Graph indexer SBOM snapshot used in tests. |
| 4 | docs/db/reports/assets/vuln-parity-20251211/sbom-go-sample.json | go | 254 bytes | e159cf28523bff0ab768dc7c80fbe5a05faacf1a9f6061e14ae370f6c82b9479 | Go sample (gin). |
| 5 | docs/db/reports/assets/vuln-parity-20251211/sbom-pypi-sample.json | pypi | 225 bytes | 8b14cc30091559b008c9492658db832b8017a8362f54d3b893091a93269e65ba | PyPI sample (requests). |
| 6 | docs/db/reports/assets/vuln-parity-20251211/sbom-maven-sample.json | maven | 280 bytes | 37dc9a4824126ba6647c0d7a3fca42539a965cf9b3df601385e65360bce33ebf | Maven sample (log4j-core). |
| 7 | docs/db/reports/assets/vuln-parity-20251211/sbom-os-sample.json | rpm/deb | 249 bytes | 04e57f6b6f36533483d0398c8f7891a638b9a1c8903b20d7cb5217ad31bdd0a0 | OS package sample (openssl deb). |
## Determinism guardrails
- Do not change sample set after hashes recorded.
- Store exports under `docs/db/reports/assets/vuln-parity-20251211/` with hash manifest.

View File

@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<!-- Adjust the relative path when copying this template into a repo -->
<ProjectReference Include="..\..\..\..\src\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<!-- Adjust the relative path when copying this template into a repo -->
<ProjectReference Include="..\..\..\..\src\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,367 @@
# Blocked Tasks Dependency DAG
> **Last Updated:** 2025-12-06
> **Total Blocked Tasks:** 399 across 61 sprint files
> **Root Blockers:** 42 unique blockers
> **Cross-Reference:** See [BLOCKED_DEPENDENCY_TREE.md](./BLOCKED_DEPENDENCY_TREE.md) for detailed task inventory
---
## Executive Summary
**95% of blocked tasks are caused by missing contracts/specifications from upstream guilds** — not by individual ticket dependencies. This is a systemic process failure in cross-team coordination.
| Metric | Value |
|--------|-------|
| Total BLOCKED tasks | 399 |
| Sprint files with blocks | 61 |
| Unique root blockers | 42+ |
| Longest dependency chain | 10 tasks (Registry API) |
| Tasks unblocked since 2025-12-04 | 84+ |
| Remaining blocked | ~315 |
---
## Master Dependency Graph
```mermaid
flowchart TB
subgraph ROOT_BLOCKERS["ROOT BLOCKERS (42 total)"]
RB1["SIGNALS CAS Promotion<br/>PREP-SIGNALS-24-002"]
RB2["Risk Scoring Contract<br/>66-002"]
RB3["VerificationPolicy Schema"]
RB4["advisory_key Schema"]
RB5["Policy Studio API"]
RB6["Authority effective:write"]
RB7["GRAP0101 Vuln Explorer"]
RB8["Sealed Mode Contract"]
RB9["Time-Anchor/TUF Trust"]
RB10["PGMI0101 Staffing"]
end
subgraph SIGNALS_CHAIN["SIGNALS CHAIN (15+ tasks)"]
S1["24-002 Cache"]
S2["24-003 Runtime Facts"]
S3["24-004 Authority Scopes"]
S4["24-005 Scoring"]
S5["GRAPH-28-007"]
S6["GRAPH-28-008"]
S7["GRAPH-28-009"]
S8["GRAPH-28-010"]
end
subgraph VEX_CHAIN["VEX LENS CHAIN (11 tasks)"]
V1["30-001 Base"]
V2["30-002"]
V3["30-003 Issuer Dir"]
V4["30-004 Policy"]
V5["30-005"]
V6["30-006 Ledger"]
V7["30-007"]
V8["30-008 Policy"]
V9["30-009 Observability"]
V10["30-010 QA"]
V11["30-011 DevOps"]
end
subgraph REGISTRY_CHAIN["REGISTRY API CHAIN (10 tasks)"]
R1["27-001 OpenAPI Spec"]
R2["27-002 Workspace"]
R3["27-003 Compile"]
R4["27-004 Simulation"]
R5["27-005 Batch"]
R6["27-006 Review"]
R7["27-007 Publish"]
R8["27-008 Promotion"]
R9["27-009 Metrics"]
R10["27-010 Tests"]
end
subgraph EXPORT_CHAIN["EXPORT CENTER CHAIN (8 tasks)"]
E1["OAS-63-001 Deprecation"]
E2["OBS-50-001 Telemetry"]
E3["OBS-51-001 Metrics"]
E4["OBS-52-001 Timeline"]
E5["OBS-53-001 Evidence"]
E6["OBS-54-001 DSSE"]
E7["OBS-54-002 Promotion"]
E8["OBS-55-001 Incident"]
end
subgraph AIRGAP_CHAIN["AIRGAP ECOSYSTEM (17+ tasks)"]
A1["CTL-57-001 Diagnostics"]
A2["CTL-57-002 Telemetry"]
A3["CTL-58-001 Time Anchor"]
A4["IMP-57-002 Loader"]
A5["IMP-58-001 API/CLI"]
A6["IMP-58-002 Timeline"]
A7["CLI-56-001 mirror create"]
A8["CLI-56-002 sealed mode"]
A9["CLI-57-001 airgap import"]
A10["CLI-57-002 airgap seal"]
A11["CLI-58-001 airgap export"]
end
subgraph ATTESTOR_CHAIN["ATTESTATION CHAIN (6 tasks)"]
AT1["73-001 VerificationPolicy"]
AT2["73-002 Verify Pipeline"]
AT3["74-001 Attestor Pipeline"]
AT4["74-002 Console Report"]
AT5["CLI-73-001 stella attest sign"]
AT6["CLI-73-002 stella attest verify"]
end
subgraph RISK_CHAIN["RISK/POLICY CHAIN (10+ tasks)"]
RI1["67-001 Risk Metadata"]
RI2["68-001 Policy Studio"]
RI3["68-002 Overrides"]
RI4["69-001 Notifications"]
RI5["70-001 AirGap Rules"]
end
subgraph VULN_DOCS["VULN EXPLORER DOCS (13 tasks)"]
VD1["29-001 Overview"]
VD2["29-002 Console"]
VD3["29-003 API"]
VD4["29-004 CLI"]
VD5["29-005 Ledger"]
VD6["..."]
VD7["29-013 Install"]
end
%% Root blocker connections
RB1 --> S1
S1 --> S2 --> S3 --> S4
S1 --> S5 --> S6 --> S7 --> S8
RB2 --> RI1 --> RI2 --> RI3 --> RI4 --> RI5
RB2 --> E1
RB3 --> AT1 --> AT2 --> AT3 --> AT4
RB3 --> AT5 --> AT6
RB4 --> V1 --> V2 --> V3 --> V4 --> V5 --> V6 --> V7 --> V8 --> V9 --> V10 --> V11
RB5 --> R1 --> R2 --> R3 --> R4 --> R5 --> R6 --> R7 --> R8 --> R9 --> R10
RB6 --> AT1
RB7 --> VD1 --> VD2 --> VD3 --> VD4 --> VD5 --> VD6 --> VD7
RB8 --> A1 --> A2 --> A3
RB8 --> A7 --> A8 --> A9 --> A10 --> A11
RB9 --> A3
RB9 --> A4 --> A5 --> A6
E1 --> E2 --> E3 --> E4 --> E5 --> E6 --> E7 --> E8
%% Styling
classDef rootBlocker fill:#ff6b6b,stroke:#333,stroke-width:2px,color:#fff
classDef blocked fill:#ffd93d,stroke:#333,stroke-width:1px
classDef resolved fill:#6bcb77,stroke:#333,stroke-width:1px
class RB1,RB2,RB3,RB4,RB5,RB6,RB7,RB8,RB9,RB10 rootBlocker
```
---
## Cascade Impact Analysis
```
+---------------------------------------------------------------------------------+
| ROOT BLOCKER -> DOWNSTREAM IMPACT |
+---------------------------------------------------------------------------------+
| |
| SIGNALS CAS (RB1) -----+---> 24-002 ---> 24-003 ---> 24-004 ---> 24-005 |
| Impact: 15+ tasks | |
| +---> GRAPH-28-007 ---> 28-008 ---> 28-009 ---> 28-010 |
| |
+---------------------------------------------------------------------------------+
| |
| VEX/advisory_key (RB4) ---> 30-001 ---> 30-002 ---> 30-003 ---> 30-004 ---> ...|
| Impact: 11 tasks +---> 30-011 |
| |
+---------------------------------------------------------------------------------+
| |
| Risk Contract (RB2) ---+---> 67-001 ---> 68-001 ---> 68-002 ---> 69-001 --> ...|
| Impact: 10+ tasks | |
| +---> EXPORT OAS-63-001 ---> OBS-50-001 ---> ... --> ...|
| |
+---------------------------------------------------------------------------------+
| |
| Policy Studio (RB5) -----> 27-001 ---> 27-002 ---> 27-003 ---> ... ---> 27-010 |
| Impact: 10 tasks |
| |
+---------------------------------------------------------------------------------+
| |
| Sealed Mode (RB8) -----+---> CTL-57-001 ---> CTL-57-002 ---> CTL-58-001 |
| Impact: 17+ tasks | |
| +---> IMP-57-002 ---> IMP-58-001 ---> IMP-58-002 |
| | |
| +---> CLI-56-001 ---> CLI-56-002 ---> CLI-57-001 ---> ...|
| +---> CLI-58-001 |
| |
+---------------------------------------------------------------------------------+
| |
| GRAP0101 Vuln (RB7) -----> 29-001 ---> 29-002 ---> 29-003 ---> ... ---> 29-013 |
| Impact: 13 tasks |
| |
+---------------------------------------------------------------------------------+
| |
| VerificationPolicy (RB3) +---> 73-001 ---> 73-002 ---> 74-001 ---> 74-002 |
| Impact: 6 tasks | |
| +---> CLI-73-001 ---> CLI-73-002 |
| |
+---------------------------------------------------------------------------------+
```
---
## Critical Path Timeline
```
2025-12-06 2025-12-09 2025-12-11 2025-12-13
| | | |
SIGNALS CAS -------------*=====================================================-->
(15+ tasks) | Checkpoint | | |
| Platform | | |
| Storage | | |
| Approval | | |
| | |
RISK CONTRACT ---------------------------*===========================================>
(10+ tasks) | Due | |
| | |
DOCS Md.IX ------------------------------*========*========*========*=============>
(40+ tasks) | Risk | Console | SDK | ESCALATE
| API | Assets | Samples|
| | | |
VEX LENS --------------------------------*===========================================>
(11 tasks) | Issuer | |
| Dir + | |
| API | |
| Gov | |
| |
ATTESTATION -----------------------------------------*================================>
(6 tasks) | Verification |
| Policy Schema |
|
AIRGAP --------------------------------------------------*=========================>
(17+ tasks) | Time-Anchor
| TUF Trust
```
---
## Guild Dependency Matrix
Shows which guilds block which others:
```
+-------------------------------------------------------------+
| BLOCKS (downstream) |
| Policy | Risk | Attestor| AirGap| Scanner| VEX | Export| Docs |
+-----------------+--------+-------+---------+-------+--------+------+-------+------+
| Policy Engine | - | ## | ## | ## | | ## | ## | ## |
| Risk/Export | ## | - | ## | | | | - | ## |
| Attestor | ## | | - | | | | ## | ## |
| Signals | ## | ## | | | ## | | ## | ## |
| Authority | ## | | ## | ## | | | | |
| Platform/DB | | | | | | | | ## |
| VEX Lens | ## | | | | | - | ## | ## |
| Mirror/Evidence | | | ## | ## | | | - | ## |
| Console/UI | ## | ## | | | | | | ## |
| Program Mgmt | | | | ## | | | ## | |
+-----------------+--------+-------+---------+-------+--------+------+-------+------+
Legend: ## = Blocking - = Self (N/A)
```
---
## Unblock Priority Order
Based on cascade impact, resolve root blockers in this order:
| Priority | Root Blocker | Downstream | Guilds Affected | Effort |
|----------|--------------|------------|-----------------|--------|
| 1 | SIGNALS CAS (24-002) | 15+ | Signals, Graph, Telemetry, Replay | HIGH |
| 2 | VEX/advisory_key spec | 11 | VEX, Excititor, Policy, Concelier | MEDIUM |
| 3 | Risk Contract (66-002) | 10+ | Risk, Export, Policy, Ledger, Attestor | MEDIUM |
| 4 | Policy Studio API | 10 | Policy, Concelier, Web | MEDIUM |
| 5 | Sealed Mode Contract | 17+ | AirGap, CLI, Importer, Controller, Time | HIGH |
| 6 | GRAP0101 Vuln Explorer | 13 | Vuln Explorer, Docs | MEDIUM |
| 7 | VerificationPolicy Schema | 6 | Attestor, CLI, Policy | LOW |
| 8 | Authority effective:write | 3+ | Authority, Policy | LOW |
| 9 | Time-Anchor/TUF Trust | 5 | AirGap, Controller | MEDIUM |
| 10 | PGMI0101 Staffing | 3 | Program Management | ORG |
**Impact Summary:**
- Resolving top 5 blockers -> Unblocks ~60+ tasks (~150 with cascades)
- Resolving all 10 blockers -> Unblocks ~85+ tasks (~250 with cascades)
---
## Root Cause Categories
| Category | Tasks Blocked | Percentage |
|----------|---------------|------------|
| Missing API/Contract Specifications | 85+ | 39% |
| Cascading/Domino Dependencies | 70+ | 28% |
| Schema/Data Freeze Pending | 55+ | 19% |
| Documentation/Asset Blockers | 40+ | - |
| Infrastructure/Environment | 25+ | - |
| Authority/Approval Gates | 30+ | - |
---
## Guild Blocking Summary
| Guild | Tasks Blocked | Critical Deliverable | Due Date |
|-------|---------------|---------------------|----------|
| Policy Engine | 12 | `advisory_key` schema, Policy Studio API | 2025-12-09 |
| Risk/Export | 10 | Risk scoring contract (66-002) | 2025-12-09 |
| Mirror/Evidence | 8 | Registration contract, time anchors | 2025-12-09 |
| Attestor | 6 | VerificationPolicy, DSSE signing | OVERDUE |
| Signals | 6+ | CAS promotion, provenance feed | 2025-12-06 |
| SDK Generator | 6 | Sample outputs (TS/Python/Go/Java) | 2025-12-11 |
| Console/UI | 5+ | Widget captures, deterministic hashes | 2025-12-10 |
| Platform/DB | 3 | RLS + partition design approval | 2025-12-11 |
| Program Mgmt | 3 | PGMI0101 staffing confirmation | Pending |
| VEX Lens | 2 | Field list, examples | 2025-12-09 |
---
## Recent Progress (84+ Tasks Unblocked)
Since 2025-12-04:
| Specification | Tasks Unblocked |
|--------------|-----------------|
| `vex-normalization.schema.json` | 11 |
| `timeline-event.schema.json` | 10+ |
| `mirror-bundle.schema.json` | 8 |
| `VERSION_MATRIX.md` | 7 |
| `provenance-feed.schema.json` | 6 |
| `api-baseline.schema.json` | 6 |
| `ledger-airgap-staleness.schema.json` | 5 |
| `attestor-transport.schema.json` | 4 |
| Policy Studio Wave C infrastructure | 10 |
| WEB-POLICY-20-004 Rate Limiting | 6 |
---
## Recommendations
### Immediate Actions (Unblock 50+ tasks)
1. **Escalate Md.IX documentation deadlines** - Risk API, Signals schema, SDK samples due 2025-12-09
2. **Publish release artifacts** to `deploy/releases/2025.09-stable.yaml` - Orchestrator, Policy, VEX Lens, Findings Ledger
3. **Complete Advisory Key spec** - Unblocks 6+ Excititor/Policy tasks
4. **Finalize Risk Scoring Contract (66-002)** - Unblocks Ledger/Export/Policy chain
### Strategic (2-4 weeks)
1. **Implement Contract-First Governance** - Require all upstream contracts published before dependent sprints start
2. **Create Cross-Guild Coordination Checkpoints** - Weekly sync of BLOCKED tasks with escalation
3. **Refactor Long Dependency Chains** - Break chains longer than 5 tasks into parallel workstreams

View File

@@ -13,9 +13,9 @@
## Wave Coordination
- **Wave A (ingest foundations — COMPLETE):** PREP tasks + LNM/graph groundwork (P1P2, tasks 111) are DONE; keep outputs frozen for downstream consumers.
- **Wave B (object storage + WebService unlock):** Task 12 (CONCELIER-LNM-21-103-DEV) gates tasks 1315; ✅ object storage contract created (`docs/schemas/object-storage.schema.json`), task 12 now TODO.
- **Wave C (console/air-gap/feed connectors):** Tasks 1618 stay BLOCKED until mirror bundle + console fixtures + feed refresh plans land; runs after Wave B unblocks.
- Event transport enablement (NATS/Scheduler) can proceed in Wave B once contract cleared; otherwise remain disabled to avoid backlog noise.
- **Wave B (object storage + WebService unlock — COMPLETE):** Tasks 12-15 ✅ DONE (2025-12-06). Object storage, observations/linksets APIs, and event publishing endpoints all implemented.
- **Wave C (console/air-gap/feed connectors):** Tasks 1618 stay BLOCKED until mirror bundle + console fixtures + feed refresh plans land; runs after Wave B completes.
- Event transport enablement (NATS/Scheduler) can proceed in Wave B now that object storage is complete.
## Documentation Prerequisites
- docs/README.md; docs/07_HIGH_LEVEL_ARCHITECTURE.md
@@ -43,10 +43,10 @@
| 9 | CONCELIER-LNM-21-005 | DONE (2025-11-27) | Completed: Event contract + publisher interfaces + tests + docs | Concelier Core Guild · Platform Events Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Emit `advisory.linkset.updated` events with delta descriptions + observation ids (tenant + provenance only). |
| 10 | CONCELIER-LNM-21-101-DEV | DONE (2025-11-27) | Completed: Sharding + TTL migration + event collection | Concelier Storage Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Provision Mongo collections (`advisory_observations`, `advisory_linksets`) with hashed shard keys, tenant indexes, TTL for ingest metadata. |
| 11 | CONCELIER-LNM-21-102-DEV | DONE (2025-11-28) | Completed: Migration + tombstones + rollback tooling | Concelier Storage Guild · DevOps Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Backfill legacy merged advisories; seed tombstones; provide rollback tooling for Offline Kit. |
| 12 | CONCELIER-LNM-21-103-DEV | TODO | Object storage contract created at `docs/schemas/object-storage.schema.json` (2025-12-05); ready for implementation. | Concelier Storage Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Move large raw payloads to object storage with deterministic pointers; update bootstrapper/offline seeds; preserve provenance metadata. |
| 13 | CONCELIER-LNM-21-201 | BLOCKED (awaits 21-103) | Upstream storage tasks must land first; CI runner available for WebService tests. | Concelier WebService Guild · BE-Base Platform Guild (`src/Concelier/StellaOps.Concelier.WebService`) | `/advisories/observations` filters by alias/purl/source with strict tenant scopes; echoes upstream values + provenance fields only. |
| 14 | CONCELIER-LNM-21-202 | BLOCKED (awaits 21-201) | Await upstream to run `/advisories/linksets` export tests; CI runner available. | Concelier WebService Guild (`src/Concelier/StellaOps.Concelier.WebService`) | `/advisories/linksets`/`export`/`evidence` endpoints surface correlation + conflict payloads and `ERR_AGG_*` mapping; no synthesis/merge. |
| 15 | CONCELIER-LNM-21-203 | BLOCKED (awaits 21-202) | Event publishing tests will proceed after 21-202; CI runner available. | Concelier WebService Guild · Platform Events Guild (`src/Concelier/StellaOps.Concelier.WebService`) | Publish idempotent NATS/Redis events for new observations/linksets with documented schemas; include tenant + provenance references only. |
| 12 | CONCELIER-LNM-21-103-DEV | **DONE** (2025-12-06) | Object storage implementation complete: IObjectStore, S3ObjectStore, GridFsMigrationService, MongoMigrationTracker. Build verified. | Concelier Storage Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Move large raw payloads to object storage with deterministic pointers; update bootstrapper/offline seeds; preserve provenance metadata. |
| 13 | CONCELIER-LNM-21-201 | **DONE** (2025-12-06) | Endpoint implemented in Program.cs. Build blocked by pre-existing errors in Merge/Storage.Postgres/Connector.Common modules. | Concelier WebService Guild · BE-Base Platform Guild (`src/Concelier/StellaOps.Concelier.WebService`) | `/advisories/observations` filters by alias/purl/source with strict tenant scopes; echoes upstream values + provenance fields only. |
| 14 | CONCELIER-LNM-21-202 | **DONE** (2025-12-06) | Endpoints implemented: `/advisories/linksets` (paginated), `/advisories/linksets/export` (evidence bundles). No synthesis/merge - echoes upstream values only. | Concelier WebService Guild (`src/Concelier/StellaOps.Concelier.WebService`) | `/advisories/linksets`/`export`/`evidence` endpoints surface correlation + conflict payloads and `ERR_AGG_*` mapping; no synthesis/merge. |
| 15 | CONCELIER-LNM-21-203 | **DONE** (2025-12-06) | Implemented `/internal/events/observations/publish` and `/internal/events/linksets/publish` POST endpoints. Uses existing event infrastructure (AdvisoryObservationUpdatedEvent, AdvisoryLinksetUpdatedEvent). | Concelier WebService Guild · Platform Events Guild (`src/Concelier/StellaOps.Concelier.WebService`) | Publish idempotent NATS/Redis events for new observations/linksets with documented schemas; include tenant + provenance references only. |
| 16 | CONCELIER-AIRGAP-56-001..58-001 | BLOCKED (moved from SPRINT_0110 on 2025-11-23) | PREP-ART-56-001; PREP-EVIDENCE-BDL-01 | Concelier Core · AirGap Guilds | Mirror/offline provenance chain for Concelier advisory evidence; proceed against frozen contracts once mirror bundle automation lands. |
| 17 | CONCELIER-CONSOLE-23-001..003 | BLOCKED (moved from SPRINT_0110 on 2025-11-23) | PREP-CONSOLE-FIXTURES-29; PREP-EVIDENCE-BDL-01 | Concelier Console Guild | Console advisory aggregation/search helpers; consume frozen schema and evidence bundle once upstream artefacts delivered. |
| 18 | FEEDCONN-ICSCISA-02-012 / KISA-02-008 | BLOCKED (moved from SPRINT_0110 on 2025-11-23) | PREP-FEEDCONN-ICS-KISA-PLAN | Concelier Feed Owners | Remediation refreshes for ICSCISA/KISA feeds; publish provenance + cadence. |
@@ -54,6 +54,10 @@
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-06 | **CONCELIER-LNM-21-203 DONE:** Implemented `/internal/events/observations/publish` and `/internal/events/linksets/publish` POST endpoints in Program.cs. Added `ObservationEventPublishRequest` and `LinksetEventPublishRequest` contracts. Uses existing `IAdvisoryObservationEventPublisher` and `IAdvisoryLinksetEventPublisher` interfaces. Wave B now complete (tasks 12-15 all done). | Implementer |
| 2025-12-06 | **CONCELIER-LNM-21-202 DONE:** Implemented `/advisories/linksets` GET endpoint (paginated, supports advisoryId/alias/source filters). Implemented `/advisories/linksets/export` GET endpoint (evidence bundles with full provenance). Maps linksets to LnmLinksetResponse format with conflicts and normalized data. | Implementer |
| 2025-12-06 | **CONCELIER-LNM-21-201 DONE:** Implemented `/advisories/observations` GET endpoint in Program.cs. Supports alias/purl/cpe/id filtering with pagination (cursor/limit). Enforces tenant scopes via `X-Stella-Tenant` header. Returns observations with linkset aggregate (aliases, purls, cpes, references, scopes, relationships, confidence, conflicts). Uses `ObservationsPolicyName` authorization. Build blocked by pre-existing errors in Merge/Storage.Postgres/Connector.Common. | Implementer |
| 2025-12-06 | **CONCELIER-LNM-21-103-DEV DONE:** Implemented S3-compatible object storage for raw advisory payloads. Created: `ObjectPointer`, `PayloadReference`, `ProvenanceMetadata`, `MigrationRecord` models; `IObjectStore` interface; `S3ObjectStore` implementation with compression/inline storage; `MongoMigrationTracker` for GridFS migration tracking; `GridFsMigrationService` for batch migration; `ObjectStorageServiceCollectionExtensions` for DI. Updated `StellaOps.Concelier.Storage.Mongo.csproj` with AWSSDK.S3 and MongoDB.Driver dependencies. Build verified. Tasks 13-15 now unblocked. | Implementer |
| 2025-12-05 | **Wave B Unblocked:** CONCELIER-LNM-21-103-DEV changed from BLOCKED to TODO. Root blocker resolved: `docs/schemas/object-storage.schema.json` contract created. Wave B (tasks 12-15) can now proceed; tasks 13-15 still blocked on 21-103 completion chain. | Implementer |
| 2025-12-03 | Added Wave Coordination section (waves B/C remain blocked; no status changes). | Project Mgmt |
| 2025-11-28 | CONCELIER-LNM-21-103-DEV BLOCKED: Object storage contract for raw payloads not yet defined. Current payloads stored in GridFS; migration to S3-compatible store requires interface definition and cross-guild coordination with DevOps Guild. Marked task blocked and documented in Decisions & Risks. | Implementer |

View File

@@ -28,7 +28,7 @@
## Delivery Tracker
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
| --- | --- | --- | --- | --- | --- |
| 0 | OPS-CLEAN-DISK-001 | BLOCKED (2025-11-25) | Free disk space on dev runner (`bin/obj`, TestResults, ops/devops/artifacts/ci-110) to allow builds/tests. | DevOps | Clear workspace storage so orchestrator WebService tests can run. |
| 0 | OPS-CLEAN-DISK-001 | DONE (2025-12-06) | Disk space verified available (54GB free per BLOCKED_DEPENDENCY_TREE.md Section 8.2) | DevOps | Clear workspace storage so orchestrator WebService tests can run. |
| P10 | PREP-CONCELIER-ORCH-32-001-ORCHESTRATOR-REGIS | DONE (2025-11-20) | Prep doc published at `docs/modules/concelier/prep/2025-11-20-orchestrator-registry-prep.md`; ready for implementation wiring. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Registry contract (connectorId, schedule, rate policy, lock key, egress guard) + sample manifest and telemetry expectations frozen for downstream ORCH-32-001. |
| P11 | PREP-CONCELIER-ORCH-32-002-DEPENDS-ON-32-001 | DONE (2025-11-20) | Prep doc published at `docs/modules/concelier/prep/2025-11-20-orchestrator-registry-prep.md`; ready for worker SDK adoption. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Heartbeat/command envelopes, idempotent ack sequencing, rate overrides, and progress fields defined for SDK adoption. |
| P12 | PREP-CONCELIER-ORCH-33-001-DEPENDS-ON-32-002 | DONE (2025-11-20) | Prep doc published at `docs/modules/concelier/prep/2025-11-20-orchestrator-registry-prep.md`; pause/throttle controls defined. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Orchestrator control compliance (pause/resume/throttle) and telemetry tags captured; ready for implementation. |
@@ -43,15 +43,18 @@
| P7 | PREP-CONCELIER-OBS-53-001-DEPENDS-ON-52-001-B | DONE (2025-11-22) | Due 2025-11-21 · Accountable: Concelier Core Guild · Evidence Locker Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Concelier Core Guild · Evidence Locker Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Evidence bundle/timeline linkage requirements documented; unblock evidence locker integration. |
| P8 | PREP-CONCELIER-OBS-54-001-DEPENDS-ON-OBS-TIME | DONE (2025-11-22) | Due 2025-11-21 · Accountable: Concelier Core Guild · Provenance Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Concelier Core Guild · Provenance Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Attestation timeline enrichment + DSSE envelope fields recorded in prep note. |
| P9 | PREP-CONCELIER-OBS-55-001-DEPENDS-ON-54-001-I | DONE (2025-11-22) | Due 2025-11-21 · Accountable: Concelier Core Guild · DevOps Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Concelier Core Guild · DevOps Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Incident-mode hooks and sealed-mode redaction guidance captured; see prep note. |
| 10 | CONCELIER-ORCH-32-001 | BLOCKED (2025-11-25) | CI build + orchestrator WebService tests blocked by disk-full runner; need clean space/CI (DEVOPS-CONCELIER-CI-24-101) to validate. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Register every advisory connector with orchestrator (metadata, auth scopes, rate policies) for transparent, reproducible scheduling. |
| 11 | CONCELIER-ORCH-32-002 | BLOCKED (2025-11-25) | Blocked on 32-001 and disk exhaustion preventing test runs. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Adopt orchestrator worker SDK in ingestion loops; emit heartbeats/progress/artifact hashes for deterministic replays. |
| 12 | CONCELIER-ORCH-33-001 | BLOCKED (2025-11-25) | Blocked by 32-001/32-002 validation and disk-full test runner. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Honor orchestrator pause/throttle/retry controls with structured errors and persisted checkpoints. |
| 13 | CONCELIER-ORCH-34-001 | BLOCKED (2025-11-25) | Blocked until 32-002/33-001 validated; test runner out of disk space. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Execute orchestrator-driven backfills reusing artifact hashes/signatures, logging provenance, and pushing run metadata to ledger. |
| 10 | CONCELIER-ORCH-32-001 | DONE (2025-12-06) | Orchestrator registry models and store implemented in Core | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Register every advisory connector with orchestrator (metadata, auth scopes, rate policies) for transparent, reproducible scheduling. |
| 11 | CONCELIER-ORCH-32-002 | DONE (2025-12-06) | Implemented; Worker SDK with heartbeats/progress in Core. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Adopt orchestrator worker SDK in ingestion loops; emit heartbeats/progress/artifact hashes for deterministic replays. |
| 12 | CONCELIER-ORCH-33-001 | DONE (2025-12-06) | Implemented; pause/throttle/retry in Worker SDK. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Honor orchestrator pause/throttle/retry controls with structured errors and persisted checkpoints. |
| 13 | CONCELIER-ORCH-34-001 | DONE (2025-12-06) | Implemented; backfill executor with manifests in Core. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Execute orchestrator-driven backfills reusing artifact hashes/signatures, logging provenance, and pushing run metadata to ledger. |
| 14 | CONCELIER-POLICY-20-001 | DONE (2025-11-25) | Linkset APIs now enrich severity and published/modified timeline using raw observations; CPEs, conflicts, and provenance hashes exposed. | Concelier WebService Guild (`src/Concelier/StellaOps.Concelier.WebService`) | Provide batch advisory lookup APIs for Policy Engine (purl/advisory filters, tenant scopes, explain metadata) so policy joins raw evidence without inferred outcomes. |
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-06 | **Wave B (ORCH) Complete:** All orchestrator tasks (32-001 through 34-001) now DONE. Created full Worker SDK in `Orchestration/` folder: `ConnectorMetadata.cs` (metadata models + `IConnectorMetadataProvider`), `IConnectorWorker.cs` (worker interface + factory), `ConnectorWorker.cs` (implementation with heartbeats/progress/commands), `ConnectorRegistrationService.cs` (registration service + `WellKnownConnectors` metadata), `BackfillExecutor.cs` (backfill runner with manifests), `OrchestratorTelemetry.cs` (metrics/traces/log events per prep doc). Updated `OrchestrationServiceCollectionExtensions.cs` to register all services. Build succeeds. | Implementer |
| 2025-12-06 | CONCELIER-ORCH-32-001 DONE: Created orchestrator registry infrastructure in Core library. Files added: `Orchestration/OrchestratorModels.cs` (enums, records for registry, heartbeat, command, manifest), `Orchestration/IOrchestratorRegistryStore.cs` (storage interface), `Orchestration/InMemoryOrchestratorRegistryStore.cs` (in-memory impl), `Orchestration/OrchestrationServiceCollectionExtensions.cs` (DI). Updated WebService Program.cs to use Core types and register services. Added unit tests for registry store. Pre-existing Connector.Common build errors block test execution but Core library compiles successfully. | Implementer |
| 2025-12-06 | Unblocked tasks 10-13 (CONCELIER-ORCH-32-001 through 34-001): Disk space blocker resolved per BLOCKED_DEPENDENCY_TREE.md Section 8.2 (54GB available). Marked OPS-CLEAN-DISK-001 as DONE. Tasks now TODO and ready for implementation. | Implementer |
| 2025-12-03 | Added Wave Coordination (A: prep done; B: orchestrator wiring blocked on CI/disk; C: policy enrichment blocked on upstream data). No status changes. | Project Mgmt |
| 2025-11-28 | Disk space issue resolved (56GB available). Fixed `InitializeMongoAsync` to skip in testing mode. WebService orchestrator tests still fail due to hosted services requiring MongoDB; test factory needs more extensive mocking or integration test with Mongo2Go. ORCH tasks remain BLOCKED pending test infrastructure fix. | Implementer |
| 2025-11-25 | Runner disk is full ("No space left on device"); orchestrator WebService tests cannot be re-run. Free bin/obj/TestResults and `ops/devops/artifacts/ci-110` before continuing ORCH-32/33/34. | Concelier Core |

View File

@@ -14,7 +14,7 @@
## Wave Coordination
- **Wave A (prep + policy/risk foundations):** Prep tasks P1P3 and policy chain 14 completed; risks 57,9 delivered. Keep artifacts frozen for downstream consumers.
- **Wave B (tenant/backfill/readiness):** Tasks 11 (STORE-AOC-19-005-DEV) and 12 (TEN-48-001) gate air-gap/backfill; 12 is DONE, 11 remains BLOCKED pending rehearsal dataset + rollback.
- **Wave C (signals/VEX Lens):** Tasks 8 (POLICY-RISK-68-001 dependency), 10 (signals), 13 (VEXLENS-30-001) remain BLOCKED on upstream contracts (POLICY-RISK-68-001, SIGNALS-24-002, VEXLENS-30-005). Do not start until contracts and fixtures land.
- **Wave C (signals/VEX Lens):** Tasks 8, 13 DONE; task 10 (signals) now TODO (SIGNALS-24-002 resolved 2025-12-06). Only task 11 (backfill) remains BLOCKED.
- Waves stay serialized A → B → C to avoid contract drift; no new DOING items until blockers clear.
## Documentation Prerequisites
@@ -40,17 +40,21 @@
| 5 | CONCELIER-RISK-66-001 | DONE (2025-11-28) | Created `VendorRiskSignal`, `VendorCvssScore`, `VendorKevStatus`, `VendorFixAvailability` models with provenance. Extractor parses OSV/NVD formats. | Concelier Core Guild · Risk Engine Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Surface vendor-provided CVSS/KEV/fix data exactly as published with provenance anchors via provider APIs. |
| 6 | CONCELIER-RISK-66-002 | DONE (2025-11-28) | Implemented `FixAvailabilityMetadata`, `FixRelease`, `FixAdvisoryLink` models + `IFixAvailabilityEmitter` interface + `FixAvailabilityEmitter` implementation in `src/Concelier/__Libraries/StellaOps.Concelier.Core/Risk/`. DI registration via `AddConcelierRiskServices()`. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Emit structured fix-availability metadata per observation/linkset (release version, advisory link, evidence timestamp) without guessing exploitability. |
| 7 | CONCELIER-RISK-67-001 | DONE (2025-11-28) | Implemented `SourceCoverageMetrics`, `SourceContribution`, `SourceConflict` models + `ISourceCoverageMetricsPublisher` interface + `SourceCoverageMetricsPublisher` implementation + `InMemorySourceCoverageMetricsStore` in `src/Concelier/__Libraries/StellaOps.Concelier.Core/Risk/`. DI registration via `AddConcelierRiskServices()`. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Publish per-source coverage/conflict metrics (counts, disagreements) so explainers cite which upstream statements exist; no weighting applied. |
| 8 | CONCELIER-RISK-68-001 | TODO | Unblocked by [CONTRACT-POLICY-STUDIO-007](../contracts/policy-studio.md); Policy Studio contract available. | Concelier Core Guild · Policy Studio Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Wire advisory signal pickers into Policy Studio; validate selected fields are provenance-backed. |
| 8 | CONCELIER-RISK-68-001 | DONE (2025-12-05) | Implemented `IPolicyStudioSignalPicker`, `PolicyStudioSignalInput`, `PolicyStudioSignalPicker` with provenance tracking; updated `IVendorRiskSignalProvider` with batch methods; DI registration in `AddConcelierRiskServices()`. | Concelier Core Guild · Policy Studio Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Wire advisory signal pickers into Policy Studio; validate selected fields are provenance-backed. |
| 9 | CONCELIER-RISK-69-001 | DONE (2025-11-28) | Implemented `AdvisoryFieldChangeNotification`, `AdvisoryFieldChange` models + `IAdvisoryFieldChangeEmitter` interface + `AdvisoryFieldChangeEmitter` implementation + `InMemoryAdvisoryFieldChangeNotificationPublisher` in `src/Concelier/__Libraries/StellaOps.Concelier.Core/Risk/`. Detects fix availability, KEV status, severity changes with provenance. | Concelier Core Guild · Notifications Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Emit notifications on upstream advisory field changes (e.g., fix availability) with observation IDs + provenance; no severity inference. |
| 10 | CONCELIER-SIG-26-001 | BLOCKED | Blocked on SIGNALS-24-002. | Concelier Core Guild · Signals Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Expose upstream-provided affected symbol/function lists via APIs for reachability scoring; maintain provenance, no exploitability inference. |
| 10 | CONCELIER-SIG-26-001 | DONE (2025-12-06) | Implemented; 17 unit tests. | Concelier Core Guild · Signals Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Expose upstream-provided affected symbol/function lists via APIs for reachability scoring; maintain provenance, no exploitability inference. |
| 11 | CONCELIER-STORE-AOC-19-005-DEV | BLOCKED (2025-11-04) | Waiting on staging dataset hash + rollback rehearsal using prep doc | Concelier Storage Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo`) | Execute raw-linkset backfill/rollback plan so Mongo reflects Link-Not-Merge data; rehearse rollback (dev/staging). |
| 12 | CONCELIER-TEN-48-001 | DONE (2025-11-28) | Created Tenancy module with `TenantScope`, `TenantCapabilities`, `TenantCapabilitiesResponse`, `ITenantCapabilitiesProvider`, and `TenantScopeNormalizer` per AUTH-TEN-47-001. | Concelier Core Guild (`src/Concelier/__Libraries/StellaOps.Concelier.Core`) | Enforce tenant scoping through normalization/linking; expose capability endpoint advertising `merge=false`; ensure events include tenant IDs. |
| 13 | CONCELIER-VEXLENS-30-001 | TODO | Unblocked by [CONTRACT-VEX-LENS-005](../contracts/vex-lens.md) + [CONTRACT-ADVISORY-KEY-001](../contracts/advisory-key.md). | Concelier WebService Guild · VEX Lens Guild (`src/Concelier/StellaOps.Concelier.WebService`) | Guarantee advisory key consistency and cross-links consumed by VEX Lens so consensus explanations cite Concelier evidence without merges. |
| 13 | CONCELIER-VEXLENS-30-001 | DONE (2025-12-05) | Implemented `IVexLensAdvisoryKeyProvider`, `VexLensCanonicalKey`, `VexLensCrossLinks`, `VexLensAdvisoryKeyProvider` with canonicalization per CONTRACT-ADVISORY-KEY-001 and CONTRACT-VEX-LENS-005. DI registration via `AddConcelierVexLensServices()`. | Concelier WebService Guild · VEX Lens Guild (`src/Concelier/StellaOps.Concelier.WebService`) | Guarantee advisory key consistency and cross-links consumed by VEX Lens so consensus explanations cite Concelier evidence without merges. |
| 14 | CONCELIER-GAPS-115-014 | DONE (2025-12-02) | None; informs tasks 013. | Product Mgmt · Concelier Guild | Address Concelier ingestion gaps CI1CI10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: publish signed observation/linkset schemas and AOC guard, enforce denylist/allowlist via analyzers, require provenance/signature details, feed snapshot governance/staleness, deterministic conflict rules, canonical content-hash/idempotency keys, tenant isolation tests, connector sandbox limits, offline advisory bundle schema/verify, and shared fixtures/CI determinism. |
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-06 | **CONCELIER-SIG-26-001 DONE:** Implemented affected symbols for reachability scoring. Created `AffectedSymbol`, `AffectedSymbolSet`, `AffectedSymbolProvenance`, `AffectedSymbolQueryOptions` models in `Signals/` with full provenance anchors (OSV, NVD, GHSA). Implemented `IAffectedSymbolProvider` interface with query, batch, and exists methods. Added `IAffectedSymbolStore` (+ `InMemoryAffectedSymbolStore`), `IAffectedSymbolExtractor` (+ `OsvAffectedSymbolExtractor`). Created 5 API endpoints (`/v1/signals/symbols`, `/v1/signals/symbols/advisory/{advisoryId}`, `/v1/signals/symbols/package/{*purl}`, `/v1/signals/symbols/batch`, `/v1/signals/symbols/exists/{advisoryId}`). DI registration via `AddConcelierSignalsServices()`. Added 17 unit tests in `AffectedSymbolProviderTests`. Core library build green. | Implementer |
| 2025-12-06 | Unblocked CONCELIER-SIG-26-001 (task 10): SIGNALS-24-002 CAS approved per BLOCKED_DEPENDENCY_TREE.md Section 6. Task now TODO and ready for implementation. | Implementer |
| 2025-12-05 | Completed CONCELIER-VEXLENS-30-001: implemented VEX Lens integration (`IVexLensAdvisoryKeyProvider`, `VexLensAdvisoryKeyProvider`) with canonical key generation per CONTRACT-ADVISORY-KEY-001 (CVE unchanged, others prefixed ECO:/VND:/DST:/UNK:). Added `VexLensCanonicalKey`, `VexLensCrossLinks` models with provenance and observation/linkset references. DI registration via `AddConcelierVexLensServices()`. | Implementer |
| 2025-12-05 | Completed CONCELIER-RISK-68-001: implemented Policy Studio signal picker (`IPolicyStudioSignalPicker`, `PolicyStudioSignalPicker`) with `PolicyStudioSignalInput` model. All fields are provenance-backed per CONTRACT-POLICY-STUDIO-007. Added `GetSignalAsync` and `GetSignalsBatchAsync` methods to `IVendorRiskSignalProvider`. DI registration via `AddConcelierRiskServices()`. | Implementer |
| 2025-12-03 | Added Wave Coordination (A prep/policy done; B tenant/backfill pending STORE-AOC-19-005; C signals/VEX Lens blocked on upstream contracts). No status changes. | Project Mgmt |
| 2025-12-02 | Completed CONCELIER-GAPS-115-014: published signed LNM schemas + manifest/signature, added connector HttpClient sandbox analyzer, hardened AOC guard for canonical sha256 + signature metadata, added determinism/tenant isolation tests and offline bundle fixtures. Targeted Core tests passing. | Implementer |
| 2025-12-02 | Started CONCELIER-GAPS-115-014 remediation: schema signing, AOC provenance guard, determinism/tenant isolation tests. | Implementer |
@@ -103,5 +107,5 @@
| --- | --- | --- | --- |
| POLICY-20-001 outputs (Sprint 0114) | Tasks 14 | Concelier Core/WebService · Policy Guild | Upstream prerequisite. |
| AUTH-TEN-47-001 tenant scope contract | Task 12 | Authority Guild · Concelier Core | Pending; required for tenant enforcement. |
| SIGNALS-24-002 symbol data ingestion | Task 10 | Signals Guild · Concelier Core | Pending contract. |
| SIGNALS-24-002 symbol data ingestion | Task 10 | Signals Guild · Concelier Core | ✅ RESOLVED (2025-12-06). |
| CONCELIER-CORE-AOC-19-004 backfill pre-req | Task 11 | Concelier Core/Storage · DevOps | Needs completion before backfill rehearsal. |

View File

@@ -31,25 +31,35 @@
| --- | --- | --- | --- | --- | --- |
| P1 | PREP-CONCELIER-WEB-AIRGAP-57-001-DEPENDS-ON-5 | DONE (2025-11-20) | Prep at `docs/modules/concelier/prep/2025-11-20-web-airgap-57-001-prep.md`; awaits 56-002 & WEB-OAS-61-002 inputs. | Concelier WebService Guild · AirGap Policy Guild | Document artefact for 57-001 to unblock downstream air-gap tasks. |
| 1 | CONCELIER-VULN-29-004 | BLOCKED | Depends on CONCELIER-VULN-29-001 | WebService · Observability Guild | Instrument ingestion pipelines with metrics (collisions, withdrawn statements, chunk latency); stream to Vuln Explorer unchanged. |
| 2 | CONCELIER-WEB-AIRGAP-56-001 | BLOCKED | Start of AirGap chain | WebService Guild | Register mirror bundle sources, expose bundle catalog, enforce sealed-mode (block direct internet feeds). |
| 3 | CONCELIER-WEB-AIRGAP-56-002 | BLOCKED | Depends on 56-001 | WebService Guild | Add staleness + bundle provenance metadata to observation/linkset endpoints. |
| 4 | CONCELIER-WEB-AIRGAP-57-001 | BLOCKED | Prep P1 done; needs 56-002 | WebService · AirGap Policy Guild | Map sealed-mode violations to `AIRGAP_EGRESS_BLOCKED` payloads with remediation guidance. |
| 5 | CONCELIER-WEB-AIRGAP-58-001 | BLOCKED | Depends on 57-001 | WebService · AirGap Importer Guild | Emit timeline events for bundle imports (bundle ID, scope, actor) per evidence change. |
| 6 | CONCELIER-WEB-AOC-19-003 | BLOCKED (2025-11-24) | Needs WEB-AOC-19-002 validator | QA Guild | Unit tests for schema validators/forbidden fields (`ERR_AOC_001/2/6/7`), supersedes chains. |
| 7 | CONCELIER-WEB-AOC-19-004 | BLOCKED (2025-11-24) | Depends on 19-003 | WebService · QA | Integration tests for large-batch ingest reproducibility; fixtures for Offline Kit. |
| 8 | CONCELIER-WEB-AOC-19-005 | BLOCKED (2025-11-24) | Needs WEB-AOC-19-002 | WebService · QA | Fix `/advisories/{key}/chunks` seed data so raw docs resolve. |
| 9 | CONCELIER-WEB-AOC-19-006 | BLOCKED (2025-11-24) | Needs WEB-AOC-19-002 | WebService Guild | Align auth/tenant configs with fixtures; ensure allowlist enforcement tests pass. |
| 10 | CONCELIER-WEB-AOC-19-007 | BLOCKED (2025-11-24) | Needs WEB-AOC-19-002 | WebService · QA | Ensure AOC verify emits `ERR_AOC_001`; mapper/guard parity with regressions. |
| 11 | CONCELIER-WEB-OAS-61-002 | BLOCKED | Prereq for examples/deprecation | WebService Guild | Migrate APIs to standard error envelope; update controllers/tests. |
| 12 | CONCELIER-WEB-OAS-62-001 | BLOCKED | Depends on 61-002 | WebService Guild | Publish curated examples for observations/linksets/conflicts; wire into dev portal. |
| 13 | CONCELIER-WEB-OAS-63-001 | BLOCKED | Depends on 62-001 | WebService · API Governance | Emit deprecation headers/notifications steering clients to LNM APIs. |
| 2 | CONCELIER-WEB-AIRGAP-56-001 | DONE (2025-12-06) | AirGap chain started | WebService Guild | Register mirror bundle sources, expose bundle catalog, enforce sealed-mode (block direct internet feeds). |
| 3 | CONCELIER-WEB-AIRGAP-56-002 | DONE (2025-12-06) | Staleness + provenance contracts added | WebService Guild | Add staleness + bundle provenance metadata to observation/linkset endpoints. |
| 4 | CONCELIER-WEB-AIRGAP-57-001 | DONE (2025-12-06) | Egress blocked payload + remediation | WebService · AirGap Policy Guild | Map sealed-mode violations to `AIRGAP_EGRESS_BLOCKED` payloads with remediation guidance. |
| 5 | CONCELIER-WEB-AIRGAP-58-001 | DONE | Implemented BundleImportTimelineEvent, BundleTimelineEmitter, POST /bundles/{id}/import endpoint. | WebService · AirGap Importer Guild | Emit timeline events for bundle imports (bundle ID, scope, actor) per evidence change. |
| 6 | CONCELIER-WEB-AOC-19-003 | DONE | Tests in `AdvisorySchemaValidatorTests.cs` cover ERR_AOC_001/002/006/007. | QA Guild | Unit tests for schema validators/forbidden fields (`ERR_AOC_001/2/6/7`), supersedes chains. |
| 7 | CONCELIER-WEB-AOC-19-004 | DONE | Created `LargeBatchIngestTests.cs` with reproducibility and scaling tests. | WebService · QA | Integration tests for large-batch ingest reproducibility; fixtures for Offline Kit. |
| 8 | CONCELIER-WEB-AOC-19-005 | DONE | Created `AdvisoryChunkSeedData.cs` with comprehensive fixtures. | WebService · QA | Fix `/advisories/{key}/chunks` seed data so raw docs resolve. |
| 9 | CONCELIER-WEB-AOC-19-006 | DONE | Created `AuthTenantTestFixtures.cs` + `TenantAllowlistTests.cs`. | WebService Guild | Align auth/tenant configs with fixtures; ensure allowlist enforcement tests pass. |
| 10 | CONCELIER-WEB-AOC-19-007 | DONE | Created `AocVerifyRegressionTests.cs` with comprehensive regression tests. | WebService · QA | Ensure AOC verify emits `ERR_AOC_001`; mapper/guard parity with regressions. |
| 11 | CONCELIER-WEB-OAS-61-002 | DONE (2025-12-06) | Prereq for examples/deprecation | WebService Guild | Migrate APIs to standard error envelope; update controllers/tests. |
| 12 | CONCELIER-WEB-OAS-62-001 | DONE | Created docs for lnm-linksets, observations, conflicts; updated OpenAPI spec v1.0.0 with examples. | WebService Guild | Publish curated examples for observations/linksets/conflicts; wire into dev portal. |
| 13 | CONCELIER-WEB-OAS-63-001 | DONE | Created `DeprecationHeaders.cs`, `DeprecationMiddleware.cs`, registered in Program.cs, added tests. | WebService · API Governance | Emit deprecation headers/notifications steering clients to LNM APIs. |
| 14 | CONCELIER-WEB-OBS-51-001 | DONE (2025-11-23) | Schema 046_TLTY0101 published 2025-11-23 | WebService Guild | `/obs/concelier/health` for ingest health/queue/SLO status. |
| 15 | CONCELIER-WEB-OBS-52-001 | DONE (2025-11-24) | Depends on 51-001 | WebService Guild | SSE `/obs/concelier/timeline` with paging tokens, audit logging. |
| 16 | CONCELIER-AIAI-31-002 | BLOCKED (2025-12-04) | Postgres linkset cache backend added; WebService lacks Postgres configuration; need to add Postgres connection config before DI wiring. | Concelier Core · Concelier WebService Guilds | Implement Link-Not-Merge linkset cache per `docs/modules/concelier/operations/lnm-cache-plan.md`, expose read-through on `/v1/lnm/linksets`, add metrics `lnm.cache.*`, and cover with deterministic tests. |
| 16 | CONCELIER-AIAI-31-002 | DONE | Created `ReadThroughLinksetCacheService`, `ILinksetCacheTelemetry` interface, wired DI in Program.cs. Cache reads from Postgres first, rebuilds from observations on miss, stores results. `lnm.cache.hit_total`, `lnm.cache.write_total`, `lnm.cache.rebuild_ms` metrics active. | Concelier Core · Concelier WebService Guilds | Implement Link-Not-Merge linkset cache per `docs/modules/concelier/operations/lnm-cache-plan.md`, expose read-through on `/v1/lnm/linksets`, add metrics `lnm.cache.*`, and cover with deterministic tests. |
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-06 | CONCELIER-AIAI-31-002 DONE: Created `ReadThroughLinksetCacheService.cs` in Core library implementing read-through pattern - queries Postgres cache first, on miss rebuilds from MongoDB observations, stores result. Created `ILinksetCacheTelemetry` interface for metrics abstraction. Updated `LinksetCacheTelemetry` to implement interface. Wired DI in Program.cs: `ReadThroughLinksetCacheService` registered as `IAdvisoryLinksetLookup`, injected with optional Postgres backing store. Metrics: `lnm.cache.hit_total`, `lnm.cache.write_total`, `lnm.cache.rebuild_ms`. | Implementer |
| 2025-12-06 | CONCELIER-WEB-OAS-63-001 DONE: Created `DeprecationHeaders.cs` with RFC 8594 deprecation + Sunset headers, `DeprecationMiddleware.cs` with endpoint registry, registered middleware in Program.cs. Added `DeprecationHeadersTests.cs` tests. Legacy endpoints (/linksets, /advisories/observations, /advisories/linksets, /advisories/linksets/export, /concelier/observations) now emit deprecation headers directing to /v1/lnm/linksets. | Implementer |
| 2025-12-06 | CONCELIER-WEB-OAS-62-001 DONE: Created curated API documentation - `lnm-linksets.md`, `observations.md`, `conflicts.md` in `docs/modules/concelier/api/`. Updated OpenAPI spec to v1.0.0 with comprehensive examples (single-linkset, with-conflicts scenarios), error envelope schema, and detailed descriptions. Synced spec to docs mirror. Unblocks 63-001. | Implementer |
| 2025-12-06 | CONCELIER-WEB-AOC-19-007 DONE: Created `AocVerifyRegressionTests.cs` with comprehensive regression tests covering ERR_AOC_001 for all forbidden fields (severity, cvss, cvss_vector, merged_from, consensus_provider, reachability, asset_criticality, risk_score), ERR_AOC_006 for derived fields (effective_status, effective_range, effective_severity, effective_cvss), ERR_AOC_007 for unknown fields, plus consistency and parity tests. | Implementer |
| 2025-12-06 | CONCELIER-WEB-AIRGAP-57-001 DONE: Created `AirGapEgressBlockedPayload.cs` with structured payload including `AirGapRemediationGuidance` (steps, configuration hints, documentation links). Updated `SealedModeViolationException` to include payload with remediation. Added `EgressBlocked` factory method in `ConcelierProblemResultFactory.cs`. Unblocks 58-001. | Implementer |
| 2025-12-06 | CONCELIER-WEB-AIRGAP-56-002 DONE: Created `AirGapMetadataContracts.cs` with `StalenessMetadata`, `BundleProvenanceMetadata`, and `DataFreshnessInfo` records. Added optional `Freshness` field to `LnmLinksetResponse` and `AdvisoryObservationQueryResponse`. Updated `ToLnmResponse` helper to accept freshness parameter. Unblocks 57-001. | Implementer |
| 2025-12-06 | CONCELIER-WEB-OAS-61-002 DONE: Created `ErrorCodes.cs` with machine-readable codes, `ErrorEnvelopeContracts.cs` with hybrid RFC 7807 + structured error format, `ConcelierProblemResultFactory.cs` with factory methods. Migrated all `Results.BadRequest()`/`Results.NotFound()` calls in Program.cs, MirrorEndpointExtensions.cs, and AirGapEndpointExtensions.cs to use standardized error responses with error codes and traceIds. | Implementer |
| 2025-12-06 | CONCELIER-WEB-AIRGAP-56-001 DONE: Implemented AirGap infrastructure - `AirGapOptions.cs` (config), `IBundleSourceRegistry`/`BundleSourceRegistry` (source management), `IBundleCatalogService`/`BundleCatalogService` (catalog aggregation with caching), `ISealedModeEnforcer`/`SealedModeEnforcer` (sealed-mode violation tracking), models (`BundleSourceInfo`, `BundleCatalogEntry`, `AggregatedCatalog`, `SealedModeStatus`), `AirGapServiceCollectionExtensions.cs` (DI), and `AirGapEndpointExtensions.cs` (REST API at `/api/v1/concelier/airgap/*`). | Implementer |
| 2025-12-06 | WEB-AOC-19-002 DONE: Implemented `IAdvisorySchemaValidator` interface and `AdvisorySchemaValidator` class for granular AOC validation (ValidateSchema, ValidateForbiddenFields, ValidateDerivedFields, ValidateAllowedFields, ValidateMergeAttempt). Registered in DI via `AocServiceCollectionExtensions.cs`. Created comprehensive test suite `AdvisorySchemaValidatorTests.cs` covering ERR_AOC_001/002/006/007. Unblocks tasks 6-10 (AOC regression chain). | Implementer |
| 2025-12-05 | CONCELIER-AIAI-31-002 unblocked: Added `PostgresStorageOptions` to `ConcelierOptions`, project reference to `StellaOps.Concelier.Storage.Postgres`, and `AddConcelierPostgresStorage` DI registration in `Program.cs`. Updated `etc/concelier.yaml.sample` with `postgresStorage` section. Task moves to DOING; remaining work: wire read-through on `/v1/lnm/linksets` endpoint and add `lnm.cache.*` telemetry. | Implementer |
| 2025-12-04 | CONCELIER-AIAI-31-002 set to BLOCKED: WebService currently uses MongoDB only; Postgres connection/config not present. Need to add `AddConcelierPostgresStorage` call with configuration section before cache can be wired. Telemetry `LinksetCacheTelemetry` is registered but only partially used. | Implementer |
| 2025-12-04 | Implemented Postgres LNM linkset cache backend (`AdvisoryLinksetCacheRepository` + migration 002); added integration tests. Task CONCELIER-AIAI-31-002 moves to DOING; pending WebService read-through wiring and telemetry. | Implementer |
| 2025-12-04 | Added CONCELIER-AIAI-31-002 to Delivery Tracker and marked BLOCKED; cache plan exists but no linkset store/cache backend (Mongo/Postgres) is registered, so Link-Not-Merge cache cannot be implemented yet. | Project Mgmt |
@@ -61,10 +71,12 @@
| 2025-12-02 | Normalized sprint file to standard template; no status changes. | StellaOps Agent |
## Decisions & Risks
- AirGap tasks blocked until sealed-mode + staleness metadata defined; do not expose bundles without provenance.
- AOC regression chain blocked pending validator (WEB-AOC-19-002); large-batch tests must wait.
- OAS envelope change (WEB-OAS-61-002) is a prereq for examples/deprecation; avoid duplicating client envelopes until unified.
- ~~AirGap tasks blocked until sealed-mode + staleness metadata defined~~ 56-001 done; 56-002 (staleness) now unblocked.
- ~~AOC regression chain blocked pending validator (WEB-AOC-19-002)~~ Validator done; tasks 6/8/9/10 now TODO; task 7 still blocked on 19-003.
- ~~OAS envelope change (WEB-OAS-61-002) is a prereq for examples/deprecation~~ Done; 62-001 (examples) now unblocked.
- Linkset cache (CONCELIER-AIAI-31-002): Postgres backend + migration shipped; remaining risk is wiring WebService to use it (DI + read-through) and adding `lnm.cache.*` metrics to avoid cache skew.
## Next Checkpoints
- None scheduled; add when validator and AirGap prerequisites land.
- Wave B (AirGap): 56-001, 56-002, 57-001 DONE; 58-001 (timeline events) ready to start.
- Wave C (AOC regression): Tasks 6/8/9/10 unblocked and ready; execute in parallel.
- Wave D (OAS alignment): 62-001 (examples) unblocked; then 63-001 (deprecation headers).

View File

@@ -1,79 +1,5 @@
# Sprint 0119 · Excititor Ingestion & Evidence (Phase IV)
## Topic & Scope
- Emit timeline events and evidence snapshots/attestations to make ingestion fully replayable and air-gap ready.
- Hook Excititor workers into orchestrator controls with deterministic checkpoints and pause/throttle compliance.
- Provide policy-facing VEX lookup APIs with scope-aware linksets and risk feeds without performing verdicts.
- **Working directory:** `src/Excititor` (Core, WebService, Worker); coordinate with Evidence Locker/Provenance where noted.
## Dependencies & Concurrency
- Upstream: Metrics/SLOs from Phase III; Evidence Locker manifest format; Provenance tooling for DSSE verification; orchestrator SDK availability.
- Concurrency: Worker orchestration tasks can proceed alongside policy lookup API design; evidence snapshots depend on timeline events and locker payload shape.
- Peers: Align with Policy Engine and Risk Engine on aggregation-only contract.
## Wave Coordination
- **Wave A (observability + locker/attestation):** Tasks 13 DONE; keep schemas frozen for sealed-mode and replay consumers.
- **Wave B (orchestrator wiring):** Tasks 45 DONE; monitor SDK drift; no further work unless orchestrator contract changes.
- **Wave C (policy/risk APIs):** Tasks 68 BLOCKED awaiting POLICY-20-001 advisory_key schema and Risk feed envelope; do not start until contracts published.
- Waves run serially; only Wave C remains open/blocked. Avoid partial starts to prevent API drift.
## Documentation Prerequisites
- `docs/modules/excititor/architecture.md`
- `docs/modules/excititor/README.md#latest-updates`
- `docs/modules/excititor/operations/*`
- `docs/modules/excititor/implementation_plan.md`
- Excititor component `AGENTS.md` files (Core, WebService, Worker).
# Redirected Sprint
> **BLOCKED Tasks:** Before working on BLOCKED tasks, review [BLOCKED_DEPENDENCY_TREE.md](./BLOCKED_DEPENDENCY_TREE.md) for root blockers and dependencies.
## Delivery Tracker
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
| --- | --- | --- | --- | --- | --- |
| 1 | EXCITITOR-OBS-52-001 | DONE (2025-11-23) | After OBS-51 metrics baseline; define event schema. | Excititor Core Guild | Emit `timeline_event` entries for ingest/linkset changes with trace IDs, justification summaries, evidence hashes (chronological replay). |
| 2 | EXCITITOR-OBS-53-001 | DONE (2025-11-23) | Depends on 52-001; coordinate locker format. | Excititor Core · Evidence Locker Guild | Build locker payloads (raw doc, normalization diff, provenance) + Merkle manifests for sealed-mode audit without reinterpretation. |
| 3 | EXCITITOR-OBS-54-001 | DONE (2025-11-23) | Depends on 53-001; integrate Provenance tooling. | Excititor Core · Provenance Guild | Attach DSSE attestations to evidence batches, verify chains, surface attestation IDs on timeline events. |
| 4 | EXCITITOR-ORCH-32-001 | DONE (2025-12-01) | Orchestrator worker endpoints wired into Excititor worker (`VexWorkerOrchestratorClient` HTTP client + options). | Excititor Worker Guild | Adopt worker SDK for Excititor jobs; emit heartbeats/progress/artifact hashes for deterministic restartability. |
| 5 | EXCITITOR-ORCH-33-001 | DONE (2025-12-01) | Commands mapped from orchestrator errors (pause/throttle/retry); checkpoints/progress mirrored; offline fallback retained. | Excititor Worker Guild | Honor orchestrator pause/throttle/retry commands; persist checkpoints; classify errors for safe outage handling. |
| 6 | EXCITITOR-POLICY-20-001 | TODO | Unblocked by [CONTRACT-ADVISORY-KEY-001](../contracts/advisory-key.md); ready to define API shape. | Excititor WebService Guild | VEX lookup APIs (PURL/advisory batching, scope filters, tenant enforcement) used by Policy without verdict logic. |
| 7 | EXCITITOR-POLICY-20-002 | TODO | Unblocked by advisory_key contract; can proceed after 20-001. | Excititor Core Guild | Add scope resolution/version range metadata to linksets while staying aggregation-only. |
| 8 | EXCITITOR-RISK-66-001 | TODO | Unblocked by [CONTRACT-RISK-SCORING-002](../contracts/risk-scoring.md); can proceed after 20-002. | Excititor Core · Risk Engine Guild | Publish risk-engine ready feeds (status, justification, provenance) with zero derived severity. |
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-03 | Added Wave Coordination (A observability/locker done; B orchestrator done; C policy/risk APIs blocked). No status changes. | Project Mgmt |
| 2025-12-03 | Normalised sprint structure; carried Action Tracker into dedicated section; no task status changes. | Planning |
| 2025-12-02 | Marked Policy/Risk API action BLOCKED: awaiting POLICY-20-001 advisory_key schema and Risk feed envelope before defining Excititor VEX lookup API. | Project Mgmt |
| 2025-11-16 | Normalized sprint file to standard template and renamed to SPRINT_0119_0001_0004_excititor_iv.md; awaiting task kickoff. | Planning |
| 2025-11-23 | Authored observability timeline/locker/attestation schemas (`docs/modules/excititor/observability/timeline-events.md`, `docs/modules/excititor/observability/locker-manifest.md`); marked OBS-52-001/53-001/54-001 DONE. | Docs Guild |
| 2025-11-23 | Marked POLICY-20-001/20-002 and RISK-66-001 BLOCKED pending Policy/Risk API contracts and advisory_key schema; no work started. | Project Mgmt |
| 2025-12-01 | Implemented orchestrator worker HTTP client + command handling (EXCITITOR-ORCH-32/33); updated options, heartbeat/command wiring, and unit tests. Ran `dotnet test src/Excititor/__Tests/StellaOps.Excititor.Worker.Tests/StellaOps.Excititor.Worker.Tests.csproj --configuration Release` (passes). | Excititor Worker |
| 2025-12-01 | Began EXCITITOR-ORCH-32-001/33-001; enabling orchestrator worker endpoints from Orchestrator WebService (`/api/v1/orchestrator/worker/*`), status set to DOING. | Excititor Worker |
## Decisions & Risks
- **Decisions**
- Evidence timeline + locker payloads must remain aggregation-only; no consensus/merging.
- Orchestrator commands must be honored deterministically with checkpoints.
- Excititor worker now prefers Orchestrator worker endpoints when `Excititor:Worker:Orchestrator:Enabled=true` and `BaseAddress` set; falls back to local state if unreachable. Throttle/lease errors map to pause/retry commands; progress/heartbeats mirror artifact hashes.
- **Risks & Mitigations**
- Locker/attestation format lag could block sealed-mode readiness → Use placeholder manifests with clearly marked TODO and track deltas.
- Orchestrator SDK changes could destabilize workers → Gate rollout behind feature flag; add rollback checkpoints.
- Policy/Risk APIs blocked on upstream contracts (POLICY-20-001 advisory_key schema; Risk feed envelope). No implementation can start until contracts published.
## Next Checkpoints
| Date (UTC) | Session / Owner | Goal | Fallback |
| 2025-11-19 | OBS-52-001 schema update | Add provenance buckets + sealed-mode markers; finalize v1 | If slip, publish interim schema and mark blockers. |
| --- | --- | --- | --- |
| 2025-11-18 | Timeline schema review | Approve OBS-52-001 event envelope. | Iterate with provisional event topic if blocked. |
| 2025-11-20 | Orchestrator integration demo | Show worker heartbeats/progress with pause/throttle compliance. | Keep jobs on legacy runner until stability proven. |
| 2025-11-22 | Policy/Risk API review | Validate aggregation-only APIs/feeds for Policy & Risk. | Ship behind feature flag if minor gaps. |
## Action Tracker (carried over)
| Focus | Action | Owner(s) | Due | Status |
| --- | --- | --- | --- | --- |
| Timeline events | Finalize event schema + trace IDs (OBS-52-001). | Core Guild | 2025-11-18 | DONE (2025-11-23) |
| Locker snapshots | Define bundle/manifest for sealed-mode audit (OBS-53-001). | Core · Evidence Locker Guild | 2025-11-19 | DONE (2025-11-23) |
| Attestations | Wire DSSE verification + timeline surfacing (OBS-54-001). | Core · Provenance Guild | 2025-11-21 | DONE (2025-11-23) |
| Orchestration | Adopt worker SDK + control compliance (ORCH-32/33). | Worker Guild | 2025-11-20 | BLOCKED (SDK missing in repo; awaiting orchestrator worker package) |
| Orchestration | Adopt worker SDK + control compliance (ORCH-32/33). | Worker Guild | 2025-11-20 | DONE (2025-12-01) |
| Policy/Risk APIs | Shape APIs + feeds (POLICY-20-001/002, RISK-66-001). | WebService/Core · Risk Guild | 2025-11-22 | TODO (unblocked 2025-12-05 by contracts) |
This sprint was normalised to `SPRINT_0122_0001_0004_excititor_iv.md`. Do not edit this file; update the canonical sprint instead.

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