@stage5/lumine 0.3.34 → 0.3.35
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-daily.js +33 -8
- package/lib/admin.js +11 -0
- package/lib/commands.js +4 -0
- package/lib/rewards.js +42 -3
- package/package.json +1 -1
- package/sdk/LUMINE_ADMIN.md +27 -4
package/lib/admin-daily.js
CHANGED
|
@@ -23,6 +23,7 @@ export function runAdmin(
|
|
|
23
23
|
args,
|
|
24
24
|
{ outputFile, extraEnv, retryRead = false, spawnImpl = spawnSync } = {},
|
|
25
25
|
) {
|
|
26
|
+
const startedAt = performance.now();
|
|
26
27
|
const attemptFile = outputFile
|
|
27
28
|
? `${outputFile}.attempt-${randomUUID()}.json`
|
|
28
29
|
: null;
|
|
@@ -78,6 +79,7 @@ export function runAdmin(
|
|
|
78
79
|
featured: ["list", "history"],
|
|
79
80
|
sponsor: ["applications"],
|
|
80
81
|
"bot-output": [null],
|
|
82
|
+
"math-lab": ["queue"],
|
|
81
83
|
subjects: ["candidates"],
|
|
82
84
|
subject: ["get"],
|
|
83
85
|
};
|
|
@@ -96,6 +98,7 @@ export function runAdmin(
|
|
|
96
98
|
const retried = runAdmin(args, { outputFile, extraEnv, spawnImpl });
|
|
97
99
|
return {
|
|
98
100
|
...retried,
|
|
101
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
99
102
|
attempts: 2,
|
|
100
103
|
recovered: retried.ok,
|
|
101
104
|
firstError:
|
|
@@ -104,6 +107,7 @@ export function runAdmin(
|
|
|
104
107
|
}
|
|
105
108
|
return {
|
|
106
109
|
ok,
|
|
110
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
107
111
|
attempts: 1,
|
|
108
112
|
status: result.status,
|
|
109
113
|
json: error ? { ok: false, status: "unconfirmed", error } : parsed,
|
|
@@ -159,6 +163,7 @@ export function renderBotOutput({ chatMessages, comments }, file) {
|
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
export async function dailyRunGather(options) {
|
|
166
|
+
const startedAt = performance.now();
|
|
162
167
|
const dir = ensureDir(
|
|
163
168
|
path.resolve(options.adminOutputDir || options.adminDir || "."),
|
|
164
169
|
);
|
|
@@ -172,6 +177,7 @@ export async function dailyRunGather(options) {
|
|
|
172
177
|
file,
|
|
173
178
|
ok: res.ok,
|
|
174
179
|
attempts: res.attempts,
|
|
180
|
+
durationMs: res.durationMs,
|
|
175
181
|
recovered: Boolean(res.recovered),
|
|
176
182
|
...(res.ok ? {} : { error: res.json?.error || res.stderr }),
|
|
177
183
|
});
|
|
@@ -180,6 +186,7 @@ export async function dailyRunGather(options) {
|
|
|
180
186
|
step("energy-budget", ["energy-budget"], "energy-budget.json");
|
|
181
187
|
step("news", ["news"], "news.json");
|
|
182
188
|
step("brief", ["brief"], "brief.json");
|
|
189
|
+
step("math-lab-queue", ["math-lab", "queue"], "math-lab-queue.json");
|
|
183
190
|
step("ai-costs-monthly", ["ai-costs", "monthly"], "ai-monthly.json");
|
|
184
191
|
step("ai-costs-day", ["ai-costs", "day", yesterday], "ai-day.json");
|
|
185
192
|
step("media-costs", ["media-costs", "monthly"], "media.json");
|
|
@@ -239,6 +246,8 @@ export async function dailyRunGather(options) {
|
|
|
239
246
|
if (botOk) renderBotOutput({ chatMessages, comments }, path.join(dir, "bot-chats.txt"));
|
|
240
247
|
|
|
241
248
|
const summary = {
|
|
249
|
+
collectedAt: new Date().toISOString(),
|
|
250
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
242
251
|
dir,
|
|
243
252
|
yesterdayUtc: yesterday,
|
|
244
253
|
botOutput: {
|
|
@@ -377,13 +386,19 @@ fetch('daily-management-report.md',{cache:'no-store'}).then(r=>{if(!r.ok)throw n
|
|
|
377
386
|
export function waitForReportServer(child, timeoutMs = 10000) {
|
|
378
387
|
return new Promise((resolve, reject) => {
|
|
379
388
|
let buf = "";
|
|
389
|
+
let stderr = "";
|
|
380
390
|
let settled = false;
|
|
391
|
+
const clean = value => String(value || "")
|
|
392
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
393
|
+
.replace(/[\u0000-\u001f\u007f]+/g, " ").trim().slice(-2048);
|
|
394
|
+
const diagnostic = message => new Error(`${message}${clean(stderr) ? ` ${clean(stderr)}` : ""}`);
|
|
381
395
|
const finish = (error, url) => {
|
|
382
396
|
if (settled) return;
|
|
383
397
|
settled = true;
|
|
384
398
|
clearTimeout(timer);
|
|
385
399
|
child.stdout.removeListener("data", onData);
|
|
386
|
-
child.removeListener("
|
|
400
|
+
child.stderr?.removeListener("data", onStderr);
|
|
401
|
+
child.removeListener("close", onClose);
|
|
387
402
|
child.removeListener("error", onError);
|
|
388
403
|
if (error) {
|
|
389
404
|
child.kill();
|
|
@@ -399,29 +414,36 @@ export function waitForReportServer(child, timeoutMs = 10000) {
|
|
|
399
414
|
if (!buf.includes("\n")) return;
|
|
400
415
|
try {
|
|
401
416
|
const parsed = JSON.parse(buf.split("\n")[0]);
|
|
417
|
+
if (parsed.ok === false) {
|
|
418
|
+
const reason = clean(parsed.error?.message || parsed.message || parsed.error);
|
|
419
|
+
return finish(diagnostic(`Report server failed to start${reason ? `: ${reason}` : "."}`));
|
|
420
|
+
}
|
|
402
421
|
const url = new URL(parsed.data?.url);
|
|
403
422
|
if (
|
|
404
423
|
parsed.ok !== true ||
|
|
405
424
|
parsed.status !== "success" ||
|
|
406
425
|
url.protocol !== "http:" ||
|
|
407
|
-
url.hostname !== "127.0.0.1"
|
|
426
|
+
url.hostname !== "127.0.0.1" || url.username || url.password
|
|
408
427
|
)
|
|
409
428
|
throw new Error("invalid receipt");
|
|
410
429
|
finish(null, url.href);
|
|
411
430
|
} catch {
|
|
412
|
-
finish(
|
|
431
|
+
finish(diagnostic("Report server returned an invalid startup receipt."));
|
|
413
432
|
}
|
|
414
433
|
};
|
|
415
|
-
const
|
|
416
|
-
|
|
434
|
+
const onStderr = chunk => { stderr = (stderr + chunk).slice(-8192); };
|
|
435
|
+
// close follows drained stdio; exit can arrive before the error text.
|
|
436
|
+
const onClose = (code, signal) =>
|
|
437
|
+
finish(diagnostic(`Report server exited (${signal || code}) before it was ready.`));
|
|
417
438
|
const onError = (error) => finish(error);
|
|
418
439
|
const timer = setTimeout(
|
|
419
440
|
() =>
|
|
420
|
-
finish(
|
|
441
|
+
finish(diagnostic("Report server did not start before its deadline.")),
|
|
421
442
|
timeoutMs,
|
|
422
443
|
);
|
|
423
444
|
child.stdout.on("data", onData);
|
|
424
|
-
child.
|
|
445
|
+
child.stderr?.on("data", onStderr);
|
|
446
|
+
child.once("close", onClose);
|
|
425
447
|
child.once("error", onError);
|
|
426
448
|
});
|
|
427
449
|
}
|
|
@@ -488,10 +510,13 @@ export async function reportServe(
|
|
|
488
510
|
"--json",
|
|
489
511
|
"--no-update-check",
|
|
490
512
|
],
|
|
491
|
-
{ detached: true, stdio: ["ignore", "pipe", "
|
|
513
|
+
{ detached: true, stdio: ["ignore", "pipe", "pipe"] },
|
|
492
514
|
);
|
|
493
515
|
const url = await waitForReportServer(child);
|
|
494
516
|
child.stdout.destroy();
|
|
517
|
+
// Keep draining diagnostics without retaining them or keeping the CLI up.
|
|
518
|
+
child.stderr?.resume();
|
|
519
|
+
child.stderr?.unref?.();
|
|
495
520
|
child.unref();
|
|
496
521
|
const opened = options.adminOpen
|
|
497
522
|
? openImpl("open", ["-a", "Google Chrome", url])
|
package/lib/admin.js
CHANGED
|
@@ -3973,6 +3973,9 @@ export function parseAdminOperation(options) {
|
|
|
3973
3973
|
},
|
|
3974
3974
|
);
|
|
3975
3975
|
}
|
|
3976
|
+
if (namespace === "math-lab" && action === "queue") {
|
|
3977
|
+
return readOperation("math-lab.queue", "/cli/admin/math-lab/queue");
|
|
3978
|
+
}
|
|
3976
3979
|
if (namespace === "builds" && action === "review") {
|
|
3977
3980
|
const parsed = parseAdminCommentTarget({ target, explicitType: "build" });
|
|
3978
3981
|
if (parsed.type !== "build") {
|
|
@@ -7330,6 +7333,14 @@ function printAiBucketRuleResult({ operation, data }) {
|
|
|
7330
7333
|
|
|
7331
7334
|
function printAdminResult({ operation, result }) {
|
|
7332
7335
|
const data = result?.data || {};
|
|
7336
|
+
if (operation.name === "math-lab.queue") {
|
|
7337
|
+
console.log(`Math Lab: published artifact ${data.publishedArtifactVersionId}, review ${data.reviewId}, day ${data.dayKey} UTC.`);
|
|
7338
|
+
for (const grade of data.grades || []) console.log(grade.available
|
|
7339
|
+
? `${grade.ruleId}: ${grade.current || "exhausted"}${grade.clearedToday ? " (cleared today)" : ""}; ${grade.remaining} unearned of ${grade.sets}${grade.refillDue ? `; refill to ${data.refillTarget}` : ""}`
|
|
7340
|
+
: `${grade.ruleId}: queue unavailable`);
|
|
7341
|
+
if (!data.complete) console.log("Queue coverage is incomplete; unavailable grades are not empty queues.");
|
|
7342
|
+
return;
|
|
7343
|
+
}
|
|
7333
7344
|
if (printChatSafetyResult({ operation, data })) return;
|
|
7334
7345
|
const chatReportLines = formatChatReportResult({ operation, data });
|
|
7335
7346
|
if (chatReportLines) {
|
package/lib/commands.js
CHANGED
|
@@ -2479,6 +2479,7 @@ export function parseArgs(args) {
|
|
|
2479
2479
|
"foreground",
|
|
2480
2480
|
"open",
|
|
2481
2481
|
"dryRun",
|
|
2482
|
+
"preflight",
|
|
2482
2483
|
"confirmEmailRule",
|
|
2483
2484
|
"includePrefix",
|
|
2484
2485
|
"allowSharedNetwork",
|
|
@@ -2802,6 +2803,8 @@ export function parseArgs(args) {
|
|
|
2802
2803
|
adminEditionId: raw.editionId ? String(raw.editionId) : "",
|
|
2803
2804
|
adminLeaseToken: raw.leaseToken ? String(raw.leaseToken) : "",
|
|
2804
2805
|
adminFile: raw.file ? String(raw.file) : "",
|
|
2806
|
+
rewardPreflight: Boolean(raw.preflight),
|
|
2807
|
+
rewardApprovalFile: raw.approvalFile ? String(raw.approvalFile) : "",
|
|
2805
2808
|
// admin craft revise --item <id|name> [--why] [--emoji] [--category]
|
|
2806
2809
|
// (kept raw: a flag given without a text value is refused in
|
|
2807
2810
|
// admin-craft.js rather than sent as the string "true")
|
|
@@ -3255,6 +3258,7 @@ export function printHelp() {
|
|
|
3255
3258
|
lumine admin chat-reports export <report-id> | --hold <hold-id> --out <new-dir> [--json] (evidence package + SHA-256 manifest)
|
|
3256
3259
|
lumine admin chat-reports suspend --user <id> --note <why> [--json] (full ban: signs them out)
|
|
3257
3260
|
lumine admin reward-review show <review-id> [--dir <path>] [--json]
|
|
3261
|
+
lumine admin math-lab queue [--json] (published sets remaining per grade, all-time claims, refill flags)
|
|
3258
3262
|
lumine admin reward-review approve <review-id> [--config <rules.json>] [--reason <text>] [--json] (approval publishes the approved version)
|
|
3259
3263
|
lumine admin reward-review propose <review-id> --dir <edited-snapshot> --config <rules.json> [--reason <text>] [--title <new app name>] [--thumbnail <image|workspace>] [--json]
|
|
3260
3264
|
lumine admin reward-review reject|revoke <review-id> --reason <text> [--json]
|
package/lib/rewards.js
CHANGED
|
@@ -18,6 +18,15 @@ import { resolveRequiredBuildIdOrSelected } from "./commands.js";
|
|
|
18
18
|
|
|
19
19
|
const REWARDS_FILE = "rewards.json";
|
|
20
20
|
|
|
21
|
+
export function reviewApprovalFromReceipt(receipt, buildId) {
|
|
22
|
+
if (!receipt || receipt.ready !== true || receipt.buildId !== buildId ||
|
|
23
|
+
!Number.isSafeInteger(receipt.sourceVersionId) || receipt.sourceVersionId <= 0 ||
|
|
24
|
+
!["sourceHash", "sheetHash", "configHash"].every(key => /^[a-f0-9]{64}$/.test(receipt[key] || ""))) {
|
|
25
|
+
throw new Error("Use the complete, ready preflight JSON for this Build as --approval-file.");
|
|
26
|
+
}
|
|
27
|
+
return Object.fromEntries(["buildId", "sourceVersionId", "sourceHash", "sheetHash", "configHash"].map(key => [key, receipt[key]]));
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
async function readWorkspaceRewardsJson(options) {
|
|
22
31
|
const localProject = await findLocalProjectMetadata(
|
|
23
32
|
path.resolve(options.dir || process.cwd()),
|
|
@@ -208,13 +217,39 @@ export async function rewardsCommand(options) {
|
|
|
208
217
|
return;
|
|
209
218
|
}
|
|
210
219
|
if (action === "review") {
|
|
220
|
+
if (options.rewardPreflight && options.rewardApprovalFile)
|
|
221
|
+
throw new Error("Choose --preflight or --approval-file, not both.");
|
|
222
|
+
let receipt;
|
|
223
|
+
if (options.rewardApprovalFile) {
|
|
224
|
+
receipt = JSON.parse(await fs.readFile(path.resolve(options.rewardApprovalFile), "utf8"));
|
|
225
|
+
} else {
|
|
226
|
+
receipt = await requestJson({
|
|
227
|
+
url: `${options.apiUrl}/build/${buildId}/rewards/review-preflight`,
|
|
228
|
+
authToken: auth.token, timeoutMs: options.timeoutMs,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (options.rewardPreflight) {
|
|
232
|
+
if (options.json) console.log(JSON.stringify(receipt, null, 2));
|
|
233
|
+
else {
|
|
234
|
+
console.log(`Build ${buildId}, saved v${receipt.savedVersion} (artifact ${receipt.sourceVersionId}).`);
|
|
235
|
+
console.log(receipt.publicationBehavior);
|
|
236
|
+
console.log(`Source: ${receipt.sourceHash}\nQuestion sheet: ${receipt.sheetHash}\nReward configuration: ${receipt.configHash}`);
|
|
237
|
+
for (const error of receipt.errors || []) console.log(` - ${error}`);
|
|
238
|
+
console.log("Save this preflight with --json, review the candidate, then pass --approval-file <receipt.json> after approval.");
|
|
239
|
+
}
|
|
240
|
+
if (!receipt.ready) process.exitCode = 1;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const expectedApproval = reviewApprovalFromReceipt(receipt, buildId);
|
|
244
|
+
if (receipt.willPublishImmediately && !options.rewardApprovalFile)
|
|
245
|
+
throw new Error("This owner's reward review publishes immediately. Run `lumine rewards review --preflight --json`, approve that exact candidate, then use `--approval-file <receipt.json>`.");
|
|
211
246
|
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
212
247
|
const result = await requestJson({
|
|
213
248
|
url: `${options.apiUrl}/build/${buildId}/rewards/reviews`,
|
|
214
249
|
method: "POST",
|
|
215
250
|
authToken: auth.token,
|
|
216
251
|
timeoutMs: options.timeoutMs,
|
|
217
|
-
body: {},
|
|
252
|
+
body: { expectedApproval },
|
|
218
253
|
});
|
|
219
254
|
if (options.json) {
|
|
220
255
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -222,10 +257,12 @@ export async function rewardsCommand(options) {
|
|
|
222
257
|
}
|
|
223
258
|
const reviewId = result?.policy?.latestReviewId;
|
|
224
259
|
console.log(
|
|
225
|
-
`
|
|
260
|
+
`Reward review for Build ${buildId}${reviewId ? ` (request #${reviewId})` : ""}. Server state: ${result?.state || "unknown"}.`,
|
|
226
261
|
);
|
|
227
262
|
console.log(
|
|
228
|
-
|
|
263
|
+
receipt.willPublishImmediately
|
|
264
|
+
? "The server attempted automatic approval and publication. Check the returned state and published artifact before reporting it live."
|
|
265
|
+
: "The approved candidate is frozen for the reviewer. Saving again closes this request.",
|
|
229
266
|
);
|
|
230
267
|
return;
|
|
231
268
|
}
|
|
@@ -239,6 +276,8 @@ function printRewardsHelp() {
|
|
|
239
276
|
lumine rewards sheet <file.json> Upload the private question sheet ({ rules: { <ruleId>: { questions?, sets? } } })
|
|
240
277
|
lumine rewards sheet --show Summarize the sheet on file (never prints answer keys)
|
|
241
278
|
lumine rewards review Send the saved version (with the sheet on file) for XP/Coin review, like "Send for review" on the website
|
|
279
|
+
lumine rewards review --preflight --json Read exact version/source/sheet/config hashes and publication behavior; no mutation
|
|
280
|
+
lumine rewards review --approval-file <receipt.json> Request only the exact approved preflight candidate; owner auto-approval publishes immediately
|
|
242
281
|
|
|
243
282
|
rewards.json (project root) declares the economy the reviewer approves:
|
|
244
283
|
{ "userDailyXP", "userDailyCoins", "userDailyClaims"?,
|
package/package.json
CHANGED
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -622,6 +622,10 @@ additions are not the live inventory. At two or fewer remaining, prepare a
|
|
|
622
622
|
refill to at least ten, with complete interactive guides, and track it until
|
|
623
623
|
approved publication. Report one or zero remaining prominently. Details are
|
|
624
624
|
in the linked guide's **Daily queue monitoring and refill** section.
|
|
625
|
+
Read this inventory with `lumine admin math-lab queue --json`. Its receipt
|
|
626
|
+
binds the published artifact and live reward review, includes all twelve
|
|
627
|
+
grades, and reports incomplete coverage explicitly. Today’s cleared set stays
|
|
628
|
+
current until UTC midnight, but no longer counts as unearned inventory.
|
|
625
629
|
This is not part of Featured-only or newspaper-only work and is not a new
|
|
626
630
|
scheduler, delegated API scope, or automatic extension of admin permissions.
|
|
627
631
|
Use the expressly authorized owner Build workflow for Math Lab; retain the
|
|
@@ -878,6 +882,8 @@ type StandalonePost = {
|
|
|
878
882
|
updatedAt: null;
|
|
879
883
|
title: string | null;
|
|
880
884
|
question: string | null;
|
|
885
|
+
grade?: "Pass" | "Fail" | "Masterpiece" | null; // Daily Reflection only
|
|
886
|
+
masterpieceType?: "heart" | "mind" | "heart_and_mind" | null;
|
|
881
887
|
content: string | null;
|
|
882
888
|
explanation: string | null;
|
|
883
889
|
imagePath: string | null;
|
|
@@ -1152,7 +1158,7 @@ lumine admin report serve --file <dir>/daily-management-report.md [--port <n>] [
|
|
|
1152
1158
|
```
|
|
1153
1159
|
|
|
1154
1160
|
These compose the documented commands in this contract, running them as child processes, so every scope check, audit and pagination rule applies unchanged.
|
|
1155
|
-
- **`gather`** saves each read-only report into `<dir
|
|
1161
|
+
- **`gather`** saves each read-only report into `<dir>`, including the canonical published Math Lab queue in `math-lab-queue.json`. It follows `bot-output` to exhaustion and renders `bot-chats.txt`, then lists any failed step in `gather.json`. Total/per-step `durationMs`, attempts and recovered reads make recurring slow or failing collection steps visible during the daily review. It also creates `review-state.json`: every source is fetched, not read. Children bypass update checks, require matching fresh stdout/file receipts, and retry an unconfirmed opted-in read at most once. Mutations never auto-retry. It needs the active run for run-scoped reads.
|
|
1156
1162
|
- **`progress`** reads local review state and explicit operator acknowledgments, writes private `progress.json`, and keeps reading, canonical effort mutations, the run lease, and browser checks separate. Add all required duties for the actual run scope and its carryovers; collection alone is not a full review. A completed run lease cannot complete a pending duty. Reading acknowledgments bind to the current file bytes and are operator attestations, not proof of comprehension.
|
|
1157
1163
|
- **`featured candidates`** returns Subjects posted after the Bangkok-midnight cutoff that are off the board and provably never Featured. Each one comes with its human/bot comment counts, explicit comment truncation/completeness, and whether Mikey has viewed it. Counts from truncated context are partial. An unavailable Subject context fails the collection. Eligibility only: ranking stays editorial.
|
|
1158
1164
|
- **`report serve`** detaches a loopback server that answers only `/`, `marked.min.js` and the report Markdown. `--open` opens Chrome and reports whether the OS accepted the request. Render, navigation and original-source verification remain pending until actually checked. Startup failures stop only the child this command started.
|
|
@@ -1479,9 +1485,8 @@ Rule fields (all server-enforced, none inferred from app code):
|
|
|
1479
1485
|
- Top-level `userDailyClaims`: receipts one learner may earn per site day
|
|
1480
1486
|
across all rules. `1` is "one bounty a day".
|
|
1481
1487
|
|
|
1482
|
-
Math Lab's economy (Mikey, 2026-
|
|
1483
|
-
day,
|
|
1484
|
-
100,000 + 10,000; `retry` 50 % XP / 0 % Coins; `maxAttempts` null;
|
|
1488
|
+
Math Lab's economy (Mikey, equal pay 2026-10-04): twelve level rules, one per grade per
|
|
1489
|
+
day, all paying 70,000 XP + 5,000 Coins; `retry` 50 % XP / 0 % Coins; `maxAttempts` null;
|
|
1485
1490
|
`userDailyClaims` 1; until-earned sets authored from the Korean curriculum.
|
|
1486
1491
|
Arcade Typing (Mikey, 2026-09-12): XP for clearing campaign stages, up to
|
|
1487
1492
|
10,000 XP per learner per day, no Coins. Platform ceilings: 100,000 XP /
|
|
@@ -1497,6 +1502,18 @@ also closes itself on that save). Rejection and revocation require a
|
|
|
1497
1502
|
needs a new request. Never approve without reading the code; never approve a
|
|
1498
1503
|
request whose `isLatest` is false.
|
|
1499
1504
|
|
|
1505
|
+
Mikey-owned apps automatically attempt approval and publication when the owner
|
|
1506
|
+
requests reward review. Before that write, run
|
|
1507
|
+
`lumine rewards review --preflight --json` and retain its complete receipt.
|
|
1508
|
+
It identifies the saved version, artifact ID, source hash, private sheet hash
|
|
1509
|
+
and effective reward configuration hash, and states the publication behavior.
|
|
1510
|
+
After Mikey approves that exact candidate, use
|
|
1511
|
+
`lumine rewards review --approval-file <receipt.json>`. The API checks those
|
|
1512
|
+
values under the build lock before freezing content; a change returns
|
|
1513
|
+
`build_reward_approval_stale`. There is no silent fallback to an unbound request.
|
|
1514
|
+
Saving a new preflight is read-only and is not approval. These commands require
|
|
1515
|
+
the matching API and CLI release.
|
|
1516
|
+
|
|
1500
1517
|
`propose <id> --dir <edited> --config rules.json [--reason]` sends the edited
|
|
1501
1518
|
directory (text files only; dotfiles and tool folders skipped) as the
|
|
1502
1519
|
reviewer's proposal: the review moves to `changes_offered`, the creator's card
|
|
@@ -2480,6 +2497,12 @@ Inspection never silently bypasses secret semantics. Until the selected bot is
|
|
|
2480
2497
|
the author or has canonically responded/revealed, secret values and comments
|
|
2481
2498
|
remain unavailable.
|
|
2482
2499
|
|
|
2500
|
+
Daily Reflection post reads and recommendation candidates include the stored
|
|
2501
|
+
`grade` and `masterpieceType` (nullable), independently of XP and recommendations.
|
|
2502
|
+
An absent grade is unknown. Verify `grade === "Masterpiece"` before placing one
|
|
2503
|
+
of Mikey's reflections anywhere on the newspaper front page; lower or missing
|
|
2504
|
+
grades can be considered for inside pages. The CLI does not infer a grade.
|
|
2505
|
+
|
|
2483
2506
|
## Subject and Featured mutations
|
|
2484
2507
|
|
|
2485
2508
|
```bash
|