$source) if ($source['field'] === $field && ($path === '' || str_starts_with($source['path'], $path))) return $id; throw new RuntimeException('Missing test source.'); } /** Pure Contract/API controls; no ledger builder or stream class is invoked. */ function evidenceContractMetaChecks(array $index, string $first, string $denialId, string $wrongId): void { $head = $first . "二、八纲\n"; $capability = '但对脏腑定位并无额外证明作用'; $classified = '尚无足以确立痰、湿、瘀、食积等实邪的阳性证据'; foreach ([$capability . '。', '对于证型与病机没有独立佐证作用。', '对辨证结论尚无直接支持作用。', $classified . '。', '没有足以确认痰或湿等病邪的独立证据。', '无足以支持气虚证与阴虚证等证候的直接依据。', '无足以证明阳虚、阴虚或阳亢等证候的充分阳性证据。', '无足以排除阴虚的独立证据。', '不能据此写成“无实邪”。', '不得写为「无胸痛」。', '不应当直接表述为"无口干"。', '不能写为“无副作用”。', '现有证据不能直接归类为“无实邪”。', '尚不能据此判定为“无证候”。', $classified . ',不能据此写成“无实邪”。', '不能据此写成“无实邪”,2026-10-23“无胸痛”(' . $denialId . ')。', '不能据此写成2026-10-23“无胸痛”(' . $denialId . ')。'] as $phrase) { evidenceContractCheck($index, $head . $phrase, true); } foreach (['患者无副作用。', '药物没有相互作用。', '对脏腑定位并无副作用。', '对阴虚没有相互作用。', '对口干或舌红无额外证明作用。', '对患者有胸痛诊断无直接证明作用。', $capability . '性皮疹。', $capability . ',患者无口干。', $capability . '且患者无头晕。', '尚无足以确立口干、舌红等症状的阳性证据。', '尚无足以确立口干、舌红等实邪的阳性证据。', '无足以确立患者无口干证的独立证据。', '无足以确立患者有胸痛诊断的阳性证据。', '无足以确立痰、湿、瘀、食积的阳性证据。', '无痰的独立证据。', '无足以出现阴虚的独立证据。', $classified . '性皮疹。', $classified . '证明患者无头晕。', $classified . ',患者无口干。', '无足以确立' . implode('、', array_fill(0, 9, '痰')) . '等实邪的阳性证据。'] as $assertion) { evidenceContractCheck($index, $head . $assertion, false, 'UNQUOTED_PATIENT_DENIAL'); } foreach (['可据此写成“无实邪”。', '写成“无口干”。', '并非不能据此写成“无实邪”。', '不是不能写成“无实邪”。', '不能说不能写成“无口干”。', '并非“这一结论”不能写成“无实邪”。', '不能据此写成“无实邪”是事实。', '不能据此写成“无实邪”“无口干”。', '不能据此写成“无实邪”(s999)。', '不能据此写成“无实邪”(s0)。', '不能据此写成“无实邪”(非来源)。', '不能据此写成“无' . str_repeat('甲', 80) . '”。'] as $notIllustrative) { evidenceContractCheck($index, $head . $notIllustrative, false, 'DENIAL_SOURCE_QUOTE_INVALID'); } evidenceContractCheck($index, $head . '不能据此写成“无实邪”,患者无口干。', false, 'UNQUOTED_PATIENT_DENIAL'); evidenceContractCheck($index, $head . '不得据此写成“无副作用”,药物无相互作用。', false, 'UNQUOTED_PATIENT_DENIAL'); evidenceContractCheck($index, $head . '不能据此写成“无实邪”,患者否认头晕。', false, 'UNQUOTED_PATIENT_DENIAL'); evidenceContractCheck($index, $head . '不能据此写成“无胸痛”(' . $wrongId . ')。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $head . '不能据此写成2026-10-22“无胸痛”(' . $denialId . ')。', false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, $head . '不能据此写成本次2026-10-22“无晕厥”(s90)。', false, 'EXAM_SOURCE_NOT_CURRENT'); evidenceContractCheck($index, '一、四诊' . "\n问诊:不能据此写成“无口干”。", false, 'EXAM_FACT_PROTOCOL_INVALID'); foreach ([$capability, $classified] as $prefix) { evidenceContractCheck($index, $head . $prefix . ';“无胸痛”(s999)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $head . $prefix . ';2026-10-22“无胸痛”(' . $denialId . ')。', false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, $head . $prefix . ';本次2026-10-22“无晕厥”(s90)。', false, 'EXAM_SOURCE_NOT_CURRENT'); } } function evidenceContractR13Units(): array { return [ '寒热:口干、少津、苔少提示津液不足,但不足以单独成立热证;寒证亦无独立阳性寒象。寒热两端现有资料不足以确定,不能因一端证据不足反定另一端。' . "\n\n", '虚实:神疲、语声低微、动则气短、自汗,兼脉细弱、按之无力(s1、s3、s10),共同支持虚证。实证缺乏独立阳性依据;舌脉相关限制见s9、s10,不能据此反推纯虚无实。' . "\n\n", ]; } /** Typed epistemic roles do not authorize an unquoted patient/observed absence. */ function evidenceContractInferenceChecks(array $index, string $first, string $denialId, string $wrongId): void { $head = $first . "二、八纲\n"; foreach (['寒证亦无独立阳性寒象。', '热证尚无明确独立阳性热象证据。', '虚证没有充分独立阳性虚象依据。', '表证亦无阳性的独立表象。', '阴证无独立的阳性阴象。', '寒热:寒证亦无独立阳性寒象。', '不能据此反推纯虚无实。', '不可仅据此推出单纯热证无寒证。', '现有资料不应由此判定为无实证。', '不得直接推断为阴证而没有阳证。', '不能反推出纯虚无实的结论。', '虚实:不能据此反推纯虚无实。'] as $phrase) { evidenceContractCheck($index, $head . $phrase, true); } foreach (['患者无独立阳性寒象。', '患者寒证亦无独立阳性寒象。', '患者:寒证亦无独立阳性寒象。', '本次查见:寒证亦无独立阳性寒象。', '寒证患者亦无独立阳性寒象。', '寒证亦无寒象。', '寒证亦无独立寒象。', '寒证亦无独立阳性畏寒肢冷。', '寒证亦无独立阳性口干。', '寒证亦无独立阳性舌红。', '寒证亦无独立阳性热象。', '寒证亦无独立阳性寒象性皮疹。', '不能据此反推无口干。', '不能据此反推无舌红。', '不能据此反推无寒冷。', '不能据此反推无畏寒肢冷。', '不能据此反推患者纯虚无实。', '不能据此反推本次无寒象。', '不能据此反推查见无实证。', '患者不能据此反推纯虚无实。', '患者:不能据此反推纯虚无实。', '并非不能据此反推纯虚无实。', '不是不能反推纯虚无实。', '不能不反推纯虚无实。', '可以据此反推纯虚无实。', '不能据此反推纯虚无实性皮疹。'] as $assertion) { evidenceContractCheck($index, $head . $assertion, false, 'UNQUOTED_PATIENT_DENIAL'); } evidenceContractCheck($index, $first . "三、气血津液与脏腑\n寒证亦无独立阳性寒象。", false, 'UNQUOTED_PATIENT_DENIAL'); foreach (['寒证亦无独立阳性寒象', '不能据此反推纯虚无实'] as $epistemic) { foreach ([',患者无口干。', '且否认头晕。', ';本次未见舌红。', ',无副作用。', ',没有相互作用。'] as $tail) { evidenceContractCheck($index, $head . $epistemic . $tail, false, 'UNQUOTED_PATIENT_DENIAL'); } evidenceContractCheck($index, $head . $epistemic . ';“无胸痛”(s999)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $head . $epistemic . ';“无胸痛”(' . $wrongId . ')。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $head . $epistemic . ';2026-10-22“无胸痛”(' . $denialId . ')。', false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, $head . $epistemic . ';本次2026-10-22“无晕厥”(s90)。', false, 'EXAM_SOURCE_NOT_CURRENT'); evidenceContractCheck($index, $head . $epistemic . ';2026-10-23“无胸痛”(' . $denialId . ')。', true); evidenceContractCheck($index, "一、四诊\n问诊:" . $epistemic . '。', false, 'EXAM_FACT_PROTOCOL_INVALID'); } $qualified = $index; $qualified['sources']['s88'] = ['quote' => '无明显畏寒、肢冷。', 'scope' => 'current', 'date' => '2026-10-23', 'record_state' => 'negative', 'spans' => [['role' => 'interview', 'recorded' => true, 'quote' => '无明显畏寒、肢冷。']]]; evidenceContractCheck($qualified, $head . '不能据此反推纯虚无实;2026-10-23“无明显畏寒、肢冷。”(s88)。', true); foreach (['无畏寒、肢冷。', '无明显畏寒'] as $widenedQuote) { evidenceContractCheck($qualified, $head . '不能据此反推纯虚无实;2026-10-23“' . $widenedQuote . '”(s88)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); } foreach (['categoricalEvidenceAbsence' => '寒证亦无独立阳性寒象', 'negatedCategoricalInference' => '不能据此反推纯虚无实'] as $method => $phrase) { foreach ([',患者有胸痛。', ',舌质红绛。', ',脉滑。', ',胸痛未记录。'] as $tail) { $masked = (new ReflectionMethod(Contract::class, $method))->invoke(null, $phrase . $tail); ++$GLOBALS['count']; if (!str_ends_with($masked, $tail)) throw new RuntimeException('Epistemic noun scope consumed an adjacent factual predicate.'); } } foreach (evidenceContractR13Units() as $unit) evidenceContractCheck($index, $head . $unit, true); } function evidenceContractR15Units(): array { return [ 'tcm_complete_v43r14_openai_deadline_20261024_openai_next_full90_openai.rejected.json' => '虚实:乏力、动则气短、自汗、神疲、语声低微及脉细弱无力(s1、s3、s10)共同支持虚证。实证所需壅滞、积聚等阳性依据不足,不能仅据虚证直接判定全属虚而无实。' . "\n\n", 'tcm_complete_v43r14_supplement_ooc2_20261024_1_openai.rejected.json' => '阴阳:本次可判断病性偏虚,但虚证不能单独推出阴证;亦无支持阳证的独立阳性组合,故阴阳属性现有资料不足以确定。' . "\n\n", ]; } /** Explicit abstract premises/complements cannot lend their role to patient assertions. */ function evidenceContractR15InferenceChecks(array $index, string $first, string $denialId, string $wrongId): void { $head = $first . "二、八纲\n"; $inference = '不能仅据虚证直接判定全属虚而无实'; $support = '亦无支持阳证的独立阳性组合'; foreach ([$inference . '。', $support . '。', '不可只凭热证推定全为热而没有寒。', '现有证据不应当单凭里证认定均属里而无表。', '不得依据阴证直接判断为纯阴无阳。', '不宜根据实证推出全部属于实而无虚。', '不能由此判定全属虚证而无实证的结论。', '不能直接判定全属虚而无实。', '不能仅据阴证判定没有阳证。', '不能只据寒证推断为单纯寒证无热证。', '没有佐证里证的充分独立阳性组合。', '尚无证明寒证的阳性的独立依据。', '仍无支持阴证型的可靠独立的阳性组合。'] as $phrase) { evidenceContractCheck($index, $head . $phrase, true); } foreach (['患者全属虚而无实。', '本次查见全属虚而无实。', '已经判定全属虚而无实。', '可以仅据虚证直接判定全属虚而无实。', '并非' . $inference . '。', '不是' . $inference . '。', '不能不根据虚证直接判定全属虚而无实。', '不应当不依据虚证判定全属虚而无实。', '不能仅据虚证直接不判定全属虚而无实。', '不能仅据患者虚证直接判定全属虚而无实。', '不能仅据本次虚证直接判定全属虚而无实。', '不能仅据查见虚证直接判定全属虚而无实。', '不能仅据虚证直接判定患者全属虚而无实。', '不能仅据口干直接判定全属虚而无实。', '不能仅据舌红证直接判定全属虚而无实。', '不能仅据虚证直接判定全属虚而无口干。', '不能仅据虚证直接判定全属虚而无舌红。', '不能仅据虚证直接判定全属虚而无畏寒肢冷。', '不能仅据虚证未记录直接判定全属虚而无实。', $inference . '性皮疹。', $inference . '(s2)。', '患者:' . $inference . '。', '2026-10-23' . $inference . '。', '没有支持口干的独立阳性组合。', '无支持舌红的独立阳性组合。', '无支持患者有口干阳证的独立阳性组合。', '无支持本次阳证的独立阳性组合。', '无支持阳证未记录的独立阳性组合。', '无支持阳证的组合。', '无支持阳证的独立组合。', '无支持阳证的阳性组合。', '无支持阳证的独立阳性口干。', $support . '性皮疹。', '并非' . $support . '。', '无并非支持阳证的独立阳性组合。'] as $assertion) { evidenceContractCheck($index, $head . $assertion, false, 'UNQUOTED_PATIENT_DENIAL'); } evidenceContractCheck($index, $first . "三、气血津液与脏腑\n" . $support . '。', false, 'UNQUOTED_PATIENT_DENIAL'); foreach ([$inference, $support] as $phrase) { foreach ([',患者无口干。', '且否认头晕。', '而本次未见舌红。', ';没有相互作用。'] as $tail) { evidenceContractCheck($index, $head . $phrase . $tail, false, 'UNQUOTED_PATIENT_DENIAL'); } evidenceContractCheck($index, $head . '患者无口干,' . $phrase . '。', false, 'UNQUOTED_PATIENT_DENIAL'); evidenceContractCheck($index, $head . $phrase . ';“无胸痛”(s999)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $head . $phrase . ';“无胸痛”(' . $wrongId . ')。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $head . $phrase . ';2026-10-22“无胸痛”(' . $denialId . ')。', false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, $head . $phrase . ';本次2026-10-22“无晕厥”(s90)。', false, 'EXAM_SOURCE_NOT_CURRENT'); evidenceContractCheck($index, $head . $phrase . ';2026-10-23“无胸痛”(' . $denialId . ')。', true); evidenceContractCheck($index, "一、四诊\n问诊:" . $phrase . '。', false, 'EXAM_FACT_PROTOCOL_INVALID'); } $qualified = $index; $qualified['sources']['s88'] = ['quote' => '无明显畏寒、肢冷。', 'scope' => 'current', 'date' => '2026-10-23', 'record_state' => 'negative', 'spans' => [['role' => 'interview', 'recorded' => true, 'quote' => '无明显畏寒、肢冷。']]]; foreach ([$inference, $support] as $phrase) { evidenceContractCheck($qualified, $head . $phrase . ';2026-10-23“无明显畏寒、肢冷。”(s88)。', true); evidenceContractCheck($qualified, $head . $phrase . ';2026-10-23“无畏寒、肢冷。”(s88)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); } foreach (['negatedCategoricalInference' => $inference, 'categoricalEvidenceAbsence' => $support] as $method => $phrase) { foreach (['患者有胸痛。', '患者无口干。', '舌质红绛。', '脉滑。', '胸痛未记录。'] as $assertion) { foreach ([$assertion . ',' . $phrase, $phrase . ',' . $assertion] as $text) { $masked = (new ReflectionMethod(Contract::class, $method))->invoke(null, $text); ++$GLOBALS['count']; if (!str_contains($masked, $assertion)) throw new RuntimeException('Abstract inference/support role consumed an adjoining factual predicate.'); } } } foreach (evidenceContractR15Units() as $file => $unit) { $path = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/' . $file; if (is_file($path)) { $saved = json_decode((string) file_get_contents($path), true); ++$GLOBALS['count']; if (($saved['rejections'][0]['unit'] ?? '') !== $unit) throw new RuntimeException('Portable r15 unit differs from retained original model bytes.'); } evidenceContractCheck($index, $head . $unit, true); } } function evidenceContractR14Condition(): string { return '适用条件:复核后仍以气虚、津亏和自汗为主要矛盾,且没有新出现的实邪、急性脱水或需优先处置的异常时,方可由医师决定是否采用。若证候转变,应重新辨证。'; } /** Future criteria are not present findings, and cannot absorb neighbouring claims. */ function evidenceContractFutureConditionChecks(array $index, string $first, string $denialId, string $wrongId): void { $head = $first . "六、中药方药及复核监测\n"; $condition = evidenceContractR14Condition(); foreach ([$condition, '适用前提:复诊后没有新增异常时,方可考虑。', '采用条件:重新评估后仍符合原证候,且无新出现的不适时方可决定是否采用。', '用药条件:核实后没有需要优先处理的异常时,方可考虑。', '适用条件:复核后,若没有新增口干时,方可考虑。'] as $future) { evidenceContractCheck($index, $head . $future, true); } foreach (['没有新出现的实邪、急性脱水或需优先处置的异常。', '适用条件:没有新出现的异常时,方可考虑。', '适用条件:复核后没有新出现的异常,方可考虑。', '适用条件:复核后没有新出现的异常时,可以考虑。', '适用条件:已复核后没有新出现的异常时,方可考虑。', '适用条件:患者复核后没有新出现的异常时,方可考虑。', '适用条件:复核后患者没有新出现的异常时,方可考虑。', '适用条件:复核后发现没有新出现的异常时,方可考虑。', '适用条件:复核后仍符合原证候,且目前没有新出现的异常时,方可考虑。', '适用条件:复核后仍符合原证候,且没有已经确认的异常时,方可考虑。', '适用条件:复核后仍符合原证候,且没有此前出现的异常时,方可考虑。', '适用条件:复核后没有新出现的异常且患者无口干时,方可考虑。', '适用条件:复核后没有新出现的异常,患者无口干时,方可考虑。', '适用条件:复核后没有新出现的异常并否认头晕时,方可考虑。', '适用条件:复核后没有胸痛未记录时,方可考虑。', '适用条件:复核后没有新出现的异常(s999)时,方可考虑。', '适用条件:复核后没有' . str_repeat('甲', 65) . '时,方可考虑。', '适用条件:复核后' . str_repeat('甲', 65) . '且没有新增异常时,方可考虑。'] as $notFutureOnly) { evidenceContractCheck($index, $head . $notFutureOnly, false, 'UNQUOTED_PATIENT_DENIAL'); } foreach (['患者无口干。' . $condition, '患者无口干;' . $condition, $condition . '患者无口干。', $condition . '且患者无头晕。', $condition . '患者未见舌红。'] as $adjoiningDenial) { evidenceContractCheck($index, $head . $adjoiningDenial, false, 'UNQUOTED_PATIENT_DENIAL'); } evidenceContractCheck($index, $head . $condition . '“无胸痛”(s999)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $head . $condition . '“无胸痛”(' . $wrongId . ')。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $head . $condition . '2026-10-22“无胸痛”(' . $denialId . ')。', false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, $head . $condition . '本次2026-10-22“无晕厥”(s90)。', false, 'EXAM_SOURCE_NOT_CURRENT'); evidenceContractCheck($index, $head . $condition . '2026-10-23“无胸痛”(' . $denialId . ')。', true); $qualified = $index; $qualified['sources']['s88'] = ['quote' => '无明显畏寒、肢冷。', 'scope' => 'current', 'date' => '2026-10-23', 'record_state' => 'negative', 'spans' => [['role' => 'interview', 'recorded' => true, 'quote' => '无明显畏寒、肢冷。']]]; evidenceContractCheck($qualified, $head . $condition . '2026-10-23“无畏寒、肢冷。”(s88)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, "一、四诊\n问诊:" . $condition, false, 'EXAM_FACT_PROTOCOL_INVALID'); foreach (['患者有胸痛。', '舌质红绛。', '脉滑。', '胸痛未记录。'] as $assertion) { foreach ([$assertion . $condition, $condition . $assertion] as $text) { $masked = (new ReflectionMethod(Contract::class, 'prospectiveApplicabilityAbsence'))->invoke(null, $text); ++$GLOBALS['count']; if (!str_contains($masked, $assertion)) throw new RuntimeException('A future criterion consumed an adjoining factual predicate.'); } } $path = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/tcm_complete_v43r13_single_openai_20261024_0_openai.rejected.json'; if (is_file($path)) { $saved = json_decode((string) file_get_contents($path), true); $unit = $saved['rejections'][1]['unit'] ?? ''; ++$GLOBALS['count']; if (!is_string($unit) || !str_contains($unit, $condition)) throw new RuntimeException('The retained r13 future-condition unit differs from the expected raw bytes.'); evidenceContractCheck($index, $first . $unit, true); } } // This narrow mode can run while other workers own the source builder/stream. if (in_array('--pure-meta', $argv ?? [], true)) { if (class_exists(Index::class, false)) throw new RuntimeException('Pure mode cannot use the mutable production Index.'); final class EvidenceContractPureIndex { public const VERSION = 'isolated-contract-test-index'; public const ROLES = ['望诊' => 'inspection', '闻诊' => 'listening', '问诊' => 'interview', '切诊' => 'palpation', '舌象' => 'tongue', '脉象' => 'pulse']; } class_alias(EvidenceContractPureIndex::class, Index::class); $pureIndex = ['version' => Index::VERSION, 'valid' => true, 'sources' => []]; $pureSpecs = [ 's1' => ['望诊', '神疲。'], 's2' => ['问诊', '口干。无胸痛。未记录头晕。'], 's3' => ['闻诊', '语声低微。'], 's4' => ['舌象', '舌质淡红,舌面少津。'], 's5' => ['脉象', '脉细弱,按之无力。'], ]; $pureLines = []; foreach ($pureSpecs as $id => [$role, $quote]) { $pureIndex['sources'][$id] = ['quote' => $quote, 'scope' => 'current', 'date' => '2026-10-23', 'record_state' => 'positive', 'spans' => [['role' => Index::ROLES[$role], 'recorded' => true, 'quote' => $quote]]]; $pureLines[$role] = $role . ':2026-10-23“' . $quote . '”(' . $id . ')。'; } $pureIndex['sources']['s90'] = ['quote' => '活动后汗出。无晕厥。', 'scope' => 'historical', 'date' => '2026-10-22', 'record_state' => 'positive', 'spans' => []]; $pureFirst = "一、四诊\n" . implode("\n", [$pureLines['望诊'], $pureLines['闻诊'], $pureLines['问诊'], '切诊:其他触诊未记录,脉象见独立脉象项。', $pureLines['舌象'], $pureLines['脉象']]) . "\n"; evidenceContractCheck($pureIndex, $pureFirst, true, '', true); evidenceContractMetaChecks($pureIndex, $pureFirst, 's2', 's1'); evidenceContractInferenceChecks($pureIndex, $pureFirst, 's2', 's1'); evidenceContractR15InferenceChecks($pureIndex, $pureFirst, 's2', 's1'); evidenceContractFutureConditionChecks($pureIndex, $pureFirst, 's2', 's1'); $r13Path = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/tcm_complete_v43r12_openai_deadline_20261024_openai_next_full90_openai.rejected.json'; if (is_file($r13Path)) { $r13Saved = json_decode((string) file_get_contents($r13Path), true); ++$count; if (array_column($r13Saved['rejections'] ?? [], 'unit') !== evidenceContractR13Units()) { throw new RuntimeException('Portable r13 units differ from retained raw failure bytes.'); } } foreach (['tcm_complete_v43r10_single_openai_20261024_0_openai.rejected.json', 'tcm_complete_v43r10_supplement_ooc2_20261024_1_openai.rejected.json'] as $file) { $path = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/' . $file; if (!is_file($path)) continue; $saved = json_decode((string) file_get_contents($path), true); $unit = $saved['rejections'][0]['unit'] ?? null; if (!is_string($unit) || $unit === '') throw new RuntimeException('Saved r10 pure unit is unavailable.'); evidenceContractCheck($pureIndex, $pureFirst . "二、八纲\n" . $unit, true); } ++$count; if (class_exists(Guard::class, false) || class_exists(\app\adminapi\service\TcmClinicalReportStream::class, false) || (new ReflectionClass(Index::class))->getName() !== EvidenceContractPureIndex::class) { throw new RuntimeException('Pure Contract mode must not load mutable source/stream dependencies.'); } echo "Clinical evidence contract pure metalinguistic controls: {$count} assertions passed.\n"; exit(0); } $row = ['id' => 71, 'diagnosis_date' => '2026-10-23', 'chief_complaint' => '口干3个月。', 'present_illness' => '活动后汗出。无胸痛。未记录头晕。', 'symptoms' => '望诊神疲、面色少华;闻诊语声低微,无异常气味。问诊及切诊见本次记录。', 'tongue' => '舌质淡红,舌面少津;无黄腻苔。', 'pulse' => '脉细弱,按之无力。']; $ledger = Guard::build($row); $ledger['sources']['s90'] = ['id' => 's90', 'path' => 'chat.im.0.text', 'field' => 'text', 'scope' => 'historical', 'date' => '2026-10-22', 'quote' => '活动后汗出。无晕厥。']; $ledger['sources']['s91'] = ['id' => 's91', 'path' => 'prescriptions.0.prescription_name', 'field' => 'prescription_name', 'scope' => 'historical', 'date' => '2020-05-02', 'quote' => '历史方药早已停用。']; $index = Index::build($ledger); $history = evidenceSourceId($index, 'present_illness'); $mixed = evidenceSourceId($index, 'symptoms'); $tongue = evidenceSourceId($index, 'tongue'); $pulse = evidenceSourceId($index, 'pulse'); $first = "一、四诊\n\n望诊:2026-10-23“神疲、面色少华”({$mixed})。\n\n" . "闻诊:2026-10-23“语声低微,无异常气味”({$mixed})。\n\n" . "问诊:2026-10-23“活动后汗出。无胸痛。未记录头晕。”({$history})。\n\n" . "切诊:其他触诊未记录,脉象见独立脉象项。\n\n" . "舌象:2026-10-23“舌质淡红,舌面少津;无黄腻苔。”({$tongue})。\n\n" . "脉象:2026-10-23“脉细弱,按之无力。”({$pulse})。\n\n" . "追踪:2026-10-22“活动后汗出。无晕厥。”(s90)。\n\n"; evidenceContractCheck($index, $first, true, '', true); evidenceContractCheck($index, '一、四诊', true); evidenceContractCheck($index, "一、四诊\n\n望诊:2026-10-23“神疲、面色少华”({$mixed})。", true); evidenceContractCheck($index, "一、四诊\n\n望诊:2026-10-23“神疲、面色少华”({$mixed})。", false, 'EXAM_ROLE_MISSING', true); evidenceContractCheck($index, "望诊:2026-10-23“活动后汗出”({$history})。", false, 'EXAM_ROLE_MISMATCH'); evidenceContractCheck($index, "切诊:2026-10-23“活动后汗出”({$history})。", false, 'EXAM_ROLE_MISMATCH'); evidenceContractCheck($index, "望诊:2026-10-23“语声低微”({$mixed})。", false, 'EXAM_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, "闻诊:2026-10-23“神疲”({$mixed})。", false, 'EXAM_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, "切诊:2026-10-23“及切诊见本次记录”({$mixed})。", false, 'EXAM_ROLE_MISMATCH'); evidenceContractCheck($index, "脉象:2026-10-23“舌质淡红”({$tongue})。", false, 'EXAM_ROLE_MISMATCH'); evidenceContractCheck($index, "问诊:2026-10-21“活动后汗出”({$history})。", false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, "问诊:2026-10-23、2026-99-99“活动后汗出”({$history})。", false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, "问诊:“活动后汗出”({$history})。", false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, "问诊:2026年10月23日“活动后汗出”({$history})。", true); evidenceContractCheck($index, "问诊补充:2026-10-23“活动后汗出”({$history})。", true); evidenceContractCheck($index, "切诊补充:2026-10-23“活动后汗出”({$history})。", false, 'EXAM_ROLE_MISMATCH'); evidenceContractCheck($index, "望诊补充:2026-10-23“活动后汗出”({$history})。", false, 'EXAM_ROLE_MISMATCH'); evidenceContractCheck($index, "问诊补充:2026-10-22“活动后汗出”({$history})。", false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, "问诊补充:2026-10-23“活动后汗出”({$history}),患者无头晕。", false, 'EXAM_FACT_PROTOCOL_INVALID'); evidenceContractCheck($index, "问诊:日期未记录“活动后汗出”({$history})。", false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); $undatedRow = $row; unset($undatedRow['diagnosis_date']); $undatedIndex = Index::build(Guard::build($undatedRow)); $undatedHistory = evidenceSourceId($undatedIndex, 'present_illness'); evidenceContractCheck($undatedIndex, "问诊:日期未记录“活动后汗出”({$undatedHistory})。", true); evidenceContractCheck($undatedIndex, "问诊:2026-10-23“活动后汗出”({$undatedHistory})。", false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($undatedIndex, '问诊:未记录。', false, 'EXAM_KNOWN_ROLE_TO_UNKNOWN'); evidenceContractCheck($undatedIndex, "问诊:2026-10-23日期未记录“活动后汗出”({$undatedHistory})。", false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, "问诊:2026-10-23“活动后汗出”({$history}),“无胸痛”({$history})。", true); evidenceContractCheck($index, "问诊:2026-10-23“活动后大汗出”({$history})。", false, 'EXAM_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, "问诊:2026-10-23“胸痛”({$history})。", false, 'EXAM_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, "问诊:2026-10-23“记录头晕”({$history})。", false, 'EXAM_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, "问诊:2026-10-23“活动后汗出”(s999)。", false, 'SOURCE_ANNOTATION_INVALID'); evidenceContractCheck($index, '问诊:活动后汗出,气虚。', false, 'EXAM_FACT_PROTOCOL_INVALID'); evidenceContractCheck($index, "问诊:2026-10-23“活动后汗出”({$history}),可见气虚。", false, 'EXAM_FACT_PROTOCOL_INVALID'); evidenceContractCheck($index, "问诊:2026-10-23“活动后汗出”。", false, 'SOURCE_ANNOTATION_INVALID'); foreach (['望诊', '闻诊', '问诊', '舌象', '脉象'] as $role) evidenceContractCheck($index, $role . ':未记录。', false, 'EXAM_KNOWN_ROLE_TO_UNKNOWN'); evidenceContractCheck($index, '切诊:未记录。', true); $withPalpation = $row; $withPalpation['symptoms'] .= '切诊腹软,无压痛。'; $palpationIndex = Index::build(Guard::build($withPalpation)); evidenceContractCheck($palpationIndex, '切诊:其他触诊未记录,脉象见独立脉象项。', false, 'EXAM_KNOWN_ROLE_TO_UNKNOWN'); evidenceContractCheck($index, '问诊:2026-10-22“活动后汗出”(s90)。', false, 'EXAM_SOURCE_NOT_CURRENT'); evidenceContractCheck($index, '追踪:2026-10-22“活动后汗出”(s90)。', true); foreach (['历史访谈', '历史问诊', '追踪资料'] as $historyLabel) { evidenceContractCheck($index, $historyLabel . ':2026-10-22“活动后汗出”(s90)。', true); evidenceContractCheck($index, $historyLabel . ':2026-10-23“活动后汗出”(s90)。', false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, $historyLabel . ':2026-10-22“患者无头晕”(s90)。', false, 'EXAM_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $historyLabel . ':2026-10-22“活动后汗出”(s90),本次望诊见自汗。', false, 'LONGITUDINAL_FACT_PROTOCOL_INVALID'); } evidenceContractCheck($index, '追踪:2026-10-23“活动后汗出”(s90)。', false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, "历史:2026-10-23“舌质淡红”({$tongue})。", false, 'LONGITUDINAL_SOURCE_NOT_HISTORICAL'); evidenceContractCheck($index, '追踪:2026-10-22“活动后汗出”(s90),本次望诊见自汗。', false, 'LONGITUDINAL_FACT_PROTOCOL_INVALID'); evidenceContractCheck($index, $first . "二、八纲\n证据不足,血红蛋白与出血史未记录。", true, '', true); foreach (['无血色素、出血史及其他血虚表现', '本次无晕厥史', '2026-10-23无血红蛋白异常', '未见明确红绛', '没有头晕', '否认多尿', '不伴心悸'] as $denial) { evidenceContractCheck($index, "二、八纲\n" . $denial . '。', false, 'UNQUOTED_PATIENT_DENIAL'); } evidenceContractCheck($index, $first . "二、八纲\n2026-10-23“无胸痛”({$history})仅为记录,其他资料未记录。", true); evidenceContractCheck($index, "二、八纲\n2026-10-22“无胸痛”({$history})。", false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, "二、八纲\n“无头晕”({$history})。", false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, '二、八纲' . "\n“无晕厥”(s999)。", false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $first . '二、八纲' . "\n核实汗出有无外邪未解,复核有无头晕。", true); evidenceContractCheck($index, '二、八纲' . "\n核实有无头晕,患者无血红蛋白异常。", false, 'UNQUOTED_PATIENT_DENIAL'); evidenceContractCheck($index, $first . '二、八纲' . "\n无法判断,无论哪种情况均需复核;按之无力,无须重复同一资料。", true); evidenceContractCheck($index, '二、八纲' . "\n不等于绝对无风险,患者无头晕。", false, 'UNQUOTED_PATIENT_DENIAL'); evidenceContractCheck($index, $first . '二、八纲' . "\n不等于绝对无风险。", true); foreach (['阳证亦无完整独立证据。', '肾的定位无充分依据,需继续采集。', '无明确证据支持该推断。'] as $epistemic) { evidenceContractCheck($index, $first . "二、八纲\n" . $epistemic, true); } foreach (['无完整独立证据,患者无头晕。', '无完整独立证据证明患者无头晕。', '患者无胸痛,无完整独立证据。', '无心悸证据。', '无完整独立证据性皮疹。'] as $assertion) { evidenceContractCheck($index, $first . "二、八纲\n" . $assertion, false, 'UNQUOTED_PATIENT_DENIAL'); } $unknownLedger = Guard::build(['present_illness' => '无']); $unknownIndex = Index::build($unknownLedger); $unknownId = evidenceSourceId($unknownIndex, 'present_illness'); evidenceContractCheck($unknownIndex, "问诊:日期未记录“无”({$unknownId})。", false, 'EXAM_ROLE_MISMATCH'); evidenceContractCheck($unknownIndex, "二、八纲\n“无”({$unknownId})。", false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($unknownIndex, "历史:日期未记录“无”({$unknownId})。", false, 'EXAM_SOURCE_QUOTE_INVALID'); $denialList = Index::build(Guard::build(['present_illness' => '否认:胸痛、心悸。'])); $denialListId = evidenceSourceId($denialList, 'present_illness'); evidenceContractCheck($denialList, "问诊:日期未记录“胸痛、心悸”({$denialListId})。", false, 'EXAM_SOURCE_QUOTE_INVALID'); evidenceContractCheck($denialList, "问诊:日期未记录“否认:胸痛、心悸。”({$denialListId})。", true); evidenceContractCheck([], '', false, 'EVIDENCE_INDEX_UNAVAILABLE'); evidenceContractCheck($index, "\xC3\x28", false, 'EVIDENCE_INVALID_UTF8'); // Exact saved v42 content failures, retained as data instead of an expected syndrome. $fixture = Index::build(Guard::build(CompleteTcmCase::current(), AiRequiredSource::submission(CompleteTcmCase::rows()))); $fixtureLedger = Guard::build(CompleteTcmCase::current(), AiRequiredSource::submission(CompleteTcmCase::rows())); $neutralPrerequisite = '适用前须核实:复核自汗是否由低血糖、感染或其他急性原因引起。'; foreach ([$neutralPrerequisite => '', $neutralPrerequisite . '患者无固定刺痛。' => 'UNKNOWN_TO_NEGATIVE', $neutralPrerequisite . '且患者有心悸。' => 'NEGATIVE_TO_POSITIVE', $neutralPrerequisite . '心悸未记录。' => 'NEGATIVE_TO_UNKNOWN', '适用条件:患者无固定刺痛。' => 'UNKNOWN_TO_NEGATIVE', '无急性感染诱因。' => 'NEGATION_SCOPE_WIDENED'] as $condition => $expectedCode) { $check = Guard::validate($fixtureLedger, $condition); ++$count; if ($check['valid'] !== ($expectedCode === '') || ($expectedCode !== '' && !in_array($expectedCode, array_column($check['issues'], 'code'), true))) { throw new RuntimeException('Neutral future question changed a neighbouring patient assertion boundary.'); } } evidenceContractCheck($fixture, '一、四诊' . "\n望诊:2026-10-23见“神疲、面色少华”;症状逐渐出现。", false, 'EXAM_FACT_PROTOCOL_INVALID'); evidenceContractCheck($fixture, '一、四诊' . "\n切诊:本次症状随活动加重,结合自汗、气短提示卫外及推动功能偏弱;腹诊、皮肤温度、四肢温度等切诊细节未记录,不能补作正常。", false, 'EXAM_FACT_PROTOCOL_INVALID'); evidenceContractCheck($fixture, '三、气血津液与脏腑' . "\n血:面色少华、脉细可作血少鉴别线索,但无血色素、出血史及其他血虚表现,现有资料不足以确定血虚。", false, 'UNQUOTED_PATIENT_DENIAL'); evidenceContractCheck($fixture, '六、中药方药及复核监测' . "\n反证与局限:舌质仅为淡红,未见明确红绛;虚热症状证据不足,故“阴虚”程度需复核。", false, 'UNQUOTED_PATIENT_DENIAL'); // Exact rejected v43 model unit: the common historical interview label is // metadata only. Its original date/quote/source still need verification. $fixtureRows = CompleteTcmCase::rows(); $fixtureRows['chat_records'] = ['tencent_im' => $fixtureRows['im_messages'], 'wechat_work' => []]; $fixtureWithChat = Index::build(Guard::build(CompleteTcmCase::current(), AiRequiredSource::submission($fixtureRows))); $fixtureChat = evidenceSourceId($fixtureWithChat, 'text', 'chat.'); evidenceContractCheck($fixtureWithChat, '历史访谈:2026-10-23“合成访谈:活动后出汗和气短明显,喝水每次不多,没有最近高热、恶寒、腹泻。”(' . $fixtureChat . ')。', true); // Structural provenance does not certify the medical explanatory bridge. evidenceContractCheck($index, $first . "四、证名\n推断:用已引来源支持任意不相干的证名。", true, '', true); // Simulated paragraph release checks at actual byte boundaries; the validator does no forwarding. foreach ([1, 2, 7, 31] as $size) { $pending = ''; $accepted = ''; foreach (str_split($first, $size) as $chunk) { $pending .= $chunk; while (preg_match('/(?:\r?\n){2}/', $pending, $boundary, PREG_OFFSET_CAPTURE)) { $end = $boundary[0][1] + strlen($boundary[0][0]); $unit = substr($pending, 0, $end); $pending = substr($pending, $end); evidenceContractCheck($index, $accepted . $unit, true); $accepted .= $unit; } } ++$count; if ($accepted . $pending !== $first) throw new RuntimeException('Incremental original bytes changed.'); evidenceContractCheck($index, $accepted . $pending, true, '', true); evidenceContractCheck($index, $accepted . "二、八纲\n患者无头晕。\n\n", false, 'UNQUOTED_PATIENT_DENIAL'); } // A comma can continue a denial list. It is not a safe quote boundary. $commaIndex = Index::build(Guard::build(['diagnosis_date' => '2026-10-23', 'present_illness' => '无明显胸痛,心悸;口干,少量频饮。'])); $commaSource = evidenceSourceId($commaIndex, 'present_illness'); foreach (['心悸', '明显胸痛', '无明显胸痛', '口干', '少量频饮'] as $cut) { evidenceContractCheck($commaIndex, "问诊:2026-10-23“{$cut}”({$commaSource})。", false, 'EXAM_SOURCE_QUOTE_INVALID'); } foreach (['无明显胸痛,心悸', '口干,少量频饮'] as $whole) { evidenceContractCheck($commaIndex, "问诊:2026-10-23“{$whole}”({$commaSource})。", true); } evidenceContractCheck($index, "一、四诊\n\n望诊:2026-10-23“神疲、面色少华”({$mixed})。\n\n二、八纲\n\n", false, 'EXAM_ROLE_MISSING'); // Independently reproduced bypass: entering a later heading must fail before // either that heading or its unsupported patient observation reaches forward. foreach (['二、八纲', '三、气血津液与脏腑', '六、中药方药及复核监测'] as $laterHeading) { $wire = $laterHeading . "\n\n本次新见结节肿块。\n\n"; evidenceContractCheck($index, $wire, false, 'EXAM_ROLE_MISSING'); foreach ([1, 2, 7, 31] as $size) { $visible = ''; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); foreach (str_split($wire, $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $visible !== '' || $stream->finish() || !in_array('EXAM_ROLE_MISSING', array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('Later-section entry bypassed the actual pre-forward role gate.'); } } } // Even all six quoted role fragments do not establish an omitted first heading. $headinglessFirst = substr($first, strlen("一、四诊\n\n")); evidenceContractCheck($index, $headinglessFirst . "二、八纲\n\n", false, 'EXAM_ROLE_MISSING'); evidenceContractCheck($index, $headinglessFirst, false, 'EXAM_ROLE_MISSING', true); // Exercise the actual shared stream gate, not a simulated forwarding loop. foreach (['无完整独立证据且患者有胸痛。' => 'NEGATIVE_TO_POSITIVE', '无完整独立证据,患者无头晕。' => 'UNKNOWN_TO_NEGATIVE', '无完整独立证据,胸痛未记录。' => 'NEGATIVE_TO_UNKNOWN'] as $adjacent => $expectedCode) { foreach ([1, 31] as $size) { $visible = ''; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); $prefix = $first . "二、八纲\n\n"; foreach (str_split($prefix, $size) as $chunk) { if (!$stream->push($chunk)) throw new RuntimeException('Valid source frame rejected before adjacent assertion test.'); } foreach (str_split($adjacent . "\n\n", $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $visible !== $prefix || !in_array($expectedCode, array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('Evidence-absence noun masked an adjacent actual patient assertion.'); } } } foreach ([1, 2, 7, 31] as $size) { $visible = ''; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); foreach (str_split($first, $size) as $chunk) { ++$count; if (!$stream->push($chunk)) throw new RuntimeException('Valid protocol rejected before original bytes forward.'); } ++$count; if ($visible !== $first) throw new RuntimeException('Shared stream changed accepted role protocol bytes.'); $badRole = "切诊:2026-10-23“活动后汗出”({$history})。\n\n"; foreach (str_split($badRole, $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $visible !== $first || !in_array('EXAM_ROLE_MISMATCH', array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('A source-backed history cannot leak through as palpation.'); } } // Heading annotations do not change the acquisition role or patient facts. foreach (['已记录', '补充', '(已记录)', '(补充)', '已记录补充', '(已记录补充)'] as $metadata) { evidenceContractCheck($index, "问诊{$metadata}:2026-10-23“活动后汗出”({$history})。", true); evidenceContractCheck($index, "望诊{$metadata}:2026-10-23“活动后汗出”({$history})。", false, 'EXAM_ROLE_MISMATCH'); } foreach (['阴性', '(阴性)', '(阴性)', '已记录阴性', '阴性补充', '补充(阴性)', '(已记录阴性)', '已记录(阴性)补充'] as $metadata) { evidenceContractCheck($index, "问诊{$metadata}:2026-10-23“无胸痛”({$history})。", true); evidenceContractCheck($index, "望诊{$metadata}:2026-10-23“无胸痛”({$history})。", false, 'EXAM_ROLE_MISMATCH'); evidenceContractCheck($index, "问诊{$metadata}:2026-10-22“无胸痛”({$history})。", false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, "问诊{$metadata}:2026-10-23“无胸痛”(s999)。", false, 'SOURCE_ANNOTATION_INVALID'); evidenceContractCheck($index, "问诊{$metadata}:2026-10-23“无胸痛”({$history}),“活动后汗出”({$history})。", false, 'EXAM_NEGATIVE_HEADING_UNSUPPORTED'); evidenceContractCheck($index, "问诊{$metadata}:2026-10-23“无胸痛”({$history}),患者无头晕。", false, 'EXAM_FACT_PROTOCOL_INVALID'); } foreach (['阳性', '(阳性)', '正常', '(正常)', '已记录正常', '正常补充', '(阴性正常)', '阴性阳性', '阴性阴性', '((阴性))', '(阴性', '阴性)', '(阴性,未记录)', '其他', '无异常'] as $metadata) { evidenceContractCheck($index, "问诊{$metadata}:2026-10-23“无胸痛”({$history})。", false, 'EXAM_HEADING_METADATA_UNSUPPORTED'); } evidenceContractCheck($index, '切诊阴性:未记录。', false, 'EXAM_NEGATIVE_HEADING_UNSUPPORTED'); evidenceContractCheck($index, '切诊(阴性):其他触诊未记录,脉象见独立脉象项。', false, 'EXAM_NEGATIVE_HEADING_UNSUPPORTED'); evidenceContractCheck($unknownIndex, "问诊阴性:日期未记录“无”({$unknownId})。", false, 'EXAM_NEGATIVE_HEADING_UNSUPPORTED'); evidenceContractCheck($index, "脉象阴性:2026-10-23“脉细弱,按之无力。”({$pulse})。", false, 'EXAM_NEGATIVE_HEADING_UNSUPPORTED'); evidenceContractCheck($index, "问诊阴性:2026-10-22“活动后汗出。无晕厥。”(s90)。", false, 'EXAM_SOURCE_NOT_CURRENT'); evidenceContractCheck($index, "问诊阴性:2026-10-23“无明显胸痛”({$history})。", false, 'EXAM_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, "问诊(阴性)\n2026-10-23“无胸痛”({$history})。\n“活动后汗出”({$history})。", false, 'EXAM_NEGATIVE_HEADING_UNSUPPORTED'); $generalLedger = Guard::build(['diagnosis_date' => '2026-10-23', 'present_illness' => '食欲一般;无胸痛。', 'tongue' => '舌象正常。']); $generalIndex = Index::build($generalLedger); $generalSource = evidenceSourceId($generalIndex, 'present_illness'); $normalTongue = evidenceSourceId($generalIndex, 'tongue'); evidenceContractCheck($generalIndex, "问诊阴性:2026-10-23“食欲一般”({$generalSource})。", false, 'EXAM_NEGATIVE_HEADING_UNSUPPORTED'); evidenceContractCheck($generalIndex, "问诊阴性:2026-10-23“食欲一般;无胸痛。”({$generalSource})。", true); evidenceContractCheck($generalIndex, "问诊阳性:2026-10-23“食欲一般”({$generalSource})。", false, 'EXAM_HEADING_METADATA_UNSUPPORTED'); evidenceContractCheck($generalIndex, "舌象正常:2026-10-23“舌象正常。”({$normalTongue})。", false, 'EXAM_HEADING_METADATA_UNSUPPORTED'); evidenceContractCheck($generalIndex, "舌象:2026-10-23“舌象正常。”({$normalTongue})。", true); // Use the production pure snapshot projection and sanitization, preserving // the exact sN mapping of the saved v43r4 rejected model units. $savedSnapshot = (new ReflectionMethod(\app\adminapi\logic\tcm\PatientAiReportLogic::class, 'buildSourceSnapshotFromRows')) ->invoke(null, CompleteTcmCase::rows()); $savedSubmission = (new ReflectionMethod(\app\adminapi\logic\tcm\PatientAiReportLogic::class, 'sanitizeSnapshotForUpstream')) ->invoke(null, AiRequiredSource::submission($savedSnapshot)); $savedLedger = Guard::build(CompleteTcmCase::current(), $savedSubmission); $savedIndex = Index::build($savedLedger); $savedUnits = [ '问诊阴性:2026-10-23“无恶寒发热,无高热烦渴、汗出淋漓或意识异常。”(s2)。“无明显五心烦热、潮热盗汗,无畏寒肢冷。”(s2)。“无咳嗽咯痰、胸痛心悸、浮肿或腹泻。”(s2)。“合成病史:2型糖尿病4年;无已知冠心病、心衰或慢性肾病。”(s4)。' . "\n\n", '问诊阴性:2026-10-23“无恶寒发热,无高热烦渴、汗出淋漓或意识异常。”(s2)。“无明显五心烦热、潮热盗汗,无畏寒肢冷。”(s2)。“无咳嗽咯痰、胸痛心悸、浮肿或腹泻。”(s2)。“合成病史:2型糖尿病4年;无已知冠心病、心衰或慢性肾病。”(s4)。“本次询问无已知药物过敏史”(s5)。' . "\n\n", ]; $savedRejectionsPath = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/tcm_complete_v43r4_single_openai_20261024_0_openai.rejected.json'; if (is_file($savedRejectionsPath)) { $savedRejections = json_decode((string) file_get_contents($savedRejectionsPath), true); ++$count; if (array_column($savedRejections['rejections'] ?? [], 'unit') !== $savedUnits) throw new RuntimeException('Portable v43r4 unit bytes differ from the retained original artifact.'); } $savedPrefix = "一、四诊\n\n望诊:2026-10-23“神疲、面色少华”(s3)。\n\n闻诊:2026-10-23“语声低微,无喘促及异常气味”(s3)。\n\n" . '问诊:2026-10-23“乏力、动则气短、自汗、口干3个月。”(s1)。“合成接诊记录:神疲乏力,轻微活动后气短,白天自汗、活动后加重,口干欲饮、少量频饮。”(s2)。“食欲一般,大便稍干、每日1次,小便量较以往略多、夜尿1次。”(s2)。“此次症状逐渐出现,无明显急性感染诱因;睡眠一般,未记录腰酸耳鸣或固定刺痛。”(s2)。' . "\n\n"; foreach ($savedUnits as $savedUnit) { evidenceContractCheck($savedIndex, $savedPrefix . $savedUnit, true); $savedOriginal = $savedUnit; foreach ([1, 2, 7, 31] as $size) { $visible = ''; $stream = new \app\adminapi\service\TcmClinicalReportStream($savedLedger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $savedIndex); foreach (str_split($savedPrefix . $savedUnit, $size) as $chunk) { if (!$stream->push($chunk)) throw new RuntimeException('Exact v43r4 negative subgroup failed the actual pre-forward gate.'); } ++$count; if (!$stream->finish() || $stream->rejected() || $visible !== $savedPrefix . $savedOriginal || $stream->acceptedContent() !== $visible || $savedUnit !== $savedOriginal) { throw new RuntimeException('Heading compatibility changed original source/report bytes.'); } } } $badHeadingUnits = [ '望诊阴性:2026-10-23“无胸痛”(' . $history . ')。' . "\n\n" => 'EXAM_ROLE_MISMATCH', '问诊阴性:2026-10-22“无胸痛”(' . $history . ')。' . "\n\n" => 'EVIDENCE_SOURCE_DATE_MISMATCH', '问诊阴性:2026-10-23“活动后汗出”(' . $history . ')。' . "\n\n" => 'EXAM_NEGATIVE_HEADING_UNSUPPORTED', '脉象阴性:2026-10-23“脉细弱,按之无力。”(' . $pulse . ')。' . "\n\n" => 'EXAM_NEGATIVE_HEADING_UNSUPPORTED', '问诊阴性:2026-10-23“无胸痛”(' . $history . '),患者无头晕。' . "\n\n" => '', '问诊正常:2026-10-23“无胸痛”(' . $history . ')。' . "\n\n" => 'EXAM_HEADING_METADATA_UNSUPPORTED', ]; foreach ($badHeadingUnits as $unit => $expectedCode) foreach ([1, 2, 7, 31] as $size) { $visible = ''; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); foreach (str_split($first, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Valid prefix failed in heading adversarial test.'); foreach (str_split($unit, $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $stream->finish() || $visible !== $first || ($expectedCode !== '' && !in_array($expectedCode, array_column($stream->issues(), 'code'), true))) { throw new RuntimeException('Heading metadata waived or forwarded an invalid role/date/polarity/adjacent assertion.'); } } // Bounded diagnostic evidence absence is distinct from a patient-feature absence. $evidenceAbsencePhrases = ['现有资料没有表证的独立阳性依据。', '没有气虚证的充分证据。', '没有寒热属性判断的明确依据。', '没有脏腑定位的直接依据。', '当前没有辨证结论的独立依据。', '没有证型的依据。', '没有表证的独立阳性依据支持这一推断。']; foreach ($evidenceAbsencePhrases as $phrase) evidenceContractCheck($index, $first . "二、八纲\n" . $phrase, true); foreach (['没有口干的证据。', '没有舌红的独立阳性依据。', '患者没有胸痛。', '没有心悸证据。', '没有患者无口干的表证的独立阳性依据。', '没有患者有胸痛诊断的充分依据。', '没有存在胸痛证的独立依据。', '没有口干属于热证的明确证据。', '没有是否表证的依据。', '没有表证的独立阳性依据性皮疹。', '没有表证的独立阳性依据无头晕。', '没有表证的证据证明患者无头晕。', '没有表证的独立阳性依据,患者无头晕。', '没有表证的独立阳性依据,否认头晕。', '没有表证的依据,未见红绛。'] as $actualAssertion) { evidenceContractCheck($index, $first . "二、八纲\n" . $actualAssertion, false, 'UNQUOTED_PATIENT_DENIAL'); } $evidenceAbsenceHead = '现有资料没有表证的独立阳性依据;'; evidenceContractCheck($index, $first . "二、八纲\n" . $evidenceAbsenceHead . '“无胸痛”(s999)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $first . "二、八纲\n" . $evidenceAbsenceHead . '“无胸痛”(' . $mixed . ')。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $first . "二、八纲\n" . $evidenceAbsenceHead . '2026-10-22“无胸痛”(' . $history . ')。', false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, $first . "二、八纲\n" . $evidenceAbsenceHead . '2026-10-23“无胸痛”(' . $history . ')。', true); // Exact v43r7 units: the extra first-section prose remains rejected; the // evidence-noun statement after the complete first section is accepted intact. $r7Units = [ '问诊:2026-10-23“此次症状逐渐出现,无明显急性感染诱因;睡眠一般,未记录腰酸耳鸣或固定刺痛。”(s2)。婚姻、体重变化及详细饮水总量未记录。' . "\n\n", '表里:现有资料没有表证的独立阳性依据;症状持续3个月并涉及汗、饮、便、尿(s1、s2),提示内在功能失调倾向,但定位为里证仍需结合病程及脏腑证据复核。' . "\n\n", ]; $r7RejectionsPath = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/tcm_complete_v43r7_single_openai_20261024_0_openai.rejected.json'; if (is_file($r7RejectionsPath)) { $r7Rejections = json_decode((string) file_get_contents($r7RejectionsPath), true); ++$count; if (array_column($r7Rejections['rejections'] ?? [], 'unit') !== $r7Units) throw new RuntimeException('Portable v43r7 units differ from the retained original bytes.'); } $savedTongueId = evidenceSourceId($savedIndex, 'tongue'); $savedPulseId = evidenceSourceId($savedIndex, 'pulse'); $r7First = $savedPrefix . "切诊:其他触诊未记录,脉象见独立脉象项。\n\n" . '舌象:2026-10-23“' . $savedIndex['sources'][$savedTongueId]['quote'] . '”(' . $savedTongueId . ')。' . "\n\n" . '脉象:2026-10-23“' . $savedIndex['sources'][$savedPulseId]['quote'] . '”(' . $savedPulseId . ')。' . "\n\n"; evidenceContractCheck($savedIndex, $savedPrefix . $r7Units[0], false, 'EXAM_FACT_PROTOCOL_INVALID'); evidenceContractCheck($savedIndex, $r7First . "二、八纲\n\n" . $r7Units[1], true); evidenceContractCheck($savedIndex, $savedPrefix . $r7Units[1], false, 'EXAM_FACT_PROTOCOL_INVALID'); foreach ([1, 2, 7, 31] as $size) { $visible = ''; $stream = new \app\adminapi\service\TcmClinicalReportStream($savedLedger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $savedIndex); foreach (str_split($savedPrefix, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Saved first-section prefix rejected.'); foreach (str_split($r7Units[0], $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $stream->finish() || $visible !== $savedPrefix || !in_array('EXAM_FACT_PROTOCOL_INVALID', array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('Evidence-noun correction weakened the first-section quote-only boundary.'); } $visible = ''; $wire = $r7First . "二、八纲\n\n" . $r7Units[1]; $stream = new \app\adminapi\service\TcmClinicalReportStream($savedLedger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $savedIndex); foreach (str_split($wire, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Exact diagnostic evidence-absence unit rejected before forward.'); ++$count; if (!$stream->finish() || $stream->rejected() || $visible !== $wire || $stream->acceptedContent() !== $wire) { throw new RuntimeException('Diagnostic evidence-absence compatibility changed original report bytes.'); } } // The source-state guard still sees all neighbouring patient predicates. $absenceAdjacentUnits = [ $evidenceAbsenceHead . '患者有胸痛。' => 'NEGATIVE_TO_POSITIVE', $evidenceAbsenceHead . '患者无口干。' => 'POSITIVE_TO_NEGATIVE', $evidenceAbsenceHead . '患者无头晕。' => 'UNKNOWN_TO_NEGATIVE', $evidenceAbsenceHead . '胸痛未记录。' => 'NEGATIVE_TO_UNKNOWN', $evidenceAbsenceHead . '舌质红绛。' => 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', $evidenceAbsenceHead . '脉滑。' => 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', ]; foreach ($absenceAdjacentUnits as $unit => $expectedCode) foreach ([1, 2, 7, 31] as $size) { $visible = ''; $prefix = $first . "二、八纲\n\n"; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); foreach (str_split($prefix, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Valid prefix failed the adjacent evidence-absence test.'); foreach (str_split($unit . "\n\n", $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $stream->finish() || $visible !== $prefix || !in_array($expectedCode, array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('Diagnostic evidence absence waived a neighbouring source-state or examination assertion.'); } } // Diagnostic lists have a positive noun grammar, not a blacklist of the // current fixture's symptoms. Modifiers may sit on either side of the list. $diagnosticLists = ['阳虚、阴虚或阳亢', '气虚与血虚', '气阴两虚及阴阳两虚', '表证、热证与阴虚火旺证', '脏腑定位以及辨证结论']; foreach (['无', '没有'] as $operator) foreach (['', '足够', '完整独立'] as $beforeList) foreach (['', '独立', '充分阳性'] as $afterList) foreach ($diagnosticLists as $list) { evidenceContractCheck($index, $first . "二、八纲\n现有资料" . $operator . $beforeList . $list . '的' . $afterList . '证据。', true); } evidenceContractCheck($index, $first . "二、八纲\n没有足够气虚或血虚独立证据。", true); evidenceContractCheck($index, $first . "二、八纲\n无独立阴虚依据。", true); $listEvidenceHead = '且无足够阳虚、阴虚或阳亢的独立证据'; foreach (['无足够口干或舌红的独立证据。', '没有口干、阳虚的独立证据。', '无足够阴虚、舌红或阳亢的证据。', '无阳虚、没有口干的证据。', '无足够阳虚、患者无口干证的独立证据。', '没有阴虚、患者有胸痛诊断的证据。', '无足够阴虚与存在胸痛证的独立证据。', '没有是否阴虚或阳虚的证据。', $listEvidenceHead . '性皮疹。', $listEvidenceHead . '证明患者无头晕。', $listEvidenceHead . ',患者无口干。', $listEvidenceHead . '且否认头晕。', '无' . implode('、', array_fill(0, 9, '阳虚')) . '的独立证据。', '没有' . str_repeat('甲', 17) . '证的独立依据。'] as $notEvidenceOnly) { evidenceContractCheck($index, $first . "二、八纲\n" . $notEvidenceOnly, false, 'UNQUOTED_PATIENT_DENIAL'); } evidenceContractCheck($index, $first . "二、八纲\n" . $listEvidenceHead . ';“无胸痛”(s999)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $first . "二、八纲\n" . $listEvidenceHead . ';“无胸痛”(' . $mixed . ')。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $first . "二、八纲\n" . $listEvidenceHead . ';2026-10-22“无胸痛”(' . $history . ')。', false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, $first . "二、八纲\n" . $listEvidenceHead . ';本次“无晕厥”(s90)。', false, 'EXAM_SOURCE_NOT_CURRENT'); $r9EpistemicUnit = '阴阳:本次虚象较明确,但虚证不能单独推出阴证。寒热均未定,且无足够阳虚、阴虚或阳亢的独立证据,故阴阳总纲现有资料不足以确定。' . "\n\n"; $r9RejectionPath = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/tcm_complete_v43r9_single_openai_20261024_0_openai.rejected.json'; if (is_file($r9RejectionPath)) { $r9Rejections = json_decode((string) file_get_contents($r9RejectionPath), true); ++$count; if (($r9Rejections['rejections'][0]['unit'] ?? '') !== $r9EpistemicUnit) throw new RuntimeException('Portable r9 evidence-list unit differs from the original saved bytes.'); } evidenceContractCheck($savedIndex, $r7First . "二、八纲\n\n" . $r9EpistemicUnit, true); foreach ([1, 2, 7, 31] as $size) { $visible = ''; $wire = $r7First . "二、八纲\n\n" . $r9EpistemicUnit; $stream = new \app\adminapi\service\TcmClinicalReportStream($savedLedger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $savedIndex); foreach (str_split($wire, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Exact r9 evidence-list unit rejected before forward.'); ++$count; if (!$stream->finish() || $stream->rejected() || $visible !== $wire || $stream->acceptedContent() !== $wire) { throw new RuntimeException('The bounded evidence-list grammar changed original report bytes.'); } } // Every adjacent patient assertion remains visible to the shared source-state // guard. This is deliberately a full stream check, not just the noun grammar. $listAdjacent = [ $listEvidenceHead . ',患者有胸痛。' => 'NEGATIVE_TO_POSITIVE', $listEvidenceHead . ',患者无口干。' => 'POSITIVE_TO_NEGATIVE', $listEvidenceHead . ',患者无头晕。' => 'UNKNOWN_TO_NEGATIVE', $listEvidenceHead . ',胸痛未记录。' => 'NEGATIVE_TO_UNKNOWN', $listEvidenceHead . ',舌质红绛。' => 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', $listEvidenceHead . ',脉滑。' => 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', ]; foreach ($listAdjacent as $unit => $expectedCode) foreach ([1, 2, 7, 31] as $size) { $visible = ''; $prefix = $first . "二、八纲\n\n"; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); foreach (str_split($prefix, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Valid prefix failed in evidence-list adjacency test.'); foreach (str_split($unit . "\n\n", $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $stream->finish() || $visible !== $prefix || !in_array($expectedCode, array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('The evidence-list noun phrase waived an adjoining patient assertion.'); } } evidenceContractMetaChecks($index, $first, $history, $mixed); // The saved r10 units are data, not a preferred clinical conclusion. The // production fixture projection above supplies the original source index. $r10Units = [ 'tcm_complete_v43r10_single_openai_20261024_0_openai.rejected.json' => [0, '本例支持点:气短、乏力、自汗与脉弱构成气虚主轴(s1、s10);口干欲饮、少量频饮及舌少津构成津伤兼证(s2、s9)。该方向可同时覆盖益气、生津、敛汗,但对脏腑定位并无额外证明作用。' . "\n\n"], 'tcm_complete_v43r10_supplement_ooc2_20261024_1_openai.rejected.json' => [0, '虚实:神疲、动则气短、自汗、语声低微、面色少华及脉细弱无力(s1、s2、s3、s10)共同支持虚证。尚无足以确立痰、湿、瘀、食积等实邪的阳性证据,不能据此写成“无实邪”。' . "\n\n"], ]; foreach ($r10Units as $file => [$unitIndex, $unit]) { $path = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/' . $file; if (is_file($path)) { $saved = json_decode((string) file_get_contents($path), true); ++$count; if (($saved['rejections'][$unitIndex]['unit'] ?? '') !== $unit) throw new RuntimeException('Portable r10 unit differs from retained original bytes.'); } evidenceContractCheck($savedIndex, $r7First . "二、八纲\n\n" . $unit, true); foreach ([1, 2, 7, 31] as $size) { $visible = ''; $wire = $r7First . "二、八纲\n\n" . $unit; $stream = new \app\adminapi\service\TcmClinicalReportStream($savedLedger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $savedIndex); foreach (str_split($wire, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Exact r10 evidential/meta unit rejected before forward.'); ++$count; if (!$stream->finish() || $stream->rejected() || $visible !== $wire || $stream->acceptedContent() !== $wire) { throw new RuntimeException('The evidential/metalinguistic grammar changed original r10 report bytes.'); } } } $metaAdjacentHeads = ['对脏腑定位并无额外证明作用', '尚无足以确立痰、湿、瘀、食积等实邪的阳性证据', '不能据此写成“无实邪”']; foreach ($metaAdjacentHeads as $metaHead) foreach ([ '患者有胸痛。' => 'NEGATIVE_TO_POSITIVE', '患者无口干。' => 'POSITIVE_TO_NEGATIVE', '患者无头晕。' => 'UNKNOWN_TO_NEGATIVE', '胸痛未记录。' => 'NEGATIVE_TO_UNKNOWN', '舌质红绛。' => 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑。' => 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', ] as $tail => $expectedCode) foreach ([1, 2, 7, 31] as $size) { $visible = ''; $prefix = $first . "二、八纲\n\n"; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); foreach (str_split($prefix, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Valid prefix failed in r10 adjacency control.'); foreach (str_split($metaHead . ',' . $tail . "\n\n", $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $stream->finish() || $visible !== $prefix || !in_array($expectedCode, array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('An evidential/metalinguistic phrase waived an adjacent patient assertion.'); } } evidenceContractInferenceChecks($index, $first, $history, $mixed); $r13Path = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/tcm_complete_v43r12_openai_deadline_20261024_openai_next_full90_openai.rejected.json'; if (is_file($r13Path)) { $r13Saved = json_decode((string) file_get_contents($r13Path), true); ++$count; if (array_column($r13Saved['rejections'] ?? [], 'unit') !== evidenceContractR13Units()) { throw new RuntimeException('Portable r13 units differ from retained original failure bytes.'); } } foreach (evidenceContractR13Units() as $unit) { evidenceContractCheck($savedIndex, $r7First . "二、八纲\n\n" . $unit, true); foreach ([1, 2, 7, 31] as $size) { $wire = $r7First . "二、八纲\n\n" . $unit; $visible = ''; $stream = new \app\adminapi\service\TcmClinicalReportStream($savedLedger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $savedIndex); foreach (str_split($wire, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Exact r13 epistemic unit rejected before forward.'); ++$count; if (!$stream->finish() || $stream->rejected() || $visible !== $wire || $stream->acceptedContent() !== $wire) { throw new RuntimeException('Abstract epistemic/inference handling changed original r13 report bytes.'); } } } foreach (['寒证亦无独立阳性寒象', '不能据此反推纯虚无实'] as $epistemic) foreach ([ '患者有胸痛。' => 'NEGATIVE_TO_POSITIVE', '患者无口干。' => 'POSITIVE_TO_NEGATIVE', '患者无头晕。' => 'UNKNOWN_TO_NEGATIVE', '胸痛未记录。' => 'NEGATIVE_TO_UNKNOWN', '舌质红绛。' => 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑。' => 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', ] as $tail => $expectedCode) foreach ([1, 2, 7, 31] as $size) { $visible = ''; $prefix = $first . "二、八纲\n\n"; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); foreach (str_split($prefix, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Valid prefix failed in r13 assertion-scope control.'); foreach (str_split($epistemic . ',' . $tail . "\n\n", $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $stream->finish() || $visible !== $prefix || !in_array($expectedCode, array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('An abstract epistemic/inference role waived a neighbouring patient assertion.'); } } evidenceContractFutureConditionChecks($index, $first, $history, $mixed); $r14Path = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/tcm_complete_v43r13_single_openai_20261024_0_openai.rejected.json'; if (is_file($r14Path)) { $r14Saved = json_decode((string) file_get_contents($r14Path), true); $r14Unit = $r14Saved['rejections'][1]['unit'] ?? ''; ++$count; if (!is_string($r14Unit) || !str_contains($r14Unit, evidenceContractR14Condition())) throw new RuntimeException('Saved r14 applicability unit differs from expected original bytes.'); evidenceContractCheck($savedIndex, $r7First . $r14Unit, true); foreach ([1, 2, 7, 31] as $size) { $wire = $r7First . $r14Unit; $visible = ''; $stream = new \app\adminapi\service\TcmClinicalReportStream($savedLedger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $savedIndex); foreach (str_split($wire, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Original future-applicability unit rejected before forwarding.'); ++$count; if (!$stream->finish() || $stream->rejected() || $visible !== $wire || $stream->acceptedContent() !== $wire) { throw new RuntimeException('Future-applicability compatibility changed original report bytes.'); } } } foreach ([ '患者有胸痛。' => 'NEGATIVE_TO_POSITIVE', '患者无口干。' => 'POSITIVE_TO_NEGATIVE', '患者无头晕。' => 'UNKNOWN_TO_NEGATIVE', '胸痛未记录。' => 'NEGATIVE_TO_UNKNOWN', '舌质红绛。' => 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑。' => 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', ] as $assertion => $expectedCode) foreach ([true, false] as $before) foreach ([1, 2, 7, 31] as $size) { $visible = ''; $prefix = $first . "六、中药方药及复核监测\n\n"; $unit = ($before ? $assertion . evidenceContractR14Condition() : evidenceContractR14Condition() . $assertion) . "\n\n"; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); foreach (str_split($prefix, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Valid prefix failed in future-applicability adjacency control.'); foreach (str_split($unit, $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $stream->finish() || $visible !== $prefix || !in_array($expectedCode, array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('A future applicability condition waived a preceding or following actual assertion.'); } } // Shared source/stream checks are intentionally separate from --pure-meta. evidenceContractR15InferenceChecks($index, $first, $history, $mixed); foreach (evidenceContractR15Units() as $unit) { evidenceContractCheck($savedIndex, $r7First . "二、八纲\n\n" . $unit, true); foreach ([1, 2, 7, 31] as $size) { $wire = $r7First . "二、八纲\n\n" . $unit; $visible = ''; $stream = new \app\adminapi\service\TcmClinicalReportStream($savedLedger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $savedIndex); foreach (str_split($wire, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Exact r15 category inference/support unit rejected before forwarding.'); ++$count; if (!$stream->finish() || $stream->rejected() || $visible !== $wire || $stream->acceptedContent() !== $wire) { throw new RuntimeException('Category inference/support compatibility changed original model bytes.'); } } } foreach (['不能仅据虚证直接判定全属虚而无实', '亦无支持阳证的独立阳性组合'] as $phrase) foreach ([ '患者有胸痛。' => 'NEGATIVE_TO_POSITIVE', '患者无口干。' => 'POSITIVE_TO_NEGATIVE', '患者无头晕。' => 'UNKNOWN_TO_NEGATIVE', '胸痛未记录。' => 'NEGATIVE_TO_UNKNOWN', '舌质红绛。' => 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑。' => 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', ] as $assertion => $expectedCode) foreach ([true, false] as $before) foreach ([1, 2, 7, 31] as $size) { $visible = ''; $prefix = $first . "二、八纲\n\n"; $unit = ($before ? $assertion . $phrase : $phrase . ',' . $assertion) . "\n\n"; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); foreach (str_split($prefix, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Valid prefix failed in r15 factual-adjacency control.'); foreach (str_split($unit, $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $stream->finish() || $visible !== $prefix || !in_array($expectedCode, array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('A bounded category inference/support role waived a neighbouring patient assertion.'); } } echo "Clinical evidence contract: {$count} assertions passed.\n"; /** Retained model units are regression data, not preferred clinical conclusions. */ function evidenceContractR17Units(): array { return [ ['tcm_complete_v43r16_supplement_ooc2_20261024_1_openai.rejected.json', 0, "二、八纲\n", '表里:现有资料未见足以独立确定表证或里证的阳性定位依据。病程3个月仅提示迁延,不能单独判里;表里均尚不能定性。' . "\n\n"], ['tcm_complete_v43r16_supplement_ooc2_20261024_1_openai.rejected.json', 1, "五、治法\n", '生津针对口干少量频饮及舌面少津(s2、s9),居兼治位;不采用温阳、清热、滋阴降火或活血等尚无独立证据的限定治法。' . "\n\n"], ['tcm_complete_v43r16_openai_deadline_20261024_openai_next_full90_openai.rejected.json', 0, "二、八纲\n", '寒热:口干、少津、苔少及大便稍干(s2、s9)可提示津少,尚不足单独成立热证;寒象亦无本次独立阳性证据。寒热属性现有资料不足以确定。' . "\n\n"], ]; } /** Can run through a shutdown callback after existing --pure-meta exits, with no peers loaded. */ function evidenceContractR17MetaChecks(array $index, string $first, string $denialId, string $wrongId): void { $head = $first . "二、八纲\n"; $evidence = '现有资料未见足以独立确定表证或里证的阳性定位依据'; $withheld = '不采用温阳、清热、滋阴降火或活血等尚无独立证据的限定治法'; foreach ([$evidence . '。', $withheld . '。', '现有证据未见足以直接确定寒证或热证的独立辨证依据。', '未见足以明确支持阴虚证及气虚证的充分诊断证据。', '没有足以独立确认表证的阳性定位证据。', '无足以充分确定里证的独立分型依据。', '不选用温补、清解等仍没有可靠依据的具体治疗方法。', '故不予采用活血与温阳等尚无充分证据的特定治则。', '暂不采用养津等无独立证据的治法。'] as $phrase) { evidenceContractCheck($index, $head . $phrase, true); } foreach (['患者未见口干。', '本次未见舌红。', '查见未见口干、舌红。', '未见足以独立确定口干或舌红的阳性定位依据。', '未见足以独立确定患者口干证的阳性定位依据。', '未见足以独立确定患者无口干证的阳性定位依据。', '未见足以独立确定本次有胸痛诊断的阳性定位依据。', '未见足以独立确定表证未记录的阳性定位依据。', '未见足以独立确定口干、舌红的证据。', '未见足以独立确定表证。口干诊断的阳性定位依据。', '未见足以独立确定表证;口干诊断的阳性定位依据。', '未见足以独立确定表证的阳性口干。', $evidence . '性皮疹。', '否认足以独立确定表证的阳性定位依据。', '有无足以独立确定表证的阳性定位依据。', '不采用温阳、患者无口干等尚无独立证据的限定治法。', '不采用温阳、无胸痛等尚无独立证据的限定治法。', '不采用温阳等尚无独立证据的限定口干。', '不采用温阳等尚无独立证据的限定治法是患者无口干依据。', '并非不采用温阳等尚无独立证据的限定治法。', '患者不采用温阳等尚无独立证据的限定治法。', '不采用' . str_repeat('甲', 13) . '等尚无独立证据的限定治法。', '不采用' . implode('、', array_fill(0, 9, '甲')) . '等尚无独立证据的限定治法。', '不采用温阳等尚未见独立证据的限定治法。'] as $assertion) { evidenceContractCheck($index, $head . $assertion, false, 'UNQUOTED_PATIENT_DENIAL'); } foreach ([$evidence, $withheld] as $phrase) { foreach ([',患者无口干。', '且否认头晕。', '而本次未见舌红。', ';没有相互作用。'] as $tail) { evidenceContractCheck($index, $head . $phrase . $tail, false, 'UNQUOTED_PATIENT_DENIAL'); } evidenceContractCheck($index, $head . '患者无口干;' . $phrase . '。', false, 'UNQUOTED_PATIENT_DENIAL'); evidenceContractCheck($index, $head . $phrase . ';“无胸痛”(s999)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $head . $phrase . ';“无胸痛”(' . $wrongId . ')。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($index, $head . $phrase . ';2026-10-22“无胸痛”(' . $denialId . ')。', false, 'EVIDENCE_SOURCE_DATE_MISMATCH'); evidenceContractCheck($index, $head . $phrase . ';本次2026-10-22“无晕厥”(s90)。', false, 'EXAM_SOURCE_NOT_CURRENT'); evidenceContractCheck($index, $head . $phrase . ';2026-10-23“无胸痛”(' . $denialId . ')。', true); evidenceContractCheck($index, "一、四诊\n问诊:" . $phrase . '。', false, 'EXAM_FACT_PROTOCOL_INVALID'); } $qualified = $index; $qualified['sources']['s88'] = ['quote' => '无明显畏寒、肢冷。', 'scope' => 'current', 'date' => '2026-10-23', 'record_state' => 'negative', 'spans' => [['role' => 'interview', 'recorded' => true, 'quote' => '无明显畏寒、肢冷。']]]; $qualified['sources']['s89'] = ['quote' => '无头晕。', 'scope' => 'current', 'date' => '2026-10-23', 'record_state' => 'unknown', 'spans' => [['role' => 'interview', 'recorded' => false, 'quote' => '无头晕。']]]; foreach ([$evidence, $withheld] as $phrase) { evidenceContractCheck($qualified, $head . $phrase . ';2026-10-23“无明显畏寒、肢冷。”(s88)。', true); evidenceContractCheck($qualified, $head . $phrase . ';2026-10-23“无畏寒、肢冷。”(s88)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); evidenceContractCheck($qualified, $head . $phrase . ';2026-10-23“无头晕。”(s89)。', false, 'DENIAL_SOURCE_QUOTE_INVALID'); } foreach (['diagnosticEvidenceAbsence' => $evidence, 'withheldMethodEvidenceAbsence' => $withheld] as $method => $phrase) { foreach (['患者有胸痛。', '患者无口干。', '舌质红绛。', '脉滑。', '现用药未记录。'] as $assertion) { foreach ([$assertion . ';' . $phrase, $phrase . ',' . $assertion] as $text) { $masked = (new ReflectionMethod(Contract::class, $method))->invoke(null, $text); ++$GLOBALS['count']; if (!str_contains($masked, $assertion)) throw new RuntimeException('Diagnostic meta role consumed an adjoining factual predicate.'); } } } $methodInput = '不采用温阳、患者无口干等尚无独立证据的限定治法'; $methodMasked = (new ReflectionMethod(Contract::class, 'withheldMethodEvidenceAbsence'))->invoke(null, $methodInput); ++$GLOBALS['count']; if ($methodMasked !== str_replace('尚无独立', '尚' . str_repeat(' ', strlen('无')) . '独立', $methodInput)) { throw new RuntimeException('Withheld-method grammar must mask only its evidence operator, retaining actual denials in the list.'); } foreach (evidenceContractR17Units() as [$file, $unitIndex, $section, $unit]) { $path = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/' . $file; if (is_file($path)) { $saved = json_decode((string) file_get_contents($path), true); ++$GLOBALS['count']; if (($saved['rejections'][$unitIndex]['unit'] ?? '') !== $unit) throw new RuntimeException('Portable r17 unit differs from retained original model bytes.'); } evidenceContractCheck($index, $first . $section . $unit, true); } } // Appended-only r17 coverage: existing expectations and pure-mode entry remain unchanged. evidenceContractR17MetaChecks($index, $first, $history, $mixed); foreach (evidenceContractR17Units() as [$file, $unitIndex, $section, $unit]) { evidenceContractCheck($savedIndex, $r7First . $section . $unit, true); foreach ([1, 2, 7, 31] as $size) { $wire = $r7First . $section . "\n" . $unit; $visible = ''; $stream = new \app\adminapi\service\TcmClinicalReportStream($savedLedger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $savedIndex); foreach (str_split($wire, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Exact r17 diagnostic meta unit rejected before forwarding.'); ++$count; if (!$stream->finish() || $stream->rejected() || $visible !== $wire || $stream->acceptedContent() !== $wire) { throw new RuntimeException('Diagnostic meta handling changed retained original model bytes.'); } } } foreach (['现有资料未见足以独立确定表证或里证的阳性定位依据', '不采用温阳、清热、滋阴降火或活血等尚无独立证据的限定治法'] as $phrase) foreach ([ '患者有胸痛。' => 'NEGATIVE_TO_POSITIVE', '患者无口干。' => 'POSITIVE_TO_NEGATIVE', '患者无头晕。' => 'UNKNOWN_TO_NEGATIVE', '胸痛未记录。' => 'NEGATIVE_TO_UNKNOWN', '舌质红绛。' => 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑。' => 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', ] as $assertion => $expectedCode) foreach ([true, false] as $before) foreach ([1, 2, 7, 31] as $size) { $visible = ''; $prefix = $first . "二、八纲\n\n"; $unit = ($before ? $assertion . $phrase : $phrase . ',' . $assertion) . "\n\n"; $stream = new \app\adminapi\service\TcmClinicalReportStream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, '', null, $index); foreach (str_split($prefix, $size) as $chunk) if (!$stream->push($chunk)) throw new RuntimeException('Valid prefix failed in r17 diagnostic meta adjacency control.'); foreach (str_split($unit, $size) as $chunk) if (!$stream->push($chunk)) break; ++$count; if (!$stream->rejected() || $stream->finish() || $visible !== $prefix || !in_array($expectedCode, array_column($stream->issues(), 'code'), true)) { throw new RuntimeException('A diagnostic meta role waived a neighbouring clinical assertion.'); } } echo "Clinical evidence contract including appended r17 checks: {$count} assertions passed.\n";