@stage5/lumine 0.2.40 → 0.2.42

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,8 +1,8 @@
1
1
  # Build SDK Index
2
2
 
3
- Version: 1.32.1
4
- Updated: 2026-08-14
5
- Generated: 2026-08-14T03:20:48.358Z
3
+ Version: 1.33.0
4
+ Updated: 2026-08-15
5
+ Generated: 2026-08-15T03:00:24.685Z
6
6
 
7
7
  ## Notes
8
8
  - This SDK is injected into Build iframes via the Build preview/runtime.
@@ -76,6 +76,27 @@ files:read, user:read, users:read, dailyReflections:read, content:read, content:
76
76
  - Returns: Canonical shareable deep-link URL string, or null when app info is unavailable
77
77
  - Builds a canonical shareable deep link into this app, e.g. https://www.twin-kle.com/app/884/432-the-great-gatsby.
78
78
  - Example: await Twinkle.app.getShareUrl('432-the-great-gatsby');
79
+ - history.getState() | scopes: none
80
+ - Returns: The current app-owned history state object, or null
81
+ - Read the current Build app view state stored through Twinkle.app.history.
82
+ - History state is local to this iframe session and never changes the parent Twinkle URL.
83
+ - Use this for archive/detail/page state inside one Build document; use navigate() to load another project file.
84
+ - history.push(state) | scopes: none
85
+ - Returns: A JSON-cloned copy of the stored state
86
+ - Add a confirmed in-app view transition to browser history so Back stays inside the Build app.
87
+ - State must be a JSON-serializable object no larger than 16 KB.
88
+ - Push only after the requested view has loaded successfully; do not synthesize server-owned state.
89
+ - Example: Twinkle.app.history.push({ view: 'edition', dayIndex: 2080, page: 'scores' });
90
+ - history.replace(state) | scopes: none
91
+ - Returns: A JSON-cloned copy of the stored state
92
+ - Replace the current in-app history entry without adding a Back step.
93
+ - Use this to establish the initial confirmed view or reconcile a loading-only change.
94
+ - history.subscribe(listener, { immediate } = {}) | scopes: none
95
+ - Returns: unsubscribe function
96
+ - Restore app views when the viewer moves through browser Back or Forward history.
97
+ - The listener receives a cloned app state object, or null for an entry not owned by this app.
98
+ - The listener is called immediately by default; pass { immediate: false } to wait for Back or Forward.
99
+ - Example: const off = Twinkle.app.history.subscribe((state) => restoreView(state), { immediate: false });
79
100
  - async navigate(target) | scopes: none
80
101
  - Returns: { success, src }
81
102
  - Navigate to another Build preview route through the parent bridge without dropping Twinkle SDK access.
@@ -89,12 +89,52 @@ Mikey; routine administrator runs still escalate suspected alternate accounts
89
89
  and never auto-enforce. `accounts add` and `note set` still accept only an
90
90
  existing **unbanned** bucket.
91
91
 
92
+ ### Audited identity inspection
93
+
94
+ Account-family evidence is private operator work, never a Zero/Ciel public
95
+ action and never a reason to browse unrelated user activity. Use the dedicated
96
+ lookup instead of direct database queries:
97
+
98
+ ```bash
99
+ lumine admin identity inspect Jay1216 \
100
+ --reason "Confirm the account family before updating its quota bucket" --json
101
+ lumine admin identity inspect Jay1216 \
102
+ --reason "Mikey requested DOB and email evidence for this decision" \
103
+ --include-private-evidence --json
104
+ ```
105
+
106
+ The default result resolves the exact username or user ID from the writer,
107
+ returns the canonical current AI bucket, orders candidate accounts oldest
108
+ first, identifies the oldest account within the strongest canonical family
109
+ boundary (bucket before email; never device alone) when that family fits in the
110
+ bounded evidence set, reports whether each
111
+ account has a DOB, and explains whether the link
112
+ came from explicit bucket membership, a verified-email match, or bounded exact-
113
+ device evidence. It does **not** reveal email addresses, DOB values, device IDs,
114
+ IP evidence, private messages, or unrelated activity. `--include-private-evidence`
115
+ adds DOB values and verified email addresses only; exact device IDs are never
116
+ returned.
117
+
118
+ Every inspection requires a concrete `--reason`. Before loading the evidence,
119
+ the API commits a private `identity.inspect` audit receipt containing the real
120
+ operator, requested target, reason, and whether private evidence was requested.
121
+ The evidence itself is deliberately not copied into the audit log. Inspection
122
+ is run-independent and has no public actor. Results are **candidate accounts
123
+ for human judgment**, not an automatic ownership finding and never automatic
124
+ grounds for moderation, bans, or bucket changes.
125
+
92
126
  ## Editorial priorities
93
127
 
94
128
  The CLI enforces none of this — it is the standing instruction for the operator
95
129
  or agent making the judgments, and it applies to every verb below: recommends,
96
130
  rewards, effort levels, Featured, skips, comments, and replies.
97
131
 
132
+ Public text authored as Ciel must be English. This is an operator and generation
133
+ instruction, not a script or keyword test: writing systems do not identify a
134
+ language reliably, and the API must not pretend otherwise. This is a
135
+ presentation rule, not an invitation to correct or lecture a member who writes
136
+ in another language; reply naturally in concise English.
137
+
98
138
  **Twinkle is not Reddit.** Do not rank a run's attention by popularity,
99
139
  recommendation count, or polish. Most users here are young children, and the
100
140
  posts that most need Zero or Ciel are the ones nobody else answered.
@@ -168,6 +208,13 @@ a human owner can decide, and a finding nobody reports is a finding that did not
168
208
  happen. **Every run ends with an escalation list**, and it belongs in the run's
169
209
  final report whether or not anyone asks for it.
170
210
 
211
+ Keep that list narrow enough to be useful. Escalate concrete child-safety,
212
+ exploitation, privacy, targeted harassment, or platform/system-abuse risk — not
213
+ ordinary children experimenting, arguing, making rumors, proposing informal
214
+ in-site loans or contests, asking where media can be found, or making an
215
+ unverified ownership claim. Those may merit a normal age-appropriate response,
216
+ but they are not escalations without credible harmful conduct or a real victim.
217
+
171
218
  Escalate, with the canonical `https://www.twin-kle.com/subjects/<id>` or
172
219
  `/comments/<id>` URL, a one-line summary, and why it needs him:
173
220
 
@@ -178,11 +225,10 @@ Escalate, with the canonical `https://www.twin-kle.com/subjects/<id>` or
178
225
  happened. These outrank every other category.
179
226
  - **Account integrity** — someone posting from another person's account,
180
227
  impersonation, shared logins, or a user operating a set of alternate accounts.
181
- - **Economy manipulation** — coin or XP farming across alternate accounts,
182
- paid-grinding arrangements, "invest and I will pay you back more" offers,
183
- pay-me-to-win contests, and anything that teaches other children a method for
184
- any of these. Note the recommendation count: a manipulation how-to that other
185
- kids are recommending is spreading, and that is the urgent part.
228
+ - **Economy exploitation** — coordinated coin or XP farming across alternate
229
+ accounts, coercive or deceptive arrangements, or a repeatable abuse of the
230
+ platform economy with concrete evidence. A child offering a voluntary loan,
231
+ repayment, prize, or contest is not enough by itself.
186
232
  - **AI-cost exploits** — patterns that convert free AI allowances into farmable
187
233
  value: clusters of young accounts with heavy AI/battery usage, one person
188
234
  operating many accounts that feed a single build through team branches,
@@ -224,6 +270,29 @@ Two rules that keep the list worth reading:
224
270
  design. Do not delete, hide, argue with, or publicly accuse anyone, and do not
225
271
  warn a child that they are in trouble. Report it and let Mikey decide.
226
272
 
273
+ Mikey's decision must remain attached after the originating run closes. These
274
+ commands are private, run-independent bookkeeping:
275
+
276
+ ```bash
277
+ lumine admin escalation list --json
278
+ lumine admin escalation list --status all --json
279
+ lumine admin escalation set 123 --status acknowledged \
280
+ --note "Mikey is reviewing the bot response" --json
281
+ lumine admin escalation set 123 --status resolved \
282
+ --note "No user fault; this audit concerned Zero's response" --json
283
+ ```
284
+
285
+ `list` defaults to unresolved `open` items. `--status` also accepts
286
+ `acknowledged`, `resolved`, or `all`. The number passed to `set` is the original
287
+ `run.escalation` audit ID returned by the run report/list. Every disposition is
288
+ an immutable private `escalation.status.set` audit event. A revision allocated
289
+ while the original escalation is locked orders concurrent decisions, so the
290
+ last applied decision is the canonical status and annotation even if request
291
+ audit IDs were reserved in another order. `--status open` can deliberately
292
+ reopen an item with an explanatory note. Status filters walk indexed escalation
293
+ history rather than a fixed latest-event window. No active run or public bot
294
+ identity is used.
295
+
227
296
  ## Common JSON types
228
297
 
229
298
  All `--json` success output is one uncolored JSON value:
@@ -237,7 +306,9 @@ type Success<D> = {
237
306
  };
238
307
  ```
239
308
 
240
- Failures print one JSON value, write no progress prose, and exit nonzero:
309
+ Failures print one JSON value to stdout and exit nonzero. A failing `--all`
310
+ scan may already have written bounded page progress to stderr; stdout remains
311
+ protocol-clean:
241
312
 
242
313
  ```ts
243
314
  type Failure = {
@@ -418,6 +489,8 @@ lumine admin identity status --json
418
489
  lumine admin identity use zero --json
419
490
  lumine admin identity use ciel --json
420
491
  lumine admin identity use auto --json
492
+ lumine admin identity inspect Jay1216 \
493
+ --reason "Confirm the account family before a bucket change" --json
421
494
  ```
422
495
 
423
496
  Schemas:
@@ -436,6 +509,51 @@ type IdentityStatus = Success<{
436
509
  }>;
437
510
 
438
511
  type IdentityUse = IdentityStatus;
512
+
513
+ type IdentityInspection = Success<{
514
+ inspection: {
515
+ targetUserId: number;
516
+ privateEvidenceIncluded: boolean;
517
+ manualBucket: {
518
+ id: number;
519
+ label: string;
520
+ memberCount: number;
521
+ isBanned: boolean;
522
+ } | null;
523
+ oldestAccount: IdentityCandidate | null;
524
+ oldestAccountBasis: "manual_bucket" | "verified_email" | "target_only";
525
+ oldestAccountComplete: boolean;
526
+ candidateAltCount: number;
527
+ accounts: IdentityCandidate[];
528
+ evidenceCoverage: {
529
+ deviceLookbackDays: number;
530
+ targetDeviceEvidenceRows: number;
531
+ targetDeviceIdsConsidered: number;
532
+ relatedDeviceEvidenceRows: number;
533
+ candidateLimit: number;
534
+ truncated: boolean;
535
+ };
536
+ };
537
+ }>;
538
+
539
+ type IdentityCandidate = {
540
+ userId: number;
541
+ username: string | null;
542
+ joinedAt: number | null;
543
+ isTarget: boolean;
544
+ isOldestAccount: boolean;
545
+ hasDateOfBirth: boolean;
546
+ banned: boolean;
547
+ deleted: boolean;
548
+ relationBasis: Array<
549
+ "target" | "manual_bucket" | "verified_email" | "exact_device"
550
+ >;
551
+ sharedDeviceCount: number;
552
+ privateEvidence?: {
553
+ dateOfBirth: string | null;
554
+ verifiedEmails: string[];
555
+ };
556
+ };
439
557
  ```
440
558
 
441
559
  `identity use` changes only the preference for a future start. It never changes
@@ -446,8 +564,16 @@ lumine admin daily-run start --identity auto --comment-mode off --json
446
564
  lumine admin daily-run start --identity ciel --comment-mode draft \
447
565
  --run-key daily:2026-08-06:review --json
448
566
  lumine admin daily-run status --json
567
+ lumine admin daily-run escalation add --target subject:123 \
568
+ --note "Public contact details need owner review" --severity urgent --json
569
+ lumine admin daily-run escalation add --target chatMessage:3768159 \
570
+ --note "Concrete safety issue in a bot-authored chat message" --json
571
+ lumine admin daily-run report --json
449
572
  lumine admin daily-run complete --json
450
573
  lumine admin daily-run fail --reason "operator stopped" --json
574
+ lumine admin escalation list --status all --json
575
+ lumine admin escalation set 123 --status resolved \
576
+ --note "Final owner decision" --json
451
577
  ```
452
578
 
453
579
  Schemas:
@@ -465,6 +591,18 @@ type DailyRunComplete = Success<{
465
591
  type DailyRunFail = DailyRunComplete;
466
592
  ```
467
593
 
594
+ Record only qualifying escalations as they are confirmed. `daily-run report`
595
+ then composes the active run's canonical audit events, successful mutations,
596
+ completed queue scans, recorded escalations, and the most useful brief deltas
597
+ into one result. Generate it before `complete`, because run-scoped reads require
598
+ the current active run. Queue coverage is written automatically only after an
599
+ `--all` traversal reaches canonical exhaustion; an interrupted scan remains in
600
+ its local checkpoint and cannot be misreported as complete.
601
+
602
+ Creating an escalation belongs to the active run; acknowledging, annotating,
603
+ resolving, or reopening it does not. Use the run-independent `escalation`
604
+ commands after Mikey responds instead of starting a follow-up delegated run.
605
+
468
606
  `lastRun` makes a lost-response retry of `complete` or `fail` possible after
469
607
  the active pointer has been cleared. Other run-scoped commands accept only the
470
608
  current unexpired `active` run. Completion first finalizes any mutation whose
@@ -498,13 +636,17 @@ JSON error includes `details.retryIdempotencyKey` for a safe exact retry.
498
636
 
499
637
  ```bash
500
638
  lumine admin recommendations list --kind recommend \
501
- --content-types comment,dailyReflection --cursor '<cursor>' --json
639
+ --content-types comment,dailyReflection --all --json
640
+ lumine admin recommendations list --after 2026-08-14T00:00:00Z \
641
+ --all --checkpoint recommendations.json --json
642
+ lumine admin recommendations list --include-legacy --all --json
502
643
  lumine admin recommendations list --unviewed --json
503
644
  lumine admin subjects candidates --after 2026-08-01T00:00:00Z \
504
- --cursor '<cursor>' --json
645
+ --all --checkpoint subjects.json --json
505
646
  lumine admin subjects candidates --effort unassigned --json
506
647
  lumine admin subjects candidates --unviewed --json
507
- lumine admin builds candidates --cursor '<cursor>' --limit 50 --json
648
+ lumine admin builds candidates --all --limit 50 --json
649
+ lumine admin builds review build:884 --output-dir ./build-review --json
508
650
  ```
509
651
 
510
652
  Schemas:
@@ -575,12 +717,34 @@ type BuildCandidates = Success<{
575
717
  }>;
576
718
  ```
577
719
 
578
- Both cursors freeze a primary-key high-water mark and traverse descending IDs,
579
- so concurrent inserts cannot shift or duplicate later pages. Both walks scan a
580
- bounded primary-key window (500 rows) per call before applying their residual
581
- filters, so a page — recommendation or subject — can be empty while `hasMore`
582
- remains true; continue until `exhausted`. Subject `--after` is inclusive, and
583
- the opaque cursor is bound to its original date and effort filters.
720
+ Subject cursors freeze a primary-key high-water mark and traverse descending
721
+ IDs. Bounded recommendation cursors freeze both the feed-ID high-water mark and
722
+ the server timestamp, then traverse the indexed `(timeStamp, id)` order; this
723
+ also catches a Daily Reflection whose old feed row moved forward when it was
724
+ reshared. Explicit legacy scans retain the descending primary-key walk. A page
725
+ can be empty while `hasMore` remains true; continue until `exhausted`. `--all`
726
+ does that automatically and writes a private checkpoint after every
727
+ server-confirmed page; `--resume` continues only when the checkpoint belongs to
728
+ the same API, run, and exact request. The final result can be copied to
729
+ `--output`, while `--checkpoint` is resumable operational state. Subject
730
+ `--after` is inclusive, and every opaque cursor is bound to its original
731
+ filters.
732
+
733
+ For `--all --json`, stdout remains exactly one JSON value. Scan-start, first-
734
+ page, every-tenth-page, and exhaustion progress is written to **stderr** with
735
+ only page/scanned/candidate counts and the private checkpoint path. A long
736
+ traversal therefore no longer looks stalled, while piping stdout to `jq` or a
737
+ file remains safe.
738
+
739
+ Recommendations default to `--since-run`: the server uses the previous
740
+ completed run's start time (or the same bounded seven-day fallback used by the
741
+ brief on a first run). That deliberate start-to-start overlap gives the queue
742
+ at-least-once coverage when content arrives after the prior snapshot but before
743
+ that run completes. `--after` supplies an explicit inclusive timestamp.
744
+ All-history traversal is deliberately available only through
745
+ `--include-legacy`. The CLI requires the API to echo the canonical `after`
746
+ boundary for bounded modes, so deploying a new CLI against an older API cannot
747
+ silently fall back to a million-row historical scan.
584
748
 
585
749
  `builds candidates` is a management-agent discovery view over the canonical
586
750
  public Build browser, ordered by the current published release. It is
@@ -592,6 +756,15 @@ genuinely try the published runtime, or pull and read an open-source project,
592
756
  before making that judgment. Direct API/persona automation is never a review
593
757
  substitute.
594
758
 
759
+ `builds review` is the managed runtime path: it fetches the current published
760
+ artifact identity, launches the app in an isolated temporary Chromium profile,
761
+ captures a screenshot and bounded console evidence, then fetches the identity
762
+ again. It writes `review.json` in a unique per-review subdirectory only when
763
+ the browser completed, the screenshot exists, and the artifact did not change
764
+ mid-review. Attach the returned `receiptPath` with
765
+ `comment draft ... --review-receipt review.json`; this binds the draft to the
766
+ exact reviewed artifact without copying a version number by hand.
767
+
595
768
  During every management run, scan recent Build candidates back through the
596
769
  run's review window alongside Subjects and the recommendation queue. An app
597
770
  that is thin, broken, private, unchanged since a prior substantive bot
@@ -919,6 +1092,10 @@ If recommendation succeeds but reward fails, the command exits nonzero with
919
1092
  ```bash
920
1093
  lumine admin post skip dailyReflection:99 --json
921
1094
  lumine admin post skip comment:456 --reason "one-line answer, nothing to add" --json
1095
+ lumine admin post skip-batch --target-file skip-targets.json \
1096
+ --checkpoint skip-progress.json --json
1097
+ lumine admin post skip-batch --target-file skip-targets.json \
1098
+ --checkpoint skip-progress.json --resume --json
922
1099
  ```
923
1100
 
924
1101
  A skip records that the management rotation has judged a recommend-queue item
@@ -937,6 +1114,13 @@ metadata — it is the agent's memory of the judgment, not public content.
937
1114
  The skip requires the `recommendation:write` scope and is audited like every
938
1115
  other mutation.
939
1116
 
1117
+ `skip-batch` accepts either a JSON array (strings or `{ "target", "reason" }`
1118
+ objects), `{ "targets": [...] }`, or one target per text line. It deduplicates
1119
+ targets, submits them sequentially through the same canonical audited endpoint,
1120
+ and checkpoints only after each response is confirmed. `--resume` verifies the
1121
+ exact target-set fingerprint and run ID before continuing; it never guesses
1122
+ which writes succeeded.
1123
+
940
1124
  ```ts
941
1125
  type PostSkip = Success<{
942
1126
  skip: {
@@ -953,9 +1137,10 @@ type PostSkip = Success<{
953
1137
 
954
1138
  ```bash
955
1139
  lumine admin news --json
956
- lumine admin news claim --json
957
- lumine admin news submit --edition-id 42 --lease-token <token> \
958
- --file editorial.json --model "Claude" --json
1140
+ lumine admin news claim --output claim.json --scaffold editorial.json --json
1141
+ lumine admin news validate --claim claim.json --file editorial.json --json
1142
+ lumine admin news submit --claim claim.json --file editorial.json \
1143
+ --model "Ciel" --json
959
1144
  lumine admin news print --json
960
1145
  ```
961
1146
 
@@ -968,9 +1153,18 @@ the run, and if `printedToday` is false with no edition `pending` or
968
1153
  **Preferred: write the editorial yourself.** `news claim` reserves today's
969
1154
  edition under the server's generation lease and returns the exact canonical
970
1155
  event digest the server would otherwise send to its own model, so no provider
971
- API credits are spent. Write a `GeneratedEditorial` JSON and send it back with
972
- `news submit` within the ten-minute lease. The server treats the editorial as
973
- untrusted regardless of author: every story must cite an exact `eventKey`
1156
+ API credits are spent. With `--output` and `--scaffold`, the CLI writes that
1157
+ lease/digest to a private claim file and creates an editable editorial shell.
1158
+ `news validate` runs locally, before authentication or a network request, and
1159
+ checks the complete citation graph plus byte-exact quote boundaries. Submit the
1160
+ validated pair with `news submit --claim`; the CLI reads the edition and lease
1161
+ from the claim file and validates again immediately before the request. The
1162
+ explicit `--edition-id` / `--lease-token` form remains available for backwards
1163
+ compatibility.
1164
+
1165
+ Write a `GeneratedEditorial` JSON and send it back within the ten-minute lease.
1166
+ The server still treats the editorial as untrusted regardless of author: every
1167
+ story must cite an exact `eventKey`
974
1168
  from the digest, front-page `sourceQuote`s must be verbatim contiguous
975
1169
  passages of the cited event's summary (invalid quotes are replaced with
976
1170
  canonical text), section and page layout are server-enforced, announcements
@@ -1189,6 +1383,22 @@ rows per source — retry with a narrower `--days` window, and do not complete
1189
1383
  the run while either flag remains true. Run it right after the
1190
1384
  brief, and **read every row** — the tool deliberately does no filtering,
1191
1385
  scoring, or keyword matching, because the judgment is the reviewing agent's.
1386
+
1387
+ **Privacy boundary:** this is an audit of how Twinkle's bots treated members,
1388
+ not a moderation queue for members' private use of the tool. Treat private
1389
+ human messages and creative work as confidential context. Read every
1390
+ bot-authored row, but inspect adjacent human messages only when the minimum
1391
+ necessary context is needed to judge the bot's response; never browse the rest
1392
+ of a private conversation out of curiosity. Do not characterize or escalate a
1393
+ member's lawful private creative writing — including a teenager's romance
1394
+ fiction — merely because its subject is intimate or romantic. An escalation
1395
+ must identify what **Zero or Ciel** did (for example, an invented premise,
1396
+ pressure, sexualization, abuse, or a failed boundary), include only the narrow
1397
+ context needed for Mikey to decide a remedy, and never reuse private material
1398
+ for public editorial judgment, Notable User selection, or unrelated identity
1399
+ investigation. A separate concrete risk to a member may still be escalated,
1400
+ but it does not authorize a broader review of their private activity.
1401
+
1192
1402
  Judge against the same values the editorial priorities encode:
1193
1403
 
1194
1404
  - **premises must be real.** The 08-11 message didn't merely choose a bad
@@ -1272,11 +1482,16 @@ farm-signal sections added the same day):
1272
1482
  ("$X so far today; complete days run ~$Y/day"). Real
1273
1483
  incident: a run report quoted a ~15%-complete day bucket ($5) as the site's
1274
1484
  daily AI spend (complete days were running ~$40-50). Flag accounts that jumped tiers or
1275
- dominate that report period. May be `{ unavailable: true }` if the cost
1485
+ dominate that report period. Routine `topAccounts` rows identify the account
1486
+ by user ID/username and expose only an `identitySummary` count/manual-bucket
1487
+ flag; raw identity strings and verified email addresses are deliberately
1488
+ omitted. Use reason-required `identity inspect`, with
1489
+ `--include-private-evidence` only when Mikey's concrete decision needs the
1490
+ addresses or DOB values. May be `{ unavailable: true }` if the cost
1276
1491
  report fails; say so rather than guessing. This section is also the run's
1277
1492
  AI-cost exploit watch: while reading it, actively look for the signatures the
1278
1493
  brief actually exposes — one risk group spanning several user IDs, repeated
1279
- plus-tag or dot-variant email families among top accounts, or heavy spend by
1494
+ account groups in `farmSignals.inboxFamilies`, or heavy spend by
1280
1495
  accounts that `economy.topGainers` or `notableCandidates` independently marks
1281
1496
  as recent signups. Cross-check those signals against the escalation
1282
1497
  categories. Missing join-date or community data is unknown, not evidence that
@@ -1291,9 +1506,11 @@ farm-signal sections added the same day):
1291
1506
  execute them with
1292
1507
  `lumine admin notable add <userId|username> --note "<specific rationale>"`
1293
1508
  (idempotent —
1294
- an existing member returns `already_done`; requires the `notable:write`
1295
- scope, audited as `notable.add`, and writes through the management page's
1296
- own canonical service). Without his approval the run only proposes.
1509
+ an existing member returns `already_done`; run-independent, privately audited
1510
+ as `notable.add`, and writes through the management page's own canonical
1511
+ service). It can therefore record Mikey's approval after the daily run has
1512
+ closed without opening another delegated run. Without his approval the run
1513
+ only proposes.
1297
1514
  **Always pass `--note`** with a concrete one-or-two-sentence record of what
1298
1515
  made them notable — real numbers and specifics from the brief window, not
1299
1516
  "active user". It lands in the management page's reason column, which is
@@ -1353,12 +1570,14 @@ farm-signal sections added the same day):
1353
1570
  Deliberately coarse: it is an onboarding health check, not per-child
1354
1571
  session tracking.
1355
1572
  - `farmSignals` — AI-cost farm signatures derivable with ZERO new data
1356
- collection: `inboxFamilies` (verified emails from accounts active in the
1357
- last `inboxFamilyActivityDays`, with only Gmail/googlemail's documented
1573
+ collection: `inboxFamilies` (account groups formed from verified emails of
1574
+ accounts active in the last `inboxFamilyActivityDays`, with only
1575
+ Gmail/googlemail's documented
1358
1576
  plus-tag and dot aliases collapsed, flagging inboxes behind 3+ accounts) and
1359
1577
  `youngAccountAiUsage` (accounts under 30 days old drawing battery in the
1360
- whole-day `aiUsageDayWindow`). SIGNAL ONLY: siblings legitimately share a
1361
- parent inbox, so an inbox family is a reason to look, never proof or grounds
1578
+ whole-day `aiUsageDayWindow`). The raw canonical inbox is never returned in
1579
+ the routine brief. SIGNAL ONLY: siblings legitimately share a parent inbox,
1580
+ so an inbox family is a reason to look, never proof or grounds
1362
1581
  for action. Feed real suspicions to the AI-cost escalation category. Shared
1363
1582
  AI device/IP risk evidence is already in `aiSpending.topRiskGroups`; do not
1364
1583
  guess it from inbox similarity.
@@ -1451,7 +1670,28 @@ type InsightsBrief = Success<{
1451
1670
  endDayInProgress: boolean;
1452
1671
  summary: unknown;
1453
1672
  byDay: unknown[];
1454
- topAccounts: unknown[];
1673
+ topAccounts: Array<{
1674
+ userId: number;
1675
+ username: string;
1676
+ eventCount: number;
1677
+ requestCount: number;
1678
+ estimatedCostUsd: number;
1679
+ inputTokens: number;
1680
+ cachedInputTokens: number;
1681
+ cacheEligibleInputTokens: number;
1682
+ outputTokens: number;
1683
+ totalTokens: number;
1684
+ imageCount: number;
1685
+ audioSeconds: number;
1686
+ energyUnits: number;
1687
+ energyChargedUnits: number;
1688
+ energyOverflowUnits: number;
1689
+ coinCharged: number;
1690
+ identitySummary: {
1691
+ observedIdentityCount: number;
1692
+ manualBucketObserved: boolean;
1693
+ };
1694
+ }>;
1455
1695
  topRiskGroups: unknown[];
1456
1696
  }
1457
1697
  | InsightUnavailable;
@@ -1539,7 +1779,6 @@ type InsightsBrief = Success<{
1539
1779
  | {
1540
1780
  inboxFamilyActivityDays: number;
1541
1781
  inboxFamilies: Array<{
1542
- inbox: string;
1543
1782
  accounts: Array<{
1544
1783
  userId: number;
1545
1784
  username: string | null;