{"record":{"id":"e91e656f3b94edcb","repo":"abhigyanpatwari/GitNexus","slug":"links-i-role-link-role-is-invalid-expect","errorCode":null,"errorMessage":"links[${i}].role \"${link.role}\" is invalid. Expected: provider | consumer","messagePattern":"links\\[(.+?)\\]\\.role \"(.+?)\" is invalid\\. Expected: provider \\| consumer","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/group/config-parser.ts","lineNumber":80,"sourceCode":"  const repos = raw.repos as Record<string, string>;\n  const repoPaths = new Set(Object.keys(repos));\n\n  const rawLinks = (raw.links as unknown[]) || [];\n  const links: GroupManifestLink[] = rawLinks.map((l: unknown, i: number) => {\n    const link = l as Record<string, unknown>;\n    if (!link.from || !repoPaths.has(link.from as string)) {\n      throw new Error(`links[${i}].from \"${link.from}\" does not match any repo path in group`);\n    }\n    if (!link.to || !repoPaths.has(link.to as string)) {\n      throw new Error(`links[${i}].to \"${link.to}\" does not match any repo path in group`);\n    }\n    if (!VALID_CONTRACT_TYPES.includes(link.type as ContractType)) {\n      throw new Error(\n        `links[${i}].type \"${link.type}\" is invalid. Expected: ${VALID_CONTRACT_TYPES.join(', ')}`,\n      );\n    }\n    if (!VALID_ROLES.includes(link.role as ContractRole)) {\n      throw new Error(`links[${i}].role \"${link.role}\" is invalid. Expected: provider | consumer`);\n    }\n    if (\n      link.contract === undefined ||\n      link.contract === null ||\n      String(link.contract).trim() === ''\n    ) {\n      throw new Error(`links[${i}].contract is required`);\n    }\n    return {\n      from: link.from as string,\n      to: link.to as string,\n      type: link.type as ContractType,\n      contract: String(link.contract),\n      role: link.role as ContractRole,\n    };\n  });\n\n  const detect = { ...DEFAULT_DETECT, ...((raw.detect as object) || {}) };","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus/src/core/group/config-parser.ts#L62-L98","documentation":"Thrown during links[] validation when a link's 'role' field is not 'provider' or 'consumer'. VALID_ROLES is a closed two-value enum describing which side of the contract the 'from' repo plays. The message restates both allowed values.","triggerScenarios":"A link with role: 'Provider' (wrong case), role: 'server', role: 'client', role: 'producer', or a missing role field.","commonSituations":"Wrong-case typo; using a synonym (server/client, producer/subscriber) instead of the canonical provider/consumer; missing role in a hand-authored link.","solutions":["Set link.role to exactly 'provider' or 'consumer' (lowercase).","Use 'provider' when the 'from' repo exposes the contract; 'consumer' when it calls it."],"exampleFix":"# before — synonym + wrong case\nlinks:\n  - { from: services/api, to: services/worker, type: http, contract: GET /v1, role: Server }\n\n# after\nlinks:\n  - { from: services/api, to: services/worker, type: http, contract: GET /v1, role: provider }","handlingStrategy":"type-guard","validationCode":"const VALID_ROLES = ['provider','consumer'] as const;\ntype ContractRole = typeof VALID_ROLES[number];\nfunction isContractRole(v: unknown): v is ContractRole {\n  return typeof v === 'string' && (VALID_ROLES as readonly string[]).includes(v);\n}","typeGuard":"function isContractRole(v: unknown): v is ContractRole {\n  return v === 'provider' || v === 'consumer';\n}","tryCatchPattern":null,"preventionTips":["Use 'provider' or 'consumer' verbatim — never synonyms like server/client.","Add a JSON Schema enum for role to enable editor autocompletion.","Cross-check role semantics: provider exposes the contract; consumer calls it."],"tags":["yaml","group-config","links","validation","contract-role","config-parser"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}