openvibers/openvibe.ai Commits
Community pools: route, verify and label a pool's offer as one machine's (#335)
Changes
@@@ -12,8 +12,14 @@* - spot re-runs: about 1% of answers are re-run on a trusted machine serving the same model and compared;* - reputation: canaries, spot runs and observed uptime, 0-100.* A new machine serves only the free tier (a run with no Balance payer, ctx.creditsRequired) until verified. The answer-- * always carries `openvibe.community`, so a caller can see it came from a person's machine. The prompt is visible to-- * that machine, which is why only opted-in requests ever go there.++ * always carries `openvibe.community`, so a caller can see it came from a person's machine (its `label` is "Community", or++ * "Community pool (N machines)" for a pool). The prompt is visible to that machine, which is why only opted-in requests++ * ever go there.++ *++ * Pools (ai.offer-pool@1, server/community/pools.js): several of one owner's machines on one local network serving one++ * model through llama.cpp RPC. Network names the pool on the head's offer; requests go to the head (which sees the++ * prompt; the others see tensors), and the same checks as one machine run on the pool's answers, keyed on its exact++ * membership. A new pool, and one that failed verification, serves the free tier only and earns nothing there.** Money: a self-served run (the requester is the owner, or merged into it) earns 0. Verified credits are `pending`* until the requester's own Balance charge exists in the same request (`spendable`), so none are spendable yet. A@@@ -32,6 +38,7 @@*/const { createServiceTokenClient } = require('openvibe-sdk/auth');const { AiError, sha256 } = require('../util');++const pools = require('./pools');const TEXT_OPS = new Set(['chat', 'generate', 'summarize', 'classify', 'extract', 'enrich', 'complete', 'messages']);const USR = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;@@@ -145,7 +152,7 @@ function createCommunity({ db, config, env = process.env, machines = null, endpoconst followersCache = new Map();const aliasCache = new Map(); // subject -> survivor subject (account merges, 5 minutes)-- const keyOf = (offer, model) => `${offer.offer_id}|${model}`;++ const keyOf = (offer, model) => `${offer.offer_id}|${model}${pools.identity(offer.pool)}`;/** One Network offer doc as this module uses it. Null for anything that is not a complete AI offer. */function normalize(doc) {@@@ -157,9 +164,12 @@ function createCommunity({ db, config, env = process.env, machines = null, endpoconst owner = doc.owner || d.owner;if (typeof d.machine_id !== 'string' || typeof owner !== 'string' || !USR.test(owner)) return null;if (!Array.isArray(d.models) || !d.models.length || !d.hours || !d.price) return null;++ const pool = d.pool === undefined ? null : pools.poolOf(d.pool, d.machine_id);++ if (d.pool !== undefined && !pool) return null;const price = d.price;return {offer_id: String(doc.offer_id || d.offer_id), machine_id: d.machine_id, owner,++ ...(pool ? { pool } : {}),models: d.models.filter((m) => typeof m === 'string' && m).slice(0, 40),hours: { start: String(d.hours.start || ''), end: String(d.hours.end || ''), tz: String(d.hours.tz || 'UTC') },concurrency: Number(d.concurrency) || 1,@@@ -431,7 +441,7 @@ function createCommunity({ db, config, env = process.env, machines = null, endpofor (const model of offer.models) {const key = keyOf(offer, model);const s = await stateOf(key);-- if (s && s.status === 'failed') continue;++ if (s && s.status === 'failed' && !(offer.pool && freeTier(ctx))) continue;if ((!s || s.status === 'new') && !freeTier(ctx)) continue;if (ctx.communityModel && String(ctx.communityModel) !== model) continue;if (!(await audienceAllows(offer, ctx))) continue;@@@ -549,7 +559,16 @@ function createCommunity({ db, config, env = process.env, machines = null, endpoasync function execute({ route = null, operation = 'chat', req = {}, ctx = {} } = {}) {const chosen = await choose({ ctx, operation, route });if (!chosen) return null;-- await ensure(chosen.offer, chosen.model);++ try {++ // The same checks as one machine: a canary round when there is no recent one, keyed on the pool's exact++ // membership (pools.identity), so another member or head starts unverified.++ await ensure(chosen.offer, chosen.model);++ } catch (err) {++ // A pool that failed verification is free tier only (the pooled machines design): it may still answer a++ // free-tier request, earns nothing for it, and says `verification: failed`. A single machine that fails++ // is not used at all, as before.++ if (!(chosen.offer.pool && freeTier(ctx) && err && err.code === 'community.verification_failed')) throw err;++ }// The canary round may just have verified it: this first answer already earns and is no longer free-tier only.const fresh = await stateOf(chosen.key);if (fresh) { chosen.status = fresh.status; chosen.reputation = Number(fresh.reputation) || 0; }@@@ -566,7 +585,7 @@ function createCommunity({ db, config, env = process.env, machines = null, endpoconst facts = {resolved_model: `community/${chosen.model}`, revision: null, deployment: source,provider_access: isEndpoint ? 'COMMUNITY_ENDPOINT' : 'COMMUNITY', rate_version: 'community', policy: 'community',-- community: { kind: isEndpoint ? 'endpoint' : 'machine', machine_id: chosen.offer.machine_id, owner: chosen.offer.owner, model: chosen.model, price: chosen.offer.price,++ community: { label: pools.label(chosen.offer.pool), ...(chosen.offer.pool ? { pool: chosen.offer.pool } : {}), kind: chosen.offer.pool ? 'pool' : isEndpoint ? 'endpoint' : 'machine', machine_id: chosen.offer.machine_id, owner: chosen.offer.owner, model: chosen.model, price: chosen.offer.price,verification: chosen.status, reputation: chosen.reputation, free_tier: freeTier(ctx), self_served: selfServed },};if (typeof ctx.onRoute === 'function') {@@@ -588,9 +607,11 @@ function createCommunity({ db, config, env = process.env, machines = null, endpothrow err;}const usage = answer.usage || { input: 0, output: 0, cached: 0 };++ // A spot re-run that disagrees drops this answer (the router falls back to the network) and fails the offer,++ // for a pool as for one machine; a failed pool then serves the free tier only.await spotCheck(chosen, messages, answer);const latencyMs = clock.now() - t0;-- const recorded = await recordUsage(chosen, { requestId, usage, verified: chosen.status !== 'new', requester, projectId: ctx.projectId || null, latencyMs,++ const recorded = await recordUsage(chosen, { requestId, usage, verified: chosen.status === 'verified', requester, projectId: ctx.projectId || null, latencyMs,chargeId: ctx.requesterChargeId || null });const text = answer.text || '';const message = answer.message || { role: 'assistant', content: text };@@@ -604,9 +625,11 @@ function createCommunity({ db, config, env = process.env, machines = null, endpoconst explain = {objective: 'community', reasons: [recorded.self_served? 'self-served: the requester owns this offer, so the answer earns no credits'-- : `${isEndpoint ? 'a community endpoint' : 'a community machine'} offered ${chosen.model}${chosen.offer.price.in_per_million || chosen.offer.price.out_per_million ? ` at ${chosen.offer.price.in_per_million}/${chosen.offer.price.out_per_million} credits per million` : ' for free'}`],++ : `${isEndpoint ? 'a community endpoint' : chosen.offer.pool ? `a community pool of ${chosen.offer.pool.machines.length} machines` : 'a community machine'} offered ${chosen.model}${chosen.offer.price.in_per_million || chosen.offer.price.out_per_million ? ` at ${chosen.offer.price.in_per_million}/${chosen.offer.price.out_per_million} credits per million` : ' for free'}`],selected: `${source}/${chosen.model}`, paid_by: 'community', candidates: [],-- attempts: [{ key: 'community', provider: source, model: chosen.model, why: isEndpoint ? 'an opted-in request placed on a verified community endpoint' : 'an opted-in request placed on a verified community machine', status: 'ok', error: null }],++ attempts: [{ key: 'community', provider: source, model: chosen.model, why: isEndpoint ? 'an opted-in request placed on a verified community endpoint'++ : chosen.offer.pool ? `an opted-in request placed on a community pool through its head (${chosen.status === 'verified' ? 'verified' : 'free tier only'})`++ : 'an opted-in request placed on a verified community machine', status: 'ok', error: null }],community, openvibe: facts,};return {@@@ -675,11 +698,11 @@ function createCommunity({ db, config, env = process.env, machines = null, endpoconst kudos = Math.max(0, Number(kudosRow.balance) || 0);const offerViews = await Promise.all(list.map(async (o) => {const openNow = o.hours ? withinHours(o, clock.now()) : null;-- const out = { offer_id: o.offer_id, kind: o.kind || 'machine', machine_id: o.machine_id, models: o.models, hours: o.hours, concurrency: o.concurrency,++ const out = { offer_id: o.offer_id, kind: o.pool ? 'pool' : o.kind || 'machine', label: pools.label(o.pool), ...(o.pool ? { pool: o.pool } : {}), machine_id: o.machine_id, models: o.models, hours: o.hours, concurrency: o.concurrency,price: o.price, audience: o.audience, group: o.group, health: o.health, open_now: openNow, ready: o.ready && openNow !== false,verification: null, reputation: 0, free_only: true, verified: false,tokens_per_s: Math.max(0, ...served.filter((x) => x.offer_key.startsWith(`${o.offer_id}|`)).map((x) => x.tokens_per_s || 0)) || null };-- const verified = o.models.map((m) => byKey.get(`${o.offer_id}|${m}`)).filter(Boolean);++ const verified = o.models.map((m) => byKey.get(keyOf(o, m))).filter(Boolean);if (verified.length) {const s = verified.find((x) => x.status === 'verified') || verified[0];const canaries = verified.reduce((n, x) => n + Number(x.canary_passes), 0);@@@ -725,14 +748,19 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo/** What Exchange may show about community capacity: no owner subject, no machine id, no prompt. */async function publicOffers() {const list = await offers();-- return list.map((o) => {++ return Promise.all(list.map(async (o) => {const openNow = withinHours(o, clock.now());++ const states = o.pool ? await Promise.all(o.models.map((model) => stateOf(keyOf(o, model)))) : [];return {// The public id is a stable hash: Exchange can list and link an offer without publishing which machine it is.-- offer_id: `community_${sha256(o.offer_id).slice(0, 16)}`, kind: o.kind || 'machine', models: o.models, hours: o.hours, concurrency: o.concurrency,++ offer_id: `community_${sha256(o.offer_id).slice(0, 16)}`, kind: o.pool ? 'pool' : o.kind || 'machine', label: pools.label(o.pool), models: o.models,++ // A pool's members as stable pseudonyms (Exchange shows the pool's card with its members), and whether++ // every model on it is verified: a new or failed pool is free tier only.++ ...(o.pool ? { pool: pools.publicPool(o.pool), free_only: !states.every((s) => s && s.status === 'verified'), verified: states.every((s) => s && s.status === 'verified') } : {}),++ hours: o.hours, concurrency: o.concurrency,price: o.price, audience: o.audience, health: o.health, open_now: openNow, ready: o.ready && openNow,};-- });++ }));}return { enabled, offers, choose, execute, verify, sell, publicOffers, kudosBenefits, kudosBonus, queuePriority, _test: { normalize, withinHours, refreshReputation, fingerprintMatches, similar, messagesOf, freeTier } };nnew file mode 100644@@@ -0,0 +1,31 @@++'use strict';++/**++ * The `pool` part of a community AI offer (ai.offer-pool@1; the pooled machines design, 2026-11-06): several of one++ * owner's machines on one local network serving one model through llama.cpp RPC, answering through the head.++ * Network adds it from its approved pool record; this module only reads it.++ *++ * poolOf(value, machineId) { machines, head } sorted, or null when it is not exactly a valid pool whose head is the++ * offer's own machine (a malformed pool drops the whole offer: fail closed)++ * label(pool) "Community pool (N machines)", or "Community" for one machine++ * identity(pool) the suffix of the verification key: another member or head is a new, unverified pool++ * publicPool(pool) the members as stable pseudonyms, for Exchange (never a machine id)++ */++const { sha256 } = require('../util');++++const DEVICE = /^dev_[0-9A-HJKMNP-TV-Z]{26}$/;++++function poolOf(value, machine) {++ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some((k) => !['machines', 'head'].includes(k))) return null;++ const members = value.machines;++ if (!Array.isArray(members) || members.length < 2 || members.length > 8 || new Set(members).size !== members.length++ || members.some((m) => typeof m !== 'string' || !DEVICE.test(m)) || !members.includes(value.head) || value.head !== machine) return null;++ return { machines: [...members].sort(), head: value.head };++}++const label = (pool) => (pool ? `Community pool (${pool.machines.length} machines)` : 'Community');++const identity = (pool) => (pool ? `|pool:${sha256(JSON.stringify(pool)).slice(0, 24)}` : '');++function publicPool(pool) {++ const pseudonym = (id) => `machine_${sha256(id).slice(0, 16)}`;++ return { machines: pool.machines.map(pseudonym), head: pseudonym(pool.head) };++}++++module.exports = { poolOf, label, identity, publicPool };nnew file mode 100644@@@ -0,0 +1,185 @@++'use strict';++// Pooled community offers (ai.offer-pool@1): a pool's offer routes through its head, is verified as one machine is,++// is labelled "Community pool (N machines)", and a pool that is new or failed verification serves the free tier only.++const assert = require('node:assert/strict');++const { boot, suite } = require('./helpers');++const { createCommunity } = require('../server/community');++const pools = require('../server/community/pools');++++const t = suite('community pools');++let harness;++async function fresh() {++ if (!harness) harness = await boot({ env: { AI_COMMUNITY: 'false' } });++ await harness.db.prepare('DELETE FROM community_usage').run();++ await harness.db.prepare('DELETE FROM community_offers').run();++ return harness;++}++const owner = 'usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1';++const peerOwner = 'usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A2';++const head = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1';++const member = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D2';++const third = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D3';++const single = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D9';++const FREE = { communityOptIn: true, communityFree: true };++const PAID = { communityOptIn: true };++const ask = { messages: [{ role: 'user', content: 'hello' }] };++++const offerDoc = (machine, extra = {}) => ({ offer_id: `ai:${machine}`, machine_id: machine, owner, models: ['pool-model'],++ hours: { start: '00:00', end: '00:00', tz: 'UTC' }, concurrency: 1,++ price: { in_per_million: 1, out_per_million: 2 }, audience: 'everyone', group: null,++ ready: true, health: { status: 'up' }, ...extra });++const poolDoc = (machines = [head, member]) => offerDoc(head, { pool: { machines, head } });++++/**++ * A community module over fake Network offers and fake machines. `answers[machine]` is how a machine answers:++ * 'good' passes every canary, 'bad' fails the arithmetic canary, 'odd' passes canaries but answers prompts differently.++ */++function setup(db, docs, answers = {}, { spotRate = 0 } = {}) {++ const calls = [];++ const community = createCommunity({ db, config: { networkInternalUrl: 'http://network.test', community: { enabled: true, refreshMs: 1, spotRate } },++ env: {}, tokens: { getToken: async () => 'test-token' },++ fetchImpl: async () => ({ ok: true, json: async () => ({ offers: docs }) }),++ machines: { enabled: true, chat: async (request) => {++ calls.push(request);++ const how = answers[request.machine] || 'good';++ const message = request.messages.at(-1).content;++ const text = message.includes('OPENVIBE-CANARY') ? 'OPENVIBE-CANARY-7F3A'++ : message.includes('17 + 25') ? (how === 'bad' ? '41' : '42')++ : message.includes('canary-ok') ? 'canary-ok'++ : how === 'odd' ? 'purple elephants dance quietly tonight' : 'Hello from the answering machine';++ return { text, model: request.model, usage: { input: 10, output: 5 } };++ } }, log: { warn() {} }, random: () => 0 });++ return { community, calls };++}++const usage = async (db) => db.prepare('SELECT credits_micro, verified, earning_status FROM community_usage ORDER BY created_at').all();++++t.test('a pool offer routes through its head, verifies like one machine, and is labelled', async () => {++ const h = await fresh();++ const { community: c, calls } = setup(h.db, [poolDoc()]);++ const [offer] = await c.offers();++ assert.deepEqual(offer.pool, { machines: [head, member].sort(), head });++ assert.equal(await c.choose({ ctx: PAID, operation: 'chat' }), null, 'a new pool cannot receive paid work');++ const out = await c.execute({ ctx: { ...FREE, runId: 'pool-1' }, req: ask });++ assert.equal(out.explain.community.label, 'Community pool (2 machines)');++ assert.equal(out.explain.community.kind, 'pool');++ assert.deepEqual(out.explain.community.pool, offer.pool);++ assert.equal(out.explain.community.verification, 'verified', 'the canary round ran on the pool');++ assert.equal(calls.length, 4, 'three canaries and the prompt');++ assert.ok(calls.every((r) => r.machine === head && r.community === true), 'canaries and prompts all go to the head');++ assert.ok(calls.every((r) => r.machine !== member), 'a member is never addressed directly');++ // Verified once, like one machine: the next request is just the prompt.++ await c.execute({ ctx: { ...PAID, runId: 'pool-2' }, req: ask });++ assert.equal(calls.length, 5, 'no canary round on every request');++ assert.ok(await c.choose({ ctx: PAID, operation: 'chat' }), 'a verified pool may serve paid work');++ const rows = await usage(h.db);++ assert.equal(rows.length, 2);++ assert.equal(rows[1].verified, true);++ assert.ok(Number(rows[1].credits_micro) > 0, 'a verified pool earns');++++ const pub = (await c.publicOffers())[0];++ assert.equal(pub.kind, 'pool');++ assert.equal(pub.label, 'Community pool (2 machines)');++ assert.equal(pub.free_only, false);++ assert.equal(pub.verified, true);++ assert.equal(pub.pool.machines.length, 2);++ assert.ok(pub.pool.machines.includes(pub.pool.head));++ const shown = JSON.stringify(pub);++ assert.ok(!shown.includes(owner) && !shown.includes(head) && !shown.includes(member), 'no owner or machine id is public');++++ const sold = await c.sell(owner);++ assert.equal(sold.offers[0].kind, 'pool');++ assert.equal(sold.offers[0].label, 'Community pool (2 machines)');++ assert.equal(sold.offers[0].verified, true);++});++++t.test('a pool that fails verification serves the free tier only, earns nothing, and is labelled failed', async () => {++ const h = await fresh();++ const { community: c, calls } = setup(h.db, [poolDoc()], { [head]: 'bad' });++ const first = await c.execute({ ctx: { ...FREE, runId: 'failed-1' }, req: ask });++ assert.equal(first.explain.community.verification, 'failed');++ assert.equal(first.explain.community.label, 'Community pool (2 machines)');++ assert.match(first.explain.attempts[0].why, /free tier only/);++ assert.equal(await c.choose({ ctx: PAID, operation: 'chat' }), null, 'a failed pool never gets paid work');++ assert.ok(await c.choose({ ctx: FREE, operation: 'chat' }), 'a failed pool stays on the free tier');++ const before = calls.length;++ const second = await c.execute({ ctx: { ...FREE, runId: 'failed-2' }, req: ask });++ assert.equal(second.explain.community.verification, 'failed');++ assert.equal(calls.length, before + 1, 'a failed pool is not re-verified by traffic');++ const rows = await usage(h.db);++ assert.equal(rows.length, 2);++ assert.ok(rows.every((r) => Number(r.credits_micro) === 0 && r.verified === false), 'nothing earned on a failed pool');++ const pub = (await c.publicOffers())[0];++ assert.equal(pub.free_only, true);++ assert.equal(pub.verified, false);++});++++t.test('a single machine that fails is still not used, even for the free tier', async () => {++ const h = await fresh();++ const { community: c } = setup(h.db, [offerDoc(single)], { [single]: 'bad' });++ await assert.rejects(c.execute({ ctx: { ...FREE, runId: 'single-1' }, req: ask }), (err) => err.code === 'community.verification_failed');++ assert.equal(await c.choose({ ctx: FREE, operation: 'chat' }), null);++ assert.equal((await c.publicOffers())[0].label, 'Community');++});++++t.test('a spot re-run that disagrees drops the answer and leaves the pool free tier only', async () => {++ const h = await fresh();++ // A dearer trusted machine of another owner: the cheaper pool is chosen first, the peer re-runs the spot.++ const peer = { ...offerDoc(single), owner: peerOwner, price: { in_per_million: 5, out_per_million: 5 } };++ const { community: c } = setup(h.db, [poolDoc(), peer], { [head]: 'odd' }, { spotRate: 1 });++ const peerOffer = (await c.offers()).find((o) => !o.pool);++ await c.verify(peerOffer, 'pool-model');++ // A paid request on the verified pool: the canaries pass, then the spot re-run on the trusted peer disagrees.++ const poolOffer = (await c.offers()).find((o) => o.pool);++ await c.verify(poolOffer, 'pool-model');++ await assert.rejects(c.execute({ ctx: { ...PAID, communityModel: 'pool-model', runId: 'spot-1' }, req: ask }),++ (err) => err.code === 'community.verification_failed', 'the disagreeing answer is not returned');++ const left = await c.choose({ ctx: PAID, operation: 'chat' });++ assert.ok(left && !left.offer.pool, 'the pool lost paid work; the trusted machine keeps it');++ const pub = (await c.publicOffers()).find((o) => o.kind === 'pool');++ assert.equal(pub.free_only, true);++ // Without the peer, the failed pool still answers the free tier, and only the free tier.++ const alone = setup(h.db, [poolDoc()], { [head]: 'odd' });++ assert.equal(await alone.community.choose({ ctx: PAID, operation: 'chat' }), null);++ const freeOut = await alone.community.execute({ ctx: { ...FREE, runId: 'spot-2' }, req: ask });++ assert.equal(freeOut.explain.community.verification, 'failed');++ assert.equal(freeOut.explain.community.credits_micro, 0);++});++++t.test('another member or head is a new, unverified pool', async () => {++ const h = await fresh();++ const a = setup(h.db, [poolDoc()]);++ await a.community.execute({ ctx: { ...FREE, runId: 'm-1' }, req: ask });++ assert.ok(await a.community.choose({ ctx: PAID, operation: 'chat' }), 'verified as [head, member]');++ const b = setup(h.db, [poolDoc([head, member, third])]);++ assert.equal(await b.community.choose({ ctx: PAID, operation: 'chat' }), null, 'a third member makes it unverified again');++ const out = await b.community.execute({ ctx: { ...FREE, runId: 'm-2' }, req: ask });++ assert.equal(out.explain.community.label, 'Community pool (3 machines)');++ assert.equal(b.calls.length, 4, 'the new membership runs its own canary round');++});++++t.test('a malformed pool fails closed', async () => {++ const valid = pools.poolOf({ machines: [member, head], head }, head);++ assert.deepEqual(valid, { machines: [head, member].sort(), head });++ const bad = [++ { machines: [head, head], head },++ { machines: [head, member], head: third },++ { machines: [head], head },++ { machines: [head, member], head, address: '192.168.1.2/24' },++ { machines: [head, member], head, certificates: ['-----BEGIN CERTIFICATE-----'] },++ { machines: [head, 'node:abc'], head },++ { machines: Array.from({ length: 9 }, (_, i) => `dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2E${i}`), head: 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2E0' },++ [head, member],++ 'pool',++ ];++ for (const value of bad) assert.equal(pools.poolOf(value, head), null, JSON.stringify(value));++ assert.equal(pools.poolOf({ machines: [head, member], head: member }, head), null, 'the head is the offer\'s own machine');++ assert.notEqual(pools.identity(valid), pools.identity({ machines: [head, third].sort(), head }));++ assert.equal(pools.identity(null), '', 'one machine keeps its verification key');++ assert.equal(pools.label(null), 'Community');++ const h = await fresh();++ const doc = poolDoc(); doc.pool.head = third;++ assert.deepEqual(await setup(h.db, [doc]).community.offers(), [], 'an offer with a malformed pool is dropped whole');++});++++t.test('close the isolated test server', async () => { if (harness) await harness.stop(); });++t.run();
@@ -12,8 +12,14 @@
@@ -12,8 +12,14 @@
* - spot re-runs: about 1% of answers are re-run on a trusted machine serving the same model and compared;
* - spot re-runs: about 1% of answers are re-run on a trusted machine serving the same model and compared;
* - reputation: canaries, spot runs and observed uptime, 0-100.
* - reputation: canaries, spot runs and observed uptime, 0-100.
* A new machine serves only the free tier (a run with no Balance payer, ctx.creditsRequired) until verified. The answer
* A new machine serves only the free tier (a run with no Balance payer, ctx.creditsRequired) until verified. The answer
- * always carries `openvibe.community`, so a caller can see it came from a person's machine. The prompt is visible to
- * that machine, which is why only opted-in requests ever go there.
+ * always carries `openvibe.community`, so a caller can see it came from a person's machine (its `label` is "Community", or
+ * "Community pool (N machines)" for a pool). The prompt is visible to that machine, which is why only opted-in requests
+ * ever go there.
+ *
+ * Pools (ai.offer-pool@1, server/community/pools.js): several of one owner's machines on one local network serving one
+ * model through llama.cpp RPC. Network names the pool on the head's offer; requests go to the head (which sees the
+ * prompt; the others see tensors), and the same checks as one machine run on the pool's answers, keyed on its exact
+ * membership. A new pool, and one that failed verification, serves the free tier only and earns nothing there.
*
*
* Money: a self-served run (the requester is the owner, or merged into it) earns 0. Verified credits are `pending`
* Money: a self-served run (the requester is the owner, or merged into it) earns 0. Verified credits are `pending`
* until the requester's own Balance charge exists in the same request (`spendable`), so none are spendable yet. A
* until the requester's own Balance charge exists in the same request (`spendable`), so none are spendable yet. A
@@ -32,6 +38,7 @@
@@ -32,6 +38,7 @@
*/
*/
const { createServiceTokenClient } = require('openvibe-sdk/auth');
const { createServiceTokenClient } = require('openvibe-sdk/auth');
const { AiError, sha256 } = require('../util');
const { AiError, sha256 } = require('../util');
+const pools = require('./pools');
const TEXT_OPS = new Set(['chat', 'generate', 'summarize', 'classify', 'extract', 'enrich', 'complete', 'messages']);
const TEXT_OPS = new Set(['chat', 'generate', 'summarize', 'classify', 'extract', 'enrich', 'complete', 'messages']);
const USR = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;
const USR = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;
@@ -145,7 +152,7 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
@@ -145,7 +152,7 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
const followersCache = new Map();
const followersCache = new Map();
const aliasCache = new Map(); // subject -> survivor subject (account merges, 5 minutes)
const aliasCache = new Map(); // subject -> survivor subject (account merges, 5 minutes)
- const keyOf = (offer, model) => `${offer.offer_id}|${model}`;
+ const keyOf = (offer, model) => `${offer.offer_id}|${model}${pools.identity(offer.pool)}`;
/** One Network offer doc as this module uses it. Null for anything that is not a complete AI offer. */
/** One Network offer doc as this module uses it. Null for anything that is not a complete AI offer. */
function normalize(doc) {
function normalize(doc) {
@@ -157,9 +164,12 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
@@ -157,9 +164,12 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
const owner = doc.owner || d.owner;
const owner = doc.owner || d.owner;
if (typeof d.machine_id !== 'string' || typeof owner !== 'string' || !USR.test(owner)) return null;
if (typeof d.machine_id !== 'string' || typeof owner !== 'string' || !USR.test(owner)) return null;
if (!Array.isArray(d.models) || !d.models.length || !d.hours || !d.price) return null;
if (!Array.isArray(d.models) || !d.models.length || !d.hours || !d.price) return null;
+ const pool = d.pool === undefined ? null : pools.poolOf(d.pool, d.machine_id);
+ if (d.pool !== undefined && !pool) return null;
const price = d.price;
const price = d.price;
return {
return {
offer_id: String(doc.offer_id || d.offer_id), machine_id: d.machine_id, owner,
offer_id: String(doc.offer_id || d.offer_id), machine_id: d.machine_id, owner,
+ ...(pool ? { pool } : {}),
models: d.models.filter((m) => typeof m === 'string' && m).slice(0, 40),
models: d.models.filter((m) => typeof m === 'string' && m).slice(0, 40),
hours: { start: String(d.hours.start || ''), end: String(d.hours.end || ''), tz: String(d.hours.tz || 'UTC') },
hours: { start: String(d.hours.start || ''), end: String(d.hours.end || ''), tz: String(d.hours.tz || 'UTC') },
concurrency: Number(d.concurrency) || 1,
concurrency: Number(d.concurrency) || 1,
@@ -431,7 +441,7 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
@@ -431,7 +441,7 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
for (const model of offer.models) {
for (const model of offer.models) {
const key = keyOf(offer, model);
const key = keyOf(offer, model);
const s = await stateOf(key);
const s = await stateOf(key);
- if (s && s.status === 'failed') continue;
+ if (s && s.status === 'failed' && !(offer.pool && freeTier(ctx))) continue;
if ((!s || s.status === 'new') && !freeTier(ctx)) continue;
if ((!s || s.status === 'new') && !freeTier(ctx)) continue;
if (ctx.communityModel && String(ctx.communityModel) !== model) continue;
if (ctx.communityModel && String(ctx.communityModel) !== model) continue;
if (!(await audienceAllows(offer, ctx))) continue;
if (!(await audienceAllows(offer, ctx))) continue;
@@ -549,7 +559,16 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
@@ -549,7 +559,16 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
async function execute({ route = null, operation = 'chat', req = {}, ctx = {} } = {}) {
async function execute({ route = null, operation = 'chat', req = {}, ctx = {} } = {}) {
const chosen = await choose({ ctx, operation, route });
const chosen = await choose({ ctx, operation, route });
if (!chosen) return null;
if (!chosen) return null;
- await ensure(chosen.offer, chosen.model);
+ try {
+ // The same checks as one machine: a canary round when there is no recent one, keyed on the pool's exact
+ // membership (pools.identity), so another member or head starts unverified.
+ await ensure(chosen.offer, chosen.model);
+ } catch (err) {
+ // A pool that failed verification is free tier only (the pooled machines design): it may still answer a
+ // free-tier request, earns nothing for it, and says `verification: failed`. A single machine that fails
+ // is not used at all, as before.
+ if (!(chosen.offer.pool && freeTier(ctx) && err && err.code === 'community.verification_failed')) throw err;
+ }
// The canary round may just have verified it: this first answer already earns and is no longer free-tier only.
// The canary round may just have verified it: this first answer already earns and is no longer free-tier only.
const fresh = await stateOf(chosen.key);
const fresh = await stateOf(chosen.key);
if (fresh) { chosen.status = fresh.status; chosen.reputation = Number(fresh.reputation) || 0; }
if (fresh) { chosen.status = fresh.status; chosen.reputation = Number(fresh.reputation) || 0; }
@@ -566,7 +585,7 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
@@ -566,7 +585,7 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
const facts = {
const facts = {
resolved_model: `community/${chosen.model}`, revision: null, deployment: source,
resolved_model: `community/${chosen.model}`, revision: null, deployment: source,
provider_access: isEndpoint ? 'COMMUNITY_ENDPOINT' : 'COMMUNITY', rate_version: 'community', policy: 'community',
provider_access: isEndpoint ? 'COMMUNITY_ENDPOINT' : 'COMMUNITY', rate_version: 'community', policy: 'community',
- community: { kind: isEndpoint ? 'endpoint' : 'machine', machine_id: chosen.offer.machine_id, owner: chosen.offer.owner, model: chosen.model, price: chosen.offer.price,
+ community: { label: pools.label(chosen.offer.pool), ...(chosen.offer.pool ? { pool: chosen.offer.pool } : {}), kind: chosen.offer.pool ? 'pool' : isEndpoint ? 'endpoint' : 'machine', machine_id: chosen.offer.machine_id, owner: chosen.offer.owner, model: chosen.model, price: chosen.offer.price,
verification: chosen.status, reputation: chosen.reputation, free_tier: freeTier(ctx), self_served: selfServed },
verification: chosen.status, reputation: chosen.reputation, free_tier: freeTier(ctx), self_served: selfServed },
};
};
if (typeof ctx.onRoute === 'function') {
if (typeof ctx.onRoute === 'function') {
@@ -588,9 +607,11 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
@@ -588,9 +607,11 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
throw err;
throw err;
}
}
const usage = answer.usage || { input: 0, output: 0, cached: 0 };
const usage = answer.usage || { input: 0, output: 0, cached: 0 };
+ // A spot re-run that disagrees drops this answer (the router falls back to the network) and fails the offer,
+ // for a pool as for one machine; a failed pool then serves the free tier only.
await spotCheck(chosen, messages, answer);
await spotCheck(chosen, messages, answer);
const latencyMs = clock.now() - t0;
const latencyMs = clock.now() - t0;
- const recorded = await recordUsage(chosen, { requestId, usage, verified: chosen.status !== 'new', requester, projectId: ctx.projectId || null, latencyMs,
+ const recorded = await recordUsage(chosen, { requestId, usage, verified: chosen.status === 'verified', requester, projectId: ctx.projectId || null, latencyMs,
chargeId: ctx.requesterChargeId || null });
chargeId: ctx.requesterChargeId || null });
const text = answer.text || '';
const text = answer.text || '';
const message = answer.message || { role: 'assistant', content: text };
const message = answer.message || { role: 'assistant', content: text };
@@ -604,9 +625,11 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
@@ -604,9 +625,11 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
const explain = {
const explain = {
objective: 'community', reasons: [recorded.self_served
objective: 'community', reasons: [recorded.self_served
? 'self-served: the requester owns this offer, so the answer earns no credits'
? 'self-served: the requester owns this offer, so the answer earns no credits'
- : `${isEndpoint ? 'a community endpoint' : 'a community machine'} offered ${chosen.model}${chosen.offer.price.in_per_million || chosen.offer.price.out_per_million ? ` at ${chosen.offer.price.in_per_million}/${chosen.offer.price.out_per_million} credits per million` : ' for free'}`],
+ : `${isEndpoint ? 'a community endpoint' : chosen.offer.pool ? `a community pool of ${chosen.offer.pool.machines.length} machines` : 'a community machine'} offered ${chosen.model}${chosen.offer.price.in_per_million || chosen.offer.price.out_per_million ? ` at ${chosen.offer.price.in_per_million}/${chosen.offer.price.out_per_million} credits per million` : ' for free'}`],
selected: `${source}/${chosen.model}`, paid_by: 'community', candidates: [],
selected: `${source}/${chosen.model}`, paid_by: 'community', candidates: [],
- attempts: [{ key: 'community', provider: source, model: chosen.model, why: isEndpoint ? 'an opted-in request placed on a verified community endpoint' : 'an opted-in request placed on a verified community machine', status: 'ok', error: null }],
+ attempts: [{ key: 'community', provider: source, model: chosen.model, why: isEndpoint ? 'an opted-in request placed on a verified community endpoint'
+ : chosen.offer.pool ? `an opted-in request placed on a community pool through its head (${chosen.status === 'verified' ? 'verified' : 'free tier only'})`
+ : 'an opted-in request placed on a verified community machine', status: 'ok', error: null }],
community, openvibe: facts,
community, openvibe: facts,
};
};
return {
return {
@@ -675,11 +698,11 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
@@ -675,11 +698,11 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
const kudos = Math.max(0, Number(kudosRow.balance) || 0);
const kudos = Math.max(0, Number(kudosRow.balance) || 0);
const offerViews = await Promise.all(list.map(async (o) => {
const offerViews = await Promise.all(list.map(async (o) => {
const openNow = o.hours ? withinHours(o, clock.now()) : null;
const openNow = o.hours ? withinHours(o, clock.now()) : null;
- const out = { offer_id: o.offer_id, kind: o.kind || 'machine', machine_id: o.machine_id, models: o.models, hours: o.hours, concurrency: o.concurrency,
+ const out = { offer_id: o.offer_id, kind: o.pool ? 'pool' : o.kind || 'machine', label: pools.label(o.pool), ...(o.pool ? { pool: o.pool } : {}), machine_id: o.machine_id, models: o.models, hours: o.hours, concurrency: o.concurrency,
price: o.price, audience: o.audience, group: o.group, health: o.health, open_now: openNow, ready: o.ready && openNow !== false,
price: o.price, audience: o.audience, group: o.group, health: o.health, open_now: openNow, ready: o.ready && openNow !== false,
verification: null, reputation: 0, free_only: true, verified: false,
verification: null, reputation: 0, free_only: true, verified: false,
tokens_per_s: Math.max(0, ...served.filter((x) => x.offer_key.startsWith(`${o.offer_id}|`)).map((x) => x.tokens_per_s || 0)) || null };
tokens_per_s: Math.max(0, ...served.filter((x) => x.offer_key.startsWith(`${o.offer_id}|`)).map((x) => x.tokens_per_s || 0)) || null };
- const verified = o.models.map((m) => byKey.get(`${o.offer_id}|${m}`)).filter(Boolean);
+ const verified = o.models.map((m) => byKey.get(keyOf(o, m))).filter(Boolean);
if (verified.length) {
if (verified.length) {
const s = verified.find((x) => x.status === 'verified') || verified[0];
const s = verified.find((x) => x.status === 'verified') || verified[0];
const canaries = verified.reduce((n, x) => n + Number(x.canary_passes), 0);
const canaries = verified.reduce((n, x) => n + Number(x.canary_passes), 0);
@@ -725,14 +748,19 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
@@ -725,14 +748,19 @@ function createCommunity({ db, config, env = process.env, machines = null, endpo
/** What Exchange may show about community capacity: no owner subject, no machine id, no prompt. */
/** What Exchange may show about community capacity: no owner subject, no machine id, no prompt. */
async function publicOffers() {
async function publicOffers() {
const list = await offers();
const list = await offers();
- return list.map((o) => {
+ return Promise.all(list.map(async (o) => {
const openNow = withinHours(o, clock.now());
const openNow = withinHours(o, clock.now());
+ const states = o.pool ? await Promise.all(o.models.map((model) => stateOf(keyOf(o, model)))) : [];
return {
return {
// The public id is a stable hash: Exchange can list and link an offer without publishing which machine it is.
// The public id is a stable hash: Exchange can list and link an offer without publishing which machine it is.
- offer_id: `community_${sha256(o.offer_id).slice(0, 16)}`, kind: o.kind || 'machine', models: o.models, hours: o.hours, concurrency: o.concurrency,
+ offer_id: `community_${sha256(o.offer_id).slice(0, 16)}`, kind: o.pool ? 'pool' : o.kind || 'machine', label: pools.label(o.pool), models: o.models,
+ // A pool's members as stable pseudonyms (Exchange shows the pool's card with its members), and whether
+ // every model on it is verified: a new or failed pool is free tier only.
+ ...(o.pool ? { pool: pools.publicPool(o.pool), free_only: !states.every((s) => s && s.status === 'verified'), verified: states.every((s) => s && s.status === 'verified') } : {}),
+ hours: o.hours, concurrency: o.concurrency,
price: o.price, audience: o.audience, health: o.health, open_now: openNow, ready: o.ready && openNow,
price: o.price, audience: o.audience, health: o.health, open_now: openNow, ready: o.ready && openNow,
};
};
- });
+ }));
}
}
return { enabled, offers, choose, execute, verify, sell, publicOffers, kudosBenefits, kudosBonus, queuePriority, _test: { normalize, withinHours, refreshReputation, fingerprintMatches, similar, messagesOf, freeTier } };
return { enabled, offers, choose, execute, verify, sell, publicOffers, kudosBenefits, kudosBonus, queuePriority, _test: { normalize, withinHours, refreshReputation, fingerprintMatches, similar, messagesOf, freeTier } };
new file mode 100644
new file mode 100644
@@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
+'use strict';
+/**
+ * The `pool` part of a community AI offer (ai.offer-pool@1; the pooled machines design, 2026-11-06): several of one
+ * owner's machines on one local network serving one model through llama.cpp RPC, answering through the head.
+ * Network adds it from its approved pool record; this module only reads it.
+ *
+ * poolOf(value, machineId) { machines, head } sorted, or null when it is not exactly a valid pool whose head is the
+ * offer's own machine (a malformed pool drops the whole offer: fail closed)
+ * label(pool) "Community pool (N machines)", or "Community" for one machine
+ * identity(pool) the suffix of the verification key: another member or head is a new, unverified pool
+ * publicPool(pool) the members as stable pseudonyms, for Exchange (never a machine id)
+ */
+const { sha256 } = require('../util');
+
+const DEVICE = /^dev_[0-9A-HJKMNP-TV-Z]{26}$/;
+
+function poolOf(value, machine) {
+ if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some((k) => !['machines', 'head'].includes(k))) return null;
+ const members = value.machines;
+ if (!Array.isArray(members) || members.length < 2 || members.length > 8 || new Set(members).size !== members.length
+ || members.some((m) => typeof m !== 'string' || !DEVICE.test(m)) || !members.includes(value.head) || value.head !== machine) return null;
+ return { machines: [...members].sort(), head: value.head };
+}
+const label = (pool) => (pool ? `Community pool (${pool.machines.length} machines)` : 'Community');
+const identity = (pool) => (pool ? `|pool:${sha256(JSON.stringify(pool)).slice(0, 24)}` : '');
+function publicPool(pool) {
+ const pseudonym = (id) => `machine_${sha256(id).slice(0, 16)}`;
+ return { machines: pool.machines.map(pseudonym), head: pseudonym(pool.head) };
+}
+
+module.exports = { poolOf, label, identity, publicPool };
new file mode 100644
new file mode 100644
@@ -0,0 +1,185 @@
@@ -0,0 +1,185 @@
+'use strict';
+// Pooled community offers (ai.offer-pool@1): a pool's offer routes through its head, is verified as one machine is,
+// is labelled "Community pool (N machines)", and a pool that is new or failed verification serves the free tier only.
+const assert = require('node:assert/strict');
+const { boot, suite } = require('./helpers');
+const { createCommunity } = require('../server/community');
+const pools = require('../server/community/pools');
+
+const t = suite('community pools');
+let harness;
+async function fresh() {
+ if (!harness) harness = await boot({ env: { AI_COMMUNITY: 'false' } });
+ await harness.db.prepare('DELETE FROM community_usage').run();
+ await harness.db.prepare('DELETE FROM community_offers').run();
+ return harness;
+}
+const owner = 'usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1';
+const peerOwner = 'usr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A2';
+const head = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D1';
+const member = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D2';
+const third = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D3';
+const single = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2D9';
+const FREE = { communityOptIn: true, communityFree: true };
+const PAID = { communityOptIn: true };
+const ask = { messages: [{ role: 'user', content: 'hello' }] };
+
+const offerDoc = (machine, extra = {}) => ({ offer_id: `ai:${machine}`, machine_id: machine, owner, models: ['pool-model'],
+ hours: { start: '00:00', end: '00:00', tz: 'UTC' }, concurrency: 1,
+ price: { in_per_million: 1, out_per_million: 2 }, audience: 'everyone', group: null,
+ ready: true, health: { status: 'up' }, ...extra });
+const poolDoc = (machines = [head, member]) => offerDoc(head, { pool: { machines, head } });
+
+/**
+ * A community module over fake Network offers and fake machines. `answers[machine]` is how a machine answers:
+ * 'good' passes every canary, 'bad' fails the arithmetic canary, 'odd' passes canaries but answers prompts differently.
+ */
+function setup(db, docs, answers = {}, { spotRate = 0 } = {}) {
+ const calls = [];
+ const community = createCommunity({ db, config: { networkInternalUrl: 'http://network.test', community: { enabled: true, refreshMs: 1, spotRate } },
+ env: {}, tokens: { getToken: async () => 'test-token' },
+ fetchImpl: async () => ({ ok: true, json: async () => ({ offers: docs }) }),
+ machines: { enabled: true, chat: async (request) => {
+ calls.push(request);
+ const how = answers[request.machine] || 'good';
+ const message = request.messages.at(-1).content;
+ const text = message.includes('OPENVIBE-CANARY') ? 'OPENVIBE-CANARY-7F3A'
+ : message.includes('17 + 25') ? (how === 'bad' ? '41' : '42')
+ : message.includes('canary-ok') ? 'canary-ok'
+ : how === 'odd' ? 'purple elephants dance quietly tonight' : 'Hello from the answering machine';
+ return { text, model: request.model, usage: { input: 10, output: 5 } };
+ } }, log: { warn() {} }, random: () => 0 });
+ return { community, calls };
+}
+const usage = async (db) => db.prepare('SELECT credits_micro, verified, earning_status FROM community_usage ORDER BY created_at').all();
+
+t.test('a pool offer routes through its head, verifies like one machine, and is labelled', async () => {
+ const h = await fresh();
+ const { community: c, calls } = setup(h.db, [poolDoc()]);
+ const [offer] = await c.offers();
+ assert.deepEqual(offer.pool, { machines: [head, member].sort(), head });
+ assert.equal(await c.choose({ ctx: PAID, operation: 'chat' }), null, 'a new pool cannot receive paid work');
+ const out = await c.execute({ ctx: { ...FREE, runId: 'pool-1' }, req: ask });
+ assert.equal(out.explain.community.label, 'Community pool (2 machines)');
+ assert.equal(out.explain.community.kind, 'pool');
+ assert.deepEqual(out.explain.community.pool, offer.pool);
+ assert.equal(out.explain.community.verification, 'verified', 'the canary round ran on the pool');
+ assert.equal(calls.length, 4, 'three canaries and the prompt');
+ assert.ok(calls.every((r) => r.machine === head && r.community === true), 'canaries and prompts all go to the head');
+ assert.ok(calls.every((r) => r.machine !== member), 'a member is never addressed directly');
+ // Verified once, like one machine: the next request is just the prompt.
+ await c.execute({ ctx: { ...PAID, runId: 'pool-2' }, req: ask });
+ assert.equal(calls.length, 5, 'no canary round on every request');
+ assert.ok(await c.choose({ ctx: PAID, operation: 'chat' }), 'a verified pool may serve paid work');
+ const rows = await usage(h.db);
+ assert.equal(rows.length, 2);
+ assert.equal(rows[1].verified, true);
+ assert.ok(Number(rows[1].credits_micro) > 0, 'a verified pool earns');
+
+ const pub = (await c.publicOffers())[0];
+ assert.equal(pub.kind, 'pool');
+ assert.equal(pub.label, 'Community pool (2 machines)');
+ assert.equal(pub.free_only, false);
+ assert.equal(pub.verified, true);
+ assert.equal(pub.pool.machines.length, 2);
+ assert.ok(pub.pool.machines.includes(pub.pool.head));
+ const shown = JSON.stringify(pub);
+ assert.ok(!shown.includes(owner) && !shown.includes(head) && !shown.includes(member), 'no owner or machine id is public');
+
+ const sold = await c.sell(owner);
+ assert.equal(sold.offers[0].kind, 'pool');
+ assert.equal(sold.offers[0].label, 'Community pool (2 machines)');
+ assert.equal(sold.offers[0].verified, true);
+});
+
+t.test('a pool that fails verification serves the free tier only, earns nothing, and is labelled failed', async () => {
+ const h = await fresh();
+ const { community: c, calls } = setup(h.db, [poolDoc()], { [head]: 'bad' });
+ const first = await c.execute({ ctx: { ...FREE, runId: 'failed-1' }, req: ask });
+ assert.equal(first.explain.community.verification, 'failed');
+ assert.equal(first.explain.community.label, 'Community pool (2 machines)');
+ assert.match(first.explain.attempts[0].why, /free tier only/);
+ assert.equal(await c.choose({ ctx: PAID, operation: 'chat' }), null, 'a failed pool never gets paid work');
+ assert.ok(await c.choose({ ctx: FREE, operation: 'chat' }), 'a failed pool stays on the free tier');
+ const before = calls.length;
+ const second = await c.execute({ ctx: { ...FREE, runId: 'failed-2' }, req: ask });
+ assert.equal(second.explain.community.verification, 'failed');
+ assert.equal(calls.length, before + 1, 'a failed pool is not re-verified by traffic');
+ const rows = await usage(h.db);
+ assert.equal(rows.length, 2);
+ assert.ok(rows.every((r) => Number(r.credits_micro) === 0 && r.verified === false), 'nothing earned on a failed pool');
+ const pub = (await c.publicOffers())[0];
+ assert.equal(pub.free_only, true);
+ assert.equal(pub.verified, false);
+});
+
+t.test('a single machine that fails is still not used, even for the free tier', async () => {
+ const h = await fresh();
+ const { community: c } = setup(h.db, [offerDoc(single)], { [single]: 'bad' });
+ await assert.rejects(c.execute({ ctx: { ...FREE, runId: 'single-1' }, req: ask }), (err) => err.code === 'community.verification_failed');
+ assert.equal(await c.choose({ ctx: FREE, operation: 'chat' }), null);
+ assert.equal((await c.publicOffers())[0].label, 'Community');
+});
+
+t.test('a spot re-run that disagrees drops the answer and leaves the pool free tier only', async () => {
+ const h = await fresh();
+ // A dearer trusted machine of another owner: the cheaper pool is chosen first, the peer re-runs the spot.
+ const peer = { ...offerDoc(single), owner: peerOwner, price: { in_per_million: 5, out_per_million: 5 } };
+ const { community: c } = setup(h.db, [poolDoc(), peer], { [head]: 'odd' }, { spotRate: 1 });
+ const peerOffer = (await c.offers()).find((o) => !o.pool);
+ await c.verify(peerOffer, 'pool-model');
+ // A paid request on the verified pool: the canaries pass, then the spot re-run on the trusted peer disagrees.
+ const poolOffer = (await c.offers()).find((o) => o.pool);
+ await c.verify(poolOffer, 'pool-model');
+ await assert.rejects(c.execute({ ctx: { ...PAID, communityModel: 'pool-model', runId: 'spot-1' }, req: ask }),
+ (err) => err.code === 'community.verification_failed', 'the disagreeing answer is not returned');
+ const left = await c.choose({ ctx: PAID, operation: 'chat' });
+ assert.ok(left && !left.offer.pool, 'the pool lost paid work; the trusted machine keeps it');
+ const pub = (await c.publicOffers()).find((o) => o.kind === 'pool');
+ assert.equal(pub.free_only, true);
+ // Without the peer, the failed pool still answers the free tier, and only the free tier.
+ const alone = setup(h.db, [poolDoc()], { [head]: 'odd' });
+ assert.equal(await alone.community.choose({ ctx: PAID, operation: 'chat' }), null);
+ const freeOut = await alone.community.execute({ ctx: { ...FREE, runId: 'spot-2' }, req: ask });
+ assert.equal(freeOut.explain.community.verification, 'failed');
+ assert.equal(freeOut.explain.community.credits_micro, 0);
+});
+
+t.test('another member or head is a new, unverified pool', async () => {
+ const h = await fresh();
+ const a = setup(h.db, [poolDoc()]);
+ await a.community.execute({ ctx: { ...FREE, runId: 'm-1' }, req: ask });
+ assert.ok(await a.community.choose({ ctx: PAID, operation: 'chat' }), 'verified as [head, member]');
+ const b = setup(h.db, [poolDoc([head, member, third])]);
+ assert.equal(await b.community.choose({ ctx: PAID, operation: 'chat' }), null, 'a third member makes it unverified again');
+ const out = await b.community.execute({ ctx: { ...FREE, runId: 'm-2' }, req: ask });
+ assert.equal(out.explain.community.label, 'Community pool (3 machines)');
+ assert.equal(b.calls.length, 4, 'the new membership runs its own canary round');
+});
+
+t.test('a malformed pool fails closed', async () => {
+ const valid = pools.poolOf({ machines: [member, head], head }, head);
+ assert.deepEqual(valid, { machines: [head, member].sort(), head });
+ const bad = [
+ { machines: [head, head], head },
+ { machines: [head, member], head: third },
+ { machines: [head], head },
+ { machines: [head, member], head, address: '192.168.1.2/24' },
+ { machines: [head, member], head, certificates: ['-----BEGIN CERTIFICATE-----'] },
+ { machines: [head, 'node:abc'], head },
+ { machines: Array.from({ length: 9 }, (_, i) => `dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2E${i}`), head: 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2E0' },
+ [head, member],
+ 'pool',
+ ];
+ for (const value of bad) assert.equal(pools.poolOf(value, head), null, JSON.stringify(value));
+ assert.equal(pools.poolOf({ machines: [head, member], head: member }, head), null, 'the head is the offer\'s own machine');
+ assert.notEqual(pools.identity(valid), pools.identity({ machines: [head, third].sort(), head }));
+ assert.equal(pools.identity(null), '', 'one machine keeps its verification key');
+ assert.equal(pools.label(null), 'Community');
+ const h = await fresh();
+ const doc = poolDoc(); doc.pool.head = third;
+ assert.deepEqual(await setup(h.db, [doc]).community.offers(), [], 'an offer with a malformed pool is dropped whole');
+});
+
+t.test('close the isolated test server', async () => { if (harness) await harness.stop(); });
+t.run();