Files
Tony C 35c6f8532c Integration Capabilities framework (#355) (#359)
* Rename Connect vocabulary across the integrations framework

Three coordinated rename groups (no behavior change):

- test_connection -> validate_access on IntegrationGateway and all
  service overrides + call sites. The generalized verb covers both
  network probes (today) and access checks for future Import-capability
  integrations.
- is_initial_import -> is_initial_connect across the synchronizer
  protocol, view layer, templates, and tests. Internal sync /
  synchronize vocabulary is preserved.
- UI labels updated: IMPORT -> CONNECT (in live-discovery contexts),
  REFRESH -> UPDATE (button) / Update (inline link), Initial Import ->
  Initial Connect, Refresh Result -> Update Check Result. The
  no-items hero copy reads 'No items found.' with non-redundant
  sub-text.

Prerequisite for the broader integration-capabilities work in #355.

* Restructure HomeBox importer/ — Connect code to connector/, dormant preserved

The reachable Connect-mode synchronizer code moves from
services/homebox/importer/ to connector/:

- HomeBoxSynchronizer relocates to connector/hb_sync.py.
- New HbEntityFactory in connector/hb_entity_factory.py carries the
  two entity-create/update classmethods previously on HbImporter.

The dormant attribute-population code stays in importer/ for #358:

- HbImporter retains only its four attribute-side classmethods with a
  narrowed docstring noting their dormant status.
- The synchronizer's removed attribute-orchestration methods extract as
  HbAttributeSync in importer/hb_attribute_sync.py with a docstring
  marking them preserved for the HomeBox Importer in #358.

No behavior change. (#355)

* Phase 3 (#355): split integrations app into umbrella + connect sub-package

Relocate the Connect-capability machinery (gateways, synchronizers, sync-check,
views, view mixins, entity operations, attribute edit context, sync-result, and
related transient models) from hi/integrations/ into hi/integrations/connect/.
Umbrella-level modules (models, forms, enums, manager, urls, exceptions, etc.)
stay at hi/integrations/ as the framework root.

This re-shapes the package along capability lines ahead of the Import
capability being added by #358: the umbrella holds capability-agnostic
framework code; each capability sub-package holds the per-capability machinery.

No behavior changes. All call sites updated to the new module paths; all 3141
tests pass and lint is clean.

* Tolerate unreadable sync-check cache entries

The sync-check probe state is stored in Redis as a pickled
SyncCheckResult dataclass. Any class rename, module move, or shape
change can leave stale entries that fail to deserialize on read,
which previously propagated ModuleNotFoundError / AttributeError /
UnpicklingError straight out through the manage page.

Wrap the cache.get in get_state with a broad except: log the failure,
evict the bad key so the next request runs clean, and degrade to a
cache miss. The next probe cycle (or a Refresh) writes fresh state.

The cache is informational drift state, not load-bearing — a soft
miss is the right failure mode.

* Phase 4 (#355): add EntityDataSource enum + Entity.data_source field

EntityDataSource is the entity-wide provenance signal (INTERNAL when HI
owns the representation; EXTERNAL when an upstream system does).
Surfaces via Entity.data_source — a typed accessor over the new
data_source_str CharField, following the existing entity_type pattern.

Migration 0021 backfills integration-attached entities that disallow
internal attributes to EXTERNAL. Today this picks out only HomeBox-
Connect entities; HA / ZM / Frigate / native entities stay INTERNAL.

No UI impact yet. Phase 6 will consume data_source for cross-capability
transition prompts and Import-discard scoping.

* Phase 5 (#355): add IntegrationCapability enum + metadata declaration

IntegrationCapability declares what an integration brings to HI:
CONNECT (live mirror) or IMPORT (one-shot pull). IntegrationMetaData
now carries a `capabilities` frozenset declaring which capability set
the integration participates in; defaults to {CONNECT} since that is
the existing behavior for all four current integrations. An empty
set raises at construction time so a future integration cannot
silently register with no capability.

ALL_CAPABILITIES is provided as a frozenset convenience over all
enum members for callers that want "available everywhere" semantics.

No UI impact yet. Per-attribute capability filtering deferred until
Phase 6 introduces the consumer.

* Phase 6 (#355): make capability filtering explicit at integration list and attribute formset sites

Extend IntegrationAttributeType to accept an optional 8th tuple element
declaring which IntegrationCapabilities the attribute applies to
(defaults to ALL_CAPABILITIES, so existing declarations are unchanged).

Convert the previously-implicit "everything is Connect" assumption into
explicit capability filters at four sites that all run in Connect-mode
context today:

  * IntegrationSelectView (Enable Integrations picker modal)
  * IntegrationManageView (Config tab — enabled-integrations list and
    default-integration selector)
  * IntegrationAttributeItemEditContext (formset construction, used by
    both the Configure modal and the Config tab attribute pane)

IntegrationManager.get_integration_data_list() and
get_default_integration_data() gain an optional `capabilities`
frozenset kwarg. IntegrationAttributeItemEditContext gains a required
`capability` __init__ arg; its formset queryset is now filtered to
attribute rows whose AttributeType includes that capability. The same
class can be reused for the Import edit context in #358 by passing
IntegrationCapability.IMPORT.

Behavior unchanged today (all integrations are Connect, all attributes
default to ALL_CAPABILITIES) but the filter rules are now testable
with mixed-capability fixtures.

The Importer protocol, workflow/views/templates, and cross-capability
transition prompts originally listed in Phase 6 absorb into #358 (the
HomeBox Importer) — they are easier to design with a concrete consumer
driving them.

* Phase 7 (#355): collapse Configure + Pre-Sync + Sync into one CONNECT click

The initial-connect flow becomes a single workflow under
IntegrationEnableView: the user fills the configure form once and
clicks CONNECT, which validates access, saves attributes, enables
the integration, and runs the synchronizer in one pass — landing
directly on the sync-result modal. The previous Configure → Pre-Sync
confirm → Sync handshake is gone from this path, along with the
REVIEW CONFIG round-trip affordance.

Extracted IntegrationViewMixin.render_sync_result so the
synchronizer-invocation + cache-invalidation + sync-result rendering
is shared between IntegrationEnableView (initial-connect) and
IntegrationSyncView (update-check). Action button label flips from
the conditional CONFIGURE/UPDATE to a fixed CONNECT.

IntegrationPreSyncView and its template still serve the manage-page
UPDATE / CONNECT-when-no-entities paths unchanged; the
removal_summary policy choice (RETAIN MISSING / REMOVE MISSING) for
user-data-bearing integrations is preserved end-to-end.

Net behavior change: one fewer modal click on first-time setup.

* Phase 8 (#355): align documentation with the new Connect/Update vocabulary

Update the user-facing button-label references in the per-integration
docs (Refresh / REFRESH → Update / UPDATE; Import action → Connect)
and the developer-facing references where they were tied to user
surfaces. Internal developer vocabulary stays as "sync" — that is
the implementation-side term.

Add a Capability Model subsection to integration-guidelines.md
covering IntegrationCapability, the optional per-attribute capability
restriction, and EntityDataSource. Points at the enum modules; keeps
the explanation brief since the code is the authoritative source.

Update the per-integration doc template's vocabulary instruction so
new integrations start with the right terms.

* Address review feedback: sharpen vocabulary and add IntegrationEnableView POST tests

Four fixes from the pre-PR review pass:

- Sharpen EntityDataSource enum descriptions to disambiguate from raw
  value provenance: "HI owns the editable representation" /
  "Upstream constrains HI-side edits" (the actual semantics codified
  by migration 0021's allow_internal_attributes-based backfill).
- Replace the self-contradicting "first **Import**" / "post-import
  state" in docs/integrations/_template.md with the **Connect**
  vocabulary the same template instructs authors to use.
- Reword frigate.md troubleshooting heading "Update imports zero
  cameras" → "Update finds zero cameras" to stop mixing the UPDATE
  button label with the "imports" verb.
- Add two IntegrationEnableView.post() tests covering the Phase 7
  collapse: the happy path verifies enable + synchronizer.sync()
  fire and the sync-result modal renders; the synchronizer-less
  path verifies enable still flips but no sync-result is returned.
2026-05-24 16:58:18 -05:00
..