@oxy.so/contracts 4.13.0 → 4.14.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.
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { z } from 'zod';
10
10
  import { avatarCharacterSchema } from './avatarCharacter.js';
11
+ import { ORGANIZATION_TYPES, ORGANIZATION_NAME_MAX_LENGTH, organizationNameSchema, } from './organizations.js';
11
12
  import { usernameSchema, usernameSchemaForAccountKind } from './username.js';
12
13
  /**
13
14
  * The union is spelled out above and the array proves coverage BOTH ways
@@ -369,11 +370,11 @@ export function kindAcceptsAccountCategories(kind) {
369
370
  * `composeDisplayName`, which already preferred an explicit value — only the
370
371
  * storage for one was missing).
371
372
  */
372
- const accountNameSchema = z
373
+ export const accountNameSchema = z
373
374
  .object({
374
375
  first: z.string().trim().max(100).optional(),
375
376
  last: z.string().trim().max(100).optional(),
376
- displayName: z.string().trim().max(100).optional(),
377
+ displayName: z.string().trim().max(ORGANIZATION_NAME_MAX_LENGTH).optional(),
377
378
  })
378
379
  .optional();
379
380
  /**
@@ -426,6 +427,7 @@ export const createAccountRequestSchema = z
426
427
  color: z.string().trim().max(32).optional(),
427
428
  /** Ordered, PRIMARY FIRST — see rule 2 above {@link ACCOUNT_CATEGORY_IDS}. */
428
429
  accountCategories: accountCategoriesSchema.optional(),
430
+ organizationType: z.enum(ORGANIZATION_TYPES).optional(),
429
431
  /**
430
432
  * Create the account already opted OUT of discovery.
431
433
  *
@@ -483,6 +485,23 @@ export const createAccountRequestSchema = z
483
485
  message: 'Only a bot account carries an avatar character',
484
486
  });
485
487
  }
488
+ const displayName = request.name?.displayName;
489
+ if (displayName) {
490
+ const parsedName = request.kind === 'organization'
491
+ ? organizationNameSchema.safeParse(displayName)
492
+ : z.string().max(100).safeParse(displayName);
493
+ if (!parsedName.success) {
494
+ for (const issue of parsedName.error.issues) {
495
+ ctx.addIssue({ ...issue, path: ['name', 'displayName', ...issue.path] });
496
+ }
497
+ }
498
+ }
499
+ if (request.organizationType !== undefined && request.kind !== 'organization')
500
+ ctx.addIssue({
501
+ code: z.ZodIssueCode.custom,
502
+ path: ['organizationType'],
503
+ message: 'Organization type requires an organization account',
504
+ });
486
505
  // The ONE place `kind` and `username` arrive in the same object, so it is the
487
506
  // only place a wire schema CAN apply the per-kind half of the policy: a bot's
488
507
  // handle must end in `bot`. Not a second rule — it asks
package/dist/esm/index.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * Platform-agnostic — zod is the only runtime dependency. No react/react-native/
10
10
  * expo, no `require()` in the ESM build.
11
11
  */
12
- export { ACCOUNT_KINDS, accountKindSchema, CHILD_ACCOUNT_KINDS, childAccountKindSchema, isAccountKind, isDelegatedActAsEligibleKind, isOperatorSwitchTargetKind, ACCOUNT_CATEGORY_IDS, ACCOUNT_CATEGORY_KINDS, accountCategoriesSchema, accountCategoryIdSchema, isSelectableAccountCategoryId, kindAcceptsAccountCategories, MAX_ACCOUNT_CATEGORIES, newlyAddedRetiredCategories, RETIRED_ACCOUNT_CATEGORY_IDS, SELECTABLE_ACCOUNT_CATEGORY_IDS, createAccountRequestSchema, } from './accountGraph.js';
12
+ export { ACCOUNT_KINDS, accountKindSchema, CHILD_ACCOUNT_KINDS, childAccountKindSchema, isAccountKind, isDelegatedActAsEligibleKind, isOperatorSwitchTargetKind, ACCOUNT_CATEGORY_IDS, ACCOUNT_CATEGORY_KINDS, accountCategoriesSchema, accountCategoryIdSchema, isSelectableAccountCategoryId, kindAcceptsAccountCategories, MAX_ACCOUNT_CATEGORIES, newlyAddedRetiredCategories, RETIRED_ACCOUNT_CATEGORY_IDS, SELECTABLE_ACCOUNT_CATEGORY_IDS, createAccountRequestSchema, accountNameSchema, } from './accountGraph.js';
13
13
  export { AVATAR_EYE_SPACING, AVATAR_PICKABLE_COLORS, AVATAR_PICKABLE_SHAPES, avatarCharacterFor, avatarCharacterSchema, parseAvatarCharacter, } from './avatarCharacter.js';
14
14
  export { ACCOUNT_ACTOR_NATURES, ACCOUNT_SUBJECT_CONTRACT_VERSION, FINANCIAL_EFFECT_DIRECTIONS, KIND_INDEPENDENT_ACCOUNT_DIMENSIONS, accountActorChainFromSession, accountActorChainSchema, accountActorNatureSchema, accountKindActorNature, accountKindActsAsItself, attributeFinancialEffect, financialEffectAttributionSchema, } from './accountSubject.js';
15
15
  export { usernameSchema, usernameSchemaForAccountKind, isValidUsername, stripDisallowedUsernameCharacters, applyBotUsernameSuffix, USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH, USERNAME_INVALID_MESSAGE, BOT_USERNAME_INVALID_MESSAGE, RESERVED_USERNAME_MESSAGE, NUMERIC_USERNAME_MESSAGE, } from './username.js';
@@ -202,3 +202,5 @@ export * from './agentKeys.js';
202
202
  export { productSubscriptionSummarySchema, productSubscriptionsResponseSchema, subscriptionCreditGrantSchema, subscriptionCreditGrantsResponseSchema, cancelProductSubscriptionSchema, productSubscriptionCancellationResultSchema, } from './products/billing.js';
203
203
  export { personalPlanDisplayPriceSchema, personalPlanCatalogueSchema, } from './products/plans.js';
204
204
  export * from './products/checkout.js';
205
+ export * from './organizations.js';
206
+ export * from './safety.js';
@@ -0,0 +1,207 @@
1
+ import { z } from 'zod';
2
+ /** Real-world identity classification, independent of account topics and permissions. */
3
+ export const ORGANIZATION_TYPES = [
4
+ 'business',
5
+ 'nonprofit',
6
+ 'public_body',
7
+ 'community',
8
+ 'other',
9
+ ];
10
+ export const ORGANIZATION_STATUSES = ['active', 'inactive', 'merged', 'suppressed'];
11
+ export const ORGANIZATION_CLAIM_STATUSES = ['unclaimed', 'claimed'];
12
+ export const ORGANIZATION_ALIAS_KINDS = [
13
+ 'name_variant',
14
+ 'former_name',
15
+ 'localized_name',
16
+ 'marketing_name',
17
+ 'misspelling',
18
+ ];
19
+ export const ORGANIZATION_CLAIM_DECISIONS = ['pending', 'approved', 'rejected'];
20
+ const id = z.string().min(1).max(128);
21
+ const optionalText = (max) => z.string().trim().min(1).max(max).nullable().optional();
22
+ export const organizationAliasSchema = z
23
+ .object({
24
+ alias: z.string().trim().min(1).max(300),
25
+ kind: z.enum(ORGANIZATION_ALIAS_KINDS),
26
+ language: z.string().min(2).max(35).optional(),
27
+ })
28
+ .strict();
29
+ export const ORGANIZATION_NAME_MAX_LENGTH = 300;
30
+ export const organizationNameSchema = z
31
+ .string()
32
+ .trim()
33
+ .min(1)
34
+ .max(ORGANIZATION_NAME_MAX_LENGTH)
35
+ .refine((value) => !/[<>]/.test(value) &&
36
+ Array.from(value).every((character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127), 'Organization name must be plain text');
37
+ export const createOrganizationSchema = z
38
+ .object({
39
+ name: organizationNameSchema,
40
+ organizationType: z.enum(ORGANIZATION_TYPES),
41
+ legalName: optionalText(300),
42
+ websiteUrl: z
43
+ .string()
44
+ .url()
45
+ .max(2048)
46
+ .refine((value) => /^https?:\/\//.test(value), 'Use HTTP or HTTPS')
47
+ .nullable()
48
+ .optional(),
49
+ countryCode: z
50
+ .string()
51
+ .regex(/^[A-Z]{2}$/)
52
+ .nullable()
53
+ .optional(),
54
+ aliases: z.array(organizationAliasSchema).max(100).optional(),
55
+ })
56
+ .strict();
57
+ export const updateOrganizationSchema = createOrganizationSchema.partial();
58
+ export const organizationSchema = createOrganizationSchema
59
+ .extend({
60
+ accountId: id,
61
+ username: z.string().nullable(),
62
+ avatar: z.string().nullable(),
63
+ legalName: z.string().nullable(),
64
+ websiteUrl: z.string().nullable(),
65
+ countryCode: z.string().nullable(),
66
+ aliases: z.array(organizationAliasSchema),
67
+ verifiedDomains: z.array(z.string()),
68
+ claimStatus: z.enum(ORGANIZATION_CLAIM_STATUSES),
69
+ status: z.enum(ORGANIZATION_STATUSES),
70
+ mergedIntoAccountId: id.nullable(),
71
+ createdAt: z.string().datetime(),
72
+ updatedAt: z.string().datetime(),
73
+ })
74
+ .strict();
75
+ export const organizationSearchSchema = z
76
+ .object({
77
+ query: z.string().trim().max(200).default(''),
78
+ organizationType: z.enum(ORGANIZATION_TYPES).optional(),
79
+ limit: z.coerce.number().int().min(1).max(50).default(20),
80
+ cursor: z.string().min(1).max(2048).optional(),
81
+ })
82
+ .strict();
83
+ export const organizationListSchema = z
84
+ .object({ items: z.array(organizationSchema), nextCursor: z.string().nullable() })
85
+ .strict();
86
+ export const organizationBatchInputSchema = z.object({ ids: z.array(id).min(1).max(100) }).strict();
87
+ export const organizationBatchSchema = z
88
+ .object({
89
+ items: z.array(z.object({ requestedAccountId: id, organization: organizationSchema }).strict()),
90
+ missingIds: z.array(id),
91
+ })
92
+ .strict();
93
+ export const organizationClaimInputSchema = z
94
+ .object({
95
+ evidenceReference: z.string().trim().min(1).max(1000),
96
+ })
97
+ .strict();
98
+ export const organizationClaimSchema = z
99
+ .object({
100
+ id,
101
+ accountId: id,
102
+ claimantUserId: id,
103
+ status: z.enum(ORGANIZATION_CLAIM_DECISIONS),
104
+ createdAt: z.string().datetime(),
105
+ })
106
+ .strict();
107
+ export const organizationClaimDetailSchema = organizationClaimSchema
108
+ .extend({
109
+ evidenceReference: z.string(),
110
+ reviewEvidenceReference: z.string().nullable(),
111
+ reviewedAt: z.string().datetime().nullable(),
112
+ })
113
+ .strict();
114
+ export const organizationClaimListQuerySchema = z
115
+ .object({
116
+ status: z.enum(ORGANIZATION_CLAIM_DECISIONS).optional(),
117
+ limit: z.coerce.number().int().min(1).max(50).default(20),
118
+ cursor: z.string().min(1).max(2048).optional(),
119
+ })
120
+ .strict();
121
+ export const organizationClaimListSchema = z
122
+ .object({ items: z.array(organizationClaimDetailSchema), nextCursor: z.string().nullable() })
123
+ .strict();
124
+ export const organizationClaimReviewSchema = z
125
+ .object({
126
+ decision: z.enum(['approved', 'rejected']),
127
+ evidenceReference: z.string().trim().min(1).max(1000),
128
+ })
129
+ .strict();
130
+ export const organizationExternalInputSchema = z
131
+ .object({
132
+ namespace: z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/),
133
+ externalId: z.string().min(1).max(256),
134
+ accountId: id.optional(),
135
+ organization: createOrganizationSchema,
136
+ evidenceReference: z.string().trim().min(1).max(1000),
137
+ avatarFileId: id.optional(),
138
+ })
139
+ .strict();
140
+ /** Automated ingestion can create neutral identities in its granted namespace only. */
141
+ export const organizationImportBatchSchema = z
142
+ .object({
143
+ namespace: organizationExternalInputSchema.shape.namespace,
144
+ items: z
145
+ .array(organizationExternalInputSchema.pick({
146
+ externalId: true,
147
+ organization: true,
148
+ evidenceReference: true,
149
+ avatarFileId: true,
150
+ }))
151
+ .min(1)
152
+ .max(100),
153
+ })
154
+ .strict()
155
+ .refine((input) => new Set(input.items.map((item) => item.externalId)).size === input.items.length, 'External IDs must be unique within the batch');
156
+ export const organizationImportBatchResultSchema = z
157
+ .object({
158
+ items: z.array(z.object({ externalId: z.string(), accountId: id, created: z.boolean() }).strict()),
159
+ })
160
+ .strict();
161
+ export const organizationExternalSchema = z
162
+ .object({ accountId: id, created: z.boolean() })
163
+ .strict();
164
+ export const organizationExternalResolutionSchema = z.object({ accountId: id }).strict();
165
+ export const organizationMergeSchema = z
166
+ .object({
167
+ targetAccountId: id,
168
+ evidenceReference: z.string().trim().min(1).max(1000),
169
+ })
170
+ .strict();
171
+ export const ORGANIZATION_ERROR_CODES = [
172
+ 'bad_request',
173
+ 'unauthorized',
174
+ 'forbidden',
175
+ 'not_found',
176
+ 'unknown_route',
177
+ 'conflict',
178
+ 'validation_failed',
179
+ 'rate_limited',
180
+ 'internal_error',
181
+ 'service_unavailable',
182
+ ];
183
+ export const organizationErrorSchema = z
184
+ .object({
185
+ error: z
186
+ .object({
187
+ code: z.enum(ORGANIZATION_ERROR_CODES),
188
+ message: z.string(),
189
+ details: z
190
+ .record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()]))
191
+ .optional(),
192
+ })
193
+ .strict(),
194
+ })
195
+ .strict();
196
+ /** Staff curation corrects neutral facts or moderates visibility without claiming ownership. */
197
+ export const organizationCurationSchema = z
198
+ .object({
199
+ changes: updateOrganizationSchema.optional(),
200
+ status: z.enum(['active', 'inactive', 'suppressed']).optional(),
201
+ evidenceReference: z.string().trim().min(1).max(1000),
202
+ })
203
+ .strict()
204
+ .refine((input) => input.changes !== undefined || input.status !== undefined, 'At least one change is required');
205
+ export const organizationCurationResultSchema = z
206
+ .object({ accountId: id, status: z.enum(['active', 'inactive', 'suppressed']) })
207
+ .strict();
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ /** The requester is authenticated by the bearer; applications cannot submit viewer IDs. */
3
+ export const profileAccessInputSchema = z
4
+ .object({ subjectAccountId: z.string().min(1).max(128) })
5
+ .strict();
6
+ export const profileAccessDecisionSchema = z
7
+ .object({
8
+ allowed: z.boolean(),
9
+ reason: z.enum(['public', 'owner', 'private', 'blocked', 'unavailable']),
10
+ policyVersion: z.literal('1'),
11
+ })
12
+ .strict();