chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
The pages say what to do on the page, not which command to run
.changeset/ejected-logs.mdnew+7−0
| @@ -0,0 +1,7 @@ | ||
--- | ||
default: patch | ||
--- | ||
| ||
# `knotless show` names the command that reads an ejected pull's logs | ||
| ||
A pull the merge queue ejected as its checks failed shows `logs: knotless checks <url> --pull <pull> --log`, or `--queue <branch>` for a batch, after where it stands. The runner no longer writes that command into the reason it records, which the web pages show beside links to the checks that failed. | ||
crates/knotless/src/cli.rs+5−0
| @@ -1721,6 +1721,11 @@ async fn command( | ||
let w = crate::queue::standing(ctx, &url, &i.uri).await?; | ||
let said = w.map(|w| w.said()).unwrap_or_default(); | ||
writeln!(out, "merge queue: {} at {}\n{said}", q.state, q.created_at)?; | ||
if let Some(logs) = | ||
crate::queue::logs(&url.to_string(), &i.uri, i.base.as_deref(), q) | ||
{ | ||
writeln!(out, "logs: {logs}")?; | ||
} | ||
} | ||
if let Some(body) = &t.item.body { | ||
writeln!(out, "\n{body}")?; | ||
crates/knotless/src/queue.rs+57−1
| @@ -22,7 +22,7 @@ use crate::approvals; | ||
use crate::checks::{self, Check, Filter, Standing}; | ||
use crate::closing; | ||
use crate::env::Ctx; | ||
use crate::forge::{self, Entry, Item, Target}; | ||
use crate::forge::{self, Entry, Item, Queued, Target}; | ||
use crate::git::Git; | ||
use crate::keys; | ||
use crate::push::Update; | ||
| @@ -415,6 +415,25 @@ impl Waiting { | ||
} | ||
} | ||
| ||
/// For a pull ejected as checks failed, the command that reads their | ||
/// logs: its own runs, for a pre-flight, or the batches of the queue into | ||
/// `base`. None for one ejected otherwise, or whose reason, as runners | ||
/// once wrote it, names the command itself. | ||
pub fn logs(url: &str, pull: &str, base: Option<&str>, q: &Queued) -> Option<String> { | ||
let why = q.reason.as_deref().unwrap_or_default(); | ||
if q.state != EJECTED || q.checks.is_empty() || why.contains("`knotless checks ") { | ||
return None; | ||
} | ||
let of = match base { | ||
Some(base) if !why.starts_with("its pre-flight") => { | ||
let branch = base.strip_prefix("refs/heads/").unwrap_or(base); | ||
format!("--queue {branch}") | ||
} | ||
_ => format!("--pull {pull}"), | ||
}; | ||
Some(format!("knotless checks {url} {of} --log")) | ||
} | ||
| ||
/// `n` as `1st`, `2nd`, `3rd`, `4th` and on. | ||
pub fn ordinal(n: usize) -> String { | ||
let suffix = match (n % 10, n % 100) { | ||
| @@ -984,4 +1003,41 @@ mod tests { | ||
); | ||
assert_eq!(branches(&items), ["main"]); | ||
} | ||
| ||
#[test] | ||
fn ejected_logs_name_their_runs() { | ||
let q = |reason: &str, checks: Vec<FailedCheck>| Queued { | ||
state: EJECTED.into(), | ||
reason: Some(reason.into()), | ||
commit: None, | ||
checks, | ||
by: "did:plc:o".into(), | ||
at: "t".into(), | ||
created_at: "2026-01-01T00:00:00Z".into(), | ||
}; | ||
let failed = || { | ||
vec![FailedCheck { | ||
workflow: "test".into(), | ||
status: Some("failure".into()), | ||
check: None, | ||
}] | ||
}; | ||
let of = |q: &Queued| logs("knotless://o/r", "at://p", Some("refs/heads/main"), q); | ||
let flight = q("its pre-flight failed at its head h", failed()); | ||
assert_eq!( | ||
of(&flight).as_deref(), | ||
Some("knotless checks knotless://o/r --pull at://p --log") | ||
); | ||
let batch = q("its batch failed its checks at t", failed()); | ||
assert_eq!( | ||
of(&batch).as_deref(), | ||
Some("knotless checks knotless://o/r --queue main --log") | ||
); | ||
assert_eq!(of(&q("it conflicts in f", vec![])), None); | ||
let old = q( | ||
"x. Their logs: `knotless checks u --pull p --log`", | ||
failed(), | ||
); | ||
assert_eq!(of(&old), None); | ||
} | ||
} | ||
crates/knotless-run/src/queue.rs+3−7
| @@ -64,7 +64,7 @@ use std::sync::{Arc, Mutex}; | ||
fn refused(err: &anyhow::Error) -> Option<String> { | ||
let refused = XrpcError::of(err).filter(|e| e.status == 403)?; | ||
Some(format!( | ||
"the gateway would not land its batch: {}. A runner lands the merge queue through the gateway the owner delegates writes to: under Delegated writes in their settings on its pages, or with `knotless delegate`", | ||
"the gateway would not land its batch: {}. A runner lands the merge queue through the gateway the owner delegates writes to: under Delegated writes in their settings", | ||
refused.message | ||
)) | ||
} | ||
| @@ -399,13 +399,11 @@ impl Runner { | ||
} | ||
self.drop_from(on.watch, line, 0); | ||
let why = format!( | ||
"its batch failed its checks at {}, on {} at {}: {}. Their logs: `knotless checks {} --queue {} --log`", | ||
"its batch failed its checks at {}, on {} at {}: {}", | ||
short(flight.stack.tip()), | ||
on.branch, | ||
short(&flight.stack.base), | ||
failed.join(", "), | ||
on.watch.remote.url, | ||
on.branch, | ||
); | ||
match &flight.stack.stacked[..] { | ||
[one] => { | ||
| @@ -565,13 +563,11 @@ impl Runner { | ||
.map(|c| format!("{} ({})", c.record.workflow, c.record.status)) | ||
.collect(); | ||
let why = format!( | ||
"its pre-flight failed at its head {}, on {} at {}: {}. Their logs: `knotless checks {} --pull {} --log`", | ||
"its pre-flight failed at its head {}, on {} at {}: {}", | ||
short(&sha), | ||
on.branch, | ||
short(tip), | ||
failed.join(", "), | ||
on.watch.remote.url, | ||
item.uri, | ||
); | ||
let checks: Vec<FailedCheck> = (stands.failed.iter()) | ||
.map(|c| FailedCheck { | ||
crates/knotless-run/tests/run.rs+14−3
| @@ -3026,7 +3026,9 @@ fn a_merge_queue_lands_in_batches() { | ||
} | ||
let shown = q.shows(&x, "merge queue: ejected"); | ||
assert!(shown.contains("Halving the batch found it"), "{shown}"); | ||
assert!(shown.contains("`knotless checks "), "{shown}"); | ||
assert!(shown.contains("\nlogs: knotless checks "), "{shown}"); | ||
assert!(shown.contains(" --queue main --log"), "{shown}"); | ||
assert!(!shown.contains("Their logs"), "{shown}"); | ||
assert!(shown.contains(". Failed: ci (failure)"), "{shown}"); | ||
// It names the check that failed, as the runner filed it. | ||
let listed = q.ok(&["queue", &url, "--json"]); | ||
| @@ -3182,7 +3184,10 @@ fn a_runner_named_lands_through_the_gateway() { | ||
"{shown}" | ||
); | ||
assert!(shown.contains("has not delegated writes"), "{shown}"); | ||
assert!(shown.contains("`knotless delegate`"), "{shown}"); | ||
assert!( | ||
shown.contains("under Delegated writes in their settings"), | ||
"{shown}" | ||
); | ||
} | ||
| ||
#[test] | ||
| @@ -3252,7 +3257,13 @@ fn a_merge_queue_waits_for_preflights() { | ||
shown.contains("its pre-flight failed at its head "), | ||
"{shown}" | ||
); | ||
assert!(shown.contains(&format!("--pull {p} --log`")), "{shown}"); | ||
assert!( | ||
shown.contains(&format!( | ||
"\nlogs: knotless checks {} --pull {p} --log", | ||
q.url | ||
)), | ||
"{shown}" | ||
); | ||
assert!(shown.contains(". Failed: first (failure)"), "{shown}"); | ||
assert!(in_batch(&r) && !in_batch(&s) && !in_batch(&p)); | ||
| ||
crates/knotless-serve/src/web/landing.rs+1−1
| @@ -262,7 +262,7 @@ pub async fn queue_pull( | ||
) -> Result<Response> { | ||
let (ctx, repo) = acting(&app, viewer, &headers, "queue a pull", &input.repo).await?; | ||
if undelegated(&app, &repo) { | ||
let why = "its merge queue lands through this server once its owner delegates writes to it, under Delegated writes in their settings or with `knotless delegate`"; | ||
let why = "its merge queue lands through this server once its owner delegates writes to it, under Delegated writes in their settings"; | ||
return Err(Shown(StatusCode::BAD_REQUEST, why.into())); | ||
} | ||
let queued = knotless::queue::enqueue(&ctx, &url_of(&repo), &input.pull).await; | ||
docs/design/merge-queue.md+7−5
| @@ -235,9 +235,9 @@ alpha reaches go unrun, and the batch's run tests against both and holds | ||
the 100% gate. | ||
| ||
An ejected pull never entered a batch. Its reason names the head, the | ||
branch and its tip, the failed workflows, and `knotless checks <repo> | ||
--pull <uri> --log` for their logs, and `checks` points at their | ||
records. | ||
branch and its tip, and the failed workflows, and `checks` points at | ||
their records. `knotless show` adds the command that reads their logs, | ||
`knotless checks <repo> --pull <uri> --log`. | ||
| ||
When nobody has run a pre-flight at the head, the runner runs only those | ||
workflows, on the pull itself, as a pull's run: the head merged with the | ||
| @@ -326,8 +326,10 @@ and those of any batch ahead of it, are cancelled. The failure says some | ||
pull broke something, not which. | ||
| ||
A batch of one names its culprit, so that pull is ejected at its head. The | ||
reason names the tip, the branch and its commit, the failed workflows, and | ||
how to read their logs, and `checks` points at their records. | ||
reason names the tip, the branch and its commit, and the failed | ||
workflows, and `checks` points at their records. `knotless show` adds the | ||
command that reads their logs, `knotless checks <repo> --queue <branch> | ||
--log`. | ||
| ||
A larger batch's pulls become suspects, and the runner halves: the first | ||
half of the suspects, in queue order and rounded up, runs alone as the | ||
docs/reference/cli.md+1−1
| @@ -164,7 +164,7 @@ What they print: | ||
| `comment` | the `at://` URI of each comment it wrote, a line each: the review first, then its comments on lines | | ||
| `resolve`, `unresolve` | `resolved`, `unresolved` | | ||
| `list` | a line per item: `<kind>`, `<state>`, `<uri>`, `<title>`, tab-separated | | ||
| `show` | `<state> <kind> by <author>: <title>`, then `draft` for a draft pull, `review requested: <handles>` for one that asks for reviews, then `closed by <pull>` for an issue a pull closed, `mentioned in <pull>` for each pull whose title or body names an issue, oldest first, ending `, to close it` where it names it after a closing keyword, `reverts <pull>` for a revert, `reverted by <pull>` for a merged pull whose revert merged, then for a pull each reviewer's standing review as `review: <reviewer> <verdict> at <commit>` (the first 12 characters of the head it looked at), then for an open pull in or ejected from the merge queue `merge queue: <state> at <createdAt>` and where it stands, then the body and each comment as `<author> [<verdict>]: <body>`, where a comment on lines says ` on <path>:<lines>[:old] (<uri>[, resolved])` before the colon, and an answer in a thread ` answering <uri>` | | ||
| `show` | `<state> <kind> by <author>: <title>`, then `draft` for a draft pull, `review requested: <handles>` for one that asks for reviews, then `closed by <pull>` for an issue a pull closed, `mentioned in <pull>` for each pull whose title or body names an issue, oldest first, ending `, to close it` where it names it after a closing keyword, `reverts <pull>` for a revert, `reverted by <pull>` for a merged pull whose revert merged, then for a pull each reviewer's standing review as `review: <reviewer> <verdict> at <commit>` (the first 12 characters of the head it looked at), then for an open pull in or ejected from the merge queue `merge queue: <state> at <createdAt>` and where it stands, and for one ejected as checks failed `logs: <command>`, the `knotless checks` that reads their logs, then the body and each comment as `<author> [<verdict>]: <body>`, where a comment on lines says ` on <path>:<lines>[:old] (<uri>[, resolved])` before the colon, and an answer in a thread ` answering <uri>` | | ||
| `merge` | the commit the branch is now at, then `deleted <branch>` and a `closed <issue>` line per issue it closed; queued instead, `<base> takes pulls through the merge queue: queued, <n> of <of>`, then where to follow it | | ||
| `merge --queue` | `queued into <base>: <n> of <of>`, then where to follow it | | ||
| `merge --check` | `<state>`, `<method>` and `checks <checks>`, and `reviews <passed or blocked>` when its branch asks for reviews, tab-separated, then a `missing <what>` line for each thing its reviews lack (`reviews 1 of 2`, `owners of web/: @alice or @bob`, `changes asked by @carol`; owners who are the pull's author alone add `, its author alone, whose own approval never counts, so web/ needs another owner in CODEOWNERS`), an `uncounted <why>` line for each approval that does not count (`@dan's approval does not count: only the owner's and collaborators' approvals count`), a `warning <what>` line for each CODEOWNERS line skipped, a `conflict <path>` line per file that conflicts, `deletes <branch>` for the branch it would delete, `may <what>`, `queue only` when its branch takes pulls only through the merge queue, and last why it cannot merge, in the words `merge` would refuse with, and that the caller may not merge it when it may not | | ||
harness/shots/fixtures.json+2−2
| @@ -237,7 +237,7 @@ | ||
"content-type": "application/json", | ||
"cache-control": "no-cache" | ||
}, | ||
"body": "{\"branches\":[{\"branch\":\"main\",\"pulls\":[{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhbuzv5n3\",\"kind\":\"pull\",\"title\":\"Count bytes in files that are not UTF-8\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T12:31:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-15T09:30:00.000Z\",\"said\":\"In a batch of 2 being checked\",\"batch\":0},{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhc5eopws\",\"kind\":\"pull\",\"title\":\"Line the columns up with tabs\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T14:22:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-16T09:30:00.000Z\",\"said\":\"In a batch of 2 being checked\",\"batch\":0},{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcffbj7l\",\"kind\":\"pull\",\"title\":\"Count words in any script\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T16:13:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-17T09:30:00.000Z\",\"said\":\"Queued, 3rd of 3\"}],\"batches\":[{\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"state\":\"checking\",\"size\":2,\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6.226488be4afaf037c2331c7480d4a48790f20eba.build\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"build\",\"status\":\"success\",\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"startedAt\":\"2026-10-20T10:00:00Z\",\"finishedAt\":\"2026-10-20T10:01:35.000Z\",\"images\":[]},{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"running\",\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"startedAt\":\"2026-10-20T10:00:00Z\",\"images\":[]}]}]}],\"ejected\":[{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcnn4cic\",\"kind\":\"pull\",\"title\":\"Print a total for many files\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T18:04:00.000Z\",\"comments\":0},\"ejectedAt\":\"2026-10-19T15:02:00.000Z\",\"reason\":\"its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure). Their logs: `knotless checks knotless://bob.test/tally --queue main --log`\",\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.6f89484c63a36649168e3d77a1e631f695469b74.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"failure\",\"commit\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"startedAt\":\"2026-10-19T15:00:00Z\",\"finishedAt\":\"2026-10-19T15:01:35.000Z\",\"images\":[]}]}],\"landed\":[{\"branch\":\"main\",\"commit\":\"226488be4afaf037c2331c7480d4a48790f20eba\",\"landedAt\":\"2026-10-19T09:01:35.000Z\",\"pulls\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhbacgmx6\",\"kind\":\"pull\",\"title\":\"Show usage on --help\",\"state\":\"merged\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T08:49:00.000Z\",\"comments\":0}]}]}" | ||
"body": "{\"branches\":[{\"branch\":\"main\",\"pulls\":[{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhbuzv5n3\",\"kind\":\"pull\",\"title\":\"Count bytes in files that are not UTF-8\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T12:31:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-15T09:30:00.000Z\",\"said\":\"In a batch of 2 being checked\",\"batch\":0},{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhc5eopws\",\"kind\":\"pull\",\"title\":\"Line the columns up with tabs\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T14:22:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-16T09:30:00.000Z\",\"said\":\"In a batch of 2 being checked\",\"batch\":0},{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcffbj7l\",\"kind\":\"pull\",\"title\":\"Count words in any script\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T16:13:00.000Z\",\"comments\":0},\"queuedAt\":\"2026-10-17T09:30:00.000Z\",\"said\":\"Queued, 3rd of 3\"}],\"batches\":[{\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"state\":\"checking\",\"size\":2,\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6.226488be4afaf037c2331c7480d4a48790f20eba.build\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"build\",\"status\":\"success\",\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"startedAt\":\"2026-10-20T10:00:00Z\",\"finishedAt\":\"2026-10-20T10:01:35.000Z\",\"images\":[]},{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"running\",\"commit\":\"ee347cbe693bbc8c7f70dbb7b30bc329f34dcfa6\",\"startedAt\":\"2026-10-20T10:00:00Z\",\"images\":[]}]}]}],\"ejected\":[{\"pull\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcnn4cic\",\"kind\":\"pull\",\"title\":\"Print a total for many files\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T18:04:00.000Z\",\"comments\":0},\"ejectedAt\":\"2026-10-19T15:02:00.000Z\",\"reason\":\"its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure)\",\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.6f89484c63a36649168e3d77a1e631f695469b74.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"failure\",\"commit\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"startedAt\":\"2026-10-19T15:00:00Z\",\"finishedAt\":\"2026-10-19T15:01:35.000Z\",\"images\":[]}]}],\"landed\":[{\"branch\":\"main\",\"commit\":\"226488be4afaf037c2331c7480d4a48790f20eba\",\"landedAt\":\"2026-10-19T09:01:35.000Z\",\"pulls\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhbacgmx6\",\"kind\":\"pull\",\"title\":\"Show usage on --help\",\"state\":\"merged\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T08:49:00.000Z\",\"comments\":0}]}]}" | ||
}, | ||
"/xrpc/dev.knotless.getRepo?repo=at%3A%2F%2Falice.test%2Fdev.knotless.repo%2Fscratch": { | ||
"status": 200, | ||
| @@ -341,7 +341,7 @@ | ||
"content-type": "application/json", | ||
"cache-control": "no-cache" | ||
}, | ||
"body": "{\"item\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcnn4cic\",\"kind\":\"pull\",\"title\":\"Print a total for many files\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T18:04:00.000Z\",\"comments\":0},\"base\":\"main\",\"head\":{\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"branch\":\"totals\"},\"comments\":[],\"closes\":[],\"queue\":{\"state\":\"ejected\",\"reason\":\"its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure). Their logs: `knotless checks knotless://bob.test/tally --queue main --log`\",\"createdAt\":\"2026-10-19T15:02:00.000Z\",\"said\":\"Ejected: its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure). Their logs: `knotless checks knotless://bob.test/tally --queue main --log`. Failed: test (failure)\",\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.6f89484c63a36649168e3d77a1e631f695469b74.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"failure\",\"commit\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"startedAt\":\"2026-10-19T15:00:00Z\",\"finishedAt\":\"2026-10-19T15:01:35.000Z\",\"images\":[]}]},\"checks\":[],\"minutes\":[{\"runner\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"minutes\":2140,\"allowance\":2000,\"freeing\":[{\"at\":\"2026-11-02T00:00:00Z\",\"minutes\":400}],\"updatedAt\":\"2026-10-25T17:52:00Z\"}],\"changes\":{\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"commits\":[{\"sha\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"parents\":[{\"sha\":\"226488be4afaf037c2331c7480d4a48790f20eba\"}],\"author\":{\"name\":\"Bob Tanaka\",\"actor\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"}},\"coauthors\":[],\"date\":\"2026-10-11T12:00:00Z\",\"committedAt\":\"2026-10-11T12:00:00Z\",\"message\":\"Print a total for many files\"}],\"files\":[{\"old\":\"docs/totals.md\",\"new\":\"docs/totals.md\",\"status\":\"added\",\"added\":1,\"removed\":0,\"rows\":[],\"rowCount\":2,\"hunkCount\":1}]},\"card\":\"/bob.test/tally/pull/did:plc:55j6e4wxzlzndxm4nov2lz7x/3myvwhcnn4cic/card.png?v=0bde049a81b5f11e\"}" | ||
"body": "{\"item\":{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.pull/3myvwhcnn4cic\",\"kind\":\"pull\",\"title\":\"Print a total for many files\",\"state\":\"open\",\"author\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"createdAt\":\"2026-10-10T18:04:00.000Z\",\"comments\":0},\"base\":\"main\",\"head\":{\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"branch\":\"totals\"},\"comments\":[],\"closes\":[],\"queue\":{\"state\":\"ejected\",\"reason\":\"its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure)\",\"createdAt\":\"2026-10-19T15:02:00.000Z\",\"said\":\"Ejected: its batch failed its checks at 6f89484c63a3, on main at 226488be4afa: test (failure). Failed: test (failure)\",\"checks\":[{\"uri\":\"at://did:plc:55j6e4wxzlzndxm4nov2lz7x/dev.knotless.check/qdjbmvovu2tnodagwluxk6jxmq.3myvwh72yvtgz.gkn5el4746pzeyvms3btnm2gqt.6f89484c63a36649168e3d77a1e631f695469b74.226488be4afaf037c2331c7480d4a48790f20eba.test\",\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"workflow\":\"test\",\"status\":\"failure\",\"commit\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"startedAt\":\"2026-10-19T15:00:00Z\",\"finishedAt\":\"2026-10-19T15:01:35.000Z\",\"images\":[]}]},\"checks\":[],\"minutes\":[{\"runner\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"},\"minutes\":2140,\"allowance\":2000,\"freeing\":[{\"at\":\"2026-11-02T00:00:00Z\",\"minutes\":400}],\"updatedAt\":\"2026-10-25T17:52:00Z\"}],\"changes\":{\"repo\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\",\"name\":\"tally\"},\"commits\":[{\"sha\":\"6f89484c63a36649168e3d77a1e631f695469b74\",\"parents\":[{\"sha\":\"226488be4afaf037c2331c7480d4a48790f20eba\"}],\"author\":{\"name\":\"Bob Tanaka\",\"actor\":{\"did\":\"did:plc:55j6e4wxzlzndxm4nov2lz7x\",\"handle\":\"bob.test\"}},\"coauthors\":[],\"date\":\"2026-10-11T12:00:00Z\",\"committedAt\":\"2026-10-11T12:00:00Z\",\"message\":\"Print a total for many files\"}],\"files\":[{\"old\":\"docs/totals.md\",\"new\":\"docs/totals.md\",\"status\":\"added\",\"added\":1,\"removed\":0,\"rows\":[],\"rowCount\":2,\"hunkCount\":1}]},\"card\":\"/bob.test/tally/pull/did:plc:55j6e4wxzlzndxm4nov2lz7x/3myvwhcnn4cic/card.png?v=0bde049a81b5f11e\"}" | ||
}, | ||
"/xrpc/dev.knotless.getTree?repo=at%3A%2F%2Falice.test%2Fdev.knotless.repo%2Fscratch": { | ||
"status": 200, | ||
harness/shots/world.mjs+1−1
| @@ -697,7 +697,7 @@ export async function create() { | ||
} | ||
await write('dev.knotless.queue', { | ||
repo, subject: ejected, state: 'ejected', commit: tip('totals'), checks: failed, | ||
reason: `its batch failed its checks at ${tip('totals').slice(0, 12)}, on main at ${landed.slice(0, 12)}: test (failure). Their logs: \`knotless checks knotless://bob.test/tally --queue main --log\``, | ||
reason: `its batch failed its checks at ${tip('totals').slice(0, 12)}, on main at ${landed.slice(0, 12)}: test (failure)`, | ||
createdAt: '2026-10-19T15:02:00.000Z', | ||
}) | ||
} | ||