Merge remote-tracking branch 'origin/main' into fix/served-git-concurrency-cap

This commit is contained in:
beardthelion committed 2026-08-03 00:20:03 -05:00
commit 17f9edad47
4 files changed
+264 -16

No files matched your search

Generated
+81 -6
View File
@@ -770,6 +770,23 @@ dependencies = [
"zeroize",
]
[[package]]
name = "ark-ff"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af"
dependencies = [
"ark-ff-asm 0.6.0",
"ark-ff-macros 0.6.0",
"ark-serialize 0.6.0",
"ark-std 0.6.0",
"digest 0.10.7",
"educe",
"num-bigint",
"num-traits",
"zeroize",
]
[[package]]
name = "ark-ff-asm"
version = "0.3.0"
@@ -800,6 +817,16 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "ark-ff-asm"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2"
dependencies = [
"quote",
"syn 2.0.117",
]
[[package]]
name = "ark-ff-macros"
version = "0.3.0"
@@ -838,6 +865,19 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "ark-ff-macros"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8"
dependencies = [
"num-bigint",
"num-traits",
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "ark-serialize"
version = "0.3.0"
@@ -871,6 +911,30 @@ dependencies = [
"num-bigint",
]
[[package]]
name = "ark-serialize"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b"
dependencies = [
"ark-serialize-derive",
"ark-std 0.6.0",
"digest 0.10.7",
"num-bigint",
"serde_with",
]
[[package]]
name = "ark-serialize-derive"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "ark-std"
version = "0.3.0"
@@ -901,6 +965,16 @@ dependencies = [
"rand 0.8.6",
]
[[package]]
name = "ark-std"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155"
dependencies = [
"num-traits",
"rand 0.8.6",
]
[[package]]
name = "arrayref"
version = "0.3.9"
@@ -2556,7 +2630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de"
dependencies = [
"data-encoding",
"syn 1.0.109",
"syn 2.0.117",
]
[[package]]
@@ -2928,7 +3002,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -5741,14 +5815,15 @@ dependencies = [
[[package]]
name = "ruint"
version = "1.17.2"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a"
checksum = "f5e99bff0393163bb25029a6af25d3d8d202ba5b5438a74d1bd8789f5c822970"
dependencies = [
"alloy-rlp",
"ark-ff 0.3.0",
"ark-ff 0.4.2",
"ark-ff 0.5.0",
"ark-ff 0.6.0",
"bytes",
"fastrlp 0.3.1",
"fastrlp 0.4.0",
@@ -5822,7 +5897,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -6716,7 +6791,7 @@ dependencies = [
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
+170 -10
View File
@@ -917,6 +917,10 @@ pub(crate) fn normalize_owner_key(did: &str) -> &str {
/// drift apart. If you change `normalize_owner_key`, update this const too.
const OWNER_KEY_CASE_SQL: &str = "CASE WHEN owner_did LIKE 'did:key:%' AND position(':' in substr(owner_did, 9)) = 0 THEN substr(owner_did, 9) ELSE owner_did END";
/// SQL CASE expression byte-identical to `normalize_owner_key`, but for columns
/// named `did` (like in agent_profiles) instead of `owner_did`.
const PROFILE_DID_CASE_SQL: &str = "CASE WHEN did LIKE 'did:key:%' AND position(':' in substr(did, 9)) = 0 THEN substr(did, 9) ELSE did END";
#[cfg(test)]
mod normalize_owner_key_tests {
use super::normalize_owner_key;
@@ -3454,6 +3458,9 @@ impl Db {
let new_website = website.or(existing.website.as_deref());
let new_socials = socials.or(existing.socials.as_deref());
// get_profile equates bare short ids with did:key:<id>. UPDATE must target
// the stored row identity (existing.did), not the caller's raw input form,
// or a did:key: alias against a bare-stored profile updates zero rows.
sqlx::query(
"UPDATE agent_profiles
SET display_name=$1, bio=$2, avatar_url=$3, website=$4, socials=$5, updated_at=$6
@@ -3465,12 +3472,12 @@ impl Db {
.bind(new_website)
.bind(new_socials)
.bind(&now)
.bind(did)
.bind(&existing.did)
.execute(&self.pool)
.await?;
Ok(ProfileRecord {
did: did.to_string(),
did: existing.did,
display_name: new_name.map(String::from),
bio: new_bio.map(String::from),
avatar_url: new_avatar.map(String::from),
@@ -3511,14 +3518,20 @@ impl Db {
}
pub async fn get_profile(&self, did: &str) -> Result<Option<ProfileRecord>> {
let row = sqlx::query(
// Same owner-key contract as get_repo: strip `did:key:` only when the
// remainder is a bare key id. The old `LIKE '%:' || $1` matched any DID
// method that shared a suffix and could resolve the wrong profile.
let did_key = normalize_owner_key(did);
let sql = format!(
"SELECT did, display_name, bio, avatar_url, website, socials, profile_cid, created_at, updated_at
FROM agent_profiles
WHERE did = $1 OR did LIKE '%:' || $1",
)
.bind(did)
.fetch_optional(&self.pool)
.await?;
WHERE ({key}) = $1",
key = PROFILE_DID_CASE_SQL
);
let row = sqlx::query(&sql)
.bind(did_key)
.fetch_optional(&self.pool)
.await?;
Ok(row.map(|r| ProfileRecord {
did: r.get("did"),
@@ -3534,10 +3547,17 @@ impl Db {
}
pub async fn set_profile_cid(&self, did: &str, cid: &str) -> Result<()> {
sqlx::query("UPDATE agent_profiles SET profile_cid = $1, updated_at = $2 WHERE did = $3")
// Same did:key / bare equivalence as get_profile so a full did:key: form
// updates a profile stored under the bare short id.
let did_key = normalize_owner_key(did);
let sql = format!(
"UPDATE agent_profiles SET profile_cid = $1, updated_at = $2 WHERE ({key}) = $3",
key = PROFILE_DID_CASE_SQL
);
sqlx::query(&sql)
.bind(cid)
.bind(Utc::now().to_rfc3339())
.bind(did)
.bind(did_key)
.execute(&self.pool)
.await?;
Ok(())
@@ -4671,6 +4691,103 @@ mod dedup_db_tests {
assert!(!got.is_public, "non-key row's is_public must be preserved");
}
/// get_profile must not resolve a non-key DID (e.g. did:gitlawb:) when
/// queried with the bare short id. The old `LIKE '%:' || $1` clause was too
/// broad and could return the wrong profile row.
#[sqlx::test]
async fn get_profile_does_not_match_non_key_did(pool: PgPool) {
let db = db(pool).await;
let short = "z6Mkprof1";
// Seed only a non-key DID row first. When queried with the bare short ID,
// get_profile must return None (lone non-key fixture test).
db.upsert_profile(
&format!("did:gitlawb:{short}"),
Some("other-method"),
None,
None,
None,
None,
)
.await
.unwrap();
let got = db.get_profile(short).await.unwrap();
assert!(
got.is_none(),
"bare short id must not resolve a lone non-key DID profile"
);
// Now seed the canonical bare key profile row as well.
db.upsert_profile(short, Some("canonical"), None, None, None, None)
.await
.unwrap();
let got = db
.get_profile(short)
.await
.unwrap()
.expect("bare short id should resolve the key-form profile");
assert_eq!(got.did, short);
assert_eq!(got.display_name.as_deref(), Some("canonical"));
let got = db
.get_profile(&format!("did:key:{short}"))
.await
.unwrap()
.expect("did:key form should also resolve the key-form profile");
assert_eq!(got.did, short);
let got = db
.get_profile(&format!("did:gitlawb:{short}"))
.await
.unwrap()
.expect("full non-key DID should resolve its own profile");
assert_eq!(got.did, format!("did:gitlawb:{short}"));
assert_eq!(got.display_name.as_deref(), Some("other-method"));
}
/// upsert_profile must update a bare-stored profile when called with the
/// full did:key: form. get_profile equates the two, so the UPDATE has to
/// target existing.did rather than the raw input.
#[sqlx::test]
async fn upsert_profile_updates_via_did_key_alias(pool: PgPool) {
let db = db(pool).await;
let short = "z6Mkprof2";
db.upsert_profile(short, Some("before"), None, None, None, None)
.await
.unwrap();
let updated = db
.upsert_profile(
&format!("did:key:{short}"),
Some("after"),
None,
None,
None,
None,
)
.await
.unwrap();
assert_eq!(updated.did, short, "preserve the stored did form on update");
assert_eq!(updated.display_name.as_deref(), Some("after"));
let got = db
.get_profile(short)
.await
.unwrap()
.expect("profile should still resolve by bare short id");
assert_eq!(got.did, short);
assert_eq!(got.display_name.as_deref(), Some("after"));
db.set_profile_cid(&format!("did:key:{short}"), "bafytestcid")
.await
.unwrap();
let got = db.get_profile(short).await.unwrap().unwrap();
assert_eq!(got.profile_cid.as_deref(), Some("bafytestcid"));
}
/// Verify that the Rust `normalize_owner_key` and the `OWNER_KEY_CASE_SQL`
/// expression agree on every boundary value in the owner-key normalization
/// set. A mismatch would let the Rust code bind a different key than the SQL
@@ -4720,6 +4837,49 @@ mod dedup_db_tests {
);
}
}
/// Verify that `PROFILE_DID_CASE_SQL` (which aliases the column `did`) also
/// agrees with Rust `normalize_owner_key` across the full boundary matrix.
#[sqlx::test]
async fn profile_did_case_sql_matches_normalize_owner_key(pool: PgPool) {
let boundary_values = [
"did:key:z6Mkfoo",
"z6Mkfoo",
"did:gitlawb:z6Mkfoo",
"did:web:example.com:alice",
"did:key:did:gitlawb:z6Mkfoo",
"",
"did:key:",
"DID:KEY:z6Mkfoo",
];
let values_sql: String = boundary_values
.iter()
.map(|v| format!("('{}'::text)", v))
.collect::<Vec<_>>()
.join(", ");
let sql = format!(
"WITH data(did) AS (VALUES {values_sql})
SELECT did, ({key}) AS normalized FROM data ORDER BY did",
key = super::PROFILE_DID_CASE_SQL
);
let rows: Vec<(String, String)> = sqlx::query_as(&sql).fetch_all(&pool).await.unwrap();
assert_eq!(
rows.len(),
boundary_values.len(),
"every boundary value must produce a row"
);
for (val, sql_result) in &rows {
let rust_result = super::normalize_owner_key(val);
assert_eq!(
sql_result, rust_result,
"PROFILE_DID_CASE_SQL(\"{val}\") mismatch: Rust = \"{rust_result}\", SQL CASE = \"{sql_result}\""
);
}
}
}
/// Exercises the iCaptcha single-use proof ledger (`icaptcha_consumed_proofs`),
@@ -880,6 +880,7 @@ mod tests {
));
}
#[cfg(unix)]
#[test]
fn containment_rejects_a_symlinked_directory_inside_the_root() {
use std::os::unix::fs::symlink;
@@ -896,6 +897,7 @@ mod tests {
));
}
#[cfg(unix)]
#[test]
fn containment_rejects_a_symlinked_file_inside_the_root() {
use std::os::unix::fs::symlink;
@@ -927,6 +929,7 @@ mod tests {
));
}
#[cfg(unix)]
#[test]
fn containment_rejects_a_missing_candidate_under_a_symlinked_parent() {
use std::os::unix::fs::symlink;
@@ -943,6 +946,7 @@ mod tests {
));
}
#[cfg(unix)]
#[test]
fn containment_reports_io_error_for_a_dangling_symlink() {
// The link entry exists, so the candidate is the thing to resolve, and
+9
View File
@@ -1406,6 +1406,7 @@ mod tests {
.unwrap();
}
#[cfg(unix)]
#[sqlx::test]
async fn process_batch_rejects_a_symlinked_owner_directory(pool: PgPool) {
// The slug is textually valid, so U1's character rules pass it. The
@@ -1438,6 +1439,7 @@ mod tests {
assert_eq!(sync_status(&pool, "z6Mkfoo/hello").await, "failed");
}
#[cfg(unix)]
#[sqlx::test]
async fn process_batch_rejects_a_symlinked_mirror_path(pool: PgPool) {
// The leaf case a parent-only canonicalize misses: the owner directory
@@ -1561,6 +1563,7 @@ mod tests {
);
}
#[cfg(unix)]
#[sqlx::test]
async fn process_batch_leaves_the_row_pending_when_the_mirror_cannot_be_inspected(
pool: PgPool,
@@ -1604,6 +1607,7 @@ mod tests {
assert!(owner_dir.join("hello.git").is_dir());
}
#[cfg(unix)]
#[sqlx::test]
async fn process_batch_leaves_the_row_pending_when_the_owner_dir_cannot_be_created(
pool: PgPool,
@@ -1680,6 +1684,7 @@ mod tests {
assert!(mirrors_under(home.path()).is_empty());
}
#[cfg(unix)]
#[sqlx::test]
async fn process_batch_terminally_fails_when_a_dangling_symlink_occupies_the_owner_path(
pool: PgPool,
@@ -1732,6 +1737,7 @@ mod tests {
/// only needs +x on repos_dir), so this defers at `path_within_root`
/// instead — the stall path that survives the AlreadyExists
/// classification, which is what keeps the starvation tests load-bearing.
#[cfg(unix)]
fn make_stuck_owner(repos_dir: &Path, owner: &str) -> std::path::PathBuf {
use std::os::unix::fs::PermissionsExt;
let dir = repos_dir.join(owner);
@@ -1752,11 +1758,13 @@ mod tests {
dir
}
#[cfg(unix)]
fn unstick(dir: &Path) {
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o755)).unwrap();
}
#[cfg(unix)]
#[sqlx::test]
async fn a_full_batch_of_stuck_rows_does_not_starve_a_healthy_one(pool: PgPool) {
// Ten rows that defer forever, exactly filling the batch, with one
@@ -1811,6 +1819,7 @@ mod tests {
unstick(&stuck);
}
#[cfg(unix)]
#[sqlx::test]
async fn a_stuck_set_larger_than_the_batch_still_yields(pool: PgPool) {
// 25 stuck rows against a batch size of 10. The healthy row lands