# Export Bundle Scheduler Contract **Contract ID:** `CONTRACT-EXPORT-BUNDLE-009` **Version:** 1.0 **Status:** Published **Last Updated:** 2025-12-05 ## Overview This contract defines the export bundle job scheduling and manifest format used by the Export Center. It covers job definitions, scheduling, output formats, and attestation integration. ## Implementation References - **Export Center:** `src/ExportCenter/` - **API Spec:** `src/Api/StellaOps.Api.OpenApi/export-center/openapi.yaml` ## Data Models ### ExportBundleJob Job definition for scheduled exports. ```json { "job_id": "job-001", "tenant_id": "default", "name": "daily-vex-export", "description": "Daily VEX advisory export", "query": { "type": "vex", "filters": { "severity": ["critical", "high"], "providers": ["github", "redhat"] } }, "format": "openvex", "schedule": "0 0 * * *", "destination": { "type": "s3", "config": { "bucket": "exports", "prefix": "vex/daily/" } }, "signing": { "enabled": true, "predicate_type": "stella.ops/vex@v1" }, "enabled": true, "created_at": "2025-12-05T10:00:00Z", "last_run_at": "2025-12-05T00:00:00Z", "next_run_at": "2025-12-06T00:00:00Z" } ``` ### Export Formats | Format | Description | MIME Type | |--------|-------------|-----------| | `openvex` | OpenVEX JSON | application/json | | `csaf` | CSAF VEX | application/json | | `cyclonedx` | CycloneDX VEX | application/json | | `spdx` | SPDX document | application/json | | `ndjson` | Newline-delimited JSON | application/x-ndjson | | `json` | Standard JSON array | application/json | ### Schedule Format Cron expressions (5 fields): ``` ┌───────────── minute (0-59) │ ┌───────────── hour (0-23) │ │ ┌───────────── day of month (1-31) │ │ │ ┌───────────── month (1-12) │ │ │ │ ┌───────────── day of week (0-6, Sunday=0) │ │ │ │ │ * * * * * ``` Examples: | Schedule | Description | |----------|-------------| | `0 0 * * *` | Daily at midnight | | `0 */6 * * *` | Every 6 hours | | `0 0 * * 0` | Weekly on Sunday | | `0 0 1 * *` | Monthly on the 1st | ### Destination Types #### S3 Destination ```json { "type": "s3", "config": { "bucket": "my-exports", "prefix": "vex/", "region": "us-east-1", "endpoint": "https://s3.amazonaws.com" } } ``` #### File Destination ```json { "type": "file", "config": { "path": "/exports/vex/" } } ``` #### Webhook Destination ```json { "type": "webhook", "config": { "url": "https://example.com/webhook", "headers": { "Authorization": "Bearer ${SECRET}" } } } ``` ### ExportBundleManifest Manifest for completed export. ```json { "bundle_id": "bundle-001", "job_id": "job-001", "tenant_id": "default", "created_at": "2025-12-05T00:00:00Z", "format": "openvex", "artifact_digest": "sha256:abc123...", "artifact_size_bytes": 1048576, "query_signature": "sha256:def456...", "item_count": 150, "policy_digest": "sha256:...", "consensus_digest": "sha256:...", "score_digest": "sha256:...", "attestation": { "predicate_type": "stella.ops/vex@v1", "rekor_uuid": "24296fb24b8ad77a...", "rekor_index": 12345, "signed_at": "2025-12-05T00:00:01Z" } } ``` ## API Endpoints ### Job Management #### Create Export Job ``` POST /api/v1/export/jobs Content-Type: application/json Authorization: Bearer { "name": "daily-vex-export", "query": {...}, "format": "openvex", "schedule": "0 0 * * *", "destination": {...} } Response: 201 Created { "job_id": "job-001", ... } ``` #### Update Job ``` PUT /api/v1/export/jobs/{job_id} Content-Type: application/json { "schedule": "0 */12 * * *", "enabled": true } Response: 200 OK ``` #### Delete Job ``` DELETE /api/v1/export/jobs/{job_id} Response: 204 No Content ``` #### List Jobs ``` GET /api/v1/export/jobs?tenant_id=default Response: 200 OK { "items": [...], "total": 5 } ``` ### Manual Execution #### Trigger Job ``` POST /api/v1/export/jobs/{job_id}/run Response: 202 Accepted { "execution_id": "exec-001", "status": "running" } ``` #### Get Execution Status ``` GET /api/v1/export/jobs/{job_id}/executions/{execution_id} Response: 200 OK { "execution_id": "exec-001", "status": "completed", "bundle_id": "bundle-001", "started_at": "2025-12-05T00:00:00Z", "completed_at": "2025-12-05T00:00:05Z" } ``` ### Bundle Retrieval #### Get Bundle Manifest ``` GET /api/v1/export/bundles/{bundle_id} Response: 200 OK { "bundle_id": "bundle-001", "artifact_digest": "sha256:...", ... } ``` #### Download Bundle ``` GET /api/v1/export/bundles/{bundle_id}/download Response: 200 OK Content-Type: application/json Content-Disposition: attachment; filename="vex-export-2025-12-05.json" [bundle content] ``` ## Signing Configuration ### Enable Signing ```json { "signing": { "enabled": true, "predicate_type": "stella.ops/vex@v1", "key_id": "signing-key-001", "include_rekor": true } } ``` ### Predicate Types | Type | Description | |------|-------------| | `stella.ops/vex@v1` | VEX export attestation | | `stella.ops/sbom@v1` | SBOM export attestation | | `stella.ops/policy@v1` | Policy result export | ## Job Status | Status | Description | |--------|-------------| | `idle` | Job is waiting for next scheduled run | | `running` | Job is currently executing | | `completed` | Last run completed successfully | | `failed` | Last run failed | | `disabled` | Job is disabled | ## Error Codes | Code | Message | |------|---------| | `ERR_EXP_001` | Invalid schedule expression | | `ERR_EXP_002` | Invalid destination config | | `ERR_EXP_003` | Export failed | | `ERR_EXP_004` | Signing failed | | `ERR_EXP_005` | Job not found | ## Unblocks This contract unblocks the following tasks: - EXPORT-CONSOLE-23-001 ## Related Contracts - [Mirror Bundle Contract](./mirror-bundle.md) - Bundle format for air-gap - [Risk Scoring Contract](./risk-scoring.md) - Score digest in exports