@stage5/lumine 0.2.67 → 0.2.69

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.
@@ -130,7 +130,7 @@ function pathEntryExists(filePath) {
130
130
  }
131
131
  }
132
132
 
133
- function acquireCheckpointLock(checkpointPath, operationFingerprint) {
133
+ export function acquireCheckpointLock(checkpointPath, operationFingerprint) {
134
134
  const lockPath = `${checkpointPath}.lock`;
135
135
  const reclaimPath = `${lockPath}.reclaim`;
136
136
  mkdirSync(path.dirname(lockPath), { recursive: true });
@@ -253,7 +253,7 @@ function removeDeadCheckpointLock(lockPath) {
253
253
  }
254
254
  }
255
255
 
256
- function releaseCheckpointLock(lock) {
256
+ export function releaseCheckpointLock(lock) {
257
257
  try {
258
258
  const fileState = lstatSync(lock.lockPath);
259
259
  if (!fileState.isFile() || fileState.isSymbolicLink()) return;
@@ -282,6 +282,15 @@ function operationFingerprintValue({
282
282
  apiUrl: String(options.apiUrl || "").replace(/\/$/, ""),
283
283
  name: operation.name,
284
284
  path: pathWithCursor(operation.path, ""),
285
+ ...(operation.pagination.summaryKeys
286
+ ? { summaryKeys: operation.pagination.summaryKeys }
287
+ : {}),
288
+ // A completed pre-upgrade scan can still contain the old 30-day cap.
289
+ // Reject that checkpoint too: it would otherwise skip fetching entirely
290
+ // and record truncated coverage as a successful since-run scan.
291
+ ...(operation.pagination.coverageMode === "since-run"
292
+ ? { discoveryWindowContract: "stored-run-start-v1" }
293
+ : {}),
285
294
  ...(operation.name === "builds.candidates"
286
295
  ? {
287
296
  resultTransform: {
@@ -637,6 +646,16 @@ export async function forEachPaginatedResultItem(result, visit) {
637
646
  "The result does not contain spooled pagination data.",
638
647
  );
639
648
  }
649
+ if (storage.parts) {
650
+ let index = 0;
651
+ for (const part of storage.parts) {
652
+ await forEachPaginatedResultItem(
653
+ attachPaginationStorage({}, part),
654
+ async (item) => visit(item, index++),
655
+ );
656
+ }
657
+ return;
658
+ }
640
659
  verifyCandidateSpool({ ...storage, discardUnconfirmedTail: false });
641
660
  const input = createReadStream(storage.spoolPath, { encoding: "utf8" });
642
661
  const observedHash = createHash("sha256");
@@ -706,7 +725,7 @@ export async function writePaginatedResultJson({ result, write }) {
706
725
  await write("]}}\n");
707
726
  }
708
727
 
709
- async function writePaginatedResultFile(filePath, result) {
728
+ export async function writePaginatedResultFile(filePath, result) {
710
729
  const resolved = path.resolve(String(filePath || "").trim());
711
730
  if (!String(filePath || "").trim()) {
712
731
  throw validationError("An output file path is required.");
@@ -743,9 +762,26 @@ async function writePaginatedResultFile(filePath, result) {
743
762
  return resolved;
744
763
  }
745
764
 
765
+ // Keep large history event sets on disk when combining independently bounded
766
+ // subject batches. Each part retains its own verified spool and snapshot.
767
+ export function combinePaginatedResults(result, parts) {
768
+ const storages = parts.map(getPaginatedResultStorage);
769
+ if (
770
+ !storages.length ||
771
+ storages.some((entry) => !entry || entry.collectionKey !== storages[0].collectionKey)
772
+ ) {
773
+ throw validationError("Cannot combine incompatible paginated results.");
774
+ }
775
+ return attachPaginationStorage(result, {
776
+ collectionKey: storages[0].collectionKey,
777
+ candidateCount: storages.reduce((sum, entry) => sum + entry.candidateCount, 0),
778
+ parts: storages,
779
+ });
780
+ }
781
+
746
782
  function aggregatePageResult({ operation, lastPage, checkpointPath, state }) {
747
783
  const last = lastPage || { ok: true, status: "success", data: {} };
748
- const lastData = { ...(last.data || {}) };
784
+ const lastData = { ...(last.data || {}), ...state.summaryData };
749
785
  delete lastData[operation.pagination.collectionKey];
750
786
  const lastPagination = { ...(last.data?.pagination || {}) };
751
787
  const pageScannedCount = normalizeScannedCount(
@@ -809,6 +845,17 @@ function aggregatePageResult({ operation, lastPage, checkpointPath, state }) {
809
845
  });
810
846
  }
811
847
 
848
+ function retainedSummaryData(operation, data) {
849
+ return Object.fromEntries(
850
+ (operation.pagination.summaryKeys || []).map((key) => {
851
+ if (!data || !Object.hasOwn(data, key)) {
852
+ throw validationError(`History scan is missing ${key} summary metadata. Start a fresh scan.`);
853
+ }
854
+ return [key, data[key]];
855
+ }),
856
+ );
857
+ }
858
+
812
859
  export async function runAutomaticPagination(args) {
813
860
  const { options, operation, runId } = args;
814
861
  if (!operation.pagination) {
@@ -929,6 +976,7 @@ async function runAutomaticPaginationWithLock({
929
976
  filterSummariesComplete: true,
930
977
  clientFilter: null,
931
978
  operatorViewFilter: null,
979
+ summaryData: {},
932
980
  resumed: false,
933
981
  updatedAt: new Date().toISOString(),
934
982
  };
@@ -1059,6 +1107,7 @@ async function runAutomaticPaginationWithLock({
1059
1107
  savedPages > 0 && saved?.filterSummariesComplete === true
1060
1108
  ? normalizeOperatorViewFilterSummary(saved?.operatorViewFilter)
1061
1109
  : null,
1110
+ summaryData: savedPages > 0 ? retainedSummaryData(operation, saved.summaryData) : {},
1062
1111
  resumed: true,
1063
1112
  };
1064
1113
  spoolHash = verifyCandidateSpool({
@@ -1215,6 +1264,7 @@ async function runAutomaticPaginationWithLock({
1215
1264
  spoolSha256: appended.spoolSha256,
1216
1265
  clientFilter,
1217
1266
  operatorViewFilter,
1267
+ summaryData: retainedSummaryData(operation, data),
1218
1268
  nextCursor,
1219
1269
  exhausted: pagination.exhausted,
1220
1270
  updatedAt: new Date().toISOString(),