{"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":90,"sourceCode":"  }\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 ManifestContractType)) {\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 ManifestContractType,\n      contract: String(link.contract),\n      role: link.role as ContractRole,\n    };\n  });\n\n  const rawDetect = raw.detect;","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/52924ef12c2290ceee4612526a828ec4cdf2047f/gitnexus/src/core/group/config-parser.ts#L72-L108","documentation":"Thrown while validating a group.yaml links entry: the link's `role` is not exactly 'provider' or 'consumer' — the only two valid roles. Role records which side of the contract this link describes (the repo in `from` exposing vs consuming the interface), which the contract matching step relies on.","triggerScenarios":"A links[i].role that is missing, misspelled (providor, consumes), pluralized (providers), or uses a different vocabulary (server, client).","commonSituations":"Thinking role describes the `to` repo and writing client/server; auto-completing to provider/consumer variants; omitting role on some links while copying others.","solutions":["Set role to exactly `provider` or `consumer` (lowercase, singular), describing the `from` repo's relation to the contract.","For a from-repo that implements/exposes the interface use provider; for one that calls it use consumer.","Check every link — the validator stops at the first bad entry, so fix iteratively or lint the whole file."],"exampleFix":"# before (group.yaml)\nlinks:\n  - from: services/api\n    to: services/web\n    type: http\n    role: server\n# -> Error: links[0].role \"server\" is invalid. Expected: provider | consumer\n\n# after\nlinks:\n  - from: services/api\n    to: services/web\n    type: http\n    role: provider","handlingStrategy":"validation","validationCode":"import yaml from 'js-yaml';\n\nconst VALID_ROLES = ['provider', 'consumer'];\n\nexport function validateLinkRoles(content: string): void {\n  const doc = yaml.load(content, { schema: yaml.JSON_SCHEMA }) as Record<string, any>;\n  (doc.links ?? []).forEach((link: any, i: number) => {\n    if (!VALID_ROLES.includes(link?.role)) {\n      throw new Error(\n        `links[${i}].role must be 'provider' or 'consumer' (describing the \\`from\\` repo) — got ${JSON.stringify(link?.role)}`,\n      );\n    }\n  });\n}","typeGuard":"export const isContractRole = (r: unknown): r is 'provider' | 'consumer' =>\n  r === 'provider' || r === 'consumer';","tryCatchPattern":"try {\n  config = await loadGroupConfig(groupDir);\n} catch (err) {\n  if (err instanceof Error && /links\\[\\d+\\]\\.role .* is invalid/.test(err.message)) {\n    throw new Error(\n      'link role must be exactly provider or consumer (lowercase), describing the from-repo',\n      { cause: err },\n    );\n  }\n  throw err;\n}","preventionTips":["Memorize the semantics: role describes the from repo — provider if it exposes the contract, consumer if it calls it.","Do not use client/server/exposes vocabulary; the schema accepts only provider|consumer.","Validate the entire links array up front so one bad role does not surface per sync iteration."],"tags":["yaml","configuration","validation","group","links","enum"],"backgroundTag":"invalid-config-value","analyzedSha":"52924ef12c2290ceee4612526a828ec4cdf2047f","analyzedAt":"2026-08-20T23:29:22.980Z","contentChangedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}