@stage5/lumine 0.2.28 → 0.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/sdk/LUMINE_ADMIN.md +202 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.28",
3
+ "version": "0.2.30",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -139,6 +139,17 @@ Escalate, with the canonical `https://www.twin-kle.com/subjects/<id>` or
139
139
  pay-me-to-win contests, and anything that teaches other children a method for
140
140
  any of these. Note the recommendation count: a manipulation how-to that other
141
141
  kids are recommending is spreading, and that is the urgent part.
142
+ - **AI-cost exploits** — patterns that convert free AI allowances into farmable
143
+ value: clusters of young accounts with heavy AI/battery usage, one person
144
+ operating many accounts that feed a single build through team branches,
145
+ plus-tagged or dot-variant email families (`kid+1@`, `k.id@`) behind multiple
146
+ active accounts, repeated first exchanges across accounts already linked by
147
+ independent evidence, or a rescue claim by an account created under 30 days
148
+ ago (the API maturity gate should make that last case impossible). A new
149
+ member's one first exchange is intended onboarding and is not suspicious by
150
+ itself. The daily battery is real provider money; treat farming signatures
151
+ with the same seriousness as coin farming. Escalate the account list and
152
+ evidence; never auto-enforce.
142
153
  - **Bug reports** the run encountered, even secondhand in a comment thread.
143
154
 
144
155
  Two rules that keep the list worth reading:
@@ -1052,7 +1063,8 @@ end every run report with an **"Insights for Mikey"** section carrying only
1052
1063
  the deltas and anomalies worth his time, next to the escalation list. Never
1053
1064
  dump raw sections at him.
1054
1065
 
1055
- Four sections (Mikey's chosen cut, 2026-08-10):
1066
+ Nine sections (Mikey's chosen cut 2026-08-10; behavioral-insight and
1067
+ farm-signal sections added the same day):
1056
1068
 
1057
1069
  - `economy` — `topGainers` (coin-ledger aggregation over the window: gained,
1058
1070
  spent, net, current balance per user, Zero/Ciel excluded) and `topBalances`
@@ -1068,7 +1080,16 @@ Four sections (Mikey's chosen cut, 2026-08-10):
1068
1080
  report period can begin up to one day before or after the exact brief window,
1069
1081
  so use those bounds when describing it. Flag accounts that jumped tiers or
1070
1082
  dominate that report period. May be `{ unavailable: true }` if the cost
1071
- report fails; say so rather than guessing.
1083
+ report fails; say so rather than guessing. This section is also the run's
1084
+ AI-cost exploit watch: while reading it, actively look for the signatures the
1085
+ brief actually exposes — one risk group spanning several user IDs, repeated
1086
+ plus-tag or dot-variant email families among top accounts, or heavy spend by
1087
+ accounts that `economy.topGainers` or `notableCandidates` independently marks
1088
+ as recent signups. Cross-check those signals against the escalation
1089
+ categories. Missing join-date or community data is unknown, not evidence that
1090
+ an account is young or empty. A run that reads the spending report without
1091
+ asking "could any of this be one person with many accounts?" has skipped a
1092
+ duty.
1072
1093
  - `notableCandidates` — kids (never bots, staff `userType`s, or users already
1073
1094
  on the Notable Users list) ranked by authored activity in the window, with
1074
1095
  `isNewUser` marking window-new signups. Use it to find the overlooked and
@@ -1097,14 +1118,62 @@ Four sections (Mikey's chosen cut, 2026-08-10):
1097
1118
  calendar day before the exact `window.sinceTs`.
1098
1119
  Report the contrast between the buckets, and treat `genuinelyInterested` as
1099
1120
  notable-users-but-for-teachers.
1121
+ - `engagementPulse` — distinct users per surface (activeUsers, subjects,
1122
+ comments, recommendations, wordle, reflections, dailyTasks, aiChat,
1123
+ lumineBuildChat, buildsEdited, buildsPlayed) for the current window vs the
1124
+ equal-length previous window, each as `{ current, previous, delta }`.
1125
+ Presence, build edits, and build plays come from durable action/version/view
1126
+ events, not mutable `lastActive`/`updatedAt` snapshots. Zero/Ciel are excluded
1127
+ from authored surfaces. Wordle, daily tasks, and AI chat use the equal
1128
+ calendar-bucket ranges in `dayWindow`; those can begin before the exact
1129
+ timestamp window but always compare the same number of days. This is the
1130
+ "where do users actually live, and is it shifting?" section: report only the
1131
+ deltas that mean something, and read a surface's absolute size before
1132
+ dramatizing a small delta.
1133
+ - `launchMetrics` — readouts for recently shipped features so nothing ships
1134
+ unmeasured. v1 carries `firstBuildRescue` (offers recorded and redemptions
1135
+ in the window, each `{ total, byEventType }`, plus first-Lumine-exchange
1136
+ claims) and `wordleSkipShield` (`startDayIndex`, `active`, judged dodges,
1137
+ and skip covers split `earned` vs `fromRescue`). Wordle metrics cover only
1138
+ completed, actually judged days in `judgedWindow`; today's in-progress game
1139
+ is never called a dodge. Offers without redemptions are a reason to inspect
1140
+ sample size, event type, and offer age — not proof of a broken funnel by
1141
+ themselves.
1142
+ - `goneQuiet` — the inverse of `notableCandidates`: users whose `lastActive`
1143
+ fell in the 14 days before the window (so they were around, then stopped),
1144
+ ranked by how regular they were in the prior 30 days (daily tasks and
1145
+ Wordle), capped at 15 with `daysQuiet`. Use it for product signal (what did
1146
+ they stop doing?) and gentle outreach candidates; never guilt a child in
1147
+ public about absence.
1148
+ - `newUserFunnel` — signups in the window with `activeOnDayOne` (any
1149
+ XP-ledger event within 24h of joining) and `returnedAfterDayOne`
1150
+ (`lastActive` beyond their first day), plus the newest few accounts.
1151
+ Deliberately coarse: it is an onboarding health check, not per-child
1152
+ session tracking.
1153
+ - `farmSignals` — AI-cost farm signatures derivable with ZERO new data
1154
+ collection: `inboxFamilies` (verified emails from accounts active in the
1155
+ last `inboxFamilyActivityDays`, with only Gmail/googlemail's documented
1156
+ plus-tag and dot aliases collapsed, flagging inboxes behind 3+ accounts) and
1157
+ `youngAccountAiUsage` (accounts under 30 days old drawing battery in the
1158
+ whole-day `aiUsageDayWindow`). SIGNAL ONLY: siblings legitimately share a
1159
+ parent inbox, so an inbox family is a reason to look, never proof or grounds
1160
+ for action. Feed real suspicions to the AI-cost escalation category. Shared
1161
+ AI device/IP risk evidence is already in `aiSpending.topRiskGroups`; do not
1162
+ guess it from inbox similarity.
1100
1163
 
1101
1164
  The command needs only an active run's `content:read` scope and mutates
1102
1165
  nothing; reading the brief is not audited content action. Window boundaries
1103
1166
  on the big append-only ledgers are found by binary-searching the PRIMARY key
1104
1167
  (several tables have no timeStamp index), avoiding lifetime scans; aggregation
1105
- is still bounded to the selected 1–30 day window.
1168
+ is still bounded to the selected 1–30 day window. The optional sections run
1169
+ serially around the existing core report so this low-frequency command cannot
1170
+ occupy the production reader pool; one unavailable section does not suppress
1171
+ the others.
1106
1172
 
1107
1173
  ```ts
1174
+ type InsightUnavailable = { unavailable: true; error: string };
1175
+ type WindowDelta = { current: number; previous: number; delta: number };
1176
+
1108
1177
  type TeacherInsight = {
1109
1178
  userId: number;
1110
1179
  username: string | null;
@@ -1180,7 +1249,115 @@ type InsightsBrief = Success<{
1180
1249
  topAccounts: unknown[];
1181
1250
  topRiskGroups: unknown[];
1182
1251
  }
1183
- | { unavailable: true; error: string };
1252
+ | InsightUnavailable;
1253
+ engagementPulse:
1254
+ | {
1255
+ windowDays: number;
1256
+ dayWindow: {
1257
+ currentStartDayIndex: number;
1258
+ currentEndDayIndex: number;
1259
+ previousStartDayIndex: number;
1260
+ previousEndDayIndex: number;
1261
+ dayCount: number;
1262
+ };
1263
+ surfaces: {
1264
+ activeUsers: WindowDelta;
1265
+ subjects: WindowDelta;
1266
+ comments: WindowDelta;
1267
+ recommendations: WindowDelta;
1268
+ wordle: WindowDelta;
1269
+ reflections: WindowDelta;
1270
+ dailyTasks: WindowDelta;
1271
+ aiChat: WindowDelta;
1272
+ lumineBuildChat: WindowDelta;
1273
+ buildsEdited: WindowDelta;
1274
+ buildsPlayed: WindowDelta;
1275
+ };
1276
+ }
1277
+ | InsightUnavailable;
1278
+ launchMetrics:
1279
+ | {
1280
+ firstBuildRescue: {
1281
+ offersRecorded: {
1282
+ total: number;
1283
+ byEventType: Record<string, number>;
1284
+ };
1285
+ redemptions: {
1286
+ total: number;
1287
+ byEventType: Record<string, number>;
1288
+ };
1289
+ firstLumineExchangeClaims: number;
1290
+ };
1291
+ wordleSkipShield: {
1292
+ startDayIndex: number;
1293
+ active: boolean;
1294
+ judgedWindow: {
1295
+ startDayIndex: number;
1296
+ endDayIndex: number;
1297
+ } | null;
1298
+ judgedDodges: number;
1299
+ skipCovers: { earned: number; fromRescue: number };
1300
+ };
1301
+ }
1302
+ | InsightUnavailable;
1303
+ goneQuiet:
1304
+ | {
1305
+ users: Array<{
1306
+ userId: number;
1307
+ username: string | null;
1308
+ lastActive: number | null;
1309
+ daysQuiet: number | null;
1310
+ dailyTasksPrior30d: number;
1311
+ wordlePlaysPrior30d: number;
1312
+ regularityScore: number;
1313
+ }>;
1314
+ totals: { wentQuiet: number; previouslyRegular: number };
1315
+ }
1316
+ | InsightUnavailable;
1317
+ newUserFunnel:
1318
+ | {
1319
+ totals: {
1320
+ signups: number;
1321
+ activeOnDayOne: number;
1322
+ returnedAfterDayOne: number;
1323
+ };
1324
+ newest: Array<{
1325
+ userId: number;
1326
+ username: string | null;
1327
+ joinedAt: number | null;
1328
+ activeOnDayOne: boolean;
1329
+ returnedAfterDayOne: boolean;
1330
+ }>;
1331
+ }
1332
+ | InsightUnavailable;
1333
+ farmSignals:
1334
+ | {
1335
+ inboxFamilyActivityDays: number;
1336
+ inboxFamilies: Array<{
1337
+ inbox: string;
1338
+ accounts: Array<{
1339
+ userId: number;
1340
+ username: string | null;
1341
+ joinedAt: number | null;
1342
+ lastActive: number | null;
1343
+ }>;
1344
+ accountCount: number;
1345
+ youngAccounts: number;
1346
+ }>;
1347
+ aiUsageDayWindow: {
1348
+ startDayIndex: number;
1349
+ endDayIndex: number;
1350
+ };
1351
+ youngAccountAiUsage: Array<{
1352
+ userId: number;
1353
+ username: string | null;
1354
+ joinedAt: number | null;
1355
+ energyUnits: number;
1356
+ replies: number;
1357
+ }>;
1358
+ notes: string;
1359
+ }
1360
+ | InsightUnavailable;
1184
1361
  }>;
1185
1362
  ```
1186
1363
 
@@ -1314,6 +1491,27 @@ tried it — ask the author about it instead. This is the standing rule for
1314
1491
  every composed comment, applied to apps: never claim an experience the
1315
1492
  session did not actually have.
1316
1493
 
1494
+ **Offer a Lumine prompt when the moment invites it (Mikey's direction,
1495
+ 2026-08-10).** Zero and Ciel may include one concrete, copy-pasteable Lumine
1496
+ prompt in a comment or reply — a genuinely powerful one, tailored to what the
1497
+ kid is already doing — when the occasion naturally calls for it. Appropriate
1498
+ occasions: a kid describes an idea they wish existed, asks how something on
1499
+ the site was made, hits the edge of what a post/drawing/story can do, shares
1500
+ a Build app that could grow a specific feature, or shows an interest (space,
1501
+ cats, chess, comics) that maps cleanly onto something Lumine could build with
1502
+ them. On those occasions, the prompt IS the helpful answer: quote it so it
1503
+ can be copied as-is, keep it specific to their interest, and mention it works
1504
+ in the Build workspace chat. Restraint rules: never more than one prompt per
1505
+ comment; never in condolence, conflict, wellbeing, or moderation-adjacent
1506
+ threads; never as a reflex closing line on ordinary comments — if the comment
1507
+ is complete without the prompt, post it without the prompt. A run where only
1508
+ a few comments carry a prompt is healthy; a run where most do is shameless
1509
+ plugging, which is exactly what Mikey asked to avoid. SDK-aware prompt ideas
1510
+ are especially good ("ask Lumine to make a magazine that pulls real Twinkle
1511
+ posts with Twinkle.subjects.search") because kids do not know the content
1512
+ APIs exist — but only suggest SDK capabilities that actually exist; check
1513
+ TWINKLE_BUILD_SDK.md if unsure.
1514
+
1317
1515
  A composed draft (`--file`, plain UTF-8 text, at most the website's 10,000
1318
1516
  character comment limit) flows through the identical draft lifecycle —
1319
1517
  reservation, idempotency, context-revision CAS, publish fencing, audit