"""Trusted control-plane source. Deploy outside the Codex-writable checkout before use.""" from __future__ import annotations import argparse import base64 import contextlib from datetime import datetime, timezone import hashlib import json import os from pathlib import Path import re import shutil import signal import subprocess import sys import tempfile import time import uuid import xml.etree.ElementTree as ET import local_mode import assessment_adapter class GateError(RuntimeError): pass class PublicationError(GateError): pass class Interrupted(GateError): pass STATUSES = {'PENDING', 'READY', 'RUNNING', 'BLOCKED', 'AWAITING_APPROVAL', 'FAILED', 'CONDITIONALLY_ACCEPTED', 'ACCEPTED', 'COMPLETED'} OUTCOMES = {'complete', 'checkpoint', 'blocked', 'approval', 'conditional', 'failed'} SAFE_BRANCH = re.compile(r'^(?:codex|feature|planning|test)/[A-Za-z0-9][A-Za-z0-9/_-]*$') def utc(): return datetime.now(timezone.utc).isoformat() def canonical(value): return json.dumps(value, sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode('utf-8') def digest(value): return hashlib.sha256(canonical(value)).hexdigest() def sha_file(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest() def under(root, name): root = Path(root).resolve() p = (root / name).resolve() if not p.is_relative_to(root) or p == root: raise GateError(f'Path must be a child of {root}: {name}') # resolve() checks junction/symlink escape as well as traversal. return p def atomic(path, value): path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) fd, temporary = tempfile.mkstemp(prefix='.' + path.name, dir=path.parent) try: with os.fdopen(fd, 'wb') as stream: stream.write(canonical(value)) stream.flush() os.fsync(stream.fileno()) os.replace(temporary, path) finally: if os.path.exists(temporary): os.unlink(temporary) def read_json(path): return json.loads(Path(path).read_text(encoding='utf-8-sig')) def recorded_process_exists(marker): pid=marker.get('pid') if not isinstance(pid,int) or pid <= 0: raise GateError('Launch intent has no valid PID; operator must inspect all runner children before clearing it') p=subprocess.run(['tasklist','/FI',f'PID eq {pid}','/FO','CSV','/NH'],capture_output=True,text=True,timeout=15) if p.returncode: raise GateError('Could not verify recorded process termination') return str(pid) in p.stdout def redact(text): if text.lstrip().startswith(('{', '[')): try: value = json.loads(text) except ValueError: value = None if isinstance(value, (dict, list)): def scrub(item): if isinstance(item, dict): return {key: '[REDACTED]' if re.fullmatch( r'(?i)password|token|secret|api[_-]?key|authorization|access_token|refresh_token|id_token', key) else scrub(child) for key, child in item.items()} if isinstance(item, list): return [scrub(child) for child in item] return redact(item) if isinstance(item, str) else item return json.dumps(scrub(value), ensure_ascii=False) text = re.sub(r'(?i)(bearer\s+)\S+', r'\1[REDACTED]', text) text = re.sub(r'(?i)((?:password|token|secret|api[_-]?key|authorization)\s*[=:]\s*)[^\s,;]+', r'\1[REDACTED]', text) text = re.sub(r'(https?://)[^/@\s]+:[^/@\s]+@', r'\1[REDACTED]@', text) return text def validate_manifest(m, root=None): if m.get('schemaVersion') != 1 or not isinstance(m.get('executionEnabled'), bool): raise GateError('Unsupported manifest or missing executionEnabled boolean') if not SAFE_BRANCH.fullmatch(m.get('integrationBranch', '')): raise GateError('Unsafe integration branch') if not re.fullmatch(r'[0-9a-f]{40}', m.get('baselineCommit', '')): raise GateError('Baseline must be an exact 40-character commit') if not SAFE_BRANCH.fullmatch(m.get('baselineBranch', '')): raise GateError('Unsafe baseline branch') if not re.fullmatch(r'[A-Za-z0-9_-]+', m.get('remote', '')): raise GateError('Remote must be a configured name, not a URL or option') seen, sequences, branches = set(), [], set() if not m.get('stages'): raise GateError('Queue is empty') for s in m['stages']: required = {'id', 'sequence', 'sourcePrompt', 'promptSha256', 'sourceStatus', 'dependencies', 'branch', 'requiredOwnerApprovals', 'retryPolicy', 'status', 'evidence', 'reports', 'contextFiles', 'checkpoints'} if not required.issubset(s) or s['id'] in seen or not re.fullmatch(r'[A-Z0-9-]+', s['id']): raise GateError('Missing stage fields or duplicate/invalid workstream ID') if not set(s['dependencies']).issubset(seen): raise GateError(f'Forward, unknown or cyclic dependency: {s["id"]}') if not SAFE_BRANCH.fullmatch(s['branch']) or '..' in s['branch'] or s['branch'] in branches: raise GateError('Unsafe or duplicate stage branch') if s['branch'] == m['integrationBranch'] or s['status'] not in STATUSES: raise GateError('Invalid branch/status') seen.add(s['id']) branches.add(s['branch']) sequences.append(s['sequence']) p = s['retryPolicy'] if not (1 <= p['maxAttempts'] <= 10 and 1 <= p['maxSessions'] <= 100 and 0 <= p['backoffSeconds'] <= 3600): raise GateError('Retry/session budget out of bounds') checkpoints = set() if not s['checkpoints']: raise GateError('Stage has no checkpoints') for cp in s['checkpoints']: if cp['id'] in checkpoints or not re.fullmatch(r'[A-Za-z0-9-]+', cp['id']): raise GateError('Duplicate/invalid checkpoint') checkpoints.add(cp['id']) for field in ('instruction', 'approvalsBefore', 'approvalsAfter', 'deferrableGates', 'requiredArtifacts', 'checks', 'evidenceRequirements'): if field not in cp: raise GateError(f'Missing checkpoint field: {field}') check_ids = set() for check in cp['checks']: if check['id'] in check_ids or not check.get('argv'): raise GateError('Duplicate check or empty test command') check_ids.add(check['id']) if check.get('adapter') == 'assessment-evidence-inspection': if not m.get('executionScope') or not re.fullmatch(r'[0-9a-f]{64}',check.get('receiptSha256','')): raise GateError('Assessment adapter requires a restricted policy and pinned receipt') if check.get('configured') is not False: raise GateError('Evidence inspection cannot qualify the command-running composite adapter') if root and sha_file(under(root,check['receipt'])) != check['receiptSha256']: raise GateError('Assessment qualification receipt changed') if not all(isinstance(a, str) and '\x00' not in a for a in check['argv']): raise GateError('Command arguments must be strings') if check.get('report') and check['report']['format'] not in ('trx', 'jest', 'playwright', 'junit'): raise GateError('Unsupported structured test format') if root and s['sourceStatus'] == 'VERIFIED': path = under(root, s['sourcePrompt']) if not path.is_file() or not path.stat().st_size or sha_file(path) != s['promptSha256']: raise GateError(f'Prompt missing/changed: {s["id"]}') if root: for item in s.get('supplementalPrompts', []): path = under(root, item['path']) if not path.is_file() or sha_file(path) != item['sha256']: raise GateError('Supplemental prompt missing/changed') if sequences != sorted(set(sequences)): raise GateError('Queue sequence must be unique and ascending') scope = m.get('executionScope') if scope: stage = next((s for s in m['stages'] if s['id'] == scope.get('stage')), None) cp = next((x for x in stage['checkpoints'] if x['id'] == scope.get('checkpoint')), None) if stage else None if (scope.get('mode') != 'assessment-only' or scope.get('stage') != 'PHASE-10' or scope.get('checkpoint') != 'P10-00' or not cp or scope.get('reportDirectory') != 'docs/automation/stages/PHASE-10' or 'candidate-specific-cleanup-scope' not in cp['approvalsAfter']): raise GateError('Assessment policy requires an exact checkpoint and protected stop') if not re.fullmatch(r'[0-9a-f]{40}', scope.get('handoffCommit','')) or not SAFE_BRANCH.fullmatch(scope.get('handoffBranch','')): raise GateError('Invalid published handoff identity') class StateStore: def __init__(self, root): self.path = Path(root) / 'state.json' self.backup = Path(root) / 'state.last-good.json' def decode(self, path): envelope = read_json(path) if envelope['sha256'] != digest(envelope['state']): raise ValueError('state checksum mismatch') return envelope['state'] def read(self): try: return self.decode(self.path) except (OSError, ValueError, KeyError, TypeError): try: state = self.decode(self.backup) except (OSError, ValueError, KeyError, TypeError) as error: raise GateError('State and last-good copy unreadable; restore verified backup, never reinitialize') from error state['recoveryRequired'] = True return state def write(self, state): if self.path.exists(): try: previous = self.decode(self.path) atomic(self.backup, {'state': previous, 'sha256': digest(previous)}) except (OSError, ValueError, KeyError, TypeError): pass # keep the last verified backup, never copy corrupt bytes state['updatedUtc'] = utc() atomic(self.path, {'state': state, 'sha256': digest(state)}) if not self.backup.exists(): atomic(self.backup, {'state': state, 'sha256': digest(state)}) class RepoLock: """OS releases this lock on process death. Shared Git directory locks all worktrees.""" def __init__(self, repo): p = subprocess.run(['git', '-C', str(repo), 'rev-parse', '--path-format=absolute', '--git-common-dir'], capture_output=True, text=True) if p.returncode: raise GateError('Repository is not a Git checkout') self.path = Path(p.stdout.strip()) / 'jobjakt-orchestrator.lock' self.stream = None def __enter__(self): self.stream = open(self.path, 'a+b') self.stream.seek(0) self.stream.write(b'0') self.stream.flush() self.stream.seek(0) try: if os.name == 'nt': import msvcrt msvcrt.locking(self.stream.fileno(), msvcrt.LK_NBLCK, 1) else: import fcntl fcntl.flock(self.stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) except OSError as error: self.stream.close() raise GateError('Another orchestrator controls this repository') from error return self def __exit__(self, *args): if self.stream: self.stream.seek(0) if os.name == 'nt': import msvcrt msvcrt.locking(self.stream.fileno(), msvcrt.LK_UNLCK, 1) else: import fcntl fcntl.flock(self.stream.fileno(), fcntl.LOCK_UN) self.stream.close() def parse_report(path, spec): """Count assertions independently, require membership, and reject skips/failures.""" fmt = spec['format'] names, passed, failed, skipped = [], 0, 0, 0 if fmt == 'trx': tree = ET.parse(path) for node in tree.iter(): if node.tag.split('}')[-1] == 'UnitTestResult': names.append(node.attrib['testName']) outcome = node.attrib.get('outcome') passed += outcome == 'Passed' failed += outcome not in ('Passed', 'NotExecuted') skipped += outcome == 'NotExecuted' elif fmt == 'junit': for node in ET.parse(path).iter('testcase'): names.append(node.attrib.get('classname', '') + '.' + node.attrib['name']) bad = node.find('failure') is not None or node.find('error') is not None skip = node.find('skipped') is not None failed += bad skipped += skip passed += not (bad or skip) elif fmt == 'jest': data = read_json(path) if data.get('success') is not True: raise GateError('Jest success flag is false') for suite in data['testResults']: for case in suite['assertionResults']: names.append(case['fullName']) passed += case['status'] == 'passed' failed += case['status'] == 'failed' skipped += case['status'] not in ('passed', 'failed') else: data = read_json(path) if data.get('errors'): raise GateError('Playwright global errors') def visit(suites): nonlocal passed, failed, skipped for suite in suites: for test_spec in suite.get('specs', []): for case in test_spec['tests']: names.append(f'{test_spec.get("file", "")}:{test_spec["title"]}:{case["projectName"]}') results = case.get('results', []) ok = (len(results) == 1 and results[0]['status'] == 'passed' and case.get('expectedStatus', 'passed') == 'passed') passed += ok failed += not ok visit(suite.get('suites', [])) visit(data['suites']) if not names or passed < spec.get('minimumPassed', 1) or failed or skipped: raise GateError(f'Invalid/empty/skipped/failing report: passed={passed}, failed={failed}, skipped={skipped}') expected = spec.get('expectedNames', []) if expected and sorted(names) != sorted(expected): raise GateError('Expected test membership differs from report') if len(names) != len(set(names)): raise GateError('Duplicate test identities in report') return dict(passed=passed, failed=failed, skipped=skipped, membershipSha256=digest(sorted(names))) class WindowsJob: """A non-inherited kill-on-close Job handle bounds the complete child process tree.""" def __init__(self, config): import ctypes as ct from ctypes import wintypes as wt class Basic(ct.Structure): _fields_ = [('processTime', ct.c_longlong), ('jobTime', ct.c_longlong), ('flags', wt.DWORD), ('minimumWorkingSet', ct.c_size_t), ('maximumWorkingSet', ct.c_size_t), ('activeProcesses', wt.DWORD), ('affinity', ct.c_size_t), ('priority', wt.DWORD), ('scheduling', wt.DWORD)] class Io(ct.Structure): _fields_ = [(name, ct.c_ulonglong) for name in ('readOps', 'writeOps', 'otherOps', 'readBytes', 'writeBytes', 'otherBytes')] class Extended(ct.Structure): _fields_ = [('basic', Basic), ('io', Io), ('processMemory', ct.c_size_t), ('jobMemory', ct.c_size_t), ('peakProcessMemory', ct.c_size_t), ('peakJobMemory', ct.c_size_t)] self.api = ct.WinDLL('kernel32', use_last_error=True) self.api.CreateJobObjectW.argtypes = [ct.c_void_p, wt.LPCWSTR] self.api.CreateJobObjectW.restype = wt.HANDLE self.api.SetInformationJobObject.argtypes = [wt.HANDLE, ct.c_int, ct.c_void_p, wt.DWORD] self.api.SetInformationJobObject.restype = wt.BOOL self.api.AssignProcessToJobObject.argtypes = [wt.HANDLE, wt.HANDLE] self.api.AssignProcessToJobObject.restype = wt.BOOL self.api.CloseHandle.argtypes = [wt.HANDLE] self.api.CloseHandle.restype = wt.BOOL self.handle = self.api.CreateJobObjectW(None, None) if not self.handle: raise GateError('Cannot create Windows process containment job') limits = Extended() limits.basic.flags = 0x2000 | 0x8 | 0x200 # KILL_ON_JOB_CLOSE | ACTIVE_PROCESS | JOB_MEMORY limits.basic.activeProcesses = config.get('maxProcesses', 64) limits.jobMemory = config.get('maxJobMemoryMiB', 8192) * 1024 * 1024 if not self.api.SetInformationJobObject(self.handle, 9, ct.byref(limits), ct.sizeof(limits)): self.close() raise GateError('Cannot enforce Windows process/memory limits') def assign(self, process): if not self.api.AssignProcessToJobObject(self.handle, int(process._handle)): raise GateError('Cannot attach worker to Windows containment job; refusing uncontained execution') def resume(self, process): import ctypes as ct from ctypes import wintypes as wt class ThreadEntry(ct.Structure): _fields_ = [('size', wt.DWORD), ('usage', wt.DWORD), ('threadId', wt.DWORD), ('processId', wt.DWORD), ('basePriority', wt.LONG), ('deltaPriority', wt.LONG), ('flags', wt.DWORD)] self.api.CreateToolhelp32Snapshot.argtypes = [wt.DWORD, wt.DWORD] self.api.CreateToolhelp32Snapshot.restype = wt.HANDLE self.api.Thread32First.argtypes = [wt.HANDLE, ct.POINTER(ThreadEntry)] self.api.Thread32Next.argtypes = [wt.HANDLE, ct.POINTER(ThreadEntry)] self.api.OpenThread.argtypes = [wt.DWORD, wt.BOOL, wt.DWORD] self.api.OpenThread.restype = wt.HANDLE self.api.ResumeThread.argtypes = [wt.HANDLE] self.api.ResumeThread.restype = wt.DWORD snapshot = self.api.CreateToolhelp32Snapshot(0x4, 0) # TH32CS_SNAPTHREAD if snapshot in (None, ct.c_void_p(-1).value): raise GateError('Cannot enumerate suspended worker thread') entry = ThreadEntry() entry.size = ct.sizeof(entry) resumed = False try: valid = self.api.Thread32First(snapshot, ct.byref(entry)) while valid: if entry.processId == process.pid: thread = self.api.OpenThread(0x2, False, entry.threadId) if not thread: raise GateError('Cannot open suspended worker thread') try: if self.api.ResumeThread(thread) == 0xFFFFFFFF: raise GateError('Cannot resume contained worker') resumed = True finally: self.api.CloseHandle(thread) valid = self.api.Thread32Next(snapshot, ct.byref(entry)) finally: self.api.CloseHandle(snapshot) if not resumed: raise GateError('Suspended worker thread was not found') def close(self): if self.handle: self.api.CloseHandle(self.handle) self.handle = None class ProcessRunner: def __init__(self): self.child = None def codex_command(self, engine, *args): return [engine.config['codexExecutable'], *args] def capability_args(self, engine): if local_mode.single(engine.config) and engine.config.get('trustedCodexOverrides'): raise GateError('single-user-local plugin/MCP capabilities require separate qualification') result = [] for item in engine.config.get('trustedCodexOverrides', []): if not re.fullmatch(r'(?:mcp_servers|plugins)\.[A-Za-z0-9_.-]+', item['key']): raise GateError('Only separately qualified MCP/plugin capability overrides are permitted') result += ['-c', item['key'] + '=' + item['toml']] return result def permission_args(self, engine, command): if local_mode.single(engine.config): return local_mode.permission_args(engine.root,engine.config,command) if command == 'exec': return ['--sandbox','workspace-write','-c','approval_policy="never"', '-c','sandbox_workspace_write.network_access=false'] return ['-c','sandbox_mode="workspace-write"','-c','approval_policy="never"', '-c','sandbox_workspace_write.network_access=false'] def supervise(self, engine, argv, directory, seconds, stdin=None, cwd=None): directory.mkdir(parents=True, exist_ok=True) env = {k: v for k, v in os.environ.items() if k.upper() in { 'PATH', 'SYSTEMROOT', 'WINDIR', 'TEMP', 'TMP', 'USERPROFILE', 'LOCALAPPDATA', 'APPDATA', 'PROGRAMFILES', 'PROGRAMFILES(X86)', 'COMSPEC', 'PATHEXT', 'HOME','SYSTEMDRIVE'}} env['CODEX_HOME'] = engine.config['workerCodeXHome'] env['JOBBJAKT_ORCHESTRATED'] = '1' if local_mode.single(engine.config): env['TEMP'] = env['TMP'] = engine.config['workerTemp'] # Explicitly do not inherit production credentials, Docker/SSH sockets or API key variables. flags = (subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW | 0x4) if os.name == 'nt' else 0 started = time.monotonic() input_path = directory / 'stdin.txt' input_path.write_text(stdin or '', encoding='utf-8', newline='') marker = dict(pid=None, argv=argv, startedUtc=utc(), session=str(directory)) atomic(engine.root / 'active-process.json', marker) # durable launch intent before spawn process = None job = WindowsJob(engine.config) if os.name == 'nt' else None # Reader threads drain both pipes; logs are local, redacted, bounded, and never notifications. import threading log_error = [] def drain(stream, target): total, pending = 0, b'' with open(target, 'w', encoding='utf-8', newline='') as output: while chunk := stream.read1(8192): total += len(chunk) if total > engine.config['maxLogBytes']: if not log_error: log_error.append('log byte budget exceeded') continue pending += chunk if len(pending) > min(engine.config['maxLogBytes'], 1048576): if not log_error: log_error.append('log byte budget exceeded for a single line') pending = b'' continue while b'\n' in pending: line, pending = pending.split(b'\n', 1) output.write(redact(line.rstrip(b'\r').decode('utf-8', errors='replace')) + '\n') output.flush() if pending and not log_error: output.write(redact(pending.decode('utf-8', errors='replace'))) threads = [] try: with open(input_path, 'rb') as input_stream: process = subprocess.Popen(argv, cwd=cwd or engine.repo, env=env, stdin=input_stream, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=flags, start_new_session=os.name != 'nt') self.child = process marker['pid'] = process.pid atomic(engine.root / 'active-process.json', marker) if job: job.assign(process) job.resume(process) # no worker instruction can execute before containment threads = [threading.Thread(target=drain, args=(process.stdout, directory / 'stdout.log'), daemon=True), threading.Thread(target=drain, args=(process.stderr, directory / 'stderr.log'), daemon=True)] for thread in threads: thread.start() while process.poll() is None: if (engine.root / 'pause.request').exists(): raise Interrupted('Owner requested pause') if time.monotonic() - started > seconds or time.monotonic() > engine.deadline: raise Interrupted('Session/run time budget exceeded') if not engine.in_window(): raise Interrupted('Execution window closed') if log_error: raise GateError(log_error[0]) time.sleep(0.25) if process.returncode: raise GateError(f'Process exited {process.returncode}; see redacted local logs') finally: if job: job.close() # closes/kills all descendants, including on normal parent-child completion if process is not None and process.poll() is None: if os.name == 'nt': subprocess.run(['taskkill', '/PID', str(process.pid), '/T', '/F'], capture_output=True, timeout=20) else: os.killpg(process.pid, signal.SIGKILL) process.wait(timeout=20) for thread in threads: thread.join(timeout=10) if any(t.is_alive() for t in threads): raise GateError('Descendant still holds log handles; reconcile active-process marker') if process is not None: process.stdout.close() process.stderr.close() self.child = None (engine.root / 'active-process.json').unlink(missing_ok=True) if log_error: raise GateError(log_error[0]) def run_session(self, engine, stage, checkpoint, directory, prompt): if local_mode.single(engine.config): local_mode.require_unattended(engine.root,engine.config) result = directory / 'result.json' argv = self.codex_command(engine, 'exec', '--ignore-user-config', *self.permission_args(engine,'exec'), '-c', 'features.multi_agent=false', *self.capability_args(engine), '--json', '--color', 'never', '--output-schema', str(engine.root / 'schemas/result.schema.json'), '--output-last-message', str(result), '--cd', str(engine.repo), '-') # Output is written by the CLI parent, not a model shell; worker cannot write protected state. self.supervise(engine, argv, directory, engine.config['maxSessionSeconds'], stdin=prompt) events = [] for line in (directory / 'stdout.log').read_text(encoding='utf-8').splitlines(): if not line.strip(): continue try: events.append(json.loads(line)) except ValueError: raise GateError('CLI event stream is not valid JSONL') if (not any(e.get('type') == 'turn.completed' for e in events) or any(e.get('type') in ('turn.failed', 'error') for e in events)): raise GateError('CLI did not produce an unambiguous completed turn') thread_ids = [e['thread_id'] for e in events if e.get('type') == 'thread.started'] atomic(directory / 'session.json', dict(threadIds=thread_ids, finishedUtc=utc())) data = read_json(result) if (set(data) != {'outcome', 'summary', 'blockers', 'requestedApprovals', 'checkpoint'} or data['outcome'] not in OUTCOMES or data['checkpoint'] != checkpoint['id'] or not isinstance(data['summary'], str) or not isinstance(data['blockers'], list) or not isinstance(data['requestedApprovals'], list) or not all(isinstance(x, str) for x in data['blockers'] + data['requestedApprovals'])): raise GateError('Invalid structured handoff') return data def run_check(self, engine, check, directory): if check.get('adapter') == 'assessment-evidence-inspection': raise GateError('Evidence inspection cannot replace command-running composite qualification') if local_mode.single(engine.config): local_mode.require_unattended(engine.root,engine.config) working = engine.repo if check['cwd'] == '.' else under(engine.repo, check['cwd']) report_spec = check.get('report') report = None if report_spec: report = under(engine.repo, report_spec['path']) report.unlink(missing_ok=True) # old evidence cannot satisfy a new run argv = self.codex_command(engine, 'sandbox', '-C', str(working), *self.permission_args(engine,'sandbox'), *check['argv']) self.supervise(engine, argv, directory, engine.config['maxCheckSeconds'], cwd=working) result = dict(id=check['id'], exitCode=0, passed=None, failed=0, report=None) if report: if not report.is_file(): raise GateError(f'Required test report missing: {report_spec["path"]}') result.update(parse_report(report, report_spec)) # Preserve exact structured report separately; private local evidence, never notifications. shutil.copyfile(report, directory / report.name) result['report'] = {'path': str(directory / report.name), 'sha256': sha_file(report)} atomic(directory / 'verification.json', result) return result class Engine: def __init__(self, root, manifest, config, *, runner=None, approval_verifier=None, host_verifier=None): self.root = Path(root).resolve() self.manifest = manifest self.config = config if manifest.get('executionScope'): self.config = dict(config) self.config['assessmentReportDirectory'] = manifest['executionScope']['reportDirectory'] self.repo = Path(config['repository']).resolve() self.store = StateStore(self.root) self.runner = runner or ProcessRunner() self.approval_verifier = approval_verifier or self.verify_approval self.host_verifier = host_verifier or self.verify_host self.deadline = float('inf') def git(self, *args): p = subprocess.run([self.config.get('gitExecutable','git'), '-c', 'core.hooksPath=', '-c', 'credential.interactive=false', '-C', str(self.repo), *args], text=True, capture_output=True, encoding='utf-8', timeout=60, env={**os.environ, 'GIT_TERMINAL_PROMPT': '0'}) if p.returncode: raise GateError(redact(f'git {args[0]} failed: {p.stderr.strip()}')) return p.stdout.strip() def initialize(self): validate_manifest(self.manifest) if self.store.path.exists() or self.store.backup.exists(): raise GateError('State already exists; use resume, never overwrite progress') state = dict(schemaVersion=1, manifestSha256=digest(self.manifest), integrationCommit=self.manifest['baselineCommit'], paused=False, recoveryRequired=False, stages={}, recentSessions=[], latestFailure=None, ownerDecisions=[], productionRelease='NOT_AUTHORISED') for stage in self.manifest['stages']: state['stages'][stage['id']] = dict(status='PENDING', checkpoint=0, attempts=0, sessions=0, pendingPublication=None, latestCommit=None, evidence=[], reports=[], lastResult=None, entryBaseline=None, checkpointBase=None, checkpointCommit=None, awaiting=[], verifiedChecks=[], failures=[]) self.store.write(state) def request(self, stage, cp, kind, state, detail=None): ss = state['stages'][stage['id']] request = dict(schemaVersion=1, manifestSha256=digest(self.manifest), policySha256=self.policy_hash(), kind=kind, stage=stage['id'], checkpoint=cp['id'], baselineCommit=ss['entryBaseline'] or state['integrationCommit'], candidateCommit=ss['checkpointCommit'], evidence=ss['evidence'], detail=detail) if kind in ('environment-qualified', 'roadmap-execution'): request.update(stage='QUEUE', checkpoint='POLICY', baselineCommit=self.manifest['baselineCommit'], candidateCommit=None, evidence=[], detail='Entire exact manifest; development only; no production authority') if self.manifest.get('executionScope'): request['executionScope'] = self.manifest['executionScope'] path = self.root / 'requests' / (digest(request) + '.json') atomic(path, request) approval = self.approval_verifier(request) if not approval: ss['awaiting'] = [dict(kind=kind, request=str(path))] ss['status'] = 'AWAITING_APPROVAL' self.store.write(state) return False ss['awaiting'] = [] if isinstance(approval, dict): record = dict(requestSha256=digest(request), kind=kind, stage=stage['id'], checkpoint=cp['id'], decision=approval.get('decision', 'Approved as requested'), disposition=approval.get('disposition', 'Approve'), manifestSha256=digest(self.manifest)) if record not in state['ownerDecisions']: state['ownerDecisions'].append(record) self.store.write(state) if approval.get('disposition', 'Approve') != 'Approve': if approval.get('disposition') == 'Revise' and ss['checkpointCommit'] and kind in ( 'homepage-design-selection', 'worker-decision', 'stage-requirements-review'): ss['latestCommit'] = ss['checkpointCommit'] ss['checkpointCommit'] = None ss['pendingPublication'] = None ss['evidence'] = [] ss['verifiedChecks'] = [] ss['verificationPrepared'] = False ss['attempts'] = 0 ss['status'] = 'PENDING' else: ss['status'] = 'BLOCKED' ss['awaiting'] = [dict(kind=kind, request=str(path), decision='Owner rejected or requested changed scope')] self.store.write(state) return False return True def policy_hash(self): files = ['controller.py', 'local_mode.py', 'assessment_adapter.py', 'Probe-JobjaktSandbox.ps1', 'Approve-JobjaktBacklog.ps1', 'config.json', 'schemas/result.schema.json'] return digest({name: sha_file(self.root / name) for name in files if (self.root / name).exists()}) def verify_approval(self, request): path = self.root / 'approvals' / (digest(request) + '.json') if not path.is_file(): return False p = subprocess.run([self.config['powershellExecutable'], '-NoProfile', '-File', str(self.root / 'Approve-JobjaktBacklog.ps1'), '-Action', 'Verify', '-RequestPath', str(self.root / 'requests' / (digest(request) + '.json')), '-ApprovalPath', str(path), '-CertificatePath', str(self.root / self.config['publicKeyCertificate']), '-ExpectedOwnerSid', self.config['ownerSid']], capture_output=True, text=True, timeout=30) if p.returncode: return False envelope = read_json(path) return json.loads(base64.b64decode(envelope['payloadBase64'])) def verify_host(self): if self.root.is_relative_to(self.repo) or self.repo.is_relative_to(self.root): raise GateError('Control plane and worker checkout must be disjoint') if os.name != 'nt': raise GateError('Real execution currently qualifies Windows hosts only') if local_mode.single(self.config): _,sid = local_mode.detect_identity() local_mode.validate_identity(self.config,sid) local_mode.require_unattended(self.root,self.config) if self.manifest.get('executionScope') and not local_mode.single(self.config): raise GateError('Restricted assessment filesystem profile currently qualifies single-user-local only') certificate = under(self.root, self.config['publicKeyCertificate']) if not certificate.is_file() or sha_file(certificate) != self.config['ownerCertificateSha256']: raise GateError('Owner public certificate is missing or differs from protected configuration pin') for field in ('maxSessionSeconds', 'maxCheckSeconds', 'maxRunSeconds', 'maxLogBytes'): if not isinstance(self.config[field], int) or self.config[field] <= 0: raise GateError('Positive integer resource budgets required') identity = subprocess.run(['whoami', '/user', '/fo', 'csv', '/nh'], capture_output=True, text=True, timeout=15).stdout if not local_mode.single(self.config) and (self.config['runnerSid'] not in identity or self.config['ownerSid'] in identity or not re.fullmatch(r'S-1-[0-9-]+', self.config['runnerSid'])): raise GateError('Run under the configured dedicated runner identity, separate from signing owner') version = subprocess.run([self.config['codexExecutable'], '--version'], capture_output=True, text=True, timeout=15) found = re.search(r'(\d+)\.(\d+)\.(\d+)', version.stdout) if not found or tuple(map(int, found.groups())) < (0, 163, 0): raise GateError('Codex CLI 0.163.0 or later required; qualify new versions before deployment') worker_home = Path(self.config['workerCodeXHome']).resolve() if worker_home.is_relative_to(self.repo): raise GateError('CODEX_HOME must be outside worker workspace') # Capability settings are a protected, signed policy input, not worker-controlled config. ProcessRunner().capability_args(self) # Refuse user/plugin configuration and repo MCP endpoints. A dedicated auth-only home is required. if (worker_home / 'config.toml').exists() or (worker_home / 'plugins').exists(): raise GateError('Use a dedicated auth-only CODEX_HOME; do not inherit MCP/plugins/config') for name in ('.mcp.json', '.codex/config.toml'): if (self.repo / name).exists(): raise GateError('Repository MCP/Codex config needs removal from isolated runner clone') if local_mode.single(self.config): report = local_mode.qualify(self) if report['status'] != 'PASS' or report['ownerKeyDenied'] is not True: raise GateError('Native MXC/key qualification failed; operator-controlled pause required') else: self.probe_sandbox() def probe_sandbox(self): # A real negative write probe, with a harmless disposable sentinel in the protected root. sentinel = self.root / ('sandbox-deny-' + uuid.uuid4().hex + '.txt') script = ('try { [IO.File]::WriteAllText(' + "'" + str(sentinel).replace("'", "''") + "','unexpected'); exit 9 } catch { exit 0 }") argv = [self.config['codexExecutable'], 'sandbox', '-C', str(self.repo), '-c', 'sandbox_mode="workspace-write"', '-c', 'approval_policy="never"', '-c', 'sandbox_workspace_write.network_access=false', self.config['powershellExecutable'], '-NoProfile', '-Command', script] probe = self.root / 'probe' / uuid.uuid4().hex try: self.runner.supervise(self, argv, probe, 30) if sentinel.exists(): raise GateError('Sandbox wrote to protected control plane') finally: sentinel.unlink(missing_ok=True) def verify_remote(self, branch, commit): try: lines = self.git('ls-remote', '--exit-code', self.manifest['remote'], 'refs/heads/' + branch) if lines.split()[0] != commit: raise PublicationError(f'Remote HEAD differs: {branch}') except (GateError, IndexError) as error: raise PublicationError(f'Publication not verified for {branch}: {error}') from error def publish(self, branch, commit): if not SAFE_BRANCH.fullmatch(branch): raise PublicationError('Refusing publication to protected branch') try: self.git('push', self.manifest['remote'], f'{commit}:refs/heads/{branch}') self.verify_remote(branch, commit) except GateError as error: raise PublicationError(str(error)) from error def event(self, kind, stage, message): path = self.root / 'events.jsonl' # Events contain IDs and bounded redacted status only, never prompts or documents. with open(path, 'a', encoding='utf-8') as stream: stream.write(json.dumps(dict(at=utc(), event=kind, stage=stage, message=redact(message)[:600])) + '\n') def pause(self): atomic(self.root / 'pause.request', dict(at=utc())) def resume(self): with RepoLock(self.repo): state = self.store.read() if state.get('recoveryRequired'): raise GateError('Recovered backup requires signed state-recovery approval; run recovery command') (self.root / 'pause.request').unlink(missing_ok=True) state['paused'] = False self.store.write(state) def retry(self, stage_id): with RepoLock(self.repo): state = self.store.read() stage = next(s for s in self.manifest['stages'] if s['id'] == stage_id) ss = state['stages'][stage_id] if ss['status'] != 'FAILED': raise GateError('Only FAILED stages can be retried; resume handles blocked publication/approvals') if ss['attempts'] >= stage['retryPolicy']['maxAttempts']: # Budget resets are never implicit. Owner signs an exact reset request through run(). ss['status'] = 'AWAITING_APPROVAL' ss['budgetResetRequested'] = True else: ss['status'] = 'PENDING' self.store.write(state) def in_window(self): windows = self.config.get('executionWindows', []) if not windows: return True now = datetime.now().strftime('%H:%M') return any((w['start'] <= now < w['end']) if w['start'] < w['end'] else (now >= w['start'] or now < w['end']) for w in windows) def prepare_branch(self, stage, ss, state): current = self.git('branch', '--show-current') if current in ('main', 'master', 'production', ''): raise GateError('Checkout of main/master/production or detached HEAD is prohibited') if ss['entryBaseline'] is None: if self.git('status', '--porcelain', '--untracked-files=all'): raise GateError('New stage requires a clean worktree, including untracked files') scope = self.manifest.get('executionScope') if scope and not any(x['status'] == 'COMPLETED' for x in state['stages'].values()): self.verify_remote(scope['handoffBranch'], scope['handoffCommit']) self.git('merge-base', '--is-ancestor', state['integrationCommit'], scope['handoffCommit']) changed = self.git('diff','--name-only',state['integrationCommit'],scope['handoffCommit']).splitlines() if any(not name.startswith('docs/automation/handoffs/pre-phase10/') for name in changed): raise GateError('Published handoff changed outside the evidence envelope') else: self.verify_remote(self.manifest['baselineBranch'] if not any( x['status'] == 'COMPLETED' for x in state['stages'].values()) else self.manifest['integrationBranch'], state['integrationCommit']) baseline = state['integrationCommit'] branches = self.git('for-each-ref', '--format=%(refname:short)', 'refs/heads').splitlines() if stage['branch'] in branches: if self.git('rev-parse', stage['branch']) != baseline: raise GateError('Existing stage branch is not exactly the accepted baseline') self.git('switch', stage['branch']) else: self.git('switch', '-c', stage['branch'], baseline) ss['entryBaseline'] = baseline self.store.write(state) elif current != stage['branch']: raise GateError('Unexpected branch after interruption; reconcile manually') self.git('merge-base', '--is-ancestor', ss['entryBaseline'], 'HEAD') expected = ss['checkpointCommit'] or ss['latestCommit'] or ss['entryBaseline'] if self.git('rev-parse', 'HEAD') != expected: # Crash between commit and state save: exact controller trailer permits reconciliation. trailer = f'Orchestrator-Checkpoint: {ss.get("transaction", "missing")}' body = self.git('log', '-1', '--format=%B') parent = self.git('rev-parse', 'HEAD^') if (trailer not in body or parent != expected or not ss.get('verificationPrepared') or self.content_hash() != ss.get('testedContentSha256')): raise GateError('Unexpected HEAD; refusing stale evidence or unrelated commit') self.verify_evidence(ss) ss['checkpointCommit'] = self.git('rev-parse', 'HEAD') ss['pendingPublication'] = ss['checkpointCommit'] self.store.write(state) def prompt(self, stage, cp, ss, state): source = under(self.root, stage['sourcePrompt']) if stage['sourceStatus'] != 'VERIFIED' or not source.is_file() or not source.stat().st_size: raise GateError(f'Missing complete authoritative prompt for {stage["id"]}') if sha_file(source) != stage['promptSha256']: raise GateError('Prompt hash changed; owner must install/re-authorize a new manifest') chunks = [source.read_text(encoding='utf-8-sig')] for item in stage.get('supplementalPrompts', []): path = under(self.root, item['path']) if sha_file(path) != item['sha256']: raise GateError('Supplemental prompt hash changed') if item.get('checkpoints') is not None and cp['id'] not in item['checkpoints']: continue chunks.append(f'\nCOMPLETE SUPPLEMENTAL INSTRUCTIONS {item["path"]}\n' + path.read_text(encoding='utf-8-sig')) for name in stage['contextFiles']: path = under(self.repo, name) if not path.is_file(): raise GateError('Required prior context missing: ' + name) chunks.append(f'\nPRIOR AUTHORITY {name}\n' + path.read_text(encoding='utf-8-sig')) for dependency in stage['dependencies']: prior = state['stages'][dependency] if prior['reports']: receipt = prior['reports'][-1] report_path = Path(receipt['path']) if sha_file(report_path) != receipt['sha256']: raise GateError('Prior acceptance report changed') chunks.append(f'\nFULL ACCEPTED DEPENDENCY REPORT {dependency}\n' + report_path.read_text(encoding='utf-8')) for evidence in prior['evidence']: path = under(self.repo, evidence['path']) if sha_file(path) != evidence['sha256']: raise GateError('Prior accepted artifact changed') if path.suffix.lower() in ('.md', '.txt', '.json'): chunks.append(f'\nFULL PRIOR ACCEPTANCE ARTIFACT {evidence["path"]}\n' + path.read_text(encoding='utf-8-sig')) chunks.append('\nPRIOR CONTROLLER REPORTS\n' + json.dumps(state['recentSessions'][-12:], ensure_ascii=False)) chunks.append('\nVERIFIED OWNER DECISIONS\n' + json.dumps(state['ownerDecisions'], ensure_ascii=False)) chunks.append('\nACTIVE CHECKPOINT ONLY\n' + json.dumps(dict(stage=stage['id'], checkpoint=cp, integrationCommit=state['integrationCommit'], lastResult=ss['lastResult'], priorEvidence=ss['evidence'], failures=ss['failures'][-5:], remainingAttempts=stage['retryPolicy']['maxAttempts'] - ss['attempts']), ensure_ascii=False)) chunks.append('''\nCONTROL CONTRACT: Work only on this checkpoint and current branch. Read repository AGENTS.md. Do not start another checkpoint, another Codex process, or another orchestrator. Do not commit, push, switch branches, merge, deploy, change approval/control files, access production services, enable models, accept security risks, buy services or remove failing tests. The controller owns Git publication and independent verification. Report blockers honestly. Preserve ALL source prompt requirements; where a prompt stops pending owner approval, return outcome approval. A signed checkpoint entry grant permits only its named implementation scope; it never approves production. Return the required structured JSON. outcome checkpoint means more work is needed on this same checkpoint; complete means ready for independent checks. Do not infer acceptance from a test count. ''') return '\n'.join(chunks) def verify_evidence(self, ss): for artifact in ss['evidence']: path = under(self.repo, artifact['path']) if not path.is_file() or sha_file(path) != artifact['sha256']: raise GateError('Checkpoint evidence changed since verification') for check in ss['verifiedChecks']: if check.get('report'): report = Path(check['report']['path']) if not report.is_file() or sha_file(report) != check['report']['sha256']: raise GateError('Trusted test report missing or changed') def checkpoint(self, stage, cp, ss, state): # Recovery after commit/push uses stored evidence, never another model session. if ss['pendingPublication']: self.verify_evidence(ss) self.publish(stage['branch'], ss['pendingPublication']) ss['latestCommit'] = ss['pendingPublication'] ss['pendingPublication'] = None self.store.write(state) return self.finish_checkpoint(stage, cp, ss, state) if ss['checkpointCommit']: return self.finish_checkpoint(stage, cp, ss, state) if ss['attempts'] >= stage['retryPolicy']['maxAttempts']: if not ss.get('budgetResetRequested'): ss['status'] = 'FAILED' self.store.write(state) return False if not self.request(stage, cp, 'retry-budget-reset', state, ss['failures']): return False ss['attempts'] = 0 ss['budgetResetRequested'] = False if ss['sessions'] >= stage['retryPolicy']['maxSessions']: raise GateError('Total stage session limit reached; owner must revise policy') for kind in cp['approvalsBefore']: if not self.request(stage, cp, kind, state): return False if ss.get('requestedDecision'): if not self.request(stage, cp, 'worker-decision', state, ss['requestedDecision']): return False ss.pop('requestedDecision') prompt = self.prompt(stage, cp, ss, state) if not cp['checks'] or any(check.get('adapter') == 'assessment-evidence-inspection' or not check.get('configured', True) for check in cp['checks']): raise GateError('Independent test adapters must be configured and owner-qualified before execution') ss['attempts'] += 1 ss['sessions'] += 1 ss['status'] = 'RUNNING' ss['checkpointBase'] = self.git('rev-parse', 'HEAD') session_id = uuid.uuid4().hex ss['transaction'] = session_id directory = self.root / 'sessions' / stage['id'] / cp['id'] / session_id directory.mkdir(parents=True) atomic(directory / 'input.json', dict(prompt=prompt, promptSha256=stage['promptSha256'], sourceCommit=ss['checkpointBase'], manifestSha256=digest(self.manifest), at=utc())) self.store.write(state) # durable RUNNING before launching child result = self.runner.run_session(self, stage, cp, directory, prompt) self.verify_assessment_writes() ss['lastResult'] = result state['recentSessions'].append(dict(id=session_id, stage=stage['id'], checkpoint=cp['id'], outcome=result['outcome'], summary=redact(result['summary'])[:600], directory=str(directory))) self.store.write(state) if self.git('rev-parse', 'HEAD') != ss['checkpointBase'] or self.git('branch', '--show-current') != stage['branch']: raise GateError('Worker performed forbidden Git operation') if result['outcome'] == 'blocked': ss['requestedDecision'] = result ss['status'] = 'BLOCKED' self.store.write(state) return False if result['outcome'] == 'failed': raise GateError('Worker reports failure: ' + redact(result['summary'])) if result['outcome'] == 'conditional' and not cp['deferrableGates']: ss['status'] = 'CONDITIONALLY_ACCEPTED' ss['requestedDecision'] = result self.store.write(state) return False if result['outcome'] == 'checkpoint': ss['status'] = 'PENDING' ss['attempts'] -= 1 # bounded by total sessions, not an ordinary failure self.store.write(state) return True ss['verifiedChecks'] = [] for check in cp['checks']: ss['verifiedChecks'].append(self.runner.run_check(self, check, directory / 'checks' / check['id'])) self.verify_assessment_writes() ss['evidence'] = [] for name in cp['requiredArtifacts']: artifact = under(self.repo, name) if not artifact.is_file() or artifact.stat().st_size == 0: raise GateError('Required nonempty artifact missing: ' + name) ss['evidence'].append(dict(path=name, sha256=sha_file(artifact))) if result['outcome'] == 'complete' and (result['blockers'] or result['requestedApprovals']): raise GateError('Complete result still contains blockers/approval requests') # Pin tested content before Git hooks or unrelated processes can alter it. before = self.content_hash() ss['testedContentSha256'] = before ss['verificationPrepared'] = True self.store.write(state) # crash after commit can reconcile only these independently verified bytes self.git('add', '--all') if self.content_hash() != before: raise GateError('Worktree changed during verification/commit') if self.git('diff', '--cached', '--name-only'): self.git('commit', '-m', f'{stage["id"]}: {cp["id"]} validated checkpoint\n\nOrchestrator-Checkpoint: {session_id}') ss['checkpointCommit'] = self.git('rev-parse', 'HEAD') ss['pendingPublication'] = ss['checkpointCommit'] ss['testedContentSha256'] = before ss['status'] = 'ACCEPTED' report = dict(stage=stage['id'], checkpoint=cp['id'], candidateCommit=ss['checkpointCommit'], promptSha256=stage['promptSha256'], checks=ss['verifiedChecks'], evidence=ss['evidence'], result=result, productionRelease='NOT_AUTHORISED') report_path = self.root / 'reports' / (session_id + '.json') atomic(report_path, report) ss['reports'].append(dict(path=str(report_path), sha256=sha_file(report_path))) self.store.write(state) self.publish(stage['branch'], ss['pendingPublication']) ss['latestCommit'] = ss['pendingPublication'] ss['pendingPublication'] = None self.store.write(state) return self.finish_checkpoint(stage, cp, ss, state) def verify_assessment_writes(self): scope = self.manifest.get('executionScope') if not scope: return prefix = scope['reportDirectory'] + '/' changed = self.git('diff', '--name-only', 'HEAD', '-z').split('\0') changed += self.git('ls-files', '--others', '--exclude-standard', '-z').split('\0') deleted = self.git('diff', '--name-only', '--diff-filter=D', 'HEAD') if deleted or any(name and (not name.startswith(prefix) or not name.endswith('.md')) for name in changed): raise GateError('Read-only assessment modified a forbidden path or deleted a file') def content_hash(self): # Git blob hashes include tracked modifications and untracked source without reading secrets into logs. files = self.git('ls-files', '--cached', '--others', '--exclude-standard', '-z').split('\0') return digest({name: sha_file(under(self.repo, name)) for name in sorted(set(files)) if name and under(self.repo, name).is_file()}) def finish_checkpoint(self, stage, cp, ss, state): self.verify_evidence(ss) if ss.get('testedContentSha256') and self.content_hash() != ss['testedContentSha256']: raise GateError('Tested source changed before acceptance') self.verify_remote(stage['branch'], ss['checkpointCommit']) kinds = list(cp['approvalsAfter']) if ss['lastResult']['outcome'] == 'approval': kinds.append('worker-decision') if ss['lastResult']['outcome'] == 'conditional': kinds.append('conditional-deferral') for kind in kinds: detail = dict(requirements=cp['evidenceRequirements'], workerResult=ss['lastResult'], deferrableGates=cp['deferrableGates'] if kind == 'conditional-deferral' else []) if not self.request(stage, cp, kind, state, detail): return False ss['checkpoint'] += 1 ss['attempts'] = 0 ss['checkpointCommit'] = None ss['checkpointBase'] = None ss['verificationPrepared'] = False ss['lastVerifiedChecks'] = ss['verifiedChecks'] ss['verifiedChecks'] = [] ss['status'] = 'PENDING' self.store.write(state) if ss['checkpoint'] == len(stage['checkpoints']): self.integrate_stage(stage, ss, state) return True def integrate_stage(self, stage, ss, state): commit = ss['latestCommit'] integration = self.manifest['integrationBranch'] if self.git('rev-parse', 'HEAD') != commit or self.git('status', '--porcelain', '--untracked-files=all'): raise GateError('Integration requires the clean, verified stage commit') self.verify_remote(stage['branch'], commit) self.git('merge-base', '--is-ancestor', state['integrationCommit'], commit) existing = self.git('for-each-ref', '--format=%(objectname)', 'refs/heads/' + integration) if existing and existing not in (state['integrationCommit'], commit): raise GateError('Integration branch diverged; no automatic merge/rebase') self.git('update-ref', 'refs/heads/' + integration, commit, existing or '0'*40) self.publish(integration, commit) state['integrationCommit'] = commit ss['status'] = 'COMPLETED' self.event('stage-completed', stage['id'], 'Development accepted and published; production remains unauthorised') self.store.write(state) def run(self): self.deadline = time.monotonic() + self.config['maxRunSeconds'] with RepoLock(self.repo): state = self.store.read() if state['manifestSha256'] != digest(self.manifest): raise GateError('Manifest changed since initialization; owner-controlled migration required') if not self.manifest['executionEnabled']: state['latestFailure'] = 'Execution disabled: initial orchestrator-building task only' self.store.write(state) return if (self.root / 'pause.request').exists() or state['paused']: state['paused'] = True self.store.write(state) return if state.get('recoveryRequired') or (self.root / 'active-process.json').exists(): raise GateError('Recovery/orphan reconciliation required; no model launched') if any(s['status'] == 'RUNNING' for s in state['stages'].values()): state['recoveryRequired'] = True self.store.write(state) raise GateError('Interrupted RUNNING state requires signed orphan reconciliation, even without a PID marker') self.host_verifier() for stage in self.manifest['stages']: ss = state['stages'][stage['id']] if ss['status'] == 'COMPLETED': continue if not all(state['stages'][dep]['status'] == 'COMPLETED' for dep in stage['dependencies']): ss['status'] = 'BLOCKED' state['latestFailure'] = 'Dependencies are not accepted and published' self.store.write(state) break while ss['checkpoint'] < len(stage['checkpoints']): cp = stage['checkpoints'][ss['checkpoint']] if (self.root / 'pause.request').exists() or time.monotonic() > self.deadline or not self.in_window(): state['paused'] = (self.root / 'pause.request').exists() self.store.write(state) return try: scope = self.manifest.get('executionScope') if scope and (stage['id'], cp['id']) != (scope['stage'], scope['checkpoint']): raise GateError('Restricted assessment scope ends after P10-00; new owner-scoped policy required') # Check source before requesting approval or modifying Git. self.prompt(stage, cp, ss, state) if ss['entryBaseline'] is None: for kind in ['environment-qualified', 'roadmap-execution', *stage['requiredOwnerApprovals']]: if kind == 'product-ranked-scope' and any(d['kind'] == kind and d.get('disposition') == 'Approve' and d.get('manifestSha256') == digest(self.manifest) for d in state['ownerDecisions']): continue if not self.request(stage, cp, kind, state): return self.prepare_branch(stage, ss, state) if not self.checkpoint(stage, cp, ss, state): return except Interrupted as error: ss['status'] = 'BLOCKED' state['latestFailure'] = str(error) self.store.write(state) self.event('interrupted', stage['id'], str(error)) return except PublicationError as error: ss['status'] = 'BLOCKED' state['latestFailure'] = str(error) self.store.write(state) self.event('publication-blocked', stage['id'], str(error)) return except (GateError, OSError, ValueError, KeyError, subprocess.SubprocessError) as error: message = redact(str(error)) state['latestFailure'] = message ss['failures'].append(dict(at=utc(), checkpoint=cp['id'], message=message)) if (self.root / 'active-process.json').exists(): ss['status'] = 'BLOCKED' self.store.write(state) return # failed cleanup can never launch an overlapping remediation worker # Failures after acceptance are policy/evidence failures, never remediation input. if ss['attempts'] and not ss['checkpointCommit'] and ss['status'] == 'RUNNING': ss['status'] = 'FAILED' self.store.write(state) if ss['attempts'] < stage['retryPolicy']['maxAttempts']: wait = stage['retryPolicy']['backoffSeconds'] until = time.monotonic() + wait while time.monotonic() < until: if (self.root / 'pause.request').exists() or time.monotonic() > self.deadline: return time.sleep(0.25) continue else: ss['status'] = 'BLOCKED' self.store.write(state) self.event('blocked', stage['id'], message) return # Resume after a crash while publishing the integration ref. if ss['checkpoint'] == len(stage['checkpoints']) and ss['status'] != 'COMPLETED': try: self.integrate_stage(stage, ss, state) except GateError as error: ss['status'] = 'BLOCKED' state['latestFailure'] = str(error) self.store.write(state) return if all(s['status'] == 'COMPLETED' for s in state['stages'].values()): self.event('roadmap-development-completed', None, 'Awaiting independent production release approval; no deploy action exists') def status(engine): state = engine.store.read() stages = [] for stage in engine.manifest['stages']: ss = state['stages'][stage['id']] checks = ss['verifiedChecks'] or ss.get('lastVerifiedChecks', []) stages.append(dict(id=stage['id'], status=ss['status'], checkpoint=stage['checkpoints'][ss['checkpoint']]['id'] if ss['checkpoint'] < len(stage['checkpoints']) else 'done', branch=stage['branch'], latestVerifiedCommit=ss['latestCommit'], attempts=ss['attempts'], sessions=ss['sessions'], requiredOwnerAction=ss['awaiting'], testsPassed=sum(x.get('passed') or 0 for x in checks), testsFailed=sum(x.get('failed') or 0 for x in checks), testState='VERIFIED' if checks else 'NOT_RUN', reports=ss['reports'])) return dict(mode=engine.config.get('mode','dedicated-runner'), executionPolicy=engine.config.get('executionPolicy','dedicated-runner'), paused=state['paused'] or (engine.root / 'pause.request').exists(), overall='COMPLETED_DEVELOPMENT' if all(s['status'] == 'COMPLETED' for s in stages) else 'GATED', integrationCommit=state['integrationCommit'], currentGitBranch=engine.git('branch', '--show-current'), productionRelease='NOT_AUTHORISED', stages=stages, latestFailure=state['latestFailure'], recentSessions=state['recentSessions'][-10:], recoveryRequired=state['recoveryRequired']) def install(source, root, repository, mode='dedicated-runner'): root = root.resolve() repo = Path(repository).resolve() if root.exists(): raise GateError('ControlRoot must be a new directory; do not overwrite trusted state') if root.is_relative_to(repo) or repo.is_relative_to(root): raise GateError('Install control plane outside worker checkout') root.mkdir(parents=True) for name in ('controller.py', 'local_mode.py', 'assessment_adapter.py', 'backlog.json', 'config.example.json'): shutil.copyfile(source / name, root / name) for name in ('schemas', 'prompts'): shutil.copytree(source / name, root / name) shutil.copyfile(source.parent / 'scripts/Approve-JobjaktBacklog.ps1', root / 'Approve-JobjaktBacklog.ps1') shutil.copyfile(source.parent / 'scripts/Run-JobjaktBacklog.ps1', root / 'Run-JobjaktBacklog.ps1') for name in ('Probe-JobjaktSandbox.ps1','Use-JobjaktBacklog.ps1','Register-JobjaktBacklogTask.ps1'): shutil.copyfile(source.parent/'scripts'/name,root/name) config = read_json(root / 'config.example.json') config['repository'] = str(repo) if mode == 'single-user-local': config = local_mode.configure_current_user(root,config) atomic(root / 'config.json', config) manifest = read_json(root / 'backlog.json') Engine(root, manifest, config).initialize() def main(argv=None): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('command', choices=['init', 'validate', 'dry-run', 'start', 'resume', 'status', 'pause', 'retry', 'current', 'logs', 'recover', 'configure', 'setup','auth','auth-status','qualify','approve','prepare','prepare-entry','key','enable-unattended']) parser.add_argument('--control-root', type=Path, required=True) parser.add_argument('--repository') parser.add_argument('--stage') parser.add_argument('--manifest', type=Path) args = parser.parse_args(argv) root = args.control_root.resolve() if args.command in ('init','setup'): if not args.repository: parser.error('init requires --repository (isolated development clone/worktree)') if args.command == 'setup' and (root/'config.json').exists(): existing = read_json(root/'config.json') state = StateStore(root).read() source = Path(__file__).resolve().parent if (local_mode.single(existing) and source != root and not (root/'active-process.json').exists() and not read_json(root/'backlog.json')['executionEnabled'] and not any(s['sessions'] for s in state['stages'].values())): with RepoLock(Path(existing['repository'])): for name in ('controller.py','local_mode.py','assessment_adapter.py'): shutil.copyfile(source/name,root/name) for name in ('Run-JobjaktBacklog.ps1','Approve-JobjaktBacklog.ps1','Probe-JobjaktSandbox.ps1', 'Use-JobjaktBacklog.ps1','Register-JobjaktBacklogTask.ps1'): shutil.copyfile(source.parent/'scripts'/name,root/name) print(json.dumps(dict(existingInstallation=str(root),mode=existing.get('mode','dedicated-runner'), note='Disabled unused single-user installation may refresh code; state/auth/policy preserved; requalify after code changes'),indent=2)) return 0 install(Path(__file__).resolve().parent, root, args.repository, mode='single-user-local' if args.command == 'setup' else 'dedicated-runner') print('Initialized disabled queue. Review protected config, identities, source gaps and gates before authorization.') return 0 engine = Engine(root, read_json(root / 'backlog.json'), read_json(root / 'config.json')) if args.command in ('key','enable-unattended'): if not local_mode.single(engine.config) or not sys.stdin.isatty() or os.environ.get('JOBBJAKT_ORCHESTRATED'): raise GateError('Run this single-user operator action yourself from an interactive PowerShell terminal') with RepoLock(engine.repo): state=engine.store.read() if (root/'active-process.json').exists() or state['recoveryRequired'] or any(s['status']=='RUNNING' for s in state['stages'].values()): raise GateError('Stop/reconcile workers before changing signing or execution settings') _,sid=local_mode.detect_identity() local_mode.validate_identity(engine.config,sid) if args.command == 'key': if engine.config.get('ownerCertificateThumbprint'): print('Owner certificate already configured; it will not be replaced automatically') return 0 created=subprocess.run([engine.config['powershellExecutable'],'-NoProfile','-File', str(root/'Approve-JobjaktBacklog.ps1'),'-Action','NewKey'],capture_output=True,text=True,check=True) metadata=json.loads(created.stdout) if metadata['OwnerSid'] != sid: raise GateError('Signing certificate was created under an unexpected identity') certificate=root/'owner.cer' subprocess.run([engine.config['powershellExecutable'],'-NoProfile','-File',str(root/'Approve-JobjaktBacklog.ps1'), '-Action','ExportPublicKey','-Thumbprint',metadata['Thumbprint'],'-CertificatePath',str(certificate)],check=True) engine.config.update(ownerCertificateThumbprint=metadata['Thumbprint'],publicKeyCertificate='owner.cer', ownerCertificateSha256=sha_file(certificate)) atomic(root/'config.json',engine.config) print('Nonexportable owner key configured under your existing login. Run qualify to test that exact key. No phase authorized.') else: proposed={**engine.config,'executionPolicy':'qualified-unattended'} local_mode.require_unattended(root,proposed) if input('Type ENABLE to allow qualified unattended workers (roadmap approval still separate): ').strip() != 'ENABLE': raise GateError('Unattended selection cancelled') engine.config.update(executionPolicy='qualified-unattended') atomic(root/'config.json',engine.config) print('Qualified unattended policy selected. Queue activation and phase entry grants are still required.') elif args.command in ('auth','auth-status'): if not local_mode.single(engine.config): raise GateError('Use the single-user-local setup for the managed auth-only profile') local_mode.validate_config(root,engine.config) if args.command == 'auth' and (not sys.stdin.isatty() or os.environ.get('JOBBJAKT_ORCHESTRATED')): raise GateError('Run auth yourself from a PowerShell terminal; no token copying or background login') env = {**os.environ,'CODEX_HOME':engine.config['workerCodeXHome']} result = subprocess.run(local_mode.auth_command(engine.config,args.command == 'auth-status'),env=env, capture_output=args.command == 'auth-status',text=True) if args.command == 'auth-status': kind = 'CHATGPT_SIGNED_IN' if result.returncode == 0 and 'chatgpt' in (result.stdout+result.stderr).lower() else 'NOT_SIGNED_IN_WITH_CHATGPT' print(json.dumps(dict(authentication=kind,profile=engine.config['workerCodeXHome'], sameAccount='Sign in with your existing ChatGPT account; identity not inferred or copied'))) return result.returncode elif args.command == 'qualify': if not local_mode.single(engine.config): raise GateError('qualify is for single-user-local; legacy preflight is unchanged') with RepoLock(engine.repo): state = engine.store.read() if (root/'active-process.json').exists() or any(s['status']=='RUNNING' for s in state['stages'].values()): raise GateError('Stop/reconcile all workers before qualification') report = local_mode.qualify(engine) print(json.dumps(report,indent=2)) return 0 if report['status']=='PASS' else 3 elif args.command == 'approve': if not sys.stdin.isatty() or os.environ.get('JOBBJAKT_ORCHESTRATED'): raise GateError('Human approval requires your interactive terminal, never an autonomous subprocess') with RepoLock(engine.repo): state = engine.store.read() pending = next((ss['awaiting'][0] for ss in state['stages'].values() if ss['awaiting']),None) if not pending: raise GateError('No pending owner request') request = Path(pending['request']) recovery = (pending['kind']=='recovery-reconciled' and read_json(request).get('kind')=='recovery-reconciled') if (state['recoveryRequired'] or any(s['status']=='RUNNING' for s in state['stages'].values())) and not recovery: raise GateError('Only the exact recovery request may be approved during reconciliation') if (root/'active-process.json').exists(): if not recovery or recorded_process_exists(read_json(root/'active-process.json')): raise GateError('Stop and verify the recorded process tree before approval') _,sid = local_mode.detect_identity() if sid != engine.config['ownerSid']: raise GateError('Wrong owner identity') print(request.read_text(encoding='utf-8')) if recovery and input('Type RECONCILED after inspecting that the recorded worker and all descendants are gone: ').strip() != 'RECONCILED': raise GateError('Operator reconciliation not confirmed') challenge = uuid.uuid4().hex[:8] if input('Type '+challenge+' to review/sign this exact request: ').strip() != challenge: raise GateError('Approval cancelled') disposition = input('Disposition (Approve/Revise/Reject): ').strip() if disposition not in ('Approve','Revise','Reject'): raise GateError('Invalid disposition') decision = input('Your scope, design selection or revision: ').strip() if not decision: raise GateError('Concrete owner decision required') thumb = engine.config.get('ownerCertificateThumbprint') if not thumb: raise GateError('Configure your owner signing certificate first; no writable-JSON approval fallback exists') result = subprocess.run([engine.config['powershellExecutable'],'-NoProfile','-File', str(root/'Approve-JobjaktBacklog.ps1'),'-Action','Sign','-RequestPath',str(request), '-ApprovalPath',str(root/'approvals'/request.name),'-Thumbprint',thumb,'-ExpectedOwnerSid',sid, '-Disposition',disposition,'-Decision',decision]) return result.returncode elif args.command == 'prepare-entry': with RepoLock(engine.repo): validate_manifest(engine.manifest,root) state=engine.store.read() scope=engine.manifest.get('executionScope') if not scope or not engine.manifest['executionEnabled']: raise GateError('Adopt the signed restricted assessment policy first') if (root/'active-process.json').exists() or state['recoveryRequired'] or any(s['sessions'] for s in state['stages'].values()): raise GateError('Entry preparation requires an unused reconciled queue') local_mode.require_unattended(root,engine.config) if engine.git('status','--porcelain','--untracked-files=all') or engine.git('rev-parse','HEAD') != engine.manifest['baselineCommit']: raise GateError('Assessment entry requires a clean exact baseline checkout') engine.verify_remote(scope['handoffBranch'],scope['handoffCommit']) engine.git('merge-base','--is-ancestor',engine.manifest['baselineCommit'],scope['handoffCommit']) stage=next(s for s in engine.manifest['stages'] if s['id']==scope['stage']) cp=next(x for x in stage['checkpoints'] if x['id']==scope['checkpoint']) engine.prompt(stage,cp,state['stages'][stage['id']],state) if not any(check['id']=='qualified-provider-browser-python-pdf-security-preservation' and check.get('configured') is True for check in cp['checks']): raise GateError('P10-00 composite adapter is not qualified') for check in cp['checks']: if check.get('adapter') == 'assessment-evidence-inspection' or not check.get('configured'): raise GateError('P10-00 composite adapter is not qualified') for kind in ['environment-qualified','roadmap-execution',*stage['requiredOwnerApprovals'],*cp['approvalsBefore']]: if not engine.request(stage,cp,kind,state): print('Protected entry request prepared; approve it and run prepare-entry again. No worker launched.') return 3 print('All exact restricted entry approvals verified. Owner may deliberately run start. No worker launched.') elif args.command == 'prepare': with RepoLock(engine.repo): state = engine.store.read() stage = next(s for s in engine.manifest['stages'] if state['stages'][s['id']]['status']!='COMPLETED') ss = state['stages'][stage['id']] cp = stage['checkpoints'][min(ss['checkpoint'],len(stage['checkpoints'])-1)] path = root/'operator-review.txt' path.write_text('INSPECTION ONLY. This file grants no Phase 10 or execution authorization.\n'+ engine.prompt(stage,cp,ss,state),encoding='utf-8') print('Operator-controlled review prepared at '+str(path)+'; no worker/test/Git operation launched') elif args.command in ('validate', 'dry-run'): validate_manifest(engine.manifest, root) if local_mode.single(engine.config): local_mode.validate_config(root,engine.config) report = dict(configuration='PASS',mode=engine.config.get('mode','dedicated-runner'), executionPolicy=engine.config.get('executionPolicy','dedicated-runner'), runtimeReady=False if local_mode.single(engine.config) else None, executionEnabled=engine.manifest['executionEnabled'], sourceGaps=[dict(id=s['id'], status=s['sourceStatus']) for s in engine.manifest['stages'] if s['sourceStatus'] != 'VERIFIED'], unqualifiedAdapters=sorted({check['id'] for s in engine.manifest['stages'] for cp in s['checkpoints'] for check in cp['checks'] if not check.get('configured', True)}), checkpoints=sum(len(s['checkpoints']) for s in engine.manifest['stages']), phase10='NO-GO', note='Read-only planning; no model, Git mutation, network, service, signing or scheduling action') print(json.dumps(report, indent=2)) elif args.command == 'pause': engine.pause() print('Pause persisted; active process tree will be stopped by the controller.') elif args.command in ('status', 'current'): data = status(engine) if args.command == 'current': data = next((s for s in data['stages'] if s['status'] != 'COMPLETED'), {'status': 'COMPLETED_DEVELOPMENT'}) print(json.dumps(data, indent=2)) elif args.command == 'logs': path = root / 'events.jsonl' print('\n'.join(path.read_text(encoding='utf-8').splitlines()[-25:]) if path.exists() else 'No events yet') elif args.command == 'retry': if not args.stage: parser.error('retry requires --stage') engine.retry(args.stage) elif args.command == 'configure': if not args.manifest: parser.error('configure requires --manifest (owner-prepared proposed manifest)') proposed = read_json(args.manifest) validate_manifest(proposed, root) with RepoLock(engine.repo): state = engine.store.read() if (root / 'active-process.json').exists() or state['recoveryRequired']: raise GateError('Recover/reconcile active work before changing policy') if [s['id'] for s in proposed['stages']] != [s['id'] for s in engine.manifest['stages']]: raise GateError('This policy adoption preserves the existing stage IDs/order; new queues need a new installation') for old, new in zip(engine.manifest['stages'], proposed['stages']): if old != new and state['stages'][old['id']]['sessions']: raise GateError('Cannot rewrite a started stage; preserve its immutable policy and evidence') if any(s['sessions'] for s in state['stages'].values()) and ( proposed['baselineCommit'] != engine.manifest['baselineCommit'] or proposed['integrationBranch'] != engine.manifest['integrationBranch'] or proposed['remote'] != engine.manifest['remote']): raise GateError('Cannot replace baseline/integration/remote after execution starts') stage = next((s for s in engine.manifest['stages'] if state['stages'][s['id']]['status'] != 'COMPLETED'), engine.manifest['stages'][-1]) cp = stage['checkpoints'][min(state['stages'][stage['id']]['checkpoint'], len(stage['checkpoints']) - 1)] if not engine.request(stage, cp, 'manifest-policy-update', state, dict(proposedManifestSha256=digest(proposed))): return 3 state['manifestSha256'] = digest(proposed) if not any(s['sessions'] for s in state['stages'].values()): state['integrationCommit'] = proposed['baselineCommit'] atomic(root / 'backlog.json', proposed) engine.store.write(state) print('Signed manifest adopted. Resume still enforces environment and stage entry approvals.') elif args.command == 'recover': with RepoLock(engine.repo): state = engine.store.read() state['recoveryRequired'] = True engine.store.write(state) # a pending/unsigned request cannot clear the interrupted-run guard stage = next(s for s in engine.manifest['stages'] if state['stages'][s['id']]['status'] != 'COMPLETED') cp = stage['checkpoints'][min(state['stages'][stage['id']]['checkpoint'], len(stage['checkpoints']) - 1)] marker = read_json(root / 'active-process.json') if (root / 'active-process.json').exists() else None if not engine.request(stage, cp, 'recovery-reconciled', state, marker): return 3 # Operator must first prove process tree is gone. Never kill an unverified reused PID. if marker: if recorded_process_exists(marker): raise GateError('Recorded process may still exist; inspect identity/tree before owner stops it') (root / 'active-process.json').unlink() state['recoveryRequired'] = False for ss in state['stages'].values(): if ss['status'] == 'RUNNING': ss['status'] = 'BLOCKED' engine.store.write(state) else: if args.command == 'resume': engine.resume() engine.run() print(json.dumps(status(engine), indent=2)) return 0 if __name__ == '__main__': try: sys.exit(main()) except (GateError, OSError, ValueError, KeyError, subprocess.SubprocessError) as error: print(redact(str(error)), file=sys.stderr) sys.exit(2)