fix: resolve 4 unhealthy services from fresh volume rebuild

- router-gateway: sync 10 missing jobengine routes to local config (prevent array merge bleed-through)
- findings-ledger-web: add VulnExplorer tables to postgres-init bootstrap script
- timeline-web: replace competing migration hosted service with standard AddStartupMigrations
- graph-api: handle null PostgresGraphRepository gracefully, add graph schema to init
- scheduler-web: add failure_signatures table to init bootstrap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-09 16:23:52 +03:00
parent 537f4f17fc
commit 3a36aefd81
13 changed files with 419 additions and 89 deletions

View File

@@ -313,13 +313,15 @@ export VAULT_TOKEN=stellaops-dev-root-token-2026
vault kv put secret/harbor robot-account="harbor-robot-token"
vault kv put secret/github app-private-key="your-key"
vault kv put secret/gitea api-token="your-gitea-token"
vault kv put secret/gitlab access-token="glpat-your-token"
vault kv put secret/gitlab access-token="glpat-your-token" registry-basic="root:glpat-your-token"
vault kv put secret/jenkins api-token="user:token"
vault kv put secret/nexus admin-password="your-password"
```
Gitea is now bootstrapped by the compose service itself: a fresh `stellaops-gitea-data` volume creates the default local admin user and the repository root before the container reports healthy. Personal access tokens remain a manual step because Gitea only reveals the token value when it is created.
When you enable the optional GitLab registry surface (`GITLAB_ENABLE_REGISTRY=true`), register it through the `GitLabContainerRegistry` provider with `authref://vault/gitlab#registry-basic`. The local Docker registry connector now follows the registry's Bearer challenge and exchanges that `username:personal-access-token` secret against `jwt/auth` before retrying catalog and tag probes.
`docker-compose.testing.yml` is a separate infrastructure-test lane. It starts `postgres-test`, `valkey-test`, mocks, and an isolated Gitea profile on different ports; it does not start Consul or GitLab. Use `docker-compose.integrations.yml` only when you need real third-party providers for connector validation.
**Backend connector plugins** (8 total, loaded in Integrations service):

View File

@@ -10,6 +10,7 @@ CREATE SCHEMA IF NOT EXISTS notify;
CREATE SCHEMA IF NOT EXISTS notifier;
CREATE SCHEMA IF NOT EXISTS evidence;
CREATE SCHEMA IF NOT EXISTS findings;
CREATE SCHEMA IF NOT EXISTS graph;
CREATE SCHEMA IF NOT EXISTS timeline;
CREATE SCHEMA IF NOT EXISTS doctor;
CREATE SCHEMA IF NOT EXISTS issuer_directory;

View File

@@ -563,3 +563,86 @@ BEGIN
END $$;
COMMENT ON TABLE ledger_snapshots IS 'Point-in-time snapshots of ledger state for time-travel queries';
-- ============================================================================
-- 010_vex_fix_audit_tables.sql - VulnExplorer persistence tables
-- ============================================================================
BEGIN;
CREATE TABLE IF NOT EXISTS vex_decisions (
id UUID NOT NULL,
tenant_id TEXT NOT NULL,
vulnerability_id TEXT NOT NULL,
subject_type TEXT NOT NULL,
subject_name TEXT NOT NULL,
subject_digest JSONB NOT NULL DEFAULT '{}'::JSONB,
subject_sbom_node_id TEXT,
status TEXT NOT NULL,
justification_type TEXT NOT NULL,
justification_text TEXT,
evidence_refs JSONB,
scope_environments TEXT[],
scope_projects TEXT[],
valid_not_before TIMESTAMPTZ,
valid_not_after TIMESTAMPTZ,
attestation_ref_id TEXT,
attestation_ref_digest JSONB,
attestation_ref_storage TEXT,
signed_override JSONB,
supersedes_decision_id UUID,
created_by_id TEXT NOT NULL,
created_by_name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ,
CONSTRAINT pk_vex_decisions PRIMARY KEY (tenant_id, id)
) PARTITION BY LIST (tenant_id);
CREATE TABLE IF NOT EXISTS vex_decisions_default PARTITION OF vex_decisions DEFAULT;
CREATE INDEX IF NOT EXISTS ix_vex_decisions_vuln
ON vex_decisions (tenant_id, vulnerability_id, created_at DESC);
CREATE INDEX IF NOT EXISTS ix_vex_decisions_status
ON vex_decisions (tenant_id, status);
CREATE INDEX IF NOT EXISTS ix_vex_decisions_subject
ON vex_decisions (tenant_id, subject_name);
CREATE TABLE IF NOT EXISTS fix_verifications (
id UUID NOT NULL DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL,
cve_id TEXT NOT NULL,
component_purl TEXT NOT NULL,
artifact_digest TEXT,
verdict TEXT NOT NULL DEFAULT 'pending',
transitions JSONB NOT NULL DEFAULT '[]'::JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT pk_fix_verifications PRIMARY KEY (tenant_id, id),
CONSTRAINT uq_fix_verifications_cve UNIQUE (tenant_id, cve_id)
) PARTITION BY LIST (tenant_id);
CREATE TABLE IF NOT EXISTS fix_verifications_default PARTITION OF fix_verifications DEFAULT;
CREATE INDEX IF NOT EXISTS ix_fix_verifications_cve
ON fix_verifications (tenant_id, cve_id);
CREATE INDEX IF NOT EXISTS ix_fix_verifications_verdict
ON fix_verifications (tenant_id, verdict);
CREATE TABLE IF NOT EXISTS audit_bundles (
id UUID NOT NULL DEFAULT gen_random_uuid(),
tenant_id TEXT NOT NULL,
bundle_id TEXT NOT NULL,
decision_ids UUID[] NOT NULL,
attestation_digest TEXT,
evidence_refs TEXT[] NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT pk_audit_bundles PRIMARY KEY (tenant_id, id),
CONSTRAINT uq_audit_bundles_bundle_id UNIQUE (tenant_id, bundle_id)
) PARTITION BY LIST (tenant_id);
CREATE TABLE IF NOT EXISTS audit_bundles_default PARTITION OF audit_bundles DEFAULT;
CREATE INDEX IF NOT EXISTS ix_audit_bundles_created
ON audit_bundles (tenant_id, created_at DESC);
COMMIT;

View File

@@ -690,3 +690,26 @@ ALTER TABLE scheduler.scheduler_exceptions FORCE ROW LEVEL SECURITY;
CREATE POLICY scheduler_exceptions_tenant_isolation ON scheduler.scheduler_exceptions FOR ALL
USING (tenant_id = scheduler_app.require_current_tenant())
WITH CHECK (tenant_id = scheduler_app.require_current_tenant());
-- ============================================================================
-- failure_signatures - Scheduler failure signature tracking
-- ============================================================================
CREATE TABLE IF NOT EXISTS scheduler.failure_signatures (
signature_id UUID NOT NULL,
tenant_id TEXT NOT NULL,
signature_key TEXT NOT NULL,
pattern TEXT NOT NULL,
severity TEXT NOT NULL DEFAULT 'medium',
description TEXT,
remediation TEXT,
auto_retry BOOLEAN NOT NULL DEFAULT false,
max_retries INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
CONSTRAINT failure_signatures_pkey PRIMARY KEY (signature_id)
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_scheduler_failure_signatures_key
ON scheduler.failure_signatures(tenant_id, signature_key);

View File

@@ -84,6 +84,16 @@
{ "Type": "Microservice", "Path": "^/api/v1/release-control(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/v1/release-control$1" },
{ "Type": "Microservice", "Path": "^/api/v1/gateway/rate-limits(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/v1/gateway/rate-limits$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/quotas(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/v1/jobengine/quotas$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/registry/packs(.*)", "IsRegex": true, "TranslatesTo": "http://packsregistry.stella-ops.local/api/v1/packs$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/deadletter(.*)", "IsRegex": true, "TranslatesTo": "http://release-orchestrator.stella-ops.local/api/v1/jobengine/deadletter$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/jobs(.*)", "IsRegex": true, "TranslatesTo": "http://release-orchestrator.stella-ops.local/api/v1/jobengine/jobs$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/runs(.*)", "IsRegex": true, "TranslatesTo": "http://release-orchestrator.stella-ops.local/api/v1/jobengine/runs$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/dag(.*)", "IsRegex": true, "TranslatesTo": "http://release-orchestrator.stella-ops.local/api/v1/jobengine/dag$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/pack-runs(.*)", "IsRegex": true, "TranslatesTo": "http://release-orchestrator.stella-ops.local/api/v1/jobengine/pack-runs$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/stream(.*)", "IsRegex": true, "TranslatesTo": "http://release-orchestrator.stella-ops.local/api/v1/jobengine/stream$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/audit(.*)", "IsRegex": true, "TranslatesTo": "http://release-orchestrator.stella-ops.local/api/v1/jobengine/audit$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/sources(.*)", "IsRegex": true, "TranslatesTo": "http://release-orchestrator.stella-ops.local/api/v1/jobengine/sources$1" },
{ "Type": "Microservice", "Path": "^/api/v1/jobengine/slos(.*)", "IsRegex": true, "TranslatesTo": "http://release-orchestrator.stella-ops.local/api/v1/jobengine/slos$1" },
{ "Type": "Microservice", "Path": "^/api/v1/reachability(.*)", "IsRegex": true, "TranslatesTo": "http://reachgraph.stella-ops.local/api/v1/reachability$1" },
{ "Type": "Microservice", "Path": "^/api/v1/timeline(.*)", "IsRegex": true, "TranslatesTo": "http://timeline.stella-ops.local/api/v1/timeline$1" },
{ "Type": "Microservice", "Path": "^/api/v1/audit(.*)", "IsRegex": true, "TranslatesTo": "http://timeline.stella-ops.local/api/v1/audit$1" },