diff --git a/Cargo.lock b/Cargo.lock index 185fe345..3d735ff8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,7 +486,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "c0mpute-api" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "c0mpute-proto", @@ -501,7 +501,7 @@ dependencies = [ [[package]] name = "c0mpute-bench" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "blake3", @@ -512,7 +512,7 @@ dependencies = [ [[package]] name = "c0mpute-cli" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "axum", @@ -546,7 +546,7 @@ dependencies = [ [[package]] name = "c0mpute-core" -version = "0.3.4" +version = "0.3.5" dependencies = [ "aes-gcm", "anyhow", @@ -596,7 +596,7 @@ dependencies = [ [[package]] name = "c0mpute-doctor" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "serde", @@ -608,7 +608,7 @@ dependencies = [ [[package]] name = "c0mpute-envelope" -version = "0.3.4" +version = "0.3.5" dependencies = [ "blake3", "bs58", @@ -621,7 +621,7 @@ dependencies = [ [[package]] name = "c0mpute-gateway" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "axum", @@ -643,7 +643,7 @@ dependencies = [ [[package]] name = "c0mpute-market" -version = "0.3.4" +version = "0.3.5" dependencies = [ "c0mpute-envelope", "serde", @@ -653,7 +653,7 @@ dependencies = [ [[package]] name = "c0mpute-net" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "async-trait", @@ -671,7 +671,7 @@ dependencies = [ [[package]] name = "c0mpute-placement" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "async-trait", @@ -692,7 +692,7 @@ dependencies = [ [[package]] name = "c0mpute-proto" -version = "0.3.4" +version = "0.3.5" dependencies = [ "blake3", "hex", @@ -704,7 +704,7 @@ dependencies = [ [[package]] name = "c0mpute-sealed" -version = "0.3.4" +version = "0.3.5" dependencies = [ "aes", "aes-gcm", @@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "c0mpute-secure-chat" -version = "0.3.4" +version = "0.3.5" dependencies = [ "aes-gcm", "anyhow", @@ -749,7 +749,7 @@ dependencies = [ [[package]] name = "c0mpute-store" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "blake3", @@ -767,7 +767,7 @@ dependencies = [ [[package]] name = "c0mpute-transcode" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "c0mpute-proto", @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "c0mpute-update" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "hex", @@ -795,7 +795,7 @@ dependencies = [ [[package]] name = "c0mpute-verify" -version = "0.3.4" +version = "0.3.5" dependencies = [ "anyhow", "blake3", diff --git a/Cargo.toml b/Cargo.toml index 09d9c622..939e857d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ ] [workspace.package] -version = "0.3.4" +version = "0.3.5" edition = "2024" license = "MIT" repository = "https://github.com/profullstack/c0mpute" diff --git a/apps/tui/package.json b/apps/tui/package.json index 05c215d1..9a1de6eb 100644 --- a/apps/tui/package.json +++ b/apps/tui/package.json @@ -1,6 +1,6 @@ { "name": "@c0mpute/tui", - "version": "0.3.4", + "version": "0.3.5", "private": true, "type": "module", "bin": { diff --git a/apps/web/package.json b/apps/web/package.json index 4dddd73d..635dec47 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "c0mpute", - "version": "0.3.4", + "version": "0.3.5", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/app/releases/latest.json/route.ts b/apps/web/src/app/releases/latest.json/route.ts index 054b949e..86ef0ecf 100644 --- a/apps/web/src/app/releases/latest.json/route.ts +++ b/apps/web/src/app/releases/latest.json/route.ts @@ -28,7 +28,7 @@ export const dynamic = "force-dynamic"; * this in sync with the workspace version in `Cargo.toml` on each release so a * freshly built CLI reports "already latest" rather than a phantom upgrade. */ -const FALLBACK_VERSION = "0.3.4"; +const FALLBACK_VERSION = "0.3.5"; const FALLBACK_MIN_REQUIRED = "0.0.1"; type Channel = "stable" | "beta" | "nightly"; diff --git a/node/crates/c0mpute-core/src/hosting/mod.rs b/node/crates/c0mpute-core/src/hosting/mod.rs index 41043048..a703e3a7 100644 --- a/node/crates/c0mpute-core/src/hosting/mod.rs +++ b/node/crates/c0mpute-core/src/hosting/mod.rs @@ -118,6 +118,16 @@ pub enum HostingMsg { /// Where it is paid for marketplace copies. #[serde(default, skip_serializing_if = "Option::is_none")] payout: Option, + /// It hosts but cannot take another copy (disk at its free floor or + /// hosting budget used up). Other nodes leave it out of the + /// rendezvous fill, so a site it would rank first for still gets its + /// copies from the next ranked nodes. Absent (false) from older nodes. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + full: bool, + /// Free disk above its floor, whole GiB: nodes with more room rank + /// first in the rendezvous fill. Absent from older nodes. + #[serde(default, skip_serializing_if = "Option::is_none")] + room_gib: Option, }, /// A signed availability receipt (proof-of-storage challenge result). /// Older nodes do not know this variant and skip it. @@ -395,6 +405,8 @@ impl Hosting { addrs, price, payout, + full, + room_gib, } => { if let Some(pid) = msg.source { for a in addrs.iter().take(8) { @@ -416,10 +428,10 @@ impl Hosting { let (_, all) = partial.remove(&source).unwrap_or_default(); let price = price.as_deref().and_then(|p| crate::payments::parse_usd(p).ok()); let payout = payout.filter(|p| p.valid()); - h.0.map - .write() - .await - .apply_holding_meta(&source, all, Instant::now(), hosts, price, payout); + let mut map = h.0.map.write().await; + map.apply_holding_meta(&source, all, Instant::now(), hosts, price, payout); + map.set_full(&source, full); + map.set_room(&source, room_gib); } } HostingMsg::Receipt { receipt } => { @@ -462,15 +474,13 @@ impl Hosting { } else { (None, None) }; + let full = self.0.hosts && self.is_full().await; + let room_gib = if self.0.hosts { self.room_gib() } else { None }; if self.0.hosts || !mine.is_empty() { - self.0.map.write().await.apply_holding_meta( - &self.0.me, - mine, - Instant::now(), - self.0.hosts, - price, - payout.clone(), - ); + let mut map = self.0.map.write().await; + map.apply_holding_meta(&self.0.me, mine, Instant::now(), self.0.hosts, price, payout.clone()); + map.set_full(&self.0.me, full); + map.set_room(&self.0.me, room_gib); } else { return; } @@ -499,6 +509,8 @@ impl Hosting { addrs: addrs.clone(), price: price.map(crate::payments::format_usd), payout: payout.clone(), + full, + room_gib, }) .await; } @@ -546,6 +558,9 @@ impl Hosting { /// One pass: prune silent peers, then replicate, update or drop. pub async fn repair_once(&self) { + // A full node does not put itself forward for new copies (it would be + // chosen, fail, and hold the slot from the next ranked node). + let can_take = self.0.hosts && !self.is_full().await; let actions: Vec<(SiteRecord, Action)> = { let mut map = self.0.map.write().await; map.prune(Instant::now(), self.0.timing.holder_ttl); @@ -556,7 +571,7 @@ impl Hosting { &map, &r.site_id, &self.0.me, - self.0.hosts, + can_take, TARGET_REPLICAS, MAX_REPLICAS, ); @@ -608,6 +623,23 @@ impl Hosting { }); } + /// True when this node cannot take another copy: its disk is at the free + /// floor (with a little headroom) or its hosting budget is used up. + pub async fn is_full(&self) -> bool { + let (disk_free, disk_total) = crate::opendisk::disk_for(self.0.store.root()); + !leaves_free_floor(disk_free, disk_total, FULL_HEADROOM_BYTES) + || self.held_bytes().await.saturating_add(FULL_HEADROOM_BYTES) > self.0.max_bytes + } + + /// Free disk above the floor in whole GiB, or None when the disk is unknown. + fn room_gib(&self) -> Option { + let (disk_free, disk_total) = crate::opendisk::disk_for(self.0.store.root()); + if disk_total == 0 { + return None; + } + Some(disk_free.saturating_sub(free_disk_floor(disk_total)) / GIB_BYTES) + } + async fn held_bytes(&self) -> u64 { self.0.catalog.read().await.sites.values().map(|r| r.size).sum() } @@ -1429,6 +1461,9 @@ const GIB_BYTES: u64 = 1024 * 1024 * 1024; /// whichever is more, capped at 50 GiB. Every node hosts by default, including /// busy production boxes, so the 5 GiB hosting budget alone could fill a disk /// that was already nearly full. +/// Room a node must have beyond its floor/budget to call itself not full. +const FULL_HEADROOM_BYTES: u64 = 256 * 1024 * 1024; + fn free_disk_floor(disk_total: u64) -> u64 { (disk_total / 20).clamp(10 * GIB_BYTES, 50 * GIB_BYTES) } @@ -1646,6 +1681,8 @@ mod tests { addrs: vec![], price: Some("0.004".into()), payout: None, + full: false, + room_gib: Some(3000), }; let bytes = serde_json::to_vec(&m).unwrap(); assert!(bytes.len() < 1024); @@ -1679,6 +1716,8 @@ mod tests { wallet: "0x".to_string() + &"a".repeat(40), coin: "USDC_POL".into(), }), + full: true, + room_gib: Some(u64::MAX), }) .unwrap(); assert!(bytes.len() < 64 * 1024, "{} bytes", bytes.len()); diff --git a/node/crates/c0mpute-core/src/hosting/replicas.rs b/node/crates/c0mpute-core/src/hosting/replicas.rs index 5af1c97a..e2a3e742 100644 --- a/node/crates/c0mpute-core/src/hosting/replicas.rs +++ b/node/crates/c0mpute-core/src/hosting/replicas.rs @@ -35,6 +35,12 @@ pub struct Holding { pub price: Option, /// Where it wants marketplace payments, when it says. pub payout: Option, + /// It hosts but cannot take another copy right now; left out of the + /// rendezvous fill (its existing copies still count). + pub full: bool, + /// Free disk above its floor, GiB, when it says. More room ranks higher + /// in the fill (see [`space_tier`]). + pub room_gib: Option, } #[derive(Debug, Default)] @@ -98,10 +104,26 @@ impl ReplicaMap { hosts, price, payout, + full: false, + room_gib: None, }, ); } + /// Record how much room `peer` says it has (see [`Holding::room_gib`]). + pub fn set_room(&mut self, peer: &str, room_gib: Option) { + if let Some(h) = self.peers.get_mut(peer) { + h.room_gib = room_gib; + } + } + + /// Mark whether `peer` can take new copies (see [`Holding::full`]). + pub fn set_full(&mut self, peer: &str, full: bool) { + if let Some(h) = self.peers.get_mut(peer) { + h.full = full; + } + } + /// Keep a verified receipt (deduplicated, newest [`proof::KEEP_PER_HOLDER`] /// per holder). Returns true when it was new. pub fn apply_receipt(&mut self, r: Receipt) -> bool { @@ -168,6 +190,18 @@ impl ReplicaMap { .retain(|_, h| h.seen.is_some_and(|t| now.duration_since(t) <= ttl)); } + /// Live hosting peers that can take a new copy, sorted. + pub fn candidate_peers(&self) -> Vec { + let mut v: Vec = self + .peers + .iter() + .filter(|(_, h)| h.hosts && !h.full) + .map(|(p, _)| p.clone()) + .collect(); + v.sort(); + v + } + /// Live hosting peers, sorted. pub fn live_peers(&self) -> Vec { let mut v: Vec = self @@ -274,6 +308,26 @@ pub fn chosen(site_id: &str, candidates: &[String], need: usize) -> Vec ranked.into_iter().take(need).map(|(p, _)| p.clone()).collect() } +/// A peer's priority tier from the room it reports: the bit length of its +/// free GiB, so a node with about twice the room ranks one tier higher. A +/// peer that does not say (an older node) sits at the 64 GiB tier. Integer +/// only, so every node computes the same tiers. +pub fn space_tier(room_gib: Option) -> u32 { + let gib = room_gib.unwrap_or(64); + 64 - gib.leading_zeros() +} + +/// Like [`chosen`], but nodes with more free disk come first: candidates are +/// ordered by [`space_tier`], then by rendezvous score within a tier, so the +/// biggest disks take copies first while load still spreads among equals. +pub fn chosen_by_space(map: &ReplicaMap, site_id: &str, candidates: &[String], need: usize) -> Vec { + let tier = |p: &String| space_tier(map.peers.get(p).and_then(|h| h.room_gib)); + let mut ranked: Vec<(&String, u32, [u8; 32])> = + candidates.iter().map(|p| (p, tier(p), score(site_id, p))).collect(); + ranked.sort_by(|a, b| b.1.cmp(&a.1).then(b.2.cmp(&a.2)).then(a.0.cmp(b.0))); + ranked.into_iter().take(need).map(|(p, _, _)| p.clone()).collect() +} + /// When a site has more than `max` live copies, the lowest-ranked holder /// lets its copy go. Returns that holder. pub fn surplus(site_id: &str, holders: &[String], max: usize) -> Option { @@ -331,15 +385,17 @@ pub fn decide(map: &ReplicaMap, site_id: &str, me: &str, i_host: bool, target: u // The rendezvous fill. Preferred hosts that do not hold yet are left out: // they replicate on their own, and counting on them would leave the site // short if one never manages to. + // Full peers are left out too: one that ranks first but cannot take the + // copy would otherwise hold the slot from the next ranked node forever. let mut candidates: Vec = map - .live_peers() + .candidate_peers() .into_iter() .filter(|p| !holders.contains(p) && !preferred.contains(p)) .collect(); if !candidates.iter().any(|p| p == me) { candidates.push(me.to_string()); } - if chosen(site_id, &candidates, target - holders.len()) + if chosen_by_space(map, site_id, &candidates, target - holders.len()) .iter() .any(|p| p == me) { @@ -353,6 +409,63 @@ pub fn decide(map: &ReplicaMap, site_id: &str, me: &str, i_host: bool, target: u mod tests { use super::*; + #[test] + fn a_full_peer_that_ranks_first_does_not_hold_the_slot() { + let kp = c0mpute_net::libp2p::identity::Keypair::generate_ed25519(); + let r = rec(&kp, 1); + let site = r.site_id.clone(); + let mut m = ReplicaMap::default(); + m.apply_record(r); + let now = Instant::now(); + let peers: Vec = (0..6).map(|i| format!("h{i}")).collect(); + for p in &peers { + m.apply_holding_meta(p, HashMap::new(), now, true, None, None); + } + // Whoever ranks first for this site is full. + let first = chosen(&site, &peers, 1)[0].clone(); + m.set_full(&first, true); + let takers: Vec = peers + .iter() + .filter(|p| decide(&m, &site, p, !m.peers[*p].full, 3, 5) == Action::Replicate) + .cloned() + .collect(); + assert_eq!(takers.len(), 3, "three other nodes fill the site: {takers:?}"); + assert!(!takers.contains(&first)); + } + + #[test] + fn nodes_with_more_room_take_copies_first() { + let kp = c0mpute_net::libp2p::identity::Keypair::generate_ed25519(); + let r = rec(&kp, 1); + let site = r.site_id.clone(); + let mut m = ReplicaMap::default(); + m.apply_record(r); + let now = Instant::now(); + let peers: Vec = (0..8).map(|i| format!("h{i}")).collect(); + for p in &peers { + m.apply_holding_meta(p, HashMap::new(), now, true, None, None); + m.set_room(p, Some(20)); + } + // The three that rank LAST by hash have the big disks. + let mut by_hash = chosen(&site, &peers, peers.len()); + let big: Vec = by_hash.split_off(peers.len() - 3); + for p in &big { + m.set_room(p, Some(3000)); + } + let takers: Vec = peers + .iter() + .filter(|p| decide(&m, &site, p, true, 3, 5) == Action::Replicate) + .cloned() + .collect(); + let mut want = big.clone(); + want.sort(); + assert_eq!(takers, want, "the roomiest disks fill the site"); + assert_eq!(space_tier(Some(0)), 0); + assert_eq!(space_tier(Some(1)), 1); + assert_eq!(space_tier(Some(3000)), 12); + assert_eq!(space_tier(None), space_tier(Some(64))); + } + fn rec(kp: &c0mpute_net::libp2p::identity::Keypair, seq: u64) -> SiteRecord { SiteRecord::sign(kp, "site", &Hash::of(&seq.to_le_bytes()), seq, 10, vec![]).unwrap() } diff --git a/package.json b/package.json index 16f37dc3..02f04d02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "c0mpute-monorepo", - "version": "0.3.4", + "version": "0.3.5", "private": true, "workspaces": [ "apps/*", diff --git a/packages/captcha/package.json b/packages/captcha/package.json index d5b07131..55ce5b3d 100644 --- a/packages/captcha/package.json +++ b/packages/captcha/package.json @@ -1,6 +1,6 @@ { "name": "@profullstack/c0mpute-captcha", - "version": "0.3.4", + "version": "0.3.5", "description": "Solve image, slider, gap, click-text, audio and math captchas on the c0mpute network: library (fetch().then(solveCaptcha)), CLI and stdio MCP server.", "type": "module", "license": "MIT", diff --git a/packages/shared/package.json b/packages/shared/package.json index 3d563d9a..82c724c5 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@c0mpute/shared", - "version": "0.3.4", + "version": "0.3.5", "private": true, "type": "module", "main": "./dist/index.js", diff --git a/plugins/ad-generator-agent/module.toml b/plugins/ad-generator-agent/module.toml index 77c33a1e..a92a6b54 100644 --- a/plugins/ad-generator-agent/module.toml +++ b/plugins/ad-generator-agent/module.toml @@ -21,7 +21,7 @@ [module] id = "ad-generator-agent" name = "Ad Generator Agent — every ad type from one URL" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "applications" status = "available" diff --git a/plugins/ads-manager/module.toml b/plugins/ads-manager/module.toml index 77bc8667..d3cf9b02 100644 --- a/plugins/ads-manager/module.toml +++ b/plugins/ads-manager/module.toml @@ -38,7 +38,7 @@ [module] id = "ads-manager" name = "Ads Manager — shared ad operations layer" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "applications" status = "partial" diff --git a/plugins/apps/module.toml b/plugins/apps/module.toml index 8b03b8a6..6c698ba1 100644 --- a/plugins/apps/module.toml +++ b/plugins/apps/module.toml @@ -18,7 +18,7 @@ [module] id = "apps" name = "Apps — backends with 3x redundancy" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "infrastructure" status = "available" diff --git a/plugins/captcha/module.toml b/plugins/captcha/module.toml index 5c7af001..9a4813f3 100644 --- a/plugins/captcha/module.toml +++ b/plugins/captcha/module.toml @@ -22,7 +22,7 @@ [module] id = "captcha" name = "Captcha — image, slider, audio and math solving" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "ai" status = "available" diff --git a/plugins/coinpay/module.toml b/plugins/coinpay/module.toml index 4971456f..aa7256c7 100644 --- a/plugins/coinpay/module.toml +++ b/plugins/coinpay/module.toml @@ -7,7 +7,7 @@ [module] id = "coinpay" name = "CoinPay — DID, payments, escrow, reputation" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "applications" status = "available" diff --git a/plugins/crawl/module.toml b/plugins/crawl/module.toml index aabf9a13..a4d046e9 100644 --- a/plugins/crawl/module.toml +++ b/plugins/crawl/module.toml @@ -8,7 +8,7 @@ [module] id = "crawl" name = "Crawl — distributed web fetching" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "data" status = "available" diff --git a/plugins/crowdgpt/module.toml b/plugins/crowdgpt/module.toml index cb27cb38..45a9e7ed 100644 --- a/plugins/crowdgpt/module.toml +++ b/plugins/crowdgpt/module.toml @@ -43,7 +43,7 @@ [module] id = "crowdgpt" name = "CrowdGPT: donate idle compute" -version = "0.3.4" +version = "0.3.5" kind = "donation" category = "ai" status = "available" diff --git a/plugins/ctv-ads/module.toml b/plugins/ctv-ads/module.toml index f3728056..6c579978 100644 --- a/plugins/ctv-ads/module.toml +++ b/plugins/ctv-ads/module.toml @@ -28,7 +28,7 @@ [module] id = "ctv-ads" name = "CTV Ads — VAST and VMAP from every gateway" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "applications" status = "partial" diff --git a/plugins/diffusion/module.toml b/plugins/diffusion/module.toml index d8b439df..60b3c189 100644 --- a/plugins/diffusion/module.toml +++ b/plugins/diffusion/module.toml @@ -11,7 +11,7 @@ [module] id = "diffusion" name = "Diffusion — image generation" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "ai" status = "available" diff --git a/plugins/embed/module.toml b/plugins/embed/module.toml index 4a340fb9..e5069de0 100644 --- a/plugins/embed/module.toml +++ b/plugins/embed/module.toml @@ -19,7 +19,7 @@ [module] id = "embed" name = "Embeddings — vector generation" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "ai" status = "available" diff --git a/plugins/gameserver/module.toml b/plugins/gameserver/module.toml index 364cac7d..b61b3acc 100644 --- a/plugins/gameserver/module.toml +++ b/plugins/gameserver/module.toml @@ -55,7 +55,7 @@ [module] id = "gameserver" name = "Game server — Minecraft / Factorio / Valheim" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "infrastructure" status = "available" diff --git a/plugins/hosting/module.toml b/plugins/hosting/module.toml index de916b81..701a34a0 100644 --- a/plugins/hosting/module.toml +++ b/plugins/hosting/module.toml @@ -30,7 +30,7 @@ [module] id = "hosting" name = "Hosting — p2p static sites" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "data" status = "available" diff --git a/plugins/infernet/module.toml b/plugins/infernet/module.toml index 1b05edcd..e7e4de8b 100644 --- a/plugins/infernet/module.toml +++ b/plugins/infernet/module.toml @@ -22,7 +22,7 @@ [module] id = "infernet" name = "Infernet Protocol — AI inference" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "ai" status = "available" diff --git a/plugins/live-stream/module.toml b/plugins/live-stream/module.toml index a9f402bd..5c148d36 100644 --- a/plugins/live-stream/module.toml +++ b/plugins/live-stream/module.toml @@ -34,7 +34,7 @@ [module] id = "live-stream" name = "Live Stream — p2p HLS live streaming" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "media" status = "available" diff --git a/plugins/music/module.toml b/plugins/music/module.toml index d3f85188..d205bbf2 100644 --- a/plugins/music/module.toml +++ b/plugins/music/module.toml @@ -15,7 +15,7 @@ [module] id = "music" name = "Music — song generation (ACE-Step 1.5)" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "ai" status = "available" diff --git a/plugins/ocr/module.toml b/plugins/ocr/module.toml index 23530f34..f532c213 100644 --- a/plugins/ocr/module.toml +++ b/plugins/ocr/module.toml @@ -19,7 +19,7 @@ [module] id = "ocr" name = "OCR — document text extraction" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "ai" status = "available" diff --git a/plugins/podcast-ads/module.toml b/plugins/podcast-ads/module.toml index 327b8097..2095ecbf 100644 --- a/plugins/podcast-ads/module.toml +++ b/plugins/podcast-ads/module.toml @@ -31,7 +31,7 @@ [module] id = "podcast-ads" name = "Podcast Ads — dynamic ad insertion" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "applications" status = "partial" diff --git a/plugins/podcasting/module.toml b/plugins/podcasting/module.toml index a2fdd7ba..8e356548 100644 --- a/plugins/podcasting/module.toml +++ b/plugins/podcasting/module.toml @@ -24,7 +24,7 @@ [module] id = "podcasting" name = "Podcasting — p2p podcast publishing" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "media" status = "available" diff --git a/plugins/podcasts/module.toml b/plugins/podcasts/module.toml index cb0baf64..f837427d 100644 --- a/plugins/podcasts/module.toml +++ b/plugins/podcasts/module.toml @@ -24,7 +24,7 @@ [module] id = "podcasts" name = "Podcasts — p2p podcast listener" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "media" status = "available" diff --git a/plugins/render/module.toml b/plugins/render/module.toml index 7c6f555a..c753dc53 100644 --- a/plugins/render/module.toml +++ b/plugins/render/module.toml @@ -33,7 +33,7 @@ [module] id = "render" name = "Render — Blender 3D rendering" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "media" status = "available" diff --git a/plugins/secure-chat/module.toml b/plugins/secure-chat/module.toml index 64550ad7..9bd35597 100644 --- a/plugins/secure-chat/module.toml +++ b/plugins/secure-chat/module.toml @@ -21,7 +21,7 @@ [module] id = "secure-chat" name = "Secure Chat — E2E encrypted p2p messaging" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "applications" status = "available" diff --git a/plugins/storage/module.toml b/plugins/storage/module.toml index 1a56631c..ddb1e165 100644 --- a/plugins/storage/module.toml +++ b/plugins/storage/module.toml @@ -20,7 +20,7 @@ [module] id = "storage" name = "Storage — Reed-Solomon shard service" -version = "0.3.4" +version = "0.3.5" kind = "service" category = "data" status = "available" diff --git a/plugins/train/module.toml b/plugins/train/module.toml index 307127ef..7034f1b4 100644 --- a/plugins/train/module.toml +++ b/plugins/train/module.toml @@ -55,7 +55,7 @@ [module] id = "train" name = "Fine-tune — LoRA training" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "ai" status = "available" diff --git a/plugins/transcode/module.toml b/plugins/transcode/module.toml index d2c5b1ec..453a305c 100644 --- a/plugins/transcode/module.toml +++ b/plugins/transcode/module.toml @@ -7,7 +7,7 @@ [module] id = "transcode" name = "FFmpeg Transcoding" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "media" status = "available" diff --git a/plugins/tts/module.toml b/plugins/tts/module.toml index 45428e0f..6a7f7813 100644 --- a/plugins/tts/module.toml +++ b/plugins/tts/module.toml @@ -15,7 +15,7 @@ [module] id = "tts" name = "TTS — text-to-speech" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "ai" status = "available" diff --git a/plugins/video/module.toml b/plugins/video/module.toml index 89ea3bf8..3af1496e 100644 --- a/plugins/video/module.toml +++ b/plugins/video/module.toml @@ -65,7 +65,7 @@ [module] id = "video" name = "Video — generation (Wan 2.2, Wan 2.1 1.3B on 8 GiB or CPU, LTX-2.3 with audio)" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "ai" status = "available" diff --git a/plugins/webhook/module.toml b/plugins/webhook/module.toml index 1695348b..056eb973 100644 --- a/plugins/webhook/module.toml +++ b/plugins/webhook/module.toml @@ -7,7 +7,7 @@ [module] id = "webhook" name = "Webhooks — paid, end-to-end encrypted delivery" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "infrastructure" status = "available" diff --git a/plugins/whisper/module.toml b/plugins/whisper/module.toml index ad351413..5a382e74 100644 --- a/plugins/whisper/module.toml +++ b/plugins/whisper/module.toml @@ -7,7 +7,7 @@ [module] id = "whisper" name = "Whisper — speech-to-text" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "ai" status = "available" diff --git a/plugins/workflow-step/module.toml b/plugins/workflow-step/module.toml index 8092fd6f..a906a3f1 100644 --- a/plugins/workflow-step/module.toml +++ b/plugins/workflow-step/module.toml @@ -14,7 +14,7 @@ [module] id = "workflow-step" name = "Workflow step — webhook workflow steps (meshhook)" -version = "0.3.4" +version = "0.3.5" kind = "workload" category = "infrastructure" status = "available"