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 7eb5576
Browse filesBrowse the repository at this point in the historyBrowse files
feat(spec,metadata-protocol): a seed dataset column can be written only when its row is created (`columns.FIELD.applyOn: 'create'`)
8
+
9
+
Clause-②: yes
10
+
Metadata-protocol: added
11
+
12
+
A seed is replayed on every dev-server boot and every package re-publish, and an `upsert` replay re-asserts every column a record declares on the row it matched. That reverts the columns an app's own jobs write after the first seed (a status a payment job advances, a flag a renewal job sets), so a re-run undid the jobs' work with nothing in the log.
13
+
14
+
A seed dataset now takes a per-column write policy, keyed by field name:
-**`applyOn: 'create'`** writes the column when the seed creates the row and leaves it out of every write to a row the seed matched by its `externalId`: an `upsert` replay over an existing row, and every row of an `update` dataset. The matched row keeps the value it holds, while the `always` columns are refreshed. A row whose only differences are create-only columns is not written at all. A create-only reference column on a matched row is not resolved and not deferred to pass 2, so nothing later in the load writes it back. A column of the `externalId` is the key the row was matched by and stays in its update. `insert`, `ignore` and `replace` never update a matched row, so nothing changes on those modes.
28
+
-**`applyOn: 'always'`** (the default, and every column without a `columns` entry) is the behaviour before this change.
29
+
- The values, the default and the meaning are those of the import door's `FieldMappingEntry.applyOn`. Both doors now read one enum, exported as `ColumnApplyOnSchema` from `@objectstack/spec/data`. `SeedColumnSchema` (with `SeedColumn` / `SeedColumnParsed`) is the per-column shape.
30
+
- A `columns` key must name a column the object has: a declared field, or a system column the platform injects on it such as `owner_id`. `defineSeed` refuses one that does not when it runs (and at compile time for an object literal), naming the key with a near miss. The seed loader logs an `error` naming the column, once per dataset, whenever it loads a seed that names one, since a stored seed never passes through `defineSeed`. The rows still load.
31
+
-`SeedSchema` refuses an unknown key on a column (`unrecognized_keys`), and a dataset-level `applyOn` is pointed at `columns`.
32
+
33
+
`@objectstack/skills` only regenerates its schema reference indexes, which now list `SeedColumnSchema` and `ColumnApplyOnSchema`.
feat(spec,cli): reports are translatable — `reports.<name>` in the translation bundle
12
+
13
+
Clause-②: yes
14
+
Metadata-protocol: added
15
+
16
+
An app authored in one language listed its reports (`reports[].label`, `reports[].description`) in that language on the app's report index and the report page heading. Before this change the translation bundle had no key for them: `reports` was a retired object-first dialect key, so a bundle carrying it was refused by `os validate`, and `os i18n check` could not report the gap.
17
+
18
+
- **`@objectstack/spec`:** `TranslationData` (and the registered `translation` item and the platform bundle, which share its groups) gains `reports`, keyed by the report's `name`, carrying `label` and `description` (strict: any other key is refused — `name` or `title` with a message pointing at `label`, and the singular spelling `report` with a message pointing at `reports`). `reports` leaves `LEGACY_OBJECT_FIRST_KEYS`, so a stored translation row carrying it now loads through the authored-row sync instead of being skipped as a retired dialect; the key's measured footprint in the legacy dialect was zero (#3778). A new translator, `translateReport`, overlays the two strings and is registered in the metadata-document dispatch, so `GET /meta/report` (list and item) is served in the request's locale. A key the bundle does not carry keeps the authored string. A report is overlay-editable (ADR-0126 Regime O), so the translator honours the packaged base: a label or description an org re-authored is served as written (ADR-0029 D9.2a), and the strings it did not touch are still translated.
19
+
-**`@objectstack/metadata-protocol`:**`getPackagedReportBase(name)` returns the shipped report document beneath any overlay, the base the REST read path hands the translator.
20
+
-**`@objectstack/rest`:** the meta read path supplies that base for `report`, as it does for `dashboard` and `view`.
21
+
-**`@objectstack/lint`:**`translation-target-unknown` reports a `reports.<name>` key that names no report a package of the stack declares, as an error with the nearest declared name.
22
+
-**`@objectstack/cli`:**`os i18n extract` scaffolds `reports.<name>.label` / `.description` from the stack's `reports[]`, and `os i18n check` / `os lint` coverage report an untranslated one in its own bucket (`i18n/missing-report`).
23
+
-**`@object-ui/app-shell`, `@object-ui/i18n`:** the translation designer's preview draws the `reports` group (a "Reports" / 「报表」 category beside the others), and the console's translation loader recognises a payload that carries only `reports`.
24
+
25
+
Example (`zh-CN` bundle of an English-authored app):
fix(service-automation): a refusal a flow's write node meets is worded in the language of the person who started the run, as their own record save is
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
A validation rule tripped through a record door (`POST` / `PATCH /api/v1/data/...`, batch, import) answers in the caller's locale: the engine looks the authored `message` up at `objects.OBJECT._validations.RULE.message` in the app's bundle. The same rule tripped by a flow's `create_record` or `update_record` answered in the authored English, and a `DELETE_RESTRICTED` refusal met by `delete_record` named its objects by their English labels — so a `FLOW_FAILED` from `POST /api/v1/actions/...` carried an English `innerRefusal.message` on a request whose screens rendered in Chinese.
11
+
12
+
The run already knew the language (`AutomationContext.locale`, seeded by the `/actions` and trigger doors); `resolveRunDataContext` dropped it from the context its data nodes hand the engine. It now carries `locale` on both `runAs` arms, and the engine's one translation hook does the rest. Nothing new translates anything: a run no person started has no locale and keeps the authored text.
13
+
14
+
What to do: nothing. Ship your translations under `objects.OBJECT._validations.RULE.message` as you already do for the record doors; a flow write now reads the same entry.
(`"account"`, `"fields"`, `"list"`, `"industries"`) is rejected **by name**
405
405
at both authoring doors, with the group to use instead named in the
@@ -483,7 +483,7 @@ what the overlay writes back as the node's `title`.
483
483
The contract is `t(key, locale, params?)` — the target locale is always passed
484
484
explicitly, and the key itself is returned when no translation is found (there
485
485
is no `defaultValue` option). The key is a **dot path into `TranslationData`**,
486
-
so it starts at one of the thirteen declared groups.
486
+
so it starts at one of the fourteen declared groups.
487
487
488
488
```typescript
489
489
const i18n =kernel.getService('i18n');
@@ -1063,6 +1063,7 @@ beneath an action entry are judged the same way under either.
1063
1063
|`flows.{flow}`| a flow's machine `name`, declared |
1064
1064
|`positions.{name}`| a position's `name`, declared, or an audience anchor (`everyone`, `guest`) |
1065
1065
|`permissions.{name}`| a permission set's `name`, declared |
1066
+
|`reports.{name}`| a report's `name`, declared |
1066
1067
|`flows.{flow}.screens.{node}`| the `id` of a `type: 'screen'` node in that flow, nodes nested in loop, parallel and try/catch regions included |
1067
1068
|`flows.{flow}.screens.{node}.fields.{field}`| a `config.fields[].name` on that screen — an object-form screen (`config.objectName`) declares none, and its input labels live under `objects.{objectName}.fields`|
|**configuration**|`never`| optional |[REMOVED]`manifest.configuration` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, `properties.*.secret` promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in `defineStack({ plugins: [new MyPlugin({ … })] })`, which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
|**extensions**|`never`| optional |[REMOVED]`manifest.extensions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: `contributes.kinds` registers metadata kinds, `navigationContributions` injects navigation into other packages' apps, and code-level extension happens in the plugin itself (`init`/`start`). |
105
105
|**navigationContributions**|`{ app: string; group?: string; priority?: integer; items: (object \| … +9 more)[] }[]`| optional | Navigation items this package contributes into apps owned by other packages |
@@ -281,7 +281,7 @@ This schema accepts one of the following structures:
|**configuration**|`never`| optional |[REMOVED]`manifest.configuration` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, `properties.*.secret` promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in `defineStack({ plugins: [new MyPlugin({ … })] })`, which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
|**capabilities**|`never`| optional |[REMOVED]`manifest.capabilities` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no discovery path ever consulted the block: nothing read `implements`, `provides`, `requires`, `extensionPoints` or `extensions`, so the declared "interoperability and automatic discovery" never happened. Delete the key. Real dependency resolution runs off top-level `manifest.dependencies`, which stays. Capability-based discovery must be designed with an enforcing reader first, not revived here. |
286
286
|**extensions**|`never`| optional |[REMOVED]`manifest.extensions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: `contributes.kinds` registers metadata kinds, `navigationContributions` injects navigation into other packages' apps, and code-level extension happens in the plugin itself (`init`/`start`). |
287
287
|**navigationContributions**|`{ app: string; group?: string; priority?: integer; items: (object \| … +9 more)[] }[]`| optional | Navigation items this package contributes into apps owned by other packages |
@@ -345,7 +345,7 @@ This schema accepts one of the following structures:
|**configuration**|`never`| optional |[REMOVED]`manifest.configuration` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, `properties.*.secret` promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in `defineStack({ plugins: [new MyPlugin({ … })] })`, which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
|**extensions**|`never`| optional |[REMOVED]`manifest.extensions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: `contributes.kinds` registers metadata kinds, `navigationContributions` injects navigation into other packages' apps, and code-level extension happens in the plugin itself (`init`/`start`). |
351
351
|**navigationContributions**|`{ app: string; group?: string; priority?: integer; items: (object \| … +9 more)[] }[]`| optional | Navigation items this package contributes into apps owned by other packages |
0 commit comments