diff --git a/Cargo.lock b/Cargo.lock index 24217800..cdfb2a80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -687,6 +687,8 @@ dependencies = [ "clap", "confique", "futures", + "jacquard-common", + "jacquard-identity", "serde", "socket2", "thiserror 2.0.18", @@ -821,6 +823,7 @@ dependencies = [ "futures", "getrandom 0.4.3", "http", + "jacquard-common", "reqwest 0.13.1", "thiserror 2.0.18", "tokio", @@ -922,6 +925,8 @@ dependencies = [ "futures", "http", "jacquard-common", + "jacquard-identity", + "reqwest 0.13.1", "serde", "serde_json", "thiserror 2.0.18", @@ -4174,6 +4179,29 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "jacquard-identity" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0a89c960d6c2245f26642830ecf85155eaf7395e5da8794173a78854f0e22b" +dependencies = [ + "bon", + "bytes", + "http", + "jacquard-common", + "jacquard-lexicon", + "miette", + "mini-moka-wasm", + "n0-future", + "reqwest 0.12.28", + "serde", + "serde_html_form", + "serde_json", + "thiserror 2.0.18", + "tokio", + "trait-variant", +] + [[package]] name = "jacquard-lexicon" version = "0.12.1" @@ -5232,6 +5260,20 @@ dependencies = [ "unicase", ] +[[package]] +name = "mini-moka-wasm" +version = "0.10.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0102b9a2ad50fa47ca89eead2316c8222285ecfbd3f69ce99564fbe4253866e8" +dependencies = [ + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "smallvec", + "tagptr", + "triomphe", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -8372,6 +8414,12 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "triomphe" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae" + [[package]] name = "try-lock" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index 8b5a9de1..0e5d9be9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,7 @@ knot-edge = { path = "knot2/crates/knot-edge" } jacquard-common = "0.12.1" jacquard-derive = "0.12.1" jacquard-lexicon = { version = "0.12.1", default-features = false } +jacquard-identity = { version = "0.12.1", features = ["cache"] } jacquard-repo = "0.12.1" gix = { version = "0.84", features = ["parallel", "revision", "blob-diff", "worktree-archive", "tree-editor", "sha1", "sha256"] } diff --git a/bobbin/crates/bobbin/Cargo.toml b/bobbin/crates/bobbin/Cargo.toml index 6ee7112b..e53a3ff4 100644 --- a/bobbin/crates/bobbin/Cargo.toml +++ b/bobbin/crates/bobbin/Cargo.toml @@ -19,6 +19,8 @@ bobbin-runtime = { workspace = true } bobbin-search = { workspace = true } bobbin-slingshot-client = { workspace = true } bobbin-xrpc = { workspace = true } +jacquard-common = { workspace = true } +jacquard-identity = { workspace = true } axum = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/bobbin/crates/bobbin/src/main.rs b/bobbin/crates/bobbin/src/main.rs index bdb082dd..5f10bb3e 100644 --- a/bobbin/crates/bobbin/src/main.rs +++ b/bobbin/crates/bobbin/src/main.rs @@ -14,15 +14,19 @@ use bobbin_knot_ingest::{CapabilityGate, KnotClient, KnotRegistry, Orchestrator} use bobbin_knot_proxy::{KnotHttpConfig, KnotProxy, KnotProxyConfig, classify_ip}; use bobbin_record_lru::{CacheCapacity, LruRecordStore, RecordStore}; use bobbin_runtime::{ - Clock, GuardedWs, MemoryBudget, NetworkError, OsEntropy, RuntimeHasher, SystemClock, - TungsteniteWs, WsTransport, + Clock, GuardedWs, MemoryBudget, NetworkError, OsEntropy, ReqwestHttp, RuntimeHasher, + SystemClock, TungsteniteWs, WsTransport, }; use bobbin_search::{SearchIndex, SearchReader}; use bobbin_slingshot_client::SlingshotClient; +use bobbin_slingshot_client::default_http_client; use bobbin_xrpc::{ AppState, HeavyLimiter, MaxInFlight, PerRequestAnonBytes, ReservedFloor, router, }; use clap::{Parser, Subcommand}; +use jacquard_common::deps::fluent_uri::Uri; +use jacquard_identity::JacquardResolver; +use jacquard_identity::resolver::{DidStep, PlcSource, ResolverOptions}; use tokio::signal::unix::{SignalKind, signal}; use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; @@ -180,6 +184,22 @@ async fn run(cfg: BobbinConfig) -> anyhow::Result<()> { let records: Arc = Arc::new(LruRecordStore::new(CacheCapacity::from_bytes(lru_cap))); let slingshot = SlingshotClient::with_default_http(cfg.slingshot.url.clone())?; + let mut resolver_opts = ResolverOptions::default(); + // NOTE: see https://tangled.org/nonbinary.computer/jacquard/issues/39. + resolver_opts.did_order = vec![ + DidStep::DidWebHttps, + // DidStep::PlcHttp, + DidStep::PdsResolveDid, + ]; + let directory = Arc::new( + JacquardResolver::new(ReqwestHttp::new(default_http_client()?), resolver_opts) + .with_plc_source(PlcSource::Slingshot { + base: Uri::parse(cfg.slingshot.url.as_str()) + .context("slingshot url is not a valid URI")? + .to_owned(), + }) + .with_cache(), + ); let resolver = Arc::new(RepoIdResolver::with_slingshot( slingshot.clone(), clock.clone(), @@ -315,6 +335,7 @@ async fn run(cfg: BobbinConfig) -> anyhow::Result<()> { knots, search as Arc, resolver, + directory, ) .with_limiter(limiter); let app = router(state); diff --git a/bobbin/crates/runtime/Cargo.toml b/bobbin/crates/runtime/Cargo.toml index 4d78689d..2c03198f 100644 --- a/bobbin/crates/runtime/Cargo.toml +++ b/bobbin/crates/runtime/Cargo.toml @@ -11,6 +11,7 @@ bytes = { workspace = true } futures = { workspace = true } getrandom = { workspace = true } http = { workspace = true } +jacquard-common = { workspace = true } reqwest = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } diff --git a/bobbin/crates/runtime/src/network.rs b/bobbin/crates/runtime/src/network.rs index 80afcee7..90359f6b 100644 --- a/bobbin/crates/runtime/src/network.rs +++ b/bobbin/crates/runtime/src/network.rs @@ -92,6 +92,34 @@ impl HttpTransport for ReqwestHttp { } } +/// Lets jacquard resolve identities over the workspace reqwest (0.13); jacquard's own +/// `HttpClient` impl is against reqwest 0.12, which is built here without TLS. +impl jacquard_common::http_client::HttpClient for ReqwestHttp { + type Error = reqwest::Error; + + async fn send_http( + &self, + request: http::Request>, + ) -> Result>, reqwest::Error> { + let (parts, body) = request.into_parts(); + let mut req = self + .client + .request(parts.method, parts.uri.to_string()) + .body(body); + for (name, value) in parts.headers.iter() { + req = req.header(name, value); + } + + let resp = req.send().await?; + let mut builder = http::Response::builder().status(resp.status()); + for (name, value) in resp.headers().iter() { + builder = builder.header(name, value); + } + let body = resp.bytes().await?.to_vec(); + Ok(builder.body(body).expect("response parts came from reqwest")) + } +} + fn map_reqwest(err: reqwest::Error) -> NetworkError { let msg = err.to_string(); if err.is_timeout() { diff --git a/bobbin/crates/xrpc/Cargo.toml b/bobbin/crates/xrpc/Cargo.toml index 6ef8b957..cadeb571 100644 --- a/bobbin/crates/xrpc/Cargo.toml +++ b/bobbin/crates/xrpc/Cargo.toml @@ -15,6 +15,7 @@ bobbin-search = { workspace = true } bobbin-slingshot-client = { workspace = true } bobbin-knot-proxy = { workspace = true } jacquard-common = { workspace = true } +jacquard-identity = { workspace = true } axum = { workspace = true } chrono = { workspace = true } @@ -27,6 +28,7 @@ tower = { workspace = true } tower-http = { workspace = true, features = ["trace"] } tracing = { workspace = true } url = { workspace = true } +reqwest = { workspace = true } [dev-dependencies] bobbin-runtime = { workspace = true } diff --git a/bobbin/crates/xrpc/src/lib.rs b/bobbin/crates/xrpc/src/lib.rs index 0b3a7e7f..9839d513 100644 --- a/bobbin/crates/xrpc/src/lib.rs +++ b/bobbin/crates/xrpc/src/lib.rs @@ -29,6 +29,7 @@ use bobbin_edge_index::{ use bobbin_knot_proxy::{KnotHost, KnotProxy, KnotProxyError, ProxyResponse, RepoSlug}; use bobbin_record_lru::RecordStore; use bobbin_resolver::RepoIdResolver; +use bobbin_runtime::ReqwestHttp; use bobbin_search::{ SearchCursor, SearchError, SearchFilters, SearchHit, SearchOffset, SearchReader, }; @@ -87,6 +88,7 @@ use jacquard_common::types::recordkey::Rkey; use jacquard_common::types::string::{AtUri, Cid}; use jacquard_common::xrpc::XrpcResp; use jacquard_common::{DefaultStr, IntoStatic}; +use jacquard_identity::JacquardResolver; use serde::{Deserialize, Serialize}; use std::convert::Infallible; use std::time::Duration; @@ -109,6 +111,13 @@ use filter::{CountFilter, IssueFilter, ListFilter, NoFilter, PullFilter}; const DEFAULT_LIMIT: u32 = 50; const FETCH_CONCURRENCY: usize = 8; + +pub type Directory = JacquardResolver; + +pub fn default_directory() -> Directory { + JacquardResolver::new(ReqwestHttp::new(reqwest::Client::new()), Default::default()) +} + #[derive(Clone)] pub struct AppState { pub records: Arc, @@ -120,6 +129,7 @@ pub struct AppState { pub knots: Arc, pub search: Arc, pub resolver: Arc, + pub directory: Arc, pub limiter: Option>, enrich_router: Arc>, } @@ -136,6 +146,7 @@ impl AppState { knots: Arc, search: Arc, resolver: Arc, + directory: Arc, ) -> Self { Self { records, @@ -147,6 +158,7 @@ impl AppState { knots, search, resolver, + directory, limiter: None, enrich_router: Arc::new(std::sync::OnceLock::new()), } diff --git a/bobbin/crates/xrpc/tests/aggregation.rs b/bobbin/crates/xrpc/tests/aggregation.rs index 14d82023..51cf9865 100644 --- a/bobbin/crates/xrpc/tests/aggregation.rs +++ b/bobbin/crates/xrpc/tests/aggregation.rs @@ -92,6 +92,7 @@ impl Harness { SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), ) as Arc, Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), + Arc::new(bobbin_xrpc::default_directory()), ); Self { server, diff --git a/bobbin/crates/xrpc/tests/bulk.rs b/bobbin/crates/xrpc/tests/bulk.rs index b8a18635..7e57e906 100644 --- a/bobbin/crates/xrpc/tests/bulk.rs +++ b/bobbin/crates/xrpc/tests/bulk.rs @@ -69,6 +69,7 @@ impl Harness { SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), ) as Arc, Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), + Arc::new(bobbin_xrpc::default_directory()), ); Self { server, state } } diff --git a/bobbin/crates/xrpc/tests/cold_start.rs b/bobbin/crates/xrpc/tests/cold_start.rs index bcd4d811..73191213 100644 --- a/bobbin/crates/xrpc/tests/cold_start.rs +++ b/bobbin/crates/xrpc/tests/cold_start.rs @@ -56,6 +56,7 @@ async fn fresh_app(server_uri: &Url) -> AppState { Arc::new(SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap()) as Arc, Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), + Arc::new(bobbin_xrpc::default_directory()), ) } diff --git a/bobbin/crates/xrpc/tests/coverage.rs b/bobbin/crates/xrpc/tests/coverage.rs index bc89a13c..7a2cedc8 100644 --- a/bobbin/crates/xrpc/tests/coverage.rs +++ b/bobbin/crates/xrpc/tests/coverage.rs @@ -44,6 +44,7 @@ impl Harness { SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), ) as Arc, Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), + Arc::new(bobbin_xrpc::default_directory()), ); Self { coverage, state } } diff --git a/bobbin/crates/xrpc/tests/enrich.rs b/bobbin/crates/xrpc/tests/enrich.rs index 3f4daac2..57cf0770 100644 --- a/bobbin/crates/xrpc/tests/enrich.rs +++ b/bobbin/crates/xrpc/tests/enrich.rs @@ -73,6 +73,7 @@ impl Harness { SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), ) as Arc, Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), + Arc::new(bobbin_xrpc::default_directory()), ); Self { server, diff --git a/bobbin/crates/xrpc/tests/extended.rs b/bobbin/crates/xrpc/tests/extended.rs index 2c6df453..49536fd0 100644 --- a/bobbin/crates/xrpc/tests/extended.rs +++ b/bobbin/crates/xrpc/tests/extended.rs @@ -81,6 +81,7 @@ impl Harness { SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), ) as Arc, Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), + Arc::new(bobbin_xrpc::default_directory()), ); Self { server, diff --git a/bobbin/crates/xrpc/tests/knot_proxy.rs b/bobbin/crates/xrpc/tests/knot_proxy.rs index d4bd032c..e0105694 100644 --- a/bobbin/crates/xrpc/tests/knot_proxy.rs +++ b/bobbin/crates/xrpc/tests/knot_proxy.rs @@ -82,6 +82,7 @@ impl Harness { SearchIndex::new(DEFAULT_WRITER_HEAP_BYTES, Arc::new(SystemClock::new())).unwrap(), ) as Arc, Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), + Arc::new(bobbin_xrpc::default_directory()), ); Self { slingshot: slingshot_server, diff --git a/bobbin/crates/xrpc/tests/search.rs b/bobbin/crates/xrpc/tests/search.rs index e6b6ac9a..513c8caa 100644 --- a/bobbin/crates/xrpc/tests/search.rs +++ b/bobbin/crates/xrpc/tests/search.rs @@ -76,6 +76,7 @@ impl Harness { ), search.clone() as Arc, Arc::new(RepoIdResolver::detached(RuntimeHasher::default())), + Arc::new(bobbin_xrpc::default_directory()), ); Self { server,