772 lines
29 KiB
JSON
772 lines
29 KiB
JSON
{
|
||
"schema_version": "source_control_primary_rollback_adr_v1",
|
||
"status": "draft_waiting_owner_review",
|
||
"date": "2026-06-04",
|
||
"mode": "rollback_adr_only",
|
||
"runtime_execution_authorized": false,
|
||
"source_indexes": [
|
||
"docs/security/source-control-primary-readiness-gate.snapshot.json",
|
||
"docs/security/source-control-ref-truth-classification.snapshot.json",
|
||
"docs/security/source-control-workflow-secret-name-inventory.snapshot.json",
|
||
"docs/security/source-control-workflow-secret-name-export-request.snapshot.json",
|
||
"docs/security/source-control-approval-board.snapshot.json",
|
||
"docs/security/security-followup-runtime-gate.snapshot.json",
|
||
"docs/security/security-rollout-policy.snapshot.json"
|
||
],
|
||
"summary": {
|
||
"candidate_repo_count": 8,
|
||
"in_scope_repo_count": 7,
|
||
"external_scope_count": 1,
|
||
"repo_rollback_plan_count": 7,
|
||
"owner_approved_count": 0,
|
||
"dry_run_completed_count": 0,
|
||
"active_cutover_count": 0,
|
||
"rollback_owner_handoff_package_ready": true,
|
||
"rollback_owner_handoff_completion_percent": 100,
|
||
"rollback_owner_handoff_check_count": 6,
|
||
"rollback_owner_handoff_packet_field_count": 11,
|
||
"rollback_owner_request_dispatch_authorized": false,
|
||
"rollback_owner_response_received_count": 0,
|
||
"rollback_owner_response_accepted_count": 0,
|
||
"rollback_owner_response_rejected_count": 0,
|
||
"rollback_execution_authorized": false,
|
||
"github_primary_switch_authorized": false,
|
||
"gitea_disable_authorized": false,
|
||
"action_buttons_allowed": false
|
||
},
|
||
"rollback_principles": [
|
||
"GitHub primary 是長期方向,但每個 repo 必須先有 owner-approved rollback plan 才能進入 cutover review。",
|
||
"Gitea 在 cutover 前後都必須保留為本地 mirror / fallback,不得因 GitHub primary 準備而停用、刪除或封存。",
|
||
"Rollback ADR 只定義人工決策、驗證窗口與回退條件;不授權任何 refs sync、primary switch 或 webhook 修改。",
|
||
"任何回退都必須有新的 runtime gate、人工批准與 evidence snapshot,不得由本 ADR 自動觸發。",
|
||
"初期只做 observe / approval_required,不把缺 LOW / MEDIUM evidence 變成 production blocker。"
|
||
],
|
||
"cutover_preconditions": [
|
||
{
|
||
"gate_id": "gitea_authenticated_inventory_approved",
|
||
"title": "Gitea private/internal 全量 inventory 已完成",
|
||
"required_evidence": [
|
||
"Gitea authenticated inventory 或 redacted admin export status=ok",
|
||
"確認所有 private/internal repos 都在 migration matrix",
|
||
"只保存 token_present=true/false,不保存 token value"
|
||
],
|
||
"current_status": "blocked",
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"gate_id": "refs_truth_and_parity_approved",
|
||
"title": "refs truth / branch-tag parity 已由 owner 批准",
|
||
"required_evidence": [
|
||
"main/dev 真相來源已人工判定",
|
||
"release tags 保留 / 棄用決策完成",
|
||
"deprecated refs 已由 repo owner review"
|
||
],
|
||
"current_status": "waiting_owner_review",
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"gate_id": "workflow_secret_export_accepted",
|
||
"title": "workflow / runner / webhook / secret name redacted export 已驗收",
|
||
"required_evidence": [
|
||
"S4.3 export request 對應的 webhook、runner、deploy key、branch protection 與 repository secret name parity evidence 已補齊",
|
||
"任何 secret value、token value、private key 或 webhook secret 都未被保存",
|
||
"GitHub hosted runner 額度風險與 self-hosted runner owner 已確認"
|
||
],
|
||
"current_status": "draft_only",
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"gate_id": "owner_visibility_canonical_approved",
|
||
"title": "owner / visibility / canonical 已批准",
|
||
"required_evidence": [
|
||
"7 個 in-scope repos 都有 owner approval",
|
||
"not_found_or_private repo 已明確判定建立、取得權限或排除",
|
||
"ewoooc / momo-pro-system canonical 關係已定案"
|
||
],
|
||
"current_status": "waiting_owner_review",
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"gate_id": "rollback_owner_and_monitoring_approved",
|
||
"title": "rollback owner、監控窗口與通知責任已批准",
|
||
"required_evidence": [
|
||
"每個 repo 都有 rollback owner 與值班窗口",
|
||
"cutover 後 1h / 24h 的驗證項目已明確",
|
||
"失敗時的人工停損與回退 decision record 格式已確認"
|
||
],
|
||
"current_status": "draft_only",
|
||
"execution_authorized": false
|
||
}
|
||
],
|
||
"repo_rollback_plans": [
|
||
{
|
||
"repo_key": "awoooi",
|
||
"github_repo": "owenhytsai/awoooi",
|
||
"source_key": "wooo/awoooi",
|
||
"scope_status": "in_scope",
|
||
"risk": "HIGH",
|
||
"rollback_state": "draft_waiting_owner_review",
|
||
"primary_ready": false,
|
||
"fallback_role": "Gitea remains protected local fallback until owner-approved cutover and rollback drill are complete.",
|
||
"required_owner_decisions": [
|
||
"main/dev 真相來源",
|
||
"production deploy workflow canonical",
|
||
"webhook single-sender policy",
|
||
"rollback owner"
|
||
],
|
||
"rollback_evidence_required": [
|
||
"main SHA / tag parity evidence",
|
||
"deployment marker workflow evidence",
|
||
"runner owner / self-hosted evidence",
|
||
"repository secret name parity evidence",
|
||
"post-cutover health and deploy verification checklist"
|
||
],
|
||
"rollback_triggers": [
|
||
"production deploy workflow fails after primary switch",
|
||
"duplicate webhook causes duplicate deploy or duplicate notification",
|
||
"required runner label unavailable",
|
||
"secret name parity gap blocks deployment"
|
||
],
|
||
"manual_recovery_outline": [
|
||
"Freeze additional refs changes and record decision in approval audit.",
|
||
"Keep Gitea fallback intact and route deploy decision back to approved source after runtime gate approval.",
|
||
"Collect post-rollback evidence before re-entering primary readiness review."
|
||
],
|
||
"execution_authorized": false,
|
||
"still_forbidden": [
|
||
"switch_github_primary",
|
||
"disable_gitea",
|
||
"sync_refs",
|
||
"modify_workflow",
|
||
"move_secret_values"
|
||
]
|
||
},
|
||
{
|
||
"repo_key": "clawbot-v5",
|
||
"github_repo": "owenhytsai/clawbot-v5",
|
||
"source_key": "wooo/clawbot-v5",
|
||
"scope_status": "in_scope",
|
||
"risk": "MEDIUM",
|
||
"rollback_state": "draft_waiting_owner_review",
|
||
"primary_ready": false,
|
||
"fallback_role": "Gitea remains fallback until tag retention and refs truth are approved.",
|
||
"required_owner_decisions": [
|
||
"main 真相來源",
|
||
"Gitea-only tag 保留或棄用",
|
||
"是否需要 workflow / secret inventory",
|
||
"rollback owner"
|
||
],
|
||
"rollback_evidence_required": [
|
||
"branch/tag parity evidence",
|
||
"owner-approved tag policy",
|
||
"repository secret name parity or no-secret attestation",
|
||
"post-cutover smoke check"
|
||
],
|
||
"rollback_triggers": [
|
||
"GitHub target misses owner-approved tag",
|
||
"automation expects a Gitea-only ref",
|
||
"repo owner rejects target canonical decision"
|
||
],
|
||
"manual_recovery_outline": [
|
||
"Pause primary review and keep Gitea as canonical fallback.",
|
||
"Ask owner to classify missing refs before any retry.",
|
||
"Require new approval record before resuming cutover preparation."
|
||
],
|
||
"execution_authorized": false,
|
||
"still_forbidden": [
|
||
"push_refs",
|
||
"delete_refs",
|
||
"switch_github_primary",
|
||
"delete_gitea_repo"
|
||
]
|
||
},
|
||
{
|
||
"repo_key": "wooo-aiops",
|
||
"github_repo": "owenhytsai/wooo-aiops",
|
||
"source_key": "wooo/wooo-aiops",
|
||
"scope_status": "in_scope",
|
||
"risk": "MEDIUM",
|
||
"rollback_state": "draft_waiting_owner_review",
|
||
"primary_ready": false,
|
||
"fallback_role": "Gitea remains fallback until GitHub-only refs and workflow ownership are resolved.",
|
||
"required_owner_decisions": [
|
||
"GitHub-only branch / tag 來源",
|
||
"webhook owner",
|
||
"runner / workflow owner",
|
||
"rollback owner"
|
||
],
|
||
"rollback_evidence_required": [
|
||
"GitHub-only refs classification",
|
||
"webhook redacted export",
|
||
"runner owner evidence",
|
||
"repository secret name parity evidence"
|
||
],
|
||
"rollback_triggers": [
|
||
"GitHub-only refs are later classified as active source",
|
||
"webhook route changes produce duplicate events",
|
||
"workflow runner mismatch increases hosted runner usage unexpectedly"
|
||
],
|
||
"manual_recovery_outline": [
|
||
"Stop cutover review and preserve both remote states.",
|
||
"Route owner decision back through source-control approval board.",
|
||
"Re-run readiness gate only after redacted evidence is updated."
|
||
],
|
||
"execution_authorized": false,
|
||
"still_forbidden": [
|
||
"delete_github_only_refs",
|
||
"modify_webhook",
|
||
"switch_github_primary",
|
||
"force_push"
|
||
]
|
||
},
|
||
{
|
||
"repo_key": "wooo-infra-config",
|
||
"github_repo": "owenhytsai/wooo-infra-config",
|
||
"source_key": "wooo/wooo-infra-config",
|
||
"scope_status": "in_scope",
|
||
"risk": "MEDIUM",
|
||
"rollback_state": "draft_waiting_owner_review",
|
||
"primary_ready": false,
|
||
"fallback_role": "Gitea and internal 110 remote roles stay unchanged until infra owner signs off.",
|
||
"required_owner_decisions": [
|
||
"110 internal remote purpose",
|
||
"deploy key owner",
|
||
"infra secret name owner",
|
||
"rollback owner"
|
||
],
|
||
"rollback_evidence_required": [
|
||
"internal remote purpose decision",
|
||
"deploy key redacted inventory",
|
||
"branch protection / required check export",
|
||
"infra secret name parity evidence"
|
||
],
|
||
"rollback_triggers": [
|
||
"internal remote is still an active source",
|
||
"deploy key ownership is ambiguous",
|
||
"required status checks are missing on GitHub target",
|
||
"infra secret parity gap blocks validation"
|
||
],
|
||
"manual_recovery_outline": [
|
||
"Keep current infra source untouched and do not delete remotes.",
|
||
"Escalate to infra owner for manual source-of-truth decision.",
|
||
"Resume only after a new redacted export snapshot is committed."
|
||
],
|
||
"execution_authorized": false,
|
||
"still_forbidden": [
|
||
"delete_remote",
|
||
"move_secret_values",
|
||
"export_private_key",
|
||
"switch_github_primary"
|
||
]
|
||
},
|
||
{
|
||
"repo_key": "ewoooc",
|
||
"github_repo": "owenhytsai/ewoooc",
|
||
"source_key": "wooo/ewoooc / root/momo-pro-system / momo working trees",
|
||
"scope_status": "in_scope",
|
||
"risk": "HIGH",
|
||
"rollback_state": "draft_waiting_owner_review",
|
||
"primary_ready": false,
|
||
"fallback_role": "No primary decision until canonical repository and unrelated history risk are resolved.",
|
||
"required_owner_decisions": [
|
||
"GitHub target access or creation decision",
|
||
"ewoooc / momo-pro-system canonical decision",
|
||
"unrelated history handling",
|
||
"rollback owner"
|
||
],
|
||
"rollback_evidence_required": [
|
||
"canonical repo decision record",
|
||
"server-side refs diff",
|
||
"workflow / secret name redacted export",
|
||
"post-cutover web and deploy health checks"
|
||
],
|
||
"rollback_triggers": [
|
||
"target repo access remains unresolved",
|
||
"canonical decision conflicts with local lineage evidence",
|
||
"unrelated histories cannot be reconciled without data loss risk"
|
||
],
|
||
"manual_recovery_outline": [
|
||
"Keep all existing working trees untouched.",
|
||
"Do not create or merge GitHub target automatically.",
|
||
"Require canonical owner approval before any new cutover attempt."
|
||
],
|
||
"execution_authorized": false,
|
||
"still_forbidden": [
|
||
"auto_create_repo",
|
||
"auto_merge_unrelated_histories",
|
||
"delete_working_tree",
|
||
"switch_github_primary"
|
||
]
|
||
},
|
||
{
|
||
"repo_key": "bitan-pharmacy",
|
||
"github_repo": "owenhytsai/bitan-pharmacy",
|
||
"source_key": "bitan-pharmacy",
|
||
"scope_status": "in_scope",
|
||
"risk": "MEDIUM",
|
||
"rollback_state": "draft_waiting_owner_review",
|
||
"primary_ready": false,
|
||
"fallback_role": "Current local / 110 source remains evidence source until active-state and GitHub target are approved.",
|
||
"required_owner_decisions": [
|
||
"repo active or archive decision",
|
||
"GitHub target decision",
|
||
"secret name / deploy owner decision",
|
||
"rollback owner"
|
||
],
|
||
"rollback_evidence_required": [
|
||
"GitHub target visibility decision",
|
||
"repository secret name parity or no-secret attestation",
|
||
"active-state owner decision",
|
||
"post-cutover smoke check if active"
|
||
],
|
||
"rollback_triggers": [
|
||
"repo is confirmed inactive or out of scope",
|
||
"GitHub target cannot be verified",
|
||
"owner cannot confirm deploy / secret requirements"
|
||
],
|
||
"manual_recovery_outline": [
|
||
"Keep repo out of primary cutover queue until owner decision exists.",
|
||
"Do not create GitHub target automatically.",
|
||
"If inactive, record owner decision rather than deleting source."
|
||
],
|
||
"execution_authorized": false,
|
||
"still_forbidden": [
|
||
"auto_create_repo",
|
||
"push_refs",
|
||
"delete_110_remote",
|
||
"switch_github_primary"
|
||
]
|
||
},
|
||
{
|
||
"repo_key": "tsenyang-website",
|
||
"github_repo": "owenhytsai/tsenyang-website",
|
||
"source_key": "tsenyang-website",
|
||
"scope_status": "in_scope",
|
||
"risk": "MEDIUM",
|
||
"rollback_state": "draft_waiting_owner_review",
|
||
"primary_ready": false,
|
||
"fallback_role": "Current local / 110 source remains evidence source until active-state and GitHub target are approved.",
|
||
"required_owner_decisions": [
|
||
"repo active or archive decision",
|
||
"GitHub target decision",
|
||
"secret name / deploy owner decision",
|
||
"rollback owner"
|
||
],
|
||
"rollback_evidence_required": [
|
||
"GitHub target visibility decision",
|
||
"repository secret name parity or no-secret attestation",
|
||
"active-state owner decision",
|
||
"post-cutover smoke check if active"
|
||
],
|
||
"rollback_triggers": [
|
||
"repo is confirmed inactive or out of scope",
|
||
"GitHub target cannot be verified",
|
||
"owner cannot confirm deploy / secret requirements"
|
||
],
|
||
"manual_recovery_outline": [
|
||
"Keep repo out of primary cutover queue until owner decision exists.",
|
||
"Do not create GitHub target automatically.",
|
||
"If inactive, record owner decision rather than deleting source."
|
||
],
|
||
"execution_authorized": false,
|
||
"still_forbidden": [
|
||
"auto_create_repo",
|
||
"push_refs",
|
||
"delete_110_remote",
|
||
"switch_github_primary"
|
||
]
|
||
},
|
||
{
|
||
"repo_key": "open-design",
|
||
"github_repo": "nexu-io/open-design",
|
||
"source_key": "open-design",
|
||
"scope_status": "external_scope_review",
|
||
"risk": "LOW",
|
||
"rollback_state": "scope_review_only",
|
||
"primary_ready": false,
|
||
"fallback_role": "Not in AWOOOI primary cutover scope until ownership is confirmed.",
|
||
"required_owner_decisions": [
|
||
"scope ownership decision"
|
||
],
|
||
"rollback_evidence_required": [
|
||
"scope review evidence"
|
||
],
|
||
"rollback_triggers": [
|
||
"repo is later confirmed in scope and needs a separate ADR"
|
||
],
|
||
"manual_recovery_outline": [
|
||
"Keep out of primary queue.",
|
||
"Create a separate in-scope approval item if ownership changes."
|
||
],
|
||
"execution_authorized": false,
|
||
"still_forbidden": [
|
||
"加入 primary cutover queue",
|
||
"修改 repo visibility",
|
||
"sync refs"
|
||
]
|
||
}
|
||
],
|
||
"rollback_owner_handoff": {
|
||
"status": "ready_not_dispatched",
|
||
"package_ready": true,
|
||
"handoff_completion_percent": 100,
|
||
"repo_template_count": 7,
|
||
"preflight_checks": [
|
||
{
|
||
"check_id": "preflight-latest-source-control-baseline",
|
||
"title": "送件前確認 gitea/main、P1-2、P1-3、P1-4 與 S4.13 最新狀態",
|
||
"completion_state": "defined_not_dispatched",
|
||
"current_status": "已定義,未送件"
|
||
},
|
||
{
|
||
"check_id": "preflight-seven-in-scope-repos-only",
|
||
"title": "只向 7 個 in-scope repos 收 rollback owner / fallback / trigger / validation 回覆",
|
||
"completion_state": "defined_not_dispatched",
|
||
"current_status": "已定義,未送件"
|
||
},
|
||
{
|
||
"check_id": "preflight-fallback-role-kept",
|
||
"title": "回覆必須確認 Gitea 或現行來源仍保留 fallback 角色",
|
||
"completion_state": "defined_not_dispatched",
|
||
"current_status": "已定義,未送件"
|
||
},
|
||
{
|
||
"check_id": "preflight-validation-windows-bound",
|
||
"title": "每個 repo 必須對應 pre-cutover、1h、24h 三個驗證窗口",
|
||
"completion_state": "defined_not_dispatched",
|
||
"current_status": "已定義,未送件"
|
||
},
|
||
{
|
||
"check_id": "preflight-redacted-evidence-only",
|
||
"title": "只收 owner role/team、決策理由、脫敏 evidence ref 與 follow-up owner",
|
||
"completion_state": "defined_not_dispatched",
|
||
"current_status": "已定義,未送件"
|
||
},
|
||
{
|
||
"check_id": "preflight-no-cutover-or-rollback-command",
|
||
"title": "primary switch、rollback execution、refs sync、workflow/secret 變更與 Gitea disable 全部拒收",
|
||
"completion_state": "defined_not_dispatched",
|
||
"current_status": "已定義,未送件"
|
||
}
|
||
],
|
||
"handoff_packet": {
|
||
"request_id": "p1_5_primary_rollback_owner_handoff",
|
||
"stage_id": "S4.4",
|
||
"prerequisite_gates": [
|
||
"S4.9 Gitea owner response gate",
|
||
"P1-2 Gitea authenticated inventory request handoff",
|
||
"P1-3 GitHub target owner response handoff",
|
||
"P1-4 workflow / secret owner response handoff",
|
||
"S4.13 owner response validation rollup"
|
||
],
|
||
"requested_repo_templates": [
|
||
"awoooi",
|
||
"clawbot-v5",
|
||
"wooo-aiops",
|
||
"wooo-infra-config",
|
||
"ewoooc",
|
||
"bitan-pharmacy",
|
||
"tsenyang-website"
|
||
],
|
||
"recipient_role_or_team": "repo owner / release owner / fallback owner role only;不收個人 credential",
|
||
"required_response_fields": [
|
||
"owner_role_or_team",
|
||
"decision",
|
||
"decision_reason",
|
||
"fallback_role_confirmation",
|
||
"rollback_trigger_scope",
|
||
"validation_window_owner",
|
||
"redacted_evidence_refs",
|
||
"followup_owner"
|
||
],
|
||
"validation_window_refs": [
|
||
"pre_cutover_freeze_review",
|
||
"post_cutover_one_hour_observe",
|
||
"post_cutover_twenty_four_hour_review"
|
||
],
|
||
"allowed_evidence_refs": [
|
||
"docs/security/source-control-primary-readiness-gate.snapshot.json",
|
||
"docs/security/source-control-ref-truth-owner-response.snapshot.json",
|
||
"docs/security/github-target-owner-decision-response.snapshot.json",
|
||
"docs/security/source-control-workflow-secret-name-owner-response.snapshot.json",
|
||
"docs/security/source-control-owner-response-validation-rollup.snapshot.json"
|
||
],
|
||
"forbidden_inputs": [
|
||
"token value",
|
||
"secret value",
|
||
"private key",
|
||
"runner registration token",
|
||
"webhook secret",
|
||
"repo write instruction",
|
||
"refs sync or delete instruction",
|
||
"GitHub primary switch request",
|
||
"rollback execution request",
|
||
"Gitea disable or archive request",
|
||
"active scan or host maintenance request"
|
||
],
|
||
"not_approval": true,
|
||
"request_dispatch_authorized": false
|
||
},
|
||
"repo_owner_response_templates": [
|
||
{
|
||
"repo_key": "awoooi",
|
||
"github_repo": "owenhytsai/awoooi",
|
||
"rollback_owner_status": "waiting_owner_response",
|
||
"fallback_role_confirmation_required": true,
|
||
"trigger_review_required": true,
|
||
"validation_window_refs": [
|
||
"pre_cutover_freeze_review",
|
||
"post_cutover_one_hour_observe",
|
||
"post_cutover_twenty_four_hour_review"
|
||
],
|
||
"required_response_fields": [
|
||
"owner_role_or_team",
|
||
"decision",
|
||
"decision_reason",
|
||
"fallback_role_confirmation",
|
||
"rollback_trigger_scope",
|
||
"validation_window_owner",
|
||
"redacted_evidence_refs",
|
||
"followup_owner"
|
||
],
|
||
"response_received": false,
|
||
"response_accepted": false,
|
||
"primary_ready": false,
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"repo_key": "clawbot-v5",
|
||
"github_repo": "owenhytsai/clawbot-v5",
|
||
"rollback_owner_status": "waiting_owner_response",
|
||
"fallback_role_confirmation_required": true,
|
||
"trigger_review_required": true,
|
||
"validation_window_refs": [
|
||
"pre_cutover_freeze_review",
|
||
"post_cutover_one_hour_observe",
|
||
"post_cutover_twenty_four_hour_review"
|
||
],
|
||
"required_response_fields": [
|
||
"owner_role_or_team",
|
||
"decision",
|
||
"decision_reason",
|
||
"fallback_role_confirmation",
|
||
"rollback_trigger_scope",
|
||
"validation_window_owner",
|
||
"redacted_evidence_refs",
|
||
"followup_owner"
|
||
],
|
||
"response_received": false,
|
||
"response_accepted": false,
|
||
"primary_ready": false,
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"repo_key": "wooo-aiops",
|
||
"github_repo": "owenhytsai/wooo-aiops",
|
||
"rollback_owner_status": "waiting_owner_response",
|
||
"fallback_role_confirmation_required": true,
|
||
"trigger_review_required": true,
|
||
"validation_window_refs": [
|
||
"pre_cutover_freeze_review",
|
||
"post_cutover_one_hour_observe",
|
||
"post_cutover_twenty_four_hour_review"
|
||
],
|
||
"required_response_fields": [
|
||
"owner_role_or_team",
|
||
"decision",
|
||
"decision_reason",
|
||
"fallback_role_confirmation",
|
||
"rollback_trigger_scope",
|
||
"validation_window_owner",
|
||
"redacted_evidence_refs",
|
||
"followup_owner"
|
||
],
|
||
"response_received": false,
|
||
"response_accepted": false,
|
||
"primary_ready": false,
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"repo_key": "wooo-infra-config",
|
||
"github_repo": "owenhytsai/wooo-infra-config",
|
||
"rollback_owner_status": "waiting_owner_response",
|
||
"fallback_role_confirmation_required": true,
|
||
"trigger_review_required": true,
|
||
"validation_window_refs": [
|
||
"pre_cutover_freeze_review",
|
||
"post_cutover_one_hour_observe",
|
||
"post_cutover_twenty_four_hour_review"
|
||
],
|
||
"required_response_fields": [
|
||
"owner_role_or_team",
|
||
"decision",
|
||
"decision_reason",
|
||
"fallback_role_confirmation",
|
||
"rollback_trigger_scope",
|
||
"validation_window_owner",
|
||
"redacted_evidence_refs",
|
||
"followup_owner"
|
||
],
|
||
"response_received": false,
|
||
"response_accepted": false,
|
||
"primary_ready": false,
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"repo_key": "ewoooc",
|
||
"github_repo": "owenhytsai/ewoooc",
|
||
"rollback_owner_status": "waiting_owner_response",
|
||
"fallback_role_confirmation_required": true,
|
||
"trigger_review_required": true,
|
||
"validation_window_refs": [
|
||
"pre_cutover_freeze_review",
|
||
"post_cutover_one_hour_observe",
|
||
"post_cutover_twenty_four_hour_review"
|
||
],
|
||
"required_response_fields": [
|
||
"owner_role_or_team",
|
||
"decision",
|
||
"decision_reason",
|
||
"fallback_role_confirmation",
|
||
"rollback_trigger_scope",
|
||
"validation_window_owner",
|
||
"redacted_evidence_refs",
|
||
"followup_owner"
|
||
],
|
||
"response_received": false,
|
||
"response_accepted": false,
|
||
"primary_ready": false,
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"repo_key": "bitan-pharmacy",
|
||
"github_repo": "owenhytsai/bitan-pharmacy",
|
||
"rollback_owner_status": "waiting_owner_response",
|
||
"fallback_role_confirmation_required": true,
|
||
"trigger_review_required": true,
|
||
"validation_window_refs": [
|
||
"pre_cutover_freeze_review",
|
||
"post_cutover_one_hour_observe",
|
||
"post_cutover_twenty_four_hour_review"
|
||
],
|
||
"required_response_fields": [
|
||
"owner_role_or_team",
|
||
"decision",
|
||
"decision_reason",
|
||
"fallback_role_confirmation",
|
||
"rollback_trigger_scope",
|
||
"validation_window_owner",
|
||
"redacted_evidence_refs",
|
||
"followup_owner"
|
||
],
|
||
"response_received": false,
|
||
"response_accepted": false,
|
||
"primary_ready": false,
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"repo_key": "tsenyang-website",
|
||
"github_repo": "owenhytsai/tsenyang-website",
|
||
"rollback_owner_status": "waiting_owner_response",
|
||
"fallback_role_confirmation_required": true,
|
||
"trigger_review_required": true,
|
||
"validation_window_refs": [
|
||
"pre_cutover_freeze_review",
|
||
"post_cutover_one_hour_observe",
|
||
"post_cutover_twenty_four_hour_review"
|
||
],
|
||
"required_response_fields": [
|
||
"owner_role_or_team",
|
||
"decision",
|
||
"decision_reason",
|
||
"fallback_role_confirmation",
|
||
"rollback_trigger_scope",
|
||
"validation_window_owner",
|
||
"redacted_evidence_refs",
|
||
"followup_owner"
|
||
],
|
||
"response_received": false,
|
||
"response_accepted": false,
|
||
"primary_ready": false,
|
||
"execution_authorized": false
|
||
}
|
||
],
|
||
"post_handoff_invariants": [
|
||
"request dispatch authorized 仍為 false,未送件前不得增加 request_sent_count。",
|
||
"owner response received / accepted / rejected 全部維持 0。",
|
||
"owner response 通過前不得把任何 repo 標記 primary_ready。",
|
||
"dry-run completed count 與 active cutover count 維持 0。",
|
||
"不得把 rollback owner handoff 當成 GitHub primary approval、rollback execution approval 或 Gitea disable approval。",
|
||
"未來即使 owner response 通過,也只能更新 read-only rollback ADR、primary readiness wording、approval board 與 status rollup。"
|
||
]
|
||
},
|
||
"rollback_triggers": [
|
||
"main/dev SHA 或 tag parity 與 owner-approved truth 不一致",
|
||
"workflow、webhook、runner、deploy key、branch protection 或 repository secret name parity evidence 不完整",
|
||
"GitHub hosted runner 使用量或 billing risk 超出 owner-approved 範圍",
|
||
"deploy marker、release workflow 或 required status check 在 cutover 後失敗",
|
||
"duplicate webhook 造成重複部署、重複通知或 approval queue 重複事件",
|
||
"owner / visibility / canonical decision 被撤回或出現衝突",
|
||
"post-cutover 1h 或 24h validation window 未通過"
|
||
],
|
||
"validation_windows": [
|
||
{
|
||
"window_id": "pre_cutover_freeze_review",
|
||
"title": "切換前 freeze review",
|
||
"required_checks": [
|
||
"確認 refs truth、workflow / secret export、owner / visibility / canonical、rollback owner 全部已批准",
|
||
"確認沒有 unresolved HIGH risk blocker",
|
||
"確認 Gitea fallback 保持可用"
|
||
],
|
||
"failure_handling": "任何缺口都只回到 approval review,不執行 cutover。",
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"window_id": "post_cutover_one_hour_observe",
|
||
"title": "切換後 1 小時觀察窗口",
|
||
"required_checks": [
|
||
"deploy workflow / required checks 成功",
|
||
"webhook 沒有重複事件",
|
||
"runner 使用符合 owner-approved self-hosted / hosted policy",
|
||
"核心服務健康與告警量未異常升高"
|
||
],
|
||
"failure_handling": "只建立 rollback approval candidate;不得自動切回或自動改 webhook。",
|
||
"execution_authorized": false
|
||
},
|
||
{
|
||
"window_id": "post_cutover_twenty_four_hour_review",
|
||
"title": "切換後 24 小時 review",
|
||
"required_checks": [
|
||
"refs、deploy、webhook、runner、secret name parity 沒有新增 drift",
|
||
"AwoooP audit evidence 完整",
|
||
"repo owner 確認可維持 GitHub primary 或要求 rollback review"
|
||
],
|
||
"failure_handling": "若未通過,保留 Gitea fallback 並等待人工 rollback decision record。",
|
||
"execution_authorized": false
|
||
}
|
||
],
|
||
"acceptance_rules": [
|
||
"本 ADR 草案完成不代表任何 repo 可切 GitHub primary。",
|
||
"每個 in-scope repo 必須有 owner-approved rollback plan、pre-cutover evidence、post-cutover validation window 與 rollback owner。",
|
||
"任何 rollback 或 primary switch 都必須另有 runtime gate 與人工批准,不得由本 snapshot 自動觸發。",
|
||
"Gitea fallback 不得在 24h review 完成前停用、刪除、封存或降級。",
|
||
"任何 secret、token、cookie、private key、webhook secret 或 runner token 都不得保存。"
|
||
],
|
||
"forbidden_actions": [
|
||
"switch_github_primary",
|
||
"execute_rollback",
|
||
"create_github_repo",
|
||
"change_repo_visibility",
|
||
"sync_git_refs",
|
||
"delete_git_refs",
|
||
"force_push",
|
||
"modify_webhook",
|
||
"modify_workflow",
|
||
"modify_branch_protection",
|
||
"move_secret_values",
|
||
"disable_gitea",
|
||
"delete_or_archive_gitea_repo",
|
||
"add_action_button"
|
||
]
|
||
}
|