affaan-m/ECC · error · Error

unknown role " "; expected one of

Error message

unknown role "${spec.role}"; expected one of ${Object.keys(ROLE_CLAUSES).join(', ')}

What it means

ROLE_CLAUSES is a fixed lookup of agreement roles; buildValues() selects the clause template by spec.role. When the role is not one of the known keys, it throws this error listing the valid alternatives. The message is dynamic: it echoes the offending role and enumerates accepted values.

Solutions

  1. Read the error message — it lists exactly which roles are accepted.
  2. Change spec.role to one of the listed values, matching case exactly.
  3. Check the project docs/templates for the canonical role names if the list is unfamiliar.
  4. If the role list genuinely needs extending, add the clause to ROLE_CLAUSES in build-agreement.js rather than guessing a value.

Example fix

// before
const spec = { file: 'NDA', short: 'NDA', role: 'Contractor' };
// after
const spec = { file: 'NDA', short: 'NDA', role: 'consultant' }; // use a listed role
Defensive patterns

Strategy: validation

Validate before calling

const VALID_ROLES = Object.keys(ROLE_CLAUSES); // or hardcode the known list
if (!VALID_ROLES.includes(spec.role)) {
  throw new Error(`role must be one of: ${VALID_ROLES.join(', ')}`);
}

Type guard

function isKnownRole(role, knownRoles) {
  return typeof role === 'string' && knownRoles.includes(role);
}

Try / catch

try {
  buildValues(spec, now);
} catch (e) {
  if (e.message.startsWith('unknown role')) {
    console.error(`${e.message} — check spec.role casing and spelling`);
  } else throw e;
}

Prevention

When it happens

Trigger: spec.role = 'Contractor' (wrong case), spec.role = 'vendor' when only e.g. consultant/employee/client exist, or role omitted-but-coerced (e.g. undefined interpolated into the message).

Common situations: Typo or wrong casing in the spec JSON, following outdated documentation that used a renamed role, or copying a spec from another generator with a different role taxonomy.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/527c35dd142a703f. Report an issue: GitHub.

Appendix: source

Thrown at skills/master-agreement-generator/scripts/build-agreement.js:96

function buildValues(spec, now) {
  if (!spec || typeof spec !== 'object') {
    throw new Error('spec must be an object');
  }
  for (const key of ['file', 'short', 'role']) {
    if (typeof spec[key] !== 'string' || spec[key].trim() === '') {
      throw new Error(`spec.${key} is required`);
    }
  }
  // Reject path syntax on every host, including Windows paths supplied on POSIX.
  if (/[<>:"/\\|?*\p{Cc}]/u.test(spec.file) ||
      /[. ]$/.test(spec.file) ||
      /^(con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(spec.file)) {
    throw new Error('spec.file must be a portable filename without path components or control characters');
  }
  const clauses = ROLE_CLAUSES[spec.role];
  if (!clauses) {
    throw new Error(`unknown role "${spec.role}"; expected one of ${Object.keys(ROLE_CLAUSES).join(', ')}`);
  }
  const cp = spec.short;
  const fill = text => text.split('{cp}').join(cp);
  const supplement = typeof spec.supplement === 'string' && spec.supplement.trim() ? `${spec.supplement.trim()}; ` : '';

  return {
    FEE_TITLE: clauses.title,
    CP_SHORT: cp,
    DATE: spec.date || defaultDate(now),
    CP_LEGAL: spec.legal || BLANK,
    CP_JURIS: spec.juris || BLANK,
    CP_ADDR: spec.addr || BLANK,
    ROLE_CLAUSE: fill(clauses.role),
    FEE_CLAUSE: fill(clauses.fee),
    SCHEDULE_ROWS: renderScheduleRows(spec.schedule),
    SUPPLEMENT_CLAUSE: supplement,
    CP_SIGBLOCK: (spec.legal || cp).toUpperCase(),
    CP_SIGNER: spec.signer || BLANK,

View on GitHub (pinned to 8321021c54)