yadflow 3.19.0-next.2 → 4.0.0-next.3
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 +371 -0
- package/README.md +160 -22
- package/bin/commands.mjs +46 -0
- package/bin/yad.mjs +506 -117
- package/cli/artifact-status.mjs +30 -11
- package/cli/capture.mjs +417 -0
- package/cli/checkpoint.mjs +47 -27
- package/cli/claims.mjs +165 -0
- package/cli/codeowners-command.mjs +170 -0
- package/cli/codeowners.mjs +397 -0
- package/cli/commit.mjs +13 -9
- package/cli/dial.mjs +183 -0
- package/cli/docs.mjs +88 -32
- package/cli/doctor.mjs +1682 -90
- package/cli/epic-state.mjs +3460 -211
- package/cli/epic.mjs +506 -0
- package/cli/errors.mjs +3 -0
- package/cli/fold.mjs +247 -0
- package/cli/gate-local.mjs +296 -0
- package/cli/gate.mjs +977 -183
- package/cli/history.mjs +556 -0
- package/cli/hook.mjs +264 -53
- package/cli/hubcommit.mjs +9 -19
- package/cli/index-command.mjs +87 -0
- package/cli/ledger.mjs +54 -4
- package/cli/lib.mjs +161 -9
- package/cli/manifest.mjs +376 -38
- package/cli/migrate.mjs +683 -52
- package/cli/mode.mjs +170 -0
- package/cli/next.mjs +365 -86
- package/cli/openpr.mjs +193 -34
- package/cli/owners.mjs +266 -0
- package/cli/people.mjs +657 -0
- package/cli/plan.mjs +466 -125
- package/cli/platform.mjs +109 -128
- package/cli/product-index.mjs +287 -0
- package/cli/protection.mjs +706 -0
- package/cli/reconcile.mjs +40 -12
- package/cli/repo-publish.mjs +24 -26
- package/cli/repo.mjs +23 -14
- package/cli/report.mjs +24 -14
- package/cli/review.mjs +19 -22
- package/cli/riskmap-command.mjs +289 -0
- package/cli/riskmap.mjs +373 -0
- package/cli/setup.mjs +127 -279
- package/cli/ship.mjs +6 -5
- package/cli/skill.mjs +180 -0
- package/cli/skip.mjs +211 -30
- package/cli/thread.mjs +41 -16
- package/cli/tidy.mjs +18 -18
- package/cli/update-commit.mjs +21 -21
- package/cli/usage.mjs +115 -109
- package/package.json +3 -3
- package/skills/sdlc/config.yaml +149 -70
- package/skills/sdlc/module-help.csv +24 -24
- package/skills/yad-analysis/SKILL.md +121 -59
- package/skills/yad-architecture/SKILL.md +30 -18
- package/skills/yad-architecture/references/contract-format.md +8 -5
- package/skills/yad-backfill/SKILL.md +10 -4
- package/skills/yad-backfill/references/backfill.md +8 -1
- package/skills/yad-backfill/templates/checks/backfill-check.sh +10 -2
- package/skills/yad-change/SKILL.md +119 -42
- package/skills/yad-change/references/triage.md +42 -27
- package/skills/yad-checks/SKILL.md +68 -35
- package/skills/yad-checks/references/check-gates.md +310 -85
- package/skills/yad-checks/templates/checks/commit-message.sh +17 -3
- package/skills/yad-checks/templates/checks/contract-check.sh +130 -9
- package/skills/yad-checks/templates/checks/epic-open.sh +2 -2
- package/skills/yad-checks/templates/checks/ledger-guard.sh +55 -8
- package/skills/yad-checks/templates/checks/lineage-check.sh +23 -9
- package/skills/yad-checks/templates/checks/reconcile-debt-check.sh +1 -1
- 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 +13 -2
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +5 -5
- package/skills/yad-checks/templates/github/yad-update-guard.yml +7 -5
- package/skills/yad-checks/templates/github/yad-verified-commits.yml +4 -4
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +10 -1
- 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 +3 -3
- package/skills/yad-checks/templates/hooks/ledger-guard-cursor.sh +91 -0
- package/skills/yad-checks/templates/hooks/ledger-guard.sh +37 -6
- package/skills/yad-checks/templates/hooks/yad-capture.sh +53 -0
- package/skills/yad-commit/SKILL.md +4 -4
- package/skills/yad-connect-design/SKILL.md +5 -5
- 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 +11 -11
- 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 +66 -45
- package/skills/yad-connect-repos/references/code-context.md +4 -4
- package/skills/yad-connect-repos/references/hub-config.md +47 -51
- package/skills/yad-connect-repos/references/repos-registry.md +8 -7
- package/skills/yad-connect-repos/references/risk-map.md +81 -0
- package/skills/yad-connect-testing/SKILL.md +5 -5
- 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 +7 -7
- package/skills/yad-discovery/SKILL.md +131 -94
- package/skills/yad-discovery/references/discovery-schema.md +22 -6
- package/skills/yad-discovery/references/foundation-schema.md +374 -0
- package/skills/yad-docs/SKILL.md +13 -8
- 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 +30 -15
- package/skills/yad-docs-overview/references/pipeline-model.md +33 -20
- 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 +81 -17
- package/skills/yad-engineer-review/references/ship-and-record.md +17 -8
- package/skills/yad-epic/SKILL.md +173 -92
- package/skills/yad-epic/references/state-schema.md +610 -94
- package/skills/yad-hub-bridge/SKILL.md +57 -38
- package/skills/yad-hub-bridge/references/bridge.md +94 -67
- 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 +21 -11
- package/skills/yad-implement/SKILL.md +27 -13
- 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 +61 -26
- package/skills/yad-pair-review/SKILL.md +7 -5
- package/skills/yad-pair-review/references/session-state.md +1 -1
- package/skills/yad-pr-template/SKILL.md +47 -26
- package/skills/yad-pr-template/references/risk-routing.md +97 -24
- package/skills/yad-pr-template/templates/checks/pr-template.sh +34 -11
- package/skills/yad-pr-template/templates/checks/pr-title.sh +32 -9
- 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 +14 -13
- package/skills/yad-pr-template/templates/hub/gitlab/merge_request_templates/Default.md +14 -12
- package/skills/yad-reconcile/SKILL.md +3 -3
- package/skills/yad-report/SKILL.md +5 -5
- package/skills/yad-review-companion/SKILL.md +6 -3
- package/skills/yad-review-gate/SKILL.md +203 -93
- package/skills/yad-review-gate/references/gating.md +232 -54
- package/skills/yad-run/SKILL.md +81 -51
- package/skills/yad-run/references/run-loop.md +61 -39
- package/skills/yad-ship/SKILL.md +15 -11
- package/skills/yad-spec/SKILL.md +21 -6
- package/skills/yad-spec/references/spec-handoff.md +17 -5
- package/skills/yad-status/SKILL.md +115 -50
- package/skills/yad-stories/SKILL.md +37 -21
- package/skills/yad-stories/references/story-schema.md +7 -6
- package/skills/yad-stub/SKILL.md +58 -47
- package/skills/yad-sync-repos/SKILL.md +2 -2
- package/skills/yad-test-cases/SKILL.md +32 -23
- package/skills/yad-test-cases/references/test-cases-schema.md +7 -4
- package/skills/yad-timeline/SKILL.md +8 -7
- package/skills/yad-ui/SKILL.md +41 -18
- package/cli/roster.mjs +0 -164
- package/skills/sdlc/install.sh +0 -68
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-hub-bridge
|
|
3
|
-
description: 'The templated PR/MR bridge for the Shape review gate. When the
|
|
3
|
+
description: 'The templated PR/MR bridge for the Shape review gate. When the Product has a platform (.sdlc/hub.json), it opens a review PR/MR on the Product for an authored artifact (the optional analysis / epic / architecture+contract / ui-design / stories / test-cases), applies the touched-domain labels (it requests no reviewers), and provides the read-only gh/glab recipes that yad-review-gate uses to pull platform comments + approvals back into the file ledger. Can also wire merge-time sync on the Product: a CI workflow that runs `yad gate ci` when a human merges a review PR/MR — CI is the sole ledger writer and writes only at merge, on the default branch (during review the platform PR/MR is the source of truth; CI never touches the review branch). Local-user auth only — no stored tokens. The file ledger stays the source of truth; degrades to the local gate when there is no platform / no CLI. Use when the user says "open the review PR", "route the review", "wire the gate sync", or it is invoked by yad-review-gate open/sync.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# SDLC — Hub Review Bridge (the templated PR/MR bridge)
|
|
7
7
|
|
|
8
|
-
**Goal:** Run the Shape review/comment/approval cycle through a **real PR/MR on the
|
|
8
|
+
**Goal:** Run the Shape review/comment/approval cycle through a **real PR/MR on the Product**,
|
|
9
9
|
without changing the gate's predicate or making the file ledger optional. The verified ledger is an **alternate
|
|
10
10
|
input path** into `yad-review-gate`: it opens a review PR for an artifact, reviewers approve/comment on
|
|
11
11
|
the platform with **their own** `gh`/`glab` auth, and the gate's `sync` action (which calls this skill's
|
|
@@ -17,12 +17,13 @@ keeps platform mechanics out of the gate). `yad-review-gate` *calls* it; it neve
|
|
|
17
17
|
|
|
18
18
|
## Conventions
|
|
19
19
|
|
|
20
|
-
- `{project-root}` is the **
|
|
21
|
-
(`config.yaml` `hub.config
|
|
22
|
-
- Per-step review-PR record: `epics/EP-<slug>/.sdlc/
|
|
20
|
+
- `{project-root}` is the **Product**. The Product platform lives in `.sdlc/hub.json` — it holds no
|
|
21
|
+
list of people (`config.yaml` `product.config` (older projects: `hub.config`); schema in `../yad-connect-repos/references/hub-config.md`).
|
|
22
|
+
- Per-step review-PR record: `epics/EP-<slug>/.sdlc/product-prs.json`, written together with its
|
|
23
|
+
older name `hub-prs.json` (`config.yaml` `product.pr_ledger` (older projects: `hub.pr_ledger`)) — a
|
|
23
24
|
sibling ledger to `approvals.json`, so `state.json`'s locked step shape is untouched.
|
|
24
|
-
- The review-PR body is the
|
|
25
|
-
- Branch per artifact: `review/EP-<slug>/<artifact-base>` (`config.yaml` `hub.artifact_branch`).
|
|
25
|
+
- The review-PR body is the Product template from `yad-pr-template` (`templates/hub/<platform>/…`).
|
|
26
|
+
- Branch per artifact: `review/EP-<slug>/<artifact-base>` (`config.yaml` `product.artifact_branch` (older projects: `hub.artifact_branch`)).
|
|
26
27
|
- **Local-user auth only; store no tokens.** Use the user's own `gh`/`glab`. If neither is installed/
|
|
27
28
|
authenticated, STOP this path and tell the gate to fall back to local — never embed a credential.
|
|
28
29
|
|
|
@@ -35,31 +36,40 @@ keeps platform mechanics out of the gate). `yad-review-gate` *calls* it; it neve
|
|
|
35
36
|
|
|
36
37
|
## Preconditions (the verified ledger runs only when all hold)
|
|
37
38
|
|
|
38
|
-
`.sdlc/hub.json` exists with a non-null `platform`, `ledger: "verified"` (or, before `yad migrate`, `bridge_enabled: true`), `config.yaml` `
|
|
39
|
-
true
|
|
39
|
+
`.sdlc/hub.json` exists with a non-null `platform`, `ledger: "verified"` (or, before `yad migrate`, `bridge_enabled: true`), `config.yaml` `product.bridge: true`
|
|
40
|
+
(older projects: `hub.bridge: true`), and `gh` (GitHub) / `glab` (GitLab) installed **and authenticated**. If any fails, report that the
|
|
40
41
|
gate proceeds **local** (no error) and stop.
|
|
41
42
|
|
|
42
43
|
## On Activation
|
|
43
44
|
|
|
44
45
|
### Step 1 — Resolve platform + routing
|
|
45
|
-
Read `.sdlc/hub.json` for the platform
|
|
46
|
-
matching `review+approve` step's `risk_tags` from `.sdlc/state.json`. Compute the
|
|
47
|
-
with `route` (below) — the same rule `yad-review-gate` enforces: base
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
Read `.sdlc/hub.json` for the platform, `epics/<epic>/epic.md` for `repos` + `owner` (the artifact's
|
|
47
|
+
author), and the matching `review+approve` step's `risk_tags` from `.sdlc/state.json`. Compute the
|
|
48
|
+
**count** with `route` (below) — the same rule `yad-review-gate` enforces: `needed = base 1 + risk step`
|
|
49
|
+
distinct approvers, where `contract` adds 2 and `auth`/`payments` add 1 (the highest tag, never the sum).
|
|
50
|
+
Only the base (1 distinct approver, who should not be the author) holds the gate; the risk step is
|
|
51
|
+
advisory. The gate caps the count at the active people less one and prints it (E72), but does
|
|
52
|
+
not enforce it yet; when it cannot count the people — as in Product CI, which checks out only the hub —
|
|
53
|
+
no cap is shown. Compute the **touched domains** too: the union of story `repos` for
|
|
54
|
+
`stories-review`, the epic's `repos` for a step tagged `contract`, `auth` or `payments`, none otherwise.
|
|
55
|
+
They become `domain:<repo>` labels and
|
|
56
|
+
are a hint for whom to ask. There is no roster to turn them into people.
|
|
51
57
|
|
|
52
58
|
### Step 2 — `open` (create the review PR/MR)
|
|
53
|
-
1. From the
|
|
54
|
-
(and, for architecture, `contract.md` + `.sdlc/contract-lock.json`) is committed on it
|
|
59
|
+
1. From the Product default branch, create `review/EP-<slug>/<artifact-base>` and ensure the artifact file
|
|
60
|
+
(and, for architecture, `contract.md` + `.sdlc/contract-lock.json`) is committed on it — by the step's
|
|
61
|
+
`yad fold` commit (E44); `yad open-pr` warns when the step's files have changes no fold has committed. Push as the
|
|
55
62
|
local user. **First gate of a new epic:** cut the review branch from the **authoring** branch
|
|
56
63
|
(`epic/…`, `change/…`, `analysis/…`, `discovery/…`) instead, so it carries the `.sdlc/` **seed** —
|
|
57
64
|
that PR/MR is the only way a new epic's ledger reaches the default branch, and `ledger-guard`
|
|
58
65
|
exempts it (see step 4).
|
|
59
|
-
2. Open the PR/MR with `gh`/`glab` using the
|
|
66
|
+
2. Open the PR/MR with `gh`/`glab` using the Product body template (`yad-pr-template` `templates/hub/…`),
|
|
60
67
|
filled with the epic, artifact, gate step, owner, `epic.repos`, and the step's risk tags.
|
|
61
|
-
3. **Request
|
|
62
|
-
|
|
68
|
+
3. **Request no reviewers.** Assign the person opening it — `@me` on GitHub (`gh` resolves it), the
|
|
69
|
+
login `glab api user` reports on GitLab (no assignee when that lookup fails) — and add a
|
|
70
|
+
`domain:<repo>` label per touched repo. `yad gate open` prints
|
|
71
|
+
`no reviewers were requested — ask them on the PR itself`; the author asks people on the PR. (A Shape review PR gets no
|
|
72
|
+
reviewer suggestion: E68's suggestion reads a code change, so only `yad open-pr` (and `yad ship`) on a code repo prints it.)
|
|
63
73
|
4. **Do not write the ledger.** CI is the sole writer and writes only at merge. During review nothing
|
|
64
74
|
is recorded in the ledger — the platform PR/MR holds the review state (native approvals + threads).
|
|
65
75
|
At merge, CI records the `hub-prs.json` entry (in the shape below) and advances on the default
|
|
@@ -68,22 +78,27 @@ each required domain-owner to a platform `login` via the roster (a roster `name`
|
|
|
68
78
|
{ "step": "<review step id>", "artifact": "<artifact>", "platform": "github|gitlab",
|
|
69
79
|
"number": <n>, "url": "<pr/mr url>", "branch": "review/EP-<slug>/<artifact-base>", "lastSyncedAt": null }
|
|
70
80
|
```
|
|
71
|
-
A human commit touching the gate-state files (`.sdlc/{state,approvals,comments,hub-prs}.json` or
|
|
81
|
+
A human commit touching the gate-state files (`.sdlc/{state,approvals,comments,product-prs,hub-prs}.json` or
|
|
72
82
|
`reviews/*.md`; `.sdlc/contract-lock.json` is artifact-side and allowed) on a review PR is rejected
|
|
73
83
|
by the `ledger-guard` check. (The `yad gate open` CLI behaves the same: in verified mode it opens the
|
|
74
84
|
PR only and writes no ledger.) The **one** exception is a brand-new epic's **seed** — no CI path can
|
|
75
85
|
create a ledger, so an epic whose `.sdlc/state.json` is absent from the base ref may be created by a
|
|
76
86
|
human on this first PR/MR (#162). Every later change to it is CI's alone.
|
|
77
|
-
5. Report the PR/MR URL and the
|
|
87
|
+
5. Report the PR/MR URL and the count the step needs. **Do not** record approvals or advance — reviewers
|
|
78
88
|
act on the platform; CI (`yad gate ci`) reconciles it onto the default branch at merge.
|
|
79
89
|
|
|
80
|
-
### Step 3 — `route` (print
|
|
81
|
-
Compute and print the
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
### Step 3 — `route` (print the count)
|
|
91
|
+
Compute and print the count as above. Use `templates/checks/hub-route.sh <body>` to parse a PR/MR
|
|
92
|
+
body's Impact & Risk block when given one; it prints e.g. `ROUTE: 3 approvers = base 1 + contract risk 2
|
|
93
|
+
(risk tag: contract)`, says only the base holds the gate (1 approval), that the risk step is advisory
|
|
94
|
+
and `yad gate status` prints it capped by the active people, and lists the touched repos as a hint for whom
|
|
95
|
+
to ask. With no `contract`/`auth`/`payments` tag it prints `ROUTE: 1 approver = base 1 (no risk step).`
|
|
96
|
+
and no repo list. Otherwise derive from
|
|
97
|
+
`epic.repos` + the step's `risk_tags`. Stories do not route per repo. Advisory only — it routes the
|
|
98
|
+
human review, it does not approve.
|
|
99
|
+
|
|
100
|
+
### Step 4 — `wire` (merge-time sync on the Product)
|
|
101
|
+
Install the Product CI that turns the human **merge** into a `yad gate ci` run, with CI as the **sole
|
|
87
102
|
writer** of the ledger. There is no pre-merge CI write — during review the platform PR/MR is the
|
|
88
103
|
source of truth (native approvals + threads). On merge, CI re-reads approvals from the platform,
|
|
89
104
|
advances the step, and flips the artifact `status:` on the **default branch** (the only place CI ever
|
|
@@ -103,12 +118,15 @@ default branch. (local mode keeps `yad gate sync` as the local writer.)
|
|
|
103
118
|
enabled bridge in `.sdlc/hub.json` it installs
|
|
104
119
|
- GitHub → `.github/workflows/yad-gate-sync.yml` (from `templates/github/yad-gate-sync.yml`)
|
|
105
120
|
- GitLab → `.gitlab/ci/yad-gate-sync.yml` (from `templates/gitlab/yad-gate-sync.gitlab-ci.yml`)
|
|
106
|
-
- plus the
|
|
107
|
-
owned by `yad-checks`) so review PRs accept only signed commits
|
|
121
|
+
- plus the Product-side **verified-commits** gate (`checks/verified-commits.sh` + its workflow/fragment,
|
|
122
|
+
owned by `yad-checks`) so review PRs accept only platform-Verified (signed) commits — there is no
|
|
123
|
+
author allowlist
|
|
108
124
|
- the wired job runs an **exact** version, resolved at run time from the repo: the `YAD_VERSION`
|
|
109
125
|
variable, else `hub.json` `gate_sync_version`, else the `.sdlc/cli-version.json` stamp, else the
|
|
110
|
-
floating
|
|
111
|
-
|
|
126
|
+
floating major the fragment ships with (`YAD_MAJOR`, `4` in this release) — a committed pin is used
|
|
127
|
+
only when it is an exact release of that major, so `yad update` moves the fragment and the stamp
|
|
128
|
+
to a new major together. It stopped floating on the major because that let a release change a scheduled job's
|
|
129
|
+
behaviour unattended — how #163's churn reached Products that never opted in. So upgrading is now a
|
|
112
130
|
deliberate act (`yad update`); if the resolved pin predates 3.15.3, upgrade or disable the
|
|
113
131
|
schedule. Never edit a version into the wired file — `yad check --fix` rewrites it byte-for-byte
|
|
114
132
|
from the template. See `references/bridge.md`.
|
|
@@ -128,7 +146,7 @@ default branch. (local mode keeps `yad gate sync` as the local writer.)
|
|
|
128
146
|
case set the variable to a real tag. The variable lives in project settings, so it survives
|
|
129
147
|
every `yad` sync. Single value only — `tags: [$VAR]` is one tag equal to the whole variable,
|
|
130
148
|
not a comma-split.
|
|
131
|
-
3. Commit the workflow to the
|
|
149
|
+
3. Commit the workflow to the Product. GitHub needs nothing else — the ephemeral `github.token` reads the
|
|
132
150
|
PR and pushes the merge advance to the default branch, and the workflow's reconcile **schedule**
|
|
133
151
|
runs automatically (no setup) as the safety net that recovers a merge whose run failed transiently.
|
|
134
152
|
If the default branch is protected, see the workflow header for the bypass / PAT options; until then
|
|
@@ -143,7 +161,8 @@ default branch. (local mode keeps `yad gate sync` as the local writer.)
|
|
|
143
161
|
is the source of truth** and the gate predicate (in `yad-review-gate`) is unchanged.
|
|
144
162
|
- **the verified ledger never approves on a reviewer's behalf.** Reviewers approve/merge with their own auth. The
|
|
145
163
|
step advances when a human **merges** the approved, fully-resolved review PR (the merge is that human
|
|
146
|
-
act) — `yad gate
|
|
164
|
+
act) — `yad gate ci` at merge (or `yad gate sync` with a local ledger) records the approvals +
|
|
165
|
+
resolution + merged state and advances; unresolved
|
|
147
166
|
comments or a changed artifact hold it `in_review`. The mechanical sync is the `yad gate` CLI.
|
|
148
167
|
- **CI never approves and never merges.** The wired workflow only runs `gate ci` — the same sync +
|
|
149
168
|
unchanged predicate. It does **nothing pre-merge** (the platform PR/MR holds the review state); at
|
|
@@ -154,7 +173,7 @@ default branch. (local mode keeps `yad gate sync` as the local writer.)
|
|
|
154
173
|
ephemeral `github.token` (nothing stored). GitLab requires a stored masked `SDLC_GATE_TOKEN`
|
|
155
174
|
project access token because `CI_JOB_TOKEN` can neither read the approvals API nor push — say so
|
|
156
175
|
when wiring, and scope it to `read_api` + `write_repository` only.
|
|
157
|
-
- **Protect the
|
|
176
|
+
- **Protect the Product default branch.** Require that `epics/**` artifacts change only through a review
|
|
158
177
|
PR/MR (branch protection). This keeps revoke-on-change sound: it removes the only window where a
|
|
159
178
|
delayed reconcile could advance on an out-of-band post-merge artifact change (see `references/bridge.md`,
|
|
160
179
|
"Known limitation"). Safe to require: a new epic's `.sdlc/` seed rides its first review PR/MR, so
|
|
@@ -164,6 +183,6 @@ default branch. (local mode keeps `yad gate sync` as the local writer.)
|
|
|
164
183
|
## Reference
|
|
165
184
|
- PR-body→ledger mapping, the read-only gh/glab recipes, idempotent re-sync, contract re-lock handling:
|
|
166
185
|
`references/bridge.md`.
|
|
167
|
-
-
|
|
186
|
+
- How approvals, comments and records are attributed to a platform login (no roster): `references/login-roster.md`.
|
|
168
187
|
- The gate this feeds (open hook + sync action + unchanged predicate): `../yad-review-gate/SKILL.md`, `../yad-review-gate/references/gating.md`.
|
|
169
|
-
- The
|
|
188
|
+
- The Product PR/MR body template + the code-repo routing analogue: `../yad-pr-template/`.
|
|
@@ -8,8 +8,8 @@ predicate (`../yad-review-gate/references/gating.md`) runs unchanged. The verifi
|
|
|
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
15
|
`approvals.json` records from the verified ledger carry `"source": "bridge"`; **manual** approvals have no such
|
|
@@ -23,13 +23,13 @@ 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
35
|
All commands run as the **local user**; the verified ledger stores no tokens. If the CLI is missing/unauthenticated
|
|
@@ -41,70 +41,81 @@ or the remote is unreachable, the verified ledger stops and the gate falls back
|
|
|
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
|
|
@@ -220,15 +246,15 @@ commit — the advance plus the `draft → approved` status flip — lands on th
|
|
|
220
246
|
`[skip ci]`.
|
|
221
247
|
|
|
222
248
|
**The ledger is CI-owned (verified mode only).** Humans never commit gate-state files: the `ledger-guard`
|
|
223
|
-
check (yad-checks) FAILs any commit on a review PR that touches `.sdlc/{state,approvals,comments,hub-prs}
|
|
249
|
+
check (yad-checks) FAILs any commit on a review PR that touches `.sdlc/{state,approvals,comments,product-prs,hub-prs}
|
|
224
250
|
.json` or `reviews/*.md` (`.sdlc/contract-lock.json` is artifact-side and allowed). "verified mode" there
|
|
225
251
|
means the same thing it means everywhere else — a `platform` **and** the verified ledger flag, `isVerifiedLedger`'s
|
|
226
|
-
predicate. The gate used to enable itself on the flag alone, which let a platform-less
|
|
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
|
-
|
|
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`
|
|
232
258
|
opens the PR only; local `yad gate sync` is advisory in verified mode (writes nothing). After a merge,
|
|
233
259
|
everyone `git checkout <default> && git pull`. (with a local ledger, humans own the ledger locally and
|
|
234
260
|
these guards are no-ops.)
|
|
@@ -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
|
|
@@ -270,7 +296,8 @@ push retries with a rebase.
|
|
|
270
296
|
Actions, else a fine-grained PAT as `SDLC_GATE_TOKEN` on the mergesync checkout.
|
|
271
297
|
|
|
272
298
|
**Manual sync & recovery.** In verified mode `yad gate sync` is **advisory** (read-only) — it prints the
|
|
273
|
-
predicate
|
|
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,
|
|
@@ -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.
|