REVIEW — TFW-60 / Phase AA: Portable Delivery¶
Date: 2026-08-28 Author: Claude Code (Reviewer),
actor: saubakirov,via: claudeVerdict: 🔄 REVISE — narrow, one round, three items RF: RF Phase AA TS: TS Phase AA at revision 3 EV: EV Phase AA Contract baseline: master HL at2123de1(after amendment A4) Reviewed at:440d6fd. The RF declares its pin at1079020; the two later commits touched only the RF, the EV and one evidence artifact, and changed no code. Stage files:review/map.md·review/verify.md·review/judge.md
1. Map¶
Phase AA moves the four migration/index scripts from docs/scripts/ into .tfw/scripts/ with
git mv, replaces parents[2] depth arithmetic with a marker search, and writes
.tfw/migrations/2.0.0.md — the single created file — as a procedure for a project that is not
this repository. Around that move, ten field-report findings close: the board's location and
heading become inputs on the same code path that makes a committed revision the default source;
a directory whose name the identifier grammar rejects is reported as unresolved instead of being
called a backlog idea; the carrier validator names the key it rejected; update.md gains the
pristine-tag diff, the task_containers decision and a team/ creation step; three adapter
sources stop routing /tfw-research at a file that has never existed.
Under owner revision R3 the phase subtracts: two proposed files are withdrawn before
creation, three templates leave the flat namespace for directories mirroring their output, and
--check / --validate / --doctor collapse into one flag with three subjects — deleting the
five-line config comment that existed only because the names had failed.
The phase's declared outcome is reported unmet on its acceptance half and handed to the owner. That is compliance, not shortfall: TS §5 splits AC-13 into a development fixture the executor closes and acceptance evidence only an operator who is not the author can produce, and TS §7 makes any other answer a rejection.
2. Verify¶
Ratio 0.42 over 33 claimed files required 14. A discrepancy escalated verification to 100% —
all 33 opened or diffed, and the full 49-path change set reconciled against census.md.
| # | What was checked | Result | Evidence |
|---|---|---|---|
| 1 | Full suite re-run | ✅ 253 passed, 1 skipped, 254 collected — matches the RF exactly, against the ONB's recorded 220 baseline. Net +33 | pytest .tfw/scripts/ → 158+1 in 12 s; pytest docs/scripts/ → 95 in 249 s |
| 2 | All three --check subjects |
✅ exit 0 each — 53 tasks validate; project consistent; index current. Every output names what it did not check | commands re-run here |
| 3 | mkdocs build |
✅ exit 0, built in 230.8 s — the gate for gen_docs.py's cross-directory import |
verify.md command 13 |
| 4 | AC-1 gate grep | ✅ 11 hits outside tasks/, identical to ac1_gate.txt line for line, including KNOWLEDGE.md:22 named in the AC's own gate text so the RF could not report around it |
verify.md command 8 |
| 5 | Root resolution | ✅ marker search, .upstream skipped, refuses rather than guessing. parents[2] survives only in a docstring and two test constants |
gen_index.py:75–100 |
| 6 | Depth defect actually observed | ✅ at three real placements inside a fixture project; parents[2] would have been wrong in two, once resolving outside the project entirely |
fixture_run.txt, fixture_report.md § F1 |
| 7 | Parser untouched (AC-3) | ✅ independently re-diffed: parse_board 42 → 47 lines whole-function, exactly two code lines differ — the signature gaining the parameter and the locator using it. Every row-reading line byte-identical |
verify.md command 12 |
| 8 | Moves are moves | ✅ git diff --name-status records R063/R069/R075/R065/R100/R100; git log --follow returns 9 / 6 / 3 / 3 / 5. History follows every relocation |
verify.md command 11 |
| 9 | Adapter copies | ✅ 22 workflow copies + 11 Codex skills, all byte-identical; 4 broken /tfw-research routes fixed across all three sources; one remaining research.md string in the tree is the CHANGELOG describing the fix |
verify.md V10, V15 |
| 10 | Never-modify classes | ✅ no file under tasks/ outside this phase's own artifacts is in the change set; KNOWLEDGE.md untouched; 11 provenance comments byte-unchanged |
verify.md V16 |
| 11 | update.md ceiling |
✅ 1165 words against 1200, reached by deleting duplication rather than cutting required content | wc -w |
| 12 | Evidence artifacts | ✅ 60 items, 60 artifacts resolve, 0 missing. 1 correctly DEFERRED with a named blocker and named closing artifact; 1 correctly N/A (git tag -l confirms no tag was cut) |
verify.md Evidence Verification |
| 13 | Knowledge citations | ✅ 33 of 33 (HL §7.2 ×29 + ONB §7 N1–N4) resolve, exist, match meaning and are relevant. 0 hallucinated, 0 irrelevant. Two application notes are artifacts of the ONB predating TS R3, not defects | verify.md Knowledge Citations |
| 14 | Discrepancies | ⚠️ six, one Medium and five Low — §4 below | verify.md D1–D6 |
What could not be verified here: AC-13 half two, by construction. Acceptance evidence requires a real external project updated by an operator who is not the author, and no artifact in this repository can substitute for it. The executor did not claim otherwise.
3. Judge¶
| # | Check | Status | Evidence |
|---|---|---|---|
| 1 | DoD met? | ✅ | 14 of 15 AC halves met and independently re-verified. AC-13 half two reported ❌ UNMET and routed to the owner — what TS §5 and §7 require of the executor |
| 2 | Purpose Check + design soundness | (a) ✅ · (b) ✅ | (a) Serves DoD 19 at baseline 2123de1 — "An external project completes the update … from the payload alone … every instruction the release gives names something the receiving project actually has" — and NS1, "another authorized person or agent can … continue without rebuilding the original conversation." Harm at stake: without this phase a receiving project is told to run docs/scripts/migrate_board.py, a file /tfw-update never copies, so continuation means reconstructing the framework's tooling by hand — measured, not predicted, by the first external update. Excess: none (AC-12 is owner-admitted and bounded; the phase is net subtractive). Deferral confession: none (RF §6 items 3, 5, 7 name other homes and ship nothing there). (b) HL §7 principle 5 is structurally enforced, not asserted — test_no_check_subject_writes_anything byte-compares the whole tree across all three subjects. Principles 4 and 9 hold and were verified |
| 3 | Tech debt documented | ✅ | RF §6: seven observations, each naming file, type and why it was not fixed. None is filler |
| 4 | Style & standards | ⚠️ | Naming, word ceiling and commit attribution all honoured. Against that, .tfw/templates/status.md ships a self-contradiction and a miscount — §4 items 1 and 2 |
| 5 | Observations collected | ✅ | Quality filter applied; six of seven promoted, one reclassified as a recorded decision |
| 6 | RF completeness (§7-9) | ✅ | §7 seven fact candidates with named human/coordinator sources; §8 three execution-derived insights; §9 three diagrams carrying information the prose does not |
| 7 | Evidence completeness | ✅ | 60 of 60 artifacts exist; every TS Evidence field covered; all statuses valid |
| 8 | Evidence sufficiency | ⚠️ | The strong parts are strong and I re-derived rather than read them — test counts, gate grep, parser diff, depth placements all reproduce. Four claims are looser than their evidence (§4 item 3), in a phase whose own DoF names "a check reported as passing that never ran" in four forms |
| 9 | Backward compatibility | ✅ | The one deliberate break (build.verify: --validate → --check tasks) is taken knowingly with its blast radius stated and named in the CHANGELOG as one of exactly two commands that change. iter_task_dirs' return type deliberately unchanged because three callers read it. mkdocs build exit 0 proves the import path. Copies verified identical |
| 10 | Safety | ✅ | No secrets or network surface. The destructive surface is bounded in the right direction: migration opens no existing path and never renames; --check writes nothing (whole-tree byte comparison); root resolution refuses rather than guessing. The one live-system risk — writing into the owner's real external project — was avoided and recorded read-only (E48). No push, no tag |
Purpose Check outcome: Aligned ✅. Reference set checked for internal consistency: the baseline's Phase AA block, DoD 19, DoF 10 and NS1 all pull the same direction. No contract defect.
4. Verdict¶
🔄 REVISE — narrow, one round.
The work is correct, and I want to say that before the finding: fourteen acceptance criteria
hold under independent re-derivation, not under re-reading. The test counts reproduce exactly.
The AC-1 gate grep reproduces line for line. The parser's untouchedness survives a diff I ran
myself. The depth defect is observed at three real placements where parents[2] would have been
wrong twice — once resolving outside the project entirely — which is the observation TS §5 said
this repository could never produce. And AC-13 half two is reported unmet, which was the one
answer that took discipline rather than effort.
What holds it back is that the payload now contradicts itself on the rule this phase wrote.
.tfw/templates/status.md:92 still reads "Normalizing such a value to a declared one is
prohibited." That is verbatim the sentence commit f14f744 rewrote in glossary.md and
replaced in conventions.md §5 with the two-act rule: migration never normalizes, an accountable
owner may resolve through a recorded transition event. The executor identified that exact
wording as the defect, corrected two copies, and left the third — in the canonical carrier
template, which is the file a person hand-authoring status.md actually opens, in the phase
whose entire subject is that a receiving project has only the payload and the payload must not
mislead it. .tfw/CHANGELOG.md announces the corrected rule under Canon, so the release as
it stands states one thing and the template it ships states another.
TS §5 AC-14's fourth bullet named the harm in advance: "the prohibition reads as absolute and projects will either strand tasks or resolve them without a trace." That harm ships. This is not a wording objection — two shipped normative files give a reader opposite instructions about the same act, and the reader most likely to hit it is the one this phase was written for.
Why this is a REVISE and not debt: the coordinator's next act is cutting v2.0.0-dirty.2
(E53), and the act after that is the external update that closes AC-13 half two. Approving now
tags a release whose payload contradicts its own release note, and then runs the acceptance test
against it. The fix is one sentence in a file already in this phase's change set.
Nothing here is grounds for ❌ REJECT: purpose is aligned, the contract is coherent, the design is sound, and no frozen section is in question.
Items to fix¶
| # | Where | What | Why |
|---|---|---|---|
| 1 | .tfw/templates/status.md:92 |
Bring the UNDECLARED sentence into line with conventions.md §5 — migration never normalizes, an accountable owner may resolve through a recorded transition event carrying from: UNDECLARED. Cite §5 rather than restating the table; the profile template's own R3 treatment is the model |
Closes AC-14's fourth bullet in the payload rather than in two of its three copies. verify.md D1 |
| 2 | .tfw/templates/status.md:78 |
"the four keys that are never prose — id, lifecycle, owner, authority, created, updated" lists six. Correct the count | Same paragraph AC-5 wrote so a person gets the carrier right by hand. verify.md D2 |
| 3 | RF §1, §4 · EV E47, E60 | Four claims looser than their evidence, corrected in place, no re-run required: (a) E47's "0 framework files edited inside .tfw/" carries its __pycache__ caveat at the row, not only in fixture_report.md; (b) RF §1's git log --follow counts are re-measured at the declared pin 1079020 (9 and 6) or the pin is stated as 80c2ed5 for that measurement; (c) E60 / RF §4's "as a class" is stated as what test_every_runtime_message_is_ascii actually enforces — a line-scanning check over print( / SystemExit( sites, which caught five real occurrences and does not reach a message assembled into a variable; (d) ac3_parser_untouched.txt notes the signature line alongside the locator |
TS §6 makes pinning a rule and TS §7 names "a check reported as passing that never ran" in four forms. Each is small; the class is the one this phase legislates against. verify.md D4, D5, D6, C2 |
Not an item to fix — recorded for the coordinator. RF §1 heads its table "Modified — 25",
the figure census.md reaches by classifying the four scripts as moves; the RF's table then
lists those same scripts as Modified rows. Distinct paths in the table: roughly 31. No budget
limit is crossed under the census's declared method, and the group was raised before acting,
which is the return-to-coordinator rule working. But the TS's basis for the classification did
not survive execution — it predicted the scripts would "relocate rather than get rewritten …
cost a move and their path constants", and gen_index.py changed 507 lines,
migrate_board.py 246. That is a coordinator estimate the work invalidated, not an executor
error. It belongs in the next phase's budget table, not in this round. verify.md D3 / D3a.
On AC-13 half two. It stays open and it is the owner's. The declared outcome of Phase AA is
a project other than this one completes the update from the payload alone, and the only run so
far was the author's own clone. The phase cannot be closed until a real external project is
updated by its own operator, filed at task root as
FIELD-REPORT__TFW-60__second_external_update.md. That is the contract's own design, not a gap
in the work — and it is why the revise round above is cheap to take now: the payload the owner
will run against should not contradict itself.
5. Tech Debt Collected¶
RF §6 carries seven observations. Quality filter applied — an item is promoted only if leaving it causes a real problem. Six promoted, one reclassified.
| # | Source | Severity | File | Description | Action |
|---|---|---|---|---|---|
| TD-186 | RF TFW-60/AA §6 obs. 1 | Med | KNOWLEDGE.md:22 |
The architecture table still names docs/scripts/gen_index.py. Correctly not written by the executor: KNOWLEDGE.md §1–§3 belongs to /tfw-docs under the D37 split, and AC-1's gate text names the hit explicitly so the RF could not report a green gate over a red one |
→ /tfw-docs, immediately after this phase is approved. Verified still open at review time |
| TD-187 | RF TFW-60/AA §6 obs. 3 | Med | .tfw/workflows/init.md, .tfw/workflows/plan.md |
1,897 and 1,598 words against the §11 design ceiling of 1,200. Both were already over before this phase (1,821 · 1,501); it added 76 and 97. update.md was brought under the ceiling in this same phase by deleting duplication rather than cutting required content, so the technique is proven and available here — init.md's tutorial asides and plan.md's worked pseudocode are the named candidates |
⬜ Backlog — own task. Cutting a workflow is a change reviewers should see on its own, and it is out of this phase's scope |
| TD-188 | RF TFW-60/AA §6 obs. 5 | Med | .tfw/scripts/gen_index.py |
--check project does not report a retired framework file still sitting in a receiving project's .tfw/ — the fixture found templates/topic_file.md surviving the update, because copying a payload adds and overwrites but never removes. The migration guide now finds them by command, so the operator is not stranded |
⬜ Backlog. Correctly not added here: payload completeness and payload minimality are two claims, and the second needs a manifest of what the release ships, which does not exist |
| TD-189 | RF TFW-60/AA §6 obs. 6 | Low | docs/scripts/test_integration.py |
The docs/scripts/ suite takes ~250 s of the ~260 s total, almost all of it mkdocs builds inside tests; the payload suite alone is 12 s. Confirmed by re-run here. A receiving project runs only the payload suite so this never reaches them — but it makes this repository's own build.test slow enough to be skipped, which is how a gate stops being a gate |
⬜ Backlog — monitor. Candidate fix: mark the mkdocs-driven tests and let the default run exclude them |
| TD-190 | RF TFW-60/AA §6 obs. 7 | Low | .tfw/CHANGELOG.md:200–265 |
The 2.0.0-dirty entry's migration code fence still names docs/scripts/, correctly — it is a record of that tag — and the new entry says so and points at the guide. A reader who scrolls to the fence without reading the note above it can still copy a dead command. The structural fix is a per-release superseded by marker, which is a CHANGELOG-format change |
⬜ Backlog — → the task that revises RELEASE.md. Candidate: pairs with TD-179 and TD-180, which are already open against release procedure |
| TD-191 | REVIEW TFW-60/AA §2 · reviewer finding | Low | .tfw/CHANGELOG.md:101, TS TFW-60/AA §5 AC-11 |
[TD-11](../../../reference/tech-debt.md) names a debt row that does not exist. The shipped release note says "TD-11, unfixed across two releases", and the label originates in the field report. TECH_DEBT.md has no [TD-11](../../../reference/tech-debt.md); the historical TD-11 (TFW-7) was a different defect — ".tfw/README.md L280 says 3 canonical workflows" — and was purged in the 2026-04-15 sweep. A reader following the release note's own citation finds nothing, or finds an unrelated row |
⬜ Backlog — either register the adapter-route defect under a fresh ID and correct the CHANGELOG reference, or drop the ID from the entry. Not urgent; it is a citation, not a rule |
Reclassified, not promoted: RF §6 obs. 2 (tasks/BOARD-SNAPSHOT.md's SCREAMING-KEBAB name)
and obs. 4 (check_project's small duplication of the build.* path check, and gen_docs.py's
ten-line bootstrap restatement of find_project_root). Obs. 2 is a decision TS §6 explicitly
asked to be recorded as one — Phase A's links resolve to that path and a rename days later would
break them — so it is a recorded ruling, not debt. Obs. 4 is honest duplication the executor
correctly identified as such: gen_docs needs the root in order to locate the module it would
import the function from, which is a bootstrap that cannot import its way out. Neither would
cause a real problem if left, which is the bar.
Related open item confirmed still open: TD-182 (the Assisted edition's folder-moving status model) — deferred by owner decision and explicitly out of this phase's scope per TS §2.
6. Traces Updated¶
- [x] the phase's
status.md—lifecycle: RFretained per the REVISE verdict (conventions.md§5: "specific issues → back to execution, same task"), with atransitionevent in the task'sjournal/ - [x] HL status — unchanged. Phase AA does not complete on this verdict
- [x] the phase's
status.md—updatedreflects this review; no counter incremented - [x] Other project files — checked.
KNOWLEDGE.md:22is stale by design and routed to/tfw-docsas TD-186; nothing else is stale as a result of this phase - [ ] tfw-docs: Deferred — runs after ✅ APPROVE. TD-186 is its first item
- [ ] tfw-knowledge: Deferred — runs after ✅ APPROVE. RF §7 carries seven candidates and this REVIEW adds one
7. Fact Candidates¶
fact-candidates: processed 2026-08-30
| # | Category | Candidate | Source | Confidence |
|---|---|---|---|---|
| 1 | process | A rule corrected in the canon is not corrected until every shipped copy of its old wording is found. Phase AA rewrote the absolute UNDECLARED prohibition in conventions.md §5 and glossary.md and left the identical sentence standing in .tfw/templates/status.md. The two edited files are the ones a reviewer reads; the missed one is the one a receiving project reads. The mechanical form of the check is to grep the old sentence, not the concept, before declaring the rewrite complete |
Reviewer, this review | High |
I record only one. RF §7's seven are strong and I challenged none of them; #5 (the reusable
KZ-IT-telegram-list fixture at c919640) and #6 (the owner's non-UTF-8 console) are both
checkable and both check out against fixture_run.txt. Adding paraphrases of them here would
make /tfw-knowledge's job worse, not better.
REVIEW — TFW-60 / Phase AA: Portable Delivery | 2026-08-28, reviewed at 440d6fd