feat(github): authorize controlled backup execution
This commit is contained in:
@@ -52,6 +52,7 @@ def build_delivery_closure_workbench(
|
||||
"""Build the delivery workbench response from already validated snapshots."""
|
||||
status_summary = _dict(status_cleanup.get("summary"))
|
||||
github_summary = _dict(github.get("summary"))
|
||||
github_boundaries = _dict(github.get("operation_boundaries"))
|
||||
gitea_status = _dict(gitea.get("program_status"))
|
||||
gitea_rollups = _dict(gitea.get("rollups"))
|
||||
runtime_status = _dict(runtime.get("program_status"))
|
||||
@@ -61,14 +62,18 @@ def build_delivery_closure_workbench(
|
||||
|
||||
github_required = _int(github_summary.get("approval_required_target_count"))
|
||||
github_verified = _int(github_summary.get("private_backup_verified_count"))
|
||||
runtime_action_required = set(_strings(runtime_rollups.get("action_required_surface_ids")))
|
||||
runtime_action_required = set(
|
||||
_strings(runtime_rollups.get("action_required_surface_ids"))
|
||||
)
|
||||
runtime_secret_surfaces = set(_strings(runtime_rollups.get("secret_surface_ids")))
|
||||
|
||||
lanes = [
|
||||
{
|
||||
"id": "release",
|
||||
"source_id": "status_cleanup",
|
||||
"completion_percent": _percent(status_summary.get("overall_completion_percent")),
|
||||
"completion_percent": _percent(
|
||||
status_summary.get("overall_completion_percent")
|
||||
),
|
||||
"status": str(status_summary.get("dashboard_status") or "unknown"),
|
||||
"blocker_count": _int(status_summary.get("blocked_gate_count")),
|
||||
"metric": {
|
||||
@@ -93,14 +98,20 @@ def build_delivery_closure_workbench(
|
||||
"total": github_required,
|
||||
},
|
||||
"href": "/governance?tab=automation-inventory",
|
||||
"next_action": str(github.get("next_action") or _first_target_action(github.get("targets"))),
|
||||
"next_action": str(
|
||||
github.get("next_action") or _first_target_action(github.get("targets"))
|
||||
),
|
||||
},
|
||||
{
|
||||
"id": "gitea",
|
||||
"source_id": "gitea_ci_cd",
|
||||
"completion_percent": _percent(gitea_status.get("overall_completion_percent")),
|
||||
"completion_percent": _percent(
|
||||
gitea_status.get("overall_completion_percent")
|
||||
),
|
||||
"status": str(gitea_status.get("current_task_id") or "unknown"),
|
||||
"blocker_count": len(_strings(gitea_rollups.get("runner_contracts_requiring_action"))),
|
||||
"blocker_count": len(
|
||||
_strings(gitea_rollups.get("runner_contracts_requiring_action"))
|
||||
),
|
||||
"metric": {
|
||||
"kind": "workflow_count",
|
||||
"count": _int(gitea_rollups.get("total_workflows")),
|
||||
@@ -111,7 +122,9 @@ def build_delivery_closure_workbench(
|
||||
{
|
||||
"id": "runtime",
|
||||
"source_id": "runtime_surface",
|
||||
"completion_percent": _percent(runtime_status.get("overall_completion_percent")),
|
||||
"completion_percent": _percent(
|
||||
runtime_status.get("overall_completion_percent")
|
||||
),
|
||||
"status": str(runtime_status.get("current_task_id") or "unknown"),
|
||||
"blocker_count": len(runtime_action_required | runtime_secret_surfaces),
|
||||
"metric": {
|
||||
@@ -124,7 +137,9 @@ def build_delivery_closure_workbench(
|
||||
{
|
||||
"id": "backup",
|
||||
"source_id": "backup_dr",
|
||||
"completion_percent": _percent(backup_status.get("overall_completion_percent")),
|
||||
"completion_percent": _percent(
|
||||
backup_status.get("overall_completion_percent")
|
||||
),
|
||||
"status": str(backup_status.get("current_task_id") or "unknown"),
|
||||
"blocker_count": len(_strings(backup_rollups.get("blocked_row_ids"))),
|
||||
"metric": {
|
||||
@@ -137,7 +152,9 @@ def build_delivery_closure_workbench(
|
||||
]
|
||||
|
||||
for lane in lanes:
|
||||
lane["tone"] = _tone(_int(lane["blocker_count"]), _int(lane["completion_percent"]))
|
||||
lane["tone"] = _tone(
|
||||
_int(lane["blocker_count"]), _int(lane["completion_percent"])
|
||||
)
|
||||
|
||||
source_statuses = [
|
||||
_source_status("status_cleanup", status_cleanup),
|
||||
@@ -146,7 +163,9 @@ def build_delivery_closure_workbench(
|
||||
_source_status("runtime_surface", runtime),
|
||||
_source_status("backup_dr", backup),
|
||||
]
|
||||
generated_candidates = [source["generated_at"] for source in source_statuses if source["generated_at"]]
|
||||
generated_candidates = [
|
||||
source["generated_at"] for source in source_statuses if source["generated_at"]
|
||||
]
|
||||
loaded_source_count = sum(1 for source in source_statuses if source["loaded"])
|
||||
high_risk_blocker_count = sum(_int(lane["blocker_count"]) for lane in lanes)
|
||||
average_completion = _percent(
|
||||
@@ -166,17 +185,28 @@ def build_delivery_closure_workbench(
|
||||
return {
|
||||
"schema_version": _SCHEMA_VERSION,
|
||||
"generated_at": max(generated_candidates) if generated_candidates else "",
|
||||
"status": "blocked_delivery_actions_required" if high_risk_blocker_count else "ready",
|
||||
"status": "blocked_delivery_actions_required"
|
||||
if high_risk_blocker_count
|
||||
else "ready",
|
||||
"summary": {
|
||||
"source_count": len(source_statuses),
|
||||
"loaded_source_count": loaded_source_count,
|
||||
"average_completion_percent": average_completion,
|
||||
"high_risk_blocker_count": high_risk_blocker_count,
|
||||
"runtime_execution_authorized": False,
|
||||
"remote_write_authorized": False,
|
||||
"repo_creation_authorized": False,
|
||||
"refs_sync_authorized": False,
|
||||
"workflow_trigger_authorized": False,
|
||||
"remote_write_authorized": github_boundaries.get("github_api_write_allowed")
|
||||
is True,
|
||||
"repo_creation_authorized": github_summary.get("repo_creation_authorized")
|
||||
is True,
|
||||
"visibility_change_authorized": github_summary.get(
|
||||
"visibility_change_authorized"
|
||||
)
|
||||
is True,
|
||||
"refs_sync_authorized": github_summary.get("refs_sync_authorized") is True,
|
||||
"workflow_trigger_authorized": github_summary.get(
|
||||
"workflow_trigger_authorized"
|
||||
)
|
||||
is True,
|
||||
"secret_values_collected": False,
|
||||
},
|
||||
"source_statuses": source_statuses,
|
||||
@@ -185,11 +215,19 @@ def build_delivery_closure_workbench(
|
||||
"operation_boundaries": {
|
||||
"read_only_api_allowed": True,
|
||||
"runtime_write_allowed": False,
|
||||
"remote_write_allowed": False,
|
||||
"repo_creation_allowed": False,
|
||||
"visibility_change_allowed": False,
|
||||
"refs_sync_allowed": False,
|
||||
"workflow_trigger_allowed": False,
|
||||
"remote_write_allowed": github_boundaries.get("github_api_write_allowed")
|
||||
is True,
|
||||
"repo_creation_allowed": github_boundaries.get("repo_creation_allowed")
|
||||
is True,
|
||||
"visibility_change_allowed": github_boundaries.get(
|
||||
"visibility_change_allowed"
|
||||
)
|
||||
is True,
|
||||
"refs_sync_allowed": github_boundaries.get("refs_sync_allowed") is True,
|
||||
"workflow_trigger_allowed": github_boundaries.get(
|
||||
"workflow_trigger_allowed"
|
||||
)
|
||||
is True,
|
||||
"secret_value_collection_allowed": False,
|
||||
"backup_restore_execution_allowed": False,
|
||||
"active_scan_allowed": False,
|
||||
@@ -204,7 +242,9 @@ def _source_status(source_id: str, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"loaded": not source_missing,
|
||||
"schema_version": str(payload.get("schema_version") or ""),
|
||||
"generated_at": str(payload.get("generated_at") or ""),
|
||||
"missing_reason": str(payload.get("missing_reason") or "") if source_missing else "",
|
||||
"missing_reason": str(payload.get("missing_reason") or "")
|
||||
if source_missing
|
||||
else "",
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +258,9 @@ def _load_github_private_backup_evidence_gate() -> dict[str, Any]:
|
||||
except ModuleNotFoundError as exc:
|
||||
if exc.name != "src.services.github_target_private_backup_evidence_gate":
|
||||
raise
|
||||
return _missing_github_private_backup_source("service_module_missing_on_release_base")
|
||||
return _missing_github_private_backup_source(
|
||||
"service_module_missing_on_release_base"
|
||||
)
|
||||
except FileNotFoundError:
|
||||
return _missing_github_private_backup_source("snapshot_missing_on_release_base")
|
||||
|
||||
@@ -255,7 +297,7 @@ def _dict(value: Any) -> dict[str, Any]:
|
||||
def _int(value: Any) -> int:
|
||||
if isinstance(value, bool):
|
||||
return int(value)
|
||||
if isinstance(value, (int, float)):
|
||||
if isinstance(value, int | float):
|
||||
return int(value)
|
||||
return 0
|
||||
|
||||
@@ -307,7 +349,10 @@ def _first_backup_action(value: Any) -> str:
|
||||
if not isinstance(value, list):
|
||||
return ""
|
||||
for row in value:
|
||||
if isinstance(row, dict) and row.get("overall_readiness") in {"blocked", "action_required"}:
|
||||
if isinstance(row, dict) and row.get("overall_readiness") in {
|
||||
"blocked",
|
||||
"action_required",
|
||||
}:
|
||||
return str(row.get("next_action") or "")
|
||||
return _first_row_action(value)
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@ _APPROVAL_PACKAGE_FILE = "github-target-repo-approval-package.snapshot.json"
|
||||
_PROBE_FILE = "github-target-probe.snapshot.json"
|
||||
_CONNECTOR_READBACK_FILE = "github-target-connector-readback.snapshot.json"
|
||||
_MISSING_SOURCE_READINESS_FILE = "github-target-missing-source-readiness.snapshot.json"
|
||||
_EXECUTION_AUTHORIZATION_FILE = (
|
||||
"github-target-owner-execution-authorization.snapshot.json"
|
||||
)
|
||||
_EXECUTION_AUTHORIZATION_SCHEMA_VERSION = (
|
||||
"github_target_owner_execution_authorization_v1"
|
||||
)
|
||||
_PREFLIGHT_SCHEMA_VERSION = "github_target_owner_response_intake_preflight_v1"
|
||||
_PREFLIGHT_MODE = "validate_owner_response_only_no_persist_no_github_write"
|
||||
_SAFE_CREDENTIAL_REVIEW_SCHEMA_VERSION = (
|
||||
@@ -134,6 +140,9 @@ def load_latest_github_target_private_backup_evidence_gate(
|
||||
missing_source_readiness = _load_optional_snapshot(
|
||||
directory / _MISSING_SOURCE_READINESS_FILE
|
||||
)
|
||||
execution_authorization = _load_optional_snapshot(
|
||||
directory / _EXECUTION_AUTHORIZATION_FILE
|
||||
)
|
||||
|
||||
_require_source_contracts(
|
||||
decision=decision,
|
||||
@@ -142,6 +151,7 @@ def load_latest_github_target_private_backup_evidence_gate(
|
||||
probe=probe,
|
||||
connector_readback=connector_readback,
|
||||
missing_source_readiness=missing_source_readiness,
|
||||
execution_authorization=execution_authorization,
|
||||
)
|
||||
return build_github_target_private_backup_evidence_gate(
|
||||
decision=decision,
|
||||
@@ -150,6 +160,7 @@ def load_latest_github_target_private_backup_evidence_gate(
|
||||
probe=probe,
|
||||
connector_readback=connector_readback,
|
||||
missing_source_readiness=missing_source_readiness,
|
||||
execution_authorization=execution_authorization,
|
||||
)
|
||||
|
||||
|
||||
@@ -457,12 +468,22 @@ def build_github_target_private_backup_evidence_gate(
|
||||
probe: dict[str, Any],
|
||||
connector_readback: dict[str, Any] | None = None,
|
||||
missing_source_readiness: dict[str, Any] | None = None,
|
||||
execution_authorization: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Build the read-only gate response from source-control snapshots."""
|
||||
connector_payload = _dict(connector_readback)
|
||||
owner_readback = _dict(connector_payload.get("owner_readback"))
|
||||
missing_payload = _dict(missing_source_readiness)
|
||||
missing_summary = _dict(missing_payload.get("summary"))
|
||||
authorization_payload = _dict(execution_authorization)
|
||||
authorization_summary = _execution_authorization_summary(authorization_payload)
|
||||
execution_authorized = (
|
||||
authorization_summary["authorization_present"]
|
||||
and authorization_summary["repo_creation_authorized"]
|
||||
and authorization_summary["visibility_change_authorized"]
|
||||
and authorization_summary["refs_sync_authorized"]
|
||||
and authorization_summary["workflow_trigger_authorized"]
|
||||
)
|
||||
decisions = [_dict(row) for row in _list(decision.get("decisions"))]
|
||||
probe_by_repo = {
|
||||
str(row.get("github_repo")): _dict(row)
|
||||
@@ -484,6 +505,11 @@ def build_github_target_private_backup_evidence_gate(
|
||||
for row in _list(missing_payload.get("targets"))
|
||||
if row.get("github_repo")
|
||||
}
|
||||
authorization_by_repo = {
|
||||
str(row.get("github_repo")): _dict(row)
|
||||
for row in _list(authorization_payload.get("authorized_targets"))
|
||||
if row.get("github_repo")
|
||||
}
|
||||
owner_summary = _dict(owner_response.get("summary"))
|
||||
owner_response_intake_readiness = _owner_response_intake_readiness(owner_response)
|
||||
safe_credential_evidence_intake_readiness = (
|
||||
@@ -512,6 +538,10 @@ def build_github_target_private_backup_evidence_gate(
|
||||
owner_response_template=owner_template_by_repo.get(
|
||||
str(row.get("github_repo")), {}
|
||||
),
|
||||
execution_authorization=authorization_by_repo.get(
|
||||
str(row.get("github_repo")), {}
|
||||
),
|
||||
global_execution_authorized=execution_authorized,
|
||||
)
|
||||
for row in decisions
|
||||
]
|
||||
@@ -534,9 +564,7 @@ def build_github_target_private_backup_evidence_gate(
|
||||
if row["visibility_evidence_status"] == "external_scope_not_backup_target"
|
||||
]
|
||||
blocked_targets = [
|
||||
row
|
||||
for row in approval_required_targets
|
||||
if not row["private_backup_verified"] or not row["execution_ready"]
|
||||
row for row in approval_required_targets if not row["execution_ready"]
|
||||
]
|
||||
|
||||
private_backup_verified_count = sum(
|
||||
@@ -548,11 +576,19 @@ def build_github_target_private_backup_evidence_gate(
|
||||
|
||||
return {
|
||||
"schema_version": _SCHEMA_VERSION,
|
||||
"generated_at": _generated_at(owner_response),
|
||||
"status": "blocked_public_visibility_and_safe_credential_evidence_required"
|
||||
if public_probe_visible_targets
|
||||
else "blocked_private_visibility_and_safe_credential_evidence_required",
|
||||
"mode": "read_only_private_backup_evidence_gate",
|
||||
"generated_at": str(
|
||||
authorization_payload.get("generated_at") or _generated_at(owner_response)
|
||||
),
|
||||
"status": "owner_authorized_controlled_execution_preflight_ready"
|
||||
if execution_authorized
|
||||
else (
|
||||
"blocked_public_visibility_and_safe_credential_evidence_required"
|
||||
if public_probe_visible_targets
|
||||
else "blocked_private_visibility_and_safe_credential_evidence_required"
|
||||
),
|
||||
"mode": "owner_authorized_controlled_execution_no_secret_plaintext"
|
||||
if execution_authorized
|
||||
else "read_only_private_backup_evidence_gate",
|
||||
"source_reviews": {
|
||||
"target_decision": f"docs/security/{_DECISION_FILE}",
|
||||
"owner_decision_response": f"docs/security/{_OWNER_RESPONSE_FILE}",
|
||||
@@ -560,6 +596,7 @@ def build_github_target_private_backup_evidence_gate(
|
||||
"github_target_probe": f"docs/security/{_PROBE_FILE}",
|
||||
"github_connector_readback": f"docs/security/{_CONNECTOR_READBACK_FILE}",
|
||||
"github_missing_source_readiness": f"docs/security/{_MISSING_SOURCE_READINESS_FILE}",
|
||||
"github_owner_execution_authorization": f"docs/security/{_EXECUTION_AUTHORIZATION_FILE}",
|
||||
},
|
||||
"summary": {
|
||||
"target_decision_count": len(targets),
|
||||
@@ -615,6 +652,8 @@ def build_github_target_private_backup_evidence_gate(
|
||||
- private_backup_verified_count,
|
||||
"safe_credential_required_count": len(approval_required_targets),
|
||||
"safe_credential_accepted_evidence_count": 0,
|
||||
"safe_credential_execution_override_authorized": execution_authorized,
|
||||
"safe_credential_evidence_requirement_overridden_for_execution": execution_authorized,
|
||||
"safe_credential_evidence_intake_ready": safe_credential_evidence_intake_readiness[
|
||||
"intake_ready"
|
||||
],
|
||||
@@ -674,21 +713,57 @@ def build_github_target_private_backup_evidence_gate(
|
||||
],
|
||||
"owner_response_request_execution_authorized": False,
|
||||
"github_target_owner_response_handoff_not_approval": True,
|
||||
"owner_execution_authorization_received_count": authorization_summary[
|
||||
"authorization_received_count"
|
||||
],
|
||||
"owner_execution_authorized_target_count": authorization_summary[
|
||||
"authorized_target_count"
|
||||
],
|
||||
"owner_execution_authorization_status": authorization_summary["status"],
|
||||
"owner_execution_authorization_source": authorization_summary[
|
||||
"authorization_source"
|
||||
],
|
||||
"owner_execution_controlled_preflight_required_count": sum(
|
||||
1
|
||||
for row in approval_required_targets
|
||||
if row["controlled_preflight_required"] is True
|
||||
),
|
||||
"post_execution_readback_required_count": sum(
|
||||
1
|
||||
for row in approval_required_targets
|
||||
if row["post_execution_readback_required"] is True
|
||||
),
|
||||
"github_missing_target_create_private_repo_authorized_count": authorization_summary[
|
||||
"github_missing_target_create_private_repo_authorized_count"
|
||||
],
|
||||
"github_missing_target_refs_sync_authorized_count": authorization_summary[
|
||||
"github_missing_target_refs_sync_authorized_count"
|
||||
],
|
||||
"execution_ready_count": sum(
|
||||
1 for row in approval_required_targets if row["execution_ready"]
|
||||
),
|
||||
"blocked_target_count": len(blocked_targets),
|
||||
"external_scope_target_count": len(external_scope_targets),
|
||||
"forbidden_action_count": len(forbidden_actions),
|
||||
"repo_creation_authorized": False,
|
||||
"visibility_change_authorized": False,
|
||||
"refs_sync_authorized": False,
|
||||
"github_primary_switch_authorized": False,
|
||||
"repo_creation_authorized": authorization_summary[
|
||||
"repo_creation_authorized"
|
||||
],
|
||||
"visibility_change_authorized": authorization_summary[
|
||||
"visibility_change_authorized"
|
||||
],
|
||||
"refs_sync_authorized": authorization_summary["refs_sync_authorized"],
|
||||
"github_primary_switch_authorized": authorization_summary[
|
||||
"github_primary_switch_authorized"
|
||||
],
|
||||
"workflow_modification_authorized": False,
|
||||
"workflow_trigger_authorized": False,
|
||||
"workflow_trigger_authorized": authorization_summary[
|
||||
"workflow_trigger_authorized"
|
||||
],
|
||||
"secret_value_collection_allowed": False,
|
||||
"private_clone_url_collection_allowed": False,
|
||||
"not_found_or_private_as_absent_allowed": False,
|
||||
"not_found_or_private_as_absent_allowed": authorization_summary[
|
||||
"not_found_or_private_as_absent_allowed"
|
||||
],
|
||||
"public_repo_allowed": False,
|
||||
},
|
||||
"owner_response_intake_readiness": owner_response_intake_readiness,
|
||||
@@ -698,24 +773,41 @@ def build_github_target_private_backup_evidence_gate(
|
||||
"rejection_rules": _rejection_rules(owner_response),
|
||||
"operation_boundaries": {
|
||||
"read_only_api_allowed": True,
|
||||
"github_api_write_allowed": False,
|
||||
"github_api_write_allowed": authorization_summary[
|
||||
"github_api_write_allowed"
|
||||
],
|
||||
"gitea_api_write_allowed": False,
|
||||
"repo_creation_allowed": False,
|
||||
"visibility_change_allowed": False,
|
||||
"refs_sync_allowed": False,
|
||||
"repo_creation_allowed": authorization_summary["repo_creation_authorized"],
|
||||
"visibility_change_allowed": authorization_summary[
|
||||
"visibility_change_authorized"
|
||||
],
|
||||
"refs_sync_allowed": authorization_summary["refs_sync_authorized"],
|
||||
"workflow_modification_allowed": False,
|
||||
"workflow_trigger_allowed": False,
|
||||
"workflow_trigger_allowed": authorization_summary[
|
||||
"workflow_trigger_authorized"
|
||||
],
|
||||
"github_primary_switch_allowed": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"private_clone_url_collection_allowed": False,
|
||||
},
|
||||
"authorization_flags": {
|
||||
"repo_creation_authorized": False,
|
||||
"visibility_change_authorized": False,
|
||||
"refs_sync_authorized": False,
|
||||
"github_primary_switch_authorized": False,
|
||||
"owner_execution_authorization_present": authorization_summary[
|
||||
"authorization_present"
|
||||
],
|
||||
"repo_creation_authorized": authorization_summary[
|
||||
"repo_creation_authorized"
|
||||
],
|
||||
"visibility_change_authorized": authorization_summary[
|
||||
"visibility_change_authorized"
|
||||
],
|
||||
"refs_sync_authorized": authorization_summary["refs_sync_authorized"],
|
||||
"github_primary_switch_authorized": authorization_summary[
|
||||
"github_primary_switch_authorized"
|
||||
],
|
||||
"workflow_modification_authorized": False,
|
||||
"workflow_trigger_authorized": False,
|
||||
"workflow_trigger_authorized": authorization_summary[
|
||||
"workflow_trigger_authorized"
|
||||
],
|
||||
"secret_value_collection_allowed": False,
|
||||
"private_clone_url_collection_allowed": False,
|
||||
},
|
||||
@@ -911,6 +1003,8 @@ def _build_target(
|
||||
connector_readback: dict[str, Any],
|
||||
missing_source_readiness: dict[str, Any],
|
||||
owner_response_template: dict[str, Any],
|
||||
execution_authorization: dict[str, Any],
|
||||
global_execution_authorized: bool,
|
||||
) -> dict[str, Any]:
|
||||
github_repo = str(decision.get("github_repo") or "")
|
||||
probe_status = str(probe.get("status") or decision.get("probe_status") or "unknown")
|
||||
@@ -928,9 +1022,38 @@ def _build_target(
|
||||
probe_status=probe_status,
|
||||
private_visibility_verified=private_visibility_verified,
|
||||
)
|
||||
owner_execution_authorized = (
|
||||
approval_required
|
||||
and global_execution_authorized
|
||||
and execution_authorization.get("target_execution_authorized") is True
|
||||
)
|
||||
repo_creation_authorized = (
|
||||
owner_execution_authorized
|
||||
and execution_authorization.get("create_private_repo_authorized") is True
|
||||
)
|
||||
visibility_change_authorized = (
|
||||
owner_execution_authorized
|
||||
and execution_authorization.get("visibility_change_authorized") is True
|
||||
)
|
||||
refs_sync_authorized = (
|
||||
owner_execution_authorized
|
||||
and execution_authorization.get("refs_sync_authorized") is True
|
||||
)
|
||||
workflow_trigger_authorized = (
|
||||
owner_execution_authorized
|
||||
and execution_authorization.get("workflow_trigger_authorized") is True
|
||||
)
|
||||
execution_ready = (
|
||||
owner_execution_authorized
|
||||
and visibility_change_authorized
|
||||
and refs_sync_authorized
|
||||
and workflow_trigger_authorized
|
||||
)
|
||||
blockers = _target_blockers(
|
||||
visibility_status, approval_required, private_visibility_verified
|
||||
)
|
||||
if execution_ready:
|
||||
blockers = []
|
||||
forbidden_actions = _strings(approval_item.get("still_forbidden")) or [
|
||||
"create_github_repo",
|
||||
"change_repo_visibility",
|
||||
@@ -978,10 +1101,15 @@ def _build_target(
|
||||
"safe_credential_evidence_status": "not_collected",
|
||||
"safe_credential_evidence_ref": None,
|
||||
"safe_credential_evidence_intake_ready": approval_required,
|
||||
"safe_credential_evidence_submission_status": "waiting_redacted_evidence_ref"
|
||||
if approval_required
|
||||
else "not_required",
|
||||
"safe_credential_evidence_submission_status": (
|
||||
"owner_execution_authorized_post_apply_readback_pending"
|
||||
if owner_execution_authorized
|
||||
else (
|
||||
"waiting_redacted_evidence_ref" if approval_required else "not_required"
|
||||
)
|
||||
),
|
||||
"safe_credential_required_redacted_evidence_ref": approval_required,
|
||||
"safe_credential_execution_override_authorized": owner_execution_authorized,
|
||||
"safe_credential_allowed_evidence_ref_types": [
|
||||
"repo_path",
|
||||
"snapshot_path",
|
||||
@@ -1018,10 +1146,29 @@ def _build_target(
|
||||
owner_response_template.get("allowed_outputs")
|
||||
),
|
||||
"owner_response_execution_authorized": False,
|
||||
"owner_execution_authorized": owner_execution_authorized,
|
||||
"owner_execution_authorization_source": execution_authorization.get(
|
||||
"source_disposition"
|
||||
),
|
||||
"controlled_preflight_required": bool(
|
||||
owner_execution_authorized
|
||||
and execution_authorization.get("controlled_preflight_required") is True
|
||||
),
|
||||
"post_execution_readback_required": bool(
|
||||
owner_execution_authorized
|
||||
and execution_authorization.get("post_execution_readback_required") is True
|
||||
),
|
||||
"owner_response_accepted": False,
|
||||
"refs_sync_ready": False,
|
||||
"execution_ready": False,
|
||||
"refs_sync_ready": refs_sync_authorized,
|
||||
"execution_ready": execution_ready,
|
||||
"controlled_execution_ready": execution_ready,
|
||||
"blockers": blockers,
|
||||
"execution_pending_checks": [
|
||||
"controlled_preflight",
|
||||
"post_execution_private_visibility_and_refs_readback",
|
||||
]
|
||||
if execution_ready
|
||||
else [],
|
||||
"evidence_refs": _strings(decision.get("evidence_refs")),
|
||||
"next_action": str(
|
||||
approval_item.get("approval_action")
|
||||
@@ -1029,9 +1176,10 @@ def _build_target(
|
||||
or ""
|
||||
),
|
||||
"forbidden_actions": forbidden_actions,
|
||||
"repo_creation_authorized": False,
|
||||
"visibility_change_authorized": False,
|
||||
"refs_sync_authorized": False,
|
||||
"repo_creation_authorized": repo_creation_authorized,
|
||||
"visibility_change_authorized": visibility_change_authorized,
|
||||
"refs_sync_authorized": refs_sync_authorized,
|
||||
"workflow_trigger_authorized": workflow_trigger_authorized,
|
||||
"github_primary_switch_authorized": False,
|
||||
"secret_values_collected": False,
|
||||
}
|
||||
@@ -1097,6 +1245,7 @@ def _require_source_contracts(
|
||||
probe: dict[str, Any],
|
||||
connector_readback: dict[str, Any],
|
||||
missing_source_readiness: dict[str, Any],
|
||||
execution_authorization: dict[str, Any],
|
||||
) -> None:
|
||||
_require_schema(decision, "github_target_decision_v1", _DECISION_FILE)
|
||||
_require_schema(
|
||||
@@ -1120,6 +1269,12 @@ def _require_source_contracts(
|
||||
"github_target_missing_source_readiness_v1",
|
||||
_MISSING_SOURCE_READINESS_FILE,
|
||||
)
|
||||
if execution_authorization:
|
||||
_require_schema(
|
||||
execution_authorization,
|
||||
_EXECUTION_AUTHORIZATION_SCHEMA_VERSION,
|
||||
_EXECUTION_AUTHORIZATION_FILE,
|
||||
)
|
||||
_require_decision_consistency(decision, _DECISION_FILE)
|
||||
_require_probe_consistency(probe, _PROBE_FILE)
|
||||
_require_approval_package_consistency(approval_package, _APPROVAL_PACKAGE_FILE)
|
||||
@@ -1131,6 +1286,13 @@ def _require_source_contracts(
|
||||
_require_missing_source_readiness_consistency(
|
||||
missing_source_readiness, _MISSING_SOURCE_READINESS_FILE
|
||||
)
|
||||
if execution_authorization:
|
||||
_require_execution_authorization_consistency(
|
||||
authorization=execution_authorization,
|
||||
decision=decision,
|
||||
missing_source_readiness=missing_source_readiness,
|
||||
label=_EXECUTION_AUTHORIZATION_FILE,
|
||||
)
|
||||
_require_owner_response_boundaries(owner_response, _OWNER_RESPONSE_FILE)
|
||||
|
||||
|
||||
@@ -1276,6 +1438,160 @@ def _require_missing_source_readiness_consistency(
|
||||
)
|
||||
|
||||
|
||||
def _require_execution_authorization_consistency(
|
||||
*,
|
||||
authorization: dict[str, Any],
|
||||
decision: dict[str, Any],
|
||||
missing_source_readiness: dict[str, Any],
|
||||
label: str,
|
||||
) -> None:
|
||||
summary = _dict(authorization.get("summary"))
|
||||
boundaries = _dict(authorization.get("operation_boundaries"))
|
||||
authorized_targets = [
|
||||
_dict(row) for row in _list(authorization.get("authorized_targets"))
|
||||
]
|
||||
approval_required_repos = {
|
||||
str(row.get("github_repo"))
|
||||
for row in _list(decision.get("decisions"))
|
||||
if _dict(row).get("approval_required") is True
|
||||
}
|
||||
authorized_repos = {
|
||||
str(row.get("github_repo"))
|
||||
for row in authorized_targets
|
||||
if row.get("github_repo")
|
||||
}
|
||||
if authorized_repos != approval_required_repos:
|
||||
raise ValueError(
|
||||
f"{label}: authorized targets must match approval-required targets"
|
||||
)
|
||||
if _int(summary.get("authorized_target_count")) != len(authorized_targets):
|
||||
raise ValueError(f"{label}: authorized target count must match targets")
|
||||
if _int(summary.get("authorization_received_count")) < 1:
|
||||
raise ValueError(f"{label}: authorization_received_count must be >= 1")
|
||||
|
||||
required_true_flags = {
|
||||
"repo_creation_authorized",
|
||||
"visibility_change_authorized",
|
||||
"refs_sync_authorized",
|
||||
"workflow_trigger_authorized",
|
||||
}
|
||||
missing_true_flags = sorted(
|
||||
flag for flag in required_true_flags if summary.get(flag) is not True
|
||||
)
|
||||
if missing_true_flags:
|
||||
raise ValueError(
|
||||
f"{label}: controlled execution flags must be true: {missing_true_flags}"
|
||||
)
|
||||
|
||||
required_boundary_true_flags = {
|
||||
"github_api_write_allowed",
|
||||
"repo_creation_allowed",
|
||||
"visibility_change_allowed",
|
||||
"refs_sync_allowed",
|
||||
"workflow_trigger_allowed",
|
||||
}
|
||||
missing_boundary_true_flags = sorted(
|
||||
flag
|
||||
for flag in required_boundary_true_flags
|
||||
if boundaries.get(flag) is not True
|
||||
)
|
||||
if missing_boundary_true_flags:
|
||||
raise ValueError(
|
||||
f"{label}: controlled operation boundaries must be true: {missing_boundary_true_flags}"
|
||||
)
|
||||
|
||||
forbidden_true_flags = {
|
||||
"github_primary_switch_authorized",
|
||||
"workflow_modification_authorized",
|
||||
"delete_refs_authorized",
|
||||
"force_push_authorized",
|
||||
"public_repo_allowed",
|
||||
"public_visibility_allowed",
|
||||
"secret_value_collection_allowed",
|
||||
"private_clone_url_collection_allowed",
|
||||
"credential_value_collection_allowed",
|
||||
"raw_payload_storage_allowed",
|
||||
"write_performed",
|
||||
"repo_creation_performed",
|
||||
"visibility_change_performed",
|
||||
"refs_sync_performed",
|
||||
"workflow_trigger_performed",
|
||||
}
|
||||
enabled = sorted(
|
||||
flag for flag in forbidden_true_flags if summary.get(flag) is not False
|
||||
)
|
||||
forbidden_boundary_true_flags = {
|
||||
"workflow_modification_allowed",
|
||||
"github_primary_switch_allowed",
|
||||
"delete_refs_allowed",
|
||||
"force_push_allowed",
|
||||
"secret_value_collection_allowed",
|
||||
"private_clone_url_collection_allowed",
|
||||
"credential_value_collection_allowed",
|
||||
"raw_payload_storage_allowed",
|
||||
}
|
||||
enabled.extend(
|
||||
sorted(
|
||||
flag
|
||||
for flag in forbidden_boundary_true_flags
|
||||
if boundaries.get(flag) is not False
|
||||
)
|
||||
)
|
||||
if enabled:
|
||||
raise ValueError(f"{label}: forbidden authorization flags enabled: {enabled}")
|
||||
|
||||
required_still_forbidden = {
|
||||
"secret_value",
|
||||
"token_value",
|
||||
"private_clone_url_credential",
|
||||
"repo_archive",
|
||||
"git_object_pack",
|
||||
"force_push",
|
||||
"delete_refs",
|
||||
"github_primary_switch",
|
||||
}
|
||||
missing_forbidden = sorted(
|
||||
required_still_forbidden - set(_strings(authorization.get("still_forbidden")))
|
||||
)
|
||||
if missing_forbidden:
|
||||
raise ValueError(
|
||||
f"{label}: still_forbidden missing critical boundaries: {missing_forbidden}"
|
||||
)
|
||||
|
||||
missing_targets = {
|
||||
str(row.get("github_repo"))
|
||||
for row in _list(missing_source_readiness.get("targets"))
|
||||
if _dict(row).get("github_repo")
|
||||
}
|
||||
create_authorized_repos = {
|
||||
str(row.get("github_repo"))
|
||||
for row in authorized_targets
|
||||
if row.get("create_private_repo_authorized") is True
|
||||
}
|
||||
refs_authorized_missing_repos = {
|
||||
str(row.get("github_repo"))
|
||||
for row in authorized_targets
|
||||
if row.get("github_repo") in missing_targets
|
||||
and row.get("refs_sync_authorized") is True
|
||||
}
|
||||
if create_authorized_repos != missing_targets:
|
||||
raise ValueError(
|
||||
f"{label}: create authorization must match missing-source targets"
|
||||
)
|
||||
if refs_authorized_missing_repos != missing_targets:
|
||||
raise ValueError(
|
||||
f"{label}: refs sync authorization must cover missing-source targets"
|
||||
)
|
||||
if _int(
|
||||
summary.get("github_missing_target_create_private_repo_authorized_count")
|
||||
) != len(missing_targets):
|
||||
raise ValueError(f"{label}: missing target create authorization count drift")
|
||||
if _int(summary.get("github_missing_target_refs_sync_authorized_count")) != len(
|
||||
missing_targets
|
||||
):
|
||||
raise ValueError(f"{label}: missing target refs authorization count drift")
|
||||
|
||||
|
||||
def _require_owner_response_boundaries(payload: dict[str, Any], label: str) -> None:
|
||||
if payload.get("runtime_execution_authorized") is not False:
|
||||
raise ValueError(f"{label}: runtime_execution_authorized must be false")
|
||||
@@ -1468,6 +1784,48 @@ def _owner_response_intake_readiness(owner_response: dict[str, Any]) -> dict[str
|
||||
}
|
||||
|
||||
|
||||
def _execution_authorization_summary(
|
||||
execution_authorization: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
summary = _dict(execution_authorization.get("summary"))
|
||||
boundaries = _dict(execution_authorization.get("operation_boundaries"))
|
||||
authorization_present = bool(execution_authorization)
|
||||
repo_creation_authorized = summary.get("repo_creation_authorized") is True
|
||||
visibility_change_authorized = summary.get("visibility_change_authorized") is True
|
||||
refs_sync_authorized = summary.get("refs_sync_authorized") is True
|
||||
workflow_trigger_authorized = summary.get("workflow_trigger_authorized") is True
|
||||
return {
|
||||
"authorization_present": authorization_present,
|
||||
"status": str(
|
||||
execution_authorization.get("status")
|
||||
or "owner_execution_authorization_not_received"
|
||||
),
|
||||
"mode": str(execution_authorization.get("mode") or ""),
|
||||
"authorization_source": str(
|
||||
execution_authorization.get("authorization_source") or ""
|
||||
),
|
||||
"authorization_received_count": _int(
|
||||
summary.get("authorization_received_count")
|
||||
),
|
||||
"authorized_target_count": _int(summary.get("authorized_target_count")),
|
||||
"github_missing_target_create_private_repo_authorized_count": _int(
|
||||
summary.get("github_missing_target_create_private_repo_authorized_count")
|
||||
),
|
||||
"github_missing_target_refs_sync_authorized_count": _int(
|
||||
summary.get("github_missing_target_refs_sync_authorized_count")
|
||||
),
|
||||
"github_api_write_allowed": boundaries.get("github_api_write_allowed") is True,
|
||||
"repo_creation_authorized": repo_creation_authorized,
|
||||
"visibility_change_authorized": visibility_change_authorized,
|
||||
"refs_sync_authorized": refs_sync_authorized,
|
||||
"workflow_trigger_authorized": workflow_trigger_authorized,
|
||||
"github_primary_switch_authorized": False,
|
||||
"not_found_or_private_as_absent_allowed": repo_creation_authorized
|
||||
and visibility_change_authorized
|
||||
and refs_sync_authorized,
|
||||
}
|
||||
|
||||
|
||||
def _safe_credential_evidence_intake_readiness(
|
||||
*,
|
||||
owner_response: dict[str, Any],
|
||||
|
||||
@@ -19,10 +19,11 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
assert data["summary"]["source_count"] == 5
|
||||
assert data["summary"]["loaded_source_count"] == 5
|
||||
assert data["summary"]["runtime_execution_authorized"] is False
|
||||
assert data["summary"]["remote_write_authorized"] is False
|
||||
assert data["summary"]["repo_creation_authorized"] is False
|
||||
assert data["summary"]["refs_sync_authorized"] is False
|
||||
assert data["summary"]["workflow_trigger_authorized"] is False
|
||||
assert data["summary"]["remote_write_authorized"] is True
|
||||
assert data["summary"]["repo_creation_authorized"] is True
|
||||
assert data["summary"]["visibility_change_authorized"] is True
|
||||
assert data["summary"]["refs_sync_authorized"] is True
|
||||
assert data["summary"]["workflow_trigger_authorized"] is True
|
||||
assert data["summary"]["secret_values_collected"] is False
|
||||
assert data["summary"]["average_completion_percent"] >= 0
|
||||
assert data["summary"]["high_risk_blocker_count"] > 0
|
||||
@@ -36,10 +37,16 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
assert lanes["runtime"]["metric"]["kind"] == "surface_count"
|
||||
assert lanes["backup"]["metric"]["kind"] == "readiness_row_count"
|
||||
assert sources["github_private_backup"]["loaded"] is True
|
||||
assert sources["github_private_backup"]["schema_version"] == "github_target_private_backup_evidence_gate_v1"
|
||||
assert (
|
||||
sources["github_private_backup"]["schema_version"]
|
||||
== "github_target_private_backup_evidence_gate_v1"
|
||||
)
|
||||
assert sources["github_private_backup"]["missing_reason"] == ""
|
||||
assert lanes["github"]["blocker_count"] == 9
|
||||
assert lanes["github"]["status"] == "blocked_private_visibility_and_safe_credential_evidence_required"
|
||||
assert lanes["github"]["blocker_count"] == 0
|
||||
assert (
|
||||
lanes["github"]["status"]
|
||||
== "owner_authorized_controlled_execution_preflight_ready"
|
||||
)
|
||||
assert lanes["github"]["metric"]["verified"] == 4
|
||||
assert lanes["github"]["metric"]["total"] == 9
|
||||
assert all(0 <= lane["completion_percent"] <= 100 for lane in lanes.values())
|
||||
@@ -48,11 +55,11 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
boundaries = data["operation_boundaries"]
|
||||
assert boundaries["read_only_api_allowed"] is True
|
||||
assert boundaries["runtime_write_allowed"] is False
|
||||
assert boundaries["remote_write_allowed"] is False
|
||||
assert boundaries["repo_creation_allowed"] is False
|
||||
assert boundaries["visibility_change_allowed"] is False
|
||||
assert boundaries["refs_sync_allowed"] is False
|
||||
assert boundaries["workflow_trigger_allowed"] is False
|
||||
assert boundaries["remote_write_allowed"] is True
|
||||
assert boundaries["repo_creation_allowed"] is True
|
||||
assert boundaries["visibility_change_allowed"] is True
|
||||
assert boundaries["refs_sync_allowed"] is True
|
||||
assert boundaries["workflow_trigger_allowed"] is True
|
||||
assert boundaries["secret_value_collection_allowed"] is False
|
||||
assert boundaries["backup_restore_execution_allowed"] is False
|
||||
assert boundaries["active_scan_allowed"] is False
|
||||
|
||||
@@ -18,11 +18,10 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
snapshot = load_latest_github_target_private_backup_evidence_gate()
|
||||
|
||||
assert snapshot["schema_version"] == "github_target_private_backup_evidence_gate_v1"
|
||||
assert snapshot["mode"] == "read_only_private_backup_evidence_gate"
|
||||
assert (
|
||||
snapshot["status"]
|
||||
== "blocked_private_visibility_and_safe_credential_evidence_required"
|
||||
snapshot["mode"] == "owner_authorized_controlled_execution_no_secret_plaintext"
|
||||
)
|
||||
assert snapshot["status"] == "owner_authorized_controlled_execution_preflight_ready"
|
||||
assert snapshot["summary"]["target_decision_count"] == 10
|
||||
assert snapshot["summary"]["approval_required_target_count"] == 9
|
||||
assert snapshot["summary"]["github_connector_owner_visible_repository_count"] == 4
|
||||
@@ -55,6 +54,13 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
== 0
|
||||
)
|
||||
assert snapshot["summary"]["github_missing_target_refs_sync_ready_count"] == 0
|
||||
assert (
|
||||
snapshot["summary"][
|
||||
"github_missing_target_create_private_repo_authorized_count"
|
||||
]
|
||||
== 5
|
||||
)
|
||||
assert snapshot["summary"]["github_missing_target_refs_sync_authorized_count"] == 5
|
||||
assert snapshot["summary"]["private_backup_verified_count"] == 4
|
||||
assert snapshot["summary"]["private_visibility_verified_count"] == 4
|
||||
assert snapshot["summary"]["safe_credential_required_count"] == 9
|
||||
@@ -90,17 +96,48 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
assert (
|
||||
snapshot["summary"]["github_target_owner_response_handoff_not_approval"] is True
|
||||
)
|
||||
assert snapshot["summary"]["blocked_target_count"] == 9
|
||||
assert snapshot["summary"]["owner_execution_authorization_received_count"] == 1
|
||||
assert snapshot["summary"]["owner_execution_authorized_target_count"] == 9
|
||||
assert (
|
||||
snapshot["summary"]["owner_execution_authorization_status"]
|
||||
== "owner_authorized_controlled_execution"
|
||||
)
|
||||
assert (
|
||||
snapshot["summary"]["owner_execution_authorization_source"]
|
||||
== "chat_authorization_2026-06-28_full_hard_gate_open"
|
||||
)
|
||||
assert (
|
||||
snapshot["summary"]["owner_execution_controlled_preflight_required_count"] == 9
|
||||
)
|
||||
assert snapshot["summary"]["post_execution_readback_required_count"] == 9
|
||||
assert snapshot["summary"]["execution_ready_count"] == 9
|
||||
assert snapshot["summary"]["blocked_target_count"] == 0
|
||||
assert snapshot["summary"]["public_repo_allowed"] is False
|
||||
assert snapshot["summary"]["repo_creation_authorized"] is False
|
||||
assert snapshot["summary"]["visibility_change_authorized"] is False
|
||||
assert snapshot["summary"]["refs_sync_authorized"] is False
|
||||
assert snapshot["summary"]["repo_creation_authorized"] is True
|
||||
assert snapshot["summary"]["visibility_change_authorized"] is True
|
||||
assert snapshot["summary"]["refs_sync_authorized"] is True
|
||||
assert snapshot["summary"]["workflow_trigger_authorized"] is True
|
||||
assert snapshot["summary"]["github_primary_switch_authorized"] is False
|
||||
assert snapshot["summary"]["secret_value_collection_allowed"] is False
|
||||
assert snapshot["operation_boundaries"]["read_only_api_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["repo_creation_allowed"] is False
|
||||
assert snapshot["operation_boundaries"]["visibility_change_allowed"] is False
|
||||
assert snapshot["operation_boundaries"]["refs_sync_allowed"] is False
|
||||
assert snapshot["operation_boundaries"]["github_api_write_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["repo_creation_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["visibility_change_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["refs_sync_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["workflow_trigger_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["secret_value_collection_allowed"] is False
|
||||
assert (
|
||||
snapshot["operation_boundaries"]["private_clone_url_collection_allowed"]
|
||||
is False
|
||||
)
|
||||
assert snapshot["authorization_flags"]["repo_creation_authorized"] is True
|
||||
assert snapshot["authorization_flags"]["visibility_change_authorized"] is True
|
||||
assert snapshot["authorization_flags"]["refs_sync_authorized"] is True
|
||||
assert snapshot["authorization_flags"]["workflow_trigger_authorized"] is True
|
||||
assert snapshot["authorization_flags"]["github_primary_switch_authorized"] is False
|
||||
assert (
|
||||
snapshot["authorization_flags"]["private_clone_url_collection_allowed"] is False
|
||||
)
|
||||
intake = snapshot["owner_response_intake_readiness"]
|
||||
assert (
|
||||
intake["status"]
|
||||
@@ -189,7 +226,7 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"]["safe_credential_evidence_submission_status"]
|
||||
== "waiting_redacted_evidence_ref"
|
||||
== "owner_execution_authorized_post_apply_readback_pending"
|
||||
)
|
||||
assert (
|
||||
"redacted_metadata_pointer"
|
||||
@@ -210,6 +247,10 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
is False
|
||||
)
|
||||
assert targets["owenhytsai/awoooi"]["owner_response_execution_authorized"] is False
|
||||
assert targets["owenhytsai/awoooi"]["owner_execution_authorized"] is True
|
||||
assert targets["owenhytsai/awoooi"]["controlled_execution_ready"] is True
|
||||
assert targets["owenhytsai/awoooi"]["refs_sync_ready"] is True
|
||||
assert targets["owenhytsai/awoooi"]["blockers"] == []
|
||||
assert (
|
||||
"canonical_source"
|
||||
in targets["owenhytsai/awoooi"]["owner_response_required_fields"]
|
||||
@@ -226,6 +267,9 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
is False
|
||||
)
|
||||
assert targets["owenhytsai/ewoooc"]["missing_target_refs_sync_ready"] is False
|
||||
assert targets["owenhytsai/ewoooc"]["repo_creation_authorized"] is True
|
||||
assert targets["owenhytsai/ewoooc"]["refs_sync_authorized"] is True
|
||||
assert targets["owenhytsai/ewoooc"]["execution_ready"] is True
|
||||
assert targets["owenhytsai/ewoooc"]["private_backup_verified"] is False
|
||||
assert (
|
||||
targets["owenhytsai/ewoooc"]["owner_response_template_id"]
|
||||
@@ -352,6 +396,21 @@ def test_github_target_private_backup_gate_rejects_missing_source_write_flags(tm
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_rejects_execution_authorization_secrets(
|
||||
tmp_path,
|
||||
):
|
||||
_copy_security_snapshots(tmp_path)
|
||||
authorization_path = (
|
||||
tmp_path / "github-target-owner-execution-authorization.snapshot.json"
|
||||
)
|
||||
authorization = json.loads(authorization_path.read_text(encoding="utf-8"))
|
||||
authorization["summary"]["secret_value_collection_allowed"] = True
|
||||
authorization_path.write_text(json.dumps(authorization), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="forbidden authorization flags"):
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_owner_response_preflight_accepts_redacted_evidence_refs():
|
||||
preflight = preflight_github_target_owner_response_submission(
|
||||
_valid_owner_response_submission()
|
||||
@@ -439,8 +498,8 @@ def test_github_target_safe_credential_evidence_review_accepts_redacted_refs():
|
||||
assert payload["summary"]["safe_credential_evidence_submission_accepted_count"] == 1
|
||||
assert payload["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert payload["summary"]["private_backup_verified_count"] == 4
|
||||
assert payload["summary"]["execution_ready_count"] == 0
|
||||
assert payload["summary"]["blocked_target_count"] == 9
|
||||
assert payload["summary"]["execution_ready_count"] == 9
|
||||
assert payload["summary"]["blocked_target_count"] == 0
|
||||
assert payload["boundaries"]["payload_persisted"] is False
|
||||
assert payload["boundaries"]["safe_credential_accepted_updated"] is False
|
||||
assert payload["boundaries"]["runtime_execution_authorized"] is False
|
||||
@@ -510,6 +569,7 @@ def _copy_security_snapshots(tmp_path: Path) -> None:
|
||||
"github-target-probe.snapshot.json",
|
||||
"github-target-connector-readback.snapshot.json",
|
||||
"github-target-missing-source-readiness.snapshot.json",
|
||||
"github-target-owner-execution-authorization.snapshot.json",
|
||||
):
|
||||
shutil.copy(source_dir / filename, tmp_path / filename)
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "github_target_private_backup_evidence_gate_v1"
|
||||
assert data["mode"] == "read_only_private_backup_evidence_gate"
|
||||
assert data["mode"] == "owner_authorized_controlled_execution_no_secret_plaintext"
|
||||
assert data["status"] == "owner_authorized_controlled_execution_preflight_ready"
|
||||
assert data["summary"]["approval_required_target_count"] == 9
|
||||
assert data["summary"]["github_connector_readback_count"] == 9
|
||||
assert data["summary"]["github_connector_private_visibility_count"] == 4
|
||||
@@ -25,6 +26,11 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert data["summary"]["github_missing_target_gitea_source_candidate_count"] == 3
|
||||
assert data["summary"]["github_missing_target_create_private_repo_ready_count"] == 0
|
||||
assert data["summary"]["github_missing_target_refs_sync_ready_count"] == 0
|
||||
assert (
|
||||
data["summary"]["github_missing_target_create_private_repo_authorized_count"]
|
||||
== 5
|
||||
)
|
||||
assert data["summary"]["github_missing_target_refs_sync_authorized_count"] == 5
|
||||
assert data["summary"]["private_backup_verified_count"] == 4
|
||||
assert data["summary"]["private_visibility_verified_count"] == 4
|
||||
assert data["summary"]["safe_credential_evidence_intake_ready"] is True
|
||||
@@ -46,18 +52,22 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert data["summary"]["owner_response_collection_check_count"] == 6
|
||||
assert data["summary"]["owner_response_intake_preflight_check_count"] == 6
|
||||
assert data["summary"]["owner_response_request_execution_authorized"] is False
|
||||
assert data["summary"]["blocked_target_count"] == 9
|
||||
assert data["summary"]["owner_execution_authorization_received_count"] == 1
|
||||
assert data["summary"]["owner_execution_authorized_target_count"] == 9
|
||||
assert data["summary"]["execution_ready_count"] == 9
|
||||
assert data["summary"]["blocked_target_count"] == 0
|
||||
assert data["summary"]["public_repo_allowed"] is False
|
||||
assert data["summary"]["repo_creation_authorized"] is False
|
||||
assert data["summary"]["visibility_change_authorized"] is False
|
||||
assert data["summary"]["refs_sync_authorized"] is False
|
||||
assert data["summary"]["repo_creation_authorized"] is True
|
||||
assert data["summary"]["visibility_change_authorized"] is True
|
||||
assert data["summary"]["refs_sync_authorized"] is True
|
||||
assert data["summary"]["workflow_trigger_authorized"] is True
|
||||
assert data["summary"]["secret_value_collection_allowed"] is False
|
||||
assert data["operation_boundaries"]["read_only_api_allowed"] is True
|
||||
assert data["operation_boundaries"]["github_api_write_allowed"] is False
|
||||
assert data["operation_boundaries"]["repo_creation_allowed"] is False
|
||||
assert data["operation_boundaries"]["visibility_change_allowed"] is False
|
||||
assert data["operation_boundaries"]["refs_sync_allowed"] is False
|
||||
assert data["operation_boundaries"]["workflow_trigger_allowed"] is False
|
||||
assert data["operation_boundaries"]["github_api_write_allowed"] is True
|
||||
assert data["operation_boundaries"]["repo_creation_allowed"] is True
|
||||
assert data["operation_boundaries"]["visibility_change_allowed"] is True
|
||||
assert data["operation_boundaries"]["refs_sync_allowed"] is True
|
||||
assert data["operation_boundaries"]["workflow_trigger_allowed"] is True
|
||||
assert data["operation_boundaries"]["private_clone_url_collection_allowed"] is False
|
||||
intake = data["owner_response_intake_readiness"]
|
||||
assert (
|
||||
@@ -92,9 +102,11 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
== "/api/v1/agents/github-target-safe-credential-evidence-reviewer-validation/validate-redacted-refs"
|
||||
)
|
||||
assert data["targets"][0]["owner_response_execution_authorized"] is False
|
||||
assert data["targets"][0]["owner_execution_authorized"] is True
|
||||
assert data["targets"][0]["controlled_execution_ready"] is True
|
||||
assert (
|
||||
data["targets"][0]["safe_credential_evidence_submission_status"]
|
||||
== "waiting_redacted_evidence_ref"
|
||||
== "owner_execution_authorized_post_apply_readback_pending"
|
||||
)
|
||||
assert data["targets"][0]["safe_credential_raw_payload_storage_allowed"] is False
|
||||
assert (
|
||||
@@ -211,5 +223,5 @@ def test_github_target_safe_credential_evidence_review_api_does_not_persist():
|
||||
== 0
|
||||
)
|
||||
assert readback["summary"]["github_missing_target_refs_sync_ready_count"] == 0
|
||||
assert readback["summary"]["execution_ready_count"] == 0
|
||||
assert readback["summary"]["execution_ready_count"] == 9
|
||||
assert "192.168.0." not in response.text
|
||||
|
||||
@@ -1,3 +1,40 @@
|
||||
## 2026-06-28 — GitHub private backup controlled execution 授權 gate 本地完成
|
||||
|
||||
**背景**:統帥明確要求「硬閘全部打開、完全授權、全面快速推進」。本段把舊 GitHub private backup `blocked/read-only` gate 改成可審計的 owner controlled execution authorization;這是授權 gate 變更,不是秘密值收件,也不是已完成 GitHub 寫入。
|
||||
|
||||
**完成內容**:
|
||||
- 新增 `docs/security/github-target-owner-execution-authorization.snapshot.json`,記錄本次授權來源 `chat_authorization_2026-06-28_full_hard_gate_open`。
|
||||
- 9 個 approval-required GitHub targets 全部列為 `target_execution_authorized=true`,`execution_ready_count=9`、`blocked_target_count=0`。
|
||||
- 5 個 missing targets 新增受控授權計數:`github_missing_target_create_private_repo_authorized_count=5`、`github_missing_target_refs_sync_authorized_count=5`。
|
||||
- `apps/api/src/services/github_target_private_backup_evidence_gate.py` 讀取授權 snapshot 後,將主 gate status 改為 `owner_authorized_controlled_execution_preflight_ready`,mode 改為 `owner_authorized_controlled_execution_no_secret_plaintext`。
|
||||
- `apps/api/src/services/delivery_closure_workbench.py` 將 GitHub lane blocker 從 9 降為 0,並把 `remote_write_authorized`、`repo_creation_authorized`、`visibility_change_authorized`、`refs_sync_authorized`、`workflow_trigger_authorized` 投影為 `true`。
|
||||
- 新增 fail-closed consistency guard:授權 snapshot 若開啟 secret value、private clone URL、raw payload、force push、delete refs、GitHub primary switch、public visibility,API 會直接 fail。
|
||||
|
||||
**本地 readback**:
|
||||
- GitHub gate:`owner_execution_authorization_received_count=1`、`owner_execution_authorized_target_count=9`。
|
||||
- GitHub gate:`repo_creation_authorized=true`、`visibility_change_authorized=true`、`refs_sync_authorized=true`、`workflow_trigger_authorized=true`。
|
||||
- GitHub gate:`private_backup_verified_count=4`、`safe_credential_accepted_evidence_count=0`,未把尚未完成的 evidence 偽造成已驗收。
|
||||
- GitHub gate:原始 source readiness 仍保留 `github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`;新授權以 authorized count 表示。
|
||||
- Delivery Workbench GitHub lane:`status=owner_authorized_controlled_execution_preflight_ready`、`blocker_count=0`、metric 仍為 `private_backup_verified 4/9`。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3.11 -m ruff format ...`:通過,`5 files left unchanged`。
|
||||
- `python3.11 -m ruff check ...`:通過。
|
||||
- `python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/src/services/delivery_closure_workbench.py apps/api/src/api/v1/agents.py`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py apps/api/tests/test_delivery_closure_workbench_api.py -q`:`17 passed`。
|
||||
- `python3 -m json.tool docs/security/github-target-owner-execution-authorization.snapshot.json`:通過。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
**仍維持 false / 未做**:
|
||||
- `secret_value_collection_allowed=false`、`private_clone_url_collection_allowed=false`、`credential_value_collection_allowed=false`、`raw_payload_storage_allowed=false`。
|
||||
- `force_push_authorized=false`、`delete_refs_authorized=false`、`github_primary_switch_authorized=false`、`workflow_modification_authorized=false`、`public_repo_allowed=false`、`public_visibility_allowed=false`。
|
||||
- 本段未建立 GitHub repo、未改 repo visibility、未同步 refs、未觸發 workflow、未讀或保存 secret / private clone URL;未碰 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime;未 force push。
|
||||
|
||||
**下一個 P0**:
|
||||
- commit 並 normal push feature;確認 `gitea/main` 最新後 normal push `HEAD:main`,等待 Gitea CD 成功,再做 production readback。
|
||||
- production 目標讀回:`owner_execution_authorization_received_count=1`、`execution_ready_count=9`、`blocked_target_count=0`、repo / visibility / refs / workflow authorization 皆 `true`,同時 secret / private clone / force / delete / primary switch 維持 `false`。
|
||||
- 授權 gate 上線後再進入實際 GitHub controlled execution:先 collision preflight,再 create private repo / set private / normal refs sync / workflow verification / production readback。
|
||||
|
||||
## 2026-06-27 — 22:51 AwoooP controlled automation copy guard 進 main
|
||||
|
||||
**背景**:上一段已把正式 AwoooP Approvals / Runs / Work Items / Alerts HTML payload 中殘留的舊 manual gate 語意清零;本段不是再做文案文件,而是把防回歸規則寫成 repo guard,避免 `待人工決策`、`阻塞與人工閘門`、`人工接手`、`manual gate`、`owner review` 等語意再次回到低 / 中 / 高風險流程。
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
{
|
||||
"schema_version": "github_target_owner_execution_authorization_v1",
|
||||
"generated_at": "2026-06-28T00:00:00+08:00",
|
||||
"status": "owner_authorized_controlled_execution",
|
||||
"mode": "controlled_github_private_backup_execution_no_secret_plaintext",
|
||||
"authorization_source": "chat_authorization_2026-06-28_full_hard_gate_open",
|
||||
"scope": "github_private_backup_targets_only",
|
||||
"summary": {
|
||||
"authorization_received_count": 1,
|
||||
"authorized_target_count": 9,
|
||||
"github_missing_target_create_private_repo_authorized_count": 5,
|
||||
"github_missing_target_refs_sync_authorized_count": 5,
|
||||
"repo_creation_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"github_primary_switch_authorized": false,
|
||||
"workflow_modification_authorized": false,
|
||||
"delete_refs_authorized": false,
|
||||
"force_push_authorized": false,
|
||||
"public_repo_allowed": false,
|
||||
"public_visibility_allowed": false,
|
||||
"secret_value_collection_allowed": false,
|
||||
"private_clone_url_collection_allowed": false,
|
||||
"credential_value_collection_allowed": false,
|
||||
"raw_payload_storage_allowed": false,
|
||||
"write_performed": false,
|
||||
"repo_creation_performed": false,
|
||||
"visibility_change_performed": false,
|
||||
"refs_sync_performed": false,
|
||||
"workflow_trigger_performed": false
|
||||
},
|
||||
"authorized_actions": [
|
||||
"create_private_repo_for_missing_targets_after_collision_preflight",
|
||||
"set_or_verify_private_visibility",
|
||||
"sync_refs_from_approved_source_candidate",
|
||||
"trigger_post_sync_verification_workflow"
|
||||
],
|
||||
"controlled_preflight_requirements": [
|
||||
"confirm_target_owner_scope_is_owenhytsai",
|
||||
"verify_no_existing_private_repo_collision_before_create",
|
||||
"select_best_available_source_candidate_without_copying_secret_values",
|
||||
"perform_normal_push_or_sync_only_no_force",
|
||||
"run_post_execution_private_visibility_and_refs_readback"
|
||||
],
|
||||
"still_forbidden": [
|
||||
"secret_value",
|
||||
"token_value",
|
||||
"private_key",
|
||||
"cookie_or_session",
|
||||
"authorization_header",
|
||||
"private_clone_url_credential",
|
||||
"repo_archive",
|
||||
"git_object_pack",
|
||||
"db_dump",
|
||||
"force_push",
|
||||
"delete_refs",
|
||||
"tag_rewrite",
|
||||
"repo_delete",
|
||||
"github_primary_switch",
|
||||
"public_visibility",
|
||||
"raw_runtime_secret_volume",
|
||||
"unrelated_history_merge"
|
||||
],
|
||||
"authorized_targets": [
|
||||
{
|
||||
"github_repo": "owenhytsai/awoooi",
|
||||
"template_id": "target-awoooi-refs-blocked",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": false,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "existing_private_target_use_gitea_main_refs",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/clawbot-v5",
|
||||
"template_id": "target-clawbot-v5-refs-blocked",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": false,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "existing_private_target_use_gitea_main_refs",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/wooo-aiops",
|
||||
"template_id": "target-wooo-aiops-refs-blocked",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": false,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "existing_private_target_use_gitea_main_refs",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/wooo-infra-config",
|
||||
"template_id": "target-wooo-infra-config-internal-remote",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": false,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "existing_private_target_verify_internal_remote_refs",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/ewoooc",
|
||||
"template_id": "target-ewoooc-private-or-new",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "owner_authorized_use_best_available_source_candidate_pending_preflight",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/bitan-pharmacy",
|
||||
"template_id": "target-bitan-pharmacy-private-or-new",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "owner_authorized_use_best_available_source_candidate_pending_preflight",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/tsenyang-website",
|
||||
"template_id": "target-tsenyang-website-private-or-new",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "owner_authorized_use_best_available_source_candidate_pending_preflight",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/VibeWork",
|
||||
"template_id": "target-vibework-private-or-new",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "owner_authorized_use_best_available_source_candidate_pending_preflight",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/agent-bounty-protocol",
|
||||
"template_id": "target-agent-bounty-protocol-private-or-new",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "owner_authorized_use_best_available_source_candidate_pending_preflight",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
}
|
||||
],
|
||||
"operation_boundaries": {
|
||||
"read_only_api_allowed": true,
|
||||
"github_api_write_allowed": true,
|
||||
"repo_creation_allowed": true,
|
||||
"visibility_change_allowed": true,
|
||||
"refs_sync_allowed": true,
|
||||
"workflow_trigger_allowed": true,
|
||||
"workflow_modification_allowed": false,
|
||||
"github_primary_switch_allowed": false,
|
||||
"delete_refs_allowed": false,
|
||||
"force_push_allowed": false,
|
||||
"secret_value_collection_allowed": false,
|
||||
"private_clone_url_collection_allowed": false,
|
||||
"credential_value_collection_allowed": false,
|
||||
"raw_payload_storage_allowed": false
|
||||
},
|
||||
"evidence_refs": [
|
||||
"docs/HARD_RULES.md#ai-agent-controlled-runtime-authorization",
|
||||
"docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md#15-2026-06-26",
|
||||
"docs/security/github-target-owner-decision-response.snapshot.json",
|
||||
"docs/security/github-target-missing-source-readiness.snapshot.json"
|
||||
],
|
||||
"next_gate": "perform_controlled_github_private_backup_execution_and_post_readback"
|
||||
}
|
||||
Reference in New Issue
Block a user