Spec-side half of cloud#1216, filed by the repo:cloud seat per the maintainer's ruling there (2026-08-10, directed in session session_01BPWqbmEFU8gJepBJTHESXd), verbatim:
> Direction hinges on one measurement: does any reader branch on the plan values (solo / team / business) — in framework code, console, or cloud itself beyond writing them? […] All readers treat it opaquely → spec re-types the field as an opaque plan identifier whose vocabulary is control-plane config (documented as such in the describe), and cloud owns the value set without touching the schema.
The measurement has been run (2026-08-11, cloud seat, session session_01PPDkETJ37ntVTFUZWX5FQg). Result: the opaque arm. No reader outside the cloud distribution branches on plan values.
Measurement (all at origin/main of each repo; framework also valid at cloud's pin 62b6a2fb)
Framework — zero TenantPlan consumers outside spec, and the one plan-adjacent seam is deliberately opaque:
git grep -l "TenantPlan" origin/main -- 'packages/**/*.ts' | grep -v packages/spec/ → empty (positive control: the same grep unfiltered hits the spec files).
packages/cloud-connection/src/runtime-config-plugin.ts is plan-agnostic by design: the header says which keys exist and "which billing plan unlocks them is a distribution concern" (:25), the resolver "receives an opaque environment token (the cloud distribution passes the plan string)" (:30), and the only touch is resolvePlanFeatures?: (plan: string | undefined) (:135) called as features = featuresFor(resolved.plan, features) (:272-273) — a pass-through into a distribution-supplied callback, typed string | undefined, no value branching.
Console (objectui) — display-only:
- The only non-noise reader is
packages/app-shell/src/environment/entitlements.ts:68-71 (planPhrase): interpolates the plan string into upgrade-dialog copy, branching only on falsy/'free' — a value both vocabularies share. No branch on solo/team/business anywhere in src/** / packages/** (checked with the noise classes excluded: flow-node 'team' kinds, sys_team, business-unit).
Cloud itself — the branching readers are the distribution's own policy modules, i.e. exactly "control-plane config":
packages/objectos-runtime/src/plan-entitlements.ts (isFreePlan, planAllowsAiStudio, planToAiModel, planAllowsScim, aiDailyQuotaUnitsForPlan, planAllowsCustomDomain) and its documented mirror packages/service-cloud/src/plan-entitlements.ts — consumed by cloud-runtime-config-plugin.ts:43, which plugs into the framework's opaque seam above. The ADR-0005 comments in those files state the policy is cloud-owned.
Re-check commands (per premise-line discipline):
git grep -l "TenantPlan" origin/main -- 'packages/**/*.ts' | grep -v packages/spec/ # framework, expect empty
git grep -nw "plan" origin/main -- packages/cloud-connection/src/runtime-config-plugin.ts
# objectui: git grep -nE "'(solo|team|business)'" origin/main -- 'packages/**/*.ts*' (exclude team_member/flow-kind noise)
Ask (spec lane)
Per the ruling's opaque arm:
- Replace the closed
TenantPlanSchema enum (packages/spec/src/cloud/tenant.zod.ts — free/starter/pro/enterprise/custom) with an opaque plan identifier (string), with the describe stating the vocabulary is control-plane config owned by the cloud distribution, not protocol.
- One sentence worth keeping in the describe: the shared convention that empty/unknown ⇒ free tier (
isFreePlan in both cloud mirrors normalizes that way, and the console's only branch is 'free').
- This is a widening — the enum's current values are a strict subset of "any string" — and nothing was found that parses the enum at runtime today (cloud#1216 established the row is validated by the cloud-declared object, not spec, on this path). Version/strictness handling per the spec seat's own rules.
Downstream (stays in cloud, blocked on this card): cloud#1216 — drop the hand-written BillingPlan union's divergence from spec once the opaque type lands (the #1027-style consolidation this divergence currently blocks).
(Unlabeled on purpose — packages/spec has one owner; routing is the triage seat's.)
Spec-side half of cloud#1216, filed by the repo:cloud seat per the maintainer's ruling there (2026-08-10, directed in session
session_01BPWqbmEFU8gJepBJTHESXd), verbatim:> Direction hinges on one measurement: does any reader branch on the plan values (
solo/team/business) — in framework code, console, or cloud itself beyond writing them? […] All readers treat it opaquely → spec re-types the field as an opaque plan identifier whose vocabulary is control-plane config (documented as such in the describe), and cloud owns the value set without touching the schema.The measurement has been run (2026-08-11, cloud seat, session
session_01PPDkETJ37ntVTFUZWX5FQg). Result: the opaque arm. No reader outside the cloud distribution branches on plan values.Measurement (all at
origin/mainof each repo; framework also valid at cloud's pin62b6a2fb)Framework — zero
TenantPlanconsumers outside spec, and the one plan-adjacent seam is deliberately opaque:git grep -l "TenantPlan" origin/main -- 'packages/**/*.ts' | grep -v packages/spec/→ empty (positive control: the same grep unfiltered hits the spec files).packages/cloud-connection/src/runtime-config-plugin.tsis plan-agnostic by design: the header says which keys exist and "which billing plan unlocks them is a distribution concern" (:25), the resolver "receives an opaque environment token (the cloud distribution passes the plan string)" (:30), and the only touch isresolvePlanFeatures?: (plan: string | undefined)(:135) called asfeatures = featuresFor(resolved.plan, features)(:272-273) — a pass-through into a distribution-supplied callback, typedstring | undefined, no value branching.Console (objectui) — display-only:
packages/app-shell/src/environment/entitlements.ts:68-71(planPhrase): interpolates the plan string into upgrade-dialog copy, branching only on falsy/'free'— a value both vocabularies share. No branch onsolo/team/businessanywhere insrc/**/packages/**(checked with the noise classes excluded: flow-node'team'kinds,sys_team, business-unit).Cloud itself — the branching readers are the distribution's own policy modules, i.e. exactly "control-plane config":
packages/objectos-runtime/src/plan-entitlements.ts(isFreePlan,planAllowsAiStudio,planToAiModel,planAllowsScim,aiDailyQuotaUnitsForPlan,planAllowsCustomDomain) and its documented mirrorpackages/service-cloud/src/plan-entitlements.ts— consumed bycloud-runtime-config-plugin.ts:43, which plugs into the framework's opaque seam above. The ADR-0005 comments in those files state the policy is cloud-owned.Re-check commands (per premise-line discipline):
Ask (spec lane)
Per the ruling's opaque arm:
TenantPlanSchemaenum (packages/spec/src/cloud/tenant.zod.ts—free/starter/pro/enterprise/custom) with an opaque plan identifier (string), with the describe stating the vocabulary is control-plane config owned by the cloud distribution, not protocol.isFreePlanin both cloud mirrors normalizes that way, and the console's only branch is'free').Downstream (stays in cloud, blocked on this card): cloud#1216 — drop the hand-written
BillingPlanunion's divergence from spec once the opaque type lands (the #1027-style consolidation this divergence currently blocks).(Unlabeled on purpose —
packages/spechas one owner; routing is the triage seat's.)