Files
member-console/design/documents/changeset-v9-integration-analysis.md
T
cgalo5758 bfe9cee0fe Consolidate design docs into documents directory
- Remove per-module projection files (README, architecture, companion,
  interfaces, model) under design/<module>/
- Add design/documents/ with numbered design docs, references, policies,
  and manifest
- Update design/README.md to describe the directory as a mirror of
  membcons-db's normative surfaces
- Record Decisions 140-141 in companion and glossary; update
  data-model.md schema organization
2026-08-21 00:55:42 -05:00

15 KiB
Raw Blame History

Changeset Analysis: v8 → v9 Integration Architecture

Prepared: 2026-02-27 Applied: 2026-02-28 Source conversations: 16 (Domain Boundaries), 17 (Integration Architecture) Source artifacts: #88, #89, #90, #91, #92 Target: data-model-v8.0.md → v9.0, v8_companion.md → v9.0


1. Artifact Roles and Relevance

Artifact #88 — Integration Architecture Patterns from Production Billing Systems

Role: Research reference. Surveys how Kill Bill, Lago, Saleor, Medusa, and Odoo organize integration data.

What's useful:

  • Confirms payment methods, refunds, and disputes are universally modeled as core entities
  • Documents the webhook idempotency pattern (shared webhook_events table with UNIQUE(provider, provider_event_id))
  • Documents the transactional outbox pattern for outbound integrations
  • Documents how production systems map internal customers to external provider representations via junction tables

What's projection about our model:

  • Its concluding "synthesized specification" recommends a single-schema approach (Pattern D), which #92 subsequently overrode with schema-per-provider after deeper analysis. The single-schema recommendation in #88 was based on absence of evidence from ORM-constrained platforms, not a deliberate architectural evaluation.
  • Recommends "JSONB data or metadata columns on core models" for provider-specific data — contradicts our JSONB governance policy (Decision 34) which prohibits PII in JSONB. #92 correctly corrects this to separate integration tables with relational columns.
  • References "provider_id or provider_type discriminator" on core entities — our model deliberately avoids this (Decision 8).

Disposition: Reference document. Already curated. No direct model changes flow from it — its findings were consumed and refined by #92.


Artifact #91 — Resolving Boundary-Ambiguous Concepts in Domain Schema Design

Role: Theoretical methodology. Provides the five decision heuristics and seven-step methodology for determining what belongs in core vs. integration.

Relationship to #92: #91 is the intellectual foundation that justifies #92's specific recommendations. When #92 says "payment methods are core entities" and "provisioning state is a core concept," the reasoning chain runs through #91's heuristics (Business Question Test, Provider Swap Test, etc.). #91 was produced in Conversation 16, which preceded the final synthesis round of Conversation 17.

Direct effect on v8: None — it's a methodology document, not a specification. However, the five heuristics should be referenced in the companion as the decision framework that drives boundary decisions, analogous to how the Polymorphic Pattern Policy is referenced.

Disposition: Already curated as reference-domain-integration-boundary-methodology.md. Reference from companion; no DDL changes.


Artifact #92 — Integration Architecture (Doc 21)

Role: The primary architectural specification. Proposes 15 decisions, 4 new core tables, 2 new operational tables, 5 integration schemas, and a publiccore rename.

This is the artifact that requires careful decomposition. See sections 25 below.


2. What #92 Gets Right About v8

These claims in #92 are accurate and verified against the reference:

Claim Verified
48 tables in the core model Yes (48 tables in v8 index)
UUIDv7 primary keys Yes (Structural Policy)
ON DELETE RESTRICT as universal default Yes (Structural Policy)
PII prohibition in JSONB (Decision 34) Yes
Audit tables with monthly RANGE partitioning Yes (4 audit tables + audit_outbox). Note: #92 cited a separate audit schema as precedent; this was reversed in v9 — audit tables live in core alongside all other domain tables.
actor_type = 'system' and actor_credential_type in audit_logs Yes
payments.status includes refunded but no refunds entity Yes — payments has refunded and partially_refunded status + refunded_at timestamp, no refunds table
billing_accounts are separate from organizations Yes (org_id FK, GCP orthogonality principle)
entitlements belong to resource_pools, sourced from pool_provisions Yes
Products → product_entitlement_rules → entitlements materialization chain Yes
payment_method_type and payment_method_details on payments but no payment_methods table Yes — payments has method info inline

3. What #92 Gets Wrong or Assumes

3a. Multi-tenant provider configuration (HALLUCINATED ARCHITECTURE)

The claim: #92's provider_configs template has organization_id as the scoping key, implying each organization has its own Stripe account, Nextcloud instance, etc. This appears throughout: stripe.provider_configs, nextcloud.provider_configs, tax.provider_configs, polar.provider_configs — all keyed by (organization_id, environment).

The reality: The v8 model is a single cooperative platform, not a multi-tenant SaaS where each tenant brings their own payment processor. The cooperative itself operates Stripe accounts, provisions Nextcloud instances, and manages tax computation. Individual organizations (which may be personal orgs of individual members) do not configure their own Stripe API keys.

Impact: The provider_configs pattern as designed is significantly over-engineered for our use case. The cooperative likely has:

  • One Stripe account (possibly one live + one test)
  • One Polar account
  • One Nextcloud cluster
  • One tax provider

A platform-level configuration model (perhaps a single integration_configs table, or even application configuration) is more appropriate than per-org provider configs.

Caveat: The model does support cross-org billing via pool_provisions.billing_account_id being unconstrained. It's conceivable that federated cooperatives could bring their own payment processors. But this is Issue 7 territory (Cross-Organization Billing, currently deferred), not something to bake into v9.

3b. core.resource_provisions overlaps with pool_provisions

The claim: #92 proposes a new core.resource_provisions table tracking infrastructure provisioning state per entitlement.

The reality: The v8 model already has pool_provisions which tracks the lifecycle of provisions (active, suspended, ended) and the entitlements table which tracks whether capabilities are enabled. The proposed resource_provisions table introduces a third provisioning concept (requested, provisioning, active, deprovisioning, deprovisioned, failed) that sits between entitlements and external infrastructure.

Assessment: This is arguably a valid addition — pool_provisions tracks the financial provision ("your subscription pays for this"), entitlements tracks the logical capability ("you have access to feature X"), and resource_provisions would track the physical infrastructure state ("the Nextcloud instance is being created"). These are three genuinely different things. But the name collision with pool_provisions needs resolution, and the relationship to entitlements needs careful thought — does every entitlement need a resource provision, or only entitlements with an infrastructure footprint?

3c. The publiccore schema rename

The claim: Move all 48 current tables from public to a core schema.

Assessment: This is a significant operational decision that affects every query, every migration, every ORM configuration, every test. The conceptual benefit is real (making the domain boundary explicit), but the implementation cost is nontrivial. This is a decision that should be made when there's an application layer, not during data model design. The reference model currently uses unqualified table names — it doesn't assume any particular schema.

3d. TypeScript stack assumption

The claim: Section 2.6 states "For this project's TypeScript stack, Drizzle provides the best experience."

The reality: No stack has been chosen. The data model is PostgreSQL DDL. The reference model and companion are technology-neutral.

3e. Source-of-truth conventions per entity class

The claim: Section 4.7 declares specific sync directions (e.g., "Products: core → provider", "Invoices: provider → core", "Payments: provider → core").

Assessment: These are reasonable starting positions but are implementation decisions that depend on the actual application architecture. For example, whether invoices are "provider authoritative" depends on whether the platform generates its own invoices (it does — the core has a complete invoice model) or only mirrors provider invoices. The current model's invoices table is richer than a mere projection of Stripe invoices. This belongs in implementation documentation, not in the data model reference.


4. Proposed Changeset: What Belongs in v9

Category A: Core schema additions (high confidence)

These new tables answer genuine business questions that v8 cannot currently answer without external API calls. They follow directly from the self-sufficiency principle and are validated by #88's survey of production systems.

Table Business question answered Notes
payment_methods "Does this billing account have a payment method on file?" FK to billing_accounts. Provider-agnostic: type, brand, last4, expiry, status, is_default.
refunds "Was this payment refunded? How much?" Child of payments. Amount, currency, reason, status. Replaces the status-flag-only approach.
disputes "Is this payment under dispute?" Child of payments. Amount, reason, status, evidence deadline.

Open question — resource_provisions: Provisionally include, but rename to avoid collision with pool_provisions, and scope narrowly: only entitlements that represent provisionable infrastructure (not all entitlements). Perhaps infrastructure_provisions or provisioned_resources. Requires further discussion.

Category B: Operational/cross-cutting tables (medium confidence)

Table Purpose Notes
webhook_events Inbound webhook idempotency and processing state Monthly RANGE partitioned, matching audit log pattern. Shared across providers.
integration_outbox Transactional outbox for outbound integration triggers Ensures atomicity between domain state changes and integration side effects.

Assessment: These are operationally essential for any integration implementation, and they live in the core schema (or a shared operational schema). However, they're closer to application infrastructure than domain modeling. The audit_outbox in v8 sets precedent — it's the same pattern applied to audit delivery. Whether these belong in the reference model or in a separate operational specification is a judgment call.

Category C: Companion decisions (high confidence)

New decisions to record regardless of which tables are added:

Decision Summary
Integration namespace strategy Schema-per-provider for integration data; integration schemas reference core via FKs; integration schemas do not reference each other
Integration table conventions UUIDv7 PKs on integration tables; external IDs stored as TEXT with length CHECK; no PII in JSONB on integration tables (extending Decision 34)
External ID storage External IDs are never PKs; stored as indexed TEXT columns with UNIQUE constraints per provider config
Boundary methodology Five heuristics (from #91) adopted as the standing framework for domain-vs-integration decisions

Category D: Integration schema specifications (OUT OF SCOPE for v9)

The full DDL for stripe.*, polar.*, nextcloud.*, tax.*, notify.* schemas is not appropriate for the data model reference. Reasons:

  1. The reference model documents the core domain — the 48+ tables that model business semantics independent of any provider.
  2. Integration schemas are provider-specific implementation details. They should be designed and documented when each integration is actually built.
  3. #92's integration schemas make assumptions about specific Stripe API mappings, Polar order structures, and Nextcloud provisioning flows that haven't been validated against real API contracts.
  4. The multi-tenant provider_configs pattern needs redesign for our single-cooperative architecture.

Recommended: The patterns (provider config → entity mappings → provider-specific objects) belong in the companion as architectural guidance. The specific schemas belong in implementation documentation when each integration is built.

Category E: Reference documents (high confidence)

Document Role in companion
Doc 21 (artifact #92) Referenced as the exploratory document that informed integration decisions
reference-domain-integration-boundary-methodology.md (artifact #91) Referenced as the standing methodology for boundary decisions
reference-postgresql-schemas-production.md (artifact #89) Referenced as evidence for schema-per-concern viability

5. What NOT to Do

  1. Do not rename public to core in the reference model. The reference model is schema-neutral. Schema naming is an implementation decision.
  2. Do not include integration schema DDL in the reference model. The reference tracks core domain tables only.
  3. Do not include source-of-truth conventions per entity class. These are implementation decisions that depend on application architecture.
  4. Do not include per-org provider_configs. The multi-tenant provider config pattern needs rethinking for a single-cooperative deployment model.
  5. Do not add the TypeScript/Drizzle ORM recommendations. No stack has been chosen.
  6. Do not add access control DDL (GRANT/ROLE statements). These are deployment-time concerns, not data model concerns.

6. Summary of Changes Applied

v8 → v9 (applied 2026-02-28):

  • +3 core tables: payment_methods, refunds, disputes (Layer 4: Billing & Value)
  • +2 integration infrastructure tables: webhook_events (partitioned), integration_outbox (new section)
  • +5 companion decisions: 85 (core schema namespace), 86 (schema-per-provider), 87 (core billing entities), 88 (integration infrastructure), 89 (external ID conventions)
  • Issue 10 closed as fully resolved — namespace strategy decided, core additions made, individual integration schemas explicitly deferred to implementation
  • Schema rename: Domain tables now live in core schema (noted in new Schema Organization section)
  • Issue 18 opened: Billing Layer Strategic Assessment (build-vs-buy analysis)
  • Issue 19 opened: Resource Pool Architecture Documentation (prerequisite before any Layer 3 changes)
  • Reference Documents section added to companion (boundary methodology, PostgreSQL schemas guide, production billing patterns survey)

Deferred:

  • Resource provisioning state table — blocked on Issue 19 (Resource Pool Architecture Documentation)
  • Individual integration schema DDL (stripe, polar, nextcloud, tax, notify) — designed when each integration is built
  • Per-org provider_configs — requires rethinking for single-cooperative model (also depends on Issue 18)

Table count: 48 → 53