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 '20261025165102861-assistants/migration.dart';
part '20261025184004860-assistant-conversations/migration.dart';
part '20261026140329810-assistant-budgets/migration.dart';
/// Migration registry for the client-side database.
class MigrationRegistry {
@ -68,6 +69,7 @@ class MigrationRegistry {
_Migration20261025135744595_waiting_accounts(),
_Migration20261025165102861_assistants(),
_Migration20261025184004860_assistant_conversations(),
_Migration20261026140329810_assistant_budgets(),
];
/// List of all client-side database migration versions.

View file

@ -58,6 +58,7 @@ Map<String, int> _fillSyncedTables(Database db) {
final path = _id();
final step = _id();
final pdf = _id();
final conversation = _id();
final rows = <String, (String, List<Object?>)>{
'studyset': (
"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)",
[_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 = {

View file

@ -26,4 +26,9 @@
/// 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
/// (`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
20261025165102861-assistants
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
20261025135744931-waiting-accounts
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,
# 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).
@ -6,10 +6,10 @@
# restart: docker compose up -d server. Anything the server can't use stops it as it
# starts, naming the variable.
#
# Nobody has an assistant until an operator grants it to them in the app, with limits
# per day and month; each person agrees once to what goes where. The instance keeps
# what each person used (requests, tokens, cost) per day for 3 months, never what was
# asked.
# Nobody has an assistant until an operator grants it to them in the app, with a budget
# per day and month for all their assistants together, in money and in tokens (D58,
# D59); each person agrees once to what goes where. The instance keeps what each person
# 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,
# 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.
#ASSISTANT_CLAUDE_IMAGES=on
# 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
# figures are in money; without, in tokens. A change counts from then on; a grant made
# before prices came or went holds no more until its limits are set again.
# ASSISTANT_CURRENCY, with a point for decimals: both or none. With prices, it counts
# against a budget's money and tokens; without, against its tokens alone. A change
# counts from then on.
#ASSISTANT_CLAUDE_PRICE_INPUT=2
#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:
#ASSISTANTS=claude gpt

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