Skip to content

Commit ed6ca4e

Browse files
sudoshiruvnet
andcommitted
test(publication): guard against manuscript numeric fabrication (A3 Phase 6)
ADR-0020 Phase 6 risk mitigation: asserts the ManuscriptComposer never prints an effect-estimate-magnitude decimal in the Results section that is absent from the source result_json (allowing only the published config thresholds and the gate's reported SMD value). The allowed set is derived programmatically from the execution payload, so the guard tracks the composer rather than a frozen literal. Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 8fd842c commit ed6ca4e

1 file changed

Lines changed: 26 additions & 0 deletions

File tree

backend/tests/Feature/Studies/ManuscriptComposerTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use App\Enums\GateStatus;
4+
use App\Models\App\AnalysisExecution;
45
use App\Models\App\EstimationAnalysis;
56
use App\Models\App\Source;
67
use App\Models\App\Study;
@@ -98,6 +99,31 @@ function abbyManuscriptStudy(User $user, string $gateStatus): Study
9899
->and($doc['manuscript_meta']['effect_estimates_included'])->toBeFalse();
99100
});
100101

102+
it('never prints an effect-estimate number absent from the source result payload', function () {
103+
// ADR-0020 Phase 6 safety property: the composer pulls numbers from result
104+
// payloads and must never fabricate one. Every estimate-magnitude decimal in
105+
// the Results section must come from the source result_json (plus the
106+
// published config thresholds and the gate's reported SMD value).
107+
$user = User::factory()->create();
108+
$study = abbyManuscriptStudy($user, GateStatus::Passed->value);
109+
110+
$doc = app(ManuscriptComposer::class)->compose($study);
111+
$results = (string) collect($doc['sections'])->firstWhere('key', 'results')['content'];
112+
113+
$execution = AnalysisExecution::query()->latest('id')->firstOrFail();
114+
preg_match_all('/\d+\.\d+/', (string) json_encode($execution->result_json), $payloadNums);
115+
$allowed = array_merge(
116+
$payloadNums[0],
117+
['0.80', '0.10', '0.30', '0.312'], // config thresholds + the gate's SMD reason value
118+
);
119+
120+
preg_match_all('/\d+\.\d+/', $results, $emitted);
121+
$fabricated = array_values(array_unique(array_diff($emitted[0], $allowed)));
122+
123+
expect($fabricated)->toBe([]);
124+
expect($results)->toContain('1.47'); // the real calibrated estimate is still reported
125+
});
126+
101127
it('emits descriptive subsections and reports each contrast by its own diagnostics', function () {
102128
$user = User::factory()->create();
103129
$study = Study::create([

0 commit comments

Comments
 (0)