Repository navigation
Commit 5c4e752
feat(spec): push delivery data names the server that sent it — RegisterDeviceRequest, UserDevice and PushDeliveryData gain an optional origin (#29767)
domain:spec#3 · os-support-ai · session_01WGufzeYAsmoHHYA59sCBZa
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBQgSBJZBjCuYUW1nMNHz3yx)_
Fixes #29642
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — no file under `skills/**` teaches device
registration or push delivery data (a `git grep -i` of `skills/` for
`push`, `device`, `notifications/devices` and `window.location.origin`
names only unrelated lines: `Array.push` calls, query push-down, and the
viewport and touch-device notes in `skills/objectui/guides/mobile.md`),
so no taught line changes.
## 维护者速读
- 改了什么:设备注册请求 `RegisterDeviceRequest`、设备记录 `UserDevice`、推送数据
`PushDeliveryData` 各加一个可选字段 `origin`,同一条规则:`http(s)://` + 小写主机 +
可选端口,不带路径。
- 为什么改:App 里存了多台服务器时,点开 B 服务器发来的推送会在 A 上打开;按裁决 B,由 Console
注册时报上自己的来源,服务器存进设备记录,推送原样带回,App 按它选服务器。
- 风险与回滚:纯放宽,三个字段都可选,不带它的请求照旧通过;唯一新增的拒收是注册时带了格式不对的
`origin`(今天没有客户端会发)。服务器暂不存、推送暂不带,设备侧两个字段标了 `[PLANNED — not yet
stored]`。回滚即 revert。
- 先看:`packages/spec/src/system/push-server-origin.ts`(共享的正则和拒收文案),再看
`push-server-origin.test.ts`(三个字段同一组接受/拒收用例)。
- 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run
src/system/push-server-origin.test.ts`,预期 68 个测试全过。
- 需要你做的:无,席位落地。
## The ruling
「Ruling: batch #1 item 1 · letter B · maintainer 「同意」 2026-11-03T02:26Z」
(record 6593872191): **Option B:** "the Console sends the origin it was
loaded from in `RegisterDeviceRequest`. The server stores it on the
`UserDevice` row, and the push channel copies it into
`PushDeliveryDataSchema`. All three are new optional members." #29218
has merged, so this is its own spec PR widening three optional members.
## The name: `origin`
`packages/spec` already spells a URL origin `origin`: the action
`navigate` template's `${ctx.origin}` (`ui/action.zod.ts`, the
`navigate` describe, "`${ctx.*}`
(origin/apiBase/user/org/recordId/selection)"; the Console fills it with
`window.location.origin`,
`ui/packages/app-shell/src/views/metadata-admin/MetadataTypeActions.tsx`),
and CORS `origins` (`shared/http.zod.ts`, values like
`https://app.example.com`). `serverOrigin` has zero hits in
`packages/spec/src`. The value is exactly `window.location.origin`, so
the one name is `origin` on all three schemas.
## What changes
- **`system/push-server-origin.ts` (new, internal).**
`PUSH_SERVER_ORIGIN_PATTERN` and `PUSH_SERVER_ORIGIN_MESSAGE`, the one
rule the three members share. It follows the
`system/push-device-platform.ts` precedent: no barrel re-exports it, so
it adds no public name (`api-surface/` is unchanged), and each schema
builds its own `z.string().regex(...)` from it. The pattern accepts
`http://` or `https://`, a lowercase host (DNS labels, punycode
included, or a bracketed IPv6 address) and an optional `:PORT`, and
nothing after it. That is the serialized form `window.location.origin`
answers, so the shell can compare it with a saved server byte for byte.
- **`RegisterDeviceRequestSchema.origin`** (`api/protocol.zod.ts`): the
Console writes it at registration. Describe opens with `[PLANNED — not
yet stored]`.
- **`UserDeviceSchema.origin`** (`system/push-device.zod.ts`): the
registration's value, kept on the row. Same marker.
- **`PushDeliveryDataSchema.origin`** (`api/push-delivery.zod.ts`): the
push channel copies it from the device; the shell opens the tap on the
saved server with that origin and resolves `route` there. The
schema-level `[PLANNED — not yet served]` marker stays, and its describe
now names the origin. The file's docblock gains a "Which server sent it"
paragraph.
- **Generated:** `authorable-surface/api.json`
(+`api/PushDeliveryData:origin`, +`api/RegisterDeviceRequest:origin`),
`authorable-surface/system.json` (+`system/UserDevice:origin`), and the
three `content/docs/references/**` pages (`api/protocol.mdx`,
`api/push-delivery.mdx`, `system/push-device.mdx`), written by
`gen:docs`.
- **Changeset:** `.changeset/29642-push-server-origin.md`,
`@objectstack/spec` minor, carrying both declaration lines.
## Tests
`packages/spec/src/system/push-server-origin.test.ts`, 68 cases,
`describe.each` over the three carriers so each member is held to the
same rule:
- optional: the body without `origin` parses unchanged (no `origin` key
appears);
- five valid origins round-trip: `https://crm.example.com`,
`http://localhost:3000`, a punycode host with a port, an IPv4 host with
a port, `http://[::1]:3000`;
- sixteen refusals, each answering exactly one issue at path
`['origin']` with code `invalid_format` and the one shared message: a
trailing `/`, a path, a query, a fragment; `ftp:`, `wss:`,
`javascript:`; a bare host, a bare host with a port, a protocol-relative
`//host`; an upper-case scheme, an upper-case host, user info, an empty
port, a leading space, the empty string;
- one contract: all three JSON Schemas publish the same `pattern` and
none lists `origin` as required;
- the markers: both device-side members open with `[PLANNED — not yet
stored]`, and the push data keeps its schema-level `[PLANNED — not yet
served]`.
**Ablation** (fix committed first, `scripts/ablation-replace.mjs` on the
pattern line of `push-server-origin.ts`, replaced with `/[\s\S]*/;`):
Tests 48 failed | 20 passed (68). Every refusal case went red on all
three carriers (16 × 3); the optional, valid and contract cases stayed
green. Restored: blob equals HEAD (`8c3209e4`), `git diff HEAD` empty.
The suite imports the schemas from source, so no `dist/` was involved.
## Acceptance notes
- **`Metadata-protocol: added`, not `none`.** All three schemas are
listed on `packages/spec/authorable-surface/`, and adding the members
moved two shards. `check-changeset-no-major` refuses `none` over a moved
authorable-surface shard, so the kind is `added` and `@objectstack/spec`
takes `minor`. The claim's `none` line needs amending to match.
- **No liveness rows.** None of the three schemas is a governed metadata
type (`packages/spec/liveness/` has no row for any of them), and
`check:liveness` is green with no change.
- **Iso_ pins hold unchanged.**
`Iso_api_protocol__RegisterDeviceRequestSchema`,
`Iso_api_pushDelivery__PushDeliveryDataSchema` and
`Iso_system_pushDevice__UserDeviceSchema` in
`type-alias-convention.pin.test.ts` still assert input equals output: an
optional string with no default or transform keeps them equal. No new
export, so no new pin.
- **No refinement.** The rule is a `.regex()`, which the JSON Schema
projects, so `dropped-refinements.baseline.json` does not move.
- **The one new refusal.** From this release, `POST
/api/v1/notifications/devices`
(`packages/runtime/src/domains/notifications.ts`, which `safeParse`s the
body with `RegisterDeviceRequestSchema`) refuses a registration whose
`origin` is malformed. Before, an unknown `origin` key was stripped. No
client sends one today: `git grep` for a `registerDevice` caller under
`ui/` finds none.
- **The producers are not in this PR** (named for the `domain:server`
follow-up):
-
`packages/services/service-messaging/src/objects/user-device.object.ts`:
`sys_user_device` has no `origin` field (beside `app_id` and `locale`).
- `packages/services/service-messaging/src/device-registry.ts`: the row
write (`app_id: request.appId ?? null`, `locale: …`) and the row read
(`if (row.locale) device.locale = …`) carry no `origin`.
- The push channel (#29041): nothing outside `packages/spec` produces
`PushDeliveryData` yet (`git grep PushDeliveryData -- packages
':!packages/spec'` is empty).
- The Console registration call rides #29042.
- That follow-up takes the `[PLANNED — not yet stored]` markers off and
flips the marker pin in `push-server-origin.test.ts`.
- `packages/client` `registerDevice(request: RegisterDeviceRequest)`
widens with the type and needs no edit.
## Verification
All on head `2aa2ae0a7` (base `9b4fa6cc`; `origin/main` has since moved
to `3eeb81d19`, and none of its changed files is in this diff — the one
shared directory, `packages/spec/authorable-surface/`, moved only
`data.json` there — so no merge was made).
<details><summary>Package readings</summary>
- `@objectstack/spec` under `scripts/pm/os-verify-lock.sh`: `test` exit
0 (Test Files 804 passed (804), Tests 23860 passed, 1 todo); `typecheck`
exit 0 (includes `check:test-typecheck`, which reads
`type-alias-convention.pin.test.ts` and the new test).
- `check:generated` (after `build`): 14 of 14 artifacts current. The
first run named `content/docs/references/**` stale, regenerated with
`gen:docs`, and a type error in the new test, fixed in the test (no
ledger entry).
- Spec consumers, public surface changed: `pnpm --filter
@objectstack/service-messaging --filter @objectstack/runtime --filter
@objectstack/client run typecheck` exit 0;
`@objectstack/service-messaging` `test` exit 0 (65 files, 787 tests).
Lock verdict command-exit 0.
- `test:repo` (the vitest `repo` project) not run locally: the diff
edits none of its tests and the derived list does not name it; CI `Test
Core` runs it.
- Full `pnpm build` (117 of 117 tasks, exit 0) for the built-output
gates below.
</details>
<details><summary>Derived gates</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 102 families, each run and recorded as `CMD
:: exit N`. `--ran` reconciles: "102 derived, 102 run, 0 NOT-MEASURED, 0
UNRUN".
- 95 exited 0 on the first serial pass. Seven exited 3 (prerequisite not
met, nothing measured): `check-plugin-teardown-shape --self-test`
(shallow clone; its pinned fixture commit fetched), and
`check:doc-formula-expressions`, `check:doc-security-posture`,
`check:skill-examples`, `check:docs-transcript-drift`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure` (no full
build). After the fetch and the full build, all seven exit 0.
- Local-only families: `check:dev-prereqs` exit 0,
`check:skill-frame-freshness` exit 0.
- `check-changeset-no-major` with this body as the `pull_request` event:
exit 0 ("LEVEL AXIS: this PR declares clause-② `yes (widening)` and
metadata-protocol `added`, and no package whose published source … it
moves is graded `patch`"). Control: the same body with
`Metadata-protocol: none` exits 1.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzeYAsmoHHYA59sCBZa)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8c28972 commit 5c4e752
11 files changed
Lines changed: 221 additions & 3 deletions
File tree
- .changeset
- content/docs/references
- api
- system
- packages/spec
- authorable-surface
- src
- api
- system
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2775 | 2775 | | |
2776 | 2776 | | |
2777 | 2777 | | |
| 2778 | + | |
2778 | 2779 | | |
2779 | 2780 | | |
2780 | 2781 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
52 | 62 | | |
53 | 63 | | |
54 | 64 | | |
| |||
71 | 81 | | |
72 | 82 | | |
73 | 83 | | |
74 | | - | |
| 84 | + | |
75 | 85 | | |
76 | 86 | | |
77 | 87 | | |
78 | 88 | | |
79 | 89 | | |
80 | 90 | | |
81 | 91 | | |
| 92 | + | |
82 | 93 | | |
83 | 94 | | |
84 | 95 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| 41 | + | |
41 | 42 | | |
42 | 43 | | |
43 | 44 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1439 | 1439 | | |
1440 | 1440 | | |
1441 | 1441 | | |
| 1442 | + | |
1442 | 1443 | | |
1443 | 1444 | | |
1444 | 1445 | | |
| |||
1542 | 1543 | | |
1543 | 1544 | | |
1544 | 1545 | | |
| 1546 | + | |
1545 | 1547 | | |
1546 | 1548 | | |
1547 | 1549 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1405 | 1405 | | |
1406 | 1406 | | |
1407 | 1407 | | |
| 1408 | + | |
1408 | 1409 | | |
1409 | 1410 | | |
1410 | 1411 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| |||
3584 | 3585 | | |
3585 | 3586 | | |
3586 | 3587 | | |
| 3588 | + | |
| 3589 | + | |
| 3590 | + | |
| 3591 | + | |
| 3592 | + | |
| 3593 | + | |
| 3594 | + | |
3587 | 3595 | | |
3588 | 3596 | | |
3589 | 3597 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
| |||
47 | 48 | | |
48 | 49 | | |
49 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
50 | 61 | | |
51 | 62 | | |
52 | 63 | | |
| |||
93 | 104 | | |
94 | 105 | | |
95 | 106 | | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
96 | 113 | | |
97 | | - | |
98 | | - | |
| 114 | + | |
| 115 | + | |
99 | 116 | | |
100 | 117 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
60 | 66 | | |
61 | 67 | | |
62 | 68 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
0 commit comments