{"version":3,"file":"compatibility.d.ts","sourceRoot":"","sources":["../../../src/core/assignment/compatibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,iDAAiD,CAAC;AACpH,OAAO,KAAK,EACX,mBAAmB,EAEnB,mBAAmB,EACnB,2BAA2B,EAC3B,mBAAmB,EACnB,MAAM,uBAAuB,CAAC;AAc/B;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACxC,UAAU,EAAE,oBAAoB,EAChC,UAAU,CAAC,EAAE,oBAAoB,GAC/B,oBAAoB,CAUtB;AAcD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACpC,YAAY,EAAE,mBAAmB,GAAG,SAAS,EAC7C,YAAY,EAAE,oBAAoB,GAChC,mBAAmB,CAmGrB;AAMD,MAAM,WAAW,kBAAkB;IAClC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,YAAY,EAAE,oBAAoB,CAAC;IACnC,MAAM,EAAE,2BAA2B,CAAC;IACpC,OAAO,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CAuCpF;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,sBAAsB,GAAG,2BAA2B,CAejG","sourcesContent":["/**\n * Assignment Compatibility Engine (2.11.0).\n *\n * Deterministic, explainable capability matching between a structured\n * MissionRequirements and an executor's effective capabilities. No scoring, no\n * ranking, no fuzzy model matching, and no scheduler placement.\n *\n * `compatible` (capability match) is intentionally distinct from `assignable`\n * (capability match + executor state policy). The two are collapsed only by the\n * caller when it actually creates an assignment.\n */\n\nimport type { ExecutorCapabilities, ExecutorLivenessStatus } from \"../executor-registry/executor-registry-types.js\";\nimport type {\n\tAssignabilityResult,\n\tCompatibilityRequirementItem,\n\tCompatibilityResult,\n\tExecutorAssignabilityStatus,\n\tMissionRequirements,\n} from \"./assignment-types.js\";\n\n// =============================================================================\n// Effective capability merge\n// =============================================================================\n\nfunction union(...lists: (string[] | undefined)[]): string[] {\n\tconst seen = new Set<string>();\n\tfor (const list of lists) {\n\t\tfor (const entry of list ?? []) seen.add(entry);\n\t}\n\treturn [...seen].sort();\n}\n\n/**\n * Effective capabilities for assignment evaluation: the union of the stable\n * configured definition and the current runtime advertisement. Platform is\n * taken from the runtime advertisement when present (it is an observation of\n * the actual process), falling back to the configured platform.\n */\nexport function mergeExecutorCapabilities(\n\tconfigured: ExecutorCapabilities,\n\tadvertised?: ExecutorCapabilities,\n): ExecutorCapabilities {\n\treturn {\n\t\tplatform: advertised?.platform ?? configured.platform,\n\t\texecution: union(configured.execution, advertised?.execution),\n\t\tproviders: union(configured.providers, advertised?.providers),\n\t\tmodels: union(configured.models, advertised?.models),\n\t\ttools: union(configured.tools, advertised?.tools),\n\t\tspecialized: union(configured.specialized, advertised?.specialized),\n\t\textra: union(configured.extra, advertised?.extra),\n\t};\n}\n\n// =============================================================================\n// Deterministic compatibility evaluation\n// =============================================================================\n\nfunction has(list: string[] | undefined, value: string): boolean {\n\treturn (list ?? []).includes(value);\n}\n\nfunction platformValue(capabilities: ExecutorCapabilities, key: \"os\" | \"arch\"): string | undefined {\n\treturn capabilities.platform?.[key];\n}\n\n/**\n * Evaluate requirements against effective capabilities. Every requirement that\n * cannot be satisfied is recorded in `unsatisfied` with the observed value, so\n * operators and the future scheduler can see *why* an executor did not match.\n */\nexport function evaluateCompatibility(\n\trequirements: MissionRequirements | undefined,\n\tcapabilities: ExecutorCapabilities,\n): CompatibilityResult {\n\tconst satisfied: CompatibilityRequirementItem[] = [];\n\tconst unsatisfied: CompatibilityRequirementItem[] = [];\n\tconst warnings: string[] = [];\n\n\tconst req = requirements ?? {};\n\n\tif (req.platform?.os !== undefined) {\n\t\tconst observed = platformValue(capabilities, \"os\");\n\t\tconst item: CompatibilityRequirementItem = { kind: \"platform.os\", requirement: req.platform.os, observed };\n\t\tif (observed === req.platform.os) satisfied.push(item);\n\t\telse unsatisfied.push(item);\n\t}\n\n\tif (req.platform?.arch !== undefined) {\n\t\tconst observed = platformValue(capabilities, \"arch\");\n\t\tconst item: CompatibilityRequirementItem = { kind: \"platform.arch\", requirement: req.platform.arch, observed };\n\t\tif (observed === req.platform.arch) satisfied.push(item);\n\t\telse unsatisfied.push(item);\n\t}\n\n\tfor (const value of req.execution ?? []) {\n\t\tconst item: CompatibilityRequirementItem = {\n\t\t\tkind: \"execution\",\n\t\t\trequirement: value,\n\t\t\tobserved: capabilities.execution ?? [],\n\t\t};\n\t\tif (has(capabilities.execution, value)) satisfied.push(item);\n\t\telse unsatisfied.push(item);\n\t}\n\n\tif (req.providers && req.providers.length > 0) {\n\t\tconst observed = capabilities.providers ?? [];\n\t\tconst matched = req.providers.filter((value) => has(capabilities.providers, value));\n\t\tconst item: CompatibilityRequirementItem = {\n\t\t\tkind: \"provider\",\n\t\t\trequirement: `anyOf:${req.providers.join(\",\")}`,\n\t\t\tobserved,\n\t\t};\n\t\tif (matched.length > 0) {\n\t\t\tsatisfied.push(item);\n\t\t\tif (matched.length > 1) warnings.push(`multiple providers satisfy ${matched.join(\", \")}`);\n\t\t} else {\n\t\t\tunsatisfied.push(item);\n\t\t}\n\t}\n\n\tif (req.models && req.models.length > 0) {\n\t\tconst observed = capabilities.models ?? [];\n\t\tconst matched = req.models.filter((value) => has(capabilities.models, value));\n\t\tconst item: CompatibilityRequirementItem = {\n\t\t\tkind: \"model\",\n\t\t\trequirement: `anyOf:${req.models.join(\",\")}`,\n\t\t\tobserved,\n\t\t};\n\t\tif (matched.length > 0) {\n\t\t\tsatisfied.push(item);\n\t\t\tif (matched.length > 1) warnings.push(`multiple models satisfy ${matched.join(\", \")}`);\n\t\t} else {\n\t\t\tunsatisfied.push(item);\n\t\t}\n\t}\n\n\tfor (const value of req.tools ?? []) {\n\t\tconst item: CompatibilityRequirementItem = {\n\t\t\tkind: \"tool\",\n\t\t\trequirement: value,\n\t\t\tobserved: capabilities.tools ?? [],\n\t\t};\n\t\tif (has(capabilities.tools, value)) satisfied.push(item);\n\t\telse unsatisfied.push(item);\n\t}\n\n\tfor (const value of req.specialized ?? []) {\n\t\tconst item: CompatibilityRequirementItem = {\n\t\t\tkind: \"specialized\",\n\t\t\trequirement: value,\n\t\t\tobserved: capabilities.specialized ?? [],\n\t\t};\n\t\tif (has(capabilities.specialized, value)) satisfied.push(item);\n\t\telse unsatisfied.push(item);\n\t}\n\n\tfor (const value of req.extra ?? []) {\n\t\tconst item: CompatibilityRequirementItem = {\n\t\t\tkind: \"extra\",\n\t\t\trequirement: value,\n\t\t\tobserved: capabilities.extra ?? [],\n\t\t};\n\t\tif (has(capabilities.extra, value)) satisfied.push(item);\n\t\telse unsatisfied.push(item);\n\t}\n\n\treturn {\n\t\tcompatible: unsatisfied.length === 0,\n\t\tsatisfied,\n\t\tunsatisfied,\n\t\twarnings,\n\t};\n}\n\n// =============================================================================\n// Assignability (compatibility + executor state policy)\n// =============================================================================\n\nexport interface AssignabilityInput {\n\trequirements?: MissionRequirements;\n\tcapabilities: ExecutorCapabilities;\n\tstatus: ExecutorAssignabilityStatus;\n\tretired: boolean;\n}\n\n/**\n * Assignability is compatibility plus state policy: a retired/offline/stale\n * executor may be capability-compatible but is not presently assignable. The\n * distinction is surfaced so a future failover/scheduler policy can reason\n * about it without parsing prose.\n */\nexport function evaluateAssignability(input: AssignabilityInput): AssignabilityResult {\n\tconst compatibility = evaluateCompatibility(input.requirements, input.capabilities);\n\n\tif (!compatibility.compatible) {\n\t\treturn {\n\t\t\tcompatible: false,\n\t\t\tassignable: false,\n\t\t\tstatus: input.status,\n\t\t\treason: \"executor capabilities do not satisfy mission requirements\",\n\t\t\tcompatibility,\n\t\t};\n\t}\n\n\tif (input.retired) {\n\t\treturn {\n\t\t\tcompatible: true,\n\t\t\tassignable: false,\n\t\t\tstatus: \"RETIRED\",\n\t\t\treason: \"executor is retired\",\n\t\t\tcompatibility,\n\t\t};\n\t}\n\n\tif (input.status !== \"ONLINE\") {\n\t\treturn {\n\t\t\tcompatible: true,\n\t\t\tassignable: false,\n\t\t\tstatus: input.status,\n\t\t\treason: `executor is not ONLINE (${input.status})`,\n\t\t\tcompatibility,\n\t\t};\n\t}\n\n\treturn {\n\t\tcompatible: true,\n\t\tassignable: true,\n\t\tstatus: \"ONLINE\",\n\t\tcompatibility,\n\t};\n}\n\n/** Normalize an executor registry liveness status to the assignment domain. */\nexport function toAssignabilityStatus(status: ExecutorLivenessStatus): ExecutorAssignabilityStatus {\n\tswitch (status) {\n\t\tcase \"REGISTERED\":\n\t\t\treturn \"REGISTERED\";\n\t\tcase \"ONLINE\":\n\t\t\treturn \"ONLINE\";\n\t\tcase \"STALE\":\n\t\t\treturn \"STALE\";\n\t\tcase \"OFFLINE\":\n\t\t\treturn \"OFFLINE\";\n\t\tcase \"RETIRED\":\n\t\t\treturn \"RETIRED\";\n\t\tdefault:\n\t\t\treturn \"UNKNOWN\";\n\t}\n}\n"]}