@stage5/lumine 0.2.65 → 0.2.66
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 +22 -3
- package/lib/admin-workflows.js +232 -16
- package/lib/admin.js +243 -28
- package/lib/commands.js +6 -1
- package/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +11 -7
- package/sdk/LUMINE_ADMIN.md +171 -42
package/README.md
CHANGED
|
@@ -350,6 +350,7 @@ lumine admin economy trace lock --days 3 \
|
|
|
350
350
|
lumine admin rescue wordle-audit --days 30 \
|
|
351
351
|
--reason "Identify recorded Wordle breaks and rescue status" --json
|
|
352
352
|
lumine admin daily-run start --identity auto --comment-mode off --json
|
|
353
|
+
lumine admin daily-run start --scope featured --identity auto --json
|
|
353
354
|
lumine admin sponsor applications list --status pending --json
|
|
354
355
|
lumine admin sponsor applications review 12 --decision approve \
|
|
355
356
|
--note "Approved for probationary duty" --json
|
|
@@ -378,6 +379,9 @@ lumine admin subject reveal 123 --json
|
|
|
378
379
|
lumine admin subject effort set 123 --level 3 --json
|
|
379
380
|
lumine admin subject creator set-made-by-poster 123 --json
|
|
380
381
|
lumine admin featured list --json
|
|
382
|
+
lumine admin featured history --subject-ids 50,40 --all --json
|
|
383
|
+
lumine admin featured add --subject-ids 50,40 \
|
|
384
|
+
--posted-after 2026-08-27T00:00:00+07:00 --json
|
|
381
385
|
lumine admin subject feature 123 --json
|
|
382
386
|
lumine admin subject unfeature 123 --json
|
|
383
387
|
lumine admin featured reorder --subject-ids 30,20,10 --json
|
|
@@ -412,7 +416,19 @@ and posts use the selected bot's server-owned canonical persona. Outside Build
|
|
|
412
416
|
threads, a later human reply is handled by Twinkle's existing autonomous
|
|
413
417
|
Zero/Ciel responder without Lumine remaining active.
|
|
414
418
|
|
|
415
|
-
|
|
419
|
+
`--scope full` is the default and authorizes the complete daily-management
|
|
420
|
+
workflow. `--scope featured` is a deliberately narrow run for a requested
|
|
421
|
+
Featured-only slice: it cannot comment, does not surface carry-over todos, does
|
|
422
|
+
not write full-run queue coverage, does not require sponsor review, and never
|
|
423
|
+
advances full-run review windows. Its dedicated start endpoint also prevents an
|
|
424
|
+
older API from silently interpreting it as a full run. The
|
|
425
|
+
atomic `featured add` verb preserves the supplied relevance order and requires
|
|
426
|
+
the server to prove both a strict posting-date boundary and complete
|
|
427
|
+
never-Featured history. `featured history` returns that evidence without the
|
|
428
|
+
large repeated board snapshots in the general audit trail.
|
|
429
|
+
|
|
430
|
+
Management agents also inspect recent public Build candidates during each full
|
|
431
|
+
daily review.
|
|
416
432
|
`builds review` opens one published app in an isolated temporary Chromium
|
|
417
433
|
profile, captures a screenshot and console evidence, verifies that the
|
|
418
434
|
published version stayed fixed, and writes a review receipt in a unique output
|
|
@@ -451,6 +467,9 @@ follows them automatically, fsyncs each confirmed page to a private NDJSON
|
|
|
451
467
|
candidate spool, saves only bounded cursor/boundary/count metadata in the
|
|
452
468
|
checkpoint, and records completed queue coverage in the run audit; `--resume`
|
|
453
469
|
verifies the confirmed spool prefix and continues the exact same request. The
|
|
470
|
+
automatic checkpoint name includes the exact request fingerprint (including
|
|
471
|
+
client-side view filters and result-transform inputs), and an
|
|
472
|
+
exclusive adjacent lock prevents concurrent processes from sharing it. The
|
|
454
473
|
final JSON contract still contains the complete collection, streamed from the
|
|
455
474
|
spool instead of accumulated in memory. With `--all --json`, bounded progress
|
|
456
475
|
goes to stderr so stdout remains one pipe-safe JSON value. Recommendation scans default to the previous completed
|
|
@@ -462,7 +481,7 @@ cursors are bound to the original date and effort filters.
|
|
|
462
481
|
`news claim` can write both the canonical leased digest and an editable
|
|
463
482
|
editorial scaffold. `news validate` is local and checks every citation and
|
|
464
483
|
quote before submission; `news submit --claim` reads the lease identity from
|
|
465
|
-
the claim file. Every `daily-run start` response includes writer-confirmed
|
|
484
|
+
the claim file. Every full `daily-run start` response includes writer-confirmed
|
|
466
485
|
unfinished private todos, with once-per-run surfacing telemetry, so an agent
|
|
467
486
|
can resume earlier work without relying on conversation memory. Record progress
|
|
468
487
|
with `todo update`; completing a run does not complete its todos. Experiments
|
|
@@ -470,7 +489,7 @@ must meet their stated acceptance criteria—lower AI cost with weaker user
|
|
|
470
489
|
responses is not a successful optimization. `daily-run report` summarizes
|
|
471
490
|
confirmed mutations, completed queue coverage, explicitly recorded escalations,
|
|
472
491
|
unfinished todos, sponsor-integrity state, and the run brief before the run is
|
|
473
|
-
completed. A run cannot complete until its bounded sponsor-integrity snapshot
|
|
492
|
+
completed. A full run cannot complete until its bounded sponsor-integrity snapshot
|
|
474
493
|
has been scanned and every selected case is cleared or disqualified. `hold` and
|
|
475
494
|
`flag` deliberately keep the run open for human judgment; scans never suspend,
|
|
476
495
|
revoke, or disqualify a sponsor automatically.
|
package/lib/admin-workflows.js
CHANGED
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
chmodSync,
|
|
4
4
|
closeSync,
|
|
5
5
|
createReadStream,
|
|
6
|
+
fchmodSync,
|
|
6
7
|
fsyncSync,
|
|
8
|
+
linkSync,
|
|
7
9
|
lstatSync,
|
|
8
10
|
mkdirSync,
|
|
9
11
|
openSync,
|
|
@@ -74,9 +76,198 @@ function pathWithCursor(requestPath, cursor) {
|
|
|
74
76
|
return `${url.pathname}${url.search}`;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
function defaultCheckpointPath({
|
|
79
|
+
function defaultCheckpointPath({
|
|
80
|
+
runId,
|
|
81
|
+
operationName,
|
|
82
|
+
operationFingerprint = "",
|
|
83
|
+
}) {
|
|
78
84
|
const safeName = String(operationName).replace(/[^a-z0-9_.-]+/gi, "-");
|
|
79
|
-
|
|
85
|
+
const fingerprintSuffix = operationFingerprint
|
|
86
|
+
? `-${operationFingerprint}`
|
|
87
|
+
: "";
|
|
88
|
+
return path.join(
|
|
89
|
+
os.tmpdir(),
|
|
90
|
+
`lumine-admin-run-${runId}-${safeName}${fingerprintSuffix}.json`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function resolveAutomaticCheckpointPath({
|
|
95
|
+
options,
|
|
96
|
+
operation,
|
|
97
|
+
runId,
|
|
98
|
+
operationFingerprint,
|
|
99
|
+
}) {
|
|
100
|
+
if (String(options.adminCheckpoint || "").trim()) {
|
|
101
|
+
return path.resolve(options.adminCheckpoint);
|
|
102
|
+
}
|
|
103
|
+
const keyedPath = defaultCheckpointPath({
|
|
104
|
+
runId,
|
|
105
|
+
operationName: operation.name,
|
|
106
|
+
operationFingerprint,
|
|
107
|
+
});
|
|
108
|
+
if (
|
|
109
|
+
options.adminResume &&
|
|
110
|
+
!pathEntryExists(keyedPath) &&
|
|
111
|
+
!pathEntryExists(`${keyedPath}.lock`) &&
|
|
112
|
+
!pathEntryExists(`${keyedPath}.lock.reclaim`)
|
|
113
|
+
) {
|
|
114
|
+
const legacyPath = defaultCheckpointPath({
|
|
115
|
+
runId,
|
|
116
|
+
operationName: operation.name,
|
|
117
|
+
});
|
|
118
|
+
if (pathEntryExists(legacyPath)) return legacyPath;
|
|
119
|
+
}
|
|
120
|
+
return keyedPath;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function pathEntryExists(filePath) {
|
|
124
|
+
try {
|
|
125
|
+
lstatSync(filePath);
|
|
126
|
+
return true;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error?.code === "ENOENT") return false;
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function acquireCheckpointLock(checkpointPath, operationFingerprint) {
|
|
134
|
+
const lockPath = `${checkpointPath}.lock`;
|
|
135
|
+
const reclaimPath = `${lockPath}.reclaim`;
|
|
136
|
+
mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
137
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
138
|
+
if (pathEntryExists(reclaimPath)) {
|
|
139
|
+
throw validationError(
|
|
140
|
+
`Another Lumine admin scan is recovering checkpoint ${checkpointPath}. Wait for it to finish or choose a different --checkpoint.`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const token = randomUUID();
|
|
144
|
+
let descriptor;
|
|
145
|
+
let ownsLock = false;
|
|
146
|
+
try {
|
|
147
|
+
descriptor = openSync(lockPath, "wx", 0o600);
|
|
148
|
+
ownsLock = true;
|
|
149
|
+
fchmodSync(descriptor, 0o600);
|
|
150
|
+
writeFileSync(
|
|
151
|
+
descriptor,
|
|
152
|
+
`${JSON.stringify({
|
|
153
|
+
kind: "admin-pagination-lock",
|
|
154
|
+
pid: process.pid,
|
|
155
|
+
token,
|
|
156
|
+
operationFingerprint,
|
|
157
|
+
createdAt: new Date().toISOString(),
|
|
158
|
+
})}\n`,
|
|
159
|
+
);
|
|
160
|
+
fsyncSync(descriptor);
|
|
161
|
+
closeSync(descriptor);
|
|
162
|
+
descriptor = undefined;
|
|
163
|
+
// A stale-lock reclaimer can begin after the check above but before this
|
|
164
|
+
// O_EXCL create. It hard-links the exact old inode before removal. If
|
|
165
|
+
// that claim still exists, relinquish our new lock rather than allowing
|
|
166
|
+
// two owners to cross during the handoff.
|
|
167
|
+
if (pathEntryExists(reclaimPath)) {
|
|
168
|
+
releaseCheckpointLock({ lockPath, token });
|
|
169
|
+
ownsLock = false;
|
|
170
|
+
throw validationError(
|
|
171
|
+
`Another Lumine admin scan is recovering checkpoint ${checkpointPath}. Retry after it finishes.`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return { lockPath, token };
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (descriptor !== undefined) {
|
|
177
|
+
try {
|
|
178
|
+
closeSync(descriptor);
|
|
179
|
+
} catch {
|
|
180
|
+
// The failed lock initialization may already have closed it.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (ownsLock) {
|
|
184
|
+
try {
|
|
185
|
+
unlinkSync(lockPath);
|
|
186
|
+
} catch {
|
|
187
|
+
// A failed initialization may leave no owned lock to remove.
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (error?.code !== "EEXIST") throw error;
|
|
191
|
+
if (attempt > 0 || !removeDeadCheckpointLock(lockPath)) {
|
|
192
|
+
throw validationError(
|
|
193
|
+
`Another Lumine admin scan owns checkpoint ${checkpointPath}. Wait for it to finish or choose a different --checkpoint.`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
throw validationError(
|
|
199
|
+
`Could not acquire the pagination checkpoint lock for ${checkpointPath}.`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function removeDeadCheckpointLock(lockPath) {
|
|
204
|
+
const reclaimPath = `${lockPath}.reclaim`;
|
|
205
|
+
let ownsReclaim = false;
|
|
206
|
+
try {
|
|
207
|
+
// Creating a fixed-name hard link is an atomic claim on the exact lock
|
|
208
|
+
// inode. It serializes reclaimers and lets us prove we are not unlinking a
|
|
209
|
+
// replacement lock created after another process removed the stale one.
|
|
210
|
+
linkSync(lockPath, reclaimPath);
|
|
211
|
+
ownsReclaim = true;
|
|
212
|
+
const fileState = lstatSync(reclaimPath);
|
|
213
|
+
if (!fileState.isFile() || fileState.isSymbolicLink()) return false;
|
|
214
|
+
if (fileState.size > 16 * 1024) return false;
|
|
215
|
+
const state = JSON.parse(readFileSync(reclaimPath, "utf8"));
|
|
216
|
+
if (
|
|
217
|
+
state?.kind !== "admin-pagination-lock" ||
|
|
218
|
+
!/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i.test(
|
|
219
|
+
String(state?.token || ""),
|
|
220
|
+
) ||
|
|
221
|
+
!/^[a-f0-9]{64}$/i.test(String(state?.operationFingerprint || ""))
|
|
222
|
+
) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const pid = Number(state?.pid);
|
|
226
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
227
|
+
try {
|
|
228
|
+
process.kill(pid, 0);
|
|
229
|
+
return false;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (error?.code !== "ESRCH") return false;
|
|
232
|
+
}
|
|
233
|
+
const claimedState = lstatSync(reclaimPath);
|
|
234
|
+
const currentState = lstatSync(lockPath);
|
|
235
|
+
if (
|
|
236
|
+
claimedState.dev !== currentState.dev ||
|
|
237
|
+
claimedState.ino !== currentState.ino
|
|
238
|
+
) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
unlinkSync(lockPath);
|
|
242
|
+
return true;
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
} finally {
|
|
246
|
+
if (ownsReclaim) {
|
|
247
|
+
try {
|
|
248
|
+
unlinkSync(reclaimPath);
|
|
249
|
+
} catch {
|
|
250
|
+
// A failed or completed reclaim leaves the current lock untouched.
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function releaseCheckpointLock(lock) {
|
|
257
|
+
try {
|
|
258
|
+
const fileState = lstatSync(lock.lockPath);
|
|
259
|
+
if (!fileState.isFile() || fileState.isSymbolicLink()) return;
|
|
260
|
+
const saved = JSON.parse(readFileSync(lock.lockPath, "utf8"));
|
|
261
|
+
if (saved?.token === lock.token && Number(saved?.pid) === process.pid) {
|
|
262
|
+
unlinkSync(lock.lockPath);
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error?.code !== "ENOENT") {
|
|
266
|
+
process.stderr.write(
|
|
267
|
+
`Lumine admin: could not release pagination lock ${lock.lockPath} (${error?.message || error}).\n`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
80
271
|
}
|
|
81
272
|
|
|
82
273
|
function operationFingerprintValue({
|
|
@@ -91,6 +282,13 @@ function operationFingerprintValue({
|
|
|
91
282
|
apiUrl: String(options.apiUrl || "").replace(/\/$/, ""),
|
|
92
283
|
name: operation.name,
|
|
93
284
|
path: pathWithCursor(operation.path, ""),
|
|
285
|
+
...(operation.name === "builds.candidates"
|
|
286
|
+
? {
|
|
287
|
+
resultTransform: {
|
|
288
|
+
siteUrl: String(options.siteUrl || "").replace(/\/$/, ""),
|
|
289
|
+
},
|
|
290
|
+
}
|
|
291
|
+
: {}),
|
|
94
292
|
pagination: {
|
|
95
293
|
collectionKey: operation.pagination.collectionKey,
|
|
96
294
|
coverageQueue: operation.pagination.coverageQueue || null,
|
|
@@ -604,22 +802,46 @@ function aggregatePageResult({ operation, lastPage, checkpointPath, state }) {
|
|
|
604
802
|
});
|
|
605
803
|
}
|
|
606
804
|
|
|
607
|
-
export async function runAutomaticPagination({
|
|
805
|
+
export async function runAutomaticPagination(args) {
|
|
806
|
+
const { options, operation, runId } = args;
|
|
807
|
+
if (!operation.pagination) {
|
|
808
|
+
throw validationError("--all is supported only by paginated admin listings.");
|
|
809
|
+
}
|
|
810
|
+
const operationFingerprint = operationFingerprintValue({
|
|
811
|
+
schemaVersion: ADMIN_PAGINATION_SCHEMA_VERSION,
|
|
812
|
+
options,
|
|
813
|
+
operation,
|
|
814
|
+
runId,
|
|
815
|
+
});
|
|
816
|
+
const checkpointPath = resolveAutomaticCheckpointPath({
|
|
817
|
+
options,
|
|
818
|
+
operation,
|
|
819
|
+
runId,
|
|
820
|
+
operationFingerprint,
|
|
821
|
+
});
|
|
822
|
+
const lock = acquireCheckpointLock(checkpointPath, operationFingerprint);
|
|
823
|
+
try {
|
|
824
|
+
return await runAutomaticPaginationWithLock({
|
|
825
|
+
...args,
|
|
826
|
+
checkpointPath,
|
|
827
|
+
operationFingerprint,
|
|
828
|
+
});
|
|
829
|
+
} finally {
|
|
830
|
+
releaseCheckpointLock(lock);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
async function runAutomaticPaginationWithLock({
|
|
608
835
|
options,
|
|
609
836
|
operation,
|
|
610
837
|
runId,
|
|
838
|
+
checkpointPath,
|
|
839
|
+
operationFingerprint,
|
|
611
840
|
fetchPage,
|
|
612
841
|
transformPage,
|
|
613
842
|
recordCoverage,
|
|
614
843
|
reportProgress = (message) => process.stderr.write(`${message}\n`),
|
|
615
844
|
}) {
|
|
616
|
-
if (!operation.pagination) {
|
|
617
|
-
throw validationError("--all is supported only by paginated admin listings.");
|
|
618
|
-
}
|
|
619
|
-
const checkpointPath = path.resolve(
|
|
620
|
-
options.adminCheckpoint ||
|
|
621
|
-
defaultCheckpointPath({ runId, operationName: operation.name }),
|
|
622
|
-
);
|
|
623
845
|
const requestedOutputPath = String(options.adminOutput || "").trim();
|
|
624
846
|
if (options.adminOutput && !requestedOutputPath) {
|
|
625
847
|
throw validationError("An output file path is required.");
|
|
@@ -632,12 +854,6 @@ export async function runAutomaticPagination({
|
|
|
632
854
|
"The pagination checkpoint and result output must use different files.",
|
|
633
855
|
);
|
|
634
856
|
}
|
|
635
|
-
const operationFingerprint = operationFingerprintValue({
|
|
636
|
-
schemaVersion: ADMIN_PAGINATION_SCHEMA_VERSION,
|
|
637
|
-
options,
|
|
638
|
-
operation,
|
|
639
|
-
runId,
|
|
640
|
-
});
|
|
641
857
|
const legacyOperationFingerprint = operationFingerprintValue({
|
|
642
858
|
schemaVersion: LEGACY_ADMIN_PAGINATION_SCHEMA_VERSION,
|
|
643
859
|
options,
|