Skip to content

Status: advisory. It states what happened and recommends; it decides nothing, amends no frozen section and changes no task state. Relates to: TFW-60 Phase AA/AB — the first field evidence for an update within the 2.0.0 line, and the first consumer-side confirmation that the actor removal at .3 closes the defect report two reported. Payload taken at: tag v2.0.0-dirty.4 = 51677ff0 on D:/projects/research/steps-framework. Tag verified against the pinned commit before VERSION was trusted and re-verified after the copy; source .tfw/ clean at both checks. installed_from recorded. Committed: 01ec75b in the receiving project — 27 payload files, 11 adapter files, 1 project document, matching the 27-file payload delta between the two tags exactly.


Разбор полётов: обновление TFW 2.0.0-dirty.2 → 2.0.0-dirty.4

Проект: innoforce-ai-first. Дата: 2026-08-30. Прыжок через тег: .3 не устанавливался. Исполнитель: сессия Claude Code от имени saubakirov, режим CL — владелец отвечал по ходу. Предыдущее обновление того же проекта — FIELD-REPORT__TFW-60__second_external_update.md.

Одной строкой

Инкрементальное обновление стоит порядка пятнадцати команд и одного ручного слияния — и это первый раз, когда фреймворк развернул инструкцию, по которой получатель уже сработал, а процедура обновления этого не заметила. На .2 адаптерный README требовал: «Commands never duplicate workflow content — they reference it». Я это исполнил, переписал двенадцать команд в тонкие адаптеры и записал принцип в CLAUDE.md проекта. На .3 фреймворк объявил обратное — «Copies are the model» — и дал Step 6 недостающий ряд. Step 6 молча перезаписал команды копиями, что правильно; но CLAUDE.md проекта продолжал утверждать отменённый принцип, и это поймал не гейт, а перечитывание руками.

Второй по важности результат — консьюмерское подтверждение, что снятие actor в .3 закрывает дефект, о котором отчитывался отчёт два. Оно дороже, чем выглядит: между двумя обновлениями это противоречие успело стоить проекту четырёх переписанных неизменяемых событий.

Что произошло, в числах

Измерение Значение
Дельта нагрузки .2.4 27 файлов, +1448 / −561
Файлов нагрузки в коммите получателя 27 — сошлось с дельтой ровно
Новых файлов нагрузки 1 (templates/bindings.yaml)
Ретировано файлов 0
Step 3: файлов, отличных от установленной базы 3
Из них реальных кастомизаций 1 (project_config.yaml)
Из них ⚫ состояние проекта 1 (knowledge_state.yaml)
Из них дрейф происхождения 1 (CHANGELOG.md — текст того же тега, переписанный в источнике)
Ручных слияний 1 — конфиг
Ложных CUSTOMIZED 0
Адаптерных копий ре-синхронизировано 11 команд Claude + 11 воркфлоу Antigravity + 11 скиллов Codex; блок AGENTS.md уже совпадал
Хитов ретированной лексики вне allowlist 0 по духу, 6 по букве (см. дефект 5)
Тесты нагрузки 179 passed, 1 skipped
--check index / tasks / project все зелёные
Ошибок оператора, пойманных гейтом 0
Коммитов чужих агентов между двумя обновлениями проекта 19, два инструмента, шесть ролей-фаз
Конфликтов с ними 0

Что сработало отлично

installed_from окупился на первом же следующем обновлении. Ключ добавлен в .3 по отчёту два — и Шаг 3 сразу использовал его правильно: измерял против фактически установленной базы, а не против номера версии. Это дало нулевые ложные срабатывания там, где у третьего потребителя их было 10 из 12.

Различение «provenance drift» отработало на неочевидном случае. CHANGELOG.md разошёлся с источником — но не потому, что его правили в проекте, а потому что сам тег v2.0.0-dirty.2 был переписан в источнике после того, как я с него ставился: запись .2 в CHANGELOG выросла на двенадцать строк. По старой процедуре это ушло бы в «ручное слияние framework-файла»; по новой формулировке — «older than the target → overwritten». Правило поймало класс, которого его автор, вероятно, не имел в виду.

bindings.yaml существует. Отчёт два, находка 2: семь воркфлоу велят прочитать ~/.tfw/bindings.yaml, а формата нет нигде. В .3 шаблон отгружен и описан в conventions.md §4. Закрыто.

Ряд Step 6 для .claude/commands/. Отчёт два, находка 5. Именно этот ряд сделал откат моих тонких адаптеров механическим: одиннадцать cmp -s вместо одиннадцати решений.

Разделение наборов тестов. 179 тестов нагрузки прошли у получателя, не унаследовав проверок состояния чужого репозитория. У третьего потребителя это было два падения из коробки.

Пин источника — та часть, что исполнима. Проверка тега до чтения VERSION и повторно после копирования поймала бы подмену. Источник за время обновления не двигался; проверка это зафиксировала явно, а не молчанием.

Дефекты — по тяжести

1. Противоречие в actor стоило проекту четырёх переписанных неизменяемых событий

Это консьюмерское подтверждение того, что .3 чинил, и оно дороже строчки в CHANGELOG. Полная цепочка в одном проекте, вся в коммитах:

  1. Обновление .2. Поле actor требовало одновременно удостоверения писателя (значит, профиль в team/) и уникальности имени файла (значит, разного значения на сессию). Я разрешил это единственным способом, который позволял работать: профиль на сессию агента.
  2. Соседняя сессия скопировала соглашение и завела себе второй такой профиль.
  3. Кто-то удалил оба профиля. Гейт --check tasks стал красным навсегда — события неизменяемы, профили нет.
  4. Владелец велел восстановить профили из git. Зелёный.
  5. Коммит d409627: четыре события переписаны — переименованы файлы (актор вшит в имя) и изменено поле actor. Из его же текста: «Правка нарушает неизменяемость журнала осознанно и по решению владельца».
  6. Тот же коммит: «Профили claude-20260828a/b удалены ПОВТОРНО: соседняя сессия пересоздала их при исполнении Фазы A… Не решено механически: следующая сессия может пересоздать снова».

То есть одно противоречивое поле произвело: нарушение центрального инварианта журнала, принятое владельцем как меньшее зло; и соглашение, которое воспроизводило себя быстрее, чем его успевали отменять.

После .4 проверено: удаляю оба агентских профиля — --check tasks остаётся зелёным, 15 задач валидны, ни одно событие не тронуто. Обещание .3«measured on the two consumers: one was red on two events naming deleted profiles and now validates, with nothing in that project changed» — держится. Уточнение к формулировке: в этом проекте «nothing changed» верно про механизм, но проект к тому моменту уже заплатил за противоречие пунктами 1–6.

2. Фреймворк развернул принцип, по которому получатель уже сработал, — и обновление этого не видит

На .2 в .tfw/adapters/claude-code/README.md стояло: «The canonical workflows in .tfw/workflows/ are the single source of truth. Commands never duplicate workflow content — they reference it.» Отчёт два цитировал эту строку как основание для конверсии двенадцати команд в тонкие адаптеры, и записал принцип в CLAUDE.md получателя.

На .3 принцип отменён: «Copies are the model: full copies, where each tool expects them».

Что произошло при обновлении: Step 6 перезаписал одиннадцать команд копиями. Правильно и молча. Но CLAUDE.md проекта — документ получателя, не нагрузки — продолжал утверждать отменённый принцип, ссылаясь на дату и на причину, которых больше нет. Ни один гейт этого не видит: реестр ретированной лексики ловит термины, а здесь ретирован принцип, и он сформулирован разными словами в двух местах.

Рекомендация. Когда релиз разворачивает нормативное утверждение, вносить в CHANGELOG не только новую формулировку, но и отменяемую — дословно, как строку для поиска. Получатели цитируют принципы фреймворка в своих CLAUDE.md, AGENTS.md и правилах; дословная старая формулировка — единственное, по чему это можно найти grep-ом.

3. Пути обновления с .2 на .4 не существует

CHANGELOG .4 содержит раздел «Updating from 2.0.0-dirty.3» и никакого другого. Проект на .2 обязан прочитать обе записи и слить инструкции сам. Конкретно из записи .3 пришлось доставать то, чего в .4 нет: ре-синхронизировать .claude/commands/ (ряд Step 6 появился в .3), завести installed_from, знать, что actor снят и профили агентов не нужны.

Пропуск тега — нормальный сценарий для получателя, тем более когда теги локальные и не пушатся: между двумя обновлениями одного проекта их вышло два. Записи же предполагают последовательное принятие каждого.

Рекомендация. Либо давать в разделе обновления диапазон («from .2 or .3»), либо одну строку-указатель: «если вы на .2, выполните также раздел обновления записи .3». Второе стоит одну строку на релиз.

4. Ряд Step 6 «Claude rules → managed CLAUDE.md content» неисполним механически

В таблице Step 6 семь рядов, и они выглядят однородно. Но:

  • у Codex-адаптера есть маркеры TFW:CODEX:START/END — ряд исполняется как байтовая операция и проверяется cmp;
  • у claude-code адаптера маркеров ноль (grep -c 'TFW:' .tfw/adapters/claude-code/CLAUDE.md.template = 0).

Значит, для Codex «managed content» — определённое множество байт, а для Claude — «слейте по своему разумению в файл, который проект сильно кастомизировал». Step 8 при этом велит «verify installed adapter copies», как будто обе операции одного рода. У меня CLAUDE.md правился руками в три места, и проверить это можно только чтением.

Рекомендация. Дать claude-code шаблону такие же маркеры, как у Codex, и в таблице развести два вида рядов: копия целиком и блок в кастомизированном файле.

5. Правило «zero hits outside allowlist» формально красное у каждого получателя

Независимое подтверждение находки третьего потребителя, ровно те же шесть хитов: workflows/update.md:69, workflows/init.md:123 и их четыре адаптерные копии. Все шесть — инструкции ретирования («Delete retired keys initial_seq, id_max_retries…», «No initial_seq»). По духу — allowlist; по букве Step 6 («canonical migration or changelog text and byte-identical copies») — нет: update.md и init.md не migration и не changelog.

Два получателя независимо пришли к «принял по духу и записал в отчёт». Это уже не крайний случай, а поведение по умолчанию.

Рекомендация. Расширить формулировку allowlist до «text whose purpose is to retire the term». Одно предложение снимает гейт, который иначе никогда не бывает буквально зелёным.

6. Команда пина из Step 0 не работает на живом источнике

Написано:

source_head=$(git -C {source} rev-parse HEAD)
target=$(git -C {source} show "$source_head:.tfw/VERSION")
tag_commit=$(git -C {source} rev-parse --verify "refs/tags/v${target}^{commit}")
test "$tag_commit" = "$source_head"

Источник — рабочее дерево, в котором продолжается разработка: HEAD ушёл вперёд от v2.0.0-dirty.4. Проверка tag_commit = source_head при таком HEAD не может пройти никогда, хотя ровно этот тег и есть цель обновления.

Я закрепился на коммите тега вместо HEAD и проверил равенство в другую сторону — гарантия сохранена, буква шага нарушена. Менее внимательный оператор здесь либо бросит проверку («она у меня не проходит»), либо возьмёт HEAD — то есть нетегированную нагрузку, ровно то, что шаг существует предотвращать. Третий потребитель сообщал о смежном («источник двигался во время обновления»); это другое: тут источник стоит, а команда всё равно неисполнима.

Рекомендация. Написать шаг от цели, а не от HEAD: target_ref задаёт оператор, из него выводится source_head=$(git rev-parse "$target_ref^{commit}"), а VERSION читается из него же и сверяется с именем тега. HEAD как источник пина корректен только для репозитория, который стоит на релизе, — то есть для самого фреймворка.

7. --check project не знает, кто ссылается на team/

Замечание из прошлой недели, проверенное эмпирически и приложенное сюда, потому что механизм пережил снятие actor. Когда события ссылались на удалённые профили, --check project отвечал «project is consistent with the release it declares» и «1 participant(s) declared» — и ни слова о висящих ссылках; красным был только --check tasks.

actor снят, но on_behalf_of по-прежнему указывает в team/. Удалите профиль человека — и расщепление воспроизведётся: единственный чекер, читающий team/ целиком, не знает, кто в него ссылается, а тот, кто знает, не читает каталог.

8. Формат installed_from не задан

Step 7: {resolved-source}@{verified-tag-or-commit}. Отгруженный шаблон конфига несёт installed_from: "self". Я записал D:/projects/research/[email protected] — абсолютный путь Windows с буквой диска, в файле, который коммитится и читается на других машинах. Ничего не говорит, нормализовать ли путь, допустим ли URL, что писать при переносе проекта. Двое получателей напишут две формы, а Шаг 3 следующего обновления это разбирает.

Наблюдение по существу TFW-60

Фаза A обещает: два участника продвигают две задачи, не встречаясь в одном файле. У этого проекта есть прямое измерение, а не мнение.

Между двумя обновлениями каркаса (8b52dd69b6a832) в дерево легли 19 коммитов от чужих сессий: семь codex/…/phase-a-pass2/executor, три codex/…/phase-a-pass1/executor, далее reviewer и coordinator обоих инструментов — шесть различных ролей-фаз, два разных инструмента, все по INNO-8. Моя работа по каркасу шла в .tfw/, адаптерах и конфиге.

Пересечений — ноль. Ни одного конфликта слияния, ни одной потерянной записи. Единственная точка касания за оба обновления — workspace/00-INDEX.md, который соседняя сессия перегенерировала, пока я работал; и он производный и сам объявляет себя неавторитетным, так что это не конфликт, а устаревание вида. На старой доске каждый из этих 19 коммитов правил бы ту же таблицу в README, что и я.

Обратная сторона названа в пункте 1 и в коммите d409627 дословно: соглашение, введённое одной сессией, воспроизводится другими быстрее, чем отменяется. Профили пересоздавались дважды после удаления. Файловая развязка убрала конфликты записи; она не убирает распространение решения между сессиями, и .3 починил это единственно верным способом — убрав поле, которое требовало такого решения.

Что изменено в проекте сверх процедуры

  • Одиннадцать команд возвращены к байтовым копиям воркфлоу; /tfw-task не тронут — он проектный, канонического воркфлоу за ним нет, и Step 6 запрещает трогать соседние проектные команды.
  • CLAUDE.md — три правки: версия и происхождение; принцип адаптеров (копии, не роутеры) с явной причиной и датой разворота; схема события без actor, с on_behalf_of/via, запретом на профиль под сессию и новой грамматикой INNO_{YYYYMMDD}-{HHMMSS}_{ABBR}.
  • Конфиг: id_max_retries удалён, installed_from добавлен, id_format заменён, прежняя clock-грамматика оставлена комментарием как read-only форма. review.default_mode в этом проекте никогда не стоял.
  • Оба агентских профиля удалены, team/ снова только люди.

Рекомендации другим пользователям TFW — по порядку исполнения

  1. Если вы пропустили тег, прочитайте раздел обновления каждого пропущенного. В целевом его для вас нет. Дороже всего обходится не пропущенный ключ, а пропущенный ряд Step 6.
  2. После Step 6 перечитайте собственные CLAUDE.md / AGENTS.md / правила на предмет цитат из фреймворка. Реестр ретированной лексики ловит термины, а не отменённые принципы; ваш файл будет уверенно утверждать вчерашнее правило, и никто вам об этом не скажет.
  3. Пиньтесь на тег, а не на HEAD источника. Если источник — живое дерево разработки, команда из Step 0 у вас не пройдёт, и это не повод её бросать.
  4. Не заводите профиль под сессию агента. На .3 и позже это не нужно и активно вредно. Если у вас остались такие профили с прошлых версий — удаляйте, гейт останется зелёным.
  5. Никогда не переписывайте написанное событие. Если противоречие спецификации загоняет вас туда, это баг фреймворка, и он стоит отчёта, а не правки корпуса. Один такой отчёт уже изменил схему.
  6. Проверьте installed_from после обновления — он теперь определяет, против чего измеряется следующее.

Четвёртый полевой отчёт TFW-60. Первый об обновлении внутри линии 2.0.0. Каждое число получено измерением; каждая ссылка на коммит проверена в репозитории получателя.