$id] : null; } } } namespace app\common\service\followupaudio { final class FollowupAudioStore { public static bool $enabled = true; public static bool $verified = true; public static array $events = []; public static bool $lease = true; public static function enabled(): bool { return self::$enabled; } public static function verified(?string $profile = null): bool { return self::$verified; } public static function claim(): ?array { self::$events[] = 'claim'; return ['id' => 1, 'actor_id' => 1, 'diagnosis_id' => 1, 'lease_token' => 'test', 'model_key' => 'qwen']; } public static function heartbeat(int $id, string $token): bool { self::$events[] = 'heartbeat'; return self::$lease; } public static function checkpoint(int $id, string $token, array $fields): bool { self::$events[] = ['checkpoint' => $fields]; return self::$lease; } public static function complete(int $id, string $token, array $extraction): bool { self::$events[] = ['complete' => $extraction]; return self::$lease; } public static function fail(int $id, string $token, string $code, string $message, bool $uncertain): bool { self::$events[] = ['fail' => $code, 'uncertain' => $uncertain, 'message' => $message]; return true; } } final class FollowupAudioAccess { public static bool $allowed = true; public static function task(int $id, int $actor, array $info): array { if (!self::$allowed) { throw new \RuntimeException('private-patient'); } return ['id' => $id]; } } final class FollowupAudioDify { public static string $mode = 'success'; public function analyze(array $task, callable $heartbeat): array { if ($heartbeat(['upstream_started_at' => 1, 'stage' => 'uploading']) === false) { throw new FollowupAudioException('LEASE_LOST'); } if (self::$mode === 'uncertain') { throw new FollowupAudioException('UPSTREAM_UNCERTAIN', true); } if (self::$mode === 'internal') { throw new \RuntimeException('private-patient'); } if (self::$mode === 'revoke') { \app\common\service\prescriptionai\PrescriptionAiAccess::$active = false; } return ['summary' => 'synthetic', 'items' => []]; } } } namespace { require dirname(__DIR__) . '/app/common/service/followupaudio/FollowupAudioException.php'; require dirname(__DIR__) . '/app/common/service/followupaudio/FollowupAudioWorker.php'; use app\common\service\followupaudio\FollowupAudioStore as Store; use app\common\service\followupaudio\FollowupAudioDify as Dify; use app\common\service\followupaudio\FollowupAudioWorker as Worker; use app\common\service\followupaudio\FollowupAudioAccess as Access; use app\common\service\prescriptionai\PrescriptionAiAccess as Actors; $checks = 0; $expect = static function (bool $ok, string $why) use (&$checks): void { if (!$ok) { throw new \RuntimeException($why); } $checks++; }; $worker = new Worker(new Dify()); Store::$enabled = false; $expect(!$worker->runOnce() && Store::$events === [], 'disabled before claim'); Store::$enabled = true; Store::$verified = false; $expect(!$worker->runOnce() && Store::$events === [], 'unverified before claim'); Store::$verified = true; $expect($worker->runOnce(), 'success consumed task'); $expect(isset(Store::$events[count(Store::$events)-1]['complete']), 'success completed after authorization'); foreach (['uncertain' => 'UPSTREAM_UNCERTAIN', 'internal' => 'INTERNAL_ERROR', 'revoke' => 'LEASE_LOST'] as $mode => $code) { Dify::$mode = $mode; Store::$events = []; Actors::$active = true; $expect($worker->runOnce(), $mode . ' consumed'); $last = end(Store::$events); $expect($last['fail'] === $code && $last['uncertain'] === true, $mode . ' reconciliation required'); $expect(!str_contains($last['message'], 'private-patient'), 'sanitized ' . $mode); } Actors::$active = false; Store::$events = []; $worker->runOnce(); $last = end(Store::$events); $expect($last['fail'] === 'ACCESS_REVOKED' && !$last['uncertain'], 'inactive actor before upstream'); Actors::$active = true; Access::$allowed = false; Store::$events = []; $worker->runOnce(); $last = end(Store::$events); $expect($last['fail'] === 'INTERNAL_ERROR' && !$last['uncertain'], 'scope denied before upstream'); echo 'FOLLOWUP_AUDIO_WORKER assertions=' . $checks . ' PASS gate=1 actor_recheck=1 reconciliation=1' . PHP_EOL; }