From 465d5da1c243b21faa106be6df28c38d45e6ed03 Mon Sep 17 00:00:00 2001 From: Yuto Nishida Date: Sun, 12 Jul 2026 18:29:48 -0700 Subject: [PATCH] [silverwood] Part 1 --- experimental/silverwood/Cargo.lock | 1601 ++++++++++++++++- experimental/silverwood/Cargo.toml | 4 + experimental/silverwood/TODO.md | 34 +- .../crates/silverwood-core/Cargo.toml | 4 + .../crates/silverwood-core/src/doc.rs | 133 ++ .../crates/silverwood-core/src/error.rs | 22 + .../crates/silverwood-core/src/forest.rs | 120 +- .../crates/silverwood-core/src/lib.rs | 17 +- .../crates/silverwood-core/src/provider.rs | 47 + .../crates/silverwood-core/src/source.rs | 60 + .../crates/silverwood-core/src/workstream.rs | 185 ++ .../silverwood-core/tests/provisioning.rs | 128 ++ experimental/silverwood/deny.toml | 1 + 13 files changed, 2292 insertions(+), 64 deletions(-) create mode 100644 experimental/silverwood/crates/silverwood-core/src/doc.rs create mode 100644 experimental/silverwood/crates/silverwood-core/src/provider.rs create mode 100644 experimental/silverwood/crates/silverwood-core/src/source.rs create mode 100644 experimental/silverwood/crates/silverwood-core/src/workstream.rs create mode 100644 experimental/silverwood/crates/silverwood-core/tests/provisioning.rs diff --git a/experimental/silverwood/Cargo.lock b/experimental/silverwood/Cargo.lock index 153897e..1bb0708 100644 --- a/experimental/silverwood/Cargo.lock +++ b/experimental/silverwood/Cargo.lock @@ -2,6 +2,28 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "1.0.0" @@ -52,12 +74,91 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "append-only-bytes" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac436601d6bdde674a0d7fb593e829ffe7b3387c351b356dd20e2d40f5bf3ee5" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "arref" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ccd462b64c3c72f1be8305905a85d85403d768e8690c9b8bd3b9009a5761679" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "bumpalo" version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -92,10 +193,10 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -104,18 +205,183 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.118", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "ensure-cov" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33753185802e107b8fa907192af1f0eca13b1fb33327a59266d650fef29b2b4e" + +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures-core" version = "0.3.32" @@ -140,6 +406,60 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + +[[package]] +name = "generic-btree" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c1bce85c110ab718fd139e0cc89c51b63bd647b14a767e24bdfc77c83df79b" +dependencies = [ + "arref", + "heapless 0.9.3", + "itertools 0.11.0", + "loro-thunderdome", + "proc-macro2", + "rustc-hash", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -148,15 +468,79 @@ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -164,69 +548,650 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "indexmap" -version = "2.14.0" +name = "icu_collections" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ - "equivalent", - "hashbrown", + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.2" +name = "icu_locale_core" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] [[package]] -name = "js-sys" -version = "0.3.103" +name = "icu_normalizer" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", ] [[package]] -name = "libc" -version = "0.2.186" +name = "icu_normalizer_data" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] -name = "memchr" -version = "2.8.3" +name = "icu_properties" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] [[package]] -name = "once_cell" -version = "1.21.4" +name = "icu_properties_data" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] -name = "once_cell_polyfill" -version = "1.70.2" +name = "icu_provider" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] [[package]] -name = "pin-project-lite" -version = "0.2.17" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "idna" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "unicode-ident", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "loro" +version = "1.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221b567f34e3a7b38d05345b17797a7ca252d28e1fae479b4bb55c1a19616ddc" +dependencies = [ + "enum-as-inner 0.6.1", + "generic-btree", + "loro-common", + "loro-delta", + "loro-internal", + "loro-kv-store", + "rustc-hash", + "tracing", +] + +[[package]] +name = "loro-common" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b51042936156a1d537df8a38938ec8ed24b45de14530af933c1d02de39c9e056" +dependencies = [ + "arbitrary", + "enum-as-inner 0.6.1", + "leb128", + "loro-rle", + "nonmax", + "rustc-hash", + "serde", + "serde_columnar", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "loro-delta" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96f381e7fb5d4bf5605b4f4a9241f3adf61403ded978d757552004c8302cccbb" +dependencies = [ + "arrayvec", + "enum-as-inner 0.5.1", + "generic-btree", + "heapless 0.8.0", +] + +[[package]] +name = "loro-internal" +version = "1.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb15cd83866965b50b4893246699312624808a1f2e2d205432237287d947b822" +dependencies = [ + "append-only-bytes", + "arref", + "bytes", + "either", + "ensure-cov", + "enum-as-inner 0.6.1", + "enum_dispatch", + "generic-btree", + "getrandom 0.2.17", + "im", + "itertools 0.12.1", + "leb128", + "loom", + "loro-common", + "loro-delta", + "loro-kv-store", + "loro-rle", + "loro_fractional_index", + "md5", + "nonmax", + "num", + "num-traits", + "once_cell", + "parking_lot", + "pest", + "pest_derive", + "postcard", + "pretty_assertions", + "rand", + "rustc-hash", + "serde", + "serde_columnar", + "serde_json", + "smallvec", + "thiserror 1.0.69", + "thread_local", + "tracing", + "wasm-bindgen", + "xxhash-rust", +] + +[[package]] +name = "loro-kv-store" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950cc8bcc64dcff536e949fbc56ccc3d0759646fa441c7f76b3cd7c3eafa2096" +dependencies = [ + "bytes", + "ensure-cov", + "loro-common", + "lz4_flex", + "once_cell", + "quick_cache", + "rustc-hash", + "tracing", + "xxhash-rust", +] + +[[package]] +name = "loro-rle" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76400c3eea6bb39b013406acce964a8db39311534e308286c8d8721baba8ee20" +dependencies = [ + "append-only-bytes", + "num", + "smallvec", +] + +[[package]] +name = "loro-thunderdome" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3d053a135388e6b1df14e8af1212af5064746e9b87a06a345a7a779ee9695a" + +[[package]] +name = "loro_fractional_index" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aca7180674d0273ddf37049a5efcde4547fd5330d24abb7519bb9d9eb6780d5b" +dependencies = [ + "once_cell", + "rand", + "serde", +] + +[[package]] +name = "lz4_flex" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "pest_meta" +version = "2.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210" +dependencies = [ + "pest", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless 0.7.17", + "serde", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick_cache" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c6658afe513a3b484e3abfdaa0d03ef3c0bbf017542c178dd55f94eb3051f9" +dependencies = [ + "ahash", + "equivalent", + "hashbrown 0.16.1", + "parking_lot", ] [[package]] @@ -239,10 +1204,96 @@ dependencies = [ ] [[package]] -name = "r-efi" -version = "6.0.0" +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-automata" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] [[package]] name = "rustversion" @@ -250,6 +1301,24 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -260,6 +1329,31 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_columnar" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a16e404f17b16d0273460350e29b02d76ba0d70f34afdc9a4fa034c97d6c6eb" +dependencies = [ + "itertools 0.11.0", + "postcard", + "serde", + "serde_columnar_derive", + "thiserror 1.0.69", +] + +[[package]] +name = "serde_columnar_derive" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45958fce4903f67e871fbf15ac78e289269b21ebd357d6fecacdba233629112e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -277,7 +1371,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", ] [[package]] @@ -289,6 +1396,21 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "silverwood-cli" version = "0.1.0" @@ -301,24 +1423,73 @@ dependencies = [ name = "silverwood-core" version = "0.1.0" dependencies = [ + "loro", "serde", - "thiserror", + "serde_json", + "thiserror 2.0.18", + "time", "toml", + "url", "uuid", ] +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.118" @@ -330,13 +1501,44 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -347,7 +1549,56 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", ] [[package]] @@ -391,12 +1642,109 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -409,12 +1757,39 @@ version = "1.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a" dependencies = [ - "getrandom", + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.126" @@ -447,7 +1822,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -466,6 +1841,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -483,3 +1867,130 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "xxhash-rust" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d93c89cdc2d3a63c3ec48ffe926931bdc069eafa8e4402fe6d8f790c9d1e576" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zmij" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2f034a4bebf216c9e4b7083603e024cf930873fd67830cfb083c9fa33129d9" diff --git a/experimental/silverwood/Cargo.toml b/experimental/silverwood/Cargo.toml index c789901..1f270cc 100644 --- a/experimental/silverwood/Cargo.toml +++ b/experimental/silverwood/Cargo.toml @@ -13,6 +13,10 @@ silverwood-core = { path = "crates/silverwood-core" } uuid = { version = "1", features = ["v7", "serde"] } serde = { version = "1", features = ["derive"] } +serde_json = "1" toml = "0.8" thiserror = "2" clap = { version = "4", features = ["derive"] } +loro = "1" +url = "2" +time = { version = "0.3", features = ["std", "formatting"] } diff --git a/experimental/silverwood/TODO.md b/experimental/silverwood/TODO.md index 68fadf1..cd393c2 100644 --- a/experimental/silverwood/TODO.md +++ b/experimental/silverwood/TODO.md @@ -30,16 +30,29 @@ Part 0 notes: placeholder-name warning for the fmt/audit/deny check derivations. Harmless; checks pass. - Files are `git add`-staged (flake eval needs it) but NOT committed. -## Part 1 — workstream model + code-checkout provisioning (jj-colocated) -- [ ] `Workstream` / `Checkout` / `Session` structs ⇄ Loro container mapping (hand-written; no derive) -- [ ] one `LoroDoc` per workstream; snapshot export/import round-trip through `DocStore` -- [ ] `HttpsGitUrl` newtype + https-scheme validation (reject ssh/git@ → typed error) -- [ ] `CheckoutMode` open enum (`JjColocated` only); `NewWorkstream` / `NewPrimitive` types -- [ ] `CheckoutProvider` trait + `JjColocated` impl = `jj git clone --colocate working-copies/` -- [ ] `create_workstream`: mint id/created_at/active → write doc with checkout `state="pending"` → clone → flip `ready`/`failed` -- [ ] `list` (filter archived unless asked) / `get` / `archive` (in-doc tombstone, never delete the file) -- [ ] verify (structural): unit test round-trips a workstream doc; `create_workstream` against a real PUBLIC https repo clones + persists + reloads -- [ ] verify (interactive, user): create from a GitHub HTTPS URL → `working-copies/` is a colocated repo (`.jj` AND `.git` present, `jj status` works); reopen forest → workstream still there +## Part 1 — workstream model + code-checkout provisioning (jj-colocated) — DONE +- [x] `Workstream`/`Checkout`/`Session`/`WorkstreamBody` structs ⇄ Loro container mapping (`doc.rs`, hand-written; no derive) +- [x] one `LoroDoc` per workstream; snapshot export/import round-trip through `DocStore` +- [x] `HttpsGitUrl` newtype + https-scheme validation (reject ssh/git@/http → typed `InvalidSource`) + unit tests +- [x] `CheckoutMode` open enum (`#[non_exhaustive]`, `JjColocated` only); `NewWorkstream`/`NewPrimitive`/`CheckoutState` types +- [x] `CheckoutProvider` trait + `JjColocated` impl = `jj git clone --colocate working-copies/` (captures stderr on failure) +- [x] `create_workstream`: mint id/created_at/active → write doc (checkout `pending`) → provision → flip `ready`/`failed` in place → persist +- [x] `list` (filter archived unless asked, sorted by id) / `get` / `archive` (in-doc `status=archived` tombstone, file retained) +- [x] provider injected via `Forest::open_with_provider` so tests avoid network; `FakeOk`/`FakeFail` integration tests in nextest +- [x] verify (structural): `nix flake check` green — doc round-trip, CRUD, failed-provision-recoverable, persist-across-reopen (FakeProvider) +- [x] verify (behavioral): `#[ignore]` `real_jj_colocated_clone` (devshell, `cargo test -- --ignored`) — real clone → `.jj` AND `.git` present, state `Ready`, persists across reopen. PASSED. + +Part 1 notes: +- **Loro 1.13.6**. Confirmed API: `doc.get_map(name)` (root by name), `map.insert(k, scalar)`, `map.insert_container(k, LoroMap::new())`, + `doc.export(ExportMode::snapshot())` / `doc.import(&bytes)`, `doc.set_peer_id` (rejects only `PeerID::MAX`). Reads hydrate via + `serde_json::to_value(&map.get_deep_value())` → `serde_json::from_value` (LoroValue: Serialize; `get_deep_value` resolves nested + containers so no `🦜:` container refs). Nav for in-place edits: `map.get(k)` → `Some(ValueOrContainer::Container(Container::Map(m)))`. +- **Lineage preserved**: build the doc once (all containers created once); the pending→ready/failed flip and archive mutate EXISTING + containers in place (never rebuild), so future cross-forest merge stays valid. +- **deny**: added `BSL-1.0` (Boost) — a permissive license used by a crate in Loro's dependency tree. +- **created_at**: `time::OffsetDateTime::now_utc()` formatted RFC3339. +- **jj/git wrapper still deferred** — moved to Part 3: `create_workstream` shells out to `jj`, but the shipped `silverwood` binary + doesn't expose create until the Part 3 CLI, so the package only needs the jj/git PATH wrapper once `new` lands. (Dev shell has both.) ## Part 2 — associated data: kv + sessions - [ ] `set_kv` / `get_kv` / `list_kv` — namespaced, value = opaque JSON string, core never interprets @@ -48,6 +61,7 @@ Part 0 notes: - [ ] (later) session auto-discovery from the checkout path → `~/.claude/projects//` ## Part 3 — CLI surface (`--json`) +- [ ] **wrap `packages.default` with `makeWrapper` (jujutsu + git on PATH)** — deferred from Part 0/1; due now that the CLI's `new` shells out to `jj` - [ ] subcommands: `new`, `ls`, `show`, `archive`, `kv (get/set/ls)`, `session (attach/ls/rename/detach)` - [ ] all inputs explicit (no defaults): `new --name --source --mode jj-colocated` - [ ] dual output: human-readable default + `--json` for frontends diff --git a/experimental/silverwood/crates/silverwood-core/Cargo.toml b/experimental/silverwood/crates/silverwood-core/Cargo.toml index 236df85..70cd110 100644 --- a/experimental/silverwood/crates/silverwood-core/Cargo.toml +++ b/experimental/silverwood/crates/silverwood-core/Cargo.toml @@ -9,5 +9,9 @@ description = "Frontend-agnostic backend for code/agent workstreams: forest, wor [dependencies] uuid.workspace = true serde.workspace = true +serde_json.workspace = true toml.workspace = true thiserror.workspace = true +loro.workspace = true +url.workspace = true +time.workspace = true diff --git a/experimental/silverwood/crates/silverwood-core/src/doc.rs b/experimental/silverwood/crates/silverwood-core/src/doc.rs new file mode 100644 index 0000000..607d797 --- /dev/null +++ b/experimental/silverwood/crates/silverwood-core/src/doc.rs @@ -0,0 +1,133 @@ +//! Mapping between a [`Workstream`] and its Loro document. +//! +//! One `LoroDoc` per workstream. Loro has no derive layer, so this module owns +//! the container⇄struct translation by hand. Writes go through typed containers; +//! reads hydrate via `get_deep_value()` + serde. Mutations of an existing +//! document reuse its containers (load → edit in place → snapshot) so document +//! lineage — and therefore future mergeability — is preserved. + +use loro::{Container, ExportMode, LoroDoc, LoroMap, ValueOrContainer}; + +use crate::error::{Error, Result}; +use crate::id::WorkstreamId; +use crate::workstream::{Checkout, CheckoutState, Status, Workstream, WorkstreamBody}; + +/// Name of the single root map container. +const ROOT: &str = "workstream"; + +fn loro_err(e: E) -> Error { + Error::Loro(e.to_string()) +} + +/// Build a fresh document from a body, authored under `peer_id`. +pub(crate) fn build(peer_id: u64, body: &WorkstreamBody) -> Result { + let doc = LoroDoc::new(); + doc.set_peer_id(peer_id).map_err(loro_err)?; + + let root = doc.get_map(ROOT); + root.insert("name", body.name.as_str()).map_err(loro_err)?; + root.insert("status", body.status.as_str()) + .map_err(loro_err)?; + root.insert("kind", body.kind.as_str()).map_err(loro_err)?; + root.insert("created_at", body.created_at.as_str()) + .map_err(loro_err)?; + + let primitive = root + .insert_container("primitive", LoroMap::new()) + .map_err(loro_err)?; + primitive + .insert("source", body.primitive.source.as_str()) + .map_err(loro_err)?; + primitive + .insert("mode", body.primitive.mode.as_str()) + .map_err(loro_err)?; + + let checkouts = root + .insert_container("checkouts", LoroMap::new()) + .map_err(loro_err)?; + for (forest_id, checkout) in &body.checkouts { + write_checkout(&checkouts, forest_id, checkout)?; + } + + // Empty collection containers, created once so later edits mutate in place. + root.insert_container("sessions", LoroMap::new()) + .map_err(loro_err)?; + root.insert_container("kv", LoroMap::new()) + .map_err(loro_err)?; + + doc.commit(); + Ok(doc) +} + +/// Load a document from stored bytes, ready to author under `peer_id`. +pub(crate) fn load(peer_id: u64, bytes: &[u8]) -> Result { + let doc = LoroDoc::new(); + doc.import(bytes).map_err(loro_err)?; + doc.set_peer_id(peer_id).map_err(loro_err)?; + Ok(doc) +} + +/// Serialize a document to a snapshot for persistence. +pub(crate) fn snapshot(doc: &LoroDoc) -> Result> { + doc.export(ExportMode::snapshot()).map_err(loro_err) +} + +/// Hydrate a [`Workstream`] from stored bytes. +pub(crate) fn hydrate(id: WorkstreamId, bytes: &[u8]) -> Result { + let doc = LoroDoc::new(); + doc.import(bytes).map_err(loro_err)?; + let value = doc.get_map(ROOT).get_deep_value(); + let json = serde_json::to_value(&value) + .map_err(|e| Error::Corrupt(format!("workstream {id} not serializable: {e}")))?; + let body: WorkstreamBody = serde_json::from_value(json) + .map_err(|e| Error::Corrupt(format!("workstream {id}: {e}")))?; + Ok(Workstream { id, body }) +} + +/// Overwrite the root `status` scalar (used to archive). +pub(crate) fn set_status(doc: &LoroDoc, status: Status) -> Result<()> { + doc.get_map(ROOT) + .insert("status", status.as_str()) + .map_err(loro_err)?; + doc.commit(); + Ok(()) +} + +/// Overwrite the `state` of an existing per-forest checkout entry, in place. +pub(crate) fn set_checkout_state( + doc: &LoroDoc, + forest_id: &str, + state: CheckoutState, +) -> Result<()> { + let root = doc.get_map(ROOT); + let checkouts = child_map(&root, "checkouts")?; + let entry = child_map(&checkouts, forest_id)?; + entry.insert("state", state.as_str()).map_err(loro_err)?; + doc.commit(); + Ok(()) +} + +/// Write a checkout entry as a fresh nested map under `checkouts`. +fn write_checkout(checkouts: &LoroMap, forest_id: &str, checkout: &Checkout) -> Result<()> { + let entry = checkouts + .insert_container(forest_id, LoroMap::new()) + .map_err(loro_err)?; + entry + .insert("location", checkout.location.as_str()) + .map_err(loro_err)?; + entry + .insert("state", checkout.state.as_str()) + .map_err(loro_err)?; + entry + .insert("mode", checkout.mode.as_str()) + .map_err(loro_err)?; + Ok(()) +} + +/// Fetch an existing child map container from `parent` for in-place mutation. +fn child_map(parent: &LoroMap, key: &str) -> Result { + match parent.get(key) { + Some(ValueOrContainer::Container(Container::Map(map))) => Ok(map), + _ => Err(Error::Corrupt(format!("expected map at {key:?}"))), + } +} diff --git a/experimental/silverwood/crates/silverwood-core/src/error.rs b/experimental/silverwood/crates/silverwood-core/src/error.rs index 772398d..06510d1 100644 --- a/experimental/silverwood/crates/silverwood-core/src/error.rs +++ b/experimental/silverwood/crates/silverwood-core/src/error.rs @@ -1,5 +1,7 @@ use std::path::PathBuf; +use crate::id::WorkstreamId; + /// Errors returned by `silverwood-core`. #[derive(Debug, thiserror::Error)] pub enum Error { @@ -26,6 +28,26 @@ pub enum Error { /// A document store held a file whose name is not a valid workstream id. #[error("invalid workstream id in doc store: {0:?}")] InvalidDocId(PathBuf), + + /// The underlying CRDT engine (Loro) reported an error. + #[error("crdt error: {0}")] + Loro(String), + + /// A code-checkout source was not a valid HTTPS git endpoint. + #[error("invalid source: {0}")] + InvalidSource(String), + + /// Provisioning a code-checkout failed. + #[error("checkout provisioning failed: {0}")] + Provision(String), + + /// No workstream exists with the given id. + #[error("workstream not found: {0}")] + NotFound(WorkstreamId), + + /// A stored workstream document did not match the expected structure. + #[error("corrupt workstream document: {0}")] + Corrupt(String), } /// Convenience alias for results from this crate. diff --git a/experimental/silverwood/crates/silverwood-core/src/forest.rs b/experimental/silverwood/crates/silverwood-core/src/forest.rs index dca54bf..4bc250d 100644 --- a/experimental/silverwood/crates/silverwood-core/src/forest.rs +++ b/experimental/silverwood/crates/silverwood-core/src/forest.rs @@ -1,11 +1,21 @@ +use std::collections::BTreeMap; use std::fs; use std::io; use std::path::{Path, PathBuf}; +use time::format_description::well_known::Rfc3339; +use time::OffsetDateTime; + use crate::config::ForestConfig; -use crate::docstore::FilesDocStore; +use crate::doc; +use crate::docstore::{DocStore, FilesDocStore}; use crate::error::{Error, Result}; -use crate::id::ForestId; +use crate::id::{ForestId, WorkstreamId}; +use crate::provider::{CheckoutProvider, JjColocated}; +use crate::workstream::{ + Checkout, CheckoutPrimitive, CheckoutState, NewPrimitive, NewWorkstream, Status, Workstream, + WorkstreamBody, CODE_CHECKOUT_KIND, +}; /// Config file at the forest root. const CONFIG_FILE: &str = "config.toml"; @@ -24,11 +34,21 @@ pub struct Forest { root: PathBuf, config: ForestConfig, docs: FilesDocStore, + provider: Box, } impl Forest { - /// Open the forest at `root`, creating its layout and identity if absent. + /// Open the forest at `root` with the default (jj-colocated) provider. pub fn open(root: impl AsRef) -> Result { + Self::open_with_provider(root, Box::new(JjColocated)) + } + + /// Open the forest at `root` with an explicit checkout provider (used by + /// tests to avoid the network). + pub fn open_with_provider( + root: impl AsRef, + provider: Box, + ) -> Result { let root = root.as_ref().to_path_buf(); ensure_dir(&root)?; let workstreams = root.join(WORKSTREAMS_DIR); @@ -41,6 +61,7 @@ impl Forest { root, config, docs: FilesDocStore::new(workstreams), + provider, }) } @@ -64,9 +85,89 @@ impl Forest { &self.config } - /// The workstream document store. - pub fn docs(&self) -> &FilesDocStore { - &self.docs + /// Create a workstream, provisioning its code-checkout. + /// + /// The document is written first with the checkout `Pending`, then the + /// checkout is provisioned, then the state is flipped to `Ready`/`Failed` + /// in place. A failed provision leaves a recoverable workstream (its + /// document persists with state `Failed`) and surfaces the error. + pub fn create_workstream(&self, new: NewWorkstream) -> Result { + let (source, mode) = match &new.primitive { + NewPrimitive::CodeCheckout { source, mode } => (source, *mode), + }; + + let id = WorkstreamId::generate(); + let forest_id = self.id().to_string(); + let dest = self.root.join(WORKING_COPIES_DIR).join(id.to_string()); + + let mut checkouts = BTreeMap::new(); + checkouts.insert( + forest_id.clone(), + Checkout { + location: dest.display().to_string(), + state: CheckoutState::Pending, + mode, + }, + ); + let body = WorkstreamBody { + name: new.name, + status: Status::Active, + kind: CODE_CHECKOUT_KIND.to_string(), + created_at: now_rfc3339(), + primitive: CheckoutPrimitive { + source: source.as_str().to_string(), + mode, + }, + checkouts, + sessions: BTreeMap::new(), + kv: BTreeMap::new(), + }; + + // Persist the pending document, then provision, then record the outcome + // by mutating the same in-memory document in place. + let doc = doc::build(self.peer_id(), &body)?; + self.docs.save(id, &doc::snapshot(&doc)?)?; + + let provisioned = self.provider.provision(mode, source, &dest); + let state = if provisioned.is_ok() { + CheckoutState::Ready + } else { + CheckoutState::Failed + }; + doc::set_checkout_state(&doc, &forest_id, state)?; + self.docs.save(id, &doc::snapshot(&doc)?)?; + + provisioned?; + self.get(id) + } + + /// Load a workstream by id. + pub fn get(&self, id: WorkstreamId) -> Result { + let bytes = self.docs.load(id)?.ok_or(Error::NotFound(id))?; + doc::hydrate(id, &bytes) + } + + /// List workstreams, sorted by id (roughly creation order). Archived + /// workstreams are excluded unless `include_archived` is set. + pub fn list(&self, include_archived: bool) -> Result> { + let mut out = Vec::new(); + for id in self.docs.list_ids()? { + let ws = self.get(id)?; + if include_archived || ws.body.status == Status::Active { + out.push(ws); + } + } + out.sort_by_key(|w| w.id); + Ok(out) + } + + /// Archive a workstream (tombstone; the document is retained). + pub fn archive(&self, id: WorkstreamId) -> Result<()> { + let bytes = self.docs.load(id)?.ok_or(Error::NotFound(id))?; + let doc = doc::load(self.peer_id(), &bytes)?; + doc::set_status(&doc, Status::Archived)?; + self.docs.save(id, &doc::snapshot(&doc)?)?; + Ok(()) } } @@ -92,6 +193,13 @@ fn load_or_init_config(root: &Path) -> Result { } } +/// The current time as an RFC3339 string. +fn now_rfc3339() -> String { + OffsetDateTime::now_utc() + .format(&Rfc3339) + .expect("RFC3339 formatting of now_utc is infallible") +} + #[cfg(test)] mod tests { use super::*; diff --git a/experimental/silverwood/crates/silverwood-core/src/lib.rs b/experimental/silverwood/crates/silverwood-core/src/lib.rs index e6f62fd..cd4b799 100644 --- a/experimental/silverwood/crates/silverwood-core/src/lib.rs +++ b/experimental/silverwood/crates/silverwood-core/src/lib.rs @@ -5,18 +5,29 @@ //! its own document via a [`DocStore`]. This crate is the real API; frontends //! (including the CLI) are thin layers over it. See `DESIGN.md`. //! -//! Part 0 establishes the skeleton: identity types, the forest config, the -//! [`DocStore`] abstraction with a files-per-document backend, and -//! [`Forest::open`]. Workstream documents and provisioning arrive in Part 1. +//! Part 0 established the skeleton (identity, config, [`DocStore`], +//! [`Forest::open`]). Part 1 adds the workstream document — one Loro document +//! per workstream — and the code-checkout primitive with jj-colocated +//! provisioning. mod config; +mod doc; mod docstore; mod error; mod forest; mod id; +mod provider; +mod source; +mod workstream; pub use config::ForestConfig; pub use docstore::{DocStore, FilesDocStore}; pub use error::{Error, Result}; pub use forest::Forest; pub use id::{ForestId, WorkstreamId}; +pub use provider::{CheckoutProvider, JjColocated}; +pub use source::HttpsGitUrl; +pub use workstream::{ + Checkout, CheckoutMode, CheckoutPrimitive, CheckoutState, NewPrimitive, NewWorkstream, Session, + Status, Workstream, WorkstreamBody, +}; diff --git a/experimental/silverwood/crates/silverwood-core/src/provider.rs b/experimental/silverwood/crates/silverwood-core/src/provider.rs new file mode 100644 index 0000000..e3316cb --- /dev/null +++ b/experimental/silverwood/crates/silverwood-core/src/provider.rs @@ -0,0 +1,47 @@ +use std::path::Path; +use std::process::Command; + +use crate::error::{Error, Result}; +use crate::source::HttpsGitUrl; +use crate::workstream::CheckoutMode; + +/// Materializes a code-checkout on disk for a chosen [`CheckoutMode`]. +/// +/// Provisioning is a local, fallible side effect: an implementation clones +/// `source` into `dest` (which must not already exist). Injected into +/// [`crate::Forest`] so tests can substitute a fake that skips the network. +pub trait CheckoutProvider { + /// Clone `source` into `dest` using `mode`. + fn provision(&self, mode: CheckoutMode, source: &HttpsGitUrl, dest: &Path) -> Result<()>; +} + +/// The default provider: clones into a jj/git colocated repository via +/// `jj git clone --colocate`. +pub struct JjColocated; + +impl CheckoutProvider for JjColocated { + fn provision(&self, mode: CheckoutMode, source: &HttpsGitUrl, dest: &Path) -> Result<()> { + // Exhaustive today; a new mode forces this provider to handle it. + match mode { + CheckoutMode::JjColocated => {} + } + + let output = Command::new("jj") + .arg("git") + .arg("clone") + .arg("--colocate") + .arg(source.as_str()) + .arg(dest) + .output() + .map_err(|e| Error::Provision(format!("spawning jj: {e}")))?; + + if !output.status.success() { + return Err(Error::Provision(format!( + "`jj git clone --colocate` exited {}: {}", + output.status, + String::from_utf8_lossy(&output.stderr).trim() + ))); + } + Ok(()) + } +} diff --git a/experimental/silverwood/crates/silverwood-core/src/source.rs b/experimental/silverwood/crates/silverwood-core/src/source.rs new file mode 100644 index 0000000..558fda7 --- /dev/null +++ b/experimental/silverwood/crates/silverwood-core/src/source.rs @@ -0,0 +1,60 @@ +use crate::error::{Error, Result}; + +/// An HTTPS git endpoint a code-checkout is cloned from (e.g. a GitHub clone +/// URL). Validated at construction: the scheme must be `https` and a host must +/// be present. Non-HTTPS forms (`ssh://`, `git@…`) are rejected — per +/// `DESIGN.md`, silverwood accepts HTTPS sources only for now. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HttpsGitUrl(String); + +impl HttpsGitUrl { + /// Parse and validate an HTTPS git endpoint. + pub fn parse(input: &str) -> Result { + let url = + url::Url::parse(input).map_err(|e| Error::InvalidSource(format!("{input:?}: {e}")))?; + if url.scheme() != "https" { + return Err(Error::InvalidSource(format!( + "{input:?}: scheme must be https, got {:?}", + url.scheme() + ))); + } + if url.host_str().is_none() { + return Err(Error::InvalidSource(format!("{input:?}: missing host"))); + } + Ok(HttpsGitUrl(url.to_string())) + } + + /// The validated URL as a string. + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl std::fmt::Display for HttpsGitUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn accepts_https_github_url() { + let u = HttpsGitUrl::parse("https://github.com/octocat/Hello-World.git").unwrap(); + assert_eq!(u.as_str(), "https://github.com/octocat/Hello-World.git"); + } + + #[test] + fn rejects_ssh_and_scp_forms() { + assert!(HttpsGitUrl::parse("git@github.com:octocat/Hello-World.git").is_err()); + assert!(HttpsGitUrl::parse("ssh://git@github.com/octocat/Hello-World.git").is_err()); + } + + #[test] + fn rejects_http_and_garbage() { + assert!(HttpsGitUrl::parse("http://github.com/x/y.git").is_err()); + assert!(HttpsGitUrl::parse("not a url").is_err()); + } +} diff --git a/experimental/silverwood/crates/silverwood-core/src/workstream.rs b/experimental/silverwood/crates/silverwood-core/src/workstream.rs new file mode 100644 index 0000000..1f76e9a --- /dev/null +++ b/experimental/silverwood/crates/silverwood-core/src/workstream.rs @@ -0,0 +1,185 @@ +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +use crate::id::WorkstreamId; +use crate::source::HttpsGitUrl; + +/// The `kind` stored on a code-checkout workstream. +pub(crate) const CODE_CHECKOUT_KIND: &str = "code-checkout"; + +/// Lifecycle status of a workstream. Deletion is the `Archived` tombstone — +/// documents are never removed, so archival merges under future sync. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Status { + Active, + Archived, +} + +/// How a code-checkout is materialized on disk. Open enum: one mode today. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[non_exhaustive] +pub enum CheckoutMode { + /// Clone into a jj/git colocated repository (`jj git clone --colocate`). + JjColocated, +} + +/// Provisioning state of a per-forest checkout. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CheckoutState { + /// The document exists; the working copy is not yet provisioned. + Pending, + /// The working copy has been provisioned successfully. + Ready, + /// Provisioning failed; the workstream is recoverable. + Failed, +} + +impl Status { + pub(crate) fn as_str(self) -> &'static str { + match self { + Status::Active => "active", + Status::Archived => "archived", + } + } +} + +impl CheckoutMode { + pub(crate) fn as_str(self) -> &'static str { + match self { + CheckoutMode::JjColocated => "jj-colocated", + } + } +} + +impl CheckoutState { + pub(crate) fn as_str(self) -> &'static str { + match self { + CheckoutState::Pending => "pending", + CheckoutState::Ready => "ready", + CheckoutState::Failed => "failed", + } + } +} + +/// The code-checkout primitive's stored configuration. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CheckoutPrimitive { + /// The HTTPS source the checkout is cloned from. + pub source: String, + /// The mode the checkout was created with. + pub mode: CheckoutMode, +} + +/// A per-forest materialization of a workstream's checkout. Keyed by forest id +/// in the document, so two forests provisioning the same workstream never +/// conflict. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Checkout { + /// Absolute path to the working copy on the owning forest. + pub location: String, + /// Provisioning state. + pub state: CheckoutState, + /// The mode this checkout was provisioned with. + pub mode: CheckoutMode, +} + +/// A Claude Code session associated with a workstream. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Session { + /// Human-friendly name for the session. + pub name: String, + /// When the association was created (RFC3339). + pub created_at: String, +} + +/// The stored body of a workstream document — everything but its id (the id is +/// the document's key in the [`crate::DocStore`], not stored inside it). +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct WorkstreamBody { + /// Human-friendly name. + pub name: String, + /// Lifecycle status. + pub status: Status, + /// Primitive kind (`"code-checkout"`). + pub kind: String, + /// Creation timestamp (RFC3339), minted by core. + pub created_at: String, + /// The code-checkout primitive configuration. + pub primitive: CheckoutPrimitive, + /// Per-forest checkout materializations, keyed by forest id. + #[serde(default)] + pub checkouts: BTreeMap, + /// Associated Claude sessions, keyed by session id. + #[serde(default)] + pub sessions: BTreeMap, + /// Open frontend state: namespace → key → JSON-encoded value. + #[serde(default)] + pub kv: BTreeMap>, +} + +/// A workstream: its stable id plus its stored body. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Workstream { + /// Stable id (also the document key). + pub id: WorkstreamId, + /// The stored document body. + #[serde(flatten)] + pub body: WorkstreamBody, +} + +/// Parameters to create a workstream. Every field is explicit — core supplies +/// no defaults (see `DESIGN.md` §2.4). +#[derive(Debug, Clone)] +pub struct NewWorkstream { + /// Human-friendly name. + pub name: String, + /// The primitive to build the workstream around. + pub primitive: NewPrimitive, +} + +/// The primitive to build a new workstream around. +#[derive(Debug, Clone)] +pub enum NewPrimitive { + /// A code-checkout cloned from an HTTPS source in a given mode. + CodeCheckout { + source: HttpsGitUrl, + mode: CheckoutMode, + }, +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The hand-written `as_str` forms must match the serde string forms, since + /// the document is written via `as_str` and read back via serde. + #[test] + fn as_str_matches_serde() { + assert_eq!( + serde_json::to_value(Status::Active).unwrap(), + serde_json::json!(Status::Active.as_str()) + ); + assert_eq!( + serde_json::to_value(Status::Archived).unwrap(), + serde_json::json!(Status::Archived.as_str()) + ); + assert_eq!( + serde_json::to_value(CheckoutMode::JjColocated).unwrap(), + serde_json::json!(CheckoutMode::JjColocated.as_str()) + ); + for state in [ + CheckoutState::Pending, + CheckoutState::Ready, + CheckoutState::Failed, + ] { + assert_eq!( + serde_json::to_value(state).unwrap(), + serde_json::json!(state.as_str()) + ); + } + } +} diff --git a/experimental/silverwood/crates/silverwood-core/tests/provisioning.rs b/experimental/silverwood/crates/silverwood-core/tests/provisioning.rs new file mode 100644 index 0000000..5aacd45 --- /dev/null +++ b/experimental/silverwood/crates/silverwood-core/tests/provisioning.rs @@ -0,0 +1,128 @@ +//! End-to-end tests for workstream creation + code-checkout provisioning. +//! +//! The automated tests use a fake provider (no network, no `jj`). The real +//! jj-colocated clone is behind `#[ignore]` — run it in the dev shell with +//! `cargo test -- --ignored`. + +use std::path::{Path, PathBuf}; + +use silverwood_core::{ + CheckoutMode, CheckoutProvider, CheckoutState, Error, Forest, HttpsGitUrl, NewPrimitive, + NewWorkstream, Result, Status, +}; + +/// A fresh, unique temp dir for an isolated forest. +fn temp_forest(tag: &str) -> PathBuf { + let dir = std::env::temp_dir().join(format!("silverwood-it-{}-{}", std::process::id(), tag)); + let _ = std::fs::remove_dir_all(&dir); + dir +} + +/// A provider that just creates the destination directory — no network, no jj. +struct FakeOk; +impl CheckoutProvider for FakeOk { + fn provision(&self, _mode: CheckoutMode, _source: &HttpsGitUrl, dest: &Path) -> Result<()> { + std::fs::create_dir_all(dest).unwrap(); + Ok(()) + } +} + +/// A provider that always fails provisioning. +struct FakeFail; +impl CheckoutProvider for FakeFail { + fn provision(&self, _mode: CheckoutMode, _source: &HttpsGitUrl, _dest: &Path) -> Result<()> { + Err(Error::Provision("boom".into())) + } +} + +fn new_ws(name: &str) -> NewWorkstream { + NewWorkstream { + name: name.into(), + primitive: NewPrimitive::CodeCheckout { + source: HttpsGitUrl::parse("https://github.com/octocat/Hello-World.git").unwrap(), + mode: CheckoutMode::JjColocated, + }, + } +} + +#[test] +fn create_list_get_archive_round_trip() { + let dir = temp_forest("crud"); + let forest = Forest::open_with_provider(&dir, Box::new(FakeOk)).unwrap(); + + let ws = forest.create_workstream(new_ws("demo")).unwrap(); + assert_eq!(ws.body.status, Status::Active); + assert_eq!(ws.body.kind, "code-checkout"); + let checkout = ws.body.checkouts.values().next().expect("one checkout"); + assert_eq!(checkout.state, CheckoutState::Ready); + + // get reloads to an equal value. + assert_eq!(forest.get(ws.id).unwrap(), ws); + + // list shows the active workstream. + let listed = forest.list(false).unwrap(); + assert_eq!(listed.len(), 1); + assert_eq!(listed[0].id, ws.id); + + // archive tombstones it. + forest.archive(ws.id).unwrap(); + assert_eq!(forest.get(ws.id).unwrap().body.status, Status::Archived); + assert!(forest.list(false).unwrap().is_empty()); + assert_eq!(forest.list(true).unwrap().len(), 1); + + let _ = std::fs::remove_dir_all(&dir); +} + +#[test] +fn failed_provision_is_recoverable() { + let dir = temp_forest("fail"); + let forest = Forest::open_with_provider(&dir, Box::new(FakeFail)).unwrap(); + + let err = forest.create_workstream(new_ws("doomed")).unwrap_err(); + assert!(matches!(err, Error::Provision(_))); + + // The workstream persists with its checkout marked Failed — recoverable. + let all = forest.list(false).unwrap(); + assert_eq!(all.len(), 1); + let checkout = all[0].body.checkouts.values().next().unwrap(); + assert_eq!(checkout.state, CheckoutState::Failed); + + let _ = std::fs::remove_dir_all(&dir); +} + +#[test] +fn persists_across_reopen() { + let dir = temp_forest("reopen"); + let id = { + let forest = Forest::open_with_provider(&dir, Box::new(FakeOk)).unwrap(); + forest.create_workstream(new_ws("persist")).unwrap().id + }; + let forest = Forest::open_with_provider(&dir, Box::new(FakeOk)).unwrap(); + assert_eq!(forest.get(id).unwrap().body.name, "persist"); + + let _ = std::fs::remove_dir_all(&dir); +} + +#[test] +#[ignore = "network + jj; run via `cargo test -- --ignored`"] +fn real_jj_colocated_clone() { + let dir = temp_forest("real-clone"); + let forest = Forest::open(&dir).unwrap(); // real JjColocated provider + + let ws = forest.create_workstream(new_ws("hello")).unwrap(); + let checkout = ws.body.checkouts.values().next().unwrap(); + assert_eq!(checkout.state, CheckoutState::Ready); + + let checkout_dir = Path::new(&checkout.location); + assert!(checkout_dir.join(".jj").exists(), ".jj must exist"); + assert!( + checkout_dir.join(".git").exists(), + ".git must exist (colocated)" + ); + + // Workstream persists across reopen. + let reopened = Forest::open(&dir).unwrap(); + assert_eq!(reopened.get(ws.id).unwrap().id, ws.id); + + let _ = std::fs::remove_dir_all(&dir); +} diff --git a/experimental/silverwood/deny.toml b/experimental/silverwood/deny.toml index 795df49..ad4c6a7 100644 --- a/experimental/silverwood/deny.toml +++ b/experimental/silverwood/deny.toml @@ -17,6 +17,7 @@ allow = [ "Apache-2.0 WITH LLVM-exception", "BSD-2-Clause", "BSD-3-Clause", + "BSL-1.0", "ISC", "Unicode-3.0", "Unicode-DFS-2016", -- 2.51.2