Files
member-console/status/integration-interdependence-exploration.md
T
cgalo5758 c5b52945f3 Propose dissolving member domains
Move member domain management affordances to point-of-use within site
flows and retire the standalone member `/domains` page. Document
integration interdependence exploration.
2026-07-26 02:07:12 -05:00

6.8 KiB
Raw Blame History

Integration interdependence — the road not taken for domains

2026-07-26. Written while de-centering domains from the console IA. This records the path we considered and deferred — registering domains as a provider and letting integrations depend on each other — so a future external factor can pick it up without re-deriving it.

Where this came from

The de-centering discussion produced two candidate homes for the operator domains surface: registry membership ("one click, visible" — domains becomes a provider row and the Integration group lists it like Discourse or FedWiki), or staying a plainly-listed core capability. We chose the second: Domains keeps its operator sidebar entry (honest — it is a cross-workspace operator capability), the member surface dissolves to point-of-use, and the provider question is parked here.

What registry membership would have looked like (mechanically cheap)

The registry would take domains today with almost no force:

  • core.providers stores no credentials or endpoints — slug, kind, display_name, nullable operator_surface_path, free-text status (internal/db/migrations/00001_init.sql:864-875). An "internal" provider fits the table as-is.
  • Registration is code-driven and idempotent: a ProviderSource manifest collected at boot, upserted by integration.RegisterProviders (cmd/start.go:165-172, internal/integration/registration.go:123-234). Domains would ship a manifest like any integration package.
  • The operator sidebar's Integration group is registry-driven — ListProvidersByKind('provisioning'), skipping rows without a surface path (internal/server/operator_pages.go:49-67). A domains row with OperatorSurfacePath: "/operator/domains" appears with zero template work.
  • The one schema obstacle: chk_providers_kind_valid closes the kind enum at payment | provisioning | notification | tax (00001_init.sql:874, mirrored in registration.go:18-23). Domains either reuses provisioning (defensible: it provisions names) or the CHECK gains an infrastructure/internal kind by migration.
  • Bonus repair: the manifest could own the external_domain_claims resource key, pulling the custom-domains entitlement gate toward core. Today that gate is enforced in fedwiki's web layer (internal/integrations/fedwiki/web/customdomain.go:37-56) — a misplacement that predates this discussion and is filed as extraction-prep debt regardless of path.

Why we didn't: the categorical problem

Every dependency arrow points into internal/domains, and the reverse edge is explicitly forbidden (internal/domains/authorizer.go doc comment, design D5/D6). FedWiki's create saga reserves names via the registry (internal/integrations/fedwiki/workflows/activities.go:107-118), its sync projects placements, and core defines the domainsReconciler hook that integrations implement (cmd/start.go:34-48). Integrations are tenants of the namespace; domains is the landlord. The operator-panel-navigation spec encodes the same idea as its one-way catalog → runtime → integration dependency rule: the Integration group is for things at the top of the dependency order, and domains sits at the bottom.

Registering the landlord in the tenants list is mechanically free and conceptually wrong — unless interdependence becomes a first-class, designed-for property of the provider model. That design work is the real cost, and it's what this document scopes.

The design work interdependence actually needs

If integrations may depend on each other (or on provider-shaped infrastructure like domains), each of these needs an answer before the first dependent ships — retrofitting any of them is misery:

  1. Dependency declaration. Manifests grow DependsOn []slug. RegisterProviders validates the graph is a DAG and refuses cycles at boot, loudly. Ordering of registration/boot hooks derives from the topological sort, not package-import accident.
  2. Enablement vs health. status today is one free-text field. Interdependence forces the split: a provider can be healthy but disabled, enabled but degraded, and — the new case — enabled but starved because a dependency is disabled. Disabling a depended-on provider must either cascade (disable dependents), refuse ("FedWiki depends on Domains"), or degrade dependents explicitly. Silent starvation is the tomfoolery baseline to design against.
  3. Mediated capability vs code import. FedWiki imports internal/domains directly. A provider-mediated dependency implies the registry (or a capability interface at that layer) fronts what dependents may call — otherwise DependsOn is decorative metadata over ordinary imports, and the model lies. Deciding metadata-only is a legitimate outcome, but it must be decided, not drifted into.
  4. Blast radius. Placements already accept any registered provider slug; the write model is "integrations grant on and write domains.*" (internal/domains/store.go:19). Interdependence needs explicit rules for what a provider may do to a dependency's resources — per-operation grants, not ambient schema access — or contraption-building follows.
  5. UI honesty. If infrastructural providers list under Integrations, the group stops meaning "external systems" and starts meaning "registered modules." Either the group's definition changes in the IA spec, or infrastructural providers render distinctly (sub-grouped, badged). The sidebar is a claim about architecture; it should stay true.

Reopening triggers

Park until one of these applies, then start from this list:

  • A second consumer of domains (anything beyond FedWiki wanting placements — forum vanity domains, static-site hosting, …).
  • M13 multi-farm, which the 10d exploration already flags as landing "in the FedWiki integration, not the registry" — pressure on the boundary will make the dependency explicit.
  • Actual extraction demand: someone wants the domains model as a standalone service/package. The module's leaf-with-IoC shape is extraction-ready; what extraction adds is a network API in front of Registry/Tx, inter-service authn, latency tolerance in the create saga, and schema ownership — at which point domains-as-provider (kind infrastructure, remote endpoint) becomes the natural registration and points 14 above become mandatory rather than speculative.
  • Any new integration that needs another integration — the general case arriving by a different door.

Decision record

2026-07-26 — deferred. Chosen instead: member /domains dissolves into the site-creation/site-management flow; operator Domains keeps its sidebar entry; overview metric departs in the overview-polish change; the entitlement-gate misplacement is tracked as debt. No registry membership, no DependsOn, no new provider kind.