koala73/worldmonitor · error · Error

${chokepoint.id}: editorialLinks requires a canonical blog p

Error message

${chokepoint.id}: editorialLinks requires a canonical blog post path and label: ${link?.href}

What it means

During the same chokepoint page-link build, every entry of declaration.editorialLinks must reference a canonical blog post (its href must exist in the blogPostPaths Set) and carry a non-blank string label. This throw fires when any link fails that check — including null/undefined entries, hrefs not in blogPostPaths, or missing/blank labels. It prevents dead or unlabeled editorial links in generated crawlable chokepoint pages.

Solutions

  1. Correct the href to an existing canonical blog post path present in blogPostPaths, or restore/rename the referenced post.
  2. Add a non-empty trimmed string label to the link entry.
  3. If invoking the builder directly, pass the real blogPostPaths Set derived from the blog corpus instead of the empty default.
  4. If the blog post was intentionally deleted, remove the editorialLinks entry that points at it.

Example fix

// before
editorialLinks: [{ href: 'https://worldmonitor.example/blog/old-post', label: '' }]
// after
editorialLinks: [{ href: '/blog/strait-of-hormuz-explainer', label: 'Strait of Hormuz explainer' }]
Defensive patterns

Strategy: validation

Validate before calling

const bad = (content[chokepoint.id]?.editorialLinks ?? []).filter(
  (l) => !blogPostPaths.has(l?.href) || typeof l.label !== 'string' || !l.label.trim()
);
if (bad.length) throw new Error(`invalid editorialLinks: ${bad.map((l) => l?.href).join(', ')}`);

Type guard

const isValidEditorialLink = (link, blogPostPaths) =>
  !!link && blogPostPaths.has(link.href) && typeof link.label === 'string' && link.label.trim().length > 0;

Try / catch

try {
  buildChokepointPageLinks({ chokepoints, countries, crises, blogPostPaths, content });
} catch (err) {
  if (err.message.includes('editorialLinks requires a canonical blog post path')) {
    console.error(`${err.message}; known paths: ${[...blogPostPaths].join(', ')}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: buildChokepointPageLinks() receives an editorialLinks entry where link?.href is not a member of blogPostPaths (stale path, typo, absolute URL, or null link), or link.label is not a non-whitespace string. Also fires when the caller omits the blogPostPaths option (defaults to an empty Set), making every href fail.

Common situations: A blog post was renamed or removed so its path no longer appears in blogPostPaths while a chokepoint still links to it; the label was omitted, set to '', or left as whitespace; buildChokepointPageLinks was called directly in a test without passing blogPostPaths; a contributor pasted a full https:// URL instead of the canonical site path.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at scripts/build-crawlable-corpus.mjs:1376

    }))
    .filter((entry) => entry.id && entry.displayName)
    .sort((a, b) => a.displayName.localeCompare(b.displayName));
}

export function buildChokepointPageLinks({ chokepoints, countries, crises, blogPostPaths = new Set(), content = CHOKEPOINT_CONTENT }) {
  const countryByCode = new Map(countries.map((country) => [country.code, country]));
  const crisisBySlug = new Map(crises.map((crisis) => [crisis.slug, crisis]));
  const byChokepointId = new Map();
  const byCountryCode = new Map();
  const byCrisisSlug = new Map();
  for (const chokepoint of chokepoints) {
    const declaration = content[chokepoint.id] || {};
    const editorialLinks = declaration.editorialLinks ?? [];
    if (!Array.isArray(editorialLinks)) throw new Error(`${chokepoint.id}: editorialLinks must be an array`);
    const editorial = new Map();
    for (const link of editorialLinks) {
      if (!blogPostPaths.has(link?.href) || typeof link.label !== 'string' || !link.label.trim()) {
        throw new Error(`${chokepoint.id}: editorialLinks requires a canonical blog post path and label: ${link?.href}`);
      }
      if (!editorial.has(link.href)) editorial.set(link.href, link);
    }
    const resolve = (field, targets, inverse) => {
      const ids = declaration[field] ?? [];
      if (!Array.isArray(ids)) throw new Error(`${chokepoint.id}: ${field} must be an array`);
      return [...new Set(ids)].map((id) => {
        const target = targets.get(id);
        if (!target) throw new Error(`${chokepoint.id}: ${field} contains unknown target ${id}`);
        const entries = inverse.get(id) || [];
        entries.push(chokepoint);
        inverse.set(id, entries);
        return target;
      });
    };
    byChokepointId.set(chokepoint.id, {
      countries: resolve('countryCodes', countryByCode, byCountryCode),
      crises: resolve('crisisSlugs', crisisBySlug, byCrisisSlug),

View on GitHub (pinned to 7d06c8633d)