Repository navigation
fix(app-shell,i18n): the organization slug field is read-only while environments reference the slug (objectui#11720) - #11735
Conversation
…nvironments reference the slug (objectui#11720) From framework 17.7.0 better-auth's organization update refuses a new slug with 403 while the organization has an environment that is neither archived nor failed; every cloud organization is born with one, so every owner's slug edit on the Settings page failed. No existing signal says the host serves cloud's change-slug route, so the ruled fallback (B) lands: the page reads the organization's sys_environment rows the way the guard counts them and renders the slug read-only with a note while one counts. A save now sends slug only when it changed, so a name-only save carries no slug and answers 200 on every host. Outside cloud the read is refused and the form behaves as before. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
…s read for (objectui#11720) The environment answer is stored with its organization id, so another organization reads as pending again without a synchronous reset inside the effect. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
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
Size Limits
|
…name Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
… single-environment runtime is not asked (objectui#11720) The locked-slug note now states only the measured cause (active environments; a rename also moves their subdomains) in all ten packs, and drops the pointer to the organization's record, which nothing measured as reachable from this console. readOrgEnvironmentPresence answers unknown without a request when the runtime config serves singleEnvironment: true. @objectstack/spec lists sys_environment in CLOUD_PROVIDED_OBJECT_NAMES, absent from a single-environment OSS runtime, so the read there could only fail; the answer is unchanged and the request is gone. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
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
Size Limits
|
Contract reviewServed-tier: Inputs: card #11720 (body and all six comments, rulings ① Derived judgmentsRoute B, as ruled — RIGHT. Triage
The published component's behaviour (
Pins. Check-runs on the head. 43, all completed: 40 success, 3 skipped ( ② Semver level
Paragraph 1, sentence by sentence: (1) "no longer offers a slug edit the framework will refuse" — holds where the read answers Paragraph 2: (1) "A save sends The changeset's Clause-② paragraph names the one key, the ten packs, no export, prop or schema key, and the module-private read: all read off the entries above. The PR body's second line Clause-②: yes (widening) A widening (one ③ Boundary flagsThe dev's two open questions (round 0), answered by the seat in Round-1 deviations, each answered. (a) The single-environment skip on a cloud environment hostname (env-registry resolution): the skip's answer Out-of-scope findings (3, carrier none, not filed), acknowledged. Escalated to the seat, one item, a landing or post-landing measurement rather than a FAIL. The card's done-when on cloud is unmeasured end to end, because Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #11720
Clause-②: yes
Implemented by the
os-devrun dispatched on claim comment 6019734714, sessionhttps://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju.What changed
The organization Settings page (
SettingsPagein@object-ui/app-shell) no longer offers a slug edit the framework will refuse, and a name-only save no longer carries a slug.readOrgEnvironmentPresence, beside the page inconsole/organizations/manage/orgEnvironments.ts. It sendsGET /api/v1/data/sys_environmentwith the canonicalfilter(the organization id) andselect=statusparams. It counts rows the way the framework's slug guard (beforeUpdateOrganizationin plugin-auth) counts them: any status exceptarchivedandfailed, and a row with no status counts too. It answerspresent,noneorunknown, and it never rejects. Only the{ success: true, data: { records } }envelope is read. That is the shape the framework data domain answers a list with (FindDataResponseSchemainside the dispatcher's success envelope). Any other body reads asunknown.singleEnvironment: true(the CLI'sos servearm serves it throughServe.RUNTIME_CONFIG_OPTIONS), the helper answersunknownwithout a request. Three readings at objectstack1fb274e6back this:@objectstack/speclistssys_environmentinCLOUD_PROVIDED_OBJECT_NAMES("They do not exist in a single-environment OSS runtime"); no framework source defines the object (control: the same search findssys_organization's definition); and the guard itself returns early whengetSchema('sys_environment')is empty. The read there could only fail, so the answer is unchanged and the request is gone.presentlocks the field. The slug input is read-only, and a note (new keyorganization.settings.slugLockedNote, in all ten packs) states the measured cause and nothing else: the organization has active environments, so the slug can't be changed here, because a rename also moves their subdomains. It names no rename path, because none was measured reachable from this console. Every other answer leaves the field as it always was.handleSavesendsslugonly when it changed: non-empty, different from the organization's current slug, and the field not locked. A name-only save carries no slug on any host, so the guard has nothing to judge and the save answers200. A stale form also cannot write back a slug that was renamed somewhere else.Why B and not A: no existing signal says the host serves cloud's change-slug route
The triage ruling (comment 6019042638) made A depend on an existing signal, read and not invented, and named B as the fallback. I checked every signal the console reads, on
origin/mainata58626cand against the producers in objectstack01e0f71a. None of them says the host serves the route:AppShellRuntimeConfig):cloudUrlis''both when the runtime IS the cloud and on the CLI's air-gapped arm. The framework's ownisControlPlaneDeclineddoc says the CLI passescontrolPlaneUrl: ''on both arms, so the URL tells you nothing about the deployment.singleEnvironmentisfalseon every multi-tenant host.features.*key (installLocal,marketplace,aiStudio,autoPublishAiBuilds,customDomain,sso,scim,storageUsage) stands for a different route or a plan entitlement. None stands for the organization rename.AuthPublicConfig.features): no key says anything about cloud.CoreServiceNamehas no cloud slot, andApiRoutesSchemahas no cloud route. The only discovery services objectui reads areauthandai.sys_organizationdeclares achange_slugrecord action that targets/api/v1/cloud/organizations/{id}/change-slug. The framework's platform-objects declare it on every host, though, gated only bymultiOrgEnabled, so finding it does not show the route is served.So A is not built, no key is added, and B lands. The premise still holds: framework commit
131b937aee(the forcing change named on the card) is contained in the17.7.0tags. At objectstack01e0f71a,beforeUpdateOrganizationreadssys_environmentunder the system context and throwsFORBIDDENwhile any non-archived, non-failed row exists.Pins (transport stubbed at
createAuthenticatedFetch)settings-slug-environments-11720.test.tsx, 12 tests:filterandselectparams.presentfor a counted row and for a row with no status.nonefor retired rows only and for no rows.unknownfor a 404, a network failure, and a bare body outside the envelope. On a single-environment runtime,unknownwith no request made.aria-describedby. The note's text is exactly the measured cause and matches neitherrecordnorrename it from. A save sends no slug.sys_environment): the field stays editable, and a changed slug goes out in the one update call, as before.Three ablations, all on the committed tree at
169584a. Each went through objectstack'sscripts/ablation-replace.mjsin wrap mode, with the restore armed on exit, INT and TERM:locked: presence === 'present'replaced bylocked: false). Predicted: only the "with environments" test goes red. Observed:Tests 1 failed | 11 passed (12), and the failure is that test. Restore proven: blob after restorefbf7960a002dequals the blob at HEAD, andgit diff HEADis empty.Tests 3 failed | 9 passed (12), and those are the three. Restore proven the same way.singleEnvironment === trueearly return deleted). Predicted: the helper's and the page's single-environment pins go red. Observed:Tests 2 failed | 10 passed (12). The page pin failed on "expected vi.fn() to not be called at all, but actually been called 1 times", and the helper pin on "expected 'present' to be 'unknown'". Restore proven: blobb04f375652a8equals HEAD, andgit diff HEADis empty.Clause-② reading
I built
@object-ui/i18ntwice, at the merge base0cfe772(a throwaway detached worktree, since removed) and at this branch's169584a.diffof the twodist/locales/en.d.tsfiles is exactly one added line insideorganization.settings:readonly slugLockedNote: "This organization has active environments, so its slug can’t be changed here: renaming it also moves their subdomains.";.dist/index.d.tsis byte-identical between the two builds. No export, prop or schema key is added, andreadOrgEnvironmentPresenceis not exported from the package entry.Local verification (head
169584a)The branch merged
origin/mainat0cfe772as a merge commit (b8deb0d) before this round's change.pnpm exec turbo run build --filter=@object-ui/app-shell^... --concurrency=2: 28 of 28 tasks.pnpm --filter @object-ui/app-shell type-checkandpnpm --filter @object-ui/i18n type-check: exit 0.pnpm --filter @object-ui/app-shell lintandpnpm --filter @object-ui/i18n lint: exit 0, 0 errors. The page's two remainingset-state-in-effectwarnings were already there.pnpm exec vitest run packages/app-shell/src/console/organizations/ packages/i18n/:Test Files 94 passed (94),Tests 1435 passed | 13 skipped.check:new-line-citations(0 new),check:control-bytes,check:i18n-keys,check:i18n-drift(against0cfe772: 0 en values changed, 1 key added),check:i18n-dead-keys,check:changeset-claims,check:pending-changeset-literals,check-changeset-no-major,check-changeset-fixed,check-changeset-presence,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:unreferenced-sources,check:test-path-roots.node scripts/check-governed-queue-guard.mjs --testover the 14 paths: NOT GOVERNED.check:eager-locale-catalogues. Reason: PREREQUISITE NOT MET, because the gate weighs the built console bundle (apps/console/dist/eager-closure.json) and the console was not built here. CI owns it, along with the repo-widepnpm lintand the fullpnpm test.Acceptance notes
sys_environmentobject, and there the read still answers an error once per owner visit to this page. No served signal says such a host has no environment registry. The/organizations/SLUGroutes also render outsideConnectedShell, so the metadata registry (useObjectPresence, objectui#7476's tool) is not available on this page. The error reads asunknown, and the page then behaves as before. Single-environment runtimes are no longer asked.RuntimeConfigPluginalso servessingleEnvironment: truewhen the request host resolves through theenv-registryservice to one environment, and no framework package registers that service. On such a host the page does not ask either.unknownkeeps the field editable and the server deciding, which is the behaviour before this PR. I could not measure whethersys_environmentis readable on such a host, because the cloud repository is not reachable from this container. Either way the skip cannot lock a field by mistake.sys_organization.change_slug(target/api/v1/cloud/organizations/{id}/change-slug,record_header, gated only bymultiOrgEnabled) is declared on every host by platform-objects, but nothing in objectstack mounts that route. I read this from the source only and measured no public door. carrier: none.CreateWorkspaceDialog's module header says an owner can still change the slug later in organization settings. Under cloud with environments the field is now read-only. I left it alone because it is outside the claim's file surface. carrier: none.OrganizationLayoutresolves the organization from the slug in the URL, so after a successful slug rename off-cloud,/organizations/OLD-SLUG/settingsno longer matches. I read this from the source only and did not reproduce it. carrier: none.Generated by Claude Code