Observation filed while implementing #5521 (PR #5575). Not a defect today — both spellings are currently correct. Filed because the asymmetry is the kind that grows a third spelling.
What is there
packages/app-shell/src/runtime-config.ts gives features.marketplace a documented accessor:
export function isMarketplaceEnabled(): boolean {
return current.features?.marketplace !== false;
}
Its docblock is where the fail-open doctrine is written down — "Fails OPEN (!== false): a runtime predating /api/v1/runtime/config, or one whose config fetch failed, keeps the default true" — plus the "never infer this from the shape of a failure" warning.
features.aiStudio has no such accessor. It is read inline at two call sites:
Why it is worth recording
The two inline reads are not identical, and the difference is not cosmetic. ChatDock's omits the optional chain on features. PR #5575 measured that features is genuinely absent on real code paths — four sibling Home suites mock getRuntimeConfig as getRuntimeConfig: () => ({ branding: … }), and hosts may supply a partial snapshot the same way. Reading .aiStudio off undefined there is a TypeError, not a fail-open; in #5575 the un-chained form crashed 29 tests across 4 suites before it was corrected.
Whether ChatDock can actually reach that state was not investigated — its own tests pass today, so this is a latent shape rather than a live bug, and it should be measured rather than assumed either way before anyone edits it.
The second cost is that the fail-open doctrine for aiStudio is written down nowhere. A reader at either call site sees !== false with no explanation of why it is not === true, and the next person adding a third reader has no canonical form to copy.
Shape of a fix
An isAiStudioEnabled() sibling next to isMarketplaceEnabled(), carrying the same docblock treatment, with both call sites moved onto it.
Why PR #5575 did not do it
Adding the export means teaching four neighbouring Home suites' module mocks about it — they mock ../../../runtime-config with an explicit factory, so any new export they do not list is undefined at the call site. That is four extra files outside the card's declared surface, for a refactor its card did not ask for. The trade is recorded in a code comment at the new call site rather than left implicit.
Refs
Generated by Claude Code
Observation filed while implementing #5521 (PR #5575). Not a defect today — both spellings are currently correct. Filed because the asymmetry is the kind that grows a third spelling.
What is there
packages/app-shell/src/runtime-config.tsgivesfeatures.marketplacea documented accessor:Its docblock is where the fail-open doctrine is written down — "Fails OPEN (
!== false): a runtime predating/api/v1/runtime/config, or one whose config fetch failed, keeps the defaulttrue" — plus the "never infer this from the shape of a failure" warning.features.aiStudiohas no such accessor. It is read inline at two call sites:packages/app-shell/src/layout/ChatDock.tsx:263—getRuntimeConfig().features.aiStudio !== falsepackages/app-shell/src/console/home/HomePage.tsx—getRuntimeConfig().features?.aiStudio !== false(added by PR Console Home honours features.aiStudio for the authoring front door (#5521) #5575)Why it is worth recording
The two inline reads are not identical, and the difference is not cosmetic.
ChatDock's omits the optional chain onfeatures. PR #5575 measured thatfeaturesis genuinely absent on real code paths — four sibling Home suites mockgetRuntimeConfigasgetRuntimeConfig: () => ({ branding: … }), and hosts may supply a partial snapshot the same way. Reading.aiStudiooffundefinedthere is aTypeError, not a fail-open; in #5575 the un-chained form crashed 29 tests across 4 suites before it was corrected.Whether
ChatDockcan actually reach that state was not investigated — its own tests pass today, so this is a latent shape rather than a live bug, and it should be measured rather than assumed either way before anyone edits it.The second cost is that the fail-open doctrine for
aiStudiois written down nowhere. A reader at either call site sees!== falsewith no explanation of why it is not=== true, and the next person adding a third reader has no canonical form to copy.Shape of a fix
An
isAiStudioEnabled()sibling next toisMarketplaceEnabled(), carrying the same docblock treatment, with both call sites moved onto it.Why PR #5575 did not do it
Adding the export means teaching four neighbouring Home suites' module mocks about it — they mock
../../../runtime-configwith an explicit factory, so any new export they do not list isundefinedat the call site. That is four extra files outside the card's declared surface, for a refactor its card did not ask for. The trade is recorded in a code comment at the new call site rather than left implicit.Refs
features.*andstudio.access— two of them contradict a flag the server already sends as false #5521 / PR Console Home honours features.aiStudio for the authoring front door (#5521) #5575 — where the second call site was added and thefeatures?.difference was measuredGenerated by Claude Code