@oxyhq/contracts 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Canonical contract for the "Sign in with Oxy" approval handoff.
3
+ *
4
+ * SINGLE SOURCE OF TRUTH for the closed set of reasons an approver may attach
5
+ * when it DENIES a pending request via
6
+ * `POST /auth/session/deny/:authorizeCode`.
7
+ *
8
+ * That endpoint is UNAUTHENTICATED — the public `authorizeCode` is the only
9
+ * credential — so a free-form string from it is never stored: it would be an
10
+ * unauthenticated write of arbitrary text onto a record other surfaces read.
11
+ * The set is therefore deliberately tiny, and closed:
12
+ *
13
+ * - `'declined'` the approver rejected a request they recognised ("Not now").
14
+ * - `'not_me'` the approver did not start the request ("This wasn't me").
15
+ * The ONE value that records the denial as suspicious rather
16
+ * than an ordinary cancel, so a UI may only offer it where the
17
+ * user genuinely said so.
18
+ *
19
+ * Why this lives in `@oxyhq/contracts` rather than in either consumer: the same
20
+ * closed set is enforced in three places — the request schema of the API route,
21
+ * the `enum` of the persisted `AuthSession.deniedReason` field, and the client
22
+ * SDK's `denyCommonsSignIn` parameter. Two hand-maintained copies of a wire
23
+ * contract drift the moment a value is added on one side only, and the failure
24
+ * lands at runtime, in an auth path, as a generic validation error. One
25
+ * declaration makes that impossible.
26
+ *
27
+ * Platform-agnostic — zod only, no react/react-native/expo. ESM-safe (no
28
+ * `require()`).
29
+ */
30
+ import { z } from 'zod';
31
+ /**
32
+ * The closed set, as a value — consumed directly where a runtime list is
33
+ * required (e.g. the Mongoose `enum` of `AuthSession.deniedReason`, which is
34
+ * the storage-level guarantee that an unauthenticated caller can never write
35
+ * free-form text into the field).
36
+ */
37
+ export declare const COMMONS_DENY_REASONS: readonly ["declined", "not_me"];
38
+ /**
39
+ * The same set as a zod enum — the edge validator. Anything outside it
40
+ * (including free-form text) is rejected with 400 before any handler runs.
41
+ */
42
+ export declare const commonsDenyReasonSchema: z.ZodEnum<["declined", "not_me"]>;
43
+ /** Why the approver denied a "Sign in with Oxy" request. */
44
+ export type CommonsDenyReason = z.infer<typeof commonsDenyReasonSchema>;
45
+ /**
46
+ * Android notification channel id the identity-approval push is sent on.
47
+ *
48
+ * A wire contract for the same reason the deny set is: Android 8+ DROPS a
49
+ * notification whose channel id the app has not created, silently and with no
50
+ * client-side error. The API attaches this id when it sends, and the vault
51
+ * creates the channel with it before registering a push token — two hand-typed
52
+ * copies of that string would fail as "the notification never arrived", which
53
+ * is the single hardest push symptom to diagnose.
54
+ *
55
+ * The channel's user-visible NAME and description are deliberately NOT here:
56
+ * those are localized app copy, and the vault owns them.
57
+ */
58
+ export declare const IDENTITY_APPROVAL_PUSH_CHANNEL = "auth-approval";
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Device-to-device identity transfer contracts (b3 Feature 2 — "add a device").
3
+ *
4
+ * SINGLE SOURCE OF TRUTH for the wire shape of the short-lived, unauthenticated
5
+ * relay that carries E2E-encrypted key material from an existing (old) device to
6
+ * a fresh (new) device so both end up holding the SAME secp256k1 private key
7
+ * (key cloning). The relay is E2E-encrypted via an ephemeral secp256k1 ECDH
8
+ * handshake: the server stores only the two ephemeral public keys plus an opaque
9
+ * AEAD ciphertext + nonce and NEVER holds a decryption key.
10
+ *
11
+ * Flow:
12
+ * 1. New device (no identity) generates an ephemeral pair and calls
13
+ * `POST /identity/device-transfer/init { newEphPub, newDeviceLabel? }` →
14
+ * `{ pairingId, expiresAt }`. The QR carries ONLY `pairingId` (not
15
+ * self-contained — mirrors the QR sign-in `approve-info` resolution).
16
+ * 2. Old device (has identity) scans, resolves the request via
17
+ * `GET /identity/device-transfer/:pairingId` (returns `newEphPub` + label),
18
+ * derives `transferKey = HKDF(ECDH(oldEphPriv, newEphPub), pairingId,
19
+ * 'oxy-device-transfer-v1')`, AEAD-encrypts `{ privateKey, publicKey }`, and
20
+ * calls `POST /identity/device-transfer/:pairingId/approve` with the
21
+ * ciphertext PLUS a fresh signature over
22
+ * `{ action:'approve_device_transfer', pairingId, timestamp }` made with the
23
+ * CURRENT identity key (dual-proof: a bearer alone cannot exfiltrate the key).
24
+ * 3. New device (socket push or poll fallback) re-derives the same
25
+ * `transferKey` from `ECDH(newEphPriv, oldEphPub)`, decrypts, and imports the
26
+ * private key, then completes a NORMAL challenge/verify sign-in.
27
+ *
28
+ * The load-bearing response shapes are declared as explicit `interface`s (same
29
+ * `moduleResolution: node` rationale as `UserNameResponse` / the identity/civic
30
+ * contracts: a nested `z.infer<>` can degrade to `{}` under a consumer's
31
+ * `moduleResolution: "node"`), with the runtime schemas annotated
32
+ * `z.ZodType<Interface>`.
33
+ *
34
+ * Platform-agnostic — zod only, no react/react-native/expo, ESM-safe.
35
+ */
36
+ import { z } from 'zod';
37
+ /**
38
+ * Pairing lifecycle:
39
+ * - `pending` — created by the new device, awaiting the old device's approval.
40
+ * - `approved` — the old device sealed and posted the encrypted key material.
41
+ * - `denied` — the old device explicitly cancelled the transfer.
42
+ * - `expired` — the 3-minute TTL elapsed before approval.
43
+ */
44
+ export declare const devicePairingStatusSchema: z.ZodEnum<["pending", "approved", "denied", "expired"]>;
45
+ export type DevicePairingStatus = z.infer<typeof devicePairingStatusSchema>;
46
+ /** Request body for `POST /identity/device-transfer/init` (public). */
47
+ export declare const deviceTransferInitRequestSchema: z.ZodObject<{
48
+ /** The new device's ephemeral secp256k1 public key (single-use). */
49
+ newEphPub: z.ZodString;
50
+ /** Optional human-readable label for the new device (e.g. "iPhone 15"). */
51
+ newDeviceLabel: z.ZodOptional<z.ZodString>;
52
+ }, "strip", z.ZodTypeAny, {
53
+ newEphPub: string;
54
+ newDeviceLabel?: string | undefined;
55
+ }, {
56
+ newEphPub: string;
57
+ newDeviceLabel?: string | undefined;
58
+ }>;
59
+ export type DeviceTransferInitRequest = z.infer<typeof deviceTransferInitRequestSchema>;
60
+ export interface DeviceTransferInitResponse {
61
+ /** 128-bit single-use handle carried in the QR. Also the HKDF salt. */
62
+ pairingId: string;
63
+ /** ISO-8601 expiry (3 minutes from creation). */
64
+ expiresAt: string;
65
+ }
66
+ export declare const deviceTransferInitResponseSchema: z.ZodType<DeviceTransferInitResponse>;
67
+ export interface DeviceTransferInfoResponse {
68
+ pairingId: string;
69
+ /** The new device's ephemeral public key (so the old device can ECDH). */
70
+ newDeviceEphemeralPublicKey: string;
71
+ /** Optional new-device label supplied at init. */
72
+ newDeviceLabel: string | null;
73
+ status: DevicePairingStatus;
74
+ /** ISO-8601 expiry. */
75
+ expiresAt: string;
76
+ /**
77
+ * The old device's ephemeral public key — present ONLY once `status` is
78
+ * `approved` (so the new device can re-derive the shared secret).
79
+ */
80
+ oldDeviceEphemeralPublicKey: string | null;
81
+ /** AEAD ciphertext (hex) — present ONLY once `status` is `approved`. */
82
+ ciphertext: string | null;
83
+ /** AEAD nonce (hex) — present ONLY once `status` is `approved`. */
84
+ nonce: string | null;
85
+ }
86
+ export declare const deviceTransferInfoResponseSchema: z.ZodType<DeviceTransferInfoResponse>;
87
+ /**
88
+ * Request body for `POST /identity/device-transfer/:pairingId/approve`
89
+ * (bearer-authenticated AND signature-proven). The `signature` covers
90
+ * `JSON.stringify({ action:'approve_device_transfer', pairingId, timestamp })`
91
+ * made with the caller's CURRENT identity key — dual-proof so a bearer token
92
+ * alone can never exfiltrate the private key.
93
+ */
94
+ export declare const deviceTransferApproveRequestSchema: z.ZodObject<{
95
+ /** The old device's ephemeral secp256k1 public key (single-use). */
96
+ oldEphPub: z.ZodString;
97
+ /** AEAD ciphertext of `{ privateKey, publicKey }`, hex. */
98
+ ciphertext: z.ZodString;
99
+ /** AEAD nonce, hex (24 bytes). */
100
+ nonce: z.ZodString;
101
+ /** ECDSA (DER, hex) signature proving control of the CURRENT identity key. */
102
+ signature: z.ZodString;
103
+ /** Signing timestamp (ms since epoch) — freshness-checked server-side. */
104
+ timestamp: z.ZodNumber;
105
+ }, "strip", z.ZodTypeAny, {
106
+ signature: string;
107
+ nonce: string;
108
+ ciphertext: string;
109
+ oldEphPub: string;
110
+ timestamp: number;
111
+ }, {
112
+ signature: string;
113
+ nonce: string;
114
+ ciphertext: string;
115
+ oldEphPub: string;
116
+ timestamp: number;
117
+ }>;
118
+ export type DeviceTransferApproveRequest = z.infer<typeof deviceTransferApproveRequestSchema>;
119
+ export interface DeviceTransferApproveResponse {
120
+ success: boolean;
121
+ pairingId: string;
122
+ status: DevicePairingStatus;
123
+ }
124
+ export declare const deviceTransferApproveResponseSchema: z.ZodType<DeviceTransferApproveResponse>;
125
+ export interface DeviceTransferDenyResponse {
126
+ success: boolean;
127
+ pairingId: string;
128
+ status: DevicePairingStatus;
129
+ }
130
+ export declare const deviceTransferDenyResponseSchema: z.ZodType<DeviceTransferDenyResponse>;
@@ -169,16 +169,27 @@ export type DeviceSessionSync = z.infer<typeof deviceSessionSyncSchema>;
169
169
  * possession of the secret IS the proof of device ownership. The server matches
170
170
  * `sha256(deviceSecret)` against the device's stored `secretHash` (constant-time)
171
171
  * and mints a short access token for the device's active account.
172
+ *
173
+ * `accountId` pins the mint to ONE account of that device instead of whichever
174
+ * account is currently active. It exists for identity-bound clients (Commons),
175
+ * whose authenticated user is determined by a local cryptographic key and must
176
+ * never follow an account switch made by another app on the same device. The
177
+ * account must already be a member of the device session; the mint NEVER
178
+ * mutates `activeAccountId`, so pinning is read-only with respect to the device
179
+ * state every other app observes.
172
180
  */
173
181
  export declare const deviceTokenMintRequestSchema: z.ZodObject<{
174
182
  deviceId: z.ZodString;
175
183
  deviceSecret: z.ZodString;
184
+ accountId: z.ZodOptional<z.ZodString>;
176
185
  }, "strip", z.ZodTypeAny, {
177
186
  deviceId: string;
178
187
  deviceSecret: string;
188
+ accountId?: string | undefined;
179
189
  }, {
180
190
  deviceId: string;
181
191
  deviceSecret: string;
192
+ accountId?: string | undefined;
182
193
  }>;
183
194
  /**
184
195
  * Wire shape of a successful `POST /session/device/token`: the freshly-minted
@@ -270,51 +281,6 @@ export declare const deviceTokenMintResponseSchema: z.ZodObject<{
270
281
  }>;
271
282
  export type DeviceTokenMintRequest = z.infer<typeof deviceTokenMintRequestSchema>;
272
283
  export type DeviceTokenMintResponse = z.infer<typeof deviceTokenMintResponseSchema>;
273
- /** Request body for `POST /session/device/hub-ticket`. */
274
- export declare const deviceHubTicketIssueRequestSchema: z.ZodObject<{
275
- returnOrigin: z.ZodString;
276
- }, "strip", z.ZodTypeAny, {
277
- returnOrigin: string;
278
- }, {
279
- returnOrigin: string;
280
- }>;
281
- /** Response from `POST /session/device/hub-ticket`. */
282
- export declare const deviceHubTicketIssueResponseSchema: z.ZodObject<{
283
- ticket: z.ZodString;
284
- expiresIn: z.ZodNumber;
285
- }, "strip", z.ZodTypeAny, {
286
- ticket: string;
287
- expiresIn: number;
288
- }, {
289
- ticket: string;
290
- expiresIn: number;
291
- }>;
292
- /** Request body for `POST /session/device/redeem-ticket`. */
293
- export declare const deviceHubTicketRedeemRequestSchema: z.ZodObject<{
294
- ticket: z.ZodString;
295
- returnOrigin: z.ZodString;
296
- }, "strip", z.ZodTypeAny, {
297
- returnOrigin: string;
298
- ticket: string;
299
- }, {
300
- returnOrigin: string;
301
- ticket: string;
302
- }>;
303
- /** Response from `POST /session/device/redeem-ticket`. */
304
- export declare const deviceHubTicketRedeemResponseSchema: z.ZodObject<{
305
- deviceId: z.ZodString;
306
- deviceSecret: z.ZodString;
307
- }, "strip", z.ZodTypeAny, {
308
- deviceId: string;
309
- deviceSecret: string;
310
- }, {
311
- deviceId: string;
312
- deviceSecret: string;
313
- }>;
314
- export type DeviceHubTicketIssueRequest = z.infer<typeof deviceHubTicketIssueRequestSchema>;
315
- export type DeviceHubTicketIssueResponse = z.infer<typeof deviceHubTicketIssueResponseSchema>;
316
- export type DeviceHubTicketRedeemRequest = z.infer<typeof deviceHubTicketRedeemRequestSchema>;
317
- export type DeviceHubTicketRedeemResponse = z.infer<typeof deviceHubTicketRedeemResponseSchema>;
318
284
  /**
319
285
  * Name of the token-free Socket.IO event emitted to room `user:<userId>` on
320
286
  * every DeviceSession mutation that changes what is signed in for that user.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Canonical contract for Inbox new-mail push notifications.
3
+ *
4
+ * The Android channel id and payload `type` are wire contracts: Android 8+
5
+ * drops a notification whose channel the app has not created, and the client
6
+ * only routes taps it recognises. Two hand-typed copies of either string fail as
7
+ * "the notification never arrived" or "tapping does nothing" — the hardest push
8
+ * symptoms to diagnose.
9
+ *
10
+ * Platform-agnostic — zod only, no react/react-native/expo.
11
+ */
12
+ import { z } from 'zod';
13
+ /** Android notification channel id the new-mail push is sent on. */
14
+ export declare const INBOX_EMAIL_PUSH_CHANNEL = "email";
15
+ /** Runtime type discriminator of the new-mail push payload. */
16
+ export declare const INBOX_EMAIL_PUSH_TYPE = "oxy_inbox_new_message";
17
+ export declare const inboxEmailPushDataSchema: z.ZodObject<{
18
+ type: z.ZodLiteral<"oxy_inbox_new_message">;
19
+ messageId: z.ZodString;
20
+ mailboxId: z.ZodString;
21
+ }, "strip", z.ZodTypeAny, {
22
+ type: "oxy_inbox_new_message";
23
+ messageId: string;
24
+ mailboxId: string;
25
+ }, {
26
+ type: "oxy_inbox_new_message";
27
+ messageId: string;
28
+ mailboxId: string;
29
+ }>;
30
+ export type InboxEmailPushData = z.infer<typeof inboxEmailPushDataSchema>;
@@ -15,6 +15,10 @@ export { userNameSchema, userRelationshipSchema, themePreferenceSchema, userResp
15
15
  export type { UserNameResponse, UserRelationship, ThemePreference, UserResponse, UserProfileUpdate, CurrentUserResponseContract, DeviceLinkedSessionResponse, DeviceLinkedSessionsResponseContract, } from './userResponse';
16
16
  export { applicationTypeSchema, publicApplicationSchema, sessionStatusSchema, } from './sessionStatus';
17
17
  export type { ApplicationTypeContract, PublicApplicationResponse, SessionStatusResponse, } from './sessionStatus';
18
+ export { COMMONS_DENY_REASONS, commonsDenyReasonSchema, IDENTITY_APPROVAL_PUSH_CHANNEL, } from './commonsSignIn';
19
+ export type { CommonsDenyReason } from './commonsSignIn';
20
+ export { INBOX_EMAIL_PUSH_CHANNEL, INBOX_EMAIL_PUSH_TYPE, inboxEmailPushDataSchema, } from './inboxPush';
21
+ export type { InboxEmailPushData } from './inboxPush';
18
22
  export { recommendationExcludeTypeSchema, recommendationBoostSchema, recommendationSignalWeightsSchema, recommendationRequestSchema, recommendationCountSchema, recommendationItemSchema, recommendationResponseSchema, appEndorsementInputSchema, appInterestInputSchema, appUserSignalIngestSchema, appAffinityEventTypeSchema, appAffinityEventSchema, appAffinityEventsIngestSchema, } from './recommendations';
19
23
  export type { RecommendationExcludeType, RecommendationBoost, RecommendationSignalWeights, RecommendationRequest, RecommendationCount, RecommendationItem, RecommendationResponse, AppEndorsementInput, AppInterestInput, AppUserSignalIngest, AppAffinityEventType, AppAffinityEvent, AppAffinityEventsIngest, } from './recommendations';
20
24
  export { verificationMethodSchema, didServiceSchema, didDocumentSchema, signedRecordEnvelopeSchema, verifiedDomainSchema, domainVerificationRequestSchema, domainVerificationInstructionsSchema, authMethodEntrySchema, authMethodsResponseSchema, exportAttestationSchema, exportBundleSchema, } from './identity';
@@ -27,8 +31,8 @@ export { publicCardSchema, signedPublicCardSchema, realLifeAttestationRecordSche
27
31
  export type { CardTrustTier, PersonhoodStatus, PublicCard, SignedPublicCard, RealLifeAttestationRecord, RealLifeAttestationResult, ValidationVerdict, ValidationRequestStatus, ValidationVerdictRecord, ValidationOpenRequest, ValidationOpenResult, ValidationRequestSummary, ValidationVoteResult, PersonhoodVouchRecord, PersonhoodBreakdown, PersonhoodStatusResult, VouchResult, CredentialStatus, CredentialRecord, VerifiableCredentialResponse, CredentialIssueResult, CredentialListResult, CredentialVerifyResult, } from './civic';
28
32
  export { linkPreviewSchema, linkPreviewBatchRequestSchema, linkPreviewBatchResponseSchema, linkPreviewResponseSchema, } from './links';
29
33
  export type { LinkPreviewStatus, LinkPreview, LinkPreviewBatchRequest, LinkPreviewBatchResponse, } from './links';
30
- export { sessionAccountSchema, deviceSessionStateSchema, activeTokenSchema, deviceSessionSyncSchema, deviceTokenMintRequestSchema, deviceTokenMintResponseSchema, deviceHubTicketIssueRequestSchema, deviceHubTicketIssueResponseSchema, deviceHubTicketRedeemRequestSchema, deviceHubTicketRedeemResponseSchema, SESSION_ACCOUNTS_CHANGED_EVENT, sessionAccountsChangedReasonSchema, sessionAccountsChangedEventSchema, } from './deviceSession';
31
- export type { SessionAccount, DeviceSessionState, ActiveToken, DeviceSessionSync, DeviceTokenMintRequest, DeviceTokenMintResponse, DeviceHubTicketIssueRequest, DeviceHubTicketIssueResponse, DeviceHubTicketRedeemRequest, DeviceHubTicketRedeemResponse, SessionAccountsChangedReason, SessionAccountsChangedEvent, } from './deviceSession';
34
+ export { sessionAccountSchema, deviceSessionStateSchema, activeTokenSchema, deviceSessionSyncSchema, deviceTokenMintRequestSchema, deviceTokenMintResponseSchema, SESSION_ACCOUNTS_CHANGED_EVENT, sessionAccountsChangedReasonSchema, sessionAccountsChangedEventSchema, } from './deviceSession';
35
+ export type { SessionAccount, DeviceSessionState, ActiveToken, DeviceSessionSync, DeviceTokenMintRequest, DeviceTokenMintResponse, SessionAccountsChangedReason, SessionAccountsChangedEvent, } from './deviceSession';
32
36
  export { loginResultSchema, } from './deviceBoot';
33
37
  export type { LoginSessionResult, LoginResult, SecurityAlert, SecurityAlertAnomaly, } from './deviceBoot';
34
38
  export { rotateKeyChallengeResponseSchema, rotateKeyCompleteRequestSchema, rotateKeyCompleteResponseSchema, } from './keyRotation';
@@ -39,3 +43,7 @@ export { updatePlatformSchema, updateStatusSchema, updateAssetStatusSchema, sha2
39
43
  export type { UpdatePlatform, UpdateStatus, UpdateAssetStatus, AssetInitItem, AssetInitRequest, AssetUploadTicket, AssetInitResponse, AssetCompleteRequest, AssetCompleteResultItem, AssetCompleteResponse, UpdateAssetRef, CreateUpdateRequest, Update, CreateUpdateResponse, RollbackToEmbeddedEntry, Channel, ChannelListResponse, UpdateListResponse, RollbackRequest, RollbackToEmbeddedRequest, PromoteRequest, UpdateRolloutPatch, } from './updates';
40
44
  export { webauthnRegisterOptionsRequestSchema, webauthnLoginOptionsRequestSchema, webauthnRegisterVerifyRequestSchema, webauthnLoginVerifyRequestSchema, } from './webauthn';
41
45
  export type { WebauthnRegisterOptionsRequest, WebauthnLoginOptionsRequest, WebauthnRegisterVerifyRequest, WebauthnLoginVerifyRequest, } from './webauthn';
46
+ export { devicePairingStatusSchema, deviceTransferInitRequestSchema, deviceTransferInitResponseSchema, deviceTransferInfoResponseSchema, deviceTransferApproveRequestSchema, deviceTransferApproveResponseSchema, deviceTransferDenyResponseSchema, } from './devicePairing';
47
+ export type { DevicePairingStatus, DeviceTransferInitRequest, DeviceTransferInitResponse, DeviceTransferInfoResponse, DeviceTransferApproveRequest, DeviceTransferApproveResponse, DeviceTransferDenyResponse, } from './devicePairing';
48
+ export { transparencyCheckpointSignatureSchema, transparencyAnchorSchema, transparencyCheckpointSchema, transparencyInclusionProofSchema, transparencyCheckpointListSchema, } from './transparency';
49
+ export type { TransparencyCheckpointSignature, TransparencyAnchor, TransparencyCheckpoint, TransparencyInclusionProof, TransparencyCheckpointList, } from './transparency';
@@ -61,17 +61,17 @@ export declare const encryptedBackupEnvelopeSchema: z.ZodObject<{
61
61
  }, "strip", z.ZodTypeAny, {
62
62
  version: number;
63
63
  nonce: string;
64
+ ciphertext: string;
64
65
  algorithm: "xchacha20poly1305";
65
66
  kdfInfo: string;
66
- ciphertext: string;
67
67
  publicKeyHint: string;
68
68
  createdAt: string;
69
69
  }, {
70
70
  version: number;
71
71
  nonce: string;
72
+ ciphertext: string;
72
73
  algorithm: "xchacha20poly1305";
73
74
  kdfInfo: string;
74
- ciphertext: string;
75
75
  publicKeyHint: string;
76
76
  createdAt: string;
77
77
  }>;
@@ -100,18 +100,18 @@ export declare const backupUploadRequestSchema: z.ZodObject<{
100
100
  }, "strip", z.ZodTypeAny, {
101
101
  version: number;
102
102
  nonce: string;
103
+ ciphertext: string;
103
104
  algorithm: "xchacha20poly1305";
104
105
  kdfInfo: string;
105
- ciphertext: string;
106
106
  publicKeyHint: string;
107
107
  createdAt: string;
108
108
  lookupId: string;
109
109
  }, {
110
110
  version: number;
111
111
  nonce: string;
112
+ ciphertext: string;
112
113
  algorithm: "xchacha20poly1305";
113
114
  kdfInfo: string;
114
- ciphertext: string;
115
115
  publicKeyHint: string;
116
116
  createdAt: string;
117
117
  lookupId: string;
@@ -72,17 +72,17 @@ export declare const rotateKeyCompleteRequestSchema: z.ZodObject<{
72
72
  signOutEverywhere: z.ZodOptional<z.ZodBoolean>;
73
73
  }, "strip", z.ZodTypeAny, {
74
74
  signature: string;
75
+ timestamp: number;
75
76
  challenge: string;
76
77
  newPublicKey: string;
77
78
  newKeyProof: string;
78
- timestamp: number;
79
79
  signOutEverywhere?: boolean | undefined;
80
80
  }, {
81
81
  signature: string;
82
+ timestamp: number;
82
83
  challenge: string;
83
84
  newPublicKey: string;
84
85
  newKeyProof: string;
85
- timestamp: number;
86
86
  signOutEverywhere?: boolean | undefined;
87
87
  }>;
88
88
  export type RotateKeyCompleteRequest = z.infer<typeof rotateKeyCompleteRequestSchema>;
@@ -113,6 +113,14 @@ export type PublicApplicationResponse = z.infer<typeof publicApplicationSchema>;
113
113
  * current producer and are never `null`, but stay `.optional()` so the contract
114
114
  * tolerates leaner shapes from other producers of this same payload without a
115
115
  * coordinated bump.
116
+ *
117
+ * `pushSentAt` / `openedAt` are DELIVERY PROGRESS, not authorization state: they
118
+ * let a waiting surface render "Check Commons on your phone" → "Opened in
119
+ * Commons" without inventing competing statuses. `status` remains the only
120
+ * authority on whether the request is pending, authorized, cancelled or expired.
121
+ * Both are `.nullable().optional()` for the same reason as `sessionId` — the
122
+ * producer always emits the key with `null` until that step happens, and an
123
+ * older API that omits them entirely must degrade, not fail the parse.
116
124
  */
117
125
  export declare const sessionStatusSchema: z.ZodObject<{
118
126
  status: z.ZodString;
@@ -162,6 +170,13 @@ export declare const sessionStatusSchema: z.ZodObject<{
162
170
  sessionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
163
171
  publicKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
164
172
  userId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
173
+ /**
174
+ * What approving this request does. Legacy rows read as `device_sign_in`.
175
+ * OAuth-bound sessions finalize into an authorization code (no `sessionId`).
176
+ */
177
+ purpose: z.ZodOptional<z.ZodEnum<["device_sign_in", "oauth_authorization"]>>;
178
+ pushSentAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
179
+ openedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
165
180
  }, "strip", z.ZodTypeAny, {
166
181
  status: string;
167
182
  publicKey?: string | null | undefined;
@@ -184,6 +199,9 @@ export declare const sessionStatusSchema: z.ZodObject<{
184
199
  termsUrl?: string | undefined;
185
200
  developerName?: string | undefined;
186
201
  } | null | undefined;
202
+ purpose?: "device_sign_in" | "oauth_authorization" | undefined;
203
+ pushSentAt?: string | null | undefined;
204
+ openedAt?: string | null | undefined;
187
205
  }, {
188
206
  status: string;
189
207
  publicKey?: string | null | undefined;
@@ -206,5 +224,8 @@ export declare const sessionStatusSchema: z.ZodObject<{
206
224
  termsUrl?: string | undefined;
207
225
  developerName?: string | undefined;
208
226
  } | null | undefined;
227
+ purpose?: "device_sign_in" | "oauth_authorization" | undefined;
228
+ pushSentAt?: string | null | undefined;
229
+ openedAt?: string | null | undefined;
209
230
  }>;
210
231
  export type SessionStatusResponse = z.infer<typeof sessionStatusSchema>;