Skip to content

Commit 14e5287

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-21528-resume-plan-identity
2 parents ae27f00 + 24dc7c1 commit 14e5287

52 files changed

Lines changed: 2091 additions & 482 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.

‎.changeset/21412-metadata-protocol-save-door-container-name.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ Clause-②: no (narrowing)
1616

1717
**The fix.** Drop the body's `name` (the door stamps the save name), or set it to the name the container is saved under.
1818

19-
Not judged here: a standalone view record (`viewKind`) and every other metadata type.
19+
A standalone view record (`viewKind`) and every other metadata type are judged too, by the same release's every-type refusal at the save, restore and publish doors (its own entry).

‎.changeset/21412-metadata-view-container-name-judge.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ One judge for a view container's own `name` at every door that files a container
66

77
Clause-②: yes
88

9-
- New subpath `@objectstack/metadata/view-container-name`. It exports `viewContainerNameRefusal(container, sourceLabel, ownerId)`, the source registrars' entry, whose key is the object the container binds to (its own `object`, else `list.data.object` / `form.data.object`). It also exports `savedViewContainerNameRefusal(container, saveName)`, the runtime save door's entry, whose key is the name the row is saved under, and the `ViewContainerNameRefusal` type. Both return a `VALIDATION_ERROR` / 400 refusal for an aggregated view container whose own `name` is set and differs from that key, and `undefined` otherwise. A container with no `name`, and a standalone view record (`viewKind`), are not judged.
9+
- New subpath `@objectstack/metadata/view-container-name`. It exports `viewContainerNameRefusal(container, sourceLabel, ownerId)`, the source registrars' entry, whose key is the object the container binds to (its own `object`, else `list.data.object` / `form.data.object`). It returns a `VALIDATION_ERROR` / 400 refusal for an aggregated view container whose own `name` is set and differs from that key, and `undefined` otherwise; a container with no `name`, and a standalone view record (`viewKind`), are not judged by it. The subpath also exports `savedItemNameRefusal(type, item, saveName, door)`, the runtime write doors' entry, which judges every metadata type against the name the row is written under, and the `ViewContainerNameRefusal` type both entries return.
1010
- The artifact/HMR loader's container branch now refuses such a container through the judge, before it files anything. What it refuses and the envelope are unchanged (`VALIDATION_ERROR` / 400). The message is now the judge's, the words the ObjectQL boot loop and `os validate` print, where it was the generic `IMetadataService.register` contract's.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/metadata-protocol': minor
3+
---
4+
5+
Every runtime door that writes a metadata row refuses a body whose own `name` disagrees with the row's name, for every metadata type
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) A validity narrowing at the runtime write doors over an existing key: no `name` key of any metadata schema is removed, renamed or re-shaped, so there is no tombstone and nothing mechanical for `objectstack migrate meta` to rewrite. Which of the two names a divergent body meant (its own, or the one it was saved under) is authoring intent no conversion entry can decide. The at-rest census found no such row: 0 `sys_metadata` and 0 `sys_metadata_history` rows in the four bootable example apps, booted with their seeds; the hosted-tenant shape was not measured. New writes are refused with the remedy; a row stored before this change keeps its bytes and stays readable. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers this rule and this diff adds none (not registered / already-registered); and the change narrows what runtime write doors accept, not a runtime interface or a type surface alone (not runtime-interface-only / type-surface-only). -->
10+
11+
**BREAKING** accept-set narrowing at the runtime write doors, shipped as `minor` under the repo's launch-window convention for breaking changes, the grade the view-container half of this refusal takes in the same release.
12+
13+
**What was accepted before.** The runtime stores a metadata row under the name the request names and registers its body under the body's own `name`. These doors accepted a body whose `name` was not the row's, so the row answered under a name nobody saved it under, and under none by its own:
14+
15+
- `saveMetaItem`, which `PUT /api/v1/meta/:type/:name` and the dispatcher's metadata save both call, for every type but a view container (a dashboard saved as `dash_a` with `name: 'dash_b'` registered as `dash_b`; a record view saved as `crm_lead.mine` with `name: 'crm_lead.other'` registered as `crm_lead.other`);
16+
- `rollbackMetaItem` and `revertCommit`, which wrote such a stored history version back as the active row without passing `saveMetaItem`;
17+
- `publishMetaItem` and `publishPackageDrafts`, which promoted such a stored draft the same way.
18+
19+
**What is refused now.** Each of those bodies, with `VALIDATION_ERROR` / 400, before anything is stored or registered, through the judge the view-container refusal already used (`savedItemNameRefusal`, `@objectstack/metadata/view-container-name`). `rollbackMetaItem` and `publishMetaItem` throw it. `revertCommit` reports the item in `failed[]` with `code: 'VALIDATION_ERROR'`. `publishPackageDrafts` aborts the batch on it, as it does on any refused draft: nothing in the batch is published. A body with no `name` is accepted as before. A `name` the body carries is judged whatever its value; a `translation` saved with `name: ''`, which its schema accepts, is now refused instead of being registered under the empty string. A view at the save door is the exception: a missing or empty view `name` is still stamped with the save name. A `field` written through the `OS_METADATA_WRITABLE` operator hatch is accepted only without a body `name`: its row is named `object.field`, which the column `name` cannot spell, and registered it answered under the column name alone. Where the type's schema already refused such a body (an empty or non-string `name` on most types, any `name` on a `seed`, whose schema declares none), the answer is now this refusal (`VALIDATION_ERROR` / 400) instead of the schema's `INVALID_METADATA` / 422; nothing is stored either way.
20+
21+
**The fix.** Set the body's `name` to the name you save it under, or save the item under the body's own `name`; for a view or a `field`, dropping `name` works too. To bring back a version or a draft that carries another `name`, save the item again with that fix, and publish that save if it is a draft.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/metadata': minor
3+
---
4+
5+
The runtime write doors' `name` judge covers every metadata type: `savedItemNameRefusal` replaces `savedViewContainerNameRefusal` on `@objectstack/metadata/view-container-name`
6+
7+
Clause-②: yes
8+
9+
- `savedItemNameRefusal(type, item, saveName, door)` is the one entry the runtime write doors of `@objectstack/metadata-protocol` call. It returns a `VALIDATION_ERROR` / 400 refusal when a body of any type carries its own `name` and that `name` differs from the name the row is written under, and `undefined` otherwise. `door` is `'save'`, `'restore'` or `'publish'`. A body with no `name` passes. A `name` the body does carry is judged whatever its value (`''`, `null` and non-strings included), with one exception: a `view` at the `'save'` door, which stamps a missing name there, is judged only on a non-empty string `name`.
10+
- It replaces `savedViewContainerNameRefusal(container, saveName)`, which judged view containers only. That export was added to this subpath in this same release cycle and never shipped in a published version, so no published export is removed.
11+
- The words name the type and give a remedy that works for it: "drop `name`, or set it to KEY" for a view, whose missing name the save door stamps; "drop `name`" for a `field`, whose row is named `object.field`, which its dot-free column `name` cannot spell; "set `name` to KEY, or save the item under NAME" for every other type; and at the restore and publish doors, whose caller cannot edit the stored body in place, the save that fixes it. For a view container at the save door the message is byte for byte the one `savedViewContainerNameRefusal` returned.
12+
- `viewContainerNameRefusal` (the source registrars' entry), its words and the `ViewContainerNameRefusal` type are unchanged.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
A view container's `form` is its default form: it is never collapsed into a named form, and no named form is promoted to default
6+
7+
Clause-②: no
8+
9+
`ViewSchema` declares `form` the container's default form and `formViews` additional named forms. `expandViewContainer` / `expandViewContainerWithDiagnostics`, which every view registrar shares, now serves exactly that. Behaviour changes for authors:
10+
11+
- **A container with no `form` no longer serves its first named form as the default create/edit form.** Before, the first `formViews` entry was flagged `isDefault`, whatever it was: in the CRM example that was the anonymous Web-to-Lead form. Now no form item is flagged, and each named form is served only where it is asked for by name (a form action's `target`, `addRecord.formView`, a public `sharing.publicLink`). If you relied on the old promotion, move the intended create/edit form into `form`: `formViews: { edit: { … } }` becomes `form: { … }`, and a reference to `<object>.edit` becomes `<object>.form`.
12+
- **A named form no longer replaces `form`.** Before, `form` was dropped when any named form shared its `type`, `label` and `columns`, even with different sections, and the first named form became the default. Now `form` is always served as `<object>.form`, flagged `isDefault`, and is the only form item flagged. A named form whose body equals `form` stays its own named item.
13+
- **The default `list` collapses only into a named list that restates its whole body.** A `listViews` entry that repeats `list` key for key, the list's own `name` aside (the "default == `listViews.all`" pattern), still folds into that one named item. A named list that shares `list`'s `type`, `label` and `columns` but differs in anything else (a filter, a sort) is now its own view, and `list` is served beside it as `<object>.default`, the default list. Before, such a named list took the default's place, and the default list's own body was not served.
14+
15+
The CRM and showcase examples move their create/edit form into `form`. The showcase task's `showcase_log_time` and `showcase_new_task` actions now target `showcase_task.form`. The public Web-to-Lead and contact-us forms stay named.
16+
17+
⛔ No schema, parse, export or accept-set change.

‎.changeset/21515-job-body.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
A job can carry a sandboxed `body`, the same JavaScript body hooks and script actions carry, so its work travels with the metadata; `handler` is deprecated beside it (#21515).
6+
7+
Clause-②: yes (widening)
8+
9+
- **`JobSchema.body`** is `ScriptBodySchema` by reference: `{ language: 'js', source, capabilities, memoryMb }`, strict as on hooks. It runs in the QuickJS sandbox with no module scope, reaches data only through `ctx.api` under its declared `capabilities`, and logs through `ctx.log`. The in-process `JobHandlerContext` members (`ql`, `logger`, `bundle`) do not exist there.
10+
- **`handler` is optional and DEPRECATED, "prefer `body`".** When both are present `body` wins, as for hooks. A job that declares neither is refused at parse, located at `body`, with a message naming both keys. The rule is published in the JSON Schema too (`anyOf` of one `required` per key), not only enforced by the parse.
11+
- **Only the L2 body.** An expression (L1) body is refused on a job at `body.language`, and the message says why: an expression performs no I/O, so its only effect would be a returned value, and a job runs for its effects. The message lives on `ScriptBodySchema.language` and fires only where that shape is used on its own; hook and action bodies are unchanged.
12+
- **One time limit.** A body job's limit is the job's own `timeoutMs`: one attempt is one sandbox run, bounded by that value. `body.timeoutMs` (capped at 30 s on hooks and actions) is refused on a job, with the prescription to move the value to `timeoutMs`. The job-level key has no cap, so long-running work states its limit there or splits into bounded runs. The `timeoutMs` describe is the one place this is stated.
13+
- **Not yet run by the runtime.** Scheduling a job's `body` is a separate change. Until it lands a job runs through `handler`, and a body-only job is skipped at boot with a warning. The liveness ledger grades `job.body` `planned`, so `os validate`, `os lint` and `os build` warn wherever a job sets a `body`. `objectstack build` does not mint a job body from the function a `handler` names; write it as data.
14+
15+
Nothing that parsed before is refused now: every existing job declares `handler`, and none declares `body`.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
fix(cli): `os init` prints its dependency-install and scaffold-validation refusals once (#21523)
6+
7+
Clause-②: no
8+
9+
`os init demo -p npm` with an unreachable package registry printed `✗ Project scaffolded, but dependency installation failed.`, then a second `✗ Dependency installation failed`, then oclif's `Error: Dependency installation failed`, and exited 2. The second `✗` line came from the command's outer `catch`: the `this.error(…)` inside its `try` throws oclif's exit signal, and the `catch` reported it again. A scaffold that failed its own validation got a second `✗ Scaffold validation failed` line under its refusal the same way.
10+
11+
The `catch` now lets the signal through. Each refusal prints its `✗` line once, followed by oclif's `Error:` line as before, and the exit status is still 2.

0 commit comments

Comments
 (0)