~ 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

knotless-serve answers Cargo's registry for a repository's crates

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:serve/cargo

Only what this pull adds. A release not made says why, rather than always that it exists landed as e784063, so its changes do not show here again.

.changeset/cargo-registry.mdnew+7−0
@@ -0,0 +1,7 @@
---
default: minor
---
# A repository's crates can be published and used with Cargo
knotless-serve answers Cargo's registry protocol for each repository, so `cargo publish`, `cargo yank` and `cargo search` work with `index = "sparse+https://knotless.dev/cargo/<did>/<repo key>/"`, and the token is `<handle>:<app password>`.
crates/knotless/src/records.rs+12−0
@@ -981,12 +981,24 @@ pub struct ReleaseRecord {
pub notes: Option<String>,
pub files: Vec<ReleaseFile>,
pub created_at: String,
/// What Cargo's registry says of it, if it is a crate.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cargo: Option<CargoCrate>,
/// Whether its owner yanked it: read from its
/// [`nsid::RELEASE_YANK`] record, and no part of this one.
#[serde(skip)]
pub yanked: bool,
}
/// A release as a crate: its file is `<name>-<version>.crate`.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CargoCrate {
/// Its line of Cargo's index, as JSON, but for `yanked`.
pub index: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
/// That its owner yanked a release, keyed as the release is, which never
/// changes: deleting this undoes it.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
crates/knotless/src/release.rs+19−4
@@ -148,6 +148,7 @@ pub async fn publish(ctx: &Ctx, url: &RepoUrl, spec: Publish) -> Result<String>
commit: spec.commit,
check: spec.check,
notes: spec.notes,
cargo: None,
};
target.create(&auth, files, made).await
}
@@ -184,6 +185,7 @@ pub struct Made {
pub commit: Option<String>,
pub check: Option<String>,
pub notes: Option<String>,
pub cargo: Option<records::CargoCrate>,
}
/// Where a version of a package is to be published: a public repository,
@@ -248,6 +250,7 @@ impl Target {
notes: made.notes,
files,
created_at: records::now(),
cargo: made.cargo,
yanked: false,
};
let typed = records::typed(nsid::RELEASE, &record);
@@ -280,16 +283,27 @@ pub async fn yank(
undo: bool,
) -> Result<String> {
let remote = Remote::open(ctx, url).await?;
let auth = session::ensure(ctx, &remote.identity).await?.auth();
yank_as(&remote, &auth, package, version, undo).await
}
/// [`yank`], with `auth`, the owner's authority.
pub async fn yank_as(
remote: &Remote,
auth: &Auth,
package: &str,
version: &str,
undo: bool,
) -> Result<String> {
let Some(repo) = remote.repo().await? else {
bail!("{url}: no such repository");
bail!("{}: no such repository", remote.url);
};
let key = repo.value.key;
let rkey = keys::release_rkey(&key, package, version);
let found = remote.get_record::<ReleaseRecord>(nsid::RELEASE, &rkey);
if found.await?.is_none() {
bail!("{url} has published no {package} {version}");
bail!("{} has published no {package} {version}", remote.url);
}
let auth = session::ensure(ctx, &remote.identity).await?.auth();
let held = remote.get_record::<ReleaseYankRecord>(nsid::RELEASE_YANK, &rkey);
if held.await?.is_some() == undo {
let write = if undo {
@@ -302,7 +316,7 @@ pub async fn yank(
let typed = records::typed(nsid::RELEASE_YANK, &record);
remote::create(nsid::RELEASE_YANK, &rkey, typed)
};
remote.apply(vec![write], &auth).await?;
remote.apply(vec![write], auth).await?;
}
Ok(format!("at://{}/{}/{rkey}", remote.did(), nsid::RELEASE))
}
@@ -477,6 +491,7 @@ mod tests {
notes: None,
files: Vec::new(),
created_at: "2026-10-27T00:00:00.000Z".into(),
cargo: None,
yanked: false,
}
}
crates/knotless-serve/src/web/cargo.rsnew+663−0
663 lines changed: not loaded until asked for.
crates/knotless-serve/src/web/releases.rs+2−1
@@ -276,7 +276,7 @@ fn released(app: &App, repo: &Repo, rest: &str) -> Option<(String, ReleaseFile)>
/// What a release's file `f` is sent with: as bytes to save, never a page
/// of the site; checked again each time, so a takedown holds at once; and
/// `etag`, its hash, for a cache to ask by.
fn sending(f: &ReleaseFile, etag: String) -> [(HeaderName, String); 6] {
pub(super) fn sending(f: &ReleaseFile, etag: String) -> [(HeaderName, String); 6] {
let says = |s: &str| s.to_string();
let saved = format!("attachment; filename=\"{}\"", f.name);
[
@@ -320,6 +320,7 @@ mod tests {
notes: None,
files: files.collect(),
created_at: "2026-10-01T00:00:00Z".into(),
cargo: None,
yanked: false,
};
(
crates/knotless-serve/src/web/steps.rs+1−0
@@ -268,6 +268,7 @@ mod tests {
notes: None,
files: Vec::new(),
created_at: String::new(),
cargo: None,
yanked: false,
};
(v.into(), record, Value::Null)
crates/knotless-serve/src/web.rs+20−0
@@ -47,6 +47,7 @@ mod account_settings;
mod actor;
mod archive;
mod badges;
mod cargo;
mod controls;
mod delegation;
mod feed;
@@ -274,6 +275,25 @@ pub fn routes() -> Router<Arc<App>> {
"/{authority}/{repo}/releases/download/{*rest}",
get(releases::download),
)
.route("/cargo/{did}/{key}/config.json", get(cargo::config))
.route("/cargo/{did}/{key}/api/v1/crates", get(cargo::search))
.route(
"/cargo/{did}/{key}/api/v1/crates/new",
axum::routing::put(cargo::publish),
)
.route(
"/cargo/{did}/{key}/api/v1/crates/{name}/{version}/yank",
axum::routing::delete(cargo::yank),
)
.route(
"/cargo/{did}/{key}/api/v1/crates/{name}/{version}/unyank",
axum::routing::put(cargo::unyank),
)
.route(
"/cargo/{did}/{key}/dl/{name}/{version}/download",
get(cargo::download),
)
.route("/cargo/{did}/{key}/{*path}", get(cargo::index))
.route("/{authority}/{repo}/archive/{*rest}", get(archive::archive))
.merge(compressed)
.layer(middleware::from_fn(renamed))
crates/knotless-serve/tests/web.rs+362−0
@@ -3212,6 +3212,368 @@ fn delete_record(owner: &Sandbox, collection: &str, rkey: &str) {
assert!(deleted.status().is_success());
}
/// Real Cargo, with a home of its own, knowing nothing but `registries`.
struct Cargo {
home: std::path::PathBuf,
token: String,
}
impl Cargo {
fn run(&self, dir: &std::path::Path, args: &[&str]) -> (bool, String) {
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let mut command = std::process::Command::new(cargo);
for (var, _) in std::env::vars() {
// What building this test set, as a coverage run does.
let building = ["CARGO", "RUSTC", "RUSTFLAGS", "LLVM_", "RUSTDOC"];
if building.iter().any(|b| var.starts_with(b)) {
command.env_remove(var);
}
}
let out = command
.args(args)
.current_dir(dir)
.env("CARGO_HOME", &self.home)
.env("CARGO_TARGET_DIR", dir.join("target"))
.env("CARGO_TERM_COLOR", "never")
.env("CARGO_REGISTRIES_HERE_TOKEN", &self.token)
.output()
.unwrap();
let said = String::from_utf8_lossy(&out.stderr).into_owned();
(
out.status.success(),
said + &String::from_utf8_lossy(&out.stdout),
)
}
fn ok(&self, dir: &std::path::Path, args: &[&str]) -> String {
let (ok, said) = self.run(dir, args);
assert!(ok, "cargo {args:?}: {said}");
said
}
}
/// A crate's directory, of `toml` after its `[package]`, and `lib` or `main`.
fn cargo_crate(
root: &std::path::Path,
name: &str,
version: &str,
toml: &str,
file: &str,
code: &str,
) -> std::path::PathBuf {
let dir = root.join(format!("{name}-{version}"));
std::fs::create_dir_all(dir.join("src")).unwrap();
let manifest = format!(
"[package]\nname = \"{name}\"\nversion = \"{version}\"\nedition = \"2021\"\ndescription = \"says {name}\"\nlicense = \"MIT\"\n{toml}\n"
);
std::fs::write(dir.join("Cargo.toml"), manifest).unwrap();
std::fs::write(dir.join("src").join(file), code).unwrap();
dir
}
#[test]
fn a_cargo_registry() {
use serde_json::json;
const PUBLISH: [&str; 4] = ["publish", "--registry", "here", "--allow-dirty"];
let site = serve(true);
let owner = person(account(), &site);
let handle = owner.account.handle.clone();
let url = owner.url("crates");
ok(&owner, &["repo", "create", &url]);
let work = owner.work("w", 1);
owner.git(&work, &["push", "-q", &url, "main"]);
let did = owner.account.did.clone();
let key = owner.record("dev.knotless.repo", "crates")["key"]
.as_str()
.unwrap()
.to_string();
let here = format!("/cargo/{did}/{key}");
wait_until(|| site.status(&format!("{here}/config.json")).0 == 200);
let config = site.json(&format!("{here}/config.json"));
assert_eq!(
config["api"].as_str().unwrap(),
format!("{}{here}", site.base)
);
assert_eq!(
config["dl"].as_str().unwrap(),
format!("{}{here}/dl/{{crate}}/{{version}}/download", site.base)
);
let root = owner.path("cargo");
let home = root.join("home");
std::fs::create_dir_all(&home).unwrap();
std::fs::write(
home.join("config.toml"),
format!(
"[registries.here]\nindex = \"sparse+{}{here}/\"\n",
site.base
),
)
.unwrap();
let cargo = Cargo {
home,
token: format!("{handle}:{}", owner.account.password),
};
// A crate, and one depending on it, renamed and with a namespaced feature.
let leaf = cargo_crate(
&root,
"leaf",
"1.0.0",
"[features]\nnothing = []\n",
"lib.rs",
"pub fn says() -> u8 { 1 }\n",
);
cargo.ok(&leaf, &PUBLISH);
let extra = cargo_crate(
&root,
"extra",
"1.0.0",
"",
"lib.rs",
"pub fn says() -> u8 { 2 }\n",
);
cargo.ok(&extra, &PUBLISH);
let mid = cargo_crate(
&root,
"mid",
"0.2.0",
"[dependencies]\nnamed = { package = \"leaf\", version = \"1\", registry = \"here\" }\nextra = { version = \"1\", registry = \"here\", optional = true }\n[features]\nwith-extra = [\"dep:extra\"]\n",
"lib.rs",
"pub fn says() -> u8 { named::says() + 40 }\n",
);
let said = cargo.ok(&mid, &PUBLISH);
// Found at once, with no wait for the firehose.
assert!(!said.contains("timed out"), "{said}");
assert!(said.contains("Published mid v0.2.0"), "{said}");
// The index's line and a read of it, by its ETag.
let line = site.status(&format!("{here}/3/m/mid")).1;
let line: Value = serde_json::from_str(line.trim()).unwrap();
assert_eq!(
(line["vers"].as_str(), line["v"].as_i64()),
(Some("0.2.0"), Some(2))
);
assert_eq!(line["yanked"], false);
let index = format!("{}{here}/3/m/mid", site.base);
let first = reqwest::blocking::get(&index).unwrap();
let etag = first.headers()["etag"].to_str().unwrap().to_string();
let again = reqwest::blocking::Client::new()
.get(&index)
.header("if-none-match", &etag)
.send()
.unwrap();
assert_eq!(again.status(), 304);
assert_eq!(site.status(&format!("{here}/m/id/mid")).0, 404);
assert_eq!(site.status(&format!("{here}/no/ne/none")).0, 404);
assert_eq!(
site.status(&format!("{here}/dl/none/1.0.0/download")).0,
404
);
// Used by something that names only the registry, from nothing kept.
let app_toml = "[dependencies]\nmid = { version = \"0.2\", registry = \"here\", features = [\"with-extra\"] }\n";
let app = cargo_crate(
&root,
"app",
"0.0.1",
app_toml,
"main.rs",
"fn main() { println!(\"{}\", mid::says()); }\n",
);
let said = cargo.ok(&app, &["run", "-q"]);
assert!(said.contains("41"), "{said}");
// Refused: what is there, a token that is no good, another's, or none.
let (done, said) = cargo.run(&leaf, &PUBLISH);
assert!(!done && said.contains("already exists"), "{said}");
let other = cargo_crate(&root, "other", "1.0.0", "", "lib.rs", "");
let denied = |token: &str| {
let cargo = Cargo {
home: cargo.home.clone(),
token: token.into(),
};
let (done, said) = cargo.run(&other, &PUBLISH);
assert!(!done && said.contains("403"), "{said}");
said
};
assert!(denied(&format!("{handle}:wrong")).contains("could not log in"));
assert!(denied("nocolon").contains("log in: cargo login"));
let stranger = account();
let theirs = format!("{}:{}", stranger.handle, stranger.password);
assert!(denied(&theirs).contains("only "));
// What Cargo would never send is refused too.
let put = |crate_: &[u8], said: Value, token: &str| {
let said = said.to_string();
let mut body = (said.len() as u32).to_le_bytes().to_vec();
body.extend(said.as_bytes());
body.extend((crate_.len() as u32).to_le_bytes());
body.extend(crate_);
reqwest::blocking::Client::new()
.put(format!("{}{here}/api/v1/crates/new", site.base))
.header("authorization", token)
.body(body)
.send()
.unwrap()
};
let packed = |dir: &std::path::Path, name: &str, version: &str| {
cargo.ok(
dir,
&[
"package",
"--allow-dirty",
"--no-verify",
"--registry",
"here",
],
);
std::fs::read(
dir.join("target/package")
.join(format!("{name}-{version}.crate")),
)
.unwrap()
};
let token = cargo.token.as_str();
let crate_ = packed(&other, "other", "1.0.0");
let says = |res: reqwest::blocking::Response| (res.status().as_u16(), res.text().unwrap());
let meta = |name: &str, version: &str| json!({"name": name, "vers": version});
let (status, said) = says(put(&crate_, meta("claims", "1.0.0"), token));
assert!(
status == 400 && said.contains("its Cargo.toml is not"),
"{said}"
);
let (status, said) = says(put(&crate_, meta("Other", "1.0.0"), token));
assert!(status == 400 && said.contains("package's name"), "{said}");
let (status, said) = says(put(b"not a crate", meta("other", "1.0.0"), token));
assert!(status == 400 && said.contains("no Cargo.toml"), "{said}");
let (status, said) = says(put(&crate_, json!("no"), token));
assert_eq!(status, 400, "{said}");
let raw = reqwest::blocking::Client::new()
.put(format!("{}{here}/api/v1/crates/new", site.base))
.header("authorization", token)
.body("xx")
.send()
.unwrap();
assert_eq!(raw.status(), 400);
let long = "x".repeat(100_001);
let (status, said) = says(put(
&crate_,
json!({"name": "other", "vers": "1.0.0", "links": long}),
token,
));
assert!(status == 400 && said.contains("index line"), "{said}");
// Build metadata makes a record of another key, which then goes.
let twin = cargo_crate(&root, "leaf", "1.0.0+b", "", "lib.rs", "");
let crate_ = packed(&twin, "leaf", "1.0.0+b");
let (status, said) = says(put(&crate_, meta("leaf", "1.0.0+b"), token));
assert!(
status == 400 && said.contains("leaf 1.0.0 is published already"),
"{said}"
);
let versions = |name: &str| -> Vec<String> {
owner
.records("dev.knotless.release")
.into_iter()
.filter(|(_, r)| r["package"] == name)
.map(|(_, r)| r["version"].as_str().unwrap().to_string())
.collect()
};
assert_eq!(versions("leaf"), ["1.0.0"]);
// A yank: a built lockfile still builds from nothing kept, and nothing
// new takes it, until it is undone. Told at once, as a publish is.
let yank = ["yank", "--registry", "here", "--version", "1.0.0", "extra"];
cargo.ok(&extra, &yank);
let line = site.status(&format!("{here}/ex/tr/extra")).1;
assert!(line.contains("\"yanked\":true"), "{line}");
std::fs::remove_dir_all(cargo.home.join("registry")).unwrap();
std::fs::remove_dir_all(app.join("target")).unwrap();
assert!(cargo.ok(&app, &["run", "-q", "--locked"]).contains("41"));
let new_toml = "[dependencies]\nextra = { version = \"1\", registry = \"here\" }\n";
let new = cargo_crate(&root, "new", "0.0.1", new_toml, "main.rs", "fn main() {}\n");
let (done, said) = cargo.run(&new, &["build"]);
assert!(!done && said.contains("yanked"), "{said}");
cargo.ok(
&extra,
&[
"yank",
"--registry",
"here",
"--undo",
"--version",
"1.0.0",
"extra",
],
);
cargo.ok(&new, &["build"]);
let (done, said) = cargo.run(
&extra,
&["yank", "--registry", "here", "--version", "9.0.0", "extra"],
);
assert!(
!done && said.contains("has published no extra 9.0.0"),
"{said}"
);
// Searched for by name, each at its latest, as many as asked.
let found = site.json(&format!("{here}/api/v1/crates?q=e&per_page=1"));
assert_eq!(found["meta"]["total"], 2);
assert_eq!(
found["crates"][0],
json!({"name": "extra", "max_version": "1.0.0", "description": "says extra"})
);
assert_eq!(found["crates"].as_array().unwrap().len(), 1);
let said = cargo.ok(&root, &["search", "--registry", "here", "mid"]);
assert!(
said.contains("mid = \"0.2.0\"") && said.contains("says mid"),
"{said}"
);
let everything = site.json(&format!("{here}/api/v1/crates"));
assert_eq!(everything["meta"]["total"], 3);
// A file whose bytes are not what its record says is not sent, and a
// body too large is refused as it is read.
let blob = owner.upload_blob(b"x", "application/octet-stream");
let bad = serde_json::json!({
"$type": "dev.knotless.release", "repo": key, "package": "bad", "version": "1.0.0",
"cargo": {"index": json!({"name": "bad", "vers": "1.0.0"}).to_string()},
"files": [{"name": "bad-1.0.0.crate", "size": 1, "sha256": "a".repeat(64),
"parts": [{"blob": blob}]}],
"createdAt": "2026-10-27T00:00:00.000Z",
});
owner.put_record("dev.knotless.release", "bad", bad);
wait_until(|| site.status(&format!("{here}/3/b/bad")).0 == 200);
assert_eq!(site.status(&format!("{here}/dl/bad/1.0.0/download")).0, 502);
let (status, _) = says(
reqwest::blocking::Client::new()
.put(format!("{}{here}/api/v1/crates/new", site.base))
.header("authorization", token)
.body(vec![0u8; (64 << 20) + 1])
.send()
.unwrap(),
);
assert_eq!(status, 413);
// A repository that is not there is none of it.
assert_eq!(
site.status(&format!("/cargo/{did}/nokey/config.json")).0,
404
);
let (status, _) = says(
reqwest::blocking::Client::new()
.put(format!("{}/cargo/{did}/nokey/api/v1/crates/new", site.base))
.header("authorization", token)
.body("xx")
.send()
.unwrap(),
);
assert_eq!(status, 404);
}
#[test]
fn the_releases() {
let site = serve(true);
docs/design/packages.md+59−0
@@ -260,6 +260,63 @@ entry point. deploy/README.md lists the commands. It is one platform,
linux/amd64, as the host builds it, and its layers are as docker keeps
them, uncompressed.
## Crates
A crate is a release too, and knotless-serve answers Cargo's registry
protocol from those records, one registry for each repository:
```toml
# .cargo/config.toml
[registries.tools]
index = "sparse+https://knotless.dev/cargo/<did>/<repo key>/"
```
`cargo login --registry tools` takes `<handle>:<app password>`. Then
`cargo publish --registry tools` and `cargo yank` work as with any
registry, and a dependency names the registry:
`tool = { version = "1", registry = "tools" }`. Reads need no login: Cargo
sends the token only to publish and yank. The password logs in to the
owner's PDS for that request, and is kept nowhere but on the publisher's
machine, in Cargo's `credentials.toml`.
**One registry for each repository.** A crate's name has no owner in it, so
one registry for all of knotless would need someone to decide who owns a
name. Nor can one for each account work, since nothing stops two of an
account's repositories publishing a name, and a publish would not say which
it is for. A crate of one repository can depend on a crate of another:
Cargo keeps the other's address in the index line, and follows it.
**An address that does not change.** Cargo keeps the registry's address in
`Cargo.lock` and in those index lines, and takes another address for another
registry, so a rename would make the same crate look like two. So the
address holds the owner's DID and the repository's key, which never change,
and no names.
**Its line of the index is in the record.** A release of a crate has a
`cargo` object, whose `index` is the line of Cargo's index as `cargo publish`
described it, with the `.crate`'s SHA-256 as `cksum`. It is kept as Cargo's
JSON rather than as fields of knotless's, since Cargo's format is Cargo's to
change, and readers pass it on. The index is then made from records alone:
no blob is read to answer it, and a publish uploads one blob-sized part
fewer. Only `yanked` is added, as it is read, from the
[yank](#yanking) of its key.
**What it reads is what the index has,** so a takedown holds and a private
repository has none. `cargo publish` waits up to a minute to find its crate,
and warns if it does not, so a publish and a yank put the record in the index
themselves, without waiting to hear of it.
**One version, however its build metadata differs.** Cargo's index holds one
of `1.0.0` and `1.0.0+b`, which have different record keys, so creating the
record cannot refuse the second. A publish looks once its record is made: if
an older record holds that version, it deletes its own and is refused. The
index takes the oldest should both remain.
A package's name here is lowercase, as a release's is, so a crate with
capitals is refused. `cargo owner` is not answered: a repository has one
owner. Neither `commit` nor `check` is set by a publish, as Cargo sends
neither.
## On the pages
knotless-serve indexes release records as it indexes the rest, so a
@@ -326,6 +383,8 @@ split git refs into one record each ([prior-art.md](prior-art.md)). With
one record per version, two publishes of different versions never touch
the same record.
A registry beside the release records, in knotless-serve, needs nothing of
the PDS, as [crates](#crates) and [images](#images) show.
npm-compatible package names leave room to serve the npm protocol later,
from knotless-serve rather than the PDS.
docs/reference/api.md+21−0
@@ -512,6 +512,27 @@ A version or file the releases do not have is 404. A repository that
cannot be read, or a file that cannot be read or kept, is 502 with the
error.
## Crates
A Cargo registry of the crates a repository publishes as releases
([packages.md](../design/packages.md#crates)), under
`/cargo/<did>/<repo key>/`, where `<did>` is its owner's. What it reads is what
the index has: with none, or for a repository the index lacks or has taken
down, all of it is 404. A token, in `Authorization`, is `<handle>:<app
password>`, which logs in to the owner's PDS.
| Route | |
| --- | --- |
| `GET config.json` | `{dl, api}`, at the request's origin |
| `GET <index path>` | a crate's file of the index, by Cargo's layout (`1/a`, `2/ab`, `3/a/abc`, `ab/cd/abcd`): a line per version, the oldest first, `no-cache`, with an `ETag`; 304 to one seen, 404 for none |
| `GET dl/<name>/<version>/download` | the `.crate`, as a release's files are sent |
| `PUT api/v1/crates/new` | as Cargo publishes: the crate uploaded to the owner's PDS and its release made, at most 64 MiB. 400 for a body that is not one, a name or version a release does not take, a `Cargo.toml` that says otherwise, an index line over 100,000 bytes, or a version published already |
| `DELETE api/v1/crates/<name>/<version>/yank`, `PUT .../unyank` | `{ok: true}`: [yanks](../design/packages.md#yanking) it, or takes that back |
| `GET api/v1/crates?q=&per_page=` | the crates whose names hold `q`, each at its latest: `{crates: [{name, max_version, description}], meta: {total}}`, 10 by default, 100 at most |
Errors are Cargo's, `{"errors": [{"detail"}]}`. A write with no token, a token
not of that form, one the PDS refuses or another account's is 403.
## Images
An OCI registry of the images repositories publish as releases, with
harness/lexicons.mjs+4−1
@@ -1,5 +1,5 @@
// The lexicons, as the Lexicon Style Guide and CLAUDE.md ("Lexicons") ask:
// goat's lint, less the two warnings kept on purpose; what that lint does
// goat's lint, less the three warnings kept on purpose; what that lint does
// not see; and that each one only grows from what `base` has, since a
// schema already published is evolved, never changed, with records and
// clients written to it out there.
@@ -51,8 +51,11 @@ const problems = []
const say = (at, what) => retracted.has(at) || problems.push(`${at}: ${what}`)
// goat (github.com/bluesky-social/goat), one finding a line.
// `release`'s `cargo.index` is Cargo's own line, whose size is the crate's:
// `web-sys`'s is 54 KB, and a limit once published cannot be raised.
const kept = new Set([
'dev.knotless.pack large-bytes',
'dev.knotless.release large-string',
'dev.knotless.listReports field-name-syntax',
])
let linted
lexicons/dev/knotless/release.json+10−0
lexicons/AGENTS.md+3−2