Second Codex review pass. Two P1s meant the descriptor mechanism was dead for
every real plugin; both are verified against the pinned SDK's converter.
Backend:
- The SDK's CapabilityRecordsFromManifest nests a capability's arbitrary
metadata struct under "metadata" rather than flattening it, so an installed
plugin's contract lives at metadata.metadata.scan_source. The top-level lookup
never matched, silently resolving every plugin to host defaults. Look in the
nested location first, keeping the flat path for host-built descriptors.
- The same converter persists config_schema without admin_form, so a plugin's
form could never reach the UI through that route. The canonical location is
now config_form inside the typed block, which survives installation; the
config_schema path is retained only as a fallback.
- Compatibility merging now tracks which fields the manifest actually declared
instead of comparing against defaults. A plugin explicitly declaring a
default-valued field was previously indistinguishable from silence and got
overwritten, contradicting the stated manifest-wins contract.
- Dynamic-option fields are dropped from source config forms: their options come
from a connection-aware probe only the plugin-config page performs, so a
required one could never be satisfied and would block creation permanently.
Frontend:
- The mapping editor asked for "the path your download manager reports" and
suggested /downloads/tv. arrwebhook.Parse consumes episodeFile.path /
movieFile.path — the arr's imported library paths, not the download client's
working directory — so following the example produced rewrites that never
matched. Relabelled to the Sonarr/Radarr root folder.
- Full-state saves no longer carry an invalid config draft. Enabling, renaming,
or editing an interval serialized the in-progress draft, bypassing the
validity gate; unrelated mutations now send the persisted config instead.
- Legacy movie_nested_paths/tv_nested_paths migration is scoped to the CephFS
identity. Applied globally it would rename an unrelated plugin's
identically-named key on first save, losing its configuration.
- Rows re-parse when the descriptor arrives after /sources. Mounting with the
default descriptor left stored switch values as strings, so "false" rendered
as an enabled switch. Done during render rather than in an effect, and skipped
once the row is dirty so an in-progress edit survives.
- Values hidden by an unsatisfied show_when are no longer persisted, and
displayed defaults are. Both come from reusing buildSchemaValues, the same
helper the plugin-config page uses.
- The manual connection form asks which service it is when a descriptor accepts
several, instead of always recording the first kind.
- The inline connection picker remounts per source, so a half-entered server is
not carried into a different plugin's descriptor.
- emits_native_paths sources report unknown targets rather than "can't match
anything": they discover Silo-native paths at poll time and need no
configured root.