{"record":{"id":"c193a177a6557bd0","repo":"abhigyanpatwari/GitNexus","slug":"links-i-contract-is-required","errorCode":null,"errorMessage":"links[${i}].contract is required","messagePattern":"links\\[(.+?)\\]\\.contract is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/group/config-parser.ts","lineNumber":97,"sourceCode":"      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;\n  if (\n    rawDetect !== undefined &&\n    (!rawDetect || typeof rawDetect !== 'object' || Array.isArray(rawDetect))\n  ) {\n    throw new Error('detect must be a mapping of boolean flags');\n  }\n  for (const [key, value] of Object.entries((rawDetect as Record<string, unknown>) || {})) {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/52924ef12c2290ceee4612526a828ec4cdf2047f/gitnexus/src/core/group/config-parser.ts#L79-L115","documentation":"Thrown while validating a group.yaml links entry: the link's `contract` field is undefined, null, or trims to an empty string. Every link must point at a concrete contract identifier (the shared interface both sides are matched against), so a blank value cannot be defaulted or guessed.","triggerScenarios":"A links[i] entry that has from/to/type/role but no contract key; contract: '' or contract: null; a contract value consisting only of whitespace.","commonSituations":"Filling in the easy fields first and forgetting the identifier; commenting out a contract line while debugging but leaving the link; whitespace introduced by YAML multi-line block scalars.","solutions":["Add a non-empty contract identifier to the link — the path/name of the shared API, proto, topic, or library both repos use.","Match the convention of existing links in the file (e.g. services/api/openapi.yaml or the exported symbol/package name).","If the link has no meaningful contract, remove the whole link entry instead of leaving contract blank."],"exampleFix":"# before (group.yaml)\nlinks:\n  - from: services/web\n    to: services/api\n    type: http\n    role: consumer\n# -> Error: links[0].contract is required\n\n# after\nlinks:\n  - from: services/web\n    to: services/api\n    type: http\n    role: consumer\n    contract: services/api/openapi.yaml","handlingStrategy":"validation","validationCode":"import yaml from 'js-yaml';\n\nexport function validateLinkContracts(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 (link?.contract === undefined || link?.contract === null || String(link.contract).trim() === '') {\n      throw new Error(\n        `links[${i}].contract is required — point it at the shared API/proto/topic/package both repos use`,\n      );\n    }\n  });\n}","typeGuard":"export const hasContract = (link: unknown): link is { contract: string } =>\n  !!link && typeof link === 'object' &&\n  typeof String((link as any).contract?.trim?.() ?? '') === 'string' &&\n  String((link as any).contract ?? '').trim().length > 0;","tryCatchPattern":"try {\n  config = await loadGroupConfig(groupDir);\n} catch (err) {\n  if (err instanceof Error && /links\\[\\d+\\]\\.contract is required/.test(err.message)) {\n    throw new Error(\n      'every link needs a non-empty contract identifier (e.g. services/api/openapi.yaml) — or drop the link',\n      { cause: err },\n    );\n  }\n  throw err;\n}","preventionTips":["Fill contract at the same time as type/role — a link without a contract has no matching meaning.","Adopt one convention for contract ids (proto path, openapi path, topic name, package name) and stick to it.","If a link is speculative, comment out the entire entry; a blank contract fails validation."],"tags":["yaml","configuration","validation","group","links","missing-field"],"backgroundTag":"missing-required-config-field","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"}