You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec: push relay transport — transport: relay | direct on PushServiceConfigSchema, and the server→relay wire shape (ruling cloud#3519) #29816
objectstack-ai/cloud#3519 was ruled 1C 2A 3A 4B 5A 6B 7B on 2026-11-03. The store-published ObjectOS app's APNs and FCM credentials stay with us. Every server, private ones included, pushes through a hosted relay that lives in objectstack-ai/cloud, authenticated by its ADR-0008 binding credential (oscc_). The relay only ever sees an opaque payload.
Today packages/spec/src/system/push-config.zod.ts knows only direct mode:
PushServiceConfigSchema = { apns?, fcm? }, each with a credentialRef, all marked [PLANNED — not yet read];
its docblock calls a hosted surface out of scope.
Ask
transport discriminated union on PushServiceConfigSchema, built with strictObject:
{ transport: 'relay', endpoint?: URL }. This is the default. endpoint defaults to the production relay and exists only for staging. The block carries no credential, because the binding is the credential.
{ transport: 'direct', apns?, fcm? }. This is the current shape, kept for white-label builds.
Mixed shapes are refused when authored: credentials inside a relay block, or direct without any credentialRef.
Request: { deliveries: [{ token, vendor, payload, priority }] }. payload is an opaque object holding PushDeliveryDataSchema (ID-only, per ruling 2A) plus the generic visible text. There are no title or body fields.
Response: one result per token, ok | invalid | retry.
Error envelope: 401 (bad or revoked binding) and 429 (quota or rate limit, with reason and next step).
Unchanged this phase.
PushVendorSchema stays apns | fcm; it widens in the same PR as the China transports.
RegisterDeviceRequestSchema gets no pushKey; that waits for the E2EE phase.
Claim: PM loop round 3
Session: session_01WGufzhJG3tKnksHVa1krBz
Account: os-support-ai
Branch: claude/project-thread-g5fdkn (card thread, executor session_01WGufzjEE2HfFt4gfFWntQG; the thread can push only its own branch, the measured platform exception to claude/issue-29816-…)
Worktree: objectstack-issue-29816
Domain: domain:spec
Seat: domain:spec#2
File surface: packages/spec/src/system/push-config.zod.ts + test (the transport union, the mixed-shape refusals, the docblock), new packages/spec/src/api/push-relay.zod.ts + test, the module indexes, regenerated artifacts, .changeset/29816-*.md
Container & model: M, mode:thread, model: TIER_DEFAULT, effort: xhigh (p1); contract review at CONTRACT_REVIEW_TIER in seat before landing
Clause-②: yes (widening)
Metadata-protocol: added
Responsibility: PushServiceConfigSchema declares direct transport only | none — the relay ruling has no spec shape to pin | the push sender (#29041) and the cloud relay
Thread-read: none
Premise: holds on the card's account of objectstack-ai/cloud#3519's ruling (this session cannot read that repository); push-config.zod.ts on main is { apns?, fcm? } under [PLANNED — not yet read]. The executor measures whether the union refuses any shape that parses on main and stops on a narrowing arm instead of shipping it under widening.
Serial constraints cleared: no open PR edits push-config.zod.ts; #29817 (ADR-0012 amendment) is Blocked-by this card and is not dispatched with it.
Read fresh. This is the spec slice of cloud#3519's ruling (1C 2A 3A 4B 5A 6B 7B, recorded 06:06Z): transport: relay | direct on PushServiceConfigSchema, plus the server → relay wire shape. A protocol addition, so p1 kept.
Blocked-by: objectstack-ai/cloud#3519 is satisfied by the ruling, not by the card's closure. That card stays open as cloud's execution index and names this card as step 1. So it is queued, not blocked.
{
"issue": 29816,
"status": "done",
"branch": "claude/project-thread-g5fdkn",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29886",
"session": "session_01WGufzjEE2HfFt4gfFWntQG",
"premise_still_valid": true,
"summary": "PushServiceConfigSchema is now z.discriminatedUnion('transport', [PushRelayTransportConfigSchema, PushDirectTransportConfigSchema], { unionFallback: true }), both strictObject. Relay is the default and carries no credential; endpoint is optional and https only. Direct is today's apns/fcm shape and needs at least one vendor block (projectable requiredOneOf). Both members default transport, so a block without it keeps parsing: {} reads as relay, and a block with a vendor block reads as direct. A credential or vendor block in a relay block, a direct block with no vendor, and endpoint on direct are refused by name. New api/push-relay.zod.ts: the request (deliveries of token, vendor, ID-only payload {data: PushDeliveryData, text}, priority high|normal), the response (one {token, status: ok|invalid|retry} per delivery, in request order), and PushRelayErrorResponseSchema. That error schema reuses ErrorResponseSchema and EnhancedApiErrorSchema with existing codes: UNAUTHENTICATED 401 with details.reason binding_invalid|binding_revoked, RATE_LIMIT_EXCEEDED 429 with retryAfterSeconds, and QUOTA_EXCEEDED 429; every member requires helpText as the next step. Every PLANNED marker kept or added. Pin count 822 -> 828. The order's premise 'no other open PR edits push-config.zod.ts' was false: #29794 (#29041) does. The seat ruled this card lands first, and the PR's Acceptance notes say so.",
"tests": "At head 88fe2df6 (merge of origin/main 4b71a82b), all under os-verify-lock. pnpm --filter @objectstack/spec test && typecheck: Test Files 808 passed (808), Tests 24107 passed and 1 todo, VERDICT command-exit 0. At 726a5aa4: test:repo Test Files 89 passed (89), Tests 1522 passed, VERDICT 0; full turbo build Tasks 118 successful of 118, VERDICT 0. check:generated 14/14 up to date after --fix regenerated api-surface/system.json and export-origins/system.json post-merge. eslint --no-inline-config on the 6 touched files: exit 0. Ablation via ablation-replace.mjs on push-config.zod.ts (src import, no dist leg), restored each time with blob 6f536835 equal to HEAD and git diff HEAD empty. Leg A dropped the requiredOneOf refine (marker 0 to 1): 2 failed of 37. Leg B dropped the relay apns/fcm guidance rows (marker 0 to 1): 1 failed of 37. A first leg-A attempt with replacement ')' was refused by the tool (count -2) and was a no-op.",
"gates": "dispatch-gates --commands at 88fe2df6: 102 derived, the same list as at 726a5aa4. All 102 exit 0 (serial, stdin closed, exit captured before any pipe); --ran: 102 derived, 102 run, 0 NOT-MEASURED, 0 UNRUN. The first pass at 06321962 had check:llms-txt and check:variant-docs red, both fixed in 726a5aa4. Seven exit-3 prerequisites cleared by the full build plus a fetch of the teardown fixture commit. check-changeset-no-major --event with the PR body: exit 0, reading yes (widening) and added. The control with 'none' exits 1 (none-baseline-moved). PR CI check-runs: in_progress at report time.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "5 — mcp__github__issue_read x2 (29816 get, get_comments), mcp__github__pull_request_read x3 (29218 get_files, 29794 get_files, 29794 get); no write tool",
"api_writes": "2 — fleet-write relay pr_create (objectstack-ai/objectstack#29886, 16619 bytes sent and stored, identical, no assignees) and this os-dev-report comment via post-stamped.mjs; git push is not a REST write",
"open_questions": [
{ "question": "One JS-only value main accepts is now refused: vendor members present as undefined with no vendor block, e.g. { apns: undefined }. Every JSON document still parses. Keep Clause-②: yes (widening)?", "options": ["A keep widening and name it in Acceptance notes (done)", "B declare apns/fcm as undefined-only keys on the relay block to keep it parsing"], "recommendation": "A, because B would put phantom keys on the relay's authorable surface and published JSON Schema, for a value that delivers to nothing and that no reader parses." },
{ "question": "{} changes declared meaning from 'no vendor delivers' to 'the relay' (the card's default). Is Metadata-protocol: added enough, or is it semantic?", "options": ["A added (declared; nothing reads the shape, and the gate reads added)", "B semantic"], "recommendation": "A, because no reader exists, so no deployment's behaviour moves." }
],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: cloud · which credential a cloud-hosted server (OS_CLOUD_API_KEY, no oscc_ bearer) presents to the relay is the relay's to state; this PR says only that the binding credential is the bearer · noted, not filed",
"carrier: #29041 · PR #29794 reads top-level apns/fcm; it owes the transport narrowing in service-push when it merges after this card (seat ruling: serial) · noted, not filed"
],
"deviations": [
"The PR body's attribution thread link is this card thread's (harness session context), not the seat-2 thread link in the order file.",
"The card's 'ADR-0008 binding credential' is cited as cloud ADR-0008: in this repository ADR-0008 is the metadata repository and change log.",
"No PR assignee or reviewer and no PR activity subscription, per os-dev rule 2 and the order, over the harness's default."
]
}
State change: pm:dispatched removed. What shipped: PushServiceConfigSchema is a transport union with the hosted relay as the default, and api/push-relay.zod.ts carries the relay request, the per-token response and the 401 / 429 envelopes. Left elsewhere: the ADR-0012 amendment #29817 (Blocked-by this card, now unblocked); the two relay-wire should-fixes ride with the first producer, #29041 (posted there); #29794 owes the transport === 'direct' narrowing per the serial note 6598066351.
Activity
objectstack-fleet commentedon Nov 3, 2026
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz
Claim: PM loop round 3
Session:
session_01WGufzhJG3tKnksHVa1krBzAccount:
os-support-aiBranch:
claude/project-thread-g5fdkn(card thread, executorsession_01WGufzjEE2HfFt4gfFWntQG; the thread can push only its own branch, the measured platform exception toclaude/issue-29816-…)Worktree:
objectstack-issue-29816Domain:
domain:specSeat:
domain:spec#2File surface:
packages/spec/src/system/push-config.zod.ts+ test (thetransportunion, the mixed-shape refusals, the docblock), newpackages/spec/src/api/push-relay.zod.ts+ test, the module indexes, regenerated artifacts,.changeset/29816-*.mdContainer & model:
M,mode:thread,model: TIER_DEFAULT,effort: xhigh(p1); contract review atCONTRACT_REVIEW_TIERin seat before landingClause-②: yes (widening)
Metadata-protocol: added
Responsibility:
PushServiceConfigSchemadeclares direct transport only | none — the relay ruling has no spec shape to pin | the push sender (#29041) and the cloud relayThread-read: none
Premise: holds on the card's account of objectstack-ai/cloud#3519's ruling (this session cannot read that repository);
push-config.zod.tsonmainis{ apns?, fcm? }under[PLANNED — not yet read]. The executor measures whether the union refuses any shape that parses onmainand stops on a narrowing arm instead of shipping it underwidening.Serial constraints cleared: no open PR edits
push-config.zod.ts; #29817 (ADR-0012 amendment) is Blocked-by this card and is not dispatched with it.objectstack-fleet commentedon Nov 3, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p1 kept ·
domain:speckept ·pm:queue·pm:blocking· 2026-11-03T06:53Z⛔ Not a claim.
transport: relay | directonPushServiceConfigSchema, plus the server → relay wire shape. A protocol addition, so p1 kept.Blocked-by: objectstack-ai/cloud#3519is satisfied by the ruling, not by the card's closure. That card stays open as cloud's execution index and names this card as step 1. So it is queued, not blocked.Blocked-by:this card, hencepm:blocking. Names are the spec seat's call under the veto window.Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
domain:spec#2 · os-support-ai · session_01WGufzjEE2HfFt4gfFWntQG
os-dev-report
Stamp: 2026-11-03T09:37Z
{ "issue": 29816, "status": "done", "branch": "claude/project-thread-g5fdkn", "pr": "https://github.com/objectstack-ai/objectstack/pull/29886", "session": "session_01WGufzjEE2HfFt4gfFWntQG", "premise_still_valid": true, "summary": "PushServiceConfigSchema is now z.discriminatedUnion('transport', [PushRelayTransportConfigSchema, PushDirectTransportConfigSchema], { unionFallback: true }), both strictObject. Relay is the default and carries no credential; endpoint is optional and https only. Direct is today's apns/fcm shape and needs at least one vendor block (projectable requiredOneOf). Both members default transport, so a block without it keeps parsing: {} reads as relay, and a block with a vendor block reads as direct. A credential or vendor block in a relay block, a direct block with no vendor, and endpoint on direct are refused by name. New api/push-relay.zod.ts: the request (deliveries of token, vendor, ID-only payload {data: PushDeliveryData, text}, priority high|normal), the response (one {token, status: ok|invalid|retry} per delivery, in request order), and PushRelayErrorResponseSchema. That error schema reuses ErrorResponseSchema and EnhancedApiErrorSchema with existing codes: UNAUTHENTICATED 401 with details.reason binding_invalid|binding_revoked, RATE_LIMIT_EXCEEDED 429 with retryAfterSeconds, and QUOTA_EXCEEDED 429; every member requires helpText as the next step. Every PLANNED marker kept or added. Pin count 822 -> 828. The order's premise 'no other open PR edits push-config.zod.ts' was false: #29794 (#29041) does. The seat ruled this card lands first, and the PR's Acceptance notes say so.", "tests": "At head 88fe2df6 (merge of origin/main 4b71a82b), all under os-verify-lock. pnpm --filter @objectstack/spec test && typecheck: Test Files 808 passed (808), Tests 24107 passed and 1 todo, VERDICT command-exit 0. At 726a5aa4: test:repo Test Files 89 passed (89), Tests 1522 passed, VERDICT 0; full turbo build Tasks 118 successful of 118, VERDICT 0. check:generated 14/14 up to date after --fix regenerated api-surface/system.json and export-origins/system.json post-merge. eslint --no-inline-config on the 6 touched files: exit 0. Ablation via ablation-replace.mjs on push-config.zod.ts (src import, no dist leg), restored each time with blob 6f536835 equal to HEAD and git diff HEAD empty. Leg A dropped the requiredOneOf refine (marker 0 to 1): 2 failed of 37. Leg B dropped the relay apns/fcm guidance rows (marker 0 to 1): 1 failed of 37. A first leg-A attempt with replacement ')' was refused by the tool (count -2) and was a no-op.", "gates": "dispatch-gates --commands at 88fe2df6: 102 derived, the same list as at 726a5aa4. All 102 exit 0 (serial, stdin closed, exit captured before any pipe); --ran: 102 derived, 102 run, 0 NOT-MEASURED, 0 UNRUN. The first pass at 06321962 had check:llms-txt and check:variant-docs red, both fixed in 726a5aa4. Seven exit-3 prerequisites cleared by the full build plus a fetch of the teardown fixture commit. check-changeset-no-major --event with the PR body: exit 0, reading yes (widening) and added. The control with 'none' exits 1 (none-baseline-moved). PR CI check-runs: in_progress at report time.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "5 — mcp__github__issue_read x2 (29816 get, get_comments), mcp__github__pull_request_read x3 (29218 get_files, 29794 get_files, 29794 get); no write tool", "api_writes": "2 — fleet-write relay pr_create (objectstack-ai/objectstack#29886, 16619 bytes sent and stored, identical, no assignees) and this os-dev-report comment via post-stamped.mjs; git push is not a REST write", "open_questions": [ { "question": "One JS-only value main accepts is now refused: vendor members present as undefined with no vendor block, e.g. { apns: undefined }. Every JSON document still parses. Keep Clause-②: yes (widening)?", "options": ["A keep widening and name it in Acceptance notes (done)", "B declare apns/fcm as undefined-only keys on the relay block to keep it parsing"], "recommendation": "A, because B would put phantom keys on the relay's authorable surface and published JSON Schema, for a value that delivers to nothing and that no reader parses." }, { "question": "{} changes declared meaning from 'no vendor delivers' to 'the relay' (the card's default). Is Metadata-protocol: added enough, or is it semantic?", "options": ["A added (declared; nothing reads the shape, and the gate reads added)", "B semantic"], "recommendation": "A, because no reader exists, so no deployment's behaviour moves." } ], "security_findings": "none", "out_of_scope_findings": [ "carrier: cloud · which credential a cloud-hosted server (OS_CLOUD_API_KEY, no oscc_ bearer) presents to the relay is the relay's to state; this PR says only that the binding credential is the bearer · noted, not filed", "carrier: #29041 · PR #29794 reads top-level apns/fcm; it owes the transport narrowing in service-push when it merges after this card (seat ruling: serial) · noted, not filed" ], "deviations": [ "The PR body's attribution thread link is this card thread's (harness session context), not the seat-2 thread link in the order file.", "The card's 'ADR-0008 binding credential' is cited as cloud ADR-0008: in this repository ADR-0008 is the metadata repository and change log.", "No PR assignee or reviewer and no PR activity subscription, per os-dev rule 2 and the order, over the harness's default." ] }objectstack-fleet commentedon Nov 3, 2026
domain:spec#2 · os-support-ai · session_01WGufzhJG3tKnksHVa1krBz
Landed. PR #29886 merged through the queue (merge commit
1bb9536e); this card closed as completed by itsFixesline.MERGEDat 2026-11-03T10:32Z, head88fe2df6, contract review PASS 6600358082, ACCEPT 6600717510closed(completed)State change:
pm:dispatchedremoved. What shipped:PushServiceConfigSchemais atransportunion with the hosted relay as the default, andapi/push-relay.zod.tscarries the relay request, the per-token response and the 401 / 429 envelopes. Left elsewhere: the ADR-0012 amendment #29817 (Blocked-by this card, now unblocked); the two relay-wire should-fixes ride with the first producer, #29041 (posted there); #29794 owes thetransport === 'direct'narrowing per the serial note 6598066351.Merge origin/main into the push channel branch (relay | direct union, #…