feat(base)!: one budget per person for all their assistants, the effort each takes, the operator's lists by page

A grant now says only which assistant a person may use, and keeps their
agreement; the limits are the person's (assistant_budget): one per UTC
day and month in money and in tokens, each optional, money counting the
assistants with prices, tokens every one (Halkyon Learn's ADR 0021, D58,
D59). assistantOperator.grant gives several people several assistants
with one budget, change sets one person's exactly, revoke takes back
everything; grantees and granteesOf list people with their assistants,
budget and use, 50 at a time. The migration carries each person's grant
limits into a budget: per unit, their sum, a grant without a limit making
it none.

assistants.env gives each assistant a default and a highest effort
(_EFFORT, _MAX_EFFORT; refused at start where the provider takes none);
ask takes one within that range, refusing another (invalidEffort), and
caps the output at its ceiling (D63).

instanceOperator.waiting and accounts come 50 at a time from an offset,
and counts gives the rows of the operator's screen (D62).

BREAKING CHANGE: the offer, the grants and the operator's lists changed
shape; Learn's server now asks for build 25.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-26 15:07:04 +01:00
commit 24ed63585e
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
6 changed files with 50672 additions and 1371 deletions

View file

@ -0,0 +1,34 @@
{
"__className__": "serverpod.DatabaseDefinition",
"schemaVersion": 2,
"moduleName": "halkyon_todo",
"tables": [],
"installedModules": [
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "halkyon_base",
"version": "20261026140329453-assistant-budgets"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod",
"version": "20260824182259319"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod_auth_idp",
"version": "20260924105404509"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "halkyon_oidc",
"version": "20261023180644988"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod_auth_core",
"version": "20260924105232991"
}
],
"migrationApiVersion": 1
}

View file

@ -0,0 +1,170 @@
{
"__className__": "serverpod.DatabaseMigration",
"actions": [
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "createTable",
"createTable": {
"__className__": "serverpod.TableDefinition",
"name": "assistant_budget",
"dartName": "AssistantBudget",
"module": "halkyon_base",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 6,
"isNullable": false,
"columnDefault": "serial",
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "authUserId",
"fieldName": "authUserId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "dailyCostMicros",
"fieldName": "dailyCostMicros",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "monthlyCostMicros",
"fieldName": "monthlyCostMicros",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "dailyTokens",
"fieldName": "dailyTokens",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "monthlyTokens",
"fieldName": "monthlyTokens",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
}
],
"foreignKeys": [],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "assistant_budget_person_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "authUserId"
}
],
"type": "btree",
"isUnique": true,
"isPrimary": false
}
],
"managed": true
}
},
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "alterTable",
"alterTable": {
"__className__": "serverpod.TableMigration",
"name": "assistant_grant",
"schema": "public",
"addColumns": [],
"deleteColumns": [
"limitUnit",
"dailyLimit",
"monthlyLimit"
],
"modifyColumns": [],
"addIndexes": [],
"deleteIndexes": [],
"addForeignKeys": [],
"deleteForeignKeys": [],
"warnings": [
{
"__className__": "serverpod.DatabaseMigrationWarning",
"type": "columnDropped",
"message": "Column \"limitUnit\" of table \"assistant_grant\" will be dropped.",
"table": "assistant_grant",
"columns": [
"limitUnit"
],
"destrucive": true
},
{
"__className__": "serverpod.DatabaseMigrationWarning",
"type": "columnDropped",
"message": "Column \"dailyLimit\" of table \"assistant_grant\" will be dropped.",
"table": "assistant_grant",
"columns": [
"dailyLimit"
],
"destrucive": true
},
{
"__className__": "serverpod.DatabaseMigrationWarning",
"type": "columnDropped",
"message": "Column \"monthlyLimit\" of table \"assistant_grant\" will be dropped.",
"table": "assistant_grant",
"columns": [
"monthlyLimit"
],
"destrucive": true
}
]
}
}
],
"warnings": [
{
"__className__": "serverpod.DatabaseMigrationWarning",
"type": "columnDropped",
"message": "Column \"limitUnit\" of table \"assistant_grant\" will be dropped.",
"table": "assistant_grant",
"columns": [
"limitUnit"
],
"destrucive": true
},
{
"__className__": "serverpod.DatabaseMigrationWarning",
"type": "columnDropped",
"message": "Column \"dailyLimit\" of table \"assistant_grant\" will be dropped.",
"table": "assistant_grant",
"columns": [
"dailyLimit"
],
"destrucive": true
},
{
"__className__": "serverpod.DatabaseMigrationWarning",
"type": "columnDropped",
"message": "Column \"monthlyLimit\" of table \"assistant_grant\" will be dropped.",
"table": "assistant_grant",
"columns": [
"monthlyLimit"
],
"destrucive": true
}
],
"migrationApiVersion": 1
}

View file

@ -0,0 +1,112 @@
BEGIN;
--
-- ACTION CREATE TABLE
--
CREATE TABLE "assistant_budget" (
"id" bigserial PRIMARY KEY,
"authUserId" uuid NOT NULL,
"dailyCostMicros" bigint,
"monthlyCostMicros" bigint,
"dailyTokens" bigint,
"monthlyTokens" bigint
);
-- Indexes
CREATE UNIQUE INDEX "assistant_budget_person_idx" ON "assistant_budget" USING btree ("authUserId");
--
-- DATA: each person's budget from their grants' limits, before those go
-- (Halkyon Learn's ADR 0021, D58 and D59). Per unit (a grant's limits were
-- in its assistant's cost, `costMicros`, or in `tokens`), the sum of the
-- person's grants' limits of that unit; one grant of that unit without a
-- limit makes the budget's limit none, as that grant had none; no grant of
-- that unit, none. A person whose grants had no limit at all gets no budget,
-- as before. Assistants with prices now count their tokens too, against a
-- budget in tokens taken from the others: tighter, never looser.
--
INSERT INTO "assistant_budget"
("authUserId", "dailyCostMicros", "monthlyCostMicros", "dailyTokens",
"monthlyTokens")
SELECT * FROM (
SELECT
"authUserId",
CASE WHEN bool_and("dailyLimit" IS NOT NULL)
FILTER (WHERE "limitUnit" = 'costMicros')
THEN sum("dailyLimit") FILTER (WHERE "limitUnit" = 'costMicros')
END::bigint AS "dailyCostMicros",
CASE WHEN bool_and("monthlyLimit" IS NOT NULL)
FILTER (WHERE "limitUnit" = 'costMicros')
THEN sum("monthlyLimit") FILTER (WHERE "limitUnit" = 'costMicros')
END::bigint AS "monthlyCostMicros",
CASE WHEN bool_and("dailyLimit" IS NOT NULL)
FILTER (WHERE "limitUnit" = 'tokens')
THEN sum("dailyLimit") FILTER (WHERE "limitUnit" = 'tokens')
END::bigint AS "dailyTokens",
CASE WHEN bool_and("monthlyLimit" IS NOT NULL)
FILTER (WHERE "limitUnit" = 'tokens')
THEN sum("monthlyLimit") FILTER (WHERE "limitUnit" = 'tokens')
END::bigint AS "monthlyTokens"
FROM "assistant_grant"
GROUP BY "authUserId"
) AS "limits"
WHERE num_nonnulls("dailyCostMicros", "monthlyCostMicros", "dailyTokens",
"monthlyTokens") > 0;
--
-- ACTION ALTER TABLE
--
ALTER TABLE "assistant_grant" DROP COLUMN "limitUnit";
ALTER TABLE "assistant_grant" DROP COLUMN "dailyLimit";
ALTER TABLE "assistant_grant" DROP COLUMN "monthlyLimit";
--
-- MIGRATION VERSION FOR halkyon_todo
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('halkyon_todo', '20261026140330157-assistant-budgets', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20261026140330157-assistant-budgets', "timestamp" = now();
--
-- MIGRATION VERSION FOR halkyon_base
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('halkyon_base', '20261026140329453-assistant-budgets', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20261026140329453-assistant-budgets', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod', '20260824182259319', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260824182259319', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod_auth_idp
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_auth_idp', '20260924105404509', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260924105404509', "timestamp" = now();
--
-- MIGRATION VERSION FOR halkyon_oidc
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('halkyon_oidc', '20261023180644988', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20261023180644988', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod_auth_core
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_auth_core', '20260924105232991', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260924105232991', "timestamp" = now();
COMMIT;

View file

@ -20,3 +20,4 @@
20261023192913500-simplelogin-keys
20261025135744931-waiting-accounts
20261025165103193-assistants
20261026140330157-assistant-budgets