Skip to content

fix(dashboard-api): resolve depends_on for built-in extensions in enable/disable#1713

Open
tang-vu wants to merge 1 commit into
Osmantic:mainfrom
tang-vu:fix/dashboard-api-builtin-extension-deps
Open

fix(dashboard-api): resolve depends_on for built-in extensions in enable/disable#1713
tang-vu wants to merge 1 commit into
Osmantic:mainfrom
tang-vu:fix/dashboard-api-builtin-extension-deps

Conversation

@tang-vu

@tang-vu tang-vu commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

The dashboard-api extensions router supports enabling/disabling built-in extensions (_resolve_extension_dir and _activate_service handle the EXTENSIONS_DIR + host-agent-rename path), but its dependency resolution never did — both _read_direct_deps and the disable-time dependents scan only looked at USER_EXTENSIONS_DIR.

For built-in pairs like hermes-proxydepends_on: [hermes, dashboard-api] (both category: recommended, both toggleable from the dashboard):

  • Enable: POST /api/extensions/hermes-proxy/enable with hermes disabled finds no manifest under user-extensions, reads an empty dep list, and enables the proxy alone. The merged compose project now has depends_on on an undefined service → docker compose config rejects the whole project, so ods start/restart takes down every service, not just the pair.
  • Disable: POST /api/extensions/hermes/disable scans only user-extensions for dependents, so it disables hermes with no warning at all while hermes-proxy stays enabled — same stack-wide failure on the next compose operation.

This is the dashboard-API sibling of #1712 (Windows CLI) and the same failure mode the Linux CLI already guards against: ods-cli resolves deps from the full service registry (built-ins included) in both directions.

Changes

  • _read_direct_deps now resolves the manifest user-first, then built-in, matching _resolve_extension_dir's shadowing order. The existing missing-deps 400 response and auto_enable_deps=true cascade now work for built-in extensions (activation of built-in deps already worked — it goes through _activate_service → host-agent rename).
  • Extracted _parse_manifest_deps so the disable-time dependents scan reuses the same manifest parsing instead of its inline copy.
  • The disable dependents scan covers both directories (user shadows built-in) and only reports peers whose compose.yaml is present: a disabled dependent is unaffected by the disable, and warning about it was noise. This mirrors the Linux CLI, which skips disabled dependents ([[ ! -f "$dep_cf" ]] && continue).
  • Disable stays warn-don't-block — unchanged API contract; only the scan's coverage is fixed.

Note: the catalog endpoint already computed dependents across the whole EXTENSION_CATALOG, so the UI displayed the exact relationship the mutation endpoints ignored.

Tests

New cases (all fail on main, pass with this change):

  • TestBuiltinExtensionDeps::test_enable_builtin_missing_deps_returns_list — enabling a built-in with a disabled built-in dep returns 400 + missing_dependencies + auto_enable_available.
  • TestBuiltinExtensionDeps::test_enable_builtin_auto_depsauto_enable_deps=true activates the dep first via ("activate", dep) host-agent rename, then the target.
  • TestBuiltinExtensionDeps::test_user_manifest_shadows_builtin — a user copy of the same id takes precedence over the built-in manifest.
  • TestDisableExtension::test_disable_warns_about_builtin_dependents — disabling a built-in with an enabled built-in dependent surfaces dependents_warning (the hermes/hermes-proxy shape).
  • TestDisableExtension::test_disable_skips_disabled_dependents — a dependent that is itself disabled no longer triggers the warning.

Verification

  • pytest tests/ (dashboard-api): 1175 passed, 9 skipped; the only failures are the 3 pre-existing Windows-host baseline failures (test_setup_sentinel.py ×2 bash-dependent, test_user_extensions.py::test_scan_symlink_skipped), identical on clean main.
  • ruff check ods/ --select E,F,W --ignore E501,E701,E731,E741,E402: clean.

…ble/disable

The extensions router's dependency resolution only looked at
USER_EXTENSIONS_DIR, while _resolve_extension_dir and _activate_service
fully support built-in extensions under EXTENSIONS_DIR. Two consequences
for built-in pairs like hermes-proxy (depends_on: [hermes, dashboard-api]):

- POST /api/extensions/hermes-proxy/enable with hermes disabled found no
  manifest in user-extensions, read an empty dep list, and enabled the
  proxy alone. The merged compose project then declares depends_on on an
  undefined service, and compose config fails for the whole stack.
- POST /api/extensions/hermes/disable scanned only user-extensions for
  dependents, so it disabled hermes with no warning at all while
  hermes-proxy stayed enabled -- same stack-wide failure.

_read_direct_deps now resolves the manifest user-first, then built-in,
matching _resolve_extension_dir's shadowing order, so the existing
missing-deps 400 and auto_enable_deps flows work for built-ins. The
disable dependents scan covers both directories and only reports peers
whose compose.yaml is present: a disabled dependent is unaffected, and
warning about it was noise. This mirrors the Linux CLI, which resolves
deps from the full service registry and skips disabled dependents.

The catalog endpoint already computed dependents across the whole
EXTENSION_CATALOG, so the UI showed the relationship the mutation
endpoints ignored.
@Lightheartdevs

Copy link
Copy Markdown
Collaborator

Thanks for tightening the dashboard-api enable/disable path around built-in extension dependencies, especially the hermes/hermes-proxy failure mode.

Advisory status: looks merge-ready

What looks good:

  • ods/extensions/services/dashboard-api/routers/extensions.py now resolves dependency manifests user-first, then built-in, matching _resolve_extension_dir and preserving the intended shadowing behavior.
  • The disable path now scans enabled built-in and user peers before warning, which closes the stack-wide depends_on breakage without changing the API contract from warn-only to block.
  • The added tests cover the important regressions: built-in missing deps, auto-enable order, user manifest shadowing, built-in dependent warnings, and skipping disabled dependents.

What I checked:

  • GitHub checks are passing for Python lint/type/API, shell lint, dashboard/frontend, distro matrix smoke, env validation, PowerShell lint, and secret scan.
  • Local: git diff --check origin/main...origin/pr/1713.
  • Local targeted pytest on the PR worktree: python -m pytest tests/test_extensions_deps.py::TestBuiltinExtensionDeps -vv --tb=short and python -m pytest tests/test_extensions.py::TestDisableExtension::test_disable_warns_about_builtin_dependents tests/test_extensions.py::TestDisableExtension::test_disable_skips_disabled_dependents -vv --tb=short.

Thanks again for pushing this forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants