Spec + standards review (2-subagent /code-review) found five real
gaps against the settled design; all fixed:
- enabled_member_dirs() now FILTERS by enabled state: only plugins in
some profile's plugins.enabled join the union (a disabled plugin
never syncs). Result is ENABLE-RECENCY-ORDERED (newest last) via
new pm/plugins_state.py — order-preserving reads of every profile's
enabled list — so the bisect's incumbent-wins tiebreak (pop last)
now disables the most-recently-enabled, not the alphabetical last.
- materialize_legacy_pyproject() is gated on lazy_installs_allowed():
lazy-off installs keep the plugin dir untouched (materializing
would create a member candidate and then hard-fail every sealed
sync). Settled: 'never runs when lazy installs are disabled'.
- plugins_cmd dep install routes through resolve_union + the lazy
gate (it previously drove lock_and_sync directly, bypassing both):
the would-be union resolves as a check before enable, with the
plugin's own bisect decision surfaced as the refusal reason. The
real sync after enable still runs through sync_venv (the one
authority, with receipt + write-back).
- resolve_union disable decisions are written back to the plugins
enabled config (record_disabled_plugins → pm.plugins_state.
disable_plugins) so hermes plugins list reflects reality and
re-enable retries; best-effort, never breaks the sync.
tests: member discovery now asserts enabled-filter + recency order +
orphan exclusion; plugins_state suite (5: cross-home reads, order
preservation, disable write-back across homes, noop, garbage-config);
materialize lazy-off test; deps_flow updated to the resolve_union
shape. tests/pm + deps_flow: 193 passed, 0 failed.