@stage5/lumine 0.2.48 → 0.2.50
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 +7 -2
- package/lib/admin.js +63 -24
- package/lib/app-mcp/server.js +22 -4
- package/lib/assets.js +1 -1
- package/lib/commands.js +7 -3
- package/lib/constants.js +14 -12
- package/package.json +1 -1
- package/sdk/LUMINE_ADMIN.md +179 -2
package/README.md
CHANGED
|
@@ -150,8 +150,9 @@ which can differ from a method's `Twinkle.*` SDK return shape — check
|
|
|
150
150
|
## Assets and AI image generation
|
|
151
151
|
|
|
152
152
|
Binary media never lives in the workspace — assets are uploaded to Twinkle and
|
|
153
|
-
referenced from code by URL. `lumine assets upload <file...>` uploads images
|
|
154
|
-
audio
|
|
153
|
+
referenced from code by URL. `lumine assets upload <file...>` uploads images,
|
|
154
|
+
audio, and MIDI data (`.mid`/`.midi`; playback still needs an app-side parser
|
|
155
|
+
or synth); `lumine assets list` prints your uploads and refreshes
|
|
155
156
|
`.twinkle/assets.json`.
|
|
156
157
|
|
|
157
158
|
`lumine assets generate "<prompt>" --model <gpt-image-2|nano-banana>` creates
|
|
@@ -231,6 +232,10 @@ events.
|
|
|
231
232
|
lumine admin identity list --json
|
|
232
233
|
lumine admin identity inspect Jay1216 \
|
|
233
234
|
--reason "Confirm account family before a quota-bucket change" --json
|
|
235
|
+
lumine admin economy trace lock --days 3 \
|
|
236
|
+
--reason "Investigate the anomalous coin gain" --json
|
|
237
|
+
lumine admin rescue wordle-audit --days 30 \
|
|
238
|
+
--reason "Identify recorded Wordle breaks and rescue status" --json
|
|
234
239
|
lumine admin daily-run start --identity auto --comment-mode off --json
|
|
235
240
|
lumine admin todo list --json
|
|
236
241
|
lumine admin todo add --kind experiment --status in_progress \
|
package/lib/admin.js
CHANGED
|
@@ -8,10 +8,7 @@ import {
|
|
|
8
8
|
validateNewsEditorial,
|
|
9
9
|
writeNewsClaimArtifacts,
|
|
10
10
|
} from "./admin-news.js";
|
|
11
|
-
import {
|
|
12
|
-
runAutomaticPagination,
|
|
13
|
-
runBatchSkips,
|
|
14
|
-
} from "./admin-workflows.js";
|
|
11
|
+
import { runAutomaticPagination, runBatchSkips } from "./admin-workflows.js";
|
|
15
12
|
import {
|
|
16
13
|
parseBuildReviewReceipt,
|
|
17
14
|
runManagedBuildReview,
|
|
@@ -506,6 +503,8 @@ function adminOperationRequiresRun(operation) {
|
|
|
506
503
|
"identity.status",
|
|
507
504
|
"identity.use",
|
|
508
505
|
"identity.inspect",
|
|
506
|
+
"economy.trace",
|
|
507
|
+
"rescue.wordle.audit",
|
|
509
508
|
"daily-run.start",
|
|
510
509
|
"daily-run.status",
|
|
511
510
|
"escalation.list",
|
|
@@ -584,6 +583,49 @@ export function parseAdminOperation(options) {
|
|
|
584
583
|
}
|
|
585
584
|
}
|
|
586
585
|
|
|
586
|
+
if (namespace === "economy" && action === "trace") {
|
|
587
|
+
const traceTarget = String(target || "").trim();
|
|
588
|
+
const reason = String(options.adminReason || "").trim();
|
|
589
|
+
if (!traceTarget || !reason) {
|
|
590
|
+
throw cliValidationError(
|
|
591
|
+
"Usage: lumine admin economy trace <userId|username> --reason <management reason> [--days <1..30>].",
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
if (reason.length > MAX_IDENTITY_INSPECTION_REASON_LENGTH) {
|
|
595
|
+
throw cliValidationError(
|
|
596
|
+
`An investigation reason must be at most ${MAX_IDENTITY_INSPECTION_REASON_LENGTH} characters.`,
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
return writeOperation("economy.trace", "POST", "/cli/admin/economy/trace", {
|
|
600
|
+
target: traceTarget,
|
|
601
|
+
reason,
|
|
602
|
+
days: parseRequiredInteger(options.adminDays || "3", "--days", 1, 30),
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (namespace === "rescue" && action === "wordle-audit") {
|
|
607
|
+
const reason = String(options.adminReason || "").trim();
|
|
608
|
+
if (!reason) {
|
|
609
|
+
throw cliValidationError(
|
|
610
|
+
"Usage: lumine admin rescue wordle-audit --reason <management reason> [--days <1..30>].",
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
if (reason.length > MAX_IDENTITY_INSPECTION_REASON_LENGTH) {
|
|
614
|
+
throw cliValidationError(
|
|
615
|
+
`An investigation reason must be at most ${MAX_IDENTITY_INSPECTION_REASON_LENGTH} characters.`,
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
return writeOperation(
|
|
619
|
+
"rescue.wordle.audit",
|
|
620
|
+
"POST",
|
|
621
|
+
"/cli/admin/rescues/wordle/audit",
|
|
622
|
+
{
|
|
623
|
+
reason,
|
|
624
|
+
days: parseRequiredInteger(options.adminDays || "30", "--days", 1, 30),
|
|
625
|
+
},
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
587
629
|
if (namespace === "ai-bucket" || namespace === "ai-buckets") {
|
|
588
630
|
if (action === "create") {
|
|
589
631
|
return writeOperation(
|
|
@@ -806,8 +848,7 @@ export function parseAdminOperation(options) {
|
|
|
806
848
|
recommendationWindow.mode === "after"
|
|
807
849
|
? recommendationWindow.after
|
|
808
850
|
: "",
|
|
809
|
-
includeLegacy:
|
|
810
|
-
recommendationWindow.mode === "legacy" ? "true" : "",
|
|
851
|
+
includeLegacy: recommendationWindow.mode === "legacy" ? "true" : "",
|
|
811
852
|
}),
|
|
812
853
|
{
|
|
813
854
|
pagination: {
|
|
@@ -1214,10 +1255,7 @@ export function parseAdminOperation(options) {
|
|
|
1214
1255
|
const reviewReceipt = options.adminReviewReceipt
|
|
1215
1256
|
? parseBuildReviewReceipt(options.adminReviewReceipt)
|
|
1216
1257
|
: null;
|
|
1217
|
-
if (
|
|
1218
|
-
reviewReceipt &&
|
|
1219
|
-
(reviewedBuildVersionId || buildReviewMethod)
|
|
1220
|
-
) {
|
|
1258
|
+
if (reviewReceipt && (reviewedBuildVersionId || buildReviewMethod)) {
|
|
1221
1259
|
throw cliValidationError(
|
|
1222
1260
|
"Pass either --review-receipt or manual --reviewed-version/--reviewed-via evidence, not both.",
|
|
1223
1261
|
);
|
|
@@ -1318,7 +1356,7 @@ export function parseAdminOperation(options) {
|
|
|
1318
1356
|
}
|
|
1319
1357
|
|
|
1320
1358
|
throw cliValidationError(
|
|
1321
|
-
"Usage: lumine admin identity|daily-run|escalation|todo|recommendations|builds|post|subjects|subject|featured|comment|announcement|chat|news|audit|brief|bot-output|notable ...",
|
|
1359
|
+
"Usage: lumine admin identity|economy|rescue|daily-run|escalation|todo|recommendations|builds|post|subjects|subject|featured|comment|announcement|chat|news|audit|brief|bot-output|notable ...",
|
|
1322
1360
|
);
|
|
1323
1361
|
}
|
|
1324
1362
|
|
|
@@ -1579,7 +1617,11 @@ export function parseAdminEscalationTarget(value) {
|
|
|
1579
1617
|
if (genericTarget) {
|
|
1580
1618
|
return {
|
|
1581
1619
|
targetType: genericTarget[1],
|
|
1582
|
-
targetId: parseRequiredInteger(
|
|
1620
|
+
targetId: parseRequiredInteger(
|
|
1621
|
+
genericTarget[2],
|
|
1622
|
+
"escalation target ID",
|
|
1623
|
+
1,
|
|
1624
|
+
),
|
|
1583
1625
|
url: null,
|
|
1584
1626
|
};
|
|
1585
1627
|
}
|
|
@@ -1770,13 +1812,9 @@ function parseTodoStatus(value) {
|
|
|
1770
1812
|
.trim()
|
|
1771
1813
|
.toLowerCase();
|
|
1772
1814
|
if (
|
|
1773
|
-
![
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
"blocked",
|
|
1777
|
-
"completed",
|
|
1778
|
-
"cancelled",
|
|
1779
|
-
].includes(status)
|
|
1815
|
+
!["open", "in_progress", "blocked", "completed", "cancelled"].includes(
|
|
1816
|
+
status,
|
|
1817
|
+
)
|
|
1780
1818
|
) {
|
|
1781
1819
|
throw cliValidationError(
|
|
1782
1820
|
"--status must be open, in_progress, blocked, completed, or cancelled.",
|
|
@@ -1816,7 +1854,9 @@ function parseAiBucketLabel(value) {
|
|
|
1816
1854
|
throw cliValidationError("Pass the bucket name with --label <name>.");
|
|
1817
1855
|
}
|
|
1818
1856
|
if (label.length > 120) {
|
|
1819
|
-
throw cliValidationError(
|
|
1857
|
+
throw cliValidationError(
|
|
1858
|
+
"An AI bucket name can be at most 120 characters.",
|
|
1859
|
+
);
|
|
1820
1860
|
}
|
|
1821
1861
|
return label;
|
|
1822
1862
|
}
|
|
@@ -1824,9 +1864,7 @@ function parseAiBucketLabel(value) {
|
|
|
1824
1864
|
function parseAiBucketNote(value) {
|
|
1825
1865
|
const note = String(value || "").trim();
|
|
1826
1866
|
if (!note) {
|
|
1827
|
-
throw cliValidationError(
|
|
1828
|
-
"Pass the quota-only context with --note <text>.",
|
|
1829
|
-
);
|
|
1867
|
+
throw cliValidationError("Pass the quota-only context with --note <text>.");
|
|
1830
1868
|
}
|
|
1831
1869
|
if (note.length > 255) {
|
|
1832
1870
|
throw cliValidationError(
|
|
@@ -1956,7 +1994,8 @@ function printAdminResult({ operation, result }) {
|
|
|
1956
1994
|
([name, value]) =>
|
|
1957
1995
|
`${name} ${Number(value.delta) > 0 ? "+" : ""}${Number(value.delta)}`,
|
|
1958
1996
|
);
|
|
1959
|
-
if (deltas.length)
|
|
1997
|
+
if (deltas.length)
|
|
1998
|
+
console.log(`Engagement deltas: ${deltas.join(", ")}.`);
|
|
1960
1999
|
}
|
|
1961
2000
|
const notableCount = Array.isArray(report.brief?.notableCandidates)
|
|
1962
2001
|
? report.brief.notableCandidates.length
|
package/lib/app-mcp/server.js
CHANGED
|
@@ -18,6 +18,22 @@ function delay(ms) {
|
|
|
18
18
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function rethrowAppMcpSessionError(error) {
|
|
22
|
+
const message = String(error?.message || error);
|
|
23
|
+
if (
|
|
24
|
+
Number(error?.status || 0) === 404 &&
|
|
25
|
+
/MCP session not found/i.test(message) &&
|
|
26
|
+
!/restart the Lumine app-mcp driver/i.test(message)
|
|
27
|
+
) {
|
|
28
|
+
const guided = new Error(
|
|
29
|
+
`${message}. Restart the Lumine app-mcp driver and reopen its new appUrl.`,
|
|
30
|
+
);
|
|
31
|
+
guided.status = error.status;
|
|
32
|
+
throw guided;
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
function openApp(url) {
|
|
22
38
|
const command =
|
|
23
39
|
process.platform === "darwin"
|
|
@@ -144,7 +160,7 @@ async function callAppTool({
|
|
|
144
160
|
sessionId,
|
|
145
161
|
name,
|
|
146
162
|
arguments: toolArguments,
|
|
147
|
-
});
|
|
163
|
+
}).catch(rethrowAppMcpSessionError);
|
|
148
164
|
const callId = created?.call?.id;
|
|
149
165
|
if (!callId) throw new Error("Twinkle did not create the app tool call.");
|
|
150
166
|
const deadline = Date.now() + CALL_TIMEOUT_MS;
|
|
@@ -155,7 +171,7 @@ async function callAppTool({
|
|
|
155
171
|
buildId,
|
|
156
172
|
sessionId,
|
|
157
173
|
callId,
|
|
158
|
-
});
|
|
174
|
+
}).catch(rethrowAppMcpSessionError);
|
|
159
175
|
const call = payload?.call;
|
|
160
176
|
if (call?.status === "completed") return call.result;
|
|
161
177
|
if (call?.status === "failed") {
|
|
@@ -169,14 +185,16 @@ async function callAppTool({
|
|
|
169
185
|
buildId,
|
|
170
186
|
sessionId,
|
|
171
187
|
callId,
|
|
172
|
-
});
|
|
188
|
+
}).catch(rethrowAppMcpSessionError);
|
|
173
189
|
if (finalPayload?.call?.status === "completed") {
|
|
174
190
|
return finalPayload.call.result;
|
|
175
191
|
}
|
|
176
192
|
if (finalPayload?.call?.status === "failed") {
|
|
177
193
|
throw new Error(finalPayload.call.errorMessage || "App tool failed.");
|
|
178
194
|
}
|
|
179
|
-
throw new Error(
|
|
195
|
+
throw new Error(
|
|
196
|
+
"App tool call timed out. Reload the appUrl tab, or restart the Lumine app-mcp driver and reopen its new appUrl.",
|
|
197
|
+
);
|
|
180
198
|
}
|
|
181
199
|
|
|
182
200
|
async function handleMcpMessage({ line, options, auth, buildId, session, tools }) {
|
package/lib/assets.js
CHANGED
|
@@ -284,7 +284,7 @@ export async function assetsUpload(options) {
|
|
|
284
284
|
const filePaths = options.positional.slice(1);
|
|
285
285
|
if (!filePaths.length) {
|
|
286
286
|
throw new Error(
|
|
287
|
-
"Usage: lumine assets upload <file...> (runtime media: images, audio, GLB/self-contained glTF, KTX2, HDR, EXR, BIN, DRC)",
|
|
287
|
+
"Usage: lumine assets upload <file...> (runtime media: images, audio/MIDI, GLB/self-contained glTF, KTX2, HDR, EXR, BIN, DRC)",
|
|
288
288
|
);
|
|
289
289
|
}
|
|
290
290
|
const buildId = await resolveSdkBuildId(options);
|
package/lib/commands.js
CHANGED
|
@@ -2350,7 +2350,10 @@ export function parseArgs(args) {
|
|
|
2350
2350
|
adminAfter: raw.after ? String(raw.after) : "",
|
|
2351
2351
|
adminSinceRun: Boolean(raw.sinceRun),
|
|
2352
2352
|
adminIncludeLegacy: Boolean(raw.includeLegacy),
|
|
2353
|
-
adminIncludePrivateEvidence: parseBoolean(
|
|
2353
|
+
adminIncludePrivateEvidence: parseBoolean(
|
|
2354
|
+
raw.includePrivateEvidence,
|
|
2355
|
+
false,
|
|
2356
|
+
),
|
|
2354
2357
|
adminAll: Boolean(raw.all),
|
|
2355
2358
|
adminResume: Boolean(raw.resume),
|
|
2356
2359
|
adminCheckpoint: raw.checkpoint ? String(raw.checkpoint) : "",
|
|
@@ -2365,8 +2368,7 @@ export function parseArgs(args) {
|
|
|
2365
2368
|
adminWaitMs: raw.waitMs ? String(raw.waitMs) : "",
|
|
2366
2369
|
adminBrowserPath: raw.browserPath ? String(raw.browserPath) : "",
|
|
2367
2370
|
adminEffort: raw.effort ? String(raw.effort) : "",
|
|
2368
|
-
agentEffort:
|
|
2369
|
-
command === "agent" && raw.effort ? String(raw.effort) : "",
|
|
2371
|
+
agentEffort: command === "agent" && raw.effort ? String(raw.effort) : "",
|
|
2370
2372
|
provider: raw.provider ? String(raw.provider) : "",
|
|
2371
2373
|
providerPath: raw.providerPath ? String(raw.providerPath) : "",
|
|
2372
2374
|
agentPrompt:
|
|
@@ -2733,6 +2735,8 @@ export function printHelp() {
|
|
|
2733
2735
|
lumine doctor runtime-assets
|
|
2734
2736
|
lumine admin identity list|status|use <zero|ciel|auto> [--json]
|
|
2735
2737
|
lumine admin identity inspect <user-id|username> --reason <management-reason> [--include-private-evidence] [--json]
|
|
2738
|
+
lumine admin economy trace <user-id|username> --reason <management-reason> [--days <1..30>] [--json]
|
|
2739
|
+
lumine admin rescue wordle-audit --reason <management-reason> [--days <1..30>] [--json]
|
|
2736
2740
|
lumine admin ai-bucket create --label <name> --note <text> [--json]
|
|
2737
2741
|
lumine admin ai-bucket get --bucket-id <id> [--json]
|
|
2738
2742
|
lumine admin ai-bucket accounts add --bucket-id <id> --user-ids <id,id,...> [--note <text>] [--json]
|
package/lib/constants.js
CHANGED
|
@@ -33,16 +33,11 @@ export const UPDATE_CHECK_TIMEOUT_MS = 1500;
|
|
|
33
33
|
export const DEFAULT_PROJECT_LIMIT = 50;
|
|
34
34
|
export const BUILD_VENDOR_THREE_VERSION = "0.184.0";
|
|
35
35
|
export const BUILD_VENDOR_THREE_LEGACY_VERSION = "0.160.0";
|
|
36
|
-
export const BUILD_VENDOR_THREE_PREFIX =
|
|
37
|
-
|
|
38
|
-
export const
|
|
39
|
-
|
|
40
|
-
export const
|
|
41
|
-
`${BUILD_VENDOR_THREE_PREFIX}three.webgpu.min.js`;
|
|
42
|
-
export const BUILD_VENDOR_THREE_TSL_MODULE_IMPORT =
|
|
43
|
-
`${BUILD_VENDOR_THREE_PREFIX}three.tsl.min.js`;
|
|
44
|
-
export const BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX =
|
|
45
|
-
`${BUILD_VENDOR_THREE_PREFIX}addons/`;
|
|
36
|
+
export const BUILD_VENDOR_THREE_PREFIX = `/build/vendor/three/${BUILD_VENDOR_THREE_VERSION}/`;
|
|
37
|
+
export const BUILD_VENDOR_THREE_MODULE_IMPORT = `${BUILD_VENDOR_THREE_PREFIX}three.module.min.js`;
|
|
38
|
+
export const BUILD_VENDOR_THREE_WEBGPU_MODULE_IMPORT = `${BUILD_VENDOR_THREE_PREFIX}three.webgpu.min.js`;
|
|
39
|
+
export const BUILD_VENDOR_THREE_TSL_MODULE_IMPORT = `${BUILD_VENDOR_THREE_PREFIX}three.tsl.min.js`;
|
|
40
|
+
export const BUILD_VENDOR_THREE_ADDONS_IMPORT_PREFIX = `${BUILD_VENDOR_THREE_PREFIX}addons/`;
|
|
46
41
|
export const PROJECT_METADATA_DIR = ".twinkle";
|
|
47
42
|
export const PROJECT_METADATA_FILE = "lumine-project.json";
|
|
48
43
|
export const ASSETS_METADATA_FILE = "assets.json";
|
|
@@ -66,6 +61,8 @@ export const ASSET_MIME_BY_EXTENSION = {
|
|
|
66
61
|
".heif": "image/heif",
|
|
67
62
|
".avif": "image/avif",
|
|
68
63
|
".mp3": "audio/mpeg",
|
|
64
|
+
".mid": "audio/midi",
|
|
65
|
+
".midi": "audio/midi",
|
|
69
66
|
".wav": "audio/wav",
|
|
70
67
|
".ogg": "audio/ogg",
|
|
71
68
|
".m4a": "audio/mp4",
|
|
@@ -92,7 +89,11 @@ export const PROJECT_MAX_TOTAL_BYTES_DEFAULT = 1024 * 1024;
|
|
|
92
89
|
export const RUNTIME_ASSET_REFERENCE_PATTERN =
|
|
93
90
|
/(?:https?:\/\/[^\s"'`)\\]+)?\/attachments\/(?:build-runtime|optimized)\/[^\s"'`)\\]+/g;
|
|
94
91
|
export const DEFAULT_SAVE_SUMMARY = "Saved from Lumine CLI.";
|
|
95
|
-
export const EXCLUDED_UPLOAD_DIRS = new Set([
|
|
92
|
+
export const EXCLUDED_UPLOAD_DIRS = new Set([
|
|
93
|
+
".git",
|
|
94
|
+
".twinkle",
|
|
95
|
+
"node_modules",
|
|
96
|
+
]);
|
|
96
97
|
export const SDK_REFERENCE_FILE = "TWINKLE_BUILD_SDK.md";
|
|
97
98
|
export const EXCLUDED_UPLOAD_FILES = new Set([
|
|
98
99
|
".DS_Store",
|
|
@@ -217,7 +218,8 @@ lumine save --summary "Describe the change"
|
|
|
217
218
|
- .twinkle/${ASSETS_METADATA_FILE} lists this build's uploaded assets (the current
|
|
218
219
|
CLI user's uploads) with their URLs. Reference an asset by its \`url\` value.
|
|
219
220
|
- \`lumine assets upload <file...>\` uploads supported runtime assets (images,
|
|
220
|
-
audio
|
|
221
|
+
audio and MIDI data (which needs an app-side parser/synth for playback),
|
|
222
|
+
GLB/self-contained glTF models, texture maps, HDR/EXR files, and glTF
|
|
221
223
|
companion files) from disk and prints the URL to use in code. Raw .gltf files
|
|
222
224
|
with relative companion URIs must be converted to .glb or rewritten to
|
|
223
225
|
absolute uploaded asset URLs first. Reference every uploaded asset that must
|
package/package.json
CHANGED
package/sdk/LUMINE_ADMIN.md
CHANGED
|
@@ -125,6 +125,44 @@ is run-independent and has no public actor. Results are **candidate accounts
|
|
|
125
125
|
for human judgment**, not an automatic ownership finding and never automatic
|
|
126
126
|
grounds for moderation, bans, or bucket changes.
|
|
127
127
|
|
|
128
|
+
### Audited private investigations
|
|
129
|
+
|
|
130
|
+
Use reason-required, bounded drill-downs when an aggregate management signal
|
|
131
|
+
needs causal evidence:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
lumine admin economy trace lock --days 3 \
|
|
135
|
+
--reason "Investigate the anomalous three-day coin gain" --json
|
|
136
|
+
lumine admin rescue wordle-audit --days 30 \
|
|
137
|
+
--reason "Identify recorded Wordle breaks, streak lengths, and rescue status" --json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`economy trace` reads the canonical append-only coin ledger for one exact
|
|
141
|
+
account. It returns an exact action/target breakdown for the window, the largest
|
|
142
|
+
ledger entries, direct transfer counterparties, AI Card sale/offer provenance,
|
|
143
|
+
and whether a counterparty is already in the same effective exact-account AI
|
|
144
|
+
bucket. It never reads or returns chat messages, bucket labels, verified
|
|
145
|
+
addresses, device evidence, or private identity values. Same-bucket transfers and concentration are
|
|
146
|
+
investigation signals, not automatic abuse findings.
|
|
147
|
+
|
|
148
|
+
`rescue wordle-audit` separates expired unredeemed Wordle/strict-Wordle rescue
|
|
149
|
+
offers from offers still inside their seven-day promise and from redeemed
|
|
150
|
+
offers. Each row identifies the account and labels the cause: regular Wordle is
|
|
151
|
+
an uncovered dodge, while strict Wordle is a completed but non-strict game. The
|
|
152
|
+
stored offer-time streak snapshot is reported as the broken streak length.
|
|
153
|
+
`claim_ready` requires a durable first user exchange in Lumine history; a
|
|
154
|
+
reservation claim is reported separately and is not treated as completion. An
|
|
155
|
+
open offer is never labelled a refusal, and non-redemption does not establish
|
|
156
|
+
intent. The underlying rescue table stores one current row per account; an
|
|
157
|
+
expired row may be replaced by a later offer, so the command reports exact
|
|
158
|
+
current-row evidence and explicitly marks that it is not a complete immutable
|
|
159
|
+
history of every offer ever shown.
|
|
160
|
+
|
|
161
|
+
Both commands are run-independent private operator actions. Each requires a
|
|
162
|
+
concrete reason and commits a minimized private access receipt before loading
|
|
163
|
+
the evidence. Windows are limited to 1-30 days, and neither command mutates
|
|
164
|
+
coins, streaks, buckets, messages, or public content.
|
|
165
|
+
|
|
128
166
|
## Editorial priorities
|
|
129
167
|
|
|
130
168
|
The CLI enforces none of this — it is the standing instruction for the operator
|
|
@@ -1480,6 +1518,82 @@ right after the brief, and **read every row** — the tool deliberately does no
|
|
|
1480
1518
|
filtering, scoring, or keyword matching, because the judgment is the reviewing
|
|
1481
1519
|
agent's.
|
|
1482
1520
|
|
|
1521
|
+
### API runtime-log review (same phase, every run)
|
|
1522
|
+
|
|
1523
|
+
The bot-conduct review also owns a bounded production API log review. Bot
|
|
1524
|
+
responses, community-management reads, and delegated mutations can succeed at
|
|
1525
|
+
the HTTP layer while stdout records a degraded fallback/retry loop or stderr
|
|
1526
|
+
records a side-effect failure. Reviewing only `bot-output` can therefore miss
|
|
1527
|
+
the other half of what happened.
|
|
1528
|
+
|
|
1529
|
+
The current API-side files are:
|
|
1530
|
+
|
|
1531
|
+
- `/home/ec2-user/server/logs/twinkle-api.err.log`
|
|
1532
|
+
- `/home/ec2-user/server/logs/twinkle-api.out.log`
|
|
1533
|
+
- `/home/ec2-user/server/logs/twinkle-image-optimizer.err.log`
|
|
1534
|
+
- `/home/ec2-user/server/logs/twinkle-image-optimizer.out.log`
|
|
1535
|
+
|
|
1536
|
+
Treat every current `/home/ec2-user/server/logs/*.err.log` and `*.out.log` as
|
|
1537
|
+
in scope so a later API-side worker is not silently omitted. Use the production
|
|
1538
|
+
SSH endpoint and key from the repository agent guide; all inspection commands
|
|
1539
|
+
are read-only.
|
|
1540
|
+
|
|
1541
|
+
1. Immediately after `daily-run start`, record each matching file's inode and
|
|
1542
|
+
byte size, inspect its current tail to establish service health, and read
|
|
1543
|
+
every non-empty error log before accepting that position as the run
|
|
1544
|
+
baseline. The prior run is supposed to leave the live API error log empty,
|
|
1545
|
+
so unexplained pre-existing stderr is evidence, not a reason to skip ahead.
|
|
1546
|
+
2. Run and fully paginate `bot-output`, reading every row as required above.
|
|
1547
|
+
In this same phase, read every byte appended to both error and normal-output
|
|
1548
|
+
files since the recorded baseline. Refresh the offsets after inspection.
|
|
1549
|
+
Do not rely on a fixed-line `tail`: a busy or multiline failure can begin
|
|
1550
|
+
before that arbitrary window.
|
|
1551
|
+
3. Immediately before `daily-run report` and `daily-run complete`, inspect the
|
|
1552
|
+
delta again. This catches failures caused by the curation actions performed
|
|
1553
|
+
after the first conduct/log review. If a file's inode changed or its size
|
|
1554
|
+
shrank, do not assume the missing range was clean: inspect the replacement
|
|
1555
|
+
from byte zero, check the relevant `twinkle-api.service` or
|
|
1556
|
+
`twinkle-image-optimizer.service` journal interval, and report the lost
|
|
1557
|
+
boundary.
|
|
1558
|
+
|
|
1559
|
+
For each warning, fallback, retry loop, or failure, correlate timestamps and
|
|
1560
|
+
request/target IDs with the canonical CLI response and private audit event.
|
|
1561
|
+
Distinguish an expected, handled condition from a real user-visible,
|
|
1562
|
+
reliability, security, or performance defect. A defensible defect is an
|
|
1563
|
+
in-scope bug report: trace its complete producer-to-consumer pipeline, fix the
|
|
1564
|
+
root cause in the canonical repository, add focused regression coverage, run
|
|
1565
|
+
the repository's normal validation ceiling, and verify the fix in production
|
|
1566
|
+
when deployment is authorized. Re-read the affected log boundary after live
|
|
1567
|
+
verification. Do not declare the run clean merely because a retry eventually
|
|
1568
|
+
succeeded if the underlying failure remains repeatable.
|
|
1569
|
+
|
|
1570
|
+
Never silently complete a run with an unresolved log finding. If the fix needs
|
|
1571
|
+
new commit/deployment authority, external coordination, or more time than the
|
|
1572
|
+
active run safely permits, preserve the evidence, add or update a private
|
|
1573
|
+
carry-over todo with the exact finding and acceptance criteria, and tell Mikey
|
|
1574
|
+
in the run report. Do not mark that todo complete until the fix is verified
|
|
1575
|
+
live.
|
|
1576
|
+
|
|
1577
|
+
Preserve all log evidence while any finding remains. Once every issue found in
|
|
1578
|
+
`/home/ec2-user/server/logs/twinkle-api.err.log` has been fixed and verified
|
|
1579
|
+
live, or conclusively classified as expected/non-defective, clear that exact
|
|
1580
|
+
live API stderr log through the only safe path:
|
|
1581
|
+
|
|
1582
|
+
```bash
|
|
1583
|
+
ssh -i /Users/mikey/twinkle-api.pem -o IdentitiesOnly=yes \
|
|
1584
|
+
ec2-user@api.twinkle.network \
|
|
1585
|
+
'cd /home/ec2-user/server && npm run logs:clear-errors'
|
|
1586
|
+
```
|
|
1587
|
+
|
|
1588
|
+
That command truncates the file through the service's inode-safe lifecycle; it
|
|
1589
|
+
does not restart the API. Never delete, recreate, editor-save, or manually
|
|
1590
|
+
truncate any log. Do not clear normal stdout or the optimizer logs. After the
|
|
1591
|
+
safe clear, inspect the error file and all bytes appended to the normal logs
|
|
1592
|
+
once more, and include the reviewed file set, boundaries, findings/fixes,
|
|
1593
|
+
live-verification result, clear result, and any remaining todo in the final run
|
|
1594
|
+
report. If any error-log issue remains unresolved or unverified, do not clear
|
|
1595
|
+
the error log.
|
|
1596
|
+
|
|
1483
1597
|
**Purpose and privacy boundary:** this audits how Twinkle's bots treated
|
|
1484
1598
|
members; it is not thought-policing or a moderation queue for members' private
|
|
1485
1599
|
use of the tool. The question is whether Zero or Ciel inflicted, encouraged,
|
|
@@ -1592,8 +1706,8 @@ end every run report with an **"Insights for Mikey"** section carrying only
|
|
|
1592
1706
|
the deltas and anomalies worth his time, next to the escalation list. Never
|
|
1593
1707
|
dump raw sections at him.
|
|
1594
1708
|
|
|
1595
|
-
|
|
1596
|
-
farm-signal sections added
|
|
1709
|
+
Ten sections (Mikey's chosen cut 2026-08-10; behavioral-insight and
|
|
1710
|
+
farm-signal sections added that day; AI Card summon watch added 2026-08-24):
|
|
1597
1711
|
|
|
1598
1712
|
- `economy` — `topGainers` (coin-ledger aggregation over the window: gained,
|
|
1599
1713
|
spent, net, current balance per user, Zero/Ciel excluded) and `topBalances`
|
|
@@ -1633,6 +1747,26 @@ farm-signal sections added the same day):
|
|
|
1633
1747
|
an account is young or empty. A run that reads the spending report without
|
|
1634
1748
|
asking "could any of this be one person with many accounts?" has skipped a
|
|
1635
1749
|
duty.
|
|
1750
|
+
- `aiCardSummoning` — the standing multi-alt Summoner watch for every website-
|
|
1751
|
+
management run. It counts the authoritative `ai_card_summons` daily quota counters
|
|
1752
|
+
over the whole-day `dayWindow`, so Mystery Cards and successful fallback
|
|
1753
|
+
cards are included, then lists the most active summoning accounts. It groups
|
|
1754
|
+
summons when the same exact-device hash was observed for more than one
|
|
1755
|
+
summoning user within the bounded `deviceEvidenceLookbackDays`; that evidence
|
|
1756
|
+
is correlated with the account/window, not asserted to be the device used for
|
|
1757
|
+
every individual summon. Each group reports its maximum same-day account and
|
|
1758
|
+
charged-summon totals, the multi-account days, and days above the shared
|
|
1759
|
+
three-card limit. Review every `requiresIdentityInspection` group,
|
|
1760
|
+
prioritizing `daysAboveSharedLimit > 0`, with reason-required `identity
|
|
1761
|
+
inspect`. If
|
|
1762
|
+
`riskGroupsTruncated` is true, report that the bounded watch has more groups
|
|
1763
|
+
than it returned rather than calling the review exhaustive. Add only
|
|
1764
|
+
operator-confirmed exact accounts to an unbanned quota bucket with
|
|
1765
|
+
`ai-bucket accounts add`; never infer or auto-bucket a family from a device,
|
|
1766
|
+
IP prefix, user agent, shared inbox, or anomaly alone. After a bucket update,
|
|
1767
|
+
re-read its canonical members. This duty applies even when no group crossed
|
|
1768
|
+
three cards, because the new enforcement prevents already-bucketed siblings
|
|
1769
|
+
from producing an over-limit row.
|
|
1636
1770
|
- `notableCandidates` — kids (never bots, staff `userType`s, or users already
|
|
1637
1771
|
on the Notable Users list) ranked by authored activity in the window, with
|
|
1638
1772
|
`isNewUser` marking window-new signups. Use it to find the overlooked and
|
|
@@ -1830,6 +1964,49 @@ type InsightsBrief = Success<{
|
|
|
1830
1964
|
topRiskGroups: unknown[];
|
|
1831
1965
|
}
|
|
1832
1966
|
| InsightUnavailable;
|
|
1967
|
+
aiCardSummoning:
|
|
1968
|
+
| {
|
|
1969
|
+
dayWindow: { startDayIndex: number; endDayIndex: number };
|
|
1970
|
+
deviceEvidenceLookbackDays: number;
|
|
1971
|
+
sharedDailyLimit: number;
|
|
1972
|
+
summonsCharged: number;
|
|
1973
|
+
summoningAccounts: number;
|
|
1974
|
+
riskGroupsTruncated: boolean;
|
|
1975
|
+
topAccounts: Array<{
|
|
1976
|
+
userId: number;
|
|
1977
|
+
username: string | null;
|
|
1978
|
+
joinedAt: number | null;
|
|
1979
|
+
summonsCharged: number;
|
|
1980
|
+
activeDays: number;
|
|
1981
|
+
lastSummonDayIndex: number | null;
|
|
1982
|
+
}>;
|
|
1983
|
+
multiAccountRiskGroups: Array<{
|
|
1984
|
+
riskKeyType: string;
|
|
1985
|
+
riskKeyHash: string;
|
|
1986
|
+
accountCount: number;
|
|
1987
|
+
summonsChargedOnMultiAccountDays: number;
|
|
1988
|
+
multiAccountDays: number;
|
|
1989
|
+
maxDailySummons: number;
|
|
1990
|
+
maxDailyAccounts: number;
|
|
1991
|
+
daysAboveSharedLimit: number;
|
|
1992
|
+
requiresIdentityInspection: true;
|
|
1993
|
+
dailyActivity: Array<{
|
|
1994
|
+
dayIndex: number;
|
|
1995
|
+
accountCount: number;
|
|
1996
|
+
summonsCharged: number;
|
|
1997
|
+
}>;
|
|
1998
|
+
accounts: Array<{
|
|
1999
|
+
userId: number;
|
|
2000
|
+
username: string | null;
|
|
2001
|
+
joinedAt: number | null;
|
|
2002
|
+
summonsCharged: number;
|
|
2003
|
+
activeDays: number;
|
|
2004
|
+
lastSummonDayIndex: number | null;
|
|
2005
|
+
}>;
|
|
2006
|
+
}>;
|
|
2007
|
+
notes: string;
|
|
2008
|
+
}
|
|
2009
|
+
| InsightUnavailable;
|
|
1833
2010
|
engagementPulse:
|
|
1834
2011
|
| {
|
|
1835
2012
|
windowDays: number;
|