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
@@ -3,42 +3,211 @@
3
3
  ## Reviewer rule
4
4
  Let `A` = the set of `approved` records in `.sdlc/approvals.json` for this step.
5
5
 
6
- - `owners = { a in A : a.role == "owner" }`
7
- - `reviewers = { a in A : a.role == "reviewer" }` (distinct by `approver`)
8
- - `domainOwners = { a in A : a.role == "domain-owner" }` (grouped by `a.domain`)
6
+ - `approvers = { a.approver : a in A }` (distinct people — two records from one person are one)
9
7
 
10
- **Base pass:** `|owners| >= 1` AND `|reviewers| >= default_reviewers` (default `1`).
8
+ **Pass (team mode):** `|approvers| >= 1` — the **base**. Missing, the gate reports `1 approval(s)`.
11
9
 
12
- **Escalated pass** (step `risk_tags` ∩ `{contract, auth, payments}` ≠ ∅): base pass AND, for every
13
- touched `domain`, `|domainOwners[domain]| >= 1`.
10
+ There are no roles. yadflow keeps no list of people, so there is no owner rule, no reviewer rule and no
11
+ domain-owner rule. Older records may still carry `role` and `domain`; they stay on disk and nothing
12
+ reads them. The gate's `rule` label for a team gate is `count` (`solo`, `inherited` and `skipped` are
13
+ unchanged).
14
+
15
+ ## The per-step count rule
16
+
17
+ The count is the whole rule. It counts people, not roles:
18
+
19
+ `needed = base + risk step`, where `base` is `1` — one human approval from someone other than the author.
20
+ The engine never compares the approver with the author: GitHub stops you approving your own PR, GitLab
21
+ does only when the project's approval settings say so, and on a local-only ledger nothing does — so do
22
+ not record the author's own approval — and the **risk step** comes from the step's own
23
+ `risk_tags`:
24
+
25
+ | Tags on the step | Risk | Risk step | `needed` |
26
+ |---|---|---|---|
27
+ | none | normal | +0 | 1 |
28
+ | `auth` and/or `payments` | high | +1 | 2 |
29
+ | `contract` | contract surface | +2 | 3 |
30
+
31
+ Several tags take the **highest** step, never the sum — a gate is one decision about the riskiest thing
32
+ it touches. `have` is the number of **distinct approvers**. The tags are read from the step as the epic
33
+ records it in `state.json`, so adding `auth` to a step by hand raises that epic's full count.
34
+
35
+ **What holds the gate: only the base.** The base (1) always holds every team gate, and for now it is the only part that does. The rule is one
36
+ formula: `needed = base + risk step`, **capped** at `active − 1`, with a floor of 1. `active` is the live
37
+ count of people who committed or approved lately (E71); `yad gate status` and `yad gate sync` print it,
38
+ and the predicate carries it as `active`. Since E72 the engine **computes, prints and records** the cap,
39
+ but it does **not enforce** it — the risk step stays advisory, capped or not, and its shortfall is
40
+ reported as `short`, never blocking:
41
+
42
+ | Active people | Contract gate: capped ask (full count 3) |
43
+ |---|---|
44
+ | 0–1 | 1 (never below 1) |
45
+ | 2 | 1 (one seat is left for the author) |
46
+ | 3 | 2 |
47
+ | 4 or more | 3 |
48
+
49
+ The `− 1` is **one seat left for the author**. It is a seat, not a check: the engine does not know who
50
+ the author is. The platform decides whether the author may approve (GitHub never allows it; GitLab only
51
+ when its settings say so; a local ledger checks nothing). The floor of 1 means the cap only ever trims
52
+ the risk step.
53
+
54
+ **Why the cap is not enforced yet.** The count of people errs high in the normal case. A commit is
55
+ counted by its git name, an approval by its platform login, and yadflow never joins the two without
56
+ exact evidence. So an ordinary two-person team can read as **four**. At four the cap lowers nothing,
57
+ and an enforced contract gate would ask three approvals of a team with one person who is not the author
58
+ — a gate it could never pass. A later yadflow change turns the capped count on together with
59
+ `yad gate lower --reason`, the way out of a gate that cannot be met, once the count is accurate.
60
+
61
+ **When a gate may not pass (E73).** `yad gate status` and `yad gate sync` print a warning line under a
62
+ team review gate that has not passed yet, when the count of people suggests the gate may not pass. It is a
63
+ **warning only**: it never holds a gate, never changes whether the gate passed, and writes nothing. There
64
+ are two kinds of line:
65
+
66
+ - `! may not be met: …` is about **today's rule**: the one approval that is always required (the base) may
67
+ have nobody but the author to give it.
68
+ - `! if the risk step were enforced: …` is a **what-if**. Today only the base is enforced, so the gate can
69
+ still pass, and the line ends by saying that nothing beyond the one enforced approval is needed today.
70
+ It says what would happen if a later yadflow change enforced the extra approvals that risk tags add
71
+ (the risk step). "The approvals asked" means the count after the cap. The cap limits the count to the
72
+ active people less one, and never below 1.
73
+
74
+ Every line talks about **the people counted**, and says "may" or "if", because the count can be wrong both
75
+ ways. It is too low when a reviewer has not committed or approved inside the counting window (the same
76
+ window as the `active people:` line), because nothing in that window records them. It is too high when
77
+ one person commits with a work email (recorded as a name) and approves on GitHub (recorded as a login):
78
+ yadflow never joins a name and a login without proof, so that person counts twice.
79
+
80
+ | Kind | When it prints | Example line (a 90-day window) |
81
+ |---|---|---|
82
+ | Today | 0 or 1 active person counted, and no approval in the window | ``! may not be met: only 1 active person counted and no approval in the last 90 days, so if nobody but the author can approve, this gate cannot pass. Someone who has not committed or approved in the last 90 days is not counted. Another person's approval settles it, or use the recorded way out, `yad mode solo --reason` `` |
83
+ | Today | Exactly 2 people counted, one not matched to a platform login and one login, and no approval in the window. Example: one developer who commits with a work email and also through GitHub's web editor, which records the login | ``! may not be met: 1 of the 2 people counted is not matched to a platform login and may be the same person as the one login, and there is no approval in the last 90 days, so the team may be one person. Then, if nobody but the author can approve, this gate cannot pass. Another person's approval settles it, or use the recorded way out, `yad mode solo --reason` `` |
84
+ | What-if | The cap lowered the count, and the approvals do not show more people than were counted | `! if the risk step were enforced: with no cap, the full count of 3 is more than 2 active people can give (one seat is left for the author), so if nobody else joins, this gate could not pass. Nothing beyond the one enforced approval is needed today` |
85
+ | What-if | Some people are not matched to a platform login and at least one is a login, and the smallest possible team could not give the approvals asked | `! if the risk step were enforced: 2 of the 4 people counted are not matched to a platform login, and up to 2 of them may be the same people as the logins, so the team may be as small as 2. That leaves room for 1 of the 3 approvals asked, so if the team is that small, this gate could not pass. Nothing beyond the one enforced approval is needed today` |
86
+
87
+ A what-if line is not printed when a today line is. When the cap lowered the count, the last line says
88
+ "approvals asked after the cap", because the full-count line above it quotes the number before the cap.
89
+
90
+ "Not matched to a platform login" means yadflow knows the person only by a name: a git author name, or an
91
+ approval record that carries no login (an older hand-written one, or an engineer-review record, even when
92
+ it holds a login, because nothing in it proves that). Each such name may be a second row for one of the
93
+ logins, so the smallest possible team is the larger of the two groups: the logins, or the names. It is
94
+ never fewer people than the approvals prove.
95
+
96
+ **An approval is evidence.** Any approval in the counting window shows that approvals can be given, so no
97
+ today line prints. It also raises the smallest team a what-if line assumes: at least the approvals on this
98
+ step plus one (the author), and at least two people once anyone has approved anything. That assumes
99
+ authors cannot approve their own work: always true on GitHub, true on GitLab only when its settings say
100
+ so, and never checked on a Product with no platform. Where it is not true, the smallest team is guessed
101
+ too high, so a what-if line may stay quiet when it should speak; it never raises a false alarm because of
102
+ this.
103
+
104
+ A Product with no platform (every record is a name) gets no name line, because there is no login to
105
+ compare against. Two spellings of one name (`bo` and `Bo Chen`) still count as two people; no line can
106
+ see that yet.
107
+
108
+ No line prints in solo mode, under a step that passed, under a waived step (inherited from a parent epic,
109
+ skipped, or deferred), or when the people could not be counted. A line about the whole Product prints
110
+ once per command, under the first step it applies to.
111
+
112
+ Run `yad gate status` yourself while the review is still open: reviewers can still approve then, and a
113
+ merged review PR cannot take approvals. Do not wait for CI to show it. The wired Product workflow runs only
114
+ for merged review PRs (at the merge, and in a scheduled sweep of merged PRs), and it usually cannot count
115
+ people, because the connected repos are not on disk there.
116
+
117
+ **When the people cannot be counted** (`active: null` — a source could not be read), **no cap is
118
+ computed or shown**, and the base holds as always. The engine never guesses a small number from an
119
+ unknown. Product CI is usually this case: it checks out only the hub, so on a Product with connected
120
+ repos the repos are not on disk.
121
+
122
+ **Every cap is recorded.** When a **team** gate passes on its counted approvals while the cap lowered
123
+ its ask, its closing record gets `capped: { needed, to, active }` beside `waived` — the full count, the
124
+ capped ask, and the count of people it read. It records what the gate ASKED, not what held it (the base
125
+ held). It is not written in solo mode, where nothing was counted, nor on a step that passed by its skip
126
+ or inherited shortcut, where nothing was asked. The same rules are listed in
127
+ `../../yad-epic/references/state-schema.md` (Closing records) and `docs/CLI.md`. `yad gate status` prints it as
128
+ `count capped from 3 to 1 (2 active people)`. The generated review-PR body also keeps the count as it
129
+ was when the PR was opened.
130
+
131
+ Why count people: the rule names no person, no role and no step. A stored list of people goes
132
+ stale; repository access decides who can approve.
133
+
134
+ Four surfaces print the count with its cap: `yad gate sync`, `yad gate status`, the generated
135
+ review-PR body and `yad open-pr`. (`checks/risk-route.sh` and `checks/hub-route.sh` print the count
136
+ without the cap — they cannot count people.) `yad gate sync` and `yad gate status` share one suffix; the review-PR body and `yad
137
+ open-pr` word the cap their own way (below). The shared suffix appears only when the step has a risk
138
+ step. It names the cap only when the cap lowered the ask, and always ends by saying what holds:
139
+ - ` — capped to 1: 2 active people, less one seat for the author — base enforced, risk step advisory` — the cap lowered the ask;
140
+ - ` — capped to 1: 1 active person (never below 1) — base enforced, risk step advisory` — at 1 active person (at 0 it reads `0 active people (never below 1)`);
141
+ - ` — base enforced, risk step advisory` — the cap lowered nothing, or the people could not be counted.
142
+
143
+ Where each surface puts it:
144
+ - `yad gate sync`: `1 approved; count: 3 approvers = base 1 + contract risk 2 — capped to 1: 2 active people, less one seat for the author — base enforced, risk step advisory`
145
+ - `yad gate status`: `; count: <sum>` with the same suffix, after the distinct-people count. Above the
146
+ gates it prints the count of people and what it does, for example
147
+ `active people: 2 in the last 90 days — caps each gate's count at 1 approver (one seat is left for the author); reported, only the base is enforced`
148
+ (at 0 or 1 people the bracket reads `(never below 1)`),
149
+ or, when it cannot count them, `active people: NOT COUNTED — <reason> — no cap can be shown, and only the base holds each gate`.
150
+ - the generated review-PR body: `- **Approvals needed:** 1 (enforced) · full count 3 approvers = base 1 + contract risk 2, capped to 1 for 2 active people when this PR was opened (the risk step is advisory)`.
151
+ With no cap: `- **Approvals needed:** 1 (enforced) · full count 3 approvers = base 1 + contract risk 2 (the risk step is advisory)`.
152
+ Its Active-people line: ``- **Active people:** 2 when this PR was opened (the cap is 1 (one seat is left for the author), so this gate's count is 1; `yad gate status` counts it live)``
153
+ (at 0 or 1 people the inner bracket reads `(never below 1)`);
154
+ with no risk step it ends ``… (`yad gate status` counts it live)``.
155
+ - `yad open-pr` (a code-repo task PR, Build half): `this PR asks for 3 approvers = base 1 + contract risk 2, capped to 1 for 2 active people — base enforced, risk step advisory; …`
156
+ (the line goes on to name `checks/risk-route.sh`, which prints the count without the cap). The cap
157
+ appears only when `yad open-pr` is run from the Product; from inside a code repo the people are not counted.
158
+
159
+ `yad gate review` prints JSON, and it carries the rule as an object under `step.gateRule`, and the cap under `step.cap` — an object `{ active, limit, to, capped }`, where `to` is the count asked for after the cap and
160
+ `capped` is true when the cap lowered it — instead of a sentence. The whole `step.cap` field is `null`
161
+ when the people were not counted.
162
+
163
+ Solo mode waives approvals entirely, exactly as before, and reports no shortfall. No `capped` record is
164
+ written in solo mode. The merge and the
165
+ resolved threads still advance the step, and the review step's closing record carries `waived: "solo"`
166
+ (E10). Switch with `yad mode solo --reason "<why>"` / `yad mode team`.
167
+ In solo mode, `yad gate status`, `yad mode`, `yad next` and `yad doctor` count the active people and print
168
+ `! solo mode is on, but …` when more than one person may work on the Product (E74): 2 or more platform
169
+ logins, or 2 or more names not matched to a login, or an approval by someone with a platform login in the
170
+ counting window with more than one person counted. It only suggests `yad mode team`; nothing switches by
171
+ itself, and it never suggests solo mode. It says "may", because one person with two accounts, two
172
+ spellings of their name, or a bot that commits or auto-approves also reads as two. When the people could
173
+ not be counted, nothing is suggested: `yad mode` and `yad doctor` (a warning) say so; `gate status` and
174
+ `yad next` do not repeat it.
14
175
 
15
176
  **Engagement (the Review Companion).** Each approval carries `engagement: verified | none` —
16
177
  `verified` when it was recorded through the companion (a real trailer/cards/chat session), `none` for a
17
178
  bare UI click. By **default (soft)** both count: a bare approve still passes the gate but is recorded
18
179
  `none` and draws a friendly public @-mention nudge, so review *quality* is visible without blocking
19
180
  anyone. When `hub.review.requireEngagement: true`, only `verified` approvals are counted toward the
20
- sets above (a determined faker can still run an empty session — the signal is **gameable by design**;
21
- it raises the cost of a rubber-stamp and makes laziness visible, it does not prove a human read the
22
- artifact). Philosophy: *visible, not impossible.*
23
-
24
- **Touched domains** are resolved from files, not hardcoded:
25
- - Architecture+contract review: the touched domains are the epic's `repos` (every repo shares the
26
- contract surface).
27
- - Stories review: the touched domains are the **union of every story's `repos`** under `stories/`.
28
-
29
- So one gate, two option-shapes:
30
- - Epic / UI / test-cases reviews: base rule (no risk tags, no per-repo routing).
31
- - Architecture+contract review: escalated (`risk_tags: ["contract"]`) — owner + 1 reviewer + a
32
- `domain-owner` for **each** repo in `epic.repos`. (A small team may have one engineer own several
33
- repos — one person can supply several `domain-owner` records with different `domain` values.)
34
- - Stories review: per-repo routing — owner + 1 reviewer + a `domain-owner` (the repo's engineer) for
35
- **each** repo that appears in any story's `repos`.
181
+ approvers above; when that leaves the gate short, it adds a line saying how many approvals did not count (a determined faker
182
+ can still run an empty session — the signal is **gameable by design**; it raises the cost of a
183
+ rubber-stamp and makes laziness visible, it does not prove a human read the artifact). Philosophy:
184
+ *visible, not impossible.*
185
+
186
+ **Touched domains** are resolved from files, not hardcoded. They name and label a review (`domain:<repo>`
187
+ labels on the review PR); they add no approvals:
188
+ - A step with a risk tag (e.g. architecture+contract review): the epic's `repos`.
189
+ - Stories review: the **union of every story's `repos`** under `stories/`.
190
+
191
+ So one gate, one shape — only the full count changes with the tags:
192
+ - Epic / UI / stories / test-cases reviews (no risk tags): the count asks for 1.
193
+ - Architecture+contract review (`risk_tags: ["contract"]`): the full count asks for **3 distinct
194
+ approvers** (base 1 + contract 2). The base (1) holds the gate; a shortfall against 3 is reported
195
+ without blocking.
196
+ - `stories-review` is an ordinary count gate. It no longer routes per repo.
36
197
 
37
198
  ## Staleness
38
199
  An approval round is invalidated if the authored artifact was edited after the newest `approved`
39
200
  record's date/round. When that happens, drop back to `comment` — reviewers must re-approve the new
40
201
  content. This prevents "approve, then quietly change it" (build plan §5 spirit).
41
202
 
203
+ The engine checks this by content, not by date. A bridge approval records `artifactHash`, the
204
+ fingerprint of what was approved. When the artifact's fingerprint no longer matches, the approval is
205
+ counted as revoked: it stays on disk, it no longer counts, and the gate reports `N approval(s) revoked —
206
+ artifact changed; re-approve`. An approval recorded with no `artifactHash` is never treated as stale.
207
+ A hand-written approval (the `approve` action, on a Product with no platform) carries no fingerprint and
208
+ no platform evidence, so an edit to the artifact does **not** revoke it: after a real change, remove the
209
+ old approval and record it again once the reviewer has seen the new content.
210
+
42
211
  For the architecture+contract review there is a second, content-based staleness check: recompute the
43
212
  SHA-256 of the contract-surface block and compare it to `.sdlc/contract-lock.json`. A mismatch means
44
213
  the locked surface changed even if the file's mtime looks fine — approvals are stale, re-lock and
@@ -48,24 +217,25 @@ drifted from its lock — run it rather than recomputing by hand.
48
217
 
49
218
  ## Worked example — epic gate
50
219
 
51
- 1. `action: open` → `reviews/epic--2026-06-04--comments.md` seeded; step `epic-review` set
52
- `in_review`; `currentStep = epic-review`.
53
- 2. Reviewer *bob* leaves comments → captured in the comments file; owner *alice* (pm-assisted)
220
+ 1. `action: open` → step `epic-review` set `in_review`; `currentStep = epic-review`.
221
+ 2. Reviewer *bob* leaves comments → captured in the comments file; the author *alice* (pm-assisted)
54
222
  edits `epic.md`.
55
- 3. `action: approve` approver *alice* role *owner* → ledger entry added. Predicate re-evaluated:
56
- `|owners|=1, |reviewers|=0` → **fails** (need 1 reviewer). Gate reports "missing: 1 reviewer".
57
- 4. `action: approve` approver *bob* role *reviewer* → ledger entry added. Predicate:
58
- `|owners|=1, |reviewers|=1` → **base pass**.
223
+ 3. Predicate before any approval: `|approvers|=0` → **fails**. Gate reports "missing: 1 approval(s)".
224
+ *alice* wrote the artifact, so her own approval is not recorded.
225
+ 4. `action: approve` approver *bob* → ledger entry added. Predicate: `|approvers|=1` → **base pass**
226
+ (`epic-review` has no risk tags, so the full count is also 1 and nothing is short).
59
227
  5. `action: advance` → `epic-review.status=done`, `architecture.status=in_progress`,
60
228
  `currentStep=architecture`. Gate reports the advance. The paired authoring step (`epic`) is closed
61
- too, if it was not already — a gate cannot have passed on an unauthored artifact. `doctor` reports
229
+ too, if it was not already — a gate cannot have passed on an unauthored artifact. Each step it closes
230
+ gets a `closed` record: `via: "approved"` on the review step, `via: "review-passed"` on the author step (E18). In solo mode the
231
+ review step's record also carries `waived: "solo"` (E10). `doctor` reports
62
232
  any surviving violation as `YAD-STATE-005`; `yad gate repair <epic>` heals it.
63
233
 
64
234
  ## Participation record (comments.json)
65
235
  `approvals.json` answers "who approved"; `.sdlc/comments.json` answers "who reviewed/commented". The
66
236
  gate appends a record per commenter per round on every `comment` action (the machine-readable
67
237
  counterpart to the `reviews/*--comments.md` markdown). It does **not** feed the predicate — approvals
68
- alone decide the gate — but it makes the `approved.md` roster's "Reviewed / commented by" section
238
+ alone decide the gate — but it makes the `approved.md` record's "Reviewed / commented by" section
69
239
  attributable, and it is the same shape a future service or the platform bridge can write.
70
240
 
71
241
  ## Non-blocking companion comments (`<!-- yad:noblock -->`)
@@ -77,33 +247,39 @@ marker, and the gate **excludes marked threads** from the unresolved-thread bloc
77
247
  not "resolve to pass" — it ignores them). A reviewer's *genuine* concern is posted **without** the
78
248
  marker and blocks normally, exactly as a `CHANGES_REQUESTED` or any unresolved human thread does.
79
249
 
80
- ## Platform-backed input (the bridge)
81
- When the hub has a platform (`.sdlc/hub.json`) and the bridge is enabled, reviewers can approve/comment
82
- on a real PR/MR instead of (or as well as) the skill recording it directly. `action: sync`
83
- (`yad-hub-bridge`) reads that platform state with the reviewer's own `gh`/`glab` and writes the **same**
250
+ ## Platform-backed input (the verified ledger)
251
+ When the Product has a platform (`.sdlc/hub.json`), reviewers can approve/comment
252
+ on a real PR/MR instead of (or as well as) the skill recording it directly. The sync reads that platform
253
+ state with the local user's own `gh`/`glab` and writes the **same**
84
254
  `approvals.json` / `comments.json` / `reviews/*.md` records the manual path writes — bridge approvals
85
- tagged `"source": "bridge"`. **The predicate above is unchanged**: it counts owner/reviewer/domain-owner
86
- approvals regardless of how they were recorded.
87
-
88
- - login → role(s) via the roster's **per-scope map** (`roles: { hub: [...], <repo>: [...] }`): a person
89
- can hold owner + reviewer + domain-owner at once, and a repo can list several people per role. The
90
- `hub` roles plus each touched domain's `roles[<repo>]` are emitted; `domain-owner` is also **derived**
91
- when a roster `name` equals a repo's `domain_owner`/`domain_owners` (legacy fallback) and that repo is a
92
- touched domain; an unmapped login is a plain `reviewer`, never promoted.
93
- - On PR/MR open the assignee is the committer and reviewers are the scope's `reviewer` + `domain-owner`
94
- members (minus the committer); the owner/author is recorded, not requested. See
255
+ tagged `"source": "bridge"`. With a local ledger `yad gate sync` writes them; with a verified ledger
256
+ `yad gate ci` writes them at merge, and a local `yad gate sync` only prints. **The predicate above is
257
+ unchanged**: it counts distinct approvers regardless of how they were recorded.
258
+
259
+ - The `approver` / `commenter` is the platform login that reviewed. There is no lookup and no role.
260
+ - On PR/MR open the assignee is whoever opens it: `@me` on GitHub, and the login `glab` reports on GitLab.
261
+ **No reviewers are requested** — the command prints `no reviewers were requested — ask them on the
262
+ PR itself`. `domain:<repo>` labels for the touched domains are still applied. See
95
263
  `../yad-hub-bridge/references/login-roster.md`.
96
- - `sync` is idempotent (upsert by `(step, approver, role, domain)`; key comments on comment id) and
97
- never touches **manual** approvals. A revoked approval is superseded **while the step is open**; once
264
+ - `sync` is idempotent (upsert by `(step, approver)`, one record per person; comment records by
265
+ `(step, commenter, round)`, an unchanged round rewritten in place) and never touches **manual** approvals. An older bridge record that carries `role`/`domain` is
266
+ recognised as `../yad-hub-bridge/references/login-roster.md` → "Older records" describes, and replaced
267
+ by one login-named record that keeps its fingerprint. Every sync write (`yad gate sync`, `yad gate ci`)
268
+ also records the login on the older records the roster can place for certain; only those it cannot
269
+ place still need the roster on a later sync. A bridge approval records the
270
+ platform's evidence (`approvedAt`, and on GitHub `commit`, `url`, `reviewId`) — for the record only. A revoked approval is superseded **while the step is open**; once
98
271
  the step is `done` its approvals are kept as the record of why it passed, and a re-sync only re-binds
99
272
  new ones (see `../yad-hub-bridge/references/bridge.md` → "Idempotent re-sync").
100
- - The architecture+contract staleness rule applies to bridge approvals too: a re-lock discards bridge
101
- approvals dated before the new lock.
102
- - No platform / no CLI → the gate runs file-only with no error. Detail: `../yad-hub-bridge/references/bridge.md`.
273
+ - The architecture+contract staleness rule applies to bridge approvals too: a re-lock changes the
274
+ surface fingerprint, so bridge approvals of the old surface stop counting (they stay on disk as revoked).
275
+ - No platform / no CLI → the gate runs local with no error. Detail: `../yad-hub-bridge/references/bridge.md`.
103
276
 
104
277
  ## Why this shape
105
- - Owner + 1 reviewer keeps review load low on a small team (design priority 2) while still requiring
106
- a second pair of eyes (priority 1, code quality / production safety).
107
- - Risk-based escalation spends scarce domain-owner attention only where a change can break a shared
108
- surface (contract/auth/payments).
278
+ - One approver who is not the author keeps review load low on a small team (design priority 2) while
279
+ still requiring a second pair of eyes (priority 1, code quality / production safety).
280
+ - The risk step asks for more people only where a change can break a shared surface
281
+ (contract/auth/payments). It is advisory for now: the capacity cap (E72) is reported. A later change will
282
+ enforce it together with `yad gate lower --reason`, so that a gate asking for more people than the team
283
+ has will always have a way out. Until then E73 only warns when a gate may not pass.
284
+ - No stored list of people: it goes stale, and repository access already decides who can approve.
109
285
  - Everything is a file, so a future service can drive the same gate by writing the same records.
@@ -1,26 +1,25 @@
1
1
  ---
2
2
  name: yad-run
3
- description: 'Phase 4 (automation) — the orchestrator that makes the second dial real. Drives a story''s back-half loop (spec → tasks → implement → checks) in one code repo, reading each step''s automation dial from build-state: on machine_advance it advances on its own, on human_approve it stops for a human. Records every run in the trust log (the evidence base for earning automation). Realizes Step B: when checks is earned, a clean gate pass auto-advances to engineer-review; any failure, scope overrun, or contract-surface touch HALTS and pulls in a human. Also sets a step''s dial (gated by trust evidence) and flips the system-wide kill switch. Never advances a front state or the engineer review. Use when the user says "run the build half", "advance story <id>", "set the checks dial", or "kill switch".'
3
+ description: 'Phase 4 (automation) — the orchestrator that makes the second dial real. Drives a story''s Build loop (spec → tasks → implement → checks) in one code repo, reading each step''s advance dial from build-state: on `auto` it advances on its own, on `human` it stops for a human. Records every run in the trust log, the run record `yad dial` shows as advice. With `checks` on auto, a clean gate pass advances to engineer-review; any failure, scope overrun, or contract-surface touch HALTS and pulls in a human. Also sets a step''s dial (`yad dial`, nothing to earn since E34) and flips the kill switch (`yad kill` / `yad unkill`). Never advances a Shape step or the engineer review. Use when the user says "run Build", "advance story <id>", "set the checks dial", or "kill switch".'
4
4
  ---
5
5
 
6
6
  # SDLC — Run (Phase 4 orchestrator)
7
7
 
8
- **Goal:** Be the **engine** that the `automation` dial finally drives. Until Phase 4 the dial was inert
9
- config; this skill reads it and acts. For ONE story in ONE code repo, walk the back-half steps —
8
+ **Goal:** Be the **engine** that the advance dial finally drives. Until Phase 4 the dial was inert
9
+ config; this skill reads it and acts. For ONE story in ONE code repo, walk the Build steps —
10
10
  `spec → tasks → implement → checks → engineer-review` — and at each step either **advance on its own**
11
- (dial `machine_advance`, step succeeded) or **stop for a human** (dial `human_approve`, or any halt
12
- condition). Every run is recorded in the **trust log**, the evidence that earns a step its automation.
11
+ (dial `advance: auto`, step succeeded) or **stop for a human** (dial `advance: human`, or any halt
12
+ condition). Every run is recorded in the **trust log** — the run record `yad dial` shows the team, as
13
+ advice, when it sets a dial.
13
14
 
14
15
  This is the most dangerous skill in the system, so it is built to **halt-and-escalate over guess**:
15
16
  a failing check, ambiguity, a scope overrun, or a contract-surface touch stops the loop and pulls in a
16
- human regardless of any dial. The **front states and the engineer review never auto-advance** — they
17
- are not in `automation.back_steps` and `engineer-review` is `locked`.
17
+ human regardless of any dial. This skill **drives Build only**: it never drives a Shape step, and the
18
+ **engineer review is a gate** — always a person.
18
19
 
19
- Earned so far: **`checks`** (Step B, Phase 4a — safest, a gate's pass/fail was never human judgment)
20
- and **`implement`** (Step D, Phase 4b — the `implement → check` hand-off; the scope/contract halts and
21
- the engineer review still gate the merge). **`tasks`** (Step C) and `spec` have their dials and trust
22
- hooks but stay `human_approve` until their own evidence clears the threshold — there is no historical
23
- signal to seed them from, so they are earned only on real runs.
20
+ **Nothing has to be earned (E34).** Automation used to be unlocked per step once its trust log cleared a
21
+ threshold. Now the team sets each lane step's dial with `yad dial`, which prints that step's run record
22
+ beside it as advice and never refuses on it. The kill switch (`yad kill`) holds every step at `human`.
24
23
 
25
24
  ## Conventions
26
25
 
@@ -28,16 +27,17 @@ signal to seed them from, so they are earned only on real runs.
28
27
  truth: it holds the story, the build-state, and the trust log).
29
28
  - Code repos are separate git repos under `{project-root}/demo-repos/<repo>/`
30
29
  (`config.yaml` `build.code_repos_root`). Operate inside them with absolute paths.
31
- - Automation config is `skills/sdlc/config.yaml` → `automation:` (`back_steps`, `default`,
32
- `trust_threshold`, `locked_steps`, `kill_switch`).
33
- - Per-story build-half state: `epics/<epic>/.sdlc/build-state/<story-id>.json` (per repo).
30
+ - Automation config is `.sdlc/config.yaml` → `automation:` (`back_steps`, `default`). The kill
31
+ switch and the Shape dials live in `.sdlc/automation.json`, and are read and written **only through the
32
+ engine** — `yad dial … --json`, `yad kill`, `yad unkill`. Never read or edit either file for a dial.
33
+ - Per-story Build state: `epics/<epic>/.sdlc/build-state/<story-id>.json` (per repo).
34
34
  - Trust ledger: **shard-then-fold** — each run is its own shard file
35
35
  `epics/<epic>/.sdlc/trust-log/<story>-<repo>-<step>-<uid>.json` (a fresh `uid` per run, so concurrent
36
36
  writers never conflict); readers UNION the folded `trust-log.json` with every loose shard, and
37
37
  `yad tidy up` folds finished shards back into `trust-log.json`. Schemas:
38
38
  `../yad-epic/references/state-schema.md`.
39
- - These machine-written back-half files (`build-state/<story>.json`, the `trust-log/` shards) are
40
- committed by **`yad checkpoint`** — the back-half analogue of the front-half `yad gate` sync; the loop
39
+ - These machine-written Build files (`build-state/<story>.json`, the `trust-log/` shards) are
40
+ committed by **`yad checkpoint`** — the Build analogue of the Shape `yad gate` sync; the loop
41
41
  calls it each iteration so the state is durable and shared without a human commit. (`yad checkpoint`
42
42
  stages the shard dirs; `yad tidy up` later folds finished shards — loose objects + `git gc`.)
43
43
  - The orchestrator **calls the existing step skills unchanged** — `yad-spec` (A), `yad-implement`
@@ -52,78 +52,108 @@ signal to seed them from, so they are earned only on real runs.
52
52
  - `action` — `run` (default) | `set-dial` | `kill` | `unkill`.
53
53
  - For `run`: optional `from` (the step id to start at; default the repo's `currentStep` in
54
54
  build-state) and `task` (the atomic task id for the `implement`/`checks` legs).
55
- - For `set-dial`: `step` (a `back_steps` id), `to` (`human_approve` | `machine_advance`).
55
+ - For `set-dial`: `step` (a `back_steps` id), `to` — `human` | `auto` (the older `human_approve` |
56
+ `machine_advance` mean the same two things).
57
+ - For `kill`: `reason` (required). For `unkill`: an optional `reason`.
56
58
 
57
59
  ## On Activation
58
60
 
59
61
  ### Step 0 — Load state
60
- Read `config.yaml` `automation`, the story's `build-state/<story>.json` (create it from the
61
- `back_steps` defaults if absent — all `human_approve`, `engineer-review` `locked:true`), and
62
- `trust-log.json` (treat missing as `[]`). Resolve the code repo.
62
+ Read `config.yaml` `automation` (for `back_steps`), the story's `build-state/<story>.json` (create it from the
63
+ `back_steps` defaults if absent — all `advance: human` (`automation: human_approve`), `engineer-review` `locked:true`, and
64
+ **write it to disk before the loop starts**, because `yad next` reads that file to know this lane
65
+ exists), and `trust-log.json` (treat missing as `[]`). Resolve the code repo.
66
+
67
+ If the file exists but has **no entry for this `repo`**, add that repo's entry from the same `back_steps`
68
+ defaults — the file may already hold another repo, or a skipped lane — and never remove an entry you did
69
+ not add. If this repo's entry is **`status: skipped`** (E39: the whole lane was set aside with
70
+ `yad skip <epic> <story> --repo <repo>`, because the story needs no change in this repo), **do not drive
71
+ it**: stop, report the recorded `record.reason`, and point at `yad unskip <epic> <story> --repo <repo>` if
72
+ the lane is owed after all. **Never write a Build step as `skipped` or `deferred`**: a lane is skipped
73
+ whole or not at all, and `yad doctor` reports a single step set aside.
74
+
75
+ Also read **which skill runs each step** — `yad skill list --json`, whose `steps[]` gives each step id
76
+ its `skills` array. That is the project's choice (`.sdlc/skills.json`, E6) and the engine's default
77
+ when it has made none. Read it once here; it does not change during a run.
63
78
 
64
79
  ### `action: run` — drive the loop
65
80
  Walk the steps for `repo` starting at `from`/`currentStep`. For each step:
66
81
 
67
- 1. **Run the step's skill** — `spec`→`yad-spec`, `tasks`→ the tasks leg of `yad-spec`,
68
- `implement`→`yad-implement`, `checks`→`yad-checks (action: run)`. Capture its result.
82
+ 1. **Run the step's skill — use the list from Step 0, never a name written here.** Look the step id
83
+ up in `yad skill list --json` and run every entry of its `skills` **in order**, each one seeing what
84
+ the one before it produced; the last output is the result. (`yad next <epic> --json` gives the same
85
+ answer per lane once `build-state/<story>.json` is on disk, if you already have it open.) Which
86
+ skill runs a step is the project's setting, so a hardcoded name here would make this loop do one
87
+ thing while `yad next` tells the user another — on the same project, with the automation half
88
+ winning. Unbound, the engine answers `spec`→`yad-spec`, `tasks`→ the tasks leg of `yad-spec`,
89
+ `implement`→`yad-implement`, `checks`→`yad-checks`, which is what this step used to say. Pass
90
+ `action: run` to `yad-checks`. Capture the result.
69
91
  2. **Derive trust signals & write a trust-log shard** — write the entry to its own file
70
92
  `trust-log/<story>-<repo>-<step>-<uid>.json` (a fresh `uid` per run; never append to a shared file),
71
93
  with `ranBy: machine` if this advance was automated, else `human` — see `references/run-loop.md` for
72
94
  the derivation. Do this for *every* step run, pass or fail; the log is the evidence base.
73
- 3. **Compute the effective dial.** Start from the step's `automation` in build-state, then **force it
74
- to `human_approve`** if `automation.kill_switch` is true OR the step is `locked` OR the step id is
75
- in `automation.locked_steps`. (So a kill switch or a lock always wins.)
95
+ 3. **Ask the engine for the effective dial:** `yad dial <epic> <story> --repo <repo> <step> --json`, and use
96
+ its `advance`. It is `human` whenever the step is a gate (`why: gate` — a read never refuses one), the kill switch
97
+ is on, or `.sdlc/automation.json` cannot be read (`automationError` says so) — so never work the dial out from the files yourself.
76
98
  4. **Decide:**
77
99
  - **HALT** if the step failed — any check FAIL, a scope overrun (`yad-implement` stopped on the
78
- file-boundary rule), a contract-surface touch, or any ambiguity. Set the step `status: blocked`,
79
- write the `rejected` trust entry, **stop the loop**, and report what a human must resolve.
80
- - else if effective dial is **`machine_advance`** → set the step `done`, advance `currentStep` to
81
- the next step, and **continue the loop** (this is the Step B auto-advance for `checks`).
82
- - else (**`human_approve`**) → set the step `done`/`in_review`, **stop** and report
83
- "waiting for a human at `<next-step>`".
100
+ file-boundary rule), a contract-surface touch, or any ambiguity. Set the step `status: blocked`
101
+ **with a `record`** — `{ "reason": "<the halt cause>", "by": "<login or null>", "date":
102
+ "<YYYY-MM-DD>" }` — write the `rejected` trust entry, **stop the loop**, and report what a human
103
+ must resolve. The record is not decoration: since shape 7 a `blocked` step with nothing recorded
104
+ on it reads as `todo` ("not started"), so a halt written without one is indistinguishable from a
105
+ lane nobody has begun. `yad doctor` reports the difference as `step:no-record`.
106
+ - else if effective dial is **`advance: auto`** → set the step `done` **with a `closed`** —
107
+ `{ "by": "<login or null>", "date": "<YYYY-MM-DD>", "via": "auto", "run": "<the trust-log uid>" }` —
108
+ advance `currentStep` to the next step, and **continue the loop** (this is the Step B auto-advance
109
+ for `checks`).
110
+ - else (**`advance: human`**) → set the step `done`/`in_review`, and give a `done` step a `closed` —
111
+ `{ "by": "<login or null>", "date": "<YYYY-MM-DD>", "via": "human", "run": "<the trust-log uid>" }` —
112
+ then **stop** and report "waiting for a human at `<next-step>`".
113
+ - `closed` is the step's **closing record** (E18): how the lane moved past it, and which run did it.
114
+ Never write one over a `closed` already on the step. See `references/run-loop.md`.
84
115
  5. **Always stop at `engineer-review`** (it is `locked`): hand off to `yad-engineer-review` for the human merge
85
116
  gate, which finalizes the trust verdict (confirm/override the provisional one).
86
117
 
87
118
  **Commit the machine-written state.** After each iteration's writes (the trust-log shard in 2 and the
88
119
  build-state change in 4), run `yad checkpoint --push` from `{project-root}`. It commits *only* the
89
- `trust-log/` shards + `build-state/<story>.json` (never a front-half gate file) as one `chore(hub): …`
120
+ `trust-log/` shards + `build-state/<story>.json` (never a Shape gate file) as one `chore(hub): …`
90
121
  audit-trail commit, and only ever on the default branch. It is a safe no-op when nothing changed, so
91
122
  call it every iteration — teammates don't review these machine writes, but CI and `yad status` on
92
123
  other machines must see current trust evidence. Never run it off the default branch (it will refuse):
93
- an unpushed or branch-stranded trust log quietly undermines the "earned automation" premise.
94
-
95
- ### `action: set-dial` — earn (or revert) a step's automation
96
- Flip `step`'s `automation` to `to` in build-state. Enforce, in order:
97
- - **Refuse** if `step` is in `automation.locked_steps` or is a front state or `engineer-review` —
98
- these can never be `machine_advance` (front-state lock, build plan §E). Report the refusal reason.
99
- - For `to: machine_advance`, **refuse unless the trust threshold is met**: the step's slice of the trust
100
- ledger — the **union** of the folded `trust-log.json` `runs` PLUS every `trust-log/` shard, filtered to
101
- this step (and repo) — has `>= trust_threshold.min_runs` entries AND the fraction with
102
- `verdict == "approved-unchanged"` is `>= trust_threshold.min_approved_unchanged`. If it is not met,
103
- report the current evidence (runs, % unchanged) and how far short it is — "it seems fine" is not
104
- evidence.
105
- - `to: human_approve` is **always allowed** (reverting automation is one move, never gated).
124
+ an unpushed or branch-stranded trust log leaves the run record `yad dial` shows out of date.
125
+
126
+ ### `action: set-dial` — set (or revert) a step's advance dial
127
+ Run `yad dial <epic> <story> --repo <repo> <step> --to <human|auto>` (map `human_approve` → `human` and
128
+ `machine_advance` → `auto` first). The engine writes both dial names on the lane step, refuses a gate and
129
+ a lane step `yad-run` has not written yet, and prints the step's run record in that repo **as advice**.
130
+ Relay its output. There is **no threshold** (E34): the team decides, and "it seems fine" is theirs to
131
+ judge. `--to human` is always accepted. Never edit `build-state` for a dial yourself.
132
+
133
+ For a Shape author step the team wants on auto, point at `yad dial <step> --to auto` — project-wide, and
134
+ recorded only: this loop never drives a Shape step.
106
135
 
107
136
  ### `action: kill` / `action: unkill` — the kill switch
108
- Set `automation.kill_switch` to `true` (`kill`) or `false` (`unkill`) in `config.yaml`. While true,
109
- **every** step's effective dial is `human_approve` system-wide — no per-step edits, instantly
110
- reversible (build plan §Safety). Report the new state and that `yad-status` will show it.
137
+ Run `yad kill --reason "<why>"` or `yad unkill [--reason "<why>"]`. The engine records who, when and why
138
+ in `.sdlc/automation.json`; while the switch is on, **every** step's effective dial is `human`. Tell the
139
+ user to commit that file so every machine and CI sees it. Never set a `kill_switch` in `config.yaml`:
140
+ nothing reads it, and `yad doctor` fails on one left `true`.
111
141
 
112
142
  ## Hard rules (phase-4-build-plan.md)
113
143
 
114
- - **Earned per step, with evidence.** A step goes `machine_advance` only after its trust log clears
115
- the threshold; `set-dial` enforces it.
116
- - **Reversible in one move.** `human_approve` is never gated; the kill switch reverts everything with
117
- one command and no code change.
144
+ - **The dial is the team's (E34).** `yad dial` sets it and shows the run record as advice; nothing is
145
+ earned, and nothing is refused on evidence. A gate is never auto.
146
+ - **Reversible in one move.** `yad dial … --to human` is never refused; `yad kill --reason "<why>"` holds
147
+ every step at human, with no code change.
118
148
  - **Halt-and-escalate beats guess.** A failing check, ambiguity, scope overrun, or contract-surface
119
149
  touch halts the loop and pulls in a human, regardless of the dial.
120
- - **Front states and the engineer review never auto-advance.** They are not in `back_steps`;
121
- `engineer-review` is `locked`; the kill switch and locks always override the dial.
150
+ - **This skill never drives a Shape step, and never passes the engineer review.** The review is a gate,
151
+ and the kill switch holds every step at human.
122
152
  - **The orchestrator never changes what a step does** — it calls the existing skills and owns only the
123
153
  advance decision and the trust record.
124
154
 
125
155
  ## Reference
126
- - The loop, the trust-verdict derivation, and the threshold predicate: `references/run-loop.md`.
156
+ - The loop and the trust-verdict derivation: `references/run-loop.md`.
127
157
  - State/trust schemas: `../yad-epic/references/state-schema.md`.
128
158
  - The steps it drives: `../yad-spec/`, `../yad-implement/`, `../yad-checks/`; the human gate it
129
159
  hands off to: `../yad-engineer-review/`.