Files
stack/packages/cli/tests/limits-tokens.test.mjs
jason.woltje ca15d73e9f limits: a daily token quota for the account the stack uses, and bus schema 3h to 3i (row 126, #1620)
The business file can name the account claude-code launches use,
`launch.account` = {name, credential}. `name` is an identifier and
`credential` names one login set's environment variables, never a value.
`launch.limits.tokens.daily.<account>` = {limit, since, reset, timeZone}
sets a daily quota for that account. A daily key that isn't the account's
name refuses the business read.

The new plug-in limit-check/daily-quota (interface 2, pure) starts a day at
the first instant at or after `reset` on that local date in `timeZone`. Across
DST that makes a 23- or 25-hour day, and a reset in a skipped hour starts at
the gap's end. It counts the account's known records of all four kinds since
the later of the day start and `since`, and counts unknown turns apart.

The launcher refuses account-credential-missing, account-credential-ambiguous
and account-is-credential (an account name equal to a session credential's
value). Messages name variables, never values. The account rides mayLaunch,
bindLaunch, the usage records, mustStop and the launch record. A pi launch
has no account, records NULL, and no daily quota covers it. `mosaic limits`
and the Console's Settings show one fixed line when a daily row shows:
"Daily quotas count only this stack's own use of the account, not its use
anywhere else."

Bus schema 3h to 3i: 3h reserved usage_records.account as
CHECK (account IS NULL). The new step 3h-3i.sql rebuilds the table in one
transaction, keeping every row's seq and the sqlite_sequence value, and
recreates the three append-only triggers. All of sqlite_master equals 3h's
apart from the account line. A 3h engine refuses a 3i store.
packages/bus/tests/account-migration.test.mjs covers all three, with a
step that fails partway and leaves 3h intact. 3i goes live only through
`stack-release.sh activate ... --migrate` with Jason present (P19 371); the
activate path takes the pre-migrate backup, and the way back is restoring
it (agents/dewey/work/queue-126/MIGRATION.md).

Mos's go mos-0835, six conditions: (1) one transaction, account-migration
test 2 and mutant D29; (2) sqlite_master against 3h, test 1 and mutant D28;
(3) account is never a credential, the launcher's account-is-credential
refusal (launch-account.test.mjs) and the business name pattern
(token-limit-settings.test.mjs); (4) MIGRATION.md, accepted by Mos in
mos-0837; (5) the packet's DESIGN.md "no schema change" line corrected;
(6) the public/ digest below.

The public/ change in bus.js (the daily line under the token-limit table)
carries Mos's decision 123 go mos-0833 on the +/- digest 03e6d9da...ba78,
unchanged at landing.

Owner Dewey, brief docs/plans/2026-10-14_prd-1.2-build.md, "Daily quota for
the credential the stack uses today". Review round 2 rebases the round 4
build onto 1df04f52f (row 167). Reviewer: Filbert approve 29068 on round 1
(manifest d18314f7...8e65); Filbert approve 29080 on
round 2 (manifest f332db76...6fa6).
Seat run on round 2: touched 158/0, singles 1388/0 with 1 esbuild skip,
test-land-work.sh 86/0, 30/30 mutants killed.
Sage gate on 54319f552: GATE_PASSED, 2474+422/0, all 15 packages, the queue
suite and 10 script selftests.

Nothing live.
2026-10-18 20:35:02 -05:00

66 lines
4.4 KiB
JavaScript

// Row 125 (#1619, REQ-LIMIT-3, REQ-LIMIT-4): `mosaic limits` shows each token limit with its
// count, or why it isn't counted, and `mosaic limits reset PATH...` restarts a limit's count.
import test from 'node:test';
import assert from 'node:assert/strict';
import {main} from '../src/cli.mjs';
import {formatTokens,DAILY_NOTE} from '../src/limits.mjs';
import {io,tmp} from './helpers.mjs';
const SINCE='2026-10-18T07:00:00.000Z';
const TOKENS=[
{path:['tokens','instances','coder'],limit:100,since:SINCE,status:'counted',check:'token-limit',count:120,kinds:{input:10,output:20,cacheWrite:40,cacheRead:50},unknownTurns:2},
{path:['tokens','keys','k1'],limit:5,since:SINCE,status:'not-enforced',reason:'no records name a key'},
{path:['tokens','providers','zai'],limit:9,since:SINCE,status:'not-enforced',reason:null},
{path:['tokens','models','anthropic','claude-opus-5-5'],limit:7,since:SINCE,status:'check-failed',check:'token-limit',code:'check-threw'},
{path:['tokens','instances','pm'],limit:1,since:SINCE,status:'check-failed',check:null,code:'records-unreadable'},
];
test('REQ-LIMIT-3 REQ-LIMIT-4 formatTokens shows each limit counted, not enforced, or check failed, in reset path words',()=>{
assert.equal(formatTokens(TOKENS),'token limits:\n'+[
` tokens instances coder: limit 100 since ${SINCE}, count 120 (input 10, output 20, cacheWrite 40, cacheRead 50), unknown turns 2`,
` tokens keys k1: limit 5 since ${SINCE}, not enforced: no records name a key`,
` tokens providers zai: limit 9 since ${SINCE}, not enforced`,
` tokens models anthropic claude-opus-5-5: limit 7 since ${SINCE}, check failed: check-threw (token-limit)`,
` tokens instances pm: limit 1 since ${SINCE}, check failed: records-unreadable`,
].join('\n')+'\n');
for(const none of [[],undefined])assert.equal(formatTokens(none),'token limits: none\n');
});
test('REQ-LIMIT-2 row 126: any daily quota row adds one line after the list saying the count is this stack\'s own use',()=>{
const daily={path:['tokens','daily','jason-max'],limit:40,since:SINCE,status:'counted',check:'daily-quota',count:3,kinds:{input:1,output:1,cacheWrite:0,cacheRead:1},unknownTurns:0};
const out=formatTokens([TOKENS[0],daily,{...daily,path:['tokens','daily','other'],status:'not-enforced',reason:null}]);
assert.ok(out.endsWith(` tokens daily other: limit 40 since ${SINCE}, not enforced\n${DAILY_NOTE}\n`),out);
assert.equal(out.split(DAILY_NOTE).length,2,'said once');
assert.equal(DAILY_NOTE,"Daily quotas count only this stack's own use of the account, not its use anywhere else.");
assert.ok(!formatTokens(TOKENS).includes(DAILY_NOTE));
});
test('REQ-LIMIT-3 the view prints the limits then the token lines; --json keeps tokens in the result',async t=>{
const root=tmp(t),deps={system:{dataRoot:root},transport:()=>()=>({revision:'b'.repeat(64),limits:{total:3},tokens:TOKENS})};
const x=io();await main(['limits','--business','acme'],x,deps);
const [json,lines]=x.out.text.split('token limits:\n');
assert.deepEqual(JSON.parse(json),{revision:'b'.repeat(64),limits:{total:3}});
assert.match(lines,/tokens keys k1: limit 5 since \S+, not enforced: no records name a key/);
const y=io();await main(['limits','--business','acme','--json'],y,deps);
assert.deepEqual(JSON.parse(y.out.text).tokens,TOKENS);
});
test('REQ-LIMIT-3 reset reads the revision, then sends limits.set with one path per limit, a word per level',async t=>{
const root=tmp(t),calls=[];
const deps={system:{dataRoot:root},transport:business=>(verb,args)=>{calls.push({business,verb,args});return {revision:'c'.repeat(64),limits:{},tokens:[]};}};
await main(['limits','reset','tokens','models','anthropic','claude-opus-5-5','--business','acme'],io(),deps);
assert.deepEqual(calls,[
{business:'acme',verb:'limits',args:{}},
{business:'acme',verb:'limits.set',args:{revision:'c'.repeat(64),reset:[['tokens','models','anthropic','claude-opus-5-5']]}},
]);
});
test('REQ-LIMIT-3 reset with no path, or with --file, is refused before the bus',async t=>{
const root=tmp(t),calls=[];
const deps={system:{dataRoot:root},transport:()=>verb=>{calls.push(verb);return {};}};
await assert.rejects(main(['limits','reset','--business','acme'],io(),deps),/mosaic limits/);
await assert.rejects(main(['limits','reset','tokens','--file','x.json','--business','acme'],io(),deps),/mosaic limits/);
await assert.rejects(main(['limits','view','extra','--business','acme'],io(),deps),/mosaic limits/);
assert.deepEqual(calls,[]);
});