{"record":{"id":"ce0f47ffefd58c13","repo":"stablyai/orca","slug":"targeted-update-changed-the-non-targeted-control-t","errorCode":null,"errorMessage":"Targeted update changed the non-targeted control topology","messagePattern":"Targeted update changed the non-targeted control topology","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/scripts/verify-skill-update-roundtrip.mjs","lineNumber":245,"sourceCode":"  if (shape === 'copy') {\n    // Why: hosted 1.5.17 replaces copies with aliases while equivalent local runs\n    // retain the copy. Both prove this input topology must remain ineligible.\n    const outcome = targetProviderStat.isSymbolicLink()\n      ? 'converged to an alias'\n      : targetProviderAfter === targetProviderBefore\n        ? 'remained a historical copy'\n        : 'converged as a copy'\n    console.log(`[skill-update-roundtrip] independent copy ${outcome}`)\n  }\n  if ((await packageDigestAt(controlCanonical)) !== controlBefore) {\n    throw new Error('Targeted update changed the non-targeted control skill')\n  }\n  if ((await packageDigestAt(await realpath(controlProvider))) !== controlProviderBefore) {\n    throw new Error('Targeted update changed the non-targeted control provider placement')\n  }\n  const controlProviderStat = await lstat(controlProvider)\n  if (shape === 'symlink' && !controlProviderStat.isSymbolicLink()) {\n    throw new Error('Targeted update changed the non-targeted control topology')\n  }\n} finally {\n  await rm(sandbox, { recursive: true, force: true })\n}\n","sourceCodeStart":227,"sourceCodeEnd":250,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/verify-skill-update-roundtrip.mjs#L227-L250","documentation":"Third isolation rail for the control skill in symlink shape: after a targeted update, lstat(controlProvider) must still report a symbolic link. If the targeted update replaced the control provider symlink with a copy (or vice versa), topology changed and the throw fires.","triggerScenarios":"A targeted update that normalizes topology across all placements (e.g. converts all symlinks to copies) instead of only the target; symlink shape running on a platform/filesystem that silently resolves symlinks on write.","commonSituations":"CLI regression that re-homogenizes topology on update; Windows without symlink privilege causing the placement to fall back to copy; filesystem that doesn't preserve symlinks.","solutions":["On Windows, ensure the runner has symlink privilege (Developer Mode or admin) so the control symlink is created and preserved.","Diff lstat of controlProvider before/after to see if topology flipped.","File a CLI bug: a targeted update must not alter non-target topology.","Pin a known-good CLI version."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// On Windows, refuse to run shape=symlink unless symlink privilege is available:\nif (process.platform === 'win32' && shape === 'symlink') {\n  try {\n    const probe = path.join(sandbox, 'probe')\n    await symlink(targetName, probe); await rm(probe)\n  } catch {\n    throw new Error('Windows runner lacks symlink privilege; cannot exercise shape=symlink')\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["On Windows CI, enable Developer Mode or run as admin so symlinks are preserved.","Treat any topology flip on a non-target placement after a targeted update as a CLI regression.","Pin the CLI version under test."],"tags":["skill-update","targeted-update","isolation","symlink","topology","windows"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}