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
88 changed files with 50672 additions and 1371 deletions

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

@ -33,6 +33,7 @@ part '20261023192913178-simplelogin-keys/migration.dart';
part '20261025135744595-waiting-accounts/migration.dart'; part '20261025135744595-waiting-accounts/migration.dart';
part '20261025165102861-assistants/migration.dart'; part '20261025165102861-assistants/migration.dart';
part '20261025184004860-assistant-conversations/migration.dart'; part '20261025184004860-assistant-conversations/migration.dart';
part '20261026140329810-assistant-budgets/migration.dart';
/// Migration registry for the client-side database. /// Migration registry for the client-side database.
class MigrationRegistry { class MigrationRegistry {
@ -68,6 +69,7 @@ class MigrationRegistry {
_Migration20261025135744595_waiting_accounts(), _Migration20261025135744595_waiting_accounts(),
_Migration20261025165102861_assistants(), _Migration20261025165102861_assistants(),
_Migration20261025184004860_assistant_conversations(), _Migration20261025184004860_assistant_conversations(),
_Migration20261026140329810_assistant_budgets(),
]; ];
/// List of all client-side database migration versions. /// List of all client-side database migration versions.

View file

@ -58,6 +58,7 @@ Map<String, int> _fillSyncedTables(Database db) {
final path = _id(); final path = _id();
final step = _id(); final step = _id();
final pdf = _id(); final pdf = _id();
final conversation = _id();
final rows = <String, (String, List<Object?>)>{ final rows = <String, (String, List<Object?>)>{
'studyset': ( 'studyset': (
"INSERT INTO studyset (id, spaceId, payload) VALUES (?, 1, x'00')", "INSERT INTO studyset (id, spaceId, payload) VALUES (?, 1, x'00')",
@ -164,6 +165,16 @@ Map<String, int> _fillSyncedTables(Database db) {
"sealedKey, createdAt) VALUES (?, 1, 'key', 'studyset', ?, x'00', 0)", "sealedKey, createdAt) VALUES (?, 1, 'key', 'studyset', ?, x'00', 0)",
[_id(), studyset], [_id(), studyset],
), ),
'assistant_conversation': (
'INSERT INTO assistant_conversation (id, spaceId, payload) '
"VALUES (?, 1, x'00')",
[conversation],
),
'assistant_turn': (
'INSERT INTO assistant_turn (id, spaceId, conversationId, payload) '
"VALUES (?, 1, ?, x'00')",
[_id(), conversation],
),
}; };
final synced = { final synced = {

View file

@ -26,4 +26,9 @@
/// 24 since GitHub signs nobody in (ADR 0020, D36, decided 2026-10-25): /// 24 since GitHub signs nobody in (ADR 0020, D36, decided 2026-10-25):
/// builds up to 23 (v0.20.0) read whether an account has GitHub /// builds up to 23 (v0.20.0) read whether an account has GitHub
/// (`SignInWays.gitHub`), which is gone; 24 doesn't. /// (`SignInWays.gitHub`), which is gone; 24 doesn't.
const minimumAppBuild = 24; ///
/// 25 since a budget is a person's, not each grant's (ADR 0021, D58,
/// decided 2026-10-26): builds up to 24 (v0.21.0) read each assistant's
/// limits from `assistant.offered` and grant with `assistantOperator.grant`
/// per assistant, both changed; 25 reads the person's budget.
const minimumAppBuild = 25;

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,120 @@
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_learn
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('halkyon_learn', '20261026140329810-assistant-budgets', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20261026140329810-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 serverpod_offline_sync
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_offline_sync', '20260914143806119', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260914143806119', "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

@ -51,3 +51,4 @@
20261025135744595-waiting-accounts 20261025135744595-waiting-accounts
20261025165102861-assistants 20261025165102861-assistants
20261025184004860-assistant-conversations 20261025184004860-assistant-conversations
20261026140329810-assistant-budgets

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 20261023192913500-simplelogin-keys
20261025135744931-waiting-accounts 20261025135744931-waiting-accounts
20261025165103193-assistants 20261025165103193-assistants
20261026140330157-assistant-budgets

View file

@ -1,4 +1,4 @@
# The instance's assistants (Halkyon Learn's ADR 0021, points 4 and 8, D37 and D38): # The instance's assistants (Halkyon Learn's ADR 0021, points 4 and 8, D37, D38, D63):
# copy to deploy/assistants.env, restrict it (chmod 600), and fill in. It is git-ignored, # copy to deploy/assistants.env, restrict it (chmod 600), and fill in. It is git-ignored,
# goes to Halkyon Learn's server alone (compose.yaml, `env_file`), never into an image, # goes to Halkyon Learn's server alone (compose.yaml, `env_file`), never into an image,
# the database or its backups; keep it with deploy/.env's secrets (in Proton Pass). # the database or its backups; keep it with deploy/.env's secrets (in Proton Pass).
@ -6,10 +6,10 @@
# restart: docker compose up -d server. Anything the server can't use stops it as it # restart: docker compose up -d server. Anything the server can't use stops it as it
# starts, naming the variable. # starts, naming the variable.
# #
# Nobody has an assistant until an operator grants it to them in the app, with limits # Nobody has an assistant until an operator grants it to them in the app, with a budget
# per day and month; each person agrees once to what goes where. The instance keeps # per day and month for all their assistants together, in money and in tokens (D58,
# what each person used (requests, tokens, cost) per day for 3 months, never what was # D59); each person agrees once to what goes where. The instance keeps what each person
# asked. # used (requests, tokens, cost) per day for 3 months, never what was asked.
# The assistants' IDs, in the order people see them, by commas or spaces: a letter, # The assistants' IDs, in the order people see them, by commas or spaces: a letter,
# then letters, digits or _. Each names its variables below (claude: ASSISTANT_CLAUDE_…) # then letters, digits or _. Each names its variables below (claude: ASSISTANT_CLAUDE_…)
@ -42,11 +42,19 @@ ASSISTANT_CLAUDE_KEY=
# on: people may add images (a card's picture, a PDF page) for a model that reads them. # on: people may add images (a card's picture, a PDF page) for a model that reads them.
#ASSISTANT_CLAUDE_IMAGES=on #ASSISTANT_CLAUDE_IMAGES=on
# What the model costs per million tokens, as the provider bills it, in # What the model costs per million tokens, as the provider bills it, in
# ASSISTANT_CURRENCY, with a point for decimals: both or none. With prices, limits and # ASSISTANT_CURRENCY, with a point for decimals: both or none. With prices, it counts
# figures are in money; without, in tokens. A change counts from then on; a grant made # against a budget's money and tokens; without, against its tokens alone. A change
# before prices came or went holds no more until its limits are set again. # counts from then on.
#ASSISTANT_CLAUDE_PRICE_INPUT=2 #ASSISTANT_CLAUDE_PRICE_INPUT=2
#ASSISTANT_CLAUDE_PRICE_OUTPUT=10 #ASSISTANT_CLAUDE_PRICE_OUTPUT=10
# How much the model thinks before it answers: low, medium, high, xhigh or max
# (Anthropic's names, sent in each provider's own field; Gemini takes low to high,
# Mistral none). EFFORT is what a question takes unless the person picks one;
# MAX_EFFORT the highest they may pick (unset: EFFORT). Both unset: the model's own,
# and nothing to pick. More effort spends more tokens, at the same price per token, and
# leaves more room for the answer (8,192 tokens at low to 65,536 at max).
#ASSISTANT_CLAUDE_EFFORT=medium
#ASSISTANT_CLAUDE_MAX_EFFORT=high
# A second one, through OpenAI's Responses API: # A second one, through OpenAI's Responses API:
#ASSISTANTS=claude gpt #ASSISTANTS=claude gpt

Some files were not shown because too many files have changed in this diff Show more