santifer/career-ops · error · Error

outcome-types.mjs: "${key}" is accepted by outcome.mjs but h

Error message

outcome-types.mjs: "${key}" is accepted by outcome.mjs but has no canonical meaning; add it to ALIASES or CANONICAL_OUTCOMES

What it means

outcome-types.mjs self-validates its own tables at module load: every key in OUTCOME_MAP must either be a canonical outcome or an alias pointing at one. If a synonym was added to OUTCOME_MAP without declaring what it canonically means, the module throws at import time rather than letting the journal consumers silently read it as 'unknown'. This is an internal-consistency invariant for the developer/maintainer of the module, not end users.

Source

Thrown at lib/outcome-types.mjs:78

 * result. What stops an omission here is the coverage check below and the drift
 * test, not cleverness.
 */
const ALIASES = {
  stage_reached: 'interview_progress',
  interview: 'interview_progress',
  offer: 'offer_received',
  accepted: 'hired',
  declined: 'offer_declined',
  rejection: 'rejected',
  ghosted: 'no_response',
};

// A synonym added to OUTCOME_MAP without a meaning here is a real decision, not
// a spelling. Fail loudly at load rather than let it read as unknown wherever
// the journal is consumed — which is the failure this module exists to end.
for (const key of Object.keys(OUTCOME_MAP)) {
  if (!CANONICAL_OUTCOMES.includes(key) && !(key in ALIASES)) {
    throw new Error(`outcome-types.mjs: "${key}" is accepted by outcome.mjs but has no canonical meaning; add it to ALIASES or CANONICAL_OUTCOMES`);
  }
}

/**
 * Resolve any accepted spelling to its canonical type.
 *
 * Applies the same normalization `outcome.mjs` applies before writing
 * (lowercase, `-` → `_`), so a hand-edited journal saying `Offer-Declined`
 * still resolves.
 *
 * @param {unknown} raw - A type as written in a journal or typed on the CLI.
 * @returns {string|null} The canonical type, or null when unrecognized.
 */
export function canonicalOutcome(raw) {
  const key = String(raw ?? '').trim().toLowerCase().replace(/-/g, '_');
  if (CANONICAL_OUTCOMES.includes(key)) return key;
  return ALIASES[key] ?? null;
}

View on GitHub (pinned to 1696bec4d0)

Solutions

  1. Add the new key to ALIASES, mapping it to an existing canonical outcome
  2. Or add the key to CANONICAL_OUTCOMES if it is genuinely a new outcome type
  3. Run the module's test suite after any OUTCOME_MAP change to catch this at CI time

Example fix

// before (in OUTCOME_MAP)
'declined': null, // added with no meaning declared
// after (in ALIASES)
'declined': 'rejected',
Defensive patterns

Strategy: validation

Validate before calling

// before import, in tests: assert every OUTCOME_MAP key is canonical or an alias
for (const k of Object.keys(OUTCOME_MAP)) assert(CANONICAL_OUTCOMES.includes(k) || k in ALIASES, k);

Try / catch

try { const types = await import('./lib/outcome-types.mjs'); } catch (e) { console.error('outcome-types table inconsistent:', e.message); process.exit(1); }

Prevention

When it happens

Trigger: Editing OUTCOME_MAP in lib/outcome-types.mjs to add a new synonym string without adding that key to ALIASES or to CANONICAL_OUTCOMES, then importing outcome.mjs.

Common situations: Adding a user-requested synonym ('rejected' → 'rejected') to the map but forgetting the ALIASES entry; a merge that brought in a partial key addition; renaming a canonical outcome without updating the map.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01). Data as JSON: /api/errors/81eb2f1ea40ded72. Report an issue: GitHub.