OXYGENHUB

Diffusion: the design 8620925e5c53

docs/diffusion.md: how an upstream's changes reach the repositories that
pin it as a subrepo. Signed bump proposals tested by each consumer's CI,
with the exact upstream changelog (D1); partial pins that hold back only
the change that breaks a consumer (D2); consumers' checks on upstream
proposals (D3); migrations and agent fixes (D4); auto-land, chains,
cross-hub following and channels (D5).

refs #148
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-12 19:45 UTC
Signature
✔ signed by Andy McCall
Hash
8620925e5c53d57684bf863086aa907fc7e6507e63bbb64938d0c02c7ded718d
Depends on
93be9ce4653e
Depended on by
0e04dfc20211 a2b29af1ce88 f4d6ef418a96 eb2a3502601d 73ef9f767132 757195ad7a5d 9aaa88b2d9c8 b20ddaf96167
In bookmarks
api-unknown-fields cancel-landed-runs close-proposal-140 diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
In tags
v0.5.0 v0.5.1 v0.5.2 v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
1 atom, 1 tree operation, 1 file

Browse the tree at this change

added docs/diffusion.md +209 −0
1 +
# Diffusion: upstream changes reach their consumers
2 +
3 +
Status: design, 2026-10-12. D1 is planned first; nothing is built yet.
4 +
"Diffusion" is a working name (changes diffuse from an upstream into the
5 +
repositories that pin it); Andy has not confirmed it.
6 +
7 +
## 1. The problem
8 +
9 +
R* API is a subrepo of Metal Gear, Metal Gear 2, Super Mario Land and
10 +
others. When it changes, each consumer should take the change, test it,
11 +
and propose it when it works, with no one opening proposals by hand.
12 +
Renovate and Dependabot do this for package manifests; Diffusion does it
13 +
for Oxygen pins, as part of Oxygen, and goes further than they can.
14 +
15 +
## 2. Why Oxygen can do better
16 +
17 +
- **The dependency graph is exact.** An Oxygen pin names a repository id
18 +
  and a state, and is a verifiable object (spec §7.8). The hub already
19 +
  reads every pin in a state to give jobs their subrepos. Nothing is
20 +
  scanned or guessed.
21 +
- **A pin is a set of changes, and independent changes commute.** A
22 +
  consumer can take an upstream state minus one change and whatever is
23 +
  built on it (§6). Git tools can only pin one commit: all or nothing.
24 +
- **Bumps never need rebasing.** The pin change commutes with the
25 +
  consumer's own work; only a consumer moving the same pin itself
26 +
  conflicts, as an ordinary replace conflict.
27 +
- **Everything is signed.** The bump, the upstream changes and the CI
28 +
  receipts are signed objects, so a consumer can check what it takes, even
29 +
  from another hub.
30 +
31 +
## 3. The approaches compared
32 +
33 +
| Approach | Strength | Weakness |
34 +
|---|---|---|
35 +
| Bump proposals (Renovate, Dependabot) | Consumers stay in charge; auditable; works across hubs | Reactive: breakage is found after upstream ships |
36 +
| Monorepo at head (Google) | Upstream cannot land what breaks a consumer | Takes control from consumers; upstream sees all code; one hub only |
37 +
| Upstream-driven migrations (large-scale changes, codemods) | Handles breaking changes, not just detects them | Needs tooling per change |
38 +
| Consumer-gated promotion (contract testing) | Prevents breakage | Needs consumers' tests to run upstream |
39 +
40 +
Diffusion is bump proposals as the base, with consumer checks on upstream
41 +
proposals (§7), partial pins (§6) and repair (§8) on top.
42 +
43 +
## 4. D1: follow and propose
44 +
45 +
### 4.1 The reverse pin index
46 +
47 +
When a push moves a repository's default bookmark, the hub records each
48 +
Oxygen pin in the new state: consumer, path, the upstream repository id
49 +
and the pinned heads (`diffusion_pins`). That answers "who consumes R*
50 +
API, at which state" at once. An upstream repository's page shows its
51 +
consumers on this hub ("Used by"); the API gives
52 +
`GET /repos/{owner}/{name}/consumers`.
53 +
54 +
### 4.2 Turning it on
55 +
56 +
A consumer follows a subrepo when, in order of precedence:
57 +
58 +
1. its `.oxymodules` entry says so, beside the existing `url` and
59 +
   `bookmark` keys:
60 +
61 +
   ```toml
62 +
   [rstar-api]
63 +
   url = "https://oxygenhub.io/doomsdayone/rstar-api"
64 +
   bookmark = "main"
65 +
   follow = "green"   # off | green | any
66 +
   land = "propose"   # propose | auto
67 +
   ```
68 +
69 +
2. else its repository setting (Settings › Subrepos), so an
70 +
   administrator can turn it on without a commit;
71 +
3. else the hub's default (`[diffusion] follow` in hub.toml, `off`).
72 +
73 +
`green` waits for the upstream state's required checks to pass; `any`
74 +
follows every move. `auto` lands a green bump itself when the consumer's
75 +
trust policy and required checks allow it; D1 only proposes.
76 +
77 +
### 4.3 The bump
78 +
79 +
When an upstream's followed bookmark reaches a new state S (and, for
80 +
`green`, S's required checks pass), for each consumer that follows it:
81 +
82 +
- **Skip** when the consumer's pin already holds S, or when S does not
83 +
  hold everything the current pin does (upstream moved backwards or was
84 +
  rewritten). Consumers only move forward.
85 +
- **Build** one change on the consumer's default heads that replaces the
86 +
  pin's blob with a pin of S. The hub signs it with its delegate key, as
87 +
  it signs wiki edits, authored by the hub's `diffusion` account.
88 +
- **Place** it on the bookmark `diffusion/<path>`: the consumer's default
89 +
  heads plus the change. One bookmark per consumer and subrepo, moved in
90 +
  place when upstream moves again, so there is never a pile of bumps.
91 +
- **Propose** it, once: a proposal from `diffusion/<path>` to the default
92 +
  bookmark, labelled `dependencies`, kept up to date as the bookmark
93 +
  moves. Its CI runs on the projected state like any proposal's.
94 +
95 +
A trust policy that does not admit the hub's key still gets the
96 +
proposal; it is landed from a client, as today.
97 +
98 +
### 4.4 What the proposal says
99 +
100 +
- The upstream, the old pin and the new state.
101 +
- **The exact changelog:** every upstream change in S and not in the old
102 +
  pin, with its author, whether it is signed, and the issues it closes or
103 +
  refers to.
104 +
- Its checks, as for any proposal. A red bump stays open, with the
105 +
  failing check; D2 then says which upstream change broke it.
106 +
107 +
## 5. Across hubs
108 +
109 +
Each hub only ever writes to its own repositories. What one hub tells
110 +
another is a hint; the consumer's hub fetches and verifies the facts.
111 +
112 +
- **Polling always works.** The consumer's hub has the upstream URL in
113 +
  `.oxymodules`; it asks for the followed bookmark every few minutes. An
114 +
  upstream kept in a Strata bucket with no hub is polled by reading its
115 +
  bookmark record.
116 +
- **Subscribing makes it prompt.** The consumer's hub asks the upstream's
117 +
  hub to notify it when the bookmark moves. Notes are signed with the
118 +
  upstream hub's key and carry only "repository *id* moved to state *S*";
119 +
  the consumer fetches and verifies. A forged note causes a fetch, nothing
120 +
  more; a lost one is caught by the next poll.
121 +
- **Private upstreams** are read with a read-only credential the
122 +
  consumer's owner stores as a secret on the consumer's hub.
123 +
- **"Green" across hubs** is evidence the consumer checks: a Flow receipt
124 +
  for S, signed by a runner the upstream hub vouches for. The consumer's
125 +
  policy names the upstream hub keys it accepts, and may also require
126 +
  every upstream change to be signed by the upstream's maintainers, which
127 +
  holds even if the upstream hub is compromised.
128 +
- **Consumer checks across hubs (§7)** are opt-in on both sides; the
129 +
  consumer's hub runs them on its own runners and returns only a signed
130 +
  summary, or nothing.
131 +
132 +
## 6. D2: partial pins and the culprit
133 +
134 +
A pin is a state: any set of changes closed under dependencies. So a
135 +
consumer can pin "upstream S, minus change X and everything built on X":
136 +
its heads are the maximal changes of `closure(S) − ({X} ∪ dependents(X))`,
137 +
well-formed by construction.
138 +
139 +
When a bump of N upstream changes fails:
140 +
141 +
1. Flow searches for the culprit by testing subsets of the N changes (a
142 +
   delta-debugging search over independent changes, bounded in runs).
143 +
2. The bump proposal then pins everything except the culprit and its
144 +
   dependents, and says which change is held back and why, with the
145 +
   failure.
146 +
3. The consumer gets the rest at once; the held-back change comes with a
147 +
   later bump, once upstream fixes it.
148 +
149 +
`oxy submodule` and checkout take such pins as they are (a pin is heads);
150 +
`oxy submodule status` names the changes a partial pin leaves out.
151 +
152 +
## 7. D3: consumers test upstream proposals
153 +
154 +
A proposal on R* API can run each consumer's pipeline with its pin swapped
155 +
for the proposal's projected state, committing nothing to the consumer
156 +
(the claim already carries subrepo pins; the run overrides one). Each
157 +
consumer's result appears on the upstream proposal as a check,
158 +
`consumers/<owner>/<name>`, with a summary: "Metal Gear ✔, Super Mario
159 +
Land ✘ (test X)". The person changing the API sees what breaks before it
160 +
lands, which is the monorepo's main strength without taking consumers'
161 +
control. An upstream may make consumer checks required.
162 +
163 +
## 8. D4: repair
164 +
165 +
- **Migrations with the change.** An upstream change that alters the API
166 +
  may carry a migration (a script, or instructions for an agent) under
167 +
  `.diffusion/migrations/`. A bump spanning it runs the migration in the
168 +
  consumer and adds the result as a separate change on the bump proposal.
169 +
- **An agent's fix.** When a bump is red, a scoped agent key (an agent
170 +
  home with `changes` scope on that consumer, never `refs`) may add a fix
171 +
  as a separate change to the proposal, for the consumer's owner to
172 +
  review. The bump and the fix stay separate changes, so either can be
173 +
  dropped.
174 +
175 +
## 9. D5: reach
176 +
177 +
- **Auto-land** (`land = "auto"`): a green bump lands itself when the
178 +
  consumer's trust policy and required checks allow it, and every
179 +
  upstream change is signed by keys the policy trusts.
180 +
- **Chains:** landing a bump moves the consumer's default bookmark, which
181 +
  may be followed in turn.
182 +
- **Grouping and quiet hours:** several upstreams' bumps in one proposal;
183 +
  no bumps outside set hours.
184 +
- **Cross-hub** polling and subscriptions (§5).
185 +
- **Channels:** with Cylinder, a consumer follows `rstar-api:stable`, a
186 +
  signed channel whose moves are gated by consumer checks (§7).
187 +
188 +
## 10. Order
189 +
190 +
| Step | What |
191 +
|---|---|
192 +
| D1 | The reverse pin index and Used by; turning it on; the signed bump on `diffusion/<path>`, moved in place; the proposal with the changelog |
193 +
| D2 | Partial pins; the culprit search |
194 +
| D3 | Consumer checks on upstream proposals |
195 +
| D4 | Migrations and agent fixes |
196 +
| D5 | Auto-land, chains, grouping, cross-hub, channels |
197 +
198 +
D2 is what no other tool can do; D3 is worth the most.
199 +
200 +
## 11. Open questions
201 +
202 +
- **The name:** Diffusion, or plainer ("Follow")?
203 +
- **Who may turn it on** for a consumer: its owner and administrators, or
204 +
  also the upstream's maintainers (by offering, which the consumer
205 +
  accepts)?
206 +
- **Bot identity:** a `diffusion` account per hub, or the upstream
207 +
  pusher's name with the hub as delegate?
208 +
- **Cost limits** for the culprit search and consumer checks: runs per
209 +
  bump, per upstream proposal, per day.