diff --git a/apps/api/src/services/gitea_capability_ci_cd_optimization_readback.py b/apps/api/src/services/gitea_capability_ci_cd_optimization_readback.py index 7911f32bf..151e40f3c 100644 --- a/apps/api/src/services/gitea_capability_ci_cd_optimization_readback.py +++ b/apps/api/src/services/gitea_capability_ci_cd_optimization_readback.py @@ -22,14 +22,14 @@ from src.services.backup_dr_readiness_matrix import ( from src.services.backup_notification_policy import ( load_latest_backup_notification_policy, ) -from src.services.gitea_private_inventory_p0_scorecard import ( - load_latest_gitea_private_inventory_p0_scorecard, +from src.services.gitea_cicd_alert_receipt_chain_readback import ( + load_latest_gitea_cicd_alert_receipt_chain_readback, ) from src.services.gitea_full_server_backup_dr_receipts import ( load_latest_gitea_full_server_backup_dr_receipts, ) -from src.services.gitea_cicd_alert_receipt_chain_readback import ( - load_latest_gitea_cicd_alert_receipt_chain_readback, +from src.services.gitea_private_inventory_p0_scorecard import ( + load_latest_gitea_private_inventory_p0_scorecard, ) from src.services.gitea_repo_bundle_backup_readback import ( load_latest_gitea_repo_bundle_backup_readback, @@ -84,6 +84,19 @@ _REQUIRED_OPTIMIZATION_IDS = { "dedupe_cancel_stale_run_guard", } +_REQUIRED_PROFESSIONAL_CI_CD_PRACTICE_IDS = { + "gitea_125_actions_runner_trust", + "gitea_125_secret_variable_redaction", + "gitea_125_webhook_hmac_delivery", + "gitea_125_packages_release_registry", + "gitea_125_actions_compatibility_gap_guard", + "dora_delivery_metrics_receipts", + "slsa_build_provenance_isolation", + "gitops_argocd_automated_sync_contract", + "otel_cicd_pipeline_telemetry", + "artifact_cache_sbom_retention", +} + def load_latest_gitea_capability_ci_cd_optimization_readback( *, @@ -94,9 +107,7 @@ def load_latest_gitea_capability_ci_cd_optimization_readback( root = repo_root or resolve_repo_root(Path(__file__)) operations = operations_dir or default_operations_dir(Path(__file__)) payload = build_gitea_capability_ci_cd_optimization_readback( - dev_prod_repo_readback=_load_json( - operations / _DEV_PROD_REPO_READBACK_FILE - ), + dev_prod_repo_readback=_load_json(operations / _DEV_PROD_REPO_READBACK_FILE), private_inventory=load_latest_gitea_private_inventory_p0_scorecard(), workflow_runner_health=load_latest_gitea_workflow_runner_health(), workflow_source_inventory=build_gitea_workflow_source_inventory(root), @@ -128,6 +139,7 @@ def build_gitea_workflow_source_inventory(repo_root: Path) -> dict[str, Any]: for path in workflow_files: text = path.read_text(encoding="utf-8", errors="replace") + lowered_text = text.lower() relative = path.relative_to(repo_root).as_posix() secret_refs = sorted(set(re.findall(r"\bsecrets\.([A-Z0-9_]+)", text))) vars_refs = sorted(set(re.findall(r"\bvars\.([A-Z0-9_]+)", text))) @@ -149,10 +161,51 @@ def build_gitea_workflow_source_inventory(repo_root: Path) -> dict[str, Any]: "vars_reference_count": vars_ref_count, "referenced_variable_names": vars_refs, "declared_runner_labels": _extract_runner_labels(text), + "declares_concurrency": _has_yaml_key(text, "concurrency"), + "declares_permissions": _has_yaml_key(text, "permissions"), + "declares_timeout_minutes": _has_yaml_key(text, "timeout-minutes"), + "declares_continue_on_error": _has_yaml_key(text, "continue-on-error"), + "declares_environment": _has_yaml_key(text, "environment"), + "uses_otel": "otel_" in lowered_text or "opentelemetry" in lowered_text, + "uses_artifact_transfer": ( + "upload-artifact" in lowered_text + or "download-artifact" in lowered_text + ), + "uses_cache_volume_or_action": ( + "actions/cache" in lowered_text + or "venv-cache" in lowered_text + or "npm-cache" in lowered_text + or "pip-cache" in lowered_text + ), + "uses_sbom_or_provenance": any( + marker in lowered_text + for marker in ("sbom", "provenance", "cosign", "syft", "trivy") + ), + "uses_registry_or_package_channel": ( + "registry.wooo.work" in text + or "harbor" in lowered_text + or "/packages/" in lowered_text + ), + "uses_argocd_gitops": "argocd" in lowered_text, + "uses_notification_or_webhook": ( + "telegram" in lowered_text or "webhook" in lowered_text + ), } ) workflow_ids = sorted(row["workflow_id"] for row in rows) + gitea_ignored_syntax_keys = { + "declares_concurrency", + "declares_permissions", + "declares_timeout_minutes", + "declares_continue_on_error", + "declares_environment", + } + gitea_ignored_syntax_workflow_ids = sorted( + row["workflow_id"] + for row in rows + if any(row.get(key) is True for key in gitea_ignored_syntax_keys) + ) return { "source_ref": ".gitea/workflows", "source_present": workflow_dir.is_dir(), @@ -210,6 +263,27 @@ def build_gitea_workflow_source_inventory(repo_root: Path) -> dict[str, Any]: "has_agent_market_watch": "agent-market-watch" in workflow_ids, "has_ai_technology_watch": "ai-technology-watch" in workflow_ids, }, + "professional_feature_rollups": { + "gitea_ignored_syntax_workflow_count": len( + gitea_ignored_syntax_workflow_ids + ), + "gitea_ignored_syntax_workflow_ids": gitea_ignored_syntax_workflow_ids, + "otel_workflow_count": _count_where(rows, "uses_otel"), + "artifact_transfer_workflow_count": _count_where( + rows, "uses_artifact_transfer" + ), + "cache_workflow_count": _count_where(rows, "uses_cache_volume_or_action"), + "sbom_or_provenance_workflow_count": _count_where( + rows, "uses_sbom_or_provenance" + ), + "registry_or_package_channel_workflow_count": _count_where( + rows, "uses_registry_or_package_channel" + ), + "argocd_gitops_workflow_count": _count_where(rows, "uses_argocd_gitops"), + "notification_or_webhook_workflow_count": _count_where( + rows, "uses_notification_or_webhook" + ), + }, } @@ -248,6 +322,9 @@ def build_gitea_capability_ci_cd_optimization_readback( alert_chain = _dict(gitea_cicd_alert_receipt_chain_readback) alert_chain_ready = alert_chain.get("ready") is True alert_chain_summary = _dict(alert_chain.get("summary")) + professional_feature_rollups = _dict( + workflow_source_inventory.get("professional_feature_rollups") + ) full_dr_non_restore_gap_ids = [ gap for gap in full_dr_gap_ids if gap != "restore_drill_approval_gate" ] @@ -257,7 +334,9 @@ def build_gitea_capability_ci_cd_optimization_readback( ) full_dr_verified_ready = full_dr.get("ready") is True - all_repos_have_refs = dev_prod_summary.get("all_expected_product_repos_have_ssh_refs") is True + all_repos_have_refs = ( + dev_prod_summary.get("all_expected_product_repos_have_ssh_refs") is True + ) all_repos_have_dev_main = ( dev_prod_summary.get("all_expected_product_repos_have_dev_and_main") is True ) @@ -335,7 +414,9 @@ def build_gitea_capability_ci_cd_optimization_readback( ], "preserve SSH/authenticated truth before creating or replacing repos", { - "public_visible_count": _int(dev_prod_summary.get("public_visible_count")), + "public_visible_count": _int( + dev_prod_summary.get("public_visible_count") + ), "private_or_auth_only_count": _int( dev_prod_summary.get("private_or_auth_only_count") ), @@ -345,7 +426,9 @@ def build_gitea_capability_ci_cd_optimization_readback( "pull_request_code_review", "collaboration", "Pull requests and code review", - "partially_ready" if known_workflows.get("has_code_review") else "action_required", + "partially_ready" + if known_workflows.get("has_code_review") + else "action_required", "code-review workflow exists; repository PR policy still needs live inventory", [ ".gitea/workflows/code-review.yaml", @@ -741,22 +824,294 @@ def build_gitea_capability_ci_cd_optimization_readback( ), _optimization( "dedupe_cancel_stale_run_guard", - "source_ready" if known_workflows.get("has_cd_pipeline") else "design_required", + "source_ready" + if known_workflows.get("has_cd_pipeline") + else "design_required", "Implement Gitea-compatible stale-run dedupe guards", "CD source includes stale-run guard; live receipt wiring is the remaining work" if known_workflows.get("has_cd_pipeline") else "Gitea Actions compatibility requires workflow/runner-side guards for some controls", - [".gitea/workflows/cd.yaml", "ops/runner/read-public-gitea-actions-queue.py"], + [ + ".gitea/workflows/cd.yaml", + "ops/runner/read-public-gitea-actions-queue.py", + ], "wire stale-run guard receipts into the Gitea capability readback" if known_workflows.get("has_cd_pipeline") else "add lease/marker based cancel-stale semantics instead of relying on unsupported syntax", p0=True, ), ] + professional_practices = [ + _professional_practice( + "gitea_125_actions_runner_trust", + "Gitea 1.25 Actions", + "verified_ready" + if workflow_source_count > 0 and not runner_action_ids + else "action_required", + "Trusted act runners, explicit labels, and non-110 isolation", + "workflow source and runner-label contracts are aligned" + if workflow_source_count > 0 and not runner_action_ids + else "runner trust and label ownership need closure before expansion", + [ + ".gitea/workflows", + "GET /api/v1/agents/gitea-workflow-runner-health", + "https://docs.gitea.com/1.25/usage/actions/overview", + "https://docs.gitea.com/1.25/usage/actions/act-runner", + ], + "keep trusted labels and runner health fresh; evaluate ephemeral runners for broader shared-runner lanes", + { + "workflow_source_file_count": workflow_source_count, + "runner_contracts_requiring_action": runner_action_ids, + }, + p0=True, + ), + _professional_practice( + "gitea_125_secret_variable_redaction", + "Gitea 1.25 Actions", + "source_ready" + if _int(secret_rollups.get("secret_reference_count")) > 0 + and secret_rollups.get("secret_values_collected") is False + else "authenticated_inventory_required", + "Secret and variable metadata inventory without value collection", + "workflow references are inventoried and secret values remain unread" + if _int(secret_rollups.get("secret_reference_count")) > 0 + else "configured Gitea secret and variable names still need redacted inventory", + [ + ".gitea/workflows", + "GET /api/v1/agents/gitea-actions-secret-variable-name-inventory", + "https://docs.gitea.com/1.25/usage/actions/secrets", + "https://docs.gitea.com/1.25/usage/actions/actions-variables", + ], + "verify presence and scope using redacted name-only metadata; never read secret values", + secret_rollups, + p0=True, + ), + _professional_practice( + "gitea_125_webhook_hmac_delivery", + "Gitea 1.25 Repository", + "source_ready" + if quiet_notification_count > 0 and alert_chain_ready + else "action_required", + "Webhook HMAC delivery and notification receipt chain", + "notification policy and alert receipt chain are ready" + if quiet_notification_count > 0 and alert_chain_ready + else "webhook delivery receipts need live closure", + [ + "GET /api/v1/agents/gitea-cicd-alert-receipt-chain-readback", + "GET /api/v1/agents/backup-notification-policy", + "https://docs.gitea.com/1.25/usage/repository/webhooks", + ], + "bind Gitea webhook delivery ID, signature verification result, and Telegram/AwoooP receipt stage into one readback", + { + "quiet_notification_policy_count": quiet_notification_count, + "alert_receipt_chain_ready": alert_chain_ready, + }, + ), + _professional_practice( + "gitea_125_packages_release_registry", + "Gitea 1.25 Packages", + "controlled_apply_candidate" + if _int( + professional_feature_rollups.get( + "registry_or_package_channel_workflow_count" + ) + ) + > 0 + else "action_required", + "Package registry, release artifacts, and rollback artifacts", + "registry channel is present, but Gitea package/release promotion is not standardized" + if _int( + professional_feature_rollups.get( + "registry_or_package_channel_workflow_count" + ) + ) + > 0 + else "package and release artifact channel is missing from workflow source", + [ + ".gitea/workflows/cd.yaml", + "GET /api/v1/agents/package-supply-chain-inventory", + "https://docs.gitea.com/1.25/usage/packages/overview", + ], + "standardize package/release promotion, rollback artifact naming, and package event receipts per product", + { + "registry_or_package_channel_workflow_count": _int( + professional_feature_rollups.get( + "registry_or_package_channel_workflow_count" + ) + ) + }, + ), + _professional_practice( + "gitea_125_actions_compatibility_gap_guard", + "Gitea 1.25 Actions", + "source_ready" + if known_workflows.get("has_cd_pipeline") + and _int( + professional_feature_rollups.get("gitea_ignored_syntax_workflow_count") + ) + > 0 + else "implemented", + "Gitea-compatible guards for ignored Actions syntax", + "workflow source detects ignored syntax and uses lease/marker/readback guards for critical CD semantics" + if _int( + professional_feature_rollups.get("gitea_ignored_syntax_workflow_count") + ) + > 0 + else "no ignored syntax detected in committed workflows", + [ + ".gitea/workflows/cd.yaml", + "ops/runner/read-public-gitea-actions-queue.py", + "https://docs.gitea.com/1.25/usage/actions/comparison", + ], + "keep unsupported syntax detection visible and prefer Gitea-compatible receipt guards over copied YAML semantics", + { + "gitea_ignored_syntax_workflow_count": _int( + professional_feature_rollups.get( + "gitea_ignored_syntax_workflow_count" + ) + ), + "gitea_ignored_syntax_workflow_ids": _strings( + professional_feature_rollups.get( + "gitea_ignored_syntax_workflow_ids" + ) + ), + }, + p0=True, + ), + _professional_practice( + "dora_delivery_metrics_receipts", + "DORA", + "action_required", + "Five-metric delivery performance readback", + "deploy markers and alert receipts exist, but DORA trend metrics are not yet computed per product", + [ + "GET /api/v1/agents/p0-cicd-baseline-source-readiness", + "GET /api/v1/agents/gitea-cicd-alert-receipt-chain-readback", + "https://dora.dev/guides/dora-metrics/", + ], + "derive change lead time, deployment frequency, failed deployment recovery time, change fail rate, and deployment rework rate from Gitea/CD receipts", + ), + _professional_practice( + "slsa_build_provenance_isolation", + "OpenSSF SLSA", + "action_required" + if _int( + professional_feature_rollups.get("sbom_or_provenance_workflow_count") + ) + == 0 + else "source_ready", + "Build provenance, digest identity, and isolated build controls", + "workflow source does not yet emit SLSA provenance/SBOM receipts" + if _int( + professional_feature_rollups.get("sbom_or_provenance_workflow_count") + ) + == 0 + else "workflow source contains provenance/SBOM markers", + [ + "GET /api/v1/agents/package-supply-chain-inventory", + "https://slsa.dev/spec/v1.2/build-requirements", + ], + "add digest-bound provenance/SBOM output and runner isolation evidence to build artifacts", + { + "sbom_or_provenance_workflow_count": _int( + professional_feature_rollups.get( + "sbom_or_provenance_workflow_count" + ) + ) + }, + ), + _professional_practice( + "gitops_argocd_automated_sync_contract", + "GitOps / Argo CD", + "source_ready" + if _int(professional_feature_rollups.get("argocd_gitops_workflow_count")) + > 0 + else "action_required", + "Git commit driven deployment and Argo CD sync/readback", + "CD workflow uses ArgoCD GitOps and deploy-marker readback", + [ + ".gitea/workflows/cd.yaml", + "GET /api/v1/agents/p0-cicd-baseline-source-readiness", + "https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/", + ], + "keep Argo sync, health, revision, deploy marker, and smoke readback tied to the same commit", + { + "argocd_gitops_workflow_count": _int( + professional_feature_rollups.get("argocd_gitops_workflow_count") + ) + }, + p0=True, + ), + _professional_practice( + "otel_cicd_pipeline_telemetry", + "OpenTelemetry", + "source_ready" + if _int(professional_feature_rollups.get("otel_workflow_count")) > 0 + else "action_required", + "CI/CD pipeline telemetry and low-cardinality result tags", + "workflow source exports OTEL service/resource attributes for CD lanes", + [ + ".gitea/workflows/cd.yaml", + ".gitea/workflows/cd-dev.yaml", + ".gitea/workflows/e2e-health.yaml", + "https://opentelemetry.io/docs/specs/semconv/cicd/cicd-spans/", + ], + "bind pipeline result, run id, commit, environment, and error type into CI/CD span/readback receipts", + { + "otel_workflow_count": _int( + professional_feature_rollups.get("otel_workflow_count") + ) + }, + ), + _professional_practice( + "artifact_cache_sbom_retention", + "CI/CD Artifact Governance", + "action_required" + if _int( + professional_feature_rollups.get("artifact_transfer_workflow_count") + ) + == 0 + or _int( + professional_feature_rollups.get("sbom_or_provenance_workflow_count") + ) + == 0 + else "source_ready", + "Cache keys, artifact retention, SBOM, and receipt retention", + "workflow source still lacks unified artifact/SBOM retention receipts" + if _int( + professional_feature_rollups.get("artifact_transfer_workflow_count") + ) + == 0 + or _int( + professional_feature_rollups.get("sbom_or_provenance_workflow_count") + ) + == 0 + else "workflow source contains artifact and SBOM/provenance markers", + [ + ".gitea/workflows", + "GET /api/v1/agents/gitea-workflow-runner-health", + ], + "standardize artifact upload, cache keys, SBOM/provenance naming, and retention windows across workflows", + { + "artifact_transfer_workflow_count": _int( + professional_feature_rollups.get("artifact_transfer_workflow_count") + ), + "cache_workflow_count": _int( + professional_feature_rollups.get("cache_workflow_count") + ), + "sbom_or_provenance_workflow_count": _int( + professional_feature_rollups.get( + "sbom_or_provenance_workflow_count" + ) + ), + }, + ), + ] rollups = _build_rollups( capabilities=capabilities, optimizations=optimizations, + professional_practices=professional_practices, dev_prod_summary=dev_prod_summary, workflow_source_count=workflow_source_count, workflow_contract_count=workflow_contract_count, @@ -820,17 +1175,26 @@ def build_gitea_capability_ci_cd_optimization_readback( "official_gitea_feature_refs": [ "https://docs.gitea.com/1.25/usage/access-control/permissions", "https://docs.gitea.com/1.25/usage/actions/overview", + "https://docs.gitea.com/1.25/usage/actions/act-runner", + "https://docs.gitea.com/1.25/usage/actions/comparison", "https://docs.gitea.com/1.25/usage/actions/secrets", "https://docs.gitea.com/1.25/usage/actions/actions-variables", "https://docs.gitea.com/1.25/usage/packages/overview", "https://docs.gitea.com/1.25/usage/repository/webhooks", ], + "market_practice_basis_refs": [ + "https://dora.dev/guides/dora-metrics/", + "https://slsa.dev/spec/v1.2/build-requirements", + "https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/", + "https://opentelemetry.io/docs/specs/semconv/cicd/cicd-spans/", + ], "safe_next_actions": _safe_next_actions(capabilities, optimizations), }, "rollups": rollups, "workflow_source_inventory": workflow_source_inventory, "capability_rows": capabilities, "ci_cd_optimization_rows": optimizations, + "professional_ci_cd_practice_rows": professional_practices, } _require_payload(payload, "gitea_capability_ci_cd_optimization_readback") return payload @@ -884,10 +1248,36 @@ def _optimization( } +def _professional_practice( + practice_id: str, + framework: str, + status_value: str, + title: str, + usage_state: str, + evidence_refs: list[str], + next_action: str, + metrics: dict[str, Any] | None = None, + *, + p0: bool = False, +) -> dict[str, Any]: + return { + "id": practice_id, + "framework": framework, + "status": status_value, + "priority": "P0" if p0 else "P1", + "title": title, + "usage_state": usage_state, + "evidence_refs": evidence_refs, + "next_action": next_action, + "metrics": metrics or {}, + } + + def _build_rollups( *, capabilities: list[dict[str, Any]], optimizations: list[dict[str, Any]], + professional_practices: list[dict[str, Any]], dev_prod_summary: dict[str, Any], workflow_source_count: int, workflow_contract_count: int, @@ -895,8 +1285,28 @@ def _build_rollups( ) -> dict[str, Any]: capability_status = _count_by(capabilities, "status") optimization_status = _count_by(optimizations, "status") + professional_status = _count_by(professional_practices, "status") p0_capabilities = [row for row in capabilities if row.get("priority") == "P0"] p0_optimizations = [row for row in optimizations if row.get("priority") == "P0"] + p0_professional_practices = [ + row for row in professional_practices if row.get("priority") == "P0" + ] + ready_statuses = {"verified_ready", "implemented", "source_ready"} + capability_gap_ids = [ + str(row.get("id")) + for row in capabilities + if row.get("status") not in ready_statuses + ] + optimization_gap_ids = [ + str(row.get("id")) + for row in optimizations + if row.get("status") not in ready_statuses + ] + professional_gap_ids = [ + str(row.get("id")) + for row in professional_practices + if row.get("status") not in ready_statuses + ] return { "capability_count": len(capabilities), "capability_status_counts": capability_status, @@ -917,6 +1327,21 @@ def _build_rollups( ), "p0_optimization_count": len(p0_optimizations), "p0_optimization_gap_count": _gap_count(p0_optimizations), + "professional_ci_cd_practice_count": len(professional_practices), + "professional_ci_cd_practice_status_counts": professional_status, + "professional_ci_cd_practice_gap_count": len(professional_gap_ids), + "professional_ci_cd_practice_gap_ids": professional_gap_ids, + "p0_professional_ci_cd_practice_count": len(p0_professional_practices), + "p0_professional_ci_cd_practice_gap_count": _gap_count( + p0_professional_practices + ), + "market_professional_ci_cd_gap_count": len(professional_gap_ids), + "gitea_feature_utilization_gap_ids": sorted( + set(capability_gap_ids + optimization_gap_ids + professional_gap_ids) + ), + "all_gitea_features_fully_utilized": not ( + capability_gap_ids or optimization_gap_ids or professional_gap_ids + ), "expected_product_count": _int(dev_prod_summary.get("expected_product_count")), "ssh_verified_product_repo_count": _int( dev_prod_summary.get("ssh_repo_present_count") @@ -939,13 +1364,9 @@ def _safe_next_actions( row for row in capabilities + optimizations if row.get("priority") == "P0" - and row.get("status") - not in {"verified_ready", "implemented", "source_ready"} - ] - return [ - f"{row['id']}: {row['next_action']}" - for row in rows[:8] + and row.get("status") not in {"verified_ready", "implemented", "source_ready"} ] + return [f"{row['id']}: {row['next_action']}" for row in rows[:8]] def _require_payload(payload: dict[str, Any], label: str) -> None: @@ -973,26 +1394,45 @@ def _require_payload(payload: dict[str, Any], label: str) -> None: flag for flag in blocked_true_flags if boundaries.get(flag) is not False ) if unexpected: - raise ValueError(f"{label}: blocked operation flags must be false: {unexpected}") + raise ValueError( + f"{label}: blocked operation flags must be false: {unexpected}" + ) capabilities = _list(payload.get("capability_rows")) optimizations = _list(payload.get("ci_cd_optimization_rows")) - capability_ids = {str(row.get("id") or "") for row in capabilities if isinstance(row, dict)} + professional_practices = _list(payload.get("professional_ci_cd_practice_rows")) + capability_ids = { + str(row.get("id") or "") for row in capabilities if isinstance(row, dict) + } optimization_ids = { str(row.get("id") or "") for row in optimizations if isinstance(row, dict) } + professional_practice_ids = { + str(row.get("id") or "") + for row in professional_practices + if isinstance(row, dict) + } missing_capabilities = sorted(_REQUIRED_CAPABILITY_IDS - capability_ids) missing_optimizations = sorted(_REQUIRED_OPTIMIZATION_IDS - optimization_ids) + missing_professional_practices = sorted( + _REQUIRED_PROFESSIONAL_CI_CD_PRACTICE_IDS - professional_practice_ids + ) if missing_capabilities: raise ValueError(f"{label}: missing capability rows: {missing_capabilities}") if missing_optimizations: raise ValueError(f"{label}: missing optimization rows: {missing_optimizations}") + if missing_professional_practices: + raise ValueError( + f"{label}: missing professional CI/CD practice rows: {missing_professional_practices}" + ) rollups = _dict(payload.get("rollups")) if rollups.get("capability_count") != len(capabilities): raise ValueError(f"{label}: capability_count mismatch") if rollups.get("ci_cd_optimization_count") != len(optimizations): raise ValueError(f"{label}: ci_cd_optimization_count mismatch") + if rollups.get("professional_ci_cd_practice_count") != len(professional_practices): + raise ValueError(f"{label}: professional_ci_cd_practice_count mismatch") if rollups.get("capability_status_counts") != _count_by(capabilities, "status"): raise ValueError(f"{label}: capability_status_counts mismatch") if rollups.get("ci_cd_optimization_status_counts") != _count_by( @@ -1000,6 +1440,11 @@ def _require_payload(payload: dict[str, Any], label: str) -> None: "status", ): raise ValueError(f"{label}: ci_cd_optimization_status_counts mismatch") + if rollups.get("professional_ci_cd_practice_status_counts") != _count_by( + professional_practices, + "status", + ): + raise ValueError(f"{label}: professional_ci_cd_practice_status_counts mismatch") _require_no_plaintext_secret_keys(payload, label) @@ -1018,7 +1463,9 @@ def _require_no_plaintext_secret_keys(value: Any, label: str, path: str = "$") - for key, child in value.items(): lowered = str(key).lower() if lowered in forbidden or lowered.endswith("_secret_value"): - raise ValueError(f"{label}: forbidden plaintext-like key at {path}.{key}") + raise ValueError( + f"{label}: forbidden plaintext-like key at {path}.{key}" + ) _require_no_plaintext_secret_keys(child, label, f"{path}.{key}") elif isinstance(value, list): for index, child in enumerate(value): diff --git a/apps/api/tests/test_gitea_capability_ci_cd_optimization_readback_api.py b/apps/api/tests/test_gitea_capability_ci_cd_optimization_readback_api.py index 53ebeb553..1a3fb252c 100644 --- a/apps/api/tests/test_gitea_capability_ci_cd_optimization_readback_api.py +++ b/apps/api/tests/test_gitea_capability_ci_cd_optimization_readback_api.py @@ -1,5 +1,6 @@ from __future__ import annotations +# ruff: noqa: E402 import json import os from pathlib import Path @@ -140,8 +141,7 @@ def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None: payload = _readback_payload() assert ( - payload["schema_version"] - == "gitea_capability_ci_cd_optimization_readback_v1" + payload["schema_version"] == "gitea_capability_ci_cd_optimization_readback_v1" ) assert payload["source_control_authority"] == "gitea" assert payload["status"] == "gitea_capability_ci_cd_optimization_ready" @@ -149,12 +149,29 @@ def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None: assert payload["rollups"]["ssh_verified_product_repo_count"] == 12 assert payload["rollups"]["dev_prod_environment_split_ready_count"] == 12 assert payload["workflow_source_inventory"]["workflow_file_count"] == 12 - assert payload["workflow_source_inventory"]["secret_variable_rollups"][ - "secret_values_collected" - ] is False - assert payload["workflow_source_inventory"]["secret_variable_rollups"][ - "secret_reference_count" - ] > 0 + professional_feature_rollups = payload["workflow_source_inventory"][ + "professional_feature_rollups" + ] + assert professional_feature_rollups["gitea_ignored_syntax_workflow_count"] == 9 + assert professional_feature_rollups["otel_workflow_count"] == 3 + assert professional_feature_rollups["argocd_gitops_workflow_count"] == 1 + assert ( + professional_feature_rollups["registry_or_package_channel_workflow_count"] == 4 + ) + assert professional_feature_rollups["sbom_or_provenance_workflow_count"] == 0 + assert professional_feature_rollups["artifact_transfer_workflow_count"] == 0 + assert ( + payload["workflow_source_inventory"]["secret_variable_rollups"][ + "secret_values_collected" + ] + is False + ) + assert ( + payload["workflow_source_inventory"]["secret_variable_rollups"][ + "secret_reference_count" + ] + > 0 + ) assert ( len( payload["workflow_source_inventory"]["secret_variable_rollups"][ @@ -166,22 +183,27 @@ def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None: capability_by_id = {row["id"]: row for row in payload["capability_rows"]} assert capability_by_id["code_git_source_control"]["status"] == "verified_ready" - assert ( - capability_by_id["dev_prod_environment_split"]["status"] - == "verified_ready" - ) + assert capability_by_id["dev_prod_environment_split"]["status"] == "verified_ready" assert capability_by_id["gitea_actions_workflows"]["status"] == "verified_ready" assert capability_by_id["gitea_actions_workflows"]["metrics"] == { "workflow_source_file_count": 12, "workflow_contract_count": 12, } assert capability_by_id["gitea_actions_runners"]["status"] == "verified_ready" - assert capability_by_id["branch_protection_required_checks"]["status"] == "source_ready" + assert ( + capability_by_id["branch_protection_required_checks"]["status"] + == "source_ready" + ) assert capability_by_id["backup_dr_restore"]["status"] == "source_ready" - assert capability_by_id["backup_dr_restore"]["metrics"][ - "gitea_full_dr_source_ready_component_count" - ] == 6 - assert capability_by_id["observability_alert_receipts"]["status"] == "verified_ready" + assert ( + capability_by_id["backup_dr_restore"]["metrics"][ + "gitea_full_dr_source_ready_component_count" + ] + == 6 + ) + assert ( + capability_by_id["observability_alert_receipts"]["status"] == "verified_ready" + ) assert capability_by_id["observability_alert_receipts"]["metrics"] == { "alert_receipt_chain_ready": True, "alert_receipt_chain_status": "gitea_cicd_alert_receipt_chain_ready", @@ -198,29 +220,22 @@ def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None: def test_gitea_capability_readback_prioritizes_professional_cicd_gaps() -> None: payload = _readback_payload() - optimization_by_id = { - row["id"]: row for row in payload["ci_cd_optimization_rows"] - } + optimization_by_id = {row["id"]: row for row in payload["ci_cd_optimization_rows"]} assert ( - optimization_by_id["gitea_actions_source_authority"]["status"] - == "implemented" + optimization_by_id["gitea_actions_source_authority"]["status"] == "implemented" ) assert ( optimization_by_id["dev_prod_branch_promotion_contract"]["status"] == "implemented" ) - assert ( - optimization_by_id["workflow_contract_freshness"]["status"] - == "implemented" - ) + assert optimization_by_id["workflow_contract_freshness"]["status"] == "implemented" assert ( optimization_by_id["runner_label_isolation_attestation"]["status"] == "implemented" ) assert ( - optimization_by_id["dedupe_cancel_stale_run_guard"]["status"] - == "source_ready" + optimization_by_id["dedupe_cancel_stale_run_guard"]["status"] == "source_ready" ) assert ( optimization_by_id["branch_protection_required_checks"]["status"] @@ -234,6 +249,56 @@ def test_gitea_capability_readback_prioritizes_professional_cicd_gaps() -> None: payload["ci_cd_optimization_rows"] ) assert payload["rollups"]["p0_optimization_gap_count"] == 0 + assert payload["rollups"]["professional_ci_cd_practice_count"] == len( + payload["professional_ci_cd_practice_rows"] + ) + assert payload["rollups"]["p0_professional_ci_cd_practice_gap_count"] == 0 + assert payload["rollups"]["market_professional_ci_cd_gap_count"] == 5 + assert payload["rollups"]["all_gitea_features_fully_utilized"] is False + assert payload["rollups"]["professional_ci_cd_practice_gap_ids"] == [ + "gitea_125_webhook_hmac_delivery", + "gitea_125_packages_release_registry", + "dora_delivery_metrics_receipts", + "slsa_build_provenance_isolation", + "artifact_cache_sbom_retention", + ] + + practice_by_id = { + row["id"]: row for row in payload["professional_ci_cd_practice_rows"] + } + assert ( + practice_by_id["gitea_125_actions_runner_trust"]["status"] == "verified_ready" + ) + assert ( + practice_by_id["gitea_125_secret_variable_redaction"]["status"] + == "source_ready" + ) + assert ( + practice_by_id["gitea_125_actions_compatibility_gap_guard"]["status"] + == "source_ready" + ) + assert ( + practice_by_id["gitops_argocd_automated_sync_contract"]["status"] + == "source_ready" + ) + assert practice_by_id["otel_cicd_pipeline_telemetry"]["status"] == "source_ready" + assert ( + practice_by_id["dora_delivery_metrics_receipts"]["status"] == "action_required" + ) + assert ( + practice_by_id["slsa_build_provenance_isolation"]["status"] == "action_required" + ) + assert ( + practice_by_id["artifact_cache_sbom_retention"]["status"] == "action_required" + ) + assert ( + "https://dora.dev/guides/dora-metrics/" + in payload["readback"]["market_practice_basis_refs"] + ) + assert ( + "https://slsa.dev/spec/v1.2/build-requirements" + in payload["readback"]["market_practice_basis_refs"] + ) assert payload["status"] == "gitea_capability_ci_cd_optimization_ready" assert payload["ready"] is True assert payload["active_p0_gap_ids"] == [] @@ -250,12 +315,13 @@ def test_gitea_capability_readback_endpoint_returns_payload(monkeypatch) -> None app.include_router(router, prefix="/api/v1") client = TestClient(app) - response = client.get( - "/api/v1/agents/gitea-capability-ci-cd-optimization-readback" - ) + response = client.get("/api/v1/agents/gitea-capability-ci-cd-optimization-readback") assert response.status_code == 200 data = response.json() assert data["schema_version"] == "gitea_capability_ci_cd_optimization_readback_v1" assert data["rollups"]["capability_count"] == len(data["capability_rows"]) + assert data["rollups"]["professional_ci_cd_practice_count"] == len( + data["professional_ci_cd_practice_rows"] + ) assert data["rollups"]["workflow_contract_fresh"] is True