From 62e0eff21999eebfc9aecbfe99edb14d9ccf716f Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Sun, 13 Sep 2026 14:34:17 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20delimiter=20=D1=80=D0=B5=D0=B7=D1=83=D0=BB=D1=8C?= =?UTF-8?q?=D1=82=D0=B0=D1=82=D0=B0=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20?= =?UTF-8?q?=D0=BE=D1=82=20JSON=20(#551)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: #551 User-Visible: no --- .github/workflows/process.yml | 10 +++++----- scripts/mutation-gate.mjs | 11 +++++++++++ test/review-doc-guard.test.mjs | 10 ++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/.github/workflows/process.yml b/.github/workflows/process.yml index 3be57742..ddb522a8 100644 --- a/.github/workflows/process.yml +++ b/.github/workflows/process.yml @@ -1212,11 +1212,11 @@ jobs: (.verdict == "green" or .verdict == "yellow" or .verdict == "red") and (.high | type == "number") and (.medium | type == "number") and (.summary | type == "string")' "$dir/verdict.json" >/dev/null - { - echo 'structured_output<> "$GITHUB_OUTPUT" + printf 'structured_output<> "$GITHUB_OUTPUT" + cat "$dir/verdict.json" >> "$GITHUB_OUTPUT" + # structured_output не обязан оканчиваться LF: delimiter команды + # GitHub должен начинаться с отдельной строки. + printf '\nEOF_RESULT\n' >> "$GITHUB_OUTPUT" # Ревьюер пишет только в docs/reviews/. Что именно попадёт в коммит, # решает этот шаг, а не модель: всё остальное откатывается. diff --git a/scripts/mutation-gate.mjs b/scripts/mutation-gate.mjs index d37126dd..cca30760 100644 --- a/scripts/mutation-gate.mjs +++ b/scripts/mutation-gate.mjs @@ -7869,6 +7869,17 @@ const MUTANT_DEFINITIONS = [ replace: ' printf \'%s\' "$OUT" | jq -e \'true\' > "$RUNNER_TEMP/verdict.json" # mutant: boolean payload', }], }, + { + id: 'review-integration-delimiter-follows-json-without-newline', + guard: 'node --test --test-name-pattern="#551" test/review-doc-guard.test.mjs', + because: '#551: structured verdict записан через printf без финального LF; GitHub output ' + + 'delimiter обязан начинаться с новой строки, иначе integration падает после зелёной модели', + patches: [{ + file: '.github/workflows/process.yml', + find: ' printf \'\\nEOF_RESULT\\n\' >> "$GITHUB_OUTPUT"', + replace: ' printf \'EOF_RESULT\\n\' >> "$GITHUB_OUTPUT" # mutant: delimiter glued to JSON', + }], + }, { id: 'review-integration-skips-evidence-checksum', guard: 'node --test --test-name-pattern="#551" test/review-doc-guard.test.mjs', diff --git a/test/review-doc-guard.test.mjs b/test/review-doc-guard.test.mjs index 231fc1d4..7ba41940 100644 --- a/test/review-doc-guard.test.mjs +++ b/test/review-doc-guard.test.mjs @@ -779,6 +779,16 @@ test('#551: gates, модель и интеграция имеют незави 'jq валидирует сохранённый объект, не записывает boolean предиката вместо него'); assert.doesNotMatch(seal, /jq -e '[\s\S]*> "\$RUNNER_TEMP\/verdict\.json"/, 'stdout предиката jq не становится payload интеграции'); + const resultOutput = integrate.slice( + integrate.indexOf('- name: Проверить полноту и происхождение результата'), + integrate.indexOf('- name: Опубликовать документ ревью'), + ); + assert.match(resultOutput, + /cat "\$dir\/verdict\.json" >> "\$GITHUB_OUTPUT"[\s\S]*printf '\\nEOF_RESULT\\n' >> "\$GITHUB_OUTPUT"/, + 'закрывающий delimiter начинается с новой строки даже у JSON без финального LF'); + assert.doesNotMatch(resultOutput, + /cat "\$dir\/verdict\.json"\n\s+echo 'EOF_RESULT'/, + 'cat без гарантированного LF не приклеивает delimiter к JSON'); assert.match(model, /ref: \$\{\{ needs\.prepare\.outputs\.material_sha \}\}/, 'модель получает exact material, а не подвижную ветку');