Skip to content

Commit 1063327

Browse files
committed
Merge origin/main (f76c622) into claude/issue-20749-test-strings-p
Brings main forward to re-trigger CI on the stage-25 PR. The merge touches none of the 13 test files this branch changes; nothing under packages/spec/src/api moves. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
2 parents c63eba0 + f76c622 commit 1063327

42 files changed

Lines changed: 2247 additions & 221 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
"@objectstack/service-datasource": minor
3+
"@objectstack/metadata-protocol": minor
4+
"@objectstack/runtime": minor
5+
---
6+
7+
fix(service-datasource,runtime,metadata-protocol)!: a stored datasource row no longer displaces a code-defined datasource at boot, and the metadata door refuses edits to the host's `default` (#21922, #21944)
8+
9+
Clause-②: no (narrowing)
10+
11+
A code-defined datasource (one the installed artifact declares in `*.datasource.ts`, or the host's own `default`) is read-only: `DatasourceSchema.origin` publishes it as "GitOps-owned, read-only in the UI", and the datasource-admin service states "code wins on collision". The boot restore broke both. It registered every stored `datasource` row in `sys_metadata` over whatever the runtime had registered from code, so after a restart a row left under a code-defined name was served by the admin door, editable there when it carried `origin: 'runtime'`, and handed to pool rehydration. A stored `default` row opened a second live pool named `default` on the row's own connection. The metadata door also still saved edits to `default`, the one code-defined datasource no package declares.
12+
13+
The runtime now keeps one in-memory set of the datasource names it registers from code, on the kernel service `code-datasource-names`: `AppPlugin` adds the datasources the artifact declares and `DefaultDatasourcePlugin` adds `default`, both in `init()`, so the set is complete before any `start()` runs. The boot restore skips a stored row under a name in that set, and the metadata door's code-datasource check reads the same set.
14+
15+
**BREAKING — what moves for consumers.**
16+
17+
- After a restart over a stored row under a code-defined datasource's name, `GET /api/v1/datasources` serves the code definition (`origin: code`) instead of the row, and `PATCH /api/v1/datasources/:name` answers `400 DATASOURCE_ADMIN_ERROR` ("… is code-defined and cannot be edited at runtime.") where it answered 200 for a row that carried `origin: 'runtime'`.
18+
- No live pool is opened from such a row at boot.
19+
- `PUT /api/v1/meta/datasource/default` answered 200 and now answers `403 NOT_OVERRIDABLE`. `DELETE /api/v1/meta/datasource/default` with no stored row answered 200 and now answers the same `403`. The refusal's remedy names the host's database configuration (the database URL the server starts with), which is what defines `default`; every other code-defined datasource's refusal still names its `*.datasource.ts` source.
20+
- The skipped row is kept, and the boot logs one warning naming it.
21+
22+
**Remedy.**
23+
24+
- To change a code-defined datasource, change its code definition and redeploy: its `*.datasource.ts` source, or the host's database configuration for `default`.
25+
- A row the boot warning names is removable, and removing it is the repair: `DELETE /api/v1/meta/datasource/:name` answers 200 and deletes it.
26+
27+
**Unchanged.** A runtime datasource with no code twin restores, saves and deletes through both doors as before. A host that composes neither `AppPlugin` nor `DefaultDatasourcePlugin` registers no set, and its stored rows restore as before. While a stored row exists under a code-defined name, `GET /api/v1/meta/datasource/:name` still serves that row (the metadata door reads its stored overlay first); after the `DELETE` above it serves the code definition, in the same boot.
28+
29+
<!-- adr-0087: not-required (no-migration-prescription) a boot-restore verdict and a metadata-door verdict on datasources the host registers from code, which the published contract already calls read-only: no authorable key, spelling, export or stored shape moves, and no stored row is rewritten, converted or dropped. A row an earlier runtime write left under a code-defined name stays in sys_metadata and is removed by the operator with the metadata door's own DELETE; which stored edit an operator meant to keep is not something a ledger entry can rewrite. The other categories are closed on facts: every bumped package publishes (not unpublished); no ADR-0087 id covers this restore or this door (not already-registered); and the change is a verdict, not a declaration (not runtime-interface-only or type-surface-only). -->
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
A metadata publish consults the item lock at the package key it resolved
6+
7+
- A publish that states no package promotes the draft row it resolves, under that row's own package. Its ADR-0010 lock lookup now uses that same resolved key (the stated package, else the draft row's own), the key the gate reads the draft under and the promotion writes under, instead of only the package the request stated. Where several packages' rows declare the strictest lock, the refusal now carries the lock of the package whose draft is being promoted.
8+
- The authoring gate's package narrowing is unchanged: it still uses only the package the caller stated.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/metadata-protocol': minor
3+
---
4+
5+
A metadata publish promotes only the draft its gate judged
6+
7+
Clause-②: yes (widening)
8+
9+
- A publish (`publishMetaItem`, and each promotion of `publishPackageDrafts`) reads the draft to judge it and then promotes the draft row. The promotion is now handed the judged draft's hash. A draft saved after the judgement, or a draft that appears where the judgement found none, is refused with `409 METADATA_CONFLICT`, and nothing is published. Publishing again judges and promotes the current draft.
10+
- `SysMetadataRepository.promoteDraft` takes a new optional `expectedDraftHash` (`string | null`). When it is stated, the draft row the promotion reads must carry that hash (with `null`, no draft row may exist); otherwise the promotion throws a `ConflictError` before anything is written. When it is omitted, the promotion behaves as before.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
The organization-scoped save check judges a view overlay against every package's environment-wide definition of its row
6+
7+
- An organization-scoped `view` save or publish in the organization the anonymous form endpoints read is refused when it would leave open a form the environment-wide definition withdraws. Its row anchor is now resolved per package, the way the list read resolves each package's item: each package's own environment-wide row, else the package-less environment-wide row (which stands in for every package), else that package's artifact. Before, with no environment-wide row stored, it judged only the first package's artifact in registry order, and a stored row of any one package hid every package's artifact of the name.
8+
- The known limit stated with the public-form withdrawal ("it may over-close, never under-close") is narrowed. A withdrawal of a view name still closes that name in every package, so it may over-close. The organization-scoped save check judges every package's environment-wide definition of the name. The anonymous endpoints do too, with one exception: where a package's environment-wide copy of a view container is saved, the endpoints read that copy's expansion alone for each form it expands, and can miss another package's withdrawal of that form, whether saved or shipped, until the form is withdrawn in every saved environment-wide copy of that container as well. Reading each package's expansion separately is tracked in #21967.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
The view list serves one item per package for a view name that several installed packages ship, whether or not a view row is stored
6+
7+
- Where two installed packages ship a view of the same name, the list read (`getMetaItems` for `view`) now serves each package's item of that name, as it already did while no view row was stored. Only a name that a stored view container's expansion writes is upserted by name.
8+
- The environment-wide view list is the layer the anonymous form endpoints judge a withdrawal against. A package-less organization copy of the view, stored before one package withdrew the form, is now judged against every package's body of the name there, so it stays closed whichever package withdraws.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
The datasource read redaction resolves a driver's identity the way its sibling helper does. The per-driver half of `redactableConfigKeys` now looks a driver up through `resolveDriverId`, the resolver `passthroughSecretPaths` and the write door's contract lookup already use. So every spelling the write door accepts as a builtin driver is redacted as that driver.
6+
7+
Clause-②: no
8+
9+
- The still-writable credential key is withheld on the datasource admin read (`GET /api/v1/datasources/:name`) and on the metadata read under every accepted spelling of its driver, and `redactedConfigKeys` names it.
10+
- A crafted driver id that made the read throw now answers as a driver the platform ships no contract for: the canonical credential spellings and the former aliases are withheld, and the read succeeds.
11+
- `restoreRedactedConfig` and the credential migration read the same list, so an untouched Save still restores the stored value under every accepted spelling, and the migration names the still-writable key as residue there too.
12+
- Unchanged: what the write door accepts, every export and its type, and the answer for a canonical spelling.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/service-settings': minor
3+
---
4+
5+
fix(service-settings)!: the Localization settings no longer offer `date_format`, `time_format`, `number_format` or `first_day_of_week`: dates, times, numbers and the week start follow the locale (#21958)
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) The four keys were settings values, not metadata: only the platform-shipped localizationSettingsManifest declared them (only platform code declares a settings manifest), SettingsManifestSchema and every other spec declaration are unchanged, and a value stored for one of them lives in sys_setting (runtime state, ADR-0007), not sys_metadata, and is kept as it is. Nothing read the keys in objectstack, objectui, cloud or hotcrm, so no consumer has anything to rewrite and objectstack migrate meta has nothing to reach. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers a settings key (not already-registered); and the change is the value of an exported manifest constant, not an interface or a type alone (not runtime-interface-only or type-surface-only). -->
10+
11+
**BREAKING**: the Localization settings namespace (`localizationSettingsManifest`) drops four specifiers that nothing ever read: `date_format`, `time_format`, `number_format` and `first_day_of_week`, together with the Formats group they made up and their copy in the `en`, `zh-CN`, `ja-JP` and `es-ES` settings bundles. Setup → Localization no longer shows them, and `GET /api/settings/localization` no longer serves them. The maintainer ruled that dates, times, numbers and the first day of the week follow the user's locale (language and region), as Salesforce derives them from a Locale, and that the four separate settings retire rather than being implemented. `timezone`, `locale`, `default_country`, `currency` and `fiscal_year_start` are unchanged. The manifest's `version` is now 2, as the `SettingsManifest.version` contract asks when keys are removed.
12+
13+
**A value a workspace already stored for one of the four is kept.** Measured at the REST surface:
14+
15+
- The `sys_setting` row stays exactly as it was. No read, save or reset rewrites or deletes it.
16+
- `GET /api/settings/localization` does not resolve it: the key is absent from both `manifest.specifiers` and `values`.
17+
- A `PUT /api/settings/localization` that names one of the four is refused with `400 UNKNOWN_KEY` (`details.key` names it), the answer every undeclared key gets. The refusal covers the whole batch, so a live key sent beside it does not land either.
18+
- A stored row never blocks saving the live keys, and the built-in `reset` action leaves it in place.
19+
- In process, `settings.get('localization', 'date_format')` (or any of the four) now rejects with `SETTINGS_UNKNOWN_KEY`.
20+
- An `OS_LOCALIZATION_DATE_FORMAT`, `OS_LOCALIZATION_TIME_FORMAT`, `OS_LOCALIZATION_NUMBER_FORMAT` or `OS_LOCALIZATION_FIRST_DAY_OF_WEEK` variable is no longer read. It set a value that nothing read before either.
21+
22+
**What to do after upgrading.** Nothing has to be rewritten: the four keys never changed how anything rendered. There is no replacement key. `locale`, the workspace's language and region, is what decides how dates, times and numbers are written. The console's calendar and timeline do not yet take their first day of the week from it; that is objectui work, tracked and shipped separately.
23+
24+
It ships as `minor` under the launch-window convention for narrowings.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@objectstack/platform-objects": patch
3+
---
4+
5+
Setup → Users now opens on the "All Users" list. Before this, the console opened `sys_user`'s first declared list view, "My Profile". That view is filtered to the caller with a page size of 1, so an administrator saw one row, themselves, and nothing said the rest of the organization was one tab away.
6+
7+
Clause-②: no
8+
9+
- The Setup app's `nav_users` entry now sets `viewName: 'all_users'`. The key is the one the spec already declares on an object navigation item, and the console honours it. No new key, no `listViews` reorder, and no view is removed.
10+
- "My Profile" (`me`) is still a tab on the Users page. The Account app's profile entry is unchanged: it is the `account:profile_card` component, which reads the signed-in user from the session, not this list view. The `me` view's code comment no longer says the Account app surfaces it.
11+
- ⛔ No schema, parse, export or accept-set change.

‎content/docs/ui/public-data-collection.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ A withdrawal is a kill switch across metadata layers. If the environment-wide de
6262

6363
**Which form a withdrawal closes.** Two checks apply the rule, and they match forms differently:
6464

65-
- **Saving and publishing in an organization** judges the organization's copy against the stored environment-wide definition it overrides (the row its copy is keyed by). Inside that definition, a withdrawn form is the same form as the organization's when they share a place (`form`, the same `formViews` entry, or the view's own `config`) or a public link. A match on either is enough, so a renamed `formViews` key, a `form.name`, a move to another place, a renamed expanded item, and a new or re-cased slug all still count as the same form. A form that differs from every withdrawn form in both place and link is a different form, such as a sibling in the same view.
65+
- **Saving and publishing in an organization** judges the organization's copy against the stored environment-wide definition it overrides (the row its copy is keyed by, in each package that ships it). Inside that definition, a withdrawn form is the same form as the organization's when they share a place (`form`, the same `formViews` entry, or the view's own `config`) or a public link. A match on either is enough, so a renamed `formViews` key, a `form.name`, a move to another place, a renamed expanded item, and a new or re-cased slug all still count as the same form. A form that differs from every withdrawn form in both place and link is a different form, such as a sibling in the same view.
6666
- **The anonymous endpoints** judge each form by the name of the view item they serve. Beneath the organization's read they read the environment-wide view list, and a form is closed when the environment-wide item of the same name explicitly withdraws a form in the same place or under the same link.
6767
- **A different view is a different form.** A different view that uses the same public link (for example, another app's "contact us" form) neither closes this one nor is closed by it.
6868

6969
**Forms a package ships.** A package's form is part of the environment-wide definition, not a separate layer beneath it. A definition parsed by the stack schema (strict `defineStack`, the default) gets the schema's default `enabled: false`, so a shipped form that keeps its link without setting `enabled: true` counts as withdrawn and an organization's copy cannot open it. A definition loaded without that parse (`defineStack(..., { strict: false })` or a hand-built manifest) is judged as written: a switch it leaves out is absent, which is not a withdrawal, so set `enabled: false` explicitly to ship a form closed. The environment-wide definition is the administrator's switch: an environment-wide save may open a form that the package ships closed.
7070

71-
**Known limit: packages and names.** A withdrawal of a view name closes that name in every package. When two packages each ship a view of the same name, one package's withdrawal also closes the other package's form of that name. This may close more than was meant, but it never leaves a withdrawn form open. Per-package precision is tracked in #21934.
71+
**Known limit: packages and names.** A withdrawal of a view name closes that name in every package. When two packages each ship a view of the same name, one package's withdrawal also closes the other package's form of that name, so this may close more than was meant. The organization-scoped save check judges every package's environment-wide definition of the name. The endpoints do too, with one exception: where a package's environment-wide copy of a view container is saved, the endpoints read that copy's expansion alone for each form it expands, and can miss another package's withdrawal of that form, whether saved or shipped. To close such a form at the endpoints, withdraw it in every saved environment-wide copy of that container as well. Reading each package's expansion separately is tracked in #21967.
7272

7373
**Known limit.** The save check runs only when an organization's copy is saved or published. A copy that was already stored before the environment-wide withdrawal, or that a rollback or revert restores, is judged only by the endpoints, which match by served item name. If that copy keeps the form open under a different key or place than the environment-wide definition, the endpoints can still serve it. To close it, withdraw the form in that organization's copy too; the next organization-scoped save of a copy that keeps it open is refused.
7474

0 commit comments

Comments
 (0)