You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 1b00a88
Browse filesBrowse the repository at this point in the historyBrowse files
Ruling C ("an anonymous submission carries no file") now holds at every anonymous write door of ADR-0138 D2, not only at the public form door. A record write whose caller has no user, such as the guest an `authRequired: false` endpoint executes as under the grants a deployment binds to the guest, may not put a value in a file field (`file`, `image`, `avatar`, `video`, `audio`). It is refused before anything is written, with the form door's envelope: `400 VALIDATION_FAILED`, one `fields[]` entry per refused field carrying the ADR-0114 field code `invalid_value` and the field's label. An empty or absent file field is written as before. Signed-in callers, system writes and context-less programmatic writes are unchanged. No error code is added.
8
+
9
+
Clause-②: no (narrowing) — an anonymous write that carried a value in a file field is refused. Nothing that was refused becomes accepted.
10
+
11
+
Metadata-protocol: none (a write path's file rule; nothing a metadata author writes changes)
12
+
13
+
<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling, object definition, stored row or stored shape is added, removed, renamed or re-shaped, and no exported declaration is removed or narrowed, so there is nothing for `objectstack migrate meta` to rewrite. What changes is which record writes the file-reference path accepts (an anonymous caller's write carrying a file value is refused). The remedy is a change of who provides the file (a signed-in caller), never a rewrite of a package source or a stored row. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
14
+
15
+
**BREAKING** for an anonymous write that carried a file value, shipped as `patch` because the metadata protocol does not move.
16
+
17
+
-**Before.** Only the public form door refused a file value from an anonymous caller.
18
+
-**Now.** Every write whose caller has no user is refused with `400 VALIDATION_FAILED` when a file field carries a value.
19
+
-**Fix.** Leave file fields empty in an anonymous write. A file is provided by a signed-in caller, through the authenticated upload doors.
feat(install): a package that declares an installed app in `manifest.dependencies` may share that app's namespace, and a boot hydrates installed packages in dependency order (ADR-0141 D5)
10
+
11
+
Clause-②: yes (narrowing)
12
+
Metadata-protocol: none
13
+
14
+
<!-- adr-0087: not-required (no-migration-prescription) No key of any metadata schema is added, removed, renamed or re-shaped: `manifest.dependencies` keeps its record shape and meaning. The one narrowing is an install-local acceptance detail of the not-yet-released ADR-0141 D2 check (an artifact wrapper's own id no longer counts as a delivered package), plus the matching removal of the unreleased `DeclaredDependencyCheck.deliveredIds` option; there is nothing for `objectstack migrate meta` to rewrite and no tombstone. -->
15
+
16
+
**Namespace co-ownership by declaration.** The install gate (`SchemaRegistry.installPackage`) used to admit packages into one namespace only when one release artifact delivered them together. A module shipped as its own artifact that uses its app's namespace was refused `422 NAMESPACE_CONFLICT` beside the installed app, whatever it declared. Now an installed package P co-owns namespace N with:
17
+
18
+
- an installed X that P declares in `manifest.dependencies`, when both use N;
19
+
- a sitting owner of N that declares P, which is the same relation read from the other end, so an upgrade or reinstall of the app beside its modules is admitted;
20
+
- a sibling S, when S and P each declare one installed package that holds N (two modules of one app, neither declaring the other).
21
+
22
+
Only direct declarations count. The shared parent must be installed and must hold N itself.
23
+
24
+
The object-name check reads the same co-owner set. A co-owner by declaration that defines an object name another co-owner already owns is refused `422 DUPLICATE_ARTIFACT_OBJECT_NAME` before any DDL. Examples: a module redefining its app's object, a sibling redefining the other sibling's object, or an app upgrade newly defining a name one of its modules owns. The message is worded per source (same artifact, declared dependency, sibling). `NAMESPACE_CONFLICT` now names the declaration as the fix for a package that extends the app holding the namespace.
25
+
26
+
**Hydrate order.** The `sys_packages` hydrate and the install-local `kernel:ready` rehydrate used to register in source-listing order. Now they sort their batch by `manifest.dependencies`, with declared ids as optional edges, through `resolvePluginOrder`. Before this, a module that hydrated before the app whose namespace it shares could be refused and stay absent for the process lifetime. In the install-local rehydrate, each ledger entry is one node, so a declared id resolves to the entry that carries that package. A dependency cycle never stops a hydrate: its members keep source order and an error names them.
27
+
28
+
**BREAKING** (an install-acceptance narrowing of the unreleased ADR-0141 D2 check) at `POST /api/v1/marketplace/install-local`. A multi-package artifact's own wrapper id no longer counts as delivered unless one of its packages carries it. A module declaring the wrapper id is judged like any other entry, because it would be `absent` on its first standalone install. `DeclaredDependencyCheck.deliveredIds` is removed with it. Also, an undecided range union whose installed version is readable is now reported `found: 'unreadable-range'` instead of `'unreadable-version'`; for example, `<5 || latest` against `7.0.0`.
29
+
30
+
**What changes for you.**
31
+
32
+
- A module that extends an app and uses its namespace declares the app in `manifest.dependencies`, and then installs beside it as its own artifact. Two modules of one app need not declare each other.
33
+
- Every package of the app's own artifact that uses the namespace should declare the app too.
34
+
- New exports: `@objectstack/core` exports `orderHydrationBatch` and `declaredDependencyIds`.
fix(service-automation): a `notify` node naming `email` and another channel mails an address no user holds and no longer writes an inbox row addressed to it
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
An email address written out in `recipients` is the author's constant: on a node whose `channels` name `email` it is mailed as written. The inbox delivers to a user, keying its row by the recipient as a user id, and so does every channel but `email`. So on a node whose `channels` name `email` and another channel, such as `['inbox', 'email']`, an address no user holds now goes to the email channel alone.
11
+
12
+
-**Fixed.** Before, the address went to every channel as it stood: the email channel mailed it, and the inbox also wrote a `sys_inbox_message` row whose `user_id` was the address, which no inbox reads. Now the node emits that address apart, on the `email` channel only, and the inbox writes no row for it. `user:ADDRESS` is handled the same way, whoever holds the address, because `user:` hands the address on as the user id with no lookup. An address whose holder cannot be looked up (no data engine, a lookup that fails) is mailed alone too.
13
+
-**Unchanged.** The node succeeds as before. An address a user holds, written bare, still reaches that user's inbox and is mailed. A value expression that evaluates to an address on a node naming `email` still resolves to the user who holds it, or fails the node pointing to `{ emailField }`. A node that does not name `email` still fails on an address no user holds.
14
+
-**What a run records.** When a node's recipients mix users and such addresses, the node writes two `sys_notification` events: one for the users on the node's `channels`, which the node's `notificationId` output names, and one for the addresses on `email` alone. The `delivered`, `enqueued` and `failed` outputs add up both.
15
+
-**Who moves.** Nobody needs to change a flow. A report or audit over `sys_inbox_message` no longer sees rows whose `user_id` is an address.
fix(app-shell): the flow inspector's list field shows a stored string as one entry instead of an empty list
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
A notify node's `channels` may be a single string (`channels: 'email'`) or a list; the spec accepts both and the runtime reads the string as a one-item list. Studio's flow inspector read any non-list value as empty, so the field showed no channels and adding one saved a list without `email`. A stored non-blank string now reads as a one-entry list (trimmed, as the runtime does). An edit saves the list form (`['email', 'sms']`); a field left untouched keeps the stored string.
at the commit pinned in `.objectui-sha` into `packages/console/dist`
257
-
(gitignored). It builds from a `../objectui` checkout if one sits next to this
258
-
repo, otherwise from a shallow clone of objectui into `.cache/`; either way it
259
-
installs objectui's dependencies, so it needs network. Skip it and `pnpm dev`
260
-
still serves the REST API, without the Console. Rerun it when `.objectui-sha`
261
-
moves.
255
+
`pnpm console:build` builds the Console from its source in this workspace
256
+
(`ui/apps/console`, against this tree's own spec and client) and copies the
257
+
result into `packages/console/dist` (gitignored), which is where `pnpm dev`
258
+
serves it from. Nothing is cloned or pinned. `pnpm build` builds the Console
259
+
too but does not copy it, so after `pnpm build` this step is a copy. Skip it
260
+
and `pnpm dev` still serves the REST API, without the Console. Rerun it after
261
+
pulling changes under `ui/`: `packages/console/dist` is never refreshed on its
262
+
own.
262
263
263
264
A first run is slow, not stuck. Measured once on a fresh clone of `main`
264
265
(4-vCPU container, Node 22.22, pnpm 10.31):
@@ -267,7 +268,7 @@ A first run is slow, not stuck. Measured once on a fresh clone of `main`
267
268
|:---|---:|:---|
268
269
|`pnpm install`| 16 s ||
269
270
|`pnpm build`| 6 m 54 s | 72 turbo tasks |
270
-
|`pnpm objectui:build`|10 m 36 s |clones objectui and builds the Console |
271
+
|`pnpm console:build`|12 m 16 s |72 turbo tasks: the Console and what it depends on, run before any `pnpm build` (`--concurrency=2`); a cache hit plus a copy, about 1 s, once `pnpm build` has run|
271
272
|`pnpm test`| 54 m 03 s | the full suite: 135 turbo tasks |
272
273
273
274
For your own change, test what it reaches instead of the full suite:
Copy file name to clipboardExpand all lines: content/docs/deployment/app-dependencies.mdx
+38-8Lines changed: 38 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -40,9 +40,12 @@ The declaration does two things:
40
40
reference, not the data: users still see the other app's records under that
41
41
app's own permission sets and sharing rules.
42
42
-**It is checked before install** — below.
43
+
-**It lets your package share the depended-on app's namespace** — below.
43
44
44
45
Every entry is required; there is no optional arm. Declare only the packages
45
-
your app actually references.
46
+
your app actually references. Two modules of one app need not declare each
47
+
other: each declares the app, and that is enough for both to install, in either
48
+
order, and to share the app's namespace.
46
49
47
50
## The version-range grammar
48
51
@@ -125,7 +128,11 @@ The check runs at those install acts only. It does not run:
125
128
126
129
-**at boot.** A restart re-registers what an install already admitted, while
127
130
the registry is still filling; it never refuses a package for a dependency it
128
-
has not seen yet.
131
+
has not seen yet. It does register them in dependency order: the stored
132
+
packages, and the apps installed through `os package install`, are each sorted
133
+
so a package registers after the packages it declares. Two packages that
134
+
declare each other are a cycle: they register in stored order for that boot,
135
+
and the server logs an error naming them.
129
136
-**at `PATCH /api/v1/packages/:id`.** Editing an authored package's
130
137
`dependencies` after install is applied as given; an entry added there is
131
138
judged when the package is next installed.
@@ -134,12 +141,35 @@ The check runs at those install acts only. It does not run:
134
141
135
142
## Sharing a namespace with the app you depend on
136
143
137
-
Each package's namespace must still be its own across artifacts: a package that
138
-
uses the namespace of the app it depends on is refused at install with
139
-
`NAMESPACE_CONFLICT`, exactly as before. Inside one artifact, packages may share
140
-
a namespace ([ADR-0130](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0130-release-artifact-as-co-ownership-boundary.md)).
141
-
Dependency-declared namespace co-ownership across artifacts (ADR-0141 D5) is
142
-
not delivered yet.
144
+
A namespace is otherwise one package's: a package using a namespace another
145
+
installed package holds is refused at install with `NAMESPACE_CONFLICT`. Two
146
+
relations make packages co-owners of one namespace instead:
147
+
148
+
-**the same artifact** delivers them ([ADR-0130](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0130-release-artifact-as-co-ownership-boundary.md)); or
149
+
-**a declared dependency**: your package declares an installed app that uses the
150
+
same namespace — or another installed package that declares that same app
151
+
uses it too (two modules of one app).
152
+
153
+
So a module that ships as its own artifact, declares `app.objectstack.hotcrm`
154
+
and uses `crm` installs beside HotCRM, and a second such module installs beside
155
+
both. The relation is one-way — the app declares nothing — and it is read from
156
+
either side, so upgrading or reinstalling the app beside its modules is
157
+
admitted too. A package that shares the namespace without declaring the app is
158
+
still refused, and the fix the error names is the declaration.
159
+
160
+
Sharing a namespace never means sharing an object name. A package defining an
161
+
object another co-owner already defines — your module redefining
162
+
`crm_account`, two modules both defining `crm_invoice`, or an app upgrade newly
163
+
defining a name one of its modules owns — is refused at install, before any
164
+
table is touched, with `422 DUPLICATE_ARTIFACT_OBJECT_NAME` naming both
165
+
packages and the object. Rename the object, or `extend` the other package's
166
+
object instead of owning a second one.
167
+
168
+
Every package of the depended-on app's own artifact that uses the namespace
169
+
should declare the app too (inside the artifact it costs nothing: it is judged
170
+
for presence only). The platform does not record which artifact an installed
171
+
package came from, so an undeclared companion is a sitting owner your module
172
+
cannot co-own with.
143
173
144
174
## Build-time validation cannot see a dependency's objects
0 commit comments