Bill a turn to the node that asked, not the node that generated

Every turn runs on the AI owner now, so the ledger's per-host column had
collapsed to a single name and the fleet breakdown said nothing. The asking
node travels with the request and is validated before it is written, which
also retires ai_token_sync.sh — a partner ledger is empty by construction.
This commit is contained in:
Gmer4Lfe
2026-08-20 20:04:08 -04:00
parent 0621f66889
commit 111c70d6b4
4 changed files with 46 additions and 15 deletions
+21 -4
View File
@@ -872,16 +872,26 @@ function vv_ai_token_retain(): int {
// LOCK_EX because two turns can finish together — the composer allows a second question while
// the first is still generating, and each runs in its own detached worker.
function vv_ai_token_record(string $profile, string $source, int $prompt, int $completion, ?float $tokS): void {
//
// $node is who *asked*, which stopped being the same as who generated when the mesh began sharing
// one AI: a turn started on a mirror runs on the owner and would otherwise be billed to the owner,
// making the per-host breakdown a column of the owner's own name. Defaults to this node, so every
// caller that does not know about the mesh keeps its previous meaning exactly.
function vv_ai_token_record(string $profile, string $source, int $prompt, int $completion,
?float $tokS, string $node = ''): void {
if ($prompt <= 0 && $completion <= 0) return; // nothing generated; not a turn worth a row
$db = vv_ai_token_db();
$dir = dirname($db);
if (!is_dir($dir) && !@mkdir($dir, 0755, true)) return;
// Validated, not trusted: the host column is what the per-host totals group on, and a value
// that is not a host slot would create a column nobody can place.
$node = preg_match('/^host\d+$/', $node) ? $node : vv_detect_host();
$row = implode('|', [
date('Y-m-d'), date('H:i:s'),
vv_detect_host(),
$node,
preg_replace('/[^a-z0-9_-]/i', '', $profile) ?: 'unknown',
preg_replace('/[^a-z0-9_-]/i', '', $source) ?: 'unknown',
max(0, $prompt), max(0, $completion),
@@ -938,13 +948,20 @@ function vv_ai_token_stats(): array {
'synced' => null, 'today' => $blank, 'week' => $blank, 'all' => $blank];
}
// Ledgers to read: our own, plus whatever AI/ai_token_sync.sh has pulled from partners.
// Ledgers to read: our own, plus any partner ledger still cached from when each node kept its
// own. The mesh shares one AI now — every turn generates on the owner and is recorded in the
// owner's ledger against the node that asked — so on a current install this loop finds
// nothing and the local file already holds the whole fleet.
//
// Kept rather than deleted: a cache from before the change still describes real turns, and
// dropping the read would silently retire history the tab is showing today.
//
// Each entry carries the host slot it is allowed to contribute rows for. A partner file may
// only add rows whose host column matches its filename — a ledger copied into the wrong slot,
// or a partner that somehow cached ours, would otherwise be counted twice against a total
// that still looked plausible. Our own file is trusted for any host, because it is the only
// one written here and its host column is written by vv_detect_host().
// one written here and its host column is written by vv_ai_token_record(), which validates
// the slot before it writes one.
$ledgers = [[$db, null]];
foreach ((array)@glob(VV_AI_TOKEN_CACHE_DIR . '/host*.tokens.db') as $partnerDb) {
if (!preg_match('/(host\d+)\.tokens\.db$/', $partnerDb, $m)) continue;
+5 -1
View File
@@ -525,7 +525,11 @@ function vv_ai_dispatch(string $action, array $p, bool $isPost, int &$httpStatus
. escapeshellarg($scope) . ' '
// Asked for per turn. Only meaningful on a profile holding web_search — the worker
// checks that, so a crafted web=1 against any other profile changes nothing.
. escapeshellarg(($p['web'] ?? '') === '1' ? '1' : '0')
. escapeshellarg(($p['web'] ?? '') === '1' ? '1' : '0') . ' '
// Who asked. Set by the RPC layer for a forwarded turn and absent for a local one,
// where the worker's own default is already correct. This is what keeps the token
// ledger's per-host column meaningful once every turn generates on the owner.
. escapeshellarg((string)($p['_vv_node'] ?? ''))
. ' >/dev/null 2>&1 </dev/null &';
$out = []; $rc = 0;
exec($cmd, $out, $rc);