yadflow 3.18.1 → 4.0.0-next.1

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 (156) hide show
  1. package/CHANGELOG.md +355 -0
  2. package/README.md +79 -26
  3. package/bin/commands.mjs +41 -0
  4. package/bin/yad.mjs +437 -124
  5. package/cli/artifact-status.mjs +34 -15
  6. package/cli/checkpoint.mjs +69 -49
  7. package/cli/codeowners-command.mjs +170 -0
  8. package/cli/codeowners.mjs +397 -0
  9. package/cli/commit.mjs +13 -9
  10. package/cli/companion.mjs +2 -2
  11. package/cli/dial.mjs +183 -0
  12. package/cli/docs.mjs +88 -32
  13. package/cli/doctor.mjs +1472 -97
  14. package/cli/epic-state.mjs +3478 -232
  15. package/cli/epic.mjs +506 -0
  16. package/cli/errors.mjs +4 -1
  17. package/cli/gate.mjs +1002 -209
  18. package/cli/history.mjs +556 -0
  19. package/cli/hook.mjs +266 -55
  20. package/cli/hubcommit.mjs +6 -17
  21. package/cli/index-command.mjs +87 -0
  22. package/cli/ledger.mjs +57 -7
  23. package/cli/lib.mjs +184 -18
  24. package/cli/manifest.mjs +367 -56
  25. package/cli/migrate.mjs +726 -53
  26. package/cli/mode.mjs +170 -0
  27. package/cli/next.mjs +349 -90
  28. package/cli/openpr.mjs +191 -39
  29. package/cli/people.mjs +654 -0
  30. package/cli/plan.mjs +417 -132
  31. package/cli/platform.mjs +110 -129
  32. package/cli/product-index.mjs +287 -0
  33. package/cli/protection.mjs +706 -0
  34. package/cli/reconcile.mjs +38 -12
  35. package/cli/repo-publish.mjs +24 -26
  36. package/cli/repo.mjs +23 -14
  37. package/cli/report.mjs +21 -15
  38. package/cli/review.mjs +24 -27
  39. package/cli/riskmap-command.mjs +289 -0
  40. package/cli/riskmap.mjs +373 -0
  41. package/cli/setup.mjs +139 -287
  42. package/cli/ship.mjs +7 -6
  43. package/cli/skill.mjs +180 -0
  44. package/cli/skip.mjs +211 -30
  45. package/cli/thread.mjs +42 -17
  46. package/cli/tidy.mjs +20 -20
  47. package/cli/update-commit.mjs +22 -22
  48. package/cli/usage.mjs +115 -109
  49. package/package.json +3 -3
  50. package/skills/sdlc/config.yaml +166 -87
  51. package/skills/sdlc/module-help.csv +35 -35
  52. package/skills/yad-analysis/SKILL.md +125 -65
  53. package/skills/yad-architecture/SKILL.md +34 -23
  54. package/skills/yad-architecture/references/contract-format.md +10 -8
  55. package/skills/yad-backfill/SKILL.md +14 -8
  56. package/skills/yad-backfill/references/backfill.md +1 -1
  57. package/skills/yad-change/SKILL.md +127 -52
  58. package/skills/yad-change/references/triage.md +42 -28
  59. package/skills/yad-checks/SKILL.md +89 -45
  60. package/skills/yad-checks/references/check-gates.md +315 -92
  61. package/skills/yad-checks/templates/checks/build-test-lint.sh +25 -7
  62. package/skills/yad-checks/templates/checks/commit-message.sh +17 -3
  63. package/skills/yad-checks/templates/checks/contract-check.sh +58 -2
  64. package/skills/yad-checks/templates/checks/epic-open.sh +3 -3
  65. package/skills/yad-checks/templates/checks/install-deps.sh +46 -0
  66. package/skills/yad-checks/templates/checks/ledger-guard.sh +94 -18
  67. package/skills/yad-checks/templates/checks/lineage-check.sh +23 -9
  68. package/skills/yad-checks/templates/checks/package-manager.sh +140 -0
  69. package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +4 -4
  70. package/skills/yad-checks/templates/checks/risk-map-check.sh +438 -0
  71. package/skills/yad-checks/templates/checks/verified-commits.sh +20 -46
  72. package/skills/yad-checks/templates/github/yad-checks.yml +37 -5
  73. package/skills/yad-checks/templates/github/yad-hub-checks.yml +5 -5
  74. package/skills/yad-checks/templates/github/yad-update-guard.yml +3 -4
  75. package/skills/yad-checks/templates/github/yad-verified-commits.yml +4 -4
  76. package/skills/yad-checks/templates/gitlab/.gitlab-ci.yml +7 -1
  77. package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +22 -4
  78. package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +5 -5
  79. package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +4 -4
  80. package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +91 -0
  81. package/skills/yad-checks/templates/hooks/ledger-guard.sh +38 -7
  82. package/skills/yad-commit/SKILL.md +6 -6
  83. package/skills/yad-connect-design/SKILL.md +6 -6
  84. package/skills/yad-connect-design/references/design-context.md +1 -1
  85. package/skills/yad-connect-design/references/design-registry.md +2 -2
  86. package/skills/yad-connect-docs/SKILL.md +12 -12
  87. package/skills/yad-connect-docs/references/docs-registry.md +1 -1
  88. package/skills/yad-connect-learning/SKILL.md +5 -5
  89. package/skills/yad-connect-learning/references/learning-registry.md +2 -2
  90. package/skills/yad-connect-repos/SKILL.md +92 -54
  91. package/skills/yad-connect-repos/references/code-context.md +6 -6
  92. package/skills/yad-connect-repos/references/hub-config.md +68 -58
  93. package/skills/yad-connect-repos/references/repos-registry.md +10 -9
  94. package/skills/yad-connect-repos/references/risk-map.md +81 -0
  95. package/skills/yad-connect-testing/SKILL.md +6 -6
  96. package/skills/yad-connect-testing/references/testing-context.md +3 -4
  97. package/skills/yad-connect-testing/references/testing-registry.md +2 -2
  98. package/skills/yad-defects/SKILL.md +8 -8
  99. package/skills/yad-discovery/SKILL.md +130 -94
  100. package/skills/yad-discovery/references/discovery-schema.md +23 -7
  101. package/skills/yad-discovery/references/foundation-schema.md +374 -0
  102. package/skills/yad-docs/SKILL.md +16 -11
  103. package/skills/yad-docs/references/data-mapping.md +9 -7
  104. package/skills/yad-docs/templates/app/package-lock.json +3 -3
  105. package/skills/yad-docs-overview/SKILL.md +32 -17
  106. package/skills/yad-docs-overview/references/pipeline-model.md +47 -28
  107. package/skills/yad-docs-sync/SKILL.md +10 -5
  108. package/skills/yad-docs-sync/references/staleness.md +8 -7
  109. package/skills/yad-engineer-review/SKILL.md +88 -24
  110. package/skills/yad-engineer-review/references/ship-and-record.md +25 -16
  111. package/skills/yad-epic/SKILL.md +178 -100
  112. package/skills/yad-epic/references/state-schema.md +626 -117
  113. package/skills/yad-hub-bridge/SKILL.md +66 -48
  114. package/skills/yad-hub-bridge/references/bridge.md +110 -83
  115. package/skills/yad-hub-bridge/references/login-roster.md +163 -70
  116. package/skills/yad-hub-bridge/templates/checks/hub-route.sh +22 -19
  117. package/skills/yad-hub-bridge/templates/github/yad-gate-sync.yml +34 -14
  118. package/skills/yad-hub-bridge/templates/gitlab/gitlab-ci.include-root.yml +2 -2
  119. package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +22 -12
  120. package/skills/yad-implement/SKILL.md +29 -15
  121. package/skills/yad-implement/references/implement-conventions.md +2 -2
  122. package/skills/yad-learn/SKILL.md +9 -9
  123. package/skills/yad-learn/references/learning-state.md +2 -2
  124. package/skills/yad-open-pr/SKILL.md +64 -29
  125. package/skills/yad-pair-review/SKILL.md +18 -16
  126. package/skills/yad-pair-review/references/session-state.md +4 -4
  127. package/skills/yad-pr-template/SKILL.md +48 -27
  128. package/skills/yad-pr-template/references/risk-routing.md +97 -24
  129. package/skills/yad-pr-template/templates/checks/pr-template.sh +37 -15
  130. package/skills/yad-pr-template/templates/checks/pr-title.sh +27 -13
  131. package/skills/yad-pr-template/templates/checks/risk-route.sh +107 -14
  132. package/skills/yad-pr-template/templates/github/pull_request_template.md +7 -5
  133. package/skills/yad-pr-template/templates/gitlab/merge_request_templates/Default.md +7 -5
  134. package/skills/yad-pr-template/templates/hub/github/pull_request_template.md +15 -14
  135. package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +15 -13
  136. package/skills/yad-reconcile/SKILL.md +3 -3
  137. package/skills/yad-report/SKILL.md +5 -5
  138. package/skills/yad-review-companion/SKILL.md +12 -9
  139. package/skills/yad-review-gate/SKILL.md +198 -79
  140. package/skills/yad-review-gate/references/gating.md +230 -54
  141. package/skills/yad-run/SKILL.md +86 -56
  142. package/skills/yad-run/references/run-loop.md +67 -45
  143. package/skills/yad-ship/SKILL.md +18 -14
  144. package/skills/yad-spec/SKILL.md +31 -17
  145. package/skills/yad-spec/references/spec-handoff.md +17 -5
  146. package/skills/yad-status/SKILL.md +114 -56
  147. package/skills/yad-stories/SKILL.md +42 -27
  148. package/skills/yad-stories/references/story-schema.md +10 -9
  149. package/skills/yad-stub/SKILL.md +59 -48
  150. package/skills/yad-sync-repos/SKILL.md +3 -3
  151. package/skills/yad-test-cases/SKILL.md +37 -30
  152. package/skills/yad-test-cases/references/test-cases-schema.md +8 -5
  153. package/skills/yad-timeline/SKILL.md +8 -7
  154. package/skills/yad-ui/SKILL.md +46 -25
  155. package/cli/roster.mjs +0 -164
  156. package/skills/sdlc/install.sh +0 -68
@@ -5,9 +5,9 @@ in files on disk. Nothing hidden."). No database, no browser storage.
5
5
 
6
6
  ## Every file states its shape — `schemaVersion`
7
7
 
8
- Each JSON **object** the CLI writes under a `.sdlc/` directory carries `"schemaVersion": 1` as its
9
- first key. It says what shape the file is in, so a future release can recognise an older file and
10
- upgrade it instead of guessing.
8
+ Each JSON **object** the CLI writes under a `.sdlc/` directory carries a `"schemaVersion"` as its
9
+ first key, holding the shape this release writes — **10** today. It says what shape the file is in, so
10
+ a future release can recognise an older file and upgrade it instead of guessing.
11
11
 
12
12
  **When you author one of these files by hand, include the key**, exactly as the examples below show.
13
13
  Several kinds here — `state.json` on the seeding path, `change.json`, `contract-lock.json`,
@@ -18,7 +18,7 @@ nothing to flip.
18
18
 
19
19
  ```json
20
20
  {
21
- "schemaVersion": 1,
21
+ "schemaVersion": 10,
22
22
  "epicId": "EP-checkout"
23
23
  }
24
24
  ```
@@ -28,14 +28,42 @@ Three rules go with it, and they are permanent (`docs/roadmap-idea-1.md`, Part 2
28
28
  1. **A file with no version counts as version 1.** Nothing has to be rewritten to be readable. Files
29
29
  written before the stamp existed are read as shape 1, and get the key the next time the engine
30
30
  writes them.
31
- 2. **The four list files never carry it.** `approvals.json`, `comments.json`, `hub-prs.json` and
32
- `reconcile-debt.json` are JSON arrays at the top level, and an array cannot hold a key. Rule 1
31
+ 2. **The list files never carry it.** `approvals.json`, `comments.json`, `hub-prs.json` (also written
32
+ as `product-prs.json`) and `reconcile-debt.json` are JSON arrays at the top level, and an array cannot hold a key. Rule 1
33
33
  covers them: no version means version 1.
34
34
  3. **`schemaVersion` is not the CLI version.** `.sdlc/cli-version.json` records which release of the
35
35
  `yad` CLI set the project up, and changes on every release. `schemaVersion` describes the file's
36
36
  shape and changes only when that shape really changes — which is rare, and always paired with a
37
37
  `yad migrate` step that moves existing projects onto it.
38
38
 
39
+ ## The Product index — `.sdlc/index.json` (derived; never write it by hand)
40
+
41
+ One file at the Product root that summarizes every work item (E19). It is **derived** from each item's
42
+ `state.json`, `epic.md` and, for the title of an item with none in `epic.md`, `change.json`, and rebuilt by `yad index` or a gate write on the default branch. On a
43
+ verified Product only CI writes it, and the ledger guard rejects anyone else. **A skill never writes
44
+ it.** A skill that writes `state.json` by hand leaves it behind, and `yad doctor` says so. To read it
45
+ as the files say it is now, run `yad index --json`.
46
+
47
+ ```json
48
+ {
49
+ "schemaVersion": 10,
50
+ "inputs": "sha256:…",
51
+ "items": [
52
+ { "id": "EP-checkout", "dir": "epics/EP-checkout", "title": "Checkout from the mobile app",
53
+ "kind": null, "type": "feature", "theme": null,
54
+ "parent": null, "thread": "EP-checkout", "profile": "classic", "currentStep": "architecture",
55
+ "createdAt": "2026-06-04", "repos": ["backend"],
56
+ "steps": { "todo": 3, "in_progress": 1, "in_review": 0, "done": 2, "skipped": 0, "deferred": 0, "satisfied": 0, "blocked": 0 },
57
+ "lastClosed": { "step": "epic-review", "date": "2026-06-05", "by": "ada" } },
58
+ { "id": "EP-broken", "dir": "epics/EP-broken", "unreadable": true, "why": ".sdlc/state.json does not parse" }
59
+ ]
60
+ }
61
+ ```
62
+
63
+ `title` (E111) is the `title:` key in `epic.md`; if that is missing, the `title` in the item's
64
+ `change.json` (only change items have one); if both are missing, `null`. It is always one line. A screen prints the id when the title is `null`. The Foundation's
65
+ title is always `"Foundation"`, because it has no `epic.md`.
66
+
39
67
  ## `state.json`
40
68
  The per-epic state machine.
41
69
 
@@ -43,66 +71,477 @@ The per-epic state machine.
43
71
  |-------|---------|
44
72
  | `epicId` | The stable `EP-<slug>` ID. Never renamed. |
45
73
  | `createdAt` | ISO date the epic was created. |
74
+ | `type` | The work-item type — `feature` \| `change` \| `defect` \| `hotfix` \| `chore` — copied from `epic.md`. Shape 5 on. **Not the same field as `steps[].type`**, which is `author` \| `review+approve`, and not the same as the top-level `kind` a stub or the discovery front-zero carries. |
75
+ | `profile` | The lifecycle route this chain came from — `classic` \| `analysis-first` \| `chore` \| `spike` \| `discovery` \| `foundation`. Shape 6 on (`foundation` from shape 8). See "Lifecycle profiles" below. |
46
76
  | `currentStep` | `id` of the step the workflow is waiting on right now. |
47
- | `steps[]` | Ordered list of every front-state step. |
77
+ | `steps[]` | Ordered list of every Shape step. |
48
78
 
49
79
  Each `steps[]` entry:
50
80
 
51
81
  | Field | Values | Meaning |
52
82
  |-------|--------|---------|
53
83
  | `id` | `analysis`, `analysis-review`, `epic`, `epic-review`, `architecture`, `architecture-review`, `ui-design`, `ui-design-review`, `stories`, `stories-review`, `test-cases`, `test-cases-review` | Step identity. |
84
+ | `type` | `author` \| `review+approve` | Authoring step or a team review gate. |
85
+ | `artifact` | filename or folder | The file/folder this step produces or gates. |
86
+ | `assistance` | `none` \| `review` \| `heavy` | Dial 1 (driver) — who does the work. **The name the engine reads.** |
87
+ | `driver` | `human` \| `pair` \| `agent` | Dial 1, shape-4 name, written beside `assistance`: `human`=`none`, `pair`=`review`, `agent`=`heavy`. |
88
+ | `automation` | `human_approve` \| `machine_advance` | Dial 2 (advance) — who moves it forward. **The name the engine reads.** |
89
+ | `advance` | `human` \| `auto` | Dial 2, shape-4 name, written beside `automation`: `human`=`human_approve`, `auto`=`machine_advance`. A review step is NEVER `auto`. |
90
+ | `locked` | `true` \| `false` | Seeded `true` on every Shape step. Since E34 it decides nothing about the dial on a step the catalogue knows: an author step may be set to `auto` (for the whole project, in `.sdlc/automation.json`), and a review gate is `human` because it is a gate. It is still read as a gate on a step id the catalogue does not know. |
91
+ | `status` | one of the **step states** below | Where the step stands. |
92
+ | `record` | `{ reason, by, date, link? }` | Present on a `skipped`, `deferred`, `satisfied` or `blocked` step: WHY it is in that state. |
93
+ | `closed` | `{ by, date, via, pr?, commit?, hash?, mergedBy?, run?, waived?, capped? }` | Present on a `done` step that closed from this release on: HOW it closed (E18). See "Closing records" below. |
94
+ | `risk_tags` | subset of `contract`, `auth`, `payments` | Sets the step's full approver count (build plan §4): `contract` +2, `auth`/`payments` +1 on top of a base of 1 (the highest tag, never the sum). Only the base is enforced; the risk step is advisory and reported as a shortfall against the count capped at the active people less one, floor 1 (E72; no cap when the people cannot be counted). A team gate reports `rule: "count"`. A step with one of these tags has its review PR name and label the epic's `repos`. |
95
+
96
+ ### The step catalogue
97
+
98
+ Every one of those ids is defined in **one place in the code** — `STEPS` in `cli/epic-state.mjs` (E4).
99
+ A row says which phase the step is in, whether it authors an artifact or gates one, which file it
100
+ writes, which skill runs it, and — for a gate — which step it reviews. The phase table, the skill
101
+ tables and the Build order are all views of it, held to it by tests, so a step cannot exist in one and
102
+ be missing from another.
103
+
104
+ The catalogue is code. Nothing about it is written into `state.json`, so no file shape changed.
105
+
106
+ **The one column a project overrides is the skill.** Which skill runs a step is not a fact about the
107
+ lifecycle — it depends on what a team installed — so it lives in `.sdlc/skills.json`, a Product-level
108
+ file the engine reads before it falls back to the catalogue (E6):
109
+
110
+ ```json
111
+ { "schemaVersion": 6, "steps": { "architecture": "our-arch-skill", "stories": ["shape-it", "yad-stories"] } }
112
+ ```
113
+
114
+ A value may be one skill or a list; a list runs as a **chain**, in order, each skill seeing what the
115
+ one before it produced, and the last output is the artifact. Write it with `yad skill bind <step>
116
+ <skill>…` (`yad skill list` shows what runs each step now, `yad skill unbind <step>` drops the line).
117
+ Nothing checks the skill NAME — the engine cannot know what you installed — but `yad doctor` reports a
118
+ binding that will never run: a value that is not a skill name (`YAD-CFG-006`), a step id this release
119
+ does not know (`skills:unknown-step`) and a review gate, which `yad gate` drives (`skills:review-step`).
120
+ It never rewrites the file.
121
+
122
+ **The kill switch and the Shape dials live in `.sdlc/automation.json` (E34)**, also a Product-level file,
123
+ also absent by default:
124
+
125
+ ```json
126
+ { "schemaVersion": 10, "kill": { "on": true, "reason": "bad deploy", "by": "al", "date": "2026-09-15" },
127
+ "steps": { "architecture": "auto" } }
128
+ ```
129
+
130
+ `kill` is written by `yad kill --reason "<why>"` / `yad unkill`; while `on`, every step is held at
131
+ `advance: human`; the record a `yad unkill` or a second `yad kill` replaces is kept as `kill.previous`, one level
132
+ deep. `steps` lists the feature Shape AUTHOR steps (not the Foundation's) the project set to `auto` with `yad dial <step> --to
133
+ auto` — `human` is the key's absence. A Shape `auto` is **recorded, not acted on**: nothing drives a Shape
134
+ step on its own until the engine runs agents (E26). A Build step's dial is not here; it is on its lane in
135
+ `build-state`. A review gate is never `auto`. A file that will not parse is read as the kill switch ON,
136
+ and nothing writes over it. `yad doctor` reports a broken file (`automation`), an active switch
137
+ (`automation:kill`), a gate set to auto (`automation:gate`), a Build step listed here
138
+ (`automation:build-step`), an unknown id or value (`automation:unknown`), and a `kill_switch: true` left in
139
+ `_bmad/sdlc/config.yaml`, which nothing reads any more (`automation:legacy-kill`).
140
+
141
+ **When a chain disagrees with the catalogue, the chain wins.** A change-epic seeded by hand before E42
142
+ may carry a chain the engine would not write, a project may run a chain from a newer release, and leaving a step out is normal
143
+ — not every epic has screens. `yad doctor` reports five disagreements it can see and rewrites nothing:
144
+ `step:artifact` (a step naming a different file from the one the gate hashes — different spellings of
145
+ the SAME artifact are fine, since `stories`, `stories/` and `stories.md` are one gate),
146
+ `step:no-artifact` (a step naming no file at all, the one case that stops `yad gate` outright),
147
+ `step:kind` (a step on the wrong side of the author / review line), `step:orphan-gate` (a gate whose
148
+ step is not in the chain, so nothing tells anyone to write what it reviews) and `step:off-route` (a
149
+ chain matching no lifecycle profile — see below). `skip:not-optional` is reported the same way: a step
150
+ marked N/A that this epic's route does not mark optional. An id the catalogue does not carry is left to
151
+ `phase:unknown`.
152
+
153
+ ### Lifecycle profiles
154
+
155
+ A **profile** is a named, ordered chain of catalogue steps — the route an epic takes. Six are
156
+ defined, in `LIFECYCLE_PROFILES` (`cli/epic-state.mjs`, E5). `classic`, `analysis-first` and `discovery`
157
+ are routes the skills already seeded by hand; the two short lanes (E40) and `foundation` (E75) came later:
158
+
159
+ | Profile | Steps | Seeded by |
160
+ |---|---|---|
161
+ | `classic` | the 10-step chain, `epic` first | `yad-epic`, `yad-stub`, `yad-change` |
162
+ | `analysis-first` | the 12-step chain, `analysis` before `epic` | `yad-analysis` |
163
+ | `chore` | `epic` · `epic-review` · `stories` · `stories-review` | `yad-epic --profile chore` |
164
+ | `spike` | the chore lane with `analysis` · `analysis-review` in front | `yad-analysis --profile spike` |
165
+ | `discovery` | `discovery` · `discovery-review` | the OLD spelling of the Product level — still read; `yad migrate --apply` converts it |
166
+ | `foundation` | `foundation` · `foundation-review` | `yad foundation new`, then `yad-discovery` authors the sections (E75) |
167
+
168
+ `ui-design` and its gate are the optional pair on `classic` and `analysis-first`. The short lanes mark
169
+ **nothing** optional — they drop the steps they do not need from the chain instead, so `yad skip` on
170
+ one is refused outright rather than pointing at a broken chain. **Which steps an epic may skip
171
+ comes from the route that epic is on**, and from nowhere else (E35): there is no engine-wide list of
172
+ skippable steps, so a route that drops a step no longer makes it skippable on every other route too.
173
+ The engine reads the route the epic **records** (`profile`, shape 6), and keeps reading it even when the
174
+ chain no longer fits — a chain written by a newer release carries steps this one does not know, and
175
+ treating that as "no route" would make a skip the epic already recorded start failing its gate.
176
+ `yad doctor` reports the disagreement rather than deciding it. An epic with no route at all — none
177
+ recorded that this release knows, and no match from its steps — has nothing optional, because inventing
178
+ one would let a step be skipped on the strength of a route nobody chose.
179
+
180
+ The parallel, non-blocking `test-cases` track is NOT recorded in the profile: `advanceState` still
181
+ decides it from the step id, and a second copy of that rule sitting unread in the profile would be
182
+ free to drift.
183
+
184
+ **Each epic records its route in `state.json` as `profile`** (shape 6). The value is one of `classic`,
185
+ `analysis-first`, `chore`, `spike`, `discovery` and `foundation`. `yad epic new` writes it when it seeds the chain,
186
+ the seeding skills write it in their templates, and `yad migrate` fills it in for an epic that predates
187
+ the field by reading the chain that epic already carries.
188
+
189
+ **`yad migrate` only ever writes `classic`, `analysis-first` or `discovery`.** It answers a question about the past, and
190
+ the matching rule picks the SHORTEST route a chain fits — so a chain built only from `epic`,
191
+ `epic-review`, `stories` and `stories-review` reads as `chore` now and read as `classic` before. No
192
+ chain yadflow has ever seeded is affected, because every shipped seed carries `architecture` and no
193
+ short lane has it; the freeze is what makes that a guarantee rather than a fact about today's data. An
194
+ epic seeded on a short lane never needs the stamp: `yad epic new` writes its `profile` at seed time.
195
+
196
+ The chain is still the truth. The recorded name says which route the epic was STARTED on; matching the
197
+ chain (`matchLifecycleProfile`) says which route the steps are on NOW, and `yad doctor` compares them:
198
+
199
+ | Check | Fires when |
200
+ |---|---|
201
+ | `profile:unknown` | `profile` holds a value no release carries |
202
+ | `profile:disagree` | the chain is cleanly on a route, and it is not the one recorded |
203
+ | `step:off-route` | the chain is on no route at all — a step no route has, or two out of order |
204
+
205
+ A chain missing steps still matches — dropping `ui-design` is normal. An epic whose chain matches
206
+ nothing gets NO `profile` key from the migration: inventing one would silence `step:off-route` by
207
+ making the file agree with itself.
208
+
209
+ > **Two different things are called a profile.** This one is the lifecycle route. `hub.json.profile` is
210
+ > the unrelated setup record `yad setup` writes: `{ codebase, repo_layout, team_size }`.
211
+
212
+ ### The six phases
213
+
214
+ Every step belongs to one of six named phases, and each phase sits inside one of the three parts:
215
+
216
+ | Phase | Part | Steps |
217
+ |---|---|---|
218
+ | Discover | Shape | `analysis` · `epic`, each with its review gate |
219
+ | Design | Shape | `architecture` · `ui-design`, each with its review gate |
220
+ | Plan | Shape | `stories` · `test-cases`, each with its review gate |
221
+ | Build | Build | `spec` · `tasks` · `implement` · `checks` · `engineer-review` |
222
+ | Release | Run | **planned, not built** |
223
+ | Operate | Run | **planned, not built** |
224
+
225
+ **A phase is never written into `state.json`.** It is worked out from `currentStep`, so there is
226
+ nothing to author, nothing to keep in step, and no way for it to disagree with the step it describes.
227
+
228
+ - A Shape review gate takes the phase of the artifact it reviews, so `epic-review` is Discover beside
229
+ `epic`. Build steps have no such gates — `engineer-review` is a step in its own right — so
230
+ `checks-review` is not a step and resolves to nothing.
231
+ - The `currentStep` markers are not steps and never appear in `steps[]`. Two of them
232
+ (`backfill-pending`, `backfill-done`) have no phase. **`ready-for-build` is the exception:** an epic
233
+ sitting on it is in Build, and stays there for the rest of its life, because the individual Build
234
+ steps live per story per repo in `build-state/` rather than in this file.
235
+ - The **Product level** — `EP-foundation` in `foundation/`, or `EP-discovery`, its older spelling — is
236
+ not on the six-phase ladder. It has a ladder of its own with one phase, **Foundation**, and it is in
237
+ that phase for its whole life, `foundation-done` / `discovery-done` included: the phase is decided by
238
+ the ledger's `kind`, not by the step id (E75).
239
+
240
+ `yad doctor` reports a step id no phase claims. That is the same table that binds a step to its skill,
241
+ so an id with no phase is an id no skill runs.
54
242
 
55
243
  ### Two valid chain shapes (analysis is optional)
56
244
 
57
245
  The `analysis` step (and its `analysis-review` gate) is **optional** — it exists only when the team
58
- ran `yad-analysis` before the epic. The entry-point skill (whichever runs first) is the one
59
- that assigns `EP-<slug>` and seeds `state.json` + the empty ledgers; the other skill detects an
246
+ ran `yad-analysis` before the epic. The entry-point skill (whichever runs first) assigns `EP-<slug>`
247
+ and runs **`yad epic new`**, which writes `state.json` and the empty ledgers; the other skill sees an
60
248
  existing `state.json` and does **not** re-seed.
61
249
 
62
- - **With analysis** (12 steps — `yad-analysis` seeded the chain):
250
+ **The engine owns the chain.** No skill writes one by hand on these two routes any more — `yad-epic`
251
+ runs `yad epic new EP-<slug>`, `yad-analysis` runs it with `--profile analysis-first`, and `yad-stub`
252
+ runs it with `--stub`, and `yad-discovery` runs `yad foundation new` for the Product level (E75). `yad-change`
253
+ runs it with `--parent` and `--inherits` for a threaded chain whose inherited steps are bound to the
254
+ owning epic's artifact hashes (E42). A seeded chain leaves its first author step **open**, not `done` — the
255
+ command runs before the artifact exists; `yad gate open` closes it when the gate opens.
256
+
257
+ - **With analysis** — the `analysis-first` route, 12 steps:
63
258
  `analysis → analysis-review → epic → epic-review → architecture → architecture-review → ui-design →
64
259
  ui-design-review → stories → stories-review → test-cases → test-cases-review`. Seeded `currentStep`
65
- is `analysis-review`; `epic` starts `blocked`.
66
- - **Without analysis** (10 steps — `yad-epic` is the entry point, the default):
260
+ is `analysis`, which starts `in_progress`; everything after it starts `todo`.
261
+ - **Without analysis** — the `classic` route, 10 steps, and the default:
67
262
  `epic → epic-review → … → stories-review → test-cases → test-cases-review`. Seeded `currentStep` is
68
- `epic-review`.
263
+ `epic`, which starts `in_progress`.
264
+
265
+ **The two short lanes.** Not every piece of work is the size of a feature, and before these the only
266
+ way to make the chain shorter was to skip steps — which `classic` does not allow, since only
267
+ `ui-design` is optional on it.
268
+
269
+ - **`chore`, 4 steps:** `epic → epic-review → stories → stories-review`. Upkeep somebody has already
270
+ decided on: a dependency bump, a CI move.
271
+ - **`spike`, 6 steps:** the same lane with `analysis → analysis-review` in front. A timeboxed
272
+ question, where finding the answer is the work.
273
+
274
+ Both drop `architecture`, `ui-design` and `test-cases` with their gates, and each is **absent, not
275
+ `optional`** — a recorded reason for skipping a step the route never had is noise in the audit trail.
276
+ Dropping the architecture gate drops the contract with it: a short-lane epic has no `contract.md` and
277
+ no lock, which is the point. **Work that moves the shared cross-repo surface belongs on `classic`,
278
+ whatever its size.**
279
+
280
+ Neither lane drops `epic` or `stories`, and that is not a matter of taste: `epic.md` carries the
281
+ work-item type and the `parent:` lineage, and `stories-review` is the step that makes the epic ready
282
+ for Build. The type and the route are chosen separately — `--type chore --profile classic` is right
283
+ for large upkeep that does touch the contract.
284
+
285
+ Both seeded values move to the review gate when `yad gate open` runs, which is also what closes the
286
+ authoring step. Before E17b the seeds recorded the gate directly, because a skill only seeded once it
287
+ had already written the artifact; a chain seeded that way is still perfectly valid and nothing rewrites
288
+ it.
289
+
290
+ Every other review gate — `analysis-review`, `epic-review`, `ui-design-review`, `stories-review` and
291
+ `test-cases-review` — carries no `risk_tags`: it needs 1 approval, and its full approver count is 1.
292
+
293
+ ### The step states (shape 7)
294
+
295
+ `status` is one of eight words. Seven are the model (`docs/roadmap-idea-1.md`, Part 1); `in_review` is
296
+ the eighth, and is `in_progress` on a step that is a review gate.
297
+
298
+ | State | Meaning | Chain continues past it | Artifact written here | Must record |
299
+ |-------|---------|-------------------------|-----------------------|-------------|
300
+ | `todo` | Not started | no | no | — |
301
+ | `in_progress` | Being worked on | no | no | — |
302
+ | `in_review` | Its review gate is open | no | no | — |
303
+ | `done` | Completed here | yes | **yes** | the artifact |
304
+ | `skipped` | Consciously chose not to | yes | no | reason + who + when |
305
+ | `deferred` | Will do it, later — `yad defer` | yes | no | reason (saying who waits) + who + when |
306
+ | `satisfied` | Done elsewhere | yes | no | link + who + when |
307
+ | `blocked` | Cannot proceed, **not our choice** | no | no | who or what we wait for |
308
+
309
+ Two columns, not one, and the engine reads them through `isPassed` and `isAuthored`
310
+ (`cli/epic-state.mjs`). "The chain may continue" and "there is an artifact here to audit" are different
311
+ questions: a `skipped` step lets `stories` start and has nothing to hash, so a gate audit skips it
312
+ while `preconditionsMet` walks straight past it.
313
+
314
+ The four states at the bottom carry a `record`:
315
+
316
+ ```json
317
+ { "reason": "backend-only epic, no user-facing surface", "by": "@al", "date": "2026-07-08" }
318
+ ```
319
+
320
+ `reason` is required; `by` and `date` are best-effort and may be `null` (attribution is a nicety on the
321
+ audit trail, never a gate). A `satisfied` record also carries `link`, naming where the work happened.
322
+ `yad doctor` reports a recorded state with no record as `step:no-record`.
323
+
324
+ ### Closing records (E18)
325
+
326
+ A `done` step says **how it closed**, in its own key, `closed`. It is not `record`: `record` says why a
327
+ step is *not* done, and `blocked` is read by whether it has one, so the two never share a key.
328
+
329
+ ```json
330
+ { "id": "architecture-review", "status": "done",
331
+ "closed": { "by": "yad-gate-sync[bot]", "date": "2026-06-08", "via": "merge", "pr": 7,
332
+ "commit": "c0ffee1", "hash": "sha256:…", "mergedBy": "al" } }
333
+ ```
69
334
 
70
- `analysis-review`, `ui-design-review`, and `test-cases-review` carry no `risk_tags` (base rule:
71
- owner + 1 reviewer).
335
+ | Field | Meaning |
336
+ |---|---|
337
+ | `by` | Who **wrote** the record, as on every record: the platform login `gh`/`glab` reports, else git `user.name` (on CI, usually the bot's git name). Best-effort, may be `null`. |
338
+ | `date` | When the step closed: the merge date for a merge, otherwise the day the command ran. |
339
+ | `via` | How it closed — see the table below. |
340
+ | `pr` | The review PR/MR number, when there is one. |
341
+ | `commit` | The merge commit, when the platform reports it. |
342
+ | `hash` | The artifact hash the step closed on — the one approvals bind to. |
343
+ | `mergedBy` | The platform login that merged the PR, when the platform reports it. |
344
+ | `run` | Build lanes only: the `uid` of the trust-log run that moved past the step. |
345
+ | `waived` | `solo` on a **review** step that passed while solo mode waived its approvals (E10). Absent when the gate counted approvals, and never on the author step it closes. |
346
+ | `capped` | `{ needed, to, active }` on a **review** step of a TEAM gate that passed while the capacity cap LOWERED its ask (E72): the full count, the capped ask, and the number of active people it read. It records what the gate asked, not what held it — the base held (only the base holds). `yad gate status` prints it as `count capped from 3 to 1 (2 active people)`. Absent when no cap applied (the people could not be counted, or the cap lowered nothing), never in solo mode (nothing was counted), never on a step that passed by its skip or inherited shortcut (nothing was asked), and never on the author step. |
347
+
348
+ | `via` | Written by | When |
349
+ |---|---|---|
350
+ | `merge` | `yad gate sync` / `yad gate ci` | A review gate passed on its merge |
351
+ | `approved` | the `yad-review-gate` skill, by hand | A review gate passed on recorded approvals on a Product with no platform: nothing merged, so no `pr` or `commit` |
352
+ | `review-passed` | `yad gate sync` / `yad gate ci`, or that skill | Its author step, closed when its gate passed because nothing closed it earlier |
353
+ | `review-opened` | `yad gate open` (local ledger) / `yad gate sync` | An author step, closed when its review opened |
354
+ | `repair` | `yad gate repair` | A stranded author step — an escape hatch, so it says so |
355
+ | `auto` / `human` | the `yad-run` skill, in `build-state` | A Build lane step: the dial let the run go on by itself, or it stopped for a person |
356
+
357
+ - **Optional keys are left out**, never written as `null`: a close with no PR has no `pr`.
358
+ - **The first close wins.** A step that already has a `closed` keeps it; a re-sync never rewrites it.
359
+ - **Nothing is invented.** A field the closer does not know is left out.
360
+ - **Steps closed before this release carry none**, and nothing asks for one: `yad doctor` does not warn.
361
+ - **No shape change.** An older release ignores the key, and nothing reads `done` differently because it
362
+ is there.
363
+ - `yad gate status` prints it under each review step; `yad history show` (E20) prints it under every
364
+ step, author steps included, and `yad history search` finds text in it.
365
+
366
+ **`blocked` changed meaning in shape 7.** Before it, every writer used `blocked` for "waiting on an
367
+ earlier step" — what is now `todo`. The two are told apart by the record, not by the shape number:
368
+
369
+ > `blocked` with **no** record is the old word and reads as `todo`.
370
+ > `blocked` **with** a record is a real blocker, and the record says on whom.
371
+
372
+ No release before shape 7 ever wrote a record, so that is exact. The converse is why clearing a blocker
373
+ has a verb: `yad unblock EP-<slug> <step>` (E37) moves `status` off `blocked` **and** removes the record
374
+ in one write, because deleting only the record would turn the step silently into a `todo`. An author
375
+ step whose earlier steps have all passed goes to `in_progress`; anything else, a review gate included,
376
+ goes to `todo`. It never touches `build-state/<story>.json`, which the `yad-run` skill writes. On a verified
377
+ Product it refuses once the epic's ledger is on the default branch: CI owns `state.json` there and has no
378
+ step for it yet.
379
+
380
+ A status this release does not know is left alone — the file wins — and reported as
381
+ `step:unknown-status`. It fails closed: the step counts as neither passed nor authored.
72
382
 
73
383
  ### `ui-design` is optional (skippable)
74
384
 
75
- The `ui-design` step (and its `ui-design-review` gate) is **optional** for an epic with no
385
+ Optional **on the routes that say so**, which today is `classic` and `analysis-first` — see the
386
+ profiles section above. The `chore` and `spike` lanes mark nothing optional, because they drop
387
+ `ui-design` from the chain rather than carrying it as skippable. The step (and its `ui-design-review` gate) is optional for an epic with no
76
388
  user-facing surface — a backend/API service, a data pipeline, infra work. Unlike `analysis` (which is
77
389
  optional by being **omitted** from the chain at seed time), `ui-design` is **always seeded** and then
78
390
  **marked N/A in place** so the skip stays visible and auditable. The single mechanism is
79
- `yad skip EP-<slug> ui-design --reason "<why>"` (reverse with `--undo`), usable at epic-authoring time
80
- or any point **up to authoring the `ui-design` step** — the skip is refused once its review gate has
81
- opened (the UI work is committed by then) or once `stories` has started. `--undo` is allowed until the
82
- `stories` review opens.
83
-
84
- A skipped step gets four extra fields and is pre-marked `done`:
391
+ `yad skip EP-<slug> ui-design --reason "<why>"` (reverse with `yad unskip EP-<slug> ui-design`, or the
392
+ older spelling `yad skip … --undo`), usable at epic-authoring time or any point **up to authoring the
393
+ `ui-design` step**. On a verified Product the window is shorter: once the epic's ledger is on the default
394
+ branch (its first review PR has merged), `state.json` is CI's alone, and `yad skip`, `unskip`, `defer`,
395
+ `undefer` and `unblock` refuse and write nothing. So there, skip `ui-design` before that first PR merges:
396
+ right after seeding on `classic`, and during the analysis step on `analysis-first`, whose first review PR
397
+ is the analysis review.
398
+
399
+ When each verb is too late is a rule about the chain, not about `ui-design` (E36):
400
+
401
+ - **A skip** is refused once the step's review gate has opened (the work is committed by then), or once
402
+ work has started on **any later step**.
403
+ - **An un-skip** is refused once work has started on any step **past the step after the pair**. "The
404
+ step after the pair" is the first later step that is not itself skipped — the step the skip opened.
405
+ It may still be under way, and un-skipping pushes it back to `todo` — but not finished: a `done` step
406
+ was written on the assumption the skipped step did not apply, so that refuses too.
407
+ - **"Work has started"** means the step is `in_progress`, `in_review` or `done` in this chain. `skipped`,
408
+ `satisfied`, `deferred` and `blocked` are not work done here and never count. A status this release
409
+ cannot name counts as started. A non-step entry after the pair is refused as a malformed chain.
410
+
411
+ On `classic` and `analysis-first` the step after the `ui-design` pair is `stories`: a skip is refused
412
+ once `stories` starts, and an un-skip once `stories` is finished or its review opens.
413
+
414
+ **`currentStep` on an un-skip.** The restored author step becomes `currentStep` again when every earlier
415
+ step has passed — unless the epic **earned** `ready-for-build`: its `stories-review` is `done` (or `satisfied`, reviewed
416
+ in the parent epic) and comes before the restored step. That step then runs beside Build, as `test-cases` does, and `currentStep` stays
417
+ put. An epic that reached `ready-for-build` only by skipping (a `chore` whose stories pair was skipped by
418
+ hand) is moved back to the restored step.
419
+
420
+ A skipped step is `status: "skipped"` with a `record`, and keeps four legacy fields beside them:
85
421
 
86
422
  | Field | Values | Meaning |
87
423
  |-------|--------|---------|
88
- | `skipped` | `true` | This step is N/A for this epic; pre-marked `done`, short-circuited by `gatePredicate` (`rule: "skipped"`) so no review is required. |
424
+ | `status` | `"skipped"` | The step's own state (shape 7). Short-circuited by `gatePredicate` (`rule: "skipped"`) so no review is required. |
425
+ | `record` | `{ reason, by, date }` | Why it was skipped, who marked it and when. |
426
+ | `skipped` | `true` | The pre-shape-7 spelling, still written and still read (rule 3, add before you remove). |
89
427
  | `skipReason` | string | Why it was skipped (e.g. "backend-only service, no UI"). |
90
- | `skippedBy` | login/name or `null` | Who marked it N/A (best-effort, from the roster/git identity). |
428
+ | `skippedBy` | login/name or `null` | Who marked it N/A (best-effort: the platform login from `gh`/`glab`, else git `user.name`). |
91
429
  | `skippedAt` | `YYYY-MM-DD` or `null` | When it was marked N/A. |
92
430
 
431
+ A pre-shape-7 file spells the same step `status: "done"` with `skipped: true` beside it, and `yad
432
+ migrate` translates it. Either spelling reads identically: `stepStatus` treats the flag as a legacy
433
+ encoding **only when `status` is `done`** — a `skipped: true` on an unstarted step is a hand edit, and
434
+ reading it as finished would let anyone unblock a chain by typing one word into a file.
435
+
93
436
  Both the `ui-design` **and** `ui-design-review` entries carry these fields. `advanceState` steps over
94
- any `skipped` step, so approving `architecture-review` on a UI-less epic lands directly on `stories`;
95
- `preconditionsMet` treats the pre-`done` steps as satisfied. `unskipStep` (via `yad skip … --undo`)
96
- strips the fields and restores the chain, refused once `stories-review` has opened. Only `ui-design` is
97
- skippable today (engine `SKIPPABLE_STEPS`).
437
+ any skipped step, so approving `architecture-review` on a UI-less epic lands directly on `stories`;
438
+ `preconditionsMet` treats it as passed (never as authored). `unskipStep` (via `yad unskip`, or
439
+ `yad skip … --undo`) strips the fields — the record included — and restores the chain to `todo`,
440
+ refused once the step after the pair is finished or a step past it has started (on `classic`, once `stories` is done or `stories-review` opens). `ui-design` is the
441
+ only step any route marks optional today; the engine reads that off the epic's own route
442
+ (`optionalStepsFor`) rather than from a list of its own.
443
+
444
+ ### Deferring an optional step (`yad defer`)
445
+
446
+ A step the route marks optional can also be **deferred**: it applies, and the team will do it later.
447
+ `yad defer EP-<slug> ui-design --reason "<why, and who is waiting>"` writes this on both the author step
448
+ and its `-review` gate:
449
+
450
+ ```json
451
+ { "id": "ui-design", "type": "author", "artifact": "ui-design.md",
452
+ "status": "deferred",
453
+ "record": { "reason": "screens come with the redesign; @design is waiting on it", "by": "@al", "date": "2026-09-14" } }
454
+ ```
455
+
456
+ There are no legacy fields: `deferred` was new in shape 7, so no older reader needs them. `by` is who
457
+ wrote the record, as on every record. `yad undefer EP-<slug> ui-design` removes the record and puts the
458
+ step back.
459
+
460
+ A deferral follows the skip rules above:
461
+
462
+ - **Same permission.** Only a route-optional step can be deferred, because the chain continues past a
463
+ deferred step (`isPassed`) with no approvals on its review. The review is not waived, though:
464
+ `gatePredicate` gives a deferred step no short-circuit (E38), so it still reports what is missing, and
465
+ `yad gate status` prints the step as "deferred (still owed)".
466
+ - **Same window to set aside.** `yad defer` is refused where `yad skip` is. Putting it back is
467
+ different — see "Picking a deferral up late" below.
468
+ - **Same walk.** `advanceState` and the shared skip/defer code (`setAsideStep`, `restoreStep`) step over
469
+ `skipped` **and** `deferred` steps (`isSetAside`). `yad gate open` refuses a set-aside step, and
470
+ `yad sync-status` leaves its artifact's `status:` line alone. A `deferred` status typed by hand onto a
471
+ required step is walked past too, as a hand-typed `skipped` always was; `yad doctor` reports both
472
+ (`skip:not-optional`).
473
+ - **Not interchangeable.** Deferring a skipped step, or skipping a deferred one, is refused with the
474
+ command that puts it back first, so neither record is lost.
475
+
476
+ ### Picking a deferral up late (E41)
477
+
478
+ For a skip, the un-skip window is the meaning: work finished without the step was built on it not
479
+ applying, so `yad unskip` is still refused once the step after the pair is finished. A deferral promised
480
+ to come back, so `yad undefer` has **no closing window** — except on a verified Product, where it refuses once the epic's
481
+ ledger is on the default branch. Before later work has finished it behaves like
482
+ `yad unskip`. After it has finished, it **re-opens** the pair behind that work:
483
+
484
+ | Field | After a late `yad undefer` |
485
+ |-------|----------------------------|
486
+ | author step `status` | `in_progress` (or `todo` if an earlier step has not passed); `record` removed |
487
+ | `-review` gate `status` | `todo`; `record` removed |
488
+ | every later step | unchanged |
489
+ | `currentStep` | unchanged |
490
+ | `debt` | kept, if the deferral carried it |
491
+
492
+ A late re-open needs later work that is **finished** here. If later work has only started, or holds a
493
+ status this release cannot name, `yad undefer` is refused as before. A re-opened step can be deferred
494
+ again with `yad defer` (not skipped: the finished work was built without it), so a late undefer is not
495
+ a one-way door.
496
+
497
+ **A re-opened step is read off the chain, never off a flag:** an unfinished step with a later step
498
+ **completed here** (`status: "done"`, not inherited, not skipped) that is not its own `-review` gate.
499
+ Such a step runs beside the chain, like `test-cases`:
500
+
501
+ - `preconditionsMet` does not name it the blocker of a step past that finished work. It still blocks
502
+ the steps between itself and that work, its own gate included.
503
+ - `markInReview` moves `currentStep` only forward, so opening its review does not pull the chain back.
504
+ - `advanceState`, for a gate that passed behind the chain (`currentStep` past it, or `ready-for-build`),
505
+ closes the gate and changes nothing else. In the forward case it walks past every step that has already
506
+ passed — skipped, deferred, inherited (`satisfied`) or `done` — and opens the next one only if it is `todo`.
507
+ - `yad next` lists it under `reopened` (JSON) and prints `re-opened lane: …`.
508
+
509
+ Only `done` counts as finished work. A skipped, deferred or `satisfied` step after an unfinished step is
510
+ not work built here, so it never turns that step into a lane.
511
+
512
+ ### Debt on a deferral (`debt: true`, E41)
513
+
514
+ `yad defer EP-<slug> <step> --reason "<why>" --debt` writes the deferral with one more key on both steps
515
+ of the pair:
516
+
517
+ ```json
518
+ { "id": "ui-design", "type": "author", "artifact": "ui-design.md",
519
+ "status": "deferred", "debt": true,
520
+ "record": { "reason": "launch date; @design is waiting on it", "by": "@al", "date": "2026-09-14" } }
521
+ ```
522
+
523
+ | Rule | Detail |
524
+ |------|--------|
525
+ | What it means | owed back — set aside under pressure, and reminded until paid |
526
+ | Where it may sit | a `deferred` pair only; `yad skip --debt` is refused, since a skip owes nothing |
527
+ | What it changes | nothing about the state: a debt is exactly as passed, and as unauthored, as any deferral |
528
+ | Adding it later | `yad defer … --debt` on a step already deferred adds the flag and keeps the record |
529
+ | Paying it back | `yad undefer`, early or late; the flag stays on while the step is worked on. Not on a verified Product once the ledger is on the default branch: there a debt cannot be paid back until CI has a step for it |
530
+ | Setting it aside again | `yad defer` again keeps the flag; `yad skip` is refused on a step owed as debt, because a skip owes nothing and its review would never run again |
531
+ | When it clears | `advanceState` removes it from both steps when the `-review` gate passes — nothing else does |
532
+ | Reminders | `yad next` (a warning per debt, a count in the all-epics list, `debt` in JSON), `yad doctor` (`step:debt`, a warn), `yad gate status` ("deferred (still owed, as debt)") |
533
+
534
+ Step debt is **not** `reconcile-debt.json`. That file is a hotfix's whole change owed back to a feature
535
+ thread and is enforced by the `reconcile-debt` CI gate. Step debt is one step of one epic, and it is a
536
+ reminder, never a gate.
98
537
 
99
538
  ### `test-cases` is a parallel, non-blocking track
100
539
 
101
540
  `test-cases` (and its `test-cases-review` gate) sit in `steps[]` after `stories-review`, but they are a
102
- **parallel track that does not gate the build half**. When `stories-review` passes, `advanceState`:
103
- - sets `currentStep` to the **`ready-for-build`** sentinel — so the build half (`yad-spec` → … keyed off
541
+ **parallel track that does not gate Build**. When `stories-review` passes, `advanceState`:
542
+ - sets `currentStep` to the **`ready-for-build`** sentinel — so Build (`yad-spec` → … keyed off
104
543
  `currentStep == "ready-for-build"`) can start **immediately**, and
105
- - opens `test-cases` (`blocked` → `in_progress`) so the tester can work **in parallel**.
544
+ - opens `test-cases` (`todo` → `in_progress`) so the tester can work **in parallel**.
106
545
 
107
546
  The `test-cases` track is therefore driven by its own step `status`, **not** by `currentStep`:
108
547
  `yad-test-cases` proceeds when `test-cases.status == "in_progress"`, and neither it nor the
@@ -116,7 +555,7 @@ unchanged.)
116
555
 
117
556
  Each front **authoring** step opens its own git branch at the start of the step, named
118
557
  `<step>/EP-<slug>` where `<step>` ∈ `analysis | epic | architecture | ui-design | stories | test-cases`
119
- (`config.yaml` `defaults.front_authoring_branch`). This is **distinct** from the review branch
558
+ (`config.yaml` `defaults.shape_authoring_branch`). This is **distinct** from the review branch
120
559
  `review/EP-<slug>/<artifact-base>` that `yad-hub-bridge` opens later for the review PR/MR.
121
560
 
122
561
  The shared procedure (run once the `EP-<slug>` is known):
@@ -124,45 +563,52 @@ The shared procedure (run once the `EP-<slug>` is known):
124
563
  (`git rev-parse --is-inside-work-tree` fails), skip branching with a note and author on the current
125
564
  tree — no error.
126
565
  2. Branch name = `<step>/EP-<slug>`. If it already exists, check it out; otherwise create it from the
127
- hub's default branch (`git checkout -b <step>/EP-<slug>`).
128
- 3. Author and commit the step's artifact(s) on that branch. The bridge's `review/…` branch is created
566
+ Product's default branch (`git checkout -b <step>/EP-<slug>`).
567
+ 3. Author and commit the step's artifact(s) on that branch. The verified ledger's `review/…` branch is created
129
568
  separately at review time and is untouched by this step.
130
569
 
131
- **How the seed reaches the default branch.** The `.sdlc/` ledger is seeded once, by hand, on the
132
- **entry** step's authoring branch (`analysis/…`, `epic/…`, `change/…`, `discovery/…`) — no CLI or CI
133
- path creates one (`yad gate ci` only *advances* an existing chain, at merge, on the default branch).
570
+ **How the seed reaches the default branch.** The `.sdlc/` ledger is seeded once — by `yad epic new`
571
+ (or `yad foundation new` for the Product level) — on the **entry** step's authoring branch (`analysis/…`,
572
+ `epic/…`, `change/…`, `foundation/…`, or `discovery/…` for a product level still in its old spelling).
573
+ No CI path creates one (`yad gate ci` only *advances* an existing chain, at merge, on the default branch).
134
574
  So for the **first** gate of an epic, cut `review/EP-<slug>/<artifact-base>` from that authoring
135
575
  branch: the review PR/MR then carries the seed alongside the artifact, and the ledger lands on the
136
- default branch when it merges. In bridge mode `ledger-guard` exempts exactly this case — **creation,
576
+ default branch when it merges. In verified mode `ledger-guard` exempts exactly this case — **creation,
137
577
  not mutation** (#162) — so no direct push to a protected default branch is needed. For every **later**
138
578
  gate the ledger is already on the default branch: cut the review branch from there, commit the
139
- artifact only, and leave `.sdlc/{state,approvals,comments,hub-prs}.json` and `reviews/*.md` to CI.
140
- | `type` | `author` \| `review+approve` | Authoring step or a team review gate. |
141
- | `artifact` | filename or folder | The file/folder this step produces or gates. |
142
- | `assistance` | `none` \| `review` \| `heavy` | Dial 1 — how much AI helps (build plan §2). |
143
- | `automation` | `human_approve` \| `machine_advance` | Dial 2 — who advances (build plan §2). |
144
- | `locked` | `true` \| `false` | Front steps are `true`: may NOT be set to `machine_advance` in this version. |
145
- | `status` | `blocked` \| `in_progress` \| `in_review` \| `done` | Lifecycle. `blocked` = upstream step not yet approved. |
146
- | `risk_tags` | subset of `contract`, `auth`, `payments` | Drives review escalation (build plan §4). |
579
+ artifact only, and leave `.sdlc/{state,approvals,comments,product-prs,hub-prs}.json` and `reviews/*.md` to CI.
147
580
 
148
581
  ## `approvals.json`
149
582
  Append-only ledger (an array). Each entry:
150
583
 
151
584
  ```json
152
- { "artifact": "epic.md", "step": "epic-review", "approver": "<name>", "role": "owner|reviewer|domain-owner", "domain": "<repo-or-area, optional>", "status": "approved", "date": "<YYYY-MM-DD>", "source": "<bridge, optional>" }
585
+ { "artifact": "epic.md", "step": "epic-review", "approver": "<platform login>", "status": "approved", "date": "<YYYY-MM-DD>", "source": "<bridge, optional>" }
153
586
  ```
154
587
 
155
- `source: "bridge"` marks an approval synced from a hub review PR/MR by `yad-review-gate action: sync`
156
- (via `yad-hub-bridge`). Manual approvals omit `source` and are never altered by `sync`.
157
-
158
- A **bridge** approval carries four more fields, all written by `sync` and all about *what was approved*
588
+ `approver` is the platform login (on a Product with no platform, the name the reviewer gave). The gate
589
+ counts distinct approvers and checks no role. An entry with an empty `approver` counts as nobody. An older entry
590
+ may still carry `role` and `domain` fields from the removed roster, and name the person by the roster's
591
+ name. The gate never reads the role. While the roster is on disk, the next sync write (`yad gate sync`,
592
+ `yad gate ci`) records the login on such an entry when the roster places it for certain, removes `role`
593
+ and `domain`, and keeps the old name in `rosterName` (E64). Some entries are left as they are — a name two
594
+ logins share, or several records that do not prove they are one review (most GitLab role records); see
595
+ `yad-hub-bridge/references/login-roster.md`. The dated `approved.md` still prints the roles as recorded.
596
+
597
+ `source: "bridge"` marks an approval synced from a Product review PR/MR by `yad-review-gate action: sync`
598
+ (via `yad-hub-bridge`). Manual approvals omit `source` and are never altered by `sync`, except for the
599
+ login recorded on an older entry (above). A manual approval has no `artifactHash`, so an edit to the
600
+ artifact does not revoke it.
601
+
602
+ A **bridge** approval carries more fields, all written by `sync` and all about *what was approved*
159
603
  rather than *who approved*:
160
604
 
161
605
  | Field | Meaning |
162
606
  |-------|---------|
163
- | `artifactHash` | the content fingerprint the approval is bound to (`sha256:…`). The gate drops any approval whose hash ≠ the artifact's current one — this is revoke-on-change. For architecture it is the locked contract surface, for stories the whole `stories/` set. |
164
- | `approvedAt` | when the platform says the review was submitted. Used to tell a genuine re-approval from the same review read again; **absent on GitLab**, which exposes no per-approval timestamp. |
607
+ | `artifactHash` | the content fingerprint the approval is bound to (`sha256:…`). The gate drops any approval whose hash ≠ the artifact's current one — this is revoke-on-change. For architecture it is the locked contract surface, for stories the whole `stories/` set. Every file is fingerprinted without its frontmatter `status:` line, which the gate and Build rewrite after review (shape 9); a hash an older release recorded over the whole file is still accepted. |
608
+ | `approvedAt` | when the platform says the review was submitted: GitHub's submission time, or GitLab's `approved_at`. Used to tell a genuine re-approval from the same review read again, which needs a time on both sides. A GitLab instance that does not send `approved_at`, and every GitLab record written before E64, hold the day the sync first recorded the approval — a date, which is read as "time unknown" and never compared with a time. Such a record takes the platform's time the next time the same approval is read, and keeps its `artifactHash`. |
165
609
  | `pr` | the PR/MR number the approval arrived on. The second proof of a genuine re-approval, and the only one available on GitLab: a re-opened review is always a new PR, so an approval on a different number cannot be the old one re-read. Records written before this field existed are stamped once, from the `hub-prs.json` pointer they were recorded against. |
610
+ | `commit`, `url`, `reviewId` | the platform's evidence for the review (E64): the commit it was given on, its link, and its node id. GitHub only, and only when the read gave them — never written `null`. An MR approval on GitLab is not tied to a commit, so none is recorded there. For the record only; the gate never reads them. |
611
+ | `rosterName` | on an older entry whose login was recorded from the roster (E64), the name it had. The gate lets an exact submission time move such an entry to the person who really submitted that review. |
166
612
  | `engagement` | `verified` when the approval carried the companion's engagement marker, else `none`. Advisory unless `hub.review.requireEngagement` is on. |
167
613
 
168
614
  `date` is when the sync **recorded** the approval, not when it was given — it is preserved across an
@@ -171,20 +617,26 @@ unchanged re-sync so that re-reading a review never churns the ledger.
171
617
  ## `comments.json`
172
618
  Append-only ledger (an array), the machine-readable counterpart to the `reviews/*--comments.md` markdown
173
619
  ("who reviewed/commented", as `approvals.json` is "who approved"). Written by `yad-review-gate`'s
174
- `comment` action; feeds the `approved.md` participation roster, not the gate predicate. Each entry:
620
+ `comment` action and by `yad gate sync`; feeds the `approved.md` participation list, not the gate predicate. Each entry:
175
621
 
176
622
  ```json
177
- { "artifact": "epic.md", "step": "epic-review", "commenter": "<name>", "role": "owner|reviewer|domain-owner", "domain": "<optional>", "round": <n>, "count": <comments this round>, "date": "<YYYY-MM-DD>" }
623
+ { "artifact": "epic.md", "step": "epic-review", "commenter": "<platform login>", "round": <n>, "count": <comments this round>, "date": "<YYYY-MM-DD>" }
178
624
  ```
179
625
 
626
+ `commenter` is the platform login (on a Product with no platform, the name the reviewer gave). An older entry may still carry `role` and `domain`; the gate never reads them, and while the roster is on disk the next sync write records the login on it, keeping the old name in `rosterName` — unless two records in one round would then name the same login (E64).
627
+
180
628
  ## `hub-prs.json`
181
- Present only when the front-half review runs through the platform bridge. Per review step, the review
182
- PR/MR opened on the hub (sibling of `approvals.json`, so the locked `state.json` step shape is untouched):
629
+ Present only when the Shape review runs through the platform bridge. Per review step, the review
630
+ PR/MR opened on the Product (sibling of `approvals.json`, so the locked `state.json` step shape is untouched):
183
631
 
184
632
  ```json
185
633
  { "step": "<review step id>", "artifact": "<artifact>", "platform": "github|gitlab", "number": <n>, "url": "<pr/mr url>", "branch": "review/EP-<slug>/<artifact-base>", "lastSyncedAt": "<YYYY-MM-DD or null>" }
186
634
  ```
187
635
 
636
+ The same array is written under two names: `product-prs.json` (the name from shape 3) and `hub-prs.json`
637
+ (the old name, kept for one major). `yad gate sync` may add `nudged`, the logins it has already asked to
638
+ use the review companion, so it never asks them twice.
639
+
188
640
  ## `design-links.json`
189
641
  Present only when the `ui-design` step materialized the design in a connected design tool
190
642
  (`yad-connect-design` → `.sdlc/design.json`). Written by `yad-ui`, the machine-readable screen→frame map
@@ -218,30 +670,30 @@ Human-readable review records, one file per round:
218
670
  and `<artifact-base>` is the artifact without extension (e.g. `epic`, `architecture`, `stories-S01`).
219
671
 
220
672
  ## Dial defaults & locks
221
- - Every step defaults to `automation: human_approve` (build plan §2).
222
- - The five authoring front steps and their reviews are `locked: true` — the engine refuses to set
223
- them to `machine_advance` in this version (build plan §1, §8.7). Only back states (build pipeline,
224
- steps 9–14) may move toward machine-advance in a later iteration.
673
+ - Every step defaults to `automation: human_approve` / `advance: human` (build plan §2).
674
+ - Every Shape step is seeded `locked: true`. A review gate is never `advance: auto`. Since E34 a feature
675
+ Shape author step may be set to `auto` project-wide in `.sdlc/automation.json`, but that is recorded,
676
+ not acted on (see the kill switch section above). A Build step's dial lives on its lane in `build-state`.
225
677
 
226
678
  ---
227
679
 
228
- # Phase 4 build-half state (the back half made dial-bearing)
680
+ # Phase 4 Build state (Build made dial-bearing)
229
681
 
230
- Phase 3 recorded build progress only *after the fact* in `build-log.json`. Phase 4 needs the back
231
- steps to carry their own `automation` dial so the orchestrator (`yad-run`) can read it and decide
682
+ Phase 3 recorded build progress only *after the fact* in `build-log.json`. Phase 4 needs the Build
683
+ steps to carry their own advance dial so the orchestrator (`yad-run`) can read it and decide
232
684
  whether to advance on its own. Two new files under `.sdlc/` do this.
233
685
 
234
686
  > **Who commits these.** `build-state/<story-id>.json`, `trust-log.json`, and `build-log.json` are
235
- > **machine-written** by the back half (`yad-run`, `yad-engineer-review`) and committed by
236
- > **`yad checkpoint`** — the back-half analogue of the front-half `yad gate ci` sync. It lands them as
237
- > one `chore(hub): sync back-half state — <epic>/<story> by @<login>` audit-trail commit, on the
238
- > default branch, staging **only** these three ledgers by an explicit allowlist (never a front-half
239
- > gate file — `state/approvals/comments/hub-prs.json`, `reviews/*.md` — so `ledger-guard` never trips).
687
+ > **machine-written** by Build (`yad-run`, `yad-engineer-review`) and committed by
688
+ > **`yad checkpoint`** — the Build analogue of the Shape `yad gate ci` sync. It lands them as
689
+ > one `chore(hub): sync Build state — <epic>/<story> by @<login>` audit-trail commit, on the
690
+ > default branch, staging **only** these three ledgers by an explicit allowlist (never a Shape
691
+ > gate file — `state/approvals/comments/product-prs/hub-prs.json`, `reviews/*.md` — so `ledger-guard` never trips).
240
692
  > Teammates don't review these machine writes; the commit exists so CI, `yad status`, and other
241
693
  > machines always see current trust evidence.
242
694
 
243
695
  ## `build-state/<story-id>.json`
244
- One file per story that has entered the build half. The build half is **per-story, per-repo**, so the
696
+ One file per story that has entered Build. Build is **per-story, per-repo**, so the
245
697
  steps live under each repo (mirrors the per-repo shape of `build-log.json`).
246
698
 
247
699
  ```json
@@ -251,11 +703,11 @@ steps live under each repo (mirrors the per-repo shape of `build-log.json`).
251
703
  "backend": {
252
704
  "currentStep": "checks",
253
705
  "steps": [
254
- { "id": "spec", "automation": "human_approve", "locked": false, "status": "done" },
255
- { "id": "tasks", "automation": "human_approve", "locked": false, "status": "done" },
256
- { "id": "implement", "automation": "human_approve", "locked": false, "status": "done" },
257
- { "id": "checks", "automation": "machine_advance","locked": false, "status": "in_progress" },
258
- { "id": "engineer-review", "automation": "human_approve", "locked": true, "status": "blocked" }
706
+ { "id": "spec", "automation": "human_approve", "advance": "human", "locked": false, "status": "done" },
707
+ { "id": "tasks", "automation": "human_approve", "advance": "human", "locked": false, "status": "done" },
708
+ { "id": "implement", "automation": "human_approve", "advance": "human", "locked": false, "status": "done" },
709
+ { "id": "checks", "automation": "machine_advance", "advance": "auto","locked": false, "status": "in_progress" },
710
+ { "id": "engineer-review", "automation": "human_approve", "advance": "human", "locked": true, "status": "todo" }
259
711
  ]
260
712
  }
261
713
  }
@@ -266,22 +718,48 @@ Each `steps[]` entry:
266
718
 
267
719
  | Field | Values | Meaning |
268
720
  |-------|--------|---------|
269
- | `id` | `spec`, `tasks`, `implement`, `checks`, `engineer-review` | Back-half step identity (the `back_steps` from `config.yaml` + the human merge gate). |
270
- | `automation` | `human_approve` \| `machine_advance` | Dial 2. Defaults to `human_approve`; flipped to `machine_advance` only after the trust threshold is met (and never for `locked` steps). |
721
+ | `id` | `spec`, `tasks`, `implement`, `checks`, `engineer-review` | Build step identity (the `back_steps` from `config.yaml` + the human merge gate). |
722
+ | `automation` / `advance` | `human_approve`/`human` \| `machine_advance`/`auto` | Dial 2, written under both names (the OLD one is read). Defaults to `human_approve`; `yad dial <epic> <story> --repo <r> <step> --to auto` flips it (E34 — nothing to earn), never on the `engineer-review` gate. |
271
723
  | `locked` | `true` \| `false` | `engineer-review` is `true` — it never auto-advances (build plan §E). |
272
- | `status` | `blocked` \| `in_progress` \| `in_review` \| `done` | Lifecycle. `yad-run` advances `done` steps and `blocked`s on a halt. |
724
+ | `closed` | `{ by, date, via, run }` | Written by the `yad-run` skill when it moves the lane past the step: `via` is `auto` or `human`, and `run` names the trust-log run (E18). See "Closing records". |
725
+ | `status` | the same **step states** as the Shape chain | Lifecycle. This file is **not** migrated to shape 7 — the `yad-run` / `yad-implement` skills write it, not the engine, so a rewrite would be undone by their next write. `yad-run` advances `done` steps and marks a halted lane `blocked` **with a `record`** naming the halt cause (a failed check, a scope overrun, a contract touch): that record is what separates a halted lane from one nobody started, because a bare `blocked` is the pre-shape-7 spelling of `todo` and still reads that way here. A lane halted by an older `yad-run` carries no record and reads as `todo` until the next run rewrites it — nothing advances past it either way. |
273
726
 
274
727
  `currentStep` is the `id` the orchestrator is waiting on / about to run for that repo. The file is
275
- created when a story enters the build half; all dials start `human_approve` (the `config.yaml`
728
+ created when a story enters Build; all dials start `advance: human` (`automation: human_approve`) (the `config.yaml`
276
729
  `automation.default`).
277
730
 
731
+ **A lane skipped whole (E39).** When a story declares a repo that turns out to need no change,
732
+ `yad skip <epic> <story> --repo <name> --reason "<why>"` writes that repo's entry as a skipped lane —
733
+ no `steps`, just the state and its record:
734
+
735
+ ```json
736
+ { "story": "EP-<slug>-S0N", "repos": { "web": { "status": "skipped", "record": { "reason": "no UI change", "by": "<login>", "date": "<YYYY-MM-DD>" } } } }
737
+ ```
738
+
739
+ - Refused before `stories-review` has passed (edit the story's `repos:` then), for a repo the story does
740
+ not declare, once work has started in the lane or a ship is recorded for it, and for the story's last
741
+ lane. `yad unskip <epic> <story> --repo <name>` removes the entry again.
742
+ - **Only a whole lane.** No single Build step is ever `skipped` or `deferred` — `yad doctor` reports one.
743
+ `yad-run` adds a missing repo's entry to an existing file, and never drives a skipped lane.
744
+ - It is not written into the story's `repos:` list, because that list is part of what the stories review
745
+ approved. `yad checkpoint --push` commits it with the rest of the Build state.
746
+ - Readers: `yad next` prints `skipped (N/A)` with the reason (and still points at `yad-run` while every
747
+ recorded lane is skipped and nothing has started); `yad foundation status` counts the lane as finished
748
+ once another lane really shipped; `yad checkpoint --retro-ship` refuses it, and `yad-engineer-review`
749
+ checks for it before recording a ship.
750
+ - `skipped` is honoured only over a lane with no work in it. An entry that says `skipped` beside steps that
751
+ have started — including a status word this release does not know — is read as the work, and
752
+ `yad doctor` fails it as a contradiction.
753
+
278
754
  `yad next` reads these files too: once an epic is `ready-for-build`, `yad next <epic>` resolves each
279
- story/repo's `currentStep` into the next build sub-step (`spec`/`tasks` → `yad-spec`, `implement` →
280
- `yad-implement`, `checks` → `yad-checks`, `engineer-review` → `yad-engineer-review`) and prints it with
281
- the remaining chain and the step's automation dial — so the build half is guided, not just hinted at.
755
+ story/repo's `currentStep` into the next build sub-step and prints it with the remaining chain and the
756
+ step's advance dial — so Build is guided, not just hinted at. The skill it names comes from
757
+ `.sdlc/skills.json` when the project bound one (see "The one column a project overrides is the skill"
758
+ above); unbound, the defaults are `spec`/`tasks` → `yad-spec`, `implement` → `yad-implement`,
759
+ `checks` → `yad-checks`, `engineer-review` → `yad-engineer-review`.
282
760
 
283
761
  ## `trust-log.json` (shard-then-fold)
284
- Append-only ledger, the back-half analogue of `approvals.json`. **This is the evidence base** that
762
+ Append-only ledger, the Build analogue of `approvals.json`. **This is the evidence base** that
285
763
  decides when a step is safe to automate (build plan Step A). One entry per step run.
286
764
 
287
765
  **Storage — loose shards + a folded file (the "loose objects + `git gc`" model).** Two people driving
@@ -295,15 +773,15 @@ back:
295
773
  - **Folded file:** `epics/<epic>/.sdlc/trust-log.json` = `{ "epic": "<id>", "runs": [ <entry>, … ] }`
296
774
  (also the legacy single-file layout, and the output of `yad tidy up`).
297
775
  - **Union-read rule:** to read the ledger, take the folded file's `runs` array PLUS every file in the
298
- `trust-log/` shard dir, and **concatenate** — every entry is a distinct run and the trust threshold
776
+ `trust-log/` shard dir, and **concatenate** — every entry is a distinct run and the run record
299
777
  counts re-runs, so **never dedup by `(story, repo, step)`**. (The only guard: a shard whose FULL
300
778
  identity `(story, repo, step, uid)` already appears in the folded `runs` is a half-applied tidy and is
301
779
  skipped — keying on `uid` alone would wrongly drop a different run that happened to reuse a token.) A legacy epic with only
302
780
  the folded file and no shard dir still reads correctly — nothing to union.
303
781
  - **`yad tidy up`** (manual, one person) folds a SHIPPED story's finished shards into the folded file's
304
782
  `runs` and deletes them. Writers never fold — they only add shards; `yad checkpoint` commits the shard
305
- dir, and `yad tidy up` is the back-half analogue of `git gc` folding loose objects.
306
- - The **threshold slice** (below) reads this same union, filtered to the step (and repo).
783
+ dir, and `yad tidy up` is the Build analogue of `git gc` folding loose objects.
784
+ - The **run record** `yad dial` prints reads this same union, filtered to the step and repo.
307
785
 
308
786
  ```json
309
787
  {
@@ -311,7 +789,7 @@ back:
311
789
  "repo": "backend",
312
790
  "step": "checks",
313
791
  "uid": "<short-unique-token>",
314
- "automation": "human_approve",
792
+ "automation": "human_approve", "advance": "human",
315
793
  "verdict": "approved-unchanged",
316
794
  "signals": { "checks": "pass", "human_edited_diff": false, "scope_overrun": false, "contract_touch": false },
317
795
  "ranBy": "machine",
@@ -324,7 +802,7 @@ back:
324
802
  |-------|--------|---------|
325
803
  | `step` | a `back_steps` id | Which step this run is recorded against. |
326
804
  | `uid` | short unique token | Generated fresh per run (never reused) — makes each shard file and each re-run distinct; also the folded/loose de-dup guard. Legacy folded entries may lack it. |
327
- | `automation` | dial in force at run time | So the log shows whether the run was a manual or an automated advance. |
805
+ | `automation` | dial in force at run time (recorded in the OLD vocabulary — this is history, and `yad migrate` deliberately does not rewrite it) | So the log shows whether the run was a manual or an automated advance. |
328
806
  | `verdict` | `approved-unchanged` \| `approved-with-edits` \| `rejected` | The trust signal. **Provisional verdict is derived** (below); the human gate for that step confirms or overrides it and finalizes the entry. |
329
807
  | `signals` | object | The raw inputs the provisional verdict was derived from. The fields present depend on the step (table below). |
330
808
  | `ranBy` | `machine` \| `human` | Whether the orchestrator advanced it or a human did. |
@@ -344,11 +822,9 @@ same three-way shape, anchored to each step's human gate, never self-graded):
344
822
  - accepted after a human edited the output (`human_edited_diff` / `human_edited_spec` / `task_rescoped`) → `approved-with-edits`;
345
823
  - accepted as produced → `approved-unchanged`.
346
824
 
347
- **Trust threshold** (from `config.yaml` `automation.trust_threshold`): a step is a candidate for
348
- `machine_advance` only when its slice of the trust ledger — the **union** of the folded `trust-log.json`
349
- `runs` plus every `trust-log/` shard, filtered to the same `step` (this story's repo or the project) —
350
- has `>= min_runs` entries AND the fraction with `verdict == "approved-unchanged"` is
351
- `>= min_approved_unchanged`. The dial-setter in `yad-run` enforces this; `yad-status` surfaces it.
825
+ **The run record is advice (E34).** There is no trust threshold. `yad dial` prints a step's slice — runs
826
+ and the fraction `approved-unchanged`, from the union above — beside its dial, and never refuses on it; the
827
+ team decides. `yad-status` shows the same record.
352
828
 
353
829
  ## `build-log.json` (shard-then-fold)
354
830
  The build ledger records one ship per merged task. Its schema and the ship record's fields are
@@ -377,7 +853,7 @@ storage layout is noted here (it mirrors `trust-log.json`):
377
853
  After the contract locks and code ships, a change must not **mutate** a locked artifact (that destroys
378
854
  the lock + the audit trail). Instead every change request becomes a **new epic, threaded to its parent**
379
855
  (`config.yaml` `change:`). A feature is a **thread** of linked epics (genesis → change → defect → …); a
380
- change-epic **inherits** unchanged front artifacts from its parent by reference and only **re-authors**
856
+ change-epic **inherits** unchanged Shape artifacts from its parent by reference and only **re-authors**
381
857
  what it changes. So artifacts are never stale, only *superseded*; the feature's current truth is the
382
858
  head of the thread, composed by the resolver (`yad-timeline`). `yad-change` seeds a change-epic;
383
859
  `yad-defects` / `yad-timeline` render the thread; `yad-reconcile` flags drift; three CI gates enforce it.
@@ -385,13 +861,23 @@ head of the thread, composed by the resolver (`yad-timeline`). `yad-change` seed
385
861
  ## Lineage frontmatter (added to `epic.md`)
386
862
 
387
863
  An enrichment block on `epic.md` — like the `design:` / `testing:` blocks, it does **not** change the
388
- locked `state.json` step shape. Genesis epics are backfilled once with `kind: feature`, `thread: <self>`.
864
+ locked `state.json` step shape. Genesis epics are backfilled once with `kind: feature`, `type: feature`
865
+ and `thread: <self>`.
866
+
867
+ **The work-item type has two names.** `kind:` is the original and is still the one that is READ;
868
+ `type:` is the same value under the name from shape 5 on. **Write both, with the same value.** Writing
869
+ `type:` alone on a `change`/`defect`/`hotfix` is the one mistake that breaks something:
870
+ `lineage-check.sh` runs inside the code repo, reads `kind:`, finds none, defaults to `feature`, and
871
+ stops requiring the `parent:` those three must have. `yad doctor` fails on it (`type:gate-blind`).
389
872
 
390
873
  | Field | Values | Meaning |
391
874
  |-------|--------|---------|
392
- | `kind` | `feature` \| `change` \| `defect` \| `hotfix` | Genesis is `feature` (default when absent). |
875
+ | `title` | one line of plain text, e.g. `Checkout from the mobile app` | **The work item's one-line name** (E111), written by `yad-epic`, `yad-change` and `yad-stub`. Optional: an item without one is shown by its id. Quotes are optional. A single-quoted title (`'…'`) is read without its quotes, with `''` read as `'`. A double-quoted title (`"…"`) is read with JSON's escapes (`\"`, `\\`, `\n`, `\t`, `\u00e9` …); one using any other escape is kept as written, quotes and backslashes included. A value that is not one quoted string (`"Login" is broken on "Safari"`, or `"x" # note`) is kept as written. **No title** is read from a value that begins with `[` and ends with `]` (the reader turns it into a list — double-quote such a title, writing an inner `"` as `\"`), from a YAML block marker (`>`, `\|`: only one line is read) or from YAML's null (`~`, `null`, `Null`, `NULL` — the whole value, unquoted: `'null'` and `Nullable fields` are text). A trailing `#` comment becomes part of the title. Whitespace runs, a newline included, become one space; the other control characters (such as ESC or BEL) and the bidi controls (such as U+202E, which reverses how text is shown) are dropped — from a `change.json` title too. Zero-width characters (the joiners among them) are kept, since emoji and some scripts need them. An item with no `title:` falls back to the `title` in its `change.json` (only change items have one). The epic review is bound to a hash of this file, so adding or rewording a title after approval drops the approval — set it while the file is written, and do not add one to an already-approved epic just to fill the gap. Read by `.sdlc/index.json`. |
876
+ | `kind` | `feature` \| `change` \| `defect` \| `hotfix` \| `chore` | The work-item type, under the name that is still read. Genesis is `feature` (default when absent). |
877
+ | `type` | the same five values | The same value under the name from shape 5 on. Write it beside `kind`, never instead of it. |
878
+ | `theme` | any word or short phrase, in any language, e.g. `checkout-revamp` | **Optional grouping tag.** Puts several epics under one heading. It is what this method has instead of a rung above the Epic: the ladder stays Product → Epic → Story → Task, and grouping is a label rather than a level. No fixed list and nothing to register. **One tag, never a list** — `theme: [a, b]` is read as no theme at all. Spell an existing theme exactly as the other epics do; `yad doctor` reports one theme spelled two ways (`theme:variants`), because two spellings group as two, and a `#` inside the value (`theme:commented`), because the reader keeps the whole rest of the line. Lives only in `epic.md` — there is no copy in `state.json`, so no file shape changed. Read by `yad next` (printed) and `yad thread --json`. |
393
879
  | `thread` | `EP-<genesis>` | Stable thread id = the genesis epic's id (never renamed → stablest anchor). A **derived cache** — the authoritative thread is `parent` walked to the root; a mismatch is detectable corruption (`yad doctor`). Genesis: `thread == id`. |
394
- | `parent` | `EP-<slug>` | The immediate predecessor epic. **Absent ⇔ `kind: feature`.** |
880
+ | `parent` | `EP-<slug>` | The immediate predecessor epic. **Absent for the genesis types (`feature`, `chore`); required for `change`, `defect` and `hotfix`.** A chore — a dependency bump, a CI move — usually has no feature to hang off, and an invented parent would file it under a feature it has nothing to do with. |
395
881
  | `inherits` | subset of `[epic, architecture, contract, ui-design, stories, test-cases]` | Artifact bases carried **by reference**, not re-authored. The rest are re-authored in this epic. |
396
882
  | `supersedes` | `[EP-<slug>-S0N, …]` | Optional — specific parent story IDs this epic replaces in the head. |
397
883
  | `origin` | `production` \| `staging` \| `qa` \| `review` | **defect/hotfix only.** Where the defect was found. |
@@ -407,11 +893,11 @@ In a brownfield repo not every already-built feature has an epic, so a defect/ch
407
893
  thread from (`yad-change` requires one; `lineage-check` rejects a missing parent). `yad-stub` mints the
408
894
  smallest **real** node — a **stub genesis epic** — so the bug can be captured now and formalized later.
409
895
 
410
- A stub is a normal genesis (`kind: feature`, `thread == id`, no `parent`) whose `epic.md` carries
896
+ A stub is a normal genesis (type `feature` under both names, `thread == id`, no `parent`) whose `epic.md` carries
411
897
  `stub: backfill-pending` + `verified: false` and whose `state.json` uses a **sentinel**, mirroring
412
898
  `EP-discovery` / `discovery-done`:
413
899
  - top-level `kind: "stub"` and `currentStep: "backfill-pending"`;
414
- - the **same 10-step front chain** as a normal epic, every step `status: "blocked"` (so `validateState`
900
+ - the **same 10-step Shape chain** as a normal epic, every step `status: "todo"` (so `validateState`
415
901
  passes and `promote` can "wake" the chain into normal authoring with no re-seed);
416
902
  - empty `approvals.json` / `comments.json`; **no** `contract-lock.json` (no surface locked yet).
417
903
 
@@ -433,33 +919,36 @@ approved backfill spec, **and** rewrites `state.json` — removing `kind: "stub"
433
919
  off the sentinel:
434
920
  - **light promote (default)** → `currentStep: "backfill-done"`, a **terminal sentinel** (like
435
921
  `discovery-done`): the feature is a real, verified anchor documented by its backfill spec; `nextAction`
436
- reports "documented anchor — evolve it by threading a change/defect", never a pending stub, and no build
437
- half runs directly against it;
438
- - **full promote (opt-in)** → `currentStep: "epic"`, `epic.status: "in_progress"`, to run the normal front
439
- half and lock a real contract.
922
+ reports "documented anchor — evolve it by threading a change/defect", never a pending stub, and Build
923
+ never runs directly against it;
924
+ - **full promote (opt-in)** → `currentStep: "epic"`, `epic.status: "in_progress"`, to run the normal Shape
925
+ part and lock a real contract.
440
926
 
441
927
  From promotion on, the thread's contract protection is live.
442
928
 
443
929
  ## Inherited steps in `state.json`
444
930
 
445
931
  A change-epic's `state.json` is structurally identical (so `advanceState` / `nextAction` / `gatePredicate`
446
- / the bridge run unchanged), but **inherited** steps are pre-marked `done` with two extra fields, and
447
- only re-authored steps run. The seeder sets `currentStep` to the first re-authored step.
932
+ / the verified ledger run unchanged), but **inherited** steps are pre-marked `done` with two extra fields, and
933
+ only re-authored steps run. `yad epic new <slug> --type <change|defect|hotfix> --parent <EP-parent> --inherits <bases>`
934
+ writes it (E42) and sets `currentStep` to the first re-authored step.
448
935
 
449
936
  ```json
450
937
  { "id": "architecture", "type": "author", "artifact": "architecture.md",
451
- "assistance": "review", "automation": "human_approve", "locked": true,
452
- "status": "done", "inherited": true, "inheritedFrom": "EP-checkout",
938
+ "assistance": "review", "driver": "pair", "automation": "human_approve", "advance": "human", "locked": true,
939
+ "status": "satisfied", "inherited": true, "inheritedFrom": "EP-checkout",
453
940
  "boundHash": "sha256:…", "risk_tags": [] }
454
941
  ```
455
942
 
456
943
  - `inherited` — `true` when this step's artifact is taken by reference from the thread (not authored here).
457
- - `inheritedFrom` — the epic in the thread that owns the referenced artifact.
944
+ - `inheritedFrom` — the epic along the parent's line that owns the referenced artifact (not always the parent).
458
945
  - `boundHash` — the artifact's hash at inherit time (contract surface hash for `architecture`; the
459
- `storiesHash`/file hash for others). The gate predicate short-circuits an `inherited` step as
460
- **satisfied** iff `boundHash` still equals the thread's current hash for that artifact — always true,
461
- since the artifact lives in the parent and can't be edited from the child, so inherited steps never
462
- block and are never re-reviewed.
946
+ `storiesHash`/file hash for others — without the frontmatter `status:` line from shape 9, and an older
947
+ whole-file hash is still accepted). The gate predicate short-circuits an `inherited` step as
948
+ **satisfied** iff `boundHash` still equals the current hash of that artifact in the owning epic
949
+ (`inheritedFrom`) — which holds unless the owner's copy changes, so inherited steps never block and are
950
+ never re-reviewed. Compare against the owner's copy, not the child's: a change-epic writes its own
951
+ `epic.md` (the change brief), which is not the epic it carries.
463
952
 
464
953
  `approvals.json` gets a **provenance** record per inherited gate (not a forged approval):
465
954
 
@@ -468,9 +957,29 @@ only re-authored steps run. The seeder sets `currentStep` to the first re-author
468
957
  "from": "EP-checkout", "boundHash": "sha256:…", "date": "<YYYY-MM-DD>" }
469
958
  ```
470
959
 
960
+ **What may be inherited (E42).** The engine carries a base only from the epic that owns it along the
961
+ PARENT's line (`inheritedFrom` — a sibling change off the same genesis is not something this epic builds
962
+ on), and only when that owner wrote AND approved it: both the step and its `-review` are `done`, and the
963
+ artifact is on disk. `epic`, `architecture` + `contract` (always together) and `ui-design` may be
964
+ inherited; `analysis` rides with `epic`. `stories` and `test-cases` never are — the thread's set is the
965
+ union of every contributor, and `stories-review` is what hands an epic to Build. Everything else is
966
+ refused before anything is written:
967
+
968
+ | Upstream | Refused because | Instead |
969
+ |---|---|---|
970
+ | the step is `skipped` | a skip is a decision, not an artifact — carrying it would claim a review nobody did | leave the base out, then `yad skip` it on this epic |
971
+ | the step is `deferred` | the work is still owed | leave the base out and author it here, so the owed work follows the thread |
972
+ | the step or its review is unfinished | nothing approved exists yet | finish it on the owner first |
973
+ | architecture approved, but no usable lock | rule 5 — you may skip authoring a contract, never having one | lock the owner's surface first |
974
+ | the owner's surface no longer matches its lock | a pointer would pass the drift down the thread | re-lock the owner first |
975
+ | the route has no such step (a short lane) | nothing to inherit | leave the base out |
976
+
977
+ A brownfield anchor (`kind: stub`, or a light-promoted `backfill-done`) is the one exception: its chain
978
+ was never started, so its bases carry `boundHash: null` and no pointer-lock is written.
979
+
471
980
  ## The pointer-lock — `contract-lock.json` in a change-epic
472
981
 
473
- When `architecture` is inherited, the seeder writes a **derived** `contract-lock.json` carrying the
982
+ When `architecture` is inherited, `yad epic new` writes a **derived** `contract-lock.json` carrying the
474
983
  parent's hash **verbatim** so `contract-check.sh` (which reads only `hash`) passes unchanged. There is
475
984
  no `contract.md` in the child to edit, so the surface physically cannot drift.
476
985
 
@@ -481,7 +990,7 @@ no `contract.md` in the child to edit, so the surface physically cannot drift.
481
990
 
482
991
  Omitting `architecture` from `inherits` (depth `contract-surface`) is what triggers a **real re-lock**:
483
992
  `yad-architecture` re-authors `contract.md`, computes a **new** hash, and `architecture-review` carries
484
- `risk_tags: ["contract"]` → the usual domain-owner escalation. This unifies "route back to the
993
+ `risk_tags: ["contract"]` → the usual contract-risk review (full approver count 3, capped at the active people less one and reported, E72; only the base 1 is enforced). This unifies "route back to the
485
994
  architecture gate" with "open a contract-surface change-epic" — one mechanism, not two.
486
995
 
487
996
  ## `change.json`
@@ -489,7 +998,7 @@ Intake + triage record, one per change/defect/hotfix epic (sibling of `approvals
489
998
 
490
999
  ```json
491
1000
  { "epicId": "EP-checkout-queue-filter", "thread": "EP-checkout", "parent": "EP-checkout",
492
- "kind": "defect", "depth": "defect-fix", "intakeBy": "alice", "intakeDate": "<YYYY-MM-DD>",
1001
+ "kind": "defect", "type": "defect", "depth": "defect-fix", "intakeBy": "alice", "intakeDate": "<YYYY-MM-DD>",
493
1002
  "title": "Pending queue returns fulfilled orders", "description": "…",
494
1003
  "affectedArtifacts": ["stories", "test-cases"],
495
1004
  "reauthors": ["stories", "test-cases"], "inherits": ["epic", "architecture", "contract", "ui-design"],
@@ -506,7 +1015,7 @@ Thread-level rollups (`yad-timeline` / `yad-defects`) are **derived** — walk e
506
1015
  sharing `thread` and read each `change.json`; there is no duplicated thread registry.
507
1016
 
508
1017
  ## `reconcile-debt.json`
509
- Append-only ledger of hotfix ship-first debt (a hotfix shipped code before its front gates approved).
1018
+ Append-only ledger of hotfix ship-first debt (a hotfix shipped code before its Shape gates approved).
510
1019
 
511
1020
  ```json
512
1021
  [ { "thread": "EP-checkout", "epicId": "EP-checkout-hotfix-x", "openedDate": "<date>",
@@ -516,5 +1025,5 @@ Append-only ledger of hotfix ship-first debt (a hotfix shipped code before its f
516
1025
  ```
517
1026
 
518
1027
  `status: "open"` blocks the **next** normal change on the thread (`reconcile-debt-check.sh`) until it is
519
- `"paid"` (evidence: the front artifacts updated **and** a regression test added). The debt lets a hotfix
1028
+ `"paid"` (evidence: the Shape artifacts updated **and** a regression test added). The debt lets a hotfix
520
1029
  jump the queue once, but freezes new thread work until the SDLC again describes production.