openlearn-next 0.7.3 → 0.7.4

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.
Files changed (35) hide show
  1. package/dist/assets/{ActivityWorkspaceWidget-5PzyxF6a.js → ActivityWorkspaceWidget-CMN8elwQ.js} +1 -1
  2. package/dist/assets/{AdminDirectoryView-BCtmQTEK.js → AdminDirectoryView-Dlz7q-xf.js} +1 -1
  3. package/dist/assets/{AssessmentCenter-B43ojHG-.js → AssessmentCenter-CaoAI9Ke.js} +1 -1
  4. package/dist/assets/{BatchPickerModal-CbmjInp7.js → BatchPickerModal-CzutdtIz.js} +1 -1
  5. package/dist/assets/{ClassesView-DEnMaDOt.js → ClassesView-RkFPpWRu.js} +1 -1
  6. package/dist/assets/{ClassroomCountdownWidget-CHq4983w.js → ClassroomCountdownWidget-gs42lIGW.js} +1 -1
  7. package/dist/assets/{CloudDriveModal-D2t3Uyzr.js → CloudDriveModal-C6JtK08M.js} +1 -1
  8. package/dist/assets/{ComputerLabView-YtnoT5Rb.js → ComputerLabView-Dr-g8FrU.js} +1 -1
  9. package/dist/assets/{CourseManagement-DOXyJ_j-.js → CourseManagement-DNeaJcT9.js} +1 -1
  10. package/dist/assets/{CourseWizardModal-Dbi6Caw2.js → CourseWizardModal-vw876VJr.js} +1 -1
  11. package/dist/assets/{Dashboard-2rLu_ZBy.js → Dashboard-C1iYnfCh.js} +1 -1
  12. package/dist/assets/{ExportWeightModal-BrmAh6VX.js → ExportWeightModal-CTZ-O_wi.js} +1 -1
  13. package/dist/assets/{HelpView-CVBrKDrd.js → HelpView-wyKldm_R.js} +1 -1
  14. package/dist/assets/{ImportLessonsModal-PhHtX31R.js → ImportLessonsModal-C09Z-qtx.js} +1 -1
  15. package/dist/assets/{InteractiveCoursewareViewer-Vi6lLz7a.js → InteractiveCoursewareViewer-V97euiJF.js} +1 -1
  16. package/dist/assets/{InteractiveWhiteboard-lTzeayX5.js → InteractiveWhiteboard-COLMh_-w.js} +1 -1
  17. package/dist/assets/{LazyCourseware-KIt0rGtW.js → LazyCourseware-0iFYlRGj.js} +2 -2
  18. package/dist/assets/{LazyWhiteboard-CCN3pYcW.js → LazyWhiteboard-CcoBIx6V.js} +2 -2
  19. package/dist/assets/{LessonEditorView-D_GVhHuM.js → LessonEditorView-BWmtKwmP.js} +1 -1
  20. package/dist/assets/{LiveClassroomView-BRsZ6QEq.js → LiveClassroomView-CaS1DzSS.js} +1 -1
  21. package/dist/assets/{NotificationDetailModal-tY5rxYCN.js → NotificationDetailModal-4MdNF4tw.js} +1 -1
  22. package/dist/assets/{PluginView-CedpAW6d.js → PluginView-BqTbAp30.js} +1 -1
  23. package/dist/assets/{QuizGeneratorModal-bUA002kP.js → QuizGeneratorModal-xTq22vDq.js} +1 -1
  24. package/dist/assets/{StudentAssignmentView-BhAz6Hfk.js → StudentAssignmentView-Cy_JdPYB.js} +1 -1
  25. package/dist/assets/{StudentLessonView-DC1yX4kG.js → StudentLessonView-B3fKejCU.js} +1 -1
  26. package/dist/assets/{StudentView-PHKxIV9T.js → StudentView-CpJsG7FT.js} +2 -2
  27. package/dist/assets/{SystemResourceLibraryModal-DgXYe6_x.js → SystemResourceLibraryModal-YZ85mv_v.js} +1 -1
  28. package/dist/assets/{TeacherView-DscJtOJ9.js → TeacherView-B3zhzd5g.js} +2 -2
  29. package/dist/assets/{TimetableView-D2ozoj_D.js → TimetableView-D_4XmrRN.js} +1 -1
  30. package/dist/assets/{index-2676X8YB.js → index-BzXADCXZ.js} +5 -5
  31. package/dist/assets/{pluginParsers-e4PSm2Iy.js → pluginParsers-PzgFzxS9.js} +1 -1
  32. package/dist/cli-migrator.cjs +18 -1
  33. package/dist/index.html +1 -1
  34. package/dist/server.cjs +339 -249
  35. package/package.json +3 -2
package/dist/server.cjs CHANGED
@@ -1188,12 +1188,12 @@ var init_manifest_schema = __esm({
1188
1188
  // packages/core/di/api-key-crypto.ts
1189
1189
  function resolveEncryptionKeyStorageDir(env = process.env, cwd = process.cwd()) {
1190
1190
  if (env.OPENLEARN_DATA_DIR?.trim()) {
1191
- return import_node_path3.default.join(import_node_path3.default.resolve(env.OPENLEARN_DATA_DIR.trim()), "storage");
1191
+ return import_node_path4.default.join(import_node_path4.default.resolve(env.OPENLEARN_DATA_DIR.trim()), "storage");
1192
1192
  }
1193
1193
  if (env.OPENLEARN_DB_PATH?.trim()) {
1194
- return import_node_path3.default.join(import_node_path3.default.dirname(import_node_path3.default.resolve(env.OPENLEARN_DB_PATH.trim())), "storage");
1194
+ return import_node_path4.default.join(import_node_path4.default.dirname(import_node_path4.default.resolve(env.OPENLEARN_DB_PATH.trim())), "storage");
1195
1195
  }
1196
- return import_node_path3.default.join(cwd, "storage");
1196
+ return import_node_path4.default.join(cwd, "storage");
1197
1197
  }
1198
1198
  function normalizeKeyHex(raw) {
1199
1199
  let value = raw.trim();
@@ -1214,11 +1214,11 @@ function readProcessEnvKey(env) {
1214
1214
  return normalizeKeyHex(raw);
1215
1215
  }
1216
1216
  function readDotEnvKey(cwd) {
1217
- const envPath = import_node_path3.default.resolve(cwd, ".env");
1218
- if (!import_node_fs3.default.existsSync(envPath)) return null;
1217
+ const envPath = import_node_path4.default.resolve(cwd, ".env");
1218
+ if (!import_node_fs4.default.existsSync(envPath)) return null;
1219
1219
  let content = "";
1220
1220
  try {
1221
- content = import_node_fs3.default.readFileSync(envPath, "utf8");
1221
+ content = import_node_fs4.default.readFileSync(envPath, "utf8");
1222
1222
  } catch {
1223
1223
  return null;
1224
1224
  }
@@ -1227,11 +1227,11 @@ function readDotEnvKey(cwd) {
1227
1227
  return normalizeKeyHex(match[1] ?? "");
1228
1228
  }
1229
1229
  function keyFilePath(storageDir) {
1230
- return import_node_path3.default.join(storageDir, ENCRYPTION_KEY_FILE_NAME);
1230
+ return import_node_path4.default.join(storageDir, ENCRYPTION_KEY_FILE_NAME);
1231
1231
  }
1232
1232
  function chmodKeyFile(filePath) {
1233
1233
  try {
1234
- import_node_fs3.default.chmodSync(filePath, 384);
1234
+ import_node_fs4.default.chmodSync(filePath, 384);
1235
1235
  } catch (err) {
1236
1236
  throw new Error(
1237
1237
  `\u65E0\u6CD5\u5C06 ${filePath} \u7684\u6743\u9650\u8BBE\u4E3A 0600\uFF08${err instanceof Error ? err.message : err}\uFF09\u3002\u5DF2\u62D2\u7EDD\u7EE7\u7EED\u4F7F\u7528\u8BE5\u5BC6\u94A5\u6587\u4EF6\u3002`
@@ -1239,8 +1239,8 @@ function chmodKeyFile(filePath) {
1239
1239
  }
1240
1240
  }
1241
1241
  function readKeyFile(filePath) {
1242
- if (!import_node_fs3.default.existsSync(filePath)) return null;
1243
- const normalized = normalizeKeyHex(import_node_fs3.default.readFileSync(filePath, "utf8"));
1242
+ if (!import_node_fs4.default.existsSync(filePath)) return null;
1243
+ const normalized = normalizeKeyHex(import_node_fs4.default.readFileSync(filePath, "utf8"));
1244
1244
  if (!normalized) {
1245
1245
  throw new EncryptionKeyInvalidError(
1246
1246
  `\u5BC6\u94A5\u6587\u4EF6 ${filePath} \u662F\u7A7A\u7684\u3002\u8BF7\u5220\u6389\u5B83\uFF0C\u6216\u5199\u5165\u539F\u6765\u7684 64 \u4F4D hex\u3002\u4E0D\u8981\u8BA9\u7CFB\u7EDF\u53E6\u9020\u4E00\u628A\u94A5\u5319\u3002`
@@ -1253,9 +1253,9 @@ function keysEqual(left, right) {
1253
1253
  return import_node_crypto.default.timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
1254
1254
  }
1255
1255
  function persistGeneratedKey(filePath, generated) {
1256
- import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(filePath), { recursive: true });
1256
+ import_node_fs4.default.mkdirSync(import_node_path4.default.dirname(filePath), { recursive: true });
1257
1257
  try {
1258
- import_node_fs3.default.writeFileSync(filePath, `${generated}
1258
+ import_node_fs4.default.writeFileSync(filePath, `${generated}
1259
1259
  `, { mode: 384, flag: "wx" });
1260
1260
  } catch (err) {
1261
1261
  const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
@@ -1291,7 +1291,7 @@ function resolveEncryptionKey(options = {}) {
1291
1291
  const fromDotEnv = fromProcess ? null : readDotEnvKey(cwd);
1292
1292
  const configured = fromProcess ?? fromDotEnv;
1293
1293
  const configuredSource = fromProcess ? "env" : fromDotEnv ? "env-file" : null;
1294
- const fileKey = import_node_fs3.default.existsSync(filePath) ? readKeyFile(filePath) : null;
1294
+ const fileKey = import_node_fs4.default.existsSync(filePath) ? readKeyFile(filePath) : null;
1295
1295
  if (configured && fileKey && !keysEqual(configured, fileKey)) {
1296
1296
  throw new EncryptionKeyMismatchError(filePath);
1297
1297
  }
@@ -1400,13 +1400,13 @@ function encryptStoredPlaintextApiKeys(db2) {
1400
1400
  }
1401
1401
  return { updated, skipped };
1402
1402
  }
1403
- var import_node_crypto, import_node_fs3, import_node_path3, ENCRYPTION_KEY_FILE_NAME, AUTO_GENERATED_KEY_WARNING, HEX_64, EncryptionKeyInvalidError, EncryptionKeyMismatchError, cached;
1403
+ var import_node_crypto, import_node_fs4, import_node_path4, ENCRYPTION_KEY_FILE_NAME, AUTO_GENERATED_KEY_WARNING, HEX_64, EncryptionKeyInvalidError, EncryptionKeyMismatchError, cached;
1404
1404
  var init_api_key_crypto = __esm({
1405
1405
  "packages/core/di/api-key-crypto.ts"() {
1406
1406
  "use strict";
1407
1407
  import_node_crypto = __toESM(require("node:crypto"), 1);
1408
- import_node_fs3 = __toESM(require("node:fs"), 1);
1409
- import_node_path3 = __toESM(require("node:path"), 1);
1408
+ import_node_fs4 = __toESM(require("node:fs"), 1);
1409
+ import_node_path4 = __toESM(require("node:path"), 1);
1410
1410
  ENCRYPTION_KEY_FILE_NAME = "encryption-key";
1411
1411
  AUTO_GENERATED_KEY_WARNING = "\u6B63\u5728\u4F7F\u7528\u81EA\u52A8\u751F\u6210\u7684\u52A0\u5BC6\u5BC6\u94A5\uFF08storage/encryption-key\uFF0C\u4E0E bootstrap-credentials.txt \u540C\u76EE\u5F55\uFF0C\u6743\u9650 600\uFF09\u3002\u5EFA\u8BAE\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ENCRYPTION_KEY \u4E3A\u8BE5\u6587\u4EF6\u4E2D\u7684 64 \u4F4D hex\uFF0C\u5E76\u79BB\u7EBF\u5907\u4EFD\u8BE5\u6587\u4EF6\u3002site-backup \u4E0D\u5305\u542B\u6B64\u6587\u4EF6\u3002\u4E22\u5931\u540E\u65E0\u6CD5\u89E3\u5BC6 AI Provider API Key \u4E0E\u73ED\u7EA7\u4E34\u65F6\u53E3\u4EE4\u3002";
1412
1412
  HEX_64 = /^[0-9a-fA-F]{64}$/;
@@ -1921,49 +1921,49 @@ function getMaxUncompressedSize() {
1921
1921
  }
1922
1922
  function isInsideRoot(root, target) {
1923
1923
  if (!root || !target) return false;
1924
- const rel = path7.relative(path7.resolve(root), path7.resolve(target));
1924
+ const rel = path8.relative(path8.resolve(root), path8.resolve(target));
1925
1925
  if (rel === "") return false;
1926
- if (path7.isAbsolute(rel)) return false;
1927
- return rel !== ".." && !rel.startsWith(".." + path7.sep);
1926
+ if (path8.isAbsolute(rel)) return false;
1927
+ return rel !== ".." && !rel.startsWith(".." + path8.sep);
1928
1928
  }
1929
1929
  function realPathOrNull(target) {
1930
1930
  try {
1931
- return fs6.realpathSync(target);
1931
+ return fs7.realpathSync(target);
1932
1932
  } catch {
1933
1933
  return null;
1934
1934
  }
1935
1935
  }
1936
1936
  function realPathOrSelf(target) {
1937
- return realPathOrNull(target) ?? path7.resolve(target);
1937
+ return realPathOrNull(target) ?? path8.resolve(target);
1938
1938
  }
1939
1939
  function getMaxUploadBodyBytes() {
1940
1940
  return getMaxUncompressedSize();
1941
1941
  }
1942
1942
  function readZipBufferFromPath(zipPath) {
1943
- if (!fs6.existsSync(zipPath)) {
1943
+ if (!fs7.existsSync(zipPath)) {
1944
1944
  throw new Error(`[install-utils] ZIP \u6587\u4EF6\u4E0D\u5B58\u5728: ${zipPath}`);
1945
1945
  }
1946
- return fs6.readFileSync(zipPath);
1946
+ return fs7.readFileSync(zipPath);
1947
1947
  }
1948
1948
  function writeBufferToTempZip(buffer, prefix = "plugin-upload-") {
1949
1949
  if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
1950
1950
  throw new Error("[install-utils] \u7A7A\u7684 ZIP Buffer");
1951
1951
  }
1952
- const zipPath = path7.join(os2.tmpdir(), `${prefix}${(0, import_uuid6.v7)()}.zip`);
1953
- fs6.writeFileSync(zipPath, buffer);
1952
+ const zipPath = path8.join(os2.tmpdir(), `${prefix}${(0, import_uuid6.v7)()}.zip`);
1953
+ fs7.writeFileSync(zipPath, buffer);
1954
1954
  return zipPath;
1955
1955
  }
1956
1956
  function removeTempZip(zipPath) {
1957
1957
  if (!zipPath) return;
1958
1958
  try {
1959
- fs6.rmSync(zipPath, { force: true });
1959
+ fs7.rmSync(zipPath, { force: true });
1960
1960
  } catch {
1961
1961
  }
1962
1962
  }
1963
1963
  function removeTempFile(filePath) {
1964
1964
  if (!filePath) return;
1965
1965
  try {
1966
- fs6.rmSync(filePath, { force: true });
1966
+ fs7.rmSync(filePath, { force: true });
1967
1967
  } catch {
1968
1968
  }
1969
1969
  }
@@ -2209,13 +2209,13 @@ async function bundlePlugin(entryCode, resolveDir, outfile) {
2209
2209
  const esbuild = await import("esbuild");
2210
2210
  const allowedRoots = [];
2211
2211
  const allowRoot = (dir) => {
2212
- const lexical = path7.resolve(dir);
2212
+ const lexical = path8.resolve(dir);
2213
2213
  if (allowedRoots.some((r) => r.lexical === lexical)) return;
2214
2214
  allowedRoots.push({ lexical, real: realPathOrSelf(lexical) });
2215
2215
  };
2216
2216
  allowRoot(resolveDir);
2217
2217
  const isPermittedPath = (target) => {
2218
- const resolved = path7.resolve(target);
2218
+ const resolved = path8.resolve(target);
2219
2219
  if (allowedRoots.some((r) => isInsideRoot(r.lexical, resolved))) return true;
2220
2220
  const real = realPathOrNull(resolved);
2221
2221
  if (!real) return false;
@@ -2242,14 +2242,14 @@ async function bundlePlugin(entryCode, resolveDir, outfile) {
2242
2242
  setup(build) {
2243
2243
  build.onResolve({ filter: /.*/ }, (args) => {
2244
2244
  if (args.path.includes("core/di/interfaces")) {
2245
- const tsPath = path7.resolve(process.cwd(), "packages/core/di/interfaces.ts");
2246
- if (fs6.existsSync(tsPath)) {
2247
- allowRoot(path7.dirname(tsPath));
2245
+ const tsPath = path8.resolve(process.cwd(), "packages/core/di/interfaces.ts");
2246
+ if (fs7.existsSync(tsPath)) {
2247
+ allowRoot(path8.dirname(tsPath));
2248
2248
  return { path: tsPath };
2249
2249
  }
2250
- const jsPath = path7.resolve(process.cwd(), "packages/core/di/interfaces.js");
2251
- if (fs6.existsSync(jsPath)) {
2252
- allowRoot(path7.dirname(jsPath));
2250
+ const jsPath = path8.resolve(process.cwd(), "packages/core/di/interfaces.js");
2251
+ if (fs7.existsSync(jsPath)) {
2252
+ allowRoot(path8.dirname(jsPath));
2253
2253
  return { path: jsPath };
2254
2254
  }
2255
2255
  }
@@ -2263,8 +2263,8 @@ async function bundlePlugin(entryCode, resolveDir, outfile) {
2263
2263
  };
2264
2264
  }
2265
2265
  if (args.path.startsWith(".")) {
2266
- const importerDir = args.resolveDir || path7.resolve(resolveDir);
2267
- const candidate = path7.resolve(importerDir, args.path);
2266
+ const importerDir = args.resolveDir || path8.resolve(resolveDir);
2267
+ const candidate = path8.resolve(importerDir, args.path);
2268
2268
  if (!isPermittedPath(candidate)) {
2269
2269
  return {
2270
2270
  errors: [
@@ -2302,7 +2302,7 @@ async function bundlePlugin(entryCode, resolveDir, outfile) {
2302
2302
  ]
2303
2303
  });
2304
2304
  if (outfile !== void 0) {
2305
- assertPluginCodeSafe(fs6.readFileSync(outfile, "utf-8"));
2305
+ assertPluginCodeSafe(fs7.readFileSync(outfile, "utf-8"));
2306
2306
  return void 0;
2307
2307
  }
2308
2308
  const bundled = result.outputFiles?.[0]?.text;
@@ -2316,8 +2316,8 @@ async function extractZipEntries(zip, destDir, maxTotalBytes) {
2316
2316
  let totalBytes = 0;
2317
2317
  for (const [name, file] of Object.entries(zip.files)) {
2318
2318
  if (file.dir) continue;
2319
- const filePath = path7.join(destDir, name);
2320
- fs6.mkdirSync(path7.dirname(filePath), { recursive: true });
2319
+ const filePath = path8.join(destDir, name);
2320
+ fs7.mkdirSync(path8.dirname(filePath), { recursive: true });
2321
2321
  const content = await file.async("nodebuffer");
2322
2322
  totalBytes += content.length;
2323
2323
  if (totalBytes > maxTotalBytes) {
@@ -2325,7 +2325,7 @@ async function extractZipEntries(zip, destDir, maxTotalBytes) {
2325
2325
  `ZIP bomb prevention: actual uncompressed size ${totalBytes} bytes exceeds limit of ${maxTotalBytes} bytes`
2326
2326
  );
2327
2327
  }
2328
- fs6.writeFileSync(filePath, content);
2328
+ fs7.writeFileSync(filePath, content);
2329
2329
  }
2330
2330
  }
2331
2331
  async function validateAndBundleZip(zipPath) {
@@ -2373,16 +2373,16 @@ async function validateAndBundleZip(zipPath) {
2373
2373
  throw new Error(`Entry file "${manifest.main}" specified in manifest not found in ZIP package`);
2374
2374
  }
2375
2375
  const entryCode = await entryFile.async("string");
2376
- const tmpDir = path7.join(os2.tmpdir(), `plugin-build-${(0, import_uuid6.v7)()}`);
2376
+ const tmpDir = path8.join(os2.tmpdir(), `plugin-build-${(0, import_uuid6.v7)()}`);
2377
2377
  try {
2378
- fs6.mkdirSync(tmpDir, { recursive: true });
2378
+ fs7.mkdirSync(tmpDir, { recursive: true });
2379
2379
  await extractZipEntries(zip, tmpDir, getMaxUncompressedSize());
2380
- const bundledPath = path7.join(os2.tmpdir(), `plugin-bundle-${(0, import_uuid6.v7)()}.mjs`);
2380
+ const bundledPath = path8.join(os2.tmpdir(), `plugin-bundle-${(0, import_uuid6.v7)()}.mjs`);
2381
2381
  await bundlePlugin(entryCode, tmpDir, bundledPath);
2382
2382
  return { manifest, bundledPath, entryFileName: resolvedMain, package: zip };
2383
2383
  } finally {
2384
2384
  try {
2385
- fs6.rmSync(tmpDir, { recursive: true, force: true });
2385
+ fs7.rmSync(tmpDir, { recursive: true, force: true });
2386
2386
  } catch {
2387
2387
  }
2388
2388
  }
@@ -2390,14 +2390,14 @@ async function validateAndBundleZip(zipPath) {
2390
2390
  async function extractManifestFromBundle(_bundledCode) {
2391
2391
  throw new Error("extractManifestFromBundle is not yet implemented");
2392
2392
  }
2393
- var import_jszip, fs6, os2, path7, import_uuid6, PluginCodeSecurityError, LEX_SCAN_LIMIT;
2393
+ var import_jszip, fs7, os2, path8, import_uuid6, PluginCodeSecurityError, LEX_SCAN_LIMIT;
2394
2394
  var init_install_utils = __esm({
2395
2395
  "packages/core/esm-loader/install-utils.ts"() {
2396
2396
  "use strict";
2397
2397
  import_jszip = __toESM(require("jszip"), 1);
2398
- fs6 = __toESM(require("fs"), 1);
2398
+ fs7 = __toESM(require("fs"), 1);
2399
2399
  os2 = __toESM(require("os"), 1);
2400
- path7 = __toESM(require("path"), 1);
2400
+ path8 = __toESM(require("path"), 1);
2401
2401
  import_uuid6 = require("uuid");
2402
2402
  init_manifest_schema();
2403
2403
  PluginCodeSecurityError = class extends Error {
@@ -2472,7 +2472,7 @@ var init_redaction = __esm({
2472
2472
  // packages/core/observability/rotating-file-stream.ts
2473
2473
  function removeIfExists(filePath) {
2474
2474
  try {
2475
- import_node_fs4.default.unlinkSync(filePath);
2475
+ import_node_fs5.default.unlinkSync(filePath);
2476
2476
  } catch {
2477
2477
  }
2478
2478
  }
@@ -2491,7 +2491,7 @@ function unregisterStream(stream) {
2491
2491
  function safeRename(from, to) {
2492
2492
  try {
2493
2493
  removeIfExists(to);
2494
- import_node_fs4.default.renameSync(from, to);
2494
+ import_node_fs5.default.renameSync(from, to);
2495
2495
  return true;
2496
2496
  } catch {
2497
2497
  return false;
@@ -2516,12 +2516,12 @@ function resolveAuditRotationConfig(env = process.env) {
2516
2516
  const maxFiles = Math.floor(readPositiveInt(env.AUDIT_LOG_MAX_FILES, 20, "AUDIT_LOG_MAX_FILES"));
2517
2517
  return { maxBytes: Math.floor(maxSizeMb * 1024 * 1024), maxFiles };
2518
2518
  }
2519
- var import_node_fs4, import_node_path4, import_node_stream, activeStreams, exitHookInstalled, RotatingFileStream;
2519
+ var import_node_fs5, import_node_path5, import_node_stream, activeStreams, exitHookInstalled, RotatingFileStream;
2520
2520
  var init_rotating_file_stream = __esm({
2521
2521
  "packages/core/observability/rotating-file-stream.ts"() {
2522
2522
  "use strict";
2523
- import_node_fs4 = __toESM(require("node:fs"), 1);
2524
- import_node_path4 = __toESM(require("node:path"), 1);
2523
+ import_node_fs5 = __toESM(require("node:fs"), 1);
2524
+ import_node_path5 = __toESM(require("node:path"), 1);
2525
2525
  import_node_stream = require("node:stream");
2526
2526
  activeStreams = /* @__PURE__ */ new Set();
2527
2527
  exitHookInstalled = false;
@@ -2535,7 +2535,7 @@ var init_rotating_file_stream = __esm({
2535
2535
  this.filePath = options.filePath;
2536
2536
  this.maxBytes = options.maxBytes;
2537
2537
  this.maxFiles = options.maxFiles;
2538
- this.dir = import_node_path4.default.dirname(this.filePath);
2538
+ this.dir = import_node_path5.default.dirname(this.filePath);
2539
2539
  }
2540
2540
  /** 当前活动文件路径(测试与排障用) */
2541
2541
  get activeFilePath() {
@@ -2556,7 +2556,7 @@ var init_rotating_file_stream = __esm({
2556
2556
  if (this.currentSize > 0 && this.currentSize + buf.length > this.maxBytes) {
2557
2557
  this.rotate();
2558
2558
  }
2559
- const written = import_node_fs4.default.writeSync(this.fd, buf, 0, buf.length, null);
2559
+ const written = import_node_fs5.default.writeSync(this.fd, buf, 0, buf.length, null);
2560
2560
  this.currentSize += written;
2561
2561
  callback();
2562
2562
  } catch (err) {
@@ -2576,7 +2576,7 @@ var init_rotating_file_stream = __esm({
2576
2576
  closeFd() {
2577
2577
  if (this.fd !== null) {
2578
2578
  try {
2579
- import_node_fs4.default.closeSync(this.fd);
2579
+ import_node_fs5.default.closeSync(this.fd);
2580
2580
  } catch {
2581
2581
  }
2582
2582
  this.fd = null;
@@ -2585,11 +2585,11 @@ var init_rotating_file_stream = __esm({
2585
2585
  }
2586
2586
  ensureOpen() {
2587
2587
  if (this.fd !== null) return;
2588
- import_node_fs4.default.mkdirSync(this.dir, { recursive: true });
2589
- this.fd = import_node_fs4.default.openSync(this.filePath, "a");
2588
+ import_node_fs5.default.mkdirSync(this.dir, { recursive: true });
2589
+ this.fd = import_node_fs5.default.openSync(this.filePath, "a");
2590
2590
  registerStream(this);
2591
2591
  try {
2592
- this.currentSize = import_node_fs4.default.statSync(this.filePath).size;
2592
+ this.currentSize = import_node_fs5.default.statSync(this.filePath).size;
2593
2593
  } catch {
2594
2594
  this.currentSize = 0;
2595
2595
  }
@@ -2603,13 +2603,13 @@ var init_rotating_file_stream = __esm({
2603
2603
  this.closeFd();
2604
2604
  for (let i = this.maxFiles - 1; i >= 1; i -= 1) {
2605
2605
  const from = this.historyFilePath(i);
2606
- if (import_node_fs4.default.existsSync(from)) {
2606
+ if (import_node_fs5.default.existsSync(from)) {
2607
2607
  safeRename(from, this.historyFilePath(i + 1));
2608
2608
  }
2609
2609
  }
2610
2610
  safeRename(this.filePath, this.historyFilePath(1));
2611
2611
  this.currentSize = 0;
2612
- this.fd = import_node_fs4.default.openSync(this.filePath, "a");
2612
+ this.fd = import_node_fs5.default.openSync(this.filePath, "a");
2613
2613
  }
2614
2614
  };
2615
2615
  }
@@ -2640,28 +2640,28 @@ function consoleStream() {
2640
2640
  function createLogger(component) {
2641
2641
  return logger.child({ component });
2642
2642
  }
2643
- var import_pino, import_node_fs5, import_node_path5, defaultLevel, consoleLevel, fileLevel, rootLevel, logDir, logFile, streams, fileLogging, logger;
2643
+ var import_pino, import_node_fs6, import_node_path6, defaultLevel, consoleLevel, fileLevel, rootLevel, logDir, logFile, streams, fileLogging, logger;
2644
2644
  var init_logger = __esm({
2645
2645
  "packages/core/observability/logger.ts"() {
2646
2646
  "use strict";
2647
2647
  import_pino = __toESM(require("pino"), 1);
2648
- import_node_fs5 = __toESM(require("node:fs"), 1);
2649
- import_node_path5 = __toESM(require("node:path"), 1);
2648
+ import_node_fs6 = __toESM(require("node:fs"), 1);
2649
+ import_node_path6 = __toESM(require("node:path"), 1);
2650
2650
  init_redaction();
2651
2651
  init_rotating_file_stream();
2652
2652
  defaultLevel = process.env.LOG_LEVEL || (process.env.HTTP_ACCESS_LOG === "all" ? "debug" : process.env.NODE_ENV === "production" ? "info" : "debug");
2653
2653
  consoleLevel = process.env.LOG_CONSOLE_LEVEL || defaultLevel;
2654
2654
  fileLevel = defaultLevel;
2655
2655
  rootLevel = minPinoLevel(consoleLevel, fileLevel);
2656
- logDir = import_node_path5.default.resolve(process.env.LOG_DIR || import_node_path5.default.join(process.cwd(), "logs"));
2657
- logFile = import_node_path5.default.join(logDir, "openlearn.log");
2656
+ logDir = import_node_path6.default.resolve(process.env.LOG_DIR || import_node_path6.default.join(process.cwd(), "logs"));
2657
+ logFile = import_node_path6.default.join(logDir, "openlearn.log");
2658
2658
  streams = [
2659
2659
  { level: consoleLevel, stream: consoleStream() }
2660
2660
  ];
2661
2661
  fileLogging = false;
2662
2662
  try {
2663
- if (!import_node_fs5.default.existsSync(logDir)) import_node_fs5.default.mkdirSync(logDir, { recursive: true });
2664
- import_node_fs5.default.accessSync(logDir, import_node_fs5.default.constants.W_OK);
2663
+ if (!import_node_fs6.default.existsSync(logDir)) import_node_fs6.default.mkdirSync(logDir, { recursive: true });
2664
+ import_node_fs6.default.accessSync(logDir, import_node_fs6.default.constants.W_OK);
2665
2665
  fileLogging = true;
2666
2666
  } catch (err) {
2667
2667
  console.warn(`[logger] \u65E0\u6CD5\u5199\u5165\u65E5\u5FD7\u76EE\u5F55 ${logDir}\uFF0C\u4EC5\u8F93\u51FA\u5230 stdout\u3002`, err instanceof Error ? err.message : err);
@@ -2787,15 +2787,15 @@ function buildAuditLogger() {
2787
2787
  if (process.env.NODE_ENV === "test" || process.env.VITEST) {
2788
2788
  return (0, import_pino2.default)({ level: "silent" }, import_pino2.default.destination(1));
2789
2789
  }
2790
- const logDir2 = import_node_path6.default.resolve(process.env.LOG_DIR || import_node_path6.default.join(process.cwd(), "logs"));
2790
+ const logDir2 = import_node_path7.default.resolve(process.env.LOG_DIR || import_node_path7.default.join(process.cwd(), "logs"));
2791
2791
  try {
2792
- if (!import_node_fs6.default.existsSync(logDir2)) import_node_fs6.default.mkdirSync(logDir2, { recursive: true });
2793
- import_node_fs6.default.accessSync(logDir2, import_node_fs6.default.constants.W_OK);
2792
+ if (!import_node_fs7.default.existsSync(logDir2)) import_node_fs7.default.mkdirSync(logDir2, { recursive: true });
2793
+ import_node_fs7.default.accessSync(logDir2, import_node_fs7.default.constants.W_OK);
2794
2794
  const rotation = resolveAuditRotationConfig();
2795
2795
  streams2.push({
2796
2796
  level: "info",
2797
2797
  stream: new RotatingFileStream({
2798
- filePath: import_node_path6.default.join(logDir2, "audit.log"),
2798
+ filePath: import_node_path7.default.join(logDir2, "audit.log"),
2799
2799
  maxBytes: rotation.maxBytes,
2800
2800
  maxFiles: rotation.maxFiles
2801
2801
  })
@@ -2820,12 +2820,12 @@ function recordSecurityAudit(event, fields = {}) {
2820
2820
  }
2821
2821
  return record;
2822
2822
  }
2823
- var import_node_fs6, import_node_path6, import_pino2, testSink, auditLogger;
2823
+ var import_node_fs7, import_node_path7, import_pino2, testSink, auditLogger;
2824
2824
  var init_audit_log = __esm({
2825
2825
  "packages/core/observability/audit-log.ts"() {
2826
2826
  "use strict";
2827
- import_node_fs6 = __toESM(require("node:fs"), 1);
2828
- import_node_path6 = __toESM(require("node:path"), 1);
2827
+ import_node_fs7 = __toESM(require("node:fs"), 1);
2828
+ import_node_path7 = __toESM(require("node:path"), 1);
2829
2829
  import_pino2 = __toESM(require("pino"), 1);
2830
2830
  init_logger();
2831
2831
  init_request_context();
@@ -3875,9 +3875,9 @@ var init_http_router = __esm({
3875
3875
  /**
3876
3876
  * 注册指定动词与路径的 Handler
3877
3877
  */
3878
- route(method, path45, handler, options = {}) {
3878
+ route(method, path46, handler, options = {}) {
3879
3879
  const upperMethod = method.toUpperCase();
3880
- const normalized = path45.startsWith("/") ? path45 : "/" + path45;
3880
+ const normalized = path46.startsWith("/") ? path46 : "/" + path46;
3881
3881
  const { regex, paramNames } = compileRoutePattern(normalized);
3882
3882
  this.warnIfUnscoped(upperMethod, normalized, options);
3883
3883
  this.routes.push({
@@ -3890,20 +3890,20 @@ var init_http_router = __esm({
3890
3890
  scope: options.scope
3891
3891
  });
3892
3892
  }
3893
- get(path45, handler, options) {
3894
- this.route("GET", path45, handler, options);
3893
+ get(path46, handler, options) {
3894
+ this.route("GET", path46, handler, options);
3895
3895
  }
3896
- post(path45, handler, options) {
3897
- this.route("POST", path45, handler, options);
3896
+ post(path46, handler, options) {
3897
+ this.route("POST", path46, handler, options);
3898
3898
  }
3899
- put(path45, handler, options) {
3900
- this.route("PUT", path45, handler, options);
3899
+ put(path46, handler, options) {
3900
+ this.route("PUT", path46, handler, options);
3901
3901
  }
3902
- patch(path45, handler, options) {
3903
- this.route("PATCH", path45, handler, options);
3902
+ patch(path46, handler, options) {
3903
+ this.route("PATCH", path46, handler, options);
3904
3904
  }
3905
- delete(path45, handler, options) {
3906
- this.route("DELETE", path45, handler, options);
3905
+ delete(path46, handler, options) {
3906
+ this.route("DELETE", path46, handler, options);
3907
3907
  }
3908
3908
  warnIfUnscoped(method, pattern, options) {
3909
3909
  if (options.scope) return;
@@ -3918,19 +3918,19 @@ var init_http_router = __esm({
3918
3918
  }
3919
3919
  stream(methodOrPath, pathOrHandler, maybeHandler) {
3920
3920
  if (typeof pathOrHandler === "function") {
3921
- const path45 = methodOrPath;
3921
+ const path46 = methodOrPath;
3922
3922
  const handler = pathOrHandler;
3923
- this.registerStream("GET", path45, handler);
3924
- this.registerStream("POST", path45, handler);
3923
+ this.registerStream("GET", path46, handler);
3924
+ this.registerStream("POST", path46, handler);
3925
3925
  } else {
3926
3926
  const method = methodOrPath.toUpperCase();
3927
- const path45 = pathOrHandler;
3927
+ const path46 = pathOrHandler;
3928
3928
  const handler = maybeHandler;
3929
- this.registerStream(method, path45, handler);
3929
+ this.registerStream(method, path46, handler);
3930
3930
  }
3931
3931
  }
3932
- registerStream(method, path45, handler) {
3933
- const normalized = path45.startsWith("/") ? path45 : "/" + path45;
3932
+ registerStream(method, path46, handler) {
3933
+ const normalized = path46.startsWith("/") ? path46 : "/" + path46;
3934
3934
  const { regex, paramNames } = compileRoutePattern(normalized);
3935
3935
  this.routes.push({
3936
3936
  method: method.toUpperCase(),
@@ -3944,9 +3944,9 @@ var init_http_router = __esm({
3944
3944
  /**
3945
3945
  * 检查指定动词与路径是否为流式路由
3946
3946
  */
3947
- isStream(method, path45) {
3947
+ isStream(method, path46) {
3948
3948
  const upperMethod = method.toUpperCase();
3949
- const normalized = path45.startsWith("/") ? path45 : "/" + path45;
3949
+ const normalized = path46.startsWith("/") ? path46 : "/" + path46;
3950
3950
  for (const entry of this.routes) {
3951
3951
  if (entry.method !== upperMethod) continue;
3952
3952
  if (entry.regex.test(normalized)) {
@@ -3958,9 +3958,9 @@ var init_http_router = __esm({
3958
3958
  /**
3959
3959
  * 匹配路由并提取路由参数
3960
3960
  */
3961
- match(method, path45) {
3961
+ match(method, path46) {
3962
3962
  const upperMethod = method.toUpperCase();
3963
- const normalized = path45.startsWith("/") ? path45 : "/" + path45;
3963
+ const normalized = path46.startsWith("/") ? path46 : "/" + path46;
3964
3964
  for (const entry of this.routes) {
3965
3965
  if (entry.method !== upperMethod) continue;
3966
3966
  const match = normalized.match(entry.regex);
@@ -4103,13 +4103,13 @@ function checkMethodPolicy(input) {
4103
4103
  if (tokens.frontendApi && token === tokens.frontendApi) {
4104
4104
  if (isReadOnlyMethod(method)) return null;
4105
4105
  if (method === "request" && !Array.isArray(args)) return null;
4106
- const path45 = extractPathArg(args);
4107
- const isHighRisk = HIGH_RISK_PATH_PREFIXES.some((p) => path45 === p || path45.startsWith(`${p}/`));
4108
- const isIrreversible = IRREVERSIBLE_PATH_SUFFIXES.some((s) => path45.endsWith(s));
4109
- const isEntityDelete = method === "del" && ENTITY_DELETE_RE.test(path45);
4106
+ const path46 = extractPathArg(args);
4107
+ const isHighRisk = HIGH_RISK_PATH_PREFIXES.some((p) => path46 === p || path46.startsWith(`${p}/`));
4108
+ const isIrreversible = IRREVERSIBLE_PATH_SUFFIXES.some((s) => path46.endsWith(s));
4109
+ const isEntityDelete = method === "del" && ENTITY_DELETE_RE.test(path46);
4110
4110
  if (isHighRisk || isIrreversible || isEntityDelete) {
4111
4111
  if (hasCapability(caps, CAP_API_ADMIN)) return null;
4112
- const why = isIrreversible ? `\u4E0D\u53EF\u9006\u64CD\u4F5C '${path45}'` : isEntityDelete ? `\u5B9E\u4F53\u7EA7\u5220\u9664 '${path45}'` : `\u5199\u5165\u9AD8\u5371\u8DEF\u5F84 '${path45 || "<\u672A\u63D0\u4F9B\u8DEF\u5F84>"}'`;
4112
+ const why = isIrreversible ? `\u4E0D\u53EF\u9006\u64CD\u4F5C '${path46}'` : isEntityDelete ? `\u5B9E\u4F53\u7EA7\u5220\u9664 '${path46}'` : `\u5199\u5165\u9AD8\u5371\u8DEF\u5F84 '${path46 || "<\u672A\u63D0\u4F9B\u8DEF\u5F84>"}'`;
4113
4113
  return `${why} \u9700\u8981 manifest \u58F0\u660E '${CAP_API_ADMIN}' \u80FD\u529B`;
4114
4114
  }
4115
4115
  return hasCapability(caps, CAP_API_WRITE) ? null : `\u5199\u64CD\u4F5C\uFF08${method}\uFF09\u9700\u8981 manifest \u58F0\u660E '${CAP_API_WRITE}' \u80FD\u529B\uFF08\u666E\u901A\u53EA\u8BFB\u8C03\u7528\u8BF7\u7528 get\uFF09`;
@@ -5714,7 +5714,7 @@ function spawnPluginChild(opts) {
5714
5714
  });
5715
5715
  const removeBootstrapFile = () => {
5716
5716
  try {
5717
- import_node_fs8.default.unlinkSync(bootstrapFile);
5717
+ import_node_fs9.default.unlinkSync(bootstrapFile);
5718
5718
  } catch {
5719
5719
  }
5720
5720
  };
@@ -5740,25 +5740,25 @@ function isPidAlive(pid) {
5740
5740
  }
5741
5741
  }
5742
5742
  function writeBootstrapTempFile(code) {
5743
- const file = import_node_path9.default.join(
5743
+ const file = import_node_path10.default.join(
5744
5744
  import_node_os.default.tmpdir(),
5745
5745
  `${BOOTSTRAP_PREFIX}${process.pid}-${import_node_crypto7.default.randomBytes(8).toString("hex")}${BOOTSTRAP_SUFFIX}`
5746
5746
  );
5747
- import_node_fs8.default.writeFileSync(file, code, { encoding: "utf8", mode: 384 });
5747
+ import_node_fs9.default.writeFileSync(file, code, { encoding: "utf8", mode: 384 });
5748
5748
  return file;
5749
5749
  }
5750
5750
  function cleanupStaleBootstrapFiles(now = Date.now()) {
5751
5751
  try {
5752
5752
  const tmpDir = import_node_os.default.tmpdir();
5753
- const files = import_node_fs8.default.readdirSync(tmpDir);
5753
+ const files = import_node_fs9.default.readdirSync(tmpDir);
5754
5754
  let cleaned = 0;
5755
5755
  for (const file of files) {
5756
5756
  if (!file.startsWith(BOOTSTRAP_PREFIX) || !file.endsWith(BOOTSTRAP_SUFFIX)) continue;
5757
- const full = import_node_path9.default.join(tmpDir, file);
5757
+ const full = import_node_path10.default.join(tmpDir, file);
5758
5758
  const ownerPid = bootstrapOwnerPid(file);
5759
5759
  if (ownerPid === null) {
5760
5760
  try {
5761
- if (now - import_node_fs8.default.statSync(full).mtimeMs < LEGACY_BOOTSTRAP_STALE_MS) continue;
5761
+ if (now - import_node_fs9.default.statSync(full).mtimeMs < LEGACY_BOOTSTRAP_STALE_MS) continue;
5762
5762
  } catch {
5763
5763
  continue;
5764
5764
  }
@@ -5766,7 +5766,7 @@ function cleanupStaleBootstrapFiles(now = Date.now()) {
5766
5766
  continue;
5767
5767
  }
5768
5768
  try {
5769
- import_node_fs8.default.unlinkSync(full);
5769
+ import_node_fs9.default.unlinkSync(full);
5770
5770
  cleaned++;
5771
5771
  } catch {
5772
5772
  }
@@ -5781,7 +5781,7 @@ function defaultNetPreloadStartDirs() {
5781
5781
  try {
5782
5782
  const url = import_meta5.url;
5783
5783
  if (typeof url === "string" && url.startsWith("file:")) {
5784
- dirs.push(import_node_path9.default.dirname((0, import_node_url3.fileURLToPath)(url)));
5784
+ dirs.push(import_node_path10.default.dirname((0, import_node_url3.fileURLToPath)(url)));
5785
5785
  }
5786
5786
  } catch {
5787
5787
  }
@@ -5793,27 +5793,27 @@ function defaultNetPreloadStartDirs() {
5793
5793
  function resolveNetPreloadPath(options = {}) {
5794
5794
  const cwd = options.cwd ?? process.cwd();
5795
5795
  const candidates = [
5796
- import_node_path9.default.resolve(cwd, "packages/core/worker-runtime/plugin-net-preload.cjs"),
5797
- import_node_path9.default.resolve(cwd, "dist/plugin-net-preload.cjs")
5796
+ import_node_path10.default.resolve(cwd, "packages/core/worker-runtime/plugin-net-preload.cjs"),
5797
+ import_node_path10.default.resolve(cwd, "dist/plugin-net-preload.cjs")
5798
5798
  ];
5799
5799
  for (const dir of options.startDirs ?? defaultNetPreloadStartDirs()) {
5800
- candidates.unshift(import_node_path9.default.join(dir, "plugin-net-preload.cjs"));
5800
+ candidates.unshift(import_node_path10.default.join(dir, "plugin-net-preload.cjs"));
5801
5801
  }
5802
- const found = candidates.find((candidate) => import_node_fs8.default.existsSync(candidate));
5802
+ const found = candidates.find((candidate) => import_node_fs9.default.existsSync(candidate));
5803
5803
  if (!found) {
5804
5804
  throw new Error(`[PluginNet] preload script not found. Looked in: ${candidates.join(", ")}`);
5805
5805
  }
5806
5806
  return found;
5807
5807
  }
5808
- var import_node_child_process, import_node_crypto7, import_node_fs8, import_node_os, import_node_path9, import_node_url3, import_meta5, ENV_ALLOWLIST, ENV_DENYLIST, WORKER_DATA_ENV, BOOTSTRAP_PREFIX, BOOTSTRAP_SUFFIX, LEGACY_BOOTSTRAP_STALE_MS;
5808
+ var import_node_child_process, import_node_crypto7, import_node_fs9, import_node_os, import_node_path10, import_node_url3, import_meta5, ENV_ALLOWLIST, ENV_DENYLIST, WORKER_DATA_ENV, BOOTSTRAP_PREFIX, BOOTSTRAP_SUFFIX, LEGACY_BOOTSTRAP_STALE_MS;
5809
5809
  var init_child_spawn = __esm({
5810
5810
  "packages/core/worker-runtime/child-spawn.ts"() {
5811
5811
  "use strict";
5812
5812
  import_node_child_process = require("node:child_process");
5813
5813
  import_node_crypto7 = __toESM(require("node:crypto"), 1);
5814
- import_node_fs8 = __toESM(require("node:fs"), 1);
5814
+ import_node_fs9 = __toESM(require("node:fs"), 1);
5815
5815
  import_node_os = __toESM(require("node:os"), 1);
5816
- import_node_path9 = __toESM(require("node:path"), 1);
5816
+ import_node_path10 = __toESM(require("node:path"), 1);
5817
5817
  import_node_url3 = require("node:url");
5818
5818
  init_child_transport();
5819
5819
  init_child_isolate();
@@ -5869,12 +5869,12 @@ function quotaExceeded(input) {
5869
5869
  }
5870
5870
  function readProcSample(pid) {
5871
5871
  try {
5872
- const stat = import_node_fs9.default.readFileSync(`/proc/${pid}/stat`, "utf8");
5872
+ const stat = import_node_fs10.default.readFileSync(`/proc/${pid}/stat`, "utf8");
5873
5873
  const rest = stat.slice(stat.lastIndexOf(")") + 2).trim().split(/\s+/);
5874
5874
  const utime = Number(rest[11]);
5875
5875
  const stime = Number(rest[12]);
5876
5876
  if (!Number.isFinite(utime) || !Number.isFinite(stime)) return null;
5877
- const status = import_node_fs9.default.readFileSync(`/proc/${pid}/status`, "utf8");
5877
+ const status = import_node_fs10.default.readFileSync(`/proc/${pid}/status`, "utf8");
5878
5878
  const rss = /^VmRSS:\s+(\d+)\s+kB/m.exec(status);
5879
5879
  return { cpuMs: (utime + stime) / 100 * 1e3, rssKb: rss ? Number(rss[1]) : 0 };
5880
5880
  } catch {
@@ -5917,11 +5917,11 @@ function startProcessQuota(pid, onBreach, env = process.env) {
5917
5917
  }
5918
5918
  };
5919
5919
  }
5920
- var import_node_fs9, DEFAULT_MAX_RSS_MB, DEFAULT_CPU_QUOTA_MS, DEFAULT_CPU_WINDOW_MS;
5920
+ var import_node_fs10, DEFAULT_MAX_RSS_MB, DEFAULT_CPU_QUOTA_MS, DEFAULT_CPU_WINDOW_MS;
5921
5921
  var init_plugin_quota = __esm({
5922
5922
  "packages/core/worker-runtime/plugin-quota.ts"() {
5923
5923
  "use strict";
5924
- import_node_fs9 = __toESM(require("node:fs"), 1);
5924
+ import_node_fs10 = __toESM(require("node:fs"), 1);
5925
5925
  DEFAULT_MAX_RSS_MB = 512;
5926
5926
  DEFAULT_CPU_QUOTA_MS = 8e3;
5927
5927
  DEFAULT_CPU_WINDOW_MS = 1e4;
@@ -8923,9 +8923,9 @@ __export(worker_manager_exports, {
8923
8923
  function collectSharedReadPaths(pluginDir) {
8924
8924
  if (!pluginDir) return [];
8925
8925
  const paths = /* @__PURE__ */ new Set();
8926
- const nodeModulesDir = import_node_path10.default.join(import_node_path10.default.dirname(pluginDir), "node_modules");
8926
+ const nodeModulesDir = import_node_path11.default.join(import_node_path11.default.dirname(pluginDir), "node_modules");
8927
8927
  paths.add(nodeModulesDir);
8928
- const sdkLink = import_node_path10.default.join(nodeModulesDir, "@openlearn", "plugin-sdk");
8928
+ const sdkLink = import_node_path11.default.join(nodeModulesDir, "@openlearn", "plugin-sdk");
8929
8929
  try {
8930
8930
  if (import_fs7.default.existsSync(sdkLink)) paths.add(import_fs7.default.realpathSync(sdkLink));
8931
8931
  } catch {
@@ -8964,7 +8964,7 @@ function generateBootstrapCode() {
8964
8964
  const rootPath = process.cwd().replace(/\\/g, "/");
8965
8965
  return composeBootstrapCode(`${rootPath}/package.json`);
8966
8966
  }
8967
- var import_node_worker_threads, import_fs7, import_node_path10, import_uuid12, BASE_WORKER_SERVICE_TOKENS, ALL_SERVICE_TOKENS, POINTS_SERVICE_TOKENS, MAX_WORKERS, ACTIVATE_TIMEOUT_MS, ACTIVATE_PROGRESS_SLIDE_MS, WorkerRegistry, WorkerManager;
8967
+ var import_node_worker_threads, import_fs7, import_node_path11, import_uuid12, BASE_WORKER_SERVICE_TOKENS, ALL_SERVICE_TOKENS, POINTS_SERVICE_TOKENS, MAX_WORKERS, ACTIVATE_TIMEOUT_MS, ACTIVATE_PROGRESS_SLIDE_MS, WorkerRegistry, WorkerManager;
8968
8968
  var init_worker_manager = __esm({
8969
8969
  "packages/core/worker-runtime/worker-manager.ts"() {
8970
8970
  "use strict";
@@ -8978,7 +8978,7 @@ var init_worker_manager = __esm({
8978
8978
  init_plugin_limits();
8979
8979
  init_thread_isolate();
8980
8980
  import_fs7 = __toESM(require("fs"), 1);
8981
- import_node_path10 = __toESM(require("node:path"), 1);
8981
+ import_node_path11 = __toESM(require("node:path"), 1);
8982
8982
  init_transport();
8983
8983
  init_plugin_net_proxy();
8984
8984
  init_service_host();
@@ -9383,7 +9383,7 @@ var init_worker_manager = __esm({
9383
9383
  const encodedBootstrap = Buffer.from(bootstrapCode, "utf-8").toString("base64");
9384
9384
  const bootstrapDataUrl = `data:text/javascript;base64,${encodedBootstrap}`;
9385
9385
  const allowedTokens = computeAllowedWorkerTokens(manifest, serviceTokens);
9386
- const resolvedPluginDir = pluginDir && import_fs7.default.existsSync(import_node_path10.default.join(pluginDir, "index.js")) ? pluginDir : void 0;
9386
+ const resolvedPluginDir = pluginDir && import_fs7.default.existsSync(import_node_path11.default.join(pluginDir, "index.js")) ? pluginDir : void 0;
9387
9387
  let approvedNetHosts = [];
9388
9388
  try {
9389
9389
  approvedNetHosts = listApprovedNetHosts(this.db, [pluginId, manifest.id]);
@@ -11027,7 +11027,7 @@ function simpleChecksum(text) {
11027
11027
  }
11028
11028
  return hash.toString(16);
11029
11029
  }
11030
- function runMigrations(db2, migrations) {
11030
+ function runMigrations(db2, migrations, options = {}) {
11031
11031
  db2.exec(`
11032
11032
  CREATE TABLE IF NOT EXISTS _migrations (
11033
11033
  name TEXT PRIMARY KEY,
@@ -11038,6 +11038,23 @@ function runMigrations(db2, migrations) {
11038
11038
  const appliedRows = db2.prepare("SELECT name, checksum FROM _migrations").all();
11039
11039
  const applied = new Map(appliedRows.map((r) => [r.name, r.checksum]));
11040
11040
  const byName = new Map(migrations.map((m) => [m.name, m]));
11041
+ const unknown = [...applied.keys()].filter((name) => !byName.has(name));
11042
+ if (unknown.length > 0) {
11043
+ const detail = unknown.map((n) => ` - ${n}`).join("\n");
11044
+ const msg = `[Migration] \u53F0\u8D26\u91CC\u6709 ${unknown.length} \u4E2A\u672C\u6B21\u8FC1\u79FB\u5217\u8868\u672A\u5305\u542B\u7684\u6761\u76EE\uFF1A
11045
+ ${detail}
11046
+ ` + (options.strictLedger ? ` \u542F\u52A8\u94FE\u8DEF\u5224\u5B9A\u4E3A FATAL\uFF1A
11047
+ \u6210\u56E0\u4E00\uFF1A\u7528\u66F4\u65B0\u7248\u672C\u7684\u5907\u4EFD\u6062\u590D\u4E86\u65E7\u4EE3\u7801\uFF08\u4EE3\u7801\u964D\u7EA7\uFF09\u2014\u2014\u8BF7\u5148\u5347\u7EA7\u4EE3\u7801\u518D\u542F\u52A8\uFF1B
11048
+ \u6210\u56E0\u4E8C\uFF1A\u5DF2\u5E94\u7528\u7684\u8FC1\u79FB\u6587\u4EF6\u88AB\u6539\u540D\u6216\u5220\u9664\u2014\u2014\u8FC1\u79FB\u4E00\u65E6\u5E94\u7528\u4E0D\u53EF\u6539\u540D\uFF0C\u8BF7\u6062\u590D\u6587\u4EF6\u6216\u8865\u4E00\u6761\u65B0\u8FC1\u79FB\u3002
11049
+ \u786E\u5C5E\u6709\u610F\u7684\u964D\u7EA7\u8FD0\u7EF4\uFF1A\u8BBE\u7F6E MIGRATION_ALLOW_UNKNOWN_LEDGER=true \u653E\u884C\uFF08\u4E0D\u63A8\u8350\uFF09\u3002` : ` \uFF08\u975E\u4E25\u683C\u53E3\u5F84\uFF1A\u8FC1\u79FB\u5B50\u96C6\u8C03\u7528\u5C5E\u5408\u6CD5\u7528\u6CD5\uFF0C\u4EC5\u63D0\u793A\uFF09`);
11050
+ if (process.env.MIGRATION_ALLOW_UNKNOWN_LEDGER === "true") {
11051
+ console.warn("[Migration] WARNING: \u672A\u77E5\u53F0\u8D26\u6761\u76EE\u5DF2\u6309 MIGRATION_ALLOW_UNKNOWN_LEDGER \u653E\u884C\uFF1A\n" + msg);
11052
+ } else if (options.strictLedger) {
11053
+ throw new Error(msg);
11054
+ } else {
11055
+ console.warn("[Migration] " + msg);
11056
+ }
11057
+ }
11041
11058
  const drift = [];
11042
11059
  for (const [name, stored] of applied) {
11043
11060
  const migration = byName.get(name);
@@ -11145,6 +11162,68 @@ function getDefaultMigrationsDir(options = {}) {
11145
11162
  return import_path.default.resolve(cwd, "migrations");
11146
11163
  }
11147
11164
 
11165
+ // packages/core/db/schema-golden.ts
11166
+ var import_node_fs3 = __toESM(require("node:fs"), 1);
11167
+ var import_node_path3 = __toESM(require("node:path"), 1);
11168
+ var SCHEMA_GOLDEN_FILENAME = "schema-golden.json";
11169
+ function extractSchemaSnapshot(db2) {
11170
+ const tables = db2.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name").all();
11171
+ const snapshot = {};
11172
+ for (const { name } of tables) {
11173
+ const columns = db2.prepare(`PRAGMA table_info(${JSON.stringify(name)})`).all().map(
11174
+ (c) => c.name
11175
+ );
11176
+ snapshot[name] = columns.sort();
11177
+ }
11178
+ return snapshot;
11179
+ }
11180
+ function verifySchemaAgainstGolden(db2, golden) {
11181
+ const actual = extractSchemaSnapshot(db2);
11182
+ const drifts = [];
11183
+ for (const [table, expectedColumns] of Object.entries(golden.tables)) {
11184
+ const actualColumns = actual[table];
11185
+ if (!actualColumns) {
11186
+ drifts.push({ table, missingColumns: expectedColumns, tableMissing: true });
11187
+ continue;
11188
+ }
11189
+ const actualSet = new Set(actualColumns);
11190
+ const missing = expectedColumns.filter((c) => !actualSet.has(c));
11191
+ if (missing.length > 0) {
11192
+ drifts.push({ table, missingColumns: missing, tableMissing: false });
11193
+ }
11194
+ }
11195
+ return drifts;
11196
+ }
11197
+ function formatSchemaGoldenDrifts(drifts) {
11198
+ return drifts.map(
11199
+ (d) => d.tableMissing ? ` - \u8868 ${d.table} \u6574\u8868\u7F3A\u5931\uFF08\u671F\u671B\u5217\uFF1A${d.missingColumns.join(", ")}\uFF09` : ` - \u8868 ${d.table} \u7F3A\u5217\uFF1A${d.missingColumns.join(", ")}`
11200
+ ).join("\n");
11201
+ }
11202
+ function loadSchemaGolden(migrationsDir) {
11203
+ const filePath = import_node_path3.default.join(migrationsDir, SCHEMA_GOLDEN_FILENAME);
11204
+ if (!import_node_fs3.default.existsSync(filePath)) {
11205
+ throw new Error(
11206
+ `[SchemaGolden] FATAL: ${filePath} \u4E0D\u5B58\u5728\u3002\u91D1\u6837\u4E0E\u8FC1\u79FB\u6587\u4EF6\u540C\u76EE\u5F55\u53D1\u5E03\uFF0C\u7F3A\u5931\u8BF4\u660E\u5B89\u88C5\u5305\u4E0D\u5B8C\u6574\uFF1B\u6E90\u7801\u4ED3\u5E93\u91CC\u8BF7\u8FD0\u884C pnpm db:gen-golden \u751F\u6210\u3002`
11207
+ );
11208
+ }
11209
+ try {
11210
+ return JSON.parse(import_node_fs3.default.readFileSync(filePath, "utf8"));
11211
+ } catch (err) {
11212
+ throw new Error(`[SchemaGolden] FATAL: \u91D1\u6837\u6587\u4EF6\u89E3\u6790\u5931\u8D25\uFF08${filePath}\uFF09\uFF1A${String(err)}`, { cause: err });
11213
+ }
11214
+ }
11215
+ function assertSchemaMatchesGolden(db2, migrationsDir) {
11216
+ const golden = loadSchemaGolden(migrationsDir);
11217
+ const drifts = verifySchemaAgainstGolden(db2, golden);
11218
+ if (drifts.length > 0) {
11219
+ throw new Error(
11220
+ `[SchemaGolden] FATAL: \u6570\u636E\u5E93\u7ED3\u6784\u4F4E\u4E8E\u4EE3\u7801\u671F\u671B\uFF08${drifts.length} \u5904\u7F3A\u5931\uFF09\u2014\u2014\u8FC1\u79FB\u53F0\u8D26\u867D\u5DF2\u8FFD\u5E73\uFF0C\u4F46\u5E93\u88AB\u5916\u90E8\u4FEE\u6539\u8FC7\u6216\u4ECE\u65E7\u5907\u4EFD\u505A\u4E86\u90E8\u5206\u6062\u590D\u3002\u7F3A\u5931\u660E\u7EC6\uFF1A
11221
+ ${formatSchemaGoldenDrifts(drifts)}
11222
+ \u6062\u590D\u624B\u6BB5\uFF1A\u7528 backups/ \u91CC\u6700\u65B0\u7684 pre_migration_*.db \u5FEB\u7167\u6062\u590D\uFF0C\u6216\u6838\u5BF9\u6700\u8FD1\u4E00\u6B21\u6062\u590D\u64CD\u4F5C\u7684\u6765\u6E90\u3002`
11223
+ );
11224
+ }
11225
+ }
11226
+
11148
11227
  // packages/core/db/class-passcode-crypto.ts
11149
11228
  var import_node_crypto2 = __toESM(require("node:crypto"), 1);
11150
11229
  init_api_key_crypto();
@@ -11802,7 +11881,16 @@ function initializeDatabase(instance) {
11802
11881
  if (migrations.length === 0) {
11803
11882
  throw new Error(`[DB SSOT] FATAL: \u8FC1\u79FB\u76EE\u5F55 ${migrationsDir} \u91CC\u6CA1\u6709 .sql \u6587\u4EF6\u3002\u62D2\u7EDD\u5728\u6CA1\u6709 schema \u7684\u5E93\u4E0A\u542F\u52A8\u3002`);
11804
11883
  }
11805
- runMigrations(instance, migrations);
11884
+ runMigrations(instance, migrations, { strictLedger: true });
11885
+ if (process.env.VITEST) {
11886
+ try {
11887
+ assertSchemaMatchesGolden(instance, migrationsDir);
11888
+ } catch (err) {
11889
+ console.warn("[SchemaGolden] \u6D4B\u8BD5\u73AF\u5883\u7ED3\u6784\u7EC8\u68C0\u672A\u901A\u8FC7\uFF08\u5FFD\u7565\uFF09\uFF1A", err instanceof Error ? err.message : err);
11890
+ }
11891
+ } else {
11892
+ assertSchemaMatchesGolden(instance, migrationsDir);
11893
+ }
11806
11894
  applyStartupDataMigrations(instance);
11807
11895
  const apiKeyMigration = encryptStoredPlaintextApiKeys(instance);
11808
11896
  if (apiKeyMigration.updated > 0) {
@@ -14061,10 +14149,10 @@ function parseScoreFields(fields) {
14061
14149
  if (typeof fields !== "string" || !fields.trim()) return [];
14062
14150
  return fields.split(/[\n\r,,;;|]+/).map((f) => f.trim()).filter(Boolean);
14063
14151
  }
14064
- function getNested(raw, path45) {
14152
+ function getNested(raw, path46) {
14065
14153
  if (raw === null || raw === void 0) return void 0;
14066
- if (!path45) return void 0;
14067
- const parts = String(path45).split(".").map((p) => p.trim()).filter(Boolean);
14154
+ if (!path46) return void 0;
14155
+ const parts = String(path46).split(".").map((p) => p.trim()).filter(Boolean);
14068
14156
  let cursor = raw;
14069
14157
  for (let i = 0; i < parts.length; i += 1) {
14070
14158
  if (cursor === null || cursor === void 0) return void 0;
@@ -18655,7 +18743,7 @@ var import_semver2 = __toESM(require("semver"), 1);
18655
18743
  init_types();
18656
18744
 
18657
18745
  // packages/core/plugin-host/execution-policy.ts
18658
- var import_node_path7 = __toESM(require("node:path"), 1);
18746
+ var import_node_path8 = __toESM(require("node:path"), 1);
18659
18747
  var import_node_url2 = require("node:url");
18660
18748
  init_types();
18661
18749
  var import_meta4 = {};
@@ -18668,21 +18756,21 @@ function builtinPluginsDir() {
18668
18756
  try {
18669
18757
  const url = import_meta4.url;
18670
18758
  if (typeof url === "string" && url.startsWith("file:")) {
18671
- return import_node_path7.default.resolve(import_node_path7.default.dirname((0, import_node_url2.fileURLToPath)(url)), "../../plugins");
18759
+ return import_node_path8.default.resolve(import_node_path8.default.dirname((0, import_node_url2.fileURLToPath)(url)), "../../plugins");
18672
18760
  }
18673
18761
  } catch {
18674
18762
  }
18675
18763
  if (typeof __dirname !== "undefined") {
18676
- return import_node_path7.default.resolve(__dirname, "../packages/plugins");
18764
+ return import_node_path8.default.resolve(__dirname, "../packages/plugins");
18677
18765
  }
18678
- return import_node_path7.default.resolve(process.cwd(), "packages/plugins");
18766
+ return import_node_path8.default.resolve(process.cwd(), "packages/plugins");
18679
18767
  }
18680
18768
  function isInsideBuiltinPluginsDir(sourcePath, builtinDir = builtinPluginsDir()) {
18681
18769
  if (!sourcePath) return false;
18682
- const root = import_node_path7.default.resolve(builtinDir);
18683
- const resolved = import_node_path7.default.resolve(sourcePath);
18684
- const relative2 = import_node_path7.default.relative(root, resolved);
18685
- return relative2 === "" || !relative2.startsWith("..") && !import_node_path7.default.isAbsolute(relative2);
18770
+ const root = import_node_path8.default.resolve(builtinDir);
18771
+ const resolved = import_node_path8.default.resolve(sourcePath);
18772
+ const relative2 = import_node_path8.default.relative(root, resolved);
18773
+ return relative2 === "" || !relative2.startsWith("..") && !import_node_path8.default.isAbsolute(relative2);
18686
18774
  }
18687
18775
  function isTrustedInProcessPlugin(manifestId, env = process.env, sourcePath) {
18688
18776
  if (isInsideBuiltinPluginsDir(sourcePath)) return true;
@@ -19742,7 +19830,7 @@ var PluginHostHttp = class extends PluginHostCore {
19742
19830
  /**
19743
19831
  * 判断指定插件的某个请求路径是否为流式路由 (SSE)
19744
19832
  */
19745
- isStreamRoute(pluginIdOrManifestId, method, path45) {
19833
+ isStreamRoute(pluginIdOrManifestId, method, path46) {
19746
19834
  const upperMethod = method.toUpperCase();
19747
19835
  const resolvedId = this.resolvePluginUuid(pluginIdOrManifestId);
19748
19836
  const instance = this.pluginInstances.get(resolvedId) ?? this.pluginInstances.get(pluginIdOrManifestId);
@@ -19751,7 +19839,7 @@ var PluginHostHttp = class extends PluginHostCore {
19751
19839
  for (const r of manifest.api.routes) {
19752
19840
  if (r.method.toUpperCase() === upperMethod && r.streaming === true) {
19753
19841
  const compiled = compileRoutePattern(r.path);
19754
- if (compiled.regex.test(path45)) {
19842
+ if (compiled.regex.test(path46)) {
19755
19843
  return true;
19756
19844
  }
19757
19845
  }
@@ -19759,14 +19847,14 @@ var PluginHostHttp = class extends PluginHostCore {
19759
19847
  }
19760
19848
  if (!instance) return false;
19761
19849
  if (instance.context?.http) {
19762
- return instance.context.http.isStream(upperMethod, path45);
19850
+ return instance.context.http.isStream(upperMethod, path46);
19763
19851
  }
19764
19852
  if (instance.workerRef) {
19765
19853
  const routes = instance.workerRef.serviceHost.getRegisteredRoutes();
19766
19854
  for (const r of routes) {
19767
19855
  if (r.method === upperMethod && r.isStream) {
19768
19856
  const compiled = compileRoutePattern(r.pattern);
19769
- if (compiled.regex.test(path45)) {
19857
+ if (compiled.regex.test(path46)) {
19770
19858
  return true;
19771
19859
  }
19772
19860
  }
@@ -19839,8 +19927,8 @@ init_manifest_schema();
19839
19927
  init_context_builder();
19840
19928
 
19841
19929
  // packages/core/plugin-host/declarative-migrator.ts
19842
- var import_node_fs7 = __toESM(require("node:fs"), 1);
19843
- var import_node_path8 = __toESM(require("node:path"), 1);
19930
+ var import_node_fs8 = __toESM(require("node:fs"), 1);
19931
+ var import_node_path9 = __toESM(require("node:path"), 1);
19844
19932
  init_errors2();
19845
19933
  init_plugin_tables();
19846
19934
  function ensurePluginDeclaredMigrationsTable(db2) {
@@ -19856,7 +19944,7 @@ function ensurePluginDeclaredMigrationsTable(db2) {
19856
19944
  `);
19857
19945
  }
19858
19946
  function parseMigrationFileName(fileName) {
19859
- const baseName = import_node_path8.default.basename(fileName, ".sql");
19947
+ const baseName = import_node_path9.default.basename(fileName, ".sql");
19860
19948
  const match = baseName.match(/^(\d+)[_-]?(.*)$/);
19861
19949
  if (match) {
19862
19950
  const version = parseInt(match[1], 10);
@@ -19865,22 +19953,22 @@ function parseMigrationFileName(fileName) {
19865
19953
  return { version: 0, baseName };
19866
19954
  }
19867
19955
  function loadPluginMigrations(migrationsDir) {
19868
- if (!import_node_fs7.default.existsSync(migrationsDir)) {
19956
+ if (!import_node_fs8.default.existsSync(migrationsDir)) {
19869
19957
  return [];
19870
19958
  }
19871
- const stat = import_node_fs7.default.statSync(migrationsDir);
19959
+ const stat = import_node_fs8.default.statSync(migrationsDir);
19872
19960
  if (!stat.isDirectory()) {
19873
19961
  return [];
19874
19962
  }
19875
- const files = import_node_fs7.default.readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort((a, b) => {
19963
+ const files = import_node_fs8.default.readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort((a, b) => {
19876
19964
  const vA = parseMigrationFileName(a).version;
19877
19965
  const vB = parseMigrationFileName(b).version;
19878
19966
  if (vA !== vB) return vA - vB;
19879
19967
  return a.localeCompare(b, void 0, { numeric: true, sensitivity: "base" });
19880
19968
  });
19881
19969
  return files.map((file) => {
19882
- const fullPath = import_node_path8.default.join(migrationsDir, file);
19883
- const content = import_node_fs7.default.readFileSync(fullPath, "utf8");
19970
+ const fullPath = import_node_path9.default.join(migrationsDir, file);
19971
+ const content = import_node_fs8.default.readFileSync(fullPath, "utf8");
19884
19972
  const { version, baseName } = parseMigrationFileName(file);
19885
19973
  const parsed = parseMigrationSql(baseName, content);
19886
19974
  return {
@@ -20636,27 +20724,27 @@ var import_path8 = __toESM(require("path"), 1);
20636
20724
  var import_semver3 = __toESM(require("semver"), 1);
20637
20725
 
20638
20726
  // packages/core/version.ts
20639
- var import_node_fs10 = __toESM(require("node:fs"), 1);
20640
- var import_node_path11 = __toESM(require("node:path"), 1);
20727
+ var import_node_fs11 = __toESM(require("node:fs"), 1);
20728
+ var import_node_path12 = __toESM(require("node:path"), 1);
20641
20729
  var import_node_url5 = require("node:url");
20642
20730
  var import_meta6 = {};
20643
- var FALLBACK_VERSION = "0.7.2";
20731
+ var FALLBACK_VERSION = "0.7.4";
20644
20732
  function readPlatformVersion() {
20645
20733
  const bases = [];
20646
20734
  if (typeof __dirname !== "undefined") bases.push(__dirname);
20647
20735
  try {
20648
- bases.push(import_node_path11.default.dirname((0, import_node_url5.fileURLToPath)(import_meta6.url)));
20736
+ bases.push(import_node_path12.default.dirname((0, import_node_url5.fileURLToPath)(import_meta6.url)));
20649
20737
  } catch {
20650
20738
  }
20651
20739
  for (const base of bases) {
20652
20740
  let dir = base;
20653
20741
  for (; ; ) {
20654
20742
  try {
20655
- const pkg = JSON.parse(import_node_fs10.default.readFileSync(import_node_path11.default.join(dir, "package.json"), "utf-8"));
20743
+ const pkg = JSON.parse(import_node_fs11.default.readFileSync(import_node_path12.default.join(dir, "package.json"), "utf-8"));
20656
20744
  if (pkg.name === "openlearn-next" && typeof pkg.version === "string") return pkg.version;
20657
20745
  } catch {
20658
20746
  }
20659
- const parent = import_node_path11.default.dirname(dir);
20747
+ const parent = import_node_path12.default.dirname(dir);
20660
20748
  if (parent === dir) break;
20661
20749
  dir = parent;
20662
20750
  }
@@ -21375,8 +21463,8 @@ init_audit_log();
21375
21463
 
21376
21464
  // packages/core/plugin-host/dependency-install.ts
21377
21465
  var import_node_child_process2 = require("node:child_process");
21378
- var import_node_fs11 = __toESM(require("node:fs"), 1);
21379
- var import_node_path12 = __toESM(require("node:path"), 1);
21466
+ var import_node_fs12 = __toESM(require("node:fs"), 1);
21467
+ var import_node_path13 = __toESM(require("node:path"), 1);
21380
21468
  var DependencyInstallError = class extends Error {
21381
21469
  constructor(message, pluginId, cause) {
21382
21470
  super(message);
@@ -21411,8 +21499,8 @@ function resolveRegistry() {
21411
21499
  function assertDependenciesResolved(pluginDir, dependencies, pluginId) {
21412
21500
  const names = dependencyNames(dependencies);
21413
21501
  if (names.length === 0) return;
21414
- const lockPath = import_node_path12.default.join(pluginDir, "package-lock.json");
21415
- if (!import_node_fs11.default.existsSync(lockPath)) {
21502
+ const lockPath = import_node_path13.default.join(pluginDir, "package-lock.json");
21503
+ if (!import_node_fs12.default.existsSync(lockPath)) {
21416
21504
  throw new DependencyInstallError(
21417
21505
  `\u4F9D\u8D56\u5B89\u88C5\u672A\u4EA7\u51FA package-lock.json \u2014\u2014 \u65E0\u6CD5\u786E\u8BA4\u4F9D\u8D56\u5DF2\u843D\u76D8\u3002pluginId=${pluginId}\uFF0C\u58F0\u660E\u4F9D\u8D56\uFF1A${names.join(", ")}`,
21418
21506
  pluginId
@@ -21420,7 +21508,7 @@ function assertDependenciesResolved(pluginDir, dependencies, pluginId) {
21420
21508
  }
21421
21509
  let lock;
21422
21510
  try {
21423
- lock = JSON.parse(import_node_fs11.default.readFileSync(lockPath, "utf-8"));
21511
+ lock = JSON.parse(import_node_fs12.default.readFileSync(lockPath, "utf-8"));
21424
21512
  } catch (err) {
21425
21513
  throw new DependencyInstallError(
21426
21514
  `package-lock.json \u4E0D\u662F\u5408\u6CD5 JSON \u2014\u2014 \u4F9D\u8D56\u5B89\u88C5\u7ED3\u679C\u4E0D\u53EF\u4FE1\u3002pluginId=${pluginId}`,
@@ -21493,8 +21581,8 @@ function installPluginDependencies(pluginDir, opts, runNpm = import_node_child_p
21493
21581
  const { pluginId, dependencies, operation = "install" } = opts;
21494
21582
  const depMap = toNpmDependencyMap(dependencies);
21495
21583
  if (Object.keys(depMap).length === 0) return;
21496
- const pkgJsonPath = import_node_path12.default.join(pluginDir, "package.json");
21497
- import_node_fs11.default.writeFileSync(
21584
+ const pkgJsonPath = import_node_path13.default.join(pluginDir, "package.json");
21585
+ import_node_fs12.default.writeFileSync(
21498
21586
  pkgJsonPath,
21499
21587
  JSON.stringify({ name: pluginId, version: "0.0.0", dependencies: depMap }, null, 2),
21500
21588
  "utf-8"
@@ -22763,20 +22851,20 @@ init_install_utils();
22763
22851
 
22764
22852
  // packages/core/security/plugin-signature.ts
22765
22853
  var import_node_crypto9 = __toESM(require("node:crypto"), 1);
22766
- var import_node_fs12 = __toESM(require("node:fs"), 1);
22854
+ var import_node_fs13 = __toESM(require("node:fs"), 1);
22767
22855
  function computeSha256(input) {
22768
22856
  const hash = import_node_crypto9.default.createHash("sha256");
22769
22857
  if (typeof input === "string") {
22770
- if (import_node_fs12.default.existsSync(input)) {
22771
- const fd = import_node_fs12.default.openSync(input, "r");
22858
+ if (import_node_fs13.default.existsSync(input)) {
22859
+ const fd = import_node_fs13.default.openSync(input, "r");
22772
22860
  const buf = Buffer.alloc(64 * 1024);
22773
22861
  let bytesRead = 0;
22774
22862
  try {
22775
- while ((bytesRead = import_node_fs12.default.readSync(fd, buf, 0, buf.length, null)) > 0) {
22863
+ while ((bytesRead = import_node_fs13.default.readSync(fd, buf, 0, buf.length, null)) > 0) {
22776
22864
  hash.update(buf.subarray(0, bytesRead));
22777
22865
  }
22778
22866
  } finally {
22779
- import_node_fs12.default.closeSync(fd);
22867
+ import_node_fs13.default.closeSync(fd);
22780
22868
  }
22781
22869
  const digest4 = hash.digest();
22782
22870
  return { hex: digest4.toString("hex"), base64: digest4.toString("base64") };
@@ -22845,8 +22933,8 @@ async function extractEmbeddedSignature(zipPathOrBuffer) {
22845
22933
  const JSZip5 = (await import("jszip")).default;
22846
22934
  let buffer;
22847
22935
  if (typeof zipPathOrBuffer === "string") {
22848
- if (!import_node_fs12.default.existsSync(zipPathOrBuffer)) return null;
22849
- buffer = import_node_fs12.default.readFileSync(zipPathOrBuffer);
22936
+ if (!import_node_fs13.default.existsSync(zipPathOrBuffer)) return null;
22937
+ buffer = import_node_fs13.default.readFileSync(zipPathOrBuffer);
22850
22938
  } else {
22851
22939
  buffer = zipPathOrBuffer;
22852
22940
  }
@@ -22891,9 +22979,9 @@ function resolveTrustedPublicKeys(explicitKeys, env = process.env) {
22891
22979
  for (const part of remaining.split(/[,;\n]/)) {
22892
22980
  const p = part.trim();
22893
22981
  if (!p) continue;
22894
- if (import_node_fs12.default.existsSync(p)) {
22982
+ if (import_node_fs13.default.existsSync(p)) {
22895
22983
  try {
22896
- const fileContent = import_node_fs12.default.readFileSync(p, "utf8").trim();
22984
+ const fileContent = import_node_fs13.default.readFileSync(p, "utf8").trim();
22897
22985
  const filePems = fileContent.match(/-----BEGIN [A-Z0-9 ]+-----[\s\S]+?-----END [A-Z0-9 ]+-----/g);
22898
22986
  if (filePems) {
22899
22987
  for (const fpem of filePems) {
@@ -22946,7 +23034,7 @@ async function verifyPluginPackage(options) {
22946
23034
  const JSZip5 = (await import("jszip")).default;
22947
23035
  let buffer;
22948
23036
  if (typeof packageSource === "string") {
22949
- buffer = import_node_fs12.default.readFileSync(packageSource);
23037
+ buffer = import_node_fs13.default.readFileSync(packageSource);
22950
23038
  } else {
22951
23039
  buffer = packageSource;
22952
23040
  }
@@ -23152,11 +23240,11 @@ var PluginDistributionManager = class {
23152
23240
  return;
23153
23241
  }
23154
23242
  const filePath = this.pluginHost.getPluginFilePath(this.pluginHost.resolvePluginUuid(pluginId));
23155
- const fs39 = await import("node:fs");
23156
- if (!fs39.existsSync(filePath)) {
23243
+ const fs40 = await import("node:fs");
23244
+ if (!fs40.existsSync(filePath)) {
23157
23245
  throw new Error(`Cannot reload plugin "${pluginId}": source file missing at ${filePath}`);
23158
23246
  }
23159
- const code = fs39.readFileSync(filePath, "utf-8");
23247
+ const code = fs40.readFileSync(filePath, "utf-8");
23160
23248
  await this.pluginHost.reloadPlugin(pluginId, code);
23161
23249
  }
23162
23250
  async updateFromZip(zipPath, options = {}) {
@@ -23345,8 +23433,8 @@ var HotReloadController = class {
23345
23433
  const timer = setTimeout(async () => {
23346
23434
  this.debounceTimers.delete(pluginId);
23347
23435
  try {
23348
- const fs39 = await import("fs/promises");
23349
- const newSourceCode = await fs39.readFile(filePath, "utf-8");
23436
+ const fs40 = await import("fs/promises");
23437
+ const newSourceCode = await fs40.readFile(filePath, "utf-8");
23350
23438
  console.log(`[HotReload] File changed: ${filePath} \u2192 triggering reload for "${pluginId}"`);
23351
23439
  await this.pluginHost.reloadPlugin(pluginId, newSourceCode);
23352
23440
  } catch (err) {
@@ -32579,24 +32667,24 @@ ${formatEnvIssues(warnings)}`);
32579
32667
  }
32580
32668
 
32581
32669
  // server/security/file-permissions.ts
32582
- var import_node_fs13 = __toESM(require("node:fs"), 1);
32583
- var import_node_path13 = __toESM(require("node:path"), 1);
32670
+ var import_node_fs14 = __toESM(require("node:fs"), 1);
32671
+ var import_node_path14 = __toESM(require("node:path"), 1);
32584
32672
  var FILE_PERMISSION_BASELINE_DOC = "\u4FEE\u590D\uFF1Achmod 600 <file>\uFF08\u76EE\u5F55 chmod 700\uFF09\uFF1B\u8BE6\u89C1 docs/administrator-guide/admin-manual.md \u6587\u4EF6\u6743\u9650\u57FA\u7EBF\u4E00\u8282\u3002";
32585
32673
  function modeOf(p) {
32586
32674
  try {
32587
- return import_node_fs13.default.statSync(p).mode & 511;
32675
+ return import_node_fs14.default.statSync(p).mode & 511;
32588
32676
  } catch {
32589
32677
  return null;
32590
32678
  }
32591
32679
  }
32592
32680
  function defaultPaths() {
32593
- const dbPath2 = process.env.OPENLEARN_DB_PATH ? import_node_path13.default.resolve(process.cwd(), process.env.OPENLEARN_DB_PATH) : import_node_path13.default.resolve(process.cwd(), "packages/core/db/educational_os.db");
32594
- const backupDir = process.env.BACKUP_DIR ? import_node_path13.default.resolve(process.cwd(), process.env.BACKUP_DIR) : import_node_path13.default.join(import_node_path13.default.dirname(dbPath2), "backups");
32681
+ const dbPath2 = process.env.OPENLEARN_DB_PATH ? import_node_path14.default.resolve(process.cwd(), process.env.OPENLEARN_DB_PATH) : import_node_path14.default.resolve(process.cwd(), "packages/core/db/educational_os.db");
32682
+ const backupDir = process.env.BACKUP_DIR ? import_node_path14.default.resolve(process.cwd(), process.env.BACKUP_DIR) : import_node_path14.default.join(import_node_path14.default.dirname(dbPath2), "backups");
32595
32683
  return {
32596
- envPath: import_node_path13.default.resolve(process.cwd(), ".env"),
32684
+ envPath: import_node_path14.default.resolve(process.cwd(), ".env"),
32597
32685
  dbPath: dbPath2,
32598
32686
  backupDir,
32599
- encryptionKeyPath: import_node_path13.default.resolve(process.cwd(), "storage", "encryption-key")
32687
+ encryptionKeyPath: import_node_path14.default.resolve(process.cwd(), "storage", "encryption-key")
32600
32688
  };
32601
32689
  }
32602
32690
  function checkSensitiveFilePermissions(options = {}) {
@@ -32627,11 +32715,11 @@ function checkSensitiveFilePermissions(options = {}) {
32627
32715
  }
32628
32716
  if (dirMode !== null) {
32629
32717
  let scanned = 0;
32630
- for (const entry of import_node_fs13.default.readdirSync(backupDir)) {
32718
+ for (const entry of import_node_fs14.default.readdirSync(backupDir)) {
32631
32719
  if (scanned >= maxBackupFiles) break;
32632
32720
  if (!entry.endsWith(".db")) continue;
32633
32721
  scanned++;
32634
- checkFile(import_node_path13.default.join(backupDir, entry), "600", "backup-file");
32722
+ checkFile(import_node_path14.default.join(backupDir, entry), "600", "backup-file");
32635
32723
  }
32636
32724
  }
32637
32725
  return issues;
@@ -33531,8 +33619,8 @@ var runOpenAIAgentChat = async (provider, request) => {
33531
33619
  };
33532
33620
 
33533
33621
  // server/backup-scheduler.ts
33534
- var import_node_fs14 = __toESM(require("node:fs"), 1);
33535
- var import_node_path14 = __toESM(require("node:path"), 1);
33622
+ var import_node_fs15 = __toESM(require("node:fs"), 1);
33623
+ var import_node_path15 = __toESM(require("node:path"), 1);
33536
33624
  var HOUR_MS = 60 * 60 * 1e3;
33537
33625
  var DAY_MS2 = 24 * HOUR_MS;
33538
33626
  var WAL_FULL_THRESHOLD_BYTES = 50 * 1024 * 1024;
@@ -33549,8 +33637,8 @@ function shouldFullCheckpoint(walBytes, threshold = WAL_FULL_THRESHOLD_BYTES) {
33549
33637
  }
33550
33638
  function resolveDbPath(explicit) {
33551
33639
  if (explicit) return explicit;
33552
- if (process.env.OPENLEARN_DB_PATH) return import_node_path14.default.resolve(process.cwd(), process.env.OPENLEARN_DB_PATH);
33553
- return import_node_path14.default.resolve(process.cwd(), "packages/core/db/educational_os.db");
33640
+ if (process.env.OPENLEARN_DB_PATH) return import_node_path15.default.resolve(process.cwd(), process.env.OPENLEARN_DB_PATH);
33641
+ return import_node_path15.default.resolve(process.cwd(), "packages/core/db/educational_os.db");
33554
33642
  }
33555
33643
  function startBackupScheduler(deps) {
33556
33644
  const noop = { stop: () => {
@@ -33578,7 +33666,7 @@ function startBackupScheduler(deps) {
33578
33666
  try {
33579
33667
  let walBytes = 0;
33580
33668
  try {
33581
- walBytes = import_node_fs14.default.statSync(dbPath2 + "-wal").size;
33669
+ walBytes = import_node_fs15.default.statSync(dbPath2 + "-wal").size;
33582
33670
  } catch {
33583
33671
  walBytes = 0;
33584
33672
  }
@@ -33669,9 +33757,9 @@ function isWhitelisted(route, whitelist) {
33669
33757
  (entry) => entry.method.toUpperCase() === route.method && normalizePath2(entry.path) === normalizePath2(route.path)
33670
33758
  );
33671
33759
  }
33672
- function normalizePath2(path45) {
33673
- if (!path45) return "/";
33674
- return path45.endsWith("/") && path45.length > 1 ? path45.slice(0, -1) : path45;
33760
+ function normalizePath2(path46) {
33761
+ if (!path46) return "/";
33762
+ return path46.endsWith("/") && path46.length > 1 ? path46.slice(0, -1) : path46;
33675
33763
  }
33676
33764
  function auditRouteScopes(app, whitelist = []) {
33677
33765
  const declared = [];
@@ -33849,11 +33937,11 @@ function enforcePasswordChanged(req, res, next) {
33849
33937
  if (isPasswordChangeGateSkipped()) {
33850
33938
  return next();
33851
33939
  }
33852
- const path45 = req.path || "";
33853
- if (!path45.startsWith("/api/")) {
33940
+ const path46 = req.path || "";
33941
+ if (!path46.startsWith("/api/")) {
33854
33942
  return next();
33855
33943
  }
33856
- if (PASSWORD_CHANGE_EXEMPT_PATHS.has(path45)) {
33944
+ if (PASSWORD_CHANGE_EXEMPT_PATHS.has(path46)) {
33857
33945
  return next();
33858
33946
  }
33859
33947
  const token = getCookieToken(req);
@@ -33928,8 +34016,8 @@ function csrfGuard(req, res, next) {
33928
34016
  next();
33929
34017
  return;
33930
34018
  }
33931
- const path46 = req.path;
33932
- if (EXEMPT_PREFIXES.some((p) => path46 === p || path46.startsWith(p))) {
34019
+ const path47 = req.path;
34020
+ if (EXEMPT_PREFIXES.some((p) => path47 === p || path47.startsWith(p))) {
33933
34021
  next();
33934
34022
  return;
33935
34023
  }
@@ -33939,10 +34027,10 @@ function csrfGuard(req, res, next) {
33939
34027
  const origin = req.headers.origin;
33940
34028
  const referer = req.headers.referer;
33941
34029
  const host = req.headers.host;
33942
- const path45 = req.path;
34030
+ const path46 = req.path;
33943
34031
  if (typeof origin === "string" && origin !== "") {
33944
34032
  if (origin === "null") {
33945
- if (!EXEMPT_PREFIXES.some((p) => path45 === p || path45.startsWith(p))) {
34033
+ if (!EXEMPT_PREFIXES.some((p) => path46 === p || path46.startsWith(p))) {
33946
34034
  res.status(403).json({ success: false, code: "FORBIDDEN_CROSS_SITE", error: "Opaque origin write request blocked" });
33947
34035
  return;
33948
34036
  }
@@ -33950,7 +34038,7 @@ function csrfGuard(req, res, next) {
33950
34038
  try {
33951
34039
  const originHost = new URL(origin).host;
33952
34040
  if (host && originHost.toLowerCase() !== host.toLowerCase()) {
33953
- if (!EXEMPT_PREFIXES.some((p) => path45 === p || path45.startsWith(p))) {
34041
+ if (!EXEMPT_PREFIXES.some((p) => path46 === p || path46.startsWith(p))) {
33954
34042
  res.status(403).json({
33955
34043
  success: false,
33956
34044
  code: "FORBIDDEN_CROSS_SITE",
@@ -33968,7 +34056,7 @@ function csrfGuard(req, res, next) {
33968
34056
  try {
33969
34057
  const refererHost = new URL(referer).host;
33970
34058
  if (host && refererHost.toLowerCase() !== host.toLowerCase()) {
33971
- if (!EXEMPT_PREFIXES.some((p) => path45 === p || path45.startsWith(p))) {
34059
+ if (!EXEMPT_PREFIXES.some((p) => path46 === p || path46.startsWith(p))) {
33972
34060
  res.status(403).json({
33973
34061
  success: false,
33974
34062
  code: "FORBIDDEN_CROSS_SITE",
@@ -34021,8 +34109,8 @@ function parseSentryDsn(dsn) {
34021
34109
  const projectId = url.pathname.split("/").filter(Boolean).pop();
34022
34110
  if (!projectId || !/^\d+$/.test(projectId)) return null;
34023
34111
  const prefix = url.pathname.split("/").filter(Boolean).slice(0, -1).join("/");
34024
- const path45 = prefix ? `/${prefix}` : "";
34025
- const envelopeUrl = `${url.protocol}//${url.host}${path45}/api/${projectId}/envelope/`;
34112
+ const path46 = prefix ? `/${prefix}` : "";
34113
+ const envelopeUrl = `${url.protocol}//${url.host}${path46}/api/${projectId}/envelope/`;
34026
34114
  return { envelopeUrl, publicKey };
34027
34115
  } catch {
34028
34116
  return null;
@@ -34462,23 +34550,23 @@ function createSocketIoCors(options) {
34462
34550
  }
34463
34551
 
34464
34552
  // server/routes/health.ts
34465
- var import_node_fs16 = __toESM(require("node:fs"), 1);
34466
- var import_node_path16 = __toESM(require("node:path"), 1);
34553
+ var import_node_fs17 = __toESM(require("node:fs"), 1);
34554
+ var import_node_path17 = __toESM(require("node:path"), 1);
34467
34555
 
34468
34556
  // packages/core/observability/health.ts
34469
- var import_node_fs15 = __toESM(require("node:fs"), 1);
34470
- var import_node_path15 = __toESM(require("node:path"), 1);
34557
+ var import_node_fs16 = __toESM(require("node:fs"), 1);
34558
+ var import_node_path16 = __toESM(require("node:path"), 1);
34471
34559
  var import_node_crypto13 = require("node:crypto");
34472
34560
  function probeStorageWritable(dir) {
34473
- const probe = import_node_path15.default.join(dir, `.ready-${process.pid}-${(0, import_node_crypto13.randomBytes)(4).toString("hex")}`);
34561
+ const probe = import_node_path16.default.join(dir, `.ready-${process.pid}-${(0, import_node_crypto13.randomBytes)(4).toString("hex")}`);
34474
34562
  try {
34475
- import_node_fs15.default.mkdirSync(dir, { recursive: true });
34476
- import_node_fs15.default.writeFileSync(probe, "ok", { flag: "w" });
34477
- import_node_fs15.default.unlinkSync(probe);
34563
+ import_node_fs16.default.mkdirSync(dir, { recursive: true });
34564
+ import_node_fs16.default.writeFileSync(probe, "ok", { flag: "w" });
34565
+ import_node_fs16.default.unlinkSync(probe);
34478
34566
  return true;
34479
34567
  } catch {
34480
34568
  try {
34481
- import_node_fs15.default.unlinkSync(probe);
34569
+ import_node_fs16.default.unlinkSync(probe);
34482
34570
  } catch {
34483
34571
  }
34484
34572
  return false;
@@ -34486,15 +34574,15 @@ function probeStorageWritable(dir) {
34486
34574
  }
34487
34575
  function listMigrationNames(migrationsDir) {
34488
34576
  try {
34489
- if (!import_node_fs15.default.existsSync(migrationsDir)) return null;
34490
- const names = import_node_fs15.default.readdirSync(migrationsDir).filter((file) => file.endsWith(".sql")).map((file) => import_node_path15.default.basename(file, ".sql"));
34577
+ if (!import_node_fs16.default.existsSync(migrationsDir)) return null;
34578
+ const names = import_node_fs16.default.readdirSync(migrationsDir).filter((file) => file.endsWith(".sql")).map((file) => import_node_path16.default.basename(file, ".sql"));
34491
34579
  return names.length > 0 ? names : null;
34492
34580
  } catch {
34493
34581
  return null;
34494
34582
  }
34495
34583
  }
34496
34584
  function migrationRecordName(name) {
34497
- return import_node_path15.default.basename(name.replace(/\r/g, "").trim(), ".sql");
34585
+ return import_node_path16.default.basename(name.replace(/\r/g, "").trim(), ".sql");
34498
34586
  }
34499
34587
  function migrationsComplete(expected, applied) {
34500
34588
  if (!expected) return { ok: false, reason: "migrations" };
@@ -34586,15 +34674,15 @@ function countPendingMigrations(migrationsDir, appliedMigrations) {
34586
34674
  function newestBackupAgeSeconds(backupDir, now) {
34587
34675
  let names;
34588
34676
  try {
34589
- if (!import_node_fs16.default.existsSync(backupDir)) return OPERATIONAL_GAUGE_UNKNOWN;
34590
- names = import_node_fs16.default.readdirSync(backupDir);
34677
+ if (!import_node_fs17.default.existsSync(backupDir)) return OPERATIONAL_GAUGE_UNKNOWN;
34678
+ names = import_node_fs17.default.readdirSync(backupDir);
34591
34679
  } catch {
34592
34680
  return OPERATIONAL_GAUGE_UNKNOWN;
34593
34681
  }
34594
34682
  let newestMs = Number.NEGATIVE_INFINITY;
34595
34683
  for (const name of names) {
34596
34684
  try {
34597
- const stat = import_node_fs16.default.statSync(import_node_path16.default.join(backupDir, name));
34685
+ const stat = import_node_fs17.default.statSync(import_node_path17.default.join(backupDir, name));
34598
34686
  if (!stat.isFile()) continue;
34599
34687
  if (stat.mtimeMs > newestMs) newestMs = stat.mtimeMs;
34600
34688
  } catch {
@@ -34626,7 +34714,7 @@ function defaultCountEvents() {
34626
34714
  function registerHealthRoutes(app, options) {
34627
34715
  const startTime = options?.startTime ?? Date.now();
34628
34716
  const platformVersion = options?.platformVersion ?? PLATFORM_VERSION;
34629
- const migrationsDir = options?.migrationsDir ?? import_node_path16.default.join(process.cwd(), "migrations");
34717
+ const migrationsDir = options?.migrationsDir ?? import_node_path17.default.join(process.cwd(), "migrations");
34630
34718
  const storageDir = options?.storageDir ?? getStorageDir();
34631
34719
  const backupDir = options?.backupDir ?? getBackupsDir();
34632
34720
  const countEvents = options?.countEvents ?? defaultCountEvents;
@@ -34746,8 +34834,8 @@ var import_express2 = __toESM(require("express"), 1);
34746
34834
  var import_zod2 = require("zod");
34747
34835
 
34748
34836
  // server/services/course-package-assets.ts
34749
- var import_node_fs17 = __toESM(require("node:fs"), 1);
34750
- var import_node_path17 = __toESM(require("node:path"), 1);
34837
+ var import_node_fs18 = __toESM(require("node:fs"), 1);
34838
+ var import_node_path18 = __toESM(require("node:path"), 1);
34751
34839
  var import_node_crypto14 = __toESM(require("node:crypto"), 1);
34752
34840
  var import_jszip3 = __toESM(require("jszip"), 1);
34753
34841
  var import_jspdf = require("jspdf");
@@ -34991,8 +35079,8 @@ async function materializePackageAssets(assets) {
34991
35079
  const result = /* @__PURE__ */ new Map();
34992
35080
  if (assets.length === 0) return result;
34993
35081
  const uploadsDir = getUploadsDir();
34994
- if (!import_node_fs17.default.existsSync(uploadsDir)) {
34995
- import_node_fs17.default.mkdirSync(uploadsDir, { recursive: true });
35082
+ if (!import_node_fs18.default.existsSync(uploadsDir)) {
35083
+ import_node_fs18.default.mkdirSync(uploadsDir, { recursive: true });
34996
35084
  }
34997
35085
  const writtenPaths = [];
34998
35086
  try {
@@ -35014,8 +35102,8 @@ async function materializePackageAssets(assets) {
35014
35102
  throw new Error(`\u8BFE\u7A0B\u5305\u8D44\u4EA7 "${asset.id}" \u58F0\u660E\u4E3A ${asset.contentType} \u4F46\u5185\u5BB9\u662F\u4E8C\u8FDB\u5236`);
35015
35103
  }
35016
35104
  const uniqueName = `${Date.now()}-${import_node_crypto14.default.randomBytes(4).toString("hex")}${ext}`;
35017
- const filePath = import_node_path17.default.join(uploadsDir, uniqueName);
35018
- import_node_fs17.default.writeFileSync(filePath, buffer);
35105
+ const filePath = import_node_path18.default.join(uploadsDir, uniqueName);
35106
+ import_node_fs18.default.writeFileSync(filePath, buffer);
35019
35107
  writtenPaths.push(filePath);
35020
35108
  const slideCount = isEmbeddableAssetType(asset.contentType) ? asset.contentType === "pdf" ? countPdfPages(buffer) : await countPptxSlides(buffer) : 0;
35021
35109
  result.set(asset.id, {
@@ -35029,7 +35117,7 @@ async function materializePackageAssets(assets) {
35029
35117
  } catch (err) {
35030
35118
  for (const filePath of writtenPaths) {
35031
35119
  try {
35032
- import_node_fs17.default.unlinkSync(filePath);
35120
+ import_node_fs18.default.unlinkSync(filePath);
35033
35121
  } catch (cleanupErr) {
35034
35122
  console.warn("[course-package] \u9644\u4EF6\u8865\u507F\u6E05\u7406\u5931\u8D25:", cleanupErr.message);
35035
35123
  }
@@ -40031,7 +40119,7 @@ ${runtimeScripts}` : BRIDGE_SDK_CODE;
40031
40119
  }
40032
40120
 
40033
40121
  // server/routes/lessons.ts
40034
- var import_node_fs18 = __toESM(require("node:fs"), 1);
40122
+ var import_node_fs19 = __toESM(require("node:fs"), 1);
40035
40123
 
40036
40124
  // server/services/diagnostic-service.ts
40037
40125
  var import_crypto12 = require("crypto");
@@ -41943,8 +42031,8 @@ function registerLessonsRoutes(ctx) {
41943
42031
  passed += 1;
41944
42032
  try {
41945
42033
  const dir = getUploadsDir();
41946
- import_node_fs18.default.mkdirSync(dir, { recursive: true });
41947
- import_node_fs18.default.accessSync(dir, import_node_fs18.default.constants.W_OK);
42034
+ import_node_fs19.default.mkdirSync(dir, { recursive: true });
42035
+ import_node_fs19.default.accessSync(dir, import_node_fs19.default.constants.W_OK);
41948
42036
  checks.staticResources = { status: "passed", message: "\u8BFE\u4EF6\u5B58\u50A8\u76EE\u5F55\u53EF\u5199" };
41949
42037
  passed += 1;
41950
42038
  } catch (err) {
@@ -41977,8 +42065,8 @@ function registerLessonsRoutes(ctx) {
41977
42065
  }
41978
42066
 
41979
42067
  // server/services/course-package-import.ts
41980
- var import_node_fs19 = __toESM(require("node:fs"), 1);
41981
- var import_node_path18 = __toESM(require("node:path"), 1);
42068
+ var import_node_fs20 = __toESM(require("node:fs"), 1);
42069
+ var import_node_path19 = __toESM(require("node:path"), 1);
41982
42070
  var import_uuid18 = require("uuid");
41983
42071
 
41984
42072
  // server/services/course-package-identity.ts
@@ -42253,11 +42341,11 @@ function findReferencedAsset(rawData, assets) {
42253
42341
  function cleanupMaterializedAssets(materialized) {
42254
42342
  const uploadsDir = getUploadsDir();
42255
42343
  for (const asset of materialized.values()) {
42256
- const fileName = import_node_path18.default.basename(asset.fileUrl);
42257
- const target = import_node_path18.default.join(uploadsDir, fileName);
42344
+ const fileName = import_node_path19.default.basename(asset.fileUrl);
42345
+ const target = import_node_path19.default.join(uploadsDir, fileName);
42258
42346
  try {
42259
- if (import_node_fs19.default.existsSync(target)) {
42260
- import_node_fs19.default.unlinkSync(target);
42347
+ if (import_node_fs20.default.existsSync(target)) {
42348
+ import_node_fs20.default.unlinkSync(target);
42261
42349
  }
42262
42350
  } catch (err) {
42263
42351
  console.warn(`[course-package] \u6E05\u7406\u5B64\u513F\u9644\u4EF6\u5931\u8D25\uFF1A${target}`, err.message);
@@ -48334,7 +48422,7 @@ async function checkVersion(source, installedVersion) {
48334
48422
  init_audit_log();
48335
48423
 
48336
48424
  // server/routes/plugin-api-gateway.ts
48337
- var import_node_path19 = __toESM(require("node:path"), 1);
48425
+ var import_node_path20 = __toESM(require("node:path"), 1);
48338
48426
  init_http_router();
48339
48427
  init_interfaces();
48340
48428
  var RESERVED_ACTIONS = /* @__PURE__ */ new Set([
@@ -48508,7 +48596,7 @@ async function pluginApiGatewayMiddleware(req, res, next) {
48508
48596
  res.status(400).json({ success: false, error: "Path traversal attempt detected" });
48509
48597
  return;
48510
48598
  }
48511
- const normalizedSubPath = import_node_path19.default.posix.normalize("/" + rawSubPath);
48599
+ const normalizedSubPath = import_node_path20.default.posix.normalize("/" + rawSubPath);
48512
48600
  if (normalizedSubPath.includes("..")) {
48513
48601
  res.status(400).json({ success: false, error: "Path traversal attempt detected" });
48514
48602
  return;
@@ -48945,8 +49033,8 @@ init_fetch_with_retry();
48945
49033
 
48946
49034
  // server/services/community-registry.ts
48947
49035
  var import_semver7 = __toESM(require("semver"), 1);
48948
- var import_node_fs20 = __toESM(require("node:fs"), 1);
48949
- var import_node_path20 = __toESM(require("node:path"), 1);
49036
+ var import_node_fs21 = __toESM(require("node:fs"), 1);
49037
+ var import_node_path21 = __toESM(require("node:path"), 1);
48950
49038
  var import_node_os3 = __toESM(require("node:os"), 1);
48951
49039
  var import_node_crypto16 = require("node:crypto");
48952
49040
  var COMMUNITY_REGISTRY_ENV = "PLUGIN_COMMUNITY_REGISTRY_URL";
@@ -49168,8 +49256,8 @@ async function downloadPluginPackage(downloadUrl, options = {}) {
49168
49256
  if (!response.body) {
49169
49257
  throw new Error("\u4E0B\u8F7D\u63D2\u4EF6\u5305\u5931\u8D25: \u54CD\u5E94\u4F53\u4E3A\u7A7A");
49170
49258
  }
49171
- const zipPath = import_node_path20.default.join(import_node_os3.default.tmpdir(), `plugin-download-${(0, import_node_crypto16.randomUUID)()}.zip`);
49172
- const out = import_node_fs20.default.createWriteStream(zipPath);
49259
+ const zipPath = import_node_path21.default.join(import_node_os3.default.tmpdir(), `plugin-download-${(0, import_node_crypto16.randomUUID)()}.zip`);
49260
+ const out = import_node_fs21.default.createWriteStream(zipPath);
49173
49261
  let received = 0;
49174
49262
  try {
49175
49263
  for await (const chunk of response.body) {
@@ -49191,13 +49279,13 @@ async function downloadPluginPackage(downloadUrl, options = {}) {
49191
49279
  } catch (err) {
49192
49280
  out.destroy();
49193
49281
  try {
49194
- import_node_fs20.default.rmSync(zipPath, { force: true });
49282
+ import_node_fs21.default.rmSync(zipPath, { force: true });
49195
49283
  } catch {
49196
49284
  }
49197
49285
  throw err;
49198
49286
  }
49199
49287
  if (received === 0) {
49200
- import_node_fs20.default.rmSync(zipPath, { force: true });
49288
+ import_node_fs21.default.rmSync(zipPath, { force: true });
49201
49289
  throw new Error("\u63D2\u4EF6\u5305\u4E3A\u7A7A");
49202
49290
  }
49203
49291
  return { zipPath, filename: resolveFilename(response, downloadUrl), bytes: received };
@@ -57121,8 +57209,10 @@ async function startServer() {
57121
57209
  if (migrations.length === 0) {
57122
57210
  throw new Error(`[Migration] FATAL: ${migrationsDir} \u4E2D\u6CA1\u6709\u8FC1\u79FB\u6587\u4EF6\uFF0C\u62D2\u7EDD\u542F\u52A8\u3002`);
57123
57211
  }
57124
- runMigrations(kernelContainer.db, migrations);
57212
+ runMigrations(kernelContainer.db, migrations, { strictLedger: true });
57125
57213
  console.log(`[Migration] \u542F\u52A8\u590D\u67E5\u5B8C\u6210\uFF1A${migrationsDir}\uFF08${migrations.length} \u4E2A\u6587\u4EF6\uFF09`);
57214
+ assertSchemaMatchesGolden(kernelContainer.db, migrationsDir);
57215
+ console.log("[SchemaGolden] \u542F\u52A8\u7EC8\u68C0\u901A\u8FC7\uFF1A\u6570\u636E\u5E93\u7ED3\u6784\u8986\u76D6\u4EE3\u7801\u671F\u671B\u7684\u5168\u90E8\u8868\u4E0E\u5217\u3002");
57126
57216
  } catch (err) {
57127
57217
  console.error("[Migration] FATAL: \u6570\u636E\u5E93\u8FC1\u79FB\u5931\u8D25\uFF0C\u62D2\u7EDD\u5728\u672A\u5B8C\u6210\u8FC1\u79FB\u7684\u5E93\u4E0A\u63D0\u4F9B\u670D\u52A1\u3002");
57128
57218
  console.error(err);