@stage5/lumine 0.2.40 → 0.2.41

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
@@ -1,7 +1,21 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { createHash, randomUUID } from "node:crypto";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { assertAuthScope, resolveAuth } from "./auth.js";
4
4
  import { requestJson } from "./http.js";
5
+ import {
6
+ extractNewsClaim,
7
+ readAdminJsonFile,
8
+ validateNewsEditorial,
9
+ writeNewsClaimArtifacts,
10
+ } from "./admin-news.js";
11
+ import {
12
+ runAutomaticPagination,
13
+ runBatchSkips,
14
+ } from "./admin-workflows.js";
15
+ import {
16
+ parseBuildReviewReceipt,
17
+ runManagedBuildReview,
18
+ } from "./build-review.js";
5
19
 
6
20
  const MAX_EDITORIAL_FILE_BYTES = 256 * 1024;
7
21
  const MAX_COMPOSED_TEXT_FILE_BYTES = 64 * 1024;
@@ -64,6 +78,19 @@ function readEditorialFile(filePath) {
64
78
 
65
79
  export async function adminCommand(options) {
66
80
  const operation = parseAdminOperation(options);
81
+ if (operation.name === "news.validate") {
82
+ const validation = validateNewsEditorial({
83
+ claim: operation.claim,
84
+ editorial: operation.editorial,
85
+ });
86
+ const result = {
87
+ ok: true,
88
+ status: "success",
89
+ changed: false,
90
+ data: { validation },
91
+ };
92
+ return finishAdminOutput({ options, operation, result });
93
+ }
67
94
  const viewFilter = resolveOperatorViewFilter({
68
95
  operation,
69
96
  unviewed: options.adminUnviewed,
@@ -112,34 +139,76 @@ export async function adminCommand(options) {
112
139
  options.idempotencyKey || (operation.mutates ? `cli:${randomUUID()}` : "");
113
140
  let result;
114
141
  try {
115
- result = await requestJson({
116
- method: operation.method,
117
- url: `${options.apiUrl}${operation.path}`,
118
- authToken: auth.token,
119
- body: operation.body,
120
- headers: {
121
- ...(runId ? { "x-lumine-admin-run-id": String(runId) } : {}),
122
- ...(requestId ? { "x-lumine-idempotency-key": requestId } : {}),
123
- },
124
- timeoutMs: options.timeoutMs,
125
- });
126
- if (operation.name === "builds.candidates") {
127
- result = normalizeAdminBuildCandidatesResult({
128
- result,
129
- siteUrl: options.siteUrl,
142
+ const fetchOperation = async (requestPath = operation.path) =>
143
+ requestJson({
144
+ method: operation.method,
145
+ url: `${options.apiUrl}${requestPath}`,
146
+ authToken: auth.token,
147
+ body: operation.body,
148
+ headers: {
149
+ ...(runId ? { "x-lumine-admin-run-id": String(runId) } : {}),
150
+ ...(requestId ? { "x-lumine-idempotency-key": requestId } : {}),
151
+ },
152
+ timeoutMs: options.timeoutMs,
130
153
  });
131
- }
132
- if (recommendationContentTypes) {
133
- result = filterRecommendationQueueResult({
134
- result,
135
- contentTypes: recommendationContentTypes,
154
+ const transformResult = (rawResult) =>
155
+ transformAdminResult({
156
+ operation,
157
+ result: rawResult,
158
+ options,
159
+ recommendationContentTypes,
160
+ viewFilter,
136
161
  });
137
- }
138
- if (viewFilter) {
139
- result = filterListResultByOperatorView({ result, viewFilter });
162
+ if (operation.name === "build.review") {
163
+ result = await runManagedBuildReview({
164
+ options,
165
+ authToken: auth.token,
166
+ buildId: operation.buildId,
167
+ });
168
+ } else if (operation.name === "post.skip-batch") {
169
+ result = await runBatchSkips({
170
+ options,
171
+ authToken: auth.token,
172
+ runId,
173
+ parseTarget: parseRecommendationTarget,
174
+ });
175
+ } else if (options.adminAll) {
176
+ if (options.adminCursor) {
177
+ throw cliValidationError(
178
+ "Use --resume with the scan checkpoint instead of combining --all with --cursor.",
179
+ );
180
+ }
181
+ result = await runAutomaticPagination({
182
+ options,
183
+ operation,
184
+ runId,
185
+ fetchPage: fetchOperation,
186
+ transformPage: transformResult,
187
+ recordCoverage: async (coverage) =>
188
+ requestJson({
189
+ method: "POST",
190
+ url: `${options.apiUrl}/cli/admin/daily-runs/coverage`,
191
+ authToken: auth.token,
192
+ body: coverage,
193
+ headers: {
194
+ "x-lumine-admin-run-id": String(runId),
195
+ "x-lumine-idempotency-key": `cli:queue-coverage:${runId}:${adminValueFingerprint(coverage).slice(0, 32)}`,
196
+ },
197
+ timeoutMs: options.timeoutMs,
198
+ }),
199
+ });
200
+ } else {
201
+ if (options.adminResume) {
202
+ throw cliValidationError("--resume requires --all or post skip-batch.");
203
+ }
204
+ result = transformResult(await fetchOperation());
140
205
  }
141
206
  } catch (error) {
142
- if (operation.mutates && requestId) {
207
+ if (
208
+ operation.mutates &&
209
+ requestId &&
210
+ operation.name !== "post.skip-batch"
211
+ ) {
143
212
  const retryInstruction = `Retry with --idempotency-key ${requestId}.`;
144
213
  error.data = error.data || {
145
214
  ok: false,
@@ -175,6 +244,81 @@ export async function adminCommand(options) {
175
244
  expectedContent: operation.body.content,
176
245
  });
177
246
  }
247
+ if (operation.name === "news.claim") {
248
+ const artifacts = writeNewsClaimArtifacts({
249
+ result,
250
+ outputPath: options.adminOutput,
251
+ scaffoldPath: options.adminScaffoldFile,
252
+ });
253
+ result = {
254
+ ...result,
255
+ data: { ...(result.data || {}), artifacts },
256
+ };
257
+ }
258
+ return finishAdminOutput({ options, operation, result });
259
+ }
260
+
261
+ function transformAdminResult({
262
+ operation,
263
+ result,
264
+ options,
265
+ recommendationContentTypes,
266
+ viewFilter,
267
+ }) {
268
+ let transformed = result;
269
+ if (operation.name === "recommendations.list") {
270
+ assertRecommendationWindowResult({ operation, result: transformed });
271
+ }
272
+ if (operation.name === "builds.candidates") {
273
+ transformed = normalizeAdminBuildCandidatesResult({
274
+ result: transformed,
275
+ siteUrl: options.siteUrl,
276
+ });
277
+ }
278
+ if (recommendationContentTypes) {
279
+ transformed = filterRecommendationQueueResult({
280
+ result: transformed,
281
+ contentTypes: recommendationContentTypes,
282
+ });
283
+ }
284
+ if (viewFilter) {
285
+ transformed = filterListResultByOperatorView({
286
+ result: transformed,
287
+ viewFilter,
288
+ });
289
+ }
290
+ return transformed;
291
+ }
292
+
293
+ export function assertRecommendationWindowResult({ operation, result }) {
294
+ const mode = operation?.pagination?.coverageMode;
295
+ if (!mode || mode === "legacy") return;
296
+ const after = result?.data?.pagination?.after;
297
+ const snapshotTimeStamp = result?.data?.pagination?.snapshotTimeStamp;
298
+ if (
299
+ !Number.isSafeInteger(after) ||
300
+ after < 0 ||
301
+ !Number.isSafeInteger(snapshotTimeStamp) ||
302
+ snapshotTimeStamp < 0
303
+ ) {
304
+ const error = new Error(
305
+ "The deployed API did not confirm the bounded recommendation window and snapshot. Deploy the matching API before using this CLI; use --include-legacy only for an intentional historical scan.",
306
+ );
307
+ error.code = "LUMINE_ADMIN_RECOMMENDATION_WINDOW_UNSUPPORTED";
308
+ error.data = {
309
+ ok: false,
310
+ status: "validation_error",
311
+ error: {
312
+ code: error.code,
313
+ message: error.message,
314
+ details: { requestedMode: mode },
315
+ },
316
+ };
317
+ throw error;
318
+ }
319
+ }
320
+
321
+ function finishAdminOutput({ options, operation, result }) {
178
322
  if (options.json) {
179
323
  console.log(JSON.stringify(result));
180
324
  return result;
@@ -460,6 +604,31 @@ export function parseAdminOperation(options) {
460
604
  if (action === "status") {
461
605
  return readOperation("daily-run.status", "/cli/admin/daily-runs/status");
462
606
  }
607
+ if (action === "report") {
608
+ return readOperation("daily-run.report", "/cli/admin/daily-runs/report");
609
+ }
610
+ if (action === "escalation" && target === "add") {
611
+ const rawTarget = String(options.adminTarget || "").trim();
612
+ const summary = String(options.note || "").trim();
613
+ if (!rawTarget || !summary) {
614
+ throw cliValidationError(
615
+ "Pass a concrete --target and --note when recording a run escalation.",
616
+ );
617
+ }
618
+ const parsedTarget = parseAdminEscalationTarget(rawTarget);
619
+ return writeOperation(
620
+ "daily-run.escalation.add",
621
+ "POST",
622
+ "/cli/admin/daily-runs/escalations",
623
+ {
624
+ targetType: parsedTarget.targetType || undefined,
625
+ targetId: parsedTarget.targetId || undefined,
626
+ url: parsedTarget.url || undefined,
627
+ summary,
628
+ severity: options.adminSeverity || "attention",
629
+ },
630
+ );
631
+ }
463
632
  if (action === "complete" || action === "fail") {
464
633
  return writeOperation(
465
634
  `daily-run.${action}`,
@@ -478,6 +647,7 @@ export function parseAdminOperation(options) {
478
647
  if (kind !== "recommend") {
479
648
  throw cliValidationError("--kind currently supports only recommend.");
480
649
  }
650
+ const recommendationWindow = parseRecommendationWindow(options);
481
651
  return readOperation(
482
652
  "recommendations.list",
483
653
  withQuery("/cli/admin/recommendations", {
@@ -485,7 +655,33 @@ export function parseAdminOperation(options) {
485
655
  contentTypes: options.adminContentTypes,
486
656
  cursor: options.adminCursor,
487
657
  limit: options.limit,
658
+ sinceRun: recommendationWindow.mode === "since-run" ? "true" : "",
659
+ after:
660
+ recommendationWindow.mode === "after"
661
+ ? recommendationWindow.after
662
+ : "",
663
+ includeLegacy:
664
+ recommendationWindow.mode === "legacy" ? "true" : "",
488
665
  }),
666
+ {
667
+ pagination: {
668
+ collectionKey: "items",
669
+ coverageQueue: "recommendations",
670
+ coverageMode: recommendationWindow.mode,
671
+ after:
672
+ recommendationWindow.mode === "after"
673
+ ? parseAfterForCoverage(recommendationWindow.after)
674
+ : null,
675
+ filters: {
676
+ contentTypes: options.adminContentTypes || null,
677
+ operatorView: options.adminUnviewed
678
+ ? "unviewed"
679
+ : options.adminViewed
680
+ ? "viewed"
681
+ : null,
682
+ },
683
+ },
684
+ },
489
685
  );
490
686
  }
491
687
 
@@ -498,8 +694,33 @@ export function parseAdminOperation(options) {
498
694
  cursor: options.adminCursor,
499
695
  limit: options.limit,
500
696
  }),
697
+ {
698
+ pagination: {
699
+ collectionKey: "builds",
700
+ coverageQueue: "builds",
701
+ coverageMode: "all",
702
+ after: null,
703
+ filters: { sort: "recent", scope: "all" },
704
+ },
705
+ },
501
706
  );
502
707
  }
708
+ if (namespace === "builds" && action === "review") {
709
+ const parsed = parseAdminCommentTarget({ target, explicitType: "build" });
710
+ if (parsed.type !== "build") {
711
+ throw cliValidationError(
712
+ "Build review targets a Build URL or build:<id>.",
713
+ );
714
+ }
715
+ return {
716
+ name: "build.review",
717
+ method: "GET",
718
+ path: "",
719
+ body: undefined,
720
+ mutates: false,
721
+ buildId: parsed.id,
722
+ };
723
+ }
503
724
 
504
725
  if (
505
726
  namespace === "subjects" &&
@@ -524,6 +745,7 @@ export function parseAdminOperation(options) {
524
745
  cursor: options.adminCursor,
525
746
  limit: options.limit,
526
747
  }),
748
+ { pagination: { collectionKey: "comments" } },
527
749
  );
528
750
  }
529
751
  if (action === "reveal") {
@@ -591,6 +813,7 @@ export function parseAdminOperation(options) {
591
813
  cursor: options.adminCursor,
592
814
  limit: options.limit,
593
815
  }),
816
+ { pagination: { collectionKey: "comments" } },
594
817
  );
595
818
  }
596
819
  if (action === "recommend") {
@@ -613,6 +836,20 @@ export function parseAdminOperation(options) {
613
836
  { reason: options.adminReason || undefined },
614
837
  );
615
838
  }
839
+ if (action === "skip-batch") {
840
+ if (!options.adminTargetFile) {
841
+ throw cliValidationError(
842
+ "Pass batch skip targets with --target-file <file>.",
843
+ );
844
+ }
845
+ return {
846
+ name: "post.skip-batch",
847
+ method: "POST",
848
+ path: "",
849
+ body: undefined,
850
+ mutates: true,
851
+ };
852
+ }
616
853
  if (action === "reward") {
617
854
  const parsedTarget = parseRecommendationTarget({
618
855
  target,
@@ -653,22 +890,41 @@ export function parseAdminOperation(options) {
653
890
  ...(repairDate ? { date: repairDate } : {}),
654
891
  });
655
892
  }
656
- if (action === "submit") {
657
- const editionId = parseRequiredInteger(
658
- options.adminEditionId,
659
- "--edition-id",
660
- 1,
893
+ if (action === "validate") {
894
+ const claim = extractNewsClaim(
895
+ readAdminJsonFile(options.adminClaimFile, "--claim <claim.json>"),
661
896
  );
662
- const leaseToken = String(options.adminLeaseToken || "").trim();
897
+ return {
898
+ name: "news.validate",
899
+ local: true,
900
+ mutates: false,
901
+ claim,
902
+ editorial: readEditorialFile(options.adminFile),
903
+ };
904
+ }
905
+ if (action === "submit") {
906
+ const claim = options.adminClaimFile
907
+ ? extractNewsClaim(
908
+ readAdminJsonFile(options.adminClaimFile, "--claim <claim.json>"),
909
+ )
910
+ : null;
911
+ const editionId = claim
912
+ ? claim.editionId
913
+ : parseRequiredInteger(options.adminEditionId, "--edition-id", 1);
914
+ const leaseToken = claim
915
+ ? claim.leaseToken
916
+ : String(options.adminLeaseToken || "").trim();
663
917
  if (!leaseToken) {
664
918
  throw cliValidationError(
665
919
  "Pass the claim's lease token with --lease-token <token>.",
666
920
  );
667
921
  }
922
+ const editorial = readEditorialFile(options.adminFile);
923
+ if (claim) validateNewsEditorial({ claim, editorial });
668
924
  return writeOperation("news.submit", "POST", "/cli/admin/news/submit", {
669
925
  editionId,
670
926
  leaseToken,
671
- editorial: readEditorialFile(options.adminFile),
927
+ editorial,
672
928
  model: options.model || undefined,
673
929
  });
674
930
  }
@@ -776,6 +1032,7 @@ export function parseAdminOperation(options) {
776
1032
  limit: options.limit,
777
1033
  full: options.adminFull ? "true" : "",
778
1034
  }),
1035
+ { pagination: { collectionKey: "events" } },
779
1036
  );
780
1037
  }
781
1038
 
@@ -800,20 +1057,46 @@ export function parseAdminOperation(options) {
800
1057
  const buildReviewMethod = options.adminBuildReviewMethod
801
1058
  ? parseAdminBuildReviewMethod(options.adminBuildReviewMethod)
802
1059
  : undefined;
1060
+ const reviewReceipt = options.adminReviewReceipt
1061
+ ? parseBuildReviewReceipt(options.adminReviewReceipt)
1062
+ : null;
1063
+ if (
1064
+ reviewReceipt &&
1065
+ (reviewedBuildVersionId || buildReviewMethod)
1066
+ ) {
1067
+ throw cliValidationError(
1068
+ "Pass either --review-receipt or manual --reviewed-version/--reviewed-via evidence, not both.",
1069
+ );
1070
+ }
1071
+ const confirmedBuildVersionId = reviewReceipt
1072
+ ? Number(reviewReceipt.publishedArtifactVersionId)
1073
+ : reviewedBuildVersionId;
1074
+ const confirmedBuildReviewMethod = reviewReceipt
1075
+ ? "runtime"
1076
+ : buildReviewMethod;
1077
+ if (
1078
+ reviewReceipt &&
1079
+ parsedTarget.type === "build" &&
1080
+ Number(reviewReceipt.buildId) !== parsedTarget.id
1081
+ ) {
1082
+ throw cliValidationError(
1083
+ "The review receipt belongs to a different Build.",
1084
+ );
1085
+ }
803
1086
  if (parsedTarget.type === "build") {
804
1087
  if (!options.adminFile) {
805
1088
  throw cliValidationError(
806
1089
  "Build comments are management-agent composed only; pass --file <comment.md> after reviewing the project.",
807
1090
  );
808
1091
  }
809
- if (!reviewedBuildVersionId || !buildReviewMethod) {
1092
+ if (!confirmedBuildVersionId || !confirmedBuildReviewMethod) {
810
1093
  throw cliValidationError(
811
1094
  "After reviewing the project, pass both --reviewed-version <published-artifact-id> and --reviewed-via runtime|code.",
812
1095
  );
813
1096
  }
814
1097
  } else if (
815
1098
  parsedTarget.type !== "comment" &&
816
- (reviewedBuildVersionId || buildReviewMethod)
1099
+ (confirmedBuildVersionId || confirmedBuildReviewMethod)
817
1100
  ) {
818
1101
  throw cliValidationError(
819
1102
  "Build review evidence applies only to build:<id> or a comment:<id> inside a Build.",
@@ -832,8 +1115,12 @@ export function parseAdminOperation(options) {
832
1115
  ...(options.adminFile
833
1116
  ? { content: readComposedTextFile(options.adminFile) }
834
1117
  : {}),
835
- ...(reviewedBuildVersionId ? { reviewedBuildVersionId } : {}),
836
- ...(buildReviewMethod ? { buildReviewMethod } : {}),
1118
+ ...(confirmedBuildVersionId
1119
+ ? { reviewedBuildVersionId: confirmedBuildVersionId }
1120
+ : {}),
1121
+ ...(confirmedBuildReviewMethod
1122
+ ? { buildReviewMethod: confirmedBuildReviewMethod }
1123
+ : {}),
837
1124
  },
838
1125
  );
839
1126
  }
@@ -890,6 +1177,17 @@ function subjectsListOperation(options) {
890
1177
  effort: options.adminEffort,
891
1178
  limit: options.limit,
892
1179
  }),
1180
+ {
1181
+ pagination: {
1182
+ collectionKey: "subjects",
1183
+ coverageQueue: "subjects",
1184
+ coverageMode: options.adminAfter ? "after" : "all",
1185
+ after: options.adminAfter
1186
+ ? parseAfterForCoverage(options.adminAfter)
1187
+ : null,
1188
+ filters: { effort: options.adminEffort || "all" },
1189
+ },
1190
+ },
893
1191
  );
894
1192
  }
895
1193
 
@@ -997,6 +1295,35 @@ function recommendOperation(target, options) {
997
1295
  );
998
1296
  }
999
1297
 
1298
+ export function parseRecommendationWindow(options) {
1299
+ const selected = [
1300
+ options.adminSinceRun ? "since-run" : "",
1301
+ options.adminAfter ? "after" : "",
1302
+ options.adminIncludeLegacy ? "legacy" : "",
1303
+ ].filter(Boolean);
1304
+ if (selected.length > 1) {
1305
+ throw cliValidationError(
1306
+ "Choose one recommendation window: --since-run, --after, or --include-legacy.",
1307
+ );
1308
+ }
1309
+ const mode = selected[0] || "since-run";
1310
+ if (mode === "after") parseAfterForCoverage(options.adminAfter);
1311
+ return { mode, after: mode === "after" ? options.adminAfter : "" };
1312
+ }
1313
+
1314
+ function parseAfterForCoverage(value) {
1315
+ const raw = String(value || "").trim();
1316
+ if (/^\d+$/.test(raw)) {
1317
+ const parsed = Number(raw);
1318
+ if (Number.isSafeInteger(parsed) && parsed >= 0) return parsed;
1319
+ }
1320
+ const parsedMs = Date.parse(raw);
1321
+ if (Number.isFinite(parsedMs)) return Math.floor(parsedMs / 1000);
1322
+ throw cliValidationError(
1323
+ "--after must be a Unix timestamp or ISO-8601 date.",
1324
+ );
1325
+ }
1326
+
1000
1327
  export function parseSubjectId(value) {
1001
1328
  const normalized = String(value || "").trim();
1002
1329
  const match = normalized.match(/(?:^subject:|\/subjects\/)(\d+)(?:[/?#]|$)/i);
@@ -1090,6 +1417,42 @@ export function parseAdminCommentTarget({ target, explicitType = "" }) {
1090
1417
  return baseTarget;
1091
1418
  }
1092
1419
 
1420
+ export function parseAdminEscalationTarget(value) {
1421
+ const normalized = String(value || "").trim();
1422
+ const genericTarget = normalized.match(
1423
+ /^([a-zA-Z][a-zA-Z0-9_-]{0,39}):(\d+)$/,
1424
+ );
1425
+ if (genericTarget) {
1426
+ return {
1427
+ targetType: genericTarget[1],
1428
+ targetId: parseRequiredInteger(genericTarget[2], "escalation target ID", 1),
1429
+ url: null,
1430
+ };
1431
+ }
1432
+ if (/^https:\/\//i.test(normalized)) {
1433
+ let parsedUrl;
1434
+ try {
1435
+ parsedUrl = new URL(normalized);
1436
+ } catch {
1437
+ throw cliValidationError("The escalation target URL is invalid.");
1438
+ }
1439
+ if (parsedUrl.protocol !== "https:") {
1440
+ throw cliValidationError("The escalation target URL must use HTTPS.");
1441
+ }
1442
+ if (
1443
+ /\/(?:subjects|comments|ai-stories|daily-reflections|app|build)\/\d+(?:[/?#]|$)/i.test(
1444
+ normalized,
1445
+ )
1446
+ ) {
1447
+ const parsed = parseAdminCommentTarget({ target: normalized });
1448
+ return { targetType: parsed.type, targetId: parsed.id, url: normalized };
1449
+ }
1450
+ return { targetType: null, targetId: null, url: normalized };
1451
+ }
1452
+ const parsed = parseAdminCommentTarget({ target: normalized });
1453
+ return { targetType: parsed.type, targetId: parsed.id, url: null };
1454
+ }
1455
+
1093
1456
  function parseAdminBuildReviewMethod(value) {
1094
1457
  const normalized = String(value || "")
1095
1458
  .trim()
@@ -1134,8 +1497,15 @@ export function formatAdminJsonError(error) {
1134
1497
  };
1135
1498
  }
1136
1499
 
1137
- function readOperation(name, path) {
1138
- return { name, method: "GET", path, body: undefined, mutates: false };
1500
+ function readOperation(name, path, extra = {}) {
1501
+ return {
1502
+ name,
1503
+ method: "GET",
1504
+ path,
1505
+ body: undefined,
1506
+ mutates: false,
1507
+ ...extra,
1508
+ };
1139
1509
  }
1140
1510
 
1141
1511
  function writeOperation(name, method, path, body) {
@@ -1276,8 +1646,73 @@ function cliValidationError(message) {
1276
1646
  return error;
1277
1647
  }
1278
1648
 
1649
+ function adminValueFingerprint(value) {
1650
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
1651
+ }
1652
+
1279
1653
  function printAdminResult({ operation, result }) {
1280
1654
  const data = result?.data || {};
1655
+ if (data.review) {
1656
+ console.log(
1657
+ `Confirmed Build #${data.review.buildId} runtime at published artifact #${data.review.publishedArtifactVersionId}.`,
1658
+ );
1659
+ console.log(`Screenshot: ${data.screenshotPath}`);
1660
+ console.log(`Review receipt: ${data.receiptPath}`);
1661
+ return;
1662
+ }
1663
+ if (data.validation) {
1664
+ console.log(
1665
+ `Editorial valid for edition #${data.validation.editionId}: ${data.validation.citedEventCount} cited and ${data.validation.coveredEventCount} covered event(s).`,
1666
+ );
1667
+ return;
1668
+ }
1669
+ if (data.batch) {
1670
+ console.log(
1671
+ `Skipped ${data.batch.completedCount} audited target(s); ${data.batch.changedCount} changed canonical state.`,
1672
+ );
1673
+ console.log(`Checkpoint: ${data.batch.checkpointPath}`);
1674
+ return;
1675
+ }
1676
+ if (data.report) {
1677
+ const report = data.report;
1678
+ console.log(
1679
+ `Run #${report.run.id}: ${report.mutations.successfulMutationCount} successful mutation(s), ${report.queueCoverage.length} queue coverage record(s), ${report.escalations.length} escalation(s).`,
1680
+ );
1681
+ for (const coverage of report.queueCoverage) {
1682
+ console.log(
1683
+ ` ${coverage.queue}: ${coverage.candidateCount} candidate(s), ${coverage.scannedCount} row(s) scanned across ${coverage.pages} page(s).`,
1684
+ );
1685
+ }
1686
+ for (const escalation of report.escalations) {
1687
+ const target =
1688
+ escalation.url ||
1689
+ `${escalation.targetType || "target"}:${escalation.targetId || "?"}`;
1690
+ console.log(
1691
+ ` ${String(escalation.severity || "attention").toUpperCase()} ${target} — ${escalation.summary}`,
1692
+ );
1693
+ }
1694
+ const surfaces = report.brief?.engagementPulse?.surfaces;
1695
+ if (surfaces && typeof surfaces === "object") {
1696
+ const deltas = Object.entries(surfaces)
1697
+ .filter(([, value]) => Number(value?.delta || 0) !== 0)
1698
+ .sort(
1699
+ ([, left], [, right]) =>
1700
+ Math.abs(Number(right?.delta || 0)) -
1701
+ Math.abs(Number(left?.delta || 0)),
1702
+ )
1703
+ .slice(0, 5)
1704
+ .map(
1705
+ ([name, value]) =>
1706
+ `${name} ${Number(value.delta) > 0 ? "+" : ""}${Number(value.delta)}`,
1707
+ );
1708
+ if (deltas.length) console.log(`Engagement deltas: ${deltas.join(", ")}.`);
1709
+ }
1710
+ const notableCount = Array.isArray(report.brief?.notableCandidates)
1711
+ ? report.brief.notableCandidates.length
1712
+ : 0;
1713
+ console.log(`Notable-user candidates in this brief: ${notableCount}.`);
1714
+ return;
1715
+ }
1281
1716
  if (data.bucket && Array.isArray(data.memberUserIds)) {
1282
1717
  const added = Array.isArray(data.accounts)
1283
1718
  ? `; added ${data.accounts.length} explicit account(s)`
@@ -1347,12 +1782,28 @@ function printAdminResult({ operation, result }) {
1347
1782
  return;
1348
1783
  }
1349
1784
  if (data.claim) {
1350
- console.log(
1351
- `Claimed edition #${data.claim.editionId} (${data.claim.dateKey}): ${data.claim.events.length} event(s); lease token ${data.claim.leaseToken}.`,
1352
- );
1353
- console.log(
1354
- `Write the editorial JSON, then run: lumine admin news submit --edition-id ${data.claim.editionId} --lease-token ${data.claim.leaseToken} --file editorial.json`,
1355
- );
1785
+ if (data.artifacts?.claimFile) {
1786
+ console.log(
1787
+ `Claimed edition #${data.claim.editionId} (${data.claim.dateKey}): ${data.claim.events.length} event(s).`,
1788
+ );
1789
+ console.log(`Claim file: ${data.artifacts.claimFile}`);
1790
+ if (data.artifacts?.scaffoldFile) {
1791
+ console.log(`Editorial scaffold: ${data.artifacts.scaffoldFile}`);
1792
+ }
1793
+ console.log(
1794
+ `Validate before submission: lumine admin news validate --claim ${data.artifacts.claimFile} --file ${data.artifacts.scaffoldFile || "editorial.json"}`,
1795
+ );
1796
+ console.log(
1797
+ `Submit the confirmed pair: lumine admin news submit --claim ${data.artifacts.claimFile} --file ${data.artifacts.scaffoldFile || "editorial.json"}`,
1798
+ );
1799
+ } else {
1800
+ console.log(
1801
+ `Claimed edition #${data.claim.editionId} (${data.claim.dateKey}): ${data.claim.events.length} event(s); lease token ${data.claim.leaseToken}.`,
1802
+ );
1803
+ console.log(
1804
+ `Write the editorial JSON, then run: lumine admin news submit --edition-id ${data.claim.editionId} --lease-token ${data.claim.leaseToken} --file editorial.json`,
1805
+ );
1806
+ }
1356
1807
  return;
1357
1808
  }
1358
1809
  if (data.newspaper) {