Uh oh!
There was an error while loading. Please reload this page.
spec: re-type TenantPlan / sys_environment.plan as an opaque plan identifier - #7655
Conversation
…ntifier
Widens `TenantPlanSchema` from a closed 5-value enum (free/starter/pro/
enterprise/custom) to an opaque string. The vocabulary is control-plane
config owned by the cloud distribution, not protocol -- the schema's
.describe() now states that ownership and the shared empty/unknown => free
tier convention.
Executes the OPAQUE arm of the maintainer ruling on cloud#1216
(2026-08-10), whose one open condition -- "does any reader branch on plan
values?" -- was measured (cloud seat, 2026-08-11) and resolved: no reader
outside the cloud distribution branches on plan values. Re-verified against
origin/main before implementing (empty framework-consumer grep, positive
control hits spec files).
Pure widening: every value the old enum accepted is still accepted, no
runtime parser depended on the closed set. The pre-existing pin test
asserting the closed enum is flipped, not deleted, to assert the new
contract's substance (opaque acceptance including a cloud-vocabulary value
the old enum rejected, continued rejection of non-string shapes, and the
describe() carrying the ownership + convention statements).
Regenerates content/docs/references/cloud/{tenant,environment}.mdx via
`pnpm --filter @objectstack/spec gen:docs` (plan field type: Enum<...> ->
string).
Fixes#7513
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JY2Q5Xto1u8YHADgrZDTnkThe latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
Pre-sync for the merge queue (PM instruction on #7513): the os-regen merge driver exits 0 with zero conflict markers while silently able to drop one side, so force every merge=os-regen path (.gitattributes) to origin/main's exact content rather than trust the driver's merge output. Only content/docs/references/cloud/{tenant,environment}.mdx differed (this branch's own regenerated docs) -- everything else already matched origin/main byte-for-byte. The full gen pipeline regenerates the two touched docs pages from source next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY2Q5Xto1u8YHADgrZDTnk
Full gen pipeline run post-merge (pnpm --filter @objectstack/spec build, then gen:docs) reproduces this branch's tenant/environment reference-doc changes from source, confirming check:generated is clean against the merged tree (all 13 artifacts up to date). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY2Q5Xto1u8YHADgrZDTnk
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7513
What
Widens
TenantPlanSchema/TenantPlan(packages/spec/src/cloud/tenant.zod.ts) from a closed 5-value enum (free/starter/pro/enterprise/custom) to an opaque plan identifier — any string. The schema's.describe()now states that the vocabulary is control-plane config owned by the cloud distribution, not protocol, and documents the shared convention that an empty/unrecognized value is treated as the free tier by cloud-side readers.This executes the OPAQUE arm of the maintainer ruling on cloud#1216 (2026-08-10, quoted verbatim in #7513), whose one open condition — "does any reader branch on the plan values?" — has been measured and resolved (cloud seat, 2026-08-11): no reader outside the cloud distribution branches on plan values.
Why this is safe (re-verified against
origin/mainbefore implementing)git grep -l "TenantPlan" origin/main -- 'packages/**/*.ts' | grep -v packages/spec/→ empty (positive control: the unfiltered grep hits the spec files). No framework consumer outsidepackages/specreferences the type.packages/cloud-connection/src/runtime-config-plugin.tsis plan-agnostic by design: its only touch isresolvePlanFeatures?: (plan: string | undefined) => …, called asfeaturesFor(resolved.plan, features)— a type check (typeof resolved.plan === 'string'), never a value branch.isFreePlan,planAllowsAiStudio, etc.) live in the cloud distribution's own entitlement modules — exactly "control-plane config," per the ruling.Scope
TenantPlanSchema→z.string().describe(…)with the ownership statement..default('free')usages (TenantDatabaseSchema.plan,ProvisionTenantRequestSchema.plan,EnvironmentSchema.plan) are unchanged.packages/spec/src/cloud/tenant.test.ts) is flipped, not deleted — it now asserts the new contract's substance: opaque acceptance (including a cloud-vocabulary value the old enum would have rejected, e.g.solo), continued rejection of non-string shapes, and that the.describe()carries the ownership + convention statements.content/docs/references/cloud/{tenant,environment}.mdxviapnpm --filter @objectstack/spec gen:docs(theplanfield type changed fromEnum<'free' | …>tostringin both).Out of scope (belongs to cloud#1216, downstream, blocked on this landing)
Dropping the cloud repo's hand-written
BillingPlanunion's divergence from spec — that stays in cloud#1216 per the ruling.Tests
pnpm --filter @objectstack/spec test: 376/376 test files, 9870/9870 tests passed (a filter-argument quirk ran the full suite rather than just the 3 targeted files; recorded as-is since it is a stronger, not weaker, signal).pnpm --filter @objectstack/spec typecheck: clean (tsc --noEmit,check:scripts-typecheck,check:test-typecheckall pass).pnpm --filter @objectstack/spec check:generated: found 1 stale artifact (content/docs/references/**, from the description text landing in the JSON Schema), regenerated withgen:docs; re-run green (all 13 artifacts up to date).pnpm check:merge-driver,pnpm check:adr-anchors,pnpm check:spec-parsed-alias: green.pnpm check:i18n: green, after building@objectstack/cli(its own prerequisite, unrelated to this change — the gate refuses to run against an unbuilt CLI).node scripts/check-nul-bytes.mjs: clean.Generated by Claude Code