Repository navigation
Description
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.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:
- 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 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):
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
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. - One sentence worth keeping in the describe: the shared convention that empty/unknown ⇒ free tier (
isFreePlanin 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.)
Activity
os-zhuang commentedon Aug 11, 2026
Triage:
pm:queue+domain:spec.origin/main1530870):packages/spec/src/cloud/tenant.zod.ts:35still exports the closedTenantPlanSchemaenum, consumed at:100/:234/:332. Acceptance surface changes (closed enum → opaque string = widening) ⇒domain:specby the acceptance-face criterion, red-line clause.TenantPlanoutsidepackages/spec/is empty (positive control: unfiltered grep hits spec), matching the body's measurement.pm:blockedin cloud and stays there (correctBlocked-bydirection, single dispatch entry here). No open objectstack card covers the re-type.target:v17: no — contract widening with no runtime parser of the enum on this path (per the cloud#1216 measurement); nothing a user hits today, not release-blocking by the four-class test. Size/model suggestion: S, sonnet.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
Claim: PM loop round 1 (spec lane)
Session:
session_01JY2Q5Xto1u8YHADgrZDTnkBranch:
claude/issue-7513-tenantplan-opaqueWorktree:
objectstack-issue-7513Domain:
domain:specFile surface:
packages/spec/src/cloud/tenant.zod.ts(TenantPlanSchema + in-file consumers), any in-packageTenantPlanreferences, generated references/baselines. Stop on breach; explain in the report.Container & model: S mechanical (triage suggestion adopted: S, sonnet — closed-enum → opaque-string widening whose correctness the gate farm determines),
mode:subagent, model: sonnetSerial constraints cleared: no in-flight writer on
tenant.zod.ts(open PRs + queue branches checked 09:20Z; last touch c6a4eeb 08-10).Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
OS-DEV-REPORT — #7513
{ "issue": 7513, "status": "done", "branch": "claude/issue-7513-tenantplan-opaque", "pr": "https://github.com/objectstack-ai/objectstack/pull/7655", "premise_still_valid": true, "summary": "Re-verified the premise against origin/main before implementing: the closed TenantPlanSchema enum still lived at tenant.zod.ts:35, consumed at :100/:234/:332, and the framework-consumer grep (positive control: unfiltered grep hits spec files) was still empty, matching the issue's measurement. Widened TenantPlanSchema from the closed 5-value enum to z.string().describe(...) stating the vocabulary is control-plane config owned by the cloud distribution (not protocol) plus the empty/unknown-⇒free-tier convention. Flipped (not deleted) the pre-existing pin test that asserted the closed enum so it now asserts the new contract's substance: opaque acceptance (including a cloud-vocabulary value like 'solo' the old enum would have rejected), continued rejection of non-string shapes, and that .describe() carries the ownership + convention text. Regenerated content/docs/references/cloud/{tenant,environment}.mdx via gen:docs since the plan field's rendered type changed from Enum<...> to string.", "tests": "pnpm --filter @objectstack/spec build (dependency/build closure, green) -- pnpm --filter @objectstack/spec test: filter args were not honored by this invocation (ran the full suite instead of the 3 targeted files) -- 376/376 test files, 9870/9870 tests passed, a stronger not weaker signal, recorded as-is -- pnpm --filter @objectstack/spec typecheck: clean (tsc --noEmit, check:scripts-typecheck, check:test-typecheck all green) -- pnpm --filter @objectstack/spec check:generated: found content/docs/references/** stale (description text landing in the JSON Schema), regenerated with gen:docs, re-run reports 'All 13 generated artifacts are up to date' -- pnpm check:merge-driver / check:adr-anchors / check:spec-parsed-alias: green -- pnpm check:i18n: PREREQUISITE NOT MET on first run (CLI not built); built @objectstack/cli via turbo (55/55 tasks), re-run green ('9 package(s) -- all bundles in sync, no undeclared authoring keys') -- node scripts/check-nul-bytes.mjs: clean. PR #7655 opened as draft; CI just started at report time (ESLint, TypeScript Type Check, Check Changeset, Build Core, Test Core, etc. all queued/in_progress, none failed) -- recorded honestly as in_progress, not waited on.", "open_questions": [], "out_of_scope_findings": [ "filed as #7658: [finding] build-docs.ts silently drops the entire reference-doc section (heading + .describe() text) for any top-level schema that is a bare opaque primitive (type:'string'/'number'/etc. with no enum/properties/anyOf/oneOf) -- pre-existing gap affecting 23 other exports beyond TenantPlan (e.g. EnvironmentDriver, PackageCategory, Sha256Digest, the shared/*Name family), exposed by this card's enum-to-string widening; not fixed here, out of scope for #7513" ] }Sibling in-flight cards (#7495, #7496)
Untouched, no interaction either direction. This card's only file surface is
packages/spec/src/cloud/tenant.zod.ts/tenant.test.tsplus the two regeneratedcontent/docs/references/cloud/*.mdxpages. #7495 (error-code ledger) and #7496 (submitBehavior) touch unrelated schema families with no reference toTenantPlan/TenantPlanSchemaand no shared file — this PR neither eases, complicates, nor obsoletes either.Note for the reviewing PM
pnpm --filter @objectstack/spec testran the entire package suite (376 files / 9870 tests) instead of the 3 files I passed as positional filters after--maxWorkers=2— worth a look if it recurs on other spec-package dispatches, since it costs ~5x the intended scope. Not investigated further here since the full-suite result was green and is strictly stronger evidence than the scoped run would have been.Generated by Claude Code
Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
Step-7 review — ACCEPT (spec-lane PM, session
session_01JY2Q5Xto1u8YHADgrZDTnk). PR: #7655 (draft,Fixes #7513).Verified against GitHub, not the report's claims:
tenant.zod.ts(enum →z.string().describe(…)with the ownership + free-tier-convention text),tenant.test.ts(the closed-enum pin flipped, not deleted — now asserts opaque acceptance including cloud-vocabularysolo, non-string rejection, and that.describe()carries the ownership statement), a justified@objectstack/specminor changeset, and the two regenerated reference pages. Nocontent/docs/releases/, nodocs/adr/**, no riders.git grep -l "TenantPlan" origin/main -- packages/ | grep -v packages/spec/→ empty, positive control hitstenant.zod.ts(5). Matches the dev's and triage's measurements.check:generated13/13 after regen, spec-scoped gate families green,check:i18ngreen after building its CLI prerequisite. CI recorded honestly asin_progressat report time — convergence is the PM's from here.build-docs.tsdrops the whole section for bare opaque-primitive schemas (pre-existing gap, now measured at ~23 affected exports). Filed as [finding] build-docs.ts silently drops the whole reference-doc section for a bare opaque-string top-level schema (~23 exports with a real .describe()) #7658 by the dev; the schema-level pin still guards the describe text itself.parseFilterASTlowers['a', '=', { $field: 'b' }]to a bare field spec no backend reads as equality #7597): dev confirms no shared surface — consistent with the claim-time serial check.Landing plan: flip to ready + auto-merge once the gate jobs' conclusions are green (flip check armed); merge-queue lands it; cloud#1216 unblocks on merge.
Generated by Claude Code
chore: reset os-regen-routed paths to origin/main post-merge
spec: re-type TenantPlan / sys_environment.plan as an opaque plan ide…