554 lines
24 KiB
Python
554 lines
24 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
IwoooS AI provider / model routing owner response acceptance 只讀帳本產生器。
|
||
|
||
本工具建立 AI provider、Ollama proxy、fallback order、cost、privacy、
|
||
benchmark、dry-run 與 agent replacement candidate 的 metadata-only
|
||
acceptance ledger。它不呼叫任何模型、不讀 live endpoint、不切 provider、
|
||
不改 Nginx、不送 prompt、不建立外部 API 呼叫、不收 secret value,也不開
|
||
runtime gate。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import hashlib
|
||
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))
|
||
|
||
ACCEPTANCE_FIELDS = [
|
||
"acceptance_candidate_id",
|
||
"surface_id",
|
||
"label",
|
||
"expected_scope",
|
||
"config_kind",
|
||
"control_tier",
|
||
"source_refs",
|
||
"source_ref_sha256",
|
||
"write_capable_surface",
|
||
"paid_provider_related",
|
||
"data_egress_related",
|
||
"requires_live_evidence",
|
||
"owner_response_ref",
|
||
"owner_role_or_team",
|
||
"decision",
|
||
"decision_reason",
|
||
"affected_scope",
|
||
"provider_owner",
|
||
"fallback_order_ref",
|
||
"dry_run_result_ref",
|
||
"benchmark_result_ref",
|
||
"cost_review_ref",
|
||
"privacy_review_ref",
|
||
"data_classification_ref",
|
||
"prompt_redaction_ref",
|
||
"secret_handling_ref",
|
||
"quota_budget_ref",
|
||
"latency_slo_ref",
|
||
"quality_gate_ref",
|
||
"rollback_owner",
|
||
"rollback_plan_ref",
|
||
"maintenance_window",
|
||
"postcheck_evidence_refs",
|
||
"redacted_evidence_refs",
|
||
"reviewer_outcome",
|
||
"followup_owner",
|
||
"not_approval",
|
||
]
|
||
|
||
REQUIRED_OWNER_FIELDS = [
|
||
"owner_role_or_team",
|
||
"decision",
|
||
"decision_reason",
|
||
"affected_scope",
|
||
"provider_owner",
|
||
"fallback_order_ref",
|
||
"dry_run_result_ref",
|
||
"benchmark_result_ref",
|
||
"cost_review_ref",
|
||
"privacy_review_ref",
|
||
"data_classification_ref",
|
||
"prompt_redaction_ref",
|
||
"secret_handling_ref",
|
||
"quota_budget_ref",
|
||
"latency_slo_ref",
|
||
"quality_gate_ref",
|
||
"rollback_owner",
|
||
"rollback_plan_ref",
|
||
"maintenance_window",
|
||
"postcheck_evidence_refs",
|
||
"redacted_evidence_refs",
|
||
"followup_owner",
|
||
"not_approval",
|
||
"no_secret_value_attestation",
|
||
]
|
||
|
||
REVIEWER_CHECKS = [
|
||
{"check_id": "owner_identity_present", "instruction": "owner role / team 與 provider owner 必須可追溯。"},
|
||
{"check_id": "decision_reason_present", "instruction": "decision 與 decision reason 必須同時存在。"},
|
||
{"check_id": "affected_scope_matches_surface", "instruction": "affected scope 必須能對回既有 AI provider surface。"},
|
||
{"check_id": "redacted_refs_only", "instruction": "evidence 只能是脫敏 ref、hash、ticket、commit 或 artifact pointer。"},
|
||
{"check_id": "secret_value_absent", "instruction": "不得出現 API key、token、cookie、private key、env dump、完整 DSN 或 partial secret。"},
|
||
{"check_id": "live_endpoint_absent", "instruction": "不得保存 raw live endpoint、內網位址或可直接連線 URL。"},
|
||
{"check_id": "fallback_order_present", "instruction": "必須提供 fallback order 與 degraded mode ref,不接受憑印象切 provider。"},
|
||
{"check_id": "dry_run_result_present", "instruction": "必須有 dry-run 結果或明確不適用理由;dry-run 不得呼叫 production LLM。"},
|
||
{"check_id": "benchmark_result_present", "instruction": "必須有 benchmark / replay / fixture 結果與評分方法 ref。"},
|
||
{"check_id": "cost_review_present", "instruction": "涉及雲端或付費 provider 時必須有成本上限、quota、告警與停損 ref。"},
|
||
{"check_id": "privacy_review_present", "instruction": "涉及外部 provider、prompt、log 或 embedding 時必須有資料外送與脫敏審查 ref。"},
|
||
{"check_id": "data_classification_present", "instruction": "必須標示可送出、不可送出、需遮罩與只能本地處理的資料分類。"},
|
||
{"check_id": "prompt_redaction_present", "instruction": "prompt / log / incident context 需有 redaction proof,不接受 raw prompt 或 raw log。"},
|
||
{"check_id": "secret_handling_metadata_only", "instruction": "secret handling 只能描述注入路徑與 owner,不得貼 secret value / hash / partial token。"},
|
||
{"check_id": "quota_budget_present", "instruction": "付費 provider、雲端 provider 或外部 API 必須有 quota budget 與 stop condition。"},
|
||
{"check_id": "latency_slo_present", "instruction": "需要 latency / timeout / circuit breaker / degradation SLO ref。"},
|
||
{"check_id": "quality_gate_present", "instruction": "模型切換或 agent replacement 必須有品質門檻與 incumbent baseline。"},
|
||
{"check_id": "rollback_owner_present", "instruction": "rollback owner 與 rollback plan 必須存在。"},
|
||
{"check_id": "maintenance_window_present", "instruction": "provider route、proxy、env 或 model change 必須另有維護窗口。"},
|
||
{"check_id": "postcheck_evidence_present", "instruction": "post-check 必須覆蓋 provider route、fallback、cost、privacy、observability 與 rollback stop condition。"},
|
||
{"check_id": "no_runtime_request", "instruction": "夾帶 provider switch、Nginx reload、env change、external call、prompt send、benchmark live call 或 deploy 時拒收。"},
|
||
{"check_id": "no_false_green_provider_health", "instruction": "不得把 provider route 200、dashboard up、模型回覆一次成功當成 routing / privacy / cost 已驗收。"},
|
||
{"check_id": "cross_project_sync_present", "instruction": "影響 AwoooP、IwoooS、agent-bounty、code review、StockPlatform 或公開網站時需有跨專案同步 ref。"},
|
||
{"check_id": "counts_transition_safe", "instruction": "只有 reviewer record 可更新 received / accepted;不得同時開 runtime gate。"},
|
||
]
|
||
|
||
OUTCOME_LANES = [
|
||
{"lane_id": "waiting_owner_response", "meaning": "尚未收到 AI provider owner response;所有 accepted / runtime count 維持 0。"},
|
||
{"lane_id": "quarantine_secret_or_raw_payload", "meaning": "收到 secret、raw prompt、raw log、raw endpoint、未脫敏截圖或 env dump 時隔離。"},
|
||
{"lane_id": "reject_execution_request", "meaning": "夾帶 provider switch、外部呼叫、prompt send、Nginx reload、env change 或 deploy 要求時拒收。"},
|
||
{"lane_id": "request_fallback_supplement", "meaning": "缺 fallback order、degraded mode、circuit breaker 或 rollback 時要求補件。"},
|
||
{"lane_id": "request_cost_privacy_supplement", "meaning": "缺 cost / quota / privacy / data classification / prompt redaction 時要求補件。"},
|
||
{"lane_id": "request_benchmark_supplement", "meaning": "缺 dry-run、benchmark、fixture replay、quality gate 或 incumbent baseline 時要求補件。"},
|
||
{"lane_id": "legacy_model_card_redaction_required", "meaning": "舊模型卡、runbook 或報表含 raw endpoint / 主機識別時,先改成脫敏 alias。"},
|
||
{"lane_id": "ready_for_ai_provider_review", "meaning": "metadata 合格後,只能進 AI provider reviewer review。"},
|
||
{"lane_id": "owner_review_only_update", "meaning": "只允許更新只讀 owner review ledger,不切 provider、不呼叫模型、不改 proxy。"},
|
||
{"lane_id": "waiting_runtime_gate", "meaning": "即使 owner response accepted,runtime gate 仍需獨立人工批准。"},
|
||
]
|
||
|
||
BLOCKED_ACTIONS = [
|
||
"provider_switch",
|
||
"model_route_change",
|
||
"fallback_order_change",
|
||
"ollama_proxy_change",
|
||
"nginx_reload",
|
||
"env_change",
|
||
"runtime_config_change",
|
||
"external_provider_call",
|
||
"paid_provider_call",
|
||
"prompt_send",
|
||
"raw_prompt_storage",
|
||
"raw_log_storage",
|
||
"raw_endpoint_storage",
|
||
"live_endpoint_probe",
|
||
"benchmark_live_call",
|
||
"dry_run_external_call",
|
||
"quota_increase",
|
||
"cost_limit_change",
|
||
"privacy_boundary_change",
|
||
"data_egress_change",
|
||
"secret_value_collection",
|
||
"secret_hash_collection",
|
||
"partial_token_collection",
|
||
"api_key_rotation",
|
||
"secret_store_read",
|
||
"model_download",
|
||
"sdk_install",
|
||
"agent_runtime_enable",
|
||
"shadow_or_canary_enable",
|
||
"production_deploy",
|
||
"workflow_dispatch",
|
||
"host_write",
|
||
"ssh_read",
|
||
"ssh_write",
|
||
"active_scan",
|
||
"mark_owner_response_accepted_without_reviewer_record",
|
||
"open_runtime_gate",
|
||
"add_action_button",
|
||
]
|
||
|
||
SURFACES = [
|
||
{
|
||
"surface_id": "ai_router_provider_policy",
|
||
"label": "AI router provider policy / purpose mapping",
|
||
"expected_scope": "purpose routing、provider priority、degraded mode",
|
||
"config_kind": "provider_policy",
|
||
"source_refs": [
|
||
"apps/api/src/services/ai_router.py",
|
||
"apps/api/src/services/ai_provider_route_matrix.py",
|
||
"apps/api/models.json",
|
||
"docs/ai/AI-MODEL-CARDS.md",
|
||
],
|
||
"write_capable_surface": True,
|
||
"paid_provider_related": True,
|
||
"data_egress_related": True,
|
||
"requires_live_evidence": True,
|
||
},
|
||
{
|
||
"surface_id": "ollama_proxy_gateway",
|
||
"label": "Ollama proxy gateway / local fallback boundary",
|
||
"expected_scope": "proxy route、local fallback、gateway health",
|
||
"config_kind": "proxy_gateway",
|
||
"source_refs": [
|
||
"infra/ansible/roles/nginx/templates/110-ollama-proxy.conf.j2",
|
||
"apps/api/src/services/ollama_endpoint_resolver.py",
|
||
"apps/api/src/services/ollama_failover_manager.py",
|
||
"apps/api/src/services/provider_proxy.py",
|
||
],
|
||
"write_capable_surface": True,
|
||
"paid_provider_related": False,
|
||
"data_egress_related": False,
|
||
"requires_live_evidence": True,
|
||
},
|
||
{
|
||
"surface_id": "fallback_order_and_circuit_breaker",
|
||
"label": "Fallback order / circuit breaker",
|
||
"expected_scope": "GCP / local / cloud fallback order、timeout、circuit breaker",
|
||
"config_kind": "routing_guard",
|
||
"source_refs": [
|
||
"apps/api/src/services/ollama_endpoint_circuit_breaker.py",
|
||
"apps/api/src/services/ollama_health_monitor.py",
|
||
"apps/api/src/services/failover_alerter.py",
|
||
"apps/web/messages/zh-TW.json",
|
||
],
|
||
"write_capable_surface": True,
|
||
"paid_provider_related": True,
|
||
"data_egress_related": True,
|
||
"requires_live_evidence": True,
|
||
},
|
||
{
|
||
"surface_id": "cost_budget_and_quota",
|
||
"label": "Cost budget / quota / paid provider stop condition",
|
||
"expected_scope": "quota、token budget、cost alert、paid provider stop condition",
|
||
"config_kind": "cost_policy",
|
||
"source_refs": [
|
||
"docs/ai/AI-MODEL-CARDS.md",
|
||
"apps/api/src/services/ai_rate_limiter.py",
|
||
"apps/api/src/services/ai_slo_calculator.py",
|
||
"apps/web/messages/zh-TW.json",
|
||
],
|
||
"write_capable_surface": False,
|
||
"paid_provider_related": True,
|
||
"data_egress_related": True,
|
||
"requires_live_evidence": True,
|
||
},
|
||
{
|
||
"surface_id": "privacy_data_egress_boundary",
|
||
"label": "Privacy / data egress / prompt redaction boundary",
|
||
"expected_scope": "prompt redaction、資料分級、外部 provider 資料外送",
|
||
"config_kind": "privacy_policy",
|
||
"source_refs": [
|
||
"docs/HARD_RULES.md",
|
||
"docs/ai/AI-MODEL-CARDS.md",
|
||
"apps/api/src/services/ai_providers/permissions.py",
|
||
"apps/api/src/services/alertmanager_llm_guard.py",
|
||
],
|
||
"write_capable_surface": False,
|
||
"paid_provider_related": True,
|
||
"data_egress_related": True,
|
||
"requires_live_evidence": True,
|
||
},
|
||
{
|
||
"surface_id": "benchmark_dry_run_pack",
|
||
"label": "Dry-run / benchmark / replay pack",
|
||
"expected_scope": "dry-run、benchmark、fixture replay、quality gate",
|
||
"config_kind": "evaluation_pack",
|
||
"source_refs": [
|
||
"docs/runbooks/OPENCLAW-REPLACEMENT-EVALUATION.md",
|
||
"docs/ai/agent-replacement-candidates.v1.json",
|
||
"scripts/agents/run-agent-replacement-replay.py",
|
||
"scripts/agents/evaluate-agent-promotion-gate.py",
|
||
],
|
||
"write_capable_surface": False,
|
||
"paid_provider_related": False,
|
||
"data_egress_related": True,
|
||
"requires_live_evidence": False,
|
||
},
|
||
{
|
||
"surface_id": "model_card_and_version_inventory",
|
||
"label": "Model card / model version inventory",
|
||
"expected_scope": "model card、version freshness、source-of-truth、redacted endpoint",
|
||
"config_kind": "model_metadata",
|
||
"source_refs": [
|
||
"docs/ai/AI-MODEL-CARDS.md",
|
||
"apps/api/src/services/model_registry.py",
|
||
"apps/api/src/services/model_version_tracker.py",
|
||
"apps/api/src/services/model_version_probe.py",
|
||
],
|
||
"write_capable_surface": True,
|
||
"paid_provider_related": False,
|
||
"data_egress_related": False,
|
||
"requires_live_evidence": False,
|
||
},
|
||
{
|
||
"surface_id": "agent_replacement_candidate_runtime_boundary",
|
||
"label": "Agent replacement / NemoTron / Hermes / OpenClaw candidate boundary",
|
||
"expected_scope": "agent candidate、promotion gate、shadow/canary boundary",
|
||
"config_kind": "agent_runtime_boundary",
|
||
"source_refs": [
|
||
"docs/runbooks/OPENCLAW-REPLACEMENT-EVALUATION.md",
|
||
"docs/ai/agent-market-watch-sources.v1.json",
|
||
"docs/ai/agent-replacement-candidates.v1.json",
|
||
"apps/api/src/services/ai_agent_tool_adoption_approval_package.py",
|
||
],
|
||
"write_capable_surface": True,
|
||
"paid_provider_related": True,
|
||
"data_egress_related": True,
|
||
"requires_live_evidence": True,
|
||
},
|
||
]
|
||
|
||
|
||
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 ref_digest(root: Path, refs: list[str]) -> str:
|
||
digest = hashlib.sha256()
|
||
for ref in refs:
|
||
path = root / ref
|
||
digest.update(ref.encode("utf-8"))
|
||
if path.is_file():
|
||
digest.update(path.read_bytes())
|
||
elif path.is_dir():
|
||
digest.update(b"<dir>")
|
||
else:
|
||
digest.update(b"<missing>")
|
||
return digest.hexdigest()
|
||
|
||
|
||
def build_candidate(root: Path, surface: dict[str, Any]) -> dict[str, Any]:
|
||
return {
|
||
"acceptance_candidate_id": f"ai_provider_owner_response_acceptance:{surface['surface_id']}",
|
||
"status": "waiting_owner_response",
|
||
"surface_id": surface["surface_id"],
|
||
"label": surface["label"],
|
||
"expected_scope": surface["expected_scope"],
|
||
"config_kind": surface["config_kind"],
|
||
"control_tier": "C1",
|
||
"source_refs": surface["source_refs"],
|
||
"source_ref_sha256": ref_digest(root, surface["source_refs"]),
|
||
"write_capable_surface": surface["write_capable_surface"],
|
||
"paid_provider_related": surface["paid_provider_related"],
|
||
"data_egress_related": surface["data_egress_related"],
|
||
"requires_live_evidence": surface["requires_live_evidence"],
|
||
"owner_response_ref": None,
|
||
"owner_role_or_team": "pending_owner_response",
|
||
"decision": "pending_owner_response",
|
||
"decision_reason": "pending_owner_response",
|
||
"affected_scope": "pending_owner_response",
|
||
"provider_owner": "pending_owner_response",
|
||
"fallback_order_ref": None,
|
||
"dry_run_result_ref": None,
|
||
"benchmark_result_ref": None,
|
||
"cost_review_ref": None,
|
||
"privacy_review_ref": None,
|
||
"data_classification_ref": None,
|
||
"prompt_redaction_ref": None,
|
||
"secret_handling_ref": None,
|
||
"quota_budget_ref": None,
|
||
"latency_slo_ref": None,
|
||
"quality_gate_ref": None,
|
||
"rollback_owner": "pending_owner_response",
|
||
"rollback_plan_ref": None,
|
||
"maintenance_window": "pending_owner_response",
|
||
"postcheck_evidence_refs": [],
|
||
"redacted_evidence_refs": [],
|
||
"reviewer_outcome": "waiting_owner_response",
|
||
"followup_owner": "pending_owner_response",
|
||
"acceptance_fields": ACCEPTANCE_FIELDS,
|
||
"required_owner_fields": REQUIRED_OWNER_FIELDS,
|
||
"reviewer_checks": [item["check_id"] for item in REVIEWER_CHECKS],
|
||
"outcome_lanes": [item["lane_id"] for item in OUTCOME_LANES],
|
||
"blocked_actions": BLOCKED_ACTIONS,
|
||
"not_approval": True,
|
||
"request_sent": False,
|
||
"recipient_confirmed": False,
|
||
"owner_response_received": False,
|
||
"owner_response_accepted": False,
|
||
"owner_response_rejected": False,
|
||
"owner_response_quarantined": False,
|
||
"supplement_requested": False,
|
||
"fallback_order_accepted": False,
|
||
"dry_run_result_accepted": False,
|
||
"benchmark_result_accepted": False,
|
||
"cost_review_accepted": False,
|
||
"privacy_review_accepted": False,
|
||
"data_classification_accepted": False,
|
||
"prompt_redaction_accepted": False,
|
||
"secret_handling_accepted": False,
|
||
"quota_budget_accepted": False,
|
||
"latency_slo_accepted": False,
|
||
"quality_gate_accepted": False,
|
||
"rollback_owner_accepted": False,
|
||
"maintenance_window_accepted": False,
|
||
"postcheck_evidence_accepted": False,
|
||
"provider_switch_authorized": False,
|
||
"model_route_change_authorized": False,
|
||
"fallback_order_change_authorized": False,
|
||
"ollama_proxy_change_authorized": False,
|
||
"nginx_reload_authorized": False,
|
||
"env_change_authorized": False,
|
||
"external_provider_call_authorized": False,
|
||
"paid_provider_call_authorized": False,
|
||
"prompt_send_authorized": False,
|
||
"live_endpoint_probe_authorized": False,
|
||
"benchmark_live_call_authorized": False,
|
||
"dry_run_external_call_authorized": False,
|
||
"quota_increase_authorized": False,
|
||
"cost_limit_change_authorized": False,
|
||
"privacy_boundary_change_authorized": False,
|
||
"data_egress_change_authorized": False,
|
||
"secret_value_collection_allowed": False,
|
||
"secret_hash_collection_allowed": False,
|
||
"partial_token_collection_allowed": False,
|
||
"api_key_rotation_authorized": False,
|
||
"secret_store_read_authorized": False,
|
||
"model_download_authorized": False,
|
||
"sdk_install_authorized": False,
|
||
"agent_runtime_enable_authorized": False,
|
||
"shadow_or_canary_enable_authorized": False,
|
||
"production_deploy_authorized": False,
|
||
"workflow_dispatch_authorized": False,
|
||
"host_write_authorized": False,
|
||
"ssh_read_authorized": False,
|
||
"ssh_write_authorized": False,
|
||
"active_scan_authorized": False,
|
||
"runtime_gate": False,
|
||
"action_buttons_allowed": False,
|
||
}
|
||
|
||
|
||
def build_snapshot(root: Path, generated_at: str | None = None) -> dict[str, Any]:
|
||
generated = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds")
|
||
candidates = [build_candidate(root, surface) for surface in SURFACES]
|
||
summary = {
|
||
"acceptance_candidate_count": len(candidates),
|
||
"write_capable_acceptance_candidate_count": sum(1 for item in candidates if item["write_capable_surface"]),
|
||
"paid_provider_related_candidate_count": sum(1 for item in candidates if item["paid_provider_related"]),
|
||
"data_egress_candidate_count": sum(1 for item in candidates if item["data_egress_related"]),
|
||
"live_evidence_required_candidate_count": sum(1 for item in candidates if item["requires_live_evidence"]),
|
||
"acceptance_field_count": len(ACCEPTANCE_FIELDS),
|
||
"required_owner_field_count": len(REQUIRED_OWNER_FIELDS),
|
||
"reviewer_check_count": len(REVIEWER_CHECKS),
|
||
"outcome_lane_count": len(OUTCOME_LANES),
|
||
"blocked_action_count": len(BLOCKED_ACTIONS),
|
||
"owner_response_received_count": 0,
|
||
"owner_response_accepted_count": 0,
|
||
"owner_response_rejected_count": 0,
|
||
"owner_response_quarantined_count": 0,
|
||
"supplement_requested_count": 0,
|
||
"fallback_order_accepted_count": 0,
|
||
"dry_run_result_accepted_count": 0,
|
||
"benchmark_result_accepted_count": 0,
|
||
"cost_review_accepted_count": 0,
|
||
"privacy_review_accepted_count": 0,
|
||
"data_classification_accepted_count": 0,
|
||
"prompt_redaction_accepted_count": 0,
|
||
"secret_handling_accepted_count": 0,
|
||
"quota_budget_accepted_count": 0,
|
||
"latency_slo_accepted_count": 0,
|
||
"quality_gate_accepted_count": 0,
|
||
"rollback_owner_accepted_count": 0,
|
||
"maintenance_window_accepted_count": 0,
|
||
"postcheck_evidence_accepted_count": 0,
|
||
"provider_switch_authorized_count": 0,
|
||
"external_provider_call_authorized_count": 0,
|
||
"paid_provider_call_authorized_count": 0,
|
||
"prompt_send_authorized_count": 0,
|
||
"live_endpoint_probe_authorized_count": 0,
|
||
"secret_value_collection_allowed_count": 0,
|
||
"runtime_gate_count": 0,
|
||
"action_button_count": 0,
|
||
"coverage_percent_before_acceptance": 60,
|
||
"coverage_percent_after_acceptance": 64,
|
||
}
|
||
return {
|
||
"schema_version": "ai_provider_owner_response_acceptance_v1",
|
||
"status": "owner_response_acceptance_ledger_ready_no_runtime_action",
|
||
"generated_at": generated,
|
||
"git_sha": git_short_sha(root),
|
||
"source_paths": [
|
||
"docs/HARD_RULES.md",
|
||
"docs/ai/AI-MODEL-CARDS.md",
|
||
"docs/runbooks/OPENCLAW-REPLACEMENT-EVALUATION.md",
|
||
"scripts/security/ai-provider-owner-response-acceptance.py",
|
||
"scripts/security/high-value-config-control-coverage.py",
|
||
],
|
||
"summary": summary,
|
||
"acceptance_fields": ACCEPTANCE_FIELDS,
|
||
"required_owner_fields": REQUIRED_OWNER_FIELDS,
|
||
"reviewer_checks": REVIEWER_CHECKS,
|
||
"outcome_lanes": OUTCOME_LANES,
|
||
"blocked_actions": BLOCKED_ACTIONS,
|
||
"acceptance_candidates": candidates,
|
||
"boundaries": {
|
||
"not_authorization": True,
|
||
"runtime_execution_authorized": False,
|
||
"provider_switch_authorized": False,
|
||
"model_route_change_authorized": False,
|
||
"fallback_order_change_authorized": False,
|
||
"ollama_proxy_change_authorized": False,
|
||
"nginx_reload_authorized": False,
|
||
"external_provider_call_authorized": False,
|
||
"paid_provider_call_authorized": False,
|
||
"prompt_send_authorized": False,
|
||
"live_endpoint_probe_authorized": False,
|
||
"secret_value_collection_allowed": False,
|
||
"raw_prompt_storage_allowed": False,
|
||
"raw_endpoint_storage_allowed": False,
|
||
"sdk_install_authorized": False,
|
||
"agent_runtime_enable_authorized": False,
|
||
"shadow_or_canary_enable_authorized": False,
|
||
"production_deploy_authorized": False,
|
||
"host_write_authorized": False,
|
||
"active_scan_authorized": False,
|
||
"runtime_gate": False,
|
||
"action_buttons_allowed": False,
|
||
},
|
||
}
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument("--root", default=".")
|
||
parser.add_argument(
|
||
"--output",
|
||
default="docs/security/ai-provider-owner-response-acceptance.snapshot.json",
|
||
)
|
||
parser.add_argument("--generated-at", default=None)
|
||
args = parser.parse_args()
|
||
|
||
root = Path(args.root).resolve()
|
||
output = root / args.output
|
||
snapshot = build_snapshot(root, args.generated_at)
|
||
output.parent.mkdir(parents=True, exist_ok=True)
|
||
output.write_text(json.dumps(snapshot, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||
print(
|
||
"AI_PROVIDER_OWNER_RESPONSE_ACCEPTANCE_OK "
|
||
f"candidates={snapshot['summary']['acceptance_candidate_count']} "
|
||
f"checks={snapshot['summary']['reviewer_check_count']} "
|
||
f"lanes={snapshot['summary']['outcome_lane_count']} "
|
||
f"accepted={snapshot['summary']['owner_response_accepted_count']} "
|
||
f"runtime_gate={snapshot['summary']['runtime_gate_count']}"
|
||
)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|