step.16.diff.txt -- narrative retro for step.16 (U3.3: SKELETON flow --
PAL-drafted step files; ground-truth probe prompts; LEANs presented
for ratification)

Executed: 2026-07-21, same session as the draft (single pass, the
step.7/9/10/11/14/15 convention -- drafting.py, the error type, the
package re-exports, the mock drafting fixtures, the `draft` CLI
subcommand, the 18-test suite, the design.md/end_to_end.md edits, and
the step file all built together). Companion artifact:
step.16.gdiff.txt (automated, scripts/step_gdiff). Per the operator's
explicit per-UoW checkpoint rule (same as step.14's/step.15's
dispatch), this session STOPS after U3.3 -- U3.4 is not started.

## Plan vs actual

```
+---+----------------------------------+------------------------------------------+
| # | Planned (brief)                  | Actual                                   |
+---+----------------------------------+------------------------------------------+
| 1 | Module placement: drafting.py    | app/orchestrator/drafting.py -- design   |
|   |   or skeleton.py, package        | §2.3's ledger-ops list is "operations    |
|   |   precedent from U3.1           | the ORCHESTRATOR consumes", and U3.1's   |
|   |                                  | own INTENT->SKELETON edge rationale      |
|   |                                  | already named U3.3 as its driver; the    |
|   |                                  | module joins state_machine/persistence/  |
|   |                                  | compiler as a per-concern peer.          |
| 2 | Decide the V0 probe scope        | Top-level listing + README.md +          |
|   |   honestly, bounded              | intent-named files (containment          |
|   |                                  | pre-checked; punctuation-stripped        |
|   |                                  | tokens), capped 8 files / 20k chars.     |
|   |                                  | Whole-tree rejected as unbounded egress  |
|   |                                  | (§2.4 "no more"; B-5); every content     |
|   |                                  | access via the trail-recorded tools.     |
| 3 | Record what was probed in the    | STRONGER than asking the provider to:    |
|   |   drafted §(1)                   | the engine STAMPS its own mechanical     |
|   |                                  | ENGINE PROBE RECORD block into §(1)      |
|   |                                  | after the draft parses -- the on-disk    |
|   |                                  | skeleton shows what the engine actually  |
|   |                                  | read as an engine-written fact, never    |
|   |                                  | trusted from provider prose. Post-stamp  |
|   |                                  | re-parse guards the stamp itself.        |
| 4 | Parse-retry policy: bounded,     | 1 initial + 1 fixed retry (MAX_ATTEMPTS  |
|   |   not d9's budget system         | =2), the validation error AND the failed |
|   |                                  | draft fed back, retry marker on the      |
|   |                                  | FIRST context entry (Q-4-B: makes the    |
|   |                                  | retry a distinct mock scenario; real     |
|   |                                  | adapters flatten context -- no change    |
|   |                                  | for them). Second failure refuses        |
|   |                                  | loudly naming the last error. d9 open.   |
| 5 | CLI placement vs U2.4's manual   | Top-level `draft`, `ask`'s sibling (a    |
|   |   draft-step                     | PAL round trip with engine-state         |
|   |                                  | effects, not table machinery); `ledger   |
|   |                                  | draft-step` untouched and cross-         |
|   |                                  | referenced -- manual template vs         |
|   |                                  | PAL-drafted, names keep them honest.     |
| 6 | Steps-dir / step-number for a    | <workspace_root>/steps/ (the corpus      |
|   |   target workspace; never this   | convention), always an explicit caller   |
|   |   repo's steps/ from tests       | argument; next free N by regex-fullmatch |
|   |                                  | on plain step.N.txt (retro siblings      |
|   |                                  | never counted); happy-path test asserts  |
|   |                                  | this repo's own steps/ byte-untouched.   |
| 7 | Human adapter path: exercised    | Exercised, not skipped: injected I/O     |
|   |   or justified skip              | drafts the same skeleton through the     |
|   |                                  | single content-bearing DONE (step.8's    |
|   |                                  | widened rule, kept local to drafting.py  |
|   |                                  | -- the orchestrator never imports the    |
|   |                                  | CLI layer). A human can BE the drafting  |
|   |                                  | provider.                                |
+---+----------------------------------+------------------------------------------+
```

Everything else held to plan. One small live finding (folded into the
test, not papered over): the human-relay test's first intent didn't
name hello.py, so the bounded probe correctly read only README.md and
the relayed-content assertion failed -- the intent-named probe scope
doing exactly what (2.2) says. Fixed by naming hello.py in the intent,
not by widening the probe. One design point worth naming: drafting
refuses an illegal INTENT->SKELETON move BEFORE the provider is called
(proven with a MustNotBeCalledAdapter) -- no tokens are ever spent on
a draft the state machine would refuse to record.

## Gate evidence

```
+------+---------------------------------------------------------------------+
| G1   | tests/test_orchestrator_drafting.py 18/18 PASS live: parseable      |
|      | skeleton, SKELETON status, stamped probe record ABOVE the provider  |
|      | narrative in S(1), LEAN-bearing S(5).                               |
| G2   | Unparseable-twice: SkeletonDraftError naming "no numbered sections  |
|      | found", spy counts exactly 2 run() calls, no steps/ created, state  |
|      | still INTENT. Retry-success: attempts==2, error + failed draft      |
|      | verbatim in the retry context, marker on the first entry.           |
| G3   | Human-relay drafts via injected I/O (content-bearing DONE); no      |
|      | provider name/adapter construction/adapters-import in               |
|      | app/orchestrator/ (grep gate run live).                             |
| G4   | Trail shows list_dir/read_file probe calls + the write_file of the  |
|      | skeleton; one egress record per attempt with probed content in      |
|      | sent_context; this repo's own steps/ asserted untouched.            |
| G5   | Live CLI smoke against a scratchpad workspace: skeleton written,    |
|      | "UT.1 INTENT -> SKELETON (step.1 recorded)", parseable on disk.     |
| G6   | 382p/11s baseline confirmed live -> 400p/11s after the build ->     |
|      | 402p/11s once step.16.txt joined the corpus (+18 drafting tests,    |
|      | +2 self-inclusive pickups). Zero regressions, zero tests edited.    |
| G7   | No ratify/amendment/approval code or prompt; no input() in          |
|      | app/orchestrator/; U3.4+ mentions are scope annotations only.       |
| G8   | S11 ledger row updated THROUGH the `ledger` CLI (transition U3.3    |
|      | active --step step.16; close U3.3 PENDING) against the real         |
|      | end_to_end.md.                                                      |
| G9   | Porcelain clean per commit; retro pair via the SHARED scripts/      |
|      | step_gdiff; exactly 2 commits (work + gdiff-with-backfill).         |
+------+---------------------------------------------------------------------+
```

## Findings for later steps

1. **The engine-stamp pattern generalizes.** Anything mechanical the
   engine knows for certain (what was probed, what ran, what a gate
   returned) should be ENGINE-WRITTEN into the ledger artifact, with
   provider prose layered around it -- never solicited from the
   provider and trusted. U3.5's evidence recording and U3.7's retro
   closure should reuse exactly this shape (stamp into the parsed
   StepFile via Section.raw+body, re-parse as the guard).
2. **Refuse-before-the-provider-call is cheap and worth keeping.**
   validate_transition() run against the persisted record before any
   probe/PAL work means illegal moves cost zero tokens and leave zero
   egress. U3.4's ratify gate and U3.5's scheduler entry should keep
   this ordering (state legality first, spend second).
3. **The retry-marker-as-data duplication (drafting.RETRY_MARKER vs
   fixtures._RETRY_MARKER) is pinned by a test** because adapters may
   not import the orchestrator (C-2-B). If a third copy ever appears,
   promote the marker into app/pal (the shared layer both sides may
   import) instead of pinning a triangle.
4. **The drafted skeleton's quality ceiling is the fixture's, not the
   flow's.** The mock's skeleton is minimal-but-real; the first REAL
   drafting run (claude-code or human against a live workspace) is
   U3.4's natural smoke input -- ratifying an actually-drafted
   skeleton is the honest way to exercise the amendment loop.

## Numbers

```
+----------------------------------------+----------------------------+
| new files                              | 2 (app/orchestrator/       |
|                                        | drafting.py, tests/test_   |
|                                        | orchestrator_drafting.py)  |
| + step pair                            | 2 (steps/step.16.txt,      |
|                                        | step.16.diff.txt)          |
| files edited                           | 6 (app/orchestrator/       |
|                                        | {errors,__init__}.py,      |
|                                        | adapters/mock/fixtures.py, |
|                                        | app/cli.py, design.md,     |
|                                        | end_to_end.md)             |
| drafting.py LOC                        | ~650 incl. docstrings      |
| new tests                              | 18 + 2 corpus pickups from |
|                                        | this step's own step.16.txt|
| suite before -> after                  | 382p/11s -> 402p/11s       |
| regressions                            | 0 (zero tests edited)      |
| prompts introduced                     | 0 (ratify/approval is      |
|                                        | U3.4)                      |
+----------------------------------------+----------------------------+
```

U3.3 done -- design.md §4's draft(PAL) arrow exists as concrete code:
an intent plus a real, bounded, trail-recorded workspace probe goes
through the PAL (any adapter -- mock, claude-code, or a human relaying
by hand), comes back as a whole step-file skeleton, is validated
refuse-loudly with one bounded retry, gets the engine's own probe
record stamped into its ground-truth section, lands in the target
workspace's steps/, and moves the UoW's engine state INTENT ->
SKELETON. The skeleton then SITS there awaiting ratification, because
the ratify gate is U3.4's and does not exist yet. Per the operator's
explicit scope boundary, this session STOPS here. Ledger updated
through the `ledger` CLI in the work commit; the work commit's hash is
backfilled into end_to_end.md §11 and step.16.txt §7 in the gdiff
commit -- the same 2-commit resolution steps 7-15 used.

End of retro.
