@oxyhq/contracts 0.21.0 → 0.23.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.
@@ -1,48 +1,359 @@
1
1
  "use strict";
2
2
  /**
3
- * Account graph wire contracts — organization taxonomy and create-account input.
3
+ * Account graph wire contracts — the account-kind vocabulary, the account
4
+ * category taxonomy, and the create-account input.
4
5
  *
5
- * `organizationCategory` classifies `kind: 'organization'` accounts (agency,
6
- * cooperative, landlord, …) without polluting `User.kind`. Meaningful only when
7
- * `kind === 'organization'`.
6
+ * `accountCategories` classifies a NON-PERSONAL account — what it is about, what
7
+ * it does without polluting `User.kind`. See the block above
8
+ * {@link ACCOUNT_CATEGORY_IDS} for the four rules that govern it.
8
9
  */
9
10
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.createAccountRequestSchema = exports.organizationCategorySchema = exports.ORGANIZATION_CATEGORIES = void 0;
11
+ exports.createAccountRequestSchema = exports.ACCOUNT_CATEGORY_KINDS = exports.accountCategoriesSchema = exports.MAX_ACCOUNT_CATEGORIES = exports.SELECTABLE_ACCOUNT_CATEGORY_IDS = exports.RETIRED_ACCOUNT_CATEGORY_IDS = exports.accountCategoryIdSchema = exports.ACCOUNT_CATEGORY_IDS = exports.childAccountKindSchema = exports.CHILD_ACCOUNT_KINDS = exports.accountKindSchema = exports.ACCOUNT_KINDS = void 0;
12
+ exports.isActAsEligibleKind = isActAsEligibleKind;
13
+ exports.isAccountKind = isAccountKind;
14
+ exports.isSelectableAccountCategoryId = isSelectableAccountCategoryId;
15
+ exports.newlyAddedRetiredCategories = newlyAddedRetiredCategories;
16
+ exports.kindAcceptsAccountCategories = kindAcceptsAccountCategories;
11
17
  const zod_1 = require("zod");
12
- exports.ORGANIZATION_CATEGORIES = [
18
+ /**
19
+ * The union is spelled out above and the array proves coverage BOTH ways
20
+ * (`satisfies` here, the `Gap` alias below) — the same shape this package's
21
+ * `ACCOUNT_CATEGORY_IDS` / `TRUST_TIERS` pairs use, and the one
22
+ * `db/schema/users.ts` mirrors to keep the `users_kind_check` CHECK honest.
23
+ *
24
+ * Deriving the union from the array instead would cost nothing here and be paid
25
+ * by consumers: `kind` travels into `@oxyhq/services` through
26
+ * `SwitchableAccount`, where an indexed-access type is materially more
27
+ * expensive to check than a literal union.
28
+ */
29
+ exports.ACCOUNT_KINDS = [
30
+ 'personal',
31
+ 'organization',
32
+ 'project',
33
+ 'bot',
34
+ 'channel',
35
+ ];
36
+ exports.accountKindSchema = zod_1.z.enum(exports.ACCOUNT_KINDS);
37
+ exports.CHILD_ACCOUNT_KINDS = [
38
+ 'organization',
39
+ 'project',
40
+ 'bot',
41
+ 'channel',
42
+ ];
43
+ exports.childAccountKindSchema = zod_1.z.enum(exports.CHILD_ACCOUNT_KINDS);
44
+ /**
45
+ * Whether an operator may ACT AS an account of this kind — switch the whole app
46
+ * into it (`POST /accounts/:id/switch`) or authorise an app to act as it
47
+ * (an OAuth delegated subject).
48
+ *
49
+ * Two kinds are refused, for opposite reasons:
50
+ *
51
+ * - `personal` is a human login, so assuming it would be impersonation.
52
+ * - `channel` is a CONTENT identity, not an operating one. A channel exists so
53
+ * that posts can be authored BY it; it is never a seat anybody occupies. Its
54
+ * operators act on it through their own membership, and an application
55
+ * publishes to it with its own credential. Refusing act-as is what makes
56
+ * "no login, ever" structural rather than incidental: no session can be
57
+ * minted whose subject is a channel, so no bearer exists that could add an
58
+ * auth method to one (every auth-method write resolves its target from the
59
+ * authenticated subject, never from a parameter).
60
+ *
61
+ * Consumers must gate on this predicate rather than testing `kind === 'personal'`,
62
+ * which silently admits every kind added after it was written.
63
+ */
64
+ function isActAsEligibleKind(kind) {
65
+ return kind === 'organization' || kind === 'project' || kind === 'bot';
66
+ }
67
+ /**
68
+ * Narrow an unknown value to an {@link AccountKind}.
69
+ *
70
+ * The user-DTO serializers read from structurally-permissive `unknown` sources
71
+ * (a Drizzle row, a Mongo document, an already-formatted object), so each one
72
+ * would otherwise hand-roll this check and they would drift on what counts.
73
+ */
74
+ function isAccountKind(value) {
75
+ return typeof value === 'string' && exports.ACCOUNT_KINDS.includes(value);
76
+ }
77
+ // ===========================================================================
78
+ // Account categories
79
+ //
80
+ // Four rules govern this taxonomy. Each one is here because the obvious
81
+ // alternative fails silently rather than loudly.
82
+ //
83
+ // 1. AN ID IS AN OPAQUE, IMMUTABLE SLUG — the LABEL is not stored anywhere.
84
+ // Every value below is an identifier that no rename may ever change. The
85
+ // human-readable label lives in each client's own translation catalogue,
86
+ // keyed by the id. Re-labelling `agency` from "Real estate agency" to
87
+ // "Agency" is therefore a one-line locale edit that touches no row; moving
88
+ // the label into the column or the DTO would make the same edit a data
89
+ // migration, and would pin every reader to the language of whoever chose it.
90
+ //
91
+ // 2. THE PRIMARY CATEGORY IS THE FIRST ELEMENT of an ordered list. Not a
92
+ // separate flag, and not a second field: a flag admits two primaries and
93
+ // admits none, and ordering makes both unrepresentable. The cost is that
94
+ // ORDER IS DATA — anything that re-serializes, sorts or de-duplicates this
95
+ // list can change which category is primary without erroring, so no layer
96
+ // between the column and the client may reorder it.
97
+ //
98
+ // 3. THE VOCABULARY IS APPEND-ONLY. `ACCOUNT_CATEGORY_IDS` may gain ids and may
99
+ // never lose one, because rows already carry the ids it holds. Withdrawing a
100
+ // category means listing it in {@link RETIRED_ACCOUNT_CATEGORY_IDS}, which
101
+ // removes it from the picker while leaving it readable and re-writable. The
102
+ // database enforces the same asymmetry: its CHECK is re-evaluated on EVERY
103
+ // update to a row, so narrowing the allowed set makes an unrelated write —
104
+ // saving a bio — fail on any account that had picked the withdrawn value.
105
+ // Measured on a real Postgres, `NOT VALID` included; it does not help.
106
+ //
107
+ // 4. THE SET IS CLOSED. An id nobody can validate is an id no client can render:
108
+ // the label comes from a translation key derived from the id, so an invented
109
+ // value paints as a blank or a raw slug in every app at once. Closing it also
110
+ // costs nothing that rule 3 does not already charge — adding a category needs
111
+ // a migration to widen the CHECK whether or not the enum exists, so an open
112
+ // list would buy back no work, only the validation.
113
+ // ===========================================================================
114
+ /**
115
+ * Every account category, by stable id.
116
+ *
117
+ * Grouped by comment for readability only; the storage, the wire and the picker
118
+ * all treat this as one flat list. `other` is the escape hatch for an account
119
+ * that fits nothing here.
120
+ *
121
+ * TO ADD ONE: append an id (lowercase ASCII, `snake_case`) here, publish
122
+ * `@oxyhq/contracts`, then ship a migration that widens
123
+ * `users_account_categories_check` — never edit an existing migration — and add
124
+ * an `accounts.accountCategory.<id>` label to each client's locales.
125
+ *
126
+ * TO WITHDRAW ONE: leave the id here and add it to
127
+ * {@link RETIRED_ACCOUNT_CATEGORY_IDS}. See rule 3 above.
128
+ */
129
+ exports.ACCOUNT_CATEGORY_IDS = [
130
+ // ---- media & public information -----------------------------------------
131
+ 'news',
132
+ 'politics',
133
+ // ---- business & economy --------------------------------------------------
134
+ 'business',
135
+ 'startup',
136
+ 'finance',
137
+ 'crypto',
138
+ 'marketplace',
139
+ 'retail',
140
+ // The four ids the single-valued `organizationCategory` field used to hold,
141
+ // carried forward VERBATIM. Their labels may be rewritten freely; their ids
142
+ // may not, because live rows hold them.
143
+ 'real_estate',
13
144
  'agency',
14
- 'cooperative',
15
145
  'landlord',
146
+ 'cooperative',
147
+ 'architecture',
148
+ // ---- technology ----------------------------------------------------------
149
+ 'technology',
150
+ 'software',
151
+ 'ai',
152
+ 'security',
153
+ 'automation',
154
+ // ---- knowledge -----------------------------------------------------------
155
+ 'science',
156
+ 'education',
157
+ 'books',
158
+ // ---- health --------------------------------------------------------------
159
+ 'health',
160
+ 'fitness',
161
+ // ---- sport & play --------------------------------------------------------
162
+ 'sports',
163
+ 'gaming',
164
+ // ---- culture & entertainment ---------------------------------------------
165
+ //
166
+ // There is deliberately NO generic `entertainment` here, and re-adding one is
167
+ // a regression rather than a gap. It is the only id this list ever carried
168
+ // that was dominated by its own specifics — `film`, `music`, `gaming` and
169
+ // `comedy` all exist — and a generic drawer sitting beside its four
170
+ // concretions collects the lazy pick, which degrades the data for all four at
171
+ // once: the accounts that would have said `film` say `entertainment` instead,
172
+ // and `film` stops meaning what it meant.
173
+ //
174
+ // The other overlaps in this vocabulary are NOT the same case and must not be
175
+ // merged on this reasoning: `sports`/`fitness`, `art`/`photography`,
176
+ // `business`/`startup`, `finance`/`crypto`, `home_garden`/`diy` and
177
+ // `technology`/`software`/`security` are genuinely different audiences, and
178
+ // with a cap of four the granularity is cheap.
179
+ 'music',
180
+ 'film',
181
+ 'podcast',
182
+ 'art',
183
+ 'photography',
184
+ 'comedy',
185
+ // ---- everyday life -------------------------------------------------------
186
+ 'food',
187
+ 'travel',
188
+ 'fashion',
189
+ 'home_garden',
190
+ 'diy',
191
+ 'automotive',
192
+ 'animals',
193
+ 'family',
194
+ // ---- society -------------------------------------------------------------
195
+ 'nonprofit',
196
+ 'government',
197
+ 'community',
198
+ 'activism',
199
+ 'environment',
200
+ 'religion',
201
+ // ---- fallback ------------------------------------------------------------
16
202
  'other',
17
203
  ];
18
- exports.organizationCategorySchema = zod_1.z.enum(exports.ORGANIZATION_CATEGORIES);
204
+ /**
205
+ * Accepts EVERY id, withdrawn ones included — see rule 3.
206
+ *
207
+ * A schema that rejected a withdrawn id would 400 the whole request whenever a
208
+ * client round-trips the categories it was served, so an account that had
209
+ * picked one could no longer save its bio either. That is the same failure the
210
+ * nullable `bio` / `avatar` fix addressed, wearing a different hat.
211
+ */
212
+ exports.accountCategoryIdSchema = zod_1.z.enum(exports.ACCOUNT_CATEGORY_IDS);
213
+ /**
214
+ * Ids withdrawn from the picker. Empty today.
215
+ *
216
+ * A withdrawn id keeps working everywhere it is already stored: it validates,
217
+ * it survives a round-trip save, it still renders from its label key, and it
218
+ * stays PRIMARY if it was primary. Nothing rewrites a stored list — a read-time
219
+ * or migration-time demotion would silently replace a choice its owner made,
220
+ * which is precisely what stable ids exist to prevent. The owner drops it on
221
+ * their next edit; until then it is honoured.
222
+ *
223
+ * What withdrawal changes is only this: the id leaves
224
+ * {@link SELECTABLE_ACCOUNT_CATEGORY_IDS}, so no picker offers it, and
225
+ * {@link newlyAddedRetiredCategories} refuses to let a write ADD it to an
226
+ * account that did not already have it.
227
+ */
228
+ exports.RETIRED_ACCOUNT_CATEGORY_IDS = [];
229
+ /** Whether a category may still be OFFERED. A stored one is readable either way. */
230
+ function isSelectableAccountCategoryId(id) {
231
+ return !exports.RETIRED_ACCOUNT_CATEGORY_IDS.includes(id);
232
+ }
233
+ /** The ids a picker may offer, in declaration order. */
234
+ exports.SELECTABLE_ACCOUNT_CATEGORY_IDS = exports.ACCOUNT_CATEGORY_IDS.filter(isSelectableAccountCategoryId);
235
+ /**
236
+ * Which of `next` are withdrawn ids the account did not already carry — i.e.
237
+ * the ones a write must be refused for.
238
+ *
239
+ * `retired` is a parameter rather than a module read so the rule can be
240
+ * exercised against a non-empty set while the production one is empty; a test
241
+ * over `RETIRED_ACCOUNT_CATEGORY_IDS` alone would pass vacuously today and stay
242
+ * passing if the rule were deleted.
243
+ */
244
+ function newlyAddedRetiredCategories(next, previous, retired) {
245
+ return next.filter((id) => retired.includes(id) && !previous.includes(id));
246
+ }
247
+ /**
248
+ * How many categories one account may carry.
249
+ *
250
+ * Four, not "as many as you like". Three reasons, in the order they bind:
251
+ *
252
+ * - The primary has to MEAN something. At ten categories the first element
253
+ * reads as a sort artifact rather than a choice, and rule 2 above is the
254
+ * entire mechanism by which a primary exists.
255
+ * - The profile RENDERS them as a row of chips; four labels of this length is
256
+ * what fits a phone-width profile header before the row wraps or truncates.
257
+ * - Four is enough to place a genuinely compound account without a tag cloud:
258
+ * a housing cooperative that is also a non-profit serving a local community
259
+ * spends `cooperative`, `nonprofit`, `community`, `real_estate` — and is the
260
+ * most compound real example in the ecosystem.
261
+ *
262
+ * One constant, read by the wire schema, the database CHECK and the picker, so
263
+ * changing it is one edit plus a migration.
264
+ */
265
+ exports.MAX_ACCOUNT_CATEGORIES = 4;
266
+ /**
267
+ * An account's categories on the wire. ORDER IS MEANINGFUL — index 0 is the
268
+ * primary (rule 2).
269
+ *
270
+ * A duplicate is REJECTED rather than silently collapsed. De-duplicating would
271
+ * rewrite the caller's list, and any rewrite of this list can move which id sits
272
+ * at index 0 — so the one repair available here is the one that would break the
273
+ * property the list exists to carry. A duplicate only ever comes from a client
274
+ * bug, and a 400 naming the index is how that bug gets found.
275
+ */
276
+ exports.accountCategoriesSchema = zod_1.z
277
+ .array(exports.accountCategoryIdSchema)
278
+ .max(exports.MAX_ACCOUNT_CATEGORIES)
279
+ .superRefine((ids, ctx) => {
280
+ const seen = new Set();
281
+ ids.forEach((id, index) => {
282
+ if (seen.has(id)) {
283
+ ctx.addIssue({
284
+ code: zod_1.z.ZodIssueCode.custom,
285
+ message: `Duplicate account category "${id}"`,
286
+ path: [index],
287
+ });
288
+ }
289
+ seen.add(id);
290
+ });
291
+ });
292
+ /**
293
+ * Kinds that may carry categories: every kind EXCEPT `personal`.
294
+ *
295
+ * A person has interests, not a sector — and their interests are not a
296
+ * classification anybody else gets to read off their profile. Spelled out
297
+ * positively, like {@link isActAsEligibleKind} and for the same reason: a `kind
298
+ * !== 'personal'` test silently admits every kind invented after it was
299
+ * written, whereas this list forces whoever adds one to decide.
300
+ */
301
+ exports.ACCOUNT_CATEGORY_KINDS = [
302
+ 'organization',
303
+ 'project',
304
+ 'bot',
305
+ 'channel',
306
+ ];
307
+ /**
308
+ * Whether an account of this kind may carry categories.
309
+ *
310
+ * The API refuses the write and the `users_account_categories_kind_check`
311
+ * constraint makes it unrepresentable; both derive from
312
+ * {@link ACCOUNT_CATEGORY_KINDS}, so they cannot disagree.
313
+ */
314
+ function kindAcceptsAccountCategories(kind) {
315
+ return exports.ACCOUNT_CATEGORY_KINDS.includes(kind ?? '');
316
+ }
317
+ /**
318
+ * An account's name on the create/update wire.
319
+ *
320
+ * `displayName` is EXPLICIT and stored, not derived. `first`/`last` model a
321
+ * human name, and composing a display string from them is right for a person —
322
+ * but a non-personal account has a TITLE, not a given and family name. Without
323
+ * this field the only way to name a channel "Notas de Nate" was to put the whole
324
+ * title in `first`, which renders correctly by accident while recording it as
325
+ * somebody's given name.
326
+ *
327
+ * When present it wins over the composed `first`/`last` (see the API's
328
+ * `composeDisplayName`, which already preferred an explicit value — only the
329
+ * storage for one was missing).
330
+ */
19
331
  const accountNameSchema = zod_1.z
20
332
  .object({
21
333
  first: zod_1.z.string().trim().max(100).optional(),
22
334
  last: zod_1.z.string().trim().max(100).optional(),
335
+ displayName: zod_1.z.string().trim().max(100).optional(),
23
336
  })
24
337
  .optional();
25
338
  /**
26
339
  * POST /accounts — create a non-personal account under the caller's tree.
27
- * `organizationCategory` is accepted only when `kind` is `organization`.
340
+ *
341
+ * No cross-field refinement guards `accountCategories`, and that is not an
342
+ * omission: `kind` here is a CHILD kind, and every child kind is in
343
+ * {@link ACCOUNT_CATEGORY_KINDS}, so `personal` is already unrepresentable on
344
+ * this route. The refinement the single-valued predecessor needed disappeared
345
+ * along with the restriction that made it necessary. A child kind that does NOT
346
+ * accept categories would break that reasoning silently, so
347
+ * `__tests__/accountGraph.test.ts` asserts the two lists agree.
28
348
  */
29
- exports.createAccountRequestSchema = zod_1.z
30
- .object({
349
+ exports.createAccountRequestSchema = zod_1.z.object({
31
350
  parentAccountId: zod_1.z.string().trim().min(1).optional(),
32
- kind: zod_1.z.enum(['organization', 'project', 'bot']),
351
+ kind: exports.childAccountKindSchema,
33
352
  username: zod_1.z.string().trim().min(1).max(100),
34
353
  name: accountNameSchema,
35
354
  bio: zod_1.z.string().trim().max(500).optional(),
36
355
  avatar: zod_1.z.string().optional(),
37
356
  description: zod_1.z.string().trim().max(1000).optional(),
38
- organizationCategory: exports.organizationCategorySchema.optional(),
39
- })
40
- .superRefine((data, ctx) => {
41
- if (data.organizationCategory !== undefined && data.kind !== 'organization') {
42
- ctx.addIssue({
43
- code: zod_1.z.ZodIssueCode.custom,
44
- message: 'organizationCategory applies only when kind is organization',
45
- path: ['organizationCategory'],
46
- });
47
- }
357
+ /** Ordered, PRIMARY FIRST — see rule 2 above {@link ACCOUNT_CATEGORY_IDS}. */
358
+ accountCategories: exports.accountCategoriesSchema.optional(),
48
359
  });
@@ -48,10 +48,11 @@ exports.deviceTokenMintRequestSchema = zod_1.z.object({
48
48
  });
49
49
  /**
50
50
  * Wire shape of a successful `POST /session/device/token`: the freshly-minted
51
- * short access token for the active account, its expiry, the NEXT rotating
52
- * device secret the client must persist (rotation-in-use — the presented secret
53
- * stays valid for a short grace so multi-tab races don't lock out), and the
54
- * projected device-session state.
51
+ * short access token for the active account, its expiry, the device secret the
52
+ * client must persist (`nextDeviceSecret`on mint this echoes the presented
53
+ * secret unchanged so concurrent refreshes from multiple origins do not race),
54
+ * and the projected device-session state. Sign-in rotates the secret via
55
+ * `issueDeviceSecret`; mint does not.
55
56
  */
56
57
  exports.deviceTokenMintResponseSchema = zod_1.z.object({
57
58
  accessToken: zod_1.z.string(),
package/dist/cjs/index.js CHANGED
@@ -11,14 +11,28 @@
11
11
  * expo, no `require()` in the ESM build.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- 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.oxyUserInvalidationEventSchema = exports.isPublishedOxyUserChangeReason = exports.OXY_PUBLISHED_USER_CHANGE_REASONS = exports.OXY_USER_CHANGE_REASONS = exports.OXY_USER_INVALIDATION_CHANNEL = exports.inboxEmailPushDataSchema = exports.INBOX_EMAIL_PUSH_TYPE = exports.INBOX_EMAIL_PUSH_CHANNEL = exports.IDENTITY_APPROVAL_PUSH_CHANNEL = exports.commonsDenyReasonSchema = exports.COMMONS_DENY_REASONS = exports.sessionStatusSchema = exports.publicApplicationSchema = exports.applicationTypeSchema = exports.safeParseContract = exports.resolveUserId = exports.deviceLinkedSessionsResponseSchema = exports.deviceLinkedSessionSchema = exports.currentUserResponseSchema = exports.userProfileUpdateSchema = exports.userResponseSchema = exports.themePreferenceSchema = exports.userRelationshipSchema = exports.userNameSchema = exports.createAccountRequestSchema = exports.organizationCategorySchema = exports.ORGANIZATION_CATEGORIES = void 0;
15
- exports.upsertReputationRuleSchema = exports.resolveReputationDisputeSchema = exports.createReputationDisputeSchema = exports.awardReputationSchema = exports.reverseReputationTransactionResultSchema = exports.reputationInfluenceResultSchema = exports.reputationLeaderboardEntrySchema = exports.reputationLeaderboardUserSchema = exports.reputationRuleSchema = exports.reputationDisputeSchema = exports.reputationBalanceSchema = exports.reputationBalanceSummarySchema = exports.reputationReliabilitySchema = exports.reputationInfluenceSchema = exports.reputationBalanceBreakdownSchema = exports.reputationTransactionSchema = exports.reputationInfluenceContextSchema = exports.reputationDisputeStatusSchema = exports.reputationTargetEntityTypeSchema = exports.trustTierSchema = exports.reputationTransactionStatusSchema = exports.reputationCategorySchema = exports.REPUTATION_INFLUENCE_CONTEXTS = exports.REPUTATION_DISPUTE_STATUSES = exports.REPUTATION_TARGET_ENTITY_TYPES = exports.TRUST_TIERS = exports.REPUTATION_TRANSACTION_STATUSES = exports.REPUTATION_CATEGORIES = exports.credentialVerifyResultSchema = exports.credentialListResultSchema = exports.credentialIssueResultSchema = exports.verifiableCredentialResponseSchema = exports.credentialRecordSchema = exports.vouchResultSchema = 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 = void 0;
16
- exports.linkPreviewBatchRequestSchema = exports.linkPreviewSchema = exports.applicationModerationTrustSchema = exports.reputationContextualInfluenceSchema = exports.reputationReviewingSchema = exports.reputationReportingSchema = exports.reputationConductSchema = exports.reputationContributionSchema = exports.reputationPersonhoodSchema = exports.identityBindingSchema = exports.registerIdentityBindingSchema = exports.reverseModerationEffectResultSchema = exports.applyModerationDecisionResultSchema = exports.moderationEffectSchema = exports.reverseModerationEffectSchema = exports.finalizeModerationDecisionSchema = exports.moderationDecisionEventSchema = exports.moderationPolicyVersionsSchema = exports.moderationDecisionEventSubjectSchema = exports.moderationFindingSchema = exports.applicationModerationStandingSchema = exports.identityBindingStatusSchema = exports.identityBindingTypeSchema = exports.personhoodStatusSchema = exports.contributionTierSchema = exports.conductStandingSchema = exports.conductStrikeStatusSchema = exports.moderationEffectSkipReasonSchema = exports.moderationEffectStatusSchema = exports.moderationEffectTypeSchema = exports.moderationDecisionStatusSchema = exports.moderationAttributionSchema = exports.moderationFindingScopeSchema = exports.moderationSeveritySchema = exports.APPLICATION_MODERATION_STANDINGS = exports.IDENTITY_BINDING_STATUSES = exports.IDENTITY_BINDING_TYPES = exports.PERSONHOOD_STATUSES = exports.CONTRIBUTION_TIERS = exports.CONDUCT_STANDINGS = exports.CONDUCT_STRIKE_STATUSES = exports.MODERATION_EFFECT_SKIP_REASONS = exports.MODERATION_EFFECT_STATUSES = exports.MODERATION_EFFECT_TYPES = exports.MODERATION_DECISION_STATUSES = exports.MODERATION_ATTRIBUTIONS = exports.MODERATION_FINDING_SCOPES = exports.MODERATION_SEVERITIES = exports.isFullReputationBalance = exports.reverseReputationTransactionSchema = void 0;
17
- exports.webauthnLoginOptionsRequestSchema = exports.webauthnRegisterOptionsRequestSchema = exports.updateRolloutPatchSchema = exports.promoteRequestSchema = exports.rollbackToEmbeddedRequestSchema = exports.rollbackRequestSchema = exports.updateListResponseSchema = exports.channelListResponseSchema = exports.channelSchema = exports.rollbackToEmbeddedEntrySchema = exports.createUpdateResponseSchema = exports.updateSchema = exports.createUpdateRequestSchema = exports.updateAssetRefSchema = exports.assetCompleteResponseSchema = exports.assetCompleteResultItemSchema = exports.assetCompleteRequestSchema = exports.assetInitResponseSchema = exports.assetUploadTicketSchema = exports.assetInitRequestSchema = exports.assetInitItemSchema = exports.rolloutPercentSchema = exports.runtimeVersionSchema = exports.channelNameSchema = exports.sha256HexSchema = exports.updateAssetStatusSchema = exports.updateStatusSchema = exports.updatePlatformSchema = exports.backupStatusResponseSchema = exports.backupUploadRequestSchema = exports.encryptedBackupEnvelopeSchema = exports.backupLookupIdSchema = exports.rotateKeyCompleteResponseSchema = exports.rotateKeyCompleteRequestSchema = exports.rotateKeyChallengeResponseSchema = exports.loginResultSchema = exports.sessionAccountsChangedEventSchema = exports.sessionAccountsChangedReasonSchema = exports.SESSION_ACCOUNTS_CHANGED_EVENT = exports.deviceBackgroundTokenResponseSchema = exports.deviceBackgroundTokenRequestSchema = exports.deviceBackgroundCredentialResponseSchema = exports.deviceTokenMintResponseSchema = exports.deviceTokenMintRequestSchema = exports.deviceSessionSyncSchema = exports.activeTokenSchema = exports.deviceSessionStateSchema = exports.sessionAccountSchema = exports.linkPreviewResponseSchema = exports.linkPreviewBatchResponseSchema = void 0;
18
- exports.transparencyCheckpointListSchema = exports.transparencyInclusionProofSchema = exports.transparencyCheckpointSchema = exports.transparencyAnchorSchema = exports.transparencyCheckpointSignatureSchema = exports.deviceTransferDenyResponseSchema = exports.deviceTransferApproveResponseSchema = exports.deviceTransferApproveRequestSchema = exports.deviceTransferInfoResponseSchema = exports.deviceTransferInitResponseSchema = exports.deviceTransferInitRequestSchema = exports.devicePairingStatusSchema = exports.webauthnLoginVerifyRequestSchema = exports.webauthnRegisterVerifyRequestSchema = void 0;
14
+ exports.appInterestInputSchema = exports.appEndorsementInputSchema = exports.recommendationResponseSchema = exports.recommendationItemSchema = exports.recommendationCountSchema = exports.recommendationRequestSchema = exports.recommendationSignalWeightsSchema = exports.recommendationBoostSchema = exports.recommendationExcludeTypeSchema = exports.oxyUserInvalidationEventSchema = exports.isPublishedOxyUserChangeReason = exports.OXY_PUBLISHED_USER_CHANGE_REASONS = exports.OXY_USER_CHANGE_REASONS = exports.OXY_USER_INVALIDATION_CHANNEL = exports.inboxEmailPushDataSchema = exports.INBOX_EMAIL_PUSH_TYPE = exports.INBOX_EMAIL_PUSH_CHANNEL = exports.IDENTITY_APPROVAL_PUSH_CHANNEL = exports.commonsDenyReasonSchema = exports.COMMONS_DENY_REASONS = exports.sessionStatusSchema = exports.publicApplicationSchema = exports.applicationTypeSchema = exports.safeParseContract = exports.resolveUserId = exports.deviceLinkedSessionsResponseSchema = exports.deviceLinkedSessionSchema = exports.currentUserResponseSchema = exports.userProfileUpdateSchema = exports.userResponseSchema = exports.themePreferenceSchema = exports.userRelationshipSchema = exports.userNameSchema = exports.createAccountRequestSchema = exports.SELECTABLE_ACCOUNT_CATEGORY_IDS = exports.RETIRED_ACCOUNT_CATEGORY_IDS = exports.newlyAddedRetiredCategories = exports.MAX_ACCOUNT_CATEGORIES = exports.kindAcceptsAccountCategories = exports.isSelectableAccountCategoryId = exports.accountCategoryIdSchema = exports.accountCategoriesSchema = exports.ACCOUNT_CATEGORY_KINDS = exports.ACCOUNT_CATEGORY_IDS = exports.isActAsEligibleKind = exports.isAccountKind = exports.childAccountKindSchema = exports.CHILD_ACCOUNT_KINDS = exports.accountKindSchema = exports.ACCOUNT_KINDS = void 0;
15
+ exports.reputationBalanceBreakdownSchema = exports.reputationTransactionSchema = exports.reputationInfluenceContextSchema = exports.reputationDisputeStatusSchema = exports.reputationTargetEntityTypeSchema = exports.trustTierSchema = exports.reputationTransactionStatusSchema = exports.reputationCategorySchema = exports.REPUTATION_INFLUENCE_CONTEXTS = exports.REPUTATION_DISPUTE_STATUSES = exports.REPUTATION_TARGET_ENTITY_TYPES = exports.TRUST_TIERS = exports.REPUTATION_TRANSACTION_STATUSES = exports.REPUTATION_CATEGORIES = exports.credentialVerifyResultSchema = exports.credentialListResultSchema = exports.credentialIssueResultSchema = exports.verifiableCredentialResponseSchema = exports.credentialRecordSchema = exports.vouchResultSchema = 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.appAffinityEventsIngestSchema = exports.appAffinityEventSchema = exports.appAffinityEventTypeSchema = exports.appUserSignalIngestSchema = void 0;
16
+ exports.reverseModerationEffectSchema = exports.finalizeModerationDecisionSchema = exports.moderationDecisionEventSchema = exports.moderationPolicyVersionsSchema = exports.moderationDecisionEventSubjectSchema = exports.moderationFindingSchema = exports.applicationModerationStandingSchema = exports.identityBindingStatusSchema = exports.identityBindingTypeSchema = exports.personhoodStatusSchema = exports.contributionTierSchema = exports.conductStandingSchema = exports.conductStrikeStatusSchema = exports.moderationEffectSkipReasonSchema = exports.moderationEffectStatusSchema = exports.moderationEffectTypeSchema = exports.moderationDecisionStatusSchema = exports.moderationAttributionSchema = exports.moderationFindingScopeSchema = exports.moderationSeveritySchema = exports.APPLICATION_MODERATION_STANDINGS = exports.IDENTITY_BINDING_STATUSES = exports.IDENTITY_BINDING_TYPES = exports.PERSONHOOD_STATUSES = exports.CONTRIBUTION_TIERS = exports.CONDUCT_STANDINGS = exports.CONDUCT_STRIKE_STATUSES = exports.MODERATION_EFFECT_SKIP_REASONS = exports.MODERATION_EFFECT_STATUSES = exports.MODERATION_EFFECT_TYPES = exports.MODERATION_DECISION_STATUSES = exports.MODERATION_ATTRIBUTIONS = exports.MODERATION_FINDING_SCOPES = exports.MODERATION_SEVERITIES = exports.isFullReputationBalance = exports.reverseReputationTransactionSchema = exports.upsertReputationRuleSchema = exports.resolveReputationDisputeSchema = exports.createReputationDisputeSchema = exports.awardReputationSchema = exports.reverseReputationTransactionResultSchema = exports.reputationInfluenceResultSchema = exports.reputationLeaderboardEntrySchema = exports.reputationLeaderboardUserSchema = exports.reputationRuleSchema = exports.reputationDisputeSchema = exports.reputationBalanceSchema = exports.reputationBalanceSummarySchema = exports.reputationReliabilitySchema = exports.reputationInfluenceSchema = void 0;
17
+ exports.assetCompleteResponseSchema = exports.assetCompleteResultItemSchema = exports.assetCompleteRequestSchema = exports.assetInitResponseSchema = exports.assetUploadTicketSchema = exports.assetInitRequestSchema = exports.assetInitItemSchema = exports.rolloutPercentSchema = exports.runtimeVersionSchema = exports.channelNameSchema = exports.sha256HexSchema = exports.updateAssetStatusSchema = exports.updateStatusSchema = exports.updatePlatformSchema = exports.backupStatusResponseSchema = exports.backupUploadRequestSchema = exports.encryptedBackupEnvelopeSchema = exports.backupLookupIdSchema = exports.rotateKeyCompleteResponseSchema = exports.rotateKeyCompleteRequestSchema = exports.rotateKeyChallengeResponseSchema = exports.loginResultSchema = exports.sessionAccountsChangedEventSchema = exports.sessionAccountsChangedReasonSchema = exports.SESSION_ACCOUNTS_CHANGED_EVENT = exports.deviceBackgroundTokenResponseSchema = exports.deviceBackgroundTokenRequestSchema = exports.deviceBackgroundCredentialResponseSchema = exports.deviceTokenMintResponseSchema = exports.deviceTokenMintRequestSchema = exports.deviceSessionSyncSchema = exports.activeTokenSchema = exports.deviceSessionStateSchema = exports.sessionAccountSchema = exports.linkPreviewResponseSchema = exports.linkPreviewBatchResponseSchema = exports.linkPreviewBatchRequestSchema = exports.linkPreviewSchema = exports.applicationModerationTrustSchema = exports.reputationContextualInfluenceSchema = exports.reputationReviewingSchema = exports.reputationReportingSchema = exports.reputationConductSchema = exports.reputationContributionSchema = exports.reputationPersonhoodSchema = exports.identityBindingSchema = exports.registerIdentityBindingSchema = exports.reverseModerationEffectResultSchema = exports.applyModerationDecisionResultSchema = exports.moderationEffectSchema = void 0;
18
+ exports.transparencyCheckpointListSchema = exports.transparencyInclusionProofSchema = exports.transparencyCheckpointSchema = exports.transparencyAnchorSchema = exports.transparencyCheckpointSignatureSchema = exports.deviceTransferDenyResponseSchema = exports.deviceTransferApproveResponseSchema = exports.deviceTransferApproveRequestSchema = exports.deviceTransferInfoResponseSchema = exports.deviceTransferInitResponseSchema = exports.deviceTransferInitRequestSchema = exports.devicePairingStatusSchema = exports.webauthnLoginVerifyRequestSchema = exports.webauthnRegisterVerifyRequestSchema = exports.webauthnLoginOptionsRequestSchema = exports.webauthnRegisterOptionsRequestSchema = exports.updateRolloutPatchSchema = exports.promoteRequestSchema = exports.rollbackToEmbeddedRequestSchema = exports.rollbackRequestSchema = exports.updateListResponseSchema = exports.channelListResponseSchema = exports.channelSchema = exports.rollbackToEmbeddedEntrySchema = exports.createUpdateResponseSchema = exports.updateSchema = exports.createUpdateRequestSchema = exports.updateAssetRefSchema = void 0;
19
19
  var accountGraph_1 = require("./accountGraph");
20
- Object.defineProperty(exports, "ORGANIZATION_CATEGORIES", { enumerable: true, get: function () { return accountGraph_1.ORGANIZATION_CATEGORIES; } });
21
- Object.defineProperty(exports, "organizationCategorySchema", { enumerable: true, get: function () { return accountGraph_1.organizationCategorySchema; } });
20
+ Object.defineProperty(exports, "ACCOUNT_KINDS", { enumerable: true, get: function () { return accountGraph_1.ACCOUNT_KINDS; } });
21
+ Object.defineProperty(exports, "accountKindSchema", { enumerable: true, get: function () { return accountGraph_1.accountKindSchema; } });
22
+ Object.defineProperty(exports, "CHILD_ACCOUNT_KINDS", { enumerable: true, get: function () { return accountGraph_1.CHILD_ACCOUNT_KINDS; } });
23
+ Object.defineProperty(exports, "childAccountKindSchema", { enumerable: true, get: function () { return accountGraph_1.childAccountKindSchema; } });
24
+ Object.defineProperty(exports, "isAccountKind", { enumerable: true, get: function () { return accountGraph_1.isAccountKind; } });
25
+ Object.defineProperty(exports, "isActAsEligibleKind", { enumerable: true, get: function () { return accountGraph_1.isActAsEligibleKind; } });
26
+ Object.defineProperty(exports, "ACCOUNT_CATEGORY_IDS", { enumerable: true, get: function () { return accountGraph_1.ACCOUNT_CATEGORY_IDS; } });
27
+ Object.defineProperty(exports, "ACCOUNT_CATEGORY_KINDS", { enumerable: true, get: function () { return accountGraph_1.ACCOUNT_CATEGORY_KINDS; } });
28
+ Object.defineProperty(exports, "accountCategoriesSchema", { enumerable: true, get: function () { return accountGraph_1.accountCategoriesSchema; } });
29
+ Object.defineProperty(exports, "accountCategoryIdSchema", { enumerable: true, get: function () { return accountGraph_1.accountCategoryIdSchema; } });
30
+ Object.defineProperty(exports, "isSelectableAccountCategoryId", { enumerable: true, get: function () { return accountGraph_1.isSelectableAccountCategoryId; } });
31
+ Object.defineProperty(exports, "kindAcceptsAccountCategories", { enumerable: true, get: function () { return accountGraph_1.kindAcceptsAccountCategories; } });
32
+ Object.defineProperty(exports, "MAX_ACCOUNT_CATEGORIES", { enumerable: true, get: function () { return accountGraph_1.MAX_ACCOUNT_CATEGORIES; } });
33
+ Object.defineProperty(exports, "newlyAddedRetiredCategories", { enumerable: true, get: function () { return accountGraph_1.newlyAddedRetiredCategories; } });
34
+ Object.defineProperty(exports, "RETIRED_ACCOUNT_CATEGORY_IDS", { enumerable: true, get: function () { return accountGraph_1.RETIRED_ACCOUNT_CATEGORY_IDS; } });
35
+ Object.defineProperty(exports, "SELECTABLE_ACCOUNT_CATEGORY_IDS", { enumerable: true, get: function () { return accountGraph_1.SELECTABLE_ACCOUNT_CATEGORY_IDS; } });
22
36
  Object.defineProperty(exports, "createAccountRequestSchema", { enumerable: true, get: function () { return accountGraph_1.createAccountRequestSchema; } });
23
37
  var userResponse_1 = require("./userResponse");
24
38
  // Schemas
@@ -106,10 +106,38 @@ exports.userResponseSchema = zod_1.z
106
106
  */
107
107
  verifiedDomains: zod_1.z.array(identity_1.verifiedDomainSchema).optional(),
108
108
  /**
109
- * Real-estate / team taxonomy for `kind: 'organization'` accounts.
110
- * Absent on personal, project, and bot accounts.
109
+ * Account-graph classification what KIND of account this is.
110
+ *
111
+ * ORTHOGONAL to `type` (`local` / `federated` / `agent` / `automated`),
112
+ * which says where the account lives and how it is driven; the two
113
+ * coexist and neither substitutes for the other. A `channel` is a
114
+ * publishing identity nobody can act as, so a consumer that renders
115
+ * authored content reads THIS to tell a channel's post from a person's.
116
+ *
117
+ * Optional because a DTO produced from a source that never carried the
118
+ * column omits it; absent should be read as `personal`, the column's
119
+ * default, not as unknown.
111
120
  */
112
- organizationCategory: accountGraph_1.organizationCategorySchema.optional(),
121
+ kind: accountGraph_1.accountKindSchema.optional(),
122
+ /**
123
+ * What this account is about — the field a profile screen RENDERS.
124
+ *
125
+ * **Ordered, primary first.** `accountCategories[0]` is the primary
126
+ * category; there is deliberately no sibling `primaryCategory` field,
127
+ * because two representations of one fact can disagree (see rule 2 in
128
+ * `accountGraph.ts`). Nothing downstream may sort, de-duplicate or
129
+ * otherwise reorder this array.
130
+ *
131
+ * **Ids, never labels.** Each element is a stable slug; the visible text
132
+ * comes from the reader's own translation catalogue, keyed
133
+ * `accounts.accountCategory.<id>`. A label on the wire would paint every
134
+ * profile in the language of whoever picked it.
135
+ *
136
+ * Absent when the account has none — which is every `personal` account,
137
+ * and any non-personal one that has not chosen. A renderer reads
138
+ * `user.accountCategories ?? []`.
139
+ */
140
+ accountCategories: accountGraph_1.accountCategoriesSchema.optional(),
113
141
  /**
114
142
  * The authenticated viewer's relationship to this profile. Present ONLY
115
143
  * on single-profile fetches (`GET /profiles/username/:username`,
@@ -131,6 +159,12 @@ exports.userProfileUpdateSchema = zod_1.z
131
159
  .object({
132
160
  first: zod_1.z.string().optional(),
133
161
  last: zod_1.z.string().optional(),
162
+ /**
163
+ * Explicit display name, stored rather than composed. Wins over
164
+ * `first`/`last` when set; send `''` to clear it and fall back
165
+ * to the composed pair.
166
+ */
167
+ displayName: zod_1.z.string().optional(),
134
168
  })
135
169
  .optional(),
136
170
  username: zod_1.z.string().optional(),