feat: apply reviewed goal patches

This commit is contained in:
thanhnv
2026-07-12 00:21:50 +09:00
parent 159022c73f
commit a2cca645dc
9 changed files with 378 additions and 68 deletions
@@ -11,6 +11,7 @@ import hashlib
import json
import os
import re
import shlex
import subprocess
import tempfile
import time
@@ -247,19 +248,73 @@ def build_context(job_path: str, project_id: str, goal: str):
def requests_side_effect(goal: str) -> bool:
normalized = " ".join(goal.lower().split())
patterns = [
r"^(hãy\s+)?(làm luôn|sửa|thay đổi|triển khai|chạy|tạo|xóa|cài đặt|commit|push)\b",
r"^(hãy\s+)?(làm luôn|sửa|thay đổi|triển khai|thực hiện|hoàn thành|chạy|tạo|xóa|cài đặt|commit|push)\b",
r"^(please\s+)?(implement|fix|change|deploy|run|create|delete|install|commit|push)\b",
r"\b(thực hiện thao tác|sửa code|ghi file|mở pull request|create a pull request)\b",
]
return any(re.search(pattern, normalized) for pattern in patterns)
def submit_side_effect(job: dict, manifest: dict) -> dict:
def extract_patch(text: str) -> str:
match = re.search(r"```(?:diff|patch)\s*\n(.*?)```", text, re.DOTALL | re.IGNORECASE)
candidate = match.group(1) if match else text
start = candidate.find("diff --git ")
if start < 0:
raise ValueError("goal_patch_missing")
patch = candidate[start:].strip() + "\n"
if len(patch.encode("utf-8")) > 200_000:
raise ValueError("goal_patch_too_large")
return patch
def validate_and_store_patch(job_path: str, job: dict, patch: str) -> dict:
roots = [str(value).strip("/") for value in job.get("workspace", {}).get("context_roots", [])]
changed = []
header_paths = []
for line in patch.splitlines():
if line.startswith("diff --git "):
fields = shlex.split(line)
if len(fields) != 4:
raise ValueError("goal_patch_header_invalid")
header_paths.extend(fields[2:])
elif line.startswith(("--- ", "+++ ", "rename from ", "rename to ", "copy from ", "copy to ")):
value = line.split(" ", 1)[1].split("\t", 1)[0]
header_paths.append(value)
for raw in header_paths:
path = raw.strip()
if path == "/dev/null":
continue
if path.startswith(("a/", "b/")):
path = path[2:]
if path.startswith("/") or ".." in path.split("/"):
raise ValueError("goal_patch_path_denied")
if not any(path == root or path.startswith(root + "/") for root in roots):
raise ValueError(f"goal_patch_outside_workspace:{path}")
changed.append(path)
if not changed or len(set(changed)) > 20:
raise ValueError("goal_patch_file_count_invalid")
artifact = job_path[:-5] + ".patch"
with open(artifact, "x", encoding="utf-8") as handle:
handle.write(patch)
handle.flush()
os.fsync(handle.fileno())
os.chmod(artifact, 0o600)
check = subprocess.run(["git", "apply", "--check", "--whitespace=error", artifact], cwd=ROOT, capture_output=True, text=True, timeout=30)
if check.returncode != 0:
os.unlink(artifact)
raise ValueError("goal_patch_check_failed:" + (check.stderr or check.stdout).strip()[:160])
return {"path": os.path.relpath(artifact, ROOT), "sha256": sha(patch), "files": sorted(set(changed)), "bytes": len(patch.encode("utf-8")), "status": "awaiting_approval", "preview": patch[:50_000]}
def submit_side_effect(job: dict, manifest: dict, patch_artifact: dict) -> dict:
payload = {
"goal_id": job["id"],
"project_id": job["project"],
"context_manifest_hash": manifest["bundle_sha256"],
"requested_operation": job["goal"],
"patch_sha256": patch_artifact["sha256"],
"patch_path": patch_artifact["path"],
"changed_files": patch_artifact["files"],
}
result = subprocess.run([
"python3", APPROVAL_INBOX, "submit",
@@ -561,30 +616,17 @@ def run(job_path: str) -> int:
})
emit(goal_id, "H4-security", "running", "Objective and workspace snapshot passed; model outputs pending")
if requests_side_effect(safe_goal):
proposal = submit_side_effect(job, context_manifest)
approval = {"id": proposal["id"], "status": proposal["status"], "action": proposal["action"]}
stage(job_path, "local-worker", "blocked", "Workspace execution requires approval; no model or tool was allowed to write", job.get("local_provider", ""), local_model)
stage(job_path, "cloud-reviewer", "blocked", "Reviewer is not an execution channel", job.get("cloud_provider", ""), cloud_model)
update_job(job_path, status="requires_approval", approval=approval, result="This objective requests a workspace side effect. CASAN created a governed approval proposal and did not execute or modify files.", finished_at=now())
emit(goal_id, "H2-tool", "blocked", "Side effect withheld pending approval", {"proposal_id": proposal["id"], "action": proposal["action"]})
emit(goal_id, "H3-eval", "blocked", "Cloud reviewer cannot bypass the approval boundary")
emit(goal_id, "H4-security", "pass", "Workspace remained read-only")
gated_job = load_json(job_path)
audit_hash = audit(gated_job, "requires_approval")
emit(goal_id, "H5-governance", "pass", "Approval proposal and decision anchored", {"audit_hash": audit_hash, "proposal_id": proposal["id"]})
metric(gated_job, "degraded", started, {}, {})
emit(goal_id, "H6-agentops", "pass", "Approval routing telemetry recorded")
emit(goal_id, "H7-orchestration", "blocked", "Awaiting governed approval", {"proposal_id": proposal["id"]})
update_job(job_path, audit_hash=audit_hash)
return 0
write_intent = requests_side_effect(safe_goal)
stage(job_path, "local-worker", "running", "Local model is developing the primary solution", job.get("local_provider", ""), local_model)
emit(goal_id, "H2-tool", "running", "Local worker is developing a solution", {"provider": job.get("local_provider", ""), "model": local_model})
output_contract = (
"Produce ONLY one complete unified git diff inside a ```diff fence. The diff must implement the objective, include every required file, and may only touch paths visible in the workspace snapshot. Do not include commentary outside the diff. "
if write_intent else
"Produce: clarified outcome, assumptions, ordered implementation plan, risks, and verifiable acceptance checks. Respond in the same language as the objective. "
)
local_prompt = (
"You are the local CASAN worker. Solve the user's objective concretely. "
"Produce: clarified outcome, assumptions, ordered implementation plan, risks, "
"and verifiable acceptance checks. Respond in the same language as the objective. "
"You are the local CASAN worker. Solve the user's objective concretely. " + output_contract +
"Use only the bounded, redacted workspace snapshot below as repository evidence. "
"Do not claim to inspect any filesystem outside this snapshot and do not perform side effects.\n\n"
f"OBJECTIVE:\n{safe_goal}\n\nWORKSPACE SNAPSHOT ({project_id}):\n{context_bundle}"
@@ -604,11 +646,15 @@ def run(job_path: str) -> int:
stage(job_path, "cloud-reviewer", "running", "Independent reviewer is challenging and improving the local solution", job.get("cloud_provider", ""), cloud_model)
emit(goal_id, "H3-eval", "running", "Cloud reviewer is evaluating the local solution", {"provider": job.get("cloud_provider", ""), "model": cloud_model})
reviewer_contract = (
"Return ONLY the corrected, complete unified git diff inside a ```diff fence. Preserve valid implementation details, fix defects, and include no prose outside the diff. "
if write_intent else
"Return one final actionable solution with ordered steps and acceptance checks. Respond in the same language as the objective. "
)
review_prompt = (
"You are the cloud CASAN reviewer. Critically review the local worker's proposal "
"against the objective. Correct gaps, remove unsafe or unverifiable claims, and "
"return one final actionable solution with ordered steps and acceptance checks. "
"Respond in the same language as the objective. Use only the exact bounded, redacted "
+ reviewer_contract + "Use only the exact bounded, redacted "
"workspace snapshot provided below; your execution directory is intentionally empty. "
"Do not inspect or infer from any other filesystem and do not perform side effects.\n\n"
f"OBJECTIVE:\n{safe_goal}\n\nWORKSPACE SNAPSHOT ({project_id}):\n{context_bundle}\n\n"
@@ -636,14 +682,27 @@ def run(job_path: str) -> int:
metric_status = "degraded"
emit(goal_id, "H4-security", "pass", "Objective and all released outputs passed security scans")
job = update_job(job_path, status=final_status, result=safe_result, cloud_usage=cloud_meta, finished_at=now())
if write_intent:
patch = extract_patch(safe_result)
patch_artifact = validate_and_store_patch(job_path, load_json(job_path), patch)
proposal = submit_side_effect(load_json(job_path), context_manifest, patch_artifact)
approval = {"id": proposal["id"], "status": proposal["status"], "action": proposal["action"]}
patch_artifact["approval_id"] = proposal["id"]
final_status = "requires_approval"
metric_status = "degraded"
safe_result = "Implementation patch generated and independently reviewed. Approval is required before applying it to the workspace."
emit(goal_id, "H7-orchestration", "blocked", "Reviewed patch awaits approval", {"proposal_id": proposal["id"], "patch_sha256": patch_artifact["sha256"]})
job = update_job(job_path, status=final_status, result=safe_result, patch_artifact=patch_artifact, approval=approval, cloud_usage=cloud_meta, finished_at=now())
else:
job = update_job(job_path, status=final_status, result=safe_result, cloud_usage=cloud_meta, finished_at=now())
emit(goal_id, "H5-governance", "running", "Anchoring orchestration decision")
audit_hash = audit(job, final_status)
emit(goal_id, "H5-governance", "pass", "Orchestration decision anchored", {"audit_hash": audit_hash, "status": final_status})
emit(goal_id, "H6-agentops", "running", "Recording orchestration telemetry")
metric(job, metric_status, started, local_meta, cloud_meta)
emit(goal_id, "H6-agentops", "pass", "Orchestration telemetry recorded", {"latency_ms": int((time.monotonic() - started) * 1000), "status": metric_status})
emit(goal_id, "H7-orchestration", "pass", "Local worker and cloud review workflow completed", {"status": final_status, "cloud_incorporated": cloud_ok})
if not write_intent:
emit(goal_id, "H7-orchestration", "pass", "Local worker and cloud review workflow completed", {"status": final_status, "cloud_incorporated": cloud_ok})
update_job(job_path, audit_hash=audit_hash)
return 0
except Exception as error:
@@ -0,0 +1,140 @@
#!/usr/bin/env python3
"""Apply an approved Goal patch with bounded paths and rollback-on-failure."""
import argparse
import hashlib
import json
import os
import subprocess
import tempfile
from datetime import datetime, timezone
def root() -> str:
current = os.path.abspath(os.path.dirname(__file__))
while current != os.path.dirname(current):
if os.path.isdir(os.path.join(current, ".specify")):
return current
current = os.path.dirname(current)
raise SystemExit("GOAL_APPLY_ROOT_NOT_FOUND")
ROOT = root()
INBOX = os.path.join(ROOT, "packages", "casan-harness", "scripts", "bash", "approval-inbox.py")
def now() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def load(path: str) -> dict:
with open(path, encoding="utf-8") as handle:
return json.load(handle)
def save(path: str, payload: dict) -> None:
directory = os.path.dirname(path)
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=directory, delete=False) as handle:
json.dump(payload, handle, indent=2, ensure_ascii=False)
handle.write("\n")
temporary = handle.name
os.chmod(temporary, 0o600)
os.replace(temporary, path)
def run(command: list[str], timeout: int = 180) -> subprocess.CompletedProcess[str]:
return subprocess.run(command, cwd=ROOT, capture_output=True, text=True, timeout=timeout)
def verify_approval(job: dict) -> dict:
result = run(["python3", INBOX, "list", "--status", "all"], 30)
if result.returncode != 0:
raise RuntimeError("GOAL_APPLY_APPROVAL_STORE_UNAVAILABLE")
proposal = next((row for row in json.loads(result.stdout).get("proposals", []) if row.get("id") == job.get("approval", {}).get("id")), None)
artifact = job.get("patch_artifact", {})
if not proposal or proposal.get("status") != "approved":
raise PermissionError("GOAL_APPLY_APPROVAL_REQUIRED")
if proposal.get("action") != "goal.workspace.execute" or proposal.get("payload", {}).get("goal_id") != job.get("id"):
raise PermissionError("GOAL_APPLY_APPROVAL_SCOPE_MISMATCH")
if proposal.get("payload", {}).get("patch_sha256") != artifact.get("sha256"):
raise PermissionError("GOAL_APPLY_PATCH_HASH_MISMATCH")
if sorted(proposal.get("payload", {}).get("changed_files", [])) != sorted(artifact.get("files", [])):
raise PermissionError("GOAL_APPLY_FILE_SCOPE_MISMATCH")
return proposal
def verification_commands(files: list[str]) -> list[list[str]]:
commands = [["git", "diff", "--check", "--", *files]]
if any(path.startswith("apps/okr/frontend/") for path in files):
commands.append(["npm", "run", "build", "-w", "@ainative-okr/frontend"])
commands.append(["npm", "test", "-w", "@ainative-okr/frontend"])
if any(path.startswith("apps/okr/backend/") for path in files):
commands.append(["npm", "run", "build", "-w", "@ainative-okr/backend"])
commands.append(["npm", "test", "-w", "@ainative-okr/backend"])
return commands
def execute(job_path: str, actor: str) -> dict:
job = load(job_path)
if job.get("status") != "requires_approval" or not job.get("patch_artifact"):
raise RuntimeError("GOAL_APPLY_JOB_NOT_READY")
proposal = verify_approval(job)
if proposal.get("approver") != actor:
raise PermissionError("GOAL_APPLY_APPROVER_IDENTITY_MISMATCH")
artifact = job["patch_artifact"]
patch_path = os.path.realpath(os.path.join(ROOT, artifact["path"]))
state_root = os.path.realpath(os.path.join(ROOT, ".specify", "state", "goals"))
if not patch_path.startswith(state_root + os.sep) or not os.path.isfile(patch_path):
raise PermissionError("GOAL_APPLY_ARTIFACT_PATH_DENIED")
with open(patch_path, "rb") as handle:
actual_hash = hashlib.sha256(handle.read()).hexdigest()
if actual_hash != artifact["sha256"]:
raise PermissionError("GOAL_APPLY_ARTIFACT_TAMPERED")
check = run(["git", "apply", "--check", "--whitespace=error", patch_path], 30)
if check.returncode != 0:
raise RuntimeError("GOAL_APPLY_PRECONDITION_FAILED:" + (check.stderr or check.stdout).strip()[:200])
applied = run(["git", "apply", "--whitespace=error", patch_path], 30)
if applied.returncode != 0:
raise RuntimeError("GOAL_APPLY_FAILED:" + (applied.stderr or applied.stdout).strip()[:200])
checks = []
try:
for command in verification_commands(artifact.get("files", [])):
result = run(command, 300)
checks.append({"command": " ".join(command), "exit_code": result.returncode, "output": (result.stdout + result.stderr)[-4000:]})
if result.returncode != 0:
raise RuntimeError("GOAL_APPLY_VERIFICATION_FAILED")
except Exception:
rollback = run(["git", "apply", "--reverse", patch_path], 30)
if rollback.returncode != 0:
raise RuntimeError("GOAL_APPLY_ROLLBACK_FAILED")
job.update(status="failed", error="GOAL_APPLY_VERIFICATION_FAILED_ROLLED_BACK", verification=checks, finished_at=now(), updated_at=now())
save(job_path, job)
raise
artifact["status"] = "applied"
artifact["applied_at"] = now()
artifact["applied_by"] = actor
job.update(status="completed", result="Approved implementation patch applied and verified successfully.", patch_artifact=artifact, verification=checks, finished_at=now(), updated_at=now())
save(job_path, job)
return job
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--job-file", required=True)
parser.add_argument("--actor", required=True)
args = parser.parse_args()
try:
print(json.dumps(execute(os.path.realpath(args.job_file), args.actor), ensure_ascii=False))
return 0
except PermissionError as error:
print(str(error), file=os.sys.stderr)
return 3
except Exception as error:
print(str(error), file=os.sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())