121 lines
4.1 KiB
Python
121 lines
4.1 KiB
Python
"""ADW ZTE Engine — Webhook trigger + pipeline orchestrator.
|
|
Built on the factory infrastructure (25 containers, /deploy endpoint, RSI cron)."""
|
|
import hashlib
|
|
import hmac
|
|
import json
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
import uuid
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
BASE = Path(__file__).parent
|
|
TREES = BASE / "trees"
|
|
MODULES = BASE / "adw_modules"
|
|
DEPLOY_URL = os.environ.get("DEPLOY_URL", "http://localhost:8099/deploy")
|
|
DEPLOY_TOKEN = os.environ.get("DEPLOY_TOKEN", "factory-deploy-token-2026")
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
log = logging.getLogger("adw")
|
|
|
|
def signed_payload(action, target, actor="adw", payload_hash="none"):
|
|
data = {
|
|
"timestamp": int(time.time()),
|
|
"nonce": hashlib.sha256(f"{time.time()}:{target}:{action}".encode()).hexdigest()[:16],
|
|
"actor": actor,
|
|
"action": action,
|
|
"target": target,
|
|
"payload_hash": payload_hash,
|
|
}
|
|
body = json.dumps(data, sort_keys=True, separators=(",", ":")).encode()
|
|
data["signature"] = hmac.new(DEPLOY_TOKEN.encode(), body, hashlib.sha256).hexdigest()
|
|
return data
|
|
|
|
|
|
def post_deploy(payload):
|
|
req = urllib.request.Request(
|
|
DEPLOY_URL,
|
|
data=json.dumps(payload).encode(),
|
|
headers={"Authorization": f"Bearer {DEPLOY_TOKEN}", "Content-Type": "application/json"},
|
|
method="POST",
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=15) as r:
|
|
return r.read().decode()[:200]
|
|
except urllib.error.HTTPError as e:
|
|
return e.read().decode()[:200]
|
|
except Exception as e:
|
|
return str(e)[:200]
|
|
|
|
|
|
class ADW:
|
|
def __init__(self, issue=0, title="", body="", zte=False):
|
|
self.id = f"adw-{int(datetime.now().timestamp())}-{uuid.uuid4().hex[:6]}"
|
|
self.issue = issue
|
|
self.title = title or f"ADW {self.id}"
|
|
self.body = body
|
|
self.zte = zte # uppercase ZTE = auto-ship
|
|
self.phase_results = []
|
|
|
|
def classify(self):
|
|
t = (self.title + " " + self.body).lower()
|
|
return "patch" if any(w in t for w in ["bug", "fix", "crash"]) else "feature"
|
|
|
|
def run_phase(self, name):
|
|
script = MODULES / f"adw_{name}_iso.py"
|
|
if not script.exists():
|
|
return {"phase": name, "status": "skipped"}
|
|
log.info(f"Phase: {name}")
|
|
r = subprocess.run([
|
|
"python3",
|
|
str(script),
|
|
"--adw-id",
|
|
self.id,
|
|
"--issue",
|
|
str(self.issue),
|
|
], capture_output=True, text=True, timeout=300)
|
|
return {"phase": name, "status": "ok" if r.returncode == 0 else "fail", "exit": r.returncode}
|
|
|
|
def ship(self):
|
|
payload = signed_payload(
|
|
"patch_skill_from_pr",
|
|
self.id,
|
|
payload_hash=hashlib.sha256(f"{self.id}:{self.issue}:{self.title}".encode()).hexdigest()[:16],
|
|
)
|
|
return post_deploy(payload)
|
|
|
|
def run(self):
|
|
log.info(f"ADW {self.id} | Issue #{self.issue} | ZTE={'yes' if self.zte else 'no'}")
|
|
_ = self.classify()
|
|
phases = ["plan", "build", "test", "review", "document"]
|
|
for p in phases:
|
|
r = self.run_phase(p)
|
|
self.phase_results.append(r)
|
|
if r["status"] == "fail" and not self.zte:
|
|
break
|
|
if r["status"] == "fail" and self.zte:
|
|
break
|
|
ship_result = None
|
|
all_ok = all(r["status"] == "ok" for r in self.phase_results)
|
|
if all_ok and (self.zte or input("Ship? y/N: ") == "y"):
|
|
ship_result = self.ship()
|
|
return {"id": self.id, "issue": self.issue, "zte": self.zte, "type": self.classify(),
|
|
"phases": self.phase_results, "ship": ship_result}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
|
|
a = argparse.ArgumentParser()
|
|
a.add_argument("--issue", type=int, default=0)
|
|
a.add_argument("--title", default="")
|
|
a.add_argument("--zte", action="store_true")
|
|
args = a.parse_args()
|
|
adw = ADW(issue=args.issue, title=args.title, zte=args.zte)
|
|
print(json.dumps(adw.run(), indent=2))
|