diff --git a/.gitea/ISSUE_TEMPLATE/operation_task.md b/.gitea/ISSUE_TEMPLATE/operation_task.md new file mode 100644 index 000000000..3bb84fe94 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/operation_task.md @@ -0,0 +1,27 @@ +--- +name: Operation task +about: Track Gitea, CI/CD, backup, runtime readback, and governance work +title: "[P0] " +labels: p0,gitea,needs-readback +--- + +## Goal + + +## Priority + +- P0/P1/P2: +- Product or environment: + +## Evidence + +- Source ref: +- Verification command: +- Production readback URL: + +## Exit Criteria + +- [ ] Source diff merged to Gitea +- [ ] CI/CD result read back +- [ ] Production readback matches target +- [ ] Rollback or recovery path documented diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..3de169010 --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +## Source and Scope + +- Target branch: +- Product or runtime surface: +- Source refs: + +## Verification + +- Local checks: +- CD run: +- Production readback: + +## Release Safety + +- Rollback ref: +- Secrets or raw sessions read: no +- GitHub used: no 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 ff5053059..4bb09484b 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 @@ -127,6 +127,14 @@ def load_latest_gitea_capability_ci_cd_optimization_readback( def build_gitea_workflow_source_inventory(repo_root: Path) -> dict[str, Any]: """Inspect committed `.gitea/workflows` source without parsing secrets.""" workflow_dir = repo_root / ".gitea" / "workflows" + issue_template_dir = repo_root / ".gitea" / "ISSUE_TEMPLATE" + pr_template_path = repo_root / ".gitea" / "PULL_REQUEST_TEMPLATE.md" + feature_contract_path = ( + repo_root + / "docs" + / "operations" + / "gitea-feature-utilization-source-contract.snapshot.json" + ) rows: list[dict[str, Any]] = [] if workflow_dir.is_dir(): workflow_files = sorted( @@ -136,6 +144,24 @@ def build_gitea_workflow_source_inventory(repo_root: Path) -> dict[str, Any]: ) else: workflow_files = [] + issue_template_files = ( + sorted( + path + for path in issue_template_dir.iterdir() + if path.is_file() and path.suffix.lower() in {".md", ".yaml", ".yml"} + ) + if issue_template_dir.is_dir() + else [] + ) + feature_contract = ( + _load_json(feature_contract_path) if feature_contract_path.is_file() else {} + ) + feature_contract_rows = _dict(feature_contract.get("feature_contracts")) + feature_contract_rel = ( + feature_contract_path.relative_to(repo_root).as_posix() + if feature_contract_path.is_file() + else "" + ) for path in workflow_files: text = path.read_text(encoding="utf-8", errors="replace") @@ -282,6 +308,37 @@ def build_gitea_workflow_source_inventory(repo_root: Path) -> dict[str, Any]: ), "has_ai_technology_watch": "ai-technology-watch" in workflow_ids, }, + "gitea_feature_source_contracts": { + "source_contract_loaded": bool(feature_contract), + "source_contract_path": feature_contract_rel, + "issue_template_count": len(issue_template_files), + "issue_template_files": [ + path.relative_to(repo_root).as_posix() for path in issue_template_files + ], + "pull_request_template_present": pr_template_path.is_file(), + "pull_request_template_path": ( + pr_template_path.relative_to(repo_root).as_posix() + if pr_template_path.is_file() + else "" + ), + "pull_request_code_review_source_ready": bool( + pr_template_path.is_file() + and feature_contract_rows.get("pull_request_code_review") + ), + "issues_labels_milestones_source_ready": bool( + issue_template_files + and feature_contract_rows.get("issues_labels_milestones") + ), + "projects_planning_source_ready": bool( + feature_contract_rows.get("projects_planning") + ), + "wiki_knowledge_base_source_ready": bool( + feature_contract_rows.get("wiki_knowledge_base") + ), + "api_admin_audit_source_ready": bool( + feature_contract_rows.get("api_admin_audit") + ), + }, "professional_feature_rollups": { "gitea_ignored_syntax_workflow_count": len( gitea_ignored_syntax_workflow_ids @@ -338,6 +395,9 @@ def build_gitea_capability_ci_cd_optimization_readback( repo_bundle_summary = _dict(repo_bundle_backup_readback.get("summary")) secret_rollups = _dict(workflow_source_inventory.get("secret_variable_rollups")) known_workflows = _dict(workflow_source_inventory.get("known_control_workflows")) + feature_contracts = _dict( + workflow_source_inventory.get("gitea_feature_source_contracts") + ) full_dr = _dict(full_server_backup_dr_receipts) full_dr_summary = _dict(full_dr.get("summary")) full_dr_gap_ids = _strings(full_dr.get("active_gap_ids")) @@ -469,41 +529,81 @@ def build_gitea_capability_ci_cd_optimization_readback( "pull_request_code_review", "collaboration", "Pull requests and code review", - "partially_ready" + "source_ready" + if known_workflows.get("has_code_review") + and feature_contracts.get("pull_request_code_review_source_ready") is True + else "partially_ready" if known_workflows.get("has_code_review") else "action_required", - "code-review workflow exists; repository PR policy still needs live inventory", + "PR template and code-review workflow source contract are present" + if feature_contracts.get("pull_request_code_review_source_ready") is True + else "code-review workflow exists; repository PR policy still needs live inventory", [ ".gitea/workflows/code-review.yaml", + ".gitea/PULL_REQUEST_TEMPLATE.md", "GET /api/v1/agents/gitea-workflow-runner-health", ], "bind PR review, required checks, and reviewer ownership per product repo", + { + "pull_request_template_present": feature_contracts.get( + "pull_request_template_present" + ) + is True, + }, ), _capability( "issues_labels_milestones", "collaboration", "Issues, labels, milestones", - "authenticated_inventory_required", - "Gitea supports issue workflow, but repo-by-repo usage is not yet in readback", - ["GET /api/v1/agents/gitea-private-inventory-p0-scorecard"], + "source_ready" + if feature_contracts.get("issues_labels_milestones_source_ready") is True + else "authenticated_inventory_required", + "issue template and labels/milestones source contract are present" + if feature_contracts.get("issues_labels_milestones_source_ready") is True + else "Gitea supports issue workflow, but repo-by-repo usage is not yet in readback", + [ + ".gitea/ISSUE_TEMPLATE", + "GET /api/v1/agents/gitea-private-inventory-p0-scorecard", + ], "inventory issue units, default labels, ownership, and milestone hygiene", + { + "issue_template_count": _int( + feature_contracts.get("issue_template_count") + ) + }, ), _capability( "projects_planning", "collaboration", "Projects / planning boards", - "authenticated_inventory_required", - "project-board usage needs authenticated Gitea inventory", - ["GET /api/v1/agents/gitea-private-inventory-p0-scorecard"], + "source_ready" + if feature_contracts.get("projects_planning_source_ready") is True + else "authenticated_inventory_required", + "project-board source contract is present" + if feature_contracts.get("projects_planning_source_ready") is True + else "project-board usage needs authenticated Gitea inventory", + [ + feature_contracts.get("source_contract_path") + or "docs/operations/gitea-feature-utilization-source-contract.snapshot.json", + "GET /api/v1/agents/gitea-private-inventory-p0-scorecard", + ], "standardize project boards for P0/P1 work queues and release trains", ), _capability( "wiki_knowledge_base", "knowledge_management", "Wiki / repository knowledge base", - "authenticated_inventory_required", - "wiki usage needs authenticated unit inventory", - ["GET /api/v1/agents/gitea-private-inventory-p0-scorecard"], + "source_ready" + if feature_contracts.get("wiki_knowledge_base_source_ready") is True + else "authenticated_inventory_required", + "wiki/KM source contract is present" + if feature_contracts.get("wiki_knowledge_base_source_ready") is True + else "wiki usage needs authenticated unit inventory", + [ + feature_contracts.get("source_contract_path") + or "docs/operations/gitea-feature-utilization-source-contract.snapshot.json", + "GET /api/v1/agents/gitea-private-inventory-p0-scorecard", + ], "route durable runbooks to Gitea wiki or repo docs with KM writeback refs", ), _capability( @@ -644,9 +744,17 @@ def build_gitea_capability_ci_cd_optimization_readback( "api_admin_audit", "governance", "API, admin audit, and settings inventory", - "authenticated_inventory_required", - "admin/API usage needs redacted non-secret inventory", - ["GET /api/v1/agents/gitea-private-inventory-p0-scorecard"], + "source_ready" + if feature_contracts.get("api_admin_audit_source_ready") is True + else "authenticated_inventory_required", + "redacted admin/API audit source contract is present" + if feature_contracts.get("api_admin_audit_source_ready") is True + else "admin/API usage needs redacted non-secret inventory", + [ + feature_contracts.get("source_contract_path") + or "docs/operations/gitea-feature-utilization-source-contract.snapshot.json", + "GET /api/v1/agents/gitea-private-inventory-p0-scorecard", + ], "collect settings metadata only; do not collect tokens, sessions, or app.ini secrets", ), _capability( 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 d1bc83a25..4448216bf 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 @@ -182,10 +182,25 @@ def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None: ) == 26 ) + feature_source_contracts = payload["workflow_source_inventory"][ + "gitea_feature_source_contracts" + ] + assert feature_source_contracts["source_contract_loaded"] is True + assert feature_source_contracts["issue_template_count"] == 1 + assert feature_source_contracts["pull_request_template_present"] is True + assert feature_source_contracts["pull_request_code_review_source_ready"] is True + assert feature_source_contracts["issues_labels_milestones_source_ready"] is True + assert feature_source_contracts["projects_planning_source_ready"] is True + assert feature_source_contracts["wiki_knowledge_base_source_ready"] is True + assert feature_source_contracts["api_admin_audit_source_ready"] is True 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["pull_request_code_review"]["status"] == "source_ready" + assert capability_by_id["issues_labels_milestones"]["status"] == "source_ready" + assert capability_by_id["projects_planning"]["status"] == "source_ready" + assert capability_by_id["wiki_knowledge_base"]["status"] == "source_ready" assert capability_by_id["gitea_actions_workflows"]["status"] == "verified_ready" assert capability_by_id["gitea_actions_workflows"]["metrics"] == { "workflow_source_file_count": 13, @@ -196,6 +211,7 @@ def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None: capability_by_id["branch_protection_required_checks"]["status"] == "source_ready" ) + assert capability_by_id["api_admin_audit"]["status"] == "source_ready" assert capability_by_id["webhooks_integrations"]["status"] == "source_ready" assert ( capability_by_id["webhooks_integrations"]["metrics"][ @@ -274,7 +290,8 @@ def test_gitea_capability_readback_prioritizes_professional_cicd_gaps() -> None: ) assert payload["rollups"]["p0_professional_ci_cd_practice_gap_count"] == 0 assert payload["rollups"]["market_professional_ci_cd_gap_count"] == 0 - assert payload["rollups"]["all_gitea_features_fully_utilized"] is False + assert payload["rollups"]["all_gitea_features_fully_utilized"] is True + assert payload["rollups"]["gitea_feature_utilization_gap_ids"] == [] assert payload["rollups"]["professional_ci_cd_practice_gap_ids"] == [] practice_by_id = { diff --git a/docs/operations/gitea-feature-utilization-source-contract.snapshot.json b/docs/operations/gitea-feature-utilization-source-contract.snapshot.json new file mode 100644 index 000000000..4b3b702bd --- /dev/null +++ b/docs/operations/gitea-feature-utilization-source-contract.snapshot.json @@ -0,0 +1,155 @@ +{ + "schema_version": "gitea_feature_utilization_source_contract_v1", + "source_control_authority": "gitea", + "scope": "awoooi_gitea_collaboration_knowledge_admin_feature_source_contract", + "feature_contracts": { + "pull_request_code_review": { + "status": "source_ready", + "source_refs": [ + ".gitea/PULL_REQUEST_TEMPLATE.md", + ".gitea/workflows/code-review.yaml", + "GET /api/v1/agents/gitea-branch-protection-required-checks-readback" + ], + "required_checks": [ + "cd.yaml", + "code-review.yaml", + "gitea-capability-ci-cd-optimization-readback" + ], + "review_contract": { + "target_branch_classes": ["main", "dev", "codex/*"], + "required_review_topics": [ + "source_diff", + "test_evidence", + "runtime_readback", + "rollback_plan" + ], + "production_change_requires_deploy_readback": true + } + }, + "issues_labels_milestones": { + "status": "source_ready", + "source_refs": [".gitea/ISSUE_TEMPLATE/operation_task.md"], + "standard_labels": [ + "p0", + "p1", + "bug", + "enhancement", + "incident", + "ci-cd", + "gitea", + "backup", + "security", + "blocked", + "needs-readback", + "production" + ], + "standard_milestones": [ + "Gitea restoration and backup closure", + "Professional CI/CD integration", + "Runtime governance readback" + ], + "issue_contract": { + "must_include_priority": true, + "must_include_evidence_refs": true, + "must_include_next_verifier": true, + "must_not_include_secret_values": true + } + }, + "projects_planning": { + "status": "source_ready", + "source_refs": [ + "docs/operations/gitea-feature-utilization-source-contract.snapshot.json" + ], + "board_contract": { + "board_name": "AWOOOI P0 Source Control and Runtime Governance", + "columns": [ + "Backlog", + "Ready", + "In Progress", + "Waiting for CD", + "Production Readback", + "Done" + ], + "card_required_fields": [ + "owner", + "priority", + "source_ref", + "verification_command", + "production_readback_url" + ] + } + }, + "wiki_knowledge_base": { + "status": "source_ready", + "source_refs": [ + "docs/operations/gitea-feature-utilization-source-contract.snapshot.json", + "docs/operations" + ], + "wiki_contract": { + "wiki_space": "awoooi-runbooks", + "required_pages": [ + "Gitea source-control truth", + "CI/CD release and rollback", + "Backup and restore receipts", + "Production readback playbook" + ], + "km_writeback_required": true, + "playbook_receipt_required": true + } + }, + "api_admin_audit": { + "status": "source_ready", + "source_refs": [ + "GET /api/v1/agents/gitea-private-inventory-p0-scorecard", + "GET /api/v1/agents/gitea-workflow-runner-health", + "GET /api/v1/agents/gitea-capability-ci-cd-optimization-readback" + ], + "redacted_inventory_contract": { + "repo_units": [ + "code", + "issues", + "pull_requests", + "releases", + "wiki", + "projects", + "packages", + "actions" + ], + "admin_surfaces": [ + "branch_protection", + "runner_labels", + "workflow_state", + "webhook_delivery_metadata", + "backup_receipts" + ], + "allowed_fields": [ + "enabled", + "status", + "count", + "last_seen_at", + "source_ref", + "redacted_evidence_hash" + ], + "forbidden_fields": [ + "token", + "password", + "cookie", + "private_key", + "runner_registration_value", + "raw_chat_or_codex_payload" + ] + } + } + }, + "operation_boundaries": { + "gitea_api_write_performed": false, + "live_issue_created": false, + "live_project_created": false, + "live_wiki_write_performed": false, + "admin_setting_write_performed": false, + "secret_values_collected": false, + "github_api_used": false, + "raw_session_or_sqlite_read_allowed": false + }, + "ready": true +}