Skip to content

spec: Pluggable integration registry umbrella + 20 leaves#1300

Merged
WilcoLouwerse merged 13 commits intodevelopmentfrom
specs/pluggable-integration-registry
May 7, 2026
Merged

spec: Pluggable integration registry umbrella + 20 leaves#1300
WilcoLouwerse merged 13 commits intodevelopmentfrom
specs/pluggable-integration-registry

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Summary

Spec-only PR — no code yet. Proposes a pluggable integration registry as the canonical mechanism for declaring "things linked to an OR object" (files, notes, tasks, calendar, mail, deck, contacts, talk, + new NC-native + external-via-OpenConnector). Decouples integration definitions from OR core and @conduction/nextcloud-vue.

  • 1 umbrella defining the contract, the three-stage visibility filter (registry → schema linkedTypes → component override), the four widget surfaces with graceful fallback, the CI-enforced widget-parity rule, OCS capabilities exposure, and external routing via OpenConnector.
  • 19 leaves covering every NC-native integration we want + 2 external examples (OpenProject, XWiki).
  • 1 follow-up cleanup removing the deprecated LinkedEntityService::TYPE_COLUMN_MAP after the umbrella stabilises.

Backwards-compatible with existing CnObjectSidebar and schemas.

Contents

openspec/changes/
├── pluggable-integration-registry/        # umbrella
├── integration-calendar/                  # Wave 1 — backend ready, UI missing
├── integration-email/                     # Wave 1
├── integration-deck/                      # Wave 1
├── integration-contacts/                  # Wave 1
├── integration-talk/                      # Wave 1
├── integration-forms/                     # Wave 2 — new NC-native
├── integration-polls/                     # Wave 2
├── integration-bookmarks/                 # Wave 2
├── integration-maps/                      # Wave 2
├── integration-collectives/               # Wave 2
├── integration-activity/                  # Wave 2 (introduces query-time storage)
├── integration-photos/                    # Wave 2 (filtered view of Files)
├── integration-flow/                      # Wave 2 (admin-gated)
├── integration-analytics/                 # Wave 2 (apexcharts, 5-min refresh)
├── integration-time-tracker/              # Wave 2 (configurable backend)
├── integration-shares/                    # Wave 2 (query-time)
├── integration-cospend/                   # Wave 2
├── integration-openproject/               # Wave 3 — external via OpenConnector
├── integration-xwiki/                     # Wave 3
└── cleanup-linked-entity-type-map/        # Wave 0.5 — follow-up removal

Review focus

Please critique the umbrella first — its contract constrains every leaf.

  • openspec/changes/pluggable-integration-registry/proposal.md
  • openspec/changes/pluggable-integration-registry/design.md (19 architecture decisions)
  • openspec/changes/pluggable-integration-registry/specs/generic-integrations/spec.md

Key decisions to push back on:

  1. Widget parity as CI-enforced hard rule (AD-3) — no tab-only integrations permitted
  2. Schema linkedTypes defaults to "none" — absent = empty whitelist
  3. Reference-property auto-rendering (AD-17) — extends CnFormDialog/CnDetailGrid scope significantly
  4. External routing via OpenConnector (AD-4) — not a second mechanism
  5. ADR-019 lives in hydra (org-wide) — see companion PR at ConductionNL/hydra

Leaf-level review can focus on correctness of individual contract fills; umbrella-level concerns should block first.

Test plan

  • Read umbrella proposal + design end-to-end
  • Verify contract captures the real surface area (spot-check 3-4 leaves for contract fit)
  • Confirm backwards-compat claims (existing CnObjectSidebar consumers unchanged)
  • Confirm schema linkedTypes migration story (no forced rewrite)
  • Check companion hydra PR for ADR alignment
  • Leaves cross-linked to umbrella via depends_on in each hydra.json

Introduces the IntegrationProvider contract, IntegrationRegistry service,
three-stage visibility filter (registry → schema linkedTypes → component
excludeIntegrations), four-surface widget model with graceful fallback,
OCS capabilities exposure, per-integration RBAC, external routing via
OpenConnector, and reference-property auto-rendering.

Backwards-compatible with existing CnObjectSidebar and LinkedEntityService.
TYPE_COLUMN_MAP marked deprecated; removal in follow-up cleanup leaf.

Ships with:
- proposal.md, design.md (19 architecture decisions)
- tasks.md (implementation checklist)
- specs/generic-integrations/spec.md (capability delta)
- hydra.json (umbrella leaf plan)
…sing

Five leaves whose backend services already exist (from nextcloud-entity-relations
and prior Talk work) but have no sidebar/widget UI. All depend on
pluggable-integration-registry.

- integration-calendar (Meetings via CalDAV VEVENT)
- integration-email (link-only; Mail owns compose)
- integration-deck (Cards with sticky default board + mini-kanban)
- integration-contacts (role-grouped, canonical person chip)
- integration-talk (single provider routes Chat + Conversation)

Each ships: provider, sidebar tab, 4-surface widget, registration, spec,
tests, nl+en.
Twelve new NC-native integrations, each a greenfield backend + UI vertical
slice depending on pluggable-integration-registry.

- integration-forms (responses + form-mapping for auto-link)
- integration-polls (lifecycle, tally, user vote highlight)
- integration-bookmarks (delegates scraping to Bookmarks app)
- integration-maps (cached lat/lon, address chip)
- integration-collectives (markdown preview, inline detail-page)
- integration-activity (introduces query-time storage strategy)
- integration-photos (filtered view of Files, EXIF, GPS strip)
- integration-flow (admin-gated, coexists with OR workflow engine)
- integration-analytics (apexcharts, 5-min dashboard refresh)
- integration-time-tracker (configurable backend, denormalized totals)
- integration-shares (query-time aggregation, revoke action)
- integration-cospend (project/bill link types, per-currency totals)

activity and shares introduce the new 'query-time' storage strategy;
umbrella coordination flagged in those leaves.
Two external-service integrations proving the storage='external' path
through ExternalIntegrationRouter + OpenConnector sources. Introduces
IntegrationProvider::getOpenConnectorSource() as a small umbrella extension.

- integration-openproject (first external; OAuth2; WP status badges)
- integration-xwiki (breadcrumb display, text-only preview — no macro
  execution in NC context)

openproject lands first and proves the pattern; xwiki soft-depends on it
to reuse the established routing + auth-status surfacing.
Wave 0.5 cleanup leaf. Removes LinkedEntityService::TYPE_COLUMN_MAP and
Schema::VALID_LINKED_TYPES constants once umbrella is archived and
built-in providers stabilise. Pre-removal grep sweep of the ConductionNL
org required before the removal commit. Pure refactor — no behaviour
change.
…lta format

- design.md: renumber ADs sequentially 1-21 (were in chronological insert order with
  AD-4b/4c and out-of-order AD-12/AD-10 at the bottom). Update AD-21 companion ADR
  text to reference the now-authored hydra ADR-020.
- design.md: resolve "Ten open questions" note — all have been folded into ADs.
- specs/generic-integrations/spec.md: change `## Requirements` to `## ADDED Requirements`
  per OpenSpec delta convention (hydra/openspec/AGENTS.md).
- hydra.json: drop non-schema fields (leaf_plan etc.); keep only v2 schema fields.
…rations

Per hydra/openspec/AGENTS.md, changes contribute to a shared capability via
specs/<domain>/spec.md deltas. The prior structure used per-leaf capability
names (specs/integration-calendar/, specs/integration-email/, ...) which
would have created 20 unrelated capabilities at archive time. Restructured
so every leaf writes a delta against the shared `generic-integrations`
capability.

For each of the 20 leaves:
- Move specs/integration-<id>/spec.md → specs/generic-integrations/spec.md
- Convert `## Requirements` heading to `## ADDED Requirements`
- Delete the old specs/integration-<id>/ directory

Also per-leaf housekeeping:
- hydra.json: drop non-schema fields (wave, wave_label, soft_depends_on,
  depends_on_reason, notes); keep only schema-v2 valid fields.
- integration-xwiki: drop soft-dep on integration-openproject (both now
  parallel leaves depending only on the umbrella).
- design.md: add "Cross-repo note" clarifying that `nextcloud-vue/src/...`
  paths are expected locations in a separate repo, not binding spec.
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ cdcd74d

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

Quality workflow — 2026-04-22 06:04 UTC

Download the full PDF report from the workflow artifacts.

Critical review of the capability spec surfaced gaps that would have left
leaves with ambiguous contracts. Added five new Requirements and
improved scenarios on two existing ones:

New Requirements:
- Tags and Audit-Trail as First-Class Integrations — explicit spec for
  what AD-7 decided, including audit-trail's admin-only permission.
- Registration Collision Handling — duplicate-id behavior on both sides
  (PHP DI container build failure; JS throw/warn).
- Error-Handling Contract — typed exceptions + HTTP mapping so providers
  don't leak generic 500s. ProviderUnavailable/Auth/NotFound/Validation.
- Pagination on List Endpoints — default 20, max 100, consistent total +
  hasMore in responses.
- Migration of Existing Schemas — one-time auto-populate of
  linkedTypes=[files, notes, tasks, tags, audit-trail] for absent values,
  preserving the 5-tab user-visible behaviour across the upgrade.

Added scenarios:
- Widget Surfaces: widget render failure is isolated to the failing
  widget (other widgets keep rendering).
- Reference-Property Auto-Rendering: broken references render a
  placeholder; reference to an uninstalled integration's NC app renders
  a "not installed" placeholder.

Tightened Backwards Compatibility: the first scenario now correctly
depends on the schema migration having run.
… leaf

Every leaf gets an explicit "Graceful Degradation" requirement binding it
to the umbrella's Error-Handling Contract and adding at least one
leaf-specific scenario covering an edge case real deployments hit.

Covered edge cases per leaf:
- calendar: concurrent unlink vs external delete race
- email: linked message purged from Mail trash
- deck: archived card still accessible with badge
- contacts: vCard deleted from address book
- talk: conversation deleted in Talk
- forms: form deleted while response links exist
- polls: closed vs deleted distinction + both paths
- bookmarks: URL 4xx/5xx on link check
- maps: geocoding unavailable → click-placed fallback
- collectives: per-user collective access revoked
- activity: Activity app disabled mid-session
- photos: HEIC without preview provider
- flow: rule deleted while historical fires remain
- analytics: empty dataset + chart config version mismatch
- time-tracker: reconcile repairs total drift
- shares: user lacks share-management permission
- cospend: multi-currency no-aggregation constraint
- openproject: rate limit via OpenConnector (429)
- xwiki: page moved + macro-output sanitisation

Cleanup leaf doesn't need this — it's a pure removal change.
@github-actions
Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openregister @ 37b4a1e

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

Quality workflow — 2026-04-22 07:21 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde rubenvdlinde changed the base branch from beta to development May 7, 2026 07:27
@rubenvdlinde rubenvdlinde requested a review from Rem-Dam as a code owner May 7, 2026 07:27
…e-integration-registry

# Conflicts:
#	openspec/changes/pluggable-integration-registry/design.md
#	openspec/changes/pluggable-integration-registry/proposal.md
#	openspec/changes/pluggable-integration-registry/tasks.md
Comment thread openspec/changes/pluggable-integration-registry/design.md
Comment thread openspec/changes/integration-openproject/design.md
Comment thread openspec/changes/pluggable-integration-registry/tasks.md
Comment thread openspec/changes/integration-flow/proposal.md
Comment thread openspec/changes/integration-openproject/design.md
@WilcoLouwerse
Copy link
Copy Markdown
Contributor

🟢 Minor — Inconsistent use of 'Standard four surfaces' shorthand vs explicit surface list

Several leaf specs use 'Standard four surfaces; detail-page surface mirrors tab.' (activity, cospend, maps, bookmarks, collectives, others) without listing the four surfaces explicitly. This is fine for implementers who have read the umbrella, but differs from leaves like calendar and talk which enumerate all four surfaces by name in dedicated scenarios. The inconsistency makes it harder to audit that each leaf has the same surface contract.

Suggested: Either (a) standardise all leaves to use the shorthand and rely on the umbrella's surface definition, or (b) consider adding a single reference sentence: 'Standard four surfaces: user-dashboard, app-dashboard, detail-page, single-entity — per umbrella AD-6/AD-18.' Minor — no blocking concern.

@WilcoLouwerse
Copy link
Copy Markdown
Contributor

🟢 Minor — Trailing double-period in cross-repo note boilerplate

Every leaf's design.md contains the line 'The frontend implementation PR lands in that separate repo and MAY choose different paths..' with a double period. This appears in all 19 leaves and the umbrella design.md.

Suggested: Remove the duplicate period. One-liner fix across all design.md files.

@WilcoLouwerse
Copy link
Copy Markdown
Contributor

🟢 Minor — Individual leaf tasks exceed 15 per ADR-028 for some leaves

Calendar (26 tasks), contacts (23 tasks), deck (25 tasks), openproject (24 tasks), talk (24 tasks) all exceed the 15-task ADR-028 limit, though none as severely as the umbrella (68). These are boundary cases — the tasks are genuinely scoped to one leaf — but the count is notable.

Suggested: Review whether some tasks can be consolidated (e.g., 'DI-tag, routes, unit tests' as a single task rather than three sub-items). Alternatively, document a waiver for complex leaves. Lower priority than F-006.

@WilcoLouwerse
Copy link
Copy Markdown
Contributor

🟡 Concern — No CI check runs on this PR — unexplained in spec

The PR has no reported CI check runs (anomalous per review checklist). For a spec-only PR (only openspec/changes/** files changed), this is expected if the CI workflows are path-filtered to ignore openspec/ changes. However, no CI workflow or openspec-validate rule is documented or referenced in this PR. An openspec validate or link-checker run would catch broken cross-references (found in F-003 and F-004) before merge. The absence of CI means spec defects reach review that a validator would catch automatically.

Suggested fix: Add a .github/workflows/spec-validate.yml that runs on openspec/changes/** changes and checks: (a) all cross-reference markdown links resolve, (b) hydra.json files pass schema validation, (c) tasks.md counts are reported. This does not block this PR but is a follow-up recommendation.

Copy link
Copy Markdown
Contributor

@WilcoLouwerse WilcoLouwerse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strict-mode review — 5 blockers, 7 concerns, 3 minors.

Top blockers:

Strict mode requires REQUEST_CHANGES on any 🔴 or 🟡. Please address blockers and concerns before merge.

… concerns

Umbrella + leaves: address all 5 🔴 blockers and 6 🟡 concerns plus 3 🟢 minors
from the strict-mode review.

Blockers
- Storage strategy enum: extend to `'magic-column'|'link-table'|'external'|'query-time'`
  in interface docblock, AD-22, and add a Storage Strategy Enum requirement with
  scenarios (timeout/degraded surface, NotImplemented mutations, external rejecting
  null OpenConnector source).
- OCS capabilities: role-redact `requiresPermission`/`authStatus`/`openConnectorSource`
  to admins only; non-admins see `{id,label,group,enabled,surfaces}` only.
- Two broken cross-references (shares + openproject) — sub-directory paths corrected
  to `generic-integrations/`.
- Umbrella spec cross-references — added missing `../` so paths resolve to
  `openspec/specs/...` not `openspec/changes/specs/...`.
- `getOpenConnectorSource(): ?string` added to umbrella `IntegrationProvider`
  interface + AbstractIntegrationProvider default + tasks.md.

Concerns
- ADR-028 task-cap waiver documented in proposal.md + tasks.md (umbrella) and as a
  one-line preamble on each over-cap leaf (calendar, contacts, deck, openproject,
  talk).
- Flow integration: `'admin'` permission semantics defined (NC admin group via
  IGroupManager::isAdmin); two-gate model (app gate vs permission gate) with
  scenarios; permission-string vocabulary documented in umbrella spec.
- Migration scenario added: schemas with linkedTypes referencing mid-rollout ids
  (umbrella deployed, leaf pending) — stage-1 filter drops + admin notice.
- Query-time storage risks specced (AD-22): per-render timeout (default 2s),
  degraded surface, blended-feed source-of-truth, filter-chip persistence note.
- CI parity rule: executable definition of "set" — non-null/non-undefined +
  `typeof === 'function'`; scenarios for null and non-component values; hydra-repo
  gate wiring called out as separate change.
- OpenConnector failure paths (AD-23): lazy auth check, OpenConnector-managed token
  refresh, `details.cause` distinguishing connector-down / source-missing /
  upstream-down.

Minors
- `Standard four surfaces` shorthand standardised to `Per umbrella AD-6/AD-18, the
  widget SHALL render on all four surfaces (user-dashboard, app-dashboard,
  detail-page, single-entity)` across leaves.
- Trailing double-period typo in cross-repo design boilerplate fixed across all
  leaf design.md files.

Follow-up note added to proposal.md for the absent spec-validate CI workflow that
would have caught the broken cross-references at PR-time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@WilcoLouwerse
Copy link
Copy Markdown
Contributor

🟢 Standard four surfaces shorthand — resolved in 500d5e0.

Standardised every leaf's Widget Surfaces requirement to use the umbrella reference: "Per umbrella AD-6/AD-18, the widget SHALL render on all four surfaces (user-dashboard, app-dashboard, detail-page, single-entity); ...". Applied to: photos, cospend, shares, time-tracker, openproject, analytics, activity, xwiki, flow, polls, bookmarks. Calendar/talk/email/forms already enumerated explicitly and were left intact.

@WilcoLouwerse
Copy link
Copy Markdown
Contributor

🟢 Trailing double-period typo — resolved in 500d5e0.

Fixed across all 18 leaf design.md files (umbrella design.md didn't have the cross-repo note so was already clean). One-liner sed across the tree.

@WilcoLouwerse
Copy link
Copy Markdown
Contributor

🟢 Leaf tasks exceeding 15 — resolved in 500d5e0.

Documented an ADR-028 task-cap waiver as a one-line preamble on each over-cap leaf:

  • integration-calendar (26 tasks)
  • integration-contacts (23 tasks)
  • integration-deck (25 tasks)
  • integration-openproject (24 tasks)
  • integration-talk (24 tasks)

Rationale per leaf: each is a single integration vertical slice (provider + sub-resource controller + tab + 4-surface widget + tests + nl/en); splitting it would force interleaved depends_on chains that ship slower than one cohesive leaf. Hydra's builder SHOULD batch these across multiple turns.

@WilcoLouwerse
Copy link
Copy Markdown
Contributor

🟡 No CI runs on spec PR — acknowledged with a follow-up note in 500d5e0.

Added a "CI follow-up — spec validation" section in the umbrella's proposal.md flagging the absent .github/workflows/spec-validate.yml as a separate follow-up change. The proposed workflow would:

  • resolve every markdown link in openspec/changes/** and fail on 404,
  • validate hydra.json files against the project schema,
  • report tasks.md checkbox counts so ADR-028 deviations are visible at PR-time.

Two of the blockers in this review (broken cross-references in shares + openproject + umbrella) would have been caught by such a workflow. Deliberately not bundled into this umbrella to keep its scope on the integration registry itself; tracked separately.

Copy link
Copy Markdown
Contributor

@WilcoLouwerse WilcoLouwerse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 5 🔴 blockers and 6 🟡 concerns from the prior strict review are substantively resolved in 500d5e0 — not just acknowledged, but with new normative requirements + scenarios that strengthen the umbrella contract.

Blockers resolved:

Concerns resolved:

Plus: shorthand standardised, typo fixed, spec-validate CI follow-up noted. Newman ✓ green; no failing required checks.

Looks good for merge — the umbrella contract is materially stronger than at first review.

@WilcoLouwerse WilcoLouwerse merged commit 8302ec9 into development May 7, 2026
1 check passed
@WilcoLouwerse WilcoLouwerse deleted the specs/pluggable-integration-registry branch May 7, 2026 15:31
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.

4 participants