@oxyhq/contracts 0.8.0 → 0.9.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,148 @@
1
+ "use strict";
2
+ /**
3
+ * Device-first bootstrap & token contracts (auth centralization, wave 1).
4
+ *
5
+ * SINGLE SOURCE OF TRUTH for the wire shape of the new device-first session
6
+ * bootstrap: the top-level `#oxy_boot=…` fragment the API hands back from
7
+ * `GET /auth/device/bootstrap`, the token bundle a boot code / web-session
8
+ * fast-path exchanges into, the persisted-refresh rotation, the native
9
+ * device-token issuance, the IdP chooser's device-resolve, and the first-party
10
+ * password login result (2FA arm vs. session arm). The API validates its OUTPUT
11
+ * against these schemas; every consumer (`@oxyhq/core`'s device-boot mixin, the
12
+ * SDK cold boot, the IdP chooser) validates its INPUT against the same
13
+ * definitions, so producer and consumers cannot drift.
14
+ *
15
+ * Design anchors (from the auth-centralization plan):
16
+ * - The bootstrap fragment carries NO tokens and NO deviceId — only a
17
+ * `state` echo (CSRF), a `reason`, a short-lived single-use `code`, and an
18
+ * opaque `deviceToken`. Tokens are obtained by exchanging the `code` at
19
+ * `POST /auth/device/exchange` (origin-bound GETDEL burn).
20
+ * - Refresh is ONE rotating, single-use family shared by web and native.
21
+ * - `loginResult` mirrors what `POST /auth/login` returns today
22
+ * (`buildSessionAuthResponse` in the API's `session.controller.ts`): either a
23
+ * 2FA challenge (`{ twoFactorRequired: true, loginToken }`) or a session
24
+ * payload. The session arm matches `SessionAuthResponse` EXACTLY, plus an
25
+ * optional `refreshToken` the new server adds for the persisted-refresh lane.
26
+ *
27
+ * Nested-object response shapes are declared as explicit `interface`s with the
28
+ * runtime schema annotated `z.ZodType<Interface>` — the same rationale as
29
+ * `identity.ts` / `userResponse.ts`: a `z.infer<>` of a nested object schema can
30
+ * degrade to `{}` under a consumer's `moduleResolution: "node"` (node10), so the
31
+ * load-bearing shapes are pinned by literal interfaces. Flat request/response
32
+ * shapes (no nested-object hazard) are inferred via `z.infer<>`.
33
+ *
34
+ * Platform-agnostic — zod only, no react/react-native/expo. ESM-safe (no
35
+ * `require()`).
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.deviceResolveResponseSchema = exports.deviceResolveRequestSchema = exports.loginResultSchema = exports.deviceTokenIssueResponseSchema = exports.tokenRefreshResponseSchema = exports.tokenRefreshRequestSchema = exports.authTokenBundleSchema = exports.deviceExchangeRequestSchema = exports.deviceBootFragmentSchema = exports.deviceBootReasonSchema = void 0;
39
+ const zod_1 = require("zod");
40
+ const userResponse_1 = require("./userResponse");
41
+ /* -------------------------------------------------------------------------- */
42
+ /* Bootstrap fragment */
43
+ /* -------------------------------------------------------------------------- */
44
+ /**
45
+ * Why the bootstrap hop resolved the way it did.
46
+ * - `session` — the device cookie resolved an active session; a `code` is
47
+ * present to exchange for tokens.
48
+ * - `no_session` — the device is known but has no active session; no `code`.
49
+ * - `new_device` — first contact; the cookie was just planted, no session yet.
50
+ */
51
+ exports.deviceBootReasonSchema = zod_1.z.enum(['session', 'no_session', 'new_device']);
52
+ /**
53
+ * The `#oxy_boot=<json>` fragment `GET /auth/device/bootstrap` appends to the
54
+ * `return_to` URL. Carries the CSRF `state` echo, the resolution `reason`, an
55
+ * optional single-use exchange `code` (present iff `reason === 'session'`), and
56
+ * the opaque `deviceToken`. NEVER carries tokens or a deviceId.
57
+ */
58
+ exports.deviceBootFragmentSchema = zod_1.z.object({
59
+ v: zod_1.z.literal(1),
60
+ state: zod_1.z.string().min(1).max(256),
61
+ reason: exports.deviceBootReasonSchema,
62
+ code: zod_1.z.string().min(20).max(128).optional(),
63
+ deviceToken: zod_1.z.string().min(20).max(512),
64
+ });
65
+ /* -------------------------------------------------------------------------- */
66
+ /* Boot-code exchange */
67
+ /* -------------------------------------------------------------------------- */
68
+ /** Request body for `POST /auth/device/exchange` — the single-use boot code. */
69
+ exports.deviceExchangeRequestSchema = zod_1.z.object({
70
+ code: zod_1.z.string().min(20).max(128),
71
+ });
72
+ exports.authTokenBundleSchema = zod_1.z.object({
73
+ sessionId: zod_1.z.string(),
74
+ accessToken: zod_1.z.string(),
75
+ refreshToken: zod_1.z.string(),
76
+ expiresAt: zod_1.z.string(),
77
+ user: userResponse_1.userResponseSchema,
78
+ });
79
+ /* -------------------------------------------------------------------------- */
80
+ /* Refresh-token rotation (web + native, one implementation) */
81
+ /* -------------------------------------------------------------------------- */
82
+ /** Request body for `POST /auth/refresh-token` — the current refresh token. */
83
+ exports.tokenRefreshRequestSchema = zod_1.z.object({
84
+ refreshToken: zod_1.z.string().min(20),
85
+ });
86
+ /**
87
+ * Wire shape of `POST /auth/refresh-token`: the rotated (single-use) family —
88
+ * a new access token, the next refresh token, the new access-token expiry, and
89
+ * the owning session id. `expiresAt` is an ISO string.
90
+ */
91
+ exports.tokenRefreshResponseSchema = zod_1.z.object({
92
+ accessToken: zod_1.z.string(),
93
+ refreshToken: zod_1.z.string(),
94
+ expiresAt: zod_1.z.string(),
95
+ sessionId: zod_1.z.string(),
96
+ });
97
+ /* -------------------------------------------------------------------------- */
98
+ /* Native device-token issuance */
99
+ /* -------------------------------------------------------------------------- */
100
+ /**
101
+ * Wire shape of `POST /auth/device/token` — issues (or rotates) the opaque
102
+ * device token for the native channel. The deviceId is taken from the bearer
103
+ * JWT claims server-side; only the token comes back.
104
+ */
105
+ exports.deviceTokenIssueResponseSchema = zod_1.z.object({
106
+ deviceToken: zod_1.z.string(),
107
+ });
108
+ const loginTwoFactorRequiredSchema = zod_1.z.object({
109
+ twoFactorRequired: zod_1.z.literal(true),
110
+ loginToken: zod_1.z.string(),
111
+ });
112
+ const loginSessionResultSchema = zod_1.z.object({
113
+ sessionId: zod_1.z.string(),
114
+ deviceId: zod_1.z.string(),
115
+ expiresAt: zod_1.z.string(),
116
+ accessToken: zod_1.z.string().optional(),
117
+ refreshToken: zod_1.z.string().optional(),
118
+ user: zod_1.z.object({
119
+ id: zod_1.z.string(),
120
+ username: zod_1.z.string().optional(),
121
+ avatar: zod_1.z.string().optional(),
122
+ }),
123
+ });
124
+ exports.loginResultSchema = zod_1.z.union([
125
+ loginTwoFactorRequiredSchema,
126
+ loginSessionResultSchema,
127
+ ]);
128
+ /* -------------------------------------------------------------------------- */
129
+ /* IdP chooser device-resolve */
130
+ /* -------------------------------------------------------------------------- */
131
+ /**
132
+ * Request body for `POST /auth/device/resolve` (X-Oxy-Internal, called by the
133
+ * IdP chooser) — the device key the chooser read from the first-party
134
+ * `oxy_device` cookie.
135
+ */
136
+ exports.deviceResolveRequestSchema = zod_1.z.object({
137
+ deviceKey: zod_1.z.string().min(20),
138
+ });
139
+ const deviceResolveAccountSchema = zod_1.z.object({
140
+ user: userResponse_1.userResponseSchema,
141
+ sessionId: zod_1.z.string(),
142
+ accessToken: zod_1.z.string(),
143
+ expiresAt: zod_1.z.string(),
144
+ });
145
+ exports.deviceResolveResponseSchema = zod_1.z.object({
146
+ activeAccountId: zod_1.z.string().nullable(),
147
+ accounts: zod_1.z.array(deviceResolveAccountSchema),
148
+ });
package/dist/cjs/index.js CHANGED
@@ -11,8 +11,8 @@
11
11
  * expo, no `require()` in the ESM build.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.personhoodStatusResultSchema = exports.personhoodBreakdownSchema = exports.personhoodVouchRecordSchema = exports.validationVoteResultSchema = exports.validationRequestSummarySchema = exports.validationOpenResultSchema = exports.validationOpenRequestSchema = exports.validationVerdictRecordSchema = exports.realLifeAttestationResultSchema = exports.realLifeAttestationRecordSchema = exports.signedPublicCardSchema = exports.publicCardSchema = exports.logPageResponseSchema = exports.chainHeadResponseSchema = exports.oxySignedRecordTypeSchema = exports.exportBundleSchema = exports.exportAttestationSchema = exports.authMethodsResponseSchema = exports.authMethodEntrySchema = exports.domainVerificationInstructionsSchema = exports.domainVerificationRequestSchema = exports.verifiedDomainSchema = exports.signedRecordEnvelopeSchema = exports.didDocumentSchema = exports.didServiceSchema = exports.verificationMethodSchema = exports.appUserSignalIngestSchema = exports.appInterestInputSchema = exports.appEndorsementInputSchema = exports.recommendationResponseSchema = exports.recommendationItemSchema = exports.recommendationCountSchema = exports.recommendationRequestSchema = exports.recommendationSignalWeightsSchema = exports.recommendationBoostSchema = exports.recommendationExcludeTypeSchema = exports.fedcmTokenPayloadSchema = exports.sessionStatusSchema = exports.publicApplicationSchema = exports.applicationTypeSchema = exports.safeParseContract = exports.resolveUserId = exports.deviceSessionsResponseSchema = exports.deviceSessionAccountSchema = exports.currentUserResponseSchema = exports.refreshAllResponseSchema = exports.refreshAllAccountSchema = exports.userProfileUpdateSchema = exports.userResponseSchema = exports.userNameSchema = void 0;
15
- exports.deviceSessionSyncSchema = exports.activeTokenSchema = exports.deviceSessionStateSchema = exports.sessionAccountSchema = exports.linkPreviewResponseSchema = exports.linkPreviewBatchResponseSchema = exports.linkPreviewBatchRequestSchema = exports.linkPreviewSchema = exports.credentialVerifyResultSchema = exports.credentialListResultSchema = exports.credentialIssueResultSchema = exports.verifiableCredentialResponseSchema = exports.credentialRecordSchema = exports.vouchResultSchema = void 0;
14
+ exports.validationVoteResultSchema = exports.validationRequestSummarySchema = exports.validationOpenResultSchema = exports.validationOpenRequestSchema = exports.validationVerdictRecordSchema = exports.realLifeAttestationResultSchema = exports.realLifeAttestationRecordSchema = exports.signedPublicCardSchema = exports.publicCardSchema = exports.logPageResponseSchema = exports.chainHeadResponseSchema = exports.oxySignedRecordTypeSchema = exports.exportBundleSchema = exports.exportAttestationSchema = exports.authMethodsResponseSchema = exports.authMethodEntrySchema = exports.domainVerificationInstructionsSchema = exports.domainVerificationRequestSchema = exports.verifiedDomainSchema = exports.signedRecordEnvelopeSchema = exports.didDocumentSchema = exports.didServiceSchema = exports.verificationMethodSchema = exports.appAffinityEventsIngestSchema = exports.appAffinityEventSchema = exports.appAffinityEventTypeSchema = exports.appUserSignalIngestSchema = exports.appInterestInputSchema = exports.appEndorsementInputSchema = exports.recommendationResponseSchema = exports.recommendationItemSchema = exports.recommendationCountSchema = exports.recommendationRequestSchema = exports.recommendationSignalWeightsSchema = exports.recommendationBoostSchema = exports.recommendationExcludeTypeSchema = exports.fedcmTokenPayloadSchema = exports.sessionStatusSchema = exports.publicApplicationSchema = exports.applicationTypeSchema = exports.safeParseContract = exports.resolveUserId = exports.deviceSessionsResponseSchema = exports.deviceSessionAccountSchema = exports.currentUserResponseSchema = exports.refreshAllResponseSchema = exports.refreshAllAccountSchema = exports.userProfileUpdateSchema = exports.userResponseSchema = exports.userNameSchema = void 0;
15
+ exports.deviceResolveResponseSchema = exports.deviceResolveRequestSchema = exports.loginResultSchema = exports.deviceTokenIssueResponseSchema = exports.tokenRefreshResponseSchema = exports.tokenRefreshRequestSchema = exports.authTokenBundleSchema = exports.deviceExchangeRequestSchema = exports.deviceBootFragmentSchema = exports.deviceBootReasonSchema = exports.deviceSessionSyncSchema = exports.activeTokenSchema = exports.deviceSessionStateSchema = exports.sessionAccountSchema = exports.linkPreviewResponseSchema = exports.linkPreviewBatchResponseSchema = exports.linkPreviewBatchRequestSchema = exports.linkPreviewSchema = exports.credentialVerifyResultSchema = exports.credentialListResultSchema = exports.credentialIssueResultSchema = exports.verifiableCredentialResponseSchema = exports.credentialRecordSchema = exports.vouchResultSchema = exports.personhoodStatusResultSchema = exports.personhoodBreakdownSchema = exports.personhoodVouchRecordSchema = void 0;
16
16
  var userResponse_1 = require("./userResponse");
17
17
  // Schemas
18
18
  Object.defineProperty(exports, "userNameSchema", { enumerable: true, get: function () { return userResponse_1.userNameSchema; } });
@@ -46,6 +46,9 @@ Object.defineProperty(exports, "recommendationResponseSchema", { enumerable: tru
46
46
  Object.defineProperty(exports, "appEndorsementInputSchema", { enumerable: true, get: function () { return recommendations_1.appEndorsementInputSchema; } });
47
47
  Object.defineProperty(exports, "appInterestInputSchema", { enumerable: true, get: function () { return recommendations_1.appInterestInputSchema; } });
48
48
  Object.defineProperty(exports, "appUserSignalIngestSchema", { enumerable: true, get: function () { return recommendations_1.appUserSignalIngestSchema; } });
49
+ Object.defineProperty(exports, "appAffinityEventTypeSchema", { enumerable: true, get: function () { return recommendations_1.appAffinityEventTypeSchema; } });
50
+ Object.defineProperty(exports, "appAffinityEventSchema", { enumerable: true, get: function () { return recommendations_1.appAffinityEventSchema; } });
51
+ Object.defineProperty(exports, "appAffinityEventsIngestSchema", { enumerable: true, get: function () { return recommendations_1.appAffinityEventsIngestSchema; } });
49
52
  var identity_1 = require("./identity");
50
53
  // Schemas
51
54
  Object.defineProperty(exports, "verificationMethodSchema", { enumerable: true, get: function () { return identity_1.verificationMethodSchema; } });
@@ -98,3 +101,15 @@ Object.defineProperty(exports, "sessionAccountSchema", { enumerable: true, get:
98
101
  Object.defineProperty(exports, "deviceSessionStateSchema", { enumerable: true, get: function () { return deviceSession_1.deviceSessionStateSchema; } });
99
102
  Object.defineProperty(exports, "activeTokenSchema", { enumerable: true, get: function () { return deviceSession_1.activeTokenSchema; } });
100
103
  Object.defineProperty(exports, "deviceSessionSyncSchema", { enumerable: true, get: function () { return deviceSession_1.deviceSessionSyncSchema; } });
104
+ var deviceBoot_1 = require("./deviceBoot");
105
+ // Schemas
106
+ Object.defineProperty(exports, "deviceBootReasonSchema", { enumerable: true, get: function () { return deviceBoot_1.deviceBootReasonSchema; } });
107
+ Object.defineProperty(exports, "deviceBootFragmentSchema", { enumerable: true, get: function () { return deviceBoot_1.deviceBootFragmentSchema; } });
108
+ Object.defineProperty(exports, "deviceExchangeRequestSchema", { enumerable: true, get: function () { return deviceBoot_1.deviceExchangeRequestSchema; } });
109
+ Object.defineProperty(exports, "authTokenBundleSchema", { enumerable: true, get: function () { return deviceBoot_1.authTokenBundleSchema; } });
110
+ Object.defineProperty(exports, "tokenRefreshRequestSchema", { enumerable: true, get: function () { return deviceBoot_1.tokenRefreshRequestSchema; } });
111
+ Object.defineProperty(exports, "tokenRefreshResponseSchema", { enumerable: true, get: function () { return deviceBoot_1.tokenRefreshResponseSchema; } });
112
+ Object.defineProperty(exports, "deviceTokenIssueResponseSchema", { enumerable: true, get: function () { return deviceBoot_1.deviceTokenIssueResponseSchema; } });
113
+ Object.defineProperty(exports, "loginResultSchema", { enumerable: true, get: function () { return deviceBoot_1.loginResultSchema; } });
114
+ Object.defineProperty(exports, "deviceResolveRequestSchema", { enumerable: true, get: function () { return deviceBoot_1.deviceResolveRequestSchema; } });
115
+ Object.defineProperty(exports, "deviceResolveResponseSchema", { enumerable: true, get: function () { return deviceBoot_1.deviceResolveResponseSchema; } });
@@ -12,7 +12,7 @@
12
12
  * / expo, ESM-safe).
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.appUserSignalIngestSchema = exports.appInterestInputSchema = exports.appEndorsementInputSchema = exports.recommendationResponseSchema = exports.recommendationItemSchema = exports.recommendationCountSchema = exports.recommendationRequestSchema = exports.recommendationSignalWeightsSchema = exports.recommendationBoostSchema = exports.recommendationExcludeTypeSchema = void 0;
15
+ exports.appAffinityEventsIngestSchema = exports.appAffinityEventSchema = exports.appAffinityEventTypeSchema = exports.appUserSignalIngestSchema = exports.appInterestInputSchema = exports.appEndorsementInputSchema = exports.recommendationResponseSchema = exports.recommendationItemSchema = exports.recommendationCountSchema = exports.recommendationRequestSchema = exports.recommendationSignalWeightsSchema = exports.recommendationBoostSchema = exports.recommendationExcludeTypeSchema = void 0;
16
16
  const zod_1 = require("zod");
17
17
  const userResponse_1 = require("./userResponse");
18
18
  /** User-type filters a caller may exclude from the recommendation surface. */
@@ -46,6 +46,7 @@ exports.recommendationSignalWeightsSchema = zod_1.z
46
46
  interest: zod_1.z.number().min(0).max(10).optional(),
47
47
  appBoost: zod_1.z.number().min(0).max(10).optional(),
48
48
  repCandidate: zod_1.z.number().min(0).max(10).optional(),
49
+ affinity: zod_1.z.number().min(0).max(10).optional(),
49
50
  })
50
51
  .partial();
51
52
  /**
@@ -123,3 +124,45 @@ exports.appUserSignalIngestSchema = zod_1.z
123
124
  interests: zod_1.z.array(exports.appInterestInputSchema).max(500).optional(),
124
125
  })
125
126
  .refine((value) => (value.endorsements?.length ?? 0) > 0 || (value.interests?.length ?? 0) > 0, { message: 'At least one of endorsements or interests must be non-empty' });
127
+ /**
128
+ * The directed interaction types a consuming app may report between two users.
129
+ * Each type carries a server-side default weight (see the API's
130
+ * `AFFINITY_EVENT_WEIGHTS`); a caller may override the applied weight per event.
131
+ */
132
+ exports.appAffinityEventTypeSchema = zod_1.z.enum([
133
+ 'like',
134
+ 'reply',
135
+ 'boost',
136
+ 'follow',
137
+ 'mention',
138
+ 'profile_view',
139
+ 'quote',
140
+ 'repost',
141
+ ]);
142
+ /**
143
+ * One directed interaction event: `fromUserId` interacted with `toUserId`
144
+ * (`type`) at `occurredAt`. The Oxy affinity-graph folds these into a per-app,
145
+ * time-decayed directed affinity edge (`fromUserId → toUserId`).
146
+ *
147
+ * - `weight` (optional) overrides the per-type default weight for this event.
148
+ * - `occurredAt` (optional, ISO) is the event time; absent means "now" at ingest.
149
+ * - `eventId` (optional) makes an event idempotent — a repeated `eventId` for the
150
+ * same application is folded at most once (bounded dedup window).
151
+ */
152
+ exports.appAffinityEventSchema = zod_1.z.object({
153
+ fromUserId: zod_1.z.string().trim().min(1),
154
+ toUserId: zod_1.z.string().trim().min(1),
155
+ type: exports.appAffinityEventTypeSchema,
156
+ weight: zod_1.z.number().min(0).max(100).optional(),
157
+ occurredAt: zod_1.z.string().datetime().optional(),
158
+ eventId: zod_1.z.string().trim().min(1).max(200).optional(),
159
+ });
160
+ /**
161
+ * Request body for `POST /app-signals/events` (service token, `signals:write`).
162
+ *
163
+ * A non-empty batch (1..1000) of directed interaction events for the requesting
164
+ * application. Self-edges (`fromUserId === toUserId`) are dropped server-side.
165
+ */
166
+ exports.appAffinityEventsIngestSchema = zod_1.z.object({
167
+ events: zod_1.z.array(exports.appAffinityEventSchema).min(1).max(1000),
168
+ });