Repository navigation
docs(adr): ADR-0012 — the hosted push relay transport (default) beside direct APNs/FCM, and vendor as a token type the relay routes on (ruling cloud#3519) - #29952
Conversation
…lt) | direct (APNs/FCM, white-label), vendor as a token type the relay routes on, ID-only then E2EE privacy, no system push air-gapped (ruling cloud#3519) Extends the header's Amended (2026-11-03, #29041) note for #29817: §4's push backend row, §6's domestic-vendor bullet and a new §6.1 (transports, wire, vendor as token type, privacy, air-gap, what main serves), the Goals and Non-Goals bullets that called the domestic push vendors plugin transports, the M3 milestone line, risk 5 and the Decision list. Every amended passage quotes the wording it replaces. Written to the shape main ships (#29886, #29794): the relay is decided and not yet served (#29920). ADR-0030 is unchanged. Claude-Session: https://claude.ai/code/session_01WGufzj5io3Zzyb3E6VKCtj Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K Contract-review-claim: session_01WGufzfgvGexKn9vLHsy89K · head cce992b · 2026-11-03T17:16Z Rules face ( |
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flagsos-dev-report 6610622592, each deviation answered:
④ Design judgmentHeld against
Findings:
CI on Implemented-by: VERDICT: PASS |
…' old wording, name which relay schemas carry the PLANNED marker, and the relay block's transport key Contract review round 1 on PR #29952: the status line now lists the TL;DR item and the Goals "Four built-in channels" bullet among the amended passages (both quote "push (APNs + FCM)") and names the three passages that are new; §6.1 says the relay transport block and the seven relay object schemas carry the [PLANNED — not yet served] marker and the two enums do not; the relay block's keys are transport and endpoint. Claude-Session: https://claude.ai/code/session_01WGufzj5io3Zzyb3E6VKCtj Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K Contract reviewServed-tier: ① Derived judgments
② Semver levelUnchanged from the prior record: docs-only under ③ Boundary flagsNo new deviation declared on the PR since comment 6610988758 (no later comments). Escalation carried forward unchanged: ④ Design judgmentThe delta tightens three sentences and changes no decision, rule or default; the amendment's supersession bookkeeping is now complete and the schema census is exact. One remaining nit, not blocking: §6.1 says "the two enums there ... carry none" — a third, non-exported enum ( CI on Implemented-by: VERDICT: PASS |
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K 维护者速读
席位意见:卡片要求的六处都已落实。fable 契约复审两轮都是 PASS(首轮 3 处措辞精度问题,已在本 head 修正),check-runs 全绿。同意合并。另外,spec 里两处 docblock 仍按旧的 §6 读法引用本 ADR,已另立卡 #29960,等本 PR 合并后再改。 席位核对 — ACCEPT · domain:spec#3 · head
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K
Requested by Yibo Wang · project thread
Fixes #29817
Ruling-ref: objectstack-ai/cloud#3519
Clause-②: no (docs only, no spec shape moves)
Metadata-protocol: none
Skills-docs: none — nothing under
skills/**or the hand-writtencontent/docs/**names the push relay, the push transport selection orPushVendorSchema(git grep -i -E "push relay|transport: ?'relay'|PushVendorSchema|PushRelayRequest|hosted relay" -- skills content/docs ':!content/docs/references'at cce992b: zero hits). The ADR is the only document that moves.维护者速读
vendor只是令牌类型(中继据此路由),不是插件边界;国内厂商(HMS/荣耀、小米、OPPO、vivo)第二阶段由同一个中继承接,不再是「插件」;中继永远看不到通知明文(第一阶段只传 ID,第二阶段用设备密钥端到端加密);离线(air-gap)部署没有系统推送,只有站内收件箱。每处修订都引用了被替换的原文。transport: relay | directon PushServiceConfigSchema, and the server→relay wire shape (ruling cloud#3519) #29816 → PR feat(spec): push relay transport — transport: relay | direct on PushServiceConfigSchema, and the server-to-relay wire shape #29886;Push channel: build the device registry routes (/notifications/devices) and the APNs + FCM transport that ADR-0012 marks "MUST ship M1" #29041 → PR feat(notifications): the push channel — service-push (APNs + FCM) and its channel shell in service-messaging #29794)相抵触。docs/adr/0012-notification-platform.md状态行的 Amended 说明(记录改了哪些段落)、新增的 §6.1(中继/直连、线上协议、隐私、离线、main 上已交付什么)、§4 的推送行。pnpm check:adr-index && pnpm check:adr-symbol-anchors && node scripts/check-adr-links.mjs,预期三条都退出 0;再把 §6.1 第一条与packages/spec/src/system/push-config.zod.ts的 docblock 对照,应逐句一致。docs/adr/**,Tier H):请审阅并手动合并;席位不合并、不排队、不改 draft。What changes
One file,
docs/adr/0012-notification-platform.md(+22 / −9). Every passage is written to the shapemainships at 6a4737a (PR #29886 for #29816, PR #29794 for #29041), not to the card's wording where the two differ, and every amended passage quotes the wording it replaces. What is not served yet (the relay sender #29920, the phase-2 vendors, the end-to-end encryption) is worded as decided and planned, with its card where one exists.Amended (2026-11-03, #29041): push ships asservice-push; the relay is the default per cloud#3519 and not yet served (#29920).… #29041; extended the same day by #29817 on ruling objectstack-ai/cloud#3519 — 1C 2A 3A 4B 5A 6B 7B, 2026-11-03 — and the spec shape of #29816, listing each passage the extension amends and what it read before. The status keyword staysDraft(first in the line),Superseded by ADR-0030stays.push(APNs + FCM)push(the hosted push relay by default; direct APNs + FCM for a white-label build — amended 2026-11-03, cloud#3519, §6.1); this item / bullet read "push(APNs + FCM)"MessagingChannelplugins with zero core change.vendordiscriminator onsys_user_device, but only APNs and FCM ship in core. Domestic vendors are plugin transports.vendoris the token-type discriminator the push transport routes on; core shipsapnsandfcm(PushVendorSchema, closed onmain); in phase 2 the relay fronts the domestic vendors too andPushVendorSchemawidens in the same change that routes them. The old wording is quoted; "neither plugin transports nor alternate channels".relay(hosted, the default: the server POSTs an ID-onlyPushRelayRequestSchemato the hosted relay, authenticated by its cloud binding credential; the relay holds the store app's APNs/FCM credentials) anddirect(APNs HTTP/2 token auth + FCM HTTP v1, the operator's own credentials, white-label only); thetransportunion onPushServiceConfigSchema(#29816). Status:directshipped (#29041, PR #29794);relaydecided, not yet served:PushServicePluginrefuses a relay block,{}included, at init until #29920. The pre-2026-11-03 cell text is quoted.plugin-notification-push-hmsetc.), not as transports — their on-device SDKs and quotas are too different to share a code path.vendoron the row means;PushVendorSchemaisapnsandfcm, closed onmainat this amendment (UserDeviceSchemanarrowsvendorto it); in phase 2 the relay routes HMS / Honor / Xiaomi / OPPO / vivo by the samevendorand the schema widens in the same change that routes them (the schema's docblock already says so; #29816 kept it closed on purpose); no card tracks the widening yet. The old bullet is quoted.push-config.zod.tshas them; the wire (PushRelayRequestSchema/PushRelayResponseSchemawithok/invalid/retry,PushRelayErrorResponseSchema401 / 429); vendor as token type; Privacy (phase 1 ID-only:data+ one generic visible line,title/body/ … refused by name, opaque to the relay; phase 2 end-to-end encryption with a device key,pushKeyleft out of the registration by #29816 on purpose, no card yet; the direct transport is outside the rule and sends title and body today, PR #29794); Air-gap (no system push; the in-app inbox only); Whatmainserves at this amendment (direct served byservice-push+push-channel.ts; relay refused at init withPUSH_RELAY_NOT_SERVED_MESSAGE, sender #29920,[PLANNED — not yet served]markers; phase-2 vendors and E2EE planned, no card).vendora token type the relay routes on, domestic vendors behind the relay in phase 2, never plugins; never plaintext (ID-only, then E2EE); air-gapped means no system push, inbox only.Round 1 (contract review 6610988758, no must-fix; three accuracy items): the status line now lists the TL;DR item 2 and the Goals "Four built-in channels" bullet among the amended passages (both now quote "
push(APNs + FCM)") and names the three passages that are new and replace nothing (the Non-Goals air-gap bullet, §6.1, the Decision bullet); §6.1 says which schemas carry the[PLANNED — not yet served]marker (the relay transport block and the seven relay object schemas; the two enumsPushRelayPrioritySchemaandPushRelayResultStatusSchemacarry none); the relay block's keys aretransportandendpoint.Not changed, on purpose: the §6
UserDeviceSchemaproposal block (the historical proposal; the #29041 amendment note under it already records the shipped, narrowed shape), the §1 diagram, §3 / §10 / §11 package and file names (the status line'splugin-notification-pushreading covers them), the M1 milestone and the #1292 acceptance table (what M1 delivered), and ADR-0030 (unchanged, as the card requires).Why
cloud#3519 (ruled 1C 2A 3A 4B 5A 6B 7B on 2026-11-03) decided that the store-published app pushes through a hosted relay, that
vendoris a token type the relay routes on, that the relay never sees plaintext, and that an air-gapped deployment has no system push. #29816 (PR #29886) landed the spec shape and #29041 (PR #29794) landed the direct transport with a header note saying the relay is the default and not yet served. ADR-0012 still carried the pre-ruling decisions in §4 (APNs/FCM as the only push backend), §6 (domestic vendors as alternate channel plugins), the Goals and Non-Goals (domestic vendors as plugin transports) and the M3 milestone. Prime Directive #13: a recorded decision is reversed by an ADR edit.Tests
n/a — docs only. No code, schema, test or generated artifact moves; the ADR gates below are the checks that read this file.
Acceptance notes
sys_user_device (push)and item 3push (sys_user_device + APNs/FCM). Neither says the server reaches APNs/FCM itself and neither names a vendor plugin; the relay forwards to APNs and FCM and direct reaches them, so both readings hold. ADR-0030 is untouched.transport: relay | directon PushServiceConfigSchema, and the server→relay wire shape (ruling cloud#3519) #29816 attributes (2A: ID-only).Clause-②spelling. The order saysClause-②: n/a (docs only); the claim (6610076511) saysClause-②: no (docs only, no spec shape moves). The body carries the claim's spelling:scripts/pm/clause2-line.mjsaccepts onlyyes/no(CLAUSE2_VALUES), son/awould readmalformedtocheck-changeset-no-major. Reported as a deviation from the order's spelling.packages/spec/src/system/push-config.zod.tsstill says in two places that ADR-0012 §6 routes the other vendors to alternate channel plugins (the docblock's "Out of scope here" paragraph and thePushVendorSchemacomment), andpackages/spec/src/system/push-device.zod.tssays "the domestic Android vendors plug in as alternate channel implementations". Carrier: the phase-2 change that widensPushVendorSchema(no card yet); noted here, not filed.assigneesonpr_create, no reviewer request; the seat sets both at hand-over. Thepr_createbody carries no seat signature line: the relay's signature rule scopes tocomment_edit/issue_patch, and the harness pins the attribution block to the body's first two lines (PR feat(spec): push relay transport — transport: relay | direct on PushServiceConfigSchema, and the server-to-relay wire shape #29886, same seat, same day, is the precedent); the seat appends its trailer at hand-over.skip-changesetis declared onpr_create:docs/adr/**publishes nothing.Verification
All readings at head
62ac6d3conclaude/project-thread-geqk0x, offorigin/main6a4737ab(round 0 readings atcce992bfwere identical: the same 21 commands, all exit 0) (which carries PR #29886 and PR #29794). Every exit code was captured before any pipe; the gate sweep ran serially from a runner script with stdin closed, after the formula/lint build underscripts/pm/os-verify-lock.sh.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack docs/adr/0012-notification-platform.md: 21 commands (15 pnpm, 6 direct node; 13 matched by path, 8 whole-tree). All 21 exit 0, among them the ADR gatescheck-adr-links(and its--self-test),check:adr-anchors,check:adr-index("docs/adr/README.md is current, every record parsed") andcheck:adr-symbol-anchors("3075 anchors across 149 records resolve"; the newpath#symbolanchors in the Non-Goals, §6 and §6.1 are among them), and the docs gatescheck:doc-authoring,check:doc-formula-expressions,check:nul-bytes,check-closing-keyword-parity,check:pm-governed-mergesandcheck:pm-prior-rulings.node scripts/pm/dispatch-gates.mjs --ran RAN_FILE --repo objectstack-ai/objectstackat62ac6d3c: "21 derived, 21 run, 0 NOT-MEASURED, 0 UNRUN"; change set 1 path, +22 / −9, under the 3000-line human-merge threshold.pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2underos-verify-lock.sh, thecheck:doc-formula-expressionsprerequisite:VERDICT command-exit 0, held the lock 154 s.GITHUB_EVENT_NAME=pull_request node scripts/check-changeset-no-major.mjs --base origin/main --event EVENT_JSON(a pull_request payload carrying this body and the skip-changeset label): exit 0; "LEVEL AXIS: this PR declares clause-②noand metadata-protocolnone, and it introduces no changeset entry gradedminoror above and moves no metadata-protocol baseline".!in the title and no(narrowing)arm.Human Minimum
packages/specholds no runtime logic. (No code moves.)pnpm lintpasses. (A Markdown file; the derived ADR and doc gates ran instead, below.)main, one pull request.skip-changeset; both declaration lines are above.)packages/spectouched: its generated artifacts are regenerated and committed. (Not touched.)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzj5io3Zzyb3E6VKCtj
Generated by Claude Code
Body refreshed by spec seat 3 (round 1, head
62ac6d3c): 2026-11-03T17:44Z