{"record":{"id":"f39cc185a3724d15","repo":"github/spec-kit","slug":"bundle-target-resolves-only-from-a-discovery-o","errorCode":null,"errorMessage":"Bundle '{target}' resolves only from a discovery-only source ('{resolved.source.id}'); it cannot be updated from there. Update requires an install-allowed source (FR-025).","messagePattern":"Bundle '(.+?)' resolves only from a discovery-only source \\('(.+?)'\\); it cannot be updated from there\\. Update requires an install-allowed source \\(FR-025\\)\\.","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/commands/bundle/__init__.py","lineNumber":475,"sourceCode":"            if all_bundles\n            else [bundle_id]\n        )\n        if not targets:\n            console.print(\"[yellow]No installed bundles to update.[/yellow]\")\n            return\n\n        stack = _build_stack(project_root, offline=offline)\n        from ...bundler.services.adapters import DefaultPrimitiveInstaller\n        from ...bundler.services.installer import install_bundle\n        from ...bundler.services.resolver import resolve_install_plan\n\n        installer = DefaultPrimitiveInstaller(allow_network=not offline)\n        for target in targets:\n            if not any(r.bundle_id == target for r in records):\n                raise BundlerError(f\"Bundle '{target}' is not installed.\")\n            resolved = stack.resolve(target)\n            if not resolved.install_allowed:\n                raise BundlerError(\n                    f\"Bundle '{target}' resolves only from a discovery-only source \"\n                    f\"('{resolved.source.id}'); it cannot be updated from there. \"\n                    \"Update requires an install-allowed source (FR-025).\"\n                )\n            manifest = _download_manifest(resolved, offline=offline)\n            detected = active_integration(project_root)\n            plan = resolve_install_plan(\n                manifest,\n                speckit_version=_speckit_version(),\n                active_integration=detected if detected is not None else integration,\n                integration_explicit=bool(integration) and detected is None,\n            )\n            install_bundle(project_root, plan, installer, manifest=manifest, refresh=True)\n            console.print(\n                f\"[green]✓[/green] Updated '{_escape_markup(str(target))}' \"\n                f\"to v{_escape_markup(str(plan.version))}.\"\n            )\n    except BundlerError as exc:","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/commands/bundle/__init__.py#L457-L493","documentation":"Update-side FR-025 guard: the bundle id resolves in the catalog stack, but only via a discovery-only source (resolved.install_allowed is False). Update re-downloads and re-installs components, so it requires an install-allowed origin just like install; discovery-only origins are rejected with a pointer to the rule.","triggerScenarios":"specify bundle update <installed-id> where stack.resolve(<id>) hits a source registered with install_allowed=False (e.g. the bundle is indexed by a search catalog but its install mirror was removed from the stack configuration).","commonSituations":"A bundle moved hosts after install; catalog configuration changed so only the discovery index still lists it; corporate mirror decommissioned.","solutions":["Re-point the catalog stack at an install-allowed source that carries the bundle, then re-run update.","If the source is gone permanently, uninstall and reinstall from a concrete artifact: specify bundle install <path-to-bundle.yml | dir | .zip>.","Operators: publish the bundle on an install-allowed source and keep the discovery index consistent."],"exampleFix":"# before\nspecify bundle update my-bundle   # only discovery source knows it\n\n# after\nspecify bundle uninstall my-bundle && specify bundle install ./my-bundle-1.1.0.zip","handlingStrategy":"validation","validationCode":"resolved = stack.resolve(bundle_id)\nif not resolved.install_allowed:\n    raise SystemExit(f\"Cannot update {bundle_id} from '{resolved.source.id}' (FR-025); reinstall from an artifact\")","typeGuard":"def can_update(resolved) -> bool:\n    return resolved.install_allowed","tryCatchPattern":"try:\n    bundle_update(...)\nexcept BundlerError as exc:\n    if \"cannot be updated from there\" in str(exc):\n        # uninstall + reinstall from a local artifact\n        ...","preventionTips":["Ensure an install-allowed source for each installed bundle stays configured.","Keep artifact copies of bundles whose upstream mirrors may disappear."],"tags":["bundler","update","catalog","policy"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}