138 lines
4.1 KiB
Python
Executable File
138 lines
4.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
|
|
IMPORT_LINE = (
|
|
"from aios_agent_task_projection_recovery_http import "
|
|
"get_projection as get_canonical_projection, "
|
|
"recover_from_json as recover_canonical_projection\n"
|
|
)
|
|
|
|
IMPORT_ANCHOR = '''from aios_online_agent_execution import (
|
|
GovernedExecutionBinding,
|
|
validate_execution_envelope,
|
|
)
|
|
'''
|
|
|
|
GET_OLD = ''' if self.path == "/health":
|
|
self._json(
|
|
{"status": "ok", "service": "aios-agent-task-initial-execution-projection"},
|
|
200,
|
|
)
|
|
else:
|
|
self._json({"error": "NOT_FOUND"}, 404)
|
|
'''
|
|
|
|
GET_NEW = ''' if self.path == "/health":
|
|
self._json(
|
|
{"status": "ok", "service": "aios-agent-task-initial-execution-projection"},
|
|
200,
|
|
)
|
|
else:
|
|
canonical_match = re.fullmatch(
|
|
r"/v1/tasks/([^/]+)/canonical-projection", self.path
|
|
)
|
|
if canonical_match is None:
|
|
self._json({"error": "NOT_FOUND"}, 404)
|
|
return
|
|
if not self._authorized():
|
|
self._json({"error": "UNAUTHORIZED"}, 401)
|
|
return
|
|
if self.store is None:
|
|
self._json({"error": "RUNTIME_UNAVAILABLE"}, 503)
|
|
return
|
|
status, result = get_canonical_projection(
|
|
self.store.db_path, canonical_match.group(1)
|
|
)
|
|
self._json(result, status)
|
|
'''
|
|
|
|
POST_MATCH_ANCHOR = ''' initial_match = re.fullmatch(
|
|
r"/v1/tasks/([^/]+)/initial-execution-projection", self.path
|
|
)
|
|
'''
|
|
|
|
POST_MATCH_REPLACEMENT = POST_MATCH_ANCHOR + ''' canonical_match = re.fullmatch(
|
|
r"/v1/tasks/([^/]+)/canonical-projection-recovery", self.path
|
|
)
|
|
'''
|
|
|
|
POST_VALIDATION_OLD = ''' if initial_match is None and recovery_match is None:
|
|
self._json({"error": "NOT_FOUND"}, 404)
|
|
return
|
|
'''
|
|
|
|
POST_VALIDATION_NEW = ''' if (
|
|
initial_match is None
|
|
and recovery_match is None
|
|
and canonical_match is None
|
|
):
|
|
self._json({"error": "NOT_FOUND"}, 404)
|
|
return
|
|
'''
|
|
|
|
VALUE_HANDLER_OLD = ''' if initial_match is not None:
|
|
request = InitialExecutionProjectionRequest.from_mapping(value)
|
|
'''
|
|
|
|
VALUE_HANDLER_NEW = ''' if canonical_match is not None:
|
|
status, result = recover_canonical_projection(
|
|
self.store.db_path, canonical_match.group(1), value
|
|
)
|
|
self._json(result, status)
|
|
return
|
|
if initial_match is not None:
|
|
request = InitialExecutionProjectionRequest.from_mapping(value)
|
|
'''
|
|
|
|
|
|
class PatchError(RuntimeError):
|
|
pass
|
|
|
|
|
|
def replace_once(text: str, old: str, new: str, label: str) -> str:
|
|
count = text.count(old)
|
|
if count != 1:
|
|
raise PatchError(f"{label}_ANCHOR_COUNT_{count}")
|
|
return text.replace(old, new, 1)
|
|
|
|
|
|
def apply(path: Path) -> bool:
|
|
text = path.read_text(encoding="utf-8")
|
|
if "canonical-projection-recovery" in text and IMPORT_LINE.strip() in text:
|
|
return False
|
|
text = replace_once(text, IMPORT_ANCHOR, IMPORT_ANCHOR + IMPORT_LINE, "IMPORT")
|
|
text = replace_once(text, GET_OLD, GET_NEW, "GET")
|
|
text = replace_once(
|
|
text, POST_MATCH_ANCHOR, POST_MATCH_REPLACEMENT, "POST_MATCH"
|
|
)
|
|
text = replace_once(
|
|
text, POST_VALIDATION_OLD, POST_VALIDATION_NEW, "POST_VALIDATION"
|
|
)
|
|
text = replace_once(
|
|
text, VALUE_HANDLER_OLD, VALUE_HANDLER_NEW, "VALUE_HANDLER"
|
|
)
|
|
path.write_text(text, encoding="utf-8")
|
|
return True
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("path", type=Path)
|
|
args = parser.parse_args()
|
|
try:
|
|
changed = apply(args.path)
|
|
except (OSError, PatchError) as exc:
|
|
print(f"ERROR:{exc}", file=sys.stderr)
|
|
return 2
|
|
print("CHANGED" if changed else "ALREADY_APPLIED")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|