1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
//! Version control and its remote host behind one host-neutral vocabulary.
//!
//! The review unit is a [`ChangeRequest`] — GitHub maps it to a pull request,
//! and a later host maps it to whatever it calls the same thing. [`Vcs`] owns the
//! repository side (identities, sessions, preserved work) and [`RemoteHost`] owns
//! the host side (opening a change, reading its checks, merging it). A
//! [`rules`] file decides, per repository, how a change is published and whether
//! it needs an approval. Everything a process does along the way is emitted as an
//! [`Envelope`].
//!
//! # The shape of one change
//!
//! ```text
//! session open → a per-run --shared clone and one worktree, occupancy-leased
//! → work happens in the worktree
//! → publish → fetch and merge the current base (bounded resolve-and-requeue)
//! → the repository's own merge path verifies it: its pre-push
//! hook, or the host's required checks on the change request
//! → local-direct squash, or a change request the host lands
//! → session close → the worktree goes; the branch is copied out and stays
//! ```
//!
//! Everything durable lives under one state root (`ONEVCS_HOME`, otherwise
//! `~/.onevcs`): the registry document, the advisory locks and merge-queue state,
//! the per-session workspaces, the event streams, and their artifacts.
//!
//! # Two surfaces over one decision
//!
//! [`run`] is the command line: it answers a process, with an exit code and a line
//! of prose. A caller embedding this crate wants the decision itself, so the same
//! operations answer values — [`publish`] hands back a [`Publication`],
//! [`close_session`] the session it released, [`session`] what the repository side
//! recorded, and [`EventStream`] the envelopes one session wrote. The command line
//! is a rendering of those rather than a second path through them.
use Path;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use Providers;
pub use ;
pub use Identity;
pub use ;
pub use MergePolicy;
pub use ;
pub use EventStream;
pub use ;
/// A parsed absolute URL, re-exported so a caller needs no direct dependency on
/// the parser this crate validates change-request URLs with.
pub use Url;
/// Run one parsed command line, returning the process exit code.
///
/// The binary is a thin shell over this, so a journey that drives `onevcs` and a
/// caller that embeds it take the same path and cannot disagree about an exit code.
/// Run one parsed command line against supplied implementations of the two
/// interfaces, returning the process exit code.
///
/// [`run`] is this with [`Providers::real`], so nothing about the command's own
/// behaviour changes with the implementations behind it: one code path, reached
/// through [`Vcs`] and [`Hosting`] rather than through the types that satisfy them
/// by default.
/// Verify a session's work and publish it, returning what the publication did.
///
/// The library form of `onevcs publish`, and the reason it exists: a run answers
/// with an exit code and prose, and a caller that has to branch on *what happened*
/// can only parse the prose. [`Publication`] is that answer as a value — the policy
/// it was taken under, whether it merged, opened a change request, queued one, or
/// had nothing to publish, and the failure and what became of the branch when it
/// did not land.
///
/// It runs through the seam, so a session a supplied [`Vcs`] opened publishes
/// against a supplied [`Hosting`] with no git, no host, and no process.
/// Release a session's worktree and its occupancy lease, keeping its branch.
///
/// The library form of `onevcs session close`.
/// The session's own change request — the one [`publish`] would open or adopt for
/// it, from its branch into the base its publication resolves — as the host holds
/// it, or `None` when the host holds none.
///
/// The library form of `onevcs change show`. It never invents one: a session that
/// has not published has no change request, and [`publish`] is what opens one.
/// Replace the description of the session's own change request — its body, and its
/// title when one is given — and report the change as it stands after the write.
///
/// The library form of `onevcs change describe`. It refuses a session with no change
/// request rather than opening one. A title is a [`Subject`] and is held to the
/// repository's own `commit-msg` hook exactly as a publication's is, because it is
/// the squash subject a `change-auto` merge lands under. The body is written
/// verbatim and recorded as an artifact the `change-described` event names.
/// Mark the session's own change request ready for review, and report it as it
/// stands.
///
/// The library form of `onevcs change ready`: the lift a publication carrying no
/// [`DraftReason`] performs, asked for on its own. A change request that is not a
/// draft is asked for nothing and reported as it stands.
/// Every session recorded for one repository, live or not, in token order.
///
/// The library form of `onevcs session holders`, and the question a caller asks
/// *before* it has a token: which sessions hold this repository's workspaces, which
/// of them still have an owner, and which are the remains of a run that stopped.
/// [`SessionHolder::token`] is what the rest of this surface takes, so a holder is
/// a session to act on rather than a line to read.
///
/// It takes no [`Providers`] because there is nothing here for an implementation to
/// answer: the holders are the records under this host's state root, which is where
/// `Git` writes them and where the command reads them. A `Vcs` that keeps its
/// sessions elsewhere therefore does not appear in this list — the same limit the
/// command has, since the two are one path.
///
/// A record whose owner process has exited *and* whose run root no live process is
/// working inside is forgotten as this answers rather than reported: nobody is left
/// to be asked about it, and a list of those is what buries the one holder a caller
/// had to act on. A session somebody is still working in is reported however long
/// ago the command that opened it exited.
/// What the repository side recorded about a session.
///
/// The library form of the record every command that takes a token reads: which
/// repository it belongs to, whether it is still open, and whether its branch
/// carries an incomplete-step marker.
/// What one repository releases, and what it adopts.
///
/// The library form of `onevcs release targets`. `repo` is the identity key, a
/// registered alias, an origin URL, or a path, exactly as every other command takes
/// one.
///
/// # Three layers, in one stated order
///
/// The answer is resolved from the repository's own declaration and this host's
/// document together, and which of them decides is fixed rather than a consequence
/// of read order:
///
/// 1. the **producer's** `release-targets.toml`, read from its publication
/// checkout, contributes its targets in its own publication order;
/// 2. a target this host's `releases.yml` names and the producer does not is
/// **added** — a consumer standing in where nobody declared one;
/// 3. a target both name is the **host's**, in the producer's position — an
/// override, which is how a host runs a probe differently from the way the
/// repository publishes it.
///
/// A rule saying [`DeclarationPolicy::Ignore`] drops layer 1 entirely, which is how
/// a host says it does not consume what a repository declares. A host with no rules
/// and repositories declaring nothing answers exactly what it always did.
///
/// [`RepositoryReleases::sources`] says which layer answered for each target, and
/// [`RepositoryReleases::declaration`] says what the producer's half contributed —
/// including the state that is neither a declaration nor its absence, a declaration
/// this build could **not read**, which is never "this repository publishes
/// nothing".
///
/// It takes no [`Providers`] for the reason [`session_holders`] does not: what a
/// repository releases is this host's own configuration, the repository's own
/// declaration, and this host's own record, and there is nothing here for an
/// implementation of either interface to answer.
/// Every target one repository has, and what each of them has released right now.
///
/// The library form of `onevcs release discover`, and the answer a consumer that
/// waits on a release actually needs: [`release_targets`] says what there is to wait
/// for and this says what each of those has done, over the same three layers and in
/// one resolution rather than one per target.
///
/// Each target is answered exactly as [`release_latest`] answers it, so the two
/// cannot disagree — and [`ReleaseAnswer::NotAnswered`] stays distinct from
/// [`ReleaseAnswer::NoRelease`] here as everywhere: a consumer holds on the first
/// and acts on the second.
///
/// A repository with no targets answers with none rather than refusing, because
/// "there is nothing to wait for" is a thing a consumer acts on — but what it is
/// worth depends on [`RepositoryReleases::declaration`], which travels with it.
/// What version of one target is released right now.
///
/// The library form of `onevcs release latest`. An **automated** target is answered
/// by running its probe; a **human-step** target executes nothing at all and is
/// answered from the newest acknowledgement across its landings, or
/// [`ReleaseAnswer::NoRelease`] where nobody has recorded one.
///
/// [`ReleaseAnswer::NotAnswered`] is never [`ReleaseAnswer::NoRelease`]: a consumer
/// holds on the first and acts on the second.
/// Whether the release that carries one landed change has happened yet.
///
/// The library form of `onevcs release status`. `reference` is the four-spelling
/// reference `onevcs status` takes: a change request's URL, a session token, a
/// branch name, or a commit.
/// Whether one piece of work reached its base, and what says so.
///
/// The landing decision on its own, which is what [`release_status`] answers *before*
/// it selects a release target — and the reason this exists beside it. A repository
/// that declares no release target has no target to select, so `release_status`
/// refuses it; a refusal is undecided rather than "not landed", and a caller that
/// only wanted to know whether the work is on the base was left with no answer at
/// all. This takes no target, reads no release configuration, and answers every
/// repository the same way.
///
/// `reference` is the four-spelling reference [`release_status`] takes: a change
/// request's URL, a session token, a branch name, or a commit. `repo` is the
/// repository the question is narrowed to — an identity key, a registered alias, an
/// origin URL, or a path, exactly as every other repository-taking read here takes one
/// — and `None` resolves the repository from the reference alone. Naming one is how a
/// branch name two identities both hold is asked about; a reference belonging to
/// another identity is refused rather than answered under the name of the one asked
/// about.
///
/// The tiers are decided in one private place — `landed::decide` — and nowhere else,
/// so this, `onevcs status` and `onevcs recoverable` cannot come to disagree about one
/// branch. Which tier decided travels inside the [`Landed`] value, so a caller acting
/// on a `yes` can say what it acted on.
///
/// It takes no [`Providers`] for the reason [`release_targets`] does not: a landing is
/// decided from the history of the repositories under this host's own state root, and
/// there is nothing here for an implementation of either interface to answer.
/// Record the release somebody says carries a landing.
///
/// The library form of `onevcs release acknowledge`. It accepts an automated target
/// whose landing has no established probed baseline, and refuses one whose baseline
/// is established. Human-step targets behave the same way regardless of baselines.
/// It also refuses a reference that has not landed, a version
/// that is not a semantic version, and a target the repository does not declare.
///
/// Recording the same version for the same landing again succeeds and changes
/// nothing, re-reporting the existing record with its original timestamp and actor.
/// Recording a *different* version is refused unless `supersede` is set, which
/// writes the new version and keeps the previous one in the record's own history.
/// What one repository declares that it publishes.
///
/// The library form of `onevcs release declaration`, and the producer half of the
/// release contract: [`release_targets`] answers what *this host* waits on for a
/// repository, and this answers what the repository itself says it publishes. The
/// two are different documents on purpose — see [`declaration`] — and deciding
/// between them is nobody's job here.
///
/// `path` is either a repository's root or the `release-targets.toml` in it. A
/// repository carrying no declaration is refused rather than answered with an empty
/// one: "this repository publishes nothing" and "nobody has said what this
/// repository publishes" are different answers, and a consumer that waits on a
/// release acts differently on each.
///
/// It takes no [`Providers`] for the reason [`release_targets`] does not: a
/// declaration is a file in a checkout, and there is nothing here for an
/// implementation of either interface to answer.
/// Validate one release declaration's text, and answer what it declares.
///
/// The half of [`read_release_declaration`] that touches no filesystem: a caller
/// that fetched a declaration from a host, or is about to write one, holds it to
/// exactly the checks a repository's own file gets. `origin` is what the refusals
/// name the document by — a path, a URL, or whatever the caller knows it as.
/// Render a declaration back as the TOML document it declares.
///
/// **A producer's comments are not preserved, because they were never read.** A
/// declaration is mostly prose — the reasoning about what is a target and what is
/// not — and this answers with the declaration alone, so writing the result over a
/// producer's own file deletes that reasoning. It is for *producing* a declaration;
/// editing one is a job for a person.
///
/// What it does promise is that the result reads as the same declaration, and it
/// keeps that promise by holding what it was handed to a document's own checks
/// first: a [`Declaration`] a caller *built* rather than read is refused here if it
/// is one no repository could mean, rather than written out as a document nothing
/// can read back.
/// Which rung of the adoption chain one repository resolves to.
///
/// The repository rung where a rule sets one and the global rung otherwise. It
/// never answers the node rung and never defaults to [`Adoption::Fast`] itself:
/// those two rungs belong to the consumer, and a crate that answered all four would
/// make the chain unreadable from either side.