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.
- package/CHANGELOG.md +355 -0
- package/README.md +79 -26
- package/bin/commands.mjs +41 -0
- package/bin/yad.mjs +437 -124
- package/cli/artifact-status.mjs +34 -15
- package/cli/checkpoint.mjs +69 -49
- package/cli/codeowners-command.mjs +170 -0
- package/cli/codeowners.mjs +397 -0
- package/cli/commit.mjs +13 -9
- package/cli/companion.mjs +2 -2
- package/cli/dial.mjs +183 -0
- package/cli/docs.mjs +88 -32
- package/cli/doctor.mjs +1472 -97
- package/cli/epic-state.mjs +3478 -232
- package/cli/epic.mjs +506 -0
- package/cli/errors.mjs +4 -1
- package/cli/gate.mjs +1002 -209
- package/cli/history.mjs +556 -0
- package/cli/hook.mjs +266 -55
- package/cli/hubcommit.mjs +6 -17
- package/cli/index-command.mjs +87 -0
- package/cli/ledger.mjs +57 -7
- package/cli/lib.mjs +184 -18
- package/cli/manifest.mjs +367 -56
- package/cli/migrate.mjs +726 -53
- package/cli/mode.mjs +170 -0
- package/cli/next.mjs +349 -90
- package/cli/openpr.mjs +191 -39
- package/cli/people.mjs +654 -0
- package/cli/plan.mjs +417 -132
- package/cli/platform.mjs +110 -129
- package/cli/product-index.mjs +287 -0
- package/cli/protection.mjs +706 -0
- package/cli/reconcile.mjs +38 -12
- package/cli/repo-publish.mjs +24 -26
- package/cli/repo.mjs +23 -14
- package/cli/report.mjs +21 -15
- package/cli/review.mjs +24 -27
- package/cli/riskmap-command.mjs +289 -0
- package/cli/riskmap.mjs +373 -0
- package/cli/setup.mjs +139 -287
- package/cli/ship.mjs +7 -6
- package/cli/skill.mjs +180 -0
- package/cli/skip.mjs +211 -30
- package/cli/thread.mjs +42 -17
- package/cli/tidy.mjs +20 -20
- package/cli/update-commit.mjs +22 -22
- package/cli/usage.mjs +115 -109
- package/package.json +3 -3
- package/skills/sdlc/config.yaml +166 -87
- package/skills/sdlc/module-help.csv +35 -35
- package/skills/yad-analysis/SKILL.md +125 -65
- package/skills/yad-architecture/SKILL.md +34 -23
- package/skills/yad-architecture/references/contract-format.md +10 -8
- package/skills/yad-backfill/SKILL.md +14 -8
- package/skills/yad-backfill/references/backfill.md +1 -1
- package/skills/yad-change/SKILL.md +127 -52
- package/skills/yad-change/references/triage.md +42 -28
- package/skills/yad-checks/SKILL.md +89 -45
- package/skills/yad-checks/references/check-gates.md +315 -92
- package/skills/yad-checks/templates/checks/build-test-lint.sh +25 -7
- package/skills/yad-checks/templates/checks/commit-message.sh +17 -3
- package/skills/yad-checks/templates/checks/contract-check.sh +58 -2
- package/skills/yad-checks/templates/checks/epic-open.sh +3 -3
- package/skills/yad-checks/templates/checks/install-deps.sh +46 -0
- package/skills/yad-checks/templates/checks/ledger-guard.sh +94 -18
- package/skills/yad-checks/templates/checks/lineage-check.sh +23 -9
- package/skills/yad-checks/templates/checks/package-manager.sh +140 -0
- package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +4 -4
- package/skills/yad-checks/templates/checks/risk-map-check.sh +438 -0
- package/skills/yad-checks/templates/checks/verified-commits.sh +20 -46
- package/skills/yad-checks/templates/github/yad-checks.yml +37 -5
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +5 -5
- package/skills/yad-checks/templates/github/yad-update-guard.yml +3 -4
- package/skills/yad-checks/templates/github/yad-verified-commits.yml +4 -4
- package/skills/yad-checks/templates/gitlab/.gitlab-ci.yml +7 -1
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +22 -4
- package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +5 -5
- package/skills/yad-checks/templates/gitlab/yad-verified-commits.gitlab-ci.yml +4 -4
- package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +91 -0
- package/skills/yad-checks/templates/hooks/ledger-guard.sh +38 -7
- package/skills/yad-commit/SKILL.md +6 -6
- package/skills/yad-connect-design/SKILL.md +6 -6
- package/skills/yad-connect-design/references/design-context.md +1 -1
- package/skills/yad-connect-design/references/design-registry.md +2 -2
- package/skills/yad-connect-docs/SKILL.md +12 -12
- package/skills/yad-connect-docs/references/docs-registry.md +1 -1
- package/skills/yad-connect-learning/SKILL.md +5 -5
- package/skills/yad-connect-learning/references/learning-registry.md +2 -2
- package/skills/yad-connect-repos/SKILL.md +92 -54
- package/skills/yad-connect-repos/references/code-context.md +6 -6
- package/skills/yad-connect-repos/references/hub-config.md +68 -58
- package/skills/yad-connect-repos/references/repos-registry.md +10 -9
- package/skills/yad-connect-repos/references/risk-map.md +81 -0
- package/skills/yad-connect-testing/SKILL.md +6 -6
- package/skills/yad-connect-testing/references/testing-context.md +3 -4
- package/skills/yad-connect-testing/references/testing-registry.md +2 -2
- package/skills/yad-defects/SKILL.md +8 -8
- package/skills/yad-discovery/SKILL.md +130 -94
- package/skills/yad-discovery/references/discovery-schema.md +23 -7
- package/skills/yad-discovery/references/foundation-schema.md +374 -0
- package/skills/yad-docs/SKILL.md +16 -11
- package/skills/yad-docs/references/data-mapping.md +9 -7
- package/skills/yad-docs/templates/app/package-lock.json +3 -3
- package/skills/yad-docs-overview/SKILL.md +32 -17
- package/skills/yad-docs-overview/references/pipeline-model.md +47 -28
- package/skills/yad-docs-sync/SKILL.md +10 -5
- package/skills/yad-docs-sync/references/staleness.md +8 -7
- package/skills/yad-engineer-review/SKILL.md +88 -24
- package/skills/yad-engineer-review/references/ship-and-record.md +25 -16
- package/skills/yad-epic/SKILL.md +178 -100
- package/skills/yad-epic/references/state-schema.md +626 -117
- package/skills/yad-hub-bridge/SKILL.md +66 -48
- package/skills/yad-hub-bridge/references/bridge.md +110 -83
- package/skills/yad-hub-bridge/references/login-roster.md +163 -70
- package/skills/yad-hub-bridge/templates/checks/hub-route.sh +22 -19
- package/skills/yad-hub-bridge/templates/github/yad-gate-sync.yml +34 -14
- package/skills/yad-hub-bridge/templates/gitlab/gitlab-ci.include-root.yml +2 -2
- package/skills/yad-hub-bridge/templates/gitlab/yad-gate-sync.gitlab-ci.yml +22 -12
- package/skills/yad-implement/SKILL.md +29 -15
- package/skills/yad-implement/references/implement-conventions.md +2 -2
- package/skills/yad-learn/SKILL.md +9 -9
- package/skills/yad-learn/references/learning-state.md +2 -2
- package/skills/yad-open-pr/SKILL.md +64 -29
- package/skills/yad-pair-review/SKILL.md +18 -16
- package/skills/yad-pair-review/references/session-state.md +4 -4
- package/skills/yad-pr-template/SKILL.md +48 -27
- package/skills/yad-pr-template/references/risk-routing.md +97 -24
- package/skills/yad-pr-template/templates/checks/pr-template.sh +37 -15
- package/skills/yad-pr-template/templates/checks/pr-title.sh +27 -13
- package/skills/yad-pr-template/templates/checks/risk-route.sh +107 -14
- package/skills/yad-pr-template/templates/github/pull_request_template.md +7 -5
- package/skills/yad-pr-template/templates/gitlab/merge_request_templates/Default.md +7 -5
- package/skills/yad-pr-template/templates/hub/github/pull_request_template.md +15 -14
- package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +15 -13
- package/skills/yad-reconcile/SKILL.md +3 -3
- package/skills/yad-report/SKILL.md +5 -5
- package/skills/yad-review-companion/SKILL.md +12 -9
- package/skills/yad-review-gate/SKILL.md +198 -79
- package/skills/yad-review-gate/references/gating.md +230 -54
- package/skills/yad-run/SKILL.md +86 -56
- package/skills/yad-run/references/run-loop.md +67 -45
- package/skills/yad-ship/SKILL.md +18 -14
- package/skills/yad-spec/SKILL.md +31 -17
- package/skills/yad-spec/references/spec-handoff.md +17 -5
- package/skills/yad-status/SKILL.md +114 -56
- package/skills/yad-stories/SKILL.md +42 -27
- package/skills/yad-stories/references/story-schema.md +10 -9
- package/skills/yad-stub/SKILL.md +59 -48
- package/skills/yad-sync-repos/SKILL.md +3 -3
- package/skills/yad-test-cases/SKILL.md +37 -30
- package/skills/yad-test-cases/references/test-cases-schema.md +8 -5
- package/skills/yad-timeline/SKILL.md +8 -7
- package/skills/yad-ui/SKILL.md +46 -25
- package/cli/roster.mjs +0 -164
- package/skills/sdlc/install.sh +0 -68
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
#
|
|
1
|
+
# the verified ledger — PR/MR ↔ ledger mapping, read recipes, idempotency
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
predicate (`../yad-review-gate/references/gating.md`) runs unchanged. The
|
|
3
|
+
the verified ledger maps platform review state onto the **same file records** the manual gate writes, so the gate
|
|
4
|
+
predicate (`../yad-review-gate/references/gating.md`) runs unchanged. The verified ledger only changes the
|
|
5
5
|
*input path*; it never changes what passing the gate means.
|
|
6
6
|
|
|
7
7
|
## State mapping (platform → ledger)
|
|
8
8
|
|
|
9
9
|
| Platform review state | Ledger effect |
|
|
10
10
|
|---|---|
|
|
11
|
-
| GitHub review `APPROVED` / GitLab MR approval (`approved_by`) |
|
|
12
|
-
| GitHub `COMMENTED` / `CHANGES_REQUESTED`; GitLab discussions/notes | a line under `## <
|
|
11
|
+
| GitHub review `APPROVED` / GitLab MR approval (`approved_by`) | one `approved` record per person in `approvals.json`, `approver` = the platform login, no role, tagged `"source": "bridge"` |
|
|
12
|
+
| GitHub `COMMENTED` / `CHANGES_REQUESTED`; GitLab discussions/notes | a line under `## <login> (unresolved)` or `## <login> (changes requested — **blocking**)` in `reviews/<artifact>--<date>--comments.md` + a `comments.json` record (`commenter` = the login); **never** an approval. `CHANGES_REQUESTED` is also flagged as blocking in the comments file |
|
|
13
13
|
| GitHub review dismissed / GitLab approval revoked | the prior bridge `approved` record for that approver is removed on re-sync **while the step is open**; once the step is `done` the record is kept as the audit trail of why it passed (see idempotency) |
|
|
14
14
|
|
|
15
|
-
`approvals.json` records from the
|
|
15
|
+
`approvals.json` records from the verified ledger carry `"source": "bridge"`; **manual** approvals have no such
|
|
16
16
|
tag and are **never** touched by `sync` — the two coexist.
|
|
17
17
|
|
|
18
18
|
## Read recipes (read-only, local-user auth — no tokens)
|
|
@@ -23,17 +23,17 @@ gh pr view <n> --json reviews,comments,reviewDecision,latestReviews
|
|
|
23
23
|
gh api repos/{owner}/{repo}/pulls/{n}/comments # inline review comments
|
|
24
24
|
```
|
|
25
25
|
- `reviews[].state` ∈ {APPROVED, CHANGES_REQUESTED, COMMENTED, DISMISSED}; `reviews[].author.login` is
|
|
26
|
-
the login to
|
|
26
|
+
the login written to the record. Use `latestReviews` so a superseded earlier review doesn't double-count.
|
|
27
27
|
|
|
28
28
|
**GitLab** (`glab` / `glab api`):
|
|
29
29
|
```
|
|
30
30
|
glab mr view <n>
|
|
31
31
|
glab api projects/:id/merge_requests/:iid/approvals # approved_by[].user.username
|
|
32
|
-
glab api projects/:id/merge_requests/:iid/
|
|
32
|
+
glab api projects/:id/merge_requests/:iid/discussions # discussions + their notes (comments, resolvable threads)
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
All commands run as the **local user**; the
|
|
36
|
-
or the remote is unreachable, the
|
|
35
|
+
All commands run as the **local user**; the verified ledger stores no tokens. If the CLI is missing/unauthenticated
|
|
36
|
+
or the remote is unreachable, the verified ledger stops and the gate falls back to local (no error).
|
|
37
37
|
|
|
38
38
|
> **GitLab read parity (GAP-6).** `readPrGitLab` reads approvals (`approved_by[]`) and discussions but
|
|
39
39
|
> does **not** map a "Request changes" reviewer state to `CHANGES_REQUESTED` — on GitLab the blocking
|
|
@@ -41,70 +41,81 @@ or the remote is unreachable, the bridge stops and the gate falls back to file-o
|
|
|
41
41
|
> reviewer state. (GitHub maps both.) If you need GitLab "Request changes" honored, read
|
|
42
42
|
> `reviewers[].state` from the MR and map it to `CHANGES_REQUESTED`.
|
|
43
43
|
|
|
44
|
-
## Open recipes (
|
|
44
|
+
## Open recipes (no reviewers requested — used by `yad gate open` / `yad open-pr`)
|
|
45
45
|
|
|
46
|
-
Opening the review PR/MR
|
|
47
|
-
|
|
46
|
+
Opening the review PR/MR **requests no reviewers**. yadflow keeps no list of people to pick them from
|
|
47
|
+
(E62), so the command prints `no reviewers were requested — ask them on the PR itself`, and the author
|
|
48
|
+
asks people on the PR. (On a code-repo PR, `yad open-pr` also prints a reviewer suggestion from recent history and CODEOWNERS — E68, a hint, never a request.) `yad gate open` assigns the opener and adds the touched-domain labels (`domain:<repo>`); `yad open-pr`
|
|
49
|
+
assigns the opener and adds no labels. An agent opening a PR by hand
|
|
50
|
+
uses:
|
|
48
51
|
|
|
49
|
-
**GitHub
|
|
50
|
-
whole create):
|
|
52
|
+
**GitHub:**
|
|
51
53
|
```bash
|
|
52
54
|
gh pr create --title "review: <artifact> (<epic>)" --body <body> --base <default> --head <branch> \
|
|
53
|
-
--assignee
|
|
54
|
-
gh pr edit <n> --add-reviewer <login> # once per required reviewer
|
|
55
|
+
--assignee <login|@me> --label domain:<repo>
|
|
55
56
|
```
|
|
56
57
|
|
|
57
|
-
**GitLab
|
|
58
|
-
assign the first required reviewer and **@-mention the rest in a note** so they are still notified/routed:
|
|
58
|
+
**GitLab:**
|
|
59
59
|
```bash
|
|
60
60
|
glab mr create --title "review: <artifact> (<epic>)" --description <body> \
|
|
61
|
-
--target-branch <default> --source-branch <branch> --
|
|
62
|
-
glab mr note <iid> -m "Review requested (owner + reviewer rule): @<l2> @<l3> — please review and approve/comment on this MR (this drives the gate)."
|
|
61
|
+
--target-branch <default> --source-branch <branch> --assignee <login> --label domain:<repo> --yes
|
|
63
62
|
```
|
|
64
|
-
The read side counts a mentioned reviewer normally: their eventual **approval** still appears in
|
|
65
|
-
`…/approvals → approved_by[]`, and their **note** in `…/discussions` — so the single-reviewer-field cap
|
|
66
|
-
loses only the native "Reviewers" UI chip, not the gate routing.
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
The assignee is `@me` on GitHub (`gh` resolves it on the repo's host) and the login `glab api user`
|
|
65
|
+
reports on GitLab (none when that lookup fails). The `domain:<repo>` labels are the touched domains —
|
|
66
|
+
for `stories-review` the union of every story's `repos`, for a step tagged `contract`, `auth` or
|
|
67
|
+
`payments` the epic's `repos`, and none otherwise. They are a hint for whom to ask and add no
|
|
68
|
+
approvals.
|
|
71
69
|
|
|
72
|
-
##
|
|
70
|
+
## Who a record names
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
3. Login not in the roster → `name: <login>`, `role: reviewer`, flagged
|
|
78
|
-
`<!-- unverified login: <login> -->`. **Never** auto-promoted to owner/domain-owner.
|
|
72
|
+
The platform login that reviewed is written as `approver` / `commenter`. There is no lookup and no
|
|
73
|
+
role, and no login is flagged `unverified`: the platform's record of who approved is the evidence, and
|
|
74
|
+
repository access decides who may approve. A review with no login is not counted.
|
|
79
75
|
|
|
80
|
-
(Full detail
|
|
76
|
+
(Full detail, including older roster-era records and the `by` field: `login-roster.md`.)
|
|
81
77
|
|
|
82
78
|
## Idempotent re-sync
|
|
83
79
|
|
|
84
|
-
- Key bridge approvals on `(step, approver
|
|
85
|
-
duplicate.
|
|
80
|
+
- Key bridge approvals on `(step, approver)` — one record per person. On re-sync, **upsert** — do not
|
|
81
|
+
append a duplicate. An older record that still carries `role`/`domain` (and may name the person by
|
|
82
|
+
their old roster name) is recognised as `login-roster.md` → "Older records" describes (the roster's
|
|
83
|
+
name → login pairs first; never by list order), and replaced by one login-named record that keeps its
|
|
84
|
+
`artifactHash` and dates — unless the platform shows a newer review (a later submission time, with a
|
|
85
|
+
time on both sides, or a different PR/MR), which, as for any approval, binds to the current content.
|
|
86
|
+
Every sync write (`yad gate sync`, `yad gate ci`) also records the login on the older records the
|
|
87
|
+
roster can place for certain, on every step of the epic; only the records it cannot place still need the
|
|
88
|
+
roster on a later sync (`login-roster.md` → "Recording the login on older
|
|
89
|
+
records").
|
|
90
|
+
- A bridge approval records the platform's evidence when it is given: `approvedAt` (GitHub's submission
|
|
91
|
+
time, GitLab's `approved_at`), and on GitHub the review's `commit`, `url` and `reviewId`. They are
|
|
92
|
+
audit only — the gate decides on the login and the fingerprint. A stored `approvedAt` that is only a
|
|
93
|
+
date (every GitLab record from before this) is "time unknown": it keeps its fingerprint and takes the
|
|
94
|
+
platform's time once.
|
|
86
95
|
- **On an OPEN step**, remove any bridge approval whose platform review was dismissed/revoked: the
|
|
87
96
|
platform is the live source of truth while the review is in flight.
|
|
88
97
|
- **On a step already `done`**, the record is only added to or refreshed in place — an approval the
|
|
89
98
|
platform no longer reports is **kept**. Those approvals are the audit record of *why* the gate
|
|
90
|
-
passed;
|
|
99
|
+
passed; an approval reset, or a degraded-but-successful read would otherwise erase
|
|
91
100
|
them and leave the step `done` with zero approvals.
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
round
|
|
101
|
+
- Record synced comments as one `comments.json` record per `(step, commenter, round)` with a `count`
|
|
102
|
+
(there is no per-comment id). A new round is opened only when the commenters or their counts change;
|
|
103
|
+
an unchanged re-read rewrites the latest round in place, keeping its date. Comment rounds are recorded
|
|
104
|
+
for an **open** step only, so re-visiting a merged review does not append a new round per pass.
|
|
95
105
|
- Update the step's `hub-prs.json` `lastSyncedAt` when the sync **learned something** — every sync on
|
|
96
106
|
an open step, and on a closed one only when the approval record actually changed (a re-opened review
|
|
97
107
|
that was re-approved). An identical re-sync leaves it alone, so the ledger does not churn.
|
|
98
|
-
- **Write the ledgers in a canonical order.** `approvals.json`, `comments.json` and
|
|
108
|
+
- **Write the ledgers in a canonical order.** `approvals.json`, `comments.json` and the PR ledger
|
|
109
|
+
(`product-prs.json` + `hub-prs.json`, written together) are
|
|
99
110
|
sorted on write, so the bytes are a function of the record *set* and never of which step was synced
|
|
100
111
|
last. Without this the upsert above — which drops the records it refreshes and re-appends them at the
|
|
101
112
|
tail — makes the file depend on sync order, and the wired sweep (one `gate ci --branch <ref>
|
|
102
113
|
--merged` per merged PR/MR, every 15 minutes) walks a rotation:
|
|
103
114
|
`[A,B,C] → sync A → [B,C,A] → sync B → [C,A,B] → sync C → [A,B,C]`. Every hop is a non-empty diff, so
|
|
104
115
|
every hop commits and pushes, and the pass ends where it began — an unbounded commit loop with zero
|
|
105
|
-
semantic change. That is issue #163: ~1,800 bot commits/day on the
|
|
116
|
+
semantic change. That is issue #163: ~1,800 bot commits/day on the Product that reported it. Sorting is
|
|
106
117
|
what makes the "nothing staged → nothing to commit" guard in `gate ci` actually hold.
|
|
107
|
-
- Running `sync` twice with no platform change is a no-op on the ledger — byte-identical, including
|
|
118
|
+
- Running `sync` twice on the same day with no platform change is a no-op on the ledger — byte-identical, including
|
|
108
119
|
`comments.json` and the dated `reviews/*.md` side files.
|
|
109
120
|
|
|
110
121
|
**Upgrading past #163.** The first sweep on a yadflow carrying the fix writes one canonicalizing
|
|
@@ -117,22 +128,24 @@ deliberate act: `yad update` (which re-stamps `.sdlc/cli-version.json`), or a `g
|
|
|
117
128
|
|
|
118
129
|
## Contract re-lock invalidates prior platform approvals too
|
|
119
130
|
|
|
120
|
-
For the **architecture+contract** review,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
(
|
|
131
|
+
For the **architecture+contract** review, an approval's `artifactHash` is the contract-surface hash.
|
|
132
|
+
When the surface changes and is re-locked, a bridge approval recorded against the old hash no longer
|
|
133
|
+
matches, so the gate stops counting it (it shows as stale in `yad gate status`) — exactly as for a
|
|
134
|
+
manual approval. Nothing is deleted by date and no comment is posted on the PR; the reviewer approves
|
|
135
|
+
again, and a genuinely newer review (a later submission time, or a new PR/MR) binds to the new hash. The count
|
|
136
|
+
(`risk_tags: ["contract"]` → 3 approvers, capped at the active people less one and reported; base 1
|
|
137
|
+
enforced, risk step advisory) is unchanged.
|
|
125
138
|
|
|
126
139
|
## CHANGES_REQUESTED & unresolved threads hold the gate
|
|
127
140
|
|
|
128
141
|
`CHANGES_REQUESTED` and any **unresolved review thread** are recorded as comments and surfaced as
|
|
129
142
|
**blocking**. Under the PR-driven gate they actively hold the step `in_review`: the predicate does not
|
|
130
|
-
pass while any thread is unresolved, even if the approval
|
|
143
|
+
pass while any thread is unresolved, even if the approval count is met. The owner addresses the
|
|
131
144
|
comments, replies, the reviewer **resolves** their thread, then `sync` runs again.
|
|
132
145
|
|
|
133
146
|
## Merge advances; an artifact change revokes approvals
|
|
134
147
|
|
|
135
|
-
- **Merge → advance.** When the
|
|
148
|
+
- **Merge → advance.** When the base count is met (1 distinct approver; the capped risk step is reported, not enforced; solo mode waives it), every thread is resolved, **and the review
|
|
136
149
|
PR/MR is merged**, `sync` marks the step `done` and unblocks the next step. The merge is the human
|
|
137
150
|
approval act — there is no separate machine advance. (`yad gate sync` performs this deterministically.)
|
|
138
151
|
- **Revoke on artifact change (checked at merge).** Path B reconciles at merge, so an approval given
|
|
@@ -151,27 +164,30 @@ comments, replies, the reviewer **resolves** their thread, then `sync` runs agai
|
|
|
151
164
|
advancing on approvals whose freshness cannot be proven (re-run `yad gate sync` to recover).
|
|
152
165
|
CHANGES_REQUESTED is still honored, so a degraded read can only ever *hold* the gate.
|
|
153
166
|
The `artifactHash` stamp still binds architecture approvals to the locked contract surface (see
|
|
154
|
-
"Contract re-lock" above).
|
|
155
|
-
|
|
167
|
+
"Contract re-lock" above). For every other artifact it covers the file WITHOUT its frontmatter
|
|
168
|
+
`status:` line, so the merge run's own `draft` → `approved` flip (and Build's `in-build` / `shipped`)
|
|
169
|
+
never revokes the approvals it just recorded (shape 9). An approval recorded before shape 9 hashed the
|
|
170
|
+
whole file, and is still read.
|
|
171
|
+
- **Known limitation — protect the Product default branch.** The advance hashes the artifact from the
|
|
156
172
|
default branch as it stands when CI runs, while approvals are SHA-bound to the reviewed PR/MR head.
|
|
157
173
|
Those can differ if the artifact changes on the **base** outside this review while the PR/MR is open
|
|
158
174
|
(the merge then integrates a change the reviewers never saw) or if a later out-of-band commit edits
|
|
159
175
|
the merged artifact before a delayed reconcile advances it. In both cases each approval's commit
|
|
160
176
|
still equals the reviewed head, so the SHA check passes, yet the live content was not reviewed. Close
|
|
161
|
-
it operationally: **require branch protection on the
|
|
177
|
+
it operationally: **require branch protection on the Product default branch so `epics/**` artifacts can
|
|
162
178
|
only change through their own review PR/MR** (one open review per artifact) — then the base copy of an
|
|
163
179
|
artifact cannot move while its review is open, so the merged/live content always equals the reviewed
|
|
164
180
|
content. (The complete in-code fix would hash the artifact at the reviewed PR-head revision before
|
|
165
181
|
advancing; deferred in favor of the branch-protection mitigation.)
|
|
166
182
|
|
|
167
|
-
## Event-driven sync (
|
|
183
|
+
## Event-driven sync (Product CI) — Path B
|
|
168
184
|
|
|
169
|
-
The `wire` action (SKILL.md Step 4) installs CI on the
|
|
185
|
+
The `wire` action (SKILL.md Step 4) installs CI on the Product so a **merge** drives `yad gate ci` —
|
|
170
186
|
**CI is the SOLE writer of the ledger, and it writes only at merge, only to the default branch.**
|
|
171
187
|
During review CI writes nothing: the platform PR/MR is the source of truth (native approvals +
|
|
172
188
|
threads). The CLI is self-sufficient at merge: it derives the epic + artifact from the
|
|
173
189
|
`review/EP-<slug>/<artifact-base>` head branch, takes the PR/MR number from the event (GitHub) or
|
|
174
|
-
resolves it from the platform (GitLab), upserts the
|
|
190
|
+
resolves it from the platform (GitLab), upserts the PR-ledger entry itself (under both names), and **re-reads
|
|
175
191
|
approvals fresh from the platform** — so no ledger needs to be pre-seeded on the branch. (It only
|
|
176
192
|
*advances* a chain, though: it cannot **create** one. A brand-new epic's seed therefore travels the
|
|
177
193
|
other way — up through its first review PR/MR; see "the seed of a new epic" below.)
|
|
@@ -183,28 +199,38 @@ other way — up through its first review PR/MR; see "the seed of a new epic" be
|
|
|
183
199
|
| Schedule (`*/15`) | reconcile | Safety net: enumerate recently-**merged** `review/EP-*` PRs/MRs via the API and advance any not yet `done` (idempotent). Recovers a merge whose merge-time run failed transiently, and on GitLab also picks up a squash merge whose commit dropped the branch name (and a bare approval — GitLab fires no pipeline on one). **GitHub:** a scheduled workflow, automatic once committed. **GitLab:** a pipeline schedule with `SDLC_GATE_SYNC=true` (one-time setup) |
|
|
184
200
|
|
|
185
201
|
**Which yadflow the wired job runs.** Both fragments resolve the version from a `YAD_VERSION` variable
|
|
186
|
-
and fall back to `
|
|
202
|
+
and fall back to the major the fragment ships with — `YAD_MAJOR` in its `yad-pin` block (`4` in this release):
|
|
187
203
|
|
|
188
204
|
| # | Source | Set it in |
|
|
189
205
|
|---|---|---|
|
|
190
206
|
| 1 | `YAD_VERSION` — used **verbatim**, the operator's override | GitHub: Settings → Secrets and variables → Actions → **Variables**. GitLab: Settings → CI/CD → **Variables** (beside `SDLC_GATE_TOKEN`) |
|
|
191
|
-
| 2 | `.sdlc/hub.json` → `gate_sync_version` — this
|
|
192
|
-
| 3 | `.sdlc/cli-version.json` → `version` — the yadflow that last wired the
|
|
193
|
-
| 4 | `
|
|
207
|
+
| 2 | `.sdlc/hub.json` → `gate_sync_version` — this Product's committed pin | edit `hub.json`, commit it |
|
|
208
|
+
| 3 | `.sdlc/cli-version.json` → `version` — the yadflow that last wired the Product | `yad update` re-stamps it |
|
|
209
|
+
| 4 | `$YAD_MAJOR` — floating major, only when nothing above resolves. While only prereleases of that major exist (`4.0.0-next.N`) npm cannot resolve a bare major, so the job says so on stderr first; a Product wired by that release always has a stamp that resolves | — |
|
|
194
210
|
|
|
195
|
-
Sources 2 and 3 are **validated** before use: an exact
|
|
196
|
-
(`
|
|
211
|
+
Sources 2 and 3 are **validated** before use: an exact release token of the fragment's own major, prereleases
|
|
212
|
+
included (`4.16.0-rc.1` is a legitimate pin; `latest`, a bare `4` and a `3.x` are not). A `.sdlc/cli-version.json`
|
|
197
213
|
written by a long-untouched project can still say something like `1.0.2`, a version with no `yad gate ci`
|
|
198
214
|
in it at all, and the value is interpolated into `npx -p "yadflow@$V"` on a runner holding a push token —
|
|
199
215
|
so anything that is not an exact release of this major is skipped, loudly, in favour of the next source.
|
|
200
216
|
`YAD_VERSION` is exempt: it is a human's deliberate act, and it is the only way to cross a major.
|
|
201
217
|
|
|
218
|
+
**A new major moves both files at once.** `yad update` rewrites the fragment from the release's template
|
|
219
|
+
and re-stamps `.sdlc/cli-version.json` in the same run, so a Product that updates to 4.x gets a fragment
|
|
220
|
+
with `YAD_MAJOR=4` and a `4.x` stamp together. A `gate_sync_version` pin in `hub.json` from the old major
|
|
221
|
+
is then SKIPPED (the job logs `ignoring pin … not an exact 4.x release`) — update it or remove it. A
|
|
222
|
+
fragment the team EDITED is kept by `yad update` while the stamp still moves, so an edited fragment on
|
|
223
|
+
the old major would skip the new stamp; `yad update` names that case, and `--overwrite-local` replaces
|
|
224
|
+
the fragment (saving the edit beside it). The
|
|
225
|
+
fragment's major is held to the major a release publishes by `scripts/pin-major-check.sh`, a step of the
|
|
226
|
+
release check.
|
|
227
|
+
|
|
202
228
|
**Why this is no longer a floating major.** It used to be, on the argument that a published fix should
|
|
203
229
|
reach a scheduled job with nobody in the loop — this page's own issue #163 as the example. The same
|
|
204
230
|
mechanism is how #163's churn *arrived*: the CI fragment ran `yadflow@3`, so 3.13.1 rolled onto every
|
|
205
|
-
wired
|
|
231
|
+
wired Product automatically and took the reporting one from 20 to 96 churn commits an hour, with nobody
|
|
206
232
|
deciding to upgrade. Issue #163's fourth suggested fix was to stop that. The trade-off is real and cuts
|
|
207
|
-
both ways — a
|
|
233
|
+
both ways — a Product is no longer carried onto a fix for free, so **if the resolved pin is older than
|
|
208
234
|
3.15.3, run `yad update` or disable the schedule** (`yad doctor` flags a stale stamp).
|
|
209
235
|
|
|
210
236
|
The pin is **never stamped into the wired file**: `yad` owns that file and `yad check --fix` rewrites it
|
|
@@ -219,18 +245,18 @@ each `artifactHash` against the merged content, so revoke-on-change still holds.
|
|
|
219
245
|
commit — the advance plus the `draft → approved` status flip — lands on the **default branch** with
|
|
220
246
|
`[skip ci]`.
|
|
221
247
|
|
|
222
|
-
**The ledger is CI-owned (
|
|
223
|
-
check (yad-checks) FAILs any commit on a review PR that touches `.sdlc/{state,approvals,comments,hub-prs}
|
|
224
|
-
.json` or `reviews/*.md` (`.sdlc/contract-lock.json` is artifact-side and allowed). "
|
|
225
|
-
means the same thing it means everywhere else — a `platform` **and** the
|
|
226
|
-
predicate. The gate used to enable itself on the flag alone, which let a platform-less
|
|
248
|
+
**The ledger is CI-owned (verified mode only).** Humans never commit gate-state files: the `ledger-guard`
|
|
249
|
+
check (yad-checks) FAILs any commit on a review PR that touches `.sdlc/{state,approvals,comments,product-prs,hub-prs}
|
|
250
|
+
.json` or `reviews/*.md` (`.sdlc/contract-lock.json` is artifact-side and allowed). "verified mode" there
|
|
251
|
+
means the same thing it means everywhere else — a `platform` **and** the verified ledger flag, `isVerifiedLedger`'s
|
|
252
|
+
predicate. The gate used to enable itself on the flag alone, which let a platform-less Product reject the
|
|
227
253
|
human's ledger write while the CLI still expected one (#186). Under Path B **no
|
|
228
254
|
CI commit lands in a review PR at all**, so the only ledger change the guard can see there is a human
|
|
229
255
|
edit — which it rejects, with one carve-out for a new epic's seed (below). (The `verified-commits`
|
|
230
|
-
gate still vets every commit's signature
|
|
231
|
-
|
|
232
|
-
opens the PR only; local `yad gate sync` is advisory in
|
|
233
|
-
everyone `git checkout <default> && git pull`. (
|
|
256
|
+
gate still vets every commit's platform-Verified signature — there is no author allowlist, and so no
|
|
257
|
+
gate-bot exemption either.) `yad gate open`
|
|
258
|
+
opens the PR only; local `yad gate sync` is advisory in verified mode (writes nothing). After a merge,
|
|
259
|
+
everyone `git checkout <default> && git pull`. (with a local ledger, humans own the ledger locally and
|
|
234
260
|
these guards are no-ops.)
|
|
235
261
|
|
|
236
262
|
**The one sanctioned human ledger write *in a review PR*: the seed of a new epic.** `gate ci` only
|
|
@@ -248,8 +274,8 @@ direct push to a protected default branch is needed.
|
|
|
248
274
|
**The one sanctioned human ledger write *on the default branch*: `yad gate repair`.** It heals a `YAD-STATE-005` chain (an
|
|
249
275
|
authoring step stranded behind a review gate that already advanced) by writing `state.json` alone. This
|
|
250
276
|
is not a `ledger-guard` gap: the repair commits to the **default branch**, where `ledger-guard` — which
|
|
251
|
-
only inspects review PRs — never runs, and where the `yad-update-guard` (platform-Verified signature
|
|
252
|
-
|
|
277
|
+
only inspects review PRs — never runs, and where the `yad-update-guard` (platform-Verified signature)
|
|
278
|
+
vets it instead, exactly as it does for `yad checkpoint` and `yad update`.
|
|
253
279
|
The command refuses to commit off the default branch unless `--allow-branch` is passed.
|
|
254
280
|
|
|
255
281
|
**Loop prevention & races.** The only ledger commit lands on the **default branch** at merge, which
|
|
@@ -269,12 +295,13 @@ push retries with a rebase.
|
|
|
269
295
|
- Protected default branch (GitHub): the merge advance needs to push it — prefer a ruleset bypass for
|
|
270
296
|
Actions, else a fine-grained PAT as `SDLC_GATE_TOKEN` on the mergesync checkout.
|
|
271
297
|
|
|
272
|
-
**Manual sync & recovery.** In
|
|
273
|
-
predicate
|
|
298
|
+
**Manual sync & recovery.** In verified mode `yad gate sync` is **advisory** (read-only) — it prints the
|
|
299
|
+
predicate (and any `! may not be met:` / `! if the risk step were enforced:` warning, E73) but writes
|
|
300
|
+
nothing, so it is **not** a recovery path when CI fails. If a merge-time run fails
|
|
274
301
|
(can't push, API hiccup), recovery is the scheduled **reconcile** job (automatic; it re-advances merged
|
|
275
302
|
reviews not yet `done`). To force it immediately, a maintainer runs the same command CI runs, locally on
|
|
276
303
|
the default branch: `yad gate ci --branch <review-branch> --pr <n> --merged` (this writes + pushes,
|
|
277
|
-
unlike advisory `yad gate sync`).
|
|
304
|
+
unlike advisory `yad gate sync`). Local mode (no platform) keeps `yad gate sync` as the local writer.
|
|
278
305
|
The file ledger is still the source of truth.
|
|
279
306
|
|
|
280
307
|
Because CI records the `hub-prs.json` pointer only at merge, a review the ledger has never seen still
|
|
@@ -283,17 +310,17 @@ recorded pointer, `yad gate sync <epic> <artifact>` resolves the PR/MR from the
|
|
|
283
310
|
(`review/<epic>/<artifact>`) itself, and `--pr <n>` names it outright:
|
|
284
311
|
|
|
285
312
|
```bash
|
|
286
|
-
yad gate sync EP-x architecture.md --pr 42 # advisory in
|
|
313
|
+
yad gate sync EP-x architecture.md --pr 42 # advisory in verified mode; the writer with a local ledger
|
|
287
314
|
```
|
|
288
315
|
|
|
289
316
|
An explicit `--pr` also **overrides** a recorded pointer, since a re-opened review is a new PR the
|
|
290
317
|
ledger has not seen. Before its reviewers are bound to the artifact's hash, the number is confirmed to
|
|
291
318
|
be the PR for that artifact's review branch — a mismatch is refused, and a platform that cannot answer
|
|
292
|
-
warns and proceeds. The
|
|
319
|
+
warns and proceeds. The verified ledger rule is unchanged: the resolved pointer is adopted into the ledger only
|
|
293
320
|
on the writer path, so a human never leaves a gate-state file in their working tree for `ledger-guard`
|
|
294
321
|
to reject.
|
|
295
322
|
|
|
296
|
-
**In
|
|
323
|
+
**In verified mode `gate sync` stays advisory even with `--pr`** — it prints the predicate and writes
|
|
297
324
|
nothing, because CI owns the ledger. The recovery that actually writes is the command CI itself runs,
|
|
298
325
|
on the default branch: `yad gate ci --branch <review-branch> --pr <n> --merged`.
|
|
299
326
|
|
|
@@ -302,7 +329,7 @@ sweep's recent-MR window overlaps whatever the merge-push pipeline is handling
|
|
|
302
329
|
one at a time (`concurrency: yad-gate-mergesync` on GitHub, `resource_group: yad-gate-mergesync` on
|
|
303
330
|
GitLab). Without it both runs produce the same advance, one pushes, and the other rebases onto it and
|
|
304
331
|
lands a duplicate `chore(gate): advance …` commit under a different SHA — unreviewed churn, since
|
|
305
|
-
those commits carry `[skip ci]` and go straight to the default branch. An already-wired GitLab
|
|
332
|
+
those commits carry `[skip ci]` and go straight to the default branch. An already-wired GitLab Product
|
|
306
333
|
picks the `resource_group` up on the next `yad update` / `yad check --fix`.
|
|
307
334
|
|
|
308
335
|
**`yad gate open` does not create or push the review branch.** It opens a PR/MR *against*
|
|
@@ -313,7 +340,7 @@ rather than failing inside `gh`/`glab`; an origin that cannot be reached at all
|
|
|
313
340
|
|
|
314
341
|
### Manual end-to-end verification (GitHub)
|
|
315
342
|
|
|
316
|
-
1. On a scratch
|
|
343
|
+
1. On a scratch Product: `yad setup` (platform github; a second account with write access to review) → `yad check --fix`
|
|
317
344
|
installs `.github/workflows/yad-gate-sync.yml`; commit + push it.
|
|
318
345
|
2. Author an epic → `yad gate open EP-x epic.md` → the review PR opens. CI writes nothing yet — review
|
|
319
346
|
state lives on the platform.
|