You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(example): create, enqueue, and watch demo pull requests (#569)
## Summary
### Why?
Exercising the demo meant opening pull requests by hand and pasting
their URLs into a land command — slow enough to discourage running it,
and it made the interesting case impossible to reach in practice. A
queue that only ever holds one request in flight never batches, never
analyzes a conflict against another batch, and never speculates.
### What?
`make demo-pr` creates pull requests in the scratch repository, enqueues
them, and watches them settle.
Each pull request is enqueued the moment it is created, so the queue is
already working on the first while the last is still being opened.
Nothing is awaited until everything is in; the wait happens once,
against all of them. That overlap is what puts requests in flight
against each other, which is the whole point of running more than one.
`STACKED=true` is the exception, and necessarily so: one request carries
the whole chain, so it can only be submitted once every change in it
exists. That is the atomic-stack path, where the set reaches the target
in a single push.
Progress is a table keyed on the gateway's status API, redrawn as
requests move, so it is visible which stage each one is in rather than
only whether it finished. On a terminal it redraws in place; piped to a
file it prints a fresh table whenever something changes, so logs stay
readable.
It lives beside the configuration it drives, under
`service/submitqueue/example/`, and reaches GitHub through the REST API
rather than a clone — so it needs no checkout and no git binary, only
`GITHUB_TOKEN`, the credential the stack already uses.
## Test Plan
✅ `bazel build //service/submitqueue/example/pr`; `make help` lists
`demo-pr`.
Running it end to end needs a scratch repository and a token, so it is
exercised the same way the rest of the provider path is — manually. See
`doc/howto/PROVIDER-E2E.md`.
## Also in this PR
- **feat(demo): live table with the full state trail per request**
- **feat(messagequeue): level the queue's own logs separately**
- **docs(demo): correct the sample trail to states the pipeline emits**
- **feat(demo): spread each change over a sharded file tree**
<sub>Each commit above carries its own rationale and test plan in its
message.</sub>
## Issues
Copy file name to clipboardExpand all lines: doc/howto/PROVIDER-E2E.md
+47-1Lines changed: 47 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,7 +25,7 @@ For a **fine-grained** token, grant these repository permissions. Each is here b
25
25
| Metadata | Read | mandatory on every fine-grained token; GitHub adds it for you |
26
26
| Contents | Read and write | the git merger — clone, fetch, push to the target branch, and force-move each landed change's head branch |
27
27
| Pull requests | Read | the change provider reads pull request metadata, and `land -pr` reads the head commit |
28
-
| Pull requests | Read **and write**| only for `make demo-prs`, which opens pull requests |
28
+
| Pull requests | Read **and write**| only for `make demo-pr`, which opens pull requests |
29
29
| Actions | Read and write | only if you switch the build runner to GitHub Actions — dispatch a run, poll it, cancel it |
30
30
31
31
A **classic** PAT needs `repo`, plus `workflow` if you use the GitHub Actions build runner.
@@ -89,6 +89,44 @@ make land PRS="https://github.com/<you>/<repo>/pull/1 \
89
89
90
90
The order of `PRS` is the stack order. All three land as **one push** to `main` — there is no window where a reader sees the stack half-applied — and all three show as merged. Tier 2 asserts the single-push property mechanically, by counting ref updates in the target's reflog.
91
91
92
+
## Simulating traffic
93
+
94
+
Opening pull requests by hand gets old fast. `demo-pr` creates them, enqueues them, and shows you where each one is:
95
+
96
+
```bash
97
+
make demo-pr # 3 independent PRs, each enqueued as it is created
98
+
make demo-pr COUNT=8 # more traffic
99
+
make demo-pr FILES=8 # wider changes, more files per PR
100
+
make demo-pr STACKED=true # one stack, enqueued as a single request
101
+
make demo-pr LAND=false # create only, print the land command
102
+
```
103
+
104
+
Each pull request is enqueued the moment it exists, so the queue is already working on the first while the last is still being opened. That overlap is the point: a queue holding one request at a time never batches, never analyzes a conflict against another batch, and never speculates. Nothing is awaited until every request is in.
105
+
106
+
The table is there from the start — one row per land request, drawn before the first pull request exists and filled in as the run proceeds. Whatever is happening right now is a single line underneath it, so creating and enqueuing does not scroll the table away:
demo-queue/13 #32 31s accepted → started → validated → batched
113
+
demo-queue/14 #33 28s accepted → started
114
+
115
+
▸ 1 of 3 settled
116
+
```
117
+
118
+
Each row shows the states its request passed through, not just the one it is in. That comes from the gateway's history API rather than from sampling the current status, so a transition between two polls is not missed. `CHANGES` links to the pull request: on a terminal `#31` is clickable, and in a redirected run it is written out as a full URL instead. `ELAPSED` runs from the moment the gateway accepted the request and stops when it settles, so a finished row keeps the time it took rather than counting on.
119
+
120
+
The trail is only as detailed as what the pipeline reports, which today is `accepted`, `started`, `validated`, `batched` and then a terminal `landed`, `error` or `cancelled`. The finer-grained statuses the API defines — `speculating`, `building`, `landing` and the rest — are never published, so a request sits on `batched` for the whole of its active life even while its batch is speculating and building. Do not read that as the request being stuck.
121
+
122
+
`STACKED=true` is the exception to the overlap: one request carries the whole chain, so it can only go in once every pull request in it exists. That is the atomic-stack path — the whole set reaches `main` in a single push, and the table shows it as the single row it is.
123
+
124
+
It talks to GitHub over the REST API with the same `GITHUB_TOKEN`, so it needs no clone and no git binary. Each run tags its branches with a timestamp so repeated runs do not collide, and every file a change writes is at a path no other change uses, so independent changes do not conflict by accident.
125
+
126
+
A change touches several files rather than one, each committed separately, so it arrives as a multi-file, multi-commit pull request — closer to a real change, and enough to exercise replaying a range of commits. `FILES` sets the floor (default 3); the actual count varies a little above it, derived from the run tag so replaying a tag reproduces the same run. Paths are sharded into two levels of hex buckets under `demo/` (`demo/c2/91/<tag>-<change>-<file>.txt`), which keeps the tree from degenerating into one enormous directory as runs accumulate.
127
+
128
+
The command exits non-zero if any request settles anywhere other than `landed`, so it works in a script. Piped to a file it prints a fresh table whenever a request moves — and not when only the clock did — instead of redrawing in place.
129
+
92
130
## Watching it work
93
131
94
132
```bash
@@ -101,6 +139,14 @@ Runway logs each merge and each head-branch move:
101
139
moved change head branch to its landed commit {"change": "you/repo#1", "branch": "refs/heads/feature-a", ...}
102
140
```
103
141
142
+
The message queue logs a line per message published, fetched, leased and acked, which at debug level buries everything else a service says. It is levelled separately from the rest of the service, at info by default. To follow the queue itself — chasing a message that never arrived, or a partition that never got leased — turn it back up for the services you care about:
143
+
144
+
```bash
145
+
QUEUE_LOG_LEVEL=debug make local-submitqueue-start
146
+
```
147
+
148
+
`QUEUE_LOG_LEVEL`takes any zap level name. It can only raise the queue's level above the one the service logger was built with, never lower it, so it cannot be used to make a quiet service verbose.
149
+
104
150
## When it does not work
105
151
106
152
**The push is rejected on the first try.** Branch protection on `main` — required status checks, or a linear-history or no-force-push rule — applies to the merger like anyone else. Either relax it on the scratch repo or add the token's identity to the bypass list.
0 commit comments