koala73/worldmonitor · error · Error

Incomplete PortWatch coverage; canonical retained

Error message

Incomplete PortWatch coverage; canonical retained

What it means

At the end of a seed run the script compares achieved country coverage against the target. If the canonical state cannot advance (coverage.refreshFailures non-empty and usable countries below the target), full publication is blocked and the script throws 'Incomplete PortWatch coverage; canonical retained' so previously published canonical data is preserved instead of being replaced with partial data.

Solutions

  1. Re-run the seed after the upstream/API failures resolve; canonical state is retained so it is safe to retry
  2. Inspect coverage.refreshFailures in the run log to identify which countries failed and why
  3. Verify API credentials and rate limits for the port-activity upstream source
  4. Reduce target coverage or add retry/backoff for flaky countries only if partial publication is acceptable policy-wise
  5. Check Redis connectivity and write permissions for the seed worker
Defensive patterns

Strategy: retry

Try / catch

try {
  await runSeed();
} catch (err) {
  if (err.message === 'Incomplete PortWatch coverage; canonical retained') {
    const delayMs = 5 * 60 * 1000;
    await scheduleRetry(delayMs);
  } else throw err;
}

Prevention

When it happens

Trigger: The run's canonicalAdvances flag is false: one or more country refreshes failed (network errors, upstream PortWatch/API outages, empty responses), leaving countryData.size below coverage.target, and the script reaches the final guard before logSeedResult.

Common situations: Upstream maritime/port API outage or rate limiting during the 60d window fetch; Redis write failures for some countries; running the seed with a broken API key causing per-country fetch failures; transient network partition in Railway worker.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/19698f56e2f7a2a2. Report an issue: GitHub.

Appendix: source

Thrown at scripts/seed-portwatch-port-activity.mjs:1812

      metaPayload,
      canonicalAdvances,
    });
    failureRecorded = !canonicalAdvances;
    let canonicalState = 'no prior canonical list';
    if (canonicalAdvances) {
      canonicalState = `canonical list advanced to ${countries.length} countries`;
    } else if (prevIso2List !== null) {
      canonicalState = canonicalTtlExtended
        ? `canonical list retained at ${prevIso2List.length} countries`
        : `canonical retention unconfirmed (${prevIso2List.length} countries at run start)`;
    }
    console.log(
      `  ${canonicalAdvances ? 'State saved' : 'Recovery state saved'}; ${canonicalState}; ` +
      `usable coverage ${countryData.size}/${coverage.target}; ` +
      `${canonicalAdvances ? '' : 'full publication blocked; '}` +
      `${coverage.refreshFailures.length} unresolved refresh failures`,
    );
    if (!canonicalAdvances) throw new Error('Incomplete PortWatch coverage; canonical retained');

    logSeedResult('supply_chain', countryData.size, Date.now() - startedAt, { source: 'portwatch-ports' });
    console.log(`  Seeded ${countryData.size} countries`);
    console.log(`\n=== Done (${Date.now() - startedAt}ms) ===`);
  } catch (err) {
    console.error(`  SEED FAILED: ${err.message}`);
    await extendExistingTtl([CANONICAL_KEY, META_KEY, ...prevCountryKeys], TTL).catch(() => {});
    if (previousRead && !failureRecorded) {
      await publishPortActivitySnapshot({
        countryData: new Map(),
        canonicalAdvances: false,
        metaPayload: buildPortActivityFailureMeta(previousMeta, { reason: err }),
      });
    }
    throw err;
  } finally {
    process.removeListener('SIGTERM', onSigterm);
    process.removeListener('SIGINT', onSigterm);

View on GitHub (pinned to 7d06c8633d)