@stage5/lumine 0.3.36 → 0.3.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/admin.js CHANGED
@@ -3509,7 +3509,7 @@ export function parseAdminOperation(options) {
3509
3509
  // long as two GPT-6.1 Sol calls, so it gets its own timeout. The daily
3510
3510
  // website management run calls it in a bounded loop (Mikey, 2026-09-28).
3511
3511
  const usage =
3512
- "Usage: lumine admin reward-bank coverage <buildId> [--max-step 3] | seed <buildId> [--max-step 3] [--identity zero|ciel|auto] | report <buildId> [--days 1].";
3512
+ "Usage: lumine admin reward-bank coverage <buildId> [--max-step 3] | seed <buildId> [--max-step 3] [--identity zero|ciel|auto] | report <buildId> [--days 1] | question <buildId> --question-id <id> | repair <buildId> --file <repair.json> [--dry-run].";
3513
3513
  const buildId = parseRequiredInteger(
3514
3514
  target || options.adminBuild,
3515
3515
  "Build ID",
@@ -3519,6 +3519,59 @@ export function parseAdminOperation(options) {
3519
3519
  ? parseRequiredInteger(options.adminMaxStep, "--max-step", 1)
3520
3520
  : 3;
3521
3521
  if (maxStep > 10) throw cliValidationError("--max-step must be at most 10.");
3522
+ if (action === "question") {
3523
+ const questionId = parseRequiredInteger(
3524
+ options.adminQuestionId,
3525
+ "--question-id",
3526
+ 1,
3527
+ );
3528
+ return readOperation(
3529
+ "reward-bank.question",
3530
+ withQuery("/cli/admin/reward-bank/question", {
3531
+ buildId: String(buildId),
3532
+ questionId: String(questionId),
3533
+ }),
3534
+ { requiresRun: false },
3535
+ );
3536
+ }
3537
+ if (action === "repair") {
3538
+ const repair = readAdminJsonFile(
3539
+ options.adminFile,
3540
+ "--file <repair.json>",
3541
+ { maxBytes: 16 * 1024 },
3542
+ );
3543
+ const fields = ["questionId", "expectedFingerprint", "answer", "reason"];
3544
+ if (
3545
+ !repair ||
3546
+ typeof repair !== "object" ||
3547
+ Array.isArray(repair) ||
3548
+ Object.keys(repair).some((key) => !fields.includes(key)) ||
3549
+ !Number.isSafeInteger(repair.questionId) ||
3550
+ repair.questionId <= 0 ||
3551
+ typeof repair.answer !== "number" ||
3552
+ !Number.isFinite(repair.answer) ||
3553
+ typeof repair.expectedFingerprint !== "string" ||
3554
+ !/^[a-f0-9]{64}$/.test(repair.expectedFingerprint) ||
3555
+ typeof repair.reason !== "string" ||
3556
+ !repair.reason.trim() ||
3557
+ repair.reason.length > 2000
3558
+ ) {
3559
+ throw cliValidationError(
3560
+ "Repair JSON needs questionId, the inspected expectedFingerprint, a finite numeric answer and a reason (1–2000 characters); no other fields.",
3561
+ );
3562
+ }
3563
+ return writeOperation(
3564
+ "reward-bank.repair",
3565
+ "POST",
3566
+ "/cli/admin/reward-bank/repair",
3567
+ {
3568
+ buildId,
3569
+ repair,
3570
+ dryRun: Boolean(options.adminDryRun),
3571
+ },
3572
+ { requiresRun: false, mutates: !options.adminDryRun },
3573
+ );
3574
+ }
3522
3575
  if (action === "report") {
3523
3576
  const days = options.adminDays
3524
3577
  ? parseRequiredInteger(options.adminDays, "--days", 1)
@@ -7385,6 +7438,26 @@ function printAdminResult({ operation, result }) {
7385
7438
  }
7386
7439
  }
7387
7440
  if (operation.name.startsWith("reward-bank.")) {
7441
+ if (
7442
+ ["reward-bank.question", "reward-bank.repair"].includes(operation.name)
7443
+ ) {
7444
+ const question = data.question;
7445
+ console.log(
7446
+ `Question #${question.id} · Build ${question.buildId} · ${question.mode} · ${question.status}`,
7447
+ );
7448
+ console.log(question.prompt);
7449
+ console.log(`Stored answer: ${question.answer}`);
7450
+ if (question.hint) console.log(`Hint: ${question.hint}`);
7451
+ if (question.guide) console.log(`Guide: ${question.guide}`);
7452
+ console.log(`Fingerprint: ${data.fingerprint}`);
7453
+ console.log(`History: ${JSON.stringify(data.impact)}`);
7454
+ if (Object.hasOwn(data, "proposedAnswer"))
7455
+ console.log(
7456
+ `Proposed answer: ${data.proposedAnswer} · would change: ${data.wouldChange}`,
7457
+ );
7458
+ console.log(data.historyPolicy);
7459
+ return;
7460
+ }
7388
7461
  if (operation.name === "reward-bank.seed") {
7389
7462
  console.log(
7390
7463
  data.seeded
package/lib/commands.js CHANGED
@@ -2803,6 +2803,7 @@ export function parseArgs(args) {
2803
2803
  adminEditionId: raw.editionId ? String(raw.editionId) : "",
2804
2804
  adminLeaseToken: raw.leaseToken ? String(raw.leaseToken) : "",
2805
2805
  adminFile: raw.file ? String(raw.file) : "",
2806
+ adminQuestionId: raw.questionId ? String(raw.questionId) : "",
2806
2807
  rewardPreflight: Boolean(raw.preflight),
2807
2808
  rewardApprovalFile: raw.approvalFile ? String(raw.approvalFile) : "",
2808
2809
  // admin craft revise --item <id|name> [--why] [--emoji] [--category]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.3.36",
3
+ "version": "0.3.37",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1714,8 +1714,32 @@ them so the first learner on a unit never waits or pays.
1714
1714
  lumine admin reward-bank coverage <buildId> [--max-step 3] [--json] # read-only, no run
1715
1715
  lumine admin reward-bank seed <buildId> [--max-step 3] [--json] # delegated full run
1716
1716
  lumine admin reward-bank report <buildId> [--days 1] [--json] # read-only telemetry
1717
+ lumine admin reward-bank question <buildId> --question-id <id> [--json]
1718
+ lumine admin reward-bank repair <buildId> --file <repair.json> --dry-run [--json]
1719
+ lumine admin reward-bank repair <buildId> --file <repair.json> [--json]
1717
1720
  ```
1718
1721
 
1722
+ `question` returns the complete prompt, answer, hint and guide, question
1723
+ statistics, a canonical `fingerprint`, and aggregate challenge history. It is
1724
+ owner-only and does not need a daily run. A report excerpt is not an exact
1725
+ question inspection.
1726
+
1727
+ `repair` accepts exactly `{ "questionId": 41, "expectedFingerprint": "<hash
1728
+ from inspection>", "answer": 0.5, "reason": "Verified explanation and arithmetic" }`.
1729
+ Inspect and verify the mathematics before preparing a repair. `--dry-run`
1730
+ checks the same guard without writing. The real command locks and rechecks the
1731
+ question, changes only its numeric answer, returns canonical readback and
1732
+ records an idempotent transaction audit. A stale fingerprint is rejected;
1733
+ inspect again instead of guessing a new hash. Unknown plan fields are rejected.
1734
+ No model call or AI Energy charge is involved.
1735
+
1736
+ The repaired answer governs subsequent attempts, including already-issued
1737
+ unsolved challenges. Past attempts, grades, ladders, question statistics and
1738
+ rewards stay unchanged; the receipt states this explicitly. The history counts
1739
+ are observations, not a lock over every learner's activity. If the old answer
1740
+ affected a learner, inspect that history and prepare a separate, exact remedy;
1741
+ this command never guesses compensating rewards or rewrites past grades.
1742
+
1719
1743
  `report` covers the last `--days` (1-31): generation rounds by source
1720
1744
  (learner or seed) with outcomes (`accepted`, `off_brief`: the checker judged
1721
1745
  the question outside the brief's language, level or LaTeX rule;