В разборе объяснения чаще всего виден готовый рецепт: «оберни массив в Promise.all», но отсутствуют условия, при которых результат нужен и безопасен. Цена ошибки — hand-off с неявной ложью об отмене: следующий инженер копирует фрагмент в другую границу и тратит время на последствия, которые текст обещал убрать.
Причина в том, что обратная связь проверяет наличие API-имени, а не причинную модель. Проверка должна быть короткой: есть ли исходная задача, модель aggregate, контрпример и ответ про отмену. Действие — вернуть не оценку личности, а fixed synthetic карточку с одним конкретным пробелом и упражнением на его закрытие.
Полевая ситуация без реальных людей
Возьмём fixed attempt recipe-only-v1. В нём задача названа, но поля model и counterexample пусты; ответ не фиксирует границу отмены. Это не запись о кандидате, pull request или учебной группе. Это маленький объект, которым можно воспроизводимо проверить саму форму объяснения. Его ценность в том, что другой редактор получит те же причины, а не будет угадывать настроение автора.
| Сигнал | Диагноз | Короткое упражнение | Результат |
|---|---|---|---|
| есть только await Promise.all | missing-model | назвать aggregate и inputs | return-with-specific-gap |
| нет оставшегося input | missing-counterexample | прочитать trace из двух строк | return-with-specific-gap |
| «всё отменяется» | wrong-boundary | найти отдельный cancel contract | return-with-specific-gap |
| все три части есть | модель проверяема | передать ограничение | synthetic-ready-for-human-review |
Исполняемая карточка проверки
import { createFixedTeachingAttempt, checkFixedTeachingAttempt } from './upgrade-2025-10.mjs';
const attempt = createFixedTeachingAttempt('recipe-only-v1');
const report = checkFixedTeachingAttempt(attempt);
console.log(report.reasons);
// ['missing-model', 'missing-counterexample', 'wrong-boundary']
Проверка намеренно не принимает произвольный объект как доказательство. Она принимает только frozen fixed literals этого модуля. Так не возникает видимости, будто мы валидируем реальные объяснения или строим универсальную систему оценки. В ответе есть effect: no-system-change; функция не создаёт задачу, не пишет комментарий и не меняет состояние вне памяти.
Как дать feedback, который можно выполнить
Неполезный feedback: «раскрой тему глубже». Он не называет наблюдение и даёт автору слишком много вариантов. Полезный feedback: «в строке с Promise.all добавь модель aggregate и один контрпример: после aggregate reject remaining input может завершиться; затем назови, какой отдельный contract отвечает за отмену». В нём есть объект, условие, ожидаемое изменение и граница, которую нельзя додумать автоматически.
Короткая последовательность разбора
- Зафиксируйте исходную задачу одной фразой, не начиная с имени API.
- Найдите модель: входы, aggregate и зависимый расчёт.
- Попросите один контрпример, который ломает слишком широкий рецепт.
- Запустите fixed exercise и сравните trace с выводом автора.
- Верните только конкретные gaps или synthetic hand-off для human review; не объявляйте результат доказательством обучения.
Когда рецепт допустим
Рецепт полезен, когда модель уже названа и условия узкие. Например, после объяснения можно показать await Promise.all([left, right]) как способ дождаться обоих значений перед зависимым расчётом. Но рядом остаётся ограничение: этот вызов не описывает отмену внешних действий и не выбирает обработку каждого результата. Если нужны все outcomes, вопрос меняется, и читатель должен сравнить его с Promise.allSettled, а не механически заменить имя.
Проверяемый следующий шаг
Проверьте у собственного объяснения только четыре предложения: задача, модель, контрпример, отдельный владелец отмены. Если одно отсутствует, не добавляйте ещё один фрагмент кода — заполните конкретный пробел. Положительный исход этого упражнения ограничен: model-first-v1 получает synthetic-ready-for-human-review. Он не доказывает улучшение человека, команды или production-кода.
Три вопроса для независимого читателя
Независимый читатель не должен угадывать намерение автора. Поэтому после feedback дайте ему три вопроса без подсказки: «что вернёт aggregate при первой ошибке?», «что показывает второй элемент trace?» и «какой объект имеет право остановить внешнюю работу?». Ответы проверяют не запоминание имени метода, а наличие границы. Если третий ответ заменяется словом Promise.all, возвращаем только gap wrong-boundary.
Такой способ удобно применять к собственной документации. Он не требует реального класса, оценки кандидата или pull request. Достаточно fixed примера и редакторского чтения. Важна дисциплина формулировки: feedback сообщает, что нужно добавить в текст, а не что автор «не понимает async». Это сокращает спор о тоне и оставляет один проверяемый технический предмет для следующей версии объяснения.
| Можно проверить | Нельзя заключить | Безопасный следующий шаг |
|---|---|---|
| trace имеет два разных события | реальная операция завершилась | описать synthetic модель |
| ответ называет aggregate | автор применит знание везде | добавить контрпример |
| gap конкретен | человек стал сильнее | передать на human review |
| нет side effect | production безопасен | отдельно проектировать API |
Все карточки, попытки, ответы, результаты и hand-off ниже — fixed synthetic JS literals в памяти. Модуль не читает files, сеть, Git, CI, clock, telemetry, production data или user data и не выполняет side effect.
Проверяемые источники
- ECMA-262, 16th edition (June 2025), §27.2.4.1 Promise.all — версия: 16th edition, June 2025, immutable published PDF. Алгоритм Promise.all создаёт одну capability, обходит iterable и передаёт его элементы в PerformPromiseAll; результат завершается ошибкой через reject capability. Граница: Спецификация описывает семантику языка. Она не проектирует отмену работы, timeout, retry или API конкретного приложения.
- MDN Promise.all(), immutable content commit 3fad0447 (19 August 2025) — версия: mdn/content commit 3fad0447b4901e28fe88769976787d8d8b87d66d, 2025-08-19. Promise.all выполняется успешно после выполнения всех входов либо отклоняется на первой ошибке; результат успеха сохраняет порядок входного iterable, а allSettled ждёт все исходы. Граница: Документация не утверждает, что Promise.all отменяет уже начатую внешнюю работу или заменяет протокол остановки.