Skip to content

Commit 25d0307

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-13533-system-write-sharing-materialization
# Conflicts: # content/docs/permissions/system-context.mdx
2 parents 376c04e + 20b8839 commit 25d0307

384 files changed

Lines changed: 21616 additions & 4960 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
docs(spec): `ApprovalEscalation.timeoutHours` names its clock — calendar (wall-clock) hours
6+
7+
The `timeoutHours` describe text now states that the hours are calendar
8+
(wall-clock) hours: nights, weekends and holidays count, because the platform
9+
ships no business-hours calendar, so a request opened at 17:00 on a Friday with
10+
`timeoutHours: 4` escalates at 21:00 that same Friday. The sentence is published
11+
contract text — it is what `gen:schema` emits to the JSON schema `description`
12+
and what the reference page carries — so the unit is part of the declaration an
13+
author reads at authoring time rather than prose beside it. No key is added,
14+
renamed or defaulted differently; the approvals service's arithmetic is
15+
unchanged and is now pinned by a wall-clock test (Friday 17:00 + 4 h, a 168-hour
16+
deadline across a weekend, a DST transition).
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
"@objectstack/plugin-approvals": patch
3+
---
4+
5+
fix(approvals): the #3424 override reaches a `pending` request only — override-recall of a `returned` request is now refused (#12775)
6+
7+
**Behaviour change, declared explicitly (maintainer ruling 2026-09-02) so that
8+
one revert of this changeset's PR restores the previous behaviour.**
9+
10+
`ApprovalService.recall` admits two callers: the submitter, and a platform or
11+
tenant admin releasing a stuck request (the #3424 privileged override). Recall
12+
is also valid on the LATEST `returned` request of a run — the ADR-0044 revise
13+
window, where the submitter abandons the revision instead of resubmitting.
14+
Those two rules met above the state check: the override short-circuit carried
15+
no status test of its own, so an override actor could recall a `returned`
16+
request too. Nothing else on the platform said so — `isOverrideActor`'s doc
17+
block names a PENDING request, `attachViewers` computes
18+
`viewer.can_override` as `status === 'pending' && isOverrideActor(...)`, and
19+
the `approval_recall` action's override arm reads that flag — so the reach was
20+
API-only, never offered by any UI, and pinned by nothing.
21+
22+
What changes:
23+
24+
- **Override-recall of a `returned` request is refused.** The override
25+
short-circuit in `recall` now applies only while the request is `pending`,
26+
spelled exactly as the viewer flag is computed. On `returned` an override
27+
actor is judged exactly as any other non-submitter and receives the existing
28+
refusal: `403 FORBIDDEN` over REST, with the operation catalog's
29+
`approval_recall_not_submitter` sentence. No new error code, no new envelope.
30+
- **Only `pending` requests are override-recallable** — the same scope as the
31+
other three override levers (approve / reject / reassign are pending-only at
32+
their endpoints), and the same scope the viewer flag has always declared.
33+
34+
What does not change:
35+
36+
- The submitter's own recall of a `returned` request (the ADR-0044 revise
37+
window) is untouched; so is the submitter's recall of a `pending` one.
38+
- The override actor's recall of a `pending` request — the #3424 rescue of a
39+
request routed to an unstaffed position — is untouched, lock release and all.
40+
- The refusal's message, wire code and developer log line keep their shape; the
41+
log line now also names the request status it refused on.
42+
43+
Why: the gate now agrees with the viewer flag and the documented contract at
44+
one point instead of disagreeing with both. If a real operator workflow
45+
depended on override-recalling a `returned` request, this is the change to
46+
revert; the `returned` record lock is already released, so the stuck-record
47+
rescue motive that justifies the override does not apply on that status.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
"@objectstack/plugin-auth": patch
3+
---
4+
5+
fix(plugin-auth): bind the auth email locale to the workspace language, not the build-time default (#14319)
6+
7+
Auth mail (verification, password reset, invitation, magic link, email-change
8+
notice) picked its `sys_email_template` row from
9+
`II18nService.getDefaultLocale()` alone — the app artifact's **build-time**
10+
`i18n.defaultLocale`, which is the bare `en` unless the app declares otherwise.
11+
The workspace's **runtime** language, `localization.locale` (ADR-0053, Setup ▸
12+
Localization), was never consulted, even though the very same `kernel:ready`
13+
pass already reads it a dozen lines earlier to localize auth SMS, and the four
14+
options that setting offers are exactly the four locales the auth templates
15+
ship rows for.
16+
17+
A workspace that switched itself to Chinese therefore received Chinese OTP
18+
texts and English verification mail from one plugin, on one boot.
19+
20+
`AuthPlugin` now prefers `localization.locale` whenever the operator has
21+
**explicitly** set it (`ResolvedSettingValue.source !== 'default'`) — the same
22+
precedence the sibling `branding.workspace_name` binding uses — and keeps the
23+
build-time `i18n.defaultLocale` standing underneath it, so a deployment that
24+
declared one is not demoted to the manifest default `en-US`. Neither producer
25+
answering leaves the locale unnamed, which is `EmailService`'s documented
26+
`en-US` fallback. The binding live-rebinds on `localization` settings changes,
27+
exactly as the SMS one does.
28+
29+
The 2026-08-13 ruling is unchanged: the recipient locale is the deployment
30+
default, resolved at the plugin layer; `Accept-Language` stays rejected and
31+
there is still no per-user locale.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
'@objectstack/core': patch
3+
---
4+
5+
docs(core): the `AuthzStoreUnavailableError` brand doc states the measured `structuredClone` behaviour instead of claiming survival (#14006)
6+
7+
Documentation only — no runtime change, no type change, no accept/reject
8+
behaviour moves. It ships as a patch because the docblock is a **published
9+
byte**: `tsup`'s declaration rollup carries it into `dist/index.d.ts` and
10+
`dist/index.d.cts`, so it is what a consumer reads on hover.
11+
12+
The brand's docblock justified the string-keyed own property with two reasons
13+
joined by an `and`, of which only the second was true:
14+
15+
> A string-keyed own property (not a `Symbol.for` registry key) so it survives
16+
> `structuredClone`, and so a duplicated copy of this module still brands
17+
> identically.
18+
19+
Measured on Node 22.22.2: the structured-clone algorithm gives `Error` a
20+
dedicated serialization carrying `message`, `stack` and `cause` only, and drops
21+
every other own property — the brand, the ADR-0112 `code`, `status` and
22+
`object` alike (a subclass's own `name` returns as `'Error'`). The
23+
plain-object control is the half that proves it: `{ __brand: true, code: 'C' }`
24+
keeps **both** keys through the same call, so the loss is specific to `Error`,
25+
not general to `structuredClone`.
26+
27+
The property and the reason that actually earns it are kept — a duplicated copy
28+
of the module still brands identically, which is exactly what `instanceof`
29+
cannot do across two installed copies of `@objectstack/core`. The false half is
30+
replaced by the measured behaviour, carrying the reproducible script and the
31+
Node version rather than a second unsourced assertion, and phrased to match
32+
what `service-not-registered.ts` already records for its own brand (one
33+
phrasing across the two modules, not two).
34+
35+
⛔ The clone gap is deliberately NOT "fixed" with a `toJSON` or a custom
36+
serialization: no call site crosses a clone boundary today
37+
(`rethrowAuthzStoreUnavailable` on the rest rethrow paths,
38+
`isAuthzStoreUnavailableError` inside service `catch` blocks — all in-process),
39+
and adding one would widen the module's surface with nothing pulling on it. The
40+
docblock instead names the trap the false claim invited: branching on the brand
41+
across a worker or `postMessage` boundary would answer `false` and fail OPEN on
42+
a security path.

.changeset/console-67dadd602a3a.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
"@objectstack/console": minor
3+
---
4+
5+
Console (objectui) refreshed to `67dadd602a3a`. Frontend changes in this range:
6+
7+
Derived from the changesets objectui declared over the range — 16 releasing of 24 changesets added across 24 non-merge commits; omitted: 8 release-nothing changesets (they ship no package code).
8+
9+
- **minor** — A grouped grid now says, where the group counts are, that it grouped a **page** (objectui#7189). (objectui `8952395b4`)
10+
- **minor****Breaking for authored metadata:** `DataTableSchema.toolbar` is RETIRED (objectui#6881, maintainer ruling 2026-08-31). A `data-table` node that authors `toolbar` no longer valida… (objectui `3561bd2ca`)
11+
- **minor** — Retire `ChartDataSeries.data`, and correct `ChartSchema.categories`' prose to the read it has always had (objectui#6896, ADR-0049 enforce-or-remove; maintainer ruling 2026-08-31). (objectui `b0d308da9`)
12+
- **minor****BREAKING** — Retire the form-view section `className` / `gridClassName` reads (objectstack#13626, maintainer ruling 2026-09-01, director decision batch C). (objectui `9c7490268`)
13+
- **minor**`page:header` resolves its `actions` as declared ACTION IDS (objectui#6252, implementing the objectstack#11592 ruling — maintainer, 2026-08-25, on recommendation B). (objectui `8ec11e14f`)
14+
- **patch** — FLS-gate the `$expand` projection at both build sites (objectui#7215). (objectui `67dadd602`)
15+
- **patch** — Gantt toolbar: the period label names the visible window, and the prev/next buttons step it (objectui#7203). (objectui `231d1b93c`)
16+
- **patch** — fix(app-shell,plugin-list): a list view's own `description` now reaches the screen (objectui `f626808d4`)
17+
- **patch** — A gantt list view no longer shows the record-count bar, because the bar describes a request that view does not draw (objectui#7210, half 1). (objectui `5015fcf52`)
18+
- **patch** — Fix: a grid grouped by a field it does not also show as a column no longer collapses every row into one `(empty)` group (objectui#7179). (objectui `a6d8b8d44`)
19+
- **patch** — Stop shipping `dist/__tests__/numberInputBrowserReadings.d.ts` in the published tarball (objectui#6943). `packages/fields/tsconfig.json` now excludes the tooling DIRECTORIES (`__t… (objectui `39d69ad53`)
20+
- **patch** — Scatter now says when it cannot place a row, instead of drawing an empty axis. (objectui `93bbc2055`)
21+
- **patch** — Fix: a `dependsOn` lookup column is no longer permanently uneditable in an editable `ObjectGrid`. (objectui `84ffdbcbb`)
22+
- **patch**`ObjectGrid` no longer copies `descriptionField`, `lookupColumns` or `lookupFilters` onto a relational column's `fieldMeta` (objectui#7166). No behaviour change — all three still… (objectui `a276480b7`)
23+
- **patch**`RecordComments` and `PointInTimeRestore` resolve their copy from the locale packs instead of hardcoded English (objectui#7163). (objectui `866cd1d3f`)
24+
- **patch** — Pie, donut, funnel and treemap now say when rows carry no magnitude they can draw. (objectui `5eddeeb68`)
25+
26+
⚠️ 2 of these carry a breaking change: 2 by the author's own breaking annotation in the changeset body — objectui declares no `major` inside a launch window (`scripts/check-changeset-no-major.mjs`). Each is marked **BREAKING** in the list above — read them before compiling the release record.
27+
28+
<!-- adr-0087: not-required (no-migration-prescription) Both declared-breaking entries in this range are retirements inside objectui's OWN npm packages, judged one at a time against their upstream changesets rather than as a batch. objectui `3561bd2ca` (objectui#6881) retires `DataTableSchema.toolbar` in `@object-ui/types` — a key on the SDUI `data-table` component node that the `data-table` renderer never read (declared on both published faces, mounted by nothing), now refused at parse time with a remediation. `@objectstack/spec` declares no `data-table` node schema at all (re-measured at this HEAD: zero hits for `data-table` / `DataTable` across `packages/spec/src/**/*.zod.ts`), so the key was never an ObjectStack-authorable metadata key and no stored `sys_metadata` row can carry it. objectui `9c7490268` (objectstack#13626, maintainer ruling 2026-09-01) stops `@object-ui/plugin-form` reading `className` / `gridClassName` off a form-view SECTION through `as any` casts. Those two keys sit on the SDUI-only side of the authorable boundary by this repo's own decision: `packages/spec/src/ui/component.zod.ts` deliberately does not declare `className` on props bags, `gridClassName` has zero hits anywhere under `packages/spec/src` (re-measured at this HEAD), and the authorable-surface ledger carries no entry for either — so the change is a RENDERER ceasing to consume keys the spec never admitted, in the direction the boundary already pointed; the upstream census found zero authored uses across the objectstack, objectui and hotcrm corpora. Neither is reachable through `@objectstack/console` in any case, re-measured against `packages/console/package.json` at this HEAD: it publishes a frozen prebuilt SPA whose `files` list is `["dist", "README.md", "CHANGELOG.md"]` and whose sole `exports` entry is `./package.json`, so it forwards no `@object-ui/*` module entry point and re-exports none of these types. This diff is `.objectui-sha` plus this changeset and nothing else — no `packages/spec` schema, authorable metadata key or protocol surface changes in it — so there is no stored-metadata rewrite for `objectstack migrate meta` to prescribe and therefore no ADR-0087 ledger entry to write or to name from THIS bump. This bump adds no ledger entry and claims none. -->
29+
30+
objectui range: `d8ec8d6d4f01...67dadd602a3a`
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
"@objectstack/core": minor
3+
---
4+
5+
feat(core): `Plugin.type` is the closed set the spec declares — a `PluginType` derived from `CORE_PLUGIN_TYPES` (#13925)
6+
7+
**BREAKING** accept-set narrowing on a published type, shipped as `minor`
8+
under the repo's launch-window convention for breaking changes. `Plugin.type`
9+
(and, through it, `PluginMetadata.type`) was declared `string`, so nothing
10+
type-checked a plugin author against the eight values the platform accepts —
11+
the TSDoc beside it carried the whole enumeration as prose, and prose drifted.
12+
Maintainer ruling 2026-09-01: the Zod enum in `@objectstack/spec`
13+
(`PluginSchema.type`, declared `z.enum(['standard', ...CORE_PLUGIN_TYPES])`)
14+
is the authority and the contract was always a closed set; the `string` in
15+
core was the mismatch, and narrowing it is core aligning to the declared
16+
contract rather than a new restriction. Paid in one stroke — no warning window.
17+
18+
What changes:
19+
20+
- `@objectstack/core` now exports `PluginType`, derived from the spec's own
21+
constant: `'standard' | (typeof CORE_PLUGIN_TYPES)[number]` — today
22+
`standard`, `ui`, `driver`, `server`, `app`, `theme`, `agent`, `objectql`.
23+
It is not re-spelled in core, so the compiler's accept set and the Zod gate's
24+
cannot drift apart; a runtime parity test pins the two against each other.
25+
- `Plugin.type` is typed `PluginType`. A literal outside the set, or a value
26+
typed `string`, no longer compiles. Runtime behaviour is unchanged: the Zod
27+
gate refused such a value before and still does (`invalid_value` at `type`).
28+
29+
**Migration.** A plugin that declares one of the eight members needs no change.
30+
A plugin that assigned a computed or `string`-typed value narrows it at the
31+
producer — declare the literal, or type the variable `PluginType` — rather than
32+
casting at the assignment; a value that was never one of the eight was never a
33+
valid plugin type and was already refused at parse time.
34+
35+
<!-- adr-0087: not-required (no-migration-prescription) A TypeScript narrowing on a published runtime interface, aligning `packages/core` to the accept set `packages/spec` already declared. No metadata key, spec symbol, Zod schema, object definition or stored representation is added, removed or renamed — `CORE_PLUGIN_TYPES` and `PluginSchema.type` are read, not changed — so `objectstack migrate meta` has nothing to rewrite and there is no tombstone to mint. The channel that reaches an affected author is the compiler, at the assignment, which is more precise than a ledger line; which member a formerly `string`-typed value should become is authoring intent no migration entry can decide. The in-repo census under the workspace typecheck is recorded on the PR. -->
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/cli": patch
3+
---
4+
5+
fix(cli): close the declaration-boot write guard's two named boundaries — engine-held drivers and immediate DDL (#14126)
6+
7+
`os migrate plan` / `os migrate apply` boot host plugins for their declarations behind a guard that refuses the contract's row writes and, since #13332 / #14053, prints "a plan writes nothing" only when that held. Two boundaries were left open and named in the guard's own census; both are now covered, under ONE outcome-line rule:
8+
9+
- **Engine-held drivers.** Only the default datasource is published as `driver.*`; every other driver reaches the engine through `engine.registerDriver` alone (`DatasourceConnectionService.connect()`, `AppPlugin`'s `drivers.register`, `ObjectQL.create`), so a hook writing to an object bound to a second datasource landed during a plan. The guard now shadows `registerDriver` on the engine instance the kernel publishes (`objectql` / `data`) for the length of the boot, arms each driver instance in place as it is registered — forwarding the SAME instance, never a wrapper, never a second registration under a held name — reaches drivers the engine already held through its public accessors, and restores the engine on `disarm()`. Such a write is now refused and reported as `via engine.<datasource>`.
10+
- **Immediate DDL.** `dropTable()` / `rotateShards()` are not held back by the schema deferral and execute immediately. They are still not refused (refusing DDL an operator's own hook asked for is out of this guard's scope) — but they now get `execute()`'s treatment: forwarded, counted per driver/method/object, warned once per driver on stderr, named in the notes, and the run no longer claims "a plan writes nothing".
11+
12+
The rule, decided once: the claim prints only when it held across everything the guard can see — every write refused, nothing forwarded (raw `execute()`, immediate DDL), and no instance that refused the override (a frozen driver, an engine that could not be shadowed). Each of those is named in the notes and withholds the line. An embedder with no data plane, and read/log-only hooks, are untouched: a quiet boot still renders byte-identically.

0 commit comments

Comments
 (0)