Compare commits
47 Commits
feature/do
...
69651212ec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69651212ec | ||
|
|
53889d85e7 | ||
|
|
0de92144d2 | ||
|
|
9bd6a73926 | ||
|
|
4042fc2184 | ||
|
|
dd0067ea0b | ||
|
|
f6c22854a4 | ||
|
|
05597616d6 | ||
|
|
a6f1406509 | ||
|
|
0a8f8c14af | ||
|
|
7efee7dd41 | ||
|
|
952ba77924 | ||
|
|
23e463e346 | ||
|
|
849a70f9d1 | ||
|
|
868f8e0bb6 | ||
|
|
84c42ca2d8 | ||
|
|
efd6850c38 | ||
|
|
2b892ad1b2 | ||
|
|
e16d2b5224 | ||
|
|
5e514532df | ||
|
|
2141196496 | ||
|
|
bca02ec295 | ||
|
|
8cabdce3b6 | ||
|
|
6145d89468 | ||
|
|
ee317d3f61 | ||
|
|
4cc8bdb460 | ||
|
|
95ff83e0f0 | ||
|
|
3954615e81 | ||
|
|
8948b1a3e2 | ||
|
|
5cfcf0723a | ||
|
|
ba733b9f69 | ||
|
|
79d562ea5d | ||
|
|
a7cd10020a | ||
|
|
b978ae399f | ||
|
|
570746b7d9 | ||
|
|
8318b26370 | ||
|
|
1f76650b7e | ||
|
|
37304cf819 | ||
|
|
6beb9d7c4e | ||
|
|
be8c623e04 | ||
|
|
dd4bb50076 | ||
|
|
bf6ab6ba6f | ||
|
|
02849cc955 | ||
|
|
2eaf0f699b | ||
|
|
6c1177a6ce | ||
|
|
582a88e8f8 | ||
|
|
f0662dd45f |
@@ -1,8 +1,26 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"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(wc:*)",
|
||||||
"Bash(sort:*)"
|
"Bash(find:*)",
|
||||||
|
"WebFetch(domain:docs.gradle.org)",
|
||||||
|
"WebSearch",
|
||||||
|
"Bash(dotnet msbuild:*)",
|
||||||
|
"Bash(test:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
aoc-guard:
|
aoc-guard:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
|
DOTNET_VERSION: '10.0.100'
|
||||||
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
|
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
if: github.event_name != 'schedule'
|
if: github.event_name != 'schedule'
|
||||||
env:
|
env:
|
||||||
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
|
DOTNET_VERSION: '10.0.100'
|
||||||
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
|
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
|
||||||
AOC_VERIFY_SINCE: ${{ github.event.pull_request.base.sha || 'HEAD~1' }}
|
AOC_VERIFY_SINCE: ${{ github.event.pull_request.base.sha || 'HEAD~1' }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
|
DOTNET_VERSION: '10.0.100'
|
||||||
BUILD_CONFIGURATION: Release
|
BUILD_CONFIGURATION: Release
|
||||||
CI_CACHE_ROOT: /data/.cache/stella-ops/feedser
|
CI_CACHE_ROOT: /data/.cache/stella-ops/feedser
|
||||||
RUNNER_TOOL_CACHE: /toolcache
|
RUNNER_TOOL_CACHE: /toolcache
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "10.0.100-rc.2.25502.107"
|
dotnet-version: "10.0.100"
|
||||||
|
|
||||||
- name: Install syft (SBOM)
|
- name: Install syft (SBOM)
|
||||||
uses: anchore/sbom-action/download-syft@v0
|
uses: anchore/sbom-action/download-syft@v0
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "10.0.100-rc.2.25502.107"
|
dotnet-version: "10.0.100"
|
||||||
|
|
||||||
- name: Chaos smoke
|
- name: Chaos smoke
|
||||||
if: ${{ github.event.inputs.chaos == 'true' }}
|
if: ${{ github.event.inputs.chaos == 'true' }}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- name: Setup .NET 10 preview
|
- name: Setup .NET 10 preview
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '10.0.100-rc.2.25502.107'
|
dotnet-version: '10.0.100'
|
||||||
|
|
||||||
- name: Restore Concelier solution
|
- name: Restore Concelier solution
|
||||||
run: dotnet restore src/Concelier/StellaOps.Concelier.sln
|
run: dotnet restore src/Concelier/StellaOps.Concelier.sln
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: Setup .NET 10 (preview)
|
- name: Setup .NET 10 (preview)
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: 10.0.100-rc.2.25502.107
|
dotnet-version: 10.0.100
|
||||||
|
|
||||||
- name: Build CryptoPro plugin
|
- name: Build CryptoPro plugin
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
- name: Setup .NET SDK
|
- name: Setup .NET SDK
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: '10.0.100-rc.2.25502.107'
|
dotnet-version: '10.0.100'
|
||||||
|
|
||||||
- name: Link check
|
- name: Link check
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
export-ci:
|
export-ci:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
|
DOTNET_VERSION: '10.0.100'
|
||||||
MINIO_ACCESS_KEY: exportci
|
MINIO_ACCESS_KEY: exportci
|
||||||
MINIO_SECRET_KEY: exportci123
|
MINIO_SECRET_KEY: exportci123
|
||||||
BUCKET: export-ci
|
BUCKET: export-ci
|
||||||
|
|||||||
325
.gitea/workflows/findings-ledger-ci.yml
Normal file
325
.gitea/workflows/findings-ledger-ci.yml
Normal 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
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
lnm-backfill:
|
lnm-backfill:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
|
DOTNET_VERSION: '10.0.100'
|
||||||
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
|
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
vex-backfill:
|
vex-backfill:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
|
DOTNET_VERSION: '10.0.100'
|
||||||
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
|
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: 10.0.100-rc.2.25502.107
|
dotnet-version: 10.0.100
|
||||||
include-prerelease: true
|
include-prerelease: true
|
||||||
|
|
||||||
- name: Task Pack offline bundle fixtures
|
- name: Task Pack offline bundle fixtures
|
||||||
|
|||||||
44
.gitea/workflows/mock-dev-release.yml
Normal file
44
.gitea/workflows/mock-dev-release.yml
Normal 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
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Setup .NET 10 RC
|
- name: Setup .NET 10 RC
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: 10.0.100-rc.2.25502.107
|
dotnet-version: 10.0.100
|
||||||
include-prerelease: true
|
include-prerelease: true
|
||||||
|
|
||||||
- name: Cache NuGet packages
|
- name: Cache NuGet packages
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
- name: Setup .NET 10 RC
|
- name: Setup .NET 10 RC
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: 10.0.100-rc.2.25502.107
|
dotnet-version: 10.0.100
|
||||||
include-prerelease: true
|
include-prerelease: true
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
# .gitea/workflows/promote.yml
|
# .gitea/workflows/promote.yml
|
||||||
# Manual promotion workflow to copy staged artefacts to production
|
# Manual promotion workflow to copy staged artefacts to production
|
||||||
|
|
||||||
name: Promote Feedser (Manual)
|
name: Promote Feedser (Manual)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
include_docs:
|
include_docs:
|
||||||
description: 'Also promote the generated documentation bundle'
|
description: 'Also promote the generated documentation bundle'
|
||||||
required: false
|
required: false
|
||||||
default: 'true'
|
default: 'true'
|
||||||
type: boolean
|
type: boolean
|
||||||
tag:
|
tag:
|
||||||
description: 'Optional build identifier to record in the summary'
|
description: 'Optional build identifier to record in the summary'
|
||||||
required: false
|
required: false
|
||||||
default: 'latest'
|
default: 'latest'
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
promote:
|
promote:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
environment: production
|
environment: production
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -32,178 +32,178 @@ jobs:
|
|||||||
id: staging
|
id: staging
|
||||||
run: |
|
run: |
|
||||||
missing=()
|
missing=()
|
||||||
|
|
||||||
host="${{ secrets.STAGING_DEPLOYMENT_HOST }}"
|
host="${{ secrets.STAGING_DEPLOYMENT_HOST }}"
|
||||||
if [ -z "$host" ]; then host="${{ vars.STAGING_DEPLOYMENT_HOST }}"; fi
|
if [ -z "$host" ]; then host="${{ vars.STAGING_DEPLOYMENT_HOST }}"; fi
|
||||||
if [ -z "$host" ]; then host="${{ secrets.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 host="${{ vars.DEPLOYMENT_HOST }}"; fi
|
||||||
if [ -z "$host" ]; then missing+=("STAGING_DEPLOYMENT_HOST"); fi
|
if [ -z "$host" ]; then missing+=("STAGING_DEPLOYMENT_HOST"); fi
|
||||||
|
|
||||||
user="${{ secrets.STAGING_DEPLOYMENT_USERNAME }}"
|
user="${{ secrets.STAGING_DEPLOYMENT_USERNAME }}"
|
||||||
if [ -z "$user" ]; then user="${{ vars.STAGING_DEPLOYMENT_USERNAME }}"; fi
|
if [ -z "$user" ]; then user="${{ vars.STAGING_DEPLOYMENT_USERNAME }}"; fi
|
||||||
if [ -z "$user" ]; then user="${{ secrets.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 user="${{ vars.DEPLOYMENT_USERNAME }}"; fi
|
||||||
if [ -z "$user" ]; then missing+=("STAGING_DEPLOYMENT_USERNAME"); fi
|
if [ -z "$user" ]; then missing+=("STAGING_DEPLOYMENT_USERNAME"); fi
|
||||||
|
|
||||||
path="${{ secrets.STAGING_DEPLOYMENT_PATH }}"
|
path="${{ secrets.STAGING_DEPLOYMENT_PATH }}"
|
||||||
if [ -z "$path" ]; then path="${{ vars.STAGING_DEPLOYMENT_PATH }}"; fi
|
if [ -z "$path" ]; then path="${{ vars.STAGING_DEPLOYMENT_PATH }}"; fi
|
||||||
if [ -z "$path" ]; then missing+=("STAGING_DEPLOYMENT_PATH")
|
if [ -z "$path" ]; then missing+=("STAGING_DEPLOYMENT_PATH")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docs_path="${{ secrets.STAGING_DOCS_PATH }}"
|
docs_path="${{ secrets.STAGING_DOCS_PATH }}"
|
||||||
if [ -z "$docs_path" ]; then docs_path="${{ vars.STAGING_DOCS_PATH }}"; fi
|
if [ -z "$docs_path" ]; then docs_path="${{ vars.STAGING_DOCS_PATH }}"; fi
|
||||||
|
|
||||||
key="${{ secrets.STAGING_DEPLOYMENT_KEY }}"
|
key="${{ secrets.STAGING_DEPLOYMENT_KEY }}"
|
||||||
if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
|
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.STAGING_DEPLOYMENT_KEY }}"; fi
|
||||||
if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
|
if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
|
||||||
if [ -z "$key" ]; then missing+=("STAGING_DEPLOYMENT_KEY"); fi
|
if [ -z "$key" ]; then missing+=("STAGING_DEPLOYMENT_KEY"); fi
|
||||||
|
|
||||||
if [ ${#missing[@]} -gt 0 ]; then
|
if [ ${#missing[@]} -gt 0 ]; then
|
||||||
echo "❌ Missing staging configuration: ${missing[*]}"
|
echo "❌ Missing staging configuration: ${missing[*]}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
key_file="$RUNNER_TEMP/staging_key"
|
key_file="$RUNNER_TEMP/staging_key"
|
||||||
printf '%s\n' "$key" > "$key_file"
|
printf '%s\n' "$key" > "$key_file"
|
||||||
chmod 600 "$key_file"
|
chmod 600 "$key_file"
|
||||||
|
|
||||||
echo "host=$host" >> $GITHUB_OUTPUT
|
echo "host=$host" >> $GITHUB_OUTPUT
|
||||||
echo "user=$user" >> $GITHUB_OUTPUT
|
echo "user=$user" >> $GITHUB_OUTPUT
|
||||||
echo "path=$path" >> $GITHUB_OUTPUT
|
echo "path=$path" >> $GITHUB_OUTPUT
|
||||||
echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
|
echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
|
||||||
echo "key-file=$key_file" >> $GITHUB_OUTPUT
|
echo "key-file=$key_file" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Resolve production credentials
|
- name: Resolve production credentials
|
||||||
id: production
|
id: production
|
||||||
run: |
|
run: |
|
||||||
missing=()
|
missing=()
|
||||||
|
|
||||||
host="${{ secrets.PRODUCTION_DEPLOYMENT_HOST }}"
|
host="${{ secrets.PRODUCTION_DEPLOYMENT_HOST }}"
|
||||||
if [ -z "$host" ]; then host="${{ vars.PRODUCTION_DEPLOYMENT_HOST }}"; fi
|
if [ -z "$host" ]; then host="${{ vars.PRODUCTION_DEPLOYMENT_HOST }}"; fi
|
||||||
if [ -z "$host" ]; then host="${{ secrets.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 host="${{ vars.DEPLOYMENT_HOST }}"; fi
|
||||||
if [ -z "$host" ]; then missing+=("PRODUCTION_DEPLOYMENT_HOST"); fi
|
if [ -z "$host" ]; then missing+=("PRODUCTION_DEPLOYMENT_HOST"); fi
|
||||||
|
|
||||||
user="${{ secrets.PRODUCTION_DEPLOYMENT_USERNAME }}"
|
user="${{ secrets.PRODUCTION_DEPLOYMENT_USERNAME }}"
|
||||||
if [ -z "$user" ]; then user="${{ vars.PRODUCTION_DEPLOYMENT_USERNAME }}"; fi
|
if [ -z "$user" ]; then user="${{ vars.PRODUCTION_DEPLOYMENT_USERNAME }}"; fi
|
||||||
if [ -z "$user" ]; then user="${{ secrets.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 user="${{ vars.DEPLOYMENT_USERNAME }}"; fi
|
||||||
if [ -z "$user" ]; then missing+=("PRODUCTION_DEPLOYMENT_USERNAME"); fi
|
if [ -z "$user" ]; then missing+=("PRODUCTION_DEPLOYMENT_USERNAME"); fi
|
||||||
|
|
||||||
path="${{ secrets.PRODUCTION_DEPLOYMENT_PATH }}"
|
path="${{ secrets.PRODUCTION_DEPLOYMENT_PATH }}"
|
||||||
if [ -z "$path" ]; then path="${{ vars.PRODUCTION_DEPLOYMENT_PATH }}"; fi
|
if [ -z "$path" ]; then path="${{ vars.PRODUCTION_DEPLOYMENT_PATH }}"; fi
|
||||||
if [ -z "$path" ]; then missing+=("PRODUCTION_DEPLOYMENT_PATH")
|
if [ -z "$path" ]; then missing+=("PRODUCTION_DEPLOYMENT_PATH")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docs_path="${{ secrets.PRODUCTION_DOCS_PATH }}"
|
docs_path="${{ secrets.PRODUCTION_DOCS_PATH }}"
|
||||||
if [ -z "$docs_path" ]; then docs_path="${{ vars.PRODUCTION_DOCS_PATH }}"; fi
|
if [ -z "$docs_path" ]; then docs_path="${{ vars.PRODUCTION_DOCS_PATH }}"; fi
|
||||||
|
|
||||||
key="${{ secrets.PRODUCTION_DEPLOYMENT_KEY }}"
|
key="${{ secrets.PRODUCTION_DEPLOYMENT_KEY }}"
|
||||||
if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi
|
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.PRODUCTION_DEPLOYMENT_KEY }}"; fi
|
||||||
if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
|
if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi
|
||||||
if [ -z "$key" ]; then missing+=("PRODUCTION_DEPLOYMENT_KEY"); fi
|
if [ -z "$key" ]; then missing+=("PRODUCTION_DEPLOYMENT_KEY"); fi
|
||||||
|
|
||||||
if [ ${#missing[@]} -gt 0 ]; then
|
if [ ${#missing[@]} -gt 0 ]; then
|
||||||
echo "❌ Missing production configuration: ${missing[*]}"
|
echo "❌ Missing production configuration: ${missing[*]}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
key_file="$RUNNER_TEMP/production_key"
|
key_file="$RUNNER_TEMP/production_key"
|
||||||
printf '%s\n' "$key" > "$key_file"
|
printf '%s\n' "$key" > "$key_file"
|
||||||
chmod 600 "$key_file"
|
chmod 600 "$key_file"
|
||||||
|
|
||||||
echo "host=$host" >> $GITHUB_OUTPUT
|
echo "host=$host" >> $GITHUB_OUTPUT
|
||||||
echo "user=$user" >> $GITHUB_OUTPUT
|
echo "user=$user" >> $GITHUB_OUTPUT
|
||||||
echo "path=$path" >> $GITHUB_OUTPUT
|
echo "path=$path" >> $GITHUB_OUTPUT
|
||||||
echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
|
echo "docs-path=$docs_path" >> $GITHUB_OUTPUT
|
||||||
echo "key-file=$key_file" >> $GITHUB_OUTPUT
|
echo "key-file=$key_file" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Install rsync
|
- name: Install rsync
|
||||||
run: |
|
run: |
|
||||||
if command -v rsync >/dev/null 2>&1; then
|
if command -v rsync >/dev/null 2>&1; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
CACHE_DIR="${CI_CACHE_ROOT:-/tmp}/apt"
|
CACHE_DIR="${CI_CACHE_ROOT:-/tmp}/apt"
|
||||||
mkdir -p "$CACHE_DIR"
|
mkdir -p "$CACHE_DIR"
|
||||||
KEY="rsync-$(lsb_release -rs 2>/dev/null || echo unknown)"
|
KEY="rsync-$(lsb_release -rs 2>/dev/null || echo unknown)"
|
||||||
DEB_DIR="$CACHE_DIR/$KEY"
|
DEB_DIR="$CACHE_DIR/$KEY"
|
||||||
mkdir -p "$DEB_DIR"
|
mkdir -p "$DEB_DIR"
|
||||||
if ls "$DEB_DIR"/rsync*.deb >/dev/null 2>&1; then
|
if ls "$DEB_DIR"/rsync*.deb >/dev/null 2>&1; then
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb
|
apt-get install -y --no-install-recommends "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb
|
||||||
else
|
else
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get download rsync libpopt0
|
apt-get download rsync libpopt0
|
||||||
mv rsync*.deb libpopt0*.deb "$DEB_DIR"/
|
mv rsync*.deb libpopt0*.deb "$DEB_DIR"/
|
||||||
dpkg -i "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb || apt-get install -f -y
|
dpkg -i "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb || apt-get install -f -y
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Fetch staging artefacts
|
- name: Fetch staging artefacts
|
||||||
id: fetch
|
id: fetch
|
||||||
run: |
|
run: |
|
||||||
staging_root="${{ runner.temp }}/staging"
|
staging_root="${{ runner.temp }}/staging"
|
||||||
mkdir -p "$staging_root/service" "$staging_root/docs"
|
mkdir -p "$staging_root/service" "$staging_root/docs"
|
||||||
|
|
||||||
echo "📥 Copying service bundle from staging"
|
echo "📥 Copying service bundle from staging"
|
||||||
rsync -az --delete \
|
rsync -az --delete \
|
||||||
-e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
-e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
||||||
"${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs.path }}/" \
|
"${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs.path }}/" \
|
||||||
"$staging_root/service/"
|
"$staging_root/service/"
|
||||||
|
|
||||||
if [ "${{ github.event.inputs.include_docs }}" = "true" ] && [ -n "${{ steps.staging.outputs['docs-path'] }}" ]; then
|
if [ "${{ github.event.inputs.include_docs }}" = "true" ] && [ -n "${{ steps.staging.outputs['docs-path'] }}" ]; then
|
||||||
echo "📥 Copying documentation bundle from staging"
|
echo "📥 Copying documentation bundle from staging"
|
||||||
rsync -az --delete \
|
rsync -az --delete \
|
||||||
-e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
-e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
||||||
"${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs['docs-path'] }}/" \
|
"${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs['docs-path'] }}/" \
|
||||||
"$staging_root/docs/"
|
"$staging_root/docs/"
|
||||||
else
|
else
|
||||||
echo "ℹ️ Documentation promotion skipped"
|
echo "ℹ️ Documentation promotion skipped"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "service-dir=$staging_root/service" >> $GITHUB_OUTPUT
|
echo "service-dir=$staging_root/service" >> $GITHUB_OUTPUT
|
||||||
echo "docs-dir=$staging_root/docs" >> $GITHUB_OUTPUT
|
echo "docs-dir=$staging_root/docs" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Backup production service content
|
- name: Backup production service content
|
||||||
run: |
|
run: |
|
||||||
ssh -o StrictHostKeyChecking=no -i "${{ steps.production.outputs['key-file'] }}" \
|
ssh -o StrictHostKeyChecking=no -i "${{ steps.production.outputs['key-file'] }}" \
|
||||||
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}" \
|
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}" \
|
||||||
"set -e; TARGET='${{ steps.production.outputs.path }}'; \
|
"set -e; TARGET='${{ steps.production.outputs.path }}'; \
|
||||||
if [ -d \"$TARGET\" ]; then \
|
if [ -d \"$TARGET\" ]; then \
|
||||||
parent=\$(dirname \"$TARGET\"); \
|
parent=\$(dirname \"$TARGET\"); \
|
||||||
base=\$(basename \"$TARGET\"); \
|
base=\$(basename \"$TARGET\"); \
|
||||||
backup=\"\$parent/\${base}.backup.\$(date +%Y%m%d_%H%M%S)\"; \
|
backup=\"\$parent/\${base}.backup.\$(date +%Y%m%d_%H%M%S)\"; \
|
||||||
mkdir -p \"\$backup\"; \
|
mkdir -p \"\$backup\"; \
|
||||||
rsync -a --delete \"$TARGET/\" \"\$backup/\"; \
|
rsync -a --delete \"$TARGET/\" \"\$backup/\"; \
|
||||||
ls -dt \"\$parent/\${base}.backup.*\" 2>/dev/null | tail -n +6 | xargs rm -rf || true; \
|
ls -dt \"\$parent/\${base}.backup.*\" 2>/dev/null | tail -n +6 | xargs rm -rf || true; \
|
||||||
echo 'Backup created at ' \"\$backup\"; \
|
echo 'Backup created at ' \"\$backup\"; \
|
||||||
else \
|
else \
|
||||||
echo 'Production service path missing; skipping backup'; \
|
echo 'Production service path missing; skipping backup'; \
|
||||||
fi"
|
fi"
|
||||||
|
|
||||||
- name: Publish service to production
|
- name: Publish service to production
|
||||||
run: |
|
run: |
|
||||||
rsync -az --delete \
|
rsync -az --delete \
|
||||||
-e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
-e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
||||||
"${{ steps.fetch.outputs['service-dir'] }}/" \
|
"${{ steps.fetch.outputs['service-dir'] }}/" \
|
||||||
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs.path }}/"
|
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs.path }}/"
|
||||||
|
|
||||||
- name: Promote documentation bundle
|
- name: Promote documentation bundle
|
||||||
if: github.event.inputs.include_docs == 'true' && steps.production.outputs['docs-path'] != ''
|
if: github.event.inputs.include_docs == 'true' && steps.production.outputs['docs-path'] != ''
|
||||||
run: |
|
run: |
|
||||||
rsync -az --delete \
|
rsync -az --delete \
|
||||||
-e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
-e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \
|
||||||
"${{ steps.fetch.outputs['docs-dir'] }}/" \
|
"${{ steps.fetch.outputs['docs-dir'] }}/" \
|
||||||
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs['docs-path'] }}/"
|
"${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs['docs-path'] }}/"
|
||||||
|
|
||||||
- name: Promotion summary
|
- name: Promotion summary
|
||||||
run: |
|
run: |
|
||||||
echo "✅ Promotion completed"
|
echo "✅ Promotion completed"
|
||||||
echo " Tag: ${{ github.event.inputs.tag }}"
|
echo " Tag: ${{ github.event.inputs.tag }}"
|
||||||
echo " Service: ${{ steps.staging.outputs.host }} → ${{ steps.production.outputs.host }}"
|
echo " Service: ${{ steps.staging.outputs.host }} → ${{ steps.production.outputs.host }}"
|
||||||
if [ "${{ github.event.inputs.include_docs }}" = "true" ]; then
|
if [ "${{ github.event.inputs.include_docs }}" = "true" ]; then
|
||||||
echo " Docs: included"
|
echo " Docs: included"
|
||||||
else
|
else
|
||||||
echo " Docs: skipped"
|
echo " Docs: skipped"
|
||||||
fi
|
fi
|
||||||
|
|||||||
19
.gitea/workflows/release-manifest-verify.yml
Normal file
19
.gitea/workflows/release-manifest-verify.yml
Normal 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
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
build-release:
|
build-release:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
|
DOTNET_VERSION: '10.0.100'
|
||||||
REGISTRY: registry.stella-ops.org
|
REGISTRY: registry.stella-ops.org
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "10.0.100-rc.2.25502.107"
|
dotnet-version: "10.0.100"
|
||||||
|
|
||||||
- name: Install syft (SBOM)
|
- name: Install syft (SBOM)
|
||||||
uses: anchore/sbom-action/download-syft@v0
|
uses: anchore/sbom-action/download-syft@v0
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "10.0.100-rc.2.25502.107"
|
dotnet-version: "10.0.100"
|
||||||
|
|
||||||
- name: Run determinism harness
|
- name: Run determinism harness
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
- name: Setup .NET 10 RC
|
- name: Setup .NET 10 RC
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: 10.0.100-rc.2.25502.107
|
dotnet-version: 10.0.100
|
||||||
include-prerelease: true
|
include-prerelease: true
|
||||||
|
|
||||||
- name: Cache NuGet packages
|
- name: Cache NuGet packages
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
- name: Setup .NET 10 RC
|
- name: Setup .NET 10 RC
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: 10.0.100-rc.2.25502.107
|
dotnet-version: 10.0.100
|
||||||
include-prerelease: true
|
include-prerelease: true
|
||||||
|
|
||||||
- name: Cache NuGet packages
|
- name: Cache NuGet packages
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<StellaOpsEnableCryptoPro Condition="'$(StellaOpsEnableCryptoPro)' == ''">false</StellaOpsEnableCryptoPro>
|
<StellaOpsEnableCryptoPro Condition="'$(StellaOpsEnableCryptoPro)' == ''">false</StellaOpsEnableCryptoPro>
|
||||||
|
<NoWarn>$(NoWarn);NU1608</NoWarn>
|
||||||
|
<WarningsNotAsErrors>$(WarningsNotAsErrors);NU1608</WarningsNotAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(StellaOpsEnableCryptoPro)' == 'true'">
|
<PropertyGroup Condition="'$(StellaOpsEnableCryptoPro)' == 'true'">
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
/nowarn:CA2022
|
/nowarn:CA2022
|
||||||
|
/p:DisableWorkloadResolver=true
|
||||||
|
/p:RestoreAdditionalProjectFallbackFolders=
|
||||||
|
/p:RestoreFallbackFolders=
|
||||||
|
|||||||
17
NuGet.config
17
NuGet.config
@@ -1,19 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<packageSources>
|
<packageSources>
|
||||||
<clear />
|
<add key="local" value="./local-nugets" />
|
||||||
<add key="local" value="local-nugets" />
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
<add key="ablera-mirror" value="https://mirrors.ablera.dev/nuget/nuget-mirror/v3/index.json" />
|
|
||||||
</packageSources>
|
</packageSources>
|
||||||
<config>
|
<config>
|
||||||
<add key="globalPackagesFolder" value="local-nugets/packages" />
|
<add key="globalPackagesFolder" value="/home/vlindos/.nuget/packages" />
|
||||||
</config>
|
</config>
|
||||||
<packageSourceMapping>
|
<fallbackPackageFolders>
|
||||||
<packageSource key="local">
|
</fallbackPackageFolders>
|
||||||
<package pattern="*" />
|
|
||||||
</packageSource>
|
|
||||||
<packageSource key="ablera-mirror">
|
|
||||||
<package pattern="*" />
|
|
||||||
</packageSource>
|
|
||||||
</packageSourceMapping>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -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.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.yaml` – optional OpenTelemetry collector overlay (mutual TLS, OTLP pipelines).
|
||||||
- `compose/docker-compose.telemetry-storage.yaml` – optional Prometheus/Tempo/Loki stack for observability backends.
|
- `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).
|
- `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.
|
- `tools/validate-profiles.sh` – helper that runs `docker compose config` and `helm lint/template` for every profile.
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,21 @@ These Compose bundles ship the minimum services required to exercise the scanner
|
|||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
| Path | Purpose |
|
| Path | Purpose |
|
||||||
| ---- | ------- |
|
| ---- | ------- |
|
||||||
| `docker-compose.dev.yaml` | Edge/nightly stack tuned for laptops and iterative work. |
|
| `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.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.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.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.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.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.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. |
|
| `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
|
## 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`).
|
1. Import the new manifest into `deploy/releases/` (see `deploy/README.md`).
|
||||||
2. Update image digests in the relevant Compose file(s).
|
2. Update image digests in the relevant Compose file(s).
|
||||||
3. Re-run `docker compose config` to confirm the bundle is deterministic.
|
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.
|
||||||
|
|||||||
191
deploy/compose/docker-compose.cas.yaml
Normal file
191
deploy/compose/docker-compose.cas.yaml
Normal 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
|
||||||
26
deploy/compose/docker-compose.gpu.yaml
Normal file
26
deploy/compose/docker-compose.gpu.yaml
Normal 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
|
||||||
74
deploy/compose/docker-compose.mock.yaml
Normal file
74
deploy/compose/docker-compose.mock.yaml
Normal 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
118
deploy/compose/env/cas.env.example
vendored
Normal 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
12
deploy/compose/env/mock.env.example
vendored
Normal 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
|
||||||
28
deploy/compose/scripts/backup.sh
Normal file
28
deploy/compose/scripts/backup.sh
Normal 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"
|
||||||
25
deploy/compose/scripts/quickstart.sh
Normal file
25
deploy/compose/scripts/quickstart.sh
Normal 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"
|
||||||
15
deploy/compose/scripts/reset.sh
Normal file
15
deploy/compose/scripts/reset.sh
Normal 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."
|
||||||
18
deploy/downloads/manifest.json
Normal file
18
deploy/downloads/manifest.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
64
deploy/helm/stellaops/INSTALL.md
Normal file
64
deploy/helm/stellaops/INSTALL.md
Normal 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.
|
||||||
16
deploy/helm/stellaops/README-mock.md
Normal file
16
deploy/helm/stellaops/README-mock.md
Normal 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`.
|
||||||
@@ -19,18 +19,30 @@ spec:
|
|||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
{{- include "stellaops.selectorLabels" (dict "root" $root "name" $name "svc" $svc) | nindent 6 }}
|
{{- include "stellaops.selectorLabels" (dict "root" $root "name" $name "svc" $svc) | nindent 6 }}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
{{- include "stellaops.selectorLabels" (dict "root" $root "name" $name "svc" $svc) | nindent 8 }}
|
{{- include "stellaops.selectorLabels" (dict "root" $root "name" $name "svc" $svc) | nindent 8 }}
|
||||||
annotations:
|
{{- if $svc.podAnnotations }}
|
||||||
stellaops.release/version: {{ $root.Values.global.release.version | quote }}
|
annotations:
|
||||||
stellaops.release/channel: {{ $root.Values.global.release.channel | quote }}
|
{{ toYaml $svc.podAnnotations | nindent 8 }}
|
||||||
spec:
|
{{- end }}
|
||||||
containers:
|
annotations:
|
||||||
- name: {{ $name }}
|
stellaops.release/version: {{ $root.Values.global.release.version | quote }}
|
||||||
image: {{ $svc.image | quote }}
|
stellaops.release/channel: {{ $root.Values.global.release.channel | quote }}
|
||||||
imagePullPolicy: {{ default $root.Values.global.image.pullPolicy $svc.imagePullPolicy }}
|
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 }}
|
{{- if $svc.command }}
|
||||||
command:
|
command:
|
||||||
{{- range $cmd := $svc.command }}
|
{{- range $cmd := $svc.command }}
|
||||||
@@ -81,18 +93,35 @@ spec:
|
|||||||
containerPort: {{ default (index $svcService "port") (index $svcService "targetPort") }}
|
containerPort: {{ default (index $svcService "port") (index $svcService "targetPort") }}
|
||||||
protocol: {{ default "TCP" (index $svcService "protocol") }}
|
protocol: {{ default "TCP" (index $svcService "protocol") }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if $svc.resources }}
|
{{- if $svc.resources }}
|
||||||
resources:
|
resources:
|
||||||
{{ toYaml $svc.resources | nindent 12 }}
|
{{ toYaml $svc.resources | nindent 12 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if $svc.livenessProbe }}
|
{{- if $svc.securityContext }}
|
||||||
livenessProbe:
|
securityContext:
|
||||||
{{ toYaml $svc.livenessProbe | nindent 12 }}
|
{{ toYaml $svc.securityContext | nindent 12 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if $svc.readinessProbe }}
|
{{- if $svc.securityContext }}
|
||||||
readinessProbe:
|
securityContext:
|
||||||
{{ toYaml $svc.readinessProbe | nindent 12 }}
|
{{ toYaml $svc.securityContext | nindent 12 }}
|
||||||
{{- end }}
|
{{- 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 }}
|
{{- if or $svc.volumeMounts $configMounts }}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
{{- if $svc.volumeMounts }}
|
{{- if $svc.volumeMounts }}
|
||||||
@@ -148,13 +177,32 @@ spec:
|
|||||||
affinity:
|
affinity:
|
||||||
{{ toYaml $svc.affinity | nindent 8 }}
|
{{ toYaml $svc.affinity | nindent 8 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if $svc.tolerations }}
|
{{- if $svc.tolerations }}
|
||||||
tolerations:
|
tolerations:
|
||||||
{{ toYaml $svc.tolerations | nindent 8 }}
|
{{ toYaml $svc.tolerations | nindent 8 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
---
|
{{- if $svc.pdb }}
|
||||||
{{- if $svc.service }}
|
---
|
||||||
apiVersion: v1
|
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
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ include "stellaops.fullname" (dict "root" $root "name" $name) }}
|
name: {{ include "stellaops.fullname" (dict "root" $root "name" $name) }}
|
||||||
|
|||||||
28
deploy/helm/stellaops/templates/externalsecrets.yaml
Normal file
28
deploy/helm/stellaops/templates/externalsecrets.yaml
Normal 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 }}
|
||||||
39
deploy/helm/stellaops/templates/hpa.yaml
Normal file
39
deploy/helm/stellaops/templates/hpa.yaml
Normal 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 }}
|
||||||
32
deploy/helm/stellaops/templates/ingress.yaml
Normal file
32
deploy/helm/stellaops/templates/ingress.yaml
Normal 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 }}
|
||||||
50
deploy/helm/stellaops/templates/migrations.yaml
Normal file
50
deploy/helm/stellaops/templates/migrations.yaml
Normal 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 }}
|
||||||
45
deploy/helm/stellaops/templates/networkpolicy.yaml
Normal file
45
deploy/helm/stellaops/templates/networkpolicy.yaml
Normal 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 }}
|
||||||
22
deploy/helm/stellaops/templates/orchestrator-mock.yaml
Normal file
22
deploy/helm/stellaops/templates/orchestrator-mock.yaml
Normal 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 }}
|
||||||
44
deploy/helm/stellaops/templates/packs-mock.yaml
Normal file
44
deploy/helm/stellaops/templates/packs-mock.yaml
Normal 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 }}
|
||||||
22
deploy/helm/stellaops/templates/policy-mock.yaml
Normal file
22
deploy/helm/stellaops/templates/policy-mock.yaml
Normal 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 }}
|
||||||
22
deploy/helm/stellaops/templates/vex-mock.yaml
Normal file
22
deploy/helm/stellaops/templates/vex-mock.yaml
Normal 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 }}
|
||||||
44
deploy/helm/stellaops/templates/vuln-mock.yaml
Normal file
44
deploy/helm/stellaops/templates/vuln-mock.yaml
Normal 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 }}
|
||||||
@@ -9,6 +9,45 @@ global:
|
|||||||
labels:
|
labels:
|
||||||
stellaops.io/channel: airgap
|
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:
|
configMaps:
|
||||||
notify-config:
|
notify-config:
|
||||||
data:
|
data:
|
||||||
|
|||||||
18
deploy/helm/stellaops/values-mock.yaml
Normal file
18
deploy/helm/stellaops/values-mock.yaml
Normal 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
|
||||||
@@ -10,6 +10,66 @@ global:
|
|||||||
stellaops.io/channel: stable
|
stellaops.io/channel: stable
|
||||||
stellaops.io/profile: prod
|
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:
|
configMaps:
|
||||||
notify-config:
|
notify-config:
|
||||||
data:
|
data:
|
||||||
|
|||||||
@@ -8,6 +8,45 @@ global:
|
|||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
labels: {}
|
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
|
# Surface.Env configuration for Scanner/Zastava components
|
||||||
# See docs/modules/scanner/design/surface-env.md for details
|
# See docs/modules/scanner/design/surface-env.md for details
|
||||||
surface:
|
surface:
|
||||||
@@ -224,3 +263,22 @@ services:
|
|||||||
volumeClaims:
|
volumeClaims:
|
||||||
- name: advisory-ai-data
|
- name: advisory-ai-data
|
||||||
claimName: stellaops-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
|
||||||
|
|||||||
49
deploy/releases/2025.09-mock-dev.yaml
Normal file
49
deploy/releases/2025.09-mock-dev.yaml
Normal 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
|
||||||
@@ -1,8 +1,25 @@
|
|||||||
openapi: 3.1.0
|
openapi: 3.1.0
|
||||||
info:
|
info:
|
||||||
title: StellaOps Concelier – Link-Not-Merge Policy APIs
|
title: StellaOps Concelier – Link-Not-Merge Policy APIs
|
||||||
version: "0.1.0"
|
version: "1.0.0"
|
||||||
description: Fact-only advisory/linkset retrieval for Policy Engine consumers.
|
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:
|
servers:
|
||||||
- url: /
|
- url: /
|
||||||
description: Relative base path (API Gateway rewrites in production).
|
description: Relative base path (API Gateway rewrites in production).
|
||||||
@@ -44,6 +61,65 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/PagedLinksets'
|
$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}:
|
/v1/lnm/linksets/{advisoryId}:
|
||||||
get:
|
get:
|
||||||
summary: Get linkset by advisory ID
|
summary: Get linkset by advisory ID
|
||||||
@@ -275,3 +351,63 @@ components:
|
|||||||
event: { type: string }
|
event: { type: string }
|
||||||
at: { type: string, format: date-time }
|
at: { type: string, format: date-time }
|
||||||
evidenceHash: { type: string }
|
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
|
||||||
|
|||||||
20
docs/api/console/exception-schema.md
Normal file
20
docs/api/console/exception-schema.md
Normal 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.
|
||||||
14
docs/api/console/samples/console-export-events.ndjson
Normal file
14
docs/api/console/samples/console-export-events.ndjson
Normal 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"}
|
||||||
36
docs/api/console/samples/console-export-manifest.json
Normal file
36
docs/api/console/samples/console-export-manifest.json
Normal 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..."
|
||||||
|
}
|
||||||
|
}
|
||||||
16
docs/api/console/samples/console-export-request.json
Normal file
16
docs/api/console/samples/console-export-request.json
Normal 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"
|
||||||
|
}
|
||||||
24
docs/api/console/samples/console-export-status.json
Normal file
24
docs/api/console/samples/console-export-status.json
Normal 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": []
|
||||||
|
}
|
||||||
37
docs/api/console/samples/exception-schema-sample.json
Normal file
37
docs/api/console/samples/exception-schema-sample.json
Normal 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..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -309,3 +309,85 @@ data: {
|
|||||||
- `docs/api/console/samples/vex-statement-sse.ndjson` – contains 5 chronological SSE events for screenshot reproduction.
|
- `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.
|
> 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`.
|
||||||
|
|||||||
79
docs/api/gateway/export-center.md
Normal file
79
docs/api/gateway/export-center.md
Normal 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.
|
||||||
42
docs/api/graph/overlay-schema.md
Normal file
42
docs/api/graph/overlay-schema.md
Normal 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: 0–12; 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.
|
||||||
75
docs/api/graph/samples/overlay-sample.json
Normal file
75
docs/api/graph/samples/overlay-sample.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
66
docs/api/signals/reachability-contract.md
Normal file
66
docs/api/signals/reachability-contract.md
Normal 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.
|
||||||
23
docs/api/signals/samples/callgraph-sample.json
Normal file
23
docs/api/signals/samples/callgraph-sample.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
26
docs/api/signals/samples/facts-sample.json
Normal file
26
docs/api/signals/samples/facts-sample.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
886
docs/api/taskrunner-openapi.yaml
Normal file
886
docs/api/taskrunner-openapi.yaml
Normal 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
|
||||||
11
docs/api/vex-consensus-sample.ndjson
Normal file
11
docs/api/vex-consensus-sample.ndjson
Normal 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
25
docs/api/vex-consensus.md
Normal 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.
|
||||||
1050
docs/api/vexlens-openapi.yaml
Normal file
1050
docs/api/vexlens-openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
182
docs/assets/vuln-explorer/console/CAPTURES.md
Normal file
182
docs/assets/vuln-explorer/console/CAPTURES.md
Normal 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
|
||||||
|

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

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

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

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

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

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

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

|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
369
docs/contracts/authority-crypto-provider.md
Normal file
369
docs/contracts/authority-crypto-provider.md
Normal 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 |
|
||||||
157
docs/contracts/cas-infrastructure.md
Normal file
157
docs/contracts/cas-infrastructure.md
Normal 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 |
|
||||||
425
docs/contracts/sealed-install-enforcement.md
Normal file
425
docs/contracts/sealed-install-enforcement.md
Normal 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 |
|
||||||
8
docs/db/reports/assets/vuln-parity-20251211/README.md
Normal file
8
docs/db/reports/assets/vuln-parity-20251211/README.md
Normal 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.
|
||||||
@@ -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
|
||||||
19
docs/db/reports/assets/vuln-parity-20251211/sample-sbom.json
Normal file
19
docs/db/reports/assets/vuln-parity-20251211/sample-sbom.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
110
docs/db/reports/assets/vuln-parity-20251211/sbom-snapshot.json
Normal file
110
docs/db/reports/assets/vuln-parity-20251211/sbom-snapshot.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
8
docs/db/reports/assets/vuln-parity-20251211/sbom.json
Normal file
8
docs/db/reports/assets/vuln-parity-20251211/sbom.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"bomFormat": "CycloneDX",
|
||||||
|
"specVersion": "1.5",
|
||||||
|
"version": 1,
|
||||||
|
"components": [
|
||||||
|
{"type": "container", "name": "example", "version": "1.0.0"}
|
||||||
|
]
|
||||||
|
}
|
||||||
28
docs/db/reports/mongo-removal-decisions-20251206.md
Normal file
28
docs/db/reports/mongo-removal-decisions-20251206.md
Normal 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.2–T7.1.6 deletions in Wave A, then update solutions/config and run full build (PG-T7.1.7–T7.1.10).
|
||||||
57
docs/db/reports/scheduler-graphjobs-postgres-plan.md
Normal file
57
docs/db/reports/scheduler-graphjobs-postgres-plan.md
Normal 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
|
||||||
23
docs/db/reports/scheduler-mongo-request-20251208.md
Normal file
23
docs/db/reports/scheduler-mongo-request-20251208.md
Normal 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.9–T2.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`.
|
||||||
|
|
||||||
44
docs/db/reports/scheduler-parity-20251214.md
Normal file
44
docs/db/reports/scheduler-parity-20251214.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Scheduler Parity Report · 2025-12-14
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
- Backfill and parity verification for Scheduler (Sprint 3402 · PG-T2.9–T2.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.9–T2.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.
|
||||||
|
|
||||||
50
docs/db/reports/vuln-parity-20251211.md
Normal file
50
docs/db/reports/vuln-parity-20251211.md
Normal 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.3–5b.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.
|
||||||
|
|
||||||
24
docs/db/reports/vuln-parity-sbom-sample-20251209.md
Normal file
24
docs/db/reports/vuln-parity-sbom-sample-20251209.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# SBOM & Advisory Sample List · Vulnerability Parity · 2025-12-09
|
||||||
|
|
||||||
|
Use this list for PG-T5b.3–5b.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.
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- Adjust the relative path when copying this template into a repo -->
|
<!-- Adjust the relative path when copying this template into a repo -->
|
||||||
<ProjectReference Include="..\..\..\..\src\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
|
<ProjectReference Include="..\..\..\..\src\StellaOps.Excititor.Connectors.Abstractions\StellaOps.Excititor.Connectors.Abstractions.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
367
docs/implplan/DEPENDENCY_DAG.md
Normal file
367
docs/implplan/DEPENDENCY_DAG.md
Normal 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
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
|
|
||||||
## Wave Coordination
|
## Wave Coordination
|
||||||
- **Wave A (ingest foundations — COMPLETE):** PREP tasks + LNM/graph groundwork (P1–P2, tasks 1–11) are DONE; keep outputs frozen for downstream consumers.
|
- **Wave A (ingest foundations — COMPLETE):** PREP tasks + LNM/graph groundwork (P1–P2, tasks 1–11) are DONE; keep outputs frozen for downstream consumers.
|
||||||
- **Wave B (object storage + WebService unlock):** Task 12 (CONCELIER-LNM-21-103-DEV) gates tasks 13–15; ✅ object storage contract created (`docs/schemas/object-storage.schema.json`), task 12 now TODO.
|
- **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 16–18 stay BLOCKED until mirror bundle + console fixtures + feed refresh plans land; runs after Wave B unblocks.
|
- **Wave C (console/air-gap/feed connectors):** Tasks 16–18 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 once contract cleared; otherwise remain disabled to avoid backlog noise.
|
- Event transport enablement (NATS/Scheduler) can proceed in Wave B now that object storage is complete.
|
||||||
|
|
||||||
## Documentation Prerequisites
|
## Documentation Prerequisites
|
||||||
- docs/README.md; docs/07_HIGH_LEVEL_ARCHITECTURE.md
|
- 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). |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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 | 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. |
|
| 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 | 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. |
|
| 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 | 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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
|
## Execution Log
|
||||||
| Date (UTC) | Update | Owner |
|
| 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-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-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 |
|
| 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 |
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
## Delivery Tracker
|
## Delivery Tracker
|
||||||
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
|
| # | 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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 | 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. |
|
| 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 | 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. |
|
| 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 | 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. |
|
| 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. |
|
| 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
|
## Execution Log
|
||||||
| Date (UTC) | Update | Owner |
|
| 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-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-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 |
|
| 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 |
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
## Wave Coordination
|
## Wave Coordination
|
||||||
- **Wave A (prep + policy/risk foundations):** Prep tasks P1–P3 and policy chain 1–4 completed; risks 5–7,9 delivered. Keep artifacts frozen for downstream consumers.
|
- **Wave A (prep + policy/risk foundations):** Prep tasks P1–P3 and policy chain 1–4 completed; risks 5–7,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 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.
|
- Waves stay serialized A → B → C to avoid contract drift; no new DOING items until blockers clear.
|
||||||
|
|
||||||
## Documentation Prerequisites
|
## 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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). |
|
| 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. |
|
| 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 0–13. | Product Mgmt · Concelier Guild | Address Concelier ingestion gaps CI1–CI10 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. |
|
| 14 | CONCELIER-GAPS-115-014 | DONE (2025-12-02) | None; informs tasks 0–13. | Product Mgmt · Concelier Guild | Address Concelier ingestion gaps CI1–CI10 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
|
## Execution Log
|
||||||
| Date (UTC) | Update | Owner |
|
| 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-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 | 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 |
|
| 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 1–4 | Concelier Core/WebService · Policy Guild | Upstream prerequisite. |
|
| POLICY-20-001 outputs (Sprint 0114) | Tasks 1–4 | 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. |
|
| 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. |
|
| CONCELIER-CORE-AOC-19-004 backfill pre-req | Task 11 | Concelier Core/Storage · DevOps | Needs completion before backfill rehearsal. |
|
||||||
|
|||||||
@@ -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. |
|
| 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. |
|
| 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). |
|
| 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 | BLOCKED | Depends on 56-001 | WebService Guild | Add staleness + bundle provenance metadata to observation/linkset endpoints. |
|
| 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 | BLOCKED | Prep P1 done; needs 56-002 | WebService · AirGap Policy Guild | Map sealed-mode violations to `AIRGAP_EGRESS_BLOCKED` payloads with remediation guidance. |
|
| 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 | BLOCKED | Depends on 57-001 | WebService · AirGap Importer Guild | Emit timeline events for bundle imports (bundle ID, scope, actor) per evidence change. |
|
| 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 | 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. |
|
| 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 | BLOCKED (2025-11-24) | Depends on 19-003 | WebService · QA | Integration tests for large-batch ingest reproducibility; fixtures for Offline Kit. |
|
| 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 | BLOCKED (2025-11-24) | Needs WEB-AOC-19-002 | WebService · QA | Fix `/advisories/{key}/chunks` seed data so raw docs resolve. |
|
| 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 | BLOCKED (2025-11-24) | Needs WEB-AOC-19-002 | WebService Guild | Align auth/tenant configs with fixtures; ensure allowlist enforcement tests pass. |
|
| 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 | BLOCKED (2025-11-24) | Needs WEB-AOC-19-002 | WebService · QA | Ensure AOC verify emits `ERR_AOC_001`; mapper/guard parity with regressions. |
|
| 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 | BLOCKED | Prereq for examples/deprecation | WebService Guild | Migrate APIs to standard error envelope; update controllers/tests. |
|
| 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 | BLOCKED | Depends on 61-002 | WebService Guild | Publish curated examples for observations/linksets/conflicts; wire into dev portal. |
|
| 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 | BLOCKED | Depends on 62-001 | WebService · API Governance | Emit deprecation headers/notifications steering clients to LNM APIs. |
|
| 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. |
|
| 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. |
|
| 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
|
## Execution Log
|
||||||
| Date (UTC) | Update | Owner |
|
| 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 | 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 | 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 |
|
| 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 |
|
| 2025-12-02 | Normalized sprint file to standard template; no status changes. | StellaOps Agent |
|
||||||
|
|
||||||
## Decisions & Risks
|
## Decisions & Risks
|
||||||
- AirGap tasks blocked until sealed-mode + staleness metadata defined; do not expose bundles without provenance.
|
- ~~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); large-batch tests must wait.
|
- ~~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; avoid duplicating client envelopes until unified.
|
- ~~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.
|
- 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
|
## 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).
|
||||||
|
|||||||
@@ -1,79 +1,5 @@
|
|||||||
# Sprint 0119 · Excititor Ingestion & Evidence (Phase IV)
|
# Redirected Sprint
|
||||||
|
|
||||||
## 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 1–3 DONE; keep schemas frozen for sealed-mode and replay consumers.
|
|
||||||
- **Wave B (orchestrator wiring):** Tasks 4–5 DONE; monitor SDK drift; no further work unless orchestrator contract changes.
|
|
||||||
- **Wave C (policy/risk APIs):** Tasks 6–8 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).
|
|
||||||
|
|
||||||
> **BLOCKED Tasks:** Before working on BLOCKED tasks, review [BLOCKED_DEPENDENCY_TREE.md](./BLOCKED_DEPENDENCY_TREE.md) for root blockers and dependencies.
|
> **BLOCKED Tasks:** Before working on BLOCKED tasks, review [BLOCKED_DEPENDENCY_TREE.md](./BLOCKED_DEPENDENCY_TREE.md) for root blockers and dependencies.
|
||||||
|
|
||||||
## Delivery Tracker
|
This sprint was normalised to `SPRINT_0122_0001_0004_excititor_iv.md`. Do not edit this file; update the canonical sprint instead.
|
||||||
| # | 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) |
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user