@stage5/lumine 0.3.11 → 0.3.12

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/lib/admin.js CHANGED
@@ -2021,6 +2021,32 @@ export function parseAdminOperation(options) {
2021
2021
  }
2022
2022
  }
2023
2023
 
2024
+ if (namespace === "identity" && action === "network") {
2025
+ const networkTarget = String(target || "").trim();
2026
+ const reason = String(options.adminReason || "").trim();
2027
+ if (!networkTarget || !reason) {
2028
+ throw cliValidationError(
2029
+ "Usage: lumine admin identity network <userId|username> --reason <management reason> [--include-private-evidence].",
2030
+ );
2031
+ }
2032
+ if (reason.length > MAX_IDENTITY_INSPECTION_REASON_LENGTH) {
2033
+ throw cliValidationError(
2034
+ `A network-evidence reason must be at most ${MAX_IDENTITY_INSPECTION_REASON_LENGTH} characters.`,
2035
+ );
2036
+ }
2037
+ return writeOperation(
2038
+ "identity.network",
2039
+ "POST",
2040
+ "/cli/admin/identity/network",
2041
+ {
2042
+ target: networkTarget,
2043
+ reason,
2044
+ includePrivateEvidence: options.adminIncludePrivateEvidence === true,
2045
+ },
2046
+ { requiresRun: false },
2047
+ );
2048
+ }
2049
+
2024
2050
  if (namespace === "economy" && action === "trace") {
2025
2051
  const traceTarget = String(target || "").trim();
2026
2052
  const reason = String(options.adminReason || "").trim();
@@ -5661,6 +5687,46 @@ const APPROVAL_LABELS = { dob: "birthdate", mentor: "teacher", meetup: "meetup"
5661
5687
  const approvalDate = (seconds) =>
5662
5688
  seconds ? new Date(Number(seconds) * 1000).toISOString().slice(0, 10) : "?";
5663
5689
 
5690
+ // Network evidence (users_actions addresses): a shared network (academy,
5691
+ // school) is only counted; a small one lists who shares it.
5692
+ function printNetwork(network, indent = "") {
5693
+ const addresses = network?.addresses || [];
5694
+ if (!network) return;
5695
+ if (network.unavailable) {
5696
+ console.log(`${indent}Network: could not be read this time (try again)`);
5697
+ return;
5698
+ }
5699
+ if (!addresses.length) {
5700
+ console.log(`${indent}Network: no recorded addresses`);
5701
+ return;
5702
+ }
5703
+ console.log(`${indent}Network: ${addresses.length} recent address(es)`);
5704
+ for (const a of addresses) {
5705
+ const span = `${approvalDate(a.firstAt)}..${approvalDate(a.lastAt)}`;
5706
+ const older = a.olderHistoryNotRead ? " · older history not read" : "";
5707
+ if (a.kind === "shared_network") {
5708
+ console.log(
5709
+ `${indent} - ${a.address} [${a.addressId}] · shared network: ${a.otherAccountsAtLeast}+ other accounts (not listed) · ${a.actions} actions ${span}${older}`,
5710
+ );
5711
+ continue;
5712
+ }
5713
+ if (a.kind === "only_this_account") {
5714
+ console.log(
5715
+ `${indent} - ${a.address} [${a.addressId}] · only this account · ${a.actions} actions ${span}${older}`,
5716
+ );
5717
+ continue;
5718
+ }
5719
+ console.log(
5720
+ `${indent} - ${a.address} [${a.addressId}] · small network shared with ${a.accounts.length} account(s) · ${a.actions} actions ${span}${older}`,
5721
+ );
5722
+ for (const o of a.accounts) {
5723
+ console.log(
5724
+ `${indent} ${o.username || "?"} (#${o.userId})${o.linked ? " · their linked account" : ""}${o.exists === false ? " · deleted" : ""} · age ${o.age ?? "?"} · ${o.isTeacherOrStaff ? "teacher/staff" : "member"}${o.userType ? ` · type ${o.userType}` : ""}${o.banned ? " · BANNED" : ""} · ${o.actions} actions ${approvalDate(o.firstAt)}..${approvalDate(o.lastAt)}`,
5725
+ );
5726
+ }
5727
+ }
5728
+ }
5729
+
5664
5730
  function printEvidence(evidence) {
5665
5731
  if (!evidence) return;
5666
5732
  const a = evidence.account || {};
@@ -5689,6 +5755,7 @@ function printEvidence(evidence) {
5689
5755
  ` - ${l.username} (#${l.userId}) via ${(l.linkedBy || []).join("+")}${l.sharedDeviceCount ? ` (${l.sharedDeviceCount} device)` : ""} · age ${l.age ?? "?"} · ${l.isTeacherOrStaff ? "teacher/staff" : "member"} · joined ${approvalDate(l.joinedAt)}${l.banned ? " · BANNED" : ""}`,
5690
5756
  );
5691
5757
  }
5758
+ printNetwork(evidence.network);
5692
5759
  console.log(
5693
5760
  `Flags: ${(evidence.flags || []).length ? evidence.flags.join(", ") : "none"}`,
5694
5761
  );
@@ -5907,6 +5974,18 @@ function printAdminResult({ operation, result }) {
5907
5974
  }
5908
5975
  return;
5909
5976
  }
5977
+ if (operation.name === "identity.network") {
5978
+ const account = data.account || {};
5979
+ console.log(`${account.username} (#${account.userId})`);
5980
+ printNetwork(data.network);
5981
+ console.log(
5982
+ `Flags: ${(data.network?.flags || []).length ? data.network.flags.join(", ") : "none"}`,
5983
+ );
5984
+ console.log(
5985
+ "An address many accounts share (an academy, a school) links strangers and is only counted; a small one lists who shares it. Facts, not a finding.",
5986
+ );
5987
+ return;
5988
+ }
5910
5989
  if (operation.name.startsWith("approvals.") || operation.name.startsWith("teachers.")) {
5911
5990
  printApprovalResult({ operation, data });
5912
5991
  return;
package/lib/commands.js CHANGED
@@ -2989,6 +2989,7 @@ export function printHelp() {
2989
2989
  lumine doctor runtime-assets
2990
2990
  lumine admin identity list|status|use <zero|ciel|auto> [--json]
2991
2991
  lumine admin identity inspect <user-id|username> --reason <management-reason> [--include-private-evidence] [--json]
2992
+ lumine admin identity network <user-id|username> --reason <management-reason> [--include-private-evidence] [--json] (addresses this account used; who shares the small ones)
2992
2993
  lumine admin economy trace <user-id|username> --reason <management-reason> [--days <1..30>] [--json]
2993
2994
  lumine admin rescue wordle-audit --reason <management-reason> [--days <1..30>] [--json]
2994
2995
  lumine admin ai-bucket create --label <name> --note <text> [--json]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1162,7 +1162,7 @@ lumine admin review propose rewards:70 --dir /private/tmp/reward-review-70 --con
1162
1162
 
1163
1163
  ### Management approvals and the teacher audit (any time; also a full-daily-review duty; added 2026-10-04)
1164
1164
 
1165
- Birthdate, teacher-signup (`mentor`) and meetup-achievement requests, each with its evidence: the account, the request, who invited them, accounts linked by verified email, exact device or a manual identity bucket (with each one's age and role), activity, and flags. These are Mikey-only and need no daily run.
1165
+ Birthdate, teacher-signup (`mentor`) and meetup-achievement requests, each with its evidence: the account, the request, who invited them, accounts linked by verified email, exact device or a manual identity bucket (with each one's age and role), the internet addresses the account acted from (network evidence, below), activity, and flags. These are Mikey-only and need no daily run.
1166
1166
 
1167
1167
  ```bash
1168
1168
  lumine admin approvals list [--status pending|approved|rejected|all] [--type dob|mentor|meetup]
@@ -1170,8 +1170,20 @@ lumine admin approvals show <id>
1170
1170
  lumine admin approvals approve|reject <id> [--reason <text>]
1171
1171
  lumine admin teachers audit [--limit 1-100]
1172
1172
  lumine admin teachers revoke <userId> --reason <text>
1173
+ lumine admin identity network <userId|username> --reason <text> [--include-private-evidence]
1173
1174
  ```
1174
1175
 
1176
+ **Network evidence** (`users_actions` addresses, added 2026-10-04): the account's most recently used addresses (up to 8), and for each one who else acted from it.
1177
+ - An address more than 8 other accounts use is a **shared network** (an academy, a school, a café): it is only counted, never listed, because it links strangers. Teachers and students at the same academy share one.
1178
+ - An address 8 or fewer other accounts use is a **small network** (a home): every account on it is listed with its age, role and account type. The person's own linked accounts (email, device, bucket) are marked `their linked account` and never raise a flag; logged-out visits are not accounts. If the addresses cannot be read (time cap), the pack still answers, with `network_evidence_unavailable`.
1179
+ - Addresses are masked (`58.151.x.59`) with a stable `addressId` for comparing reports; `--include-private-evidence` on `identity network` shows the full address. `identity network` commits an access receipt with your reason before reading anything, like `identity inspect`.
1180
+ - Only each address's newest 5,000 actions and each person's newest 5,000 are read (index-bounded, replica, time-capped), so `older history not read` can appear.
1181
+
1182
+ **Flags.** A linked account counts as a *minor* only with a recorded age under 18, and as a *member* when it holds no teacher/staff role (most accounts record no age, so "member" does not mean student):
1183
+ - `shares_device_with_minor_account` / `shares_device_with_member_account`, the same for `shares_email_…` and `same_identity_bucket_…`;
1184
+ - `shares_small_network_with_minor_account`, `shares_small_network_with_member_account`, `shares_small_network_with_banned_account`;
1185
+ - `linked_account_is_minor`, `own_birthdate_is_minor`, `new_account_with_linked_accounts`, `real_name_differs_from_profile`, `banned_account_in_family`.
1186
+
1175
1187
  - Flags are pointers, not verdicts: a school device can link strangers. Judge each case, recommend, and act only on Mikey's decision.
1176
1188
  - `teachers audit` lists approved teachers, riskiest first.
1177
1189
  - `teachers revoke` removes a teacher status cleanly: