docs(security): add source control approval board [skip ci]

This commit is contained in:
Your Name
2026-05-12 23:43:48 +08:00
parent 5b144f0c92
commit cd72808560
9 changed files with 905 additions and 6 deletions

View File

@@ -1,3 +1,27 @@
## 2026-05-12 | Source Control Approval Board 低摩擦決策隊列
**背景**:統帥批准繼續推進後,下一步原本是 Gitea authenticated read-only inventory但目前 `GITEA_READONLY_TOKEN` 未提供。本輪因此不使用可 push 的既有 Gitea remote credential 代替 read-only token避免把 inventory 與寫入權限憑證混在一起。
**本次交付**
- 新增 `scripts/security/source-control-approval-board.py`,只讀既有 redacted snapshot不呼叫 Gitea/GitHub API不需要 token。
- 新增 `docs/schemas/source_control_approval_board_v1.schema.json`
- 產出 `docs/security/source-control-approval-board.snapshot.json``docs/security/SOURCE-CONTROL-APPROVAL-BOARD.md`
- Board 彙整 8 個 target其中 7 個為 pending approval`awoooi``clawbot-v5``wooo-aiops``wooo-infra-config``ewoooc``bitan-pharmacy``tsenyang-website``nexu-io/open-design` 維持 scope review。
- 更新 `SECURITY-SUPPLY-CHAIN-CONTRACT-MANIFEST`contract count 從 12 增至 13新增 `source_control_approval_board_v1`
- 更新 `SECURITY-SUPPLY-CHAIN-PROGRESS``AWOOOP-MIRROR-ONLY-CONSUMPTION-CHECKLIST`,讓 AwoooP 可 mirror board 但不得執行 board item。
**邊界**
- 未使用 Gitea write-capable remote credential 做 authenticated inventory。
- 未建立 GitHub repo、未改 visibility、未同步 refs、未切 GitHub primary、未部署。
- authenticated inventory gate 仍為 `blocked`,等待 read-only token 或 redacted admin export。
**驗證**
- `source-control-approval-board.py` 產生 8 itemspending approval 7。
- JSON / schema / snapshot parse 通過。
- `scripts/security/*.py` 可編譯。
- `git diff --check` 通過。
- PR diff added lines 未命中本輪敏感 token / credential pattern。
## 2026-05-12 | Security Supply Chain PR #117 與 AwoooP 主線同步
**背景**Security Supply Chain docs-only 分支完成首次推版後,另一個 AwoooP Session 已將 `feat(awooop): harden outbound truth chain mirror` 與 deploy marker 推入 `gitea/main`。為避免雙 Session 推進互相衝突,本輪先把最新 `gitea/main` 合入資安分支,再建立 review-only PR。

View File

@@ -0,0 +1,134 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:awoooi:source-control-approval-board-v1",
"title": "AWOOOI Source Control Approval Board (v1)",
"description": "把 Gitea -> GitHub 低摩擦遷移中的逐 repo owner / visibility / canonical / refs reconcile 決策整理成可供 AwoooP mirror 的只讀 board。",
"type": "object",
"required": [
"schema_version",
"status",
"date",
"default_mode",
"authenticated_inventory_gate",
"item_count",
"pending_approval_count",
"board_items"
],
"properties": {
"schema_version": {
"const": "source_control_approval_board_v1"
},
"status": {
"type": "string",
"enum": ["draft"]
},
"date": {
"type": "string"
},
"default_mode": {
"type": "string",
"enum": ["mirror_only"]
},
"authenticated_inventory_gate": {
"type": "object",
"required": [
"status",
"reason",
"allowed_next_step",
"still_forbidden"
],
"properties": {
"status": {
"type": "string",
"enum": ["blocked", "ready"]
},
"reason": {
"type": "string"
},
"allowed_next_step": {
"type": "array",
"items": {"type": "string"}
},
"still_forbidden": {
"type": "array",
"items": {"type": "string"}
}
},
"additionalProperties": false
},
"item_count": {
"type": "integer",
"minimum": 0
},
"pending_approval_count": {
"type": "integer",
"minimum": 0
},
"board_items": {
"type": "array",
"items": {
"type": "object",
"required": [
"github_repo",
"source_key",
"lane",
"risk",
"probe_status",
"target_state",
"approval_status",
"required_decision",
"low_friction_next_step",
"blocked_until",
"allowed_after_approval",
"still_forbidden",
"evidence_refs",
"awooop_consumption"
],
"properties": {
"github_repo": {"type": "string"},
"source_key": {"type": "string"},
"lane": {
"type": "string",
"enum": [
"refs_reconcile",
"target_creation_or_access",
"internal_remote_purpose",
"scope_review"
]
},
"risk": {
"type": "string",
"enum": ["LOW", "MEDIUM", "HIGH"]
},
"probe_status": {"type": "string"},
"target_state": {"type": "string"},
"approval_status": {"type": "string"},
"required_decision": {"type": "string"},
"low_friction_next_step": {"type": "string"},
"blocked_until": {
"type": "array",
"items": {"type": "string"}
},
"allowed_after_approval": {
"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", "scope_review_only"]
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}

View File

@@ -33,6 +33,7 @@ AwoooP 初期不得直接啟動掃描、不得呼叫 Codex patch runner、不得
| `github_target_probe_v1` | 候選 GitHub repo read-only probe | Migration target evidence | mirror-only | `not_found_or_private` 不等同確認不存在 |
| `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 |
| `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` |
@@ -81,6 +82,7 @@ AwoooP 初期不得直接啟動掃描、不得呼叫 Codex patch runner、不得
| `github_target_probe_v1.status=ok` 且有 `not_found_or_private` | `observe` | 補 GitHub target 決策,不自動建立 repo |
| `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 |
| `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不阻擋既有流程 |
@@ -121,6 +123,7 @@ AwoooP 初期不得直接啟動掃描、不得呼叫 Codex patch runner、不得
| GitHub target probe snapshot | `docs/security/github-target-probe.snapshot.json` / `docs/security/GITHUB-TARGET-PROBE-SNAPSHOT.md` |
| 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` |
| 本機 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

@@ -11,7 +11,7 @@
## 0. 核心結論
目前 Security Supply Chain 已有 12 個主要契約可交給 AwoooP 消費。Manifest 的用途是把分散的 schema、snapshot、人讀文件、允許動作與禁止動作收成一份入口避免不同 Session 各自解讀。
目前 Security Supply Chain 已有 13 個主要契約可交給 AwoooP 消費。Manifest 的用途是把分散的 schema、snapshot、人讀文件、允許動作與禁止動作收成一份入口避免不同 Session 各自解讀。
初期預設仍是 `mirror_only`。Manifest 不授權 runtime enforcement、不授權 GitHub/Gitea 主控切換、不授權 repo 建立或 refs sync。
@@ -28,6 +28,7 @@
| `github_target_probe_v1` | mirror-only | GitHub target visibility | `github-target-probe.snapshot.json` |
| `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` |
| `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 |
| 本階段完成 | Security Supply Chain contract manifest + Source Control Approval Board |
| 原則 | 低摩擦分階段文件、schema、read-only evidence 優先;不做 runtime enforcement、不切 primary |
## 0. 本階段完成後整體進度
@@ -15,9 +15,9 @@
| S1 source-control read-only inventory | 進行中 | 已有 Gitea/GitHub refs、Gitea public-only user repo list、本機 remote、GitHub target probe、canonical lineage、110 refs evidence | Gitea private/internal 全量 repo list |
| 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 | 低摩擦逐項批准 |
| S1.2 GitHub target 逐 repo approval | 完成草案 | 7 個 approval-required targets 已拆成逐 repo pending package,並彙整成 8-item approval board | 低摩擦逐項批准 |
| S1.3 低摩擦 rollout policy | 完成草案 | observe-first / mirror-only matrix 已建立 | AwoooP read-only policy 消費 |
| S1.4 Contract manifest | 完成草案 | 12 個主要 contract 已集中成 manifest | AwoooP mirror-only contract registry |
| S1.4 Contract manifest | 完成草案 | 13 個主要 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 |
@@ -40,6 +40,8 @@
| GitHub target 決策 JSON | `docs/security/github-target-decision.snapshot.json` |
| GitHub target repo approval package | `docs/security/GITHUB-TARGET-REPO-APPROVAL-PACKAGE.md` |
| 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` |
| 低摩擦 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` |
@@ -65,7 +67,7 @@
## 3. 下一階段建議
1. 等待 Gitea read-only inventory approval 被批准後,再用只讀 token 或管理匯出補 private/internal server-side 全量 repo list。
2. 對 7 個 `approval_required=true` 的 GitHub target 做 owner / visibility / canonical 決策。
2.`SOURCE-CONTROL-APPROVAL-BOARD.md` 對 7 個 `approval_required=true` 的 GitHub target 做 owner / visibility / canonical 決策。
3.`awoooi``clawbot-v5``wooo-aiops` 做 refs / tags reconcile 計畫。
4.`ewoooc` / `momo-pro-system` 完成 server-side canonical 判定。
5. AwoooP 主線只建立 mirror-only / read-only policy 入口,不新增執行按鈕。

View File

@@ -0,0 +1,186 @@
# Source Control Approval Board
| 項目 | 內容 |
|------|------|
| 日期 | 2026-05-12 |
| 狀態 | `draft` |
| 預設模式 | `mirror_only` |
| authenticated inventory gate | `blocked` |
| gate 原因 | GITEA_READONLY_TOKEN 未提供,且不使用可 push 的既有 remote credential 當 read-only tokenserver-side private/internal repo list 仍未完成。 |
| repo items | 8 |
| pending approval | 7 |
## 0. 核心原則
本 board 只整理決策不授權執行。AwoooP 可以 mirror 成 approval candidate但不得建立 repo、修改 visibility、同步 refs、切 GitHub primary 或保存 credential value。
## 1. 逐 repo 決策隊列
| GitHub repo | Lane | Risk | Probe | Approval | 下一步 |
|-------------|------|------|-------|----------|--------|
| `owenhytsai/awoooi` | `refs_reconcile` | `HIGH` | `exists` | `pending` | 先產生 draft reconcile plan不 push refs、不切 primary。 |
| `owenhytsai/clawbot-v5` | `refs_reconcile` | `MEDIUM` | `exists` | `pending` | 先產生 draft reconcile plan不 push refs、不切 primary。 |
| `owenhytsai/wooo-aiops` | `refs_reconcile` | `MEDIUM` | `exists` | `pending` | 先產生 draft reconcile plan不 push refs、不切 primary。 |
| `owenhytsai/wooo-infra-config` | `internal_remote_purpose` | `MEDIUM` | `exists` | `pending` | 先文件化用途與風險,不刪除 remote、不同步 refs。 |
| `owenhytsai/ewoooc` | `target_creation_or_access` | `HIGH` | `not_found_or_private` | `pending` | 先取得 owner / visibility 決策,不自動建立 repo。 |
| `owenhytsai/bitan-pharmacy` | `target_creation_or_access` | `MEDIUM` | `not_found_or_private` | `pending` | 先取得 owner / visibility 決策,不自動建立 repo。 |
| `owenhytsai/tsenyang-website` | `target_creation_or_access` | `MEDIUM` | `not_found_or_private` | `pending` | 先取得 owner / visibility 決策,不自動建立 repo。 |
| `nexu-io/open-design` | `scope_review` | `LOW` | `exists` | `not_required` | 只標記 scope review不納入主控切換。 |
## 2. 詳細阻塞點
### owenhytsai/awoooi
- Source key`wooo/awoooi`
- Required decision決定 Gitea / GitHub refs 真相來源,並批准只產生 reconcile plan。
- AwoooP consumption`approval_candidate`
- Blocked until
- Gitea server-side 全量 repo inventory status=ok
- branches/tags/workflows/webhooks/secrets 名稱 inventory 完成
- 部署真相來源已決定
- GitHub primary ADR 與 rollback plan 完成
- Still forbidden
- 直接 push refs
- 直接切 GitHub primary
- 直接停用 Gitea
- 搬 secret value
- Evidence refs
- `docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md`
- `docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md`
- `docs/security/github-target-probe.snapshot.json`
### owenhytsai/clawbot-v5
- Source key`wooo/clawbot-v5`
- Required decision決定 Gitea / GitHub refs 真相來源,並批准只產生 reconcile plan。
- AwoooP consumption`approval_candidate`
- Blocked until
- Gitea/GitHub main SHA 對齊或人工指定真相來源
- GitHub 缺 Gitea tag 的處理方式已決定
- Still forbidden
- 直接 push refs
- 直接切 primary
- 刪除任一端 repo
- Evidence refs
- `docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md`
- `docs/security/SOURCE-CONTROL-CLAWBOT-V5-SNAPSHOT.md`
- `docs/security/github-target-probe.snapshot.json`
### owenhytsai/wooo-aiops
- Source key`wooo/wooo-aiops`
- Required decision決定 Gitea / GitHub refs 真相來源,並批准只產生 reconcile plan。
- AwoooP consumption`approval_candidate`
- Blocked until
- Gitea/GitHub main SHA 對齊或人工指定真相來源
- GitHub-only branch 與 tags 的來源已釐清
- Still forbidden
- 直接 push refs
- 直接切 primary
- 刪除 GitHub-only refs
- Evidence refs
- `docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md`
- `docs/security/SOURCE-CONTROL-WOOO-AIOPS-SNAPSHOT.md`
- `docs/security/github-target-probe.snapshot.json`
### owenhytsai/wooo-infra-config
- Source key`wooo/wooo-infra-config`
- Required decision決定 110 internal remote 是 active source、legacy mirror 或應降級。
- AwoooP consumption`approval_candidate`
- Blocked until
- 110 internal remote 用途已確認
- 若 110 remote 為舊主控,已降級或移除
- infra secrets 名稱 inventory 完成
- Still forbidden
- 直接刪除 remote
- 直接同步 refs
- 搬 infra secret value
- Evidence refs
- `docs/security/GIT-REMOTE-REFS-WOOO-INFRA-CONFIG-SNAPSHOT.md`
- `docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md`
- `docs/security/github-target-probe.snapshot.json`
### owenhytsai/ewoooc
- Source key`wooo/ewoooc / root/momo-pro-system / momo working trees`
- Required decision決定 GitHub repo owner / visibility / 是否建立或授權既有 repo。
- AwoooP consumption`approval_candidate`
- Blocked until
- ewoooc/momo-pro-system canonical 關係人工確認
- server-side refs diff 完成
- GitHub repo owner 與 visibility 決策完成
- Still forbidden
- 自動建立 mirror
- 自動合併 unrelated histories
- 刪除任一 momo/ewoooc working tree
- 切 GitHub primary
- Evidence refs
- `docs/security/GITEA-PUBLIC-REPO-SEARCH-SNAPSHOT.md`
- `docs/security/GITEA-REPO-INVENTORY-SNAPSHOT.md`
- `docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md`
- `docs/security/LOCAL-REPO-CANONICAL-EWOOOC-MOMO-SNAPSHOT.md`
- `docs/security/github-target-probe.snapshot.json`
### owenhytsai/bitan-pharmacy
- Source key`bitan-pharmacy`
- Required decision決定 GitHub repo owner / visibility / 是否建立或授權既有 repo。
- AwoooP consumption`approval_candidate`
- Blocked until
- 確認 repo 是否仍 active
- GitHub repo owner 與 visibility 決策完成
- Still forbidden
- 自動建立 repo
- 自動 push refs
- 刪除 110 remote
- Evidence refs
- `docs/security/GIT-REMOTE-REFS-BITAN-TSENYANG-SNAPSHOT.md`
- `docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md`
- `docs/security/github-target-probe.snapshot.json`
### owenhytsai/tsenyang-website
- Source key`tsenyang-website`
- Required decision決定 GitHub repo owner / visibility / 是否建立或授權既有 repo。
- AwoooP consumption`approval_candidate`
- Blocked until
- 確認 repo 是否仍 active
- GitHub repo owner 與 visibility 決策完成
- Still forbidden
- 自動建立 repo
- 自動 push refs
- 刪除 110 remote
- Evidence refs
- `docs/security/GIT-REMOTE-REFS-BITAN-TSENYANG-SNAPSHOT.md`
- `docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md`
- `docs/security/github-target-probe.snapshot.json`
### nexu-io/open-design
- Source key`open-design`
- Required decision決定此 repo 是否屬於 AWOOOI 資安供應鏈範圍。
- AwoooP consumption`scope_review_only`
- Blocked until
- 確認是否屬於 AWOOOI 資安網範圍
- Still forbidden
- auto_execute
- sync_refs
- switch_primary
- Evidence refs
- `docs/security/github-target-probe.snapshot.json`
## 3. Gate 前允許做的事
1. 更新 read-only evidence。
2. 更新 approval board / decision table。
3. 寫 draft reconcile plan。
4. 把 pending approval mirror 到 AwoooP。
## 4. Gate 前仍禁止
- 使用 write-capable credential 當作 read-only token
- 建立 GitHub repo
- 修改 repo visibility
- sync refs
- switch GitHub primary

View File

@@ -2,7 +2,7 @@
"schema_version": "security_supply_chain_contract_manifest_v1",
"status": "draft",
"default_enforcement_level": "mirror_only",
"contract_count": 12,
"contract_count": 13,
"contracts": [
{
"contract": "security_rollout_policy_v1",
@@ -117,6 +117,26 @@
"forbidden_actions": ["execute_approval_item", "push_refs", "change_visibility"],
"notes": "7 個 pending packages逐 repo 低摩擦批准。"
},
{
"contract": "source_control_approval_board_v1",
"schema_path": "docs/schemas/source_control_approval_board_v1.schema.json",
"snapshot_paths": ["docs/security/source-control-approval-board.snapshot.json"],
"human_docs": ["docs/security/SOURCE-CONTROL-APPROVAL-BOARD.md"],
"consumer": "AwoooP approval board / PR reviewer",
"consumption_mode": "approval_only",
"allowed_actions": [
"mirror_repo_decision_board",
"display_pending_owner_visibility_canonical_decisions",
"request_human_approval"
],
"forbidden_actions": [
"execute_board_item",
"sync_refs",
"create_repo",
"switch_github_primary"
],
"notes": "彙整 8 個 target其中 7 個 pending approvalauthenticated inventory gate 仍 blocked。"
},
{
"contract": "local_repo_canonical_probe_v1",
"schema_path": "docs/schemas/local_repo_canonical_probe_v1.schema.json",

View File

@@ -0,0 +1,271 @@
{
"schema_version": "source_control_approval_board_v1",
"status": "draft",
"date": "2026-05-12",
"default_mode": "mirror_only",
"authenticated_inventory_gate": {
"status": "blocked",
"reason": "GITEA_READONLY_TOKEN 未提供,且不使用可 push 的既有 remote credential 當 read-only tokenserver-side private/internal repo list 仍未完成。",
"allowed_next_step": [
"提供 read-only token 後重跑 gitea-repo-inventory",
"或提供 redacted admin export JSON",
"在 gate 前仍可維護 approval board 與 decision table"
],
"still_forbidden": [
"使用 write-capable credential 當作 read-only token",
"建立 GitHub repo",
"修改 repo visibility",
"sync refs",
"switch GitHub primary"
]
},
"item_count": 8,
"pending_approval_count": 7,
"board_items": [
{
"github_repo": "owenhytsai/awoooi",
"source_key": "wooo/awoooi",
"lane": "refs_reconcile",
"risk": "HIGH",
"probe_status": "exists",
"target_state": "exists_refs_blocked",
"approval_status": "pending",
"required_decision": "決定 Gitea / GitHub refs 真相來源,並批准只產生 reconcile plan。",
"low_friction_next_step": "先產生 draft reconcile plan不 push refs、不切 primary。",
"blocked_until": [
"Gitea server-side 全量 repo inventory status=ok",
"branches/tags/workflows/webhooks/secrets 名稱 inventory 完成",
"部署真相來源已決定",
"GitHub primary ADR 與 rollback plan 完成"
],
"allowed_after_approval": [
"產生 refs reconcile plan",
"產生 draft migration PR 或 ADR",
"更新 migration matrix 與 evidence"
],
"still_forbidden": [
"直接 push refs",
"直接切 GitHub primary",
"直接停用 Gitea",
"搬 secret value"
],
"evidence_refs": [
"docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md",
"docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md",
"docs/security/github-target-probe.snapshot.json"
],
"awooop_consumption": "approval_candidate"
},
{
"github_repo": "owenhytsai/clawbot-v5",
"source_key": "wooo/clawbot-v5",
"lane": "refs_reconcile",
"risk": "MEDIUM",
"probe_status": "exists",
"target_state": "exists_refs_blocked",
"approval_status": "pending",
"required_decision": "決定 Gitea / GitHub refs 真相來源,並批准只產生 reconcile plan。",
"low_friction_next_step": "先產生 draft reconcile plan不 push refs、不切 primary。",
"blocked_until": [
"Gitea/GitHub main SHA 對齊或人工指定真相來源",
"GitHub 缺 Gitea tag 的處理方式已決定"
],
"allowed_after_approval": [
"產生 refs reconcile plan",
"更新 migration matrix"
],
"still_forbidden": [
"直接 push refs",
"直接切 primary",
"刪除任一端 repo"
],
"evidence_refs": [
"docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md",
"docs/security/SOURCE-CONTROL-CLAWBOT-V5-SNAPSHOT.md",
"docs/security/github-target-probe.snapshot.json"
],
"awooop_consumption": "approval_candidate"
},
{
"github_repo": "owenhytsai/wooo-aiops",
"source_key": "wooo/wooo-aiops",
"lane": "refs_reconcile",
"risk": "MEDIUM",
"probe_status": "exists",
"target_state": "exists_refs_blocked",
"approval_status": "pending",
"required_decision": "決定 Gitea / GitHub refs 真相來源,並批准只產生 reconcile plan。",
"low_friction_next_step": "先產生 draft reconcile plan不 push refs、不切 primary。",
"blocked_until": [
"Gitea/GitHub main SHA 對齊或人工指定真相來源",
"GitHub-only branch 與 tags 的來源已釐清"
],
"allowed_after_approval": [
"產生 refs reconcile plan",
"更新 migration matrix"
],
"still_forbidden": [
"直接 push refs",
"直接切 primary",
"刪除 GitHub-only refs"
],
"evidence_refs": [
"docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md",
"docs/security/SOURCE-CONTROL-WOOO-AIOPS-SNAPSHOT.md",
"docs/security/github-target-probe.snapshot.json"
],
"awooop_consumption": "approval_candidate"
},
{
"github_repo": "owenhytsai/wooo-infra-config",
"source_key": "wooo/wooo-infra-config",
"lane": "internal_remote_purpose",
"risk": "MEDIUM",
"probe_status": "exists",
"target_state": "exists_aligned",
"approval_status": "pending",
"required_decision": "決定 110 internal remote 是 active source、legacy mirror 或應降級。",
"low_friction_next_step": "先文件化用途與風險,不刪除 remote、不同步 refs。",
"blocked_until": [
"110 internal remote 用途已確認",
"若 110 remote 為舊主控,已降級或移除",
"infra secrets 名稱 inventory 完成"
],
"allowed_after_approval": [
"標記 110 remote 為 mirror、legacy 或 active source",
"更新 canonical decision table"
],
"still_forbidden": [
"直接刪除 remote",
"直接同步 refs",
"搬 infra secret value"
],
"evidence_refs": [
"docs/security/GIT-REMOTE-REFS-WOOO-INFRA-CONFIG-SNAPSHOT.md",
"docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md",
"docs/security/github-target-probe.snapshot.json"
],
"awooop_consumption": "approval_candidate"
},
{
"github_repo": "owenhytsai/ewoooc",
"source_key": "wooo/ewoooc / root/momo-pro-system / momo working trees",
"lane": "target_creation_or_access",
"risk": "HIGH",
"probe_status": "not_found_or_private",
"target_state": "not_found_or_private",
"approval_status": "pending",
"required_decision": "決定 GitHub repo owner / visibility / 是否建立或授權既有 repo。",
"low_friction_next_step": "先取得 owner / visibility 決策,不自動建立 repo。",
"blocked_until": [
"ewoooc/momo-pro-system canonical 關係人工確認",
"server-side refs diff 完成",
"GitHub repo owner 與 visibility 決策完成"
],
"allowed_after_approval": [
"決定建立 GitHub repo 或授權既有 private repo",
"產生 migration plan"
],
"still_forbidden": [
"自動建立 mirror",
"自動合併 unrelated histories",
"刪除任一 momo/ewoooc working tree",
"切 GitHub primary"
],
"evidence_refs": [
"docs/security/GITEA-PUBLIC-REPO-SEARCH-SNAPSHOT.md",
"docs/security/GITEA-REPO-INVENTORY-SNAPSHOT.md",
"docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md",
"docs/security/LOCAL-REPO-CANONICAL-EWOOOC-MOMO-SNAPSHOT.md",
"docs/security/github-target-probe.snapshot.json"
],
"awooop_consumption": "approval_candidate"
},
{
"github_repo": "owenhytsai/bitan-pharmacy",
"source_key": "bitan-pharmacy",
"lane": "target_creation_or_access",
"risk": "MEDIUM",
"probe_status": "not_found_or_private",
"target_state": "not_found_or_private",
"approval_status": "pending",
"required_decision": "決定 GitHub repo owner / visibility / 是否建立或授權既有 repo。",
"low_friction_next_step": "先取得 owner / visibility 決策,不自動建立 repo。",
"blocked_until": [
"確認 repo 是否仍 active",
"GitHub repo owner 與 visibility 決策完成"
],
"allowed_after_approval": [
"決定建立 GitHub repo 或授權既有 private repo",
"產生 migration plan"
],
"still_forbidden": [
"自動建立 repo",
"自動 push refs",
"刪除 110 remote"
],
"evidence_refs": [
"docs/security/GIT-REMOTE-REFS-BITAN-TSENYANG-SNAPSHOT.md",
"docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md",
"docs/security/github-target-probe.snapshot.json"
],
"awooop_consumption": "approval_candidate"
},
{
"github_repo": "owenhytsai/tsenyang-website",
"source_key": "tsenyang-website",
"lane": "target_creation_or_access",
"risk": "MEDIUM",
"probe_status": "not_found_or_private",
"target_state": "not_found_or_private",
"approval_status": "pending",
"required_decision": "決定 GitHub repo owner / visibility / 是否建立或授權既有 repo。",
"low_friction_next_step": "先取得 owner / visibility 決策,不自動建立 repo。",
"blocked_until": [
"確認 repo 是否仍 active",
"GitHub repo owner 與 visibility 決策完成"
],
"allowed_after_approval": [
"決定建立 GitHub repo 或授權既有 private repo",
"產生 migration plan"
],
"still_forbidden": [
"自動建立 repo",
"自動 push refs",
"刪除 110 remote"
],
"evidence_refs": [
"docs/security/GIT-REMOTE-REFS-BITAN-TSENYANG-SNAPSHOT.md",
"docs/security/GITHUB-TARGET-VISIBILITY-DECISION-TABLE.md",
"docs/security/github-target-probe.snapshot.json"
],
"awooop_consumption": "approval_candidate"
},
{
"github_repo": "nexu-io/open-design",
"source_key": "open-design",
"lane": "scope_review",
"risk": "LOW",
"probe_status": "exists",
"target_state": "external_scope",
"approval_status": "not_required",
"required_decision": "決定此 repo 是否屬於 AWOOOI 資安供應鏈範圍。",
"low_friction_next_step": "只標記 scope review不納入主控切換。",
"blocked_until": [
"確認是否屬於 AWOOOI 資安網範圍"
],
"allowed_after_approval": [
"mirror_decision_only"
],
"still_forbidden": [
"auto_execute",
"sync_refs",
"switch_primary"
],
"evidence_refs": [
"docs/security/github-target-probe.snapshot.json"
],
"awooop_consumption": "scope_review_only"
}
]
}

View File

@@ -0,0 +1,258 @@
#!/usr/bin/env python3
"""產生 Gitea -> GitHub 逐 repo approval board。
此工具只讀取既有 redacted snapshot不呼叫 Gitea/GitHub API不需要 token。
用途是讓 AwoooP / PR reviewer 可以看見每個 repo 的下一個低摩擦決策點。
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def infer_lane(recommended_action: str) -> str:
if recommended_action == "hold_refs_reconcile":
return "refs_reconcile"
if recommended_action == "confirm_internal_remote_purpose":
return "internal_remote_purpose"
if recommended_action == "scope_review_only":
return "scope_review"
return "target_creation_or_access"
def required_decision(lane: str) -> str:
mapping = {
"refs_reconcile": "決定 Gitea / GitHub refs 真相來源,並批准只產生 reconcile plan。",
"target_creation_or_access": "決定 GitHub repo owner / visibility / 是否建立或授權既有 repo。",
"internal_remote_purpose": "決定 110 internal remote 是 active source、legacy mirror 或應降級。",
"scope_review": "決定此 repo 是否屬於 AWOOOI 資安供應鏈範圍。",
}
return mapping[lane]
def low_friction_next_step(lane: str) -> str:
mapping = {
"refs_reconcile": "先產生 draft reconcile plan不 push refs、不切 primary。",
"target_creation_or_access": "先取得 owner / visibility 決策,不自動建立 repo。",
"internal_remote_purpose": "先文件化用途與風險,不刪除 remote、不同步 refs。",
"scope_review": "只標記 scope review不納入主控切換。",
}
return mapping[lane]
def awooop_consumption(lane: str, approval_required: bool) -> str:
if lane == "scope_review":
return "scope_review_only"
if approval_required:
return "approval_candidate"
return "mirror_only"
def build_board(args: argparse.Namespace) -> dict[str, Any]:
decisions = load_json(Path(args.github_target_decision))
packages = load_json(Path(args.repo_approval_package))
gitea_inventory = load_json(Path(args.gitea_inventory))
package_by_repo = {
str(item.get("github_repo", "")): item
for item in packages.get("approval_items", [])
if isinstance(item, dict)
}
board_items: list[dict[str, Any]] = []
pending_count = 0
for decision in decisions.get("decisions", []):
if not isinstance(decision, dict):
continue
github_repo = str(decision.get("github_repo", ""))
package = package_by_repo.get(github_repo, {})
approval_required = bool(decision.get("approval_required", False))
if approval_required:
pending_count += 1
lane = infer_lane(str(decision.get("recommended_action", "")))
approval_status = str(package.get("approval_status") or ("pending" if approval_required else "not_required"))
blocked_until = package.get("blocked_until") or decision.get("blocked_until") or []
evidence_refs = sorted(
{
*[str(value) for value in decision.get("evidence_refs", [])],
*[str(value) for value in package.get("evidence_refs", [])],
}
)
board_items.append(
{
"github_repo": github_repo,
"source_key": str(decision.get("source_key", "")),
"lane": lane,
"risk": str(decision.get("risk", "LOW")),
"probe_status": str(decision.get("probe_status", "")),
"target_state": str(decision.get("target_state", "")),
"approval_status": approval_status,
"required_decision": required_decision(lane),
"low_friction_next_step": low_friction_next_step(lane),
"blocked_until": [str(value) for value in blocked_until],
"allowed_after_approval": [
str(value)
for value in (package.get("allowed_after_approval") or ["mirror_decision_only"])
],
"still_forbidden": [
str(value)
for value in (
package.get("still_forbidden")
or ["auto_execute", "sync_refs", "switch_primary"]
)
],
"evidence_refs": evidence_refs,
"awooop_consumption": awooop_consumption(lane, approval_required),
}
)
inventory_status = str(gitea_inventory.get("status", "blocked"))
gate_status = "ready" if inventory_status == "ok" else "blocked"
gate_reason = (
"Gitea authenticated 或 admin_export inventory 已完成。"
if gate_status == "ready"
else "GITEA_READONLY_TOKEN 未提供,且不使用可 push 的既有 remote credential 當 read-only tokenserver-side private/internal repo list 仍未完成。"
)
return {
"schema_version": "source_control_approval_board_v1",
"status": "draft",
"date": args.date,
"default_mode": "mirror_only",
"authenticated_inventory_gate": {
"status": gate_status,
"reason": gate_reason,
"allowed_next_step": [
"提供 read-only token 後重跑 gitea-repo-inventory",
"或提供 redacted admin export JSON",
"在 gate 前仍可維護 approval board 與 decision table",
],
"still_forbidden": [
"使用 write-capable credential 當作 read-only token",
"建立 GitHub repo",
"修改 repo visibility",
"sync refs",
"switch GitHub primary",
],
},
"item_count": len(board_items),
"pending_approval_count": pending_count,
"board_items": board_items,
}
def write_markdown(board: dict[str, Any], path: Path) -> None:
gate = board["authenticated_inventory_gate"]
lines = [
"# Source Control Approval Board",
"",
"| 項目 | 內容 |",
"|------|------|",
f"| 日期 | {board['date']} |",
f"| 狀態 | `{board['status']}` |",
f"| 預設模式 | `{board['default_mode']}` |",
f"| authenticated inventory gate | `{gate['status']}` |",
f"| gate 原因 | {gate['reason']} |",
f"| repo items | {board['item_count']} |",
f"| pending approval | {board['pending_approval_count']} |",
"",
"## 0. 核心原則",
"",
"本 board 只整理決策不授權執行。AwoooP 可以 mirror 成 approval candidate但不得建立 repo、修改 visibility、同步 refs、切 GitHub primary 或保存 credential value。",
"",
"## 1. 逐 repo 決策隊列",
"",
"| GitHub repo | Lane | Risk | Probe | Approval | 下一步 |",
"|-------------|------|------|-------|----------|--------|",
]
for item in board["board_items"]:
lines.append(
"| "
+ " | ".join(
[
f"`{item['github_repo']}`",
f"`{item['lane']}`",
f"`{item['risk']}`",
f"`{item['probe_status']}`",
f"`{item['approval_status']}`",
item["low_friction_next_step"],
]
)
+ " |"
)
lines.extend(["", "## 2. 詳細阻塞點", ""])
for item in board["board_items"]:
lines.extend(
[
f"### {item['github_repo']}",
"",
f"- Source key`{item['source_key']}`",
f"- Required decision{item['required_decision']}",
f"- AwoooP consumption`{item['awooop_consumption']}`",
"- Blocked until",
]
)
for value in item["blocked_until"]:
lines.append(f" - {value}")
lines.append("- Still forbidden")
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. Gate 前允許做的事",
"",
"1. 更新 read-only evidence。",
"2. 更新 approval board / decision table。",
"3. 寫 draft reconcile plan。",
"4. 把 pending approval mirror 到 AwoooP。",
"",
"## 4. Gate 前仍禁止",
"",
]
)
for value in gate["still_forbidden"]:
lines.append(f"- {value}")
lines.append("")
path.write_text("\n".join(lines), encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--date", required=True)
parser.add_argument("--github-target-decision", default="docs/security/github-target-decision.snapshot.json")
parser.add_argument(
"--repo-approval-package",
default="docs/security/github-target-repo-approval-package.snapshot.json",
)
parser.add_argument("--gitea-inventory", default="docs/security/gitea-repo-inventory.snapshot.json")
parser.add_argument("--output-json", required=True)
parser.add_argument("--output-md", required=True)
args = parser.parse_args()
board = build_board(args)
Path(args.output_json).write_text(
json.dumps(board, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
write_markdown(board, Path(args.output_md))
print(f"OK source-control approval board items={board['item_count']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())