Phase 9: Clearance Protocol — intercept, approve/deny, HUD, voice commands

- reactor.py v9.0.0: clearance endpoints, watchdog, create_job intercept
- arc.php: 7 clearance actions (pending/history/approve/deny/rules/rule_update/create)
- chat.php: Tier 0.9j voice commands — approve/deny/status clearance
- index.html: clearance banner, CLEARANCE tab with pending requests + rules + history
- admin/index.php: CLEARANCE nav + tab with full CRUD for rules and approve/deny UI
This commit is contained in:
2026-06-11 12:19:14 +00:00
parent aaf07edacb
commit 93d7594c4f
4 changed files with 615 additions and 0 deletions
+46
View File
@@ -292,6 +292,52 @@ switch ($action) {
echo json_encode(arc_request('PUT', "/missions/{$id}", ['enabled' => $enabled]));
break;
// GET /api/arc?action=clearance_pending
case 'clearance_pending':
echo json_encode(arc_request('GET', '/clearance/pending'));
break;
// GET /api/arc?action=clearance_history
case 'clearance_history':
$limit = (int)($_GET['limit'] ?? 50);
echo json_encode(arc_request('GET', "/clearance/history?limit={$limit}"));
break;
// POST /api/arc?action=clearance_approve&id=123 body: { decided_by: "..." }
case 'clearance_approve':
$id = (int)($_GET['id'] ?? $data['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
$decided_by = $data['decided_by'] ?? 'admin';
echo json_encode(arc_request('POST', "/clearance/{$id}/approve", ['decided_by' => $decided_by]));
break;
// POST /api/arc?action=clearance_deny&id=123 body: { decided_by: "...", note: "..." }
case 'clearance_deny':
$id = (int)($_GET['id'] ?? $data['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
$decided_by = $data['decided_by'] ?? 'admin';
$note = $data['note'] ?? '';
echo json_encode(arc_request('POST', "/clearance/{$id}/deny", ['decided_by' => $decided_by, 'note' => $note]));
break;
// GET /api/arc?action=clearance_rules
case 'clearance_rules':
echo json_encode(arc_request('GET', '/clearance/rules'));
break;
// PUT /api/arc?action=clearance_rule_update&id=123 body: { require_approval: 0|1, auto_approve_after_min: N, ... }
case 'clearance_rule_update':
$id = (int)($_GET['id'] ?? $data['id'] ?? 0);
if (!$id) { http_response_code(400); echo json_encode(['error' => 'Missing id']); break; }
unset($data['id']);
echo json_encode(arc_request('PUT', "/clearance/rules/{$id}", $data));
break;
// POST /api/arc?action=clearance_rule_create body: { job_type, risk_level, require_approval, ... }
case 'clearance_rule_create':
echo json_encode(arc_request('POST', '/clearance/rules', $data));
break;
default:
http_response_code(404);
echo json_encode(['error' => "Unknown arc action: {$action}"]);
+86
View File
@@ -1082,6 +1082,33 @@ if (!$reply && preg_match('/\b(news|headlines|latest|what.?s happening|current e
$arcJobId = null;
// Helper: submit job to Arc Reactor
function arcPost(string $path, array $body): ?array {
$ch = curl_init('http://127.0.0.1:7474' . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3,
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
return $res;
}
function arcGet(string $path): ?array {
$ch = curl_init('http://127.0.0.1:7474' . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3,
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
return $res;
}
function arcSubmitJob(string $type, array $payload, string $sessionId): ?array {
$ch = curl_init('http://127.0.0.1:7474/job');
curl_setopt_array($ch, [
@@ -1395,6 +1422,65 @@ if (!$reply) {
}
}
// ── Tier 0.9j: Clearance Protocol — approve/deny voice commands ─────────────
if (!$reply) {
// "approve clearance 5", "authorize clearance", "approve all clearance"
if (preg_match('/^(?:jarvis[,\s]+)?(?:approve|authorize|grant)\s+(?:all\s+)?clearance(?:\s+(?:request\s+)?#?(\d+))?/i', $message, $m)) {
$crId = isset($m[1]) && $m[1] ? (int)$m[1] : null;
if ($crId) {
$resp = arcPost('/clearance/' . $crId . '/approve', ['decided_by' => 'voice']);
if (isset($resp['ok']) && $resp['ok']) {
$reply = "◈ Clearance request #{$crId} authorized, {$userAddr}. Job dispatched.";
} else {
$reply = "Clearance #{$crId} not found or already decided.";
}
} else {
// Approve all pending
$pending = arcGet('/clearance/pending') ?: [];
if (empty($pending)) {
$reply = "No pending clearance requests, {$userAddr}.";
} else {
$approved = 0;
foreach ($pending as $cr) {
$resp = arcPost('/clearance/' . $cr['id'] . '/approve', ['decided_by' => 'voice']);
if (isset($resp['ok']) && $resp['ok']) $approved++;
}
$reply = "◈ Authorized {$approved} clearance request" . ($approved !== 1 ? 's' : '') . ", {$userAddr}.";
}
}
$source = 'arc:clearance_approve';
}
}
if (!$reply) {
// "deny clearance 5", "reject clearance 5"
if (preg_match('/^(?:jarvis[,\s]+)?(?:deny|reject|refuse)\s+clearance(?:\s+(?:request\s+)?#?(\d+))?/i', $message, $m)) {
$crId = isset($m[1]) && $m[1] ? (int)$m[1] : null;
if ($crId) {
$resp = arcPost('/clearance/' . $crId . '/deny', ['decided_by' => 'voice', 'note' => 'denied by voice command']);
$reply = isset($resp['ok']) && $resp['ok']
? "Clearance #{$crId} denied, {$userAddr}."
: "Clearance #{$crId} not found or already decided.";
} else {
$reply = "Which clearance request should I deny? Say: deny clearance [number].";
}
$source = 'arc:clearance_deny';
}
}
if (!$reply) {
// "any pending clearance", "clearance status", "show clearance"
if (preg_match('/^(?:jarvis[,\s]+)?(?:(?:any\s+|show\s+)?pending\s+clearance|clearance\s+(?:status|requests?|pending|queue))/i', $message)) {
$pending = arcGet('/clearance/pending') ?: [];
$count = count($pending);
if ($count === 0) {
$reply = "No pending clearance requests, {$userAddr}. All clear.";
} else {
$list = array_map(fn($cr) => "#{$cr['id']} {$cr['job_type']} ({$cr['risk_level']})", $pending);
$reply = "{$count} pending clearance request" . ($count !== 1 ? 's' : '') . ": " . implode(', ', $list) . ". Say 'approve clearance [number]' to authorize.";
}
$source = 'arc:clearance_status';
}
}
// ── Tier 1: Intent Engine (instant, no LLM) ───────────────────────────────
if (!$reply) {
$matched = KBEngine::match($message);