Skip to content

feat(integration): cross-app leaf provider registration (ADR-066)#2108

Merged
rubenvdlinde merged 1 commit into
developmentfrom
feat/app-leaf-provider-registration
Jul 25, 2026
Merged

feat(integration): cross-app leaf provider registration (ADR-066)#2108
rubenvdlinde merged 1 commit into
developmentfrom
feat/app-leaf-provider-registration

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Summary

Gives any sibling Nextcloud app a sanctioned server-side seam to register itself as a leaf on OpenRegister objects — contributing render surfaces and app-local read/append data — through a typed collect-event, the same idiom OpenRegister already uses for MCP tool providers and flow nodes.

Implements spec #2102 (spec/app-leaf-provider-registration) and ADR-066 (cross-app leaf registration — the render-and-read boundary; lifts the ADR-041 moratorium strictly for the collect/render/read case). Render-and-read only by construction: descriptor + provider carry no verb; cross-app commands stay ADR-041 typed events.

What changed

New

  • lib/Event/RegisterLeafProvidersEvent.php — typed collect-event mirroring RegisterMcpToolProvidersEvent line for line (registerLeaf() + getLeaves()).
  • lib/Service/Integration/LeafDescriptor.php — value object: id, label, icon, requiredApp, group, surfaces, referenceType, requiresPermission, and a non-empty kinds set {render-surface, data-provider, agent-runner} (agent-runner reserved — hermiq's change consumes it).
  • lib/Service/Integration/LeafRegistry.php — dispatches the event once, lazily, on first read; validates (non-empty/known kinds, kebab-case id, data-provider-requires-provider); first-wins on duplicate id (ADR-013); swallows a throwing listener so it costs only its own leaf; lands data-provider leaves on the shared IntegrationRegistry. Exposes describeForCapabilities() for discovery.
  • Tests: LeafDescriptorTest + LeafRegistryTest (22 tests / 174 assertions).

Changed

  • IntegrationRegistry — lazy setLeafLoader() hook (guarded, once-per-request) so a data-provider leaf lands before ObjectIntegrationsController resolves it, without eager dispatch.
  • IntegrationProvider — documents the new app-local storage strategy (data lives in the sibling app's own store; OpenRegister routes list()/optional create() and persists nothing). Existing strategies untouched.
  • IntegrationsCapability — new openregister.integrations.leaves OCS block (id, label, requiredApp, surfaces, kinds, usability) — discoverable without loading any leaf's JS.
  • Application::bootLeafRegistry() — installs the lazy loader; registers the LeafRegistry service.
  • docs/Integrations/leaf-system.md — cross-app section + worked notes-leaf example (ADR-019/041/013/066).

app-local routing

The existing ObjectIntegrationsController dispatch already calls provider methods directly, so app-local needs no controller branch: the provider's list()/create() run in the sibling app's DI context; a read-only leaf lets the AbstractIntegrationProvider default throw NotImplementedException while list() stays usable.

Verification

  • 22 PHPUnit tests pass in the nextcloud:34 container (174 assertions): announced leaf reaches catalogue; built-ins survive; throwing listener isolated; empty/unknown kinds rejected; data-provider-requires-provider; first-wins collision; namespaced ids coexist; app-local list/empty/create + read-only-throws; OCS reports leaves + usability (disabled required app → unusable); render-and-read boundary (contract exposes no command verb).
  • Live probe against the running instance: a leaf registered through the real RegisterLeafProvidersEvent dispatcher appears in describeForCapabilities(), its data-provider is routable on IntegrationRegistry, and app-local list→create appends only in the provider's own store. Probe removed (no test provider shipped).
  • php -l + composer phpcs clean on all changed lib/ files; spec-coverage (gate-16) passes vs origin/development.

Out of scope / follow-up

  • JS half + gate-24 extension — the render-surface↔JS-id correlation lives in @conduction/nextcloud-vue (registerIntegration() / check-integration-parity.js) and the canonical gate-24 in hydra run-hydra-gates.sh. The openregister wrapper only delegates to the JS check, so the cross-app correlation is a coordinated follow-up in those repos — not edited here.
  • The agent-runner kind is a reserved forward-reference only.

Spec: #2102 · ADR-066 (hydra).

Add RegisterLeafProvidersEvent — a typed collect-event mirroring
RegisterMcpToolProvidersEvent — so a sibling Nextcloud app can register
itself as a leaf on OpenRegister objects (render surface + app-local
read/append data) by writing one IEventListener. Render-and-read only by
construction: descriptor and provider carry no verb; cross-app commands
stay ADR-041 typed events.

- LeafDescriptor value object: id, label, icon, requiredApp, group,
  surfaces, referenceType, requiresPermission, kinds set
  {render-surface, data-provider, agent-runner (reserved)}.
- LeafRegistry: dispatches the event once lazily on first read, validates
  (non-empty/known kinds, kebab-case id, data-provider-requires-provider),
  first-wins on duplicate id (ADR-013), swallows a throwing listener so it
  costs only its own leaf, and lands data-provider leaves on the shared
  IntegrationRegistry via a lazy loader hook so existing per-object routing
  reaches them unchanged.
- app-local storage strategy documented on the IntegrationProvider
  contract: data lives in the sibling app's own store; OpenRegister routes
  list()/optional create() and persists nothing.
- Discovery: openregister.integrations.leaves OCS capabilities block
  (id, label, requiredApp, surfaces, kinds, usability) — discoverable
  without loading any leaf app's JS.
- Tests: 22 PHPUnit tests (event registration, app-local list/create,
  kinds, first-wins collision, throwing-listener isolation, OCS usability,
  render-and-read boundary).
- Docs: docs/Integrations/leaf-system.md cross-app section + worked notes
  leaf example, cross-referencing ADR-019/041/013/066.

Spec: openspec/changes/app-leaf-provider-registration (PR #2102).
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 84ceb23

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
composer ✅ 174/174
npm ✅ 555/555
PHPUnit
Newman
Playwright ⏭️

Quality workflow — 2026-07-25 09:42 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit e596e46 into development Jul 25, 2026
20 of 25 checks passed
@rubenvdlinde
rubenvdlinde deleted the feat/app-leaf-provider-registration branch July 25, 2026 09:44
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.

1 participant