docs(security): add source control reconcile plan [skip ci]

This commit is contained in:
Your Name
2026-05-13 00:33:31 +08:00
parent 13b2db6563
commit 892e68dcca
12 changed files with 746 additions and 13 deletions

View File

@@ -1,3 +1,27 @@
## 2026-05-12 | Source Control Draft Reconcile Plan 草案
**背景**:統帥批准繼續推進後,本輪先同步最新 `gitea/main`,納入另一個 AwoooP Session 的 `legacy mcp audit -> gateway timeline` 進度,避免雙 Session 分歧。同步後繼續沿用低摩擦原則,只針對 refs-blocked repo 產生草案,不執行同步。
**本次交付**
- 新增 `scripts/security/source-control-reconcile-plan.py`,只讀既有 redacted snapshot不呼叫遠端 Git不 fetch、不 push、不改 remote。
- 新增 `docs/schemas/source_control_reconcile_plan_v1.schema.json`
- 產出 `docs/security/source-control-reconcile-plan.snapshot.json``docs/security/SOURCE-CONTROL-RECONCILE-PLAN.md`
- Draft plan 涵蓋 3 個 refs-blocked mapped repos`wooo/awoooi``wooo/clawbot-v5``wooo/wooo-aiops`
- 更新 `SECURITY-SUPPLY-CHAIN-CONTRACT-MANIFEST`contract count 從 13 增至 14新增 `source_control_reconcile_plan_v1`
- 更新 `SECURITY-SUPPLY-CHAIN-PROGRESS``AWOOOP-MIRROR-ONLY-CONSUMPTION-CHECKLIST`,讓 AwoooP 可 mirror draft plan 但不得執行 refs sync。
**邊界**
- Plan 狀態為 `draft_blocked`authenticated / admin_export server-side inventory 尚未完成前,不可執行。
- 未 push refs、未 force push、未刪 refs、未建立 GitHub repo、未改 visibility、未切 GitHub primary、未部署。
- 人工批准未來也必須單一 repo 生效,不得批次套用到所有 repo。
**驗證**
- `source-control-reconcile-plan.py` 產生 3 plans。
- JSON / schema / snapshot parse 通過。
- `scripts/security/*.py` 可編譯。
- `git diff --check` 通過。
- PR diff added lines 未命中本輪敏感 token / credential pattern。
## 2026-05-12 | Source Control Approval Board 低摩擦決策隊列
**背景**:統帥批准繼續推進後,下一步原本是 Gitea authenticated read-only inventory但目前 `GITEA_READONLY_TOKEN` 未提供。本輪因此不使用可 push 的既有 Gitea remote credential 代替 read-only token避免把 inventory 與寫入權限憑證混在一起。

View File

@@ -0,0 +1,130 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:awoooi:source-control-reconcile-plan-v1",
"title": "AWOOOI Source Control Reconcile Plan (v1)",
"description": "針對 Gitea -> GitHub refs-blocked repo 產生只讀、不可執行的 reconcile plan 草案。",
"type": "object",
"required": [
"schema_version",
"status",
"date",
"default_mode",
"inventory_gate",
"plan_count",
"plans"
],
"properties": {
"schema_version": {
"const": "source_control_reconcile_plan_v1"
},
"status": {
"type": "string",
"enum": ["draft_blocked"]
},
"date": {
"type": "string"
},
"default_mode": {
"type": "string",
"enum": ["plan_only"]
},
"inventory_gate": {
"type": "object",
"required": ["status", "reason", "required_before_execution"],
"properties": {
"status": {
"type": "string",
"enum": ["blocked", "ready"]
},
"reason": {
"type": "string"
},
"required_before_execution": {
"type": "array",
"items": {"type": "string"}
}
},
"additionalProperties": false
},
"plan_count": {
"type": "integer",
"minimum": 0
},
"plans": {
"type": "array",
"items": {
"type": "object",
"required": [
"gitea_repo",
"github_repo",
"risk",
"source_status",
"divergence_summary",
"proposed_plan_steps",
"execution_gates",
"allowed_now",
"still_forbidden",
"evidence_refs",
"awooop_consumption"
],
"properties": {
"gitea_repo": {"type": "string"},
"github_repo": {"type": "string"},
"risk": {
"type": "string",
"enum": ["LOW", "MEDIUM", "HIGH"]
},
"source_status": {"type": "string"},
"divergence_summary": {
"type": "object",
"required": [
"gitea_branch_count",
"github_branch_count",
"gitea_tag_count",
"github_tag_count",
"gitea_main_sha",
"github_main_sha",
"blocking_reason"
],
"properties": {
"gitea_branch_count": {"type": "integer"},
"github_branch_count": {"type": "integer"},
"gitea_tag_count": {"type": "integer"},
"github_tag_count": {"type": "integer"},
"gitea_main_sha": {"type": "string"},
"github_main_sha": {"type": "string"},
"blocking_reason": {"type": "string"}
},
"additionalProperties": false
},
"proposed_plan_steps": {
"type": "array",
"items": {"type": "string"}
},
"execution_gates": {
"type": "array",
"items": {"type": "string"}
},
"allowed_now": {
"type": "array",
"items": {"type": "string"}
},
"still_forbidden": {
"type": "array",
"items": {"type": "string"}
},
"evidence_refs": {
"type": "array",
"items": {"type": "string"}
},
"awooop_consumption": {
"type": "string",
"enum": ["mirror_only", "approval_candidate"]
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}

View File

@@ -34,6 +34,7 @@ AwoooP 初期不得直接啟動掃描、不得呼叫 Codex patch runner、不得
| `github_target_decision_v1` | GitHub target 建立與可見性決策草案 | Approval candidate、Migration target evidence | mirror-only | approval 前不得建立 repo、修改 visibility、同步 refs |
| `github_target_repo_approval_package_v1` | GitHub target 逐 repo approval package | Approval queue、Migration target evidence | mirror-only | 低摩擦,只 gate 高風險執行 |
| `source_control_approval_board_v1` | 逐 repo owner / visibility / canonical / refs 決策 board | Approval queue、PR reviewer handoff | approval-only | 只顯示決策隊列,不執行 board item |
| `source_control_reconcile_plan_v1` | refs-blocked repo draft reconcile plan | Approval candidate、migration reviewer handoff | approval-only | 只顯示草案,不 push refs、不切 primary |
| `local_repo_canonical_probe_v1` | 本機 working tree lineage 比對 | Canonical decision evidence | mirror-only | 不自動合併、不自動建 repo、不刪除 |
| `git_remote_refs_probe_v1` | 指定 repo remote refs read-only probe | Source readiness evidence | mirror-only | 不 fetch、不 push、不自動 mirror |
| `approval_required_event_v1` | 上述事件的高風險 gate | Approval queue、Audit | approval-only | `blocked_until_approved=true` |
@@ -83,6 +84,7 @@ AwoooP 初期不得直接啟動掃描、不得呼叫 Codex patch runner、不得
| `github_target_decision_v1.approval_required_count>0` | `approve_required` | 產生 approval candidate但不執行 repo 建立或 visibility 修改 |
| `github_target_repo_approval_package_v1.status=draft` | `observe` | 建立 approval queue draft不阻擋 read-only evidence |
| `source_control_approval_board_v1.pending_approval_count>0` | `approve_required` | 顯示逐 repo 決策隊列,不執行 repo 建立、visibility 修改、refs sync |
| `source_control_reconcile_plan_v1.status=draft_blocked` | `approve_required` | 只顯示 refs reconcile 草案與 gate不執行 sync |
| `local_repo_canonical_probe_v1.status=unrelated` | `approve_required` | 禁止自動合併,需人工 canonical 判定 |
| `git_remote_refs_probe_v1.status=ok` | `observe` | 可作 source evidence但仍需 GitHub target 與 approval |
| `security_rollout_policy_v1.enforcement_level=mirror_only` | `observe` | 只顯示 policy不阻擋既有流程 |
@@ -124,6 +126,7 @@ AwoooP 初期不得直接啟動掃描、不得呼叫 Codex patch runner、不得
| GitHub target 決策 snapshot | `docs/security/github-target-decision.snapshot.json` / `docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md` |
| GitHub target repo approval package | `docs/security/github-target-repo-approval-package.snapshot.json` / `docs/security/GITHUB-TARGET-REPO-APPROVAL-PACKAGE.md` |
| Source Control approval board | `docs/security/source-control-approval-board.snapshot.json` / `docs/security/SOURCE-CONTROL-APPROVAL-BOARD.md` |
| Source Control draft reconcile plan | `docs/security/source-control-reconcile-plan.snapshot.json` / `docs/security/SOURCE-CONTROL-RECONCILE-PLAN.md` |
| 本機 repo canonical lineage snapshot | `docs/security/local-repo-canonical-ewoooc-momo.snapshot.json` / `docs/security/LOCAL-REPO-CANONICAL-EWOOOC-MOMO-SNAPSHOT.md` |
| Internal 110 refs snapshot | `docs/security/git-remote-refs-bitan-tsenyang.snapshot.json` / `docs/security/GIT-REMOTE-REFS-BITAN-TSENYANG-SNAPSHOT.md` |
| wooo-infra-config refs snapshot | `docs/security/git-remote-refs-wooo-infra-config.snapshot.json` / `docs/security/GIT-REMOTE-REFS-WOOO-INFRA-CONFIG-SNAPSHOT.md` |

View File

@@ -521,7 +521,7 @@ Console 初期不提供高風險 action button。
第一版 observe-only host mapping 已建立於 `docs/security/DEV-HOSTS-112-111-168-OBSERVE-ONLY-MAPPING.md`。第一版 Codex patch-only handoff prompt 已建立於 `docs/security/CODEX-PATCH-ONLY-HANDOFF-PROMPT.md`
2026-05-12 更新:已新增 `scripts/security/source-control-migration-inventory.py`,並產出 `docs/security/gitea-github-awoooi-inventory.snapshot.json`。目前 `source_control_migration_event_v1.status=blocked`,因 Gitea heads 115、GitHub heads 2、Gitea tags 2、GitHub tags 0`main` SHA 不一致。AwoooP Session 應將此視為 supply-chain evidence不得視為可切 GitHub primary。
2026-05-12 更新:已新增 `scripts/security/source-control-migration-inventory.py`,並產出 `docs/security/gitea-github-awoooi-inventory.snapshot.json`。目前 `source_control_migration_event_v1.status=blocked`,因 Gitea heads 117、GitHub heads 2、Gitea tags 2、GitHub tags 0`main` SHA 不一致。AwoooP Session 應將此視為 supply-chain evidence不得視為可切 GitHub primary。
2026-05-12 追加:已新增 `scripts/security/gitea-repo-inventory.py``docs/schemas/gitea_repo_inventory_v1.schema.json`,並產出 `docs/security/gitea-repo-inventory.snapshot.json`。目前 `gitea_repo_inventory_v1.status=partial`,因未提供 token 時只能取得 public-only `wooo/awoooi``wooo/ewoooc`;完整全量仍需只讀 token 或管理匯出。
@@ -537,6 +537,8 @@ Console 初期不提供高風險 action button。
2026-05-12 refs diff 追加:已新增 `docs/security/source-control-clawbot-v5.snapshot.json``docs/security/source-control-wooo-aiops.snapshot.json``wooo/clawbot-v5``wooo/wooo-aiops` 目前都為 `source_control_migration_event_v1.status=blocked`,不得視為 GitHub primary ready。
2026-05-12 draft reconcile plan 追加:已新增 `scripts/security/source-control-reconcile-plan.py``docs/schemas/source_control_reconcile_plan_v1.schema.json`,並產出 `docs/security/source-control-reconcile-plan.snapshot.json``docs/security/SOURCE-CONTROL-RECONCILE-PLAN.md`。此 plan 只涵蓋 `awoooi``clawbot-v5``wooo-aiops` 三個 refs-blocked mapped repos狀態為 `draft_blocked`AwoooP 可 mirror 成 approval candidate但不得 push refs、force push、刪 refs、切 GitHub primary。
2026-05-12 public search / canonical 追加Gitea public search 在未提供 token 時可見 `wooo/awoooi``wooo/ewoooc`。已新增 `docs/security/SOURCE-CONTROL-CANONICAL-DECISION-TABLE.md`,其中 `wooo/ewoooc``root/momo-pro-system``momo-pro-system``momo_pro_system` 仍需人工判定 canonical 關係,不得自動合併。
2026-05-12 GitHub target probe 追加:已新增 `scripts/security/github-target-probe.py``docs/schemas/github_target_probe_v1.schema.json``docs/security/github-target-probe.snapshot.json`。8 個候選中 5 個可讀,`owenhytsai/ewoooc``owenhytsai/bitan-pharmacy``owenhytsai/tsenyang-website``not_found_or_private`

View File

@@ -23,6 +23,7 @@
| wooo-infra-config refs probe | `docs/security/GIT-REMOTE-REFS-WOOO-INFRA-CONFIG-SNAPSHOT.md` / `docs/security/git-remote-refs-wooo-infra-config.snapshot.json` |
| GitHub target 決策表 | `docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md` / `docs/security/github-target-decision.snapshot.json` |
| GitHub target repo-by-repo approval package | `docs/security/GITHUB-TARGET-REPO-APPROVAL-PACKAGE.md` / `docs/security/github-target-repo-approval-package.snapshot.json` |
| Source Control draft reconcile plan | `docs/security/SOURCE-CONTROL-RECONCILE-PLAN.md` / `docs/security/source-control-reconcile-plan.snapshot.json` |
| Source Control 遷移矩陣 | `docs/security/SOURCE-CONTROL-MIGRATION-MATRIX.md` |
| Canonical repo 判定表 | `docs/security/SOURCE-CONTROL-CANONICAL-DECISION-TABLE.md` |
@@ -33,7 +34,7 @@
第二輪只讀盤點顯示,至少目前工作中的 `awoooi` repo 存在以下差異:
- GitHub `origin` 與 Gitea `gitea``main` SHA 不一致。
- Gitea 有大量 `drift/adopt-*` 分支GitHub 沒有;截至 2026-05-12Gitea heads 為 115GitHub heads 為 2 條。
- Gitea 有大量 `drift/adopt-*` 分支GitHub 沒有;截至 2026-05-12 最新 snapshotGitea heads 為 117GitHub heads 為 2 條。
- Gitea 有 release tagsGitHub 目前查不到 tags。
- 本機 `gitea` remote URL 內嵌憑證,這是 credential hygiene 風險;不得寫入文件、不得複製到 GitHub後續需移除並輪替。
- Gitea `wooo` user endpoint 在未提供 token 時可見 `wooo/awoooi``wooo/ewoooc`,目前 `gitea_repo_inventory_v1.status=partial`
@@ -55,7 +56,7 @@
|------|------|
| `git remote -v` | 已確認 `origin` 指向 GitHub`gitea` 指向本地 Gitea未在文件中保存憑證 |
| GitHub heads | 2 條 |
| Gitea heads | 115 條 |
| Gitea heads | 117 條 |
| GitHub tags | 0 條 |
| Gitea tag refs | 4 條 raw refs實際 tag 為 `v7.2.0``v7.3.0` |
| Gitea org API | 未認證查詢 `http://192.168.0.110:3001/api/v1/orgs/wooo/repos` 回 404保留為 endpoint 判定 evidence |
@@ -72,6 +73,7 @@
| wooo-infra-config refs 工具 | `python3 scripts/security/git-remote-refs-probe.py --group-name wooo-infra-config-remotes --repo wooo-infra-config-gitea=/Users/ogt/wooo-infra-config=gitea --repo wooo-infra-config-github=/Users/ogt/wooo-infra-config=origin --output-json docs/security/git-remote-refs-wooo-infra-config.snapshot.json --output-md docs/security/GIT-REMOTE-REFS-WOOO-INFRA-CONFIG-SNAPSHOT.md` |
| GitHub target 決策 snapshot | `docs/security/github-target-decision.snapshot.json`,依前述 read-only evidence 人工彙整,非執行工具,不授權 repo 建立或 visibility 修改 |
| GitHub target repo-by-repo approval snapshot | `docs/security/github-target-repo-approval-package.snapshot.json`,逐 repo 拆分 approval path不授權執行 |
| Source Control draft reconcile plan | `docs/security/source-control-reconcile-plan.snapshot.json`,只產生 `draft_blocked` 草案,不授權 refs sync |
## 1.1 Gitea repo list snapshot
@@ -142,7 +144,7 @@ GitHub target repo-by-repo approval package 已建立於 `docs/security/GITHUB-T
| 欄位 | Gitea | GitHub | 狀態 |
|------|-------|--------|------|
| Repo | `wooo/awoooi` | `owenhytsai/awoooi` | 已有對應 |
| `main` | `a18e2f9c3f403050d0fb7476bf6fdb860225731a` | `202071f7a8724d5e8c29de441c3f380575a0ea94` | 不一致,阻塞主控切換 |
| `main` | `0bc187877884f6fa6fe87a03dab99e9c6622fd42` | `202071f7a8724d5e8c29de441c3f380575a0ea94` | 不一致,阻塞主控切換 |
| `release/v1.0` | `d15fb7d9f4bac86873d5c16b9c17c527b8f38bef` | `d15fb7d9f4bac86873d5c16b9c17c527b8f38bef` | 一致 |
| `dev` | `25889d4b8edcb83b6ec707c5eef3c21ae5d432b0` | 無 | GitHub 缺分支 |
| `drift/adopt-*` | 多條 | 無 | GitHub 缺分支 |
@@ -193,11 +195,11 @@ GitHub target repo-by-repo approval package 已建立於 `docs/security/GITHUB-T
"schema_version": "source_control_migration_event_v1",
"gitea_repo": "wooo/awoooi",
"github_repo": "owenhytsai/awoooi",
"branch_count_gitea": 115,
"branch_count_gitea": 117,
"branch_count_github": 2,
"tag_count_gitea": 2,
"tag_count_github": 0,
"latest_sha_gitea": "a18e2f9c3f403050d0fb7476bf6fdb860225731a",
"latest_sha_gitea": "0bc187877884f6fa6fe87a03dab99e9c6622fd42",
"latest_sha_github": "202071f7a8724d5e8c29de441c3f380575a0ea94",
"workflows_mapped": false,
"webhooks_mapped": false,
@@ -213,7 +215,7 @@ GitHub target repo-by-repo approval package 已建立於 `docs/security/GITHUB-T
1.`docs/security/GITEA-READONLY-INVENTORY-APPROVAL-PACKAGE.md` 取得 Gitea 只讀 repo inventory 批准,不使用寫入 token。
2.`github_target_decision_v1` 對需要人工批准的 target 做 owner / visibility / canonical 決策。
3. 產生每個 repo 的 heads/tags SHA diff。
3.`docs/security/SOURCE-CONTROL-RECONCILE-PLAN.md` 產生每個 repo 的 heads/tags SHA diff;仍不 push refs
4. 標記「可 mirror」、「需人工判斷」、「需封存」、「不可搬」。
5. 產出 GitHub primary ADR定義切換 gate 與 rollback。
6.`source_control_migration_event_v1``gitea_repo_inventory_v1``local_git_remote_inventory_v1` mirror 到 AwoooP初期只作為 evidence。

View File

@@ -11,7 +11,7 @@
## 0. 核心結論
目前 Security Supply Chain 已有 13 個主要契約可交給 AwoooP 消費。Manifest 的用途是把分散的 schema、snapshot、人讀文件、允許動作與禁止動作收成一份入口避免不同 Session 各自解讀。
目前 Security Supply Chain 已有 14 個主要契約可交給 AwoooP 消費。Manifest 的用途是把分散的 schema、snapshot、人讀文件、允許動作與禁止動作收成一份入口避免不同 Session 各自解讀。
初期預設仍是 `mirror_only`。Manifest 不授權 runtime enforcement、不授權 GitHub/Gitea 主控切換、不授權 repo 建立或 refs sync。
@@ -29,6 +29,7 @@
| `github_target_decision_v1` | mirror-only | GitHub target 決策 | `github-target-decision.snapshot.json` |
| `github_target_repo_approval_package_v1` | approval-only | 逐 repo approval queue draft | `github-target-repo-approval-package.snapshot.json` |
| `source_control_approval_board_v1` | approval-only | 逐 repo owner / visibility / canonical / refs 決策 board | `source-control-approval-board.snapshot.json` |
| `source_control_reconcile_plan_v1` | approval-only | refs-blocked repo 的 draft reconcile plan | `source-control-reconcile-plan.snapshot.json` |
| `local_repo_canonical_probe_v1` | mirror-only | momo/ewoooc lineage evidence | `local-repo-canonical-ewoooc-momo.snapshot.json` |
| `git_remote_refs_probe_v1` | mirror-only | 110 / GitHub remote refs readiness | `bitan-tsenyang``wooo-infra-config` |
| `approval_required_event_v1` | approval-only | 高風險 / 敏感邊界 approval | `gitea-readonly-inventory-approval.snapshot.json` |

View File

@@ -4,7 +4,7 @@
|------|------|
| 日期 | 2026-05-12 |
| 狀態 | S0/S1 read-only evidence 建置中 |
| 本階段完成 | Security Supply Chain contract manifest + Source Control Approval Board |
| 本階段完成 | Security Supply Chain contract manifest + Source Control Approval Board + Draft Reconcile Plan |
| 原則 | 低摩擦分階段文件、schema、read-only evidence 優先;不做 runtime enforcement、不切 primary |
## 0. 本階段完成後整體進度
@@ -16,8 +16,9 @@
| S1.0 Gitea 全量 inventory approval | 完成草案 | 已建立 read-only token / admin export approval package | 統帥或 repo owner 批准 |
| S1.1 GitHub target 決策 | 完成草案 | 8 個 target 候選7 個需人工批准3 個 `not_found_or_private` 不得自動建立 | owner / visibility / canonical approval |
| S1.2 GitHub target 逐 repo approval | 完成草案 | 7 個 approval-required targets 已拆成逐 repo pending package並彙整成 8-item approval board | 低摩擦逐項批准 |
| S1.2a refs reconcile plan | 完成草案 | `awoooi``clawbot-v5``wooo-aiops` 已產生 draft plan狀態仍為 `draft_blocked` | authenticated inventory + branch/tag diff + single-repo approval |
| S1.3 低摩擦 rollout policy | 完成草案 | observe-first / mirror-only matrix 已建立 | AwoooP read-only policy 消費 |
| S1.4 Contract manifest | 完成草案 | 13 個主要 contract 已集中成 manifest | AwoooP mirror-only contract registry |
| S1.4 Contract manifest | 完成草案 | 14 個主要 contract 已集中成 manifest | AwoooP mirror-only contract registry |
| S2 AwoooP mirror-only | 可交接 | `AWOOOP-MIRROR-ONLY-CONSUMPTION-CHECKLIST.md` 已列出可消費事件與禁止動作 | AwoooP 主線建立只讀入口 |
| S3 approval gate | 未開始 | 已定義哪些動作要進 approval | 不得繞過人工批准 |
| S4 migration execution | 未開始 | GitHub primary 長期方向已確認,但 refs / tags / workflow / secret 名稱尚未全量驗證 | SHA/tag/workflow parity 與 rollback ADR |
@@ -42,6 +43,8 @@
| GitHub target repo approval JSON | `docs/security/github-target-repo-approval-package.snapshot.json` |
| Source Control approval board | `docs/security/SOURCE-CONTROL-APPROVAL-BOARD.md` |
| Source Control approval board JSON | `docs/security/source-control-approval-board.snapshot.json` |
| Source Control draft reconcile plan | `docs/security/SOURCE-CONTROL-RECONCILE-PLAN.md` |
| Source Control draft reconcile plan JSON | `docs/security/source-control-reconcile-plan.snapshot.json` |
| 低摩擦 rollout policy | `docs/security/SECURITY-LOW-FRICTION-ROLLOUT-POLICY.md` |
| 低摩擦 rollout policy JSON | `docs/security/security-rollout-policy.snapshot.json` |
| Security Supply Chain contract manifest | `docs/security/SECURITY-SUPPLY-CHAIN-CONTRACT-MANIFEST.md` |
@@ -68,7 +71,7 @@
1. 等待 Gitea read-only inventory approval 被批准後,再用只讀 token 或管理匯出補 private/internal server-side 全量 repo list。
2.`SOURCE-CONTROL-APPROVAL-BOARD.md` 對 7 個 `approval_required=true` 的 GitHub target 做 owner / visibility / canonical 決策。
3.`awoooi``clawbot-v5``wooo-aiops` 做 refs / tags reconcile 計畫
3.`SOURCE-CONTROL-RECONCILE-PLAN.md``awoooi``clawbot-v5``wooo-aiops` 補 branch-by-branch / tag-by-tag diff仍不得 push refs
4.`ewoooc` / `momo-pro-system` 完成 server-side canonical 判定。
5. AwoooP 主線只建立 mirror-only / read-only policy 入口,不新增執行按鈕。
6. AwoooP 主線消費 `security_rollout_policy_v1` 時,只做 read-only policy不做 runtime blocking。

View File

@@ -10,6 +10,7 @@
| GitHub target probe | `docs/security/GITHUB-TARGET-PROBE-SNAPSHOT.md` |
| GitHub target 決策 | `docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md` |
| GitHub target repo approval | `docs/security/GITHUB-TARGET-REPO-APPROVAL-PACKAGE.md` |
| Source Control draft reconcile plan | `docs/security/SOURCE-CONTROL-RECONCILE-PLAN.md` |
| 原則 | 只做盤點與分類,不做同步、不刪除、不切 primary |
## 0. 核心結論
@@ -134,7 +135,7 @@ Repo-by-repo approval package 已建立7 個 approval-required targets 皆為
| Repo | Status | Gitea branches | GitHub branches | Gitea tags | GitHub tags | Gitea main | GitHub main | Evidence |
|------|--------|----------------|-----------------|------------|-------------|------------|-------------|----------|
| `wooo/awoooi` -> `owenhytsai/awoooi` | `blocked` | `115` | `2` | `2` | `0` | `a18e2f9c3f403050d0fb7476bf6fdb860225731a` | `202071f7a8724d5e8c29de441c3f380575a0ea94` | `docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md` |
| `wooo/awoooi` -> `owenhytsai/awoooi` | `blocked` | `117` | `2` | `2` | `0` | `0bc187877884f6fa6fe87a03dab99e9c6622fd42` | `202071f7a8724d5e8c29de441c3f380575a0ea94` | `docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md` |
| `wooo/clawbot-v5` -> `owenhytsai/clawbot-v5` | `blocked` | `1` | `1` | `1` | `0` | `22074fbe4d6ec6c11c86f76139eea55756d1d160` | `7a769de46450087f9d6a8ef0d2ac23ed15565d2c` | `docs/security/SOURCE-CONTROL-CLAWBOT-V5-SNAPSHOT.md` |
| `wooo/wooo-aiops` -> `owenhytsai/wooo-aiops` | `blocked` | `2` | `3` | `0` | `19` | `507384a2e1943f4183942bf17d7b52e223067853` | `7c7aa109d93da6d75d687d6ee5131151afee37e8` | `docs/security/SOURCE-CONTROL-WOOO-AIOPS-SNAPSHOT.md` |

View File

@@ -0,0 +1,137 @@
# Source Control Draft Reconcile Plan
| 項目 | 內容 |
|------|------|
| 日期 | 2026-05-12 |
| 狀態 | `draft_blocked` |
| 預設模式 | `plan_only` |
| inventory gate | `blocked` |
| gate 原因 | Gitea authenticated / admin_export server-side inventory 尚未完成;本 plan 只能作草案,不可執行 refs sync。 |
| plan count | 3 |
## 0. 核心結論
這份文件只是 refs reconcile 草案,不是同步腳本,也不授權任何 GitHub primary 切換。AwoooP 可以 mirror 成 approval candidate但不得執行 board item 或呼叫任何 push / sync 工具。
## 1. Repo 差異摘要
| Repo | Risk | Gitea branches | GitHub branches | Gitea tags | GitHub tags | Gitea main | GitHub main |
|------|------|----------------|-----------------|------------|-------------|------------|-------------|
| `wooo/awoooi -> owenhytsai/awoooi` | `HIGH` | `117` | `2` | `2` | `0` | `0bc18787` | `202071f7` |
| `wooo/clawbot-v5 -> owenhytsai/clawbot-v5` | `MEDIUM` | `1` | `1` | `1` | `0` | `22074fbe` | `7a769de4` |
| `wooo/wooo-aiops -> owenhytsai/wooo-aiops` | `MEDIUM` | `2` | `3` | `0` | `19` | `507384a2` | `7c7aa109` |
## 2. Draft Plan
### wooo/awoooi -> owenhytsai/awoooi
- 狀態:`blocked`
- 阻塞原因branches 尚未完全對齊tags 尚未完全對齊main SHA 不一致
- 允許現在做:
- 更新 read-only evidence
- 更新 approval board
- 產生 draft reconcile plan
- 讓 AwoooP mirror plan 狀態
- 草案步驟:
- 先確認目前 production deploy 真相來源與 deploy marker 流程,避免主控切換影響發版。
- 針對 `wooo/awoooi``owenhytsai/awoooi` 產生 branch-by-branch diff 表。
- 針對 `wooo/awoooi``owenhytsai/awoooi` 產生 tag-by-tag diff 表。
- 標記每個 diff 的真相來源候選Gitea、GitHub、人工指定或 deprecated。
- 列出 workflow / webhook / runner / secret 名稱差異,只記名稱不記 value。
- 產生 dry-run PR / ADR 草案,仍不 push refs。
- 執行前 gate
- Gitea authenticated 或 admin_export server-side repo inventory status=ok
- branch-by-branch SHA diff 已完成
- tag-by-tag SHA diff 已完成
- workflow / webhook / runner / secret 名稱 inventory 已完成
- repo owner / visibility / branch protection / CODEOWNERS 已確認
- rollback plan 與 GitHub primary ADR 已完成
- 人工批准只針對單一 repo 生效,不得批次套用到所有 repo
- 仍然禁止:
- push refs
- force push
- delete refs
- create GitHub repo
- change repo visibility
- switch GitHub primary
- disable Gitea
- move secret values
- Evidence refs
- `docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md`
### wooo/clawbot-v5 -> owenhytsai/clawbot-v5
- 狀態:`blocked`
- 阻塞原因branches 尚未完全對齊tags 尚未完全對齊main SHA 不一致
- 允許現在做:
- 更新 read-only evidence
- 更新 approval board
- 產生 draft reconcile plan
- 讓 AwoooP mirror plan 狀態
- 草案步驟:
- 針對 `wooo/clawbot-v5``owenhytsai/clawbot-v5` 產生 branch-by-branch diff 表。
- 針對 `wooo/clawbot-v5``owenhytsai/clawbot-v5` 產生 tag-by-tag diff 表。
- 標記每個 diff 的真相來源候選Gitea、GitHub、人工指定或 deprecated。
- 列出 workflow / webhook / runner / secret 名稱差異,只記名稱不記 value。
- 產生 dry-run PR / ADR 草案,仍不 push refs。
- 執行前 gate
- Gitea authenticated 或 admin_export server-side repo inventory status=ok
- branch-by-branch SHA diff 已完成
- tag-by-tag SHA diff 已完成
- workflow / webhook / runner / secret 名稱 inventory 已完成
- repo owner / visibility / branch protection / CODEOWNERS 已確認
- rollback plan 與 GitHub primary ADR 已完成
- 人工批准只針對單一 repo 生效,不得批次套用到所有 repo
- 仍然禁止:
- push refs
- force push
- delete refs
- create GitHub repo
- change repo visibility
- switch GitHub primary
- disable Gitea
- move secret values
- Evidence refs
- `docs/security/SOURCE-CONTROL-CLAWBOT-V5-SNAPSHOT.md`
### wooo/wooo-aiops -> owenhytsai/wooo-aiops
- 狀態:`blocked`
- 阻塞原因branches 尚未完全對齊tags 尚未完全對齊main SHA 不一致
- 允許現在做:
- 更新 read-only evidence
- 更新 approval board
- 產生 draft reconcile plan
- 讓 AwoooP mirror plan 狀態
- 草案步驟:
- 針對 `wooo/wooo-aiops``owenhytsai/wooo-aiops` 產生 branch-by-branch diff 表。
- 針對 `wooo/wooo-aiops``owenhytsai/wooo-aiops` 產生 tag-by-tag diff 表。
- 標記每個 diff 的真相來源候選Gitea、GitHub、人工指定或 deprecated。
- 列出 workflow / webhook / runner / secret 名稱差異,只記名稱不記 value。
- 產生 dry-run PR / ADR 草案,仍不 push refs。
- 執行前 gate
- Gitea authenticated 或 admin_export server-side repo inventory status=ok
- branch-by-branch SHA diff 已完成
- tag-by-tag SHA diff 已完成
- workflow / webhook / runner / secret 名稱 inventory 已完成
- repo owner / visibility / branch protection / CODEOWNERS 已確認
- rollback plan 與 GitHub primary ADR 已完成
- 人工批准只針對單一 repo 生效,不得批次套用到所有 repo
- 仍然禁止:
- push refs
- force push
- delete refs
- create GitHub repo
- change repo visibility
- switch GitHub primary
- disable Gitea
- move secret values
- Evidence refs
- `docs/security/SOURCE-CONTROL-WOOO-AIOPS-SNAPSHOT.md`
## 3. AwoooP 消費方式
1. 只 mirror `source_control_reconcile_plan_v1`
2. 只顯示 `draft_blocked` 與 blocking reason。
3. 可產生 approval candidate但不得自動批准。
4. 不得新增 execution action button。

View File

@@ -2,7 +2,7 @@
"schema_version": "security_supply_chain_contract_manifest_v1",
"status": "draft",
"default_enforcement_level": "mirror_only",
"contract_count": 13,
"contract_count": 14,
"contracts": [
{
"contract": "security_rollout_policy_v1",
@@ -137,6 +137,26 @@
],
"notes": "彙整 8 個 target其中 7 個 pending approvalauthenticated inventory gate 仍 blocked。"
},
{
"contract": "source_control_reconcile_plan_v1",
"schema_path": "docs/schemas/source_control_reconcile_plan_v1.schema.json",
"snapshot_paths": ["docs/security/source-control-reconcile-plan.snapshot.json"],
"human_docs": ["docs/security/SOURCE-CONTROL-RECONCILE-PLAN.md"],
"consumer": "AwoooP approval candidate / migration reviewer",
"consumption_mode": "approval_only",
"allowed_actions": [
"mirror_draft_reconcile_plan",
"display_refs_blocking_reason",
"request_single_repo_approval"
],
"forbidden_actions": [
"execute_reconcile_plan",
"push_refs",
"force_push",
"switch_github_primary"
],
"notes": "只針對 3 個 refs-blocked mapped repos 產生 draft planinventory gate 仍 blocked不可執行。"
},
{
"contract": "local_repo_canonical_probe_v1",
"schema_path": "docs/schemas/local_repo_canonical_probe_v1.schema.json",

View File

@@ -0,0 +1,176 @@
{
"schema_version": "source_control_reconcile_plan_v1",
"status": "draft_blocked",
"date": "2026-05-12",
"default_mode": "plan_only",
"inventory_gate": {
"status": "blocked",
"reason": "Gitea authenticated / admin_export server-side inventory 尚未完成;本 plan 只能作草案,不可執行 refs sync。",
"required_before_execution": [
"Gitea authenticated 或 admin_export server-side repo inventory status=ok",
"branch-by-branch SHA diff 已完成",
"tag-by-tag SHA diff 已完成",
"workflow / webhook / runner / secret 名稱 inventory 已完成",
"repo owner / visibility / branch protection / CODEOWNERS 已確認",
"rollback plan 與 GitHub primary ADR 已完成",
"人工批准只針對單一 repo 生效,不得批次套用到所有 repo"
]
},
"plan_count": 3,
"plans": [
{
"gitea_repo": "wooo/awoooi",
"github_repo": "owenhytsai/awoooi",
"risk": "HIGH",
"source_status": "blocked",
"divergence_summary": {
"gitea_branch_count": 117,
"github_branch_count": 2,
"gitea_tag_count": 2,
"github_tag_count": 0,
"gitea_main_sha": "0bc187877884f6fa6fe87a03dab99e9c6622fd42",
"github_main_sha": "202071f7a8724d5e8c29de441c3f380575a0ea94",
"blocking_reason": "branches 尚未完全對齊tags 尚未完全對齊main SHA 不一致"
},
"proposed_plan_steps": [
"先確認目前 production deploy 真相來源與 deploy marker 流程,避免主控切換影響發版。",
"針對 `wooo/awoooi` 與 `owenhytsai/awoooi` 產生 branch-by-branch diff 表。",
"針對 `wooo/awoooi` 與 `owenhytsai/awoooi` 產生 tag-by-tag diff 表。",
"標記每個 diff 的真相來源候選Gitea、GitHub、人工指定或 deprecated。",
"列出 workflow / webhook / runner / secret 名稱差異,只記名稱不記 value。",
"產生 dry-run PR / ADR 草案,仍不 push refs。"
],
"execution_gates": [
"Gitea authenticated 或 admin_export server-side repo inventory status=ok",
"branch-by-branch SHA diff 已完成",
"tag-by-tag SHA diff 已完成",
"workflow / webhook / runner / secret 名稱 inventory 已完成",
"repo owner / visibility / branch protection / CODEOWNERS 已確認",
"rollback plan 與 GitHub primary ADR 已完成",
"人工批准只針對單一 repo 生效,不得批次套用到所有 repo"
],
"allowed_now": [
"更新 read-only evidence",
"更新 approval board",
"產生 draft reconcile plan",
"讓 AwoooP mirror plan 狀態"
],
"still_forbidden": [
"push refs",
"force push",
"delete refs",
"create GitHub repo",
"change repo visibility",
"switch GitHub primary",
"disable Gitea",
"move secret values"
],
"evidence_refs": [
"docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md"
],
"awooop_consumption": "approval_candidate"
},
{
"gitea_repo": "wooo/clawbot-v5",
"github_repo": "owenhytsai/clawbot-v5",
"risk": "MEDIUM",
"source_status": "blocked",
"divergence_summary": {
"gitea_branch_count": 1,
"github_branch_count": 1,
"gitea_tag_count": 1,
"github_tag_count": 0,
"gitea_main_sha": "22074fbe4d6ec6c11c86f76139eea55756d1d160",
"github_main_sha": "7a769de46450087f9d6a8ef0d2ac23ed15565d2c",
"blocking_reason": "branches 尚未完全對齊tags 尚未完全對齊main SHA 不一致"
},
"proposed_plan_steps": [
"針對 `wooo/clawbot-v5` 與 `owenhytsai/clawbot-v5` 產生 branch-by-branch diff 表。",
"針對 `wooo/clawbot-v5` 與 `owenhytsai/clawbot-v5` 產生 tag-by-tag diff 表。",
"標記每個 diff 的真相來源候選Gitea、GitHub、人工指定或 deprecated。",
"列出 workflow / webhook / runner / secret 名稱差異,只記名稱不記 value。",
"產生 dry-run PR / ADR 草案,仍不 push refs。"
],
"execution_gates": [
"Gitea authenticated 或 admin_export server-side repo inventory status=ok",
"branch-by-branch SHA diff 已完成",
"tag-by-tag SHA diff 已完成",
"workflow / webhook / runner / secret 名稱 inventory 已完成",
"repo owner / visibility / branch protection / CODEOWNERS 已確認",
"rollback plan 與 GitHub primary ADR 已完成",
"人工批准只針對單一 repo 生效,不得批次套用到所有 repo"
],
"allowed_now": [
"更新 read-only evidence",
"更新 approval board",
"產生 draft reconcile plan",
"讓 AwoooP mirror plan 狀態"
],
"still_forbidden": [
"push refs",
"force push",
"delete refs",
"create GitHub repo",
"change repo visibility",
"switch GitHub primary",
"disable Gitea",
"move secret values"
],
"evidence_refs": [
"docs/security/SOURCE-CONTROL-CLAWBOT-V5-SNAPSHOT.md"
],
"awooop_consumption": "approval_candidate"
},
{
"gitea_repo": "wooo/wooo-aiops",
"github_repo": "owenhytsai/wooo-aiops",
"risk": "MEDIUM",
"source_status": "blocked",
"divergence_summary": {
"gitea_branch_count": 2,
"github_branch_count": 3,
"gitea_tag_count": 0,
"github_tag_count": 19,
"gitea_main_sha": "507384a2e1943f4183942bf17d7b52e223067853",
"github_main_sha": "7c7aa109d93da6d75d687d6ee5131151afee37e8",
"blocking_reason": "branches 尚未完全對齊tags 尚未完全對齊main SHA 不一致"
},
"proposed_plan_steps": [
"針對 `wooo/wooo-aiops` 與 `owenhytsai/wooo-aiops` 產生 branch-by-branch diff 表。",
"針對 `wooo/wooo-aiops` 與 `owenhytsai/wooo-aiops` 產生 tag-by-tag diff 表。",
"標記每個 diff 的真相來源候選Gitea、GitHub、人工指定或 deprecated。",
"列出 workflow / webhook / runner / secret 名稱差異,只記名稱不記 value。",
"產生 dry-run PR / ADR 草案,仍不 push refs。"
],
"execution_gates": [
"Gitea authenticated 或 admin_export server-side repo inventory status=ok",
"branch-by-branch SHA diff 已完成",
"tag-by-tag SHA diff 已完成",
"workflow / webhook / runner / secret 名稱 inventory 已完成",
"repo owner / visibility / branch protection / CODEOWNERS 已確認",
"rollback plan 與 GitHub primary ADR 已完成",
"人工批准只針對單一 repo 生效,不得批次套用到所有 repo"
],
"allowed_now": [
"更新 read-only evidence",
"更新 approval board",
"產生 draft reconcile plan",
"讓 AwoooP mirror plan 狀態"
],
"still_forbidden": [
"push refs",
"force push",
"delete refs",
"create GitHub repo",
"change repo visibility",
"switch GitHub primary",
"disable Gitea",
"move secret values"
],
"evidence_refs": [
"docs/security/SOURCE-CONTROL-WOOO-AIOPS-SNAPSHOT.md"
],
"awooop_consumption": "approval_candidate"
}
]
}

View File

@@ -0,0 +1,234 @@
#!/usr/bin/env python3
"""產生 source-control refs reconcile plan 草案。
此工具只讀取既有 redacted snapshot不呼叫遠端 Git不 push、不 fetch、不改 remote。
輸出用途是 review / AwoooP mirror / approval candidate不是 execution plan runner。
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
DEFAULT_STILL_FORBIDDEN = [
"push refs",
"force push",
"delete refs",
"create GitHub repo",
"change repo visibility",
"switch GitHub primary",
"disable Gitea",
"move secret values",
]
DEFAULT_EXECUTION_GATES = [
"Gitea authenticated 或 admin_export server-side repo inventory status=ok",
"branch-by-branch SHA diff 已完成",
"tag-by-tag SHA diff 已完成",
"workflow / webhook / runner / secret 名稱 inventory 已完成",
"repo owner / visibility / branch protection / CODEOWNERS 已確認",
"rollback plan 與 GitHub primary ADR 已完成",
"人工批准只針對單一 repo 生效,不得批次套用到所有 repo",
]
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def risk_for_repo(gitea_repo: str) -> str:
if gitea_repo == "wooo/awoooi":
return "HIGH"
return "MEDIUM"
def proposed_steps(event: dict[str, Any]) -> list[str]:
gitea_repo = str(event.get("gitea_repo", ""))
github_repo = str(event.get("github_repo", ""))
steps = [
f"針對 `{gitea_repo}` 與 `{github_repo}` 產生 branch-by-branch diff 表。",
f"針對 `{gitea_repo}` 與 `{github_repo}` 產生 tag-by-tag diff 表。",
"標記每個 diff 的真相來源候選Gitea、GitHub、人工指定或 deprecated。",
"列出 workflow / webhook / runner / secret 名稱差異,只記名稱不記 value。",
"產生 dry-run PR / ADR 草案,仍不 push refs。",
]
if gitea_repo == "wooo/awoooi":
steps.insert(
0,
"先確認目前 production deploy 真相來源與 deploy marker 流程,避免主控切換影響發版。",
)
return steps
def build_plan(args: argparse.Namespace) -> dict[str, Any]:
events = [load_json(Path(path)) for path in args.source_events]
gitea_inventory = load_json(Path(args.gitea_inventory))
inventory_ready = gitea_inventory.get("status") == "ok"
inventory_gate_status = "ready" if inventory_ready else "blocked"
inventory_reason = (
"Gitea server-side inventory 已完成,可在人工批准後進入更細 diff。"
if inventory_ready
else "Gitea authenticated / admin_export server-side inventory 尚未完成;本 plan 只能作草案,不可執行 refs sync。"
)
plans: list[dict[str, Any]] = []
for event in events:
gitea_repo = str(event.get("gitea_repo", ""))
github_repo = str(event.get("github_repo", ""))
plans.append(
{
"gitea_repo": gitea_repo,
"github_repo": github_repo,
"risk": risk_for_repo(gitea_repo),
"source_status": str(event.get("status", "")),
"divergence_summary": {
"gitea_branch_count": int(event.get("branch_count_gitea", 0)),
"github_branch_count": int(event.get("branch_count_github", 0)),
"gitea_tag_count": int(event.get("tag_count_gitea", 0)),
"github_tag_count": int(event.get("tag_count_github", 0)),
"gitea_main_sha": str(event.get("latest_sha_gitea", "")),
"github_main_sha": str(event.get("latest_sha_github", "")),
"blocking_reason": str(event.get("blocking_reason", "")),
},
"proposed_plan_steps": proposed_steps(event),
"execution_gates": DEFAULT_EXECUTION_GATES,
"allowed_now": [
"更新 read-only evidence",
"更新 approval board",
"產生 draft reconcile plan",
"讓 AwoooP mirror plan 狀態",
],
"still_forbidden": DEFAULT_STILL_FORBIDDEN,
"evidence_refs": [str(value) for value in event.get("evidence_refs", [])],
"awooop_consumption": "approval_candidate",
}
)
return {
"schema_version": "source_control_reconcile_plan_v1",
"status": "draft_blocked",
"date": args.date,
"default_mode": "plan_only",
"inventory_gate": {
"status": inventory_gate_status,
"reason": inventory_reason,
"required_before_execution": DEFAULT_EXECUTION_GATES,
},
"plan_count": len(plans),
"plans": plans,
}
def short_sha(value: str) -> str:
return value[:8] if value else ""
def write_markdown(plan: dict[str, Any], path: Path) -> None:
gate = plan["inventory_gate"]
lines = [
"# Source Control Draft Reconcile Plan",
"",
"| 項目 | 內容 |",
"|------|------|",
f"| 日期 | {plan['date']} |",
f"| 狀態 | `{plan['status']}` |",
f"| 預設模式 | `{plan['default_mode']}` |",
f"| inventory gate | `{gate['status']}` |",
f"| gate 原因 | {gate['reason']} |",
f"| plan count | {plan['plan_count']} |",
"",
"## 0. 核心結論",
"",
"這份文件只是 refs reconcile 草案,不是同步腳本,也不授權任何 GitHub primary 切換。AwoooP 可以 mirror 成 approval candidate但不得執行 board item 或呼叫任何 push / sync 工具。",
"",
"## 1. Repo 差異摘要",
"",
"| Repo | Risk | Gitea branches | GitHub branches | Gitea tags | GitHub tags | Gitea main | GitHub main |",
"|------|------|----------------|-----------------|------------|-------------|------------|-------------|",
]
for item in plan["plans"]:
diff = item["divergence_summary"]
lines.append(
"| "
+ " | ".join(
[
f"`{item['gitea_repo']} -> {item['github_repo']}`",
f"`{item['risk']}`",
f"`{diff['gitea_branch_count']}`",
f"`{diff['github_branch_count']}`",
f"`{diff['gitea_tag_count']}`",
f"`{diff['github_tag_count']}`",
f"`{short_sha(diff['gitea_main_sha'])}`",
f"`{short_sha(diff['github_main_sha'])}`",
]
)
+ " |"
)
lines.extend(["", "## 2. Draft Plan", ""])
for item in plan["plans"]:
diff = item["divergence_summary"]
lines.extend(
[
f"### {item['gitea_repo']} -> {item['github_repo']}",
"",
f"- 狀態:`{item['source_status']}`",
f"- 阻塞原因:{diff['blocking_reason']}",
"- 允許現在做:",
]
)
for value in item["allowed_now"]:
lines.append(f" - {value}")
lines.append("- 草案步驟:")
for value in item["proposed_plan_steps"]:
lines.append(f" - {value}")
lines.append("- 執行前 gate")
for value in item["execution_gates"]:
lines.append(f" - {value}")
lines.append("- 仍然禁止:")
for value in item["still_forbidden"]:
lines.append(f" - {value}")
lines.append("- Evidence refs")
for value in item["evidence_refs"]:
lines.append(f" - `{value}`")
lines.append("")
lines.extend(
[
"## 3. AwoooP 消費方式",
"",
"1. 只 mirror `source_control_reconcile_plan_v1`。",
"2. 只顯示 `draft_blocked` 與 blocking reason。",
"3. 可產生 approval candidate但不得自動批准。",
"4. 不得新增 execution action button。",
"",
]
)
path.write_text("\n".join(lines), encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--date", required=True)
parser.add_argument("--gitea-inventory", default="docs/security/gitea-repo-inventory.snapshot.json")
parser.add_argument("--source-event", action="append", dest="source_events", required=True)
parser.add_argument("--output-json", required=True)
parser.add_argument("--output-md", required=True)
args = parser.parse_args()
plan = build_plan(args)
Path(args.output_json).write_text(
json.dumps(plan, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
write_markdown(plan, Path(args.output_md))
print(f"OK source-control reconcile plan count={plan['plan_count']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())