$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 bool $providerMatches = true; public static function enabled(): bool { return self::$enabled; } public static function verified(?string $profile = null): bool { return self::$verified; } public static function ready(?string $profile = null): bool { return self::$verified; } public static function claim(): ?array { self::$events[] = 'claim'; return self::$verified ? ['id' => 1, 'actor_id' => 1, 'diagnosis_id' => 1, 'lease_token' => 'test', 'model_key' => 'qwen'] : null; } public static function assertTaskProvider(array $task): void { if (!self::$providerMatches) { throw new FollowupAudioException('PROVIDER_CONFIGURATION_CHANGED'); } } 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 (self::$mode === 'change-before-send') { FollowupAudioStore::$providerMatches = false; } 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 === 'change-after-send') { FollowupAudioStore::$providerMatches = false; } 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 === ['claim'], 'unverified task is not consumed; queue may fence stale bindings'); Store::$events = []; 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'); Access::$allowed = true; Actors::$active = true; foreach (['change-before-send' => false, 'change-after-send' => true] as $mode => $uncertain) { Dify::$mode = $mode; Store::$events = []; Store::$providerMatches = true; $worker->runOnce(); $last = end(Store::$events); $expect($last['fail'] === 'PROVIDER_CONFIGURATION_CHANGED' && $last['uncertain'] === $uncertain, $mode . ' is fenced with correct upstream uncertainty'); $expect(!array_filter(Store::$events, static fn ($event): bool => is_array($event) && isset($event['complete'])), $mode . ' never completes result under a changed provider'); } Store::$providerMatches = false; Store::$events = []; Dify::$mode = 'success'; $worker->runOnce(); $last = end(Store::$events); $expect($last['fail'] === 'PROVIDER_CONFIGURATION_CHANGED' && !$last['uncertain'] && count(Store::$events) === 2, 'Initial missing or stale task fingerprint is rejected before any heartbeat or transport'); echo 'FOLLOWUP_AUDIO_WORKER assertions=' . $checks . ' PASS gate=1 actor_recheck=1 reconciliation=1' . PHP_EOL; }