@windyroad/itil 0.61.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +2 -14
  3. package/bin/{wr-itil-check-afk-accept-eligible → wr-itil-render-story-map} +2 -2
  4. package/bin/wr-itil-story-map-edit +51 -0
  5. package/bin/wr-itil-story-map-query +51 -0
  6. package/hooks/itil-no-implement-draft-gate.sh +19 -28
  7. package/lib/story-oversight.sh +154 -60
  8. package/package.json +2 -1
  9. package/scripts/check-rfc-stories-ratified.sh +15 -9
  10. package/scripts/detect-unratified-stories-maps.sh +15 -32
  11. package/scripts/mark-story-oversight-confirmed.sh +80 -31
  12. package/scripts/migrate-story-status-mirror.sh +7 -3
  13. package/scripts/reconcile-stories.sh +26 -5
  14. package/scripts/render-story-map.mjs +1057 -0
  15. package/scripts/render-story-map.sh +15 -0
  16. package/scripts/story-map-edit.mjs +280 -0
  17. package/scripts/story-map-edit.sh +10 -0
  18. package/scripts/story-map-query.mjs +227 -0
  19. package/scripts/story-map-query.sh +68 -0
  20. package/scripts/update-story-references-section.sh +6 -1
  21. package/skills/capture-rfc/SKILL.md +1 -1
  22. package/skills/capture-story/SKILL.md +16 -8
  23. package/skills/capture-story-map/SKILL.md +102 -59
  24. package/skills/list-stories/SKILL.md +16 -10
  25. package/skills/list-story-maps/SKILL.md +1 -1
  26. package/skills/manage-rfc/SKILL.md +1 -1
  27. package/skills/manage-story/SKILL.md +19 -26
  28. package/skills/manage-story-map/SKILL.md +19 -17
  29. package/skills/reconcile-stories/SKILL.md +5 -2
  30. package/skills/work-problems/SKILL.md +2 -2
  31. package/templates/story-map.css +195 -0
  32. package/templates/story-map.html +23 -0
  33. package/scripts/check-afk-accept-eligible.sh +0 -230
@@ -52,7 +52,7 @@ This skill has **two direction-setting AskUserQuestion fires** (problem-trace AN
52
52
  | Problem trace validation | Mechanical: each `P<NNN>` must exist in `docs/problems/`. Open / Known Error / Verifying = pass; Closed / Parked = advisory-warn but proceed (bounded-escape carve-out — see Step 2) | silent-mechanical |
53
53
  | JTBD trace presence | I9 hard-block — refuse on missing JTBD trace; emit deny log + halt-with-stderr-directive | direction-setting |
54
54
  | JTBD trace validation | Mechanical: each `JTBD-<NNN>` must resolve to a file under `docs/jtbd/<persona>/JTBD-<NNN>-*.md` (any lifecycle status) | silent-mechanical |
55
- | Optional `--rfc` trace validation | Mechanical: each provided `RFC-<NNN>` must resolve to a file under `docs/rfcs/`; advisory-warn on `proposed` / `verifying` lifecycle states; missing entirely = hard-block on the provided arg (the absence-from-args case is the "optional" path — the malformed-arg case is not) | silent-mechanical |
55
+ | Optional `--rfc` trace validation | Mechanical: each provided `RFC-<NNN>` must resolve either to a legacy file under `docs/rfcs/` or to a release row returned by `wr-itil-story-map-query find-rfc`; advisory-warn on `proposed` / `verifying` legacy files; missing from both surfaces = hard-block on the provided arg | silent-mechanical |
56
56
  | Optional `--story-map` trace validation | Same mechanical pattern against `docs/story-maps/*/STORY-MAP-*.html` (HTML data-attribute existence check); advisory-warn on `draft` / `in-progress` story-maps | silent-mechanical |
57
57
  | STORY ID allocation | Mechanical: `max(local, origin) + 1`, three-digit padded; enumerates `docs/stories/*/STORY-*.md` + `git ls-tree origin/main docs/stories/`. ADR-019 collision-guard inline per Slice 3 design review architect approval (finding 3 option a — inline-only path) | silent-mechanical |
58
58
  | Title kebab-slug | Mechanical: first 8-10 non-stopword tokens of description | silent-mechanical |
@@ -160,18 +160,21 @@ JTBD lifecycle states (`.proposed.md` / `.accepted.md` / `.archived.md`) all pas
160
160
  - **`--story-map` absent**: hard-block with **refuse-and-route** (parity with the I6/I9 mandatory-trace gates). Do NOT scaffold `story-maps: []`. Emit the deny log (`reason: missing-story-map-trace`) and halt with:
161
161
  > `/wr-itil:capture-story` requires a story-map trace (ADR-095 / I8): every story is born on a story map. Create a map first via `/wr-itil:capture-story-map` (or extend one via `/wr-itil:manage-story-map`), then re-invoke capture-story with `--story-map STORY-MAP-<NNN>`.
162
162
 
163
- AFK orchestrators author the map first (born `human-oversight: unconfirmed` per ADR-090, drained later), then capture the story onto it — nothing halts silently; the map is a prerequisite step.
163
+ AFK orchestrators author the map first (born `"humanOversight": "unconfirmed"` in its data island per ADR-102, drained later), then capture the story onto it — nothing halts silently; the map is a prerequisite step.
164
164
  - **`--story-map` present**: for each `STORY-MAP-<NNN>`, existence check `ls docs/story-maps/*/STORY-MAP-<NNN>-*.html 2>/dev/null`. Malformed or unresolved → hard-block (`reason: unresolved-story-map-trace`). Lifecycle advisory-warn on `draft` / `in-progress` maps; pass silently on `accepted` / `completed`.
165
165
  - **Bootstrap exemption (ADR-060 A4 / ADR-053)**: a capture carrying the inline `<!-- bootstrap-exempt: ... -->` marker bypasses the I8 hard-block for migration stories ONLY. A **non-bootstrap** capture carrying the marker is rejected (the marker is not a general capture-time escape hatch — behavioural test asserts).
166
166
 
167
167
  `--rfc` stays **OPTIONAL** at capture (I7 enforces at the `accepted` transition — a story can legitimately precede its RFC firming up):
168
168
 
169
169
  ```bash
170
- rfc_file=$(ls docs/rfcs/RFC-<NNN>-*.md 2>/dev/null | head -1)
171
- [ -z "$rfc_file" ] && unresolved_rfcs+=("RFC-<NNN>")
170
+ shopt -s nullglob
171
+ rfc_files=(docs/rfcs/RFC-<NNN>-*.md)
172
+ rfc_rows=$(wr-itil-story-map-query find-rfc RFC-<NNN>)
173
+ [ ${#rfc_files[@]} -eq 0 ] && [ "$rfc_rows" = "[]" ] && unresolved_rfcs+=("RFC-<NNN>")
174
+ shopt -u nullglob
172
175
  ```
173
176
 
174
- - `--rfc` token absent: skip (the optional path). Present-but-malformed OR resolves to no file: hard-block (`reason: unresolved-rfc-trace`).
177
+ - `--rfc` token absent: skip (the optional path). Present-but-malformed OR resolves to neither a legacy RFC file nor a release row: hard-block (`reason: unresolved-rfc-trace`). ADR-103 makes the row the normal RFC surface; a standalone file is retained only for existing RFCs.
175
178
 
176
179
  ### 3. Compute next STORY ID
177
180
 
@@ -206,9 +209,14 @@ jtbd: [JTBD-<NNN>, JTBD-<NNN>, ...]
206
209
  rfcs: [<RFC-<NNN>, ...> or empty]
207
210
  story-maps: [<STORY-MAP-<NNN>, ...>] # >=1 REQUIRED at capture (I8 / ADR-095)
208
211
  estimated-effort: <S|M|L|XL — derived at capture per ADR-067 (real best-effort value, no deferral marker)>
209
- human-oversight: unconfirmed
210
212
  ---
211
213
 
214
+ <!-- NO `human-oversight:` field, and none of its siblings (ADR-103). A story
215
+ carries no oversight marker: approval is its story map's, derived from the
216
+ `story-maps:` field above. Writing one here creates a second approval
217
+ surface that `story_is_approved` deliberately ignores, so it would be
218
+ inert as well as wrong. -->
219
+
212
220
  <!-- Frontmatter `status:` is the ONLY place a story's lifecycle state lives.
213
221
  Do NOT add a `**Status**:` body line here. It used to mirror the
214
222
  frontmatter, and because the oversight fingerprint excludes the frontmatter
@@ -257,13 +265,13 @@ human-oversight: unconfirmed
257
265
  (captured via /wr-itil:capture-story; expand at next /wr-itil:manage-story invocation)
258
266
  ```
259
267
 
260
- **Born `human-oversight: unconfirmed` (ADR-090).** A new story is created with `human-oversight: unconfirmed` in its frontmatter orthogonal to the `status:` lifecycle. It is NOT ratified until a human confirms it via `/wr-itil:manage-story <NNN> ratify` (which writes `confirmed` + an `oversight-hash` fingerprint via `wr-itil-mark-story-oversight-confirmed`). Until then `wr-itil-detect-unratified-stories-maps` surfaces it and an RFC may not reference it (`wr-itil-check-rfc-stories-ratified`). Adding a story also drifts its parent map's fingerprint, re-opening the map's ratification (lazy-fingerprint, ADR-009 lineage). Do NOT hand-write `confirmed` born-unconfirmed is the load-bearing default.
268
+ **Born with no oversight field (ADR-103).** A new story carries no `human-oversight:` and no `oversight-hash:`. Its approval is derived from the maps in its `story-maps:` field: approved when every one of them is ratified, never approved when it names none. Until then `wr-itil-detect-unratified-stories-maps` surfaces it and an RFC may not reference it (`wr-itil-check-rfc-stories-ratified`). Do NOT write an oversight field onto a story `wr-itil-mark-story-oversight-confirmed` refuses a story path, and `story_is_approved` ignores any field left behind, so a hand-written one would be wrong AND inert. **Adding a story to its map does NOT re-open the map's ratification** rows and cards sit outside the fingerprint basis, which is what removed the deadlock ADR-101 existed to work around.
261
269
 
262
270
  The deferred-section pattern matches `capture-rfc`'s placeholder approach — the captured story is intentionally minimal; full INVEST shape lands at the manage-story accepted-transition step.
263
271
 
264
272
  ### 6. Single commit — `## Stories` reverse-trace refresh; no stories README refresh
265
273
 
266
- **Stage list**: the new story file PLUS each driving problem ticket file (refresh `## Stories` reverse-trace section) PLUS each driving JTBD file (refresh `## Stories` reverse-trace section) PLUS each driving RFC file IF `--rfc` was provided (refresh `## Stories` reverse-trace section). **Do NOT** stage `docs/stories/README.md` (deferred). **Do NOT** stage any story-map HTML files — story-maps are spatially-authored HTML; new stories must be placed on the relevant map manually via `/wr-itil:manage-story-map` (when that skill lands per Slice 4 of P170 Phase 2). Capture-story emits an advisory stderr line naming the unplaced-on-map state.
274
+ **Stage list**: the new story file PLUS each driving problem ticket file (refresh `## Stories` reverse-trace section) PLUS each driving JTBD file (refresh `## Stories` reverse-trace section) PLUS each legacy driving RFC file IF `--rfc` resolved to one. A row-backed RFC has no separate reverse-trace document; its card on the story map is the reverse trace. **Do NOT** stage `docs/stories/README.md` (deferred). **Do NOT** stage any story-map HTML files — story-maps are spatially-authored HTML; new stories must be placed on the relevant map manually via `/wr-itil:manage-story-map`. Capture-story emits an advisory stderr line naming the unplaced-on-map state.
267
275
 
268
276
  The reverse-trace refresh on driving artefacts IS in-commit per ADR-014 single-commit grain — the cross-tier `## Stories` table on a problem / JTBD / RFC must stay current the moment a new story traces it. The same justification as capture-rfc's inline `## RFCs` refresh applies.
269
277
 
@@ -42,7 +42,7 @@ Positional grammar mirrors `/wr-itil:capture-story` shape (footnote per ADR-060
42
42
  | Problem-trace validation | Mechanical: each `P<NNN>` exists in `docs/problems/`; dual-tolerant lookup | silent-mechanical |
43
43
  | JTBD-trace presence | I4 hard-block — refuse on missing trace; emit deny log + halt | direction-setting |
44
44
  | JTBD-trace validation | Mechanical: each `JTBD-<NNN>` resolves to a file in `docs/jtbd/` | silent-mechanical |
45
- | STORY-MAP ID allocation | Mechanical: `max(local, origin) + 1` enumerating `docs/story-maps/*/STORY-MAP-*.html` (ADR-019 inline collision-guard) | silent-mechanical |
45
+ | STORY-MAP ID allocation | Mechanical: `max(local, origin, history) + 1` enumerating `docs/story-maps/*/STORY-MAP-*.html` (ADR-019 inline collision-guard) | silent-mechanical |
46
46
  | Title kebab-slug | Mechanical: first 8-10 non-stopword tokens of description | silent-mechanical |
47
47
  | Title prose refinement | Optional taste AskUserQuestion; silent-default to derived form | taste |
48
48
  | HTML file write | Mechanical: schema per ADR-060 § Phase 2 encoding amendment 2026-05-12 lines 381-435 | silent-mechanical |
@@ -81,7 +81,7 @@ For each `P<NNN>`:
81
81
  trace_files=$(ls docs/problems/<NNN>-*.md docs/problems/*/<NNN>-*.md 2>/dev/null)
82
82
  ```
83
83
 
84
- **I3 hard-block** (per ADR-060 line 187): trace absent / malformed / unresolved → emit deny log entry to `logs/story-map-capture-denials.jsonl`, halt with stderr directive naming `/wr-itil:capture-problem` as the open-the-driving-problem-first surface.
84
+ **I3 hard-block** (ADR-060, the story-map schema's `problems:` field): trace absent / malformed / unresolved → emit deny log entry to `logs/story-map-capture-denials.jsonl`, halt with stderr directive naming `/wr-itil:capture-problem` as the open-the-driving-problem-first surface.
85
85
 
86
86
  ### 2.5. Validate JTBD trace + I4 hard-block
87
87
 
@@ -91,82 +91,125 @@ For each `JTBD-<NNN>`:
91
91
  jtbd_file=$(ls docs/jtbd/*/JTBD-<NNN>-*.md 2>/dev/null | head -1)
92
92
  ```
93
93
 
94
- **I4 hard-block** (per ADR-060 line 188): trace absent / malformed / unresolved → emit deny log + halt. Story-maps without JTBD trace are structurally meaningless per ADR-060 ("a map with no JTBD trace is structurally meaningless"; Patton's central thesis is journey-around-user-value).
94
+ **I4 hard-block** (ADR-060, the story-map schema's `jtbd:` field): trace absent / malformed / unresolved → emit deny log + halt. Story-maps without JTBD trace are structurally meaningless per ADR-060 ("a map with no JTBD trace is structurally meaningless"; Patton's central thesis is journey-around-user-value).
95
95
 
96
96
  ### 3. Compute next STORY-MAP ID
97
97
 
98
- Inline `max(local, origin) + 1` per ADR-019 collision-guard (architect Slice 3 design review option a — inline-only path, mirrors capture-rfc + capture-story precedent):
98
+ Inline `max(local, origin, history) + 1` per ADR-019 collision-guard (architect Slice 3 design review option a — inline-only path, mirrors capture-rfc + capture-story precedent). Git history keeps deleted IDs retired without adding tombstone files:
99
99
 
100
100
  ```bash
101
101
  local_max=$(ls docs/story-maps/*/STORY-MAP-*.html 2>/dev/null | sed 's|.*/STORY-MAP-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
102
102
  origin_max=$(git ls-tree -r --name-only origin/main docs/story-maps/ 2>/dev/null | sed 's|.*/STORY-MAP-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
103
- next=$(printf '%03d' $(( 10#$(echo -e "${local_max:-0}\n${origin_max:-0}" | sort -n | tail -1) + 1 )))
103
+ history_max=$(git log --all --name-only --format= -- docs/story-maps/ 2>/dev/null | sed 's|.*/STORY-MAP-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
104
+ next=$(printf '%03d' $(( 10#$(printf '%s\n' "${local_max:-0}" "${origin_max:-0}" "${history_max:-0}" | sort -n | tail -1) + 1 )))
104
105
  ```
105
106
 
106
107
  ### 4. Optional taste prompt for title
107
108
 
108
109
  Same shape as capture-story Step 4 — silent-default when unavailable.
109
110
 
110
- ### 5. Write the story-map file
111
-
112
- **File path**: `docs/story-maps/draft/STORY-MAP-<NNN>-<kebab-title>.html`
113
-
114
- **Template** (per ADR-060 § Phase 2 encoding amendment 2026-05-12 lines 381-420 + `docs/STYLE-GUIDE.md` rules):
115
-
116
- ```html
117
- <!DOCTYPE html>
118
- <html lang="en">
119
- <head>
120
- <meta charset="UTF-8">
121
- <title>STORY-MAP-<NNN>: <Title></title>
122
- <meta name="story-map-id" content="STORY-MAP-<NNN>">
123
- <meta name="status" content="draft">
124
- <meta name="problems" content="<P<NNN>[,P<NNN>...]>">
125
- <meta name="rfcs" content="">
126
- <meta name="jtbd" content="<JTBD-<NNN>[,JTBD-<NNN>...]>">
127
- <meta name="adrs" content="">
128
- <meta name="reported" content="<YYYY-MM-DD>">
129
- <meta name="decision-makers" content="<git config user.name>">
130
- <meta name="human-oversight" content="unconfirmed">
131
- <style>
132
- body { font-family: system-ui, sans-serif; max-width: 1200px; margin: 1rem auto; padding: 0 1rem; }
133
- h1 { font-size: 1.5rem; }
134
- h2 { font-size: 1.125rem; margin-top: 1.5rem; }
135
- .backbone { display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: 1rem; margin-bottom: 2rem; }
136
- .rib-header { grid-column: 1 / -1; border-bottom: 1px solid #ccc; padding-bottom: 0.25rem; }
137
- .rib { display: contents; }
138
- .slice { border: 1px solid #ccc; padding: 0.5rem; text-decoration: none; color: inherit; display: block; }
139
- .slice:hover { border-color: #666; }
140
- </style>
141
- </head>
142
- <body>
143
- <h1>STORY-MAP-<NNN>: <Title></h1>
144
-
145
- <p>(Story-map purpose paragraph — populated at /wr-itil:manage-story-map accepted transition.)</p>
146
-
147
- <section class="backbone" style="--cols: 1">
148
- <header class="rib-header">
149
- <h2 data-rib="placeholder">Backbone — populate at /wr-itil:manage-story-map accepted transition</h2>
150
- </header>
151
- <div class="rib">
152
- <!-- Slice cards as <a class="slice" href="../../stories/<state>/STORY-NNN-<slug>.md"
153
- data-story-id="STORY-NNN" data-rfc="RFC-NNN" data-jtbd="JTBD-NNN"
154
- data-status="<draft|accepted|in-progress|done|archived>">Story title</a>
155
- per docs/story-maps/README.md schema. Populated by manage-story-map.
156
- -->
157
- </div>
158
- </section>
159
- </body>
160
- </html>
111
+ ### 5. Write the story-map JSON, then render it
112
+
113
+ **NEVER hand-write the HTML, and NEVER open an existing map to copy its shape.** Both maps and template drifted together once already: every map in the corpus became a vertical stack of headings — no journey columns, no release rows, no cells — because each new map was cloned from the last. The renderer owns the shape so that cannot recur.
114
+
115
+ **A map is ONE file**: `docs/story-maps/draft/STORY-MAP-<NNN>-<kebab-title>.html`. Its data lives inside it, in a `<script id="story-map-data" type="application/json">` island. The renderer rewrites the presentation around that island. There is no separate source file to fall out of step with the rendered map, and the file a reader opens is the file an author edits.
116
+
117
+ **There is one command and one mode.** To CREATE a map, write a file containing nothing but the data island, then render it — the renderer fills in everything around it:
118
+
119
+ ```bash
120
+ # Write docs/story-maps/draft/STORY-MAP-<NNN>-<kebab-title>.html containing only:
121
+ # <script id="story-map-data" type="application/json">
122
+ # { ...the map data... }
123
+ # </script>
124
+ wr-itil-render-story-map docs/story-maps/draft/STORY-MAP-<NNN>-<kebab-title>.html
161
125
  ```
162
126
 
163
- Per `docs/STYLE-GUIDE.md`: NO inline `style=""` on `<a class="slice">` or `<h2 data-rib>` data-bearing elements; embedded `<style>` block in `<head>` is the only permitted styling source; `--cols` custom-property on `.backbone` is the layout-container exception.
127
+ **To CHANGE a map**, edit the data island in that same file and run the same command again. Creation and editing are the same operation, so there is no seed file to clean up and no bootstrap mode. Re-rendering is idempotent. Never edit the grid, the `<style>` block, or the `<meta>` block by hand they are regenerated from the island, and a hand-edit outside it is discarded on the next render.
128
+
129
+ **What a story map is.** A grid, not a list. Backbone activities are COLUMNS across the top and form the user's journey left to right. Release slices are ROWS. Task cards sit in the cells. A row read left to right is everything that ships together — that is the whole point of the artefact, and it is what a vertical stack cannot express.
130
+
131
+ **JSON shape:**
132
+
133
+ Extracted verbatim by `test/documented-island-renders.bats` — editing which keys
134
+ appear here changes what that test asserts, and that binding is deliberate.
135
+
136
+ <!-- documented-island:begin -->
137
+ ```json
138
+ {
139
+ "storyMapId": "STORY-MAP-<NNN>",
140
+ "title": "<Title>",
141
+ "status": "draft",
142
+ "persona": "<persona>",
143
+ "reported": "<YYYY-MM-DD>",
144
+ "decisionMakers": "<git config user.name>",
145
+ "traces": { "jtbd": ["JTBD-<NNN>"] },
146
+ "backbone": [
147
+ { "id": "<slug>", "title": "A. <Activity>", "note": "<optional JTBD or gloss>" }
148
+ ],
149
+ "releases": [
150
+ { "id": "rfc-<nnn>", "name": "<what this release delivers>", "rfc": "RFC-<NNN>", "note": "<optional>" }
151
+ ],
152
+ "tasks": [
153
+ {
154
+ "activity": "<backbone id>", "release": "<release id>",
155
+ "title": "<what the persona can do>",
156
+ "storyId": "STORY-<NNN>", "rfc": "RFC-<NNN>", "jtbd": "JTBD-<NNN>",
157
+ "ref": "STORY-<NNN>, P<NNN>"
158
+ }
159
+ ]
160
+ }
161
+ ```
162
+ <!-- documented-island:end -->
163
+
164
+ **Authoring rules:**
165
+
166
+ - **The backbone must be a journey, not a list of invariants.** Activities are steps the persona walks through in sequence. "Finish a change → get it assessed → push it → get through CI → release it" is a backbone. "Leave no unscored way out", "score this change not the last one" are invariants, and a column of them is not a map.
167
+ - At capture a map may legitimately have **columns and rows but empty cells**. That is the honest state of unbuilt work, and the renderer says so in place — a wholly empty band carries its own sentence. Do not add prose at the top explaining it.
168
+ - **Composing a row asks two questions, in order.** A row is a release, so putting two stories in one is a claim that they ship together. Answer both before drawing it.
169
+
170
+ **1. Do they NEED to ship together?** Coupling. Is either broken, meaningless or misleading on its own? A migration and the code that depends on it need one row. Two fixes that merely arrived in the same conversation do not. *Absence of a dependency is not a reason to bundle* — it is the reason not to.
171
+
172
+ **2. SHOULD they ship together?** Economics, and this is the question that gets skipped. Batching imposes the delay of the slowest story on everything in the batch. It buys something back only when each release costs a lot to perform — so weigh the holding cost against the per-release transaction cost:
173
+
174
+ - **Producer-side cost is usually near zero here**: release is automated on merge, and this repo has shipped three times in a day. When transaction cost approaches zero the optimal batch approaches one, and bundling is pure loss.
175
+ - **Adopter-side cost is not zero.** Every release asks an adopter to upgrade, and upgrading has known friction. That is the real argument for a larger batch.
176
+ - **Watch for the story that REDUCES that cost.** It ships first and alone. Queueing your transaction-cost reducer behind other work is the expensive mistake, because it makes every later release cheaper — attack the cost, then the batch size falls out.
177
+ - **Cost of delay is per persona, not per story.** A gate blocking most adopters today outweighs an intermittent failure that bites across an upgrade. Name who waits, and what waiting costs each of them, before deciding the row.
178
+ - **Check the package boundary.** Stories in packages that version independently cannot share a release the tooling will actually produce; a row spanning them claims something no changeset emits.
179
+
180
+ The default is one story per row. Two stories share a row when the answer to question 1 is yes, or when question 2 shows a transaction cost high enough to pay for the delay. Neither is assumed.
181
+
182
+ - **A row IS an RFC (ADR-103).** A row a problem has proposed carries its `rfc`; drawing the row is what allocates the identity. There is no separate "not yet allocated" state and no `badge` field — a row's status is derived from its stories, and its label is its RFC id.
183
+ - **Every row carries an identity, and finishing one earns no exemption (ADR-107).** A row with no `rfc` renders as a defect — a red "Untraced" badge — whether or not its stories are done. Delivery cannot excuse a missing identity, because every row is delivered eventually; that reading would let work nobody proposed become legitimate by being finished.
184
+
185
+ The only exception is a row holding work that shipped **before rows carried identities**, and such a row says so explicitly with `"preRfc": true`. That set is closed. Do not add the marker to a new row: it is a statement about history, not a way to skip allocating an RFC. It appears in no example above because a row that has an `rfc` does not need it, and the example shows the normal case.
186
+ - **A map carries no `traces.rfcs` (ADR-107).** The map's RFC list is the union of its row identities, so authoring it restates the rows and drifts from them the moment one changes.
187
+ - `storyId` / `rfc` / `jtbd` are optional per task and emit the `data-*` reference layer that reverse-trace and the story-map queries consume. Omit them until stories exist; add them as stories are captured onto the map.
188
+ - **Author nothing a story file already says (ADR-104).** A story's lifecycle state, its value statement, and the problems it closes are all read from the story when the map renders — so a transition needs no map edit and does not re-open the map's ratification. There are no `storyStatus`, `value` or row-level `problems` fields, and no `--status` or `--value` flags. A row's problems are the union of its stories'; a map's are the union of its rows'.
189
+ - **A map carries no decision trace (ADR-106).** There is no `traces.adrs`. A decision constrains how something is built, and the thing built is the story — so a decision reference belongs on the story (`adrs:` in its frontmatter), not on the lens drawn over it.
190
+ - **Write no prose the grid already carries.** There is no `lead` and no `traceProse`. A map is a title, a grid, and the jobs it is drawn for. Where a column or a row needs a note, both carry a `note` field — put it next to the thing it describes, not in a paragraph at the top restating the picture below it. Six kinds of duplication were removed from this format for exactly this reason; the seventh will be whatever gets added back.
191
+ - **Prefer the edit command over hand-editing the island** for structural changes — it validates against the map's own backbone and bands, names what is available when you get an id wrong, and leaves the file untouched on failure:
192
+
193
+ ```bash
194
+ wr-itil-story-map-edit <map.html> add-card --story STORY-<NNN> --activity <id> --release <id> --title "..." [--ref "..."]
195
+ wr-itil-story-map-edit <map.html> move-card --story STORY-<NNN> [--activity <id>] [--release <id>]
196
+ wr-itil-story-map-edit <map.html> remove-card --story STORY-<NNN>
197
+ wr-itil-story-map-edit <map.html> add-band --id <id> --name "..." [--rfc RFC-<NNN>] [--note "..."]
198
+ wr-itil-story-map-edit <map.html> add-activity --id <id> --title "..." [--note "..."]
199
+ ```
200
+
201
+ Hand-editing the island still works — the renderer reads whatever is there — but the command is the safer path and the one to reach for by default.
202
+ - Every task needs `activity` and `release` matching an `id` in `backbone` / `releases`, or it renders nowhere.
203
+ - Presentation is not yours to set. There is no CSS in the JSON and no inline `style` anywhere; the template is the only styling source.
204
+ - Escape a literal `<` in any string as `\\u003c`. A raw `</script>` inside the island terminates the block early — in the renderer and in a browser — and the renderer will refuse the file rather than emit a truncated map.
205
+
206
+ **Born unconfirmed (ADR-090).** Do NOT author `humanOversight` at all: the renderer treats an absent field as `unconfirmed`, so writing it is writing the default, and the field exists so that `wr-itil-mark-story-oversight-confirmed` can set `confirmed` — which an agent must never hand-write (P348). The `<meta name="human-oversight">` tag is a projection the renderer regenerates from the island; never author it directly (ADR-102). The map is NOT ratified until a human confirms it via `/wr-itil:manage-story-map <NNN> ratify`, which writes `confirmed` + an `oversight-hash` fingerprint through `wr-itil-mark-story-oversight-confirmed`. Until then `wr-itil-detect-unratified-stories-maps` surfaces it and an RFC may not reference its stories (`wr-itil-check-rfc-stories-ratified`).
164
207
 
165
- **Born `human-oversight: unconfirmed` (ADR-090).** A new map is created with `<meta name="human-oversight" content="unconfirmed">`orthogonal to the `status:` lifecycle. It is NOT ratified until a human confirms it via `/wr-itil:manage-story-map <NNN> ratify` (which writes `confirmed` + an `oversight-hash` fingerprint via `wr-itil-mark-story-oversight-confirmed`). Until then `wr-itil-detect-unratified-stories-maps` surfaces it and an RFC may not reference its stories (`wr-itil-check-rfc-stories-ratified`). Any later content edit drifts the fingerprint and silently re-opens ratification (lazy-fingerprint, ADR-009 lineage). Do NOT hand-write `confirmed` here — born-unconfirmed is the load-bearing default.
208
+ **What re-opens ratification, and what does not (ADR-103).** A later edit to the map's SUBSTANCE the map's own substance as ADR-090 defines it its journey, its identity, and what it traces to; `oversight_map_substance_keys()` is the field list drifts the fingerprint and silently re-opens ratification. Release rows and the cards in them sit OUTSIDE the basis, so drawing a row or adding a story to one changes nothing. Presentation is outside it too: restyling the shared template cannot revoke an approval. Do NOT hand-write `confirmed` — born-unconfirmed is the load-bearing default.
166
209
 
167
210
  ### 6. Single commit — `## Story Maps` reverse-trace refresh
168
211
 
169
- **Stage list**: new HTML file PLUS driving problem files (refresh `## Story Maps` section via `update-problem-references-section.sh <file> "Story Maps"`) PLUS driving JTBD files (refresh `## Story Maps` section via `update-jtbd-references-section.sh <file> "Story Maps"`). Do NOT stage `docs/story-maps/README.md` (deferred).
212
+ **Stage list**: the map `.html` (it carries its own data island) AND, on a repository's first map, the shared `docs/story-maps/story-map.css` the renderer places beside them, PLUS driving problem files (refresh `## Story Maps` section via `update-problem-references-section.sh <file> "Story Maps"`) PLUS driving JTBD files (refresh `## Story Maps` section via `update-jtbd-references-section.sh <file> "Story Maps"`). Do NOT stage `docs/story-maps/README.md` (deferred).
170
213
 
171
214
  ```bash
172
215
  for pid_token in $(echo "$problem_trace" | tr ',' ' '); do
@@ -20,7 +20,7 @@ Stories live under `docs/stories/<state>/STORY-<NNN>-<slug>.md` in lifecycle sub
20
20
  - `docs/stories/done/*.md` — done (acceptance-criteria all-ticked + linked RFC closes; auto-transitioned from in-progress)
21
21
  - `docs/stories/archived/*.md` — archived (closed without completion; manual transition)
22
22
 
23
- Per ADR-060 I11 invariant (Phase 2 deferred): stories MUST NOT carry a WSJF field. Ordering inside an RFC is per the RFC's frontmatter `stories: [STORY-<NNN>, ...]` array (ordered = execution sequence per ADR-060 line 259), NOT per any per-story WSJF.
23
+ Per ADR-060 I11 invariant (Phase 2 deferred): stories MUST NOT carry a WSJF field. Legacy RFC files retain their ordered frontmatter `stories:` array. Under ADR-103, newer RFCs are release rows and their story sequence comes from the row's cards on the map, NOT from per-story WSJF.
24
24
 
25
25
  ## Argument grammar
26
26
 
@@ -59,18 +59,24 @@ ls docs/stories/draft/*.md docs/stories/accepted/*.md docs/stories/in-progress/*
59
59
 
60
60
  For each story file, parse the YAML frontmatter to extract: `story-id`, `status`, `problems`, `jtbd`, `rfcs`, `story-maps`, `estimated-effort`. Read the H1 line for the title.
61
61
 
62
- **Filtered live scan** (`--rfc RFC-<NNN>` provided) — resolve the RFC file first, then enumerate its ordered `stories:` array:
62
+ **Filtered live scan** (`--rfc RFC-<NNN>` provided) — resolve a legacy RFC file first; if absent, resolve the release row through the canonical map query:
63
63
 
64
64
  ```bash
65
- rfc_file=$(ls docs/rfcs/RFC-<NNN>-*.md 2>/dev/null | head -1)
66
- [ -z "$rfc_file" ] && echo "RFC-<NNN> not found" >&2 && exit 1
67
-
68
- # Extract the ordered stories: array from RFC frontmatter
69
- # (Phase 2 Slice 11 extension — see ADR-060 RFC frontmatter extension)
70
- stories_list=$(awk '/^stories:/,/^[a-z]/' "$rfc_file" | grep -oE 'STORY-[0-9]+')
65
+ shopt -s nullglob
66
+ rfc_files=(docs/rfcs/RFC-<NNN>-*.md)
67
+ rfc_file="${rfc_files[0]:-}"
68
+ shopt -u nullglob
69
+
70
+ if [ -n "$rfc_file" ]; then
71
+ stories_list=$(awk '/^stories:/,/^[a-z]/' "$rfc_file" | grep -oE 'STORY-[0-9]+')
72
+ else
73
+ rfc_rows=$(wr-itil-story-map-query find-rfc RFC-<NNN>)
74
+ [ "$rfc_rows" = "[]" ] && echo "RFC-<NNN> not found" >&2 && exit 1
75
+ stories_list=$(grep -oE 'STORY-[0-9]+' <<< "$rfc_rows" | awk '!seen[$0]++')
76
+ fi
71
77
  ```
72
78
 
73
- For each `STORY-<NNN>` in the ordered list, resolve to a file under `docs/stories/*/STORY-<NNN>-*.md` and parse the frontmatter as above. Preserve the RFC's array ordering in the output.
79
+ For each `STORY-<NNN>` in the ordered list, resolve to a file under `docs/stories/*/STORY-<NNN>-*.md` and parse the frontmatter as above. Preserve the legacy array or map-row card ordering in the output.
74
80
 
75
81
  ### 3. Display
76
82
 
@@ -102,7 +108,7 @@ For each `STORY-<NNN>` in the ordered list, resolve to a file under `docs/storie
102
108
 
103
109
  Omit empty sections rather than rendering empty headers. The Estimated Effort column is omitted from the Draft section because effort is deferred at capture and only required at accepted per I10 INVEST Estimable.
104
110
 
105
- **The `Accepted by` column (ADR-101, Accepted + In Progress sections only).** `accepted` carries one of two ratification bases and they must not read alike — a report that counts them together overstates how much a human has seen, which is exactly what JTBD-202's auditable-handover outcome depends on. Render `human` when the story's marker is a human ratification event, and `pure-decomposition` when it carries `oversight-basis: pure-decomposition` (machine-accepted by an opted-in project's AFK loop; awaiting post-hoc human ratification). Derive it by reading the story's `oversight-basis:` frontmatter line its absence means `human`. Never collapse the two into a single "accepted" tick.
111
+ **The `Approved by` column (ADR-103, Accepted + In Progress sections only).** A story carries no oversight field; approval is its map's. Name the ratified map that approves it, since that is what the human actually looked at, and leave the column empty when no map in the story's `story-maps:` field is ratified. Derive it with `story_is_approved` never by reading a `human-oversight:` line off the story, which no longer exists.
106
112
 
107
113
  **Filtered mode** (`--rfc RFC-<NNN>`) — render a single ordered table:
108
114
 
@@ -45,7 +45,7 @@ Enumerate each lifecycle subdir:
45
45
  ls docs/story-maps/draft/*.html docs/story-maps/accepted/*.html docs/story-maps/in-progress/*.html docs/story-maps/completed/*.html docs/story-maps/archived/*.html 2>/dev/null
46
46
  ```
47
47
 
48
- For each map file, parse the `<meta>` block to extract: `story-map-id`, `status`, `problems`, `rfcs`, `jtbd`. Use `xmllint --xpath` when available; fall back to `grep` on `<meta>` lines:
48
+ For each map file, parse the `<meta>` block to extract: `story-map-id`, `status`, `problems`, `rfcs`, `jtbd`. `problems` and `rfcs` are DERIVED — the union of the stories' `problems:` and of the release-row identities — so they carry content the data island does not. Use `xmllint --xpath` when available; fall back to `grep` on `<meta>` lines:
49
49
 
50
50
  ```bash
51
51
  status=$(xmllint --xpath 'string(//meta[@name="status"]/@content)' "$map" 2>/dev/null || \
@@ -118,7 +118,7 @@ Each transition is a `git mv` + Edit + `git add` (P057 staging trap) + README re
118
118
 
119
119
  | Transition | Checks |
120
120
  |------------|--------|
121
- | `proposed → accepted` | All `problems:` entries resolve. Architect re-review PASS. JTBD re-review PASS. `## Scope` populated (not "deferred"). `## Tasks` decomposed (≥ 1 task; not just deferred placeholder). **`stories:` non-empty — run `wr-itil-check-rfc-has-stories <rfc-file>`; hard-block on non-zero exit (empty `stories: []` or missing).** Per **ADR-089** the empty-stories fallback is removed: an atomic fix is an RFC with exactly one full story; an empty `stories:` is permitted only on a `proposed`/draft RFC before the fix is scoped, never at `accepted`. **Every listed story ratified — run `wr-itil-check-rfc-stories-ratified <rfc-file>`; hard-block on non-zero (any listed story carries `human-oversight: unconfirmed`, has no marker, or is missing).** Per **ADR-090** an RFC may reference only ratified stories; composes with the has-stories gate (has-stories checks ≥1 exists, ratified checks each is confirmed). |
121
+ | `proposed → accepted` | All `problems:` entries resolve. Architect re-review PASS. JTBD re-review PASS. `## Scope` populated (not "deferred"). `## Tasks` decomposed (≥ 1 task; not just deferred placeholder). **`stories:` non-empty — run `wr-itil-check-rfc-has-stories <rfc-file>`; hard-block on non-zero exit (empty `stories: []` or missing).** Per **ADR-089** the empty-stories fallback is removed: an atomic fix is an RFC with exactly one full story; an empty `stories:` is permitted only on a `proposed`/draft RFC before the fix is scoped, never at `accepted`. **Every listed story approved — run `wr-itil-check-rfc-stories-ratified <rfc-file> [stories-root] [maps-root]`; hard-block on non-zero (any listed story names no story map, or names one that is unratified or has drifted).** Per **ADR-103** a story's approval is its map's, so the remedy is always to ratify the MAP — a story carries no oversight marker of its own. Composes with the has-stories gate (has-stories checks ≥1 exists, this checks each is approved). |
122
122
  | `accepted → in-progress` | First commit referencing `Refs: RFC-<NNN>` exists OR is being authored in this same commit. I1 hard-block on missing/orphan trace. |
123
123
  | `in-progress → verifying` | All `## Tasks` checked. `## Verification` section drafted (release marker, user-side check, trace closure path). I1 hard-block on missing/orphan trace. |
124
124
  | `verifying → closed` | User explicitly confirms (or AFK Rule 6 fallback for evidence-based close per ADR-022 / ADR-044 framework-resolved silent dispatch). I1 advisory-with-escalation if driving problems Closed/Parked. |
@@ -30,7 +30,7 @@ Per ADR-060 Phase 2 amendment 2026-05-10 lines 200-253:
30
30
  | **I9** trace-to-JTBD | Every story traces to ≥ 1 JTBD | Hard-block at `/wr-itil:capture-story` (also verified at every transition) |
31
31
  | **I10** INVEST shape | At acceptance, INVEST behaviourally: ≥1 acceptance criterion (Testable); user-value statement (Valuable); no Blocked-by-unaccepted refs (Independent); `estimated-effort` field set (Estimable); S/M effort SHOULD; L/XL flagged decomposition-candidate (Small) | Hard-block at `manage-story <NNN> accepted` |
32
32
  | **I11** no-WSJF-leak | Phase 2: stories MUST NOT carry a WSJF field | Behavioural test at this skill (no WSJF field added/read) |
33
- | **I12** ratification | Every story carries a recorded ratification basis before it is implementable — a human ratification event, OR the ADR-101 pure-decomposition carve-out | Hard-block at `manage-story <NNN> accepted`; re-asserted at the commit locus by `itil-no-implement-draft-gate` |
33
+ | **I12** approval | Every story is approved before it is implementable — by its story map's ratification (ADR-103). A story carries no marker of its own, and a leftover one approves nothing | Hard-block at `manage-story <NNN> accepted`; re-asserted at the commit locus by `itil-no-implement-draft-gate` |
34
34
 
35
35
  **Bootstrap-exemption marker** (per ADR-060 line 339 + ADR-053 Bootstrapping precedent): the I7/I8/I9/I10 retrofit on bootstrap-migration stories rides a one-time exemption marker `<!-- bootstrap-exempt: STORY-MAP-001 migration per ADR-060 amendment 2026-05-10 -->` inline with the frontmatter. Non-bootstrap captures with the marker fail per the behavioural test.
36
36
 
@@ -42,8 +42,8 @@ Per ADR-060 Phase 2 amendment 2026-05-10 lines 200-253:
42
42
  /wr-itil:manage-story <STORY-NNN> in-progress # Manual transition (auto-fires on first non-capture commit)
43
43
  /wr-itil:manage-story <STORY-NNN> done # Transition in-progress → done (gates all-criteria-ticked + RFC closed)
44
44
  /wr-itil:manage-story <STORY-NNN> archived # Close without completion
45
- /wr-itil:manage-story <STORY-NNN> ratify # ADR-090: confirm this story (usually via manage-story-map ratify)
46
45
  /wr-itil:manage-story review # Re-rank all stories + refresh README
46
+ # (no `ratify` action — ADR-103: approval is the map's, see /wr-itil:manage-story-map <NNN> ratify)
47
47
  ```
48
48
 
49
49
  ## Rule 6 audit (per ADR-032 + ADR-013 + ADR-060)
@@ -52,9 +52,9 @@ Per ADR-060 Phase 2 amendment 2026-05-10 lines 200-253:
52
52
  |----------|-----------|-----------------|
53
53
  | Story ID resolution | Mechanical: `STORY-<NNN>` regex match against `docs/stories/*/STORY-<NNN>-*.md` | silent-mechanical |
54
54
  | Lifecycle transition validation | Mechanical: state machine — draft → accepted → in-progress → done; allow draft → archived; disallow backwards | silent-mechanical |
55
- | I7 + I8 hard-block at accepted | Mechanical: frontmatter `rfcs:` and `story-maps:` arrays MUST be non-empty AND each ID must resolve to a file in `docs/rfcs/` and `docs/story-maps/` | silent-mechanical |
55
+ | I7 + I8 hard-block at accepted | Mechanical: frontmatter `rfcs:` and `story-maps:` arrays MUST be non-empty. Each map ID resolves to `docs/story-maps/`; each RFC resolves either to a legacy file in `docs/rfcs/` or to a release row containing this story via `wr-itil-story-map-query find-rfc` | silent-mechanical |
56
56
  | I10 INVEST shape check | Mechanical: `## User value` section non-empty; `## Acceptance criteria` has ≥ 1 `- [ ]` line; `estimated-effort` field set to S/M/L/XL; L/XL flagged as decomposition-candidate (advisory, not blocking per ADR-060 line 252 architect-amendment-2026-05-10 nitpick N3) | silent-mechanical |
57
- | I12 ratification basis at accepted | Mechanical: the story is ratified (`human-oversight: confirmed` + a matching `oversight-hash`), OR `wr-itil-check-afk-accept-eligible` exits 0. Never an `AskUserQuestion` the eligibility predicate is fully framework-resolved per ADR-101, and a story that fails it is HELD for the ratification drain, not asked about | silent-mechanical |
57
+ | I12 approval at accepted | Mechanical: `story_is_approved` passes — every map in the story's `story-maps:` field is ratified (ADR-103). The story itself carries no oversight field. Never an `AskUserQuestion`: the predicate is fully framework-resolved, and a story that fails it is HELD until its map is ratified, not asked about | silent-mechanical |
58
58
  | INVEST shape violation | Halt-with-stderr-directive listing the missing INVEST attributes; user re-invokes after editing the story body | n/a (halt) |
59
59
  | README refresh on every transition | Mechanical: regenerate `docs/stories/README.md` Story Rankings + Done tables from FS truth; stage in same commit | silent-mechanical |
60
60
  | Reverse-trace refresh on driving artefacts | Mechanical: every transition refreshes `## Stories` section on each driving problem + JTBD + RFC + story-map via the Slice 2a/2b helpers | silent-mechanical |
@@ -101,13 +101,12 @@ Use `AskUserQuestion` for direction-setting fields (e.g. `## User value` rewrite
101
101
  For any transition `<from> → <to>`:
102
102
 
103
103
  1. **Verify pre-transition invariants** for `<to>`:
104
- - `accepted`: I7 + I8 + I10 + **I12** hard-block (see § I-invariant table).
104
+ - `accepted`: I7 + I8 + I10 + **I12** hard-block (see § I-invariant table). For I7, a legacy RFC file satisfies resolution as before. Otherwise `wr-itil-story-map-query find-rfc <RFC-ID>` must return a release row whose `stories` contains this story; a row with no matching card fails because the row is the RFC under ADR-103.
105
105
 
106
- **I12 — the ratification gate (ADR-090 / ADR-096, enforced per ADR-101; the hole P465 named).** A story reaches `accepted` only with a recorded ratification basis. Resolve it in this order:
106
+ **I12 — the approval gate (ADR-090 / ADR-096 / ADR-103; the hole P465 named).** A story reaches `accepted` only when it is approved. Under ADR-103 the map is the approval surface, so resolve in this order:
107
107
 
108
- 1. Already ratified (`human-oversight: confirmed` with a matching `oversight-hash`)? I12 satisfied; proceed.
109
- 2. Otherwise run `wr-itil-check-afk-accept-eligible "$story_file"`. **Exit 0** the story is pure decomposition of confirmed substance in an opted-in project; proceed and mark it with `--pure-decomposition` at step 5 below.
110
- 3. **Exit 1** — HALT. Do not accept. Interactively, route to the ratify flow (§ 7.5). Under AFK, leave the story in `draft` and queue the ratification to `outstanding_questions`; do NOT ask about it, and never hand-write the marker.
108
+ 1. Every map in the story's `story-maps:` field is ratified? I12 satisfied; proceed.
109
+ 2. Otherwise HALT. Do not accept. Interactively, ratify the **map** 7.5), which approves every story on it at once. Under AFK, leave the story in `draft` and queue the map's ratification to `outstanding_questions`; do NOT ask about it. Never write an oversight field onto the story — the marker writer refuses story paths, and there is no story-level approval to write.
111
110
 
112
111
  - `in-progress`: linked RFC status is `accepted` or `in-progress` (you can't progress a story under a proposed/closed RFC).
113
112
  - `done`: ALL `- [ ]` checkboxes in `## Acceptance criteria` are ticked (i.e. zero unticked); linked RFC status is `closed` OR the RFC's other stories have closed (transitive closure check deferred to a per-RFC `manage-rfc done-gate` check in a future slice).
@@ -122,15 +121,9 @@ For any transition `<from> → <to>`:
122
121
 
123
122
  4. **P057 staging-trap** — after the Edit, re-stage: `git add "docs/stories/${to_state}/STORY-${nnn}-${slug}.md"`.
124
123
 
125
- 5. **Ratify LAST, then re-stage (ADR-101 — write ordering is load-bearing).** Only when I12 resolved via the eligibility path at step 1. Every step above writes substance the `estimated-effort` field, the `## User value` and `## Acceptance criteria` fills and every substance write drifts the content hash. So the ratify write MUST be the LAST write of the transition, and the file MUST be re-staged after it:
124
+ 5. **No ratify write here (ADR-103).** A story transition never writes an oversight marker, because a story has none — `wr-itil-mark-story-oversight-confirmed` refuses a story path. Approval is its map's, derived at read time from `story-maps:`, so nothing in this transition can grant or revoke it.
126
125
 
127
- ```bash
128
- export CLAUDE_SESSION_ID="${CLAUDE_SESSION_ID:?marker shims silently no-op on an empty SID (P368)}"
129
- wr-itil-mark-story-oversight-confirmed --pure-decomposition "docs/stories/accepted/STORY-${nnn}-${slug}.md"
130
- git add "docs/stories/accepted/STORY-${nnn}-${slug}.md"
131
- ```
132
-
133
- Get the order wrong and the marker records a hash of a file that no longer exists — the commit-locus gate then denies the very next commit, inside an AFK loop, where the recovery is itself gated. Skip the re-stage and the committed blob carries the story WITHOUT the marker while the worktree has it; the hook reads the worktree, so nothing catches it.
126
+ The write-ordering hazard this step used to guard is now a MAP concern only: when ratifying a map, the marker write must come after every substance write to that map and the file must be re-staged afterwards, or the marker records a hash of content that no longer exists. See `/wr-itil:manage-story-map <NNN> ratify`.
134
127
 
135
128
  #### Auto-transition triggers (ADR-060 line 292)
136
129
 
@@ -177,7 +170,8 @@ for jid in $(awk '/^jtbd:/{gsub(/[][]/,""); gsub(/,/," "); for(i=2;i<=NF;i++)pri
177
170
  git add "$jtbd_file"
178
171
  done
179
172
 
180
- # RFC parents sibling shape (after Slice 11 ships the Stories section helper)
173
+ # Legacy RFC parents only. Row-backed RFCs have no markdown parent; the card
174
+ # in the release row is their reverse trace and is checked by I7 above.
181
175
  for rid in $(awk '/^rfcs:/{gsub(/[][]/,""); gsub(/,/," "); for(i=2;i<=NF;i++)print $i; exit}' "$story_file"); do
182
176
  rfc_file=$(ls docs/rfcs/${rid}-*.md 2>/dev/null | head -1)
183
177
  [ -z "$rfc_file" ] && continue
@@ -193,16 +187,15 @@ done
193
187
 
194
188
  The helpers are idempotent + lazy-empty per the Slice 2a/2b/Slice 11 contract.
195
189
 
196
- ### 7.5. Ratification flow (`ratify`) — ADR-090 / STORY-022
190
+ ### 7.5. There is no per-story ratify (ADR-103)
191
+
192
+ **`ratify` is not a story subcommand.** A story carries no oversight marker, so there is nothing to write, and `wr-itil-mark-story-oversight-confirmed` exits 2 on a story path. Approval is its story map's: **`/wr-itil:manage-story-map <NNN> ratify`** approves every story on that map at once, including stories added later.
197
193
 
198
- `ratify` confirms human oversight of a single story. Ratification is **not itself a lifecycle state, but it IS a precondition of `accepted`** (I12) and it is drift-invalidated, so any later content edit re-opens it via the `oversight-hash` fingerprint. The primary ratification surface is `/wr-itil:manage-story-map <NNN> ratify` (map first, then its stories); this per-story form is for ratifying a story on its own.
194
+ Approval is still a precondition of `accepted` (I12) and still drift-invalidated but at the map tier. A change to the map's activity columns, its identity, its prose or its traces re-opens it. Drawing a release row, putting a story in one, or editing a story's body does not.
199
195
 
200
- > This sentence previously read "orthogonal to the `status:` lifecycle", which was ambiguous between "ratification is not itself a status" (what it meant) and "ratification is not a precondition of any status" (what it read like). An agent acting on the second reading accepted and implemented an unratified story believing it compliant — the P465 failure, 2026-07-26.
196
+ > Until ADR-103 this section ratified a single story. The clause "orthogonal to the `status:` lifecycle" was ambiguous between "ratification is not itself a status" (what it meant) and "ratification is not a precondition of any status" (what it read like); an agent acting on the second reading accepted and implemented an unratified story believing it compliant — the P465 failure, 2026-07-26. That hole is closed differently now: there is one approval surface, and the commit gate reads it directly.
201
197
 
202
- 1. **Born-confirmed discipline (P348):** `export CLAUDE_SESSION_ID` first (the marker shim no-ops on an empty SID). Never write `confirmed` without the same-turn confirm below.
203
- 2. **Brief + confirm:** present the story's `## User value` + `## Acceptance criteria` — substance BEFORE the ID (P350) — then `AskUserQuestion` with two options: **Ratify** / **(type something)**. On **Ratify**: run `wr-itil-mark-story-oversight-confirmed <story-file>` (writes `confirmed` + fingerprint). On free-text: apply the correction as a story edit and re-present (the edit re-opens ratification).
204
- 3. **AFK (ADR-013 Rule 6):** if `AskUserQuestion` is unavailable, do NOT auto-ratify (hollow marker, P348) — leave it unratified for the `/wr-itil:work-problems` Step 2.4 drain. **One carve-out, and only one (ADR-101):** where `wr-itil-check-afk-accept-eligible` exits 0, the loop marks the story with `--pure-decomposition` at the accept transition (§ 7 mechanic 5). That is not a hollow marker — it records a machine-established basis rather than asserting a human confirmed something they never saw, it is opt-in per project and per story, and the story still surfaces for post-hoc human ratification via `wr-itil-detect-unratified-stories-maps --with-afk-accepted`. Everything else stays held.
205
- 4. **Single commit** per ADR-014.
198
+ **AFK (ADR-013 Rule 6):** if `AskUserQuestion` is unavailable, do NOT auto-ratify a map (hollow marker, P348). Leave it unratified for the `/wr-itil:work-problems` Step 2.4 drain. **No carve-outs** — ADR-101's AFK pure-decomposition exception is retired, because capturing a story no longer re-opens its map's approval and the deadlock it worked around is gone.
206
199
 
207
200
  ### 8. List flow (`list`)
208
201
 
@@ -251,7 +244,7 @@ After commit, report:
251
244
  | Status transitions | Owns draft → accepted → in-progress → done → archived | Out of scope (creation only) |
252
245
  | README refresh | Inline per transition (P094 mirror) | Deferred to `manage-story review` or `wr-itil-reconcile-stories` |
253
246
  | Auto-transition triggers | Fires on first implementing commit against an ACCEPTED story (accepted→in-progress; draft→in-progress REMOVED per ADR-096) + criteria-ticked + RFC-closed (in-progress→done) | n/a |
254
- | Reverse-trace refresh on parents | Inline per transition | Inline per capture |
247
+ | Reverse-trace refresh on parents | Inline for problems, JTBDs and legacy RFC files; row-backed RFCs use the map card | Same |
255
248
  | Commit grain | One commit per transition / per intake | One commit per capture |
256
249
 
257
250
  ## Related