target-supabase-sdk 0.2.24 → 0.2.25

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 (41) hide show
  1. package/dist/browser.d.ts +1 -1
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +3 -2
  4. package/dist/browser.js.map +1 -1
  5. package/dist/chunks/{enable-C7lFooTG.js → enable-DkiKPHDl.js} +20 -121
  6. package/dist/chunks/enable-DkiKPHDl.js.map +1 -0
  7. package/dist/chunks/{list.api-BzbTJBsM.js → list.api-B-aM4vzv.js} +46 -10
  8. package/dist/chunks/list.api-B-aM4vzv.js.map +1 -0
  9. package/dist/chunks/{task-queue-C_nUaM2S.js → task-queue-BYfO0Nzm.js} +11 -8
  10. package/dist/chunks/task-queue-BYfO0Nzm.js.map +1 -0
  11. package/dist/node.js +51 -23
  12. package/dist/node.js.map +1 -1
  13. package/dist/preload.js +3 -3
  14. package/dist/shared/log/core/log-batch.d.ts +4 -4
  15. package/dist/shared/log/core/log-batch.d.ts.map +1 -1
  16. package/dist/shared/log/index.d.ts +2 -1
  17. package/dist/shared/log/index.d.ts.map +1 -1
  18. package/dist/shared/log/spool/collector.d.ts +1 -1
  19. package/dist/shared/log/spool/collector.d.ts.map +1 -1
  20. package/dist/shared/log/spool/file.d.ts +2 -8
  21. package/dist/shared/log/spool/file.d.ts.map +1 -1
  22. package/dist/shared/log/spool/writer.d.ts.map +1 -1
  23. package/dist/shared/log/upload/flush.d.ts.map +1 -1
  24. package/dist/shared/log/upload/interface.d.ts +3 -11
  25. package/dist/shared/log/upload/interface.d.ts.map +1 -1
  26. package/package.json +1 -1
  27. package/src/browser.ts +2 -2
  28. package/src/shared/log/README.md +2 -3
  29. package/src/shared/log/core/log-batch.ts +12 -10
  30. package/src/shared/log/index.ts +2 -1
  31. package/src/shared/log/spool/collector.ts +46 -20
  32. package/src/shared/log/spool/file.ts +12 -25
  33. package/src/shared/log/spool/writer.ts +2 -7
  34. package/src/shared/log/upload/flush.ts +23 -38
  35. package/src/shared/log/upload/interface.ts +3 -12
  36. package/dist/chunks/enable-C7lFooTG.js.map +0 -1
  37. package/dist/chunks/list.api-BzbTJBsM.js.map +0 -1
  38. package/dist/chunks/task-queue-C_nUaM2S.js.map +0 -1
  39. package/dist/shared/log/upload/batch-id.d.ts +0 -13
  40. package/dist/shared/log/upload/batch-id.d.ts.map +0 -1
  41. package/src/shared/log/upload/batch-id.ts +0 -43
package/dist/node.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { z } from 'zod';
2
- import { M as MAX_POLL_TARGET_LIST_SIZE, V as validateWithSchema, g as createLogger, z as pollTargetList, k as generateResponse, h as createTarget, K as supabase, w as logManager, X as createScope, d as LogLevel, Y as patchScope, J as scanTargetList, Z as withModule, l as getPossibleTarget, E as postTarget, q as isCreateTargetAlreadyExistsError, m as getTarget, Q as updateTarget, t as isOptimisticLockResponse, R as updateTargetDetails } from './chunks/list.api-BzbTJBsM.js';
3
- export { B as BaseValidator, C as CREATE_TARGET_ALREADY_EXISTS_MESSAGE, a as CategoryList, L as LOG_BATCH_LOADER_KEY, b as LOG_MIN_LEVEL_ENV_KEY, c as LOG_PERSIST_LOADER_KEY, e as MAX_TARGET_LIST_PAGE_SIZE, O as OPTIMISTIC_LOCK_ERROR_CODE, f as OPTIMISTIC_LOCK_FAILED_MESSAGE, P as PostListValidator, S as StatusCode, U as UPDATE_TARGET_NOT_FOUND_MESSAGE, i as deleteTarget, j as formatZodError, n as getTargetList, o as getTargetTotalCount, p as handleSupabaseError, r as isDevEnvironment, s as isOptimisticLockError, u as logDev, v as logDevGroup, x as logMinLevelFromEnv, y as patchTarget, A as postListCreate, D as postLogListCreate, F as postTargetPayloadSchema, G as queryFilterSchema, H as resolveLogMinLevel, I as safeParseWithSchema, N as targetDraftSchema, T as validateWith } from './chunks/list.api-BzbTJBsM.js';
4
- import { l as loadEnvFiles, b as readEnv, c as requireEnv, d as resolveLogSpoolSpawnEnvForLabel, e as resolveLogSpoolConfig, f as resolveLogSpoolRoot, g as resolveAllLogSpoolProcessRoles, h as logSpoolProcessDir, s as spoolLogger, i as parseLogSpoolBatchFilename, j as computeLogBatchIdempotencyKey, k as buildLogListDraft, n as postLogBatch, a as logSpoolEnabledFromEnv, o as logSpoolConfigFromEnv, L as LOG_SPOOL_SERVICE_ID_ENV, q as ensureLogSpoolFromEnv, t as shutdownLogSpoolFromEnv, u as getLogSpoolStats } from './chunks/enable-C7lFooTG.js';
5
- export { C as CLI_PROD_FLAG, S as SERVICE_ENV_PROFILE_ENV_KEY, w as SERVICE_ENV_PROFILE_PROD, x as buildLogSpoolSpawnEnv, y as envBool, z as envInt, A as envMs, B as envNumber, D as envPort, E as envProfileFromProcess, F as resolveDefaultEnvFiles, r as resolveProjectRootByPackageName, G as resolveProjectRootFromModule, v as validateLogSpoolPreloadEnv } from './chunks/enable-C7lFooTG.js';
2
+ import { M as MAX_POLL_TARGET_LIST_SIZE, V as validateWithSchema, f as createLogger, z as pollTargetList, k as generateResponse, g as createTarget, K as supabase, w as logManager, Z as createScope, c as LogLevel, _ as patchScope, J as scanTargetList, $ as withModule, a0 as parseLogSpoolBatchFilename, l as getPossibleTarget, E as postTarget, q as isCreateTargetAlreadyExistsError, m as getTarget, Q as updateTarget, t as isOptimisticLockResponse, R as updateTargetDetails } from './chunks/list.api-B-aM4vzv.js';
3
+ export { B as BaseValidator, C as CREATE_TARGET_ALREADY_EXISTS_MESSAGE, a as CategoryList, L as LOG_BATCH_LIST_VALUE, b as LOG_MIN_LEVEL_ENV_KEY, d as MAX_TARGET_LIST_PAGE_SIZE, O as OPTIMISTIC_LOCK_ERROR_CODE, e as OPTIMISTIC_LOCK_FAILED_MESSAGE, P as PostListValidator, S as StatusCode, U as UPDATE_TARGET_NOT_FOUND_MESSAGE, h as deleteTarget, i as formatLogSpoolBatchTimestamp, j as formatZodError, n as getTargetList, o as getTargetTotalCount, p as handleSupabaseError, r as isDevEnvironment, s as isOptimisticLockError, u as logDev, v as logDevGroup, x as logMinLevelFromEnv, y as patchTarget, A as postListCreate, D as postLogListCreate, F as postTargetPayloadSchema, G as queryFilterSchema, H as resolveLogMinLevel, I as safeParseWithSchema, N as targetDraftSchema, T as validateWith } from './chunks/list.api-B-aM4vzv.js';
6
4
  import { randomBytes, createHash } from 'node:crypto';
5
+ import { l as loadEnvFiles, b as readEnv, c as requireEnv, d as resolveLogSpoolSpawnEnvForLabel, e as resolveLogSpoolConfig, f as resolveLogSpoolRoot, g as resolveAllLogSpoolProcessRoles, h as logSpoolProcessDir, s as spoolLogger, i as buildLogListDraft, j as postLogBatch, a as logSpoolEnabledFromEnv, k as logSpoolConfigFromEnv, L as LOG_SPOOL_SERVICE_ID_ENV, n as ensureLogSpoolFromEnv, o as shutdownLogSpoolFromEnv, q as getLogSpoolStats } from './chunks/enable-DkiKPHDl.js';
6
+ export { C as CLI_PROD_FLAG, S as SERVICE_ENV_PROFILE_ENV_KEY, t as SERVICE_ENV_PROFILE_PROD, u as buildLogSpoolSpawnEnv, w as envBool, x as envInt, y as envMs, z as envNumber, A as envPort, B as envProfileFromProcess, D as resolveDefaultEnvFiles, r as resolveProjectRootByPackageName, E as resolveProjectRootFromModule, v as validateLogSpoolPreloadEnv } from './chunks/enable-DkiKPHDl.js';
7
7
  import { readFile, unlink, mkdir, writeFile, rename, access, stat, readdir, constants } from 'node:fs/promises';
8
8
  import { dirname, resolve, join, isAbsolute } from 'node:path';
9
- import { bK as sha256Hex, bQ as getErrorMessage, bR as LOG_TOPIC_NODE, bb as postRegisterNode, aR as patchChangeNodeStatus, N as NodeStatus, b0 as patchStopNode, aZ as patchNodeHeartBeat, E as EMPTY_REGISTRY_SLOT_RUNTIME_STATE, bA as runRegistrySlotGuardCheck, bp as registrySlotRuntimePatchFromGuardResult, c as CategoryNode, a7 as evaluateBusyNodeLiveness, a$ as patchServiceRuntime, aD as isLogEntry, W as claimServiceRegistrySlot, p as ServiceRegistryError, aa as fetchWithRetry, aG as isRetryableHttpStatus, a9 as fetchBinaryWithRetry, aF as isOpaqueChunkUrl, e as CategoryRepo, az as isHttpUrl, ay as isFeedValue, bS as isFeedIngestSource, bT as resolveFeedOriginalStorageProvider, a8 as feedLinkDedupFilters, z as buildFeedLinkDraft, b as CategoryLink, bU as buildFeedTagList, i as IDEA_IMAGE_TEMP_DIR, at as guessMediaMime, M as MEDIA_CONTENT_HASH_ALGORITHM, aE as isMediaValue, aJ as mediaLinkDedupFilters, O as buildMediaLinkDraft, R as ResultCode, bV as LOG_TOPIC_TASK, r as TASK_REPO_USAGE, an as getScanRemoteRepoValues, aV as patchClaimTask, bW as CRITICAL_UNKNOWN_TASK_TRACE_ID, aT as patchChangeTaskStatus, t as TaskStatusAction, bX as toError, bj as postTaskSchema, bY as createTaskRow, s as TaskStatus } from './chunks/task-queue-C_nUaM2S.js';
10
- export { A as ApiMethod, C as CategoryConfig, a as CategoryExtraction, d as CategoryParcel, f as CategoryService, g as CategoryTask, D as DEFAULT_SYSTEM_REGISTRY_SEED_SLOTS, bZ as FEED_INGEST_SOURCES, F as FEED_VALUES, I as IDEA_IMAGE_LINK_NAME, h as IDEA_IMAGE_TAG_RAW, j as IDEA_LINK_VALUE, L as LINK_MANIFEST_VERSION, k as LIST_MANIFEST_VERSION, l as MEDIA_CONTENT_HASH_PREFIX_LENGTH, m as MEDIA_VALUES, P as POST_TASK_DEFAULT_MAX_ATTEMPTS, n as ParcelManager, o as PostLinkValidator, S as ServiceLifecycleStatus, q as ServiceSlotStatus, T as TARGET_SYSTEM_REGISTRY_KEY, u as apiDetailsSchema, v as appendSystemRegistryEmptySlots, w as assertRegistrySlotAvailable, x as assertRegistrySlotOwner, y as buildEmptyServiceSlots, B as buildFeedLinkName, G as buildIdeaImageLinkDraft, H as buildIdeaLinkDraft, J as buildLinkTargetDraft, K as buildListTargetDraft, Q as buildMediaNameFromLocator, U as buildReviewLinkDraft, V as buildSystemRegistryConfigDetails, X as classifyNetworkError, Y as countTasksByType, Z as createActiveServiceLifecycle, _ as createClaimedRegistrySlotRuntimeState, $ as createStorageProviderRegistry, a0 as createUploadTracker, a1 as decodeLogBatch, a2 as decodeLogBatchList, a3 as defaultL3ServiceDetails, a4 as deleteParcel, a5 as deleteParcelSchema, a6 as escapeIlikePattern, ab as fieldDefinitionSchema, ac as flattenLogBatch, ad as flattenLogBatches, ae as formatNetworkError, af as getApi, ag as getApiSchema, ah as getConfig, ai as getConfigSchema, aj as getExtraction, ak as getExtractionSchema, al as getParcel, am as getParcelSchema, ao as getScanRemoteRepoValuesSchema, ap as getService, aq as getServiceSchema, ar as getTargetSystemRegistry, as as getValueAtPath, au as ideaCreateSchema, av as ideaLinkValueFilters, aw as ideaUpdateSchema, ax as installChunkFetchRegistry, aA as isIdeaImageLinkName, aB as isLocalFilesystemPath, aC as isLogBatchList, aH as linkDedupFilters, aI as linkTargetDraftBuildInputSchema, aK as normalizeStringList, aL as parcelDetailsSchema, aM as parseIdeaOriginal, aN as parseLogBatchMeta, aO as parseProviderPrefixedUrl, aP as parseServiceSlot, aQ as parseServiceSlots, aS as patchChangeNodeStatusSchema, aU as patchChangeTaskStatusSchema, aW as patchClaimTaskSchema, aX as patchExtractionObjects, aY as patchExtractionObjectsSchema, a_ as patchNodeHeartBeatSchema, b1 as patchStopNodeSchema, b2 as patchTaskProgress, b3 as patchTaskProgressSchema, b4 as postApi, b5 as postApiSchema, b6 as postExtraction, b7 as postExtractionSchema, b8 as postLinkCreate, b9 as postParcel, ba as postParcelSchema, bc as postRegisterNodeSchema, bd as postService, be as postServiceInstance, bf as postServiceSchema, bg as postSystemRegistryConfig, bh as postSystemRegistryConfigSchema, bi as postTask, bk as postTaskWithRetry, bl as publishParcel, bm as registerProviderChunkResolver, bn as registerService, bo as registerServiceAtStartup, bq as releaseSystemRegistrySlots, br as releaseSystemRegistrySlotsByServiceId, bs as requestHasBody, bt as resetSystemRegistryConfig, bu as resetSystemRegistryConfigSchema, bv as resolveActiveRegistryServiceId, bw as resolveFetchUrl, bx as restoreParcel, by as restoreParcelById, bz as reviewLinksForNameFilters, bB as sanitizeFileName, bC as sanitizeParcelFileName, bD as scanExtractionList, bE as scanExtractionListSchema, bF as schemaDefinitionSchema, bG as serviceDetailsSchema, bH as serviceLifecycleSchema, bI as serviceNodeSnapshotSchema, bJ as serviceRuntimeSchema, bL as summarizeTaskQueue, bM as systemRegistrySeedSlotSchema, bN as toPostgrestTextArrayLiteral, bO as unregisterService, bP as unregisterServiceAtShutdown } from './chunks/task-queue-C_nUaM2S.js';
9
+ import { bK as sha256Hex, bQ as getErrorMessage, bR as LOG_TOPIC_NODE, bb as postRegisterNode, aR as patchChangeNodeStatus, N as NodeStatus, b0 as patchStopNode, aZ as patchNodeHeartBeat, E as EMPTY_REGISTRY_SLOT_RUNTIME_STATE, bA as runRegistrySlotGuardCheck, bp as registrySlotRuntimePatchFromGuardResult, c as CategoryNode, a7 as evaluateBusyNodeLiveness, a$ as patchServiceRuntime, aD as isLogEntry, W as claimServiceRegistrySlot, p as ServiceRegistryError, aa as fetchWithRetry, aG as isRetryableHttpStatus, a9 as fetchBinaryWithRetry, aF as isOpaqueChunkUrl, e as CategoryRepo, az as isHttpUrl, ay as isFeedValue, bS as isFeedIngestSource, bT as resolveFeedOriginalStorageProvider, a8 as feedLinkDedupFilters, z as buildFeedLinkDraft, b as CategoryLink, bU as buildFeedTagList, i as IDEA_IMAGE_TEMP_DIR, at as guessMediaMime, M as MEDIA_CONTENT_HASH_ALGORITHM, aE as isMediaValue, aJ as mediaLinkDedupFilters, O as buildMediaLinkDraft, R as ResultCode, bV as LOG_TOPIC_TASK, r as TASK_REPO_USAGE, an as getScanRemoteRepoValues, aV as patchClaimTask, bW as CRITICAL_UNKNOWN_TASK_TRACE_ID, aT as patchChangeTaskStatus, t as TaskStatusAction, bX as toError, bj as postTaskSchema, bY as createTaskRow, s as TaskStatus } from './chunks/task-queue-BYfO0Nzm.js';
10
+ export { A as ApiMethod, C as CategoryConfig, a as CategoryExtraction, d as CategoryParcel, f as CategoryService, g as CategoryTask, D as DEFAULT_SYSTEM_REGISTRY_SEED_SLOTS, bZ as FEED_INGEST_SOURCES, F as FEED_VALUES, I as IDEA_IMAGE_LINK_NAME, h as IDEA_IMAGE_TAG_RAW, j as IDEA_LINK_VALUE, L as LINK_MANIFEST_VERSION, k as LIST_MANIFEST_VERSION, l as MEDIA_CONTENT_HASH_PREFIX_LENGTH, m as MEDIA_VALUES, P as POST_TASK_DEFAULT_MAX_ATTEMPTS, n as ParcelManager, o as PostLinkValidator, S as ServiceLifecycleStatus, q as ServiceSlotStatus, T as TARGET_SYSTEM_REGISTRY_KEY, u as apiDetailsSchema, v as appendSystemRegistryEmptySlots, w as assertRegistrySlotAvailable, x as assertRegistrySlotOwner, y as buildEmptyServiceSlots, B as buildFeedLinkName, G as buildIdeaImageLinkDraft, H as buildIdeaLinkDraft, J as buildLinkTargetDraft, K as buildListTargetDraft, Q as buildMediaNameFromLocator, U as buildReviewLinkDraft, V as buildSystemRegistryConfigDetails, X as classifyNetworkError, Y as countTasksByType, Z as createActiveServiceLifecycle, _ as createClaimedRegistrySlotRuntimeState, $ as createStorageProviderRegistry, a0 as createUploadTracker, a1 as decodeLogBatch, a2 as decodeLogBatchList, a3 as defaultL3ServiceDetails, a4 as deleteParcel, a5 as deleteParcelSchema, a6 as escapeIlikePattern, ab as fieldDefinitionSchema, ac as flattenLogBatch, ad as flattenLogBatches, ae as formatNetworkError, af as getApi, ag as getApiSchema, ah as getConfig, ai as getConfigSchema, aj as getExtraction, ak as getExtractionSchema, al as getParcel, am as getParcelSchema, ao as getScanRemoteRepoValuesSchema, ap as getService, aq as getServiceSchema, ar as getTargetSystemRegistry, as as getValueAtPath, au as ideaCreateSchema, av as ideaLinkValueFilters, aw as ideaUpdateSchema, ax as installChunkFetchRegistry, aA as isIdeaImageLinkName, aB as isLocalFilesystemPath, aC as isLogBatchList, aH as linkDedupFilters, aI as linkTargetDraftBuildInputSchema, aK as normalizeStringList, aL as parcelDetailsSchema, aM as parseIdeaOriginal, aN as parseLogBatchMeta, aO as parseProviderPrefixedUrl, aP as parseServiceSlot, aQ as parseServiceSlots, aS as patchChangeNodeStatusSchema, aU as patchChangeTaskStatusSchema, aW as patchClaimTaskSchema, aX as patchExtractionObjects, aY as patchExtractionObjectsSchema, a_ as patchNodeHeartBeatSchema, b1 as patchStopNodeSchema, b2 as patchTaskProgress, b3 as patchTaskProgressSchema, b4 as postApi, b5 as postApiSchema, b6 as postExtraction, b7 as postExtractionSchema, b8 as postLinkCreate, b9 as postParcel, ba as postParcelSchema, bc as postRegisterNodeSchema, bd as postService, be as postServiceInstance, bf as postServiceSchema, bg as postSystemRegistryConfig, bh as postSystemRegistryConfigSchema, bi as postTask, bk as postTaskWithRetry, bl as publishParcel, bm as registerProviderChunkResolver, bn as registerService, bo as registerServiceAtStartup, bq as releaseSystemRegistrySlots, br as releaseSystemRegistrySlotsByServiceId, bs as requestHasBody, bt as resetSystemRegistryConfig, bu as resetSystemRegistryConfigSchema, bv as resolveActiveRegistryServiceId, bw as resolveFetchUrl, bx as restoreParcel, by as restoreParcelById, bz as reviewLinksForNameFilters, bB as sanitizeFileName, bC as sanitizeParcelFileName, bD as scanExtractionList, bE as scanExtractionListSchema, bF as schemaDefinitionSchema, bG as serviceDetailsSchema, bH as serviceLifecycleSchema, bI as serviceNodeSnapshotSchema, bJ as serviceRuntimeSchema, bL as summarizeTaskQueue, bM as systemRegistrySeedSlotSchema, bN as toPostgrestTextArrayLiteral, bO as unregisterService, bP as unregisterServiceAtShutdown } from './chunks/task-queue-BYfO0Nzm.js';
11
11
  import { spawn } from 'node:child_process';
12
12
  import { ProxyAgent } from 'undici';
13
13
  import { tmpdir } from 'node:os';
@@ -1728,6 +1728,7 @@ function registerServiceGuardRunner(options) {
1728
1728
  }
1729
1729
 
1730
1730
  const UPLOAD_LANE = "slow";
1731
+ const MAX_UPLOADS_PER_COLLECT_TICK = 20;
1731
1732
  /** Per serviceId — guard collect-log tick runs GC at most once per `gc.intervalMs`. */
1732
1733
  const lastGcAtMsByServiceId = new Map();
1733
1734
  async function pathExists$1(filePath) {
@@ -1759,6 +1760,9 @@ async function readBatchTmpFile(tmpPath) {
1759
1760
  function processMetaKey(serviceId, processRole) {
1760
1761
  return `${serviceId}:${processRole}`;
1761
1762
  }
1763
+ function syncedMarkerPath(jsonPath) {
1764
+ return jsonPath.replace(/\.json$/, ".synced");
1765
+ }
1762
1766
  async function uploadTmpBatch(input) {
1763
1767
  const parsed = parseLogSpoolBatchFilename(input.fileName);
1764
1768
  if (parsed == null) {
@@ -1767,6 +1771,7 @@ async function uploadTmpBatch(input) {
1767
1771
  }
1768
1772
  const jsonFileName = input.fileName.replace(/\.tmp$/, ".json");
1769
1773
  const jsonPath = join(logSpoolProcessDir(input.spoolRoot, input.serviceId, input.processRole), jsonFileName);
1774
+ const syncedPath = syncedMarkerPath(jsonPath);
1770
1775
  if (await pathExists$1(jsonPath)) {
1771
1776
  try {
1772
1777
  await unlink(input.tmpPath);
@@ -1780,6 +1785,20 @@ async function uploadTmpBatch(input) {
1780
1785
  }
1781
1786
  return "skipped";
1782
1787
  }
1788
+ if (await pathExists$1(syncedPath)) {
1789
+ try {
1790
+ await rename(input.tmpPath, jsonPath);
1791
+ }
1792
+ catch {
1793
+ try {
1794
+ await unlink(input.tmpPath);
1795
+ }
1796
+ catch {
1797
+ // best-effort
1798
+ }
1799
+ }
1800
+ return "skipped";
1801
+ }
1783
1802
  const batch = await readBatchTmpFile(input.tmpPath);
1784
1803
  if (batch == null) {
1785
1804
  spoolLogger.warn("collect-log skipped — tmp failed validation", {
@@ -1796,18 +1815,10 @@ async function uploadTmpBatch(input) {
1796
1815
  return "failed";
1797
1816
  }
1798
1817
  const processKey = processMetaKey(input.serviceId, input.processRole);
1799
- // TODO: `lane` is legacy transport metadata — scheduled for removal; fixed to "slow" for downstream decode compatibility.
1800
- const idempotencyKey = computeLogBatchIdempotencyKey({
1801
- service: input.serviceValue,
1802
- process: processKey,
1803
- lane: UPLOAD_LANE,
1804
- entries: batch.entries,
1805
- });
1806
1818
  const draft = buildLogListDraft({
1807
1819
  service: input.serviceValue,
1808
1820
  process: processKey,
1809
1821
  lane: UPLOAD_LANE,
1810
- idempotencyKey,
1811
1822
  entries: batch.entries,
1812
1823
  });
1813
1824
  try {
@@ -1822,8 +1833,21 @@ async function uploadTmpBatch(input) {
1822
1833
  });
1823
1834
  return "failed";
1824
1835
  }
1836
+ try {
1837
+ await writeFile(syncedPath, `${Date.now()}\n`, "utf8");
1838
+ }
1839
+ catch (error) {
1840
+ const message = error instanceof Error ? error.message : String(error);
1841
+ spoolLogger.warn("collect-log synced marker write failed", { syncedPath, error: message });
1842
+ }
1825
1843
  try {
1826
1844
  await rename(input.tmpPath, jsonPath);
1845
+ try {
1846
+ await unlink(syncedPath);
1847
+ }
1848
+ catch {
1849
+ // best-effort
1850
+ }
1827
1851
  }
1828
1852
  catch (error) {
1829
1853
  const message = error instanceof Error ? error.message : String(error);
@@ -1832,13 +1856,12 @@ async function uploadTmpBatch(input) {
1832
1856
  jsonPath,
1833
1857
  error: message,
1834
1858
  });
1835
- return "failed";
1859
+ return "uploaded";
1836
1860
  }
1837
1861
  spoolLogger.info("collect-log uploaded batch", {
1838
1862
  processRole: input.processRole,
1839
1863
  fileName: jsonFileName,
1840
1864
  entryCount: batch.entries.length,
1841
- idempotencyKey,
1842
1865
  });
1843
1866
  return "uploaded";
1844
1867
  }
@@ -1888,21 +1911,21 @@ async function gcSyncedJsonFiles(input) {
1888
1911
  continue;
1889
1912
  }
1890
1913
  for (const name of names) {
1891
- if (!name.endsWith(".json")) {
1914
+ if (!name.endsWith(".json") && !name.endsWith(".synced")) {
1892
1915
  continue;
1893
1916
  }
1894
- if (parseLogSpoolBatchFilename(name) == null) {
1917
+ if (parseLogSpoolBatchFilename(name) == null && !name.endsWith(".synced")) {
1895
1918
  continue;
1896
1919
  }
1897
- const jsonPath = join(processDir, name);
1920
+ const filePath = join(processDir, name);
1898
1921
  try {
1899
- const fileStat = await stat(jsonPath);
1922
+ const fileStat = await stat(filePath);
1900
1923
  if (input.nowMs - fileStat.mtimeMs <= input.syncedRetentionMs) {
1901
1924
  continue;
1902
1925
  }
1903
- await unlink(jsonPath);
1926
+ await unlink(filePath);
1904
1927
  removed += 1;
1905
- spoolLogger.info("collect-log gc removed synced json", {
1928
+ spoolLogger.info("collect-log gc removed synced file", {
1906
1929
  fileName: name,
1907
1930
  processRole,
1908
1931
  });
@@ -1924,7 +1947,7 @@ function shouldRunGc(serviceId, nowMs, intervalMs) {
1924
1947
  }
1925
1948
  /**
1926
1949
  * Guard-only: upload pending `.tmp` batches, rename to `.json`, then periodic GC.
1927
- * Upload completion is implied by `.json` presence; no sync-state file.
1950
+ * Post success is tracked via `.json` or `.synced` marker (prevents duplicate DB inserts).
1928
1951
  */
1929
1952
  async function runCollectLogTick(input) {
1930
1953
  const config = resolveLogSpoolConfig(input.config);
@@ -1937,6 +1960,7 @@ async function runCollectLogTick(input) {
1937
1960
  gcTmpRemoved: 0,
1938
1961
  gcSyncedRemoved: 0,
1939
1962
  };
1963
+ let uploadsThisTick = 0;
1940
1964
  for (const processRole of resolveAllLogSpoolProcessRoles()) {
1941
1965
  const processDir = logSpoolProcessDir(spoolRoot, input.serviceId, processRole);
1942
1966
  let names = [];
@@ -1950,6 +1974,9 @@ async function runCollectLogTick(input) {
1950
1974
  if (!name.endsWith(".tmp")) {
1951
1975
  continue;
1952
1976
  }
1977
+ if (uploadsThisTick >= MAX_UPLOADS_PER_COLLECT_TICK) {
1978
+ break;
1979
+ }
1953
1980
  const status = await uploadTmpBatch({
1954
1981
  serviceId: input.serviceId,
1955
1982
  serviceValue: input.serviceValue,
@@ -1961,6 +1988,7 @@ async function runCollectLogTick(input) {
1961
1988
  });
1962
1989
  if (status === "uploaded") {
1963
1990
  result.uploaded += 1;
1991
+ uploadsThisTick += 1;
1964
1992
  }
1965
1993
  else if (status === "skipped") {
1966
1994
  result.skipped += 1;