Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
639e986
feat(spec): one MetadataAuthoringSurfaceSchema for the save's surface…
claude Oct 18, 2026
39a80fb
feat(metadata-protocol): the draft put records draftChanges on the dr…
claude Oct 18, 2026
baf0e8a
feat(metadata-protocol): the draft save states its surface, the stric…
claude Oct 18, 2026
c6ef106
feat(rest,runtime): ?surface=studio|api on both HTTP save doors, copi…
claude Oct 18, 2026
1fbb4de
test: pin draftChanges' record, keep and serve rules and the ?surface…
claude Oct 18, 2026
e3c9b55
chore(changeset): draftChanges writer and the save's surface
claude Oct 18, 2026
2dfbaac
chore: regenerate spec artifacts and the sys_metadata i18n bundles
claude Oct 18, 2026
6b7ce97
Merge remote-tracking branch 'origin/main' into claude/issue-24395-dr…
claude Oct 18, 2026
34c9c2a
test(runtime): type the absence-case read query
claude Oct 18, 2026
00a3ef3
fix(spec): MetadataAuthoringSurface is the z.input alias, pinned isom…
claude Oct 18, 2026
86c033b
test(spec): the isomorphic-pin count moves 788 -> 789 with MetadataAu…
claude Oct 19, 2026
a392537
chore(changeset): platform-objects ships the new column's labels
claude Oct 19, 2026
3db85cc
Merge remote-tracking branch 'origin/main' into claude/issue-24395-dr…
claude Oct 19, 2026
fc7d18f
chore(docs): regenerate the reference index on the merged tree
claude Oct 19, 2026
c105df3
test(objectql): the real-engine sys_metadata stubs declare draft_changes
claude Oct 19, 2026
ede2401
Merge remote-tracking branch 'origin/main' into claude/issue-24395-dr…
claude Oct 19, 2026
49a8e27
chore: regenerate the authorable surface on the merged tree; the rele…
claude Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/24395-draft-changes-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@objectstack/spec": minor
---

feat(spec): a metadata save declares its authoring surface — `SaveMetaItemRequestSchema.surface`, in one exported closed set shared with `draftChanges[].by`

Clause-②: yes (widening)

- **One closed set, one name.** `MetadataAuthoringSurfaceSchema` (`copilot | studio | api`) is exported from `@objectstack/spec/api`, with its type `MetadataAuthoringSurface`. `GetMetaItemResponseSchema.draftChanges[].by` now reads it instead of an inline enum: the accepted values are unchanged.
- **The save states its surface.** `SaveMetaItemRequestSchema` gains an optional `surface` from that set. A draft save stamps it as `by` on each key path it changes; an absent `surface` stamps `api`. On the REST door and the runtime dispatcher it is the `?surface=` query parameter, which takes `studio` or `api` only: `copilot` is stated by an in-process AI tool, never over HTTP.
- **The writer exists.** The `draftChanges` describe no longer says no server writes it: it states the writer's rules (the first draft save is compared with the stored published row, an item with no stored published row is recorded as `[]`, and a `migrate-stored` rewrite records nothing). `18.0.0-next.4`'s note said no server writes `draftChanges` yet; from this release the draft save writes it.
16 changes: 16 additions & 0 deletions .changeset/24395-draft-changes-writer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@objectstack/metadata-protocol": minor
"@objectstack/metadata-core": minor
"@objectstack/platform-objects": minor
---

feat(metadata-protocol): the draft save records `draftChanges` — which surface, actor and instant last changed each key path of a pending draft — and the strict draft read serves it

Clause-②: yes (widening)

- **Recorded by the draft save.** `saveMetaItem({ mode: 'draft', surface })` compares the body it writes with the body it replaces, inside the write's own transaction: the draft row, else (the draft's first save) the active row its publish would replace, else nothing, which records `[]`, the whole body. A path is the object keys from the body root to the deepest changed key; an array is one leaf, with no index. Each changed path is stamped `{ by: surface ?? 'api', actor, at }`, where `actor` is the value the save's history event carries (or `null`) and `at` is the write's instant as `toISOString()`.
- **Kept until publish or discard.** No two entries share a path: a save replaces every entry at or under each path it changes, so the longest prefix answers for a key. A save that changes no key path leaves the entries as they were. Publish and discard delete the draft row, and the entries with it, so the next draft starts clean. A `migrate-stored` rewrite and an active save record nothing.
- **Served on the strict draft read only.** `getMetaItem({ state: 'draft' })` answering a stored draft row carries `draftChanges` beside `item`. It is absent on the active read, the cached branch, the `?preview=draft` read, and a draft row whose saves recorded none (one saved before this release) — never `[]`.
- **Stored on the row.** `sys_metadata` gains a nullable `draft_changes` column (`@objectstack/metadata-core`'s `SysMetadataObject`), never inside the stored body. Schema sync adds it to an existing table on boot with no manual step (measured on SQLite and the memory driver).
- **Translated labels.** `@objectstack/platform-objects`' translation bundles carry the new column's label and help text in every shipped locale.
- **An unknown `surface` is refused.** In-process, a value outside `copilot | studio | api` is refused `400 INVALID_REQUEST` before anything is written.
12 changes: 12 additions & 0 deletions .changeset/24395-http-save-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@objectstack/rest": minor
"@objectstack/runtime": minor
---

feat(rest,runtime): `PUT /meta/:type/:name?surface=studio|api` declares the save's authoring surface on both HTTP save doors; `?surface=copilot` is refused

Clause-②: yes (widening)

- **The spelling.** `?surface=` joins the save door's closed query set on `RestServer` and on the runtime dispatcher (`META_ITEM_SAVE_PARAMS`), read once through `metaSaveRequestOptions` beside `?mode=`. A draft save stamps it on each key path it changes (`draftChanges[].by` on the strict draft read); without it the save is recorded as `api`. `?surface=studio` is the spelling for a person's save in Studio.
- **The refusals.** `?surface=copilot` is `400 VALIDATION_ERROR`: it marks a write as the AI copilot's, which only an in-process tool calling `saveMetaItem` may state. Any other value, a case variant included, is the same `400`, never read as `api`; a repeated `?surface=` is refused by the arity rule. Each refusal writes nothing.
- **Why a query parameter.** It is what the save is asked to do, as `?mode=` and `?force=` are, beside the HTTP preconditions the headers carry; and it needs no CORS allow-list entry, which a custom request header would.
Loading
Loading