{"record":{"id":"c58453ce1942c9bb","repo":"santifer/career-ops","slug":"torre-invalid-experience-configured-must-b","errorCode":null,"errorMessage":"torre: invalid experience \"${configured}\" — must be one of: ${[...EXPERIENCE_LEVELS].join(', ')}","messagePattern":"torre: invalid experience \"(.+?)\" — must be one of: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/torre.mjs","lineNumber":116,"sourceCode":"\n/**\n * Build the search body from the portal entry. Only filters proven to affect\n * `total` are emitted — see the header note. Exported for tests.\n *\n * @param {any} entry\n * @returns {object}\n */\nexport function buildTorreQuery(entry) {\n  /** @type {Record<string, unknown>} */\n  const body = {};\n\n  const search = typeof entry?.search === 'string' ? entry.search.trim() : '';\n  if (search) {\n    // `experience` is mandatory here — omitting it is a hard 500, so it is\n    // always emitted alongside `text` rather than being conditional on config.\n    const configured = typeof entry?.experience === 'string' ? entry.experience.trim() : '';\n    if (configured && !EXPERIENCE_LEVELS.has(configured)) {\n      throw new Error(\n        `torre: invalid experience \"${configured}\" — must be one of: ${[...EXPERIENCE_LEVELS].join(', ')}`,\n      );\n    }\n    body['skill/role'] = { text: search, experience: configured || DEFAULT_EXPERIENCE };\n  }\n\n  // Only the positive case is expressible: `{\"remote\":{\"term\":false}}` is not a\n  // verified filter, so a falsy remote_only sends no key at all rather than a\n  // filter that might be ignored while looking effective.\n  if (entry?.remote_only === true) body.remote = { term: true };\n\n  return body;\n}\n\n/**\n * Normalize a single Torre opportunity. Exported for tests.\n *\n * Field mapping → the normalized Job shape:","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/santifer/career-ops/blob/1696bec4d021768e7359f9aad6b329cba883da20/providers/torre.mjs#L98-L134","documentation":"buildTorreQuery constructs the Torre search POST body. The API requires skill/role.experience to be one of a fixed allowlist (EXPERIENCE_LEVELS: 'potential-to-develop', '1-plus-year', '2-plus-years', '3-plus-years', '5-plus-years'); an unknown value causes a hard 500. When the entry configures an `experience` value not in that set, this error is thrown before any request is made.","triggerScenarios":"A portals entry for the Torre provider sets `experience:` to an unrecognized string — e.g. 'no-experience', 'senior', '10-plus-years', '5+ years' (spaces/plus instead of hyphenated form), or a localized label — while also setting a `search` text.","commonSituations":"Authoring portal config from memory instead of the documented enum; copying experience values from a Torre UI label rather than its API value; a typo like '1-plus-years'; an older config written against a changed enum.","solutions":["Replace the entry's experience value with one of the allowed strings: potential-to-develop, 1-plus-year, 2-plus-years, 3-plus-years, 5-plus-years.","If unsure, remove the `experience` key entirely — buildTorreQuery falls back to DEFAULT_EXPERIENCE ('1-plus-year').","Check hyphenation/spelling against EXPERIENCE_LEVELS at the top of providers/torre.mjs (line 75).","If a genuinely new level exists upstream, confirm it against the live API and add it to EXPERIENCE_LEVELS deliberately."],"exampleFix":"// before (portals.yml)\n- name: torre-backend\n  provider: torre\n  search: backend engineer\n  experience: senior\n// Error: invalid experience \"senior\" — must be one of: potential-to-develop, 1-plus-year, ...\n// after\n- name: torre-backend\n  provider: torre\n  search: backend engineer\n  experience: 5-plus-years","handlingStrategy":"validation","validationCode":"const ALLOWED = new Set(['potential-to-develop','1-plus-year','2-plus-years','3-plus-years','5-plus-years']);\nfunction hasValidTorreExperience(entry) {\n  const e = entry?.experience;\n  return e === undefined || e === '' || ALLOWED.has(String(e).trim());\n}","typeGuard":"function isTorreExperience(v) {\n  return typeof v === 'string' &&\n    ['potential-to-develop','1-plus-year','2-plus-years','3-plus-years','5-plus-years'].includes(v);\n}","tryCatchPattern":"try {\n  await torreProvider.fetch(entry, ctx);\n} catch (err) {\n  if (String(err.message).startsWith('torre: invalid experience')) {\n    console.error(`${err.message} — omit the key to use the default`);\n    // retry with experience removed, or fix config first\n  } else throw err;\n}","preventionTips":["Copy experience values only from the documented enum, never from Torre UI labels.","Omit the experience key when unsure — the provider defaults to '1-plus-year'.","Validate portal configs against the enum at load time (fail before scanning starts).","Watch for typos like '1-plus-years' or '5+ years' in hand-edited YAML."],"tags":["config","validation","enum"],"backgroundTag":"invalid-enum-value","analyzedSha":"1696bec4d021768e7359f9aad6b329cba883da20","analyzedAt":"2026-09-01T19:19:23.111Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}