Introduce `docs/models/` with the catalog index and its first card (product-catalog), recording invariants, dimensions, and drift traps per the Decision 141 documentation division. Add the model-cards openspec change (proposal, design, spec, tasks) and sync the design docs to v15: product kind taxonomy dissolved (Decisions 134–139), Decisions 140–141 ratified, and product/pricing helpers updated. The catalog is the single descriptive home for as-built models.
208 lines
22 KiB
Markdown
208 lines
22 KiB
Markdown
# Glossary
|
||
|
||
**Purpose:** Cross-cutting vocabulary for the data model. This is the authoritative source for durable names; modules and documents should cite terms by their definition here rather than re-defining them in prose.
|
||
|
||
**Related:**
|
||
- [`CONVENTIONS.md`](CONVENTIONS.md) — structural conventions for module-level documentation
|
||
- [`../documents/doc-32-plan-management-framing.md`](../documents/doc-32-plan-management-framing.md) — the investigation that produced much of the plan-related vocabulary
|
||
- [`../documents/doc-28-grants-module-placement.md`](../documents/doc-28-grants-module-placement.md) — the definiteness test, which governs module placement for new entities
|
||
|
||
**Scope:** This glossary prioritizes terms that span modules or whose meaning is load-bearing. Single-module implementation details belong in the module's own reference.
|
||
|
||
---
|
||
|
||
## 1. Commercial catalog
|
||
|
||
### product
|
||
|
||
A commercial instrument — something the cooperative sells. Carries name, presentation-layer grouping (`display_category` — a label with no behavioral weight and no structural claim, successor to the retired `product_type`; Decision 136), visibility flags, and a reference to an entitlement set specifying the capabilities conferred. Table: `billing.products`.
|
||
|
||
A product carries no unitary *kind*: the questions the retired kind taxonomy conflated are answered per consumer from structure (Decision 134) — position from `plan_ladder_tiers` (via `product_conferral_shapes`), billing and consumption shape per price from `billing.prices`, presentation from `display_category`, diagnostics from `billing.product_shape`.
|
||
|
||
A product is not a price (commercial terms attach separately, via `billing.prices`), not an entitlement set (capability specification is independently variable), and not a subscription (the recurring vehicle that pays for the product over time).
|
||
|
||
### price
|
||
|
||
Commercial terms attached to a product — amount, currency, billing schedule, trial period. Table: `billing.prices`. A product may carry multiple prices (e.g., monthly and annual pricing for identical capabilities).
|
||
|
||
### plan
|
||
|
||
A product with `plan_ladder_tiers` rows — a **position-bearing** product (see §3). Ladder membership is the sole, structural declaration of plan-ness: no label participates. (The former `product_type = 'plan'` label, its `plan_ladder_id` denormalization, and the Amendment-#1 biconditional CHECK were all retired — Doc 31 Amendment #3, completed by Decision 136.) No product is a plan without a ladder.
|
||
|
||
A plan is not a subscription — see §4 *Disambiguations*.
|
||
|
||
### plan ladder *(canonical)*, ladder *(prose shorthand)*
|
||
|
||
A named set of plan products declared mutually exclusive within a commercial domain. Tables: `billing.plan_ladders` (the named set), `billing.plan_ladder_tiers` (the ordered product memberships).
|
||
|
||
A plan ladder is a *catalog-shape* construct: it declares how products relate to one another commercially, not what capabilities they confer. Capability composition lives in `entitlement_sets`; the ladder says only "these plans are alternatives to each other."
|
||
|
||
### tier
|
||
|
||
A plan's ordered position within a ladder — the rung a product occupies. Expressed as a row in `billing.plan_ladder_tiers` with a `rank` column.
|
||
|
||
*Note — potential collision.* Stripe uses "tier" in `billing_scheme = 'tiered'` to refer to volume-bracketed pricing within a single price (e.g., first 100 units at $1, next 900 at $0.90). That is a different concept: Stripe tiers slice a *price*, while our tiers position a *product* within a *ladder*. Context should disambiguate; when in doubt, prefer "ladder tier" (our sense) and "pricing tier" (Stripe's sense).
|
||
|
||
*"Rung" is an acceptable synonym* when the ladder metaphor is active and precision matters (e.g., "the bundle occupies a rung on each ladder it participates in"). Prose should default to "tier"; "rung" is reserved for contexts where the distinction from pricing tiers is load-bearing.
|
||
|
||
### bundle
|
||
|
||
A plan product that occupies tiers on more than one ladder simultaneously. Not a separate `display_category`; bundle-ness is the emergent property of ladder multi-membership.
|
||
|
||
A bundle activation produces one `pool_provisions` row (the commercial fact) and N `pool_provision_ladders` junction rows (the catalog-shape facts, one per ladder the bundle occupies). The exclusion constraint on the junction guarantees that holding the bundle blocks any standalone tier on the ladders it covers.
|
||
|
||
### wrap product
|
||
|
||
An internal product (`is_public = FALSE`, minted directly as `published`, deliberately priceless under the amended publication gate) whose sole purpose is to carry an ad hoc entitlement set through the conferral toll (Decision 137): capability that enters a pool must pass through the catalog, where conferral can interrogate its shape. It is not a commercial artifact — it claims no ladder, no price, and no storefront presence; it records precisely what was conferred, and nothing else.
|
||
|
||
---
|
||
|
||
## 2. Commercial vehicles
|
||
|
||
### subscription
|
||
|
||
A recurring commercial agreement — a stateful binding between a billing account and one or more priced products, generating invoices over time. Table: `billing.subscriptions`.
|
||
|
||
A subscription is a *vehicle*, not a *catalog position*. A subscription may carry a plan line-item and zero or more addon line-items; the plan is one item of the subscription, not the subscription itself. Two subscriptions carrying the same plan are two distinct commercial agreements that happen to reference the same catalog entry.
|
||
|
||
The `status` column admits `'trialing'` as a first-class state alongside `'active'`, `'paused'`, `'cancelled'`, and related lifecycle values. Trial specification is price-level (`prices.trial_period_days`) and trial-instance state is subscription-level (`trial_start`, `trial_end`); this separation — specification on the price, occurrence on the vehicle — is a deliberate architectural choice that preserves the expressiveness of monthly-with-trial vs. monthly-without-trial on identical products, documented in Document 34.
|
||
|
||
### subscription item
|
||
|
||
A line item within a subscription binding it to (price, quantity). Table: `billing.subscription_items`. The product identity of the line item is reached via the price's `product_id`.
|
||
|
||
### subscription change
|
||
|
||
An audit record of a mutation to a subscription, carrying `effective_at` for scheduled transitions. Table: `billing.subscription_changes`. The general audit log for all subscription lifecycle events; not specific to plan transitions.
|
||
|
||
**Canonicalization.** `billing.subscription_changes` is canonical for *commercial mutations of a subscription* — status transitions (`trialing`, `past_due`, `canceled`, and peers), period boundaries, amount changes. Plan-position history of a pool — which ladder tier it held, when, and under whose authority — is not this table's concern; that history is carried by `entitlements.pool_provision_transitions` (see §3). Subscription-driven ladder attachments are recorded in both tables, intentionally, because they answer different questions; neither is a substitute for the other.
|
||
|
||
### purchase
|
||
|
||
A completed one-time transaction — a billing account pays once and receives access. Table: `billing.purchases`. No recurring lifecycle, no state machine beyond completion and potential refund.
|
||
|
||
A purchase may reference a plan product (a "lifetime license"); the resulting provision is indistinguishable downstream from a subscription-sourced provision.
|
||
|
||
### grant
|
||
|
||
Non-commercial administrative or promotional access. Table: `entitlements.grants`. Conferred by decree; no money changes hands; no invoice generated.
|
||
|
||
A grant names a **product** (`product_id` NOT NULL; no set arm exists — Decision 137). Ad hoc entitlement sets are conferred by minting a *wrap product* (§1). The grant is immutable ledger: no `'superseded'` status exists, and delivery state is answered by joining provisions, never read off the grant row. `grant_reason = 'evaluation'` names operator-conferred time-boxed assessment access — deliberately not called "trial," which is vehicle-owned vocabulary (Decision 119 as amended by Decision 138). See `../documents/doc-28-grants-module-placement.md` for the definiteness-test argument placing grants in entitlements — unaffected by the set-arm retirement.
|
||
|
||
---
|
||
|
||
## 3. Capability layer
|
||
|
||
### resource pool *(canonical)*, pool *(prose shorthand)*
|
||
|
||
A capability container that bridges billing and resources. Table: `entitlements.resource_pools`. Scoped to an organization via billing account. `pool_type` distinguishes `default` (auto-created per billing account, attached to all workspaces in the org), `shared` (explicit multi-workspace), and `dedicated` (explicit single-workspace).
|
||
|
||
The default pool is the org-scoped capability container by convention — see Constraint 7 (default-pool-as-org-scope).
|
||
|
||
### pool provision
|
||
|
||
A record that a pool has been provisioned from a specific source. The uniform convergence point for the three access mechanisms (subscription, purchase, grant). Table: `entitlements.pool_provisions`. Carries two creation-time denormalizations, both resolved at conferral: `product_id` — the fact the position machinery and the reconcile diff dispatch on (Decision 137) — and `entitlement_set_id`.
|
||
|
||
The provision is the commercial-fact record of "this source activated this capability in this pool at this time." The catalog-shape fact of "that activation occupies these ladder rungs" is carried separately in `pool_provision_ladders`.
|
||
|
||
### pool provision ladder *(junction)*
|
||
|
||
A row linking a provision to a ladder tier. Table: `entitlements.pool_provision_ladders`. Carries denormalized `pool_id`, `status`, `activated_at`, `ended_at` (kept in sync with the parent provision via trigger) to support the GiST exclusion constraint enforcing tier exclusivity per pool.
|
||
|
||
### pool provision transition
|
||
|
||
An audit record of a ladder-position change affecting a pool provision — an initiation, upgrade, downgrade, transfer, or end. Table: `entitlements.pool_provision_transitions`. Carries actor attribution (`actor_type`, `actor_id`), the ladder context (`plan_ladder_id`), the occupancy episode the claim is made against (`provision_ladder_id`, referential via the junction's surrogate key — Decision 138), the rank delta (`from_rank`, `to_rank`), and the distinction between when the change took effect (`effective_at`) and when the row was recorded (`created_at`).
|
||
|
||
**transfer** *(transition type, Decision 138)* — records that a position-bearing product's occupancy of a ladder passed from one source to another without changing rank: candidate and superseded incumbent are the same product — hence the same rank, by `UNIQUE (plan_ladder_id, rank)` — delivered by different source rows (the default-then-subscribe sequence; extend-as-replace). Distinct from `upgrade`/`downgrade`, whose CHECK requires strict rank inequality; naming the rank-equal case separately, rather than stretching either term to cover it, is the semantic-coherence constraint applied to the machinery's own vocabulary.
|
||
|
||
**Canonicalization.** `pool_provision_transitions` is canonical for *plan-position history of a pool*: what tier a pool held, when, who changed it, why. Commercial mutations of the subscription that may have driven a given transition — status changes, period boundaries, amount changes — remain the canonical concern of `billing.subscription_changes` (see §2). Subscription-driven ladder attachments are recorded in both tables, intentionally, because they answer different questions: an operator audit UI querying "how did this org arrive at Standard?" reads `pool_provision_transitions`; a billing audit UI querying "what happened to this subscription?" reads `subscription_changes`.
|
||
|
||
**Scope.** `transition_type` is strictly scoped to ladder-position changes: `initiate`, `upgrade`, `downgrade`, `transfer`, `end` — position-scoped over both `plan_ladder_id` and `provision_ladder_id` going forward (Decision 138; the former off-ladder branch is unwritable, off-ladder lifecycle being answered by the sources). Suspension and resumption are *not* recorded here — those lifecycle events are carried by `pool_provisions.status` and, for subscription-driven suspensions, `subscription_changes`. This scope discipline keeps the table's semantics narrow and its eventual absorption into a generic `audit.log` module mechanical rather than semantic (the columns map directly onto a standard audit projection — see the model reference).
|
||
|
||
### conferral
|
||
|
||
The source-agnostic act of attaching a product's capability to a pool: the creation of a `pool_provision` (and, for position-bearing products, its junction rows) from a subscription, purchase, or grant. One primitive performs every conferral — the enclosed five-member function family headed by `entitlements.confer` (Decision 137). Not a synonym for "grant" (one of three sources) nor for "enrollment" (a derived position fact).
|
||
|
||
### conferral shape
|
||
|
||
The projection of a product that conferral reads: entitlement set, lifecycle status, ladder count, identities, and ranks. View: `billing.product_conferral_shapes`. Reserved for that projection specifically; the diagnostics-side gate is *publication readiness*, read from `billing.product_shape` — conflating the two names would blur the term at its second use.
|
||
|
||
### position-bearing *(adjective)*
|
||
|
||
Of a conferral or provision: occupying one or more ladder positions. Of a product: having `plan_ladder_tiers` rows — being a plan, read structurally. Names the phenomenon the retired label vocabulary gestured at with "on-ladder"/"plan-flow."
|
||
|
||
### entitlement set
|
||
|
||
The declarative specification of what capabilities a provision confers. Table: `entitlements.entitlement_sets`. Multiple products may reference the same set (capability-commercial separation, Decision 112).
|
||
|
||
### entitlement set rule
|
||
|
||
A single capability declaration within an entitlement set — a boolean flag, a numeric limit, a renewable quota, or a credit grant. Table: `entitlements.entitlement_set_rules`. Single-table inheritance via `rule_type` (Decision 107).
|
||
|
||
---
|
||
|
||
## 4. Derived relations
|
||
|
||
### enrollment
|
||
|
||
**A prose term with no direct structural referent.** An enrollment is the derived fact that an entity currently holds a tier on a ladder. It is not a table, not a column, not an entity. The canonical question "what plan is this organization on?" is answered by a constrained query over `entitlements.pool_provision_ladders`, whose cardinality per (pool, ladder) pair is guaranteed by an exclusion constraint.
|
||
|
||
The term is retained for prose convenience — "the org is enrolled in the Pro plan" is clearer than "the org's default pool holds an active provision occupying the Pro tier of the Nextcloud ladder." But documentation and code should treat "enrollment" as shorthand for the derivation, never as an entity to be stored.
|
||
|
||
---
|
||
|
||
## 5. Disambiguations
|
||
|
||
The following distinctions are load-bearing and recur throughout the data model.
|
||
|
||
### 5.1 Plan vs. subscription
|
||
|
||
A plan is a *catalog position* — a product on a ladder. A subscription is a *commercial vehicle* — an ongoing agreement that pays for things. The two are colloquially conflated because the simplest SaaS arrangement (one subscription carrying one plan line-item) makes them coextensive. They are categorially distinct:
|
||
|
||
- A subscription may carry a plan item plus several addon items; the plan is one item among many.
|
||
- A plan may be reached by a purchase (lifetime license) or a grant (board-conferred access) with no subscription involved.
|
||
- A subscription may exist without any plan (pure metered usage).
|
||
|
||
### 5.2 Ladder tier vs. pricing tier
|
||
|
||
See the `tier` entry in §1. Briefly: *our* tier is a ladder rung (a product's ordered position); Stripe's `billing_scheme = 'tiered'` tier is a volume bracket within a single price. Different concepts.
|
||
|
||
### 5.3 Product vs. entitlement set
|
||
|
||
A product is the *commercial instrument* (how something is sold). An entitlement set is the *capability specification* (what the sale confers). Multiple products may reference the same entitlement set — monthly and annual pricing of identical capabilities are two products sharing one set. The separation is by design (Decision 112) and allows pricing, packaging, and capability specification to vary independently.
|
||
|
||
### 5.4 Provision vs. enrollment
|
||
|
||
A provision is a *commercial fact* ("this source activated something in this pool at time T"). An enrollment is a *catalog-shape derivation* ("that activation occupies a rung on this ladder"). One provision can give rise to multiple enrollments (a bundle activation). Enrollments are derived from provisions plus ladder membership; they are not independently authored.
|
||
|
||
### 5.5 Default pool vs. organization
|
||
|
||
The default pool is auto-created per billing account and attached to all workspaces in the owning organization. In single-billing-account organizations, the default pool is operationally equivalent to the organization as a scope. In multi-billing-account organizations, each billing account has its own default pool — a structural recognition that the organization may hold distinct commercial arrangements per billing account (Constraint 7).
|
||
|
||
### 5.6 Investigation stage vs. roadmap phase
|
||
|
||
*Phase* and *stage* are not interchangeable. A **stage** is a step in the Document 33 architectural-investigation pipeline (Stage 1 surfacing → Stage 2 research → Stage 3 brief → Stage 4 recommendation → Stage 5 framing memo) — the repeatable procedure by which an issue is resolved. A **phase** is an era in the evolution of a *roadmap*, and in any cross-document context is namespaced to the roadmap it belongs to: **Compliance Phase** (Decision 38: US hooks → pre-EU GDPR → cooperative-equity integration), **Billing-rollout Phase** (Decision 90: Stripe → cooperative maturation → execution independence), **Cooperative-maturity Phase** (Foundation → Patronage → Governance → Full Cooperative), and **Schema-build Phase** (Identity & Access → Resource & Billing → Cross-Cutting). Within a document already scoped to a single roadmap the bare ordinal ("Phase 2") is acceptable; the namespacing rule governs prose that spans roadmaps. The distinction keeps a procedure's repeatable steps lexically separate from a roadmap's evolutionary eras.
|
||
|
||
### 5.7 Module vs. seam
|
||
|
||
A **module** is a *physical* boundary: a PostgreSQL schema (Decision 113) with its own writer role and schema-qualified cross-module foreign keys (Decision 114), occupying a position in the dependency chain (`README.md`). A **seam** is a *conceptual* boundary: a coherent sub-domain — answering its own governing question, with its own vocabulary and lifecycle — that is named and reasoned about in isolation **without** being given a separate schema. A seam is a module-in-potential: the conceptual distinction recognized, the physical wall withheld. (By software analogy, a seam is a well-bounded package inside a monolith; a module is the extracted service.)
|
||
|
||
The two are easily conflated because the schema-per-module policy makes *module = schema*, which silently fuses two separable questions: **is this a distinct domain?** (conceptual) and **should it occupy its own schema?** (physical). Affirming the first does not entail the second. The seam is the construct that holds them apart — it lets the model acknowledge a distinct domain while declining the structural cost of a split when that domain's tables remain referentially fused to their host module. The governing-question test (Document 28) detects the seam; the structural-dependency test (Documents 28–29, Decision 107 — extract only when a sub-domain's referential constituency lies *entirely outside* its host) decides whether the seam should also become a module.
|
||
|
||
The worked instance is the **subscription seam** (Document 38, Decision 127): `subscriptions`, `subscription_items`, `subscription_changes`, and `subscription_scheduled_changes` answer a question distinct from billing's ("what agreement is in force?" vs. "who owes what?"), yet are referenced by billing's financial core (`invoices`, `invoice_line_items`, `discounts`, `credit_transactions`) densely enough that extraction would push the invoicing hot path across a schema boundary. They are recognized as a seam *within* `billing` — the conceptual boundary honored, the physical one deferred to the triggers in Document 38 §9.
|
||
|
||
**Addendum (Decision 140, 2026-08-20).** The definition above records the Decision-113-era usage from which the seam construct emerged. Decision 140 superseded the physical claim of Decision 113: the physical layout is now one `core` schema plus `domains`, `integration`, and per-provider schemas, and the seven domain modules are themselves seams within that coarser topology. *Module* retains its documentary sense — a top-level conceptual domain with its own governing question, vocabulary, and position in the dependency chain — while *schema* alone names a physical boundary. The detection and promotion tests above are unaffected: the governing-question test still finds conceptual boundaries, and the structural-dependency test still decides whether any boundary warrants a physical wall.
|
||
|
||
---
|
||
|
||
## 6. Deprecated or superseded terms
|
||
|
||
Terms previously used in prose or earlier document revisions that should no longer appear in new writing. If encountered in existing documents, they should be revised at next touch.
|
||
|
||
| Deprecated | Use instead | Reason |
|
||
|---|---|---|
|
||
| *product kind* (as a unitary product attribute) | the per-consumer questions of Doc 41 §2.2 — position (`plan_ladder_tiers` / `product_conferral_shapes`), billing and consumption shape (`prices` per-price), presentation (`display_category`), diagnostics (`product_shape`) | The single-axis taxonomy is dissolved (Decision 134): no one answer served every consumer, and a label purporting to describe structure was the divergence class behind the finding-#25 incident family. |
|
||
| `product_type` | `display_category` | Renamed and demoted (Decision 136): the successor is presentation-owned and carries no structural claim; structural questions are read from structure. |
|
||
|
||
*This section will accrete as durable vocabulary supersedes working terms.*
|