wiki-graph-sdk 0.7.1 → 0.8.0

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.
@@ -8,14 +8,14 @@ import {
8
8
  getChapterDetails,
9
9
  openWikimediaResolver,
10
10
  recordBuildJobInputRevision,
11
- WikiGraphArchiveFile as WikiGraphArchiveFile2,
11
+ WikiGraphArchiveFile as WikiGraphArchiveFile3,
12
12
  WikiGraphScope,
13
13
  withLoggingContext
14
14
  } from "wiki-graph-core";
15
15
  import {
16
16
  applyChapterJobArtifactFile as applyChapterJobArtifactFile3,
17
17
  createLocalChapterJobFileExecutor as createLocalChapterJobFileExecutor2,
18
- writeChapterJobInputFile as writeChapterJobInputFile2
18
+ writeChapterJobInputFile as writeChapterJobInputFile3
19
19
  } from "wiki-graph-core/worker";
20
20
 
21
21
  // src/embedding.ts
@@ -289,6 +289,14 @@ function decodeNodeResourceIdentity(identity, kind) {
289
289
  return void 0;
290
290
  }
291
291
  }
292
+ function getNodeResourcePath(resource) {
293
+ const brandedPath = getBrandedNodeResourcePath(resource, resource.kind);
294
+ if (brandedPath !== void 0) return brandedPath;
295
+ if (typeof resource === "object" && resource !== null && "path" in resource && typeof resource.path === "string") {
296
+ return resource.path;
297
+ }
298
+ throw new TypeError("Expected a Node File or Directory resource");
299
+ }
292
300
  function assertChildName(name) {
293
301
  if (name.length === 0 || name === "." || name === ".." || pathModule.isAbsolute(name) || name.includes("/") || name.includes("\\")) {
294
302
  throw new TypeError(`Directory child must be a relative name: ${name}`);
@@ -300,42 +308,42 @@ var NodeDatabaseConnection = class {
300
308
  }
301
309
  database;
302
310
  async close() {
303
- await new Promise((resolve2, reject) => {
311
+ await new Promise((resolve4, reject) => {
304
312
  this.database.close((error) => {
305
313
  if (error) reject(error);
306
- else resolve2();
314
+ else resolve4();
307
315
  });
308
316
  });
309
317
  }
310
318
  async execute(sql) {
311
- await new Promise((resolve2, reject) => {
319
+ await new Promise((resolve4, reject) => {
312
320
  this.database.exec(sql, (error) => {
313
321
  if (error) reject(error);
314
- else resolve2();
322
+ else resolve4();
315
323
  });
316
324
  });
317
325
  }
318
326
  async queryAll(sql, params = []) {
319
- return await new Promise((resolve2, reject) => {
327
+ return await new Promise((resolve4, reject) => {
320
328
  this.database.all(sql, [...params], (error, rows) => {
321
329
  if (error) reject(error);
322
- else resolve2(rows);
330
+ else resolve4(rows);
323
331
  });
324
332
  });
325
333
  }
326
334
  async queryOne(sql, params = []) {
327
- return await new Promise((resolve2, reject) => {
335
+ return await new Promise((resolve4, reject) => {
328
336
  this.database.get(sql, [...params], (error, row) => {
329
337
  if (error) reject(error);
330
- else resolve2(row);
338
+ else resolve4(row);
331
339
  });
332
340
  });
333
341
  }
334
342
  async run(sql, params = []) {
335
- await new Promise((resolve2, reject) => {
343
+ await new Promise((resolve4, reject) => {
336
344
  this.database.run(sql, [...params], (error) => {
337
345
  if (error) reject(error);
338
- else resolve2();
346
+ else resolve4();
339
347
  });
340
348
  });
341
349
  }
@@ -349,10 +357,10 @@ async function openNativeNodeDatabase(file, flags) {
349
357
  if (path === void 0) {
350
358
  throw new TypeError("The Node database adapter requires a NodeFile");
351
359
  }
352
- return await new Promise((resolve2, reject) => {
360
+ return await new Promise((resolve4, reject) => {
353
361
  const database = new nodeSqlite3.Database(path, flags, (error) => {
354
362
  if (error) reject(error);
355
- else resolve2(database);
363
+ else resolve4(database);
356
364
  });
357
365
  });
358
366
  }
@@ -361,19 +369,19 @@ async function openNodeZip(file) {
361
369
  if (path === void 0) {
362
370
  throw new TypeError("The Node ZIP adapter requires a NodeFile");
363
371
  }
364
- const zipFile = await new Promise((resolve2, reject) => {
372
+ const zipFile = await new Promise((resolve4, reject) => {
365
373
  yauzl.open(
366
374
  path,
367
375
  { autoClose: false, lazyEntries: true },
368
376
  (error, opened) => {
369
377
  if (error || opened === void 0)
370
378
  reject(error ?? new Error("Cannot open ZIP"));
371
- else resolve2(opened);
379
+ else resolve4(opened);
372
380
  }
373
381
  );
374
382
  });
375
383
  const entries = await new Promise(
376
- (resolve2, reject) => {
384
+ (resolve4, reject) => {
377
385
  const discovered = /* @__PURE__ */ new Map();
378
386
  const rejectAndClose = (error) => {
379
387
  zipFile.close();
@@ -383,7 +391,7 @@ async function openNodeZip(file) {
383
391
  };
384
392
  const resolveEntries = () => {
385
393
  zipFile.off("error", rejectAndClose);
386
- resolve2(discovered);
394
+ resolve4(discovered);
387
395
  };
388
396
  zipFile.on("entry", (entry) => {
389
397
  if (!entry.fileName.endsWith("/")) {
@@ -493,12 +501,12 @@ async function collectDecodedNodeStreamRange(input, offset, length) {
493
501
  return Buffer.concat(chunks, collected);
494
502
  }
495
503
  async function openNodeZipEntryStream(zipFile, entry, options) {
496
- return await new Promise((resolve2, reject) => {
504
+ return await new Promise((resolve4, reject) => {
497
505
  const callback = (error, opened) => {
498
506
  if (error || opened === void 0) {
499
507
  reject(error ?? new Error(`Cannot read ZIP entry: ${entry.fileName}`));
500
508
  } else {
501
- resolve2(opened);
509
+ resolve4(opened);
502
510
  }
503
511
  };
504
512
  if (options === void 0) zipFile.openReadStream(entry, callback);
@@ -629,11 +637,11 @@ async function writeNodeStream(input, writer) {
629
637
  );
630
638
  }
631
639
  async function collectNodeStream(input) {
632
- return await new Promise((resolve2, reject) => {
640
+ return await new Promise((resolve4, reject) => {
633
641
  const chunks = [];
634
642
  input.on("data", (chunk) => chunks.push(chunk));
635
643
  input.once("error", reject);
636
- input.once("end", () => resolve2(Buffer.concat(chunks)));
644
+ input.once("end", () => resolve4(Buffer.concat(chunks)));
637
645
  });
638
646
  }
639
647
  var nodeWikiGraphPlatform = {
@@ -1380,12 +1388,13 @@ var DEFAULT_GENERATION_JOB_CONCURRENCY = 3;
1380
1388
  // src/wikispine.ts
1381
1389
  import { spawn } from "child_process";
1382
1390
  var nodeWikispineCommandRunner = {
1383
- run: async ({ args, command, input, onStdout, signal }) => await new Promise((resolve2, reject) => {
1391
+ run: async ({ args, command, input, onStdout, signal }) => await new Promise((resolve4, reject) => {
1384
1392
  signal?.throwIfAborted();
1385
1393
  const child = spawn(command, [...args], {
1386
1394
  stdio: ["pipe", "pipe", "pipe"]
1387
1395
  });
1388
1396
  const stderr = [];
1397
+ let stdoutQueue = Promise.resolve();
1389
1398
  let settled = false;
1390
1399
  const settle = (operation) => {
1391
1400
  if (settled) return;
@@ -1401,14 +1410,15 @@ var nodeWikispineCommandRunner = {
1401
1410
  child.stdout.setEncoding("utf8");
1402
1411
  child.stdout.on("data", (chunk) => {
1403
1412
  if (settled) return;
1404
- try {
1405
- onStdout(chunk);
1406
- } catch (error) {
1413
+ child.stdout.pause();
1414
+ stdoutQueue = stdoutQueue.then(async () => await onStdout(chunk)).then(() => {
1415
+ child.stdout.resume();
1416
+ }).catch((error) => {
1407
1417
  child.kill();
1408
1418
  settle(
1409
1419
  () => reject(error instanceof Error ? error : new Error(String(error)))
1410
1420
  );
1411
- }
1421
+ });
1412
1422
  });
1413
1423
  child.stderr.on("data", (chunk) => stderr.push(chunk));
1414
1424
  child.on("error", (error) => {
@@ -1419,12 +1429,14 @@ var nodeWikispineCommandRunner = {
1419
1429
  );
1420
1430
  });
1421
1431
  child.on("close", (exitCode) => {
1422
- settle(
1423
- () => resolve2({
1424
- exitCode,
1425
- stderr: Buffer.concat(stderr).toString("utf8")
1426
- })
1427
- );
1432
+ void stdoutQueue.then(() => {
1433
+ settle(
1434
+ () => resolve4({
1435
+ exitCode,
1436
+ stderr: Buffer.concat(stderr).toString("utf8")
1437
+ })
1438
+ );
1439
+ });
1428
1440
  });
1429
1441
  child.stdin.end(input);
1430
1442
  })
@@ -1436,13 +1448,243 @@ function abortError(signal) {
1436
1448
  // src/worker.ts
1437
1449
  import {
1438
1450
  applyChapterJobArtifactFile,
1439
- runBuildJobWorker as runCoreBuildJobWorker
1451
+ runBuildJobWorker as runCoreBuildJobWorker,
1452
+ writeChapterJobInputFile
1440
1453
  } from "wiki-graph-core/worker";
1441
- import { WikiGraphArchiveFile } from "wiki-graph-core";
1454
+ import {
1455
+ resolveChapterPathReadonly,
1456
+ WikiGraphArchiveFile as WikiGraphArchiveFile2
1457
+ } from "wiki-graph-core";
1458
+
1459
+ // src/archive/target.ts
1460
+ import { realpath, stat as stat2 } from "fs/promises";
1461
+ import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, join as join3, relative, resolve as resolve3 } from "path";
1462
+ import {
1463
+ formatLocatedWikiGraphUri,
1464
+ formatWikiGraphLibraryUri,
1465
+ listWikiGraphLibraries,
1466
+ listWikiGraphLibraryArchives,
1467
+ parseLocatedWikiGraphUri,
1468
+ parseWikiGraphLibraryUri,
1469
+ resolveWikiGraphLibrary,
1470
+ resolveWikiGraphLibraryArchiveFile
1471
+ } from "wiki-graph-core";
1472
+
1473
+ // src/runtime-path.ts
1474
+ import { homedir as homedir3 } from "os";
1475
+ import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
1476
+ function resolveWikiGraphRuntimePath(path) {
1477
+ const context = getWikiGraphSDKRuntimeContext();
1478
+ const environmentHome = context.env.HOME?.trim();
1479
+ const home = environmentHome === void 0 || environmentHome === "" ? homedir3() : environmentHome;
1480
+ if (path === "~") return home;
1481
+ if (path.startsWith("~/")) return resolve2(home, path.slice(2));
1482
+ return isAbsolute2(path) ? path : resolve2(context.cwd, path);
1483
+ }
1484
+
1485
+ // src/archive/target.ts
1486
+ var WikiGraphArchiveOwnershipError = class extends Error {
1487
+ code = "WIKI_GRAPH_ARCHIVE_OWNERSHIP_MISMATCH";
1488
+ constructor(message) {
1489
+ super(message);
1490
+ this.name = "WikiGraphArchiveOwnershipError";
1491
+ }
1492
+ };
1493
+ async function resolveWikiGraphArchiveLocation(target) {
1494
+ if (target.kind === "standalone") {
1495
+ return await resolveStandaloneLocation(target);
1496
+ }
1497
+ return await resolveLibraryLocation(target);
1498
+ }
1499
+ async function assertStandaloneWikiGraphArchivePath(inputPath) {
1500
+ const path = resolveWikiGraphRuntimePath(inputPath);
1501
+ const candidate = await canonicalPath(path);
1502
+ const candidateIdentity = await readFileIdentity(path);
1503
+ const libraries = await listWikiGraphLibraries();
1504
+ const matches = await findMatchingMembers(
1505
+ libraries,
1506
+ candidate,
1507
+ candidateIdentity
1508
+ );
1509
+ if (matches.length > 0) {
1510
+ const match = requireUnambiguousManagedMatch(path, matches);
1511
+ throw new WikiGraphArchiveOwnershipError(
1512
+ `Standalone archive path belongs to registered library ${formatWikiGraphLibraryUri(
1513
+ match.library.isDefault ? void 0 : match.library.publicId
1514
+ )}: ${path}. Use the library UUID URI ${match.archive.uri} instead.`
1515
+ );
1516
+ }
1517
+ for (const library of libraries) {
1518
+ const rootPath = getNodeResourcePath(library.folder);
1519
+ const root = await canonicalPath(rootPath);
1520
+ if (!isWithin(root, candidate)) continue;
1521
+ throw new WikiGraphArchiveOwnershipError(
1522
+ `Standalone archive path belongs to registered library ${formatWikiGraphLibraryUri(
1523
+ library.isDefault ? void 0 : library.publicId
1524
+ )}: ${path}. Scan the library, then address the archive by its library UUID URI.`
1525
+ );
1526
+ }
1527
+ return path;
1528
+ }
1529
+ async function resolveWikiGraphArchiveTarget(file) {
1530
+ const path = getNodeResourcePath(file);
1531
+ const candidate = await canonicalPath(path);
1532
+ const candidateIdentity = await readFileIdentity(path);
1533
+ const matches = await findMatchingMembers(
1534
+ await listWikiGraphLibraries(),
1535
+ candidate,
1536
+ candidateIdentity
1537
+ );
1538
+ if (matches.length > 0) {
1539
+ const match = requireUnambiguousManagedMatch(path, matches);
1540
+ return { kind: "library", uri: match.archive.uri };
1541
+ }
1542
+ return { kind: "standalone", path: getNodeResourcePath(file) };
1543
+ }
1544
+ async function resolveStandaloneLocation(target) {
1545
+ if (target.path.startsWith("wikg://lib/")) {
1546
+ throw new WikiGraphArchiveOwnershipError(
1547
+ `A library archive URI cannot be used as a standalone path: ${target.path}`
1548
+ );
1549
+ }
1550
+ const path = await assertStandaloneWikiGraphArchivePath(target.path);
1551
+ const normalized = {
1552
+ kind: "standalone",
1553
+ path,
1554
+ ...target.objectUri === void 0 ? {} : { objectUri: target.objectUri }
1555
+ };
1556
+ return {
1557
+ archiveFile: new NodeFile(path),
1558
+ archiveKey: path,
1559
+ archivePath: path,
1560
+ indexScope: { archiveKey: path, archivePath: path, kind: "archive-index" },
1561
+ locatedUri: formatLocatedWikiGraphUri(path, target.objectUri),
1562
+ target: normalized
1563
+ };
1564
+ }
1565
+ async function resolveLibraryLocation(target) {
1566
+ const parsedLocated = parseLocatedWikiGraphUri(target.uri);
1567
+ const archiveUri = parsedLocated.archivePath ?? target.uri;
1568
+ const parsed = parseWikiGraphLibraryUri(archiveUri);
1569
+ if (parsed?.kind !== "archive") {
1570
+ throw new WikiGraphArchiveOwnershipError(
1571
+ `Library archive target must be a UUID archive URI: ${target.uri}`
1572
+ );
1573
+ }
1574
+ await resolveWikiGraphLibrary(parsed);
1575
+ const archiveFile = await resolveWikiGraphLibraryArchiveFile(archiveUri);
1576
+ const normalized = {
1577
+ kind: "library",
1578
+ uri: target.uri
1579
+ };
1580
+ return {
1581
+ archiveFile,
1582
+ archiveKey: archiveUri,
1583
+ archivePath: archiveUri,
1584
+ indexScope: {
1585
+ archiveKey: archiveUri,
1586
+ archivePath: archiveUri,
1587
+ kind: "archive-index"
1588
+ },
1589
+ libraryArchiveTarget: parsed,
1590
+ locatedUri: target.uri,
1591
+ publicArchiveUri: archiveUri,
1592
+ target: normalized
1593
+ };
1594
+ }
1595
+ async function canonicalPath(path) {
1596
+ try {
1597
+ return await realpath(path);
1598
+ } catch {
1599
+ const absolute = resolve3(path);
1600
+ const parent = dirname2(absolute);
1601
+ return parent === absolute ? absolute : join3(await canonicalPath(parent), basename2(absolute));
1602
+ }
1603
+ }
1604
+ function isWithin(root, candidate) {
1605
+ const child = relative(root, candidate);
1606
+ return child === "" || !child.startsWith("..") && !isAbsolute3(child);
1607
+ }
1608
+ async function findMatchingMembers(libraries, candidate, candidateIdentity) {
1609
+ const members = [];
1610
+ for (const library of libraries) {
1611
+ const archives = await listWikiGraphLibraryArchives({
1612
+ isDefault: library.isDefault,
1613
+ kind: "scope",
1614
+ ...library.isDefault ? {} : { publicId: library.publicId }
1615
+ });
1616
+ for (const archive of archives) {
1617
+ if (archive.file === void 0) continue;
1618
+ members.push({
1619
+ archive,
1620
+ library,
1621
+ memberPath: getNodeResourcePath(archive.file)
1622
+ });
1623
+ }
1624
+ }
1625
+ const exact = [];
1626
+ for (const member of members) {
1627
+ if (await canonicalPath(member.memberPath) === candidate) {
1628
+ exact.push(member);
1629
+ }
1630
+ }
1631
+ if (exact.length > 0 || candidateIdentity === void 0) return exact;
1632
+ const identityMatches = [];
1633
+ for (const member of members) {
1634
+ if (await readFileIdentity(member.memberPath) === candidateIdentity) {
1635
+ identityMatches.push(member);
1636
+ }
1637
+ }
1638
+ return identityMatches;
1639
+ }
1640
+ function requireUnambiguousManagedMatch(path, matches) {
1641
+ if (matches.length === 1) return matches[0];
1642
+ throw new WikiGraphArchiveOwnershipError(
1643
+ `Archive file identity is ambiguous across managed archives: ${path}. Use an explicit library UUID URI (${matches.map((match) => match.archive.uri).join(", ")}).`
1644
+ );
1645
+ }
1646
+ async function readFileIdentity(path) {
1647
+ try {
1648
+ const value = await stat2(path);
1649
+ return `${value.dev}:${value.ino}`;
1650
+ } catch {
1651
+ return void 0;
1652
+ }
1653
+ }
1654
+
1655
+ // src/archive/write.ts
1656
+ import {
1657
+ WikiGraphArchiveFile,
1658
+ writeWikiGraphLibraryArchive
1659
+ } from "wiki-graph-core";
1660
+ async function writeWikiGraphArchiveLocation(location, operation, options = {}) {
1661
+ const cacheOnly = options.searchIndexWritebackPolicy === "cache";
1662
+ const write = async (archiveFile = location.archiveFile) => await new WikiGraphArchiveFile(archiveFile).write(operation, {
1663
+ ...cacheOnly ? {} : { derivedStateKeys: [location.archiveKey, location.archivePath] },
1664
+ ...options.invalidateContinuationCursors === void 0 ? {} : {
1665
+ invalidateContinuationCursors: options.invalidateContinuationCursors
1666
+ },
1667
+ ...options.searchIndexWritebackPolicy === void 0 ? {} : { searchIndexWritebackPolicy: options.searchIndexWritebackPolicy }
1668
+ });
1669
+ if (location.libraryArchiveTarget === void 0 || cacheOnly || options.refreshLibraryIndex === false) {
1670
+ return await write();
1671
+ }
1672
+ return await writeWikiGraphLibraryArchive({
1673
+ additionalDerivedStateKeys: [location.archiveKey, location.archivePath],
1674
+ ...options.invalidateContinuationCursors === void 0 ? {} : {
1675
+ invalidateContinuationCursors: options.invalidateContinuationCursors
1676
+ },
1677
+ ...options.onIndexSyncError === void 0 ? {} : { onIndexSyncError: options.onIndexSyncError },
1678
+ operation: async (archiveFile) => await write(archiveFile),
1679
+ target: location.libraryArchiveTarget
1680
+ });
1681
+ }
1682
+
1683
+ // src/worker.ts
1442
1684
  import {
1443
1685
  applyChapterJobArtifactFile as applyChapterJobArtifactFile2,
1444
1686
  createLocalChapterJobFileExecutor,
1445
- writeChapterJobInputFile
1687
+ writeChapterJobInputFile as writeChapterJobInputFile2
1446
1688
  } from "wiki-graph-core/worker";
1447
1689
  async function runBuildJobWorker(options) {
1448
1690
  installNodeWikiGraphPlatform();
@@ -1531,10 +1773,10 @@ async function executeStep(job, kind, execution, reporter, context, inputOptions
1531
1773
  const stepWorkspace = await resetStepWorkspace(job.workspace, kind);
1532
1774
  const inputFile = await stepWorkspace.createFile("input.jsonl");
1533
1775
  const outputWorkspace = await stepWorkspace.createDirectory("output");
1534
- const revision = await new WikiGraphArchiveFile2(job.archive).readDocument(
1776
+ const revision = await new WikiGraphArchiveFile3(job.archive).readDocument(
1535
1777
  async (document) => {
1536
1778
  await getChapterDetails(document, job.chapterId);
1537
- return await writeChapterJobInputFile2(
1779
+ return await writeChapterJobInputFile3(
1538
1780
  document,
1539
1781
  job.chapterId,
1540
1782
  kind,
@@ -1573,16 +1815,23 @@ async function executeStep(job, kind, execution, reporter, context, inputOptions
1573
1815
  total: 1,
1574
1816
  unit: "item"
1575
1817
  });
1576
- await new WikiGraphArchiveFile2(job.archive).write(async (document) => {
1577
- assertJobStillRunning(await getBuildJob(job.jobId));
1578
- await applyChapterJobArtifactFile3(
1579
- document,
1580
- job.chapterId,
1581
- kind,
1582
- revision,
1583
- result.artifactFile
1584
- );
1585
- });
1818
+ const target = await resolveWikiGraphArchiveTarget(job.archive);
1819
+ const resolvedLocation = await resolveWikiGraphArchiveLocation(target);
1820
+ const location = target.kind === "standalone" ? { ...resolvedLocation, archiveFile: job.archive } : resolvedLocation;
1821
+ await writeWikiGraphArchiveLocation(
1822
+ location,
1823
+ async (document) => {
1824
+ assertJobStillRunning(await getBuildJob(job.jobId));
1825
+ await applyChapterJobArtifactFile3(
1826
+ document,
1827
+ job.chapterId,
1828
+ kind,
1829
+ revision,
1830
+ result.artifactFile
1831
+ );
1832
+ },
1833
+ { invalidateContinuationCursors: true }
1834
+ );
1586
1835
  await reporter.updatePhase({
1587
1836
  done: 1,
1588
1837
  phase: "committing",
@@ -1621,6 +1870,10 @@ async function openJobExecutor(job, config, signal, reporter) {
1621
1870
  llmRequest: requireValue(
1622
1871
  request,
1623
1872
  "Local Wikimedia resolution requires an LLM."
1873
+ ),
1874
+ llmModelId: requireValue(
1875
+ stageConfig?.llm?.model,
1876
+ "Local Wikimedia resolution requires an LLM model."
1624
1877
  )
1625
1878
  } : {
1626
1879
  endpoint: config.wikimedia.endpoint,
@@ -1658,12 +1911,12 @@ async function resetStepWorkspace(workspace, kind) {
1658
1911
  return await workspace.createDirectory(name);
1659
1912
  }
1660
1913
  async function readChapterStage(job) {
1661
- return await new WikiGraphArchiveFile2(job.archive).readDocument(
1914
+ return await new WikiGraphArchiveFile3(job.archive).readDocument(
1662
1915
  async (document) => (await getChapterDetails(document, job.chapterId)).stage
1663
1916
  );
1664
1917
  }
1665
1918
  async function assertCurrentFtsArtifact(job) {
1666
- await new WikiGraphArchiveFile2(job.archive).readDocument(async (document) => {
1919
+ await new WikiGraphArchiveFile3(job.archive).readDocument(async (document) => {
1667
1920
  const revision = await document.serials.getRevision(job.chapterId);
1668
1921
  const artifact = await document.indexArtifacts.get(job.chapterId, "fts");
1669
1922
  if (artifact?.sourceRevision !== revision) {