Repository navigation
fix(service-settings)!: retire date_format, time_format, number_format and first_day_of_week from the Localization manifest (#21958) - #21970
Conversation
…t and first_day_of_week from the Localization manifest (#21958) Formats follow the locale (ruled on objectui#11675, B). The four rows, the now-empty Formats group and their entries in the en, es-ES, ja-JP and zh-CN bundles leave; the manifest version goes to 2 per the spec's SettingsManifest.version contract (increment when keys are removed). A stored sys_setting row for one of the four is kept: it is not resolved on read, a write naming it is refused 400 UNKNOWN_KEY, and no reset or save of the live keys rewrites or deletes it. Pinned at the REST surface. Claude-Session: https://claude.ai/code/session_01WMQprn46CND82KmY8sZWBu Co-authored-by: Claude <noreply@anthropic.com>
… format keys Claude-Session: https://claude.ai/code/session_01WMQprn46CND82KmY8sZWBu Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 8 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0bef7d483b09ff0406151d38c9aa0f58059761bb && git checkout 0bef7d483b09ff0406151d38c9aa0f58059761bb
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4c49150e0cb27a493390c4ac24e0b93e13efe662 397edabf048f3dab63dd651b77d28e00136dbaa1 && git checkout -B drift-repro 4c49150e0cb27a493390c4ac24e0b93e13efe662 && git merge --no-ff 397edabf048f3dab63dd651b77d28e00136dbaa1
node scripts/docs-audit/affected-docs.mjs --json 4c49150e0cb27a493390c4ac24e0b93e13efe662 |
Fixes #21958
Clause-②: no (narrowing)
Executes the maintainer's ruling recorded on objectstack-ai/objectui#11675 (comment 6010771237, decision batch 2, item 1, B, verbatim 「同意」): dates, times, numbers and the first day of the week follow the user's locale, and
date_format,time_format,number_formatandfirst_day_of_weekleave the Localization settings without being implemented. objectui#11675 remains open for its own remainder (the calendar and timeline week start); this PR does not touch it.What changes
packages/services/service-settingsonly:src/manifests/localization.manifest.ts: the four specifiers leave, with the Formats group they made up (it would have been an empty header).timezone,locale,default_country,currencyandfiscal_year_startare unchanged.versiongoes from 1 to 2, because the spec'sSettingsManifestSchema.versioncontract reads "Manifest version. Increment when keys are renamed/removed." The namespace description and the module docblock say what replaced the four. While rewriting that docblock I corrected its stale function name (resolveExecutionContextno longer exists;resolveLocalizationContextin@objectstack/coreis what readstimezone,localeandcurrency).src/translations/{en,es-ES,ja-JP,zh-CN}.ts: the four key entries and theformatsgroup leave each bundle, and the namespace description follows the manifest..changeset/21958-retire-locale-format-settings.md:@objectstack/service-settingsminor, BREAKING banner,Clause-②: no (narrowing), ADR-0087 dispositionnot-required (no-migration-prescription). The four were settings values insys_setting, not metadata, soobjectstack migrate metahas nothing to rewrite.No
packages/spec,skills/**orcontent/docs/releases/edit.Stored values: measured, not assumed
Measured twice through the real REST handlers (
registerSettingsRoutesoverSettingsService): onorigin/main80f9f7e6ba, with a copy of the manifest minus the four registered over a store that already held adate_formatrow, and on this branch with the shipped manifest. Both gave the same answers:GET /api/settings/localizationmanifest.specifiersand fromvalues; the stored row is loaded with the namespace but never resolved.PUT /api/settings/localizationnaming one of them400 UNKNOWN_KEY,details: { namespace: 'localization', key }. The refusal covers the whole batch: atimezonesent beside it does not land.PUTof a live key, with the stale row in the storeresetactionsettings.get('localization', 'date_format')UnknownKeyError,code: 'SETTINGS_UNKNOWN_KEY'.DD.MM.YYYY, sourcetenant: the row survived every step above.OS_LOCALIZATION_DATE_FORMAT(and the other three)So a stored row is kept, never deleted: ignored on read, refused on write. No service code changed. This is the path every undeclared key already takes.
Precedent, as measured. The dispatch pointed at
settings-routes.ts:25and atsettings-service.tsnear line 920. Line 25 cites envelope siblings retired from the route module, not a settings key. Line 920 is the retiredmail.providerOPTION values (sendgrid,ses), where a stored stale value is kept and judged only when written (the TOUCH gate). No settings key had ever left a manifest before this one. I checked every commit whose diff to a*manifest*.tsoutsidepackages/spectouches akey:line (26 commits, on an unshallowed history of 15,821): no key disappears in any of them, and no manifest'sversionwas ever above 1. Two contracts already in place therefore answer the question: the generic undeclared-key path above, and the spec's ownversionrule. A retirement entry would have needed a home inpackages/spec, and nothing requires one there.SettingsManifest.versionhas no reader in objectstack or objectui atmain(grep formanifest.versionin the settings code of both), so the bump is compliance with the declared contract and changes no behaviour.Docs
No
content/docs/**page lists the four as settings. Searched outsidereferences/andreleases/for the four key names and for "Date format", "Time format", "Number format", "First day of week", "Localization settings" and "date/number formats". The only hits are field-type and i18n-API pages describing other things (data-modeling/validation-rules.mdx,protocol/kernel/i18n-standard.mdx,protocol/kernel/index.mdx). No page needed an edit. The repo-wide reader sweep for the four key names finds only SQL's owndate_format()(driver-sql, comments in core and objectql, and the twoskills/**rule files, which describe the SQL function), and the exporttransformenum value'date_format'. None of these is the settings key.Tests (head
397edabf04)src/settings-routes.test.ts: four retired rows are seeded in the store, asseedGlobalSecretin the same file seeds one. Then:GEToffers and resolves none of them;PUTnaming each is refused, assertingstatus400,error.codeUNKNOWN_KEYanderror.details;resetaction both leave the four stored rows byte-equal.src/manifests/localization.manifest.test.ts:versionis 2; the key list is five keys inregion/finance. A new negative pin asserts that none of the four, nor theformatsgroup, is offered.src/translations/settings-translation-coverage.test.tsnow also checks the other direction, forenand the three translated bundles: no copy for a group or key that no manifest declares. It measured zero such entries across all ten manifests before it was added, so it closes the class rather than pinning only these four.src/settings-service.test.ts: the two "domain-less select keeps exhaustive options" pins usedfirst_day_of_weekanddate_formatas their example key. They now usefiscal_year_start, also a domain-less select, so they pin the same behaviour.pnpm --filter @objectstack/service-settings test: 36 files, 626 tests passed.typecheck(tsc --noEmit) exit 0;--listFilesshows it compiles 36 test files, including all four edited ones. The dependency closure (--filter '@objectstack/service-settings^...') was built first.distloads through both conditions:require('./dist/index.cjs')andimport('./dist/index.js')each serveversion 2with five keys.Ablation (negative pins proven able to fail)
Both ablations used
scripts/ablation-replace.mjs(literal anchor that must hit; blob hashes verified; restore by blob to HEAD,git diff HEADempty):date_formatre-inserted, on-disk count 1). 8 tests went red: the three new route pins, the two manifest pins, and zh-CN, ja-JP and es-ES coverage. The tree was restored to blobe8f3039af917(equal to HEAD). A first attempt was refused by the tool, which is why there were two: the replacement contained its own anchor, so the anchor count did not drop. Nothing was measured on that attempt.date_formatre-added toen.tswith no manifest row). The new reverse-direction pin went red withen has undeclared settings copy: localization.key:date_format. Restored to blob0b0918e9ece5(equal to HEAD).Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon the final tree derived 63 commands. That is the dispatch's 49 plus 14 that the changeset and test edits pull in, includingcheck-adr-0087-registration,check-empty-changeset,check:engine-double-contractandcheck:type-check-coverage. All 63 were run at397edabf04with exit codes captured before any pipe. 62 exited 0.pnpm check:dual-build-cjs-loadsexited 3, PREREQUISITE NOT MET (53 packages outside this closure have nodist/): NOT MEASURED locally, and CI's full build runs it. The--ranreconciliation answers "63 derived, 62 run, 1 NOT-MEASURED, 0 UNRUN".ESLint, narrowed:
pnpm exec eslint --no-inline-config --format jsonover the 9 touched.tsfiles gave 9 files linted, 0 errors and 0 warnings. All 9 are inside the config's population (**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}minus the build dirs). The config enables no type-aware linting (noparserOptions.project), so this diff cannot move the verdict on any untouched file. The repo-widepnpm lintis CI's.Acceptance notes (not filed)
OS_LOCALIZATION_*variable naming a retired key is now silently unread. No namespace auditsOS_variables that name an undeclared key (a typo such asOS_LOCALIZATION_TIMEZONis equally silent today). Pre-existing and general, and the value was never read before either. Carrier: none.SettingsManifest.versionhas no reader anywhere measured, so the "increment when keys are removed" contract is honoured without being enforced. No pull. Carrier: none.apps/console/src/pages/settings/__tests__/SettingsField.valueDomain.test.tsxusesdate_formatandfirst_day_of_weekas hand-built fixture keys. It does not import this manifest, so it stays green and is harmless. Carrier: whoever next edits that file, possibly with objectui#11675's remainder.@objectstack/service-settings' exported types are byte-unchanged (the manifest is typedSettingsManifest, and only its value moved), and no package outside this one importslocalizationSettingsManifestor the bundles in code. So there is no downstream consumer sweep.Generated by Claude Code