{
    "schema": "https://saferpage.de/schemas/operator-api-key-readiness.v1",
    "generated_at": "2026-06-09T20:33:16+00:00",
    "available": true,
    "status": "blocked_by_production_gates",
    "summary": "API-Key-Readiness: Runtime-Kontrollen 7/7 implementiert; API-Access-Storage 0/2 Tabellen; produktive Freigaben 0/7 aktiv.",
    "metrics": {
        "store_field_count": 11,
        "scope_mapping_count": 8,
        "audit_field_count": 14,
        "implementation_gate_count": 7,
        "implementation_gate_passed_count": 7,
        "implementation_gate_missing_count": 0,
        "gate_count": 7,
        "passed_gate_count": 0,
        "blocked_gate_count": 7,
        "api_access_storage_ready": 0,
        "api_access_storage_table_count": 0,
        "api_access_public_schema_create_privilege": 0,
        "api_access_preflight_check_count": 3,
        "api_access_ready_artifact_count": 0,
        "api_access_missing_required_artifact_count": 2,
        "fallback_audit_events_24h": 11,
        "write_hmac_fixture_count": 1,
        "write_hmac_fixture_negative_test_count": 4,
        "latest_smoke_available": 1,
        "latest_smoke_ok": 1,
        "latest_smoke_target_count": 6,
        "latest_smoke_failed_check_count": 0,
        "latest_smoke_blocked_expected_count": 1
    },
    "api_access_storage_status": {
        "available": true,
        "database_name": "saferpage",
        "db_user_hash": "d3234487bb1bc80ee9da6915667ce1f2",
        "api_keys_table": false,
        "api_access_audit_log_table": false,
        "scan_results_table": true,
        "public_schema_create_privilege": false,
        "api_key_store_ready": false,
        "migration_required": true,
        "admin_dsn_required": true,
        "migration_sha256": "27d802d48887d084e9132c841b943681c70638bd432746a92cab7809be621213",
        "migration_path": "infra/postgres/migrations/api-access.sql",
        "preflight_command": "scripts/run-api-access-migration-preflight.sh",
        "preflight_evidence_url": "https://saferpage.de/evidence/api-access-migration-preflight.json",
        "safe_apply_command": "SAFERPAGE_MIGRATION_DATABASE_URL='postgresql://admin@localhost/saferpage' scripts/run-api-access-migration.sh",
        "safe_next_action": "Migration mit kurzlebigem Admin-DSN aus einer sicheren Shell anwenden; DSN nicht in Projektdateien, Public-State oder Logs speichern.",
        "secret_policy": "Nur Tabellenstatus, Hashes und Privilegstatus; keine DSN, Passwoerter, Roh-DB-User, Hosts, Ports, API-Keys oder Key-Hashes."
    },
    "api_access_migration_preflight": {
        "admin_dsn_required": true,
        "api_access_audit_log_table": false,
        "api_key_store_ready": false,
        "api_keys_table": false,
        "available": true,
        "checks": [
            {
                "id": "api_keys_table",
                "label": "API-Key-Tabelle",
                "operator_action": "infra/postgres/migrations/api-access.sql mit kurzlebigem Admin-DSN aus sicherer Shell anwenden.",
                "purpose": "Speichert nur Prefix, Hash, Scopes, Status, Ablauf und Revocation-Metadaten.",
                "ready": false,
                "required_for_key_store": true,
                "status": "missing"
            },
            {
                "id": "api_access_audit_log_table",
                "label": "API-Access-Auditlog",
                "operator_action": "infra/postgres/migrations/api-access.sql mit kurzlebigem Admin-DSN aus sicherer Shell anwenden.",
                "purpose": "Append-only Auditlog fuer Key-Prefix, Scope, Route, Entscheidung, Status und Hash-Evidence.",
                "ready": false,
                "required_for_key_store": true,
                "status": "missing"
            },
            {
                "id": "scan_results_table",
                "label": "Scan-Results-Basistabelle",
                "operator_action": "Weiter beobachten.",
                "purpose": "Vorhandene Reportbasis fuer Public- und Operator-Read-Flows.",
                "ready": true,
                "required_for_key_store": false,
                "status": "ready"
            }
        ],
        "database_name": "saferpage",
        "database_reachable": true,
        "db_user_redacted": "63a9f0ea7bb98050796b649e85481845",
        "generated_at": "2026-06-09T20:31:59.856041Z",
        "migration_path": "infra/postgres/migrations/api-access.sql",
        "migration_required": true,
        "migration_sha256": "27d802d48887d084e9132c841b943681c70638bd432746a92cab7809be621213",
        "missing_artifact_count": 2,
        "missing_required_artifacts": [
            "api_keys_table",
            "api_access_audit_log_table"
        ],
        "operator_runbook": [
            "Kurzlebigen Admin-DSN nur in einer sicheren Shell setzen; nicht in History, Projektdateien, systemd-Units oder Public-State speichern.",
            "scripts/run-api-access-migration-preflight.sh ausfuehren und missing_required_artifacts pruefen.",
            "scripts/run-api-access-migration.sh mit Admin-DSN ausfuehren.",
            "Preflight erneut ausfuehren, danach API-Key-Pepper, Domain-Claim, Write-HMAC und Deny/Allow-Smokes aktivieren."
        ],
        "public_schema_create_privilege": false,
        "ready_artifact_count": 0,
        "required_artifact_count": 2,
        "safe_apply_command": "SAFERPAGE_MIGRATION_DATABASE_URL='postgresql://admin@localhost/saferpage' scripts/run-api-access-migration.sh",
        "safe_combined_apply_command": "SAFERPAGE_MIGRATION_DATABASE_URL='postgresql://admin@localhost/saferpage' scripts/run-storage-migrations.sh",
        "safe_next_action": "Run the migration from a secure shell with a short-lived admin DSN; do not store or publish the DSN.",
        "safe_preflight_command": "scripts/run-api-access-migration-preflight.sh",
        "scan_results_table": true,
        "schema": "https://saferpage.de/schemas/operator-api-access-migration-preflight.v1",
        "secret_policy": "No DSN, password, raw database user, host, port, key hash or API key is exported.",
        "preflight_evidence_url": "https://saferpage.de/evidence/api-access-migration-preflight.json"
    },
    "latest_smoke_result": {
        "available": true,
        "url": "https://saferpage.de/evidence/api-key-readiness-smoke.json",
        "schema": "https://saferpage.de/schemas/operator-api-key-readiness-smoke.v1",
        "status": "ok",
        "ok": true,
        "generated_at": "2026-06-09T19:20:35+00:00",
        "summary": "No-Secret-Smoke fuer API-Key-Readiness, Migration-Preflight, Runtime-Gates und Operator-Go-live-Verknuepfung.",
        "claim_boundary": "Dieser Smoke erzeugt keine API-Keys, setzt keine Env-Gates, wendet keine Migration an und prüft keine privaten Zielsysteme.",
        "metrics": {
            "target_count": 6,
            "http_passed_count": 6,
            "http_failed_count": 0,
            "check_count": 6,
            "failed_check_count": 0,
            "blocked_expected_count": 1,
            "api_access_storage_table_count": 0,
            "missing_required_artifact_count": 2
        },
        "checks": [
            {
                "id": "public_routes_http_200",
                "label": "Öffentliche API-Key-Readiness-Routen erreichbar",
                "status": "passed",
                "evidence": "6/6 Route(s) liefern HTTP 200.",
                "operator_action": "Weiter mit Preflight/Go-live-Gates."
            },
            {
                "id": "migration_preflight_public",
                "label": "Migration-Preflight öffentlich und sanitisiert",
                "status": "passed",
                "evidence": "missing_required_artifacts=2, admin_dsn_required=yes.",
                "operator_action": "Admin-DSN aus sicherer Shell setzen und Migration anwenden."
            },
            {
                "id": "runtime_controls_documented",
                "label": "API-Runtime-Kontrollen dokumentiert",
                "status": "passed",
                "evidence": "7/7 Implementierungs-Gates; 8 Runtime-Control-Einträge.",
                "operator_action": "Deny-/Allow-/Revocation-Smokes nach Storage-Migration erneut ausführen."
            },
            {
                "id": "storage_blocker_explicit",
                "label": "Produktivblocker API-Storage explizit",
                "status": "blocked_expected",
                "evidence": "api_access_storage_table_count=0/2; storage_ready=0.",
                "operator_action": "Migration mit kurzlebigem Admin-DSN anwenden; keine Secrets veröffentlichen."
            },
            {
                "id": "operator_go_live_links_api_blocker",
                "label": "Operator-Go-live verlinkt API-Blocker",
                "status": "passed",
                "evidence": "Go-live API-Storage=0/2.",
                "operator_action": "Go-live-Center nach Migration erneut prüfen."
            },
            {
                "id": "migration_sql_no_secret_scan",
                "label": "Migration-SQL enthält keine offensichtlichen Secret-Werte",
                "status": "passed",
                "evidence": "Keine Forbidden-Pattern-Treffer.",
                "operator_action": "SQL kann an DB-Owner übergeben werden."
            }
        ],
        "failed_checks": [],
        "no_secret_policy": {
            "contains_secrets": false,
            "contains_private_target_urls": false,
            "contains_recipients": false,
            "contains_private_documents": false,
            "contains_visitor_logs": false,
            "sql_secret_pattern_hits": []
        }
    },
    "key_store_contract": {
        "hash_algorithm": "Argon2id fuer direkt verifizierte Keys oder HMAC-SHA-256 mit serverseitigem Pepper fuer Prefix-Lookups.",
        "one_time_display": true,
        "raw_key_storage_allowed": false,
        "max_lifetime_days": 90,
        "prefix_format": "sp_live_<8_char_prefix>",
        "fields": [
            {
                "field": "key_id",
                "type": "uuid",
                "purpose": "Interne stabile ID; nie als Auth-Secret nutzen.",
                "public_export": "allowed"
            },
            {
                "field": "key_prefix",
                "type": "text",
                "purpose": "Kurzer Prefix fuer Support, Audit und Revocation.",
                "public_export": "allowed_if_not_unique_secret"
            },
            {
                "field": "key_hash",
                "type": "argon2id_or_hmac_sha256",
                "purpose": "Klartext-Key serverseitig verifizieren; Roh-Key nie speichern.",
                "public_export": "forbidden"
            },
            {
                "field": "operator_id",
                "type": "uuid_or_external_subject",
                "purpose": "Betreiberkonto oder OIDC-Subject.",
                "public_export": "forbidden"
            },
            {
                "field": "domain_scope",
                "type": "text[]",
                "purpose": "Zulaessige Domains oder Portfolios.",
                "public_export": "redacted"
            },
            {
                "field": "scopes",
                "type": "text[]",
                "purpose": "Least-Privilege-Berechtigungen.",
                "public_export": "allowed"
            },
            {
                "field": "status",
                "type": "active|rotating|revoked|expired",
                "purpose": "Serverseitige Sperrentscheidung.",
                "public_export": "allowed"
            },
            {
                "field": "created_at",
                "type": "timestamptz",
                "purpose": "Ausstellungszeitpunkt.",
                "public_export": "allowed"
            },
            {
                "field": "expires_at",
                "type": "timestamptz",
                "purpose": "Maximal 90 Tage fuer produktive Keys.",
                "public_export": "allowed"
            },
            {
                "field": "last_used_at",
                "type": "timestamptz",
                "purpose": "Missbrauchs- und Aufraeumkontrolle.",
                "public_export": "redacted"
            },
            {
                "field": "revoked_at",
                "type": "timestamptz",
                "purpose": "Widerrufsnachweis.",
                "public_export": "allowed"
            }
        ]
    },
    "scope_endpoint_matrix": [
        {
            "scope": "reports.public:read",
            "tier": "public",
            "method": "GET",
            "endpoint_pattern": "/{domain}, /{domain}/share-card-json, /badge/{domain}",
            "risk": "niedrig",
            "decision": "ohne Key oder Public-Key mit Cache erlaubt"
        },
        {
            "scope": "schemas:read",
            "tier": "public",
            "method": "GET",
            "endpoint_pattern": "/schemas, /schemas/{schema}.v1",
            "risk": "niedrig",
            "decision": "oeffentlich cachebar"
        },
        {
            "scope": "reports:read",
            "tier": "operator_read",
            "method": "GET",
            "endpoint_pattern": "/{domain}/module-export, /report-pack/{domain}/export",
            "risk": "mittel",
            "decision": "Domain-Claim und Operator-Key erforderlich"
        },
        {
            "scope": "portfolio:read",
            "tier": "operator_read",
            "method": "GET",
            "endpoint_pattern": "/portfolio/export, /portfolio/audit-json, /portfolio/schedule-json",
            "risk": "mittel",
            "decision": "Portfolio-Zuordnung pruefen"
        },
        {
            "scope": "evidence:read",
            "tier": "operator_read",
            "method": "GET",
            "endpoint_pattern": "/nachweise/{domain}/export, /api/report/export",
            "risk": "hoch",
            "decision": "Sanitization, Domain-Claim und Auditlog erforderlich"
        },
        {
            "scope": "tickets:write",
            "tier": "operator_write",
            "method": "POST",
            "endpoint_pattern": "Remediation-/Ticket-Delivery",
            "risk": "hoch",
            "decision": "HMAC, Idempotency-Key und Zielsystem-Dry-Run erforderlich"
        },
        {
            "scope": "dispatch:write",
            "tier": "operator_write",
            "method": "POST",
            "endpoint_pattern": "Scan-Dispatch und Alert-Dispatch",
            "risk": "hoch",
            "decision": "Betreiberfreigabe, Rate-Limit und Stop-Conditions erforderlich"
        },
        {
            "scope": "keys:rotate",
            "tier": "admin",
            "method": "POST",
            "endpoint_pattern": "Key-Rotation und Revocation",
            "risk": "kritisch",
            "decision": "OIDC/MFA, Vier-Augen-Gate und Auditlog erforderlich"
        }
    ],
    "access_audit_event_schema": {
        "retention_days": 400,
        "append_only": true,
        "fields": [
            {
                "field": "event_id",
                "type": "uuid",
                "policy": "required"
            },
            {
                "field": "request_id",
                "type": "uuid",
                "policy": "required"
            },
            {
                "field": "key_prefix",
                "type": "text",
                "policy": "allowed"
            },
            {
                "field": "key_hash",
                "type": "never_export",
                "policy": "forbidden"
            },
            {
                "field": "operator_subject_hash",
                "type": "sha256",
                "policy": "redacted"
            },
            {
                "field": "domain_scope_hash",
                "type": "sha256_or_domain_if_public",
                "policy": "redacted"
            },
            {
                "field": "scope",
                "type": "text",
                "policy": "required"
            },
            {
                "field": "endpoint",
                "type": "route_id",
                "policy": "required"
            },
            {
                "field": "method",
                "type": "GET|POST|PATCH|DELETE",
                "policy": "required"
            },
            {
                "field": "decision",
                "type": "allow|deny|rate_limited|revoked|expired",
                "policy": "required"
            },
            {
                "field": "status_code",
                "type": "int",
                "policy": "required"
            },
            {
                "field": "ip_hash",
                "type": "sha256_with_rotation_salt",
                "policy": "redacted"
            },
            {
                "field": "user_agent_hash",
                "type": "sha256",
                "policy": "redacted"
            },
            {
                "field": "created_at",
                "type": "timestamptz",
                "policy": "required"
            }
        ]
    },
    "runtime_implementation_gates": [
        {
            "id": "hashed_key_store_runtime",
            "label": "Key-Store- und Hash-Vertrag im Backend vorhanden",
            "status": "implemented",
            "passed": true,
            "evidence": "Migration deklariert api_keys mit Prefix/Hash/Scopes/Status; Storage liest Key-Records ohne Roh-Key-Export.",
            "next_action": "Mit Migration, Secret-Referenzen und Smoke-Tests produktiv verifizieren.",
            "export_policy": "Nur Implementierungsstatus und Dateievidenz ausgeben; keine Secrets, Roh-Keys, Hashes oder personenbezogenen Rohdaten."
        },
        {
            "id": "scope_enforcement_runtime",
            "label": "Scope-Enforcement im Operator-Probe vorhanden",
            "status": "implemented",
            "passed": true,
            "evidence": "/api/operator/probe vergleicht angeforderten Scope mit dem Key-Record und auditiert Deny-Entscheidungen.",
            "next_action": "Mit Migration, Secret-Referenzen und Smoke-Tests produktiv verifizieren.",
            "export_policy": "Nur Implementierungsstatus und Dateievidenz ausgeben; keine Secrets, Roh-Keys, Hashes oder personenbezogenen Rohdaten."
        },
        {
            "id": "access_audit_runtime",
            "label": "Access-Audit mit Fallback vorhanden",
            "status": "implemented",
            "passed": true,
            "evidence": "Postgres-Audit und File-Fallback schreiben sanitisierte Events; Fallback-Events letzte 24h: 11.",
            "next_action": "Mit Migration, Secret-Referenzen und Smoke-Tests produktiv verifizieren.",
            "export_policy": "Nur Implementierungsstatus und Dateievidenz ausgeben; keine Secrets, Roh-Keys, Hashes oder personenbezogenen Rohdaten."
        },
        {
            "id": "rate_limit_runtime",
            "label": "Rate-Limit-Gate im Operator-Probe vorhanden",
            "status": "implemented",
            "passed": true,
            "evidence": "Operator-Probe prueft ein serverseitiges Rate-Limit vor Auth-/Scope-Entscheidung und auditiert 429.",
            "next_action": "Mit Migration, Secret-Referenzen und Smoke-Tests produktiv verifizieren.",
            "export_policy": "Nur Implementierungsstatus und Dateievidenz ausgeben; keine Secrets, Roh-Keys, Hashes oder personenbezogenen Rohdaten."
        },
        {
            "id": "revocation_runtime",
            "label": "Revocation- und Expiry-Checks vorhanden",
            "status": "implemented",
            "passed": true,
            "evidence": "Key-Status revoked/expired und expires_at werden vor Allow-Entscheidung geprueft.",
            "next_action": "Mit Migration, Secret-Referenzen und Smoke-Tests produktiv verifizieren.",
            "export_policy": "Nur Implementierungsstatus und Dateievidenz ausgeben; keine Secrets, Roh-Keys, Hashes oder personenbezogenen Rohdaten."
        },
        {
            "id": "domain_claim_runtime",
            "label": "Domain-Scope-Gate vorhanden",
            "status": "implemented",
            "passed": true,
            "evidence": "Operator-Probe erzwingt Domain-Scope fuer Operator-Scopes und auditiert Domain-Scope-Mismatches nur mit Hash-Evidence.",
            "next_action": "Mit Migration, Secret-Referenzen und Smoke-Tests produktiv verifizieren.",
            "export_policy": "Nur Implementierungsstatus und Dateievidenz ausgeben; keine Secrets, Roh-Keys, Hashes oder personenbezogenen Rohdaten."
        },
        {
            "id": "write_hmac_runtime",
            "label": "Write-HMAC- und Idempotency-Gate vorhanden",
            "status": "implemented",
            "passed": true,
            "evidence": "Write-Scopes verlangen X-SaferPage-Signature und X-SaferPage-Idempotency-Key; Secret bleibt Server-Env.",
            "next_action": "Mit Migration, Secret-Referenzen und Smoke-Tests produktiv verifizieren.",
            "export_policy": "Nur Implementierungsstatus und Dateievidenz ausgeben; keine Secrets, Roh-Keys, Hashes oder personenbezogenen Rohdaten."
        }
    ],
    "write_hmac_test_fixture": {
        "purpose": "Öffentlicher Testfall fuer Operator-API-Write-Clients. Nicht als produktives Secret verwenden.",
        "algorithm": "HMAC-SHA256",
        "signature_header": "X-SaferPage-Signature",
        "idempotency_header": "X-SaferPage-Idempotency-Key",
        "secret_ref": "SAFERPAGE_API_WRITE_HMAC_SECRET",
        "test_secret": "saferpage_api_write_test_secret_do_not_use_in_production",
        "method": "POST",
        "endpoint": "/api/operator/probe",
        "scope": "tickets:write",
        "idempotency_key": "sp-api-write-test-fixture",
        "canonical_string": "POST\n/api/operator/probe\ntickets:write\nsp-api-write-test-fixture",
        "canonical_string_sha256": "d88fdc61e9afd95ceb9d17054bb86405bff1463547b9a6d7f7cd845bbe6bddc7",
        "expected_signature": "sha256=f67e8e5281ec206981536f06eb55011b7537dbda2cfc28b1397c73464ff6f314",
        "expected_signature_header": "X-SaferPage-Signature: sha256=f67e8e5281ec206981536f06eb55011b7537dbda2cfc28b1397c73464ff6f314",
        "positive_test": "Client berechnet ueber exakt diesen kanonischen String dieselbe Signatur und sendet sie mit Idempotency-Key.",
        "negative_tests": [
            "Methode von POST auf GET aendern: Signatur muss abgelehnt werden.",
            "Scope von tickets:write auf reports:read aendern: Signatur muss abgelehnt werden.",
            "Idempotency-Key aendern oder wiederverwenden: Payload muss abgelehnt oder als Duplikat behandelt werden.",
            "Signature-Prefix entfernen oder falschen Algorithmus nutzen: Request muss abgelehnt werden."
        ],
        "sample_commands": {
            "canonical_string_sha256": "printf %s \"$CANONICAL\" | sha256sum",
            "expected_signature": "printf %s \"$CANONICAL\" | openssl dgst -sha256 -hmac \"saferpage_api_write_test_secret_do_not_use_in_production\" -binary | xxd -p -c 256",
            "curl_dry_run": "curl -X POST \"https://saferpage.de/api/operator/probe?scope=tickets%3Awrite&domain=anrufer.info\" -H \"Authorization: Bearer sp_live_testfixture.invalid\" -H \"X-SaferPage-Idempotency-Key: sp-api-write-test-fixture\" -H \"X-SaferPage-Signature: sha256=f67e8e5281ec206981536f06eb55011b7537dbda2cfc28b1397c73464ff6f314\""
        }
    },
    "fallback_audit_status": {
        "available": true,
        "events_24h": 11,
        "last_event_at": "2026-06-09T12:34:14+00:00"
    },
    "readiness_gates": [
        {
            "id": "hashed_key_store",
            "label": "Gehashter Key-Store produktiv",
            "env_ref": "SAFERPAGE_API_KEY_STORE_READY",
            "status": "required",
            "passed": false,
            "owner": "IT/Security",
            "evidence": "Server-Freigabe SAFERPAGE_API_KEY_STORE_READY ist nicht aktiv.",
            "operator_action": "Key-Store mit Hashing, Prefix und Revocation-Feldern bereitstellen.",
            "export_policy": "Nur Referenzname und Status ausgeben; keine API-Keys, Hashes, Salts, IPs oder personenbezogenen Rohdaten."
        },
        {
            "id": "scope_enforcement",
            "label": "Serverseitige Scope-Pruefung",
            "env_ref": "SAFERPAGE_API_SCOPE_ENFORCEMENT_READY",
            "status": "required",
            "passed": false,
            "owner": "Backend",
            "evidence": "Server-Freigabe SAFERPAGE_API_SCOPE_ENFORCEMENT_READY ist nicht aktiv.",
            "operator_action": "Middleware fuer Scope, Domain-Claim und Method-Policy aktivieren.",
            "export_policy": "Nur Referenzname und Status ausgeben; keine API-Keys, Hashes, Salts, IPs oder personenbezogenen Rohdaten."
        },
        {
            "id": "access_audit_log",
            "label": "Access-Auditlog aktiv",
            "env_ref": "SAFERPAGE_API_ACCESS_AUDIT_READY",
            "status": "required",
            "passed": false,
            "owner": "Compliance/IT",
            "evidence": "Server-Freigabe SAFERPAGE_API_ACCESS_AUDIT_READY ist nicht aktiv.",
            "operator_action": "Append-only Auditlog mit Request-ID, Scope, Entscheidung und Statuscode anbinden.",
            "export_policy": "Nur Referenzname und Status ausgeben; keine API-Keys, Hashes, Salts, IPs oder personenbezogenen Rohdaten."
        },
        {
            "id": "rate_limit",
            "label": "Rate-Limits je Key und Scope",
            "env_ref": "SAFERPAGE_API_RATE_LIMIT_READY",
            "status": "required",
            "passed": false,
            "owner": "Platform",
            "evidence": "Server-Freigabe SAFERPAGE_API_RATE_LIMIT_READY ist nicht aktiv.",
            "operator_action": "Rate-Limit-Store und Stop-Condition fuer Schreibpfade aktivieren.",
            "export_policy": "Nur Referenzname und Status ausgeben; keine API-Keys, Hashes, Salts, IPs oder personenbezogenen Rohdaten."
        },
        {
            "id": "revocation",
            "label": "Sofortige Sperrung und Rotation",
            "env_ref": "SAFERPAGE_API_REVOCATION_READY",
            "status": "required",
            "passed": false,
            "owner": "IT/Security",
            "evidence": "Server-Freigabe SAFERPAGE_API_REVOCATION_READY ist nicht aktiv.",
            "operator_action": "Revocation-Check vor Scope-Entscheidung ausfuehren und Rotation dokumentieren.",
            "export_policy": "Nur Referenzname und Status ausgeben; keine API-Keys, Hashes, Salts, IPs oder personenbezogenen Rohdaten."
        },
        {
            "id": "domain_claim_required",
            "label": "Domain-Claim vor Operator-Zugriff",
            "env_ref": "SAFERPAGE_API_DOMAIN_CLAIM_READY",
            "status": "required",
            "passed": false,
            "owner": "Programm-Owner",
            "evidence": "Server-Freigabe SAFERPAGE_API_DOMAIN_CLAIM_READY ist nicht aktiv.",
            "operator_action": "Domain-Verifizierung und Rollenfreigabe vor Key-Ausstellung erzwingen.",
            "export_policy": "Nur Referenzname und Status ausgeben; keine API-Keys, Hashes, Salts, IPs oder personenbezogenen Rohdaten."
        },
        {
            "id": "write_hmac",
            "label": "HMAC fuer Schreib- und Webhook-Pfade",
            "env_ref": "SAFERPAGE_API_WRITE_HMAC_READY",
            "status": "required",
            "passed": false,
            "owner": "IT/Security",
            "evidence": "Server-Freigabe SAFERPAGE_API_WRITE_HMAC_READY ist nicht aktiv.",
            "operator_action": "X-SaferPage-Signature und X-SaferPage-Idempotency-Key fuer Write-Scopes erzwingen.",
            "export_policy": "Nur Referenzname und Status ausgeben; keine API-Keys, Hashes, Salts, IPs oder personenbezogenen Rohdaten."
        }
    ],
    "migration_package": {
        "schema_version": "api-access-2026-06-09",
        "requires_role": "PostgreSQL-Owner oder Rolle mit CREATE TABLE, CREATE INDEX und CREATE TRIGGER im Schema public.",
        "does_not_require_secret_values": true,
        "sql_url": "https://saferpage.de/api-zugriff/access-migration.sql",
        "preflight_evidence_url": "https://saferpage.de/evidence/api-access-migration-preflight.json",
        "migration_required": true,
        "admin_dsn_required": true,
        "safe_next_action": "Migration mit kurzlebigem Admin-DSN aus einer sicheren Shell anwenden; DSN nicht in Projektdateien, Public-State oder Logs speichern.",
        "sql_sha256": "27d802d48887d084e9132c841b943681c70638bd432746a92cab7809be621213",
        "sql_bytes": 2211,
        "preflight_commands": [
            "scripts/run-api-access-migration-preflight.sh",
            "psql -d saferpage -Atq -c \"select current_database(), current_user;\"",
            "psql -d saferpage -Atq -c \"select coalesce(to_regclass('public.api_keys')::text,'missing'), coalesce(to_regclass('public.api_access_audit_log')::text,'missing');\""
        ],
        "migration_command": "SAFERPAGE_MIGRATION_DATABASE_URL='postgresql://admin@localhost/saferpage' scripts/run-api-access-migration.sh",
        "download_and_apply_command": "curl -fsS https://saferpage.de/api-zugriff/access-migration.sql -o /tmp/saferpage-api-access.sql && export SAFERPAGE_MIGRATION_DATABASE_URL='postgresql://admin@localhost/saferpage' && psql \"$SAFERPAGE_MIGRATION_DATABASE_URL\" -v ON_ERROR_STOP=1 -f /tmp/saferpage-api-access.sql",
        "smoke_test_commands": [
            "scripts/run-api-access-migration-preflight.sh",
            "curl -fsS https://saferpage.de/api-zugriff/key-readiness-json | python3 -m json.tool",
            "scripts/run-api-runtime-deny-smoke.sh",
            "scripts/run-api-service-smoke.sh"
        ],
        "activation_order": [
            "DDL-Migration fuer api_keys und api_access_audit_log anwenden.",
            "API-Readiness erneut pruefen: api_access_storage_table_count muss 2 zeigen.",
            "API-Key-Pepper und Write-HMAC-Secret nur im Server-Environment oder Secret Manager setzen.",
            "Domain-Claim und Betreiberrolle verifizieren.",
            "Test-Key nur einmal anzeigen, danach Deny-/Allow-/Revocation-Smokes ausfuehren.",
            "Produktive Gates erst nach erfolgreicher Smoke-Evidence aktivieren."
        ],
        "rollback_or_pause": [
            "Sofortige Pause ohne DDL-Rollback: alle SAFERPAGE_API_*_READY Freigaben entfernen und API-Service neu starten.",
            "Keys bei Verdacht auf Fehlkonfiguration auf status=revoked setzen statt Rohdaten zu exportieren.",
            "DDL-Drop nur nach Backup-, Audit- und Retention-Freigabe ausfuehren."
        ],
        "acceptance_criteria": [
            "api_keys und api_access_audit_log existieren.",
            "api_keys enthaelt nur Prefix/Hash/Scopes/Status und keinen Roh-Key.",
            "api_access_audit_log schreibt sanitisierte Deny-/Allow-Events ohne Authorization-Header.",
            "Runtime-Gate-Probe bestaetigt 401/403/429/Revocation-Fixtures.",
            "Write-HMAC-Test-Fixture ist gegen Client/Receiver verifiziert."
        ]
    },
    "operator_sequence": [
        "Migration-Preflight ausfuehren: scripts/run-api-access-migration-preflight.sh.",
        "Wenn api_keys/api_access_audit_log fehlen, Migration mit kurzlebigem Admin-DSN aus sicherer Shell anwenden.",
        "Domain-Claim und Operator-Rolle verifizieren.",
        "Key nur einmal anzeigen, Prefix und Hash speichern, Ablaufdatum maximal 90 Tage setzen.",
        "Scopes minimal vergeben und Endpoint-Matrix gegen reale Rolle pruefen.",
        "Read-/Write-/Admin-Limits getrennt aktivieren.",
        "Schreibende Aufrufe nur mit HMAC, Idempotency-Key und Audit-Event erlauben.",
        "Rotation, Revocation und Notfall-Sperrung mit Test-Key dokumentieren."
    ],
    "example_redacted_key_record": {
        "key_id": "00000000-0000-0000-0000-000000000000",
        "key_prefix": "sp_live_ab12cd34",
        "key_hash": "__FORBIDDEN_IN_EXPORT__",
        "scopes": [
            "reports:read",
            "portfolio:read"
        ],
        "status": "active",
        "expires_at": "2026-09-07T20:33:16+00:00",
        "raw_key": "__DISPLAY_ONCE_ONLY_NEVER_EXPORT__"
    },
    "links": {
        "html": "https://saferpage.de/api-zugriff/key-readiness",
        "json": "https://saferpage.de/api-zugriff/key-readiness-json",
        "csv": "https://saferpage.de/api-zugriff/key-readiness-csv",
        "markdown": "https://saferpage.de/api-zugriff/key-readiness-md",
        "api_access": "https://saferpage.de/api-zugriff/export",
        "runtime_gate_probe": "https://saferpage.de/api-zugriff/runtime-gate-probe-json",
        "migration_sql": "https://saferpage.de/api-zugriff/access-migration.sql",
        "latest_smoke_result": "https://saferpage.de/evidence/api-key-readiness-smoke.json",
        "trust_api": "https://saferpage.de/trust/anrufer.info/api-json",
        "domain_verification": "https://saferpage.de/betreiber/anrufer.info/verifizierung-json",
        "integration_setup": "https://saferpage.de/integrationen/setup-json",
        "schema": "https://saferpage.de/schemas/operator-api-key-readiness.v1"
    },
    "disclaimer": "Dieses Dossier erzeugt keine echten API-Keys, speichert keine Hashes und gibt keine Secret-Werte aus. Produktive Key-Ausstellung braucht Betreiber-Auth, Domain-Claim und Server-Gates."
}
