docs(security): add source control approval board [skip ci]
This commit is contained in:
@@ -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 items,pending 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。
|
||||
|
||||
134
docs/schemas/source_control_approval_board_v1.schema.json
Normal file
134
docs/schemas/source_control_approval_board_v1.schema.json
Normal 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
|
||||
}
|
||||
@@ -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` |
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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 入口,不新增執行按鈕。
|
||||
|
||||
186
docs/security/SOURCE-CONTROL-APPROVAL-BOARD.md
Normal file
186
docs/security/SOURCE-CONTROL-APPROVAL-BOARD.md
Normal 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 token;server-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
|
||||
@@ -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 approval;authenticated inventory gate 仍 blocked。"
|
||||
},
|
||||
{
|
||||
"contract": "local_repo_canonical_probe_v1",
|
||||
"schema_path": "docs/schemas/local_repo_canonical_probe_v1.schema.json",
|
||||
|
||||
271
docs/security/source-control-approval-board.snapshot.json
Normal file
271
docs/security/source-control-approval-board.snapshot.json
Normal 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 token;server-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"
|
||||
}
|
||||
]
|
||||
}
|
||||
258
scripts/security/source-control-approval-board.py
Normal file
258
scripts/security/source-control-approval-board.py
Normal 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 token;server-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())
|
||||
Reference in New Issue
Block a user