~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

A private repository's pages no longer wait for its space to sync

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:serve/private-settings-fast
crates/knotless-serve/src/web/settings.rs+93−65
@@ -98,13 +98,6 @@ pub async fn get_repo_settings(
Query(p): Query<SettingsParams>,
) -> Result<Response> {
let repo = repo::found(&app, &p.repo).await?;
let url = url_of(&repo);
// A private one is read as the member asking, whom finding it proved
// one; a public one as this server.
let reader = match crate::private::viewer().filter(|_| repo.in_space.is_some()) {
Some(viewer) => crate::login::ctx_for(&app, &viewer),
None => app.ctx.clone(),
};
// As the index has it, which a write from the pages updates at once,
// else as the mirror last synced.
let record = repo.record(&app).unwrap_or(repo.synced.clone());
@@ -118,6 +111,23 @@ pub async fn get_repo_settings(
}
}
let gates = futures::future::join_all(gated.into_iter().map(|did| actor(&app, did))).await;
// Who reads a private one but does not push, as its record says, which
// sharing it writes and the appview is shown at once: its owner's word,
// shown to its owner alone, as only they may list its space's members.
let mut readers = None;
if repo.in_space.is_some() {
let owner = crate::private::viewer().is_some_and(|v| v == repo.did);
let writers: Vec<&str> = record.writers().collect();
let named = record.readers.iter().map(|r| r.did.as_str());
let mut read: Vec<&str> = Vec::new();
for did in named.chain(record.runners.iter().map(|r| r.did.as_str())) {
if owner && !writers.contains(&did) && !read.contains(&did) {
read.push(did);
}
}
readers =
Some(futures::future::join_all(read.into_iter().map(|did| actor(&app, did))).await);
}
let mut triagers = Vec::new();
for t in record.triagers {
triagers.push(TriagerView::new(&app, t).await);
@@ -126,13 +136,7 @@ pub async fn get_repo_settings(
Some(to) => Some(basic(&app, to).await),
None => None,
};
let mut readers = None;
if repo.in_space.is_some() {
// Only its owner may list them; anyone else is shown none.
let members = manage::members(&reader, &url).await.unwrap_or_default();
let read = members.iter().filter(|m| m.access == "read");
readers = Some(futures::future::join_all(read.map(|m| actor(&app, &m.did))).await);
}
let owners = crate::login::ctx_for(&app, &repo.did);
let delegated = knotless::gateway::delegated(&owners, &repo.did);
Ok(answer(Settings {
@@ -153,86 +157,105 @@ pub async fn get_repo_settings(
}))
}
/// The most repositories whose storage [`Counts`] keeps: once full, it
/// starts again, as counting one again costs only that page a wait.
const COUNTS_KEPT: usize = 4096;
/// The most repositories [`Kept`] keeps for: once full, it starts again,
/// as reading one again costs only that page a wait.
const KEPT_MOST: usize = 4096;
/// What reading a repository's PDS found, by two names for it (a public
/// one's owner and key; a private one's space, and none), with how
/// far it had moved then and when it was read: good until it moves on,
/// or, unless `watching` says something tells of it moving, for `ttl`.
pub(crate) struct Kept<T>(Mutex<HashMap<(String, String), Found<T>>>);
/// Each public repository's storage as last counted, by its owner's DID
/// and key, with the events of that repository seen then
/// ([`App::stamp`]) and when it was counted: good until another such
/// event, or, with no firehose watched, for as long as a mirror is.
#[derive(Default)]
pub(crate) struct Counts(Mutex<HashMap<(String, String), Counted>>);
/// What was read, with how far its repository had moved, and when.
type Found<T> = (u64, Instant, T);
/// A repository's storage, with its events seen and the time it was counted.
type Counted = (u64, Instant, Storage);
/// Each repository's storage as last counted ([`get_repo_storage`]).
pub(crate) type Counts = Kept<Storage>;
impl Counts {
/// What `did`'s repository `key` was counted at, if nothing of it has
/// been seen since, `seen` being its events now, and it is not older
/// than `ttl` when `watching` is unset.
impl<T> Default for Kept<T> {
fn default() -> Self {
Kept(Mutex::default())
}
}
impl<T: Clone> Kept<T> {
/// What was read of `first` and `second`, if it has not moved since,
/// `seen` being how far it has now, and it is not older than `ttl`
/// when `watching` is unset.
fn get(
&self,
did: &str,
key: &str,
first: &str,
second: &str,
seen: u64,
watching: bool,
ttl: Duration,
) -> Option<Storage> {
) -> Option<T> {
let kept = self.0.lock().unwrap();
let (then, at, storage) = kept.get(&(did.to_string(), key.to_string()))?;
let (then, at, found) = kept.get(&(first.to_string(), second.to_string()))?;
let fresh = *then == seen && (watching || at.elapsed() < ttl);
fresh.then(|| storage.clone())
fresh.then(|| found.clone())
}
fn put(&self, did: &str, key: &str, seen: u64, storage: Storage, most: usize) {
fn put(&self, first: &str, second: &str, seen: u64, found: T, most: usize) {
let mut kept = self.0.lock().unwrap();
if kept.len() >= most {
kept.clear();
}
kept.insert((did.into(), key.into()), (seen, Instant::now(), storage));
kept.insert((first.into(), second.into()), (seen, Instant::now(), found));
}
/// Has `did`'s repository `key` counted again, after a write here
/// that the firehose has yet to tell of.
pub(crate) fn forget(&self, did: &str, key: &str) {
self.0
.lock()
.unwrap()
.remove(&(did.to_string(), key.to_string()));
/// Has all that was read under `first` read again, after a write here
/// that nothing has told of yet.
pub(crate) fn forget(&self, first: &str) {
self.0.lock().unwrap().retain(|(f, _), _| f != first);
}
}
/// What a repository's storage is kept under: a public one's owner and
/// key, or a private one's space.
fn counted_as(repo: &Repo) -> (String, String) {
match &repo.in_space {
Some(space) => (space.clone(), String::new()),
None => (repo.did.clone(), repo.key.clone()),
}
}
/// `dev.knotless.getRepoStorage`: counted from its pack records, which a
/// sync no longer reads, so apart from its settings, and kept.
/// sync no longer reads, so apart from its settings, and kept until the
/// repository moves: a public one's as the firehose tells of it, a
/// private one's as a sync of its space finds it.
pub async fn get_repo_storage(
State(app): State<Arc<App>>,
Query(p): Query<SettingsParams>,
) -> Result<Response> {
let repo = repo::found(&app, &p.repo).await?;
let url = url_of(&repo);
// A private one is read as the member asking, and counted each time:
// no firehose tells of its writes.
// A private one is read as the member asking, whom finding it proved
// one; a public one as this server.
let private = crate::private::viewer().filter(|_| repo.in_space.is_some());
if let Some(viewer) = private {
let reader = crate::login::ctx_for(&app, &viewer);
let counted = knotless::storage::storage(&reader, &url).await;
return Ok(answer(counted.map_err(AppError::upstream)?));
}
// Its events taken before counting, so one seen meanwhile has it
// counted again next time.
let seen = app.stamp(&repo.did, Some(&repo.key)).1;
let watching = app.watching.load(Ordering::Relaxed);
let kept = app
.counts
.get(&repo.did, &repo.key, seen, watching, app.ttl);
let reader = match &private {
Some(viewer) => crate::login::ctx_for(&app, viewer),
None => app.ctx.clone(),
};
// How far it has moved, taken before counting, so a move meanwhile
// has it counted again next time.
let (first, second) = counted_as(&repo);
let (seen, watching) = match &repo.in_space {
Some(space) => (app.moves_of(space), app.space_followed(space)),
None => (
app.stamp(&repo.did, Some(&repo.key)).1,
app.watching.load(Ordering::Relaxed),
),
};
let kept = app.counts.get(&first, &second, seen, watching, app.ttl);
if let Some(storage) = kept {
return Ok(answer(storage));
}
let counted = knotless::storage::storage(&app.ctx, &url).await;
let counted = knotless::storage::storage(&reader, &url).await;
let storage = counted.map_err(AppError::upstream)?;
app.counts
.put(&repo.did, &repo.key, seen, storage.clone(), COUNTS_KEPT);
.put(&first, &second, seen, storage.clone(), KEPT_MOST);
Ok(answer(storage))
}
@@ -394,7 +417,7 @@ pub async fn compact_repo(
let (repo, ctx) = owned(&app, viewer, &headers, &input.repo, "compact it").await?;
let compacted = knotless::compact::compact_repo(&ctx, &url_of(&repo)).await;
let c = compacted.map_err(refused)?;
app.counts.forget(&repo.did, &repo.key);
app.counts.forget(&counted_as(&repo).0);
wrote(&app, &repo.did, &[]).await?;
Ok(answer(Compacted {
packs: c.before,
@@ -532,7 +555,7 @@ mod tests {
use super::*;
#[test]
fn counts_keep_until_the_repository_moves() {
fn kept_until_the_repository_moves() {
let counts = Counts::default();
let one = Storage {
packs: 1,
@@ -542,15 +565,20 @@ mod tests {
assert_eq!(counts.get("d", "k", 0, true, day), None);
counts.put("d", "k", 3, one.clone(), 2);
assert_eq!(counts.get("d", "k", 3, true, day), Some(one.clone()));
// Another event of it, or none watched for longer than a mirror
// is trusted, and it is counted again.
// Moved on, or nothing watched for longer than `ttl`, and it is
// read again.
assert_eq!(counts.get("d", "k", 4, true, day), None);
assert_eq!(counts.get("d", "k", 3, false, Duration::ZERO), None);
assert_eq!(counts.get("d", "k", 3, false, day), Some(one.clone()));
// Another repository's is its own.
assert_eq!(counts.get("d", "j", 3, true, day), None);
counts.forget("d", "k");
// Forgotten, all under its first name go.
counts.put("d", "j", 3, one.clone(), 4);
counts.put("e", "k", 3, one.clone(), 4);
counts.forget("d");
assert_eq!(counts.get("d", "k", 3, true, day), None);
assert_eq!(counts.get("d", "j", 3, true, day), None);
assert_eq!(counts.get("e", "k", 3, true, day), Some(one.clone()));
// Full, it starts again.
counts.put("d", "a", 0, one.clone(), 2);
counts.put("d", "b", 0, one.clone(), 2);
crates/knotless-serve/src/lib.rs+10−8
@@ -87,15 +87,16 @@ pub struct App {
account_events: Mutex<HashMap<String, u64>>,
/// And those that change one repository, by its owner's DID and key.
repo_events: Mutex<HashMap<(String, String), u64>>,
/// Each public repository's storage as last counted, kept until it
/// moves (`dev.knotless.getRepoStorage`).
/// Each repository's storage as last counted, kept until it moves
/// (`dev.knotless.getRepoStorage`).
pub(crate) counts: web::Counts,
/// The last commit, by its rev, that wrote to each DID's repository
/// collections ([`first_of`]).
revs: Mutex<HashMap<String, String>>,
pub watching: AtomicBool,
/// Syncs of each space that found something written there, by URI.
moves: Mutex<HashMap<String, u64>>,
/// Syncs of each space that found something written there, by URI:
/// shared with a sync run aside from the read that asked for it.
moves: Arc<Mutex<HashMap<String, u64>>>,
/// Set while the spaces kept are synced ([`spaces::follow`]): a
/// private repository's mirror is then fresh until its space moves.
pub following: AtomicBool,
@@ -128,10 +129,11 @@ pub struct App {
/// Wakes [`spaces::follow`] for what is due.
told: tokio::sync::Notify,
/// When each space was last synced in full, as anyone here synced it:
/// a read syncs it itself once that is [`spaces::STALE`] ago, as when
/// a read syncs it itself, aside unless it never was, once that is
/// [`spaces::STALE`] ago, as when
/// the session of the member it is synced as no longer reads it. Its
/// entries taken out, every space counts as not synced lately.
pub last_synced: Mutex<HashMap<String, Instant>>,
pub last_synced: Arc<Mutex<HashMap<String, Instant>>>,
/// When this server last asked each space's authority to tell it of
/// writes there.
registered: tokio::sync::Mutex<HashMap<String, Instant>>,
@@ -246,7 +248,7 @@ impl App {
counts: web::Counts::default(),
revs: Mutex::default(),
watching: AtomicBool::new(false),
moves: Mutex::default(),
moves: Default::default(),
following: AtomicBool::new(false),
feeds: Mutex::default(),
stale: None,
@@ -259,7 +261,7 @@ impl App {
with_spaces: Mutex::default(),
due: Mutex::default(),
told: tokio::sync::Notify::new(),
last_synced: Mutex::default(),
last_synced: Default::default(),
registered: tokio::sync::Mutex::default(),
code: None,
usage: None,
crates/knotless-serve/src/private.rs+26−1
@@ -210,7 +210,7 @@ impl App {
let events = self.stamp(did, entry_key.as_deref());
// A space no one here has a session for is synced only as it is
// read, with what proved its reader a member.
let followed = self.following.load(Ordering::Relaxed) && self.syncer_of(&space).is_some();
let followed = self.space_followed(&space);
let fresh = match (&entry.git, entry.synced) {
// Nothing is written to a space but its sync finds, within
// seconds, or the pages touch.
@@ -321,6 +321,13 @@ impl App {
};
}
/// Whether the space `uri` is synced as its authority tells of writes
/// there, so that it is current until a sync of it finds one
/// ([`App::moves_of`]).
pub(crate) fn space_followed(&self, uri: &str) -> bool {
self.following.load(Ordering::Relaxed) && self.syncer_of(uri).is_some()
}
/// The member whose session here syncs the space `uri`, if any.
fn syncer_of(&self, uri: &str) -> Option<String> {
let index = self.spaces.as_ref()?;
@@ -347,6 +354,24 @@ impl App {
return Ok(());
}
let ctx = crate::login::syncer_ctx(self, syncer);
// Synced before, it is read as it was while it syncs aside, as a
// sync reads the whole space and can take seconds: only one never
// synced has nothing to read until its sync is done. Taken as
// synced now, so reads meanwhile start no other.
if synced {
self.synced_now(space);
let (moves, last) = (self.moves.clone(), self.last_synced.clone());
let (index, space) = (index.clone(), space.to_string());
tokio::spawn(async move {
let read = crate::spaces::sync(&ctx, &index, &space).await;
last.lock().unwrap().insert(space.clone(), Instant::now());
if matches!(read, Ok(true)) {
*moves.lock().unwrap().entry(space.clone()).or_default() += 1;
}
crate::telemetry::space_synced(&space, read.map(drop));
});
return Ok(());
}
let read = crate::spaces::sync(&ctx, index, space).await;
self.read_now(space, read, synced)
}
crates/knotless-serve/tests/gateway.rs+44−3
@@ -3562,6 +3562,37 @@ fn private_repositories_synced_by_readers_once_their_syncer_lapses() {
let syncers = server.app.syncers();
assert!(syncers.iter().any(|(_, d)| *d == did), "{syncers:?}");
// Once it was not synced lately, the owner's read is answered from the
// index as it was, and the space syncs aside, taking in what was
// written since.
run(&member, &["issue", &url, "Written while synced"]);
server.app.last_synced.lock().unwrap().clear();
let owners = || {
let res = client
.get(format!(
"{}/xrpc/dev.knotless.listItems?repo={at}&kind=issue",
server.base
))
.header("cookie", &cookie)
.send()
.unwrap();
assert_eq!(res.status().as_u16(), 200);
res.json::<Value>().unwrap()["items"].to_string()
};
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(20);
while !owners().contains("Written while synced") {
assert!(std::time::Instant::now() < deadline, "never synced aside");
std::thread::sleep(std::time::Duration::from_millis(100));
}
assert!(
server
.app
.last_synced
.lock()
.unwrap()
.contains_key(&knotless::space::uri(&did, "ledger"))
);
// Their session here lapses, and they stay its syncer: each sync as
// them fails, though not as one refused, which would let it go.
let pages = knotless_serve::login::ctx_for(&server.app, &did);
@@ -3599,7 +3630,10 @@ fn private_repositories_synced_by_readers_once_their_syncer_lapses() {
// Once it was not synced lately, a member's read syncs it as them.
stale();
assert_eq!(listed(), ["Written after the lapse"]);
assert_eq!(
listed(),
["Written after the lapse", "Written while synced"]
);
// A member whose repo cannot be read fails the sync: one who writes,
// then is taken down where they live. The read is answered from the
@@ -3620,7 +3654,10 @@ fn private_repositories_synced_by_readers_once_their_syncer_lapses() {
};
takedown(true);
stale();
assert_eq!(listed(), ["Written after the lapse"]);
assert_eq!(
listed(),
["Written after the lapse", "Written while synced"]
);
assert!(server.app.last_synced.lock().unwrap().contains_key(&space));
// Restored, it is taken in at the next read that finds it stale.
@@ -3628,7 +3665,11 @@ fn private_repositories_synced_by_readers_once_their_syncer_lapses() {
stale();
assert_eq!(
listed(),
["Written after the lapse", "Written before the takedown"]
[
"Written after the lapse",
"Written before the takedown",
"Written while synced"
]
);
}
crates/knotless-serve/tests/pages.rs+35−0
@@ -869,6 +869,32 @@ fn private_repositories_settings_and_forks() {
assert_eq!((status, &storage["packs"]), (200, &json!(1)), "{storage}");
assert_eq!(browse("GET", &counted, None, None).0, 404);
assert_eq!(settings(None).0, 404);
// Who reads it but does not push, to its owner, as its record says:
// the member it was shared with, for reading, and one shared so from
// the pages, at once. A member is shown none.
let read = |set: &Value| -> Vec<String> {
let readers = set["readers"].as_array().unwrap().iter();
readers
.map(|r| r["did"].as_str().unwrap().to_string())
.collect()
};
assert_eq!(
read(&set),
std::slice::from_ref(&member.account.did),
"{set}"
);
let reader = person(account());
let sharing = json!({"repo": at, "with": reader.account.handle, "write": false});
let (status, shared) = server.call("shareRepo", me, sharing);
assert_eq!(status, 200, "{shared}");
let mut both = read(&settings(me).1);
both.sort();
let mut want = [member.account.did.clone(), reader.account.did.clone()];
want.sort();
assert_eq!(both, want);
let theirs = log_in(&server, &member.account);
let (status, set) = settings(Some(theirs.as_str()));
assert_eq!((status, &set["readers"]), (200, &json!([])), "{set}");
// Changed from the pages, they say so at once: read from this server's
// index of its space, which the change is put in as it is made.
let described = json!({"repo": at, "description": "Kept quiet"});
@@ -1097,6 +1123,15 @@ fn an_account_deletes_its_private_repositories() {
{"name": "secret", "private": true, "collaborators": 1, "members": 1},
]);
assert_eq!(held["repos"], repos);
// Its runner reads it, so its settings name it among those who do; the
// member pushes, so is not.
let at = format!("at://{}/dev.knotless.repo/secret", owner.account.did);
let asked = format!("{}?repo={at}", server.xrpc("getRepoSettings"));
let (status, _, set) = browse("GET", &asked, Some(&cookie), None);
assert_eq!(status, 200, "{set}");
let readers = set["readers"].as_array().unwrap();
let dids: Vec<&Value> = readers.iter().map(|r| &r["did"]).collect();
assert_eq!(dids, [&json!(runner.account.did)], "{set}");
let did = owner.account.did.clone();
let (answers, _) = server.delete_data(&cookie, &did, 1);
docs/design/access.md+5−2
@@ -294,8 +294,11 @@ repository on the pages is a repository in that index.
index, so syncing goes on across restarts.
- A space not synced within a minute, two of the sweeps that sync every
space every 30 seconds, or not since the server started, is synced by
whoever reads it next, with their own session or delegation token,
before the page is answered. Its syncer's sync can fail without its
whoever reads it next, with their own session or delegation token. A
read with a session is answered from the index as it was while the
sync runs aside, as a sync reads the whole space and can take seconds;
one with a delegation token waits for it. Only a space never synced
makes every reader wait. Its syncer's sync can fail without its
being refused, as when their session here lapses or its grant leaves
the space out; until they logged in again, every write since would
otherwise be missing from it, for every reader. A reader's sync that
docs/design/serve.md+5−2
@@ -299,8 +299,11 @@ Details:
is kept in memory by repository, up to 4,096 of them, until the firehose
tells of a write to that repository's packs, claims or refs, or the pages
compact it; with no firehose watched, for as long as a mirror is trusted.
A private repository's is counted each time, as no firehose tells of its
writes.
A private repository's is kept by its space until a sync of the space
finds a write there, as its mirror is ([access](access.md)). Who reads a
private repository, which its owner's settings list, is read from its
record, whose `readers` and `runners` sharing it writes, rather than
asked of its space: the client shows the appview each such write.
- A repository has three Atom feeds, for feed readers: `commits.atom`, the
newest 30 commits on its default branch; `tags.atom`, its newest 30 tags
that name a commit; and `releases.atom`, its newest 30 releases, with
docs/reference/api.md+1−1
@@ -600,7 +600,7 @@ name it has now.
| `dev.knotless.getEmails` | `{count}`, how many addresses the asker's `dev.knotless.emails` claims: only hashes are kept, so they cannot be listed. 401 with no asker |
| `dev.knotless.listBlocks` | the asker's blocks, the latest first, from its own repo: each `uri`, `subject` (an actor), `repo` when it is from one repository only, and `createdAt`. Those naming a private repository live in its space and are not listed. 401 with no asker |
| `dev.knotless.getRepoSettings?repo=` | what the owner sets: description, links, default branch, protected branches, labels, collaborators, triagers, gateway, an offer to move it (`transfer`), the gateway the owner delegated to from these pages (`delegated`), for a private one, to its owner alone, the members who read but do not push (`readers`), and the accounts its rules name as [gates](../design/collaboration.md#gates), each once (`gates`) |
| `dev.knotless.getRepoStorage?repo=` | what it takes up on its owner's PDS, as `knotless storage` counts it: kept per repository, and counted again only once a write to its packs, claims or refs is seen, or the pages compact it |
| `dev.knotless.getRepoStorage?repo=` | what it takes up on its owner's PDS, as `knotless storage` counts it: kept per repository, and counted again only once a write to its packs, claims or refs is seen (for a private one, once a sync of its space finds a write), or the pages compact it |
| `dev.knotless.getOrg?actor=` | an organization's members, as the index has its `dev.knotless.org` and `dev.knotless.orgMember` records and its members' `dev.knotless.orgAcceptance` ones: `{org, organization, inside, gateway?, members}`, `gateway` the one its public `dev.knotless.org` record names, each member `{actor, role, standing, invitedAt, expiresAt?, joinedAt?}`, the first invited first, `standing` one of `invited`, `lapsed`, `public` and `hidden`. To anyone, its members in public; to the organization and its members, logged in or with a service token (`inside`), its invitations and hidden members too, from its space, which this server keeps an index of; to an account it invited, its own entry. `organization: false`, with no members, for an account that is none; `ProfileNotFound` for an unknown account |
| `dev.knotless.getActorStars?actor=` | the public repositories an account starred, newest first, each with its `language`, `starCount` and `forkCount`: 404 with no index, `ProfileNotFound` for an unknown account |
| `dev.knotless.getMergeStatus?repo=&pull=` | whether an open pull can merge now, as `knotless merge --check` says ([below](#landing-and-checks)): 401 with no asker, 400 for a pull not open, 404 for none |
harness/quarantine.json+1−0
@@ -1,6 +1,7 @@
[
{"net": "spaces", "binary": "gateway (test)", "test": "private_repositories_on_the_pages", "until": "2026-11-04"},
{"net": "spaces", "binary": "gateway (test)", "test": "private_repositories_indexed_here", "until": "2026-11-04"},
{"net": "spaces", "binary": "mcp (test)", "test": "agents_fork_review_and_merge", "until": "2026-11-06"},
{"net": "spaces", "binary": "web (test)", "test": "searching_code", "until": "2026-11-10"},
{"net": "stock", "binary": "web (test)", "test": "searching_code", "until": "2026-11-10"}
]
lexicons/dev/knotless/getRepoStorage.json+1−1
@@ -5,7 +5,7 @@
"defs": {
"main": {
"type": "query",
"description": "What a repository's packs take up on its owner's PDS, as `knotless storage` counts them: counted again only once a write to the repository's packs, claims or refs is seen, or compaction from these pages folds them. Apart from dev.knotless.getRepoSettings, as counting reads every pack record from the PDS.",
"description": "What a repository's packs take up on its owner's PDS, as `knotless storage` counts them: counted again only once a write to the repository's packs, claims or refs is seen (for a private one, once a sync of its space finds a write there), or compaction from these pages folds them. Apart from dev.knotless.getRepoSettings, as counting reads every pack record from the PDS.",
"parameters": {
"type": "params",
"required": ["repo"],