fix(ownership): sync snapshots over 10 MiB; refuse oversized publishes

On 2026-09-25 a snapshot built with --history 5 (15.3 MB) was
published, and `idx ownership sync` failed for every client:
"the response body is larger than request limit: 10485760" (ureq's
default body cap). It was republished by hand with --history 2 (8.2 MB).

- snapshot download: raise the ureq body limit to 1 GiB; the file is
  still verified against the manifest's size and SHA-256. The PDF/XLSX
  report downloads get a 256 MiB limit for the same reason.
- CLI test: sync a >10 MiB snapshot over HTTP (fails without the fix
  with the exact production error).
- build-latest-ownership-snapshot.sh: --max-snapshot-bytes (default
  10485760, the limit of released clients <= v0.2.3); an oversized
  snapshot fails the build, so the published one stays untouched.
- publish-ownership-snapshot.sh passes --max-snapshot-bytes through;
  publisher tests cover the failure and that the release is unchanged.
- docs: size table and guidance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
This commit is contained in:
hermes 2026-09-25 22:22:48 +00:00
commit de58d04da9
7 changed files with 156 additions and 5 deletions

View file

@ -1866,6 +1866,57 @@ fn ownership_import_file_zip_archive_supports_releases_ticker_and_changes() {
.stdout(predicate::str::contains("DECREASED"));
}
#[test]
fn ownership_sync_downloads_snapshot_larger_than_ten_mib_over_http() {
// ureq's default 10 MiB body limit broke `ownership sync` for every
// client when a 15.3 MB snapshot (5 months of history) was published.
let publisher_root = test_env_dir("ownership-sync-large-publisher");
let (source_db, manifest_path) = prepare_snapshot_fixture(&publisher_root);
{
let conn = Connection::open(&source_db).expect("open snapshot db");
conn.execute_batch("CREATE TABLE test_padding (data BLOB)")
.expect("create padding table");
conn.execute(
"INSERT INTO test_padding (data) VALUES (zeroblob(?1))",
[11 * 1024 * 1024],
)
.expect("insert padding");
}
let snapshot_bytes = fs::read(&source_db).expect("read padded snapshot");
assert!(snapshot_bytes.len() > 10 * 1024 * 1024);
write_snapshot_manifest(&manifest_path, &source_db, None);
let snapshot_url = spawn_single_response_server("application/octet-stream", snapshot_bytes);
let mut manifest: Value =
serde_json::from_str(&fs::read_to_string(&manifest_path).expect("read manifest"))
.expect("parse manifest");
manifest["snapshot"]["download_url"] = Value::String(format!("{snapshot_url}/snapshot.sqlite"));
fs::write(&manifest_path, manifest.to_string()).expect("write http manifest");
let sync_root = test_env_dir("ownership-sync-large-consumer");
let target_db = sync_root.join("ownership.db");
bin_with_root(&sync_root)
.args([
"config",
"set",
"ownership.db_path",
target_db.to_str().unwrap(),
])
.assert()
.success();
bin_with_root(&sync_root)
.args([
"ownership",
"sync",
"--manifest",
manifest_path.to_str().unwrap(),
])
.assert()
.success()
.stdout(predicate::str::contains("Installed ownership snapshot"));
}
#[test]
fn ownership_sync_installs_snapshot_and_preserves_query_behavior() {
let publisher_root = test_env_dir("ownership-sync-publisher");