@stage5/lumine 0.2.76 → 0.2.79
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/README.md +4 -0
- package/lib/admin.js +404 -87
- package/lib/commands.js +24 -4
- package/lib/constants.js +13 -4
- package/lib/rewards.js +46 -13
- package/lib/sdk.js +10 -2
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +17 -11
- package/sdk/LUMINE_ADMIN.md +81 -21
package/lib/commands.js
CHANGED
|
@@ -1287,7 +1287,18 @@ export async function launch(options) {
|
|
|
1287
1287
|
|
|
1288
1288
|
export function printCheck(result) {
|
|
1289
1289
|
const checks = result.checks || {};
|
|
1290
|
-
console.log(`
|
|
1290
|
+
console.log(`Saved project validation: ${result.ok ? "ok" : "fail"}`);
|
|
1291
|
+
console.log(
|
|
1292
|
+
`Publishing readiness: ${result.launchOk === true ? "ready" : "blocked"}`,
|
|
1293
|
+
);
|
|
1294
|
+
if (checks.canonicalBuild?.ok === false) {
|
|
1295
|
+
console.log(
|
|
1296
|
+
"This is a contribution branch. Validate local changes, save, then suggest the branch. The main app owner merges and publishes it.",
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
console.log(
|
|
1300
|
+
"Saved checks use the server version; local workspace changes must be saved before these results describe them.",
|
|
1301
|
+
);
|
|
1291
1302
|
if (checks.canonicalBuild) {
|
|
1292
1303
|
console.log(
|
|
1293
1304
|
`- canonical build: ${checks.canonicalBuild.ok ? "ok" : "fail"}`,
|
|
@@ -1306,6 +1317,13 @@ export function printCheck(result) {
|
|
|
1306
1317
|
console.log(` ${checks.publishPermission.reason}`);
|
|
1307
1318
|
}
|
|
1308
1319
|
}
|
|
1320
|
+
if (checks.rewardApproval) {
|
|
1321
|
+
console.log(
|
|
1322
|
+
`- XP/Coins approval: ${checks.rewardApproval.ok ? "ok" : "blocked"}`,
|
|
1323
|
+
);
|
|
1324
|
+
if (checks.rewardApproval.reason)
|
|
1325
|
+
console.log(` ${checks.rewardApproval.reason}`);
|
|
1326
|
+
}
|
|
1309
1327
|
if (typeof result.launchOk === "boolean") {
|
|
1310
1328
|
console.log(`- launch gate: ${result.launchOk ? "ok" : "fail"}`);
|
|
1311
1329
|
}
|
|
@@ -2882,9 +2900,10 @@ export function printHelp() {
|
|
|
2882
2900
|
lumine admin sponsor integrity get <case-id> [--json]
|
|
2883
2901
|
lumine admin sponsor integrity review <case-id> --decision clear|hold|flag|disqualify [--note <evidence>] [--json]
|
|
2884
2902
|
lumine admin reward-review list [--status pending|approved|all] [--cursor <id>] [--json]
|
|
2885
|
-
lumine admin reward-activity [--days <1..31>] [--build <id>] [--json]
|
|
2903
|
+
lumine admin reward-activity [--date YYYY-MM-DD] [--days <1..31>] [--build <id>] [--json]
|
|
2886
2904
|
lumine admin reward-review show <review-id> [--dir <path>] [--json]
|
|
2887
|
-
lumine admin reward-review approve <review-id> [--config <rules.json>] [--reason <text>] [--json]
|
|
2905
|
+
lumine admin reward-review approve <review-id> [--config <rules.json>] [--reason <text>] [--json] (approval publishes the approved version)
|
|
2906
|
+
lumine admin reward-review propose <review-id> --dir <edited-snapshot> --config <rules.json> [--reason <text>] [--json]
|
|
2888
2907
|
lumine admin reward-review reject|revoke <review-id> --reason <text> [--json]
|
|
2889
2908
|
lumine admin recommendations list [--since-run|--after <date>|--include-legacy] [--all --checkpoint <file> [--resume]] [--content-types comment,dailyReflection] [--unviewed|--viewed] [--cursor <cursor>] [--json]
|
|
2890
2909
|
lumine admin builds candidates [--since-run|--after <date>|--include-legacy] [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--limit <number>] [--json]
|
|
@@ -2936,6 +2955,7 @@ export function printHelp() {
|
|
|
2936
2955
|
lumine admin news submit --claim <claim.json> --file <editorial.json> [--model <name>] [--json]
|
|
2937
2956
|
lumine admin notable status <user-id|username> [--json]
|
|
2938
2957
|
lumine admin notable add <user-id|username> --note <text> [--json]
|
|
2958
|
+
lumine admin notable remove <user-id|username> --note <text> [--json]
|
|
2939
2959
|
lumine admin audit [list] [--run current|last|<run-id>] [--target <target>] [--actions <a,b>] [--full] [--all --checkpoint <file> [--resume]] [--cursor <cursor>] [--json]
|
|
2940
2960
|
|
|
2941
2961
|
Examples:
|
|
@@ -2996,7 +3016,7 @@ Options:
|
|
|
2996
3016
|
--preview-url <url> Twinkle Build preview origin
|
|
2997
3017
|
--auth-file <path> Saved login path
|
|
2998
3018
|
--auth-token <token> Override saved login
|
|
2999
|
-
--dir <path> Directory for pulled project files or a reward-review
|
|
3019
|
+
--dir <path> Directory for pulled project files, a reward-review source snapshot, or the edited snapshot a reward-review proposal sends
|
|
3000
3020
|
--config <file> Replacement earning rules JSON for reward-review approve (default: the app's own proposal)
|
|
3001
3021
|
--provider <agent> Subscription agent for lumine agent: codex or claude-code
|
|
3002
3022
|
--provider-path <p> Override the selected agent CLI executable
|
package/lib/constants.js
CHANGED
|
@@ -18,13 +18,19 @@ export const THUMBNAIL_CAPTURE_TIMEOUT_MS = 90 * 1000;
|
|
|
18
18
|
export const GENERATE_MODEL_ALIASES = {
|
|
19
19
|
"gpt-image-2.5-flare": "gpt-image-2.5-flare",
|
|
20
20
|
"gpt-image-2.5-sunburst": "gpt-image-2.5-sunburst",
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
flare: "gpt-image-2.5-flare",
|
|
22
|
+
sunburst: "gpt-image-2.5-sunburst",
|
|
23
23
|
"gpt-image-2": "gpt-image-2",
|
|
24
24
|
"nano-banana": "gemini-3-pro-image-preview",
|
|
25
25
|
"gemini-3-pro-image-preview": "gemini-3-pro-image-preview",
|
|
26
26
|
};
|
|
27
|
-
export const GENERATE_QUALITIES = new Set([
|
|
27
|
+
export const GENERATE_QUALITIES = new Set([
|
|
28
|
+
"low",
|
|
29
|
+
"medium",
|
|
30
|
+
"high",
|
|
31
|
+
"xhigh",
|
|
32
|
+
"max",
|
|
33
|
+
]);
|
|
28
34
|
// Server accepts only these thumbnail content types (8MB max).
|
|
29
35
|
export const THUMBNAIL_CONTENT_TYPE_BY_EXTENSION = {
|
|
30
36
|
".jpg": "image/jpeg",
|
|
@@ -119,6 +125,8 @@ export const BUNDLED_SDK_REFERENCE_URL = new URL(
|
|
|
119
125
|
import.meta.url,
|
|
120
126
|
);
|
|
121
127
|
export const PACKAGE_METADATA_URL = new URL("../package.json", import.meta.url);
|
|
128
|
+
export const LUMINE_MOBILE_SELECTION_GUIDANCE = `- Mobile long-press must not select game UI or open the browser's Copy/Look Up/image menu. The SDK protects standard buttons, button-like ARIA controls, and canvases. Mark the entire gameplay wrapper data-twinkle-no-select (including HUD, labels, scores, menus, controls, and empty play space); also apply user-select: none, -webkit-user-select: none, and -webkit-touch-callout: none for local previews. Protecting only one button or the canvas is insufficient.
|
|
129
|
+
- Keep inputs/contenteditable usable. Mark genuinely copyable story/chat/user-written text data-twinkle-selectable and style it with user-select: text, -webkit-user-select: text, and -webkit-touch-callout: default. Do not blanket-disable selection on document/readers or preventDefault on document-wide touch/pointer events. Test holding controls, the HUD, and empty play space in mobile Safari and Chromium, then verify release/cancel, scrolling, typing, and copying. lumine check only detects a missing no-selection rule; a pass does not prove selector coverage or mobile behavior.`;
|
|
122
130
|
export const LUMINE_WORLD_UPDATE_GUIDANCE = `- For Twinkle.world realtime presence, keep render/input loops local. Queue an update only when relevant state changes, replace any queued snapshot with the newest one, and flush on a fixed 5-15 updates-per-second schedule with at most one updatePresence request in flight. Never call or await updatePresence every animation frame, resend unchanged snapshots, overlap requests, or build a backlog.
|
|
123
131
|
- Send Twinkle.world actions only when the discrete action happens; do not poll or automatically retry them. On WORLD_EVENT_RATE_LIMITED or another recoverable non-session-ended error, drop that attempted presence/action update without an immediate retry and keep the session. Reconnect with backoff only after session.ended or Twinkle.world.isSessionEndedError(error).`;
|
|
124
132
|
export const SDK_REFERENCE_FALLBACK = `${LUMINE_SDK_REFERENCE_MARKER}
|
|
@@ -137,6 +145,7 @@ Use these current source-of-truth rules:
|
|
|
137
145
|
- Use Twinkle.aiStories.list/search/get for existing AI Story passage text, story media, and questions.
|
|
138
146
|
- Use Twinkle.ai.chat with history entries shaped as { role, content }, not { text }. Live web search is enabled by default; pass webSearch: false to disable it for the app.
|
|
139
147
|
- Use Twinkle.preview for canvas, WebGL, Three.js, fullscreen, and game layout.
|
|
148
|
+
${LUMINE_MOBILE_SELECTION_GUIDANCE}
|
|
140
149
|
${LUMINE_WORLD_UPDATE_GUIDANCE}
|
|
141
150
|
- Prefer existing documented Twinkle.* methods over guessing names from old code.
|
|
142
151
|
`;
|
|
@@ -307,7 +316,7 @@ lumine save --summary "Describe the change"
|
|
|
307
316
|
|
|
308
317
|
- Use local project files with relative or root-local imports only. Do not add package imports, CDN scripts, external network calls, or app-local /api/* routes.
|
|
309
318
|
- Build apps run in sandboxed iframes without allow-forms. Do not use <form> elements, native form submission, requestSubmit(), or browser form navigation. Build input flows with JavaScript-handled inputs and buttons instead.
|
|
310
|
-
|
|
319
|
+
${LUMINE_MOBILE_SELECTION_GUIDANCE}
|
|
311
320
|
- CAUTION: the preview runtime AUTO-DETECTS "game apps" — any <canvas> in the body (even a decorative background canvas) or game-y words in visible text switch the app to viewport-app mode: html/body get overflow:hidden !important and body becomes a centering flexbox, so tall document-flow pages clip and stop scrolling. Document-style apps that use a canvas must call Twinkle.preview.subscribe (or getLayout/reserveInsets) early at boot — any of those opts out of auto game mode — then pad by layout.safeInsets and scroll within layout.viewport.height.
|
|
312
321
|
- For canvas, WebGL, Three.js, fullscreen, or game builds, use Twinkle.preview for layout. Do not size roots from 100vh, 100vw, 100dvh, 100dvw, window.innerWidth, window.innerHeight, visualViewport, or document viewport dimensions.
|
|
313
322
|
${LUMINE_THREE_VENDOR_GUIDANCE}
|
package/lib/rewards.js
CHANGED
|
@@ -34,11 +34,19 @@ async function readWorkspaceRewardsJson(options) {
|
|
|
34
34
|
try {
|
|
35
35
|
return { present: true, value: JSON.parse(raw), filePath };
|
|
36
36
|
} catch (error) {
|
|
37
|
-
throw new Error(
|
|
37
|
+
throw new Error(
|
|
38
|
+
`${REWARDS_FILE} is not valid JSON: ${error?.message || error}`,
|
|
39
|
+
);
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
async function checkDeclaration({
|
|
43
|
+
async function checkDeclaration({
|
|
44
|
+
options,
|
|
45
|
+
auth,
|
|
46
|
+
buildId,
|
|
47
|
+
rewardsJson,
|
|
48
|
+
sheet,
|
|
49
|
+
}) {
|
|
42
50
|
return await requestJson({
|
|
43
51
|
url: `${options.apiUrl}/cli/build/${buildId}/rewards/check`,
|
|
44
52
|
method: "POST",
|
|
@@ -62,12 +70,16 @@ function printDeclaration(result, { prefix = "" } = {}) {
|
|
|
62
70
|
rule.verifier === "completion"
|
|
63
71
|
? `completion · at least ${rule.minSeconds || 0}s`
|
|
64
72
|
: `quiz · ${rule.questionSets} set(s)${rule.progression ? ` · ${rule.progression}` : ""}${rule.standingQuestions ? ` · ${rule.standingQuestions} standing` : ""}`;
|
|
65
|
-
console.log(
|
|
73
|
+
console.log(
|
|
74
|
+
`${prefix} ${rule.id}: ${rule.title} · ${rule.xp} XP + ${rule.coins} Coins · ${what}`,
|
|
75
|
+
);
|
|
66
76
|
}
|
|
77
|
+
if (result.nextStep) console.log(`${prefix}${result.nextStep}`);
|
|
67
78
|
return;
|
|
68
79
|
}
|
|
69
80
|
console.log(`${prefix}Rewards declaration: NOT ready.`);
|
|
70
|
-
for (const error of result?.errors || [])
|
|
81
|
+
for (const error of result?.errors || [])
|
|
82
|
+
console.log(`${prefix} - ${error}`);
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
// Part of `lumine check`: only speaks up when the workspace declares rewards
|
|
@@ -92,8 +104,15 @@ export async function reportRewardDeclaration({ options, auth, buildId }) {
|
|
|
92
104
|
printDeclaration(result, { prefix: "Local check: " });
|
|
93
105
|
if (!result.ok) process.exitCode = 1;
|
|
94
106
|
} catch (error) {
|
|
95
|
-
const reason = String(error?.message || error)
|
|
96
|
-
|
|
107
|
+
const reason = String(error?.message || error)
|
|
108
|
+
.replace(/<[^>]+>/g, " ")
|
|
109
|
+
.replace(/\s+/g, " ")
|
|
110
|
+
.trim()
|
|
111
|
+
.slice(0, 140);
|
|
112
|
+
console.error(
|
|
113
|
+
`Local check error: rewards declaration not verified (${reason}).`,
|
|
114
|
+
);
|
|
115
|
+
process.exitCode = 1;
|
|
97
116
|
}
|
|
98
117
|
}
|
|
99
118
|
|
|
@@ -114,7 +133,13 @@ export async function rewardsCommand(options) {
|
|
|
114
133
|
rewardsJson: local.present ? local.value : undefined,
|
|
115
134
|
});
|
|
116
135
|
if (options.json) {
|
|
117
|
-
console.log(
|
|
136
|
+
console.log(
|
|
137
|
+
JSON.stringify(
|
|
138
|
+
{ ...result, source: local.present ? "workspace" : "saved" },
|
|
139
|
+
null,
|
|
140
|
+
2,
|
|
141
|
+
),
|
|
142
|
+
);
|
|
118
143
|
} else {
|
|
119
144
|
console.log(
|
|
120
145
|
local.present
|
|
@@ -134,20 +159,26 @@ export async function rewardsCommand(options) {
|
|
|
134
159
|
timeoutMs: options.timeoutMs,
|
|
135
160
|
});
|
|
136
161
|
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
137
|
-
else if (!result.sheet)
|
|
162
|
+
else if (!result.sheet)
|
|
163
|
+
console.log("No question sheet on file for this app.");
|
|
138
164
|
else {
|
|
139
165
|
const rules = Object.entries(result.sheet.rules || {});
|
|
140
166
|
console.log(`Question sheet on file: ${rules.length} rule(s).`);
|
|
141
167
|
for (const [id, entry] of rules) {
|
|
142
168
|
const sets = Array.isArray(entry.sets) ? entry.sets.length : 0;
|
|
143
|
-
const standing = Array.isArray(entry.questions)
|
|
144
|
-
|
|
169
|
+
const standing = Array.isArray(entry.questions)
|
|
170
|
+
? entry.questions.length
|
|
171
|
+
: 0;
|
|
172
|
+
console.log(
|
|
173
|
+
` ${id}: ${sets} set(s), ${standing} standing question(s)`,
|
|
174
|
+
);
|
|
145
175
|
}
|
|
146
176
|
}
|
|
147
177
|
return;
|
|
148
178
|
}
|
|
149
179
|
const file = options.positional?.[1];
|
|
150
|
-
if (!file)
|
|
180
|
+
if (!file)
|
|
181
|
+
throw new Error("Usage: lumine rewards sheet <file.json> | --show");
|
|
151
182
|
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
152
183
|
let sheet;
|
|
153
184
|
try {
|
|
@@ -164,7 +195,9 @@ export async function rewardsCommand(options) {
|
|
|
164
195
|
});
|
|
165
196
|
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
166
197
|
else {
|
|
167
|
-
console.log(
|
|
198
|
+
console.log(
|
|
199
|
+
`Question sheet uploaded for Build ${buildId}. It is kept off the project files and merged with ${REWARDS_FILE} when you send the version for review.`,
|
|
200
|
+
);
|
|
168
201
|
printDeclaration(result);
|
|
169
202
|
}
|
|
170
203
|
if (!result.ok) process.exitCode = 1;
|
|
@@ -183,6 +216,6 @@ function printRewardsHelp() {
|
|
|
183
216
|
rewards.json (project root) declares the economy the reviewer approves:
|
|
184
217
|
{ "dailyXP", "dailyCoins", "userDailyXP", "userDailyCoins", "lifetimeXP", "lifetimeCoins", "userDailyClaims"?,
|
|
185
218
|
"rules": [{ "id", "title", "xp", "coins", "verifier": "numeric-quiz" | "completion",
|
|
186
|
-
"maxAttempts"?, "retry"?: { "xpPercent", "coinsPercent" }, "minSeconds"? (completion), "progression"?: "dated" | "until-earned" (quiz) }] }
|
|
219
|
+
"maxAttempts"?, "retry"?: { "xpPercent", "coinsPercent", "paidAttempts"? }, "minSeconds"? (completion), "progression"?: "dated" | "until-earned" (quiz) }] }
|
|
187
220
|
Questions and answer keys never go in project files; they belong in the sheet.`);
|
|
188
221
|
}
|
package/lib/sdk.js
CHANGED
|
@@ -162,8 +162,8 @@ export const SDK_CLI_METHODS = {
|
|
|
162
162
|
// token PLUS the server-issued published-runtime grant (fetched from the
|
|
163
163
|
// canonical GET /build/:id/runtime payload, never minted locally). The CLI
|
|
164
164
|
// holds no award logic; the server checks approval, version and budget.
|
|
165
|
-
//
|
|
166
|
-
//
|
|
165
|
+
// Status and receipt reads use the endpoint's rewards:claim scope, but
|
|
166
|
+
// only their fixed read operation is sent.
|
|
167
167
|
"rewards.getStatus": {
|
|
168
168
|
path: "api/rewards/status",
|
|
169
169
|
special: "rewards",
|
|
@@ -172,6 +172,14 @@ export const SDK_CLI_METHODS = {
|
|
|
172
172
|
readOnly: true,
|
|
173
173
|
mapArgs: () => ({}),
|
|
174
174
|
},
|
|
175
|
+
"rewards.getReceipt": {
|
|
176
|
+
path: "api/rewards/receipt",
|
|
177
|
+
special: "rewards",
|
|
178
|
+
operation: "receipt",
|
|
179
|
+
scopes: ["rewards:claim"],
|
|
180
|
+
readOnly: true,
|
|
181
|
+
mapArgs: (args) => ({ challengeId: args.challengeId }),
|
|
182
|
+
},
|
|
175
183
|
"rewards.start": {
|
|
176
184
|
path: "api/rewards/start",
|
|
177
185
|
special: "rewards",
|
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Build SDK Index
|
|
2
2
|
|
|
3
|
-
Version: 1.
|
|
4
|
-
Updated: 2026-09-
|
|
5
|
-
Generated: 2026-09-
|
|
3
|
+
Version: 1.45.0
|
|
4
|
+
Updated: 2026-09-14
|
|
5
|
+
Generated: 2026-09-14T06:42:36.041Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -24,16 +24,18 @@ Generated: 2026-09-12T07:41:17.555Z
|
|
|
24
24
|
- Use Twinkle.characters.chat for real Zero/Ciel NPC dialogue with shared room context and AI Energy-aware thinking modes.
|
|
25
25
|
- Twinkle.ai.chat history entries must use { role, content }; map local message.text fields to content before passing history.
|
|
26
26
|
- Live web search is enabled by default for Twinkle.ai.chat and for Medium/High Twinkle.ai.generateObject and Twinkle.characters.chat requests. App authors can pass webSearch: false to disable it for their app. Search uses the provider's live web-search tool and is included in AI Energy usage; structured and character Lite Mode remains tool-free.
|
|
27
|
-
-
|
|
27
|
+
- Mobile long-press must not select game UI or open browser Copy/Look Up/image menus. The SDK provides no-selection/callout defaults for standard buttons, button-like ARIA controls, and canvases. Mark the entire custom gameplay wrapper data-twinkle-no-select, including HUD, labels, scores, menus, controls, and empty play space; also style it with user-select: none, -webkit-user-select: none, and -webkit-touch-callout: none for local previews. Protecting only one button or the canvas is insufficient. This behavior is independent of Twinkle.preview layout mode.
|
|
28
|
+
- Keep inputs/contenteditable usable. Mark genuinely copyable story/chat/user-written text data-twinkle-selectable and style it with user-select: text, -webkit-user-select: text, and -webkit-touch-callout: default. SDK defaults have low specificity so existing explicit copyable-text styles remain effective. Preserve document/reader selection; do not block document-wide touch/pointer events or disable scrolling/zoom to prevent selection.
|
|
29
|
+
- Verify mobile long presses on controls, HUD, and empty play space in Safari and Chromium; confirm held controls still work and release/cancel correctly, and scrolling, typing, and copying still work. lumine check only detects a missing no-selection rule, not selector coverage or mobile behavior.
|
|
28
30
|
- Build app tab mute is enforced by the host runtime automatically for standard media elements and Web Audio connections to AudioContext.destination. Apps with custom audio engines can also observe Twinkle.onAudioMuteChange and check Twinkle.isAudioMuted.
|
|
29
31
|
- Use Twinkle.media for camera photos and camera-only two-second clips. Twinkle confirms each capture or paid processing action. Clips are processed to canonical 480p MP4 assets before they become visible; use sharedDb or privateDb to publish/store the returned asset metadata.
|
|
30
32
|
- Static media published through sharedDb is app-owned feed data. A public user-generated feed must provide a visible report flow and owner removal, and must not claim that Twinkle globally moderates those posts.
|
|
31
33
|
- Use Twinkle.live for one-way app livestreams and Twinkle.chat for the accompanying thread. Free livestreams require a verified host, end after at most 15 minutes, and issue at most 10 private viewer grants. Twinkle keeps platform-owned live-status/end controls above active hosts, so app code cannot hide or replace the broadcaster's Stop path.
|
|
32
34
|
- Media Energy is separate from AI Energy. Replace Media Energy UI only from canonical mediaEnergy/getUsage responses; never decrement, reserve, or synthesize it in app code.
|
|
33
35
|
- Twinkle.rewards awards real XP and Coins only in the current approved published release. Drafts, local previews, private apps and superseded releases cannot earn. The server supplies a published-runtime grant; app code cannot choose a recipient or award amount.
|
|
34
|
-
- The creator's agent designs the rewards. Declare the economy in a project file `rewards.json` at the root: budgets (dailyXP, dailyCoins, userDailyXP, userDailyCoins, lifetimeXP, lifetimeCoins, optional userDailyClaims) and rules [{ id, title, xp, coins, verifier: 'numeric-quiz' | 'completion', maxAttempts?, retry?: { xpPercent, coinsPercent }, minSeconds? (completion), progression?: 'dated' | 'until-earned' (quiz) }]. 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.
|
|
35
|
-
- 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
|
|
36
|
-
-
|
|
36
|
+
- The creator's agent designs the rewards. Declare the economy in a project file `rewards.json` at the root: budgets (dailyXP, dailyCoins, userDailyXP, userDailyCoins, lifetimeXP, lifetimeCoins, optional userDailyClaims) and rules [{ id, title, xp, coins, verifier: 'numeric-quiz' | 'completion', maxAttempts?, retry?: { xpPercent, coinsPercent, paidAttempts? }, minSeconds? (completion), progression?: 'dated' | 'until-earned' (quiz) }]. 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
|
+
- 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.
|
|
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.
|
|
37
39
|
|
|
38
40
|
## Token Scopes
|
|
39
41
|
files:read, media:read, media:write, live:read, live:write, user:read, users:read, dailyReflections:read, content:read, content:write, sharedDb:read, sharedDb:write, privateDb:read, privateDb:write, files:write, chat:read, chat:write, notifications:read, notifications:write, notifications:emit, reminders:read, reminders:write, rewards:claim
|
|
@@ -1012,23 +1014,27 @@ world.updatePresence({ x, y, z, facing });
|
|
|
1012
1014
|
- await Twinkle.rewards.getStatus() | scopes: rewards:claim
|
|
1013
1015
|
- Returns: { mode: "live", dayKey, userDailyClaims, claimsToday, budgets: { userDailyXP, userDailyCoins }, rules: [{ id, title, xp, coins, verifier: "numeric-quiz" | "completion", minSeconds?, progression?, retryReward: { xp, coins }, maxAttempts, available, setKey, questionCount }], challenges: [{ challengeId, ruleId, attempts, attemptsRemaining, state: "open" | "finished" | "earned", setKey, questions: [{ prompt, hint?, guide? }] }], history: [{ ruleId, xp, coins, attempt, createdAt }], balances: { xp, coins } } | { mode: "preview", dayKey?, rules, challenges: [], history: [], balances?, problems?: string[], message }
|
|
1014
1016
|
- Read canonical earning rules (without answer keys), today’s started challenges, today’s receipts and balances. Drafts return preview mode: for the app's owner the rules come from the draft's own rewards.json and question sheet (problems lists what is still wrong with them); anyone else sees no rules. Unapproved or revoked published releases return an error.
|
|
1015
|
-
- rules[].available is false on a
|
|
1017
|
+
- rules[].available is false on a site day (UTC) the reviewer scheduled no questions for; show the rule as not available instead of starting it. xp/coins are the first-try amounts; retryReward is what a correct answer pays after a wrong one (equal to xp/coins unless the reviewer set a retry share). maxAttempts null means unlimited wrong answers until the site's daily reset (UTC midnight, 9:00 AM in Korea). retry.paidAttempts, when set, is the last attempt number a correct answer is still paid on: a later correct answer is recorded as solved (receipt xp 0, coins 0) and pays nothing — tell the learner before they pass it.
|
|
1016
1018
|
- challenges lists challenges this viewer already started today with their questions, so an app can resume after a reload without calling start. A question's guide (reviewer-approved JSON teaching content: explanation, interactive-model configuration) is present only once the viewer has answered at least once, right or wrong; render it as the after-attempt lesson. claimsToday against userDailyClaims (null = uncapped) tells whether another bounty can still pay today.
|
|
1017
1019
|
- Under progression 'until-earned' the same set stays up day after day until somebody earns it; setKey names the set currently up. Completion rules are always available and have questionCount 0.
|
|
1020
|
+
- await Twinkle.rewards.getReceipt({ challengeId }) | scopes: rewards:claim
|
|
1021
|
+
- Returns: { mode: "live", status: "awarded" | "pending" | "expired" | "not_found", receipt: { id, challengeId, ruleId, reviewId, artifactVersionId, dayKey, xp, coins, attempt, createdAt } | null, balances: { xp, coins } } | { mode: "preview", status: "not_found", receipt: null, message }
|
|
1022
|
+
- Read an existing receipt for this app and signed-in viewer by server-issued challengeId, including previous UTC days and previous approved versions. Requires the current approved published release and runtime grant; a stale frame must reload first. Never awards, retries a claim, returns answer keys, or restores removed rewards permission.
|
|
1023
|
+
- Reconcile a durable local reward outbox after a lost claim reply: awarded confirms the exact payment; pending means no receipt yet for a current unexpired challenge, so retry the same challengeId. expired or not_found confirms no paid receipt and no claim possible for that ID under the current release. Never refund app items just because getStatus history omitted an older claim or a network request failed. Preview has no durable paid receipts; keep it separate from live recovery.
|
|
1018
1024
|
- await Twinkle.rewards.start({ ruleId }) | scopes: rewards:claim
|
|
1019
1025
|
- Returns: { mode: "live", challengeId, questions: [{ prompt, hint?, guide? }], setKey, reward: { xp, coins }, retryReward: { xp, coins }, attempts, maxAttempts, attemptsRemaining, firstTryAvailable, expiresAt }
|
|
1020
|
-
- Creates or resumes a server-issued challenge for the signed-in viewer. Render its questions (prompt and optional hint) and collect numeric answers in the same order. One daily challenge per rule/review; repeat starts cannot reset attempts. A challenge stays open until
|
|
1026
|
+
- Creates or resumes a server-issued challenge for the signed-in viewer. Render its questions (prompt and optional hint) and collect numeric answers in the same order. One daily challenge per rule/review; repeat starts cannot reset attempts. A challenge stays open until the site's daily reset (UTC midnight, 9:00 AM in Korea) (expiresAt). Resuming after a wrong answer includes each question's guide.
|
|
1021
1027
|
- 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.
|
|
1022
1028
|
- 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).
|
|
1023
1029
|
- await Twinkle.rewards.claim({ challengeId, answers?: [number] }) | scopes: rewards:claim
|
|
1024
1030
|
- Returns: { awarded: false, attempts, attemptsRemaining, questions: [{ prompt, hint?, guide? }] } | { awarded: true, duplicate, receipt: { ruleId, xp, coins, attempt, firstTry }, questions: [{ prompt, hint?, guide? }], balances: { xp, coins } }
|
|
1025
|
-
- Twinkle verifies every answer, approval, current published artifact and budget before atomically recording XP and Coins. The receipt’s xp/coins are what was actually paid: the full amounts on a first try, the retry share after a wrong answer (attempt > 1). Retry the same challengeId after a lost response; a confirmed claim returns its original receipt without another award. Never update balance UI optimistically.
|
|
1031
|
+
- Twinkle verifies every answer, approval, current published artifact and budget before atomically recording XP and Coins. The receipt’s xp/coins are what was actually paid: the full amounts on a first try, the retry share after a wrong answer (attempt > 1). Retry the same challengeId after a lost response; a confirmed claim returns its original receipt without another award. Never update balance UI optimistically. Under retry.paidAttempts a correct answer past that attempt returns awarded: true with a zero receipt: solved, not paid.
|
|
1026
1032
|
- Every claim response, wrong or right, returns the questions with their guides unlocked: show the teaching content right after the first answer. Answer keys are never returned.
|
|
1027
1033
|
- 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.
|
|
1028
1034
|
- 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.
|
|
1029
1035
|
- await Twinkle.rewards.getLeaderboard({ metric?: "xp" | "coins", period?: "day" | "week" | "all", limit? }) | scopes: rewards:claim
|
|
1030
1036
|
- 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 }
|
|
1031
|
-
- 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
|
|
1037
|
+
- 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.
|
|
1032
1038
|
- available says which boards this app's approved rules can pay: show a Coins board only when available.coins is true (an app whose rules pay XP only has no Coins standings). me is the signed-in viewer's own standing even when they fall outside the page, or null when they earned nothing in the period.
|
|
1033
1039
|
- Drafts and previews return mode 'preview' with no entries. Use Twinkle.leaderboards for app-defined scores; use this for real XP and Coins earned.
|
|
1034
1040
|
|
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -488,16 +488,24 @@ it never authorizes unrelated daily work or generic recommendation commands.
|
|
|
488
488
|
|
|
489
489
|
Mikey added Math Lab question design and publishing to the full daily workflow
|
|
490
490
|
on 2026-09-08. Follow [Math Lab daily question publishing](../../agent-guides/math-lab-daily.md)
|
|
491
|
-
for the canonical Build 2460, owner account,
|
|
492
|
-
|
|
491
|
+
for the canonical Build 2460, owner account, twelve grade queues of ordered
|
|
492
|
+
until-earned puzzles, verification, repeat-run recovery, release gates, and
|
|
493
|
+
final reporting. Every full daily run reports each grade's current question,
|
|
494
|
+
whether it was cleared today, uncleared published questions remaining, and
|
|
495
|
+
refill status. Count distinct cleared keys across all users and the app's full
|
|
496
|
+
history against the live approved sheet; the recent usage window and draft
|
|
497
|
+
additions are not the live inventory. At two or fewer remaining, prepare a
|
|
498
|
+
refill to at least ten, with complete interactive guides, and track it until
|
|
499
|
+
approved publication. Report one or zero remaining prominently. Details are
|
|
500
|
+
in the linked guide's **Daily queue monitoring and refill** section.
|
|
493
501
|
This is not part of Featured-only or newspaper-only work and is not a new
|
|
494
502
|
scheduler, delegated API scope, or automatic extension of admin permissions.
|
|
495
503
|
Use the expressly authorized owner Build workflow for Math Lab; retain the
|
|
496
504
|
normal Zero/Ciel actor separation for other administration.
|
|
497
505
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
506
|
+
Mikey authorized Math Lab's initial launch, completed on 2026-09-12. Routine
|
|
507
|
+
refills follow the standing duty but still need his exact-version approval
|
|
508
|
+
and explicit publication authorization. Local edits
|
|
501
509
|
and draft saves do not require release approval. Real XP/Coins may be changed
|
|
502
510
|
only by the currently published, approved artifact through server-verified
|
|
503
511
|
reward claims; private builds, previews, local tests, unpublished branches, and
|
|
@@ -507,6 +515,8 @@ has already been implemented. See the guide before adding reward capabilities.
|
|
|
507
515
|
|
|
508
516
|
## Escalation to Mikey
|
|
509
517
|
|
|
518
|
+
Before closing a full run, reconcile three explicit handoffs: pending reward approvals (`rewardReviews` in intake/report), every carryover todo (with new evidence or a concrete blocker and next action), and earlier-day telemetry that meets a reopening condition. `carryoverWithoutProgressThisRun` in the report identifies surfaced todos without a progress update. A pending human decision can remain open; it must be named with its exact request/version, recommendation and next owner. Never equate reading a summary with inspecting frozen implementation, clearing a stuck flag with producing the intended image, or deploying code with verifying its live outcome. The September 14 omissions were execution failures under already explicit duties; these fields make them visible, not optional.
|
|
519
|
+
|
|
510
520
|
A full daily management run is not finished when the mutations are done. Curation surfaces things only
|
|
511
521
|
a human owner can decide, and a finding nobody reports is a finding that did not
|
|
512
522
|
happen. **Every full run ends with an escalation list**, and it belongs in the run's
|
|
@@ -1055,9 +1065,16 @@ and answer keys from a private question sheet the creator's Lumine uploads with
|
|
|
1055
1065
|
readable by every player). **Send for review** freezes the code and proposes
|
|
1056
1066
|
`rewards.json` merged with the sheet. Approval is Mikey's decision: read the
|
|
1057
1067
|
frozen code, check that the amounts are right and that the app cannot be
|
|
1058
|
-
farmed, change anything that is wrong, approve.
|
|
1059
|
-
|
|
1060
|
-
|
|
1068
|
+
farmed, change anything that is wrong, approve. **Approval publishes** (since
|
|
1069
|
+
2026-09-15): the exact frozen snapshot goes live in the same transaction, with
|
|
1070
|
+
no Publish click by the creator; the app's previous release stays up until
|
|
1071
|
+
that commit lands. Instead of approving, the reviewer may **propose changes**:
|
|
1072
|
+
edit a copy of the frozen snapshot and offer it as the condition of approval.
|
|
1073
|
+
The creator sees every changed line and either accepts (the proposed version
|
|
1074
|
+
is approved and published) or declines (the request is rejected). Nothing in
|
|
1075
|
+
that flow joins the creator's team. These commands need no daily run and can
|
|
1076
|
+
be used whenever a request arrives (the reviewer also receives a DM card per
|
|
1077
|
+
request).
|
|
1061
1078
|
|
|
1062
1079
|
```bash
|
|
1063
1080
|
lumine admin reward-review list --json # pending (default)
|
|
@@ -1067,7 +1084,10 @@ lumine admin reward-review show 2 --json # summary + file si
|
|
|
1067
1084
|
lumine admin reward-review show 2 --dir /private/tmp/reward-review-2 --json
|
|
1068
1085
|
lumine admin reward-review approve 2 --json # approve exactly what the app proposed
|
|
1069
1086
|
lumine admin reward-review approve 2 --config rules.json \
|
|
1070
|
-
--reason "Halved the stage amounts" --json # approve with changes
|
|
1087
|
+
--reason "Halved the stage amounts" --json # approve with changes (publishes)
|
|
1088
|
+
lumine admin reward-review show 2 --dir /private/tmp/reward-review-2 --json # then edit that directory…
|
|
1089
|
+
lumine admin reward-review propose 2 --dir /private/tmp/reward-review-2 \
|
|
1090
|
+
--config rules.json --reason "Moved the claim after the stage clears" --json # …and offer it
|
|
1071
1091
|
lumine admin reward-review reject 2 --reason "Rewards fire on game over; nothing is earned" --json
|
|
1072
1092
|
lumine admin reward-review revoke 2 --reason "Farmable; pausing until redesigned" --json
|
|
1073
1093
|
```
|
|
@@ -1092,7 +1112,7 @@ Review questions to settle with Mikey before approving:
|
|
|
1092
1112
|
day across twelve stages.
|
|
1093
1113
|
- Quiz rules: fixed questions reachable in seconds are farmable; dated sets
|
|
1094
1114
|
or `progression: "until-earned"` sets (a set stays up until somebody earns
|
|
1095
|
-
it, then the next one comes up the following
|
|
1115
|
+
it, then the next one comes up the following site day (UTC midnight, 9:00 AM Korea)) keep them honest.
|
|
1096
1116
|
- Do the rule IDs in `rewards.json` match what the code starts? Unknown IDs
|
|
1097
1117
|
simply never pay.
|
|
1098
1118
|
- Are the amounts and the per-user, per-app and lifetime budgets conservative
|
|
@@ -1122,7 +1142,7 @@ Rule fields (all server-enforced, none inferred from app code):
|
|
|
1122
1142
|
- `verifier`: `numeric-quiz` (server-checked numeric answers) or `completion`
|
|
1123
1143
|
(a finished activity; `minSeconds` is the only proof).
|
|
1124
1144
|
- `sets`: question sets. Dated: `[{ "from": "2026-09-14", "to": "2026-09-14", "questions": [...] }]`
|
|
1125
|
-
on
|
|
1145
|
+
on site days (UTC) (inclusive, non-overlapping, up to 62). Until-earned
|
|
1126
1146
|
(`"progression": "until-earned"`): ordered sets with optional `key`; the
|
|
1127
1147
|
first set nobody earned before today is up, an unsolved set is never
|
|
1128
1148
|
replaced, and a set earned today stays up for the rest of that day.
|
|
@@ -1130,11 +1150,11 @@ Rule fields (all server-enforced, none inferred from app code):
|
|
|
1130
1150
|
after a wrong one, as a share of the rule's amounts (rounded down). Absent:
|
|
1131
1151
|
every correct answer pays the full amounts.
|
|
1132
1152
|
- `maxAttempts`: wrong answers allowed per challenge; `null` = unlimited until
|
|
1133
|
-
|
|
1153
|
+
the daily reset (UTC midnight, 9:00 AM Korea) (wrong answers are paced two seconds apart). Absent: 3.
|
|
1134
1154
|
- Per question `hint` (public from the start, ≤ 300 chars) and `guide` (a JSON
|
|
1135
1155
|
object ≤ 6,000 chars the app renders as the after-answer lesson). The server
|
|
1136
1156
|
releases a guide only after the learner's first answer.
|
|
1137
|
-
- Top-level `userDailyClaims`: receipts one learner may earn per
|
|
1157
|
+
- Top-level `userDailyClaims`: receipts one learner may earn per site day
|
|
1138
1158
|
across all rules. `1` is "one bounty a day".
|
|
1139
1159
|
|
|
1140
1160
|
Math Lab's economy (Mikey, 2026-09-12): twelve level rules, one per grade per
|
|
@@ -1146,12 +1166,49 @@ Arcade Typing (Mikey, 2026-09-12): XP for clearing campaign stages, up to
|
|
|
1146
1166
|
10,000 Coins per rule and per learner per day, 10,000,000 XP / 1,000,000 Coins
|
|
1147
1167
|
per app per day, 1,000,000,000 XP / 100,000,000 Coins per app lifetime.
|
|
1148
1168
|
|
|
1149
|
-
Approval freezes these rules with the reviewed snapshot
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
`
|
|
1169
|
+
Approval freezes these rules with the reviewed snapshot and publishes that
|
|
1170
|
+
snapshot immediately (the result carries `published.version`); an approval
|
|
1171
|
+
without at least one rule is refused, and an approval whose creator has saved
|
|
1172
|
+
past the frozen version is refused as `build_reward_review_stale` (the request
|
|
1173
|
+
also closes itself on that save). Rejection and revocation require a
|
|
1174
|
+
`--reason` the creator reads verbatim in their workspace. A later code save
|
|
1175
|
+
needs a new request. Never approve without reading the code; never approve a
|
|
1176
|
+
request whose `isLatest` is false.
|
|
1177
|
+
|
|
1178
|
+
`propose <id> --dir <edited> --config rules.json [--reason]` sends the edited
|
|
1179
|
+
directory (text files only; dotfiles and tool folders skipped) as the
|
|
1180
|
+
reviewer's proposal: the review moves to `changes_offered`, the creator's card
|
|
1181
|
+
and workspace show the note and every changed line, and the creator's
|
|
1182
|
+
**Accept & go live** publishes exactly those files with these rules (their
|
|
1183
|
+
workspace is replaced by the accepted version). **No thanks** rejects the
|
|
1184
|
+
request (`declinedByCreator: true`). A proposal must still use the rewards
|
|
1185
|
+
SDK, must differ from the submitted snapshot, and is refused once the creator
|
|
1186
|
+
saves past the submitted version. Offering again replaces the earlier offer;
|
|
1187
|
+
approving or rejecting while an offer is out decides the request as
|
|
1188
|
+
submitted. The website equivalent is the Management panel's "Edit a copy to
|
|
1189
|
+
propose changes" (a private workspace copy owned by the reviewer) followed by
|
|
1190
|
+
"Offer my copy with these rules".
|
|
1191
|
+
|
|
1192
|
+
Each offer has a server-owned revision. Changing the files, rules or note
|
|
1193
|
+
creates a new revision; a creator looking at an older comparison or decline
|
|
1194
|
+
confirmation cannot answer the replacement offer. The creator sees its reward
|
|
1195
|
+
amounts as well as its file changes. Proposed rules stay separate from the
|
|
1196
|
+
submitted rules until acceptance, so `approve` without `--config` still uses
|
|
1197
|
+
the original submitted configuration. The CLI audits the offer atomically
|
|
1198
|
+
and includes file contents in its retry fingerprint.
|
|
1199
|
+
|
|
1200
|
+
Approval also attempts a free preview thumbnail when the app has none. That
|
|
1201
|
+
capture uses the published version and cannot overwrite a later release or a
|
|
1202
|
+
thumbnail the creator chooses while it runs. It is best effort: a capture
|
|
1203
|
+
failure leaves publication successful and does not spend AI-image credits.
|
|
1204
|
+
|
|
1205
|
+
The review copy carries independent copies of referenced uploaded media.
|
|
1206
|
+
Before freezing an offer, the server reuses the creator's original media and
|
|
1207
|
+
copies new reviewer media into the creator's library within their storage
|
|
1208
|
+
quota. Its final URLs are included in the comparison, so acceptance publishes
|
|
1209
|
+
those exact files and does not depend on keeping the review copy. Re-offers
|
|
1210
|
+
reuse the media; a failed transaction cleans up its copied objects. Declining
|
|
1211
|
+
leaves the offered media as unused uploads in the creator's library.
|
|
1155
1212
|
|
|
1156
1213
|
### Reward activity report (standing duty, every full daily review; added 2026-09-12)
|
|
1157
1214
|
|
|
@@ -1159,11 +1216,13 @@ Completion rewards (Arcade Typing's stage clears) prove nothing but elapsed
|
|
|
1159
1216
|
time, so the run reads the shape of the week's claims instead of trusting them:
|
|
1160
1217
|
|
|
1161
1218
|
```bash
|
|
1162
|
-
lumine admin reward-activity --json #
|
|
1219
|
+
lumine admin reward-activity --json # 7 UTC days INCLUDING today’s partial day
|
|
1220
|
+
lumine admin reward-activity --date 2026-09-13 --json # exactly this UTC day
|
|
1221
|
+
lumine admin reward-activity --date 2026-09-13 --days 7 --json # 7 days ending on this date
|
|
1163
1222
|
lumine admin reward-activity --days 14 --build 333 --json
|
|
1164
1223
|
```
|
|
1165
1224
|
|
|
1166
|
-
Read-only, no run lease. The result lists every app that paid (claims,
|
|
1225
|
+
Read-only, no run lease. For yesterday, always pass its exact UTC `--date`; `--days 1` alone means the current partial day. `from`, `to`, `timezone`, and `includesCurrentDay` make the window explicit. Rules come from each claim’s frozen review, not the current app policy; `missingReviewIds` means rule-based flags lack context. The result lists every app that paid (claims,
|
|
1167
1226
|
earners, XP, Coins) and the flagged player-days, worst first:
|
|
1168
1227
|
|
|
1169
1228
|
- `fast`: a completion claim within 5 s of the rule's `minSeconds` — a human
|
|
@@ -3241,6 +3300,7 @@ farm-signal sections added that day; AI Card summon watch added 2026-08-24):
|
|
|
3241
3300
|
canonical writer and returns only the resolved public account identity,
|
|
3242
3301
|
current membership, and the roster rationale/timestamps when present; it
|
|
3243
3302
|
does not expose the private roster fields.
|
|
3303
|
+
When Mikey authorizes removal, use `lumine admin notable remove <userId|username> --note "<why removed>" --json`. It is run-independent, transactionally audited as `notable.remove`, verifies canonical absence, and is idempotent. Never use SQL to work around a missing CLI verb. Mikey is the administrator, not a Notable candidate; do not include him in blanket roster additions.
|
|
3244
3304
|
**Always pass `--note`** with a concrete one-or-two-sentence record of what
|
|
3245
3305
|
made them notable — real numbers and specifics from the brief window, not
|
|
3246
3306
|
"active user". It lands in the management page's reason column, which is
|