Skip to content

Commit a776b46

Browse files
authored
Rewire hub from samod access-policy fork to upstream samod 0.16.0 (#917)
* Rewire hub from samod access-policy fork to upstream samod 0.16.0 (bd-jvag5d4w) Dependency source: samod + samod-core move from the quarto-dev/samod `access-policy` branch (0.14.0 + one commit, git+ source in Cargo.lock) to crates.io samod 0.16.0 (registry source; the lockfile diff is only the two packages' version/source/checksum lines). automerge stays a single 0.12.0 and no transitive deps are added or removed — no new native-tls/openssl edges. quarto-hub, quarto-hub-provider and quarto-preview switch in lockstep so the crates unify to one copy. API migration: - The fork's synchronous `AccessPolicy` patch and `AnnouncePolicy` (`NeverAnnounce`) are replaced by upstream's `SharePolicy` seam. The hub installs a constant `ShareConfig::read_write().with_announce(false)` through a named `hub_share_policy()` seam, pinned by a unit test that asserts the fields (access=ReadWrite, announce=false) rather than comparing against the upstream constructor — the security-critical announce-off behavior, so the hub serves only documents an authenticated peer actively requests instead of advertising every loaded document ID. - `accept_axum(socket)` becomes `accept_axum(socket, None)` (the 0.15 signature). `None` is safe because nothing in the hub authorizes on a peer ID; bd-07kbzn1d tracks `Transport::with_expected_peer_id` for any future policy that does. Audit logging: REMOVED, not ported. The pre-migration spike showed SharePolicy subscription is eager per (loaded doc, connected peer): share() fires at connection establishment for every locally loaded document, before any find(), so a policy-driven "Document accessed" line would emit phantom accesses for documents the user never opened. The security audit also found the peer_emails map — keyed by the client-chosen peer ID — was a misattribution vector (a client claiming another user's peer ID overwrote, then deleted, that user's entry). Session-granularity events on the existing quarto_hub::audit target replace it: `ws_connect` when the upgrade completes (before samod sees the socket) and `ws_disconnect` when the connection ends, carrying the samod reason or "stream_closed"; both carry sub/credential_kind and are emitted only when auth is enabled, matching auth_ok. bd-tfribfyy records the loss and the design constraints for any replacement. Also inherits samod 0.15's protocol hardening for free: direct messages whose sender differs from the handshake identity, or that are addressed to another peer, are now rejected before dispatch. Verification: three-crate suite 840/840 (baseline 844 − 7 deleted access_policy tests + 3 new); clippy/fmt clean; workspace suite 17291/17292 (sole failure a proven worktree-nesting artifact, passes in the main checkout); JS↔Rust interop 5/5 against the migrated binary; on-disk smoke loads the pre-migration index without re-init; zero "Document accessed" lines under -v; two-client ephemeral/presence check through the hub passes both directions. cargo xtask verify passes with the npm legs skipped (no node_modules in the worktree, as in the July migration) and rust-tests/feature-sets covered in-phase. Plan: claude-notes/plans/2026-10-26-samod-0-16-share-policy.md Follow-ups: bd-tfribfyy (audit-log loss), bd-07kbzn1d (with_expected_peer_id), bd-e2l028k4 (cargo deny baseline). * Plan + braid bookkeeping: mark commit item done (a1dcd15), strand closed * Simplify ws handler follow-ups from bd-jvag5d4w review Review follow-ups to a1dcd15 (samod 0.16 SharePolicy rewire): - Drop the now-dead peer_info.clone() in the ClientConnected arm — it existed to keep peer_info alive for the removed connected_peer_id move; nothing uses peer_info afterwards anymore. - format_peer_info takes Option<&PeerInfo> instead of &Option<PeerInfo> (idiomatic borrow; unit tests updated). - ws_disconnect reason is now "repo_stopped" when the repo refuses the socket, instead of the misleading "stream_closed"; ops doc updated. This arm is compile-covered only — the test harness has no path to a stopped repo, same as the pre-existing warn! there. No snapshot changes. cargo nextest -p quarto-hub: 504/504; clippy --all-targets -D warnings and fmt clean.
1 parent 4d966ea commit a776b46

14 files changed

Lines changed: 777 additions & 335 deletions

File tree

‎.braid/snapshot.jsonl‎

Lines changed: 10 additions & 1 deletion
Large diffs are not rendered by default.

‎.github/workflows/release-pipeline.yml‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,10 @@
9797
# can use `install.sh --from-source`.
9898
# - The linux legs still build with `--features vendored-openssl` so
9999
# the binary has no runtime libssl dependency. openssl reaches q2
100-
# only through our samod fork's tungstenite/native-tls wiring;
101-
# bd-r7s13dfb tracks moving that to rustls, which would let this
102-
# feature (and the openssl build entirely) go away.
100+
# only through samod's tungstenite/native-tls wiring (the upstream
101+
# crate's `tungstenite` feature); bd-r7s13dfb tracks moving that to
102+
# rustls, which would let this feature (and the openssl build
103+
# entirely) go away.
103104
#
104105
# Signing key: MINISIGN_SECRET_KEY repo secret; the public half is pinned
105106
# in install.sh (MINISIGN_PUBKEY) and README. The sign step verifies its

‎Cargo.lock‎

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎claude-notes/plans/2026-10-26-samod-0-16-share-policy.md‎

Lines changed: 528 additions & 0 deletions
Large diffs are not rendered by default.

‎crates/quarto-hub-provider/Cargo.toml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ name = "quarto_hub_provider"
1313
path = "src/lib.rs"
1414

1515
[dependencies]
16-
# Automerge sync via the same samod fork the hub uses. Must match quarto-hub's
17-
# version/branch exactly, else samod (and its automerge) resolve to a second copy
18-
# and DocHandle types no longer unify across the crate boundary.
19-
samod = { version = "0.14.0", git = "https://github.com/quarto-dev/samod.git", branch = "access-policy", features = ["tokio", "tungstenite"] }
16+
# Automerge sync via the same crates.io samod the hub uses. Must match
17+
# quarto-hub's version exactly, else samod (and its automerge) resolve to a
18+
# second copy and DocHandle types no longer unify across the crate boundary.
19+
samod = { version = "0.16.0", features = ["tokio", "tungstenite"] }
2020
# Reused index-document model (file list, capture sidecar) for the join+list path.
2121
quarto-hub = { path = "../quarto-hub" }
2222
# Read file documents out of the VFS during materialization (must match the

‎crates/quarto-hub-provider/tests/integration/relay_sync.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::str::FromStr;
1010
use std::time::Duration;
1111

1212
use quarto_hub::index::IndexDocument;
13-
use samod::{BackoffConfig, DocumentId, NeverAnnounce, Repo};
13+
use samod::{BackoffConfig, DocumentId, Repo, ShareConfig};
1414
use tokio::net::TcpListener;
1515

1616
async fn spawn_relay() -> (Repo, url::Url) {
@@ -20,7 +20,7 @@ async fn spawn_relay() -> (Repo, url::Url) {
2020
async fn spawn_relay_never_announce() -> (Repo, url::Url) {
2121
spawn_relay_with(
2222
Repo::build_tokio()
23-
.with_announce_policy(NeverAnnounce)
23+
.with_share_policy(ShareConfig::read_write().with_announce(false))
2424
.load()
2525
.await,
2626
)
@@ -104,7 +104,7 @@ async fn peer_b_syncs_a_doc_created_by_peer_a_through_the_relay() {
104104
async fn peer_b_syncs_through_a_never_announce_relay() {
105105
// The real servers (quarto-hub, sync.automerge.org) do NOT proactively
106106
// announce docs to a connecting peer. If peer B still can't sync A's doc
107-
// here, we've pinned the provider's "0 file(s)" bug to a NeverAnnounce
107+
// here, we've pinned the provider's "0 file(s)" bug to an announce-off
108108
// relay — B must actively request/pull, not wait to be announced to.
109109
let (_relay, url) = spawn_relay_never_announce().await;
110110

@@ -133,5 +133,5 @@ async fn peer_b_syncs_through_a_never_announce_relay() {
133133
}
134134
tokio::time::sleep(Duration::from_millis(250)).await;
135135
}
136-
assert_eq!(last, 2, "peer B never synced through a NeverAnnounce relay");
136+
assert_eq!(last, 2, "peer B never synced through an announce-off relay");
137137
}

‎crates/quarto-hub/Cargo.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ time = "0.3"
4646
# Without this, the Rust default is UnicodeCodePoint, which disagrees with JS on
4747
# non-BMP characters (emoji etc.) — each emoji is 1 code point but 2 UTF-16 code units.
4848
automerge = { version = "0.12.0", features = ["utf16-indexing"] }
49-
# samod fork carrying the synchronous AccessPolicy trait.
50-
samod = { version = "0.14.0", git = "https://github.com/quarto-dev/samod.git", branch = "access-policy", features = ["tokio", "axum", "tungstenite"] }
49+
# Automerge sync (samod).
50+
samod = { version = "0.16.0", features = ["tokio", "axum", "tungstenite"] }
5151

5252
# Async streams (for samod event handling)
5353
futures = "0.3"

‎crates/quarto-hub/src/access_policy.rs‎

Lines changed: 0 additions & 256 deletions
This file was deleted.

0 commit comments

Comments
 (0)