Skip to content

fix(app-shell): read the provisioned env from the nested environment row - #6706

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-6629-provision-env-envelope-shape
Aug 28, 2026
Merged

fix(app-shell): read the provisioned env from the nested environment row#6706
os-sales merged 1 commit into
mainfrom
claude/issue-6629-provision-env-envelope-shape

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes #6629

POST /api/v1/cloud/environments answers { success, data: { environment, warnings, durationMs, hostnameAssignment? } } — the created row sits one level down, under environment. provisionProductionEnvironment read data FLAT and returned it as a ProvisionedEnvironment, so id and hostname were always undefined and the envelope's siblings rode along in their place.

The row is now projected to { id, hostname }.

Which half is measured, which is inherited

Consumer half — MEASURED here, on merge-base 5967be095, in packages/app-shell/src/console/organizations/provisionEnvironment.ts:

  • ProvisionedEnvironment is declared { id?: string; hostname?: string; alreadyProvisioned?: boolean }, with id documented as the control-plane sys_environment row id;
  • the body is cast to { data?: ProvisionedEnvironment }, and the envelope check is if (!data || typeof data !== 'object') throw;
  • the function then does return data — the wrapper itself.

So the card's claim is confirmed against the actual code: the check verifies data is an object and nothing about its shape. A payload of { environment, warnings, durationMs } passes it unchanged and is returned whole as a value typed ProvisionedEnvironment.

Producer half — INHERITED, not measured. The producer evidence is packages/service-cloud/src/routes/environment-lifecycle.ts in objectstack-ai/cloud, which this repo cannot read. I searched this repo for a second source of the wire shape and found none: no fixture, no recorded response, and no typed client declares the create-environment response (hostnameAssignment appears nowhere in node_modules/@objectstack/**). The only in-repo artifact that pinned the success-payload shape was this suite's own hand-written mock — and it pinned the FLAT shape, i.e. it pinned the bug. The fix is therefore pinned against the card's stated wire shape, declared as inherited.

Anti-vacuity: RED before, GREEN after

This bug is quiet by construction — both fields optional, the call best-effort by contract, 403/409 resolving to alreadyProvisioned: true, the caller swallowing genuine failures — so a test that merely asserts the fixed path is green would have been green before the fix too. The tests were therefore written and run against the unmodified merge-base implementation first.

RED on 5967be095, implementation untouched, test file only — Tests 3 failed | 4 passed (7):

test pre-fix failure
reads the created env from the nested environment row, not from the wrapper expected { …(4) } to deeply equal { id: 'env-1', …(1) }
does not fall back to a flat data shape when environment is absent expected 'flat-1' to be undefined
posts Production + the org id … and returns the env (fixture retriaged) expected { environment: { id: 'env-1', …(1) } } to match object { id: 'env-1', …(1) }

GREEN after the fix — Test Files 18 passed (18) / Tests 116 passed (116), run at 35fe8741b.

The second row is the anti-alias pin: it asserts a flat data is not picked up (no data.environment ?? data), and it is red pre-fix precisely because the old code did read it. The third was an existing fixture that pinned the wrong wire shape; retriaged rather than deleted.

Deliberately NOT folded in: tightening the envelope check

A wrong-shaped data still resolves (with id/hostname undefined) rather than throwing. Rejecting it is a tempting adjacent fix and may well be right, but it is a behaviour change on an error path that currently resolves best-effort. Blast radius as measured in this repo: the sole caller is CreateWorkspaceDialog.tsx, which wraps the call in try/catch and on throw logs a warning and falls through to the lazy onboarding gate — so a tightened check would convert a silent no-op into a logged failure plus a lazy re-provision on first navigation. That is a real user-visible path change and belongs in its own card, not here.

The ADR-0006 D2 rename is not being raced

The cloud control plane is mid-rename under ADR-0006 D2 (epic objectstack-ai/objectstack#12865), where response payload keys project / projects become environment / environments on the OTHER control-plane routes. POST /cloud/environments is not part of that rename — it has always answered environment — so this change is stable against that work rather than racing it. Reviewers should not have to rediscover this. Originally found while implementing the producer-half rename in objectstack-ai/cloud#1691.

Changeset

Scored patch in .changeset/6629-provision-env-nested-envelope.md. Reasoning: this is shipped runtime code in a released package whose return value is now different, not a comment or a test-only change, so an empty "no release" frontmatter would assert something false. Not minor — no new capability and no API surface change; ProvisionedEnvironment is unchanged. The blast radius is small today (the sole in-repo caller discards the return value, and the symbol is not on the package barrel), but small is not unreleased.

Verification

All heavy runs went through the shared verify lock. Union run at 35fe8741b; the working tree was clean at that commit, so the lint and type-check readings below are on the identical bytes.

  • pnpm exec vitest run packages/app-shell/src/console/organizations/ plus the six app-shell ratchet suites — Test Files 18 passed (18), Tests 116 passed (116), lock VERDICT command-exit 0.
  • pnpm --filter "@object-ui/app-shell" run type-checkVERDICT command-exit 0. Both passes ran (tsc --noEmit && tsc -p tsconfig.test.json); the second is what covers test files, since tsconfig.json excludes them.
  • pnpm --filter "@object-ui/app-shell" run lint — whole-package eslint ., 0 errors, 2779 warnings (all pre-existing; neither changed file appears). Scoped to app-shell because it is the only package this diff touches, and the root eslint.config.js declares no parserOptions.project / projectService, so no untouched file's verdict can move.
  • Dependency closure rebuilt first (pnpm --filter "@object-ui/app-shell^..." run build, 29 projects, exit 0) — the pre-existing dist/ was from an older base.
  • Gates: check-changeset-presence, check-changeset-fixed, check-changeset-no-major, check-changeset-overwrite, check-control-bytes, check-vi-mock-specifiers — all exit 0 with their own OK lines.

Generated by Claude Code


Generated by Claude Code

…` row

`POST /api/v1/cloud/environments` answers `{ success, data: { environment,
warnings, durationMs, hostnameAssignment? } }` — the created row is one level
down. `provisionProductionEnvironment` read `data` flat and returned it as a
`ProvisionedEnvironment`, so `id` and `hostname` were always `undefined` and
the envelope's siblings rode along in their place.

Silent by construction: both fields are optional, the call is best-effort by
contract (403/409 resolves to `alreadyProvisioned`) and the caller swallows
genuine failures — so the function reported a successful provision carrying no
environment at all, the exact outcome the strict envelope check in that file
was written to prevent. That check verifies `data` is an object and nothing
about its shape; it is left unchanged here.

The row is now projected to `{ id, hostname }`. One dialect only: no
`data.environment ?? data` alias, and a wrong-shaped `data` still resolves
rather than throws.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRJge11jso9TpXRWFt1Z49
@github-actions github-actions Bot added the tests label Aug 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 49 chunks) 3232.1 KB 3266.6 KB
Main entry chunk (gzip) 157.3 KB 350 KB
Entry file index-COXaicnT.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 11.89KB 4.50KB
app-shell (runtime-config.js) 20.61KB 7.35KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 509.32KB 115.60KB
core (index.js) 5.30KB 2.13KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 173.10KB 47.96KB
fields (index.js) 239.05KB 60.06KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.95KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.55KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.53KB 3.38KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.64KB 1.50KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 1.93KB 0.88KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.89KB 12.91KB
plugin-charts (index.js) 64.66KB 18.32KB
plugin-chatbot (index.js) 190.33KB 45.10KB
plugin-dashboard (index.js) 133.26KB 34.43KB
plugin-designer (index.js) 212.80KB 43.15KB
plugin-detail (index.js) 245.29KB 62.39KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.01KB 32.23KB
plugin-gantt (index.js) 165.20KB 40.37KB
plugin-grid (index.js) 201.51KB 54.54KB
plugin-kanban (index.js) 53.11KB 14.62KB
plugin-list (index.js) 113.01KB 27.57KB
plugin-map (index.js) 20.17KB 6.66KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.51KB 11.94KB
plugin-timeline (index.js) 26.44KB 7.59KB
plugin-tree (index.js) 9.00KB 3.08KB
plugin-view (index.js) 85.87KB 21.12KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 67.73KB 22.54KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 2.44KB 1.21KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.72KB 2.24KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

provisionProductionEnvironment reads a flat POST /cloud/environments payload; the producer nests the row under environment

2 participants