#!/usr/bin/env python3 """ IwoooS 高價值配置 owner packet 收件預檢產生器。 本工具讀取 committed owner packet snapshot,產生 request dispatch 前的 只讀預檢與 reviewer intake lanes。它不送 request、不收回覆、不寫入 reviewer queue、不建立 action button,也不開啟 runtime gate。 """ from __future__ import annotations import argparse import json import subprocess import sys from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any TAIPEI = timezone(timedelta(hours=8)) DISPATCH_PREFLIGHT_CHECKS = [ ( "baseline_commit_synced", "基線 commit 已同步", "使用最新 gitea/main 與 owner packet snapshot;不得用過期 packet 送件。", ), ( "source_snapshot_current", "來源 snapshot 目前有效", "來源必須是 committed high-value-config-owner-packet snapshot,不讀 live 主機。", ), ( "packet_scope_mapped", "Packet scope 已映射", "每包需有 category、priority、control tier、required gate 與 affected files。", ), ( "canonical_fields_present", "Canonical 欄位已固定", "每包需要求 owner role、decision、reason、scope、evidence refs、followup、rollback、window、validation。", ), ( "redacted_evidence_refs_only", "Evidence refs 只允許脫敏參照", "只收文件路徑、snapshot id、ticket id、commit、hash 或 metadata pointer。", ), ( "no_sensitive_payload", "禁止敏感 payload", "不得收 token、secret、private key、cookie、session、authorization header、runner token 或 webhook secret。", ), ( "no_execution_request", "拒收執行要求", "不得夾帶 reload、deploy、sync、host write、active scan、payout 或 withdrawal。", ), ( "maintenance_and_rollback_fields_present", "維護窗口與回滾 owner 欄位存在", "C0 / P0 仍需獨立人工批准、維護窗口與 rollback owner,不能由 packet 自動授權。", ), ( "validation_plan_present", "驗證計畫欄位存在", "必須能說明 preflight、post-check、rollback check;只讀文件變更則對應 guard / JSON / doc sanity。", ), ] REVIEWER_INTAKE_LANES = [ ( "keep_waiting_owner_response", "尚未收到 owner response 或只有空白 / 口頭同意時維持等待。", "request / received / accepted / rejected 維持 0。", ), ( "request_more_evidence", "欄位缺漏、scope 不清或 redacted evidence refs 不足時補件。", "不得增加 accepted 或 runtime gate。", ), ( "quarantine_sensitive_payload", "疑似含敏感 payload、raw log、未脫敏截圖或 private credential URL 時隔離。", "不得保存 raw payload、不得渲染到前端或 LOGBOOK。", ), ( "reject_execution_request", "夾帶 repo / refs / workflow / runner / host / scan / runtime 執行要求時拒收。", "不得建立 action button、不得轉成執行批准。", ), ( "ready_for_reviewer_validation", "欄位完整、證據脫敏、無敏感 payload 且無執行要求時才可進 reviewer checklist。", "仍不是 accepted,也不是 runtime authorization。", ), ] NEXT_STEPS = [ "先由 reviewer 確認預檢 snapshot 與 owner packet snapshot 皆為最新 committed evidence。", "送件前只準備脫敏欄位與禁止條款,不送 secret value、不附 raw payload。", "收到回覆後先依 intake lanes 分流;通過 reviewer validation 之前 received / accepted 不得增加。", "任何 runtime、Nginx reload、DNS / TLS、workflow、runner、host 或 payout 動作都必須切獨立人工批准。", ] def git_short_sha(root: Path) -> str: try: result = subprocess.run( ["git", "rev-parse", "--short", "HEAD"], cwd=root, check=True, capture_output=True, text=True, ) return result.stdout.strip() except Exception: return "unknown" def load_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) def dispatch_preflight_checks() -> list[dict[str, Any]]: return [ { "check_id": check_id, "label": label, "status": "preflight_required_before_request_dispatch", "required": True, "instruction": instruction, "gate_effect": "不增加 request_sent / received / accepted / runtime gate。", } for check_id, label, instruction in DISPATCH_PREFLIGHT_CHECKS ] def reviewer_intake_lanes() -> list[dict[str, Any]]: return [ { "lane_id": lane_id, "status": "reviewer_intake_lane_defined", "instruction": instruction, "gate_effect": gate_effect, } for lane_id, instruction, gate_effect in REVIEWER_INTAKE_LANES ] def intake_packet(packet: dict[str, Any]) -> dict[str, Any]: required_owner_fields = [ field["field"] for field in packet.get("field_templates", []) if field.get("required") is True ] return { "packet_id": packet["packet_id"], "status": "waiting_request_dispatch_preflight", "category_id": packet["category_id"], "label": packet["label"], "priority": packet["priority"], "control_tier": packet["control_tier"], "required_gate": packet["required_gate"], "affected_file_count": len(packet.get("affected_files", [])), "affected_files": packet.get("affected_files", []), "required_owner_fields": required_owner_fields, "required_validation": packet.get("required_validation", []), "request_sent": False, "received_response": False, "accepted_response": False, "rejected_response": False, "reviewer_queue_write": False, "runtime_gate": False, "action_buttons_allowed": False, "secret_value_collection_allowed": False, "production_write_authorized": False, "not_authorization": True, } def build_report(root: Path, owner_packet_report: dict[str, Any], generated_at: str | None) -> dict[str, Any]: report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds") packets = owner_packet_report.get("packets", []) c0_packets = [packet for packet in packets if packet.get("control_tier") == "C0"] c1_packets = [packet for packet in packets if packet.get("control_tier") == "C1"] canonical_owner_fields = owner_packet_report.get("canonical_owner_fields", []) required_owner_field_total = len(canonical_owner_fields) * len(packets) blocked_requests = packets[0].get("blocked_requests", []) if packets else [] false_flags = owner_packet_report.get("universal_owner_response_template", {}).get("false_flags", {}) return { "schema_version": "high_value_config_owner_packet_intake_preflight_v1", "generated_at": report_time, "git_commit": git_short_sha(root), "source_owner_packet_schema_version": owner_packet_report.get("schema_version"), "source_owner_packet_status": owner_packet_report.get("status"), "status": "request_dispatch_preflight_ready", "summary": { "packet_count": len(packets), "c0_packet_count": len(c0_packets), "c1_packet_count": len(c1_packets), "canonical_owner_field_count": len(canonical_owner_fields), "required_owner_field_total": required_owner_field_total, "dispatch_preflight_check_count": len(DISPATCH_PREFLIGHT_CHECKS), "reviewer_intake_lane_count": len(REVIEWER_INTAKE_LANES), "blocked_request_count": len(blocked_requests), "request_sent_count": 0, "received_response_count": 0, "accepted_response_count": 0, "rejected_response_count": 0, "reviewer_queue_write_count": 0, "runtime_gate_count": 0, "action_button_count": 0, }, "execution_boundaries": { "dispatch_authorized": False, "request_sent": False, "reviewer_queue_write": False, "runtime_execution_authorized": False, "secret_value_collection_allowed": False, "production_write_authorized": False, "host_write_authorized": False, "nginx_reload_authorized": False, "dns_tls_change_authorized": False, "workflow_modification_authorized": False, "active_scan_authorized": False, "action_buttons_allowed": False, "not_authorization": True, }, "canonical_owner_fields": canonical_owner_fields, "allowed_decisions": owner_packet_report.get("allowed_decisions", []), "dispatch_preflight_checks": dispatch_preflight_checks(), "reviewer_intake_lanes": reviewer_intake_lanes(), "blocked_requests": blocked_requests, "source_false_flags": false_flags, "intake_packets": [intake_packet(packet) for packet in packets], "completion": { "request_dispatch_preflight_artifact_percent": 100, "owner_request_sent_percent": 0, "owner_response_received_percent": 0, "reviewer_accepted_percent": 0, "runtime_gate_percent": 0, }, "next_steps": NEXT_STEPS, } def main() -> int: parser = argparse.ArgumentParser(description="IwoooS 高價值配置 owner packet 收件預檢產生器") parser.add_argument("--root", default=".", help="repo root") parser.add_argument( "--owner-packet-report", default="docs/security/high-value-config-owner-packet.snapshot.json", help="high-value-config-owner-packet.py 輸出的 JSON", ) parser.add_argument("--output", help="寫出 JSON 報告") parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用") args = parser.parse_args() root = Path(args.root).resolve() owner_packet_report = load_json(root / args.owner_packet_report) report = build_report(root, owner_packet_report, args.generated_at) payload = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) if args.output: output = Path(args.output) output.parent.mkdir(parents=True, exist_ok=True) output.write_text(payload + "\n", encoding="utf-8") else: print(payload) summary = report["summary"] print( "HIGH_VALUE_CONFIG_OWNER_PACKET_INTAKE_PREFLIGHT_OK " f"packets={summary['packet_count']} " f"c0={summary['c0_packet_count']} " f"checks={summary['dispatch_preflight_check_count']} " f"lanes={summary['reviewer_intake_lane_count']} " f"runtime_gate={summary['runtime_gate_count']}", file=sys.stderr, ) return 0 if __name__ == "__main__": sys.exit(main())