"""Exercise G8 continuous-improvement gates over live HTTP and PostgreSQL.""" from __future__ import annotations import argparse import copy import json import secrets import time import urllib.error import urllib.request from dataclasses import dataclass from http.cookiejar import CookieJar from pathlib import Path from typing import Any from uuid import uuid4 DATA_CLASSIFICATION = "synthetic_replay_red_team_coverage_drift" INTERNAL_HEADER = "X-Vignette-Continuous-Improvement-Token" class SmokeError(RuntimeError): pass @dataclass(frozen=True) class ApiResponse: status: int body: Any class ApiClient: def __init__(self, base_url: str, timeout: float) -> None: self.base_url = base_url.rstrip("/") self.timeout = timeout self._opener = urllib.request.build_opener( urllib.request.HTTPCookieProcessor(CookieJar()) ) def request( self, method: str, path: str, payload: dict[str, Any] | None = None, *, expected: set[int] | None = None, headers: dict[str, str] | None = None, ) -> ApiResponse: data = None request_headers = {"Accept": "application/json", **(headers or {})} if payload is not None: data = json.dumps(payload, ensure_ascii=False).encode("utf-8") request_headers["Content-Type"] = "application/json" request = urllib.request.Request( f"{self.base_url}{path}", data=data, headers=request_headers, method=method, ) try: with self._opener.open(request, timeout=self.timeout) as response: raw = response.read().decode("utf-8") result = ApiResponse( response.status, json.loads(raw) if raw else {}, ) except urllib.error.HTTPError as exc: raw = exc.read().decode("utf-8", errors="replace") try: body = json.loads(raw) if raw else {} except json.JSONDecodeError: body = {"detail": raw[:500]} result = ApiResponse(exc.code, body) except urllib.error.URLError as exc: raise SmokeError( f"{method} {path} transport failed: {type(exc.reason).__name__}" ) from exc if result.status not in (expected or {200}): detail = result.body.get("detail") if isinstance(result.body, dict) else None raise SmokeError( f"{method} {path} returned HTTP {result.status}; detail={detail!r}" ) return result def _sign_in( client: ApiClient, *, suffix: str, identity: str, role: str, ) -> None: client.request( "POST", "/auth/dev-login", { "email": f"dev.e2e.ci.{identity}.{suffix}@hs.ac.kr", "role": role, "display_name": f"CI {identity.title()}", "cohort_ids": ["e2e-hanshin"], }, ) client.request( "POST", "/users/me/onboarding", { "legal_name": f"CI {identity.title()}", "affiliation": "한신대학교", "department": "상담심리학과", "grade_level": "통합검증", "phone": "010-0000-0000", "contact_address": "경기도 오산시 한신대학교", "nickname": f"CI {identity.title()}", "self_introduction": "G8 연속 개선 API 검증 fixture입니다.", "avatar_url": "", "terms_accepted": True, "privacy_accepted": True, }, ) def _assert_safe_payload(value: Any, path: str = "response") -> None: if isinstance(value, dict): forbidden = { "aggregate", "aggregate_score", "global_score", "overall_score", "raw_transcript", "total", "total_score", "transcript", "utterance_text", } & set(value) if forbidden: raise SmokeError(f"{path} exposed forbidden keys: {sorted(forbidden)}") for flag in ( "clinical_claim_allowed", "pii_included", "raw_transcript_included", "silent_auto_promotion_allowed", ): if value.get(flag) is True: raise SmokeError(f"{path} enabled forbidden flag {flag}") for key, child in value.items(): _assert_safe_payload(child, f"{path}.{key}") elif isinstance(value, list): for index, child in enumerate(value): _assert_safe_payload(child, f"{path}[{index}]") def _stable_retry( client: ApiClient, path: str, body: dict[str, Any], *, headers: dict[str, str] | None = None, id_field: str, ) -> dict[str, Any]: created = client.request( "POST", path, body, expected={201}, headers=headers, ) retried = client.request( "POST", path, body, expected={201}, headers=headers, ) if created.body.get(id_field) != retried.body.get(id_field): raise SmokeError(f"same submission changed {id_field} for {path}") if retried.body.get("idempotent_replay") is not True: raise SmokeError(f"same submission was not idempotent for {path}") return created.body def _artifact(kind: str, suffix: str, hash_character: str) -> dict[str, str]: return { "artifact_record_id": str(uuid4()), "artifact_id": f"g8-{kind}-{suffix}", "content_sha256": hash_character * 64, "provenance_uri": f"audit://continuous-improvement/{suffix}/{kind}", } def _gate_artifacts(prefix: str, suffix: str) -> dict[str, Any]: return { "baseline": _artifact(f"{prefix}-baseline", suffix, "1"), "threshold": _artifact(f"{prefix}-threshold", suffix, "2"), "provenance": [_artifact(f"{prefix}-provenance", suffix, "3")], "rollback": _artifact(f"{prefix}-rollback", suffix, "4"), } def _content_pipeline(suffix: str) -> dict[str, Any]: source_id = f"oas-g8-source-live-{suffix}" draft_id = f"oas-g8-draft-live-{suffix}" payload_hash = "c" * 64 return { "submission_id": str(uuid4()), "pipeline_id": str(uuid4()), "benchmark_record_id": str(uuid4()), "qualification_id": str(uuid4()), "data_classification": DATA_CLASSIFICATION, "draft": { "draft_id": draft_id, "content_kind": "case", "source_refs": [source_id], "generation_model": "content-agent-live-v1", "prompt_version": "1.0.0", "prompt_sha256": "b" * 64, "payload_sha256": payload_hash, "synthetic_identity_id": f"synthetic-identity-live-{suffix}", "difficulty_level": 4, "hidden_answer_fingerprint": "d" * 64, "visible_answer_overlap_tokens": 0, "pii_findings": 0, "unsupported_clinical_claims": 0, }, "sources": [ { "source_id": source_id, "version": "1.0.0", "content_sha256": "a" * 64, "provenance_uri": f"repo://synthetic/g8/{suffix}/source", "usage_status": "approved", "citation_label": "합성 교육 source pack", } ], "reviews": [ { "review_id": f"oas-g8-review-live-{suffix}-safety", "draft_id": draft_id, "reviewer_agent_id": f"red-team-safety-{suffix}", "dimensions": ["safety", "identity", "pii", "grounding"], "findings": [], "reviewed_payload_sha256": payload_hash, }, { "review_id": f"oas-g8-review-live-{suffix}-bias", "draft_id": draft_id, "reviewer_agent_id": f"red-team-bias-{suffix}", "dimensions": ["answer_leakage", "cultural_bias", "difficulty"], "findings": [], "reviewed_payload_sha256": payload_hash, }, ], "benchmark": { "benchmark_id": f"oas-g8-benchmark-live-{suffix}", "draft_id": draft_id, "variant_count": 8, "variant_pass_rate": 0.875, "answer_leakage_count": 0, "pii_finding_count": 0, "unsupported_claim_count": 0, "safety_failure_count": 0, "reward_hacking_count": 0, "evidence_refs": [f"audit://synthetic/g8/{suffix}/benchmark"], }, } def _model_gate(suffix: str) -> dict[str, Any]: return { "submission_id": str(uuid4()), "gate_id": str(uuid4()), "baseline_snapshot_record_id": str(uuid4()), "candidate_snapshot_record_id": str(uuid4()), "data_classification": DATA_CLASSIFICATION, "baseline": { "snapshot_id": f"oas-g8-model-snapshot-baseline-{suffix}", "model": "evaluator-baseline", "prompt_version": "1.0.0", "benchmark_version": "1.0.0", "task_accuracy": 0.9, "critical_miss_count": 0, "leakage_count": 0, "pii_count": 0, "calibration_error": 0.12, "subgroup_max_gap": 0.1, }, "candidate": { "snapshot_id": f"oas-g8-model-snapshot-candidate-{suffix}", "model": "evaluator-candidate", "prompt_version": "1.1.0", "benchmark_version": "1.0.0", "task_accuracy": 0.91, "critical_miss_count": 1, "leakage_count": 0, "pii_count": 0, "calibration_error": 0.11, "subgroup_max_gap": 0.09, }, "artifacts": _gate_artifacts("model", suffix), } def _release_gate(suffix: str) -> dict[str, Any]: return { "submission_id": str(uuid4()), "gate_id": str(uuid4()), "data_classification": DATA_CLASSIFICATION, "manifest": { "release_id": f"oas-g8-release-live-{suffix}", "red_green_passed": True, "contract_passed": True, "e2e_passed": True, "runtime_proof_passed": True, "public_proof_passed": True, "ssot_synced": True, "evidence_refs": [f"audit://continuous-improvement/{suffix}/release"], }, "artifacts": _gate_artifacts("release", suffix), } def _approval( *, target_kind: str, target_id: str, decision: str, suffix: str, ) -> dict[str, Any]: return { "submission_id": str(uuid4()), "approval_event_id": str(uuid4()), "effect_record_id": str(uuid4()), "target_kind": target_kind, "target_id": target_id, "decision": decision, "reason_code": f"g8_live_{decision}", "evidence_refs": [f"audit://continuous-improvement/{suffix}/{decision}"], } def _monitor( *, target_kind: str, target_id: str, event_status: str, suffix: str, ) -> dict[str, Any]: return { "submission_id": str(uuid4()), "lifecycle_event_id": str(uuid4()), "data_classification": DATA_CLASSIFICATION, "target_kind": target_kind, "target_id": target_id, "event_status": event_status, "evidence_refs": [ f"audit://continuous-improvement/{suffix}/monitor-{event_status}" ], } def _incident(suffix: str) -> dict[str, Any]: return { "submission_id": str(uuid4()), "incident_record_id": str(uuid4()), "data_classification": DATA_CLASSIFICATION, "incident": { "incident_id": f"oas-g8-incident-live-{suffix}", "error_fingerprint": "e" * 64, "affected_contract": "continuous-improvement.synthetic-replay", "evidence_refs": [ f"audit://continuous-improvement/{suffix}/incident" ], "pii_included": False, }, } def _matching(items: list[dict[str, Any]], key: str, value: str) -> list[dict[str, Any]]: return [item for item in items if str(item.get(key)) == value] def _assert_preapproval_no_effect( view: dict[str, Any], *, qualification_id: str, model_gate_id: str, release_gate_id: str, ) -> None: if _matching(view["catalog_entries"], "qualification_id", qualification_id): raise SmokeError("content entered catalog before human approval") targets = {model_gate_id, release_gate_id, qualification_id} if any(str(item.get("target_id")) in targets for item in view["approvals"]): raise SmokeError("approval existed before the human gate") if any( str(item.get("target_id")) in {model_gate_id, release_gate_id} for item in view["lifecycle_events"] ): raise SmokeError("model/release effect existed before human approval") def _assert_gate_artifacts( view: dict[str, Any], *, owner_kind: str, owner_id: str, ) -> None: artifacts = [ item for item in view["gate_artifacts"] if item.get("owner_kind") == owner_kind and str(item.get("owner_id")) == owner_id ] kinds = {item.get("artifact_kind") for item in artifacts} if len(artifacts) != 4 or kinds != { "baseline", "threshold", "provenance", "rollback", }: raise SmokeError(f"{owner_kind} omitted one of four gate artifacts") def _assert_incident_dag( view: dict[str, Any], *, incident_record_id: str, ) -> None: nodes = _matching( view["regression_dag_nodes"], "incident_record_id", incident_record_id, ) if len(nodes) != 4: raise SmokeError("incident DAG did not persist exactly four nodes") by_type = {str(item["node_type"]): item for item in nodes} expected = {"reproduction_test", "implementation", "e2e", "runtime_proof"} if set(by_type) != expected: raise SmokeError("incident DAG node types are incomplete") reproduction = by_type["reproduction_test"] implementation = by_type["implementation"] e2e = by_type["e2e"] runtime = by_type["runtime_proof"] if reproduction["depends_on_record_ids"]: raise SmokeError("incident reproduction node unexpectedly has a dependency") chain = ( (implementation, reproduction), (e2e, implementation), (runtime, e2e), ) for child, parent in chain: if child["depends_on_record_ids"] != [parent["node_record_id"]]: raise SmokeError("incident DAG dependency chain is not ordered") def _rollback_execution_proof(event: dict[str, Any]) -> dict[str, Any]: status = str(event.get("event_status") or "") if status not in {"requested", "failed", "executed"}: raise SmokeError(f"unexpected rollback lifecycle status: {status}") evidence_refs = event.get("evidence_refs") if not isinstance(evidence_refs, list) or not evidence_refs: raise SmokeError("rollback lifecycle omitted durable evidence refs") executor_evidence_refs = event.get("executor_evidence_refs") if executor_evidence_refs is None: executor_evidence_refs = [] if not isinstance(executor_evidence_refs, list): raise SmokeError("rollback executor evidence refs are malformed") receipt_id = event.get("executor_receipt_id") approval_event_id = event.get("approval_event_id") artifact_record_id = event.get("artifact_record_id") if not approval_event_id or not artifact_record_id: raise SmokeError("rollback lifecycle omitted approval or pinned artifact") if status == "executed": if not isinstance(receipt_id, str) or not receipt_id.strip(): raise SmokeError("executed rollback omitted executor receipt id") if not executor_evidence_refs: raise SmokeError("executed rollback omitted executor evidence refs") if not set(executor_evidence_refs).issubset(evidence_refs): raise SmokeError("executor evidence is not bound into lifecycle evidence") elif receipt_id is not None or executor_evidence_refs: raise SmokeError("non-executed rollback carried false receipt evidence") return { "lifecycle_event_id": event.get("lifecycle_event_id"), "target_kind": event.get("target_kind"), "target_id": event.get("target_id"), "status": status, "approval_event_id": approval_event_id, "artifact_record_id": artifact_record_id, "executor_receipt_id": receipt_id, "evidence_refs": evidence_refs, "executor_evidence_refs": executor_evidence_refs, "executed_receipt_bound": status == "executed", "receipt_contract_satisfied": True, } def run(args: argparse.Namespace) -> dict[str, Any]: if len(args.internal_token) < 32: raise SmokeError("--internal-token must contain at least 32 characters") root = ApiClient(args.api_base_url, args.request_timeout) health = root.request("GET", "/health").body if not health.get("db") or not health.get("engine"): raise SmokeError("API health is not DB+engine ready") suffix = f"{int(time.time())}-{secrets.token_hex(4)}" internal = ApiClient(args.api_base_url, args.request_timeout) admin = ApiClient(args.api_base_url, args.request_timeout) teacher = ApiClient(args.api_base_url, args.request_timeout) learner = ApiClient(args.api_base_url, args.request_timeout) _sign_in(admin, suffix=suffix, identity="admin", role="admin") _sign_in(teacher, suffix=suffix, identity="teacher", role="teacher") _sign_in(learner, suffix=suffix, identity="learner", role="learner") token_headers = {INTERNAL_HEADER: args.internal_token} internal.request("GET", "/internal/continuous-improvement", expected={401}) internal.request( "GET", "/internal/continuous-improvement", expected={403}, headers={INTERNAL_HEADER: "wrong-token"}, ) teacher.request("GET", "/continuous-improvement", expected={403}) learner.request("GET", "/continuous-improvement", expected={403}) content = _content_pipeline(suffix) content_path = "/internal/continuous-improvement/content-pipelines" content_result = _stable_retry( internal, content_path, content, headers=token_headers, id_field="qualification_id", ) if ( content_result.get("state") != "pending_human_approval" or content_result.get("human_approval_required") is not True or content_result.get("catalog_promoted") is not False ): raise SmokeError("content qualification bypassed the human gate") changed_content = copy.deepcopy(content) changed_content["benchmark"]["variant_pass_rate"] = 0.9 internal.request( "POST", content_path, changed_content, expected={409}, headers=token_headers, ) model_gate = _model_gate(suffix) model_path = "/internal/continuous-improvement/model-change-gates" model_result = _stable_retry( internal, model_path, model_gate, headers=token_headers, id_field="gate_id", ) if ( model_result.get("gate_decision") != "rollback" or model_result.get("promotion_executed") is not False ): raise SmokeError("unsafe model candidate did not remain pending rollback") changed_model = copy.deepcopy(model_gate) changed_model["candidate"]["task_accuracy"] = 0.89 internal.request( "POST", model_path, changed_model, expected={409}, headers=token_headers, ) release_gate = _release_gate(suffix) release_path = "/internal/continuous-improvement/release-gates" release_result = _stable_retry( internal, release_path, release_gate, headers=token_headers, id_field="gate_id", ) if ( release_result.get("qualified") is not True or release_result.get("promotion_executed") is not False ): raise SmokeError("qualified release bypassed or failed its human gate") changed_release = copy.deepcopy(release_gate) changed_release["manifest"]["evidence_refs"].append( f"audit://continuous-improvement/{suffix}/changed" ) internal.request( "POST", release_path, changed_release, expected={409}, headers=token_headers, ) incident = _incident(suffix) incident_path = "/internal/continuous-improvement/incidents" incident_result = _stable_retry( internal, incident_path, incident, headers=token_headers, id_field="incident_record_id", ) if incident_result.get("node_count") != 4: raise SmokeError("incident response omitted the four-node regression DAG") changed_incident = copy.deepcopy(incident) changed_incident["incident"]["affected_contract"] = "changed.contract" internal.request( "POST", incident_path, changed_incident, expected={409}, headers=token_headers, ) preapproval_view = internal.request( "GET", "/internal/continuous-improvement", headers=token_headers, ).body qualification_id = str(content_result["qualification_id"]) model_gate_id = str(model_result["gate_id"]) release_gate_id = str(release_result["gate_id"]) _assert_preapproval_no_effect( preapproval_view, qualification_id=qualification_id, model_gate_id=model_gate_id, release_gate_id=release_gate_id, ) _assert_gate_artifacts( preapproval_view, owner_kind="model_change_gate", owner_id=model_gate_id, ) _assert_gate_artifacts( preapproval_view, owner_kind="release_gate", owner_id=release_gate_id, ) content_approval = _approval( target_kind="content_qualification", target_id=qualification_id, decision="approve_content", suffix=suffix, ) teacher.request( "POST", "/continuous-improvement/approvals", content_approval, expected={403}, ) learner.request( "POST", "/continuous-improvement/approvals", content_approval, expected={403}, ) content_approval_result = _stable_retry( admin, "/continuous-improvement/approvals", content_approval, id_field="approval_event_id", ) changed_approval = copy.deepcopy(content_approval) changed_approval["reason_code"] = "changed_reason_must_conflict" admin.request( "POST", "/continuous-improvement/approvals", changed_approval, expected={409}, ) model_approval = _approval( target_kind="model_change_gate", target_id=model_gate_id, decision="authorize_rollback", suffix=suffix, ) model_approval_result = _stable_retry( admin, "/continuous-improvement/approvals", model_approval, id_field="approval_event_id", ) release_approval = _approval( target_kind="release_gate", target_id=release_gate_id, decision="approve_promotion", suffix=suffix, ) release_approval_result = _stable_retry( admin, "/continuous-improvement/approvals", release_approval, id_field="approval_event_id", ) monitor_results: dict[str, str] = {} for event_status, target_kind, target_id in ( ("healthy", "release_gate", release_gate_id), ("drift_detected", "model_change_gate", model_gate_id), ("rollback_recommended", "model_change_gate", model_gate_id), ): body = _monitor( target_kind=target_kind, target_id=target_id, event_status=event_status, suffix=suffix, ) result = _stable_retry( internal, "/internal/continuous-improvement/monitor-events", body, headers=token_headers, id_field="lifecycle_event_id", ) monitor_results[event_status] = str(result["lifecycle_event_id"]) if event_status == "healthy": changed_monitor = copy.deepcopy(body) changed_monitor["evidence_refs"].append( f"audit://continuous-improvement/{suffix}/changed-monitor" ) internal.request( "POST", "/internal/continuous-improvement/monitor-events", changed_monitor, expected={409}, headers=token_headers, ) internal_view = internal.request( "GET", "/internal/continuous-improvement", headers=token_headers, ).body admin_view = admin.request("GET", "/continuous-improvement").body model_rollbacks = [ item for item in admin_view["lifecycle_events"] if str(item.get("target_id")) == model_gate_id and item.get("event_type") == "rollback" ] if len(model_rollbacks) != 1: raise SmokeError("model rollback authorization did not append one lifecycle event") rollback_execution = _rollback_execution_proof(model_rollbacks[0]) rollback_status = str(rollback_execution["status"]) if ( args.expected_rollback_status and rollback_status != args.expected_rollback_status ): raise SmokeError( "rollback lifecycle status did not match expectation: " f"expected={args.expected_rollback_status} actual={rollback_status}" ) if rollback_status == "executed": body = _monitor( target_kind="model_change_gate", target_id=model_gate_id, event_status="rollback_verified", suffix=suffix, ) result = _stable_retry( internal, "/internal/continuous-improvement/monitor-events", body, headers=token_headers, id_field="lifecycle_event_id", ) monitor_results["rollback_verified"] = str(result["lifecycle_event_id"]) internal_view = internal.request( "GET", "/internal/continuous-improvement", headers=token_headers, ).body admin_view = admin.request("GET", "/continuous-improvement").body for payload in (internal_view, admin_view): _assert_safe_payload(payload) catalog = _matching( admin_view["catalog_entries"], "qualification_id", qualification_id, ) if len(catalog) != 1 or catalog[0].get("status") != "approved": raise SmokeError("admin content approval did not append one catalog effect") own_approvals = [ item for item in admin_view["approvals"] if str(item.get("target_id")) in {qualification_id, model_gate_id, release_gate_id} ] if len(own_approvals) != 3: raise SmokeError("human approvals were not append-only across three targets") lifecycle = [ item for item in admin_view["lifecycle_events"] if str(item.get("target_id")) in {model_gate_id, release_gate_id} ] lifecycle_pairs = { (str(item.get("event_type")), str(item.get("event_status"))) for item in lifecycle } required_pairs = { ("promotion", "approved"), ("rollback", rollback_status), ("monitor", "healthy"), ("monitor", "drift_detected"), ("monitor", "rollback_recommended"), } if rollback_status == "executed": required_pairs.add(("monitor", "rollback_verified")) if not required_pairs.issubset(lifecycle_pairs): raise SmokeError("promotion/rollback/monitor lifecycle is incomplete") _assert_incident_dag( admin_view, incident_record_id=str(incident_result["incident_record_id"]), ) return { "ok": True, "api_base_url": args.api_base_url, "executed_at_unix": int(time.time()), "fixture_policy": "retained unique dev:e2e identities; no fixture deletion", "run_suffix": suffix, "identifiers": { "pipeline_id": content_result["pipeline_id"], "qualification_id": qualification_id, "catalog_entry_id": content_result["candidate_catalog_entry_id"], "catalog_record_id": content_approval_result["effect_record_id"], "model_gate_id": model_gate_id, "model_rollback_event_id": model_approval_result["effect_record_id"], "release_gate_id": release_gate_id, "release_promotion_event_id": release_approval_result[ "effect_record_id" ], "incident_record_id": incident_result["incident_record_id"], "monitor_event_ids": monitor_results, }, "observed_counts": { "model_gate_artifacts": 4, "release_gate_artifacts": 4, "own_human_approvals": len(own_approvals), "own_lifecycle_events": len(lifecycle), "incident_dag_nodes": 4, }, "rollback_execution": rollback_execution, "proof": { "health_db_engine_ready": True, "missing_internal_token_401": True, "wrong_internal_token_403": True, "learner_role_blocked": True, "teacher_role_blocked": True, "source_draft_independent_redteam_benchmark_persisted": True, "content_pending_before_human_approval": True, "catalog_absent_before_human_approval": True, "model_effect_absent_before_human_approval": True, "release_effect_absent_before_human_approval": True, "model_gate_four_artifact_kinds": True, "release_gate_four_artifact_kinds": True, "content_admin_approval_append_only": True, "model_rollback_admin_authorized": True, "model_rollback_status_recorded": True, "model_rollback_receipt_contract_satisfied": True, "release_promotion_admin_approved": True, "monitor_lifecycle_closed": True, "incident_four_node_dag_ordered": True, "same_submission_retries_stable": True, "changed_submission_409": True, "raw_transcript_included": False, "pii_included": False, "clinical_claim_allowed": False, "silent_auto_promotion_allowed": False, "no_aggregate_score": True, }, } def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--api-base-url", default="http://127.0.0.1:8014") parser.add_argument("--internal-token", required=True) parser.add_argument("--request-timeout", type=float, default=180.0) parser.add_argument( "--expected-rollback-status", choices=("requested", "failed", "executed"), default="", ) parser.add_argument("--out", default="") args = parser.parse_args() result = run(args) text = json.dumps(result, ensure_ascii=False, indent=2) if args.out: path = Path(args.out) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(text + "\n", encoding="utf-8") print(text) if __name__ == "__main__": main()