Repository navigation
feat(spec): push delivery data names the server that sent it — RegisterDeviceRequest, UserDevice and PushDeliveryData gain an optional origin - #29767
Conversation
…erDeviceRequest, UserDevice and PushDeliveryData gain an optional origin The Console sends the origin it was loaded from when it registers a device; the device row keeps it and the push data carries it back, so a mobile shell with several saved servers opens a tap on the right one. One rule for all three members: an http(s) origin, lowercase host, optional port, no path. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzeYAsmoHHYA59sCBZa
… the push origin members; changeset Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzeYAsmoHHYA59sCBZa
📓 Docs Drift Check5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 92976c622486c14130b3229e9656175a0a758cad && git checkout 92976c622486c14130b3229e9656175a0a758cad
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cba12d22181588d89fbd45e3a746d5fd356057ec 2aa2ae0a722b39f8cf61eb220e3a263d0e40ffa1 && git checkout -B drift-repro cba12d22181588d89fbd45e3a746d5fd356057ec && git merge --no-ff 2aa2ae0a722b39f8cf61eb220e3a263d0e40ffa1
node scripts/docs-audit/affected-docs.mjs --json cba12d22181588d89fbd45e3a746d5fd356057ec |
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K Contract-review-claim: session_01WGufzfgvGexKn9vLHsy89K · head 2aa2ae0 · 2026-11-03T03:57Z |
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K Contract reviewServed-tier: Inputs read: card #29642 (body, triage 6591841690, ruling 6593872191, the seat's claims, os-dev-report 6595041258), card #29768, PR #29767 body and file list (11 files, +221 −3), the net diff ① Derived judgments
Grammar read by hand: Refusal shape: one ② Semver level
③ Boundary flags
④ Design judgment
No must-fix. The three should-fix items are carried to #29768, which edits this module when the markers come off. Implemented-by: VERDICT: PASS |
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K 维护者速读
席位意见:名字沿用仓内已有的 席位核对 — ACCEPT · domain:spec#3 · head
|
domain:spec#3 · os-support-ai · session_01WGufzeYAsmoHHYA59sCBZa
Requested by Yibo Wang · project thread
Fixes #29642
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — no file under
skills/**teaches device registration or push delivery data (agit grep -iofskills/forpush,device,notifications/devicesandwindow.location.originnames only unrelated lines:Array.pushcalls, query push-down, and the viewport and touch-device notes inskills/objectui/guides/mobile.md), so no taught line changes.维护者速读
RegisterDeviceRequest、设备记录UserDevice、推送数据PushDeliveryData各加一个可选字段origin,同一条规则:http(s)://+ 小写主机 + 可选端口,不带路径。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 theUserDevicerow, and the push channel copies it intoPushDeliveryDataSchema. All three are new optional members." #29218 has merged, so this is its own spec PR widening three optional members.The name:
originpackages/specalready spells a URL originorigin: the actionnavigatetemplate's${ctx.origin}(ui/action.zod.ts, thenavigatedescribe, "${ctx.*}(origin/apiBase/user/org/recordId/selection)"; the Console fills it withwindow.location.origin,ui/packages/app-shell/src/views/metadata-admin/MetadataTypeActions.tsx), and CORSorigins(shared/http.zod.ts, values likehttps://app.example.com).serverOriginhas zero hits inpackages/spec/src. The value is exactlywindow.location.origin, so the one name isoriginon all three schemas.What changes
system/push-server-origin.ts(new, internal).PUSH_SERVER_ORIGIN_PATTERNandPUSH_SERVER_ORIGIN_MESSAGE, the one rule the three members share. It follows thesystem/push-device-platform.tsprecedent: no barrel re-exports it, so it adds no public name (api-surface/is unchanged), and each schema builds its ownz.string().regex(...)from it. The pattern acceptshttp://orhttps://, a lowercase host (DNS labels, punycode included, or a bracketed IPv6 address) and an optional:PORT, and nothing after it. That is the serialized formwindow.location.originanswers, 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 resolvesroutethere. 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.authorable-surface/api.json(+api/PushDeliveryData:origin, +api/RegisterDeviceRequest:origin),authorable-surface/system.json(+system/UserDevice:origin), and the threecontent/docs/references/**pages (api/protocol.mdx,api/push-delivery.mdx,system/push-device.mdx), written bygen:docs..changeset/29642-push-server-origin.md,@objectstack/specminor, carrying both declaration lines.Tests
packages/spec/src/system/push-server-origin.test.ts, 68 cases,describe.eachover the three carriers so each member is held to the same rule:originparses unchanged (nooriginkey appears);https://crm.example.com,http://localhost:3000, a punycode host with a port, an IPv4 host with a port,http://[::1]:3000;['origin']with codeinvalid_formatand 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;patternand none listsoriginas required;[PLANNED — not yet stored], and the push data keeps its schema-level[PLANNED — not yet served].Ablation (fix committed first,
scripts/ablation-replace.mjson the pattern line ofpush-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 HEADempty. The suite imports the schemas from source, so nodist/was involved.Acceptance notes
Metadata-protocol: added, notnone. All three schemas are listed onpackages/spec/authorable-surface/, and adding the members moved two shards.check-changeset-no-majorrefusesnoneover a moved authorable-surface shard, so the kind isaddedand@objectstack/spectakesminor. The claim'snoneline needs amending to match.packages/spec/liveness/has no row for any of them), andcheck:livenessis green with no change.Iso_api_protocol__RegisterDeviceRequestSchema,Iso_api_pushDelivery__PushDeliveryDataSchemaandIso_system_pushDevice__UserDeviceSchemaintype-alias-convention.pin.test.tsstill assert input equals output: an optional string with no default or transform keeps them equal. No new export, so no new pin..regex(), which the JSON Schema projects, sodropped-refinements.baseline.jsondoes not move.POST /api/v1/notifications/devices(packages/runtime/src/domains/notifications.ts, whichsafeParses the body withRegisterDeviceRequestSchema) refuses a registration whoseoriginis malformed. Before, an unknownoriginkey was stripped. No client sends one today:git grepfor aregisterDevicecaller underui/finds none.domain:serverfollow-up):packages/services/service-messaging/src/objects/user-device.object.ts:sys_user_devicehas nooriginfield (besideapp_idandlocale).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 noorigin./notifications/devices) and the APNs + FCM transport that ADR-0012 marks "MUST ship M1" #29041): nothing outsidepackages/specproducesPushDeliveryDatayet (git grep PushDeliveryData -- packages ':!packages/spec'is empty).[PLANNED — not yet stored]markers off and flips the marker pin inpush-server-origin.test.ts.packages/clientregisterDevice(request: RegisterDeviceRequest)widens with the type and needs no edit.Verification
All on head
2aa2ae0a7(base9b4fa6cc;origin/mainhas since moved to3eeb81d19, and none of its changed files is in this diff — the one shared directory,packages/spec/authorable-surface/, moved onlydata.jsonthere — so no merge was made).Package readings
@objectstack/specunderscripts/pm/os-verify-lock.sh:testexit 0 (Test Files 804 passed (804), Tests 23860 passed, 1 todo);typecheckexit 0 (includescheck:test-typecheck, which readstype-alias-convention.pin.test.tsand the new test).check:generated(afterbuild): 14 of 14 artifacts current. The first run namedcontent/docs/references/**stale, regenerated withgen:docs, and a type error in the new test, fixed in the test (no ledger entry).pnpm --filter @objectstack/service-messaging --filter @objectstack/runtime --filter @objectstack/client run typecheckexit 0;@objectstack/service-messagingtestexit 0 (65 files, 787 tests). Lock verdict command-exit 0.test:repo(the vitestrepoproject) not run locally: the diff edits none of its tests and the derived list does not name it; CITest Coreruns it.pnpm build(117 of 117 tasks, exit 0) for the built-output gates below.Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 102 families, each run and recorded asCMD :: exit N.--ranreconciles: "102 derived, 102 run, 0 NOT-MEASURED, 0 UNRUN".check-plugin-teardown-shape --self-test(shallow clone; its pinned fixture commit fetched), andcheck: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.check:dev-prereqsexit 0,check:skill-frame-freshnessexit 0.check-changeset-no-majorwith this body as thepull_requestevent: exit 0 ("LEVEL AXIS: this PR declares clause-②yes (widening)and metadata-protocoladded, and no package whose published source … it moves is gradedpatch"). Control: the same body withMetadata-protocol: noneexits 1.Generated by Claude Code