@stage5/lumine 0.3.11 → 0.3.13
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 +93 -5
- package/lib/commands.js +2 -1
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +4 -4
- package/sdk/LUMINE_ADMIN.md +13 -1
package/lib/admin.js
CHANGED
|
@@ -228,7 +228,7 @@ function parseReviewRequestRef(target, { type: fixedType, typeOption } = {}) {
|
|
|
228
228
|
|
|
229
229
|
// Management approvals (dob/mentor/meetup) and the teacher audit.
|
|
230
230
|
const APPROVAL_USAGE =
|
|
231
|
-
"Use: lumine admin approvals list [--status pending|approved|rejected|all] [--type dob|mentor|meetup] | show <id> | approve <id> [--reason <text>] | reject <id> [--reason <text>]";
|
|
231
|
+
"Use: lumine admin approvals list [--status pending|approved|rejected|all] [--type dob|mentor|meetup] | show <id> | approve <id> [--reason <text>] | reject <id> [--reason <text>] | supermod <id> [--reason <text>]";
|
|
232
232
|
const TEACHER_USAGE =
|
|
233
233
|
"Use: lumine admin teachers audit [--limit <1-100>] | revoke <userId> --reason <text>";
|
|
234
234
|
|
|
@@ -267,7 +267,9 @@ function buildApprovalOperation({ action, target, options }) {
|
|
|
267
267
|
requiresRun: false,
|
|
268
268
|
});
|
|
269
269
|
}
|
|
270
|
-
|
|
270
|
+
// supermod: a teacher signup from academy staff becomes a supermod account
|
|
271
|
+
// (the account type staff get) instead of a teacher (Mikey 2026-10-05).
|
|
272
|
+
if (action === "approve" || action === "reject" || action === "supermod") {
|
|
271
273
|
return writeOperation(
|
|
272
274
|
"approvals.decide",
|
|
273
275
|
"POST",
|
|
@@ -288,7 +290,9 @@ function buildTeacherOperation({ action, target, options }) {
|
|
|
288
290
|
? parseRequiredInteger(options.limit, "--limit", 1, 200)
|
|
289
291
|
: "",
|
|
290
292
|
}),
|
|
291
|
-
|
|
293
|
+
// Linked-account networks make large audits slow (100 teachers took
|
|
294
|
+
// over 20 s on 2026-10-05), so this read gets its own timeout.
|
|
295
|
+
{ requiresRun: false, timeoutMs: 120000 },
|
|
292
296
|
);
|
|
293
297
|
}
|
|
294
298
|
if (action === "revoke" || action === "remove") {
|
|
@@ -894,7 +898,12 @@ export async function adminCommand(options) {
|
|
|
894
898
|
...(requestId ? { "x-lumine-idempotency-key": requestId } : {}),
|
|
895
899
|
},
|
|
896
900
|
// Long operations (reward-bank seed) carry their own timeout.
|
|
897
|
-
|
|
901
|
+
// An operation's own timeout is a floor: a longer --timeout still wins.
|
|
902
|
+
timeoutMs:
|
|
903
|
+
Math.max(
|
|
904
|
+
Number(operation.timeoutMs) || 0,
|
|
905
|
+
Number(options.timeoutMs) || 0,
|
|
906
|
+
) || undefined,
|
|
898
907
|
signal,
|
|
899
908
|
});
|
|
900
909
|
if (
|
|
@@ -2021,6 +2030,32 @@ export function parseAdminOperation(options) {
|
|
|
2021
2030
|
}
|
|
2022
2031
|
}
|
|
2023
2032
|
|
|
2033
|
+
if (namespace === "identity" && action === "network") {
|
|
2034
|
+
const networkTarget = String(target || "").trim();
|
|
2035
|
+
const reason = String(options.adminReason || "").trim();
|
|
2036
|
+
if (!networkTarget || !reason) {
|
|
2037
|
+
throw cliValidationError(
|
|
2038
|
+
"Usage: lumine admin identity network <userId|username> --reason <management reason> [--include-private-evidence].",
|
|
2039
|
+
);
|
|
2040
|
+
}
|
|
2041
|
+
if (reason.length > MAX_IDENTITY_INSPECTION_REASON_LENGTH) {
|
|
2042
|
+
throw cliValidationError(
|
|
2043
|
+
`A network-evidence reason must be at most ${MAX_IDENTITY_INSPECTION_REASON_LENGTH} characters.`,
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
return writeOperation(
|
|
2047
|
+
"identity.network",
|
|
2048
|
+
"POST",
|
|
2049
|
+
"/cli/admin/identity/network",
|
|
2050
|
+
{
|
|
2051
|
+
target: networkTarget,
|
|
2052
|
+
reason,
|
|
2053
|
+
includePrivateEvidence: options.adminIncludePrivateEvidence === true,
|
|
2054
|
+
},
|
|
2055
|
+
{ requiresRun: false },
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2024
2059
|
if (namespace === "economy" && action === "trace") {
|
|
2025
2060
|
const traceTarget = String(target || "").trim();
|
|
2026
2061
|
const reason = String(options.adminReason || "").trim();
|
|
@@ -5661,6 +5696,46 @@ const APPROVAL_LABELS = { dob: "birthdate", mentor: "teacher", meetup: "meetup"
|
|
|
5661
5696
|
const approvalDate = (seconds) =>
|
|
5662
5697
|
seconds ? new Date(Number(seconds) * 1000).toISOString().slice(0, 10) : "?";
|
|
5663
5698
|
|
|
5699
|
+
// Network evidence (users_actions addresses): a shared network (academy,
|
|
5700
|
+
// school) is only counted; a small one lists who shares it.
|
|
5701
|
+
function printNetwork(network, indent = "") {
|
|
5702
|
+
const addresses = network?.addresses || [];
|
|
5703
|
+
if (!network) return;
|
|
5704
|
+
if (network.unavailable) {
|
|
5705
|
+
console.log(`${indent}Network: could not be read this time (try again)`);
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
if (!addresses.length) {
|
|
5709
|
+
console.log(`${indent}Network: no recorded addresses`);
|
|
5710
|
+
return;
|
|
5711
|
+
}
|
|
5712
|
+
console.log(`${indent}Network: ${addresses.length} recent address(es)`);
|
|
5713
|
+
for (const a of addresses) {
|
|
5714
|
+
const span = `${approvalDate(a.firstAt)}..${approvalDate(a.lastAt)}`;
|
|
5715
|
+
const older = a.olderHistoryNotRead ? " · older history not read" : "";
|
|
5716
|
+
if (a.kind === "shared_network") {
|
|
5717
|
+
console.log(
|
|
5718
|
+
`${indent} - ${a.address} [${a.addressId}] · shared network: ${a.otherAccountsAtLeast}+ other accounts (not listed) · ${a.actions} actions ${span}${older}`,
|
|
5719
|
+
);
|
|
5720
|
+
continue;
|
|
5721
|
+
}
|
|
5722
|
+
if (a.kind === "only_this_account") {
|
|
5723
|
+
console.log(
|
|
5724
|
+
`${indent} - ${a.address} [${a.addressId}] · only this account · ${a.actions} actions ${span}${older}`,
|
|
5725
|
+
);
|
|
5726
|
+
continue;
|
|
5727
|
+
}
|
|
5728
|
+
console.log(
|
|
5729
|
+
`${indent} - ${a.address} [${a.addressId}] · small network shared with ${a.accounts.length} account(s) · ${a.actions} actions ${span}${older}`,
|
|
5730
|
+
);
|
|
5731
|
+
for (const o of a.accounts) {
|
|
5732
|
+
console.log(
|
|
5733
|
+
`${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)}`,
|
|
5734
|
+
);
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
|
|
5664
5739
|
function printEvidence(evidence) {
|
|
5665
5740
|
if (!evidence) return;
|
|
5666
5741
|
const a = evidence.account || {};
|
|
@@ -5689,6 +5764,7 @@ function printEvidence(evidence) {
|
|
|
5689
5764
|
` - ${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
5765
|
);
|
|
5691
5766
|
}
|
|
5767
|
+
printNetwork(evidence.network);
|
|
5692
5768
|
console.log(
|
|
5693
5769
|
`Flags: ${(evidence.flags || []).length ? evidence.flags.join(", ") : "none"}`,
|
|
5694
5770
|
);
|
|
@@ -5716,7 +5792,7 @@ function printApprovalResult({ operation, data }) {
|
|
|
5716
5792
|
}
|
|
5717
5793
|
if (operation.name === "approvals.decide") {
|
|
5718
5794
|
console.log(
|
|
5719
|
-
`${data.ref} ${data.status}: ${APPROVAL_LABELS[data.type] || data.type} request from ${data.requesterUsername} (#${data.requesterId}).`,
|
|
5795
|
+
`${data.ref} ${data.status}${data.grantedAs ? ` as ${data.grantedAs}` : ""}: ${APPROVAL_LABELS[data.type] || data.type} request from ${data.requesterUsername} (#${data.requesterId}).`,
|
|
5720
5796
|
);
|
|
5721
5797
|
return;
|
|
5722
5798
|
}
|
|
@@ -5907,6 +5983,18 @@ function printAdminResult({ operation, result }) {
|
|
|
5907
5983
|
}
|
|
5908
5984
|
return;
|
|
5909
5985
|
}
|
|
5986
|
+
if (operation.name === "identity.network") {
|
|
5987
|
+
const account = data.account || {};
|
|
5988
|
+
console.log(`${account.username} (#${account.userId})`);
|
|
5989
|
+
printNetwork(data.network);
|
|
5990
|
+
console.log(
|
|
5991
|
+
`Flags: ${(data.network?.flags || []).length ? data.network.flags.join(", ") : "none"}`,
|
|
5992
|
+
);
|
|
5993
|
+
console.log(
|
|
5994
|
+
"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.",
|
|
5995
|
+
);
|
|
5996
|
+
return;
|
|
5997
|
+
}
|
|
5910
5998
|
if (operation.name.startsWith("approvals.") || operation.name.startsWith("teachers.")) {
|
|
5911
5999
|
printApprovalResult({ operation, data });
|
|
5912
6000
|
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]
|
|
@@ -3013,7 +3014,7 @@ export function printHelp() {
|
|
|
3013
3014
|
lumine admin sponsor integrity get <case-id> [--json]
|
|
3014
3015
|
lumine admin sponsor integrity review <case-id> --decision clear|hold|flag|disqualify [--note <evidence>] [--json]
|
|
3015
3016
|
lumine admin review list [--type rewards|project|storage|cardcraft] [--status queue|pending|approved|rejected|all] [--cursor <c>] | show <type:id> [--dir <path>] | approve <type:id> [--config <rules.json>] [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject|revoke <type:id> [--reason <text>] | propose rewards:<id> --dir <edited-snapshot> --config <rules.json>
|
|
3016
|
-
lumine admin approvals list [--status pending|approved|rejected|all] [--type dob|mentor|meetup] | show <id> | approve <id> [--reason <text>] | reject <id> [--reason <text>] (management approvals with evidence: birthdates, teacher signups, meetups)
|
|
3017
|
+
lumine admin approvals list [--status pending|approved|rejected|all] [--type dob|mentor|meetup] | show <id> | approve <id> [--reason <text>] | reject <id> [--reason <text>] | supermod <id> [--reason <text>] (management approvals with evidence: birthdates, teacher signups, meetups)
|
|
3017
3018
|
lumine admin teachers audit [--limit <1-100>] | revoke <userId> --reason <text> (approved teachers, riskiest first; revoke removes the status cleanly)
|
|
3018
3019
|
lumine admin reward-review list [--status pending|approved|all] [--cursor <id>] [--json]
|
|
3019
3020
|
lumine admin cardcraft-review list [--status pending|approved|all] [--cursor <id>] | show <id> | approve|reject|revoke <id> [--reason <text>]
|
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Version: 1.63.0
|
|
4
4
|
Updated: 2026-09-28
|
|
5
|
-
Generated: 2026-10-
|
|
5
|
+
Generated: 2026-10-05T14:12:15.800Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -36,7 +36,7 @@ Generated: 2026-10-04T06:59:28.249Z
|
|
|
36
36
|
- The creator's agent designs the rewards. Declare the economy in a project file `rewards.json` at the root: budgets (userDailyXP, userDailyCoins, optional userDailyClaims; there is no app-wide daily or lifetime budget, only what one learner can earn per day) and rules [{ id, title, howTo?, category? (section header, up to 40 characters), series? (key for repeat rules that share category and amounts), xp, coins, verifier: 'numeric-quiz' | 'completion' | 'generated-quiz', maxAttempts?, retry?: { xpPercent, coinsPercent, paidAttempts? }, minSeconds? (completion), progression?: 'dated' | 'until-earned' (quiz), bank? (generated-quiz): { steps: [percent, ...] rising to 100, startStep?, instructions, topics: [{ key, title, description }] } }]. generated-quiz rules need no question sheet. Wire the matching Twinkle.rewards calls with those literal rule ids. Questions and answer keys NEVER go in project files (published source is readable by every player): quiz rules get them from the private question sheet uploaded with `lumine rewards sheet <file.json>` ({ rules: { <ruleId>: { questions?, sets? } } }); `lumine rewards check` validates both together. A review request freezes the code and proposes rewards.json merged with the sheet; the administrator reads the code, checks the amounts and whether the app is exploitable, may change any amount, and approves. Creators are kids and teens: show approval status and one Send for review action; do not ask them to fill in technical forms. Every code update that retains rewards needs a new approval before publishing. Removing the SDK automatically clears its gate. Apps read amounts, tries and sets from getStatus, never from their own file.
|
|
37
37
|
- Verifiers: 'numeric-quiz' pays for server-checked numeric answers (retry share, attempt limits, dated sets or until-earned sets that stay up until somebody earns them, after-answer guides). 'completion' pays when the app reports an activity finished — a cleared stage, a finished round — at least minSeconds after start({ ruleId }); the server checks only the elapsed time, once per learner per site day (UTC midnight), and the budgets. Call start when the activity begins and claim({ challengeId }) with no answers when it ends; keep completion amounts and userDailyXP small enough that a player scripting the calls would not matter, because nothing else is verified. 'generated-quiz' pays from a question bank the server fills itself: for one declared topic at the learner's step of the ladder, the server writes a question with GPT-6.1 Sol (billed to that learner's AI Energy), solves it again independently and keeps it only when both answers agree; every learner who has not seen a question gets it before anything new is written. `xp`/`coins` are the top step's amounts and each step pays its `steps` percent of them (then the usual retry share). Two first-try answers in a row climb a step; two misses in a row step down. Learners may earn many bank questions a day, bounded by userDailyXP/userDailyCoins. The reviewer approves the ladder, amounts, topics and instructions, not each question; learner reports and solve rates hide bad questions. Use the rewards bank methods (getBankStatus, startBankQuestion, generateBankQuestion, claimBankQuestion, reportBankQuestion); start and claim refuse bank rules. 'archive-rematch' is the daily rematch: one retired until-earned puzzle of the app a day, chosen by the server (celebrated, high-rated solves first; never repeated within 60 days) and answered from its frozen approved sheet. Declare at most one, with no questions, sets, progression, series, minSeconds or completionProof, e.g. { "id": "rematch-daily", "title": "Daily rematch", "xp": 20000, "coins": 1000, "verifier": "archive-rematch", "retry": { "xpPercent": 50, "coinsPercent": 0, "paidAttempts": 3 }, "maxAttempts": null }. Only players who never met that puzzle (no official receipt, wrong answer or challenge while it was live, no Vault practice) may answer it; its receipt does not use userDailyClaims but counts toward userDailyXP/userDailyCoins. Use getRematch and claimRematch; start and claim refuse it.
|
|
38
38
|
- Numeric quiz answers are verified on the server; client scores, privateDb state, timers and completion booleans are not verified reward evidence. Limits reset at UTC midnight. Rules are earned once per viewer per UTC day; attempt limits and retry payouts come from the approved rule. Challenges expire at the UTC day boundary. Budgets apply across release changes.
|
|
39
|
-
- Optional reward rule controls: maxLifetimeClaims caps one learner’s receipts for that rule across every day and release; completionProof: classic-tower-v1 requires a server-simulated Classic Tower finish in addition to minSeconds. These are server-enforced controls. Existing completion rules without completionProof still verify elapsed time only. Registered proof profiles also include breadface-v1, breadface-v2 and breadface-v3 (server-simulated Breadface inputs; each is one reviewed release's exact physics, chosen by the reviewer) study-record-v1 (a private study record reviewed by JEV, billed to the learner’s AI Energy), and Groove Lab's groove-lab-song-v1 (a song the learner published today, checked on the server for length, notes and originality) and groove-lab-heard-v1 (three established accounts finished the learner's songs today), Ashen Vigil's vigil-guest-coplay-v1 (a new guest spent 10 minutes with the learner in a private world room, measured by the world relay; each guest pays once) and minecraft-first-link-v1 (the learner is the first Twinkle account ever linked to that Minecraft player; each player pays once). Profiles are platform-owned; an app cannot invent a verifier or authorize its own reward.
|
|
39
|
+
- Optional reward rule controls: maxLifetimeClaims caps one learner’s receipts for that rule across every day and release; completionProof: classic-tower-v1 requires a server-simulated Classic Tower finish in addition to minSeconds. These are server-enforced controls. Existing completion rules without completionProof still verify elapsed time only. Registered proof profiles also include breadface-v1, breadface-v2 and breadface-v3 (server-simulated Breadface inputs; each is one reviewed release's exact physics, chosen by the reviewer) study-record-v1 (a private study record reviewed by JEV, billed to the learner’s AI Energy), and Groove Lab's groove-lab-song-v1 (a song the learner published today, checked on the server for length, notes and originality) and groove-lab-heard-v1 (three established accounts finished the learner's songs today), Ashen Vigil's vigil-guest-coplay-v1 (a new guest spent 10 minutes with the learner in a private world room, measured by the world relay; each guest pays once) and minecraft-first-link-v1 (the learner is the first Twinkle account ever linked to that Minecraft player; each player pays once), and writing-words-v1 with minWords (counted words the learner wrote today as chapters in their own books registered in the app's shared books topic; each chapter is read once by a small model for genuine writing and language, English words count fully and other languages half, and the same text counts once), reading-quiz-v1 (the learner passed a server-graded three-question quiz on a chapter of someone else's registered book after its reading time; each chapter pays a reader once, one attempt per chapter per day) and reading-readers-v1 with minReaders (that many established accounts passed a quiz on the learner's chapters today). Profiles are platform-owned; an app cannot invent a verifier or authorize its own reward.
|
|
40
40
|
|
|
41
41
|
## AI decision design
|
|
42
42
|
- Twinkle.ai.decide runs JEV, a model for narrow decisions over supplied text or structured state, and it is free for viewers: decisions are covered by Twinkle and never use their AI Energy. Whenever an app makes a judgment that ordinary code can only approximate, consider JEV first. Proven patterns: an opponent or boss coach that reads the player's habits (favourite lane, repeated trick, cheese move) and picks a counter-plan from a fixed list, as in Red Square's boss and Open Football's opponent coach; NPCs that understand what a player types and choose an action; classifying or scoring user submissions; ranking supplied candidates; and picking the next exercise or difficulty from evidence about the learner. Any app with an AI opponent, NPC, or adaptive difficulty should decide explicitly whether JEV improves it. These are examples to reason from, not a keyword checklist or a requirement to add AI to every app.
|
|
@@ -1496,7 +1496,7 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1496
1496
|
- Errors: build_reward_not_scheduled when the rule has no questions for today; build_reward_daily_claims_reached when the viewer already earned today’s cap. attemptsRemaining is null for unlimited rules.
|
|
1497
1497
|
- For a completion rule call start when the activity begins (the moment the stage starts); the challenge's age is what the claim is measured against. In preview mode start also works for the owner (a stateless simulation).
|
|
1498
1498
|
- For completionProof: classic-tower-v1, start also returns completion { profile, token, maxFrames, completed, failed }. A new start resets only the simulated climb to its canonical spawn; it cannot reset daily or lifetime rewards. Record inputs from the first physics frame. The completion token is bound to the viewer, challenge, rule and published release.
|
|
1499
|
-
- For breadface-v1, breadface-v2 and breadface-v3, pass the zero-based canonical levelIndex. Record [dt, inputBits] from the first physics frame; start returns its server token and maxFrames. For study-record-v1, start returns completion { profile, usesAiEnergy: true }; there is no client-authored proof token. For groove-lab-song-v1, groove-lab-heard-v1, vigil-guest-coplay-v1
|
|
1499
|
+
- For breadface-v1, breadface-v2 and breadface-v3, pass the zero-based canonical levelIndex. Record [dt, inputBits] from the first physics frame; start returns its server token and maxFrames. For study-record-v1, start returns completion { profile, usesAiEnergy: true }; there is no client-authored proof token. For groove-lab-song-v1, groove-lab-heard-v1, vigil-guest-coplay-v1, minecraft-first-link-v1 and writing-words-v1, start returns completion { profile }; there is no progress step. For reading-quiz-v1, call progress({ challengeId, completionToken: <chapter commentId>, record: 'open' }) when the reader opens the chapter (returns readyInSeconds, readingSeconds, alreadyEarned, attemptedToday), then progress({ ..., record: 'quiz' }) once readyInSeconds has passed to get questions [{ question, options[4] }] (never answers). getStatus returns reading { chaptersEarnedToday, readersToday } when the app uses a reading proof. For writing-words-v1, getStatus also returns writing { countedWords, checkingChapters, chapters: [{ commentId, words, countedWords, status: counted|not-counted|checking|duplicate|too-short, english, reason }] } for today.
|
|
1500
1500
|
- await Twinkle.rewards.progress({ challengeId, completionToken?, frames?, record?, requestId? }) | scopes: rewards:claim
|
|
1501
1501
|
- Returns: { mode: "live" | "preview", completion: { profile, token?, completed, failed?, decision?, message?, maxFrames? }, aiUsagePolicy? }
|
|
1502
1502
|
- Verify a bounded batch of inputs for an approved server-simulated climb.
|
|
@@ -1512,7 +1512,7 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1512
1512
|
- A wrong answer within two seconds of the previous one is refused with build_reward_throttled (HTTP 429) and does not count; wait for the person to try again rather than retry-looping.
|
|
1513
1513
|
- Completion rules take no answers: claim({ challengeId }) when the activity is finished. build_reward_too_fast (HTTP 409) means fewer than minSeconds passed since start; show nothing and let play continue. In preview mode the receipt carries preview: true and nothing is paid.
|
|
1514
1514
|
- A completionProof rule also requires the signed completionToken from a successful rewards.progress response. The server simulates the registered game physics and must reach the goal. A timer, forged position, client win flag, altered inventory or token from another viewer, challenge or release cannot authorize payment. maxLifetimeClaims is enforced from receipts in the same award transaction. build_reward_lifetime_claims_reached means all rewards for this rule have been collected; do not retry it.
|
|
1515
|
-
- For study-record-v1, the server verifies the settled private review row and its viewer, app, exact release, challenge and day binding. It never trusts the client’s decision, requested award amount or AI answer. Each successful daily study record is claimable once; display only canonical receipt/balances. For groove-lab-song-v1, pass completionToken as the published song's sharedDb entry id; for groove-lab-heard-v1, pass none; for vigil-guest-coplay-v1, pass the guest's world guestId (from world events) or the co-play record id; for minecraft-first-link-v1, pass nothing or the Minecraft UUID to use. The server reads the app's own groove-lab-songs, groove-lab-song-parts and groove-lab-listens rows and refuses with build_reward_completion_proof_required and a plain reason when the song or listeners do not qualify.
|
|
1515
|
+
- For study-record-v1, the server verifies the settled private review row and its viewer, app, exact release, challenge and day binding. It never trusts the client’s decision, requested award amount or AI answer. Each successful daily study record is claimable once; display only canonical receipt/balances. For groove-lab-song-v1, pass completionToken as the published song's sharedDb entry id; for groove-lab-heard-v1, pass none; for vigil-guest-coplay-v1, pass the guest's world guestId (from world events) or the co-play record id; for minecraft-first-link-v1, pass nothing or the Minecraft UUID to use; for reading-quiz-v1, pass completionToken as the chapter commentId and answers as the three chosen option indexes (all three must be right; a wrong attempt cannot be retried that day); for reading-readers-v1, pass nothing; for writing-words-v1, pass nothing (the server counts today's chapters itself and refuses with a plain reason, e.g. "320 of 500 counted words today" or "still being checked"). The server reads the app's own groove-lab-songs, groove-lab-song-parts and groove-lab-listens rows and refuses with build_reward_completion_proof_required and a plain reason when the song or listeners do not qualify.
|
|
1516
1516
|
- await Twinkle.rewards.getLeaderboard({ metric?: "xp" | "coins", period?: "day" | "week" | "all", limit? }) | scopes: rewards:claim
|
|
1517
1517
|
- Returns: { mode: "live", metric, period, limit, dayKey, from, available: { xp, coins }, entries: [{ rank, userId, username, profilePicUrl, xp, coins, claims, lastAt }], me: { rank, xp, coins, claims } | null } | { mode: "preview", metric, period, available, entries: [], me: null, message }
|
|
1518
1518
|
- Standings of who earned the most XP or Coins in THIS app, computed by Twinkle from its own receipts (never from anything the app submits). period 'day' is today (site day, UTC), 'week' the last 7 site days, 'all' (default) every day since approval. limit defaults to 20, max 100.
|
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -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:
|